new/reworked audio plugins ported from hv72 compressor/multi/reverb, glyph workaround...
[goodguy/cinelerra.git] / cinelerra-5.1 / plugins / compressor / compressor.C
1 /*
2  * CINELERRA
3  * Copyright (C) 2008-2019 Adam Williams <broadcast at earthling dot net>
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  *
19  */
20
21 #include "bcdisplayinfo.h"
22 #include "bchash.h"
23 #include "bcsignals.h"
24 #include "asset.h"
25 #include "clip.h"
26 #include "compressor.h"
27 #include "cursors.h"
28 #include "edl.h"
29 #include "edlsession.h"
30 #include "filexml.h"
31 #include "language.h"
32 #include "samples.h"
33 #include "theme.h"
34 #include "tracking.inc"
35 #include "transportque.inc"
36 #include "units.h"
37 #include "vframe.h"
38
39 #include <math.h>
40 #include <string.h>
41
42 REGISTER_PLUGIN(CompressorEffect)
43
44 // More potential compressor algorithms:
45 // Use single readahead time parameter.  Negative readahead time uses
46 // readahead.  Positive readahead time uses slope.
47
48 // Smooth input stage if readahead.
49 // Determine slope from current smoothed sample to every sample in readahead area.
50 // Once highest slope is found, count of number of samples remaining until it is
51 // reached.  Only search after this count for the next highest slope.
52 // Use highest slope to determine smoothed value.
53
54 // Smooth input stage if not readahead.
55 // For every sample, calculate slope needed to reach current sample from
56 // current smoothed value in the readahead time.  If higher than current slope,
57 // make it the current slope and count number of samples remaining until it is
58 // reached.  If this count is met and no higher slopes are found, base slope
59 // on current sample when count is met.
60
61 // Gain stage.
62 // For every sample, calculate gain from smoothed input value.
63
64 CompressorEffect::CompressorEffect(PluginServer *server)
65  : PluginAClient(server)
66 {
67         input_buffer = 0;
68         input_size = 0;
69         input_allocated = 0;
70         input_start = 0;
71
72         need_reconfigure = 1;
73         engine = 0;
74 }
75
76 CompressorEffect::~CompressorEffect()
77 {
78         if( input_buffer ) {
79                 for( int i=0; i<PluginClient::total_in_buffers; ++i )
80                         delete input_buffer[i];
81                 delete [] input_buffer;  input_buffer = 0;
82         }
83         input_size = 0;
84         input_allocated = 0;
85         levels.remove_all();
86         delete engine;
87 }
88
89 void CompressorEffect::allocate_input(int size)
90 {
91         int channels = PluginClient::total_in_buffers;
92         if( size > input_allocated ) {
93                 Samples **new_input_buffer = new Samples*[channels];
94                 for( int i=0; i<channels; ++i ) {
95                         new_input_buffer[i] = new Samples(size);
96                         if( !input_buffer ) continue;
97                         memcpy(new_input_buffer[i]->get_data(),
98                                 input_buffer[i]->get_data(),
99                                 input_size * sizeof(double));
100                         delete input_buffer[i];
101                 }
102                 if( input_buffer ) delete [] input_buffer;
103
104                 input_allocated = size;
105                 input_buffer = new_input_buffer;
106         }
107 }
108
109 const char* CompressorEffect::plugin_title() { return N_("Compressor"); }
110 int CompressorEffect::is_realtime() { return 1; }
111 int CompressorEffect::is_multichannel() { return 1; }
112
113 void CompressorEffect::read_data(KeyFrame *keyframe)
114 {
115         FileXML input;
116         BandConfig *band_config = &config.bands[0];
117         input.set_shared_input(keyframe->xbuf);
118
119         int result = 0;
120         while( !(result=input.read_tag()) ) {
121                 if( input.tag.title_is("COMPRESSOR") ) {
122 //                      band_config->readahead_len = input.tag.get_property("READAHEAD_LEN", band_config->readahead_len);
123                         band_config->attack_len = input.tag.get_property("ATTACK_LEN", band_config->attack_len);
124                         band_config->release_len = input.tag.get_property("RELEASE_LEN", band_config->release_len);
125                         config.trigger = input.tag.get_property("TRIGGER", config.trigger);
126                         config.smoothing_only = input.tag.get_property("SMOOTHING_ONLY", config.smoothing_only);
127                         config.input = input.tag.get_property("INPUT", config.input);
128                 }
129                 else if( input.tag.title_is("COMPRESSORBAND") ) {
130                         band_config->read_data(&input, 0);
131                 }
132         }
133         config.boundaries();
134 }
135
136 void CompressorEffect::save_data(KeyFrame *keyframe)
137 {
138         FileXML output;
139         BandConfig *band_config = &config.bands[0];
140         output.set_shared_output(keyframe->xbuf);
141
142         output.tag.set_title("COMPRESSOR");
143         output.tag.set_property("TRIGGER", config.trigger);
144         output.tag.set_property("SMOOTHING_ONLY", config.smoothing_only);
145         output.tag.set_property("INPUT", config.input);
146         output.tag.set_property("ATTACK_LEN", band_config->attack_len);
147         output.tag.set_property("RELEASE_LEN", band_config->release_len);
148         output.append_tag();
149         output.append_newline();
150
151         band_config->save_data(&output, 0, 0);
152         output.tag.set_title("/COMPRESSOR");
153         output.append_tag();
154         output.append_newline();
155         output.terminate_string();
156 }
157
158
159 void CompressorEffect::update_gui()
160 {
161         if( !thread ) return;
162 // user can't change levels when loading configuration
163         thread->window->lock_window("CompressorEffect::update_gui");
164         CompressorWindow *window = (CompressorWindow *)thread->window;
165 // Can't update points if the user is editing
166         int reconfigured = window->canvas->is_dragging() ? 0 :
167                 load_configuration();
168         if( reconfigured )
169                 window->update();
170 // delete frames up to current tracking position
171         int dir = get_tracking_direction() == PLAY_FORWARD ? 1 : -1;
172         double tracking_position = get_tracking_position();
173         CompressorGainFrame *gain_frame = (CompressorGainFrame *)
174                 get_gui_frame(tracking_position, dir);
175         if( gain_frame ) {
176                 window->update_meter(gain_frame);
177                 delete gain_frame;
178         }
179         window->unlock_window();
180 }
181
182 void CompressorEffect::render_stop()
183 {
184         if( !thread ) return;
185         thread->window->lock_window("CompressorEffect::render_stop");
186         CompressorWindow *window = (CompressorWindow *)thread->window;
187         window->in->reset();
188         window->gain_change->update(1, 0);
189         window->unlock_window();
190 }
191
192 LOAD_CONFIGURATION_MACRO(CompressorEffect, CompressorConfig)
193 NEW_WINDOW_MACRO(CompressorEffect, CompressorWindow)
194
195 int CompressorEffect::process_buffer(int64_t size, Samples **buffer,
196                 int64_t start_position, int sample_rate)
197 {
198         int channels = PluginClient::total_in_buffers;
199         BandConfig *band_config = &config.bands[0];
200         int sign = get_direction() == PLAY_REVERSE ? -1 : 1;
201         load_configuration();
202
203 // restart after seeking
204         if( last_position != start_position ) {
205                 last_position = start_position;
206                 if( engine )
207                         engine->reset();
208                 input_size = 0;
209                 input_start = start_position;
210         }
211
212 // Calculate linear transfer from db
213         int nbands = band_config->levels.size();
214         while( levels.size() < nbands ) levels.append();
215
216         for( int i=0; i<band_config->levels.total; ++i ) {
217                 levels.values[i].x = DB::fromdb(band_config->levels.values[i].x);
218                 levels.values[i].y = DB::fromdb(band_config->levels.values[i].y);
219         }
220 //      min_x = DB::fromdb(config.min_db);
221 //      min_y = DB::fromdb(config.min_db);
222 //      max_x = 1.0;
223 //      max_y = 1.0;
224
225
226         int attack_samples;
227         int release_samples;
228         int preview_samples;
229
230         if( !engine ) {
231                 engine = new CompressorEngine(&config, 0);
232                 engine->gui_frame_samples = sample_rate / TRACKING_RATE + 1;
233         }
234         engine->calculate_ranges(&attack_samples, &release_samples,
235                         &preview_samples, sample_rate);
236
237 // Start of new buffer is outside the current buffer.  Start buffer over.
238         if( get_direction() == PLAY_FORWARD &&
239                 (start_position < input_start ||
240                 start_position >= input_start + input_size)
241                 ||
242                 get_direction() == PLAY_REVERSE &&
243                 (start_position > input_start ||
244                 start_position <= input_start - input_size) ) {
245 // printf("CompressorEffect::process_buffer %d start_position=%ld input_start=%ld input_size=%ld\n",
246 // __LINE__, start_position, input_start, input_size);
247                 input_size = 0;
248                 input_start = start_position;
249         }
250         else
251 // Shift current buffer so the buffer starts on start_position
252         if( get_direction() == PLAY_FORWARD &&
253                 start_position > input_start &&
254                 start_position < input_start + input_size
255                 ||
256                 get_direction() == PLAY_REVERSE &&
257                 start_position < input_start &&
258                 start_position > input_start - input_size ) {
259                 if( input_buffer ) {
260                         int len, offset;
261                         if( get_direction() == PLAY_FORWARD ) {
262                                 len = input_start + input_size - start_position;
263                                 offset = start_position - input_start;
264                         }
265                         else {
266                                 len = start_position - (input_start - input_size);
267                                 offset = input_start - start_position;
268                         }
269
270                         for( int i = 0; i < channels; i++ ) {
271                                 memcpy(input_buffer[i]->get_data(),
272                                         input_buffer[i]->get_data() + offset,
273                                         len * sizeof(double));
274                         }
275                         input_size = len;
276                         input_start = start_position;
277                 }
278         }
279
280 // Expand buffer to handle preview size
281         if( size + preview_samples > input_allocated ) {
282                 Samples **new_input_buffer = new Samples*[channels];
283                 for( int i = 0; i < channels; i++ ) {
284                         new_input_buffer[i] = new Samples(size + preview_samples);
285                         if( input_buffer ) {
286                                 memcpy(new_input_buffer[i]->get_data(),
287                                         input_buffer[i]->get_data(),
288                                         input_size * sizeof(double));
289                                 delete input_buffer[i];
290                         }
291                 }
292                 if( input_buffer ) delete [] input_buffer;
293
294                 input_allocated = size + preview_samples;
295                 input_buffer = new_input_buffer;
296         }
297
298 // Append data to input buffer to construct readahead area.
299         if( input_size < size + preview_samples ) {
300                 int fragment_size = size + preview_samples - input_size;
301                 for( int i = 0; i < channels; i++ ) {
302                         input_buffer[i]->set_offset(input_size);
303                         read_samples(input_buffer[i], i, sample_rate,
304                                 input_start + input_size * sign, fragment_size);
305                         input_buffer[i]->set_offset(0);
306                 }
307                 input_size += fragment_size;
308         }
309
310         engine->process(buffer, input_buffer, size,
311                 sample_rate, PluginClient::total_in_buffers, start_position);
312
313         double start_pos = (double)start_position / sample_rate;
314         for( int i = 0; i < engine->gui_gains.size(); i++ ) {
315                 CompressorGainFrame *gain_frame = new CompressorGainFrame();
316                 gain_frame->position = start_pos + sign*engine->gui_offsets[i];
317                 gain_frame->gain = engine->gui_gains[i];
318                 gain_frame->level = engine->gui_levels[i];
319                 add_gui_frame(gain_frame);
320         }
321
322         last_position += sign * size;
323         return 0;
324 }
325
326
327 CompressorConfig::CompressorConfig()
328  : CompressorConfigBase(1)
329 {
330 }
331
332 void CompressorConfig::copy_from(CompressorConfig &that)
333 {
334         CompressorConfigBase::copy_from(that);
335 }
336
337 int CompressorConfig::equivalent(CompressorConfig &that)
338 {
339         return CompressorConfigBase::equivalent(that);
340         return 1;
341 }
342
343 void CompressorConfig::interpolate(CompressorConfig &prev,
344         CompressorConfig &next,
345         int64_t prev_frame,
346         int64_t next_frame,
347         int64_t current_frame)
348 {
349         copy_from(prev);
350 }
351
352 CompressorWindow::CompressorWindow(CompressorEffect *plugin)
353  : PluginClientWindow(plugin, xS(650), yS(480), xS(650), yS(480), 0)
354 {
355         this->plugin = plugin;
356 }
357
358 CompressorWindow::~CompressorWindow()
359 {
360 //      delete readahead;
361         delete attack;
362         delete release;
363         delete trigger;
364         delete x_text;
365         delete y_text;
366 }
367
368 void CompressorWindow::create_objects()
369 {
370         int margin = client->get_theme()->widget_border;
371         int x = margin, y = margin;
372         int control_margin = xS(130);
373         int canvas_y2 = get_h()-yS(35);
374         BC_Title *title;
375
376         add_subwindow(title = new BC_Title(x, y, "In:"));
377         int y2 = y + title->get_h() + margin;
378         EDLSession *session = plugin->get_edl()->session;
379         add_subwindow(in = new BC_Meter(x, y2, METER_VERT, canvas_y2 - y2,
380                 session->min_meter_db, session->max_meter_db, session->meter_format,
381                 1, // use_titles
382                 -1)); // span
383         x += in->get_w() + margin;
384
385         add_subwindow(title = new BC_Title(x, y, "Gain:"));
386         add_subwindow(gain_change = new BC_Meter(x, y2, METER_VERT,
387                 canvas_y2 - y2, MIN_GAIN_CHANGE, MAX_GAIN_CHANGE, METER_DB,
388                 1, // use_titles
389                 -1, // span
390                 0, // is_downmix
391                 1)); // is_gain_change
392 //      gain_change->update(1, 0);
393
394         x += gain_change->get_w() + xS(35);
395         add_subwindow(title = new BC_Title(x, y, _("Sound level (Press shift to snap to grid):")));
396         y += title->get_h() + 1;
397         add_subwindow(canvas = new CompressorCanvas(plugin, this, x, y,
398                 get_w() - x - control_margin - xS(10), canvas_y2 - y));
399         x = get_w() - control_margin;
400
401 //      add_subwindow(new BC_Title(x, y, _("Lookahead secs:")));
402 //      y += title->get_h() + margin;
403 //      readahead = new CompressorLookAhead(plugin, this, x, y);
404 //      readahead->create_objects();
405 //      y += readahead->get_h() + margin;
406
407         add_subwindow(new BC_Title(x, y, _("Attack secs:")));
408         y += title->get_h() + margin;
409         attack = new CompressorAttack(plugin, this, x, y);
410         attack->create_objects();
411         y += attack->get_h() + margin;
412
413         add_subwindow(title = new BC_Title(x, y, _("Release secs:")));
414         y += title->get_h() + margin;
415         release = new CompressorRelease(plugin, this, x, y);
416         release->create_objects();
417         y += release->get_h() + margin;
418
419         add_subwindow(title = new BC_Title(x, y, _("Trigger Type:")));
420         y += title->get_h() + margin;
421         add_subwindow(input = new CompressorInput(plugin, x, y));
422         input->create_objects();
423         y += input->get_h() + margin;
424
425         add_subwindow(title = new BC_Title(x, y, _("Trigger:")));
426         y += title->get_h() + margin;
427         trigger = new CompressorTrigger(plugin, this, x, y);
428         trigger->create_objects();
429         if( plugin->config.input != CompressorConfig::TRIGGER ) trigger->disable();
430         y += trigger->get_h() + margin;
431
432         add_subwindow(smooth = new CompressorSmooth(plugin, x, y));
433         y += smooth->get_h() + margin;
434
435         add_subwindow(title = new BC_Title(x, y, _("Output:")));
436         y += title->get_h();
437         y_text = new CompressorY(plugin, this, x, y);
438         y_text->create_objects();
439         y += y_text->get_h() + margin;
440
441         add_subwindow(title = new BC_Title(x, y, _("Input:")));
442         y += title->get_h();
443         x_text = new CompressorX(plugin, this, x, y);
444         x_text->create_objects();
445         y += x_text->get_h() + margin;
446
447         add_subwindow(clear = new CompressorClear(plugin, x, y));
448         x = xS(10);
449         y = get_h() - yS(40);
450
451         canvas->create_objects();
452         canvas->update();
453         show_window();
454 }
455
456
457 void CompressorWindow::update()
458 {
459         update_textboxes();
460         canvas->update();
461 }
462
463 void CompressorWindow::update_meter(CompressorGainFrame *gain_frame)
464 {
465         gain_change->update(gain_frame->gain, 0);
466         in->update(gain_frame->level, 0);
467 }
468
469 void CompressorWindow::update_textboxes()
470 {
471         BandConfig *band_config = &plugin->config.bands[0];
472
473         if( atol(trigger->get_text()) != plugin->config.trigger )
474                 trigger->update((int64_t)plugin->config.trigger);
475         if( strcmp(input->get_text(), CompressorInput::value_to_text(plugin->config.input)) )
476                 input->set_text(CompressorInput::value_to_text(plugin->config.input));
477
478         if( plugin->config.input != CompressorConfig::TRIGGER && trigger->get_enabled() )
479                 trigger->disable();
480         else
481         if( plugin->config.input == CompressorConfig::TRIGGER && !trigger->get_enabled() )
482                 trigger->enable();
483
484 //      if( !EQUIV(atof(readahead->get_text()), band_config->readahead_len) )
485 //              readahead->update((float)band_config->readahead_len);
486         if( !EQUIV(atof(attack->get_text()), band_config->attack_len) )
487                 attack->update((float)band_config->attack_len);
488         if( !EQUIV(atof(release->get_text()), band_config->release_len) )
489                 release->update((float)band_config->release_len);
490         smooth->update(plugin->config.smoothing_only);
491         if( canvas->current_operation == CompressorCanvas::DRAG ) {
492                 x_text->update((float)band_config->levels.values[canvas->current_point].x);
493                 y_text->update((float)band_config->levels.values[canvas->current_point].y);
494         }
495 }
496
497 int CompressorWindow::resize_event(int w, int h)
498 {
499         return 1;
500 }
501
502
503 CompressorCanvas::CompressorCanvas(CompressorEffect *plugin,
504         CompressorWindow *window, int x, int y, int w, int h)
505  : CompressorCanvasBase(&plugin->config, plugin, window, x, y, w, h)
506 {
507 }
508
509
510 void CompressorCanvas::update_window()
511 {
512         ((CompressorWindow*)window)->update();
513 }
514
515
516 // CompressorLookAhead::CompressorLookAhead(CompressorEffect *plugin,
517 //       CompressorWindow *window, int x, int y)
518 //  : BC_TumbleTextBox(window, (float)plugin->config.bands[0].readahead_len,
519 //       (float)MIN_LOOKAHEAD, (float)MAX_LOOKAHEAD, x, y, xS(100))
520 // {
521 //      this->plugin = plugin;
522 //       set_increment(0.1);
523 //       set_precision(2);
524 // }
525 //
526 // int CompressorLookAhead::handle_event()
527 // {
528 //      plugin->config.bands[0].readahead_len = atof(get_text());
529 //      plugin->send_configure_change();
530 //      return 1;
531 // }
532 //
533 //
534
535
536 CompressorAttack::CompressorAttack(CompressorEffect *plugin,
537         CompressorWindow *window, int x, int y)
538  : BC_TumbleTextBox(window, (float)plugin->config.bands[0].attack_len,
539         (float)MIN_ATTACK, (float)MAX_ATTACK, x, y, xS(100))
540 {
541         this->plugin = plugin;
542         set_increment(0.1);
543         set_precision(2);
544 }
545
546 int CompressorAttack::handle_event()
547 {
548         plugin->config.bands[0].attack_len = atof(get_text());
549         plugin->send_configure_change();
550         return 1;
551 }
552
553 CompressorRelease::CompressorRelease(CompressorEffect *plugin,
554         CompressorWindow *window, int x, int y)
555  : BC_TumbleTextBox(window, (float)plugin->config.bands[0].release_len,
556         (float)MIN_DECAY, (float)MAX_DECAY, x, y, xS(100))
557 {
558         this->plugin = plugin;
559         set_increment(0.1);
560         set_precision(2);
561 }
562 int CompressorRelease::handle_event()
563 {
564         plugin->config.bands[0].release_len = atof(get_text());
565         plugin->send_configure_change();
566         return 1;
567 }
568
569
570 CompressorX::CompressorX(CompressorEffect *plugin,
571         CompressorWindow *window, int x, int y)
572  : BC_TumbleTextBox(window, (float)0.0,
573         plugin->config.min_db, plugin->config.max_db, x, y, xS(100))
574 {
575         this->plugin = plugin;
576         set_increment(0.1);
577         set_precision(2);
578 }
579 int CompressorX::handle_event()
580 {
581         BandConfig *band_config = &plugin->config.bands[0];
582         int current_point = ((CompressorWindow*)plugin->thread->window)->canvas->current_point;
583         if( current_point < band_config->levels.total ) {
584                 band_config->levels.values[current_point].x = atof(get_text());
585                 ((CompressorWindow*)plugin->thread->window)->canvas->update();
586                 plugin->send_configure_change();
587         }
588         return 1;
589 }
590
591 CompressorY::CompressorY(CompressorEffect *plugin,
592         CompressorWindow *window, int x, int y)
593  : BC_TumbleTextBox(window, (float)0.0,
594         plugin->config.min_db, plugin->config.max_db, x, y, xS(100))
595 {
596         this->plugin = plugin;
597         set_increment(0.1);
598         set_precision(2);
599 }
600 int CompressorY::handle_event()
601 {
602         BandConfig *band_config = &plugin->config.bands[0];
603         int current_point = ((CompressorWindow*)plugin->thread->window)->canvas->current_point;
604         if( current_point < band_config->levels.total ) {
605                 band_config->levels.values[current_point].y = atof(get_text());
606                 ((CompressorWindow*)plugin->thread->window)->canvas->update();
607                 plugin->send_configure_change();
608         }
609         return 1;
610 }
611
612
613 CompressorTrigger::CompressorTrigger(CompressorEffect *plugin,
614         CompressorWindow *window, int x, int y)
615  : BC_TumbleTextBox(window, (int)plugin->config.trigger,
616         MIN_TRIGGER, MAX_TRIGGER, x, y, xS(100))
617 {
618         this->plugin = plugin;
619         set_increment(1);
620 }
621 int CompressorTrigger::handle_event()
622 {
623         plugin->config.trigger = atol(get_text());
624         plugin->send_configure_change();
625         return 1;
626 }
627
628
629 CompressorInput::CompressorInput(CompressorEffect *plugin, int x, int y)
630  : BC_PopupMenu(x, y, xS(100),
631         CompressorInput::value_to_text(plugin->config.input), 1)
632 {
633         this->plugin = plugin;
634 }
635 int CompressorInput::handle_event()
636 {
637         plugin->config.input = text_to_value(get_text());
638         ((CompressorWindow*)plugin->thread->window)->update();
639         plugin->send_configure_change();
640         return 1;
641 }
642
643 void CompressorInput::create_objects()
644 {
645         for( int i = 0; i < 3; i++ ) {
646                 add_item(new BC_MenuItem(value_to_text(i)));
647         }
648 }
649
650 const char* CompressorInput::value_to_text(int value)
651 {
652         switch( value )
653         {
654                 case CompressorConfig::TRIGGER: return "Trigger";
655                 case CompressorConfig::MAX: return "Maximum";
656                 case CompressorConfig::SUM: return "Total";
657         }
658
659         return "Trigger";
660 }
661
662 int CompressorInput::text_to_value(char *text)
663 {
664         for( int i = 0; i < 3; i++ ) {
665                 if( !strcmp(value_to_text(i), text) ) return i;
666         }
667
668         return CompressorConfig::TRIGGER;
669 }
670
671 CompressorClear::CompressorClear(CompressorEffect *plugin, int x, int y)
672  : BC_GenericButton(x, y, _("Clear"))
673 {
674         this->plugin = plugin;
675 }
676
677 int CompressorClear::handle_event()
678 {
679         BandConfig *band_config = &plugin->config.bands[0];
680         band_config->levels.remove_all();
681 //plugin->config.dump();
682         ((CompressorWindow*)plugin->thread->window)->update();
683         plugin->send_configure_change();
684         return 1;
685 }
686
687 CompressorSmooth::CompressorSmooth(CompressorEffect *plugin, int x, int y)
688  : BC_CheckBox(x, y, plugin->config.smoothing_only, _("Smooth only"))
689 {
690         this->plugin = plugin;
691 }
692
693 int CompressorSmooth::handle_event()
694 {
695         plugin->config.smoothing_only = get_value();
696         plugin->send_configure_change();
697         return 1;
698 }
699