X-Git-Url: https://git.cinelerra-gg.org/git/?a=blobdiff_plain;f=cinelerra-5.1%2Fplugins%2Fcompressor%2Fcompressor.C;fp=cinelerra-5.1%2Fplugins%2Fcompressor%2Fcompressor.C;h=c2b3dc90b5cc059cbbdac6a2ce5b96f418b09cc0;hb=30bdb85eb33a8ee7ba675038a86c6be59c43d7bd;hp=0000000000000000000000000000000000000000;hpb=52fcc46226f9df46f9ce9d0566dc568455a7db0b;p=goodguy%2Fhistory.git diff --git a/cinelerra-5.1/plugins/compressor/compressor.C b/cinelerra-5.1/plugins/compressor/compressor.C new file mode 100644 index 00000000..c2b3dc90 --- /dev/null +++ b/cinelerra-5.1/plugins/compressor/compressor.C @@ -0,0 +1,1425 @@ + +/* + * CINELERRA + * Copyright (C) 2008 Adam Williams + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "bcdisplayinfo.h" +#include "bcsignals.h" +#include "clip.h" +#include "compressor.h" +#include "cursors.h" +#include "bchash.h" +#include "filexml.h" +#include "language.h" +#include "samples.h" +#include "theme.h" +#include "units.h" +#include "vframe.h" + +#include +#include + + + + + +REGISTER_PLUGIN(CompressorEffect) + + + + + + +// More potential compressor algorithms: +// Use single reaction time parameter. Negative reaction time uses +// readahead. Positive reaction time uses slope. + +// Smooth input stage if readahead. +// Determine slope from current smoothed sample to every sample in readahead area. +// Once highest slope is found, count of number of samples remaining until it is +// reached. Only search after this count for the next highest slope. +// Use highest slope to determine smoothed value. + +// Smooth input stage if not readahead. +// For every sample, calculate slope needed to reach current sample from +// current smoothed value in the reaction time. If higher than current slope, +// make it the current slope and count number of samples remaining until it is +// reached. If this count is met and no higher slopes are found, base slope +// on current sample when count is met. + +// Gain stage. +// For every sample, calculate gain from smoothed input value. + + + + + +CompressorEffect::CompressorEffect(PluginServer *server) + : PluginAClient(server) +{ + reset(); + +} + +CompressorEffect::~CompressorEffect() +{ + + delete_dsp(); + levels.remove_all(); +} + +void CompressorEffect::delete_dsp() +{ + if(input_buffer) + { + for(int i = 0; i < PluginClient::total_in_buffers; i++) + delete input_buffer[i]; + delete [] input_buffer; + } + + + input_buffer = 0; + input_size = 0; + input_allocated = 0; +} + + +void CompressorEffect::reset() +{ + input_buffer = 0; + input_size = 0; + input_allocated = 0; + input_start = 0; + + next_target = 1.0; + previous_target = 1.0; + target_samples = 1; + target_current_sample = -1; + current_value = 1.0; +} + +const char* CompressorEffect::plugin_title() { return _("Compressor"); } +int CompressorEffect::is_realtime() { return 1; } +int CompressorEffect::is_multichannel() { return 1; } + + + +void CompressorEffect::read_data(KeyFrame *keyframe) +{ + FileXML input; + input.set_shared_input(keyframe->get_data(), strlen(keyframe->get_data())); + + int result = 0; + config.levels.remove_all(); + while(!result) + { + result = input.read_tag(); + + if(!result) + { + if(input.tag.title_is("COMPRESSOR")) + { + config.reaction_len = input.tag.get_property("REACTION_LEN", config.reaction_len); + config.decay_len = input.tag.get_property("DECAY_LEN", config.decay_len); + config.trigger = input.tag.get_property("TRIGGER", config.trigger); + config.smoothing_only = input.tag.get_property("SMOOTHING_ONLY", config.smoothing_only); + config.input = input.tag.get_property("INPUT", config.input); + } + else + if(input.tag.title_is("LEVEL")) + { + double x = input.tag.get_property("X", (double)0); + double y = input.tag.get_property("Y", (double)0); + compressor_point_t point = { x, y }; + + config.levels.append(point); + } + } + } +} + +void CompressorEffect::save_data(KeyFrame *keyframe) +{ + FileXML output; + output.set_shared_output(keyframe->get_data(), MESSAGESIZE); + + output.tag.set_title("COMPRESSOR"); + output.tag.set_property("TRIGGER", config.trigger); + output.tag.set_property("REACTION_LEN", config.reaction_len); + output.tag.set_property("DECAY_LEN", config.decay_len); + output.tag.set_property("SMOOTHING_ONLY", config.smoothing_only); + output.tag.set_property("INPUT", config.input); + output.append_tag(); + output.tag.set_title("/COMPRESSOR"); + output.append_tag(); + output.append_newline(); + + + for(int i = 0; i < config.levels.total; i++) + { + output.tag.set_title("LEVEL"); + output.tag.set_property("X", config.levels.values[i].x); + output.tag.set_property("Y", config.levels.values[i].y); + + output.append_tag(); + output.tag.set_title("/LEVEL"); + output.append_tag(); + output.append_newline(); + } + + output.terminate_string(); +} + + +void CompressorEffect::update_gui() +{ + if(thread) + { + if(load_configuration()) + { + thread->window->lock_window("CompressorEffect::update_gui"); + ((CompressorWindow*)thread->window)->update(); + thread->window->unlock_window(); + } + } +} + + +LOAD_CONFIGURATION_MACRO(CompressorEffect, CompressorConfig) +NEW_WINDOW_MACRO(CompressorEffect, CompressorWindow) + + + + +int CompressorEffect::process_buffer(int64_t size, + Samples **buffer, + int64_t start_position, + int sample_rate) +{ + load_configuration(); + +// Calculate linear transfer from db + levels.remove_all(); + for(int i = 0; i < config.levels.total; i++) + { + levels.append(); + levels.values[i].x = DB::fromdb(config.levels.values[i].x); + levels.values[i].y = DB::fromdb(config.levels.values[i].y); + } + min_x = DB::fromdb(config.min_db); + min_y = DB::fromdb(config.min_db); + max_x = 1.0; + max_y = 1.0; + + + int reaction_samples = (int)(config.reaction_len * sample_rate + 0.5); + int decay_samples = (int)(config.decay_len * sample_rate + 0.5); + int trigger = CLIP(config.trigger, 0, PluginAClient::total_in_buffers - 1); + + CLAMP(reaction_samples, -1000000, 1000000); + CLAMP(decay_samples, reaction_samples, 1000000); + CLAMP(decay_samples, 1, 1000000); + if(labs(reaction_samples) < 1) reaction_samples = 1; + if(labs(decay_samples) < 1) decay_samples = 1; + + int total_buffers = get_total_buffers(); + if(reaction_samples >= 0) + { + if(target_current_sample < 0) target_current_sample = reaction_samples; + for(int i = 0; i < total_buffers; i++) + { + read_samples(buffer[i], + i, + sample_rate, + start_position, + size); + } + + double current_slope = (next_target - previous_target) / + reaction_samples; + double *trigger_buffer = buffer[trigger]->get_data(); + for(int i = 0; i < size; i++) + { +// Get slope required to reach current sample from smoothed sample over reaction +// length. + double sample = 0.; + switch(config.input) + { + case CompressorConfig::MAX: + { + double max = 0; + for(int j = 0; j < total_buffers; j++) + { + sample = fabs(buffer[j]->get_data()[i]); + if(sample > max) max = sample; + } + sample = max; + break; + } + + case CompressorConfig::TRIGGER: + sample = fabs(trigger_buffer[i]); + break; + + case CompressorConfig::SUM: + { + double max = 0; + for(int j = 0; j < total_buffers; j++) + { + sample = fabs(buffer[j]->get_data()[i]); + max += sample; + } + sample = max; + break; + } + } + + double new_slope = (sample - current_value) / + reaction_samples; + +// Slope greater than current slope + if(new_slope >= current_slope && + (current_slope >= 0 || + new_slope >= 0)) + { + next_target = sample; + previous_target = current_value; + target_current_sample = 0; + target_samples = reaction_samples; + current_slope = new_slope; + } + else + if(sample > next_target && current_slope < 0) + { + next_target = sample; + previous_target = current_value; + target_current_sample = 0; + target_samples = decay_samples; + current_slope = (sample - current_value) / decay_samples; + } +// Current smoothed sample came up without finding higher slope + if(target_current_sample >= target_samples) + { + next_target = sample; + previous_target = current_value; + target_current_sample = 0; + target_samples = decay_samples; + current_slope = (sample - current_value) / decay_samples; + } + +// Update current value and store gain + current_value = (next_target * target_current_sample + + previous_target * (target_samples - target_current_sample)) / + target_samples; + + target_current_sample++; + + if(config.smoothing_only) + { + for(int j = 0; j < total_buffers; j++) + buffer[j]->get_data()[i] = current_value; + } + else + { + double gain = calculate_gain(current_value); + for(int j = 0; j < total_buffers; j++) + { + buffer[j]->get_data()[i] *= gain; + } + } + } + } + else + { + if(target_current_sample < 0) target_current_sample = target_samples; + int64_t preview_samples = -reaction_samples; + +// Start of new buffer is outside the current buffer. Start buffer over. + if(start_position < input_start || + start_position >= input_start + input_size) + { + input_size = 0; + input_start = start_position; + } + else +// Shift current buffer so the buffer starts on start_position + if(start_position > input_start && + start_position < input_start + input_size) + { + if(input_buffer) + { + int len = input_start + input_size - start_position; + for(int i = 0; i < total_buffers; i++) + { + memcpy(input_buffer[i]->get_data(), + input_buffer[i]->get_data() + (start_position - input_start), + len * sizeof(double)); + } + input_size = len; + input_start = start_position; + } + } + +// Expand buffer to handle preview size + if(size + preview_samples > input_allocated) + { + Samples **new_input_buffer = new Samples*[total_buffers]; + for(int i = 0; i < total_buffers; i++) + { + new_input_buffer[i] = new Samples(size + preview_samples); + if(input_buffer) + { + memcpy(new_input_buffer[i]->get_data(), + input_buffer[i]->get_data(), + input_size * sizeof(double)); + delete input_buffer[i]; + } + } + if(input_buffer) delete [] input_buffer; + + input_allocated = size + preview_samples; + input_buffer = new_input_buffer; + } + +// Append data to input buffer to construct readahead area. +#define MAX_FRAGMENT_SIZE 131072 + while(input_size < size + preview_samples) + { + int fragment_size = MAX_FRAGMENT_SIZE; + if(fragment_size + input_size > size + preview_samples) + fragment_size = size + preview_samples - input_size; + for(int i = 0; i < total_buffers; i++) + { + input_buffer[i]->set_offset(input_size); +//printf("CompressorEffect::process_buffer %d %p %d\n", __LINE__, input_buffer[i], input_size); + read_samples(input_buffer[i], + i, + sample_rate, + input_start + input_size, + fragment_size); + input_buffer[i]->set_offset(0); + } + input_size += fragment_size; + } + + + double current_slope = (next_target - previous_target) / + target_samples; + double *trigger_buffer = input_buffer[trigger]->get_data(); + for(int i = 0; i < size; i++) + { +// Get slope from current sample to every sample in preview_samples. +// Take highest one or first one after target_samples are up. + +// For optimization, calculate the first slope we really need. +// Assume every slope up to the end of preview_samples has been calculated and +// found <= to current slope. + int first_slope = preview_samples - 1; +// Need new slope immediately + if(target_current_sample >= target_samples) + first_slope = 1; + for(int j = first_slope; + j < preview_samples; + j++) + { + double sample = 0.; + switch(config.input) + { + case CompressorConfig::MAX: + { + double max = 0; + for(int k = 0; k < total_buffers; k++) + { + sample = fabs(input_buffer[k]->get_data()[i + j]); + if(sample > max) max = sample; + } + sample = max; + break; + } + + case CompressorConfig::TRIGGER: + sample = fabs(trigger_buffer[i + j]); + break; + + case CompressorConfig::SUM: + { + double max = 0; + for(int k = 0; k < total_buffers; k++) + { + sample = fabs(input_buffer[k]->get_data()[i + j]); + max += sample; + } + sample = max; + break; + } + } + + + + + + + double new_slope = (sample - current_value) / + j; +// Got equal or higher slope + if(new_slope >= current_slope && + (current_slope >= 0 || + new_slope >= 0)) + { + target_current_sample = 0; + target_samples = j; + current_slope = new_slope; + next_target = sample; + previous_target = current_value; + } + else + if(sample > next_target && current_slope < 0) + { + target_current_sample = 0; + target_samples = decay_samples; + current_slope = (sample - current_value) / + decay_samples; + next_target = sample; + previous_target = current_value; + } + +// Hit end of current slope range without finding higher slope + if(target_current_sample >= target_samples) + { + target_current_sample = 0; + target_samples = decay_samples; + current_slope = (sample - current_value) / decay_samples; + next_target = sample; + previous_target = current_value; + } + } + +// Update current value and multiply gain + current_value = (next_target * target_current_sample + + previous_target * (target_samples - target_current_sample)) / + target_samples; +//buffer[0][i] = current_value; + target_current_sample++; + + if(config.smoothing_only) + { + for(int j = 0; j < total_buffers; j++) + { + buffer[j]->get_data()[i] = current_value; + } + } + else + { + double gain = calculate_gain(current_value); + for(int j = 0; j < total_buffers; j++) + { + buffer[j]->get_data()[i] = input_buffer[j]->get_data()[i] * gain; + } + } + } + + + + } + + + + + + return 0; +} + +double CompressorEffect::calculate_output(double x) +{ + if(x > 0.999) return 1.0; + + for(int i = levels.total - 1; i >= 0; i--) + { + if(levels.values[i].x <= x) + { + if(i < levels.total - 1) + { + return levels.values[i].y + + (x - levels.values[i].x) * + (levels.values[i + 1].y - levels.values[i].y) / + (levels.values[i + 1].x - levels.values[i].x); + } + else + { + return levels.values[i].y + + (x - levels.values[i].x) * + (max_y - levels.values[i].y) / + (max_x - levels.values[i].x); + } + } + } + + if(levels.total) + { + return min_y + + (x - min_x) * + (levels.values[0].y - min_y) / + (levels.values[0].x - min_x); + } + else + return x; +} + + +double CompressorEffect::calculate_gain(double input) +{ +// double x_db = DB::todb(input); +// double y_db = config.calculate_db(x_db); +// double y_linear = DB::fromdb(y_db); + double y_linear = calculate_output(input); + double gain; + if(input != 0) + gain = y_linear / input; + else + gain = 100000; + return gain; +} + + + + + + + + + + +CompressorConfig::CompressorConfig() +{ + reaction_len = 1.0; + min_db = -80.0; + min_x = min_db; + min_y = min_db; + max_x = 0; + max_y = 0; + trigger = 0; + input = CompressorConfig::TRIGGER; + smoothing_only = 0; + decay_len = 1.0; +} + +void CompressorConfig::copy_from(CompressorConfig &that) +{ + this->reaction_len = that.reaction_len; + this->decay_len = that.decay_len; + this->min_db = that.min_db; + this->min_x = that.min_x; + this->min_y = that.min_y; + this->max_x = that.max_x; + this->max_y = that.max_y; + this->trigger = that.trigger; + this->input = that.input; + this->smoothing_only = that.smoothing_only; + levels.remove_all(); + for(int i = 0; i < that.levels.total; i++) + this->levels.append(that.levels.values[i]); +} + +int CompressorConfig::equivalent(CompressorConfig &that) +{ + if(!EQUIV(this->reaction_len, that.reaction_len) || + !EQUIV(this->decay_len, that.decay_len) || + this->trigger != that.trigger || + this->input != that.input || + this->smoothing_only != that.smoothing_only) + return 0; + if(this->levels.total != that.levels.total) return 0; + for(int i = 0; + i < this->levels.total && i < that.levels.total; + i++) + { + compressor_point_t *this_level = &this->levels.values[i]; + compressor_point_t *that_level = &that.levels.values[i]; + if(!EQUIV(this_level->x, that_level->x) || + !EQUIV(this_level->y, that_level->y)) + return 0; + } + return 1; +} + +void CompressorConfig::interpolate(CompressorConfig &prev, + CompressorConfig &next, + int64_t prev_frame, + int64_t next_frame, + int64_t current_frame) +{ + copy_from(prev); +} + +int CompressorConfig::total_points() +{ + if(!levels.total) + return 1; + else + return levels.total; +} + +void CompressorConfig::dump() +{ + printf("CompressorConfig::dump\n"); + for(int i = 0; i < levels.total; i++) + { + printf(" %f %f\n", levels.values[i].x, levels.values[i].y); + } +} + + +double CompressorConfig::get_y(int number) +{ + if(!levels.total) + return 1.0; + else + if(number >= levels.total) + return levels.values[levels.total - 1].y; + else + return levels.values[number].y; +} + +double CompressorConfig::get_x(int number) +{ + if(!levels.total) + return 0.0; + else + if(number >= levels.total) + return levels.values[levels.total - 1].x; + else + return levels.values[number].x; +} + +double CompressorConfig::calculate_db(double x) +{ + if(x > -0.001) return 0.0; + + for(int i = levels.total - 1; i >= 0; i--) + { + if(levels.values[i].x <= x) + { + if(i < levels.total - 1) + { + return levels.values[i].y + + (x - levels.values[i].x) * + (levels.values[i + 1].y - levels.values[i].y) / + (levels.values[i + 1].x - levels.values[i].x); + } + else + { + return levels.values[i].y + + (x - levels.values[i].x) * + (max_y - levels.values[i].y) / + (max_x - levels.values[i].x); + } + } + } + + if(levels.total) + { + return min_y + + (x - min_x) * + (levels.values[0].y - min_y) / + (levels.values[0].x - min_x); + } + else + return x; +} + + +int CompressorConfig::set_point(double x, double y) +{ + for(int i = levels.total - 1; i >= 0; i--) + { + if(levels.values[i].x < x) + { + levels.append(); + i++; + for(int j = levels.total - 2; j >= i; j--) + { + levels.values[j + 1] = levels.values[j]; + } + levels.values[i].x = x; + levels.values[i].y = y; + + return i; + } + } + + levels.append(); + for(int j = levels.total - 2; j >= 0; j--) + { + levels.values[j + 1] = levels.values[j]; + } + levels.values[0].x = x; + levels.values[0].y = y; + return 0; +} + +void CompressorConfig::remove_point(int number) +{ + for(int j = number; j < levels.total - 1; j++) + { + levels.values[j] = levels.values[j + 1]; + } + levels.remove(); +} + +void CompressorConfig::optimize() +{ + int done = 0; + + while(!done) + { + done = 1; + + + for(int i = 0; i < levels.total - 1; i++) + { + if(levels.values[i].x >= levels.values[i + 1].x) + { + done = 0; + for(int j = i + 1; j < levels.total - 1; j++) + { + levels.values[j] = levels.values[j + 1]; + } + levels.remove(); + } + } + + } +} + + + + + + + + + + + + + + + + + + + + + + + + + + + +CompressorWindow::CompressorWindow(CompressorEffect *plugin) + : PluginClientWindow(plugin, + 650, + 480, + 650, + 480, + 0) +{ + this->plugin = plugin; +} + +void CompressorWindow::create_objects() +{ + int x = 35, y = 10; + int control_margin = 130; + + add_subwindow(canvas = new CompressorCanvas(plugin, + x, + y, + get_w() - x - control_margin - 10, + get_h() - y - 70)); + canvas->set_cursor(CROSS_CURSOR, 0, 0); + x = get_w() - control_margin; + add_subwindow(new BC_Title(x, y, _("Reaction secs:"))); + y += 20; + add_subwindow(reaction = new CompressorReaction(plugin, x, y)); + y += 30; + add_subwindow(new BC_Title(x, y, _("Decay secs:"))); + y += 20; + add_subwindow(decay = new CompressorDecay(plugin, x, y)); + y += 30; + add_subwindow(new BC_Title(x, y, _("Trigger Type:"))); + y += 20; + add_subwindow(input = new CompressorInput(plugin, x, y)); + input->create_objects(); + y += 30; + add_subwindow(new BC_Title(x, y, _("Trigger:"))); + y += 20; + add_subwindow(trigger = new CompressorTrigger(plugin, x, y)); + if(plugin->config.input != CompressorConfig::TRIGGER) trigger->disable(); + y += 30; + add_subwindow(smooth = new CompressorSmooth(plugin, x, y)); + y += 60; + add_subwindow(clear = new CompressorClear(plugin, x, y)); + x = 10; + y = get_h() - 40; + add_subwindow(new BC_Title(x, y, _("Point:"))); + x += 50; + add_subwindow(x_text = new CompressorX(plugin, x, y)); + x += 110; + add_subwindow(new BC_Title(x, y, _("x"))); + x += 20; + add_subwindow(y_text = new CompressorY(plugin, x, y)); + draw_scales(); + + update_canvas(); + show_window(); +} + +void CompressorWindow::draw_scales() +{ + draw_3d_border(canvas->get_x() - 2, + canvas->get_y() - 2, + canvas->get_w() + 4, + canvas->get_h() + 4, + get_bg_color(), + BLACK, + MDGREY, + get_bg_color()); + + + set_font(SMALLFONT); + set_color(get_resources()->default_text_color); + +#define DIVISIONS 8 + for(int i = 0; i <= DIVISIONS; i++) + { + int y = canvas->get_y() + 10 + canvas->get_h() / DIVISIONS * i; + int x = canvas->get_x() - 30; + char string[BCTEXTLEN]; + + sprintf(string, "%.0f", (float)i / DIVISIONS * plugin->config.min_db); + draw_text(x, y, string); + + int y1 = canvas->get_y() + canvas->get_h() / DIVISIONS * i; + int y2 = canvas->get_y() + canvas->get_h() / DIVISIONS * (i + 1); + for(int j = 0; j < 10; j++) + { + y = y1 + (y2 - y1) * j / 10; + if(j == 0) + { + draw_line(canvas->get_x() - 10, y, canvas->get_x(), y); + } + else + if(i < DIVISIONS) + { + draw_line(canvas->get_x() - 5, y, canvas->get_x(), y); + } + } + } + + + for(int i = 0; i <= DIVISIONS; i++) + { + int y = canvas->get_h() + 30; + int x = canvas->get_x() + (canvas->get_w() - 10) / DIVISIONS * i; + char string[BCTEXTLEN]; + + sprintf(string, "%.0f", (1.0 - (float)i / DIVISIONS) * plugin->config.min_db); + draw_text(x, y, string); + + int x1 = canvas->get_x() + canvas->get_w() / DIVISIONS * i; + int x2 = canvas->get_x() + canvas->get_w() / DIVISIONS * (i + 1); + for(int j = 0; j < 10; j++) + { + x = x1 + (x2 - x1) * j / 10; + if(j == 0) + { + draw_line(x, canvas->get_y() + canvas->get_h(), x, canvas->get_y() + canvas->get_h() + 10); + } + else + if(i < DIVISIONS) + { + draw_line(x, canvas->get_y() + canvas->get_h(), x, canvas->get_y() + canvas->get_h() + 5); + } + } + } + + + + flash(); +} + +void CompressorWindow::update() +{ + update_textboxes(); + update_canvas(); +} + +void CompressorWindow::update_textboxes() +{ + if(atol(trigger->get_text()) != plugin->config.trigger) + trigger->update((int64_t)plugin->config.trigger); + if(strcmp(input->get_text(), CompressorInput::value_to_text(plugin->config.input))) + input->set_text(CompressorInput::value_to_text(plugin->config.input)); + + if(plugin->config.input != CompressorConfig::TRIGGER && trigger->get_enabled()) + trigger->disable(); + else + if(plugin->config.input == CompressorConfig::TRIGGER && !trigger->get_enabled()) + trigger->enable(); + + if(!EQUIV(atof(reaction->get_text()), plugin->config.reaction_len)) + reaction->update((float)plugin->config.reaction_len); + if(!EQUIV(atof(decay->get_text()), plugin->config.decay_len)) + decay->update((float)plugin->config.decay_len); + smooth->update(plugin->config.smoothing_only); + if(canvas->current_operation == CompressorCanvas::DRAG) + { + x_text->update((float)plugin->config.levels.values[canvas->current_point].x); + y_text->update((float)plugin->config.levels.values[canvas->current_point].y); + } +} + +#define POINT_W 10 +void CompressorWindow::update_canvas() +{ + canvas->clear_box(0, 0, canvas->get_w(), canvas->get_h()); + canvas->set_line_dashes(1); + canvas->set_color(GREEN); + + for(int i = 1; i < DIVISIONS; i++) + { + int y = canvas->get_h() * i / DIVISIONS; + canvas->draw_line(0, y, canvas->get_w(), y); + + int x = canvas->get_w() * i / DIVISIONS; + canvas->draw_line(x, 0, x, canvas->get_h()); + } + canvas->set_line_dashes(0); + + + canvas->set_font(MEDIUMFONT); + canvas->draw_text(plugin->get_theme()->widget_border, + canvas->get_h() / 2, + _("Output")); + canvas->draw_text(canvas->get_w() / 2 - canvas->get_text_width(MEDIUMFONT, _("Input")) / 2, + canvas->get_h() - plugin->get_theme()->widget_border, + _("Input")); + + + canvas->set_color(WHITE); + canvas->set_line_width(2); + + double x_db = plugin->config.min_db; + double y_db = plugin->config.calculate_db(x_db); + int y1 = (int)(y_db / plugin->config.min_db * canvas->get_h()); + + for(int i=1; iget_w(); i++) + { + x_db = (1. - (double)i / canvas->get_w()) * plugin->config.min_db; + y_db = plugin->config.calculate_db(x_db); + int y2 = (int)(y_db / plugin->config.min_db * canvas->get_h()); + canvas->draw_line(i-1, y1, i, y2); + y1 = y2; + } + canvas->set_line_width(1); + + int total = plugin->config.levels.total ? plugin->config.levels.total : 1; + for(int i=0; i < total; i++) + { + x_db = plugin->config.get_x(i); + y_db = plugin->config.get_y(i); + + int x = (int)((1. - x_db / plugin->config.min_db) * canvas->get_w()); + int y = (int)(y_db / plugin->config.min_db * canvas->get_h()); + + canvas->draw_box(x - POINT_W / 2, y - POINT_W / 2, POINT_W, POINT_W); + } + + canvas->flash(); +} + +int CompressorWindow::resize_event(int w, int h) +{ + return 1; +} + + + + + + + + +CompressorCanvas::CompressorCanvas(CompressorEffect *plugin, int x, int y, int w, int h) + : BC_SubWindow(x, y, w, h, BLACK) +{ + this->plugin = plugin; + current_operation = NONE; +} + +int CompressorCanvas::button_press_event() +{ +// Check existing points + if(is_event_win() && cursor_inside()) + { + for(int i = 0; i < plugin->config.levels.total; i++) + { + double x_db = plugin->config.get_x(i); + double y_db = plugin->config.get_y(i); + + int x = (int)(((double)1 - x_db / plugin->config.min_db) * get_w()); + int y = (int)(y_db / plugin->config.min_db * get_h()); + + if(get_cursor_x() < x + POINT_W / 2 && get_cursor_x() >= x - POINT_W / 2 && + get_cursor_y() < y + POINT_W / 2 && get_cursor_y() >= y - POINT_W / 2) + { + current_operation = DRAG; + current_point = i; + return 1; + } + } + + + +// Create new point + double x_db = (double)(1 - (double)get_cursor_x() / get_w()) * plugin->config.min_db; + double y_db = (double)get_cursor_y() / get_h() * plugin->config.min_db; + + current_point = plugin->config.set_point(x_db, y_db); + current_operation = DRAG; + ((CompressorWindow*)plugin->thread->window)->update(); + plugin->send_configure_change(); + return 1; + } + return 0; +//plugin->config.dump(); +} + +int CompressorCanvas::button_release_event() +{ + if(current_operation == DRAG) + { + if(current_point > 0) + { + if(plugin->config.levels.values[current_point].x < + plugin->config.levels.values[current_point - 1].x) + plugin->config.remove_point(current_point); + } + + if(current_point < plugin->config.levels.total - 1) + { + if(plugin->config.levels.values[current_point].x >= + plugin->config.levels.values[current_point + 1].x) + plugin->config.remove_point(current_point); + } + + ((CompressorWindow*)plugin->thread->window)->update(); + plugin->send_configure_change(); + current_operation = NONE; + return 1; + } + + return 0; +} + +int CompressorCanvas::cursor_motion_event() +{ + if(current_operation == DRAG) + { + int x = get_cursor_x(); + int y = get_cursor_y(); + CLAMP(x, 0, get_w()); + CLAMP(y, 0, get_h()); + double x_db = (double)(1 - (double)x / get_w()) * plugin->config.min_db; + double y_db = (double)y / get_h() * plugin->config.min_db; + plugin->config.levels.values[current_point].x = x_db; + plugin->config.levels.values[current_point].y = y_db; + ((CompressorWindow*)plugin->thread->window)->update(); + plugin->send_configure_change(); + return 1; +//plugin->config.dump(); + } + else +// Change cursor over points + if(is_event_win() && cursor_inside()) + { + int new_cursor = CROSS_CURSOR; + + for(int i = 0; i < plugin->config.levels.total; i++) + { + double x_db = plugin->config.get_x(i); + double y_db = plugin->config.get_y(i); + + int x = (int)(((double)1 - x_db / plugin->config.min_db) * get_w()); + int y = (int)(y_db / plugin->config.min_db * get_h()); + + if(get_cursor_x() < x + POINT_W / 2 && get_cursor_x() >= x - POINT_W / 2 && + get_cursor_y() < y + POINT_W / 2 && get_cursor_y() >= y - POINT_W / 2) + { + new_cursor = UPRIGHT_ARROW_CURSOR; + break; + } + } + + + if(new_cursor != get_cursor()) + { + set_cursor(new_cursor, 0, 1); + } + } + return 0; +} + + + + + +CompressorReaction::CompressorReaction(CompressorEffect *plugin, int x, int y) + : BC_TextBox(x, y, 100, 1, (float)plugin->config.reaction_len) +{ + this->plugin = plugin; +} + +int CompressorReaction::handle_event() +{ + plugin->config.reaction_len = atof(get_text()); + plugin->send_configure_change(); + return 1; +} + +int CompressorReaction::button_press_event() +{ + if(is_event_win()) + { + if(get_buttonpress() < 4) return BC_TextBox::button_press_event(); + if(get_buttonpress() == 4) + { + plugin->config.reaction_len += 0.1; + } + else + if(get_buttonpress() == 5) + { + plugin->config.reaction_len -= 0.1; + } + update((float)plugin->config.reaction_len); + plugin->send_configure_change(); + return 1; + } + return 0; +} + +CompressorDecay::CompressorDecay(CompressorEffect *plugin, int x, int y) + : BC_TextBox(x, y, 100, 1, (float)plugin->config.decay_len) +{ + this->plugin = plugin; +} +int CompressorDecay::handle_event() +{ + plugin->config.decay_len = atof(get_text()); + plugin->send_configure_change(); + return 1; +} + +int CompressorDecay::button_press_event() +{ + if(is_event_win()) + { + if(get_buttonpress() < 4) return BC_TextBox::button_press_event(); + if(get_buttonpress() == 4) + { + plugin->config.decay_len += 0.1; + } + else + if(get_buttonpress() == 5) + { + plugin->config.decay_len -= 0.1; + } + update((float)plugin->config.decay_len); + plugin->send_configure_change(); + return 1; + } + return 0; +} + + + +CompressorX::CompressorX(CompressorEffect *plugin, int x, int y) + : BC_TextBox(x, y, 100, 1, "") +{ + this->plugin = plugin; +} +int CompressorX::handle_event() +{ + int current_point = ((CompressorWindow*)plugin->thread->window)->canvas->current_point; + if(current_point < plugin->config.levels.total) + { + plugin->config.levels.values[current_point].x = atof(get_text()); + ((CompressorWindow*)plugin->thread->window)->update_canvas(); + plugin->send_configure_change(); + } + return 1; +} + + + +CompressorY::CompressorY(CompressorEffect *plugin, int x, int y) + : BC_TextBox(x, y, 100, 1, "") +{ + this->plugin = plugin; +} +int CompressorY::handle_event() +{ + int current_point = ((CompressorWindow*)plugin->thread->window)->canvas->current_point; + if(current_point < plugin->config.levels.total) + { + plugin->config.levels.values[current_point].y = atof(get_text()); + ((CompressorWindow*)plugin->thread->window)->update_canvas(); + plugin->send_configure_change(); + } + return 1; +} + + + + + +CompressorTrigger::CompressorTrigger(CompressorEffect *plugin, int x, int y) + : BC_TextBox(x, y, (int64_t)100, (int64_t)1, (int64_t)plugin->config.trigger) +{ + this->plugin = plugin; +} +int CompressorTrigger::handle_event() +{ + plugin->config.trigger = atol(get_text()); + plugin->send_configure_change(); + return 1; +} + +int CompressorTrigger::button_press_event() +{ + if(is_event_win()) + { + if(get_buttonpress() < 4) return BC_TextBox::button_press_event(); + if(get_buttonpress() == 4) + { + plugin->config.trigger++; + } + else + if(get_buttonpress() == 5) + { + plugin->config.trigger--; + } + update((int64_t)plugin->config.trigger); + plugin->send_configure_change(); + return 1; + } + return 0; +} + + + + + +CompressorInput::CompressorInput(CompressorEffect *plugin, int x, int y) + : BC_PopupMenu(x, + y, + 100, + CompressorInput::value_to_text(plugin->config.input), + 1) +{ + this->plugin = plugin; +} +int CompressorInput::handle_event() +{ + plugin->config.input = text_to_value(get_text()); + ((CompressorWindow*)plugin->thread->window)->update(); + plugin->send_configure_change(); + return 1; +} + +void CompressorInput::create_objects() +{ + for(int i = 0; i < 3; i++) + { + add_item(new BC_MenuItem(value_to_text(i))); + } +} + +const char* CompressorInput::value_to_text(int value) +{ + switch(value) + { + case CompressorConfig::TRIGGER: return _("Trigger"); + case CompressorConfig::MAX: return _("Maximum"); + case CompressorConfig::SUM: return _("Total"); + } + + return _("Trigger"); +} + +int CompressorInput::text_to_value(char *text) +{ + for(int i = 0; i < 3; i++) + { + if(!strcmp(value_to_text(i), text)) return i; + } + + return CompressorConfig::TRIGGER; +} + + + + + + +CompressorClear::CompressorClear(CompressorEffect *plugin, int x, int y) + : BC_GenericButton(x, y, _("Clear")) +{ + this->plugin = plugin; +} + +int CompressorClear::handle_event() +{ + plugin->config.levels.remove_all(); +//plugin->config.dump(); + ((CompressorWindow*)plugin->thread->window)->update(); + plugin->send_configure_change(); + return 1; +} + + + +CompressorSmooth::CompressorSmooth(CompressorEffect *plugin, int x, int y) + : BC_CheckBox(x, y, plugin->config.smoothing_only, _("Smooth only")) +{ + this->plugin = plugin; +} + +int CompressorSmooth::handle_event() +{ + plugin->config.smoothing_only = get_value(); + plugin->send_configure_change(); + return 1; +} + + + +