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.values[i].x = DB::fromdb(band_config->levels.values[i].x);
218 levels.values[i].y = DB::fromdb(band_config->levels.values[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);
343 void CompressorConfig::interpolate(CompressorConfig &prev,
344 CompressorConfig &next,
347 int64_t current_frame)
352 CompressorWindow::CompressorWindow(CompressorEffect *plugin)
353 : PluginClientWindow(plugin, xS(650), yS(480), xS(650), yS(480), 0)
355 this->plugin = plugin;
358 CompressorWindow::~CompressorWindow()
368 void CompressorWindow::create_objects()
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);
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,
383 x += in->get_w() + margin;
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,
391 1)); // is_gain_change
392 // gain_change->update(1, 0);
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;
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;
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;
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;
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;
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;
432 add_subwindow(smooth = new CompressorSmooth(plugin, x, y));
433 y += smooth->get_h() + margin;
435 add_subwindow(title = new BC_Title(x, y, _("Output:")));
437 y_text = new CompressorY(plugin, this, x, y);
438 y_text->create_objects();
439 y += y_text->get_h() + margin;
441 add_subwindow(title = new BC_Title(x, y, _("Input:")));
443 x_text = new CompressorX(plugin, this, x, y);
444 x_text->create_objects();
445 y += x_text->get_h() + margin;
447 add_subwindow(clear = new CompressorClear(plugin, x, y));
449 y = get_h() - yS(40);
451 canvas->create_objects();
457 void CompressorWindow::update()
463 void CompressorWindow::update_meter(CompressorGainFrame *gain_frame)
465 gain_change->update(gain_frame->gain, 0);
466 in->update(gain_frame->level, 0);
469 void CompressorWindow::update_textboxes()
471 BandConfig *band_config = &plugin->config.bands[0];
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));
478 if( plugin->config.input != CompressorConfig::TRIGGER && trigger->get_enabled() )
481 if( plugin->config.input == CompressorConfig::TRIGGER && !trigger->get_enabled() )
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);
497 int CompressorWindow::resize_event(int w, int h)
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)
510 void CompressorCanvas::update_window()
512 ((CompressorWindow*)window)->update();
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))
521 // this->plugin = plugin;
522 // set_increment(0.1);
526 // int CompressorLookAhead::handle_event()
528 // plugin->config.bands[0].readahead_len = atof(get_text());
529 // plugin->send_configure_change();
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))
541 this->plugin = plugin;
546 int CompressorAttack::handle_event()
548 plugin->config.bands[0].attack_len = atof(get_text());
549 plugin->send_configure_change();
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))
558 this->plugin = plugin;
562 int CompressorRelease::handle_event()
564 plugin->config.bands[0].release_len = atof(get_text());
565 plugin->send_configure_change();
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))
575 this->plugin = plugin;
579 int CompressorX::handle_event()
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();
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))
596 this->plugin = plugin;
600 int CompressorY::handle_event()
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();
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))
618 this->plugin = plugin;
621 int CompressorTrigger::handle_event()
623 plugin->config.trigger = atol(get_text());
624 plugin->send_configure_change();
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)
633 this->plugin = plugin;
635 int CompressorInput::handle_event()
637 plugin->config.input = text_to_value(get_text());
638 ((CompressorWindow*)plugin->thread->window)->update();
639 plugin->send_configure_change();
643 void CompressorInput::create_objects()
645 for( int i = 0; i < 3; i++ ) {
646 add_item(new BC_MenuItem(value_to_text(i)));
650 const char* CompressorInput::value_to_text(int value)
654 case CompressorConfig::TRIGGER: return "Trigger";
655 case CompressorConfig::MAX: return "Maximum";
656 case CompressorConfig::SUM: return "Total";
662 int CompressorInput::text_to_value(char *text)
664 for( int i = 0; i < 3; i++ ) {
665 if( !strcmp(value_to_text(i), text) ) return i;
668 return CompressorConfig::TRIGGER;
671 CompressorClear::CompressorClear(CompressorEffect *plugin, int x, int y)
672 : BC_GenericButton(x, y, _("Clear"))
674 this->plugin = plugin;
677 int CompressorClear::handle_event()
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();
687 CompressorSmooth::CompressorSmooth(CompressorEffect *plugin, int x, int y)
688 : BC_CheckBox(x, y, plugin->config.smoothing_only, _("Smooth only"))
690 this->plugin = plugin;
693 int CompressorSmooth::handle_event()
695 plugin->config.smoothing_only = get_value();
696 plugin->send_configure_change();