4 * Copyright (C) 1997-2011 Adam Williams <broadcast at earthling dot net>
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22 #include "bcdisplayinfo.h"
30 #include "echocancel.h"
32 #include "transportque.inc"
38 #define TMPDIR "/tmp/echocancel/"
41 REGISTER_PLUGIN(EchoCancel)
44 EchoCancelConfig::EchoCancelConfig()
51 cutoff = (MIN_CUTOFF+MAX_CUTOFF)/2;
59 int EchoCancelConfig::equivalent(EchoCancelConfig &that)
61 return EQUIV(level, that.level) &&
62 normalize == that.normalize &&
63 xzoom == that.xzoom &&
65 cutoff == that.cutoff &&
66 window_size == that.window_size &&
68 history_size == that.history_size;
71 void EchoCancelConfig::copy_from(EchoCancelConfig &that)
74 normalize = that.normalize;
81 window_size = that.window_size;
83 history_size = that.history_size;
85 if( window_size ) CLAMP(window_size, MIN_WINDOW, MAX_WINDOW);
86 CLAMP(history_size, MIN_HISTORY, MAX_HISTORY);
87 CLAMP(xzoom, MIN_XZOOM, MAX_XZOOM);
88 CLAMP(peaks, MIN_PEAKS, MAX_PEAKS);
89 CLAMP(damp, MIN_DAMP, MAX_DAMP);
90 CLAMP(cutoff, MIN_CUTOFF, MAX_CUTOFF);
93 void EchoCancelConfig::interpolate(EchoCancelConfig &prev,
94 EchoCancelConfig &next,
97 int64_t current_frame)
104 EchoCancelFrame::EchoCancelFrame(int n)
107 data = new float[n + 1];
111 EchoCancelFrame::~EchoCancelFrame()
118 EchoCancelLevel::EchoCancelLevel(EchoCancel *plugin, int x, int y)
119 : BC_FPot(x, y, plugin->config.level, INFINITYGAIN, MAXGAIN)
121 this->plugin = plugin;
124 int EchoCancelLevel::handle_event()
126 plugin->config.level = get_value();
127 plugin->send_configure_change();
135 EchoCancelMode::EchoCancelMode(EchoCancel *plugin, int x, int y)
136 : BC_PopupMenu(x, y, xS(120), to_text(plugin->config.mode))
138 this->plugin = plugin;
141 void EchoCancelMode::create_objects()
143 add_item(new BC_MenuItem(to_text(CANCEL_OFF)));
144 add_item(new BC_MenuItem(to_text(CANCEL_ON)));
145 add_item(new BC_MenuItem(to_text(CANCEL_MAN)));
148 int EchoCancelMode::handle_event()
150 int new_mode = to_mode(get_text());
151 if( plugin->config.mode != new_mode ) {
152 plugin->config.mode = new_mode;
153 plugin->send_configure_change();
158 const char* EchoCancelMode::to_text(int mode)
161 case CANCEL_ON: return _("ON");
162 case CANCEL_MAN: return _("MAN");
167 int EchoCancelMode::to_mode(const char *text)
169 if(!strcmp(to_text(CANCEL_ON), text)) return CANCEL_ON;
170 if(!strcmp(to_text(CANCEL_MAN), text)) return CANCEL_MAN;
177 EchoCancelHistory::EchoCancelHistory(EchoCancel *plugin,
180 : BC_IPot(x, y, plugin->config.history_size, MIN_HISTORY, MAX_HISTORY)
182 this->plugin = plugin;
185 int EchoCancelHistory::handle_event()
187 plugin->config.history_size = get_value();
188 plugin->send_configure_change();
197 EchoCancelWindowSize::EchoCancelWindowSize(EchoCancel *plugin, int x, int y, const char *text)
198 : BC_PopupMenu(x, y, xS(120), text)
200 this->plugin = plugin;
203 int EchoCancelWindowSize::handle_event()
205 plugin->config.window_size = atoi(get_text());
206 plugin->send_configure_change();
210 const char *EchoCancelWindowSize::to_text(int size)
212 if( !size ) return _("default");
213 static char string[BCSTRLEN];
214 sprintf(string, "%d", size);
218 int EchoCancelWindowSize::to_size(const char *text)
220 return !strcmp(to_text(0),text) ? 0 : strtol(text,0,0);
225 EchoCancelWindowSizeTumbler::EchoCancelWindowSizeTumbler(EchoCancel *plugin, int x, int y)
228 this->plugin = plugin;
231 int EchoCancelWindowSizeTumbler::handle_up_event()
233 if( !plugin->config.window_size )
234 plugin->config.window_size = MIN_WINDOW;
235 else if( (plugin->config.window_size *= 2) > MAX_WINDOW )
236 plugin->config.window_size = MAX_WINDOW;
237 EchoCancelWindowSize *window_size =
238 ((EchoCancelWindow *)plugin->get_thread()->get_window())->window_size;
239 const char *wsp = EchoCancelWindowSize::to_text(plugin->config.window_size);
240 window_size->set_text(wsp);
241 plugin->send_configure_change();
245 int EchoCancelWindowSizeTumbler::handle_down_event()
247 plugin->config.window_size /= 2;
248 if(plugin->config.window_size < MIN_WINDOW)
249 plugin->config.window_size = 0;
250 EchoCancelWindowSize *window_size =
251 ((EchoCancelWindow *)plugin->get_thread()->get_window())->window_size;
252 const char *wsp = EchoCancelWindowSize::to_text(plugin->config.window_size);
253 window_size->set_text(wsp);
254 plugin->send_configure_change();
262 EchoCancelNormalize::EchoCancelNormalize(EchoCancel *plugin, int x, int y)
263 : BC_CheckBox(x, y, plugin->config.normalize, _("Normalize"))
265 this->plugin = plugin;
268 int EchoCancelNormalize::handle_event()
270 plugin->config.normalize = get_value();
271 plugin->send_configure_change();
278 EchoCancelXZoom::EchoCancelXZoom(EchoCancel *plugin, int x, int y)
279 : BC_IPot(x, y, plugin->config.xzoom, MIN_XZOOM, MAX_XZOOM)
281 this->plugin = plugin;
284 int EchoCancelXZoom::handle_event()
286 plugin->config.xzoom = get_value();
287 plugin->send_configure_change();
293 EchoCancelPeaks::EchoCancelPeaks(EchoCancel *plugin, int x, int y)
294 : BC_IPot(x, y, plugin->config.peaks, MIN_PEAKS, MAX_PEAKS)
296 this->plugin = plugin;
299 int EchoCancelPeaks::handle_event()
301 plugin->config.peaks = get_value();
302 plugin->send_configure_change();
308 EchoCancelDamp::EchoCancelDamp(EchoCancel *plugin, int x, int y)
309 : BC_IPot(x, y, plugin->config.damp, MIN_DAMP, MAX_DAMP)
311 this->plugin = plugin;
314 int EchoCancelDamp::handle_event()
316 plugin->config.damp = get_value();
317 plugin->send_configure_change();
323 EchoCancelCutoff::EchoCancelCutoff(EchoCancel *plugin, int x, int y)
324 : BC_IPot(x, y, plugin->config.cutoff, MIN_CUTOFF, MAX_CUTOFF)
326 this->plugin = plugin;
329 int EchoCancelCutoff::handle_event()
331 plugin->config.cutoff = get_value();
332 plugin->send_configure_change();
338 EchoCancelCanvas::EchoCancelCanvas(EchoCancel *plugin, int x, int y, int w, int h)
339 : BC_SubWindow(x, y, w, h, BLACK)
341 this->plugin = plugin;
342 current_operation = NONE;
345 int EchoCancelCanvas::button_press_event()
347 if(is_event_win() && cursor_inside())
350 current_operation = DRAG;
351 plugin->send_configure_change();
357 int EchoCancelCanvas::button_release_event()
359 if(current_operation == DRAG)
362 current_operation = NONE;
368 int EchoCancelCanvas::cursor_motion_event()
370 if(current_operation == DRAG)
378 void EchoCancelCanvas::calculate_point(int do_overlay)
380 int x = get_cursor_x();
381 int y = get_cursor_y();
382 CLAMP(x, 0, get_w() - 1);
383 CLAMP(y, 0, get_h() - 1);
385 EchoCancelWindow *window = (EchoCancelWindow *)plugin->thread->window;
386 window->calculate_frequency(x, y, do_overlay);
389 void EchoCancelCanvas::draw_overlay()
391 EchoCancelWindow *window = (EchoCancelWindow*)plugin->thread->window;
392 if(window->probe_x >= 0 || window->probe_y >= 0)
396 char string[BCTEXTLEN];
397 sprintf(string, "%d, %f", plugin->config.offset, plugin->config.gain);
398 draw_text(window->probe_x, window->probe_y, string);
399 draw_line(0, window->probe_y, get_w(), window->probe_y);
400 draw_line(window->probe_x, 0, window->probe_x, get_h());
412 EchoCancelWindow::EchoCancelWindow(EchoCancel *plugin)
413 : PluginClientWindow(plugin, plugin->w, plugin->h, xS(680), yS(480), 1)
415 this->plugin = plugin;
416 probe_x = probe_y = -1;
419 EchoCancelWindow::~EchoCancelWindow()
423 void EchoCancelWindow::create_objects()
425 int pad = plugin->get_theme()->widget_border;
426 add_subwindow(canvas = new EchoCancelCanvas(plugin, 0, 0,
427 get_w(), get_h() - 2*BC_Pot::calculate_h() - 3*pad));
428 canvas->set_cursor(CROSS_CURSOR, 0, 0);
430 int x = pad, y = canvas->get_y() + canvas->get_h() + pad;
433 add_subwindow(level_title = new BC_Title(x, y, _("Level:")));
434 x += level_title->get_w() + pad;
435 add_subwindow(level = new EchoCancelLevel(plugin, x, y));
436 x += level->get_w() + pad;
437 y += level->get_h() + pad;
440 add_subwindow(normalize = new EchoCancelNormalize(plugin, x, y));
441 x += normalize->get_w() + 3*pad;
442 y += normalize->get_h() - BC_Title::calculate_h(this,_("Gain: "));
443 add_subwindow(gain_title = new EchoCancelTitle(x, y, _("Gain: "), 0.));
444 x += gain_title->get_w() + 2*pad;
445 add_subwindow(offset_title = new EchoCancelTitle(x, y, _("Offset: "), 0));
447 x = x1 + level_title->get_w() + level->get_w() + 2*pad;
451 add_subwindow(mode_title = new BC_Title(x, y, _("Mode:")));
452 x += mode_title->get_w() + pad;
453 add_subwindow(mode = new EchoCancelMode(plugin, x, y));
454 mode->create_objects();
457 y += mode->get_h() + pad;
459 add_subwindow(window_size_title = new BC_Title(x, y, _("Window size:")));
460 x += window_size_title->get_w() + pad;
461 const char *wsp = EchoCancelWindowSize::to_text(plugin->config.window_size);
462 add_subwindow(window_size = new EchoCancelWindowSize(plugin, x, y, wsp));
463 x += window_size->get_w();
464 add_subwindow(window_size_tumbler = new EchoCancelWindowSizeTumbler(plugin, x, y));
466 window_size->add_item(new BC_MenuItem(EchoCancelWindowSize::to_text(0)));
467 for( int i=MIN_WINDOW; i<=MAX_WINDOW; i*=2 ) {
468 window_size->add_item(new BC_MenuItem(EchoCancelWindowSize::to_text(i)));
472 y += window_size->get_h() + pad;
474 x = x1 = window_size_tumbler->get_x() + window_size_tumbler->get_w() + pad;
476 add_subwindow(history_title = new BC_Title(x, y, _("History:")));
477 x += history_title->get_w() + pad;
478 add_subwindow(history = new EchoCancelHistory(plugin, x, y));
482 y += history->get_h() + pad;
483 add_subwindow(xzoom_title = new BC_Title(x, y, _("X Zoom:")));
484 x += xzoom_title->get_w() + pad;
485 add_subwindow(xzoom = new EchoCancelXZoom(plugin, x, y));
486 x += xzoom->get_w() + pad;
488 add_subwindow(damp_title = new BC_Title(x, y, _("Damp:")));
489 x += damp_title->get_w() + pad;
490 add_subwindow(damp = new EchoCancelDamp(plugin, x, y));
491 x += damp->get_w() + pad;
492 add_subwindow(cutoff_title = new BC_Title(x, y, _("Cutoff Hz:")));
493 x += cutoff_title->get_w() + pad;
494 add_subwindow(cutoff = new EchoCancelCutoff(plugin, x, y));
495 int x2 = x - BC_Title::calculate_w(this, _("Peaks:")) - pad;
496 add_subwindow(peaks_title = new BC_Title(x2, y2, _("Peaks:")));
497 add_subwindow(peaks = new EchoCancelPeaks(plugin, x, y2));
501 add_subwindow(freq_title = new BC_Title(x, y, _("0 Hz")));
502 y += freq_title->get_h() + pad;
503 add_subwindow(amplitude_title = new BC_Title(x, y, _("Amplitude: 0 dB")));
508 int EchoCancelWindow::resize_event(int w, int h)
510 int canvas_h = canvas->get_h();
511 int canvas_difference = get_h() - canvas_h;
513 canvas->reposition_window(0, 0, w, h - canvas_difference);
514 canvas->clear_box(0, 0, canvas->get_w(), canvas->get_h());
515 probe_x = probe_y = -1;
517 int dx = 0, dy = canvas->get_h() - canvas_h;
519 // Remove all columns which may be a different size.
520 plugin->frame_buffer.remove_all_objects();
521 plugin->frame_history.remove_all_objects();
523 level_title->reposition_window_relative(dx, dy);
524 level->reposition_window_relative(dx, dy);
525 mode_title->reposition_window_relative(dx, dy);
526 mode->reposition_window_relative(dx, dy);
527 window_size_title->reposition_window_relative(dx, dy);
528 window_size->reposition_window_relative(dx, dy);
529 window_size_tumbler->reposition_window_relative(dx, dy);
530 gain_title->reposition_window_relative(dx, dy);
531 offset_title->reposition_window_relative(dx, dy);
532 normalize->reposition_window_relative(dx, dy);
533 history_title->reposition_window_relative(dx, dy);
534 history->reposition_window_relative(dx, dy);
535 xzoom_title->reposition_window_relative(dx, dy);
536 xzoom->reposition_window_relative(dx, dy);
537 peaks_title->reposition_window_relative(dx, dy);
538 peaks->reposition_window_relative(dx, dy);
539 damp_title->reposition_window_relative(dx, dy);
540 damp->reposition_window_relative(dx, dy);
541 cutoff_title->reposition_window_relative(dx, dy);
542 cutoff->reposition_window_relative(dx, dy);
543 freq_title->reposition_window_relative(dx, dy);
544 amplitude_title->reposition_window_relative(dx, dy);
553 void EchoCancelWindow::calculate_frequency(int x, int y, int do_overlay)
555 if( !do_overlay && probe_x == x && probe_y == y ) return;
556 if( do_overlay & 2 ) canvas->draw_overlay();
557 probe_x = x; probe_y = y;
559 // Convert to coordinates in frame history
560 int need_config_update = 0;
561 double gain = plugin->config.gain;
562 double offset = plugin->config.offset;
564 if( x >= 0 && plugin->frame_history.size() ) {
565 int ww = canvas->get_w();
567 if( freq_pixel < 0 ) freq_pixel = 0;
568 if( freq_pixel > ww ) freq_pixel = ww;
570 int idx = plugin->frame_history.size()-1 - time_pixel;
571 EchoCancelFrame *frm = plugin->frame_history[idx];
572 int pixels = canvas->get_w();
573 int half_window = plugin->header.window_size / 2;
574 offset = (double)half_window * freq_pixel/(pixels * plugin->config.xzoom);
575 if( offset <= 1e-10 ) offset = 1;
576 int freq = plugin->header.sample_rate / offset;
577 int msecs = 1000. / freq;
578 char string[BCTEXTLEN];
579 sprintf(string, _("%d Hz, %d ms (%d))"), freq, msecs, (int)offset);
580 freq_title->update(string);
581 int frm_sz1 = frm->size()-1;
582 if( freq_pixel > frm_sz1 ) freq_pixel = frm_sz1;
583 float *frame_data = frm->samples();
584 double level = frame_data[freq_pixel];
585 double scale = frm->scale();
586 sprintf(string, _("Amplitude: %.3f (%.6g)"), level, scale);
587 amplitude_title->update(string);
590 int hh = canvas->get_h()/2;
591 int gain_pixel = hh - y;
592 if( gain_pixel < 0 ) gain_pixel = 0;
593 if( gain_pixel > hh ) gain_pixel = hh;
594 gain = (double)gain_pixel / hh;
596 if( plugin->config.gain != gain ) {
597 gain_title->update(plugin->config.gain = gain);
598 need_config_update = 1;
600 if( offset > 0 && plugin->config.offset != offset ) {
601 offset_title->update(plugin->config.offset = offset);
602 need_config_update = 1;
604 if( need_config_update )
605 plugin->send_configure_change();
607 if( do_overlay & 1 ) canvas->draw_overlay();
608 if( do_overlay ) canvas->flash();
611 void EchoCancelWindow::update_gui()
613 level->update(plugin->config.level);
614 char string[BCTEXTLEN];
615 sprintf(string, "%d", plugin->config.window_size);
616 window_size->set_text(string);
617 mode->set_text(EchoCancelMode::to_text(plugin->config.mode));
618 history->update(plugin->config.history_size);
619 normalize->set_value(plugin->config.normalize);
620 gain_title->update(plugin->config.gain);
621 offset_title->update(plugin->config.offset);
624 EchoCancel::EchoCancel(PluginServer *server)
625 : PluginAClient(server)
633 EchoCancel::~EchoCancel()
639 frame_buffer.remove_all_objects();
640 frame_history.remove_all_objects();
645 void EchoCancel::reset()
655 aud_real = 0; aud_imag = 0;
656 env_real = 0; env_imag = 0;
657 envelope = 0; env_data = 0;
659 memset(&header, 0, sizeof(header));
663 set_buffer(int ofs, int len)
665 int needed = ofs + len;
666 if( needed > allocated ) {
667 DataHeader *new_header = new_data_header(needed);
669 int old_size = sizeof(DataHeader) + allocated*sizeof(float);
670 memcpy(new_header, data_header, old_size);
671 delete [] (char *)data_header;
674 memset(new_header, 0, sizeof(*new_header));
675 data_header = new_header;
678 sample_data = &data_header->samples[ofs];
683 set_buffer(int ofs, int len)
685 int needed = ofs + len;
686 if( needed > allocated ) {
687 Samples *new_samples = new Samples(needed);
689 double *old_data = samples->get_data();
690 double *new_data = new_samples->get_data();
691 memcpy(new_data, old_data, allocated*sizeof(old_data[0]));
694 samples = new_samples;
697 sample_data = samples->get_data() + ofs;
702 append(double *bfr, int len)
704 set_buffer(buffer_size(), len);
705 memcpy(get_data(), bfr, len*sizeof(sample_data[0]));
712 double *data = samples->get_data();
713 int move_len = buffer_size() - len;
714 memmove(data, data+len, move_len*sizeof(double));
715 if( move_len < data_len ) {
723 const char* EchoCancel::plugin_title() { return N_("EchoCancel"); }
724 int EchoCancel::is_realtime() { return 1; }
726 static inline double sqr(double v) { return v*v; }
728 static inline void cx_product(int n, int sf, double *rp, double *ip,
729 double *arp, double *aip, double *brp, double *bip)
731 int m = !sf ? n : n/2, i = 0;
733 double ar = arp[i], ai = aip[i];
734 double br = brp[i], bi = bip[i];
735 rp[i] = ar*br - ai*bi; // complex a*ib
736 ip[i] = ar*bi + ai*br;
740 while( --m > 0 ) { rp[i] = rp[m]; ip[i] = -ip[m]; ++i; }
743 static inline void cj_product(int n, int sf, double *rp, double *ip,
744 double *arp, double *aip, double *brp, double *bip)
746 int m = !sf ? n-1 : n/2, i = 0;
748 double ar = arp[i], ai = aip[i];
749 double br = brp[i], bi = -bip[i];
750 rp[i] = ar*br - ai*bi; // complex a*ib'
751 ip[i] = ar*bi + ai*br;
755 while( --m > 0 ) { rp[i] = rp[m]; ip[i] = -ip[m]; ++i; }
758 static inline void log_power(int n, int sf, double *rp, double *arp, double *aip)
760 int m = !sf ? n : n/2, i = 0;
762 double sr = arp[i], si = aip[i];
763 double ss = sqr(sr) + sqr(si);
764 rp[i] = ss > 1e-20 ? log(ss) : -46;
768 while( --m > 0 ) { rp[i] = rp[m]; ++i; }
771 /* Adapted from Marple: Digital Spectral Analysis with Applications
772 * Solves linear simultaneous equations by the Levinson algorithm.
775 * T[m,m] a nonsymmetric Toeplitz matrix,
776 * tc[0..m-1] left column, tr[0..m-1] top row
777 * Z[m] known right-hand-side column,
779 * X[m] solution vector
780 * Returns: 1 if singular, 0 on success
783 static inline int toeplitz(int m, double *tc, double *tr, double *z, double *x)
785 double a[m], b[m], p = tc[0];
786 if( p != tr[0] || fabs(p) < 1e-20 ) return 1;
788 for( int k0=0,k=1; k<m; k0=k++ ) {
789 double sc = tc[k], sr = tr[k], xc = x[0] * sc;
790 for( int i=1,j=k0; i<k; ++i,--j ) {
795 double ac = -sc / p, br = -sr / p;
797 if( fabs(p) < 1e-20 ) return 1;
798 a[k] = ac; b[k] = br;
799 x[k] = (z[k] - xc) / p;
800 for( int i=1,j=k0; j>0; ++i,--j ) {
801 sc = a[i]; sr = b[j];
806 for( int i=0,j=k; j>0; ++i,--j )
813 void EchoCancel::cepstrum(double *audio)
815 //dfile_dump(TMPDIR "audio",audio,window_size);
816 fft->do_fft(window_size, 0, audio, 0, aud_real, aud_imag);
817 //dfile_dump(TMPDIR "aud_real",aud_real,window_size);
818 //dfile_dump(TMPDIR "aud_imag",aud_imag,window_size);
819 // half zero, half window of audio
821 for( ; k<half_window; ++k ) cor[k] = 0;
822 for( ; k<window_size; ++k ) cor[k] = audio[k];
823 fft->do_fft(window_size, 0, cor, 0, env_real, env_imag);
824 //dfile_dump(TMPDIR "env_real",env_real,window_size);
825 //dfile_dump(TMPDIR "env_imag",env_imag,window_size);
826 cj_product(window_size, 1, env_real, env_imag,
827 env_real, env_imag, aud_real, aud_imag);
828 //dfile_dump(TMPDIR "prd_real",env_real,window_size);
829 //dfile_dump(TMPDIR "prd_imag",env_imag,window_size);
830 log_power(window_size, 1, aud_real, env_real, env_imag);
831 // a = zeros, last half audio buffer, b = audio buffer
832 // COR = fft(a)*conj(fft(b)), cor = IFFT(COR)
833 // cepstrum = IFFT(log(mag(COR)**2))
834 double *cept_real = aud_real, *cept_imag = aud_imag; // do in place
835 fft->do_fft(window_size, 1, aud_real, 0, cept_real, cept_imag);
836 fft->do_fft(window_size, 1, env_real, env_imag, cor, aud_imag);
837 //dfile_dump(TMPDIR "cor",cor,half_window);
838 int damp = config.damp; // apply dampening
839 if( ++time_frames < damp ) damp = time_frames;
840 float wt = 1./damp, wt1 = 1. - wt;
841 float *dp = data->get_buffer();
842 for( int i=0; i<half_window; ++i ) {
843 double v = cept_real[i] * cor[i];
844 dp[i] = time_frame[i] = (wt1*time_frame[i] + wt*v);
846 //ffile_dump(TMPDIR "time_frame",time_frame,half_window);
849 void EchoCancel::dfile_dump(const char *fn, double *dp, int n)
851 FILE *fp = fopen(fn,"w");
853 for( int i=0; i<n; ++i ) fprintf(fp,"%f\n",dp[i]);
857 void EchoCancel::ffile_dump(const char *fn, float *dp, int n)
859 FILE *fp = fopen(fn,"w");
861 for( int i=0; i<n; ++i ) fprintf(fp,"%f\n",dp[i]);
865 void EchoCancel::calculate_envelope(int sample_rate, int peaks, int bsz)
867 int cutoff = sample_rate/config.cutoff;
868 bsz |= 1; // should be odd
871 for( int i=bsz2; --i>0; ) power += 2*cor[i];
873 int damp = config.damp; // apply dampening
874 if( ++time_frames < damp ) damp = time_frames;
875 float wt = 1./damp, wt1 = 1. - wt;
876 for( int i=0; i<half_window; ++i ) envelope[i] *= wt1;
877 if( power < 1e-12 ) return;
878 double *dp = env_real, *ep = dp;
879 for( int i=0; i<half_window; ++i ) *ep++ = time_frame[i];
880 //printf("envelope %.4f:\n", power);
881 while( --peaks >= 0 ) {
883 double *sp = dp+x, *mp = sp, *xp = sp;
884 double sx = 0; // first bsz band
885 for( int i=bsz; --i>=0; ) sx += *xp++;
889 if( sx > mx ) { mx = sx; mp = sp; }
892 double echo_signal = 0, *cp = cor+x;
893 for( int i=0; i<bsz; ++i ) echo_signal += cp[i];
894 //printf(" %d(%.3f/%.3f):\n", x, mx, echo_signal/power);
895 // positive reflections only, not too small
896 if( echo_signal/power < 0.001 ) break;
898 int ret = toeplitz(bsz, cor, cor, cp, gain);
901 for( int i=0; i<bsz; ++i ) ss += sqr(gain[i]);
902 double rms = sqrt(ss / bsz);
903 //printf("rms=%f ",rms);
904 if( rms > 2 ) ret = 1; // too wacky
906 if( ret ) { // misbehaved, use single pulse in band center
908 memset(gain, 0, bsz*sizeof(gain[0]));
909 gain[bsz2] = echo_signal / power;
912 for( int i=0; i<bsz; ++i,++x ) {
913 envelope[x] += wt * gain[i];
914 //printf("[%d]=%.3f,", x, envelope[x]); g += envelope[x];
916 //printf(" : %.4f\n",g);
918 // remove echo from search, prevent periodic reverbs
919 for(int k=x; k<half_window; k+=x ) {
920 // clear this band and adj bands
921 int j = k - bsz, n = 3*bsz;
922 if( j < 0 ) { n += j; j = 0; }
923 if( j+n > half_window ) n = half_window - j;
924 for( int i=n; --i>=0; ++j ) dp[j] = 0;
930 void EchoCancel::create_envelope(int sample_rate)
932 memset(envelope,0,half_window*sizeof(envelope[0]));
933 int offset = config.offset;
934 if( offset < 0 || offset >= half_window ) return;
935 double gain = config.gain;
936 if( gain < 0 || gain >= 1 ) return;
937 envelope[offset] = gain;
940 int EchoCancel::cancel_echo(double *bp, int size)
942 int n = half_window + size;
943 if( n < window_size ) n = window_size;
944 if( audio_buffer->buffer_size() < n ) return 1;
946 // a = audio data, t = env data: convolution = ifft(fft(a)*conj(fft(t))),
947 // but ifft(fft(a)*conj(fft(t)))==ifft(fft(a)*fft([t[0]]+t[:0:-1]))
948 // removing conj reverses t, as echos are from the past
949 if( config.mode == CANCEL_MAN ) create_envelope(sample_rate);
950 for( int i=0; i<half_window; ++i ) env_data[k++] = envelope[i];
951 while( k < window_size ) env_data[k++] = 0;
952 fft->do_fft(window_size, 0, env_data, 0, env_real, env_imag);
954 n = half_window + size;
955 if( n < window_size ) n = window_size;
956 double *ap = audio_buffer->get_buffer(-n);
957 fft->do_fft(window_size, 0, ap, 0, aud_real, aud_imag);
958 cx_product(window_size, 1, aud_real, aud_imag,
959 aud_real, aud_imag, env_real, env_imag);
960 fft->do_fft(window_size, 1, aud_real, aud_imag);
962 if( n > half_window ) n = half_window;
963 double *sp = &aud_real[window_size - n];
964 for( int i=n; --i>=0; ++bp,++sp ) *bp -= *sp;
969 void EchoCancel::delete_buffers()
971 delete [] cor; cor = 0;
972 delete [] aud_real; aud_real = 0;
973 delete [] aud_imag; aud_imag = 0;
974 delete [] envelope; envelope = 0;
975 delete [] env_real; env_real = 0;
976 delete [] env_imag; env_real = 0;
977 delete [] env_data; env_data = 0;
978 delete [] time_frame; time_frame = 0;
981 void EchoCancel::alloc_buffers(int fft_sz)
983 window_size = fft_sz;
984 half_window = window_size / 2;
985 cor = new double[window_size];
986 aud_real = new double[window_size];
987 aud_imag = new double[window_size];
988 envelope = new double[half_window];
989 env_real = new double[window_size];
990 env_imag = new double[window_size];
991 env_data = new double[window_size];
992 time_frame = new float[half_window];
995 int EchoCancel::process_buffer(int64_t size, Samples *buffer,
996 int64_t start_position, int sample_rate)
999 read_samples(buffer, 0, sample_rate, start_position, size);
1000 // Reset audio buffer
1001 load_configuration();
1002 int fft_sz = config.window_size;
1003 if( !fft_sz ) fft_sz = 1<<FFT::samples_to_bits(sample_rate);
1004 if( window_size != fft_sz ) {
1007 alloc_buffers(fft_sz);
1010 if( !time_frames ) {
1011 memset(envelope, 0, half_window*sizeof(envelope[0]));
1012 memset(time_frame, 0, half_window*sizeof(time_frame[0]));
1014 if( !data ) data = new DataBuffer(window_size);
1015 if( !audio_buffer ) audio_buffer = new AudioBuffer(window_size+2*size);
1016 if( !fft ) fft = new FFT;
1019 int past_audio = audio_buffer->buffer_size() - window_size;
1020 if( past_audio > 0 ) audio_buffer->remove(past_audio);
1021 audio_buffer->append(buffer->get_data(), size);
1023 // process half_windows
1024 double *bp = buffer->get_data();
1025 int total_windows = 0, audio_size = audio_buffer->buffer_size();
1026 while( audio_size >= window_size ) {
1027 int sample_offset = half_window * total_windows++;
1028 data->set_buffer(sample_offset, half_window);
1029 double *audio = audio_buffer->get_buffer(-audio_size);
1031 if( config.mode == CANCEL_ON )
1032 calculate_envelope(sample_rate, config.peaks, 7);
1033 if( config.mode != CANCEL_OFF && size > 0 )
1034 cancel_echo(bp, size);
1036 size -= half_window;
1037 audio_size -= half_window;
1040 DataHeader *data_header = data->get_header();
1041 data_header->window_size = window_size;
1042 data_header->interrupted = interrupted;
1043 data_header->sample_rate = sample_rate;
1044 data_header->total_windows = total_windows;
1045 data_header->level = DB::fromdb(config.level); // Linear output level
1046 send_render_gui(data_header, data_header->size());
1051 void EchoCancel::render_stop()
1054 if( audio_buffer ) audio_buffer->set_buffer(0, 0);
1055 if( data ) { delete data; data = 0; }
1060 NEW_WINDOW_MACRO(EchoCancel, EchoCancelWindow)
1063 void EchoCancelCanvas::
1064 draw_frame(float *data, int len, int hh)
1066 int w = get_w(), h = get_h();
1070 for(int x1=0,x2=1; x2<w; ++x2 ) {
1071 if( x2 < len ) y = data[x2];
1074 draw_line(x1, y1, x2, y2);
1079 void EchoCancel::update_gui()
1081 if( !thread ) return;
1082 EchoCancelWindow *window = (EchoCancelWindow*)thread->get_window();
1083 window->lock_window("EchoCancel::update_gui");
1084 if( load_configuration() )
1085 window->update_gui();
1086 // Shift frames into history
1088 while( frame_buffer.size() > 0 ) {
1089 while( frame_history.size() >= config.history_size )
1090 frame_history.remove_object_number(0);
1091 frame_history.append(frame_buffer[0]);
1092 frame_buffer.remove_number(0);
1096 if( new_frames > 0 ) {
1097 int last_frame = frame_history.size()-1;
1098 // Draw frames from history
1099 EchoCancelCanvas *canvas = (EchoCancelCanvas*)window->canvas;
1100 canvas->clear_box(0, 0, canvas->get_w(), canvas->get_h());
1101 canvas->set_line_width(1);
1102 for( int frame=0; frame<last_frame; ++frame ) {
1103 int luma = 0x80 * (frame+1)/last_frame + 0x40;
1104 canvas->set_color(luma*0x010101);
1105 EchoCancelFrame *frm = frame_history[frame];
1106 canvas->draw_frame(frm->samples(), frm->size(), h/2);
1108 canvas->set_line_width(2);
1109 canvas->set_color(WHITE);
1110 EchoCancelFrame *frm = frame_history[last_frame];
1111 canvas->draw_frame(frm->samples(), frm->size(), h/2);
1112 canvas->set_color(RED);
1113 canvas->draw_line(frm->cut_offset,0, frm->cut_offset,10);
1114 // Recompute probe level
1115 int do_overlay = canvas->is_dragging() ? 1 : 0;
1116 window->calculate_frequency(window->probe_x, window->probe_y, do_overlay);
1120 window->unlock_window();
1123 void EchoCancel::render_gui(void *data, int size)
1125 if( !thread ) return;
1126 DataHeader *data_header = (DataHeader *)data;
1127 memcpy(&header, data_header, sizeof(header));
1128 if( window_size != data_header->window_size ) {
1129 window_size = data_header->window_size;
1130 half_window = window_size / 2;
1131 frame_buffer.remove_all_objects();
1132 frame_history.remove_all_objects();
1134 if( data_header->interrupted )
1136 EchoCancelWindow *window = (EchoCancelWindow *)thread->get_window();
1137 int pixels = window->canvas->get_w();
1138 float window_scale = (double)half_window / (pixels * config.xzoom);
1139 int nwin = data_header->total_windows;
1140 int iwin = nwin - config.history_size;
1141 if( iwin < 0 ) iwin = 0;
1142 for( ; iwin < nwin; ++iwin ) {
1143 float *samples = data_header->samples + half_window*iwin;
1144 EchoCancelFrame *frm = new EchoCancelFrame(pixels);
1145 float *frm_data = frm->samples();
1146 double cut_period = (double)data_header->sample_rate / config.cutoff;
1147 frm->cut_offset = cut_period / window_scale;
1149 for(int i = 0; i < pixels; i++) {
1150 float start = i*window_scale, stop = start+window_scale;
1151 int istart = (int)start, istop = (int)stop;
1152 float fstart = start-istart, fstop = stop-istop;
1153 fstart = istart == istop ? -fstart : 1 - fstart;
1154 float sum = samples[istart++] * fstart;
1155 while( istart < istop ) sum += samples[istart++];
1156 if( fstop > 1e-10 ) sum += samples[istop] * fstop;
1157 frm_data[i] = sum/window_scale;
1160 float scale = data_header->level;
1161 if( config.normalize ) {
1163 for( int i=frm->cut_offset; i<pixels; ++i ) {
1164 float dat = fabsf(frm_data[i]);
1165 if( dat > max ) max = dat;
1167 scale = max > 1e-10 ? scale / max : 1;
1169 if( scale != 1 ) frm->rescale(scale);
1170 while( frame_buffer.size() >= config.history_size )
1171 frame_buffer.remove_object_number(0);
1172 frame_buffer.append(frm);
1178 int EchoCancel::load_configuration()
1180 KeyFrame *prev_keyframe = get_prev_keyframe(get_source_position());
1181 EchoCancelConfig old_config;
1182 old_config.copy_from(config);
1183 read_data(prev_keyframe);
1184 return !old_config.equivalent(config) ? 1 : 0;
1187 void EchoCancel::read_data(KeyFrame *keyframe)
1191 input.set_shared_input(keyframe->xbuf);
1193 while(!(result = input.read_tag()) ) {
1194 if( !input.tag.title_is("ECHOCANCEL")) continue;
1195 config.level = input.tag.get_property("LEVEL", config.level);
1196 config.normalize = input.tag.get_property("NORMALIZE", config.normalize);
1197 config.window_size = input.tag.get_property("WINDOW_SIZE", config.window_size);
1198 config.xzoom = input.tag.get_property("XZOOM", config.xzoom);
1199 config.peaks = input.tag.get_property("PEAKS", config.peaks);
1200 config.mode = input.tag.get_property("MODE", config.mode);
1201 config.damp = input.tag.get_property("DAMP", config.damp);
1202 config.history_size = input.tag.get_property("HISTORY_SIZE", config.history_size);
1203 config.gain = input.tag.get_property("GAIN", config.gain);
1204 config.offset = input.tag.get_property("OFFSET", config.offset);
1205 if( !is_defaults() ) continue;
1206 w = input.tag.get_property("W", w);
1207 h = input.tag.get_property("H", h);
1211 void EchoCancel::save_data(KeyFrame *keyframe)
1214 output.set_shared_output(keyframe->xbuf);
1216 output.tag.set_title("ECHOCANCEL");
1217 output.tag.set_property("LEVEL", config.level);
1218 output.tag.set_property("NORMALIZE", config.normalize);
1219 output.tag.set_property("WINDOW_SIZE", config.window_size);
1220 output.tag.set_property("MODE", config.mode);
1221 output.tag.set_property("XZOOM", config.xzoom);
1222 output.tag.set_property("PEAKS", config.peaks);
1223 output.tag.set_property("DAMP", config.damp);
1224 output.tag.set_property("HISTORY_SIZE", config.history_size);
1225 output.tag.set_property("GAIN", config.gain);
1226 output.tag.set_property("OFFSET", config.offset);
1227 output.tag.set_property("W", w);
1228 output.tag.set_property("H", h);
1229 output.append_tag();
1230 output.tag.set_title("/ECHOCANCEL");
1231 output.append_tag();
1232 output.append_newline();
1233 output.terminate_string();