new/reworked audio plugins ported from hv72 compressor/multi/reverb, glyph workaround...
[goodguy/cinelerra.git] / cinelerra-5.1 / plugins / compressor / compressor.C
index b167c6ee3dfa0d6b64a5212366b183e0c9d3f4d8..956d2ae7279bc41beb3b9723f0bf52f61e685aa4 100644 (file)
@@ -1,7 +1,6 @@
-
 /*
  * CINELERRA
- * Copyright (C) 2008 Adam Williams <broadcast at earthling dot net>
+ * Copyright (C) 2008-2019 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
  */
 
 #include "bcdisplayinfo.h"
+#include "bchash.h"
 #include "bcsignals.h"
+#include "asset.h"
 #include "clip.h"
 #include "compressor.h"
 #include "cursors.h"
-#include "bchash.h"
+#include "edl.h"
+#include "edlsession.h"
 #include "filexml.h"
 #include "language.h"
 #include "samples.h"
 #include "theme.h"
+#include "tracking.inc"
+#include "transportque.inc"
 #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.
+// Use single readahead time parameter.  Negative readahead time uses
+// readahead.  Positive readahead time uses slope.
 
 // Smooth input stage if readahead.
 // Determine slope from current smoothed sample to every sample in readahead area.
@@ -58,7 +53,7 @@ REGISTER_PLUGIN(CompressorEffect)
 
 // 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,
+// current smoothed value in the readahead 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.
@@ -66,123 +61,97 @@ REGISTER_PLUGIN(CompressorEffect)
 // Gain stage.
 // For every sample, calculate gain from smoothed input value.
 
-
-
-
-
 CompressorEffect::CompressorEffect(PluginServer *server)
  : PluginAClient(server)
 {
-       reset();
+       input_buffer = 0;
+       input_size = 0;
+       input_allocated = 0;
+       input_start = 0;
 
+       need_reconfigure = 1;
+       engine = 0;
 }
 
 CompressorEffect::~CompressorEffect()
 {
-
-       delete_dsp();
-       levels.remove_all();
-}
-
-void CompressorEffect::delete_dsp()
-{
-       if(input_buffer)
-       {
-               for(int i = 0; i < PluginClient::total_in_buffers; i++)
+       if( input_buffer ) {
+               for( int i=0; i<PluginClient::total_in_buffers; ++i )
                        delete input_buffer[i];
-               delete [] input_buffer;
+               delete [] input_buffer;  input_buffer = 0;
        }
-
-
-       input_buffer = 0;
        input_size = 0;
        input_allocated = 0;
+       levels.remove_all();
+       delete engine;
 }
 
-
-void CompressorEffect::reset()
+void CompressorEffect::allocate_input(int size)
 {
-       input_buffer = 0;
-       input_size = 0;
-       input_allocated = 0;
-       input_start = 0;
+       int channels = PluginClient::total_in_buffers;
+       if( size > input_allocated ) {
+               Samples **new_input_buffer = new Samples*[channels];
+               for( int i=0; i<channels; ++i ) {
+                       new_input_buffer[i] = new Samples(size);
+                       if( !input_buffer ) continue;
+                       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;
 
-       next_target = 1.0;
-       previous_target = 1.0;
-       target_samples = 1;
-       target_current_sample = -1;
-       current_value = 1.0;
+               input_allocated = size;
+               input_buffer = new_input_buffer;
+       }
 }
 
 const char* CompressorEffect::plugin_title() { return N_("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->xbuf);
+       BandConfig *band_config = &config.bands[0];
+        input.set_shared_input(keyframe->xbuf);
 
        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);
-                       }
+       while( !(result=input.read_tag()) ) {
+               if( input.tag.title_is("COMPRESSOR") ) {
+//                     band_config->readahead_len = input.tag.get_property("READAHEAD_LEN", band_config->readahead_len);
+                       band_config->attack_len = input.tag.get_property("ATTACK_LEN", band_config->attack_len);
+                       band_config->release_len = input.tag.get_property("RELEASE_LEN", band_config->release_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("COMPRESSORBAND") ) {
+                       band_config->read_data(&input, 0);
                }
        }
+       config.boundaries();
 }
 
 void CompressorEffect::save_data(KeyFrame *keyframe)
 {
        FileXML output;
+       BandConfig *band_config = &config.bands[0];
        output.set_shared_output(keyframe->xbuf);
 
        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.tag.set_property("ATTACK_LEN", band_config->attack_len);
+       output.tag.set_property("RELEASE_LEN", band_config->release_len);
        output.append_tag();
+       output.append_newline();
+
+       band_config->save_data(&output, 0, 0);
        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();
 }
 
@@ -190,469 +159,184 @@ void CompressorEffect::save_data(KeyFrame *keyframe)
 void CompressorEffect::update_gui()
 {
        if( !thread ) return;
-       CompressorWindow *window = (CompressorWindow*)thread->window;
-// load_configuration,read_data deletes levels
-       window->lock_window("CompressorEffect::update_gui");
-       if( load_configuration() )
+// user can't change levels when loading configuration
+       thread->window->lock_window("CompressorEffect::update_gui");
+       CompressorWindow *window = (CompressorWindow *)thread->window;
+// Can't update points if the user is editing
+       int reconfigured = window->canvas->is_dragging() ? 0 :
+               load_configuration();
+       if( reconfigured )
                window->update();
+// delete frames up to current tracking position
+       int dir = get_tracking_direction() == PLAY_FORWARD ? 1 : -1;
+       double tracking_position = get_tracking_position();
+       CompressorGainFrame *gain_frame = (CompressorGainFrame *)
+               get_gui_frame(tracking_position, dir);
+       if( gain_frame ) {
+               window->update_meter(gain_frame);
+               delete gain_frame;
+       }
        window->unlock_window();
 }
 
+void CompressorEffect::render_stop()
+{
+       if( !thread ) return;
+       thread->window->lock_window("CompressorEffect::render_stop");
+       CompressorWindow *window = (CompressorWindow *)thread->window;
+       window->in->reset();
+       window->gain_change->update(1, 0);
+       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)
+int CompressorEffect::process_buffer(int64_t size, Samples **buffer,
+               int64_t start_position, int sample_rate)
 {
+       int channels = PluginClient::total_in_buffers;
+       BandConfig *band_config = &config.bands[0];
+       int sign = get_direction() == PLAY_REVERSE ? -1 : 1;
        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);
+// restart after seeking
+       if( last_position != start_position ) {
+               last_position = start_position;
+               if( engine )
+                       engine->reset();
+               input_size = 0;
+               input_start = start_position;
        }
-       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;
-                               }
-                       }
+// Calculate linear transfer from db
+       int nbands = band_config->levels.size();
+       while( levels.size() < nbands ) levels.append();
 
