Merge CV, ver=5.1; ops/methods from HV, and interface from CV where possible
[goodguy/history.git] / cinelerra-5.1 / plugins / compressor / compressor.C
diff --git a/cinelerra-5.1/plugins/compressor/compressor.C b/cinelerra-5.1/plugins/compressor/compressor.C
new file mode 100644 (file)
index 0000000..c2b3dc9
--- /dev/null
@@ -0,0 +1,1425 @@
+
+/*
+ * CINELERRA
+ * Copyright (C) 2008 Adam Williams <broadcast at earthling dot net>
+ * 
+ * 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 <math.h>
+#include <string.h>
+
+
+
+
+
+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; i<canvas->get_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;
+}
+
+
+
+