3 * Copyright (C) 2008-2019 Adam Williams <broadcast at earthling dot net>
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.
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.
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
21 #include "bcdisplayinfo.h"
23 #include "bcsignals.h"
26 #include "compressor.h"
29 #include "edlsession.h"
34 #include "tracking.inc"
35 #include "transportque.inc"
42 REGISTER_PLUGIN(CompressorEffect)
44 // More potential compressor algorithms:
45 // Use single readahead time parameter. Negative readahead time uses
46 // readahead. Positive readahead time uses slope.
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.
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.
62 // For every sample, calculate gain from smoothed input value.
64 CompressorEffect::CompressorEffect(PluginServer *server)
65 : PluginAClient(server)
76 CompressorEffect::~CompressorEffect()
79 for( int i=0; i<PluginClient::total_in_buffers; ++i )
80 delete input_buffer[i];
81 delete [] input_buffer; input_buffer = 0;
89 void CompressorEffect::allocate_input(int size)
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];
102 if( input_buffer ) delete [] input_buffer;
104 input_allocated = size;
105 input_buffer = new_input_buffer;
109 const char* CompressorEffect::plugin_title() { return N_("Compressor"); }
110 int CompressorEffect::is_realtime() { return 1; }
111 int CompressorEffect::is_multichannel() { return 1; }
113 void CompressorEffect::read_data(KeyFrame *keyframe)
116 BandConfig *band_config = &config.bands[0];
117 input.set_shared_input(keyframe->xbuf);
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);
129 else if( input.tag.title_is("COMPRESSORBAND") ) {
130 band_config->read_data(&input, 0);
136 void CompressorEffect::save_data(KeyFrame *keyframe)
139 BandConfig *band_config = &config.bands[0];
140 output.set_shared_output(keyframe->xbuf);
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);
149 output.append_newline();
151 band_config->save_data(&output, 0, 0);
152 output.tag.set_title("/COMPRESSOR");
154 output.append_newline();
155 output.terminate_string();
159 void CompressorEffect::update_gui()
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();
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);
176 window->update_meter(gain_frame);
179 window->unlock_window();
182 void CompressorEffect::render_stop()
184 if( !thread ) return;
185 thread->window->lock_window("CompressorEffect::render_stop");
186 CompressorWindow *window = (CompressorWindow *)thread->window;
188 window->gain_change->update(1, 0);
189 window->unlock_window();
192 LOAD_CONFIGURATION_MACRO(CompressorEffect, CompressorConfig)
193 NEW_WINDOW_MACRO(CompressorEffect, CompressorWindow)
195 int CompressorEffect::process_buffer(int64_t size, Samples **buffer,
196 int64_t start_position, int sample_rate)
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();
203 // restart after seeking
204 if( last_position != start_position ) {
205 last_position = start_position;
209 input_start = start_position;
212 // Calculate linear transfer from db
213 int nbands = band_config->levels.size();
214 while( levels.size() < nbands ) levels.append();
216 for( int i=0; i<band_config->levels.total; ++i ) {
217 levels[i].x = DB::fromdb(band_config->levels[i].x);
218 levels[i].y = DB::fromdb(band_config->levels[i].y);
220 // min_x = DB::fromdb(config.min_db);
221 // min_y = DB::fromdb(config.min_db);
231 engine = new CompressorEngine(&config, 0);
232 engine->gui_frame_samples = sample_rate / TRACKING_RATE + 1;
234 engine->calculate_ranges(&attack_samples, &release_samples,
235 &preview_samples, sample_rate);
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)
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);
248 input_start = start_position;
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
256 get_direction() == PLAY_REVERSE &&
257 start_position < input_start &&
258 start_position > input_start - input_size ) {
261 if( get_direction() == PLAY_FORWARD ) {
262 len = input_start + input_size - start_position;
263 offset = start_position - input_start;
266 len = start_position - (input_start - input_size);
267 offset = input_start - start_position;
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));
276 input_start = start_position;
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);
286 memcpy(new_input_buffer[i]->get_data(),
287 input_buffer[i]->get_data(),
288 input_size * sizeof(double));
289 delete input_buffer[i];
292 if( input_buffer ) delete [] input_buffer;
294 input_allocated = size + preview_samples;
295 input_buffer = new_input_buffer;
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);
307 input_size += fragment_size;
310 engine->process(buffer, input_buffer, size,
311 sample_rate, PluginClient::total_in_buffers, start_position);
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);
322 last_position += sign * size;
327 CompressorConfig::CompressorConfig()
328 : CompressorConfigBase(1)
332 void CompressorConfig::copy_from(CompressorConfig &that)
334 CompressorConfigBase::copy_from(that);
337 int CompressorConfig::equivalent(CompressorConfig &that)
339 return CompressorConfigBase::equivalent(that);
342 void CompressorConfig::interpolate(CompressorConfig &prev,
343 CompressorConfig &next,
346 int64_t current_frame)
351 CompressorWindow::CompressorWindow(CompressorEffect *plugin)
352 : PluginClientWindow(plugin, xS(650), yS(480), xS(650), yS(480), 0)
354 this->plugin = plugin;
357 CompressorWindow::~CompressorWindow()
367 void CompressorWindow::create_objects()
369 int margin = client->get_theme()->widget_border;
370 int x = margin, y = margin;
371 int control_margin = xS(130);
372 int canvas_y2 = get_h()-yS(35);
375 add_subwindow(title = new BC_Title(x, y, "In:"));
376 int y2 = y + title->get_h() + margin;
377 EDLSession *session = plugin->get_edl()->session;
378 add_subwindow(in = new BC_Meter(x, y2, METER_VERT, canvas_y2 - y2,
379 session->min_meter_db, session->max_meter_db, session->meter_format,
382 x += in->get_w() + margin;
384 add_subwindow(title = new BC_Title(x, y, "Gain:"));
385 add_subwindow(gain_change = new BC_Meter(x, y2, METER_VERT,
386 canvas_y2 - y2, MIN_GAIN_CHANGE, MAX_GAIN_CHANGE, METER_DB,
390 1)); // is_gain_change
391 // gain_change->update(1, 0);
393 x += gain_change->get_w() + xS(35);
394 add_subwindow(title = new BC_Title(x, y, _("Sound level (Press shift to snap to grid):")));
395 y += title->get_h() + 1;
396 add_subwindow(canvas = new CompressorCanvas(plugin, this, x, y,
397 get_w() - x - control_margin - xS(10), canvas_y2 - y));
398 x = get_w() - control_margin;
400 // add_subwindow(new BC_Title(x, y, _("Lookahead secs:")));
401 // y += title->get_h() + margin;
402 // readahead = new CompressorLookAhead(plugin, this, x, y);
403 // readahead->create_objects();
404 // y += readahead->get_h() + margin;
406 add_subwindow(new BC_Title(x, y, _("Attack secs:")));
407 y += title->get_h() + margin;
408 attack = new CompressorAttack(plugin, this, x, y);
409 attack->create_objects();
410 y += attack->get_h() + margin;
412 add_subwindow(title = new BC_Title(x, y, _("Release secs:")));
413 y += title->get_h() + margin;
414 release = new CompressorRelease(plugin, this, x, y);
415 release->create_objects();
416 y += release->get_h() + margin;
418 add_subwindow(title = new BC_Title(x, y, _("Trigger Type:")));
419 y += title->get_h() + margin;
420 add_subwindow(input = new CompressorInput(plugin, x, y));
421 input->create_objects();
422 y += input->get_h() + margin;
424 add_subwindow(title = new BC_Title(x, y, _("Trigger:")));
425 y += title->get_h() + margin;
426 trigger = new CompressorTrigger(plugin, this, x, y);
427 trigger->create_objects();
428 if( plugin->config.input != CompressorConfig::TRIGGER ) trigger->disable();
429 y += trigger->get_h() + margin;
431 add_subwindow(smooth = new CompressorSmooth(plugin, x, y));
432 y += smooth->get_h() + margin;
434 add_subwindow(title = new BC_Title(x, y, _("Output:")));
436 y_text = new CompressorY(plugin, this, x, y);
437 y_text->create_objects();
438 y += y_text->get_h() + margin;
440 add_subwindow(title = new BC_Title(x, y, _("Input:")));
442 x_text = new CompressorX(plugin, this, x, y);
443 x_text->create_objects();
444 y += x_text->get_h() + margin;
446 add_subwindow(clear = new CompressorClear(plugin, this, x, y));
447 y += clear->get_h() + margin;
449 add_subwindow(title = new BC_Title(x, y, "Gain:"));
450 y += title->get_h() + margin;
451 BandConfig *band_config = &plugin->config.bands[0];
452 add_subwindow(mkup_gain = new CompressorMkupGain(plugin, this, x, y,
453 &band_config->mkup_gain, -10., 10.));
454 y += mkup_gain->get_h() + margin;
456 add_subwindow(reset = new CompressorReset(plugin, this, x, y));
457 // y += reset->get_h() + margin;
460 y = get_h() - yS(40);
462 canvas->create_objects();
468 void CompressorWindow::update()
471 BandConfig *band_config = &plugin->config.bands[0];
472 mkup_gain->update(band_config->mkup_gain);
476 void CompressorWindow::update_meter(CompressorGainFrame *gain_frame)
478 gain_change->update(gain_frame->gain, 0);
479 in->update(gain_frame->level, 0);
482 void CompressorWindow::update_textboxes()
484 BandConfig *band_config = &plugin->config.bands[0];
486 if( atol(trigger->get_text()) != plugin->config.trigger )
487 trigger->update((int64_t)plugin->config.trigger);
488 if( strcmp(input->get_text(), CompressorInput::value_to_text(plugin->config.input)) )
489 input->set_text(CompressorInput::value_to_text(plugin->config.input));
491 if( plugin->config.input != CompressorConfig::TRIGGER && trigger->get_enabled() )
494 if( plugin->config.input == CompressorConfig::TRIGGER && !trigger->get_enabled() )
497 // if( !EQUIV(atof(readahead->get_text()), band_config->readahead_len) )
498 // readahead->update((float)band_config->readahead_len);
499 if( !EQUIV(atof(attack->get_text()), band_config->attack_len) )
500 attack->update((float)band_config->attack_len);
501 if( !EQUIV(atof(release->get_text()), band_config->release_len) )
502 release->update((float)band_config->release_len);
503 smooth->update(plugin->config.smoothing_only);
504 if( canvas->current_operation == CompressorCanvas::DRAG ) {
505 x_text->update((float)band_config->levels[canvas->current_point].x);
506 y_text->update((float)band_config->levels[canvas->current_point].y);
510 int CompressorWindow::resize_event(int w, int h)
516 CompressorCanvas::CompressorCanvas(CompressorEffect *plugin,
517 CompressorWindow *window, int x, int y, int w, int h)
518 : CompressorCanvasBase(&plugin->config, plugin, window, x, y, w, h)
523 void CompressorCanvas::update_window()
525 ((CompressorWindow*)window)->update();
529 // CompressorLookAhead::CompressorLookAhead(CompressorEffect *plugin,
530 // CompressorWindow *window, int x, int y)
531 // : BC_TumbleTextBox(window, (float)plugin->config.bands[0].readahead_len,
532 // (float)MIN_LOOKAHEAD, (float)MAX_LOOKAHEAD, x, y, xS(100))
534 // this->plugin = plugin;
535 // set_increment(0.1);
539 // int CompressorLookAhead::handle_event()
541 // plugin->config.bands[0].readahead_len = atof(get_text());
542 // plugin->send_configure_change();
549 CompressorAttack::CompressorAttack(CompressorEffect *plugin,
550 CompressorWindow *window, int x, int y)
551 : BC_TumbleTextBox(window, (float)plugin->config.bands[0].attack_len,
552 (float)MIN_ATTACK, (float)MAX_ATTACK, x, y, xS(100))
554 this->plugin = plugin;
559 int CompressorAttack::handle_event()
561 plugin->config.bands[0].attack_len = atof(get_text());
562 plugin->send_configure_change();
566 CompressorRelease::CompressorRelease(CompressorEffect *plugin,
567 CompressorWindow *window, int x, int y)
568 : BC_TumbleTextBox(window, (float)plugin->config.bands[0].release_len,
569 (float)MIN_DECAY, (float)MAX_DECAY, x, y, xS(100))
571 this->plugin = plugin;
575 int CompressorRelease::handle_event()
577 plugin->config.bands[0].release_len = atof(get_text());
578 plugin->send_configure_change();
583 CompressorX::CompressorX(CompressorEffect *plugin,
584 CompressorWindow *window, int x, int y)
585 : BC_TumbleTextBox(window, (float)0.0,
586 plugin->config.min_db, plugin->config.max_db, x, y, xS(100))
588 this->plugin = plugin;
589 this->window = window;
593 int CompressorX::handle_event()
595 BandConfig *band_config = &plugin->config.bands[0];
596 int current_point = ((CompressorWindow*)plugin->thread->window)->canvas->current_point;
597 if( current_point < band_config->levels.total ) {
598 band_config->levels[current_point].x = atof(get_text());
599 window->canvas->update();
600 plugin->send_configure_change();
605 CompressorY::CompressorY(CompressorEffect *plugin,
606 CompressorWindow *window, int x, int y)
607 : BC_TumbleTextBox(window, (float)0.0,
608 plugin->config.min_db, plugin->config.max_db, x, y, xS(100))
610 this->plugin = plugin;
611 this->window = window;
615 int CompressorY::handle_event()
617 BandConfig *band_config = &plugin->config.bands[0];
618 int current_point = ((CompressorWindow*)plugin->thread->window)->canvas->current_point;
619 if( current_point < band_config->levels.total ) {
620 band_config->levels[current_point].y = atof(get_text());
621 window->canvas->update();
622 plugin->send_configure_change();
628 CompressorTrigger::CompressorTrigger(CompressorEffect *plugin,
629 CompressorWindow *window, int x, int y)
630 : BC_TumbleTextBox(window, (int)plugin->config.trigger,
631 MIN_TRIGGER, MAX_TRIGGER, x, y, xS(100))
633 this->plugin = plugin;
636 int CompressorTrigger::handle_event()
638 plugin->config.trigger = atol(get_text());
639 plugin->send_configure_change();
644 CompressorInput::CompressorInput(CompressorEffect *plugin, int x, int y)
645 : BC_PopupMenu(x, y, xS(100),
646 CompressorInput::value_to_text(plugin->config.input), 1)
648 this->plugin = plugin;
650 int CompressorInput::handle_event()
652 plugin->config.input = text_to_value(get_text());
653 ((CompressorWindow*)plugin->thread->window)->update();
654 plugin->send_configure_change();
658 void CompressorInput::create_objects()
660 for( int i = 0; i < 3; i++ ) {
661 add_item(new BC_MenuItem(value_to_text(i)));
665 const char* CompressorInput::value_to_text(int value)
669 case CompressorConfig::TRIGGER: return "Trigger";
670 case CompressorConfig::MAX: return "Maximum";
671 case CompressorConfig::SUM: return "Total";
677 int CompressorInput::text_to_value(char *text)
679 for( int i = 0; i < 3; i++ ) {
680 if( !strcmp(value_to_text(i), text) ) return i;
683 return CompressorConfig::TRIGGER;
686 CompressorClear::CompressorClear(CompressorEffect *plugin,
687 CompressorWindow *window, int x, int y)
688 : BC_GenericButton(x, y, _("Clear"))
690 this->plugin = plugin;
691 this->window = window;
694 int CompressorClear::handle_event()
696 BandConfig *band_config = &plugin->config.bands[0];
697 band_config->reset();
698 //plugin->config.dump();
700 plugin->send_configure_change();
704 CompressorReset::CompressorReset(CompressorEffect *plugin,
705 CompressorWindow *window, int x, int y)
706 : BC_GenericButton(x, y, _("Reset"))
708 this->plugin = plugin;
709 this->window = window;
712 int CompressorReset::handle_event()
714 plugin->config.reset_bands();
715 plugin->config.reset_base();
716 //plugin->config.dump();
718 plugin->send_configure_change();
723 CompressorMkupGain::CompressorMkupGain(CompressorEffect *plugin,
724 CompressorWindow *window, int x, int y,
725 double *output, double min, double max)
726 : BC_FPot(x, y, *output, min, max)
728 this->plugin = plugin;
729 this->window = window;
730 this->output = output;
734 int CompressorMkupGain::handle_event()
736 *output = get_value();
737 plugin->send_configure_change();
738 window->canvas->update();
743 CompressorSmooth::CompressorSmooth(CompressorEffect *plugin, int x, int y)
744 : BC_CheckBox(x, y, plugin->config.smoothing_only, _("Smooth only"))
746 this->plugin = plugin;
749 int CompressorSmooth::handle_event()
751 plugin->config.smoothing_only = get_value();
752 plugin->send_configure_change();