-                       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;
-                       }
+       for( int i=0; i<band_config->levels.total; ++i ) {
+               levels.values[i].x = DB::fromdb(band_config->levels.values[i].x);
+               levels.values[i].y = DB::fromdb(band_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;
 
-// 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++;
+       int attack_samples;
+       int release_samples;
+       int preview_samples;
 
-                       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;
-                               }
-                       }
-               }
+       if( !engine ) {
+               engine = new CompressorEngine(&config, 0);
+               engine->gui_frame_samples = sample_rate / TRACKING_RATE + 1;
        }
-       else
-       {
-               if(target_current_sample < 0) target_current_sample = target_samples;
-               int64_t preview_samples = -reaction_samples;
+       engine->calculate_ranges(&attack_samples, &release_samples,
+                       &preview_samples, sample_rate);
 
 // 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
+       if( get_direction() == PLAY_FORWARD &&
+               (start_position < input_start ||
+               start_position >= input_start + input_size)
+               ||
+               get_direction() == PLAY_REVERSE &&
+               (start_position > input_start ||
+               start_position <= input_start - input_size) ) {
+// printf("CompressorEffect::process_buffer %d start_position=%ld input_start=%ld input_size=%ld\n",
+// __LINE__, 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;
+       if( get_direction() == PLAY_FORWARD &&
+               start_position > input_start &&
+               start_position < input_start + input_size
+               ||
+               get_direction() == PLAY_REVERSE &&
+               start_position < input_start &&
+               start_position > input_start - input_size ) {
+               if( input_buffer ) {
+                       int len, offset;
+                       if( get_direction() == PLAY_FORWARD ) {
+                               len = input_start + input_size - start_position;
+                               offset = start_position - input_start;
                        }
-               }
-
-// 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];
-                               }
+                       else {
+                               len = start_position - (input_start - input_size);
+                               offset = input_start - start_position;
                        }
-                       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);
+                       for( int i = 0; i < channels; i++ ) {
+                               memcpy(input_buffer[i]->get_data(),
+                                       input_buffer[i]->get_data() + offset,
+                                       len * sizeof(double));
                        }
-                       input_size += fragment_size;
+                       input_size = len;
+                       input_start = start_position;
                }
+       }
 
-
-               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;
-                               }
+// Expand buffer to handle preview size
+       if( size + preview_samples > input_allocated ) {
+               Samples **new_input_buffer = new Samples*[channels];
+               for( int i = 0; i < channels; 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;
        }
 
-
-
-
-
-       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);
-                       }
+// Append data to input buffer to construct readahead area.
+       if( input_size < size + preview_samples ) {
+               int fragment_size = size + preview_samples - input_size;
+               for( int i = 0; i < channels; i++ ) {
+                       input_buffer[i]->set_offset(input_size);
+                       read_samples(input_buffer[i], i, sample_rate,
+                               input_start + input_size * sign, fragment_size);
+                       input_buffer[i]->set_offset(0);
                }
+               input_size += fragment_size;
        }
 
-       if(levels.total)
-       {
-               return min_y +
-                       (x - min_x) *
-                       (levels.values[0].y - min_y) /
-                       (levels.values[0].x - min_x);
-       }
-       else
-               return x;
-}
+       engine->process(buffer, input_buffer, size,
+               sample_rate, PluginClient::total_in_buffers, start_position);
 
+       double start_pos = (double)start_position / sample_rate;
+       for( int i = 0; i < engine->gui_gains.size(); i++ ) {
+               CompressorGainFrame *gain_frame = new CompressorGainFrame();
+               gain_frame->position = start_pos + sign*engine->gui_offsets[i];
+               gain_frame->gain = engine->gui_gains[i];
+               gain_frame->level = engine->gui_levels[i];
+               add_gui_frame(gain_frame);
+       }
 
-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;
+       last_position += sign * size;
+       return 0;
 }
 
 
-
-
-
-
-
-
-
-
 CompressorConfig::CompressorConfig()
+ : CompressorConfigBase(1)
 {
-       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]);
+       CompressorConfigBase::copy_from(that);
 }
 
 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 CompressorConfigBase::equivalent(that);
        return 1;
 }
 
@@ -665,395 +349,149 @@ void CompressorConfig::interpolate(CompressorConfig &prev,
        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()
+CompressorWindow::CompressorWindow(CompressorEffect *plugin)
+ : PluginClientWindow(plugin, xS(650), yS(480), xS(650), yS(480), 0)
 {
-       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();
-                       }
-               }
-
-       }
+       this->plugin = plugin;
 }
 
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-CompressorWindow::CompressorWindow(CompressorEffect *plugin)
- : PluginClientWindow(plugin,
-       xS(650),
-       yS(480),
-       xS(650),
-       yS(480),
-       0)
+CompressorWindow::~CompressorWindow()
 {
-       this->plugin = plugin;
+//     delete readahead;
+       delete attack;
+       delete release;
+       delete trigger;
+       delete x_text;
+       delete y_text;
 }
 
 void CompressorWindow::create_objects()
 {
-       int xs10 = xS(10), xs20 = xS(20), xs50 = xS(50), xs110 = xS(110);
-       int ys20 = yS(20), ys30 = yS(30), ys40 = yS(40), ys60 = yS(60), ys70 = yS(70);
-       int x = xS(35), y = yS(10);
+       int margin = client->get_theme()->widget_border;
+       int x = margin, y = margin;
        int control_margin = xS(130);
-
-       add_subwindow(canvas = new CompressorCanvas(plugin,
-               x,
-               y,
-               get_w() - x - control_margin - xs10,
-               get_h() - y - ys70));
-       canvas->set_cursor(CROSS_CURSOR, 0, 0);
+       int canvas_y2 = get_h()-yS(35);
+       BC_Title *title;
+
+       add_subwindow(title = new BC_Title(x, y, "In:"));
+       int y2 = y + title->get_h() + margin;
+       EDLSession *session = plugin->get_edl()->session;
+       add_subwindow(in = new BC_Meter(x, y2, METER_VERT, canvas_y2 - y2,
+               session->min_meter_db, session->max_meter_db, session->meter_format,
+               1, // use_titles
+               -1)); // span
+       x += in->get_w() + margin;
+
+       add_subwindow(title = new BC_Title(x, y, "Gain:"));
+       add_subwindow(gain_change = new BC_Meter(x, y2, METER_VERT,
+               canvas_y2 - y2, MIN_GAIN_CHANGE, MAX_GAIN_CHANGE, METER_DB,
+               1, // use_titles
+               -1, // span
+               0, // is_downmix
+               1)); // is_gain_change
+//     gain_change->update(1, 0);
+
+       x += gain_change->get_w() + xS(35);
+       add_subwindow(title = new BC_Title(x, y, _("Sound level (Press shift to snap to grid):")));
+       y += title->get_h() + 1;
+       add_subwindow(canvas = new CompressorCanvas(plugin, this, x, y,
+               get_w() - x - control_margin - xS(10), canvas_y2 - y));
        x = get_w() - control_margin;
-       add_subwindow(new BC_Title(x, y, _("Reaction secs:")));
-       y += ys20;
-       add_subwindow(reaction = new CompressorReaction(plugin, x, y));
-       y += ys30;
-       add_subwindow(new BC_Title(x, y, _("Decay secs:")));
-       y += ys20;
-       add_subwindow(decay = new CompressorDecay(plugin, x, y));
-       y += ys30;
-       add_subwindow(new BC_Title(x, y, _("Trigger Type:")));
-       y += ys20;
+
+//     add_subwindow(new BC_Title(x, y, _("Lookahead secs:")));
+//     y += title->get_h() + margin;
+//     readahead = new CompressorLookAhead(plugin, this, x, y);
+//     readahead->create_objects();
+//     y += readahead->get_h() + margin;
+
+       add_subwindow(new BC_Title(x, y, _("Attack secs:")));
+       y += title->get_h() + margin;
+       attack = new CompressorAttack(plugin, this, x, y);
+       attack->create_objects();
+       y += attack->get_h() + margin;
+
+       add_subwindow(title = new BC_Title(x, y, _("Release secs:")));
+       y += title->get_h() + margin;
+       release = new CompressorRelease(plugin, this, x, y);
+       release->create_objects();
+       y += release->get_h() + margin;
+
+       add_subwindow(title = new BC_Title(x, y, _("Trigger Type:")));
+       y += title->get_h() + margin;
        add_subwindow(input = new CompressorInput(plugin, x, y));
        input->create_objects();
-       y += ys30;
-       add_subwindow(new BC_Title(x, y, _("Trigger:")));
-       y += ys20;
-       add_subwindow(trigger = new CompressorTrigger(plugin, x, y));
-       if(plugin->config.input != CompressorConfig::TRIGGER) trigger->disable();
-       y += ys30;
-       add_subwindow(smooth = new CompressorSmooth(plugin, x, y));
-       y += ys60;
-       add_subwindow(clear = new CompressorClear(plugin, x, y));
-       x = xs10;
-       y = get_h() - ys40;
-       add_subwindow(new BC_Title(x, y, _("Point:")));
-       x += xs50;
-       add_subwindow(x_text = new CompressorX(plugin, x, y));
-       x += xs110;
-       add_subwindow(new BC_Title(x, y, _("x")));
-       x += xs20;
-       add_subwindow(y_text = new CompressorY(plugin, x, y));
-       draw_scales();
-
-       update_canvas();
-       show_window();
-}
+       y += input->get_h() + margin;
 
-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() + yS(10) + canvas->get_h() / DIVISIONS * i;
-               int x = canvas->get_x() - xS(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() - xS(10), y, canvas->get_x(), y);
-                       }
-                       else
-                       if(i < DIVISIONS)
-                       {
-                               draw_line(canvas->get_x() - xS(5), y, canvas->get_x(), y);
-                       }
-               }
-       }
+       add_subwindow(title = new BC_Title(x, y, _("Trigger:")));
+       y += title->get_h() + margin;
+       trigger = new CompressorTrigger(plugin, this, x, y);
+       trigger->create_objects();
+       if( plugin->config.input != CompressorConfig::TRIGGER ) trigger->disable();
+       y += trigger->get_h() + margin;
 
+       add_subwindow(smooth = new CompressorSmooth(plugin, x, y));
+       y += smooth->get_h() + margin;
 
-       for(int i = 0; i <= DIVISIONS; i++)
-       {
-               int y = canvas->get_h() + yS(30);
-               int x = canvas->get_x() + (canvas->get_w() - xS(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() + yS(10));
-                       }
-                       else
-                       if(i < DIVISIONS)
-                       {
-                               draw_line(x, canvas->get_y() + canvas->get_h(), x, canvas->get_y() + canvas->get_h() + yS(5));
-                       }
-               }
-       }
+       add_subwindow(title = new BC_Title(x, y, _("Output:")));
+       y += title->get_h();
+       y_text = new CompressorY(plugin, this, x, y);
+       y_text->create_objects();
+       y += y_text->get_h() + margin;
 
+       add_subwindow(title = new BC_Title(x, y, _("Input:")));
+       y += title->get_h();
+       x_text = new CompressorX(plugin, this, x, y);
+       x_text->create_objects();
+       y += x_text->get_h() + margin;
 
+       add_subwindow(clear = new CompressorClear(plugin, x, y));
+       x = xS(10);
+       y = get_h() - yS(40);
 
-       flash();
+       canvas->create_objects();
+       canvas->update();
+       show_window();
 }
 
+
 void CompressorWindow::update()
 {
        update_textboxes();
-       update_canvas();
+       canvas->update();
+}
+
+void CompressorWindow::update_meter(CompressorGainFrame *gain_frame)
+{
+       gain_change->update(gain_frame->gain, 0);
+       in->update(gain_frame->level, 0);
 }
 
 void CompressorWindow::update_textboxes()
 {
-       if(atol(trigger->get_text()) != plugin->config.trigger)
+       BandConfig *band_config = &plugin->config.bands[0];
+
+       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)))
+       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())
+       if( plugin->config.input != CompressorConfig::TRIGGER && trigger->get_enabled() )
                trigger->disable();
        else
-       if(plugin->config.input == CompressorConfig::TRIGGER && !trigger->get_enabled())
+       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);
+//     if( !EQUIV(atof(readahead->get_text()), band_config->readahead_len) )
+//             readahead->update((float)band_config->readahead_len);
+       if( !EQUIV(atof(attack->get_text()), band_config->attack_len) )
+               attack->update((float)band_config->attack_len);
+       if( !EQUIV(atof(release->get_text()), band_config->release_len) )
+               release->update((float)band_config->release_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 xS(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;
+       if( canvas->current_operation == CompressorCanvas::DRAG ) {
+               x_text->update((float)band_config->levels.values[canvas->current_point].x);
+               y_text->update((float)band_config->levels.values[canvas->current_point].y);
        }
-       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)
@@ -1062,250 +500,123 @@ int CompressorWindow::resize_event(int w, int h)
 }
 
 
-
-
-
-
-
-
-CompressorCanvas::CompressorCanvas(CompressorEffect *plugin, int x, int y, int w, int h)
- : BC_SubWindow(x, y, w, h, BLACK)
+CompressorCanvas::CompressorCanvas(CompressorEffect *plugin,
+       CompressorWindow *window, int x, int y, int w, int h)
+ : CompressorCanvasBase(&plugin->config, plugin, window, x, y, w, h)
 {
-       this->plugin = plugin;
-       current_operation = NONE;
-       current_point = 0;
 }
 
-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()
+void CompressorCanvas::update_window()
 {
-       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;
+       ((CompressorWindow*)window)->update();
 }
 
 
+// CompressorLookAhead::CompressorLookAhead(CompressorEffect *plugin,
+//      CompressorWindow *window, int x, int y)
+//  : BC_TumbleTextBox(window, (float)plugin->config.bands[0].readahead_len,
+//      (float)MIN_LOOKAHEAD, (float)MAX_LOOKAHEAD, x, y, xS(100))
+// {
+//     this->plugin = plugin;
+//      set_increment(0.1);
+//      set_precision(2);
+// }
+//
+// int CompressorLookAhead::handle_event()
+// {
+//     plugin->config.bands[0].readahead_len = atof(get_text());
+//     plugin->send_configure_change();
+//     return 1;
+// }
+//
+//
 
 
-
-CompressorReaction::CompressorReaction(CompressorEffect *plugin, int x, int y)
- : BC_TextBox(x, y, xS(100), 1, (float)plugin->config.reaction_len)
+CompressorAttack::CompressorAttack(CompressorEffect *plugin,
+       CompressorWindow *window, int x, int y)
+ : BC_TumbleTextBox(window, (float)plugin->config.bands[0].attack_len,
+       (float)MIN_ATTACK, (float)MAX_ATTACK, x, y, xS(100))
 {
        this->plugin = plugin;
+       set_increment(0.1);
+       set_precision(2);
 }
 
-int CompressorReaction::handle_event()
+int CompressorAttack::handle_event()
 {
-       plugin->config.reaction_len = atof(get_text());
+       plugin->config.bands[0].attack_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, xS(100), 1, (float)plugin->config.decay_len)
+CompressorRelease::CompressorRelease(CompressorEffect *plugin,
+       CompressorWindow *window, int x, int y)
+ : BC_TumbleTextBox(window, (float)plugin->config.bands[0].release_len,
+       (float)MIN_DECAY, (float)MAX_DECAY, x, y, xS(100))
 {
        this->plugin = plugin;
+       set_increment(0.1);
+       set_precision(2);
 }
-int CompressorDecay::handle_event()
+int CompressorRelease::handle_event()
 {
-       plugin->config.decay_len = atof(get_text());
+       plugin->config.bands[0].release_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, xS(100), 1, "")
+CompressorX::CompressorX(CompressorEffect *plugin,
+       CompressorWindow *window, int x, int y)
+ : BC_TumbleTextBox(window, (float)0.0,
      plugin->config.min_db, plugin->config.max_db, x, y, xS(100))
 {
        this->plugin = plugin;
+       set_increment(0.1);
+       set_precision(2);
 }
 int CompressorX::handle_event()
 {
+       BandConfig *band_config = &plugin->config.bands[0];
        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();
+       if( current_point < band_config->levels.total ) {
+               band_config->levels.values[current_point].x = atof(get_text());
+               ((CompressorWindow*)plugin->thread->window)->canvas->update();
                plugin->send_configure_change();
        }
        return 1;
 }
 
-
-
-CompressorY::CompressorY(CompressorEffect *plugin, int x, int y)
: BC_TextBox(x, y, xS(100), 1, "")
+CompressorY::CompressorY(CompressorEffect *plugin,
+       CompressorWindow *window, int x, int y)
+ : BC_TumbleTextBox(window, (float)0.0,
      plugin->config.min_db, plugin->config.max_db, x, y, xS(100))
 {
        this->plugin = plugin;
+       set_increment(0.1);
+       set_precision(2);
 }
 int CompressorY::handle_event()
 {
+       BandConfig *band_config = &plugin->config.bands[0];
        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();
+       if( current_point < band_config->levels.total ) {
+               band_config->levels.values[current_point].y = atof(get_text());
+               ((CompressorWindow*)plugin->thread->window)->canvas->update();
                plugin->send_configure_change();
        }
        return 1;
 }
 
 
-
-
-
-CompressorTrigger::CompressorTrigger(CompressorEffect *plugin, int x, int y)
- : BC_TextBox(x, y, xS(100), 1, (int64_t)plugin->config.trigger)
+CompressorTrigger::CompressorTrigger(CompressorEffect *plugin,
+       CompressorWindow *window, int x, int y)
+ : BC_TumbleTextBox(window, (int)plugin->config.trigger,
+       MIN_TRIGGER, MAX_TRIGGER, x, y, xS(100))
 {
        this->plugin = plugin;
+       set_increment(1);
 }
 int CompressorTrigger::handle_event()
 {
@@ -1314,37 +625,10 @@ int CompressorTrigger::handle_event()
        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,
-       xS(120),
-       CompressorInput::value_to_text(plugin->config.input),
-       1)
+ : BC_PopupMenu(x, y, xS(100),
+       CompressorInput::value_to_text(plugin->config.input), 1)
 {
        this->plugin = plugin;
 }
@@ -1358,39 +642,32 @@ int CompressorInput::handle_event()
 
 void CompressorInput::create_objects()
 {
-       for(int i = 0; i < 3; i++)
-       {
+       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)
+       switch( value )
        {
-               case CompressorConfig::TRIGGER: return _("Trigger");
-               case CompressorConfig::MAX: return _("Maximum");
-               case CompressorConfig::SUM: return _("Total");
+               case CompressorConfig::TRIGGER: return "Trigger";
+               case CompressorConfig::MAX: return "Maximum";
+               case CompressorConfig::SUM: return "Total";
        }
 
-       return _("Trigger");
+       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;
+       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"))
 {
@@ -1399,15 +676,14 @@ CompressorClear::CompressorClear(CompressorEffect *plugin, int x, int y)
 
 int CompressorClear::handle_event()
 {
-       plugin->config.levels.remove_all();
+       BandConfig *band_config = &plugin->config.bands[0];
+       band_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"))
 {
@@ -1421,6 +697,3 @@ int CompressorSmooth::handle_event()
        return 1;
 }
 
-
-
-