From 0e6cf5b52d1ebce9272270144bcf43df4683507e Mon Sep 17 00:00:00 2001
From: Good Guy <good1.2guy@gmail.com>
Date: Wed, 11 Dec 2019 09:17:30 -0700
Subject: [PATCH] new/reworked audio plugins ported from hv72
 compressor/multi/reverb, glyph workaround, centerline on audio, pro->mov
 extension

---
 cinelerra-5.1/Cinelerra_factory               |   32 +-
 cinelerra-5.1/cinelerra/Makefile              |    2 +
 cinelerra-5.1/cinelerra/attachmentpoint.C     |   24 +-
 cinelerra-5.1/cinelerra/attachmentpoint.h     |    7 +-
 cinelerra-5.1/cinelerra/compressortools.C     | 1072 ++++++++++++
 cinelerra-5.1/cinelerra/compressortools.h     |  329 ++++
 cinelerra-5.1/cinelerra/compressortools.inc   |   22 +
 cinelerra-5.1/cinelerra/cwindowgui.h          |    5 +-
 cinelerra-5.1/cinelerra/eqcanvas.C            |  248 +++
 cinelerra-5.1/cinelerra/eqcanvas.h            |   62 +
 cinelerra-5.1/cinelerra/eqcanvas.inc          |   12 +
 cinelerra-5.1/cinelerra/formattools.C         |    3 +-
 cinelerra-5.1/cinelerra/fourier.C             |  417 ++---
 cinelerra-5.1/cinelerra/fourier.h             |   43 +-
 cinelerra-5.1/cinelerra/mbuttons.h            |    1 +
 cinelerra-5.1/cinelerra/mwindow.C             |   36 +-
 cinelerra-5.1/cinelerra/mwindow.h             |    5 +
 cinelerra-5.1/cinelerra/playbackengine.C      |    5 +
 cinelerra-5.1/cinelerra/playbackengine.h      |    1 +
 cinelerra-5.1/cinelerra/playtransport.C       |    2 +-
 cinelerra-5.1/cinelerra/playtransport.h       |    2 +-
 cinelerra-5.1/cinelerra/pluginaclient.C       |  111 +-
 cinelerra-5.1/cinelerra/pluginaclient.h       |   36 +-
 cinelerra-5.1/cinelerra/pluginclient.C        |  250 +--
 cinelerra-5.1/cinelerra/pluginclient.h        |  275 ++--
 cinelerra-5.1/cinelerra/pluginclient.inc      |    3 +-
 cinelerra-5.1/cinelerra/pluginserver.C        |  399 ++---
 cinelerra-5.1/cinelerra/pluginserver.h        |    7 +
 cinelerra-5.1/cinelerra/resourcepixmap.C      |   41 +-
 cinelerra-5.1/cinelerra/resourcepixmap.h      |    2 +
 cinelerra-5.1/cinelerra/theme.C               |    7 +
 cinelerra-5.1/cinelerra/theme.h               |   11 +
 cinelerra-5.1/guicast/arraylist.h             |   11 +
 cinelerra-5.1/guicast/bcmeter.C               |  519 +++---
 cinelerra-5.1/guicast/bcmeter.h               |   42 +-
 cinelerra-5.1/guicast/bcpot.C                 |    2 +
 cinelerra-5.1/guicast/bcwindowbase.C          |   26 +-
 cinelerra-5.1/guicast/bcwindowbase.h          |    1 +
 cinelerra-5.1/guicast/linklist.h              |   14 +-
 cinelerra-5.1/guicast/units.C                 |   36 +-
 cinelerra-5.1/guicast/units.h                 |    5 +-
 cinelerra-5.1/plugins/Makefile                |    1 +
 cinelerra-5.1/plugins/compressor/Makefile     |    1 -
 cinelerra-5.1/plugins/compressor/compressor.C | 1463 +++++------------
 cinelerra-5.1/plugins/compressor/compressor.h |  155 +-
 .../plugins/compressormulti/Makefile          |   11 +
 .../plugins/compressormulti/comprmulti.C      |  945 +++++++++++
 .../plugins/compressormulti/comprmulti.h      |  163 ++
 .../plugins/compressormulti/comprmultigui.C   |  732 +++++++++
 .../plugins/compressormulti/comprmultigui.h   |  220 +++
 cinelerra-5.1/plugins/denoisefft/denoisefft.C |    5 +-
 cinelerra-5.1/plugins/graphic/graphic.C       |   64 +-
 cinelerra-5.1/plugins/graphic/graphic.h       |    1 +
 cinelerra-5.1/plugins/parametric/parametric.C |  120 +-
 cinelerra-5.1/plugins/parametric/parametric.h |    1 +
 cinelerra-5.1/plugins/reverb/reverb.C         |  743 ++++-----
 cinelerra-5.1/plugins/reverb/reverb.h         |  150 +-
 cinelerra-5.1/plugins/reverb/reverb.inc       |    2 -
 cinelerra-5.1/plugins/reverb/reverbwindow.C   |  739 ++++-----
 cinelerra-5.1/plugins/reverb/reverbwindow.h   |  240 ++-
 cinelerra-5.1/plugins/reverb/reverbwindow.inc |    2 -
 61 files changed, 6342 insertions(+), 3544 deletions(-)
 create mode 100644 cinelerra-5.1/cinelerra/compressortools.C
 create mode 100644 cinelerra-5.1/cinelerra/compressortools.h
 create mode 100644 cinelerra-5.1/cinelerra/compressortools.inc
 create mode 100644 cinelerra-5.1/cinelerra/eqcanvas.C
 create mode 100644 cinelerra-5.1/cinelerra/eqcanvas.h
 create mode 100644 cinelerra-5.1/cinelerra/eqcanvas.inc
 create mode 100644 cinelerra-5.1/plugins/compressormulti/Makefile
 create mode 100644 cinelerra-5.1/plugins/compressormulti/comprmulti.C
 create mode 100644 cinelerra-5.1/plugins/compressormulti/comprmulti.h
 create mode 100644 cinelerra-5.1/plugins/compressormulti/comprmultigui.C
 create mode 100644 cinelerra-5.1/plugins/compressormulti/comprmultigui.h

diff --git a/cinelerra-5.1/Cinelerra_factory b/cinelerra-5.1/Cinelerra_factory
index 05acba1a..4a4c0e58 100644
--- a/cinelerra-5.1/Cinelerra_factory
+++ b/cinelerra-5.1/Cinelerra_factory
@@ -1,17 +1,29 @@
 <?xml version="1.0"?>
 <PLUGIN TITLE=Compressor>
-<KEYFRAME TITLE=loud><COMPRESSOR TRIGGER=0 REACTION_LEN=-1.0000000000000014e-01 DECAY_LEN=9.9999999999999367e-02 SMOOTHING_ONLY=0 INPUT=2>
-<LEVEL X=-4.9852631578947367e+01 Y=0>
+<KEYFRAME TITLE=loud>
+    <COMPRESSOR TRIGGER=0 ATTACK_LEN=1e-01 RELEASE_LEN=2e-01 SMOOTHING_ONLY=0 INPUT=2>
+    <COMPRESSORBAND>
+    <LEVEL X=-42 Y=0>
+    <LEVEL X=6 Y=0>
+    </COMPRESSORBAND>
+    </COMPRESSOR>
 </KEYFRAME>
-<KEYFRAME TITLE=soft><COMPRESSOR TRIGGER=0 REACTION_LEN=-1.0000000000000014e-01 DECAY_LEN=4.9999999999999982e+00 SMOOTHING_ONLY=0 INPUT=2>
-<LEVEL X=-4.9852631578947367e+01 Y=0>
+<KEYFRAME TITLE=soft>
+    <COMPRESSOR TRIGGER=0 ATTACK_LEN=1e-01 RELEASE_LEN=1 SMOOTHING_ONLY=0 INPUT=2>
+    <COMPRESSORBAND>
+    <LEVEL X=-42 Y=0>
+    <LEVEL X=6 Y=0>
+    </COMPRESSORBAND>
+    </COMPRESSOR>
 </KEYFRAME>
-<KEYFRAME TITLE=voice><COMPRESSOR TRIGGER=0 REACTION_LEN=-1.0000000000000001e-01 DECAY_LEN=1.0000000000000001e-01 SMOOTHING_ONLY=0 INPUT=2>
-<LEVEL X=-3.0484210526315792e+01 Y=-80>
-<LEVEL X=-1.9873684210526321e+01 Y=0>
-</KEYFRAME>
-<KEYFRAME TITLE="live audio"><COMPRESSOR TRIGGER=0 REACTION_LEN=1.0000000000000001e-01 DECAY_LEN=1.0000000000000001e-01 SMOOTHING_ONLY=0 INPUT=2>
-<LEVEL X=-4.9852631578947367e+01 Y=0>
+<KEYFRAME TITLE=voice>
+    <COMPRESSOR TRIGGER=0 ATTACK_LEN=1e-01 RELEASE_LEN=2e-01 SMOOTHING_ONLY=0 INPUT=2>
+    <COMPRESSORBAND>
+    <LEVEL X=-30 Y=-78>
+    <LEVEL X=-20 Y=0>
+    <LEVEL X=6 Y=0>
+    </COMPRESSORBAND>
+    </COMPRESSOR>
 </KEYFRAME>
 </PLUGIN>
 <PLUGIN TITLE=Lens>
diff --git a/cinelerra-5.1/cinelerra/Makefile b/cinelerra-5.1/cinelerra/Makefile
index f8c0bc11..e4f782d4 100644
--- a/cinelerra-5.1/cinelerra/Makefile
+++ b/cinelerra-5.1/cinelerra/Makefile
@@ -96,6 +96,7 @@ OBJS := $(OVERLAYS) \
 	$(OBJDIR)/clippopup.o \
 	$(OBJDIR)/colorpicker.o \
 	$(OBJDIR)/commonrender.o \
+	$(OBJDIR)/compressortools.o \
 	$(OBJDIR)/confirmquit.o \
 	$(OBJDIR)/confirmsave.o \
 	$(OBJDIR)/convert.o \
@@ -126,6 +127,7 @@ OBJS := $(OVERLAYS) \
 	$(OBJDIR)/edl.o \
 	$(OBJDIR)/edlsession.o \
 	$(OBJDIR)/effectlist.o \
+	$(OBJDIR)/eqcanvas.o \
 	$(OBJDIR)/exportedl.o \
 	$(OBJDIR)/fadeengine.o \
 	$(OBJDIR)/ffmpeg.o \
diff --git a/cinelerra-5.1/cinelerra/attachmentpoint.C b/cinelerra-5.1/cinelerra/attachmentpoint.C
index 362a5e1f..adc1c4ac 100644
--- a/cinelerra-5.1/cinelerra/attachmentpoint.C
+++ b/cinelerra-5.1/cinelerra/attachmentpoint.C
@@ -211,13 +211,32 @@ int AttachmentPoint::singlechannel()
 	return 0;
 }
 
+void AttachmentPoint::reset_gui_frames(PluginServer *server)
+{
+	if( server != plugin_servers.get(0) ) return;
+	if( renderengine && renderengine->mwindow )
+		renderengine->mwindow->reset_plugin_gui_frames(plugin);
+}
 
-void AttachmentPoint::render_gui(void *data, PluginServer *server)
+void AttachmentPoint::render_gui_frames(PluginClientFrames *frames, PluginServer *server)
 {
 //printf("AttachmentPoint::render_gui 1 %p %p\n", server, plugin_servers.get(0));
 	void *This = this;
 	if(!This) printf("AttachmentPoint::render_gui 1 NULL\n");
 
+// Discard if not 1st plugin server, so single channel plugins don't get double GUI updates
+	if(server != plugin_servers.get(0)) return;
+
+	if(renderengine && renderengine->mwindow)
+		renderengine->mwindow->render_plugin_gui_frames(frames, plugin);
+}
+
+
+void AttachmentPoint::render_gui(void *data, PluginServer *server)
+{
+	void *This = this;
+	if(!This) printf("AttachmentPoint::render_gui 2 NULL\n");
+
 // Discard if not 1st plugin server, so single channel plugins don't get double GUI updates
 	if(server != plugin_servers.get(0)) return;
 
@@ -228,7 +247,7 @@ void AttachmentPoint::render_gui(void *data, PluginServer *server)
 void AttachmentPoint::render_gui(void *data, int size, PluginServer *server)
 {
 	void *This = this;
-	if(!This) printf("AttachmentPoint::render_gui 2 NULL\n");
+	if(!This) printf("AttachmentPoint::render_gui 3 NULL\n");
 
 // Discard if not 1st plugin server, so single channel plugins don't get double GUI updates
 	if(server != plugin_servers.get(0)) return;
@@ -237,6 +256,7 @@ void AttachmentPoint::render_gui(void *data, int size, PluginServer *server)
 		renderengine->mwindow->render_plugin_gui(data, size, plugin);
 }
 
+
 int AttachmentPoint::gui_open()
 {
 	if(renderengine && renderengine->mwindow)
diff --git a/cinelerra-5.1/cinelerra/attachmentpoint.h b/cinelerra-5.1/cinelerra/attachmentpoint.h
index 6a7dc0f5..ebcfdc13 100644
--- a/cinelerra-5.1/cinelerra/attachmentpoint.h
+++ b/cinelerra-5.1/cinelerra/attachmentpoint.h
@@ -32,6 +32,7 @@
 #include "mwindow.inc"
 #include "messages.inc"
 #include "plugin.inc"
+#include "pluginclient.inc"
 #include "pluginserver.inc"
 #include "renderengine.inc"
 #include "sharedlocation.h"
@@ -67,11 +68,9 @@ public:
 	int attach_virtual_plugin(VirtualNode *virtual_plugin);
 	virtual void delete_buffer_vector() {};
 
-// return 0 if ready to render
-// check all the virtual plugins for waiting status
-// all virtual plugins attached to this must be waiting for a render
-//	int sort(VirtualNode *virtual_plugin);
 // Called by plugin server to render GUI with data.
+	void reset_gui_frames(PluginServer *server);
+	void render_gui_frames(PluginClientFrames *frames, PluginServer *server);
 	void render_gui(void *data, PluginServer *server);
 	void render_gui(void *data, int size, PluginServer *server);
 	int gui_open();
diff --git a/cinelerra-5.1/cinelerra/compressortools.C b/cinelerra-5.1/cinelerra/compressortools.C
new file mode 100644
index 00000000..fee76d3d
--- /dev/null
+++ b/cinelerra-5.1/cinelerra/compressortools.C
@@ -0,0 +1,1072 @@
+/*
+ * CINELERRA
+ * 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
+ * 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
+ * 
+ */
+
+
+// Objects for compressors
+#include "clip.h"
+#include "compressortools.h"
+#include "cursors.h"
+#include "filexml.h"
+#include "language.h"
+#include "pluginclient.h"
+#include "samples.h"
+#include "theme.h"
+#include <string.h>
+
+
+BandConfig::BandConfig()
+{
+	freq = 0;
+	solo = 0;
+	bypass = 0;
+//	readahead_len = 1.0;
+	attack_len = 1.0;
+	release_len = 1.0;
+}
+
+BandConfig::~BandConfig()
+{
+	
+}
+
+void BandConfig::save_data(FileXML *xml, int number, int do_multiband)
+{
+	xml->tag.set_title("COMPRESSORBAND");
+	if( do_multiband ) {
+		xml->tag.set_property("NUMBER", number);
+		xml->tag.set_property("FREQ", freq);
+		xml->tag.set_property("BYPASS", bypass);
+		xml->tag.set_property("SOLO", solo);
+		xml->tag.set_property("ATTACK_LEN", attack_len);
+		xml->tag.set_property("RELEASE_LEN", release_len);
+	}
+	xml->append_tag();
+	xml->append_newline();
+
+	for( int i = 0; i < levels.total; i++ ) {
+		xml->tag.set_title("LEVEL");
+		xml->tag.set_property("X", levels.values[i].x);
+		xml->tag.set_property("Y", levels.values[i].y);
+		xml->append_tag();
+		xml->append_newline();
+	}
+
+	xml->tag.set_title("/COMPRESSORBAND");
+	xml->append_tag();
+	xml->append_newline();
+}
+
+void BandConfig::read_data(FileXML *xml, int do_multiband)
+{
+	if( do_multiband ) {
+		freq = xml->tag.get_property("FREQ", freq);
+		bypass = xml->tag.get_property("BYPASS", bypass);
+		solo = xml->tag.get_property("SOLO", solo);
+		attack_len = xml->tag.get_property("ATTACK_LEN", attack_len);
+		release_len = xml->tag.get_property("RELEASE_LEN", release_len);
+	}
+
+	levels.remove_all();
+	int result = 0;
+	while( !result ) {
+		result = xml->read_tag();
+		if( !result ) {
+			if( xml->tag.title_is("LEVEL") ) {
+				double x = xml->tag.get_property("X", (double)0);
+				double y = xml->tag.get_property("Y", (double)0);
+				compressor_point_t point = { x, y };
+
+				levels.append(point);
+			}
+			else
+			if( xml->tag.title_is("/COMPRESSORBAND") ) {
+				break;
+			}
+		}
+	}
+}
+
+void BandConfig::copy_from(BandConfig *src)
+{
+	levels.remove_all();
+	for( int i = 0; i < src->levels.total; i++ ) {
+		levels.append(src->levels.values[i]);
+	}
+
+//	readahead_len = src->readahead_len;
+	attack_len = src->attack_len;
+	release_len = src->release_len;
+	freq = src->freq;
+	solo = src->solo;
+	bypass = src->bypass;
+}
+
+int BandConfig::equiv(BandConfig *src)
+{
+	if( levels.total != src->levels.total ||
+		solo != src->solo ||
+		bypass != src->bypass ||
+		freq != src->freq ||
+//		!EQUIV(readahead_len, src->readahead_len) ||
+		!EQUIV(attack_len, src->attack_len) ||
+		!EQUIV(release_len, src->release_len) ) {
+		return 0;
+	}
+
+	for( int i = 0; i < levels.total && i < src->levels.total; i++ ) {
+		compressor_point_t *this_level = &levels.values[i];
+		compressor_point_t *that_level = &src->levels.values[i];
+		if( !EQUIV(this_level->x, that_level->x) ||
+			!EQUIV(this_level->y, that_level->y) ) {
+			return 0;
+		}
+	}
+	
+	return 1;
+}
+
+void BandConfig::boundaries(CompressorConfigBase *base)
+{
+	for( int i = 0; i < levels.size(); i++ ) {
+		compressor_point_t *level = &levels.values[i];
+		if( level->x < base->min_db ) level->x = base->min_db;
+		if( level->y < base->min_db ) level->y = base->min_db;
+		if( level->x > base->max_db ) level->x = base->max_db;
+		if( level->y > base->max_db ) level->y = base->max_db;
+	}
+}
+
+
+CompressorConfigBase::CompressorConfigBase(int total_bands)
+{
+	this->total_bands = total_bands;
+	bands = new BandConfig[total_bands];
+	min_db = -78.0;
+	max_db = 6.0;
+	min_value = DB::fromdb(min_db) + 0.001;
+//	min_x = min_db;  max_x = 0;
+//	min_y = min_db;  max_y = 0;
+	smoothing_only = 0;
+	trigger = 0;
+	input = CompressorConfigBase::TRIGGER;
+	for( int i=0; i<total_bands; ++i ) {
+		bands[i].freq = Freq::tofreq((i+1) * TOTALFREQS / total_bands);
+	}
+	current_band = 0;
+}
+
+
+CompressorConfigBase::~CompressorConfigBase()
+{
+	delete [] bands;
+}
+
+void CompressorConfigBase::boundaries()
+{
+	for( int i=0; i<total_bands; ++i ) {
+		bands[i].boundaries(this);
+	}
+}
+
+
+void CompressorConfigBase::copy_from(CompressorConfigBase &that)
+{
+//	min_x = that.min_x;  max_x = that.max_x;
+//	min_y = that.min_y;  max_y = that.max_y;
+	trigger = that.trigger;
+	input = that.input;
+	smoothing_only = that.smoothing_only;
+
+	for( int i=0; i<total_bands; ++i ) {
+		BandConfig *dst = &bands[i];
+		BandConfig *src = &that.bands[i];
+		dst->copy_from(src);
+	}
+}
+
+
+int CompressorConfigBase::equivalent(CompressorConfigBase &that)
+{
+	for( int i=0; i<total_bands; ++i ) {
+		if( !bands[i].equiv(&that.bands[i]) )
+			return 0;
+	}
+
+	return trigger == that.trigger &&
+		input == that.input &&
+		smoothing_only == that.smoothing_only;
+}
+
+double CompressorConfigBase::get_y(int band, int i)
+{
+	ArrayList<compressor_point_t> &levels = bands[band].levels;
+	int sz = levels.size();
+	if( !sz ) return 1.;
+	if( i >= sz ) i = sz-1;
+	return levels.values[i].y;
+}
+
+double CompressorConfigBase::get_x(int band, int i)
+{
+	ArrayList<compressor_point_t> &levels = bands[band].levels;
+	int sz = levels.size();
+	if( !sz ) return 0.;
+	if( i >= sz ) i = sz-1;
+	return levels.values[i].x;
+}
+
+double CompressorConfigBase::calculate_db(int band, double x)
+{
+	ArrayList<compressor_point_t> &levels = bands[band].levels;
+	int sz = levels.size();
+	if( !sz ) return x;
+	compressor_point_t &point0 = levels[0];
+	double px0 = point0.x, py0 = point0.y, dx0 = x - px0;
+// the only point.  Use slope from min_db
+	if( sz == 1 )
+		return py0 + dx0 * (py0 - min_db) / (px0 - min_db);
+// before 1st point, use 1:1 gain
+	double ret = py0 + dx0;
+// find point <= x
+	int k = sz;
+	while( --k >= 0 && levels[k].x > x );
+	if( k >= 0 ) {
+		compressor_point_t &curr = levels[k];
+		double cx = curr.x, cy = curr.y, dx = x - cx;
+// between 2 points.  Use slope between 2 points
+// the last point.  Use slope of last 2 points
+		if( k >= sz-1 ) --k;
+		compressor_point_t &prev = levels[k+0];
+		compressor_point_t &next = levels[k+1];
+		double px = prev.x, py = prev.y;
+		double nx = next.x, ny = next.y;
+		ret = cy + dx * (ny - py) / (nx - px);
+	}
+
+	return ret;
+}
+
+
+int CompressorConfigBase::set_point(int band, double x, double y)
+{
+	ArrayList<compressor_point_t> &levels = bands[band].levels;
+	int k = levels.size(), ret = k;
+	while( --k >= 0 && levels[k].x >= x ) ret = k;
+	compressor_point_t new_point = { x, y };
+	levels.insert(new_point, ret);
+	return ret;
+}
+
+void CompressorConfigBase::remove_point(int band, int i)
+{
+	ArrayList<compressor_point_t> &levels = bands[band].levels;
+	levels.remove_number(i);
+}
+
+
+double CompressorConfigBase::calculate_output(int band, double x)
+{
+	double x_db = DB::todb(x);
+	return DB::fromdb(calculate_db(band, x_db));
+}
+
+
+double CompressorConfigBase::calculate_gain(int band, double input_linear)
+{
+	double output_linear = calculate_output(band, input_linear);
+// output is below minimum.  Mute it
+	return output_linear < min_value ? 0. :
+// input is below minimum.  Don't change it.
+		fabs(input_linear - 0.0) < min_value ? 1. :
+// gain
+		output_linear / input_linear;
+}
+
+
+CompressorCanvasBase::CompressorCanvasBase(CompressorConfigBase *config, 
+		PluginClient *plugin, PluginClientWindow *window, 
+		int x, int y, int w, int h)
+ : BC_SubWindow(x, y, w, h, BLACK)
+{
+	this->config = config;
+	this->plugin = plugin;
+	this->window = window;
+	current_operation = NONE;
+
+	graph_x = 0;
+	graph_y = 0;
+	graph_w = w - graph_x;
+	graph_h = h - graph_y;
+	subdivisions = 6;
+	divisions = (int)(config->max_db - config->min_db) / subdivisions;
+}
+
+CompressorCanvasBase::~CompressorCanvasBase()
+{
+}
+
+
+void CompressorCanvasBase::create_objects()
+{
+	add_subwindow(menu = new CompressorPopup(this));
+	menu->create_objects();
+
+	set_cursor(CROSS_CURSOR, 0, 0);
+	draw_scales();
+	update();
+}
+
+void CompressorCanvasBase::draw_scales()
+{
+	int yfudge = yS(10);
+	window->set_font(SMALLFONT);
+	window->set_color(get_resources()->default_text_color);
+
+// output divisions
+	for( int i=0; i<=divisions; ++i ) {
+		int y = get_y() + yfudge + graph_y + graph_h * i / divisions;
+		int x = get_x();
+		char string[BCTEXTLEN];
+		sprintf(string, "%.0f", config->max_db - 
+			(float)i / divisions * (config->max_db - config->min_db));
+		int text_w = get_text_width(SMALLFONT, string);
+		if( i >= divisions ) y -= yfudge;
+		window->draw_text(x-xS(10) - text_w, y, string);
+		if( i >= divisions ) break;
+		
+		int y1 = get_y() + graph_y + graph_h * i / divisions;
+		int y2 = get_y() + graph_y + graph_h * (i + 1) / divisions;
+		int x1 = get_x() - xS(10), x2 = get_x() - xS(5);
+		for( int j=0; j<subdivisions; ++j,x1=x2 ) {
+			y = y1 + (y2 - y1) * j / subdivisions;
+			window->draw_line(x, y, x1, y);
+		}
+	}
+
+// input divisions
+	for( int i=0; i<=divisions; ++i ) {
+		int y = get_y() + get_h();
+		int x = get_x() + graph_x + graph_w * i / divisions;
+		int y0 = y + window->get_text_ascent(SMALLFONT);
+		char string[BCTEXTLEN];
+		sprintf(string, "%.0f", (float)i / divisions * 
+				(config->max_db - config->min_db) + config->min_db);
+		int text_w = get_text_width(SMALLFONT, string);
+		window->draw_text(x - text_w, y0 + yS(10), string);
+		if( i >= divisions ) break;
+
+		int x1 = get_x() + graph_x + graph_w * i / divisions;
+		int x2 = get_x() + graph_x + graph_w * (i + 1) / divisions;
+		int y1 = y + yS(10), y2 = y + yS(5);
+		for( int j=0; j<subdivisions; ++j,y1=y2 ) {
+			x = x1 + (x2 - x1) * j / subdivisions;
+			window->draw_line(x, y, x, y1);
+		}
+	}
+
+
+}
+
+#define POINT_W xS(10)
+
+// get Y from X
+int CompressorCanvasBase::x_to_y(int band, int x)
+{
+	double min_db = config->min_db, max_db = config->max_db;
+	double rng_db = max_db - min_db;
+	double x_db = min_db + (double)x / graph_w * rng_db;
+	double y_db = config->calculate_db(band, x_db);
+	int y = graph_y + graph_h - (int)((y_db - min_db) * graph_h / rng_db);
+	return y;
+}
+
+// get X from DB
+int CompressorCanvasBase::db_to_x(double db)
+{
+	double min_db = config->min_db, max_db = config->max_db;
+	double rng_db = max_db - min_db;
+	int x = graph_x + (double)(db - min_db) * graph_w / rng_db;
+	return x;
+}
+
+// get Y from DB
+int CompressorCanvasBase::db_to_y(double db)
+{
+	double min_db = config->min_db, max_db = config->max_db;
+	double rng_db = max_db - min_db;
+	int y = graph_y + graph_h - (int)((db - min_db) * graph_h / rng_db); 
+	return y;
+}
+
+
+double CompressorCanvasBase::x_to_db(int x)
+{
+	CLAMP(x, 0, get_w());
+	double min_db = config->min_db, max_db = config->max_db;
+	double rng_db = max_db - min_db;
+	double x_db = (double)(x - graph_x) * rng_db / graph_w + min_db;
+	CLAMP(x_db, min_db, max_db);
+	return x_db;
+}
+
+double CompressorCanvasBase::y_to_db(int y)
+{
+	CLAMP(y, 0, get_h());
+	double min_db = config->min_db, max_db = config->max_db;
+	double rng_db = max_db - min_db;
+	double y_db = (double)(graph_y - y) * rng_db / graph_h + max_db;
+	CLAMP(y_db, min_db, max_db);
+	return y_db;
+}
+
+
+
+void CompressorCanvasBase::update()
+{
+// headroom boxes
+	set_color(window->get_bg_color());
+	draw_box(graph_x, 0, get_w(), graph_y);
+	draw_box(graph_w, graph_y, get_w() - graph_w, get_h() - graph_y);
+//	 const int checker_w = DP(10);
+//	 const int checker_h = DP(10);
+//	 set_color(MDGREY);
+//	 for( int i = 0; i < get_h(); i += checker_h )
+//	 {
+//		 for( int j = (i % 2) * checker_w; j < get_w(); j += checker_w * 2 )
+//		 {
+//			 if( !(i >= graph_y && 
+//				 i + checker_h < graph_y + graph_h &&
+//				 j >= graph_x &&
+//				 j + checker_w < graph_x + graph_w) )
+//			 {
+//				 draw_box(j, i, checker_w, checker_h);
+//			 }
+//		 }
+//	 }
+
+// canvas boxes
+	set_color(plugin->get_theme()->graph_bg_color);
+	draw_box(graph_x, graph_y, graph_w, graph_h);
+
+// graph border
+	draw_3d_border(0, 0, get_w(), get_h(), window->get_bg_color(),
+		plugin->get_theme()->graph_border1_color,
+		plugin->get_theme()->graph_border2_color, 
+		window->get_bg_color());
+
+	set_line_dashes(1);
+	set_color(plugin->get_theme()->graph_grid_color);
+	
+	for( int i = 1; i < divisions; i++ ) {
+		int y = graph_y + graph_h * i / divisions;
+		draw_line(graph_x, y, graph_x + graph_w, y);
+// 0db 
+		if( i == 1 ) {
+			draw_line(graph_x, y + 1, graph_x + graph_w, y + 1);
+		}
+		
+		int x = graph_x + graph_w * i / divisions;
+		draw_line(x, graph_y, x, graph_y + graph_h);
+// 0db 
+		if( i == divisions - 1 ) {
+			draw_line(x + 1, graph_y, x + 1, graph_y + graph_h);
+		}
+	}
+	set_line_dashes(0);
+
+
+	set_font(MEDIUMFONT);
+	int border = plugin->get_theme()->widget_border; 
+	draw_text(border, get_h() / 2, _("Output"));
+	int tx = get_w() / 2 - get_text_width(MEDIUMFONT, _("Input")) / 2;
+	int ty = get_h() - get_text_height(MEDIUMFONT, _("Input")) - border;
+	draw_text(tx, ty, _("Input"));
+
+	for( int pass = 0; pass < 2; pass++ ) {
+		for( int band = 0; band < config->total_bands; band++ ) {
+// draw the active band on top of the others
+			if( band == config->current_band && pass == 0 ||
+				band != config->current_band && pass == 1 ) {
+				continue;
+			}
+
+			if( band == config->current_band ) {
+				set_color(plugin->get_theme()->graph_active_color);
+				set_line_width(2);
+			}
+			else {
+				set_color(plugin->get_theme()->graph_inactive_color);
+				set_line_width(1);
+			}
+
+// draw the line
+			int x1 = graph_x, y1 = x_to_y(band, x1);
+			for( int i=0; ++i <= graph_w; ) {
+				int x2 = x1+1, y2 = x_to_y(band, x2);
+				draw_line(x1,y1, x2,y2);
+				x1 = x2;  y1 = y2;
+			}
+
+			set_line_width(1);
+
+// draw the points
+			if( band == config->current_band ) {
+				ArrayList<compressor_point_t> &levels = config->bands[band].levels;
+				for( int i = 0; i < levels.size(); i++ ) {
+					double x_db = config->get_x(band, i);
+					double y_db = config->get_y(band, i);
+
+					int x = db_to_x(x_db);
+					int y = db_to_y(y_db);
+
+					if( i == current_point ) {
+						draw_box(x - POINT_W / 2, y - POINT_W / 2, POINT_W, POINT_W);
+					}
+					else {
+						draw_rectangle(x - POINT_W / 2, y - POINT_W / 2, POINT_W, POINT_W);
+					}
+				}
+			}
+		}
+	}
+	
+	flash();
+}
+
+int CompressorCanvasBase::button_press_event()
+{
+// Check existing points
+	if( is_event_win() && 
+		cursor_inside() ) {
+		if( get_buttonpress() == 3 ) {
+			menu->activate_menu();
+			return 1;
+		}
+		int band = config->current_band;
+		ArrayList<compressor_point_t> &levels = config->bands[band].levels;
+		for( int i=0; i<levels.size(); ++i ) {
+			double x_db = config->get_x(config->current_band, i);
+			double y_db = config->get_y(config->current_band, i);
+
+			int x = db_to_x(x_db);
+			int y = db_to_y(y_db);
+
+			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;
+			}
+		}
+
+		if( get_cursor_x() >= graph_x &&
+		    get_cursor_x() < graph_x + graph_w &&
+		    get_cursor_y() >= graph_y &&
+		    get_cursor_y() < graph_y + graph_h ) {
+// Create new point
+			double x_db = x_to_db(get_cursor_x());
+			double y_db = y_to_db(get_cursor_y());
+
+			current_point = config->set_point(config->current_band, x_db, y_db);
+			current_operation = DRAG;
+			update_window();
+			plugin->send_configure_change();
+			return 1;
+		}
+	}
+	return 0;
+}
+
+int CompressorCanvasBase::button_release_event()
+{
+	int band = config->current_band;
+	ArrayList<compressor_point_t> &levels = config->bands[band].levels;
+
+	if( current_operation == DRAG ) {
+		if( current_point > 0 ) {
+			if( levels[current_point].x < levels[current_point-1].x ) {
+				config->remove_point(config->current_band, current_point);
+			}
+		}
+
+		if( current_point < levels.size()-1 ) {
+			if( levels[current_point].x >= levels[current_point + 1].x ) {
+				config->remove_point(config->current_band, current_point);
+			}
+		}
+
+		update_window();
+		plugin->send_configure_change();
+		current_operation = NONE;
+		return 1;
+	}
+
+	return 0;
+}
+
+int CompressorCanvasBase::cursor_motion_event()
+{
+	int band = config->current_band;
+	ArrayList<compressor_point_t> &levels = config->bands[band].levels;
+
+	if( current_operation == DRAG ) {
+		int x = get_cursor_x();
+		int y = get_cursor_y();
+		double x_db = x_to_db(x);
+		double y_db = y_to_db(y);
+		
+		if( shift_down() ) {
+			const int grid_precision = 6;
+			x_db = config->max_db + (double)(grid_precision * (int)((x_db - config->max_db) / grid_precision - 0.5));
+			y_db = config->max_db + (double)(grid_precision * (int)((y_db - config->max_db) / grid_precision - 0.5));
+		}
+		
+		
+//printf("CompressorCanvasBase::cursor_motion_event %d x=%d y=%d x_db=%f y_db=%f\n", 
+//__LINE__, x, y, x_db, y_db);
+		levels[current_point].x = x_db;
+		levels[current_point].y = y_db;
+		update_window();
+		plugin->send_configure_change();
+		return 1;
+	}
+	else
+// Change cursor over points
+	if( is_event_win() && cursor_inside() ) {
+		int new_cursor = CROSS_CURSOR;
+
+		for( int i = 0; i < levels.size(); i++ ) {
+			double x_db = config->get_x(config->current_band, i);
+			double y_db = config->get_y(config->current_band, i);
+
+			int x = db_to_x(x_db);
+			int y = db_to_y(y_db);
+
+			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;
+			}
+		}
+
+// out of active area
+		if( get_cursor_x() >= graph_x + graph_w ||
+			get_cursor_y() < graph_y ) {
+			new_cursor = ARROW_CURSOR;
+		}
+
+		if( new_cursor != get_cursor() ) {
+			set_cursor(new_cursor, 0, 1);
+		}
+	}
+	return 0;
+}
+
+
+void CompressorCanvasBase::update_window()
+{
+	printf("CompressorCanvasBase::update_window %d empty\n", __LINE__);
+}
+
+
+int CompressorCanvasBase::is_dragging()
+{
+	return current_operation == DRAG;
+}
+
+
+CompressorClientFrame::CompressorClientFrame()
+{
+	type = -1;
+	band = 0;
+}
+CompressorClientFrame::~CompressorClientFrame()
+{
+}
+
+CompressorFreqFrame::CompressorFreqFrame()
+{
+	type = FREQ_COMPRESSORFRAME;
+	data = 0;      data_size = 0;
+	freq_max = 0;  time_max = 0;
+	nyquist = 0;
+}
+CompressorFreqFrame::~CompressorFreqFrame()
+{
+	delete [] data;
+}
+
+CompressorGainFrame::CompressorGainFrame()
+{
+	type = GAIN_COMPRESSORFRAME;
+	gain = 0;
+	level = 0;
+}
+CompressorGainFrame::~CompressorGainFrame()
+{
+}
+
+CompressorEngine::CompressorEngine(CompressorConfigBase *config,
+	int band)
+{
+	this->config = config;
+	this->band = band;
+	reset();
+}
+
+CompressorEngine::~CompressorEngine()
+{
+}
+
+
+void CompressorEngine::reset()
+{
+	slope_samples = 0;
+	slope_current_sample = 0;
+	peak_samples = 0;
+	slope_value2 = 1.0;
+	slope_value1 = 1.0;
+	slope_samples = 0;
+	slope_current_sample = 0;
+	current_value = 1.0;
+	gui_frame_samples = 2048;
+	gui_max_gain = 1.0;
+	gui_frame_counter = 0;
+}
+
+
+void CompressorEngine::calculate_ranges(int *attack_samples,
+	int *release_samples,
+	int *preview_samples,
+	int sample_rate)
+{
+	BandConfig *band_config = &config->bands[band];
+	*attack_samples = labs(Units::round(band_config->attack_len * sample_rate));
+	*release_samples = Units::round(band_config->release_len * sample_rate);
+	CLAMP(*attack_samples, 1, 1000000);
+	CLAMP(*release_samples, 1, 1000000);
+	*preview_samples = MAX(*attack_samples, *release_samples);
+}
+
+
+void CompressorEngine::process(Samples **output_buffer,
+	Samples **input_buffer,
+	int size,
+	int sample_rate,
+	int channels,
+	int64_t start_position)
+{
+	BandConfig *band_config = &config->bands[band];
+	int attack_samples;
+	int release_samples;
+	int preview_samples;
+	int trigger = CLIP(config->trigger, 0, channels - 1);
+
+	gui_gains.remove_all();
+	gui_levels.remove_all();
+	gui_offsets.remove_all();
+	
+	calculate_ranges(&attack_samples,
+		&release_samples,
+		&preview_samples,
+		sample_rate);
+	if( slope_current_sample < 0 ) slope_current_sample = slope_samples;
+
+	double *trigger_buffer = input_buffer[trigger]->get_data();
+
+	for( int i = 0; i < size; i++ ) {
+		double current_slope = (slope_value2 - slope_value1) /
+			slope_samples;
+
+// maximums in the 2 time ranges
+		double attack_slope = -0x7fffffff;
+		double attack_sample = -1;
+		int attack_offset = -1;
+		int have_attack_sample = 0;
+		double release_slope = -0x7fffffff;
+		double release_sample = -1;
+		int release_offset = -1;
+		int have_release_sample = 0;
+		if( slope_current_sample >= slope_samples ) {
+// start new line segment
+			for( int j = 1; j < preview_samples; j++ ) {
+				GET_TRIGGER(input_buffer[channel]->get_data(), i + j)
+				double new_slope = (sample - current_value) / j;
+				if( j < attack_samples && new_slope >= attack_slope ) {
+					attack_slope = new_slope;
+					attack_sample = sample;
+					attack_offset = j;
+					have_attack_sample = 1;
+				}
+				
+				if( j < release_samples && 
+					new_slope <= 0 && 
+					new_slope > release_slope ) {
+					release_slope = new_slope;
+					release_sample = sample;
+					release_offset = j;
+					have_release_sample = 1;
+				}
+			}
+
+			slope_current_sample = 0;
+			if( have_attack_sample && attack_slope >= 0 ) {
+// attack
+				peak_samples = attack_offset;
+				slope_samples = attack_offset;
+				slope_value1 = current_value;
+				slope_value2 = attack_sample;
+				current_slope = attack_slope;
+//printf("CompressorEngine::process %d position=%ld slope=%f samples=%d\n", 
+//__LINE__, start_position + i, current_slope, slope_samples);
+			}
+			else
+			if( have_release_sample ) {
+// release
+				slope_samples = release_offset;
+//				slope_samples = release_samples;
+				peak_samples = release_offset;
+				slope_value1 = current_value;
+				slope_value2 = release_sample;
+				current_slope = release_slope;
+//printf("CompressorEngine::process %d position=%ld slope=%f\n", 
+//__LINE__, start_position + i, current_slope);
+			}
+			else {
+static int bug = 0;
+if( !bug ) {
+printf("CompressorEngine::process %d have neither attack nor release position=%ld attack=%f release=%f current_value=%f\n",
+__LINE__, start_position + i, attack_slope, release_slope, current_value); bug = 1;
+}
+			}
+		}
+		else {
+// check for new peak after the line segment
+			GET_TRIGGER(input_buffer[channel]->get_data(), i + attack_samples)
+			double new_slope = (sample - current_value) /
+				attack_samples;
+			if( current_slope >= 0 ) {
+				if( new_slope > current_slope ) {
+					peak_samples = attack_samples;
+					slope_samples = attack_samples;
+					slope_current_sample = 0;
+					slope_value1 = current_value;
+					slope_value2 = sample;
+					current_slope = new_slope;
+//printf("CompressorEngine::process %d position=%ld slope=%f\n", 
+//__LINE__, start_position + i, current_slope);
+				}
+			}
+			else
+// this strings together multiple release periods instead of 
+// approaching but never reaching the ending gain
+			if( current_slope < 0 ) {
+				if( sample > slope_value2 ) {
+					peak_samples = attack_samples;
+					slope_samples = release_samples;
+					slope_current_sample = 0;
+					slope_value1 = current_value;
+					slope_value2 = sample;
+					new_slope = (sample - current_value) /
+						release_samples;
+					current_slope = new_slope;
+//printf("CompressorEngine::process %d position=%ld slope=%f\n", 
+//__LINE__, start_position + i, current_slope);
+				}
+//				else
+//				 {
+//					 GET_TRIGGER(input_buffer[channel]->get_data(), i + release_samples)
+//					 new_slope = (sample - current_value) /
+// 						release_samples;
+//					 if( new_slope < current_slope &&
+//						 slope_current_sample >= peak_samples )
+//					 {
+//						 peak_samples = release_samples;
+//						 slope_samples = release_samples;
+//						 slope_current_sample = 0;
+//						 slope_value1 = current_value;
+//						 slope_value2 = sample;
+// 						current_slope = new_slope;
+// printf("CompressorEngine::process %d position=%ld slope=%f\n", 
+// __LINE__, start_position + i, current_slope);
+//					 }
+//				 }
+			}
+		}
+
+// Update current value and multiply gain
+		slope_current_sample++;
+		current_value = slope_value1 +
+			(slope_value2 - slope_value1) * 
+			slope_current_sample / 
+			slope_samples;
+
+		if( config->smoothing_only ) {
+			for( int j = 0; j < channels; j++ ) {
+				output_buffer[j]->get_data()[i] = current_value * 2 - 1;
+			}
+		}
+		else {
+			double gain = 1.0;
+
+			if( band_config->bypass ) {
+				gain = 1.0;
+			}
+			else {
+				gain = config->calculate_gain(band, current_value);
+			}
+
+// update the GUI frames
+			if( fabs(gain - 1.0) > fabs(gui_max_gain - 1.0) ) {
+				gui_max_gain = gain;
+			}
+//if( !EQUIV(gain, 1.0) ) printf("CompressorEngine::process %d gain=%f\n", __LINE__, gain);
+
+// calculate the input level to draw.  Should it be the trigger or a channel?
+			GET_TRIGGER(input_buffer[channel]->get_data(), i);
+			if( sample > gui_max_level ) {
+				gui_max_level = sample;
+			}
+
+			gui_frame_counter++;
+			if( gui_frame_counter > gui_frame_samples ) {
+//if( !EQUIV(gui_frame_max, 1.0) ) printf("CompressorEngine::process %d offset=%d gui_frame_max=%f\n", __LINE__, i, gui_frame_max);
+				gui_gains.append(gui_max_gain);
+				gui_levels.append(gui_max_level);
+				gui_offsets.append((double)i / sample_rate);
+				gui_max_gain = 1.0;
+				gui_max_level = 0.0;
+				gui_frame_counter = 0;
+			}
+
+			for( int j = 0; j < channels; j++ ) {
+				output_buffer[j]->get_data()[i] = input_buffer[j]->get_data()[i] * gain;
+			}
+		}
+	}
+}
+
+
+CompressorPopup::CompressorPopup(CompressorCanvasBase *canvas)
+ : BC_PopupMenu(0, 0, 0, "", 0)
+{
+	this->canvas = canvas;
+}
+
+CompressorPopup::~CompressorPopup()
+{
+}
+
+	
+void CompressorPopup::create_objects()
+{
+	add_item(new CompressorCopy(this));
+	add_item(new CompressorPaste(this));
+	add_item(new CompressorClearGraph(this));
+}
+
+
+CompressorCopy::CompressorCopy(CompressorPopup *popup)
+ : BC_MenuItem(_("Copy graph"))
+{
+	this->popup = popup;
+}
+
+
+CompressorCopy::~CompressorCopy()
+{
+}
+
+int CompressorCopy::handle_event()
+{
+	FileXML output;
+	CompressorConfigBase *config = popup->canvas->config;
+	config->bands[config->current_band].save_data(&output, 0, 0);
+	output.terminate_string();
+	char *cp = output.string(); 
+	popup->to_clipboard(cp, strlen(cp), SECONDARY_SELECTION);
+	return 1;
+}
+
+
+CompressorPaste::CompressorPaste(CompressorPopup *popup)
+ : BC_MenuItem(_("Paste graph"))
+{
+	this->popup = popup;
+}
+
+
+CompressorPaste::~CompressorPaste()
+{
+}
+
+int CompressorPaste::handle_event()
+{
+	int len = popup->get_clipboard()->clipboard_len(SECONDARY_SELECTION);
+	if( len ) {
+		CompressorConfigBase *config = popup->canvas->config;
+		char *string = new char[len + 1];
+		popup->get_clipboard()->from_clipboard(string, len, SECONDARY_SELECTION);
+		
+		FileXML xml;
+		xml.read_from_string(string);
+		delete [] string;
+		int result = 0, got_it = 0;
+		while( !(result = xml.read_tag()) ) {
+			if( xml.tag.title_is("COMPRESSORBAND") ) {
+				int band = config->current_band;
+				BandConfig *band_config = &config->bands[band];
+				band_config->read_data(&xml, 0);
+				got_it = 1;
+				break;
+			}
+		}
+		
+		if( got_it ) {
+			popup->canvas->update();
+			PluginClient *plugin = popup->canvas->plugin;
+			plugin->send_configure_change();
+		}
+	}
+	return 1;
+}
+
+
+CompressorClearGraph::CompressorClearGraph(CompressorPopup *popup)
+ : BC_MenuItem(_("Clear graph"))
+{
+	this->popup = popup;
+}
+
+
+CompressorClearGraph::~CompressorClearGraph()
+{
+}
+
+int CompressorClearGraph::handle_event()
+{
+	CompressorConfigBase *config = popup->canvas->config;
+	config->bands[config->current_band].levels.remove_all();
+	popup->canvas->update();
+	PluginClient *plugin = popup->canvas->plugin;
+	plugin->send_configure_change();
+	return 1;
+}
+
diff --git a/cinelerra-5.1/cinelerra/compressortools.h b/cinelerra-5.1/cinelerra/compressortools.h
new file mode 100644
index 00000000..abfb098d
--- /dev/null
+++ b/cinelerra-5.1/cinelerra/compressortools.h
@@ -0,0 +1,329 @@
+/*
+ * CINELERRA
+ * 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
+ * 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
+ * 
+ */
+
+
+// Base classes for compressors
+
+#ifndef COMPRESSORTOOLS_H
+#define COMPRESSORTOOLS_H
+
+
+#include "guicast.h"
+#include "pluginaclient.h"
+#include "samples.inc"
+
+
+#define MIN_ATTACK -10
+#define MAX_ATTACK 10
+#define MIN_DECAY 0
+#define MAX_DECAY 255
+#define MIN_TRIGGER 0
+#define MAX_TRIGGER 255
+// range of the meter
+#define MIN_GAIN_CHANGE -20
+#define MAX_GAIN_CHANGE 20
+
+
+class CompressorConfigBase;
+class CompressorPopup;
+
+
+// get sample from trigger buffer
+#define GET_TRIGGER(buffer, offset) \
+double sample = 0; \
+switch(config->input) \
+{ \
+	case CompressorConfigBase::MAX: \
+	{ \
+		double max = 0; \
+		for(int channel = 0; channel < channels; channel++) \
+		{ \
+			sample = fabs((buffer)[offset]); \
+			if(sample > max) max = sample; \
+		} \
+		sample = max; \
+		break; \
+	} \
+ \
+	case CompressorConfigBase::TRIGGER: \
+		sample = fabs(trigger_buffer[offset]); \
+		break; \
+ \
+	case CompressorConfigBase::SUM: \
+	{ \
+		double max = 0; \
+		for(int channel = 0; channel < channels; channel++) \
+		{ \
+			sample = fabs((buffer)[offset]); \
+			max += sample; \
+		} \
+		sample = max; \
+		break; \
+	} \
+}
+
+
+
+
+
+typedef struct
+{
+// DB from min_db - 0
+	double x, y;
+} compressor_point_t;
+
+class BandConfig
+{
+public:
+	BandConfig();
+	virtual ~BandConfig();
+
+	void copy_from(BandConfig *src);
+	int equiv(BandConfig *src);
+	void boundaries(CompressorConfigBase *base);
+	void save_data(FileXML *xml, int number, int do_multiband);
+	void read_data(FileXML *xml, int do_multiband);
+
+	ArrayList<compressor_point_t> levels;
+	int solo;
+	int bypass;
+// units of seconds
+//	double readahead_len;
+	double attack_len;
+	double release_len;
+
+// upper frequency in Hz
+	int freq;
+};
+
+class CompressorConfigBase
+{
+public:
+	CompressorConfigBase(int total_bands);
+	virtual ~CompressorConfigBase();
+
+	virtual void copy_from(CompressorConfigBase &that);
+	virtual int equivalent(CompressorConfigBase &that);
+	void boundaries();
+	
+	void remove_point(int band, int number);
+	int set_point(int band, double x, double y);
+	double calculate_db(int band, double x);
+	double get_x(int band, int number);
+	double get_y(int band, int number);
+	double calculate_gain(int band, double input);
+
+// Calculate linear output from linear input
+	double calculate_output(int band, double x);
+
+// min DB of the graph
+	double min_db;
+// max DB of the graph
+	double max_db;
+
+	BandConfig *bands;
+	int total_bands;
+	int current_band;
+	int trigger;
+	int input;
+	enum
+	{
+		TRIGGER,
+		MAX,
+		SUM
+	};
+	
+	double min_value;
+//	double min_x, min_y;
+//	double max_x, max_y;
+	int smoothing_only;
+};
+
+
+class CompressorCanvasBase : public BC_SubWindow
+{
+public:
+	CompressorCanvasBase(CompressorConfigBase *config, PluginClient *plugin,
+		PluginClientWindow *window, int x, int y, int w, int h);
+	virtual ~CompressorCanvasBase();
+	int is_dragging();
+	int button_press_event();
+	int button_release_event();
+	int cursor_motion_event();
+	void create_objects();
+	void draw_scales();
+	void update();
+	int x_to_y(int band, int x);
+	int db_to_x(double db);
+	int db_to_y(double db);
+	double x_to_db(int x);
+	double y_to_db(int y);
+
+	virtual void update_window();
+
+	enum { NONE, DRAG };
+
+// clickable area of canvas
+	int graph_x, graph_y;
+	int graph_w, graph_h;
+	int current_point;
+	int current_operation;
+	int divisions;
+	int subdivisions;
+	CompressorConfigBase *config;
+	CompressorPopup *menu;
+	PluginClient *plugin;
+	PluginClientWindow *window;
+};
+
+
+#define FREQ_COMPRESSORFRAME 0
+#define GAIN_COMPRESSORFRAME 1
+// used in eqcanvas, compressortools,
+//  plugins: compressor, compressormulti
+class CompressorClientFrame : public PluginClientFrame
+{
+public:
+	CompressorClientFrame();
+	~CompressorClientFrame();
+	int type, band;
+};
+
+class CompressorFreqFrame : public CompressorClientFrame
+{
+public:
+	CompressorFreqFrame();
+	~CompressorFreqFrame();
+
+	double freq_max, time_max;
+	double *data;
+	int data_size;
+	int nyquist;
+};
+
+class CompressorGainFrame : public CompressorClientFrame
+{
+public:
+	CompressorGainFrame();
+	~CompressorGainFrame();
+
+	double gain, level;
+};
+
+class CompressorEngine
+{
+public:
+	CompressorEngine(CompressorConfigBase *config,
+		int band);
+	~CompressorEngine();
+	
+	void reset();
+	void calculate_ranges(int *attack_samples,
+		int *release_samples,
+		int *preview_samples,
+		int sample_rate);
+	void process(Samples **output_buffer,
+		Samples **input_buffer,
+		int size,
+		int sample_rate,
+		int channels,
+		int64_t start_position);
+
+	CompressorConfigBase *config;
+	int band;
+// the current line segment defining the smooth signal level
+// starting input value of line segment
+	double slope_value1;
+// ending input value of line segment
+	double slope_value2;
+// samples comprising the line segment
+	int slope_samples;
+// samples from the start of the line to the peak that determined the slope
+	int peak_samples;
+// current sample from 0 to slope_samples
+	int slope_current_sample;
+// current value in the line segment
+	double current_value;
+// gain change values to draw on the GUI
+	ArrayList<double> gui_gains;
+// input levels to draw on the GUI
+	ArrayList<double> gui_levels;
+// which second in process() the gui_values came from
+	ArrayList<double> gui_offsets;
+// samples between gui_values.  Set by the user.
+	int gui_frame_samples;
+// temporaries
+	int gui_frame_counter;
+	double gui_max_gain;
+	double gui_max_level;
+};
+
+
+class CompressorCopy : public BC_MenuItem
+{
+public:
+	CompressorCopy(CompressorPopup *popup);
+	~CompressorCopy();
+	int handle_event();
+	CompressorPopup *popup;
+};
+
+class CompressorPaste : public BC_MenuItem
+{
+public:
+	CompressorPaste(CompressorPopup *popup);
+	~CompressorPaste();
+	int handle_event();
+	CompressorPopup *popup;
+};
+
+class CompressorClearGraph : public BC_MenuItem
+{
+public:
+	CompressorClearGraph(CompressorPopup *popup);
+	~CompressorClearGraph();
+	int handle_event();
+	CompressorPopup *popup;
+};
+
+class CompressorPopup : public BC_PopupMenu
+{
+public:
+	CompressorPopup(CompressorCanvasBase *canvas);
+	~CompressorPopup();
+	
+	void create_objects();
+	
+	
+	CompressorCanvasBase *canvas;
+};
+
+#define GRAPH_BG_COLOR		0x559977
+#define GRAPH_BORDER1_COLOR	0xeeaa44
+#define GRAPH_BORDER2_COLOR	0xeeaaff
+#define GRAPH_GRID_COLOR	0xeeffcc
+#define GRAPH_ACTIVE_COLOR	0x99cc77
+#define GRAPH_INACTIVE_COLOR	0x666688
+
+#endif
+
+
+
+
diff --git a/cinelerra-5.1/cinelerra/compressortools.inc b/cinelerra-5.1/cinelerra/compressortools.inc
new file mode 100644
index 00000000..58810d9c
--- /dev/null
+++ b/cinelerra-5.1/cinelerra/compressortools.inc
@@ -0,0 +1,22 @@
+#ifndef __COMPRESSORTOOLS_INC__
+#define __COMPRESSORTOOLS_INC__
+
+class BandConfig;
+class CompressorConfigBase;
+class CompressorClientFrame;
+class CompressorFreqFrame;
+class CompressorGainFrame;
+class CompressorEngine;
+class CompressorCopy;
+class CompressorPaste;
+class CompressorClearGraph;
+class CompressorPopup;
+
+#define GRAPH_BG_COLOR		0x559977
+#define GRAPH_BORDER1_COLOR	0xeeaa44
+#define GRAPH_BORDER2_COLOR	0xeeaaff
+#define GRAPH_GRID_COLOR	0xeeffcc
+#define GRAPH_ACTIVE_COLOR	0x99cc77
+#define GRAPH_INACTIVE_COLOR	0x666688
+
+#endif
diff --git a/cinelerra-5.1/cinelerra/cwindowgui.h b/cinelerra-5.1/cinelerra/cwindowgui.h
index 7dbfde86..14e0ebfe 100644
--- a/cinelerra-5.1/cinelerra/cwindowgui.h
+++ b/cinelerra-5.1/cinelerra/cwindowgui.h
@@ -263,9 +263,8 @@ class CWindowTransport : public PlayTransport
 {
 public:
 	CWindowTransport(MWindow *mwindow,
-		CWindowGUI *gui,
-		int x,
-		int y);
+		CWindowGUI *gui, int x, int y);
+	bool use_mixers() { return true; }
 	EDL* get_edl();
 	void goto_start();
 	void goto_end();
diff --git a/cinelerra-5.1/cinelerra/eqcanvas.C b/cinelerra-5.1/cinelerra/eqcanvas.C
new file mode 100644
index 00000000..24028889
--- /dev/null
+++ b/cinelerra-5.1/cinelerra/eqcanvas.C
@@ -0,0 +1,248 @@
+#include "clip.h"
+#include "eqcanvas.h"
+#include "mwindow.h"
+#include "pluginaclient.h"
+#include "theme.h"
+
+#define graph_bg_color 0x559977
+#define graph_border1_color 0xeeaa44
+#define graph_border2_color 0xeeaaff
+#define graph_grid_color 0xeeffcc
+#define graph_active_color 0x99cc77
+#define graph_inactive_color 0x666688
+
+EQCanvas::EQCanvas(BC_WindowBase *parent,
+		int x, int y, int w, int h,
+		float min_db, float max_db)
+{
+	this->parent = parent;
+	this->x = x;  this->y = y;
+	this->w = w;  this->h = h;
+	this->min_db = min_db;
+	this->max_db = max_db;
+	canvas = 0;
+
+	minor_divisions = 5;
+	freq_divisions = 5;
+}
+
+EQCanvas::~EQCanvas()
+{
+	delete canvas;
+}
+
+void EQCanvas::initialize()
+{
+	int big_xtick = xS(10), big_ytick = yS(10);
+	int small_xtick = xS(5), small_ytick = yS(5);
+	int tiny_xtick = xS(2);
+	int db_width = parent->get_text_width(SMALLFONT, "-00", -1) + big_xtick;
+	int freq_height = parent->get_text_ascent(SMALLFONT);
+
+	canvas_x = x + db_width;
+	canvas_y = y;
+	canvas_w = w - db_width;
+	canvas_h = h - freq_height - big_ytick;
+	parent->add_subwindow(canvas = new BC_SubWindow(
+		canvas_x, canvas_y, canvas_w, canvas_h, BLACK));
+
+// Draw canvas titles
+// DB
+	parent->set_font(SMALLFONT);
+	int ascent = parent->get_text_ascent(SMALLFONT);
+// DB per minor division
+	db_per_division = 1;
+	pixels_per_division = (float)canvas_h / (max_db - min_db) * db_per_division;
+// increase the DB per minor division until they fit
+//printf("EQCanvas::initialize %d pixels_per_division=%f\n", __LINE__, pixels_per_division);
+	while( pixels_per_division < 5 ) {
+		db_per_division *= 2;
+		pixels_per_division = (float)canvas_h / (max_db - min_db) * db_per_division;
+	}
+	total_divisions = (int)((max_db - min_db) / db_per_division);
+
+	char string[BCTEXTLEN];
+	for( int i = 0; i <= total_divisions; i++ ) {
+		int y1 = canvas_y + (int)(i * pixels_per_division);
+		int y2 = y1 + ascent;
+		int x2 = canvas_x - big_xtick;
+		int x3 = canvas_x - tiny_xtick;
+		int x4 = x3 - small_xtick;
+
+		if( !(i % minor_divisions) ||
+			i == total_divisions ) {
+			if( i == total_divisions ) {
+				sprintf(string, "oo");
+			}
+			else {
+				sprintf(string, "%d", (int)(max_db - i * db_per_division));
+			}
+
+			parent->set_color(BLACK);
+			int text_w = parent->get_text_width(SMALLFONT, string, -1);
+			int x1 = canvas_x - big_xtick - text_w;
+			parent->set_color(parent->get_resources()->default_text_color);
+			parent->draw_text(x1, y2, string);
+			parent->draw_line(x2, y1, x3, y1);
+		}
+		else {
+			parent->draw_line(x4, y1, x3, y1);
+		}
+	}
+
+// freq
+	for( int i = 0; i <= freq_divisions; i++ ) {
+		int freq = Freq::tofreq(i * TOTALFREQS / freq_divisions);
+		sprintf(string, "%d", freq);
+		int x1 = canvas_x + i * canvas_w / freq_divisions;
+		int x2 = x1 - parent->get_text_width(SMALLFONT, string);
+		int y1 = canvas_y + canvas_h;
+		int y2 = y1 + big_ytick;
+		int y3 = y1 + small_ytick;
+		int y4 = y2 + parent->get_text_ascent(SMALLFONT);
+
+// 		parent->set_color(BLACK);
+// 		parent->draw_text(x2 + 1, y4 + 1, string);
+// 		parent->draw_line(x1 + 1, y1 + 1, x1 + 1, y2 + 1);
+// 		parent->set_color(RED);
+		parent->set_color(parent->get_resources()->default_text_color);
+		parent->draw_text(x2, y4, string);
+		parent->draw_line(x1, y1, x1, y2);
+
+		if( i < freq_divisions ) {
+			for( int j = 0; j < minor_divisions; j++ ) {
+				int x3 = (int)(x1 +
+					(canvas_w / freq_divisions) -
+					exp(-(double)j * 0.7) *
+					(canvas_w / freq_divisions));
+// 				parent->set_color(BLACK);
+// 				parent->draw_line(x3 + 1, y1 + 1, x3 + 1, y3 + 1);
+// 				parent->set_color(RED);
+				parent->set_color(parent->get_resources()->default_text_color);
+				parent->draw_line(x3, y1, x3, y3);
+			}
+		}
+	}
+
+	draw_grid();
+}
+
+
+void EQCanvas::draw_grid()
+{
+	canvas->set_line_dashes(1);
+	canvas->set_color(graph_grid_color);
+	for( int i = minor_divisions; i < total_divisions; i += minor_divisions ) {
+		int y = (int)(i * pixels_per_division);
+		canvas->draw_line(0, y, canvas_w, y);
+	}
+//	for( int i = 1; i < major_divisions; i++ ) {
+//		int y = canvas_h - i * canvas_h / major_divisions;
+//		canvas->draw_line(0, y, canvas_w, y);
+//	}
+	for( int i = 1; i < freq_divisions; i++ ) {
+		int x = i * canvas_w / freq_divisions;
+		canvas->draw_line(x, 0, x, canvas_h);
+	}
+	canvas->set_line_dashes(0);
+}
+
+void EQCanvas::update_spectrogram(CompressorFreqFrame *frame,
+		int offset, int size, int window_size)
+{
+//if( frame ) printf("EQCanvas::update_spectrogram %d frame->freq_max=%f frame->data=%p\n",
+// __LINE__, frame->freq_max, frame->data);
+	canvas->set_color(graph_bg_color);
+	canvas->draw_box(0, 0, canvas->get_w(), canvas->get_h());
+	draw_grid();
+
+// Draw it
+	if( frame && !EQUIV(frame->freq_max, 0.0) && frame->data ) {
+		int y1 = 0, y2 = 0;
+		if( offset < 0 ) {
+			offset = 0;
+			size = frame->data_size;
+			window_size = frame->data_size * 2;
+		}
+
+		canvas->set_color(graph_inactive_color);
+		if( !EQUIV(frame->freq_max, 0) ) {
+			for( int i = 0; i < canvas->get_w(); i++ ) {
+				int freq = Freq::tofreq(i * TOTALFREQS / canvas->get_w());
+
+				if( freq < frame->nyquist ) {
+					int index = offset +
+						(int64_t)freq * (int64_t)window_size / 2 /
+						frame->nyquist;
+
+					if( index < frame->data_size ) {
+						double magnitude = frame->data[index] /
+							frame->freq_max * frame->time_max;
+
+						y2 = (int)(canvas->get_h() -
+							(DB::todb(magnitude) - INFINITYGAIN) *
+							canvas->get_h() /
+							-INFINITYGAIN);
+						CLAMP(y2, 0, canvas->get_h() - 1);
+						if( i > 0 ) {
+							canvas->draw_line(i - 1, y1, i, y2);
+						}
+						y1 = y2;
+					}
+				}
+				else {
+//					 printf("EQCanvas::update_spectrogram %d i=%d freq=%d nyquist=%d\n",
+//						 __LINE__, i, freq, frame->nyquist);
+				}
+			}
+		}
+	}
+
+}
+
+
+void EQCanvas::draw_envelope(double *envelope,
+		int samplerate, int window_size, int is_top, int flash_it)
+{
+	int niquist = samplerate / 2;
+
+	if( is_top ) {
+		canvas->set_color(graph_active_color);
+		canvas->set_line_width(2);
+	}
+	else {
+		canvas->set_color(graph_inactive_color);
+		canvas->set_line_width(1);
+	}
+
+	int y1;
+	for( int i = 0; i < canvas->get_w(); i++ ) {
+		int freq = Freq::tofreq(i * TOTALFREQS / canvas->get_w());
+		int index = (int64_t)freq * (int64_t)window_size / 2 / niquist;
+		if( freq < niquist && index < window_size / 2 ) {
+			double mag = envelope[index];
+			int y2 = (int)(DB::todb(mag) * canvas->get_h() / INFINITYGAIN);
+
+			if( y2 >= canvas->get_h() ) {
+				y2 = canvas->get_h() - 1;
+			}
+
+			if( i > 0 ) {
+				canvas->draw_line(i - 1, y1, i, y2);
+			}
+			y1 = y2;
+		}
+		else
+		if( i > 0 ) {
+			int y2 = canvas->get_h() - 1;
+			canvas->draw_line(i - 1, y1, i, y2);
+			y1 = y2;
+		}
+	}
+
+	canvas->set_line_width(1);
+	if( flash_it ) {
+		canvas->flash(1);
+	}
+}
+
diff --git a/cinelerra-5.1/cinelerra/eqcanvas.h b/cinelerra-5.1/cinelerra/eqcanvas.h
new file mode 100644
index 00000000..318c99a7
--- /dev/null
+++ b/cinelerra-5.1/cinelerra/eqcanvas.h
@@ -0,0 +1,62 @@
+/*
+ * CINELERRA
+ * 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
+ * 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
+ * 
+ */
+
+
+// A canvas for drawing a spectrogram on top of a filter envelope
+
+#ifndef EQCANVAS_H
+#define EQCANVAS_H
+
+#include "compressortools.h"
+#include "guicast.h"
+#include "pluginclient.inc"
+#include "pluginaclient.h"
+
+class EQCanvas
+{
+public:
+	EQCanvas(BC_WindowBase *parent, 
+		int x, int y, int w, int h, 
+		float min_db, float max_db);
+	virtual ~EQCanvas();
+	
+	void initialize();
+	void draw_grid();
+	void update_spectrogram(CompressorFreqFrame *frame, 
+		int offset, int size, int window_size);
+	void draw_envelope(double *envelope, 
+		int samplerate, int window_size, int is_top, int flash_it);
+	
+	BC_WindowBase *parent;
+	BC_SubWindow *canvas;
+	int x, y, w, h;
+	int canvas_x, canvas_y;
+	int canvas_w, canvas_h;
+	float min_db, max_db;
+// divisions for the frequency
+	int freq_divisions;
+// divisions for the DB
+	int db_per_division;
+	float pixels_per_division;
+	int minor_divisions;
+	int total_divisions;
+};
+
+#endif // EQCANVAS_H
diff --git a/cinelerra-5.1/cinelerra/eqcanvas.inc b/cinelerra-5.1/cinelerra/eqcanvas.inc
new file mode 100644
index 00000000..10ecc7a7
--- /dev/null
+++ b/cinelerra-5.1/cinelerra/eqcanvas.inc
@@ -0,0 +1,12 @@
+#ifndef EQCANVAS_INC
+#define EQCANVAS_INC
+
+
+
+class EQCanvas;
+
+
+
+#endif
+
+
diff --git a/cinelerra-5.1/cinelerra/formattools.C b/cinelerra-5.1/cinelerra/formattools.C
index 2bf178ab..03d8e83e 100644
--- a/cinelerra-5.1/cinelerra/formattools.C
+++ b/cinelerra-5.1/cinelerra/formattools.C
@@ -388,7 +388,8 @@ void FormatTools::update_extension()
 			char *ptr1 = ptr;
 // change "qt" to "mov" since ffmpeg does not know qt
 			extension_ptr = asset->format != FILE_FFMPEG ? extensions.get(0) :
-				!strcmp(asset->fformat, "qt") ? "mov" : asset->fformat ;
+				!strcmp(asset->fformat, "qt" ) ||
+				!strcmp(asset->fformat, "pro" ) ? "mov" : asset->fformat ;
 			while(*extension_ptr != 0 && *extension_ptr != '/')
 				*ptr1++ = *extension_ptr++;
 			*ptr1 = 0;
diff --git a/cinelerra-5.1/cinelerra/fourier.C b/cinelerra-5.1/cinelerra/fourier.C
index 401a5faa..f78a45bd 100644
--- a/cinelerra-5.1/cinelerra/fourier.C
+++ b/cinelerra-5.1/cinelerra/fourier.C
@@ -109,7 +109,7 @@ int FFT::do_fft(int samples,	// must be a power of 2
 			int j = i, k = i+1; // cos(0)=1, sin(0)=0
 			rj = real_out[j];  ij = imag_out[j];
 			rk = real_out[k];  ik = imag_out[k];
-			real_out[j] += rk;      imag_out[j] += ik;
+			real_out[j] += rk;	  imag_out[j] += ik;
 			real_out[k] = rj - rk;  imag_out[k] = ij - ik;
 		}
 	}
@@ -119,12 +119,12 @@ int FFT::do_fft(int samples,	// must be a power of 2
 				int j = i, k = j+2; // cos(0)=1,sin(0)=0
 				rj = real_out[j];  ij = imag_out[j];
 				rk = real_out[k];  ik = imag_out[k];
-				real_out[j] += rk;      imag_out[j] += ik;
+				real_out[j] += rk;	  imag_out[j] += ik;
 				real_out[k] = rj - rk;  imag_out[k] = ij - ik;
 				j = i+1;  k = j+2; // cos(-pi/2)=0, sin(-pi/2)=-1
 				rj = real_out[j];  ij = imag_out[j];
 				rk = real_out[k];  ik = imag_out[k];
-				real_out[j] += ik;      imag_out[j] -= rk;
+				real_out[j] += ik;	  imag_out[j] -= rk;
 				real_out[k] = rj - ik;  imag_out[k] = ij + rk;
 			}
 		}
@@ -133,12 +133,12 @@ int FFT::do_fft(int samples,	// must be a power of 2
 				int j = i, k = j+2; // cos(0)=1,sin(0)=0
 				rj = real_out[j];  ij = imag_out[j];
 				rk = real_out[k];  ik = imag_out[k];
-				real_out[j] += rk;      imag_out[j] += ik;
+				real_out[j] += rk;	  imag_out[j] += ik;
 				real_out[k] = rj - rk;  imag_out[k] = ij - ik;
 				j = i+1;  k = j+2; // cos(pi/2)=0, sin(pi/2)=1
 				rj = real_out[j];  ij = imag_out[j];
 				rk = real_out[k];  ik = imag_out[k];
-				real_out[j] -= ik;      imag_out[j] += rk;
+				real_out[j] -= ik;	  imag_out[j] += rk;
 				real_out[k] = rj + ik;  imag_out[k] = ij - rk;
 			}
 		}
@@ -167,7 +167,7 @@ int FFT::do_fft(int samples,	// must be a power of 2
 				double rk = *rkp, ik = *ikp;
 				double tr = ar[0] * rk - ai[0] * ik;
 				double ti = ar[0] * ik + ai[0] * rk;
-				*rjp++ += tr;      *ijp++ += ti;
+				*rjp++ += tr;	  *ijp++ += ti;
 				*rkp++ = rj - tr;  *ikp++ = ij - ti;
 			}
 		}
@@ -204,7 +204,7 @@ unsigned int FFT::reverse_bits(unsigned int index, unsigned int bits)
 {
 	unsigned int i, rev;
 
-	for(i = rev = 0; i < bits; i++) {
+	for( i = rev = 0; i < bits; i++ ) {
 		rev = (rev << 1) | (index & 1);
 		index >>= 1;
 	}
@@ -259,8 +259,7 @@ unsigned int FFT::reverse_bits(unsigned int index, unsigned int bits)
 int FFT::symmetry(int size, double *freq_real, double *freq_imag)
 {
 	int h = size / 2;
-	for(int i = h + 1; i < size; i++)
-	{
+	for( int i = h + 1; i < size; i++ ) {
 		freq_real[i] = freq_real[size - i];
 		freq_imag[i] = -freq_imag[size - i];
 	}
@@ -273,6 +272,7 @@ int FFT::symmetry(int size, double *freq_real, double *freq_imag)
 
 CrossfadeFFT::CrossfadeFFT() : FFT()
 {
+	bands = 1;
 	reset();
 	window_size = 4096;
 }
@@ -288,6 +288,8 @@ int CrossfadeFFT::reset()
 	output_buffer = 0;
 	freq_real = 0;
 	freq_imag = 0;
+	freq_real2 = 0;
+	freq_imag2 = 0;
 	output_real = 0;
 	output_imag = 0;
 	first_window = 1;
@@ -303,12 +305,17 @@ int CrossfadeFFT::reset()
 
 int CrossfadeFFT::delete_fft()
 {
-	if(input_buffer) delete input_buffer;
-	if(output_buffer) delete [] output_buffer;
-	if(freq_real) delete [] freq_real;
-	if(freq_imag) delete [] freq_imag;
-	if(output_real) delete [] output_real;
-	if(output_imag) delete [] output_imag;
+	delete input_buffer;	  input_buffer = 0;
+	if( output_buffer ) {
+		for( int i=0; i<bands; ++i ) delete [] output_buffer[i];
+		delete [] output_buffer;  output_buffer = 0;
+	}
+	delete [] freq_real;	  freq_real = 0;
+	delete [] freq_imag;	  freq_imag = 0;
+	delete [] freq_real2;	 freq_real2 = 0;
+	delete [] freq_imag2;	 freq_imag2 = 0;
+	delete [] output_real;	output_real = 0;
+	delete [] output_imag;	output_imag = 0;
 	reset();
 	return 0;
 }
@@ -324,9 +331,10 @@ int CrossfadeFFT::fix_window_size()
 	return 0;
 }
 
-int CrossfadeFFT::initialize(int window_size)
+int CrossfadeFFT::initialize(int window_size, int bands)
 {
 	this->window_size = window_size;
+	this->bands = bands;
 	first_window = 1;
 	reconfigure();
 	return 0;
@@ -341,277 +349,172 @@ int CrossfadeFFT::reconfigure()
 {
 	delete_fft();
 	fix_window_size();
+	return 0;
+}
+void CrossfadeFFT::allocate_output(int new_allocation)
+{
+// Allocate output buffer
+	if( new_allocation > output_allocation ) {
+		if( !output_buffer ) {
+			output_buffer = new double*[bands];
+			bzero(output_buffer, sizeof(double) * bands);
+		}
 
+		for( int i = 0; i < bands; i++ ) {
+			double *new_output = new double[new_allocation];
+			if( output_buffer[i] ) {
+				memcpy(new_output, output_buffer[i],
+					sizeof(double) * (output_size + HALF_WINDOW));
+				delete [] output_buffer[i];
+			}
+			output_buffer[i] = new_output;
+		}
+		output_allocation = new_allocation;
+	}
+}
 
-
-	return 0;
+int CrossfadeFFT::process_buffer(int64_t output_sample, long size,
+		Samples *output_ptr, int direction)
+{
+	Samples *output_temp[1];
+	output_temp[0] = output_ptr;
+	return process_buffer(output_sample, size, output_temp, direction);
 }
 
-// int CrossfadeFFT::process_fifo(long size,
-// 	double *input_ptr,
-// 	double *output_ptr)
+// int CrossfadeFFT::get_read_offset()
 // {
-// // Load next input buffer
-// 	if(input_size + size > input_allocation)
-// 	{
-// 		double *new_input = new double[input_size + size];
-// 		if(input_buffer)
-// 		{
-// 			memcpy(new_input, input_buffer, sizeof(double) * input_size);
-// 			delete [] input_buffer;
-// 		}
-// 		input_buffer = new_input;
-// 		input_allocation = input_size + size;
-// 	}
-//
-// 	memcpy(input_buffer + input_size,
-// 		input_ptr,
-// 		size * sizeof(double));
-// 	input_size += size;
-//
-//
-//
-//
-//
-//
-//
-// // Have enough to do some windows
-// 	while(input_size >= window_size)
-// 	{
-// 		if(!freq_real) freq_real = new double[window_size];
-// 		if(!freq_imag) freq_imag = new double[window_size];
-// 		if(!output_real) output_real = new double[window_size];
-// 		if(!output_imag) output_imag = new double[window_size];
-//
-//
-//
-// 		do_fft(window_size,  // must be a power of 2
-//	 		0,		 // 0 = forward FFT, 1 = inverse
-//	 		input_buffer,	 // array of input's real samples
-//	 		0,	 // array of input's imag samples
-//	 		freq_real,	// array of output's reals
-//	 		freq_imag);
-//
-// 		int result = signal_process();
-//
-// 		if(!result)
-// 		{
-// 			do_fft(window_size,  // must be a power of 2
-//	 			1,			   // 0 = forward FFT, 1 = inverse
-//	 			freq_real,	 // array of input's real samples
-//	 			freq_imag,	 // array of input's imag samples
-//	 			output_real,	 // array of output's reals
-//	 			output_imag);
-// 		}
-//
-//
-// // Crossfade into the output buffer
-// 		long new_allocation = output_size + window_size;
-// 		if(new_allocation > output_allocation)
-// 		{
-// 			double *new_output = new double[new_allocation];
-//
-// 			if(output_buffer)
-// 			{
-// 				memcpy(new_output, output_buffer, sizeof(double) * output_size);
-// 				delete [] output_buffer;
-// 			}
-// 			output_buffer = new_output;
-// 			output_allocation = new_allocation;
-// 		}
-//
-// 		if(output_size >= HALF_WINDOW)
-// 		{
-// 			for(int i = 0, j = output_size - HALF_WINDOW;
-// 				i < HALF_WINDOW;
-// 				i++, j++)
-// 			{
-// 				double src_level = (double)i / HALF_WINDOW;
-// 				double dst_level = (double)(HALF_WINDOW - i) / HALF_WINDOW;
-// 				output_buffer[j] = output_buffer[j] * dst_level + output_real[i] * src_level;
-// 			}
-//
-// 			memcpy(output_buffer + output_size,
-// 				output_real + HALF_WINDOW,
-// 				sizeof(double) * (window_size - HALF_WINDOW));
-// 			output_size += window_size - HALF_WINDOW;
-// 		}
-// 		else
-// 		{
-// // First buffer has no crossfade
-// 			memcpy(output_buffer + output_size,
-// 				output_real,
-// 				sizeof(double) * window_size);
-// 			output_size += window_size;
-// 		}
-//
-//
-// // Shift input buffer forward
-// 		for(int i = window_size - HALF_WINDOW, j = 0;
-// 			i < input_size;
-// 			i++, j++)
-// 			input_buffer[j] = input_buffer[i];
-// 		input_size -= window_size - HALF_WINDOW;
-// 	}
-//
-//
-//
-//
-// // Have enough to send to output
-// 	int samples_rendered = 0;
-// 	if(output_size - HALF_WINDOW >= size)
-// 	{
-// 		memcpy(output_ptr, output_buffer, sizeof(double) * size);
-// 		for(int i = size, j = 0; i < output_size; i++, j++)
-// 			output_buffer[j] = output_buffer[i];
-// 		output_size -= size;
-// 		samples_rendered = size;
-// 	}
-// 	else
-// 	{
-// 		bzero(output_ptr, sizeof(double) * size);
-// 	}
-//
-// 	return samples_rendered;
 // }
 
-
-
 int CrossfadeFFT::process_buffer(int64_t output_sample,
-	long size,
-	Samples *output_ptr,
-	int direction)
+		long size, Samples **output_ptr, int direction)
 {
 	int result = 0;
 	int step = (direction == PLAY_FORWARD) ? 1 : -1;
 
 // User seeked so output buffer is invalid
-	if(output_sample != this->output_sample)
-	{
+	if( output_sample != this->output_sample ) {
 		output_size = 0;
 		input_size = 0;
 		first_window = 1;
 		this->output_sample = output_sample;
-		this->input_sample = output_sample;
+		input_sample = output_sample;
 	}
 
-// Fill output buffer half a window at a time until size samples are available
-	while(output_size < size)
-	{
-		if(!input_buffer) input_buffer = new Samples(window_size);
-		if(!freq_real) freq_real = new double[window_size];
-		if(!freq_imag) freq_imag = new double[window_size];
-		if(!output_real) output_real = new double[window_size];
-		if(!output_imag) output_imag = new double[window_size];
-
-// Fill enough input to make a window starting at output_sample
-		if(first_window)
-			result = read_samples(this->input_sample,
-				window_size,
-				input_buffer);
-		else
-		{
-			input_buffer->set_offset(HALF_WINDOW);
-// printf("CrossfadeFFT::process_buffer %d %lld %lld\n",
-// __LINE__,
-// this->input_sample + step * HALF_WINDOW,
-// this->input_sample + step * HALF_WINDOW + HALF_WINDOW);
-			result = read_samples(this->input_sample + step * HALF_WINDOW,
-				HALF_WINDOW,
-				input_buffer);
-			input_buffer->set_offset(0);
-		}
 
-		input_size = window_size;
-
-		if(!result)
-			do_fft(window_size,   // must be a power of 2
-				0,				// 0 = forward FFT, 1 = inverse
-				input_buffer->get_data(),	 // array of input's real samples
-				0,				// array of input's imag samples
-				freq_real,		// array of output's reals
-				freq_imag);
-		if(!result)
-			result = signal_process();
-
-		if(!result)
-			do_fft(window_size,  // must be a power of 2
-				1,			   // 0 = forward FFT, 1 = inverse
-				freq_real,	   // array of input's real samples
-				freq_imag,	   // array of input's imag samples
-				output_real,	   // array of output's reals
-				output_imag);	  // array of output's imaginaries
-
-		if(!result)
-			result = post_process();
+// printf("CrossfadeFFT::process_buffer %d size=%ld input_size=%ld output_size=%ld window_size=%ld\n",
+// __LINE__, size, input_size, output_size, window_size);
 
-// Allocate output buffer
-		int new_allocation = output_size + window_size;
-		if(new_allocation > output_allocation)
-		{
-			double *new_output = new double[new_allocation];
-			if(output_buffer)
-			{
-				memcpy(new_output,
-					output_buffer,
-					sizeof(double) * (output_size + HALF_WINDOW));
-				delete [] output_buffer;
-			}
-			output_buffer = new_output;
-			output_allocation = new_allocation;
+// must call read_samples once so the upstream plugins don't have to seek
+// must be a multiple of 1/2 window
+	int need_samples = (size - output_size) / HALF_WINDOW * HALF_WINDOW;
+// round up a half window
+	if( need_samples + output_size < size ) {
+		need_samples += HALF_WINDOW;
+	}
+// half window tail
+	need_samples += HALF_WINDOW;
+
+// extend the buffer to need_samples
+	if( !input_buffer || input_buffer->get_allocated() < need_samples ) {
+		Samples *new_input_buffer = new Samples(need_samples);
+
+		if( input_buffer ) {
+			memcpy(new_input_buffer->get_data(),
+				input_buffer->get_data(),
+				input_size * sizeof(double));
+			delete input_buffer;
 		}
 
-// Overlay processed buffer
-		if(first_window)
-		{
-			memcpy(output_buffer + output_size,
-				output_real,
-				sizeof(double) * window_size);
-			first_window = 0;
-		}
-		else
-		{
-			for(int i = 0, j = output_size; i < HALF_WINDOW; i++, j++)
-			{
-				double src_level = (double)i / HALF_WINDOW;
-				double dst_level = (double)(HALF_WINDOW - i) / HALF_WINDOW;
-				output_buffer[j] = output_buffer[j] * dst_level +
-					output_real[i] * src_level;
+		input_buffer = new_input_buffer;
+	}
+
+	input_buffer->set_offset(input_size);
+	result = read_samples(input_sample, need_samples-input_size, input_buffer);
+	input_buffer->set_offset(0);
+	input_sample += step * (need_samples - input_size);
+	input_size = need_samples;
+
+
+	if( !freq_real ) freq_real = new double[window_size];
+	if( !freq_imag ) freq_imag = new double[window_size];
+	if( !freq_real2 ) freq_real2 = new double[window_size];
+	if( !freq_imag2 ) freq_imag2 = new double[window_size];
+	if( !output_real ) output_real = new double[window_size];
+	if( !output_imag ) output_imag = new double[window_size];
+
+// Fill output buffer half a window at a time until size samples are available
+	while( !result && output_size < size ) {
+		do_fft(window_size, 0,  // forward
+			input_buffer->get_data(), 0, // input, real only
+			freq_real2, freq_imag2); // output
+		allocate_output(output_size + window_size);
+// process & overlay each band separately
+		for( int band=0; band<bands; ++band ) {
+// restore from the backup
+			memcpy(freq_real, freq_real2, sizeof(double) * window_size);
+			memcpy(freq_imag, freq_imag2, sizeof(double) * window_size);
+			signal_process(band);
+			do_fft(window_size, 1, 	// inverse
+				freq_real, freq_imag, // input
+				output_real, output_imag); // output
+			post_process(band);
+
+// Overlay processed window on the output buffers
+			if( first_window ) {
+// direct copy the 1st window
+				memcpy(output_buffer[band] + output_size,
+					output_real,
+					sizeof(double) * window_size);
 			}
+			else {
+// dissolve 1st half of later windows
+				for( int i = 0, j = output_size; i < HALF_WINDOW; i++, j++ ) {
+					double src_level = (double)i / HALF_WINDOW;
+					double dst_level = (double)(HALF_WINDOW - i) / HALF_WINDOW;
+					output_buffer[band][j] = output_buffer[band][j] * dst_level +
+						output_real[i] * src_level;
+				}
 
 //output_buffer[output_size] = 100.0;
 //output_buffer[output_size + HALF_WINDOW] = -100.0;
-
-			memcpy(output_buffer + output_size + HALF_WINDOW,
-				output_real + HALF_WINDOW,
-				sizeof(double) * HALF_WINDOW);
+// copy 2nd half of window
+				memcpy(output_buffer[band] + output_size + HALF_WINDOW,
+					output_real + HALF_WINDOW,
+					sizeof(double) * HALF_WINDOW);
+			}
 		}
 
-
+		first_window = 0;
 		output_size += HALF_WINDOW;
 
-// Shift input buffer
-		double *input_samples = input_buffer->get_data();
-		for(int i = window_size - HALF_WINDOW, j = 0;
-			i < input_size;
-			i++, j++)
-		{
-			input_samples[j] = input_samples[i];
-		}
+// Shift input buffer half a window forward
+		memcpy(input_buffer->get_data(),
+			input_buffer->get_data() + HALF_WINDOW,
+			(input_size - HALF_WINDOW) * sizeof(double));
+// 		for( int i = HALF_WINDOW, j = 0;
+// 			i < input_size;
+// 			i++, j++ )
+// 		{
+// 			input_buffer->get_data()[j] = input_buffer->get_data()[i];
+// 		}
 
-		input_size = HALF_WINDOW;
-		this->input_sample += step * HALF_WINDOW;
+		input_size -= HALF_WINDOW;
 	}
 
 
 
-// Transfer output buffer
-	if(output_ptr)
-	{
-		memcpy(output_ptr->get_data(), output_buffer, sizeof(double) * size);
+// Transfer output buffer if the user wants it
+	if( output_ptr ) {
+		for( int band = 0; band < bands; band++ ) {
+			memcpy(output_ptr[band]->get_data(), output_buffer[band], sizeof(double) * size);
+		}
+	}
+
+// shift output buffers forward
+	for( int band = 0; band < bands; band++ ) {
+		memcpy(output_buffer[band], output_buffer[band] + size,
+			sizeof(double) * (output_size + HALF_WINDOW - size));
 	}
-	for(int i = 0, j = size; j < output_size + HALF_WINDOW; i++, j++)
-		output_buffer[i] = output_buffer[j];
 
 	this->output_sample += step * size;
 	this->output_size -= size;
@@ -621,8 +524,7 @@ int CrossfadeFFT::process_buffer(int64_t output_sample,
 
 
 int CrossfadeFFT::read_samples(int64_t output_sample,
-		int samples,
-		Samples *buffer)
+		int samples, Samples *buffer)
 {
 	return 1;
 }
@@ -640,9 +542,18 @@ int CrossfadeFFT::post_process()
 }
 
 
+int CrossfadeFFT::signal_process(int band)
+{
+	signal_process();
+	return 0;
+}
 
 
 
-
+int CrossfadeFFT::post_process(int band)
+{
+	post_process();
+	return 0;
+}
 
 
diff --git a/cinelerra-5.1/cinelerra/fourier.h b/cinelerra-5.1/cinelerra/fourier.h
index 482c6fb0..9b83153a 100644
--- a/cinelerra-5.1/cinelerra/fourier.h
+++ b/cinelerra-5.1/cinelerra/fourier.h
@@ -33,16 +33,16 @@ class FFT
 	static uint8_t rev_bytes[256];
 	static unsigned int reverse_bits(unsigned int index, unsigned int bits);
 	static void bit_reverse(unsigned int samples,
-    		double *real_in, double *imag_in,
-    		double *real_out, double *imag_out);
+			double *real_in, double *imag_in,
+			double *real_out, double *imag_out);
 public:
 	FFT();
 	virtual ~FFT();
 	// can be in place, but imag_out must exist, imag_in optional
 	int do_fft(int samples, // must be a power of 2
 		int inverse,  		 // 0 = forward FFT, 1 = inverse
-    		double *real_in, double *imag_in,	// complex input
-	    	double *real_out, double *imag_out);	// complex output
+			double *real_in, double *imag_in,	// complex input
+			double *real_out, double *imag_out);	// complex output
 	int do_fft(int samples, int inverse, double *real, double *imag) {
 		return do_fft(samples, inverse, real, imag, real, imag);
 	}
@@ -58,8 +58,8 @@ public:
 	virtual ~CrossfadeFFT();
 
 	int reset();
-	int initialize(int window_size);
-	long get_delay();     // Number of samples fifo is delayed
+	int initialize(int window_size, int bands = 1);
+	long get_delay();	 // Number of samples fifo is delayed
 	int reconfigure();
 	int fix_window_size();
 	int delete_fft();
@@ -69,28 +69,33 @@ public:
 // output_sample - tells it if we've seeked and the output overflow is invalid.
 // return - 0 on success 1 on failure
 // output_sample - start of samples if forward.  End of samples if reverse.
-//               It's always contiguous.
+//			   It's always contiguous.
 // output_ptr - if nonzero, output is put here
 // direction - PLAY_FORWARD or PLAY_REVERSE
 	int process_buffer(int64_t output_sample,
-		long size,
-		Samples *output_ptr,
-		int direction);
+		long size, Samples *output_ptr, int direction);
+// multiband processing
+	int process_buffer(int64_t output_sample,
+		long size, Samples **output_ptr, int direction);
 
 // Called by process_buffer to read samples from input.
 // Returns 1 on error or 0 on success.
 	virtual int read_samples(int64_t output_sample,
-		int samples,
-		Samples *buffer);
+		int samples, Samples *buffer);
 
 // Process a window in the frequency domain
 	virtual int signal_process();
 // Process a window in the time domain after the frequency domain
 	virtual int post_process();
+// Multiband versions
+	virtual int signal_process(int band);
+	virtual int post_process(int band);
 
 // Size of a window.  Automatically fixed to a power of 2
 	long window_size;
 
+// Time domane input of complete windows
+	Samples *input_buffer;
 // Frequency domain output of FFT
 	double *freq_real;
 	double *freq_imag;
@@ -99,13 +104,16 @@ public:
 	double *output_imag;
 
 private:
+	void allocate_output(int new_allocation);
 
-// input for complete windows
-	Samples *input_buffer;
-// output for crossfaded windows with overflow
-	double *output_buffer;
+// output of crossfaded windows with overflow.  1 buffer for each band
+	double **output_buffer;
+
+// backup frequency domain for multiband
+	double *freq_real2;
+	double *freq_imag2;
 
-// samples in input_buffer
+// samples in input_buffer including the tail
 	long input_size;
 // window_size
 	long input_allocation;
@@ -119,6 +127,7 @@ private:
 	int64_t input_sample;
 // Don't crossfade the first window
 	int first_window;
+	int bands;
 };
 
 #endif
diff --git a/cinelerra-5.1/cinelerra/mbuttons.h b/cinelerra-5.1/cinelerra/mbuttons.h
index 06e53848..698c4e83 100644
--- a/cinelerra-5.1/cinelerra/mbuttons.h
+++ b/cinelerra-5.1/cinelerra/mbuttons.h
@@ -72,6 +72,7 @@ class MainTransport : public PlayTransport
 {
 public:
 	MainTransport(MWindow *mwindow, MButtons *mbuttons, int x, int y);
+	bool use_mixers() { return true; }
 	void goto_start();
 	void goto_end();
 };
diff --git a/cinelerra-5.1/cinelerra/mwindow.C b/cinelerra-5.1/cinelerra/mwindow.C
index 03e265e6..e3bf6b62 100644
--- a/cinelerra-5.1/cinelerra/mwindow.C
+++ b/cinelerra-5.1/cinelerra/mwindow.C
@@ -3457,11 +3457,12 @@ int MWindow::plugin_gui_open(Plugin *plugin)
 	return result;
 }
 
+
 void MWindow::render_plugin_gui(void *data, Plugin *plugin)
 {
 	int gui_id = plugin->gui_id;
 	if( gui_id < 0 ) return;
-	plugin_gui_lock->lock("MWindow::render_plugin_gui");
+	plugin_gui_lock->lock("MWindow::render_plugin_gui 0");
 	PluginServer *plugin_server = plugin_guis->gui_server(gui_id);
 	if( plugin_server )
 		plugin_server->render_gui(data);
@@ -3472,13 +3473,44 @@ void MWindow::render_plugin_gui(void *data, int size, Plugin *plugin)
 {
 	int gui_id = plugin->gui_id;
 	if( gui_id < 0 ) return;
-	plugin_gui_lock->lock("MWindow::render_plugin_gui");
+	plugin_gui_lock->lock("MWindow::render_plugin_gui 1");
 	PluginServer *plugin_server = plugin_guis->gui_server(gui_id);
 	if( plugin_server )
 		plugin_server->render_gui(data, size);
 	plugin_gui_lock->unlock();
 }
 
+void MWindow::reset_plugin_gui_frames(Plugin *plugin)
+{
+	int gui_id = plugin->gui_id;
+	if( gui_id < 0 ) return;
+	plugin_gui_lock->lock("MWindow::reset_plugin_gui_frames");
+	PluginServer *plugin_server = plugin_guis->gui_server(gui_id);
+	if( plugin_server )
+		plugin_server->reset_plugin_gui_frames();
+	plugin_gui_lock->unlock();
+}
+
+void MWindow::render_plugin_gui_frames(PluginClientFrames *frames, Plugin *plugin)
+{
+	int gui_id = plugin->gui_id;
+	if( gui_id < 0 ) return;
+	plugin_gui_lock->lock("MWindow::render_plugin_gui_frames");
+	PluginServer *plugin_server = plugin_guis->gui_server(gui_id);
+	if( plugin_server )
+		plugin_server->render_plugin_gui_frames(frames);
+	plugin_gui_lock->unlock();
+}
+
+double MWindow::get_tracking_position()
+{
+	return edl->local_session->get_selectionstart(1);
+}
+
+int MWindow::get_tracking_direction()
+{
+	return cwindow->playback_engine->get_direction();
+}
 
 void MWindow::update_plugin_states()
 {
diff --git a/cinelerra-5.1/cinelerra/mwindow.h b/cinelerra-5.1/cinelerra/mwindow.h
index 513e1bfe..b0e78772 100644
--- a/cinelerra-5.1/cinelerra/mwindow.h
+++ b/cinelerra-5.1/cinelerra/mwindow.h
@@ -72,6 +72,7 @@
 #include "playback3d.inc"
 #include "playbackengine.inc"
 #include "plugin.inc"
+#include "pluginclient.inc"
 #include "pluginfclient.inc"
 #include "pluginserver.inc"
 #include "pluginset.inc"
@@ -304,6 +305,10 @@ public:
 // Searches for matching plugin and renders data in it.
 	void render_plugin_gui(void *data, Plugin *plugin);
 	void render_plugin_gui(void *data, int size, Plugin *plugin);
+	void reset_plugin_gui_frames(Plugin *plugin);
+	void render_plugin_gui_frames(PluginClientFrames *frames, Plugin *plugin);
+	double get_tracking_position();
+	int get_tracking_direction();
 
 // Called from PluginVClient::process_buffer
 // Returns 1 if a GUI for the plugin is open so OpenGL routines can determine if
diff --git a/cinelerra-5.1/cinelerra/playbackengine.C b/cinelerra-5.1/cinelerra/playbackengine.C
index d9b636df..f95f5758 100644
--- a/cinelerra-5.1/cinelerra/playbackengine.C
+++ b/cinelerra-5.1/cinelerra/playbackengine.C
@@ -452,6 +452,11 @@ void PlaybackEngine::stop_playback(int wait_tracking)
 	renderengine_lock->unlock();
 }
 
+int PlaybackEngine::get_direction()
+{
+	int curr_command = is_playing_back ? this->command->command : STOP;
+	return TransportCommand::get_direction(curr_command);
+}
 
 void PlaybackEngine::send_command(int command, EDL *edl, int wait_tracking, int use_inout)
 {
diff --git a/cinelerra-5.1/cinelerra/playbackengine.h b/cinelerra-5.1/cinelerra/playbackengine.h
index ed646b06..c82d3508 100644
--- a/cinelerra-5.1/cinelerra/playbackengine.h
+++ b/cinelerra-5.1/cinelerra/playbackengine.h
@@ -85,6 +85,7 @@ public:
 	void send_command(int command, EDL *edl, int wait_tracking, int use_inout);
 	void stop_playback(int wait);
 	void refresh_frame(int change_type, EDL *edl, int dir=1);
+	int get_direction();
 
 // Maintain caches through console changes
 	CICache *audio_cache, *video_cache;
diff --git a/cinelerra-5.1/cinelerra/playtransport.C b/cinelerra-5.1/cinelerra/playtransport.C
index 0690da07..d9d9a48a 100644
--- a/cinelerra-5.1/cinelerra/playtransport.C
+++ b/cinelerra-5.1/cinelerra/playtransport.C
@@ -315,7 +315,7 @@ void PlayTransport::handle_transport(int command, int wait_tracking,
 	if( !edl ) return;
 	using_inout = use_inout;
 
-	if( !is_vwindow() )
+	if( use_mixers() )
 		mwindow->handle_mixers(edl, command, wait_tracking,
 				use_inout, toggle_audio, 0, speed);
 	engine->next_command->toggle_audio = toggle_audio;
diff --git a/cinelerra-5.1/cinelerra/playtransport.h b/cinelerra-5.1/cinelerra/playtransport.h
index 330a9585..20d98999 100644
--- a/cinelerra-5.1/cinelerra/playtransport.h
+++ b/cinelerra-5.1/cinelerra/playtransport.h
@@ -69,7 +69,7 @@ public:
 // Get the EDL to play back with default to mwindow->edl
 	virtual EDL* get_edl();
 	void change_position(double position);
-	virtual int is_vwindow() { return 0; }
+	virtual bool use_mixers() { return false; }
 
 // playback parameters
 	int reverse;
diff --git a/cinelerra-5.1/cinelerra/pluginaclient.C b/cinelerra-5.1/cinelerra/pluginaclient.C
index 8601bc0a..b1434b02 100644
--- a/cinelerra-5.1/cinelerra/pluginaclient.C
+++ b/cinelerra-5.1/cinelerra/pluginaclient.C
@@ -19,10 +19,13 @@
  *
  */
 
+#include "aattachmentpoint.h"
+#include "audiodevice.h"
 #include "edl.h"
 #include "edlsession.h"
 #include "pluginaclient.h"
 #include "pluginserver.h"
+#include "renderengine.h"
 #include "samples.h"
 
 #include <string.h>
@@ -32,15 +35,11 @@ PluginAClient::PluginAClient(PluginServer *server)
  : PluginClient(server)
 {
 	sample_rate = 0;
-	if(server &&
-		server->edl &&
-		server->edl->session)
-	{
+	if(server && server->edl && server->edl->session) {
 		project_sample_rate = server->edl->session->sample_rate;
 		sample_rate = project_sample_rate;
 	}
-	else
-	{
+	else {
 		project_sample_rate = 1;
 		sample_rate = 1;
 	}
@@ -92,54 +91,47 @@ int PluginAClient::process_realtime(int64_t size,
 }
 
 int PluginAClient::process_realtime(int64_t size,
-	Samples *input_ptr,
-	Samples *output_ptr)
+	Samples *input_ptr, Samples *output_ptr)
 {
 	return 0;
 }
 
-int PluginAClient::process_buffer(int64_t size,
-	Samples **buffer,
-	int64_t start_position,
-	int sample_rate)
+int PluginAClient::process_buffer(int64_t size, Samples **buffer,
+		int64_t start_position, int sample_rate)
 {
 	for(int i = 0; i < PluginClient::total_in_buffers; i++)
-		read_samples(buffer[i],
-			i,
-			sample_rate,
-			source_position,
-			size);
+		read_samples(buffer[i], i, sample_rate, source_position, size);
 	process_realtime(size, buffer, buffer);
 	return 0;
 }
 
-int PluginAClient::process_buffer(int64_t size,
-	Samples *buffer,
-	int64_t start_position,
-	int sample_rate)
+int PluginAClient::process_buffer(int64_t size, Samples *buffer,
+		int64_t start_position, int sample_rate)
 {
-	read_samples(buffer,
-		0,
-		sample_rate,
-		source_position,
-		size);
+	read_samples(buffer, 0, sample_rate, source_position, size);
 	process_realtime(size, buffer, buffer);
 	return 0;
 }
 
 
+void PluginAClient::begin_process_buffer()
+{
+	frame_buffer.destroy();
+}
+
+void PluginAClient::end_process_buffer()
+{
+	if( !frame_buffer.first ) return;
+	server->render_gui_frames(&frame_buffer);
+}
 
 
-int PluginAClient::plugin_start_loop(int64_t start,
-	int64_t end,
-	int64_t buffer_size,
-	int total_buffers)
+int PluginAClient::plugin_start_loop(int64_t start, int64_t end,
+		int64_t buffer_size, int total_buffers)
 {
 	sample_rate = get_project_samplerate();
-	return PluginClient::plugin_start_loop(start,
-		end,
-		buffer_size,
-		total_buffers);
+	return PluginClient::plugin_start_loop(start, end,
+		buffer_size, total_buffers);
 }
 
 int PluginAClient::plugin_get_parameters()
@@ -152,68 +144,44 @@ int PluginAClient::plugin_get_parameters()
 int64_t PluginAClient::local_to_edl(int64_t position)
 {
 	if(position < 0) return position;
-	return (int64_t)(position *
-		get_project_samplerate() /
-		sample_rate);
+	return (int64_t)(position * get_project_samplerate() / sample_rate);
 	return 0;
 }
 
 int64_t PluginAClient::edl_to_local(int64_t position)
 {
 	if(position < 0) return position;
-	return (int64_t)(position *
-		sample_rate /
-		get_project_samplerate());
+	return (int64_t)(position * sample_rate / get_project_samplerate());
 }
 
-
 int PluginAClient::plugin_process_loop(Samples **buffers, int64_t &write_length)
 {
 	write_length = 0;
-
-	if(is_multichannel())
-		return process_loop(buffers, write_length);
-	else
-		return process_loop(buffers[0], write_length);
+	return is_multichannel() ?
+		process_loop(buffers, write_length) :
+		process_loop(buffers[0], write_length);
 }
 
 int PluginAClient::read_samples(Samples *buffer,
-	int channel,
-	int64_t start_position,
-	int64_t total_samples)
+		int channel, int64_t start_position, int64_t total_samples)
 {
-	return server->read_samples(buffer,
-		channel,
-		start_position,
-		total_samples);
+	return server->read_samples(buffer, channel, start_position, total_samples);
 }
 
 int PluginAClient::read_samples(Samples *buffer,
-	int64_t start_position,
-	int64_t total_samples)
+		int64_t start_position, int64_t total_samples)
 {
-	return server->read_samples(buffer,
-		0,
-		start_position,
-		total_samples);
+	return server->read_samples(buffer, 0, start_position, total_samples);
 }
 
 int PluginAClient::read_samples(Samples *buffer,
-		int channel,
-		int sample_rate,
-		int64_t start_position,
-		int64_t len)
+		int channel, int sample_rate, int64_t start_position, int64_t len)
 {
 	return server->read_samples(buffer,
-		channel,
-		sample_rate,
-		start_position,
-		len);
+		channel, sample_rate, start_position, len);
 }
 
 
-
-
 int PluginAClient::get_project_samplerate()
 {
 	return project_sample_rate;
@@ -224,6 +192,11 @@ int PluginAClient::get_samplerate()
 	return sample_rate;
 }
 
+Samples* PluginAClient::get_output(int channel)
+{
+	return output_buffers[channel];
+}
+
 
 int64_t PluginAClient::get_startproject()
 {
diff --git a/cinelerra-5.1/cinelerra/pluginaclient.h b/cinelerra-5.1/cinelerra/pluginaclient.h
index b0761d0d..e9b43850 100644
--- a/cinelerra-5.1/cinelerra/pluginaclient.h
+++ b/cinelerra-5.1/cinelerra/pluginaclient.h
@@ -41,12 +41,10 @@ public:
 // These should return 1 if error or 0 if success.
 // Multichannel buffer process for backwards compatibility
 	virtual int process_realtime(int64_t size,
-		Samples **input_ptr,
-		Samples **output_ptr);
+		Samples **input_ptr, Samples **output_ptr);
 // Single channel buffer process for backwards compatibility and transitions
 	virtual int process_realtime(int64_t size,
-		Samples *input_ptr,
-		Samples *output_ptr);
+		Samples *input_ptr, Samples *output_ptr);
 
 // Process buffer using pull method.  By default this loads the input into the
 // buffer and calls process_realtime with input and output pointing to buffer.
@@ -54,23 +52,20 @@ public:
 //     to start of EDL.  End of buffer if reverse.
 // sample_rate - scale of start_position.
 	virtual int process_buffer(int64_t size,
-		Samples **buffer,
-		int64_t start_position,
-		int sample_rate);
+		Samples **buffer, int64_t start_position, int sample_rate);
 	virtual int process_buffer(int64_t size,
-		Samples *buffer,
-		int64_t start_position,
-		int sample_rate);
+		Samples *buffer, int64_t start_position, int sample_rate);
 
+// process render_gui frames via add_gui_frame
+	virtual void begin_process_buffer();
+	virtual void end_process_buffer();
 
 	virtual int process_loop(Samples *buffer, int64_t &write_length) { return 1; };
 	virtual int process_loop(Samples **buffers, int64_t &write_length) { return 1; };
 	int plugin_process_loop(Samples **buffers, int64_t &write_length);
 
-	int plugin_start_loop(int64_t start,
-		int64_t end,
-		int64_t buffer_size,
-		int total_buffers);
+	int plugin_start_loop(int64_t start, int64_t end,
+		int64_t buffer_size, int total_buffers);
 
 	int plugin_get_parameters();
 
@@ -92,20 +87,21 @@ public:
 // sample_rate - scale of start_position.  Provided so the client can get data
 //     at a higher fidelity than provided by the EDL.
 	int read_samples(Samples *buffer,
-		int channel,
-		int sample_rate,
-		int64_t start_position,
-		int64_t len);
+		int channel, int sample_rate, int64_t start_position, int64_t len);
 
 // Get the sample rate of the EDL
 	int get_project_samplerate();
 // Get the requested sample rate
 	int get_samplerate();
+	Samples* get_output(int channel);
+	int64_t get_startproject();
+	int64_t get_endproject();
 
 	int64_t local_to_edl(int64_t position);
 	int64_t edl_to_local(int64_t position);
-	int64_t get_startproject();
-	int64_t get_endproject();
+
+// the buffers passed to process_buffer
+	Samples **output_buffers;
 
 // point to the start of the buffers
 	ArrayList<float**> input_ptr_master;
diff --git a/cinelerra-5.1/cinelerra/pluginclient.C b/cinelerra-5.1/cinelerra/pluginclient.C
index da9a6977..dcafd890 100644
--- a/cinelerra-5.1/cinelerra/pluginclient.C
+++ b/cinelerra-5.1/cinelerra/pluginclient.C
@@ -22,6 +22,7 @@
 #include "bcdisplayinfo.h"
 #include "bchash.h"
 #include "bcsignals.h"
+#include "attachmentpoint.h"
 #include "clip.h"
 #include "condition.h"
 #include "edits.h"
@@ -40,8 +41,9 @@
 #include "pluginclient.h"
 #include "pluginserver.h"
 #include "preferences.h"
+#include "renderengine.h"
 #include "track.h"
-#include "transportque.inc"
+#include "transportque.h"
 
 #include <stdio.h>
 #include <unistd.h>
@@ -51,6 +53,15 @@
 #include <ctype.h>
 #include <errno.h>
 
+PluginClientFrame::PluginClientFrame()
+{
+	position = -1;
+}
+
+PluginClientFrame::~PluginClientFrame()
+{
+}
+
 
 PluginClientThread::PluginClientThread(PluginClient *client)
  : Thread(1, 0, 0)
@@ -63,9 +74,7 @@ PluginClientThread::PluginClientThread(PluginClient *client)
 PluginClientThread::~PluginClientThread()
 {
 	join();
-//printf("PluginClientThread::~PluginClientThread %p %d\n", this, __LINE__);
-	delete window;  window = 0;
-//printf("PluginClientThread::~PluginClientThread %p %d\n", this, __LINE__);
+	delete window;
 	delete init_complete;
 }
 
@@ -120,29 +129,6 @@ PluginClient* PluginClientThread::get_client()
 }
 
 
-
-
-
-
-PluginClientFrame::PluginClientFrame(int data_size,
-	int period_n,
-	int period_d)
-{
-	this->data_size = data_size;
-	force = 0;
-	this->period_n = period_n;
-	this->period_d = period_d;
-}
-
-PluginClientFrame::~PluginClientFrame()
-{
-
-}
-
-
-
-
-
 PluginClientWindow::PluginClientWindow(PluginClient *client,
 	int w, int h, int min_w, int min_h, int allow_resize)
  : BC_Window(client->gui_string,
@@ -421,9 +407,6 @@ int PluginText::handle_event()
 }
 
 
-
-
-
 PluginClient::PluginClient(PluginServer *server)
 {
 	reset();
@@ -444,7 +427,6 @@ PluginClient::~PluginClient()
 
 // Virtual functions don't work here.
 	if(defaults) delete defaults;
-	frame_buffer.remove_all_objects();
 	delete update_timer;
 }
 
@@ -553,7 +535,6 @@ int PluginClient::is_multichannel() { return 0; }
 int PluginClient::is_synthesis() { return 0; }
 int PluginClient::is_realtime() { return 0; }
 int PluginClient::is_fileio() { return 0; }
-int PluginClient::delete_buffer_ptrs() { return 0; }
 const char* PluginClient::plugin_title() { return _("Untitled"); }
 
 Theme* PluginClient::new_theme() { return 0; }
@@ -606,90 +587,114 @@ int PluginClient::set_string()
 
 
 
-void PluginClient::begin_process_buffer()
+PluginClientFrames::PluginClientFrames()
+{
+	count = 0;
+}
+PluginClientFrames::~PluginClientFrames()
 {
-// Delete all unused GUI frames
-	frame_buffer.remove_all_objects();
 }
 
+int PluginClientFrames::fwd_cmpr(PluginClientFrame *a, PluginClientFrame *b)
+{
+	double d = a->position - b->position;
+	return d < 0 ? -1 : !d ? 0 : 1;
+}
 
-void PluginClient::end_process_buffer()
+int PluginClientFrames::rev_cmpr(PluginClientFrame *a, PluginClientFrame *b)
 {
-	if(frame_buffer.size())
-	{
-		send_render_gui();
-	}
+	double d = b->position - a->position;
+	return d < 0 ? -1 : !d ? 0 : 1;
 }
 
+void PluginClientFrames::reset()
+{
+	destroy();
+	count = 0;
+}
 
+void PluginClientFrames::add_gui_frame(PluginClientFrame *frame)
+{
+	append(frame);
+	++count;
+}
 
-void PluginClient::plugin_update_gui()
+void PluginClientFrames::concatenate(PluginClientFrames *frames)
 {
+	concat(*frames);
+	count += frames->count;
+	frames->count = 0;
+}
 
-	update_gui();
+void PluginClientFrames::sort_position(int dir)
+{
+// enforce order
+	if( dir == PLAY_REVERSE )
+		rev_sort();
+	else
+		fwd_sort();
+}
 
-// Delete unused GUI frames
-	while(frame_buffer.size() > MAX_FRAME_BUFFER)
-		frame_buffer.remove_object_number(0);
+// pop frames until buffer passes position=pos in direction=dir
+// dir==0, pop frame; pos<0, pop all frames
+// delete past frames, return last popped frame
+PluginClientFrame* PluginClientFrames::get_gui_frame(double pos, int dir)
+{
+	if( dir ) {
+		while( first != last ) {
+			if( pos >= 0 && dir*(first->next->position - pos) > 0 ) break;
+			delete first;  --count;
+		}
+	}
+	PluginClientFrame *frame = first;
+	if( frame ) { remove_pointer(frame);  --count; }
+	return frame;
+}
 
+PluginClientFrame* PluginClient::get_gui_frame(double pos, int dir)
+{
+	return frame_buffer.get_gui_frame(pos, dir);
+}
+PluginClientFrame* PluginClient::next_gui_frame()
+{
+	return frame_buffer.first;
 }
 
-void PluginClient::update_gui()
+
+void PluginClient::plugin_update_gui()
 {
+	update_gui();
 }
 
-int PluginClient::get_gui_update_frames()
+void PluginClient::update_gui()
 {
-	if(frame_buffer.size())
-	{
-		PluginClientFrame *frame = frame_buffer.get(0);
-		int total_frames = update_timer->get_difference() *
-			frame->period_d /
-			frame->period_n /
-			1000;
-		if(total_frames) update_timer->subtract(total_frames *
-			frame->period_n *
-			1000 /
-			frame->period_d);
-
-// printf("PluginClient::get_gui_update_frames %d %ld %d %d %d\n",
-// __LINE__,
-// update_timer->get_difference(),
-// frame->period_n * 1000 / frame->period_d,
-// total_frames,
-// frame_buffer.size());
-
-// Add forced frames
-		for(int i = 0; i < frame_buffer.size(); i++)
-			if(frame_buffer.get(i)->force) total_frames++;
-		total_frames = MIN(frame_buffer.size(), total_frames);
-
-
-		return total_frames;
-	}
-	else
-	{
-		return 0;
-	}
 }
 
-PluginClientFrame* PluginClient::get_gui_frame()
+int PluginClient::pending_gui_frames()
 {
-	if(frame_buffer.size())
-	{
-		PluginClientFrame *frame = frame_buffer.get(0);
-		frame_buffer.remove_number(0);
-		return frame;
-	}
-	else
-	{
-		return 0;
-	}
+	PluginClientFrame *frame = frame_buffer.first;
+	if( !frame ) return 0;
+	double tracking_position = get_tracking_position();
+	int direction = get_tracking_direction();
+	int ret = !(direction == PLAY_REVERSE ?
+		frame->position < tracking_position :
+		frame->position > tracking_position);
+	return ret;
 }
 
 void PluginClient::add_gui_frame(PluginClientFrame *frame)
 {
-	frame_buffer.append(frame);
+	frame_buffer.add_gui_frame(frame);
+}
+
+double PluginClient::get_tracking_position()
+{
+	return server->mwindow->get_tracking_position();
+}
+
+int PluginClient::get_tracking_direction()
+{
+	return server->mwindow->get_tracking_direction();
 }
 
 void PluginClient::send_render_gui()
@@ -707,54 +712,56 @@ void PluginClient::send_render_gui(void *data, int size)
 	server->send_render_gui(data, size);
 }
 
-void PluginClient::plugin_render_gui(void *data, int size)
+
+void PluginClient::plugin_reset_gui_frames()
 {
-	render_gui(data, size);
+	if( !thread ) return;
+	BC_WindowBase *window = thread->get_window();
+	if( !window ) return;
+	window->lock_window("PluginClient::plugin_reset_gui_frames");
+	frame_buffer.reset();
+	window->unlock_window();
 }
 
+void PluginClient::plugin_render_gui_frames(PluginClientFrames *frames)
+{
+	if( !thread ) return;
+	BC_WindowBase *window = thread->get_window();
+	if( !window ) return;
+	window->lock_window("PluginClient::render_gui");
+	while( frame_buffer.count > MAX_FRAME_BUFFER )
+		delete get_gui_frame(0, 0);
+// append client frames to gui frame_buffer, consumes frames
+	frame_buffer.concatenate(frames);
+	frame_buffer.sort_position(get_tracking_direction());
+	update_timer->update();
+	window->unlock_window();
+}
 
 void PluginClient::plugin_render_gui(void *data)
 {
 	render_gui(data);
 }
 
-void PluginClient::render_gui(void *data)
+void PluginClient::plugin_render_gui(void *data, int size)
 {
-	if(thread)
-	{
-		thread->get_window()->lock_window("PluginClient::render_gui");
-
-// Set all previous frames to draw immediately
-		for(int i = 0; i < frame_buffer.size(); i++)
-			frame_buffer.get(i)->force = 1;
-
-		ArrayList<PluginClientFrame*> *src =
-			(ArrayList<PluginClientFrame*>*)data;
-
-// Shift GUI data to GUI client
-		while(src->size())
-		{
-			this->frame_buffer.append(src->get(0));
-			src->remove_number(0);
-		}
+	render_gui(data, size);
+}
 
-// Start the timer for the current buffer
-		update_timer->update();
-		thread->get_window()->unlock_window();
-	}
+void PluginClient::render_gui(void *data)
+{
+        printf("PluginClient::render_gui %d\n", __LINE__);
 }
 
 void PluginClient::render_gui(void *data, int size)
 {
-	printf("PluginClient::render_gui %d\n", __LINE__);
+        printf("PluginClient::render_gui %d\n", __LINE__);
 }
 
-
-
-
-
-
-
+void PluginClient::reset_gui_frames()
+{
+	server->reset_gui_frames();
+}
 
 int PluginClient::is_audio() { return 0; }
 int PluginClient::is_video() { return 0; }
@@ -1009,7 +1016,6 @@ int PluginClient::get_direction()
 	return direction;
 }
 
-
 int64_t PluginClient::local_to_edl(int64_t position)
 {
 	return position;
diff --git a/cinelerra-5.1/cinelerra/pluginclient.h b/cinelerra-5.1/cinelerra/pluginclient.h
index 96f5540b..67a1148f 100644
--- a/cinelerra-5.1/cinelerra/pluginclient.h
+++ b/cinelerra-5.1/cinelerra/pluginclient.h
@@ -30,6 +30,7 @@ class PluginClient;
 
 
 #include "arraylist.h"
+#include "linklist.h"
 #include "bchash.inc"
 #include "condition.h"
 #include "edlsession.inc"
@@ -137,25 +138,42 @@ int plugin_class::load_configuration() \
 }
 
 
+class PluginClientFrame : public ListItem<PluginClientFrame>
+{
+public:
+	PluginClientFrame();
+	virtual ~PluginClientFrame();
+// offset in EDL seconds for synchronizing with GUI
+	double position;
+};
+
+class PluginClientFrames : public List<PluginClientFrame>
+{
+public:
+	PluginClientFrames();
+	~PluginClientFrames();
+
+	static int fwd_cmpr(PluginClientFrame *ap, PluginClientFrame *bp);
+	static int rev_cmpr(PluginClientFrame *ap, PluginClientFrame *bp);
+	void fwd_sort() { sort(fwd_cmpr); }
+	void rev_sort() { sort(rev_cmpr); }
+	void sort_position(int dir);
+	void reset();
+	void add_gui_frame(PluginClientFrame *frame);
+	void concatenate(PluginClientFrames *frames);
+	PluginClientFrame *get_gui_frame(double pos, int dir);
+
+	int count;
+};
 
 
 class PluginClientWindow : public BC_Window
 {
 public:
 	PluginClientWindow(PluginClient *client,
-		int w,
-		int h,
-		int min_w,
-		int min_h,
-		int allow_resize);
-	PluginClientWindow(const char *title,
-		int x,
-		int y,
-		int w,
-		int h,
-		int min_w,
-		int min_h,
-		int allow_resize);
+		int w, int h, int min_w, int min_h, int allow_resize);
+	PluginClientWindow(const char *title, int x, int y,
+		int w, int h, int min_w, int min_h, int allow_resize);
 	virtual ~PluginClientWindow();
 
 	virtual int translation_event();
@@ -171,86 +189,70 @@ public:
 class PluginFPot : public BC_FPot
 {
 public:
-    PluginFPot(PluginParam *param, int x, int y);
-    int handle_event();
-        PluginParam *param;
+	PluginFPot(PluginParam *param, int x, int y);
+	int handle_event();
+		PluginParam *param;
 };
 
 class PluginIPot : public BC_IPot
 {
 public:
-    PluginIPot(PluginParam *param, int x, int y);
-    int handle_event();
-        PluginParam *param;
+	PluginIPot(PluginParam *param, int x, int y);
+	int handle_event();
+		PluginParam *param;
 };
 
 class PluginQPot : public BC_QPot
 {
 public:
-    PluginQPot(PluginParam *param, int x, int y);
-    int handle_event();
-        PluginParam *param;
+	PluginQPot(PluginParam *param, int x, int y);
+	int handle_event();
+		PluginParam *param;
 };
 
 class PluginText : public BC_TextBox
 {
 public:
-    PluginText(PluginParam *param, int x, int y, int value);
-    PluginText(PluginParam *param, int x, int y, float value);
-    int handle_event();
-        PluginParam *param;
+	PluginText(PluginParam *param, int x, int y, int value);
+	PluginText(PluginParam *param, int x, int y, float value);
+	int handle_event();
+		PluginParam *param;
 };
 
 class PluginParam
 {
 public:
-    PluginParam(PluginClient *plugin,
-        PluginClientWindow *gui,
-        int x1,
-        int x2,
-        int x3,
-        int y,
-        int text_w,
-        int *output_i,
-        float *output_f, // floating point output
-        int *output_q, // frequency output
-        const char *title,
-        float min,
-        float max);
-    ~PluginParam();
-
-    void initialize();
-    void update(int skip_text, int skip_pot);
+	PluginParam(PluginClient *plugin, PluginClientWindow *gui,
+		int x1, int x2, int x3, int y, int text_w,
+		int *output_i, float *output_f, // floating point output
+		int *output_q, // frequency output
+		const char *title, float min, float max);
+	~PluginParam();
+
+	void initialize();
+	void update(int skip_text, int skip_pot);
 // set the number of fractional digits
-    void set_precision(int digits);
-
-// 2 possible outputs
-    float *output_f;
-    PluginFPot *fpot;
-
-    int *output_i;
-    PluginIPot *ipot;
-
-    int *output_q;
-    PluginQPot *qpot;
-
-    char *title;
-    PluginText *text;
-    PluginClientWindow *gui;
-    PluginClient *plugin;
-    int x1;
-    int x2;
-    int x3;
-    int y;
-    int text_w;
-    float min;
-    float max;
-    int precision;
+	void set_precision(int digits);
+
+// possible outputs
+	float *output_f;
+	PluginFPot *fpot;
+	int *output_i;
+	PluginIPot *ipot;
+	int *output_q;
+	PluginQPot *qpot;
+
+	char *title;
+	PluginText *text;
+	PluginClientWindow *gui;
+	PluginClient *plugin;
+	int x1, x2, x3;
+	int y, text_w;
+	float min, max;
+	int precision;
 };
 
 
-
-
 class PluginClientThread : public Thread
 {
 public:
@@ -271,22 +273,6 @@ private:
 
 
 
-// Client overrides for GUI update data
-class PluginClientFrame
-{
-public:
-// Period_d is 1 second
-	PluginClientFrame(int data_size, int period_n, int period_d);
-	virtual ~PluginClientFrame();
-	int data_size;
-	int period_n;
-	int period_d;
-// Draw immediately
-	int force;
-};
-
-
-
 class PluginClient
 {
 public:
@@ -311,11 +297,6 @@ public:
 // Get theme being used by Cinelerra currently.  Used by all plugins.
 	Theme* get_theme();
 
-
-
-
-
-
 // Non realtime signal processors define these.
 // Give the samplerate of the output for a non realtime plugin.
 // For realtime plugins give the requested samplerate.
@@ -324,20 +305,17 @@ public:
 // For realtime plugins give the requested framerate.
 	virtual double get_framerate();
 	virtual int delete_nonrealtime_parameters();
-	virtual int get_parameters();     // get information from user before non realtime processing
+	virtual int get_parameters();	 // get information from user before non realtime processing
 	virtual int64_t get_in_buffers(int64_t recommended_size);  // return desired size for input buffers
-	virtual int64_t get_out_buffers(int64_t recommended_size);     // return desired size for output buffers
+	virtual int64_t get_out_buffers(int64_t recommended_size);	 // return desired size for output buffers
 	virtual int start_loop();
 	virtual int process_loop();
 	virtual int stop_loop();
 // Hash files are the defaults for rendered plugins
-	virtual int load_defaults();       // load default settings for the plugin
-	virtual int save_defaults();      // save the current settings as defaults
+	virtual int load_defaults();	   // load default settings for the plugin
+	virtual int save_defaults();	  // save the current settings as defaults
 	BC_Hash* get_defaults();
 
-
-
-
 // Realtime commands for signal processors.
 // These must be defined by the plugin itself.
 // Set the GUI title identifying the plugin to modules and patches.
@@ -360,22 +338,14 @@ public:
 	int is_defaults();
 
 	virtual void update_gui();
-	virtual void save_data(KeyFrame *keyframe) {};    // write the plugin settings to text in text format
-	virtual void read_data(KeyFrame *keyframe) {};    // read the plugin settings from the text
-	int send_hide_gui();                                    // should be sent when the GUI receives a close event from the user
+	virtual void save_data(KeyFrame *keyframe) {};	// write the plugin settings to text in text format
+	virtual void read_data(KeyFrame *keyframe) {};	// read the plugin settings from the text
+	int send_hide_gui();									// should be sent when the GUI receives a close event from the user
 // Destroys the window but not the thread pointer.
 	void hide_gui();
-
-	int get_configure_change();                             // get propogated configuration change from a send_configure_change
-
-// Called by plugin server to update GUI with rendered data.
-	void plugin_render_gui(void *data);
-	void plugin_render_gui(void *data, int size);
-
-	void begin_process_buffer();
-	void end_process_buffer();
-
 	void plugin_update_gui();
+	virtual void begin_process_buffer() {}
+	virtual void end_process_buffer() {}
 	virtual int plugin_process_loop(VFrame **buffers, int64_t &write_length) { return 1; };
 	virtual int plugin_process_loop(Samples **buffers, int64_t &write_length) { return 1; };
 // get parameters depending on video or audio
@@ -396,8 +366,8 @@ public:
 // data for every frame.
 // If the result is the default keyframe, the keyframe's position is 0.
 // position - relative to EDL rate or local rate to allow simple
-//     passing of get_source_position.
-//     If -1 the tracking position in the edl is used.
+//	 passing of get_source_position.
+//	 If -1 the tracking position in the edl is used.
 // is_local - if 1, the position is converted to the EDL rate.
 	KeyFrame* get_prev_keyframe(int64_t position, int is_local = 1);
 	KeyFrame* get_next_keyframe(int64_t position, int is_local = 1);
@@ -449,6 +419,10 @@ public:
 // Get the direction of the most recent process_buffer
 	int get_direction();
 
+// position and direction for plugin gui tracking draws
+	double get_tracking_position();
+	int get_tracking_direction();
+
 // Plugin must call this before performing OpenGL operations.
 // Returns 1 if the user supports opengl buffers.
 	int get_use_opengl();
@@ -467,8 +441,6 @@ public:
 	float get_green();
 	float get_blue();
 
-
-
 // Operations for file handlers
 	virtual int open_file() { return 0; };
 	virtual int get_audio_parameters() { return 0; };
@@ -477,20 +449,12 @@ public:
 	virtual int open_file(char *path, int wr, int rd) { return 1; };
 	virtual int close_file() { return 0; };
 
-
-
-
-
 // All plugins define these.
 	PluginClientThread* get_thread();
 
-
-
 // Non realtime operations for signal processors.
-	virtual int plugin_start_loop(int64_t start,
-		int64_t end,
-		int64_t buffer_size,
-		int total_buffers);
+	virtual int plugin_start_loop(int64_t start, int64_t end,
+		int64_t buffer_size, int total_buffers);
 	int plugin_stop_loop();
 	int plugin_process_loop();
 	MainProgressBar* start_progress(char *string, int64_t length);
@@ -508,7 +472,7 @@ public:
 	int write_frames(int64_t total_frames);  // returns 1 for failure / tells the server that all output channel buffers are ready to go
 	int write_samples(int64_t total_samples);  // returns 1 for failure / tells the server that all output channel buffers are ready to go
 	virtual int plugin_get_parameters();
-	const char* get_defaultdir();     // Directory defaults should be stored in
+	const char* get_defaultdir();	 // Directory defaults should be stored in
 	void set_interactive();
 
 // Realtime operations.
@@ -517,48 +481,37 @@ public:
 	virtual int plugin_command_derived(int plugin_command) { return 0; };
 	int plugin_get_range();
 	int plugin_init_realtime(int realtime_priority,
-		int total_in_buffers,
-		int buffer_size);
-
+		int total_in_buffers, int buffer_size);
 
 // GUI updating wrappers for realtime plugins
 // Append frame to queue for next send_frame_buffer
 	void add_gui_frame(PluginClientFrame *frame);
 
-
-
 	virtual void render_gui(void *data);
 	virtual void render_gui(void *data, int size);
-
-// Called by client to get the total number of frames to draw in update_gui
-	int get_gui_update_frames();
-// Get GUI frame from frame_buffer.  Client must delete it.
-	PluginClientFrame* get_gui_frame();
-
-// Called by client to cause GUI to be rendered with data.
 	void send_render_gui();
 	void send_render_gui(void *data);
 	void send_render_gui(void *data, int size);
+	void plugin_render_gui(void *data);
+	void plugin_render_gui(void *data, int size);
 
-
-
-
-
-
-
-
-// create pointers to buffers of the plugin's type before realtime rendering
-	virtual int delete_buffer_ptrs();
-
-
-
+	void reset_gui_frames();
+	void reset_plugin_gui_frames();
+	void plugin_reset_gui_frames();
+	void plugin_render_gui_frames(PluginClientFrames *frames);
+	int get_gui_frames();
+// Called by client to get the total number of frames to draw in update_gui
+	int pending_gui_frames();
+// pop frames until buffer passes position=pos(-1 or seconds) in direction=dir(-1,0,1)
+	PluginClientFrame *get_gui_frame(double pos, int dir);
+	PluginClientFrame* next_gui_frame();
 
 // communication convenience routines for the base class
 	int stop_gui_client();
 	int save_data_client();
 	int load_data_client();
-	int set_string_client(char *string);                // set the string identifying the plugin
-	int send_cancelled();        // non realtime plugin sends when cancelled
+	int set_string_client(char *string);				// set the string identifying the plugin
+	int send_cancelled();		// non realtime plugin sends when cancelled
 
 // ================================= Buffers ===============================
 
@@ -580,18 +533,18 @@ public:
 	ArrayList<PluginClientAuto> automation;
 
 // ================================== Messages ===========================
-	char gui_string[BCTEXTLEN];          // string identifying module and plugin
-	int master_gui_on;              // Status of the master gui plugin
-	int client_gui_on;              // Status of this client's gui
+	char gui_string[BCTEXTLEN];		  // string identifying module and plugin
+	int master_gui_on;			  // Status of the master gui plugin
+	int client_gui_on;			  // Status of this client's gui
 
-	int show_initially;             // set to show a realtime plugin initially
+	int show_initially;			 // set to show a realtime plugin initially
 // range in project for processing
 	int64_t start, end;
-	int interactive;                // for the progress bar plugin
+	int interactive;				// for the progress bar plugin
 	int success;
-	int total_out_buffers;          // total send buffers allocated by the server
-	int total_in_buffers;           // total receive buffers allocated by the server
-	int wr, rd;                     // File permissions for fileio plugins.
+	int total_out_buffers;		  // total send buffers allocated by the server
+	int total_in_buffers;		   // total receive buffers allocated by the server
+	int wr, rd;					 // File permissions for fileio plugins.
 
 // These give the largest fragment the plugin is expected to handle.
 // size of a send buffer to the server
@@ -624,7 +577,7 @@ public:
 	PluginClientThread *thread;
 
 // Frames for updating GUI
-	ArrayList<PluginClientFrame*> frame_buffer;
+	PluginClientFrames frame_buffer;
 // Time of last GUI update
 	Timer *update_timer;
 
@@ -634,7 +587,7 @@ private:
 // Temporaries set in new_window
 	int window_x, window_y;
 // File handlers:
-//	Asset *asset;     // Point to asset structure in shared memory
+//	Asset *asset;	 // Point to asset structure in shared memory
 };
 
 
diff --git a/cinelerra-5.1/cinelerra/pluginclient.inc b/cinelerra-5.1/cinelerra/pluginclient.inc
index f9ee65b6..494af128 100644
--- a/cinelerra-5.1/cinelerra/pluginclient.inc
+++ b/cinelerra-5.1/cinelerra/pluginclient.inc
@@ -23,6 +23,8 @@
 #define PLUGINCLIENT_INC
 
 class PluginClientAuto;
+class PluginClientFrames;
+class PluginClientFrame;
 class PluginClientWindow;
 class PluginFPot;
 class PluginIPot;
@@ -30,7 +32,6 @@ class PluginQPot;
 class PluginText;
 class PluginParam;
 class PluginClientThread;
-class PluginClientFrame;
 class PluginClient;
 
 #endif
diff --git a/cinelerra-5.1/cinelerra/pluginserver.C b/cinelerra-5.1/cinelerra/pluginserver.C
index 68724f27..02f19f33 100644
--- a/cinelerra-5.1/cinelerra/pluginserver.C
+++ b/cinelerra-5.1/cinelerra/pluginserver.C
@@ -92,8 +92,8 @@ PluginServer::PluginServer(MWindow *mwindow, const char *path, int type)
 {
 	char fpath[BCTEXTLEN];
 	init();
-        this->plugin_type = type;
-        this->mwindow = mwindow;
+	this->plugin_type = type;
+	this->mwindow = mwindow;
 	if( type == PLUGIN_TYPE_FFMPEG ) {
 		ff_name = cstrdup(path);
 		sprintf(fpath, "ff_%s", path);
@@ -295,12 +295,12 @@ void PluginServer::set_title(const char *string)
 void PluginServer::generate_display_title(char *string)
 {
 	char ltitle[BCTEXTLEN];
-	if(BC_Resources::locale_utf8)
+	if( BC_Resources::locale_utf8 )
 		strcpy(ltitle, _(title));
 	else
 		BC_Resources::encode(BC_Resources::encoding, 0,
 				_(title),strlen(title)+1, ltitle,BCTEXTLEN);
-	if(plugin && plugin->track)
+	if( plugin && plugin->track )
 		sprintf(string, "%s: %s", plugin->track->title, ltitle);
 	else
 		strcpy(string, ltitle);
@@ -344,7 +344,7 @@ int PluginServer::open_plugin(int master,
 	EDL *edl,
 	Plugin *plugin)
 {
-	if(plugin_open) return 0;
+	if( plugin_open ) return 0;
 
 	this->preferences = preferences;
 	this->plugin = plugin;
@@ -403,8 +403,8 @@ int PluginServer::open_plugin(int master,
 // Run initialization functions
 	realtime = client->is_realtime();
 // Don't load defaults when probing the directory.
-	if(!master) {
-		if(realtime)
+	if( !master ) {
+		if( realtime )
 			client->load_defaults_xml();
 		else
 			client->load_defaults();
@@ -426,12 +426,11 @@ int PluginServer::open_plugin(int master,
 
 int PluginServer::close_plugin()
 {
-	if(!plugin_open) return 0;
+	if( !plugin_open ) return 0;
 
-	if(client)
-	{
+	if( client ) {
 // Defaults are saved in the thread.
-//		if(client->defaults) client->save_defaults();
+//		if( client->defaults ) client->save_defaults();
 		delete client;
 	}
 
@@ -444,11 +443,10 @@ int PluginServer::close_plugin()
 void PluginServer::client_side_close()
 {
 // Last command executed in client thread
-	if(plugin)
+	if( plugin )
 		mwindow->hide_plugin(plugin, 1);
 	else
-	if(prompt)
-	{
+	if( prompt ) {
 		prompt->lock_window();
 		prompt->set_done(1);
 		prompt->unlock_window();
@@ -457,13 +455,14 @@ void PluginServer::client_side_close()
 
 void PluginServer::render_stop()
 {
-	if(client)
+	if( client )
 		client->render_stop();
+	send_reset_gui_frames();
 }
 
 void PluginServer::write_table(FILE *fp, const char *path, int idx, int64_t mtime)
 {
-	if(!fp) return;
+	if( !fp ) return;
 	fprintf(fp, "%d \"%s\" \"%s\" %jd %d %d %d %d %d %d %d %d %d %d %d\n",
 		plugin_type, path, title, mtime, idx, audio, video, theme, realtime,
 		fileio, uses_gui, multichannel, synthesis, transition, lad_index);
@@ -492,7 +491,7 @@ int PluginServer::init_realtime(int realtime_sched,
 		int buffer_size)
 {
 
-	if(!plugin_open) return 0;
+	if( !plugin_open ) return 0;
 
 // set for realtime priority
 // initialize plugin
@@ -512,7 +511,7 @@ void PluginServer::process_transition(VFrame *input,
 		int64_t current_position,
 		int64_t total_len)
 {
-	if(!plugin_open) return;
+	if( !plugin_open ) return;
 	PluginVClient *vclient = (PluginVClient*)client;
 
 	vclient->source_position = current_position;
@@ -538,7 +537,7 @@ void PluginServer::process_transition(Samples *input,
 		int64_t fragment_size,
 		int64_t total_len)
 {
-	if(!plugin_open) return;
+	if( !plugin_open ) return;
 	PluginAClient *aclient = (PluginAClient*)client;
 
 	aclient->source_position = current_position;
@@ -556,50 +555,41 @@ void PluginServer::process_buffer(VFrame **frame,
 	int64_t total_len,
 	int direction)
 {
-	if(!plugin_open) return;
+	if( !plugin_open ) return;
 	PluginVClient *vclient = (PluginVClient*)client;
-
+	vclient->in_buffer_size = vclient->out_buffer_size = 1;
 	vclient->source_position = current_position;
 	vclient->total_len = total_len;
 	vclient->frame_rate = frame_rate;
 	vclient->input = new VFrame*[total_in_buffers];
 	vclient->output = new VFrame*[total_in_buffers];
-	for(int i = 0; i < total_in_buffers; i++)
-	{
+	for( int i = 0; i < total_in_buffers; i++ ) {
 		vclient->input[i] = frame[i];
 		vclient->output[i] = frame[i];
 	}
 
-	if(plugin)
-	{
+	if( plugin ) {
 		vclient->source_start = (int64_t)plugin->startproject *
-			frame_rate /
-			vclient->project_frame_rate;
+			frame_rate / vclient->project_frame_rate;
 	}
 	vclient->direction = direction;
 
-
 //PRINT_TRACE
 //printf("plugin=%p source_start=%ld\n", plugin, vclient->source_start);
-
-	vclient->begin_process_buffer();
-	if(multichannel)
-	{
+//	vclient->begin_process_buffer();
+	if( multichannel )
 		vclient->process_buffer(frame, current_position, frame_rate);
-	}
 	else
-	{
 		vclient->process_buffer(frame[0], current_position, frame_rate);
-	}
-	vclient->end_process_buffer();
+//	vclient->end_process_buffer();
 
-	for(int i = 0; i < total_in_buffers; i++)
+	for( int i = 0; i < total_in_buffers; i++ )
 		frame[i]->push_prev_effect(title);
 
 	delete [] vclient->input;
 	delete [] vclient->output;
 
-    vclient->age_temp();
+	vclient->age_temp();
 	use_opengl = 0;
 }
 
@@ -610,38 +600,64 @@ void PluginServer::process_buffer(Samples **buffer,
 	int64_t total_len,
 	int direction)
 {
-	if(!plugin_open) return;
+	if( !plugin_open ) return;
 	PluginAClient *aclient = (PluginAClient*)client;
-
 	aclient->source_position = current_position;
 	aclient->total_len = total_len;
 	aclient->sample_rate = sample_rate;
+	aclient->in_buffer_size = aclient->out_buffer_size = fragment_size;
+	aclient->output_buffers = buffer;
 
-	if(plugin)
+	if( plugin )
 		aclient->source_start = plugin->startproject *
 			sample_rate /
 			aclient->project_sample_rate;
 
 	aclient->direction = direction;
 	aclient->begin_process_buffer();
-	if(multichannel)
-	{
+	if( multichannel ) {
 		aclient->process_buffer(fragment_size,
-			buffer,
-			current_position,
-			sample_rate);
+			buffer, current_position, sample_rate);
 	}
-	else
-	{
+	else {
 		aclient->process_buffer(fragment_size,
-			buffer[0],
-			current_position,
-			sample_rate);
+			buffer[0], current_position, sample_rate);
 	}
 	aclient->end_process_buffer();
 }
 
 
+void PluginServer::send_reset_gui_frames()
+{
+	if( !attachmentpoint ) return;
+	attachmentpoint->reset_gui_frames(this);
+}
+
+void PluginServer::send_render_gui(void *data)
+{
+	if( !attachmentpoint ) return;
+	attachmentpoint->render_gui(data, this);
+}
+
+void PluginServer::send_render_gui(void *data, int size)
+{
+	if( !attachmentpoint ) return;
+	attachmentpoint->render_gui(data, size, this);
+}
+
+void PluginServer::render_gui(void *data)
+{
+	if( !client ) return;
+	client->plugin_render_gui(data);
+}
+
+void PluginServer::render_gui(void *data, int size)
+{
+	if( !client ) return;
+	client->plugin_render_gui(data, size);
+}
+
+
 PluginGUIs::PluginGUIs(MWindow *mwindow)
 {
 	this->mwindow = mwindow;
@@ -668,26 +684,26 @@ PluginServer *PluginGUIs::gui_server(int gui_id)
 }
 
 
-void PluginServer::send_render_gui(void *data)
+void PluginServer::reset_gui_frames()
 {
-//printf("PluginServer::send_render_gui 1 %p\n", attachmentpoint);
-	if(attachmentpoint) attachmentpoint->render_gui(data, this);
+	mwindow->reset_plugin_gui_frames(plugin);
 }
 
-void PluginServer::send_render_gui(void *data, int size)
+void PluginServer::reset_plugin_gui_frames()
 {
-//printf("PluginServer::send_render_gui 1 %p\n", attachmentpoint);
-	if(attachmentpoint) attachmentpoint->render_gui(data, size, this);
+	if( !client ) return;
+	client->plugin_reset_gui_frames();
 }
 
-void PluginServer::render_gui(void *data)
+void PluginServer::render_gui_frames(PluginClientFrames *frames)
 {
-	if(client) client->plugin_render_gui(data);
+	mwindow->render_plugin_gui_frames(frames, plugin);
 }
 
-void PluginServer::render_gui(void *data, int size)
+void PluginServer::render_plugin_gui_frames(PluginClientFrames *frames)
 {
-	if(client) client->plugin_render_gui(data, size);
+	if( !client ) return;
+	client->plugin_render_gui_frames(frames);
 }
 
 MainProgressBar* PluginServer::start_progress(char *string, int64_t length)
@@ -700,30 +716,22 @@ MainProgressBar* PluginServer::start_progress(char *string, int64_t length)
 
 int64_t PluginServer::get_written_samples()
 {
-	if(!plugin_open) return 0;
+	if( !plugin_open ) return 0;
 	return written_samples;
 }
 
 int64_t PluginServer::get_written_frames()
 {
-	if(!plugin_open) return 0;
+	if( !plugin_open ) return 0;
 	return written_frames;
 }
 
 
-
-
-
-
-
-
-
-
 // ======================= Non-realtime plugin
 
 int PluginServer::get_parameters(int64_t start, int64_t end, int channels)
 {
-	if(!plugin_open) return 0;
+	if( !plugin_open ) return 0;
 
 	client->start = start;
 	client->end = end;
@@ -739,7 +747,7 @@ int PluginServer::get_parameters(int64_t start, int64_t end, int channels)
 
 int PluginServer::set_interactive()
 {
-	if(!plugin_open) return 0;
+	if( !plugin_open ) return 0;
 	client->set_interactive();
 	return 0;
 }
@@ -776,13 +784,13 @@ int PluginServer::set_realtime_sched()
 
 int PluginServer::process_loop(VFrame **buffers, int64_t &write_length)
 {
-	if(!plugin_open) return 1;
+	if( !plugin_open ) return 1;
 	return client->plugin_process_loop(buffers, write_length);
 }
 
 int PluginServer::process_loop(Samples **buffers, int64_t &write_length)
 {
-	if(!plugin_open) return 1;
+	if( !plugin_open ) return 1;
 	return client->plugin_process_loop(buffers, write_length);
 }
 
@@ -792,14 +800,14 @@ int PluginServer::start_loop(int64_t start,
 	int64_t buffer_size,
 	int total_buffers)
 {
-	if(!plugin_open) return 0;
+	if( !plugin_open ) return 0;
 	client->plugin_start_loop(start, end, buffer_size, total_buffers);
 	return 0;
 }
 
 int PluginServer::stop_loop()
 {
-	if(!plugin_open) return 0;
+	if( !plugin_open ) return 0;
 	return client->plugin_stop_loop();
 }
 
@@ -808,66 +816,42 @@ int PluginServer::read_frame(VFrame *buffer,
 	int64_t start_position)
 {
 	((VModule*)modules->values[channel])->render(buffer,
-		start_position,
-		PLAY_FORWARD,
-		mwindow->edl->session->frame_rate,
-		0,
-		0);
+		start_position, PLAY_FORWARD,
+		mwindow->edl->session->frame_rate, 0, 0);
 	return 0;
 }
 
 int PluginServer::read_samples(Samples *buffer,
-	int channel,
-	int64_t sample_rate,
-	int64_t start_position,
-	int64_t len)
+	int channel, int64_t sample_rate, int64_t start_position, int64_t len)
 {
 // len is now in buffer
-	if(!multichannel) channel = 0;
+	if( !multichannel ) channel = 0;
 
-	if(nodes->total > channel)
+	if( nodes->total > channel )
 		return ((VirtualANode*)nodes->values[channel])->read_data(buffer,
-			len,
-			start_position,
-			sample_rate);
-	else
-	if(modules->total > channel)
+			len, start_position, sample_rate);
+	if( modules->total > channel )
 		return ((AModule*)modules->values[channel])->render(buffer,
-			len,
-			start_position,
-			PLAY_FORWARD,
-			sample_rate,
-			0);
-	else
-	{
-		printf("PluginServer::read_samples no object available for channel=%d\n",
-			channel);
-	}
-
+			len, start_position, PLAY_FORWARD, sample_rate, 0);
+	printf("PluginServer::read_samples no object available for channel=%d\n",
+		channel);
 	return -1;
 }
 
 
 int PluginServer::read_samples(Samples *buffer,
-	int channel,
-	int64_t start_position,
-	int64_t size)
+		int channel, int64_t start_position, int64_t size)
 {
 // total_samples is now set in buffer
 	((AModule*)modules->values[channel])->render(buffer,
-		size,
-		start_position,
-		PLAY_FORWARD,
-		mwindow->edl->session->sample_rate,
-		0);
+		size, start_position, PLAY_FORWARD,
+		mwindow->edl->session->sample_rate, 0);
 	return 0;
 }
 
 int PluginServer::read_frame(VFrame *buffer,
-	int channel,
-	int64_t start_position,
-	double frame_rate,
-	int use_opengl)
+		int channel, int64_t start_position, double frame_rate,
+		int use_opengl)
 {
 // Data source depends on whether we're part of a virtual console or a
 // plugin array.
@@ -879,36 +863,26 @@ int PluginServer::read_frame(VFrame *buffer,
 //PRINT_TRACE
 
 	int result = -1;
-	if(!multichannel) channel = 0;
+	if( !multichannel ) channel = 0;
 
 // Push our name on the next effect stack
 	buffer->push_next_effect(title);
 //printf("PluginServer::read_frame %p\n", buffer);
 //buffer->dump_stacks();
 
-	if(nodes->total > channel)
-	{
+	if( nodes->total > channel ) {
 //printf("PluginServer::read_frame %d\n", __LINE__);
 		result = ((VirtualVNode*)nodes->values[channel])->read_data(buffer,
-			start_position,
-			frame_rate,
-			use_opengl);
+			start_position, frame_rate, use_opengl);
 	}
 	else
-	if(modules->total > channel)
-	{
+	if( modules->total > channel ) {
 //printf("PluginServer::read_frame %d\n", __LINE__);
 		result = ((VModule*)modules->values[channel])->render(buffer,
-			start_position,
-//			PLAY_FORWARD,
-			client->direction,
-			frame_rate,
-			0,
-			0,
-			use_opengl);
+			start_position, //			PLAY_FORWARD,
+			client->direction, frame_rate, 0, 0, use_opengl);
 	}
-	else
-	{
+	else {
 		printf("PluginServer::read_frame no object available for channel=%d\n",
 			channel);
 	}
@@ -920,28 +894,10 @@ int PluginServer::read_frame(VFrame *buffer,
 }
 
 
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
 // Called by client
 int PluginServer::get_gui_status()
 {
-	if(plugin)
+	if( plugin )
 		return plugin->show ? GUI_ON : GUI_OFF;
 	else
 		return GUI_OFF;
@@ -949,27 +905,25 @@ int PluginServer::get_gui_status()
 
 void PluginServer::raise_window()
 {
-	if(!plugin_open) return;
+	if( !plugin_open ) return;
 	client->raise_window();
 }
 
 void PluginServer::show_gui()
 {
-	if(!plugin_open) return;
+	if( !plugin_open ) return;
 	if( plugin ) {
 		plugin->gui_id = gui_id;
 		client->total_len = plugin->length;
 		client->source_start = plugin->startproject;
 	}
-	if(video)
-	{
+	if( video ) {
 		client->source_position = Units::to_int64(
 			mwindow->edl->local_session->get_selectionstart(1) *
 				mwindow->edl->session->frame_rate);
 	}
 	else
-	if(audio)
-	{
+	if( audio ) {
 		client->source_position = Units::to_int64(
 			mwindow->edl->local_session->get_selectionstart(1) *
 				mwindow->edl->session->sample_rate);
@@ -981,28 +935,25 @@ void PluginServer::show_gui()
 
 void PluginServer::hide_gui()
 {
-	if(!plugin_open) return;
+	if( !plugin_open ) return;
 	if( plugin ) plugin->gui_id = -1;
-	if(client->defaults) client->save_defaults();
+	if( client->defaults ) client->save_defaults();
 	client->hide_gui();
 }
 
 void PluginServer::update_gui()
 {
-	if(!plugin_open || !plugin) return;
+	if( !plugin_open || !plugin ) return;
 
 	client->total_len = plugin->length;
 	client->source_start = plugin->startproject;
 
-	if(video)
-	{
+	if( video ) {
 		client->source_position = Units::to_int64(
 			mwindow->edl->local_session->get_selectionstart(1) *
 				mwindow->edl->session->frame_rate);
 	}
-	else
-	if(audio)
-	{
+	else if( audio ) {
 		client->source_position = Units::to_int64(
 			mwindow->edl->local_session->get_selectionstart(1) *
 				mwindow->edl->session->sample_rate);
@@ -1013,7 +964,7 @@ void PluginServer::update_gui()
 
 void PluginServer::update_title()
 {
-	if(!plugin_open) return;
+	if( !plugin_open ) return;
 
 	client->update_display_title();
 }
@@ -1021,7 +972,7 @@ void PluginServer::update_title()
 
 int PluginServer::set_string(char *string)
 {
-	if(!plugin_open) return 0;
+	if( !plugin_open ) return 0;
 
 	client->set_string_client(string);
 	return 0;
@@ -1029,7 +980,7 @@ int PluginServer::set_string(char *string)
 
 int PluginServer::gui_open()
 {
-	if(attachmentpoint) return attachmentpoint->gui_open();
+	if( attachmentpoint ) return attachmentpoint->gui_open();
 	return 0;
 }
 
@@ -1047,7 +998,7 @@ int PluginServer::get_use_opengl()
 
 void PluginServer::run_opengl(PluginClient *plugin_client)
 {
-	if(vdevice)
+	if( vdevice )
 		((VDeviceX11*)vdevice->get_output_base())->run_plugin(plugin_client);
 }
 
@@ -1058,8 +1009,8 @@ void PluginServer::get_defaults_path(char *path)
 // Get plugin name from path
 	char *ptr1 = strrchr(get_path(), '/');
 	char *ptr2 = strrchr(get_path(), '.');
-	if(!ptr1) ptr1 = get_path();
-	if(!ptr2) ptr2 = get_path() + strlen(get_path());
+	if( !ptr1 ) ptr1 = get_path();
+	if( !ptr2 ) ptr2 = get_path() + strlen(get_path());
 	char string2[BCTEXTLEN], *ptr3 = string2;
 	while( ptr1 < ptr2 ) *ptr3++ = *ptr1++;
 	*ptr3 = 0;
@@ -1068,74 +1019,53 @@ void PluginServer::get_defaults_path(char *path)
 
 void PluginServer::save_defaults()
 {
-	if(client) client->save_defaults();
+	if( client ) client->save_defaults();
 }
 
 int PluginServer::get_samplerate()
 {
-	if(!plugin_open) return 0;
-	if(audio)
-	{
+	if( !plugin_open ) return 0;
+	if( audio )
 		return client->get_samplerate();
-	}
-	else
-	if(mwindow)
+	if( mwindow )
 		return mwindow->edl->session->sample_rate;
-	else
-	{
-		printf("PluginServer::get_samplerate audio and mwindow == NULL\n");
-		return 1;
-	}
+	printf("PluginServer::get_samplerate audio and mwindow == NULL\n");
+	return 1;
 }
 
 
 double PluginServer::get_framerate()
 {
-	if(!plugin_open) return 0;
-	if(video)
-	{
+	if( !plugin_open ) return 0;
+	if( video )
 		return client->get_framerate();
-	}
-	else
-	if(mwindow)
+	if( mwindow )
 		return mwindow->edl->session->frame_rate;
-	else
-	{
-		printf("PluginServer::get_framerate video and mwindow == NULL\n");
-		return 1;
-	}
+	printf("PluginServer::get_framerate video and mwindow == NULL\n");
+	return 1;
 }
 
 int PluginServer::get_project_samplerate()
 {
-	if(mwindow)
+	if( mwindow )
 		return mwindow->edl->session->sample_rate;
-	else
-	if(edl)
+	if( edl )
 		return edl->session->sample_rate;
-	else
-	{
-		printf("PluginServer::get_project_samplerate mwindow and edl are NULL.\n");
-		return 1;
-	}
+	printf("PluginServer::get_project_samplerate mwindow and edl are NULL.\n");
+	return 1;
 }
 
 double PluginServer::get_project_framerate()
 {
-	if(mwindow)
+	if( mwindow )
 		return mwindow->edl->session->frame_rate;
-	else
-	if(edl)
+	if( edl )
 		return edl->session->frame_rate;
-	else
-	{
-		printf("PluginServer::get_project_framerate mwindow and edl are NULL.\n");
-		return 1;
-	}
+	printf("PluginServer::get_project_framerate mwindow and edl are NULL.\n");
+	return 1;
 }
 
 
-
 int PluginServer::detach_buffers()
 {
 	ring_buffers_out.remove_all();
@@ -1157,10 +1087,8 @@ int PluginServer::detach_buffers()
 }
 
 int PluginServer::arm_buffer(int buffer_number,
-		int64_t offset_in,
-		int64_t offset_out,
-		int double_buffer_in,
-		int double_buffer_out)
+		int64_t offset_in, int64_t offset_out,
+		int double_buffer_in, int double_buffer_out)
 {
 	offset_in_render.values[buffer_number] = offset_in;
 	offset_out_render.values[buffer_number] = offset_out;
@@ -1183,14 +1111,14 @@ int PluginServer::set_automation(FloatAutos *autos, FloatAuto **start_auto, Floa
 
 void PluginServer::save_data(KeyFrame *keyframe)
 {
-	if(!plugin_open) return;
+	if( !plugin_open ) return;
 	client->save_data(keyframe);
 }
 
 KeyFrame* PluginServer::get_prev_keyframe(int64_t position)
 {
 	KeyFrame *result = 0;
-	if(plugin)
+	if( plugin )
 		result = plugin->get_prev_keyframe(position, client->direction);
 	else
 		result = keyframe;
@@ -1199,43 +1127,32 @@ KeyFrame* PluginServer::get_prev_keyframe(int64_t position)
 
 KeyFrame* PluginServer::get_next_keyframe(int64_t position)
 {
-	KeyFrame *result = 0;
-	if(plugin)
-		result = plugin->get_next_keyframe(position, client->direction);
-	else
-		result = keyframe;
+	KeyFrame *result = !plugin ? 0 :
+		plugin->get_next_keyframe(position, client->direction);
 	return result;
 }
 
 // Called for
 KeyFrame* PluginServer::get_keyframe()
 {
-	if(plugin)
+	if( plugin )
 // Realtime plugin case
 		return plugin->get_keyframe();
-	else
 // Rendered plugin case
-		return keyframe;
+	return keyframe;
 }
 
 
 void PluginServer::apply_keyframe(KeyFrame *src)
 {
-	if(!plugin)
-	{
+	if( !plugin )
 		keyframe->copy_data(src);
-	}
 	else
-	{
 // Span keyframes
 		plugin->keyframes->update_parameter(src);
-	}
 }
 
 
-
-
-
 void PluginServer::get_camera(float *x, float *y, float *z,
 	int64_t position, int direction)
 {
@@ -1256,17 +1173,14 @@ int PluginServer::get_interpolation_type()
 
 Theme* PluginServer::new_theme()
 {
-	if(theme)
-	{
+	if( theme )
 		return client->new_theme();
-	}
-	else
-		return 0;
+	return 0;
 }
 
 Theme* PluginServer::get_theme()
 {
-	if(mwindow) return mwindow->theme;
+	if( mwindow ) return mwindow->theme;
 	printf("PluginServer::get_theme mwindow not set\n");
 	return 0;
 }
@@ -1321,11 +1235,10 @@ VFrame *PluginServer::get_picon()
 // Called when plugin interface is tweeked
 void PluginServer::sync_parameters()
 {
-	if(video) mwindow->restart_brender();
+	if( video ) mwindow->restart_brender();
 	mwindow->sync_parameters();
 	mwindow->update_keyframe_guis();
-	if(mwindow->edl->session->auto_conf->plugins)
-	{
+	if( mwindow->edl->session->auto_conf->plugins ) {
 		mwindow->gui->lock_window("PluginServer::sync_parameters");
 		mwindow->gui->draw_overlays(1);
 		mwindow->gui->unlock_window();
diff --git a/cinelerra-5.1/cinelerra/pluginserver.h b/cinelerra-5.1/cinelerra/pluginserver.h
index 5be37a4b..16c2c5cb 100644
--- a/cinelerra-5.1/cinelerra/pluginserver.h
+++ b/cinelerra-5.1/cinelerra/pluginserver.h
@@ -270,9 +270,16 @@ public:
 // Called by rendering client to cause the GUI to display something with the data.
 	void send_render_gui(void *data);
 	void send_render_gui(void *data, int size);
+
 // Called by MWindow to cause GUI to display
 	void render_gui(void *data);
 	void render_gui(void *data, int size);
+// PluginClientFrames queuing to gui frame_buffer
+	void send_reset_gui_frames();
+	void reset_gui_frames();
+	void render_gui_frames(PluginClientFrames *frames);
+	void reset_plugin_gui_frames();
+	void render_plugin_gui_frames(PluginClientFrames *frames);
 
 // Send the boundary autos of the next fragment
 	int set_automation(FloatAutos *autos, FloatAuto **start_auto, FloatAuto **end_auto, int reverse);
diff --git a/cinelerra-5.1/cinelerra/resourcepixmap.C b/cinelerra-5.1/cinelerra/resourcepixmap.C
index c65d1b95..82dd7c80 100644
--- a/cinelerra-5.1/cinelerra/resourcepixmap.C
+++ b/cinelerra-5.1/cinelerra/resourcepixmap.C
@@ -335,7 +335,6 @@ SET_TRACE
 	IndexState *index_state = indexable->index_state;
 	double asset_over_session = (double)indexable->get_sample_rate() /
 		mwindow->edl->session->sample_rate;
-
 // Develop strategy for drawing
 // printf("ResourcePixmap::draw_audio_resource %d %p %d\n",
 // __LINE__,
@@ -345,52 +344,54 @@ SET_TRACE
 	{
 		case INDEX_NOTTESTED:
 			return;
-			break;
 // Disabled.  All files have an index.
 //		case INDEX_TOOSMALL:
 //			draw_audio_source(canvas, edit, x, w);
 //			break;
 		case INDEX_BUILDING:
-		case INDEX_READY:
-		{
+		case INDEX_READY: {
 			IndexFile indexfile(mwindow, indexable);
 			if( !indexfile.open_index() ) {
 				if( index_state->index_zoom >
-						mwindow->edl->local_session->zoom_sample *
+					mwindow->edl->local_session->zoom_sample *
 						asset_over_session ) {
-//printf("ResourcePixmap::draw_audio_resource %d\n", __LINE__);
-
 					draw_audio_source(canvas, edit, x, w);
 				}
 				else {
-//printf("ResourcePixmap::draw_audio_resource %d\n", __LINE__);
-					indexfile.draw_index(canvas,
-						this,
-						edit,
-						x,
-						w);
-SET_TRACE
+					indexfile.draw_index(canvas, this, edit, x, w);
 				}
-
 				indexfile.close_index();
-SET_TRACE
 			}
 			break;
 		}
 	}
+	if( !mwindow->preferences->rectify_audio ) {
+		int center_pixel = calculate_center_pixel(edit->track);
+		canvas->set_line_dashes(1);
+		canvas->set_color(mwindow->theme->zero_crossing_color);
+		canvas->draw_line(x, center_pixel, x + w, center_pixel, this);
+		canvas->set_line_dashes(0);
+	}
 }
 
 
-void ResourcePixmap::draw_audio_source(TrackCanvas *canvas, Edit *edit, int x, int w)
+int ResourcePixmap::calculate_center_pixel(Track *track)
 {
-	w++;
-	Indexable *indexable = edit->get_source();
 	int rect_audio = mwindow->preferences->rectify_audio;
 	int center_pixel = !rect_audio ?
 		mwindow->edl->local_session->zoom_track / 2 :
 		mwindow->edl->local_session->zoom_track ;
-	if( edit->track->show_titles() )
+	if( track->show_titles() )
 		center_pixel += mwindow->theme->get_image("title_bg_data")->get_h();
+	return center_pixel;
+}
+
+void ResourcePixmap::draw_audio_source(TrackCanvas *canvas, Edit *edit, int x, int w)
+{
+	w++;
+	Indexable *indexable = edit->get_source();
+	int center_pixel = calculate_center_pixel(edit->track);
+	int rect_audio = mwindow->preferences->rectify_audio;
 	int64_t scale_y = !rect_audio ?
 		mwindow->edl->local_session->zoom_y :
 		mwindow->edl->local_session->zoom_y * 2;
diff --git a/cinelerra-5.1/cinelerra/resourcepixmap.h b/cinelerra-5.1/cinelerra/resourcepixmap.h
index 7f5fba2c..3610c7a2 100644
--- a/cinelerra-5.1/cinelerra/resourcepixmap.h
+++ b/cinelerra-5.1/cinelerra/resourcepixmap.h
@@ -24,6 +24,7 @@
 
 #include "bctimer.inc"
 #include "edit.inc"
+#include "track.inc"
 #include "guicast.h"
 #include "mwindow.inc"
 #include "trackcanvas.inc"
@@ -46,6 +47,7 @@ public:
 		Edit *edit, int64_t edit_x, int64_t edit_w,
 		int64_t pixmap_x, int64_t pixmap_w, int64_t pixmap_h,
 		int mode, int indexes_only);
+	int calculate_center_pixel(Track *track);
 	void draw_audio_resource(TrackCanvas *canvas,
 		Edit *edit, int x, int w);
 	void draw_video_resource(TrackCanvas *canvas,
diff --git a/cinelerra-5.1/cinelerra/theme.C b/cinelerra-5.1/cinelerra/theme.C
index fb3ff4df..d35e4750 100644
--- a/cinelerra-5.1/cinelerra/theme.C
+++ b/cinelerra-5.1/cinelerra/theme.C
@@ -77,6 +77,13 @@ Theme::Theme()
 	BC_WindowBase::get_resources()->button_highlighted = 0xffe000;
 	BC_WindowBase::get_resources()->recursive_resizing = 0;
 	audio_color = BLACK;
+	zero_crossing_color = 0xc03545;
+	graph_active_color = GRAPH_ACTIVE_COLOR;
+	graph_inactive_color = GRAPH_INACTIVE_COLOR;
+	graph_grid_color = GRAPH_GRID_COLOR;
+	graph_bg_color = GRAPH_BG_COLOR;
+	graph_border1_color = GRAPH_BORDER1_COLOR;
+	graph_border2_color = GRAPH_BORDER2_COLOR;
 	fade_h = yS(22);
 	inout_highlight_color = GREEN;
 	meter_h = yS(17);
diff --git a/cinelerra-5.1/cinelerra/theme.h b/cinelerra-5.1/cinelerra/theme.h
index 7055ee15..1b1e4b38 100644
--- a/cinelerra-5.1/cinelerra/theme.h
+++ b/cinelerra-5.1/cinelerra/theme.h
@@ -25,6 +25,7 @@
 #include "awindowgui.inc"
 #include "batchrender.inc"
 #include "bctheme.h"
+#include "compressortools.inc"
 #include "cwindowgui.inc"
 #include "guicast.h"
 #include "keyframegui.inc"
@@ -165,6 +166,16 @@ public:
 	int afolders_x, afolders_y, afolders_w, afolders_h;
 	int alist_x, alist_y, alist_w, alist_h;
 	int audio_color;
+// audio zero crossing
+	int zero_crossing_color;
+// compressor graph line
+	int graph_active_color;
+	int graph_inactive_color;
+// compressor graph background
+	int graph_grid_color;
+	int graph_bg_color;
+	int graph_border1_color;
+	int graph_border2_color;
 	int assetedit_color;
 	int browse_pad;
 	int cauto_x, cauto_y, cauto_w, cauto_h;
diff --git a/cinelerra-5.1/guicast/arraylist.h b/cinelerra-5.1/guicast/arraylist.h
index ef27522f..602557f5 100644
--- a/cinelerra-5.1/guicast/arraylist.h
+++ b/cinelerra-5.1/guicast/arraylist.h
@@ -46,6 +46,17 @@ public:
 		while( ++n<total ) values[n-1]=values[n];
 		remove();
 	}
+	void remove_block(int i, int n) {
+		if( i >= total ) return;
+		for( n+=i; n<total; ) values[i++] = values[n++];
+		total = i;
+	}
+	void remove_object_block(int i, int n) {
+		if( i >= total ) return;
+		for( n+=i; n<total; ) { del_value(i); values[i++] = values[n++]; }
+		for( n=i; n<total; ++n ) del_value(n);
+		total = i;
+	}
 	void remove(TYPE value) {
 		int out = 0;
 		for( int in=0; in<total; ++in )
diff --git a/cinelerra-5.1/guicast/bcmeter.C b/cinelerra-5.1/guicast/bcmeter.C
index e4eb2bd9..673a684a 100644
--- a/cinelerra-5.1/guicast/bcmeter.C
+++ b/cinelerra-5.1/guicast/bcmeter.C
@@ -16,7 +16,6 @@
  * 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 "bcbutton.h"
@@ -24,7 +23,7 @@
 #include "bcpixmap.h"
 #include "bcresources.h"
 #include "bcwindow.h"
-#include "bccolors.h"
+#include "colors.h"
 #include "fonts.h"
 #include "vframe.h"
 #include <string.h>
@@ -44,16 +43,9 @@
 #define METER_RIGHT 3
 
 
-BC_Meter::BC_Meter(int x,
-	int y,
-	int orientation,
-	int pixels,
-	int min,
-	int max,
-	int mode,
-	int use_titles,
-	int span,
-	int downmix)
+BC_Meter::BC_Meter(int x, int y, int orientation, int pixels,
+	int min, int max, int mode, int use_titles,
+	int span, int is_downmix, int is_gain_change)
  : BC_SubWindow(x, y, -1, -1)
 {
 	this->over_delay = 150;
@@ -65,10 +57,10 @@ BC_Meter::BC_Meter(int x,
 	this->orientation = orientation;
 	this->pixels = pixels;
 	this->span = span;
-	this->downmix = downmix;
-
+	this->is_downmix = is_downmix;
+	this->is_gain_change = is_gain_change;
 //printf("BC_Meter::draw_face %d w=%d pixels=%d\n", __LINE__, w, pixels);
-	for(int i = 0; i < TOTAL_METER_IMAGES; i++) images[i] = 0;
+	for( int i = 0; i < TOTAL_METER_IMAGES; i++ ) images[i] = 0;
 	db_titles.set_array_delete();
 }
 
@@ -76,8 +68,9 @@ BC_Meter::~BC_Meter()
 {
 	db_titles.remove_all_objects();
 	title_pixels.remove_all();
+	tick_w.remove_all();
 	tick_pixels.remove_all();
-	for(int i = 0; i < TOTAL_METER_IMAGES; i++) delete images[i];
+	for( int i = 0; i < TOTAL_METER_IMAGES; i++ ) delete images[i];
 }
 
 int BC_Meter::get_title_w()
@@ -104,26 +97,29 @@ int BC_Meter::initialize()
 	level_pixel = peak_pixel = 0;
 	over_timer = 0;
 	over_count = 0;
-	peak = level = -100;
 
-	if(orientation == METER_VERT)
-	{
+	if( is_gain_change ) {
+		peak = level = 0;
+	}
+	else {
+		peak = level = -100;
+	}
+
+	if( orientation == METER_VERT ) {
 		set_images(get_resources()->ymeter_images);
 		h = pixels;
-		if(span < 0)
-		{
+		if( span < 0 ) {
 			w = images[0]->get_w();
-			if(use_titles) w += get_title_w();
+			if( use_titles ) w += get_title_w();
 		}
 		else
 			w = span;
 	}
-	else
-	{
+	else {
 		set_images(get_resources()->xmeter_images);
 		h = images[0]->get_h();
 		w = pixels;
-		if(use_titles) h += get_title_w();
+		if( use_titles ) h += get_title_w();
 	}
 
 // calibrate the db titles
@@ -138,21 +134,19 @@ int BC_Meter::initialize()
 
 void BC_Meter::set_images(VFrame **data)
 {
-	for(int i = 0; i < TOTAL_METER_IMAGES; i++) delete images[i];
-	for(int i = 0; i < TOTAL_METER_IMAGES; i++)
+	for( int i = 0; i < TOTAL_METER_IMAGES; i++ ) delete images[i];
+	for( int i = 0; i < TOTAL_METER_IMAGES; i++ )
 		images[i] = new BC_Pixmap(parent_window, data[i], PIXMAP_ALPHA);
 }
 
 int BC_Meter::reposition_window(int x, int y, int span, int pixels)
 {
-	if(pixels < 0) pixels = this->pixels;
+	if( pixels < 0 ) pixels = this->pixels;
 	this->span = span;
 	this->pixels = pixels;
-	if(orientation == METER_VERT)
-		BC_SubWindow::reposition_window(x,
-			y,
-			this->span < 0 ? w : span,
-			pixels);
+	if( orientation == METER_VERT )
+		BC_SubWindow::reposition_window(x, y,
+			this->span < 0 ? w : span, pixels);
 	else
 		BC_SubWindow::reposition_window(x, y, pixels, get_h());
 
@@ -168,7 +162,7 @@ int BC_Meter::reposition_window(int x, int y, int span, int pixels)
 	return 0;
 }
 
-int BC_Meter::reset(int dmix)
+int BC_Meter::reset(int downmix)
 {
 	level = min;
 	peak = min;
@@ -176,15 +170,15 @@ int BC_Meter::reset(int dmix)
 	peak_timer = 0;
 	over_timer = 0;
 	over_count = 0;
-	if(dmix >= 0) downmix = dmix;
+	if( downmix >= 0 )
+		is_downmix = downmix;
 	draw_face(1);
 	return 0;
 }
 
 int BC_Meter::button_press_event()
 {
-	if(cursor_inside() && is_event_win())
-	{
+	if( cursor_inside() && is_event_win() ) {
 		reset_over();
 		return 1;
 	}
@@ -210,15 +204,11 @@ int BC_Meter::change_format(int mode, int min, int max)
 int BC_Meter::level_to_pixel(float level)
 {
 	int result;
-	if(mode == METER_DB)
-	{
-		result = (int)(pixels *
-			(level - min) /
-			(max - min));
-		if(level <= min) result = 0;
+	if( mode == METER_DB ) {
+		result = (int)(pixels * (level - min) / (max - min));
+		if( level <= min ) result = 0;
 	}
-	else
-	{
+	else {
 // Not implemented anymore
 		result = 0;
 	}
@@ -236,6 +226,7 @@ void BC_Meter::get_divisions()
 	db_titles.remove_all_objects();
 	title_pixels.remove_all();
 	tick_pixels.remove_all();
+	tick_w.remove_all();
 
 	low_division = 0;
 	medium_division = 0;
@@ -243,111 +234,104 @@ void BC_Meter::get_divisions()
 
 	int current_pixel;
 // Create tick marks and titles in one pass
-	for(int current = min; current <= max; current++)
-	{
-		if(orientation == METER_VERT)
-		{
+	for( int current = min; current <= max; current++ ) {
+		if( orientation == METER_VERT ) {
 // Create tick mark
 			current_pixel = (pixels - METER_MARGIN * 2 - 2) *
 				(current - min) / (max - min) + 2;
 			tick_pixels.append(current_pixel);
 
 // Create titles in selected positions
-			if(current == min ||
-				current == max ||
-				current == 0 ||
-				(current - min > 4 && max - current > 4 && !(current % 5)))
-			{
-				int title_pixel = (pixels -
-					METER_MARGIN * 2) * (current - min) / (max - min);
-				sprintf(string, "%ld", labs(current));
+			if( current == min || current == max || current == 0 ||
+				(current - min > 4 && max - current > 4 && !(current % 5)) ) {
+				int title_pixel = (pixels - METER_MARGIN * 2) *
+					(current - min) / (max - min);
+				sprintf(string, "%d", (int)labs(current));
 				new_string = new char[strlen(string) + 1];
 				strcpy(new_string, string);
 				db_titles.append(new_string);
 				title_pixels.append(title_pixel);
+				tick_w.append(TICK_W1);
+			}
+			else {
+				tick_w.append(TICK_W2);
 			}
 		}
-		else
-		{
+		else {
 			current_pixel = (pixels - METER_MARGIN * 2) *
 				(current - min) /
 				(max - min);
 			tick_pixels.append(current_pixel);
+			tick_w.append(TICK_W1);
 // Titles not supported for horizontal
 		}
 
 // Create color divisions
-		if(current == -20)
-		{
+		if( current == -20 ) {
 			low_division = current_pixel;
 		}
 		else
-		if(current == -5)
-		{
+		if( current == -5 ) {
 			medium_division = current_pixel;
 		}
 		else
-		if(current == 0)
-		{
+		if( current == 0 ) {
 			high_division = current_pixel;
 		}
 	}
-// if(orientation == METER_VERT)
+// if( orientation == METER_VERT )
 // printf("BC_Meter::get_divisions %d %d %d %d\n",
 // low_division, medium_division, high_division, pixels);
 }
 
 void BC_Meter::draw_titles(int flush)
 {
-	if(!use_titles) return;
+	if( !use_titles ) return;
+	int tick_xfudge = xS(1);
 
 	set_font(get_resources()->meter_font);
 
-	if(orientation == METER_HORIZ)
-	{
+	if( orientation == METER_HORIZ ) {
 		draw_top_background(parent_window, 0, 0, get_w(), get_title_w());
 
-		for(int i = 0; i < db_titles.total; i++)
-		{
+		for( int i = 0; i < db_titles.total; i++ ) {
 			draw_text(0, title_pixels.values[i], db_titles.values[i]);
 		}
 
 		flash(0, 0, get_w(), get_title_w(), flush);
 	}
 	else
-	if(orientation == METER_VERT)
-	{
+	if( orientation == METER_VERT ) {
 		draw_top_background(parent_window, 0, 0, get_title_w(), get_h());
 
 // Titles
-		for(int i = 0; i < db_titles.total; i++)
-		{
-			int title_y = pixels -
-				title_pixels.values[i];
-			if(i == 0)
-				title_y -= get_text_descent(SMALLFONT_3D);
+		for( int i = 0; i < db_titles.total; i++ ) {
+			int title_y = pixels - title_pixels.values[i];
+			if( i == 0 )
+				title_y -= get_text_descent(get_resources()->meter_font);
 			else
-			if(i == db_titles.total - 1)
-				title_y += get_text_ascent(SMALLFONT_3D);
+			if( i == db_titles.total - 1 )
+				title_y += get_text_ascent(get_resources()->meter_font);
 			else
-				title_y += get_text_ascent(SMALLFONT_3D) / 2;
+				title_y += get_text_ascent(get_resources()->meter_font) / 2;
+			int title_x = get_title_w() - TICK_W1 - tick_xfudge -
+				get_text_width(get_resources()->meter_font, db_titles.values[i]);
 
 			set_color(get_resources()->meter_font_color);
-			draw_text(0,
-				title_y,
-				db_titles.values[i]);
+			draw_text(title_x, title_y, db_titles.values[i]);
 		}
 
-		for(int i = 0; i < tick_pixels.total; i++)
-		{
+		for( int i = 0; i < tick_pixels.total; i++ ) {
 // Tick marks
 			int tick_y = pixels - tick_pixels.values[i] - METER_MARGIN;
 			set_color(get_resources()->meter_font_color);
-			draw_line(get_title_w() - xS(10) - 1, tick_y, get_title_w() - 1, tick_y);
-			if(get_resources()->meter_3d)
-			{
+			draw_line(get_title_w() - tick_w.get(i) - tick_xfudge,
+				tick_y, get_title_w() - tick_xfudge, tick_y);
+
+			if( get_resources()->meter_3d ) {
 				set_color(BLACK);
-				draw_line(get_title_w() - xS(10), tick_y + 1, get_title_w(), tick_y + 1);
+				draw_line(get_title_w() - tick_w.get(i),
+					tick_y + 1, get_title_w(), tick_y + 1);
 			}
 		}
 
@@ -358,9 +342,9 @@ void BC_Meter::draw_titles(int flush)
 int BC_Meter::region_pixel(int region)
 {
 	VFrame **reference_images = get_resources()->xmeter_images;
-	int result;
+	int result = 0;
 
-	if(region == METER_RIGHT)
+	if( region == METER_RIGHT )
 		result = region * reference_images[0]->get_w() / 4;
 	else
 		result = region * reference_images[0]->get_w() / 4;
@@ -377,7 +361,7 @@ int BC_Meter::region_pixels(int region)
 
 	x1 = region * reference_images[0]->get_w() / 4;
 	x2 = (region + 1) * reference_images[0]->get_w() / 4;
-	if(region == METER_MID)
+	if( region == METER_MID )
 		result = (x2 - x1) * 2;
 	else
 		result = x2 - x1;
@@ -400,197 +384,258 @@ void BC_Meter::draw_face(int flush)
 	draw_top_background(parent_window, x, 0, w, h);
 
 // printf("BC_Meter::draw_face %d span=%d this->w=%d get_title_w()=%d %d %d\n",
-// __LINE__,
-// span,
-// this->w,
-// get_title_w(),
-// w,
-// h);
-
-	while(pixel < pixels)
-	{
-// Select image to draw
-		if(pixel < level_pixel ||
-			(pixel >= peak_pixel1 && pixel < peak_pixel2))
-		{
-			if(pixel < low_division)
-				image_number = METER_GREEN;
-			else
-			if(pixel < medium_division)
-				image_number = METER_YELLOW;
-			else
-			if(pixel < high_division)
-				image_number = METER_RED;
-			else
-				image_number = METER_WHITE;
-		}
-		else
-		{
-			image_number = METER_NORMAL;
-		}
+// __LINE__, span, this->w, get_title_w(), w, h);
+	if( is_gain_change ) {
+		int in_h = images[0]->get_h();
+		int in_third = in_h / 3;
+		int in_third3 = in_h - in_third * 2;
 
-// Select region of image to duplicate
-		if(pixel < left_pixel)
-		{
-			region = METER_LEFT;
-			in_start = pixel + region_pixel(region);
-			in_span = region_pixels(region) - (in_start - region_pixel(region));
-		}
-		else
-		if(pixel < right_pixel)
-		{
-			region = METER_MID;
-			in_start = region_pixel(region);
-			in_span = region_pixels(region);
-		}
-		else
-		{
-			region = METER_RIGHT;
-			in_start = (pixel - right_pixel) + region_pixel(region);
-			in_span = region_pixels(region) - (in_start - region_pixel(region));;
+// printf("BC_Meter::draw_face %d level=%f level_pixel=%d high_division=%d\n",
+// __LINE__, level, level_pixel, high_division);
+
+
+// fudge a line when no gain change
+		if( level_pixel == high_division ) {
+			level_pixel += 1;
 		}
 
-//printf("BC_Meter::draw_face region %d pixel %d pixels %d in_start %d in_span %d\n", region, pixel, pixels, in_start, in_span);
-		if(in_span > 0)
-		{
-// Clip length to peaks
-			if(pixel < level_pixel && pixel + in_span > level_pixel)
-				in_span = level_pixel - pixel;
-			else
-			if(pixel < peak_pixel1 && pixel + in_span > peak_pixel1)
-				in_span = peak_pixel1 - pixel;
-			else
-			if(pixel < peak_pixel2 && pixel + in_span > peak_pixel2)
-				in_span = peak_pixel2 - pixel;
+		while( pixel < pixels ) {
+// Select image to draw & extents
+			if( level_pixel < high_division ) {
+// always vertical
+				if( pixel < level_pixel ) {
+					image_number = METER_NORMAL;
+					in_span = level_pixel - pixel;
+				}
+				else
+				if( pixel < high_division ) {
+					image_number = METER_RED;
+					in_span = high_division - pixel;
+				}
+				else {
+					image_number = METER_NORMAL;
+					in_span = pixels - pixel;
+				}
+			}
+			else {
+// determine pixel range & image to draw
+				if( pixel < high_division ) {
+					image_number = METER_NORMAL;
+					in_span = high_division - pixel;
+				}
+				else
+				if( pixel < level_pixel ) {
+					image_number = METER_GREEN;
+					in_span = level_pixel - pixel;
+				}
+				else {
+					image_number = METER_NORMAL;
+					in_span = pixels - pixel;
+				}
+			}
 
-// Clip length to color changes
-			if(image_number == METER_GREEN && pixel + in_span > low_division)
-				in_span = low_division - pixel;
+// determine starting point in source to draw
+// draw starting section
+			if( pixel == 0 ) {
+				in_start = 0;
+			}
 			else
-			if(image_number == METER_YELLOW && pixel + in_span > medium_division)
-				in_span = medium_division - pixel;
+// draw middle section
+			if( pixels - pixel > in_third3 ) {
+				in_start = in_third;
+			}
 			else
-			if(image_number == METER_RED && pixel + in_span > high_division)
-				in_span = high_division - pixel;
+// draw last section
+			{
+				in_start = in_third * 2;
+			}
 
-// Clip length to regions
-			if(pixel < left_pixel && pixel + in_span > left_pixel)
-				in_span = left_pixel - pixel;
+// clamp the region to the source dimensions
+			if( in_start < in_third * 2 ) {
+				if( in_span > in_third ) {
+					in_span = in_third;
+				}
+			}
 			else
-			if(pixel < right_pixel && pixel + in_span > right_pixel)
-				in_span = right_pixel - pixel;
+// last segment
+			if( pixels - pixel < in_third3 ) {
+				in_span = pixels - pixel;
+				in_start = in_h - in_span;
+			}
 
-//printf("BC_Meter::draw_face image_number %d pixel %d pixels %d in_start %d in_span %d\n", image_number, pixel, pixels, in_start, in_span);
-//printf("BC_Meter::draw_face %d %d %d %d\n", orientation, region, images[image_number]->get_h() - in_start - in_span);
-			if(orientation == METER_HORIZ)
-			{
-				draw_pixmap(images[image_number],
-					pixel,
-					x,
-					in_span + 1,
-					get_h(),
-					in_start,
-					0);
+// printf("BC_Meter::draw_face %d dst_y=%d src_y=%d"
+// " pixels=%d pixel=%d in_h=%d in_start=%d in_span=%d in_third=%d in_third3=%d\n",
+// __LINE__, get_h() - pixel - in_span, in_h - in_start - in_span,
+// pixels, pixel, in_h, in_start, in_span, in_third, in_third3);
+			draw_pixmap(images[image_number], x, get_h() - pixel - in_span,
+				get_w(), in_span + 1, 0, in_h - in_start - in_span);
+			pixel += in_span;
+		}
+	}
+	else {
+		while( pixel < pixels ) {
+// Select image to draw
+			if( pixel < level_pixel ||
+				(pixel >= peak_pixel1 && pixel < peak_pixel2) ) {
+				if( pixel < low_division )
+					image_number = METER_GREEN;
+				else
+				if( pixel < medium_division )
+					image_number = METER_YELLOW;
+				else
+				if( pixel < high_division )
+					image_number = METER_RED;
+				else
+					image_number = METER_WHITE;
+			}
+			else {
+				image_number = METER_NORMAL;
+			}
+
+// Select region of image to duplicate
+			if( pixel < left_pixel ) {
+				region = METER_LEFT;
+				in_start = pixel + region_pixel(region);
+				in_span = region_pixels(region) - (in_start - region_pixel(region));
 			}
 			else
-			{
-//printf("BC_Meter::draw_face %d %d\n", __LINE__, span);
-				if(span < 0)
-				{
-					draw_pixmap(images[image_number],
-						x,
-						get_h() - pixel - in_span,
-						get_w(),
-						in_span + 1,
-						0,
-						images[image_number]->get_h() - in_start - in_span);
-				}
-				else
-				{
-					int total_w = get_w() - x;
-					int third = images[image_number]->get_w() / 3 + 1;
-
-
-					for(int x1 = 0; x1 < total_w; x1 += third)
-					{
-						int in_x = 0;
-						int in_w = third;
-						if(x1 >= third) in_x = third;
-						if(x1 >= total_w - third)
-						{
-							in_x = images[image_number]->get_w() -
-								(total_w - x1);
-							in_w = total_w - x1;
-						}
+			if( pixel < right_pixel ) {
+				region = METER_MID;
+				in_start = region_pixel(region);
+				in_span = region_pixels(region);
+			}
+			else {
+				region = METER_RIGHT;
+				in_start = (pixel - right_pixel) + region_pixel(region);
+				in_span = region_pixels(region) - (in_start - region_pixel(region));;
+			}
 
-						int in_y = images[image_number]->get_h() - in_start - in_span;
-//printf("BC_Meter::draw_face %d %d %d\n", __LINE__, get_w(), x + x1 + in_w, in_x, in_y, in_w, span);
+	//printf("BC_Meter::draw_face region %d pixel %d pixels %d in_start %d in_span %d\n", region, pixel, pixels, in_start, in_span);
+			if( in_span > 0 ) {
+	// Clip length to peaks
+				if( pixel < level_pixel && pixel + in_span > level_pixel )
+					in_span = level_pixel - pixel;
+				else
+				if( pixel < peak_pixel1 && pixel + in_span > peak_pixel1 )
+					in_span = peak_pixel1 - pixel;
+				else
+				if( pixel < peak_pixel2 && pixel + in_span > peak_pixel2 )
+					in_span = peak_pixel2 - pixel;
 
+	// Clip length to color changes
+				if( image_number == METER_GREEN && pixel + in_span > low_division )
+					in_span = low_division - pixel;
+				else
+				if( image_number == METER_YELLOW && pixel + in_span > medium_division )
+					in_span = medium_division - pixel;
+				else
+				if( image_number == METER_RED && pixel + in_span > high_division )
+					in_span = high_division - pixel;
 
+	// Clip length to regions
+				if( pixel < left_pixel && pixel + in_span > left_pixel )
+					in_span = left_pixel - pixel;
+				else
+				if( pixel < right_pixel && pixel + in_span > right_pixel )
+					in_span = right_pixel - pixel;
+
+	//printf("BC_Meter::draw_face image_number %d pixel %d pixels %d in_start %d in_span %d\n", image_number, pixel, pixels, in_start, in_span);
+	//printf("BC_Meter::draw_face %d %d %d %d\n", orientation, region, images[image_number]->get_h() - in_start - in_span);
+				if( orientation == METER_HORIZ ) {
+					draw_pixmap(images[image_number], pixel,
+						x, in_span + 1, get_h(), in_start, 0);
+				}
+				else {
+	//printf("BC_Meter::draw_face %d %d\n", __LINE__, span);
+					if( span < 0 ) {
 						draw_pixmap(images[image_number],
-							x + x1, get_h() - pixel - in_span,
-							in_w, in_span + 1, in_x, in_y);
+							x, get_h() - pixel - in_span,
+							get_w(), in_span + 1, 0,
+							images[image_number]->get_h() - in_start - in_span);
+					}
+					else {
+						int total_w = get_w() - x;
+						int third = images[image_number]->get_w() / 3 + 1;
+
+
+						for( int x1 = 0; x1 < total_w; x1 += third ) {
+							int in_x = 0;
+							int in_w = third;
+							if( x1 >= third ) in_x = third;
+							if( x1 >= total_w - third ) {
+								in_x = images[image_number]->get_w() -
+									(total_w - x1);
+								in_w = total_w - x1;
+							}
+
+							int in_y = images[image_number]->get_h() - in_start - in_span;
+	//printf("BC_Meter::draw_face %d %d %d\n", __LINE__, get_w(), x + x1 + in_w, in_x, in_y, in_w, span);
+
+
+							draw_pixmap(images[image_number],
+								x + x1, get_h() - pixel - in_span,
+								in_w, in_span + 1, in_x, in_y);
+						}
 					}
 				}
-			}
 
-			pixel += in_span;
-		}
-		else
-		{
-// Sanity check
-			break;
+				pixel += in_span;
+			}
+			else {
+	// Sanity check
+				break;
+			}
 		}
 	}
 
-	if(downmix) {
-		if(orientation == METER_HORIZ)
+	if( is_downmix ) {
+		if( orientation == METER_HORIZ )
 			draw_pixmap(images[METER_DOWNMIX], 0, 0);
 		else
-			draw_pixmap(images[METER_DOWNMIX], x,
-				get_h() - images[METER_DOWNMIX]->get_h()-1);
+			draw_pixmap(images[METER_DOWNMIX],
+				x, get_h() - images[METER_DOWNMIX]->get_h() - 1);
 	}
-
-	if(over_timer)
-	{
-		if(orientation == METER_HORIZ)
+	if( over_timer ) {
+		if( orientation == METER_HORIZ )
 			draw_pixmap(images[METER_OVER], xS(20), yS(2));
 		else
-			draw_pixmap(images[METER_OVER], x, get_h() - yS(100));
+			draw_pixmap(images[METER_OVER],
+				x + xS(2), get_h() - yS(100));
 
 		over_timer--;
 	}
 
-   	if(orientation == METER_HORIZ)
+	if( orientation == METER_HORIZ )
 		flash(0, 0, pixels, get_h(), flush);
 	else
 		flash(x, 0, w, pixels, flush);
 }
 
-int BC_Meter::update(float new_value, int over, int dmix)
+int BC_Meter::update(float new_value, int over, int downmix)
 {
 	peak_timer++;
 
-	if(mode == METER_DB)
-	{
-		if(new_value == 0)
+	if( mode == METER_DB ) {
+		if( new_value == 0 )
 			level = min;
 		else
-			level = db.todb(new_value);        // db value
+			level = DB::todb(new_value);		// db value
 	}
 
-	if(level > peak || peak_timer > peak_delay)
-	{
+
+	if( is_gain_change && fabs(level) > fabs(peak) ||
+		!is_gain_change && level > peak ||
+		peak_timer > peak_delay ) {
 		peak = level;
 		peak_timer = 0;
 	}
 
-// if(orientation == METER_HORIZ)
+// if( orientation == METER_HORIZ )
 // printf("BC_Meter::update %f\n", level);
-	if(over) over_timer = over_delay;
+	if( over ) over_timer = over_delay;
 // only draw if window is visible
-	if(dmix >= 0) downmix = dmix;
-
+	if( downmix >= 0 )
+		is_downmix = downmix;
 	draw_face(1);
 	return 0;
 }
diff --git a/cinelerra-5.1/guicast/bcmeter.h b/cinelerra-5.1/guicast/bcmeter.h
index 9a5cf807..e43c52b7 100644
--- a/cinelerra-5.1/guicast/bcmeter.h
+++ b/cinelerra-5.1/guicast/bcmeter.h
@@ -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
@@ -16,7 +15,6 @@
  * 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
- *
  */
 
 #ifndef BCMETER_H
@@ -35,20 +33,20 @@
 
 // Distance from subwindow border to top and bottom tick mark
 #define METER_MARGIN 0
+// tick width
+#define TICK_W1 xS(10)
+#define TICK_W2 xS(5)
 
 class BC_Meter : public BC_SubWindow
 {
 public:
-	BC_Meter(int x,
-		int y,
-		int orientation,
-		int pixels,
-		int min, /* = -40, */
-		int max,
+	BC_Meter(int x, int y, int orientation, int pixels,
+		int min, /* = -40, */ int max,
 		int mode, /* = METER_DB, */
 		int use_titles, /* = 0, */
-		int span, /* = -1 width for vertical mode only */
-		int downmix = 0);
+		int span /* = -1 width for vertical mode only */,
+		int is_downmix = 0,
+		int is_gain_change = 0);
 	virtual ~BC_Meter();
 
 	int initialize();
@@ -64,12 +62,11 @@ public:
 
 	static int get_title_w();
 	static int get_meter_w();
-	int update(float new_value, int over, int dmix=-1);
-	int reposition_window(int x,
-		int y,
+	int update(float new_value, int over, int downmix=-1);
+	int reposition_window(int x, int y,
 		int span /* = -1 for vertical mode only */,
 		int pixels);
-	int reset(int dmix=-1);
+	int reset(int downmix=-1);
 	int reset_over();
 	int change_format(int mode, int min, int max);
 
@@ -91,25 +88,22 @@ private:
 	int use_titles;
 // Tick mark positions
 	ArrayList<int> tick_pixels;
+// Tick widths
+	ArrayList<int> tick_w;
 // Title positions
 	ArrayList<int> title_pixels;
 	ArrayList<char*> db_titles;
 	float level, peak;
 	int mode;
-	DB db;
 	int peak_timer;
-
-
-
-
-
+	int is_gain_change;
 
 	int peak_pixel, level_pixel, peak_pixel1, peak_pixel2;
 	int over_count, over_timer;
 	int min, max;
-	int downmix;
-	int over_delay;       // Number of updates the over warning lasts.
-	int peak_delay;       // Number of updates the peak lasts.
+	int is_downmix;
+	int over_delay;	   // Number of updates the over warning lasts.
+	int peak_delay;	   // Number of updates the peak lasts.
 };
 
 #endif
diff --git a/cinelerra-5.1/guicast/bcpot.C b/cinelerra-5.1/guicast/bcpot.C
index b979f915..6b91b33c 100644
--- a/cinelerra-5.1/guicast/bcpot.C
+++ b/cinelerra-5.1/guicast/bcpot.C
@@ -44,6 +44,8 @@ BC_Pot::BC_Pot(int x, int y, VFrame **data)
 
 BC_Pot::~BC_Pot()
 {
+	for(int i = 0; i < POT_STATES; i++)
+		if(images[i]) delete images[i];
 }
 
 int BC_Pot::calculate_h()
diff --git a/cinelerra-5.1/guicast/bcwindowbase.C b/cinelerra-5.1/guicast/bcwindowbase.C
index 0d9f95d4..e979d5ca 100644
--- a/cinelerra-5.1/guicast/bcwindowbase.C
+++ b/cinelerra-5.1/guicast/bcwindowbase.C
@@ -624,7 +624,7 @@ int BC_WindowBase::create_window(BC_WindowBase *parent_window, const char *title
 		}
 
 		if(!hidden) show_window();
-
+		init_glyphs();
 	}
 
 	draw_background(0, 0, this->w, this->h);
@@ -2427,6 +2427,30 @@ xft_init_lock.unlock();
 #endif // HAVE_XFT
 }
 
+void BC_WindowBase::init_glyphs()
+{
+// draw all ascii char glyphs
+//  There are problems with some/my graphics boards/drivers
+//  which cause some glyphs to be munged if draws occur while
+//  the font is being loaded.  This code fills the font caches
+//  by drawing all the ascii glyphs before the system starts.
+//  Not a fix, but much better than nothing.
+	static int inited = 0;
+	if( inited ) return;
+	inited = 1;
+	int cur_font = current_font;
+// locale encodings, needed glyphs to be preloaded
+	const char *text = _( // ascii 0x20...0x7e
+		" !\"#$%&'()*+,-./0123456789:;<=>?"
+		"@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_"
+		"`abcdefghijklmnopqrstuvwxyz{|}~");
+	for( int font=SMALLFONT; font<=LARGEFONT; ++font ) {
+		set_font(font);
+		draw_text(5,5, text, 0);
+	}
+	set_font(cur_font);
+}
+
 void BC_WindowBase::init_im()
 {
 	XIMStyles *xim_styles;
diff --git a/cinelerra-5.1/guicast/bcwindowbase.h b/cinelerra-5.1/guicast/bcwindowbase.h
index 36add9c7..153aab37 100644
--- a/cinelerra-5.1/guicast/bcwindowbase.h
+++ b/cinelerra-5.1/guicast/bcwindowbase.h
@@ -576,6 +576,7 @@ private:
 	int allocate_color_table();
 	int init_gc();
 	int init_fonts();
+	void init_glyphs();
 	void init_xft();
 	void init_im();
 	void finit_im();
diff --git a/cinelerra-5.1/guicast/linklist.h b/cinelerra-5.1/guicast/linklist.h
index 0c580def..3accf281 100644
--- a/cinelerra-5.1/guicast/linklist.h
+++ b/cinelerra-5.1/guicast/linklist.h
@@ -28,6 +28,7 @@ public:
 	void remove_pointer(ListItem<TYPE> *item);
 	TYPE *append(TYPE *new_item);
 	TYPE *append() { return append(new TYPE()); }
+	void destroy() { while(last) delete last; }
 	TYPE *insert_before(TYPE *here, TYPE *item);
 	TYPE *insert_before(TYPE *here) { return insert_before(here, new TYPE()); }
 	TYPE *insert_after(TYPE *here, TYPE *item);
@@ -45,8 +46,9 @@ public:
 	void swap(TYPE *item1, TYPE *item2);
 	void sort(TYPE *ap=0, TYPE *bp=0) { return sort(cmpr,ap,bp); }
 	void sort(int (*cmp)(TYPE *a, TYPE *b), TYPE *ap=0, TYPE *bp=0);
+	void concat(List<TYPE> &b);
 	List() { first = last = 0; }
-	virtual ~List() { while(last) delete last; }
+	virtual ~List() { destroy(); }
 };
 
 // convenience macros
@@ -132,4 +134,14 @@ void List<TYPE>::sort(int (*cmpr)(TYPE *a, TYPE *b), TYPE *ll, TYPE *rr)
 	}
 }
 
+template<class TYPE>
+void List<TYPE>::concat(List<TYPE> &b)
+{
+	if( !b.first ) return;
+	*(last ? &last->next : &first) = b.first;
+	b.first->previous = last;  last = b.last;
+	TYPE *bp = b.first;  b.first = b.last = 0;
+	while( bp ) { bp->list = this;  bp = bp->next; }
+}
+
 #endif
diff --git a/cinelerra-5.1/guicast/units.C b/cinelerra-5.1/guicast/units.C
index 9c2f1d27..e0a170dc 100644
--- a/cinelerra-5.1/guicast/units.C
+++ b/cinelerra-5.1/guicast/units.C
@@ -90,14 +90,14 @@ int Freq::fromfreq()
 	int i = 0;
   	while( i<TOTALFREQS && freqtable[i]<freq ) ++i;
   	return i;
-};
+}
 
 int Freq::fromfreq(int index)
 {
 	int i = 0;
   	while( i<TOTALFREQS && freqtable[i]<index ) ++i;
   	return i;
-};
+}
 
 int Freq::tofreq(int index)
 {
@@ -105,6 +105,25 @@ int Freq::tofreq(int index)
 	return freqtable[index];
 }
 
+// frequency doubles for every OCTAVE slots.  OCTAVE must be divisible by 3
+// 27.5 is at i=1
+// 55 is at i=106
+// 110 is at i=211
+// 220 is at i=316
+// 440 is at i=421
+// 880 is at i=526
+double Freq::tofreq_f(double index)
+{
+	if( index < 0.5 ) return 0;
+	return 440.0 * pow(2, (double)(index - 421) / OCTAVE);
+}
+double Freq::fromfreq_f(double f)
+{
+	if( f < 0.5 ) return 0;
+	double result = log(f / 440) / log(2.0) * OCTAVE + 421;
+	return result < 0 ? 0 : result;
+}
+
 Freq& Freq::operator++()
 {
 	if(freq < TOTALFREQS) ++freq;
@@ -136,17 +155,8 @@ void Units::init()
 	topower[INFINITYGAIN * 10] = 0;   // infinity gain
 
 	Freq::freqtable = new int[TOTALFREQS + 1];
-// starting frequency
-	double freq1 = 27.5, freq2 = 55;
-// Some number divisable by three.  This depends on the value of TOTALFREQS
-	int scale = 105;
-
-	Freq::freqtable[0] = 0;
-	for(int i = 1, j = 0; i <= TOTALFREQS; i++, j++) {
-		Freq::freqtable[i] = (int)(freq1 + (freq2 - freq1) / scale * j + 0.5);
-		if(j < scale) continue;
-		freq1 = freq2;  freq2 *= 2;  j = 0;
-	}
+	for( int i=0; i<=TOTALFREQS; ++i )
+		Freq::freqtable[i] = Freq::tofreq_f(i);
 }
 void Units::finit()
 {
diff --git a/cinelerra-5.1/guicast/units.h b/cinelerra-5.1/guicast/units.h
index 7456b662..7e2243ef 100644
--- a/cinelerra-5.1/guicast/units.h
+++ b/cinelerra-5.1/guicast/units.h
@@ -32,6 +32,8 @@
 #define INFINITYGAIN -96
 #define MAXGAIN 50
 #define TOTALFREQS 1024
+// slots per octave
+#define OCTAVE 105
 #define TOTAL_TIMEFORMATS 7
 
 // h:mm:ss.sss
@@ -125,7 +127,8 @@ public:
 // return index of frequency
 	int fromfreq();
 	static int fromfreq(int index);
-
+	static double tofreq_f(double index);
+	static double fromfreq_f(double freq);
 // increment frequency by one
 	Freq& operator++();
 	Freq& operator--();
diff --git a/cinelerra-5.1/plugins/Makefile b/cinelerra-5.1/plugins/Makefile
index 2620b4d0..1f464d23 100644
--- a/cinelerra-5.1/plugins/Makefile
+++ b/cinelerra-5.1/plugins/Makefile
@@ -42,6 +42,7 @@ DIRS = $(OPENCV_OBJS) \
 	color3way \
 	colorbalance \
 	compressor \
+	compressormulti \
 	crikey \
 	crop \
 	crossfade \
diff --git a/cinelerra-5.1/plugins/compressor/Makefile b/cinelerra-5.1/plugins/compressor/Makefile
index 893d9917..b7294bdd 100644
--- a/cinelerra-5.1/plugins/compressor/Makefile
+++ b/cinelerra-5.1/plugins/compressor/Makefile
@@ -6,5 +6,4 @@ PLUGIN = compressor
 
 include ../../plugin_config
 
-
 $(OBJDIR)/compressor.o: compressor.C
diff --git a/cinelerra-5.1/plugins/compressor/compressor.C b/cinelerra-5.1/plugins/compressor/compressor.C
index b167c6ee..956d2ae7 100644
--- a/cinelerra-5.1/plugins/compressor/compressor.C
+++ b/cinelerra-5.1/plugins/compressor/compressor.C
@@ -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
@@ -20,35 +19,31 @@
  */
 
 #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;
 }
 
-
-
-
diff --git a/cinelerra-5.1/plugins/compressor/compressor.h b/cinelerra-5.1/plugins/compressor/compressor.h
index 412b8db9..dc9c54ad 100644
--- a/cinelerra-5.1/plugins/compressor/compressor.h
+++ b/cinelerra-5.1/plugins/compressor/compressor.h
@@ -1,7 +1,7 @@
 
 /*
  * 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
@@ -25,6 +25,7 @@
 
 
 #include "bchash.inc"
+#include "compressortools.h"
 #include "guicast.h"
 #include "mutex.h"
 #include "pluginaclient.h"
@@ -32,80 +33,78 @@
 #include "vframe.inc"
 
 class CompressorEffect;
+class CompressorWindow;
 
 
-
-
-
-class CompressorCanvas : public BC_SubWindow
+class CompressorCanvas : public CompressorCanvasBase
 {
 public:
-	CompressorCanvas(CompressorEffect *plugin, int x, int y, int w, int h);
-	int button_press_event();
-	int button_release_event();
-	int cursor_motion_event();
-
+	CompressorCanvas(CompressorEffect *plugin, CompressorWindow *window,
+			int x, int y, int w, int h);
+	void update_window();
+};
 
-	enum
-	{
-		NONE,
-		DRAG
-	};
 
-	int current_point;
-	int current_operation;
+class CompressorLookAhead : public BC_TumbleTextBox
+{
+public:
+	CompressorLookAhead(CompressorEffect *plugin, CompressorWindow *window,
+			int x, int y);
+	int handle_event();
 	CompressorEffect *plugin;
 };
 
-
-class CompressorReaction : public BC_TextBox
+class CompressorAttack : public BC_TumbleTextBox
 {
 public:
-	CompressorReaction(CompressorEffect *plugin, int x, int y);
+	CompressorAttack(CompressorEffect *plugin, CompressorWindow *window,
+			int x, int y);
 	int handle_event();
-	int button_press_event();
 	CompressorEffect *plugin;
 };
 
-class CompressorClear : public BC_GenericButton
+class CompressorRelease : public BC_TumbleTextBox
 {
 public:
-	CompressorClear(CompressorEffect *plugin, int x, int y);
+	CompressorRelease(CompressorEffect *plugin, CompressorWindow *window,
+			int x, int y);
 	int handle_event();
 	CompressorEffect *plugin;
 };
 
-class CompressorX : public BC_TextBox
+
+class CompressorClear : public BC_GenericButton
 {
 public:
-	CompressorX(CompressorEffect *plugin, int x, int y);
+	CompressorClear(CompressorEffect *plugin, int x, int y);
 	int handle_event();
 	CompressorEffect *plugin;
 };
 
-class CompressorY : public BC_TextBox
+class CompressorX : public BC_TumbleTextBox
 {
 public:
-	CompressorY(CompressorEffect *plugin, int x, int y);
+	CompressorX(CompressorEffect *plugin, CompressorWindow *window,
+			int x, int y);
 	int handle_event();
 	CompressorEffect *plugin;
 };
 
-class CompressorTrigger : public BC_TextBox
+class CompressorY : public BC_TumbleTextBox
 {
 public:
-	CompressorTrigger(CompressorEffect *plugin, int x, int y);
+	CompressorY(CompressorEffect *plugin, CompressorWindow *window,
+			int x, int y);
 	int handle_event();
-	int button_press_event();
 	CompressorEffect *plugin;
 };
 
-class CompressorDecay : public BC_TextBox
+class CompressorTrigger : public BC_TumbleTextBox
 {
 public:
-	CompressorDecay(CompressorEffect *plugin, int x, int y);
+	CompressorTrigger(CompressorEffect *plugin, CompressorWindow *window,
+			int x, int y);
 	int handle_event();
-	int button_press_event();
 	CompressorEffect *plugin;
 };
 
@@ -134,74 +133,44 @@ class CompressorWindow : public PluginClientWindow
 {
 public:
 	CompressorWindow(CompressorEffect *plugin);
+	~CompressorWindow();
+
 	void create_objects();
 	void update();
 	void update_textboxes();
-	void update_canvas();
 	void draw_scales();
+	void update_meter(CompressorGainFrame *gain_frame);
 	int resize_event(int w, int h);
 
 	CompressorCanvas *canvas;
-	CompressorReaction *reaction;
+	CompressorLookAhead *readahead;
+	CompressorAttack *attack;
 	CompressorClear *clear;
 	CompressorX *x_text;
 	CompressorY *y_text;
 	CompressorTrigger *trigger;
-	CompressorDecay *decay;
+	CompressorRelease *release;
 	CompressorSmooth *smooth;
 	CompressorInput *input;
+	BC_Meter *in;
+	BC_Meter *gain_change;
 	CompressorEffect *plugin;
 };
 
 
 
-typedef struct
-{
-// DB from min_db - 0
-	double x, y;
-} compressor_point_t;
-
-class CompressorConfig
+class CompressorConfig : public CompressorConfigBase
 {
 public:
 	CompressorConfig();
 
 	void copy_from(CompressorConfig &that);
 	int equivalent(CompressorConfig &that);
-	void interpolate(CompressorConfig &prev,
-		CompressorConfig &next,
-		int64_t prev_frame,
-		int64_t next_frame,
-		int64_t current_frame);
-
-	int total_points();
-	void remove_point(int number);
-	void optimize();
-// Return values of a specific point
-	double get_y(int number);
-	double get_x(int number);
-// Returns db output from db input
-	double calculate_db(double x);
-	int set_point(double x, double y);
-	void dump();
-
-	int trigger;
-	int input;
-	enum
-	{
-		TRIGGER,
-		MAX,
-		SUM
-	};
-	double min_db;
-	double reaction_len;
-	double decay_len;
-	double min_x, min_y;
-	double max_x, max_y;
-	int smoothing_only;
-	ArrayList<compressor_point_t> levels;
+	void interpolate(CompressorConfig &prev, CompressorConfig &next,
+		int64_t prev_frame, int64_t next_frame, int64_t current_frame);
 };
 
+
 class CompressorEffect : public PluginAClient
 {
 public:
@@ -212,45 +181,35 @@ public:
 	int is_realtime();
 	void read_data(KeyFrame *keyframe);
 	void save_data(KeyFrame *keyframe);
-	int process_buffer(int64_t size,
-		Samples **buffer,
-		int64_t start_position,
-		int sample_rate);
-	double calculate_gain(double input);
-
-// Calculate linear output from linear input
-	double calculate_output(double x);
+	int process_buffer(int64_t size, Samples **buffer,
+			int64_t start_position, int sample_rate);
+	void allocate_input(int size);
 
 
-	void reset();
 	void update_gui();
-	void delete_dsp();
+	void render_stop();
 
 	PLUGIN_CLASS_MEMBERS(CompressorConfig)
 
-// The raw input data for each channel with readahead
+// Input data + read ahead for each channel
 	Samples **input_buffer;
+
 // Number of samples in the input buffer
 	int64_t input_size;
 // Number of samples allocated in the input buffer
 	int64_t input_allocated;
 // Starting sample of input buffer relative to project in requested rate.
 	int64_t input_start;
+	int64_t last_position;
+	int need_reconfigure;
+
+
+	CompressorEngine *engine;
 
-// ending input value of smoothed input
-	double next_target;
-// starting input value of smoothed input
-	double previous_target;
-// samples between previous and next target value for readahead
-	int target_samples;
-// current sample from 0 to target_samples
-	int target_current_sample;
-// current smoothed input value
-	double current_value;
 // Temporaries for linear transfer
 	ArrayList<compressor_point_t> levels;
-	double min_x, min_y;
-	double max_x, max_y;
+//	double min_x, min_y;
+//	double max_x, max_y;
 };
 
 
diff --git a/cinelerra-5.1/plugins/compressormulti/Makefile b/cinelerra-5.1/plugins/compressormulti/Makefile
new file mode 100644
index 00000000..0a6cae53
--- /dev/null
+++ b/cinelerra-5.1/plugins/compressormulti/Makefile
@@ -0,0 +1,11 @@
+include ../../plugin_defs
+
+OBJS = $(OBJDIR)/comprmulti.o \
+	$(OBJDIR)/comprmultigui.o
+
+PLUGIN = compressormulti
+
+include ../../plugin_config
+
+$(OBJDIR)/compressor.o: comprmulti.C
+$(OBJDIR)/compressorgui.o: comprmultigui.C
diff --git a/cinelerra-5.1/plugins/compressormulti/comprmulti.C b/cinelerra-5.1/plugins/compressormulti/comprmulti.C
new file mode 100644
index 00000000..2207ba08
--- /dev/null
+++ b/cinelerra-5.1/plugins/compressormulti/comprmulti.C
@@ -0,0 +1,945 @@
+/*
+ * CINELERRA
+ * 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
+ * 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 "comprmulti.h"
+#include "comprmultigui.h"
+#include "bchash.h"
+#include "eqcanvas.h"
+#include "filexml.h"
+#include "language.h"
+#include "samples.h"
+#include "transportque.inc"
+#include "units.h"
+#include "vframe.h"
+
+#include <math.h>
+#include <string.h>
+
+REGISTER_PLUGIN(ComprMultiEffect)
+
+// 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.
+
+ComprMultiConfig::ComprMultiConfig()
+ : CompressorConfigBase(TOTAL_BANDS)
+{
+	q = 1.0;
+	window_size = 4096;
+}
+
+void ComprMultiConfig::copy_from(ComprMultiConfig &that)
+{
+	CompressorConfigBase::copy_from(that);
+
+	window_size = that.window_size;
+	q = that.q;
+}
+
+int ComprMultiConfig::equivalent(ComprMultiConfig &that)
+{
+	if( !CompressorConfigBase::equivalent(that) )
+		return 0;
+
+	if( !EQUIV(q, that.q) ||
+		window_size != that.window_size ) {
+		return 0;
+	}
+
+	return 1;
+}
+
+void ComprMultiConfig::interpolate(ComprMultiConfig &prev, ComprMultiConfig &next,
+	int64_t prev_frame, int64_t next_frame, int64_t current_frame)
+{
+	copy_from(prev);
+}
+
+
+ComprMultiEffect::ComprMultiEffect(PluginServer *server)
+ : PluginAClient(server)
+{
+	reset();
+	for( int i = 0; i < TOTAL_BANDS; i++ )
+		band_states[i] = new BandState(this, i);
+}
+
+ComprMultiEffect::~ComprMultiEffect()
+{
+   	delete_dsp();
+	for( int i = 0; i < TOTAL_BANDS; i++ ) {
+		delete band_states[i];
+	}
+}
+
+void ComprMultiEffect::delete_dsp()
+{
+#ifndef DRAW_AFTER_BANDPASS
+	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;
+	new_input_size = 0;
+#endif
+	if( fft ) {
+		for( int i = 0; i < PluginClient::total_in_buffers; i++ )
+			delete fft[i];
+		delete [] fft;
+	}
+
+	for( int i = 0; i < TOTAL_BANDS; i++ ) {
+		band_states[i]->delete_dsp();
+	}
+
+	filtered_size = 0;
+	fft = 0;
+}
+
+
+void ComprMultiEffect::reset()
+{
+	for( int i = 0; i < TOTAL_BANDS; i++ )
+		band_states[i] = 0;
+
+#ifndef DRAW_AFTER_BANDPASS
+	input_buffer = 0;
+	input_size = 0;
+	new_input_size = 0;
+#endif
+	input_start = 0;
+	filtered_size = 0;
+	last_position = 0;
+	fft = 0;
+	need_reconfigure = 1;
+	config.current_band = 0;
+}
+
+const char* ComprMultiEffect::plugin_title() { return N_("Compressor Multi"); }
+int ComprMultiEffect::is_realtime() { return 1; }
+int ComprMultiEffect::is_multichannel() { return 1; }
+
+
+void ComprMultiEffect::read_data(KeyFrame *keyframe)
+{
+	FileXML input;
+	input.set_shared_input(keyframe->xbuf);
+
+	int result = 0;
+	for( int i = 0; i < TOTAL_BANDS; i++ )
+		config.bands[i].levels.remove_all();
+
+	while( !(result = input.read_tag()) ) {
+		if( input.tag.title_is("COMPRESSOR_MULTI") ) {
+			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);
+			config.q = input.tag.get_property("Q", config.q);
+			config.window_size = input.tag.get_property("WINDOW_SIZE", config.window_size);
+		}
+		else if( input.tag.title_is("COMPRESSORBAND") ) {
+			int number = input.tag.get_property("NUMBER", 0);
+			config.bands[number].read_data(&input, 1);
+		}
+	}
+
+	config.boundaries();
+}
+
+void ComprMultiEffect::save_data(KeyFrame *keyframe)
+{
+	FileXML output;
+	output.set_shared_output(keyframe->xbuf);
+
+	output.tag.set_title("COMPRESSOR_MULTI");
+	output.tag.set_property("TRIGGER", config.trigger);
+	output.tag.set_property("SMOOTHING_ONLY", config.smoothing_only);
+	output.tag.set_property("INPUT", config.input);
+	output.tag.set_property("Q", config.q);
+	output.tag.set_property("WINDOW_SIZE", config.window_size);
+	output.append_tag();
+	output.append_newline();
+
+	for( int band = 0; band < TOTAL_BANDS; band++ ) {
+		BandConfig *band_config = &config.bands[band];
+		band_config->save_data(&output, band, 1);
+	}
+
+
+	output.tag.set_title("/COMPRESSOR_MULTI");
+	output.append_tag();
+	output.append_newline();
+	output.terminate_string();
+}
+
+void ComprMultiEffect::dump_frames()
+{
+	printf("tracking %f, direction %d\n", get_tracking_position(), get_tracking_direction());
+	CompressorClientFrame *cfp = (CompressorClientFrame *)frame_buffer.first;
+	for( int n=0; cfp; cfp=(CompressorClientFrame *)cfp->next,++n ) {
+		switch( cfp->type ) {
+		case GAIN_COMPRESSORFRAME: {
+			CompressorGainFrame *gfp = (CompressorGainFrame *)cfp;
+			printf("%3d: band %d, gain pos=%f, gain=%f, level=%f\n", n, cfp->band,
+				gfp->position, gfp->gain, gfp->level);
+			break; }
+		case FREQ_COMPRESSORFRAME: {
+			CompressorFreqFrame *ffp = (CompressorFreqFrame *)cfp;
+			printf("%3d: band %d, freq pos=%f, max=%f/%f, size=%d\n", n, ffp->band,
+				ffp->position, ffp->freq_max, ffp->time_max, ffp->data_size);
+			break; }
+		}
+	}
+}
+
+void ComprMultiEffect::update_gui()
+{
+//	dump_frames();
+	if( !thread ) return;
+	ComprMultiWindow *window = (ComprMultiWindow *)thread->window;
+	if( !window ) return;
+// user can't change levels when loading configuration
+	window->lock_window("ComprMultiEffect::update_gui 1");
+	int reconfigured = 0;
+// Can't update points if the user is editing
+	if( window->canvas->is_dragging() )
+		reconfigured = load_configuration();
+//printf("ComprMultiEffect::update_gui %d %d %d\n", __LINE__, reconfigured, total_frames);
+	if( reconfigured )
+		window->update();
+	if( pending_gui_frames() )
+		window->update_eqcanvas();
+	window->unlock_window();
+}
+
+
+void ComprMultiEffect::render_stop()
+{
+	if( !thread ) return;
+	ComprMultiWindow *window = (ComprMultiWindow*)thread->window;
+	if( !window ) return;
+	window->lock_window("ComprMultiEffect::render_stop");
+	window->in->reset();
+	window->gain_change->update(1, 0);
+	delete window->gain_frame;  window->gain_frame = 0;
+	delete window->freq_frame;  window->freq_frame = 0;
+	window->unlock_window();
+}
+
+
+LOAD_CONFIGURATION_MACRO(ComprMultiEffect, ComprMultiConfig)
+NEW_WINDOW_MACRO(ComprMultiEffect, ComprMultiWindow)
+
+
+int ComprMultiEffect::process_buffer(int64_t size, Samples **buffer,
+		int64_t start_position, int sample_rate)
+{
+	start_pos = (double)start_position / sample_rate;
+	dir = get_direction() == PLAY_REVERSE ? -1 : 1;
+	need_reconfigure |= load_configuration();
+	int channels = PluginClient::total_in_buffers;
+	if( need_reconfigure ) {
+		need_reconfigure = 0;
+//		min_x = DB::fromdb(config.min_db);  max_x = 1.0;
+//		min_y = DB::fromdb(config.min_db);  max_y = 1.0;
+
+		if( fft && fft[0]->window_size != config.window_size ) {
+			for( int i = 0; i < channels; i++ )
+ 				delete fft[i];
+			delete [] fft;
+			fft = 0;
+		}
+
+		if( !fft ) {
+			fft = new ComprMultiFFT*[channels];
+			for( int i = 0; i < channels; i++ ) {
+				fft[i] = new ComprMultiFFT(this, i);
+				fft[i]->initialize(config.window_size, TOTAL_BANDS);
+			}
+		}
+
+		for( int i = 0; i < TOTAL_BANDS; i++ )
+			band_states[i]->reconfigure();
+	}
+
+// reset after seeking
+	if( last_position != start_position ) {
+		if( fft ) {
+			for( int i = 0; i < channels; i++ )
+ 				if( fft[i] ) fft[i]->delete_fft();
+		}
+
+#ifndef DRAW_AFTER_BANDPASS
+		input_size = 0;
+#endif
+		filtered_size = 0;
+		input_start = start_position;
+		for( int band = 0; band < TOTAL_BANDS; band++ ) {
+			BandState *band_state = band_states[band];
+			if( band_state->engine )
+				band_state->engine->reset();
+		}
+	}
+
+// process frequency domain for all bands simultaneously
+// read enough samples ahead to process all the bands
+	int new_filtered_size = 0;
+	for( int band = 0; band < TOTAL_BANDS; band++ ) {
+		BandState *band_state = band_states[band];
+		if( !band_state->engine )
+			band_state->engine = new CompressorEngine(&config, band);
+		int attack_samples, release_samples, preview_samples;
+		band_state->engine->calculate_ranges(
+			&attack_samples, &release_samples, &preview_samples,
+			sample_rate);
+
+		if( preview_samples > new_filtered_size )
+			new_filtered_size = preview_samples;
+	}
+	new_filtered_size += size;
+	for( int band = 0; band < TOTAL_BANDS; band++ )
+		band_states[band]->allocate_filtered(new_filtered_size);
+
+// Append data to the buffers to fill the readahead area.
+	int remain = new_filtered_size - filtered_size;
+
+	if( remain > 0 ) {
+// printf("ComprMultiEffect::process_buffer %d filtered_size=%ld remain=%d\n",
+// __LINE__, filtered_size, remain);
+		for( int channel = 0; channel < channels; channel++ ) {
+#ifndef DRAW_AFTER_BANDPASS
+			new_input_size = input_size;
+#endif
+// create an array of filtered buffers for the output
+			Samples *filtered_arg[TOTAL_BANDS];
+			for( int band = 0; band < TOTAL_BANDS; band++ ) {
+				new_spectrogram_frames[band] = 0;
+				filtered_arg[band] = band_states[band]->filtered_buffer[channel];
+// temporarily set the output to the end to append data
+				filtered_arg[band]->set_offset(filtered_size);
+			}
+
+// starting position of the input reads
+			int64_t start = input_start + dir * filtered_size;
+// printf("ComprMultiEffect::process_buffer %d start=%ld remain=%d\n", __LINE__, start, remain);
+			fft[channel]->process_buffer(start, remain, filtered_arg, get_direction());
+
+			for( int band = 0; band < TOTAL_BANDS; band++ ) {
+				filtered_arg[band]->set_offset(0);
+			}
+//printf("ComprMultiEffect::process_buffer %d new_input_size=%ld\n", __LINE__, new_input_size);
+		}
+	}
+
+#ifndef DRAW_AFTER_BANDPASS
+	input_size = new_input_size;
+#endif
+	filtered_size = new_filtered_size;
+
+// process time domain for each band separately
+	for( int band = 0; band < TOTAL_BANDS; band++ ) {
+		BandState *band_state = band_states[band];
+		CompressorEngine *engine = band_state->engine;
+
+		engine->process(band_states[band]->filtered_buffer,
+			band_states[band]->filtered_buffer,
+			size, sample_rate, channels, start_position);
+
+		for( int i = 0; i < engine->gui_gains.size(); i++ ) {
+			CompressorGainFrame *frame = new CompressorGainFrame();
+			frame->position = start_pos + dir*engine->gui_offsets[i];
+			frame->gain = engine->gui_gains.get(i);
+			frame->level = engine->gui_levels.get(i);
+			frame->band = band;
+			add_gui_frame(frame);
+		}
+	}
+
+// Add together filtered buffers + unfiltered buffer.
+// Apply the solo here.
+	int have_solo = 0;
+	for( int band = 0; band < TOTAL_BANDS; band++ ) {
+		if( config.bands[band].solo ) {
+			have_solo = 1;
+			break;
+		}
+	}
+
+	for( int channel = 0; channel < channels; channel++ ) {
+		double *dst = buffer[channel]->get_data();
+		bzero(dst, size * sizeof(double));
+
+		for( int band = 0; band < TOTAL_BANDS; band++ ) {
+			if( !have_solo || config.bands[band].solo ) {
+				double *src = band_states[band]->filtered_buffer[channel]->get_data();
+				for( int i = 0; i < size; i++ ) {
+					dst[i] += src[i];
+				}
+			}
+		}
+	}
+
+// shift input buffers
+	for( int band = 0; band < TOTAL_BANDS; band++ ) {
+
+		for( int i = 0; i < channels; i++ ) {
+			memcpy(band_states[band]->filtered_buffer[i]->get_data(),
+				band_states[band]->filtered_buffer[i]->get_data() + size,
+				(filtered_size - size) * sizeof(double));
+
+		}
+	}
+
+#ifndef DRAW_AFTER_BANDPASS
+	for( int i = 0; i < channels; i++ ) {
+		memcpy(input_buffer[i]->get_data(),
+			input_buffer[i]->get_data() + size,
+			(input_size - size) * sizeof(double));
+	}
+	input_size -= size;
+#endif
+
+// update the counters
+	filtered_size -= size;
+	input_start += dir * size;
+	last_position = start_position + dir * size;
+	return 0;
+}
+
+void ComprMultiEffect::allocate_input(int new_size)
+{
+#ifndef DRAW_AFTER_BANDPASS
+	if( !input_buffer ||
+		new_size > input_buffer[0]->get_allocated() ) {
+		Samples **new_input_buffer = new Samples*[get_total_buffers()];
+
+		for( int i = 0; i < get_total_buffers(); i++ ) {
+			new_input_buffer[i] = new Samples(new_size);
+
+			if( input_buffer ) {
+				memcpy(new_input_buffer[i]->get_data(),
+					input_buffer[i]->get_data(),
+					input_buffer[i]->get_allocated() * sizeof(double));
+				delete input_buffer[i];
+			}
+		}
+
+		if( input_buffer ) delete [] input_buffer;
+		input_buffer = new_input_buffer;
+   	}
+#endif // !DRAW_AFTER_BANDPASS
+}
+
+
+void ComprMultiEffect::calculate_envelope()
+{
+	for( int i = 0; i < TOTAL_BANDS; i++ ) {
+		band_states[i]->calculate_envelope();
+	}
+}
+
+
+BandState::BandState(ComprMultiEffect *plugin, int band)
+{
+	this->plugin = plugin;
+	this->band = band;
+	reset();
+}
+
+BandState::~BandState()
+{
+	delete_dsp();
+}
+
+void BandState::delete_dsp()
+{
+	delete [] envelope;
+	levels.remove_all();
+	if( filtered_buffer ) {
+		for( int i = 0; i < plugin->total_in_buffers; i++ ) {
+			delete filtered_buffer[i];
+		}
+		delete [] filtered_buffer;
+	}
+	if( engine ) {
+		delete engine;
+	}
+	reset();
+}
+
+void BandState::reset()
+{
+	engine = 0;
+	envelope = 0;
+	envelope_allocated = 0;
+	filtered_buffer = 0;
+
+	next_target = 1.0;
+	previous_target = 1.0;
+	target_samples = 1;
+	target_current_sample = -1;
+	current_value = 1.0;
+}
+
+void BandState::reconfigure()
+{
+// Calculate linear transfer from db
+	levels.remove_all();
+
+	BandConfig *config = &plugin->config.bands[band];
+	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);
+	}
+
+	calculate_envelope();
+}
+
+
+void BandState::allocate_filtered(int new_size)
+{
+	if( !filtered_buffer ||
+		new_size > filtered_buffer[0]->get_allocated() ) {
+		Samples **new_filtered_buffer = new Samples*[plugin->get_total_buffers()];
+		for( int i = 0; i < plugin->get_total_buffers(); i++ ) {
+			new_filtered_buffer[i] = new Samples(new_size);
+
+			if( filtered_buffer ) {
+				memcpy(new_filtered_buffer[i]->get_data(),
+					filtered_buffer[i]->get_data(),
+					filtered_buffer[i]->get_allocated() * sizeof(double));
+				delete filtered_buffer[i];
+			}
+		}
+
+		if( filtered_buffer ) delete [] filtered_buffer;
+		filtered_buffer = new_filtered_buffer;
+	}
+}
+
+
+void BandState::calculate_envelope()
+{
+// the window size changed
+	if( envelope && envelope_allocated < plugin->config.window_size / 2 ) {
+		delete [] envelope;
+		envelope = 0;
+	}
+
+	if( !envelope ) {
+		envelope_allocated = plugin->config.window_size / 2;
+		envelope = new double[envelope_allocated];
+	}
+
+// number of slots in the edge
+	double edge = (1.0 - plugin->config.q) * TOTALFREQS / 2;
+	int max_freq = Freq::tofreq_f(TOTALFREQS - 1);
+	int nyquist = plugin->project_sample_rate / 2;
+	int freq = plugin->config.bands[band].freq;
+
+// max frequency of all previous bands is the low
+	int low = 0;
+	for( int i=0; i<band; ++i ) {
+		if( plugin->config.bands[i].freq > low )
+			low = plugin->config.bands[i].freq;
+	}
+	int high = max_freq;
+// limit the frequencies
+	if( band < TOTAL_BANDS-1 ) high = freq;
+	if( high >= max_freq ) { high = max_freq;  edge = 0; }
+// hard edges on the lowest & highest
+	if( band == 0 && high <= 0 ) edge = 0;
+	if( low > high ) low = high;
+// number of slots to arrive at 1/2 power
+#ifndef LOG_CROSSOVER
+// linear
+	double edge2 = edge / 2;
+#else
+// log
+	double edge2 = edge * 6 / -INFINITYGAIN;
+#endif
+	double low_slot = Freq::fromfreq_f(low);
+	double high_slot = Freq::fromfreq_f(high);
+// shift slots to allow crossover
+	if( band < TOTAL_BANDS-1 ) high_slot -= edge2;
+	if( band > 0 ) low_slot += edge2;
+
+	for( int i = 0; i < plugin->config.window_size / 2; i++ ) {
+		double freq = i * nyquist / (plugin->config.window_size / 2);
+		double slot = Freq::fromfreq_f(freq);
+// sum of previous bands
+		double prev_sum = 0;
+		for( int prev_band = 0; prev_band < band; prev_band++ ) {
+			double *prev_envelope = plugin->band_states[prev_band]->envelope;
+			prev_sum += prev_envelope[i];
+		}
+
+		if( slot < high_slot )
+// remain of previous bands
+			envelope[i] = 1.0 - prev_sum;
+		else if( slot < high_slot + edge ) {
+// next crossover
+			double remain = 1.0 - prev_sum;
+#ifndef LOG_CROSSOVER
+// linear
+			envelope[i] = remain - remain * (slot - high_slot) / edge;
+#else
+// log TODO
+			envelope[i] = DB::fromdb((slot - high_slot) * INFINITYGAIN / edge);
+#endif
+
+		}
+		else
+			envelope[i] = 0.0;
+	}
+}
+
+
+void BandState::process_readbehind(int size,
+		int reaction_samples, int decay_samples, int trigger)
+{
+	if( target_current_sample < 0 )
+		target_current_sample = reaction_samples;
+	double current_slope = (next_target - previous_target) / reaction_samples;
+	double *trigger_buffer = filtered_buffer[trigger]->get_data();
+	int channels = plugin->get_total_buffers();
+	for( int i = 0; i < size; i++ ) {
+// Get slope required to reach current sample from smoothed sample over reaction
+// length.
+		double sample = 0;
+		switch( plugin->config.input ) {
+		case ComprMultiConfig::MAX: {
+			double max = 0;
+			for( int j = 0; j < channels; j++ ) {
+				sample = fabs(filtered_buffer[j]->get_data()[i]);
+				if( sample > max ) max = sample;
+			}
+			sample = max;
+			break; }
+
+		case ComprMultiConfig::TRIGGER:
+			sample = fabs(trigger_buffer[i]);
+			break;
+
+		case ComprMultiConfig::SUM: {
+			double max = 0;
+			for( int j = 0; j < channels; j++ ) {
+				sample = fabs(filtered_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( plugin->config.smoothing_only ) {
+			for( int j = 0; j < channels; j++ ) {
+				filtered_buffer[j]->get_data()[i] = current_value;
+			}
+		}
+		else
+		if( !plugin->config.bands[band].bypass ) {
+			double gain = plugin->config.calculate_gain(band, current_value);
+			for( int j = 0; j < channels; j++ ) {
+				filtered_buffer[j]->get_data()[i] *= gain;
+			}
+		}
+	}
+}
+
+void BandState::process_readahead(int size, int preview_samples,
+		int reaction_samples, int decay_samples, int trigger)
+{
+	if( target_current_sample < 0 ) target_current_sample = target_samples;
+	double current_slope = (next_target - previous_target) /
+		target_samples;
+	double *trigger_buffer = filtered_buffer[trigger]->get_data();
+	int channels = plugin->get_total_buffers();
+	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++ ) {
+			int buffer_offset = i + j;
+			double sample = 0;
+			switch( plugin->config.input ) {
+			case ComprMultiConfig::MAX: {
+				double max = 0;
+				for( int k = 0; k < channels; k++ ) {
+					sample = fabs(filtered_buffer[k]->get_data()[buffer_offset]);
+					if( sample > max ) max = sample;
+				}
+				sample = max;
+				break; }
+
+			case ComprMultiConfig::TRIGGER:
+				sample = fabs(trigger_buffer[buffer_offset]);
+				break;
+
+			case ComprMultiConfig::SUM: {
+				double max = 0;
+				for( int k = 0; k < channels; k++ ) {
+					sample = fabs(filtered_buffer[k]->get_data()[buffer_offset]);
+					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;
+
+		target_current_sample++;
+
+		if( plugin->config.smoothing_only ) {
+			for( int j = 0; j < channels; j++ ) {
+				filtered_buffer[j]->get_data()[i] = current_value;
+			}
+		}
+		else
+		if( !plugin->config.bands[band].bypass ) {
+			double gain = plugin->config.calculate_gain(band, current_value);
+			for( int j = 0; j < channels; j++ ) {
+				filtered_buffer[j]->get_data()[i] *= gain;
+			}
+		}
+	}
+}
+
+
+ComprMultiFFT::ComprMultiFFT(ComprMultiEffect *plugin, int channel)
+{
+	this->plugin = plugin;
+	this->channel = channel;
+}
+
+ComprMultiFFT::~ComprMultiFFT()
+{
+}
+
+int ComprMultiFFT::signal_process(int band)
+{
+	int sample_rate = plugin->PluginAClient::project_sample_rate;
+	BandState *band_state = plugin->band_states[band];
+
+// Create new spectrogram frame for updating the GUI
+	frame = 0;
+	if(
+#ifndef DRAW_AFTER_BANDPASS
+		band == 0 &&
+#endif
+		((plugin->config.input != ComprMultiConfig::TRIGGER && channel == 0) ||
+		channel == plugin->config.trigger) ) {
+#ifndef DRAW_AFTER_BANDPASS
+		int total_data = window_size / 2;
+#else
+		int total_data = TOTAL_BANDS * window_size / 2;
+#endif
+
+// store all bands in the same GUI frame
+		frame = new CompressorFreqFrame();
+		frame->band = band;
+		frame->data_size = total_data;
+		frame->data = new double[total_data];
+		bzero(frame->data, sizeof(double) * total_data);
+		frame->nyquist = sample_rate / 2;
+
+// 		int attack_samples, release_samples, preview_samples;
+//		band_state->engine->calculate_ranges(&attack_samples,
+//			 &release_samples, &preview_samples, sample_rate);
+
+// FFT advances 1/2 a window for each spectrogram frame
+		int n = plugin->new_spectrogram_frames[band]++;
+		double sample_pos = (n * window_size / 2) / sample_rate;
+		frame->position = plugin->start_pos + plugin->dir * sample_pos;
+//if( band == 1 ) {
+// printf("ComprMultiFFT::signal_process %d top_position=%ld frame->position=%ld\n",
+// __LINE__, plugin->get_playhead_position(), frame->position);
+// printf("ComprMultiFFT::signal_process %d band=%d preview_samples=%d frames size=%ld filtered_size=%ld\n",
+// __LINE__, band, preview_samples, plugin->new_spectrogram_frames[band] *
+//  window_size, plugin->filtered_size);
+//}
+	}
+//printf("ComprMultiFFT::signal_process %d channel=%d band=%d frame=%p\n", __LINE__, channel, band, frame);
+// apply the bandpass filter
+	for( int i = 0; i < window_size / 2; i++ ) {
+		double fr = freq_real[i], fi = freq_imag[i];
+		double env = band_state->envelope[i];
+		freq_real[i] *= env;  freq_imag[i] *= env;
+		double mag = sqrt(fr*fr + fi*fi);
+
+// update the spectrogram with the output
+// neglect the true average & max spectrograms, but always use the trigger
+		if( frame ) {
+			int offset = band * window_size / 2 + i;
+#ifndef DRAW_AFTER_BANDPASS
+			frame->data[offset] = MAX(frame->data[offset], mag);
+// get the maximum output in the frequency domain
+			if( mag > frame->freq_max )
+				frame->freq_max = mag;
+#else
+			mag *= env;
+			frame->data[offset] = MAX(frame->data[offset], mag);
+// get the maximum output in the frequency domain
+			if( mag > frame->freq_max )
+				frame->freq_max = mag;
+#endif
+		}
+	}
+
+	symmetry(window_size, freq_real, freq_imag);
+	return 0;
+}
+
+
+int ComprMultiFFT::post_process(int band)
+{
+	if( frame ) {
+// get the maximum output in the time domain
+		double *buffer = output_real;
+#ifndef DRAW_AFTER_BANDPASS
+		buffer = input_buffer->get_data();
+#endif
+		double time_max = 0;
+		for( int i = 0; i<window_size; ++i ) {
+			if( fabs(buffer[i]) > time_max )
+				time_max = fabs(buffer[i]);
+		}
+		if( time_max > frame->time_max )
+			frame->time_max = time_max;
+		plugin->add_gui_frame(frame);
+	}
+	return 0;
+}
+
+
+int ComprMultiFFT::read_samples(int64_t output_sample,
+		int samples, Samples *buffer)
+{
+	int result = plugin->read_samples(buffer, channel,
+			plugin->get_samplerate(), output_sample, samples);
+#ifndef DRAW_AFTER_BANDPASS
+// append unprocessed samples to the input_buffer
+	int new_input_size = plugin->new_input_size + samples;
+	plugin->allocate_input(new_input_size);
+	memcpy(plugin->input_buffer[channel]->get_data() + plugin->new_input_size,
+		buffer->get_data(), samples * sizeof(double));
+	plugin->new_input_size = new_input_size;
+#endif // !DRAW_AFTER_BANDPASS
+	return result;
+}
+
diff --git a/cinelerra-5.1/plugins/compressormulti/comprmulti.h b/cinelerra-5.1/plugins/compressormulti/comprmulti.h
new file mode 100644
index 00000000..bc542670
--- /dev/null
+++ b/cinelerra-5.1/plugins/compressormulti/comprmulti.h
@@ -0,0 +1,163 @@
+
+/*
+ * CINELERRA
+ * 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
+ * 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
+ */
+
+#ifndef COMPRMULTI_H
+#define COMPRMULTI_H
+
+#include "bchash.inc"
+#include "compressortools.h"
+#include "fourier.h"
+#include "mutex.h"
+#include "pluginaclient.h"
+#include "samples.inc"
+#include "vframe.inc"
+
+class ComprMultiEffect;
+class ComprMultiFFT;
+
+//#define LOG_CROSSOVER
+#define DRAW_AFTER_BANDPASS
+
+#define TOTAL_BANDS 3
+
+class ComprMultiConfig : public CompressorConfigBase
+{
+public:
+	ComprMultiConfig();
+
+	void copy_from(ComprMultiConfig &that);
+	int equivalent(ComprMultiConfig &that);
+	void interpolate(ComprMultiConfig &prev, ComprMultiConfig &next,
+		int64_t prev_frame, int64_t next_frame, int64_t current_frame);
+	double q;
+	int window_size;
+};
+
+class ComprMultiFFT :public CrossfadeFFT
+{
+public:
+	ComprMultiFFT(ComprMultiEffect *plugin, int channel);
+	~ComprMultiFFT();
+	
+	int signal_process(int band);
+	int post_process(int band);
+	int read_samples(int64_t output_sample, int samples, Samples *buffer);
+
+	ComprMultiEffect *plugin;
+	int channel;
+	CompressorFreqFrame *frame;
+};
+
+// processing state of a single band
+class BandState
+{
+public:
+	BandState(ComprMultiEffect *plugin, int band);
+	~BandState();
+
+	void delete_dsp();
+	void reset();
+	void reconfigure();
+// calculate the envelope for only this band
+	void calculate_envelope();
+	void process_readbehind(int size,
+		int reaction_samples, int decay_samples, int trigger);
+	void process_readahead(int size, int preview_samples,
+		int reaction_samples, int decay_samples, int trigger);
+	void allocate_filtered(int new_size);
+
+// bandpass filter for this band
+	double *envelope;
+	int envelope_allocated;
+// The input for all channels with filtering by this band
+	Samples **filtered_buffer;
+// ending input value of smoothed input
+	double next_target;
+// starting input value of smoothed input
+	double previous_target;
+// samples between previous and next target value for readahead
+	int target_samples;
+// current sample from 0 to target_samples
+	int target_current_sample;
+// current smoothed input value
+	double current_value;
+// Temporaries for linear transfer
+	ArrayList<compressor_point_t> levels;
+	ComprMultiEffect *plugin;
+	int band;
+	CompressorEngine *engine;
+};
+
+class ComprMultiEffect : public PluginAClient
+{
+public:
+	ComprMultiEffect(PluginServer *server);
+	~ComprMultiEffect();
+
+	int is_multichannel();
+	int is_realtime();
+	void read_data(KeyFrame *keyframe);
+	void save_data(KeyFrame *keyframe);
+	int process_buffer(int64_t size, Samples **buffer,
+		int64_t start_position, int sample_rate);
+
+// calculate envelopes of all the bands
+	void calculate_envelope();
+
+	void allocate_input(int new_size);
+
+	void reset();
+	void update_gui();
+	void render_stop();
+	void delete_dsp();
+	void dump_frames();
+
+	PLUGIN_CLASS_MEMBERS(ComprMultiConfig)
+
+#ifndef DRAW_AFTER_BANDPASS
+// The out of band data for each channel with readahead
+	Samples **input_buffer;
+// Number of samples in the unfiltered input buffers
+	int64_t input_size;
+// input buffer size being calculated by the FFT readers
+	int64_t new_input_size;
+#endif // !DRAW_AFTER_BANDPASS
+// Starting sample of the input buffer relative to project in the requested rate.
+	int64_t input_start;
+// Number of samples in the filtered input buffers
+	int64_t filtered_size;
+
+// detect seeking
+	int64_t last_position;
+
+// count spectrogram frames for each band
+	int new_spectrogram_frames[TOTAL_BANDS];
+
+	BandState *band_states[TOTAL_BANDS];
+// The big FFT with multiple channels & multiple bands extracted per channel.
+	ComprMultiFFT **fft;
+
+	int need_reconfigure;
+	double start_pos;
+	int dir;
+};
+
+
+#endif
diff --git a/cinelerra-5.1/plugins/compressormulti/comprmultigui.C b/cinelerra-5.1/plugins/compressormulti/comprmultigui.C
new file mode 100644
index 00000000..95b4c5d9
--- /dev/null
+++ b/cinelerra-5.1/plugins/compressormulti/comprmultigui.C
@@ -0,0 +1,732 @@
+/*
+ * CINELERRA
+ * 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
+ * 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 "asset.h"
+#include "bchash.h"
+#include "bcsignals.h"
+#include "clip.h"
+#include "comprmultigui.h"
+#include "cursors.h"
+#include "edl.h"
+#include "edlsession.h"
+#include "eqcanvas.h"
+#include "file.h"
+#include "language.h"
+#include "theme.h"
+#include "units.h"
+
+#include <string.h>
+
+ComprMultiWindow::ComprMultiWindow(ComprMultiEffect *plugin)
+ : PluginClientWindow(plugin, xS(650),yS(560), xS(650),yS(560), 0)
+{
+	this->plugin = plugin;
+	char string[BCTEXTLEN];
+// set the default directory
+	sprintf(string, "%s/compressormulti.rc", File::get_config_path());
+	defaults = new BC_Hash(string);
+	defaults->load();
+	plugin->config.current_band = defaults->get("CURRENT_BAND", plugin->config.current_band);
+	gain_frame = 0;
+	freq_frame = 0;
+}
+
+ComprMultiWindow::~ComprMultiWindow()
+{
+	defaults->update("CURRENT_BAND", plugin->config.current_band);
+	defaults->save();
+	delete defaults;
+
+	delete eqcanvas;
+	delete reaction;
+	delete x_text;
+	delete y_text;
+	delete trigger;
+	delete decay;
+	delete gain_frame;
+	delete freq_frame;
+}
+
+
+void ComprMultiWindow::create_objects()
+{
+	int margin = client->get_theme()->widget_border;
+	int x = margin, y = margin;
+	int control_margin = xS(150);
+	int canvas_y2 = get_h() * 2 / 3;
+	BC_Title *title;
+	BandConfig *band_config = &plugin->config.bands[plugin->config.current_band];
+	BandConfig *prev_band = 0;
+	if( plugin->config.current_band > 0 ) {
+		prev_band = &plugin->config.bands[plugin->config.current_band - 1];
+	}
+
+	add_subwindow(title = new BC_Title(x, y, "In:"));
+	int y2 = y + title->get_h() + margin;
+	EDL *edl = plugin->get_edl();
+	add_subwindow(in = new BC_Meter(x, y2, METER_VERT,
+		canvas_y2 - y2,
+		edl->session->min_meter_db,
+		edl->session->max_meter_db,
+		edl->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, // downmix
+		1)); // is_gain_change
+	x += gain_change->get_w() + xS(35);
+
+	add_subwindow(title = new BC_Title(x, y, _("Current band:")));
+
+	int x1 = title->get_x() + title->get_w() + margin;
+	char string[BCTEXTLEN];
+	for( int i = 0; i < TOTAL_BANDS; i++ ) {
+		sprintf(string, "%d", i + 1);
+		add_subwindow(band[i] = new ComprMultiBand(this, plugin, 
+			x1, y, i, string));
+		x1 += band[i]->get_w() + margin;
+	}
+	y += band[0]->get_h() + 1;
+
+
+	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 ComprMultiCanvas(plugin, this,
+		x, y, get_w() - x - control_margin - xS(10), canvas_y2 - y));
+	y += canvas->get_h() + yS(30);
+
+	add_subwindow(title = new BC_Title(margin, y, _("Bandwidth:")));
+	y += title->get_h();
+	eqcanvas = new EQCanvas(this, margin, y,
+		canvas->get_w() + x - margin, get_h() - y - margin,
+		plugin->config.min_db, 0.0);
+	eqcanvas->freq_divisions = 10;
+	eqcanvas->initialize();
+
+	x = get_w() - control_margin;
+	y = margin;
+	add_subwindow(title = new BC_Title(x, y, _("Attack secs:")));
+	y += title->get_h();
+	reaction = new ComprMultiReaction(plugin, this, x, y);
+	reaction->create_objects();
+	y += reaction->get_h() + margin;
+
+	add_subwindow(title = new BC_Title(x, y, _("Release secs:")));
+	y += title->get_h();
+	decay = new ComprMultiDecay(plugin, this, x, y);
+	decay->create_objects();
+	y += decay->get_h() + margin;
+
+	add_subwindow(solo = new ComprMultiSolo(plugin, x, y));
+	y += solo->get_h() + margin;
+	add_subwindow(bypass = new ComprMultiBypass(plugin, x, y));
+	y += bypass->get_h() + margin;
+	add_subwindow(title = new BC_Title(x, y, _("Output:")));
+	y += title->get_h();
+
+	y_text = new ComprMultiY(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 ComprMultiX(plugin, this, x, y);
+	x_text->create_objects();
+	y += x_text->get_h() + margin;
+
+	add_subwindow(clear = new ComprMultiClear(plugin, x, y));
+	y += clear->get_h() + margin;
+
+	add_subwindow(title = new BC_Title(x, y, _("Freq range:")));
+	y += title->get_h();
+
+// the previous high frequency
+	int *ptr = 0;
+	if( prev_band ) {
+		ptr = &prev_band->freq;
+	}
+
+	add_subwindow(freq1 = new ComprMultiQPot(this, 
+		plugin, 
+		get_w() - (margin + BC_Pot::calculate_w()) * 2, 
+		y, 
+		ptr));
+
+// the current high frequency
+	ptr = &band_config->freq;
+	if( plugin->config.current_band == TOTAL_BANDS - 1 ) {
+		ptr = 0;
+	}
+
+	add_subwindow(freq2 = new ComprMultiQPot(this, 
+		plugin, 
+		get_w() - margin - BC_Pot::calculate_w(), 
+		y, 
+		ptr));
+	y += freq1->get_h() + margin;
+
+	BC_Bar *bar;
+	add_subwindow(bar = new BC_Bar(x, y, get_w() - x - margin));
+	y += bar->get_h() + margin;
+
+	add_subwindow(title = new BC_Title(x, y, _("Trigger Type:")));
+	y += title->get_h();
+	add_subwindow(input = new ComprMultiInput(plugin, x, y));
+	input->create_objects();
+	y += input->get_h() + margin;
+	add_subwindow(title = new BC_Title(x, y, _("Trigger:")));
+	y += title->get_h();
+
+	trigger = new ComprMultiTrigger(plugin, this, x, y);
+	trigger->create_objects();
+	if( plugin->config.input != ComprMultiConfig::TRIGGER ) trigger->disable();
+	y += trigger->get_h() + margin;
+
+	add_subwindow(smooth = new ComprMultiSmooth(plugin, x, y));
+	y += smooth->get_h() + margin;
+
+	add_subwindow(title = new BC_Title(x, y, _("Steepness:")));
+	add_subwindow(q = new ComprMultiFPot(this, plugin, 
+		get_w() - margin - BC_Pot::calculate_w(), y, 
+		&plugin->config.q, 0, 1));
+	y += q->get_h() + margin;
+
+	add_subwindow(title = new BC_Title(x, y, _("Window size:")));
+	y += title->get_h();
+	add_subwindow(size = new ComprMultiSize(this,
+		plugin,
+		x,
+		y));
+	size->create_objects();
+	size->update(plugin->config.window_size);
+	y += size->get_h() + margin;
+
+	canvas->create_objects();
+	update_eqcanvas();
+	show_window();
+}
+
+// called when the user selects a different band
+void ComprMultiWindow::update()
+{
+	BandConfig *band_config = &plugin->config.bands[plugin->config.current_band];
+
+	for( int i = 0; i < TOTAL_BANDS; i++ ) {
+		if( plugin->config.current_band == i ) {
+			band[i]->update(1);
+		}
+		else {
+			band[i]->update(0);
+		}
+	}
+
+	int *ptr = 0;
+	if( plugin->config.current_band > 0 ) {
+		ptr = &plugin->config.bands[plugin->config.current_band - 1].freq;
+	}
+	else {
+		ptr = 0;
+	}
+
+	freq1->output = ptr;
+	if( ptr ) {
+		freq1->update(*ptr);
+		freq1->enable();
+	}
+	else {
+		freq1->update(0);
+		freq1->disable();
+	}
+
+// top band edits the penultimate band
+	if( plugin->config.current_band < TOTAL_BANDS - 1 ) {
+		ptr = &band_config->freq;
+	}
+	else {
+		ptr = 0;
+	}
+
+	freq2->output = ptr;
+	if( ptr ) {
+		freq2->update(*ptr);
+		freq2->enable();
+	}
+	else {
+		freq2->update(0);
+		freq2->disable();
+	}
+
+	q->update(plugin->config.q);
+	solo->update(band_config->solo);
+	bypass->update(band_config->bypass);
+	size->update(plugin->config.window_size);
+
+	if( atol(trigger->get_text()) != plugin->config.trigger ) {
+		trigger->update((int64_t)plugin->config.trigger);
+	}
+
+	if( strcmp(input->get_text(), ComprMultiInput::value_to_text(plugin->config.input)) ) {
+		input->set_text(ComprMultiInput::value_to_text(plugin->config.input));
+	}
+
+	if( plugin->config.input != ComprMultiConfig::TRIGGER && trigger->get_enabled() ) {
+		trigger->disable();
+	}
+	else
+	if( plugin->config.input == ComprMultiConfig::TRIGGER && !trigger->get_enabled() ) {
+		trigger->enable();
+	}
+
+	if( !EQUIV(atof(reaction->get_text()), band_config->attack_len) ) {
+		reaction->update((float)band_config->attack_len);
+	}
+
+	if( !EQUIV(atof(decay->get_text()), band_config->release_len) ) {
+		decay->update((float)band_config->release_len);
+	}
+
+	smooth->update(plugin->config.smoothing_only);
+	if( canvas->current_operation == ComprMultiCanvas::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->update();
+	update_eqcanvas();
+}
+
+
+
+
+void ComprMultiWindow::update_eqcanvas()
+{
+	plugin->calculate_envelope();
+// filter GUI frames by band & data type
+	int have_meter = 0;
+	CompressorClientFrame *frame = 0;
+// gdb plugin->dump_frames();
+	double tracking_position = plugin->get_tracking_position();
+	int dir = plugin->get_tracking_direction() == PLAY_REVERSE ? -1 : 1;
+	while( (frame = (CompressorClientFrame*)plugin->next_gui_frame()) ) {
+		if( dir*(frame->position - tracking_position) > 0 ) break;
+		if( frame->band == plugin->config.current_band ) {
+// only frames for desired band
+			switch( frame->type ) {
+			case FREQ_COMPRESSORFRAME: {
+				delete freq_frame;
+				freq_frame = (CompressorFreqFrame *)
+					plugin->get_gui_frame(0, 0);
+				continue; }
+			case GAIN_COMPRESSORFRAME: {
+				delete gain_frame;
+				gain_frame = (CompressorGainFrame *)
+					plugin->get_gui_frame(0, 0);
+				have_meter = 1;
+				continue; }
+			}
+		}
+		delete plugin->get_gui_frame(0, 0);
+	}
+	if( have_meter ) {
+		gain_change->update(gain_frame->gain, 0);
+		in->update(gain_frame->level, 0);
+	}
+
+#ifndef DRAW_AFTER_BANDPASS
+	eqcanvas->update_spectrogram(freq_frame); 
+#else
+	eqcanvas->update_spectrogram(freq_frame,
+		plugin->config.current_band * plugin->config.window_size / 2,
+		TOTAL_BANDS * plugin->config.window_size / 2,
+		plugin->config.window_size);
+#endif
+
+// draw the active band on top of the others
+	for( int pass = 0; pass < 2; pass++ ) {
+		for( int band = 0; band < TOTAL_BANDS; band++ ) {
+			if( band == plugin->config.current_band && pass == 0 ||
+				band != plugin->config.current_band && pass == 1 ) {
+				continue;
+			}
+
+			eqcanvas->draw_envelope(plugin->band_states[band]->envelope,
+				plugin->PluginAClient::project_sample_rate,
+				plugin->config.window_size,
+				band == plugin->config.current_band,
+				0);
+		}
+	}
+	eqcanvas->canvas->flash(1);
+}
+
+int ComprMultiWindow::resize_event(int w, int h)
+{
+	return 1;
+}
+
+
+ComprMultiFPot::ComprMultiFPot(ComprMultiWindow *gui, ComprMultiEffect *plugin, 
+		int x, int y, double *output, double min, double max)
+ : BC_FPot(x, y, *output, min, max)
+{
+	this->gui = gui;
+	this->plugin = plugin;
+	this->output = output;
+	set_precision(0.01);
+}
+
+int ComprMultiFPot::handle_event()
+{
+	*output = get_value();
+	plugin->send_configure_change();
+	gui->update_eqcanvas();
+	return 1;
+}
+
+
+ComprMultiQPot::ComprMultiQPot(ComprMultiWindow *gui, ComprMultiEffect *plugin, 
+		int x, int y, int *output)
+ : BC_QPot(x, y, output ? *output : 0)
+{
+	this->gui = gui;
+	this->plugin = plugin;
+	this->output = output;
+}
+
+
+int ComprMultiQPot::handle_event()
+{
+	if( output ) {
+		*output = get_value();
+		plugin->send_configure_change();
+		gui->update_eqcanvas();
+	}
+	return 1;
+}
+
+
+ComprMultiSize::ComprMultiSize(ComprMultiWindow *gui, 
+	ComprMultiEffect *plugin, int x, int y)
+ : BC_PopupMenu(x, y, xS(100), "4096", 1)
+{
+	this->gui = gui;
+	this->plugin = plugin;
+}
+
+int ComprMultiSize::handle_event()
+{
+	plugin->config.window_size = atoi(get_text());
+	plugin->send_configure_change();
+	gui->update_eqcanvas();
+	return 1;
+}
+
+
+void ComprMultiSize::create_objects()
+{
+	add_item(new BC_MenuItem("2048"));
+	add_item(new BC_MenuItem("4096"));
+	add_item(new BC_MenuItem("8192"));
+	add_item(new BC_MenuItem("16384"));
+	add_item(new BC_MenuItem("32768"));
+	add_item(new BC_MenuItem("65536"));
+	add_item(new BC_MenuItem("131072"));
+	add_item(new BC_MenuItem("262144"));
+}
+
+
+void ComprMultiSize::update(int size)
+{
+	char string[BCTEXTLEN];
+	sprintf(string, "%d", size);
+	set_text(string);
+}
+
+
+ComprMultiCanvas::ComprMultiCanvas(ComprMultiEffect *plugin, 
+		ComprMultiWindow *window, int x, int y, int w, int h)
+ : CompressorCanvasBase(&plugin->config, plugin, window, x, y, w, h)
+{
+}
+
+void ComprMultiCanvas::update_window()
+{
+	((ComprMultiWindow*)window)->update();
+}
+
+
+ComprMultiReaction::ComprMultiReaction(ComprMultiEffect *plugin, 
+	ComprMultiWindow *window, int x, int y) 
+ : BC_TumbleTextBox(window,
+	(float)plugin->config.bands[plugin->config.current_band].attack_len,
+	(float)MIN_ATTACK, (float)MAX_ATTACK, x, y, xS(100))
+{
+	this->plugin = plugin;
+	set_increment(0.1);
+	set_precision(2);
+}
+
+int ComprMultiReaction::handle_event()
+{
+	plugin->config.bands[plugin->config.current_band].attack_len = atof(get_text());
+	plugin->send_configure_change();
+	return 1;
+}
+
+
+
+ComprMultiDecay::ComprMultiDecay(ComprMultiEffect *plugin, 
+	ComprMultiWindow *window, int x, int y) 
+ : BC_TumbleTextBox(window,
+	(float)plugin->config.bands[plugin->config.current_band].release_len,
+	(float)MIN_DECAY, (float)MAX_DECAY, x, y, xS(100))
+{
+	this->plugin = plugin;
+	set_increment(0.1);
+	set_precision(2);
+}
+int ComprMultiDecay::handle_event()
+{
+	plugin->config.bands[plugin->config.current_band].release_len = atof(get_text());
+	plugin->send_configure_change();
+	return 1;
+}
+
+
+ComprMultiX::ComprMultiX(ComprMultiEffect *plugin, 
+	ComprMultiWindow *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 ComprMultiX::handle_event()
+{
+	BandConfig *band_config = &plugin->config.bands[plugin->config.current_band];
+
+	int current_point = ((ComprMultiWindow*)plugin->thread->window)->canvas->current_point;
+	if( current_point < band_config->levels.total ) {
+		band_config->levels.values[current_point].x = atof(get_text());
+		((ComprMultiWindow*)plugin->thread->window)->canvas->update();
+		plugin->send_configure_change();
+	}
+	return 1;
+}
+
+
+ComprMultiY::ComprMultiY(ComprMultiEffect *plugin, 
+	ComprMultiWindow *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 ComprMultiY::handle_event()
+{
+	BandConfig *band_config = &plugin->config.bands[plugin->config.current_band];
+
+	int current_point = ((ComprMultiWindow*)plugin->thread->window)->canvas->current_point;
+	if( current_point < band_config->levels.total ) {
+		band_config->levels.values[current_point].y = atof(get_text());
+		((ComprMultiWindow*)plugin->thread->window)->canvas->update();
+		plugin->send_configure_change();
+	}
+	return 1;
+}
+
+
+ComprMultiTrigger::ComprMultiTrigger(ComprMultiEffect *plugin, 
+	ComprMultiWindow *window,
+	int x, 
+	int y) 
+ : BC_TumbleTextBox(window, (int)plugin->config.trigger,
+	MIN_TRIGGER, MAX_TRIGGER, x, y, xS(100))
+{
+	this->plugin = plugin;
+}
+int ComprMultiTrigger::handle_event()
+{
+	plugin->config.trigger = atol(get_text());
+	plugin->send_configure_change();
+	return 1;
+}
+
+
+ComprMultiInput::ComprMultiInput(ComprMultiEffect *plugin, int x, int y) 
+ : BC_PopupMenu(x, y, xS(100), 
+	ComprMultiInput::value_to_text(plugin->config.input), 1)
+{
+	this->plugin = plugin;
+}
+int ComprMultiInput::handle_event()
+{
+	plugin->config.input = text_to_value(get_text());
+	((ComprMultiWindow*)plugin->thread->window)->update();
+	plugin->send_configure_change();
+	return 1;
+}
+
+void ComprMultiInput::create_objects()
+{
+	for( int i = 0; i < 3; i++ ) {
+		add_item(new BC_MenuItem(value_to_text(i)));
+	}
+}
+
+const char* ComprMultiInput::value_to_text(int value)
+{
+	switch( value ) {
+	case ComprMultiConfig::TRIGGER: return "Trigger";
+	case ComprMultiConfig::MAX: return "Maximum";
+	case ComprMultiConfig::SUM: return "Total";
+	}
+
+	return "Trigger";
+}
+
+int ComprMultiInput::text_to_value(char *text)
+{
+	for( int i = 0; i < 3; i++ ) {
+		if( !strcmp(value_to_text(i), text) ) return i;
+	}
+
+	return ComprMultiConfig::TRIGGER;
+}
+
+
+ComprMultiClear::ComprMultiClear(ComprMultiEffect *plugin, int x, int y) 
+ : BC_GenericButton(x, y, _("Clear"))
+{
+	this->plugin = plugin;
+}
+
+int ComprMultiClear::handle_event()
+{
+	BandConfig *band_config = &plugin->config.bands[plugin->config.current_band];
+
+	band_config->levels.remove_all();
+//plugin->config.dump();
+	((ComprMultiWindow*)plugin->thread->window)->update();
+	plugin->send_configure_change();
+	return 1;
+}
+
+
+ComprMultiSmooth::ComprMultiSmooth(ComprMultiEffect *plugin, int x, int y) 
+ : BC_CheckBox(x, y, plugin->config.smoothing_only, _("Smooth only"))
+{
+	this->plugin = plugin;
+}
+
+int ComprMultiSmooth::handle_event()
+{
+	plugin->config.smoothing_only = get_value();
+	plugin->send_configure_change();
+	return 1;
+}
+
+
+ComprMultiSolo::ComprMultiSolo(ComprMultiEffect *plugin, int x, int y) 
+ : BC_CheckBox(x, y, plugin->config.bands[plugin->config.current_band].solo, _("Solo band"))
+{
+	this->plugin = plugin;
+}
+
+int ComprMultiSolo::handle_event()
+{
+	plugin->config.bands[plugin->config.current_band].solo = get_value();
+	for( int i = 0; i < TOTAL_BANDS; i++ ) {
+		if( i != plugin->config.current_band ) {
+			plugin->config.bands[i].solo = 0;
+		}
+	}
+	plugin->send_configure_change();
+	return 1;
+}
+
+
+ComprMultiBypass::ComprMultiBypass(ComprMultiEffect *plugin, int x, int y) 
+ : BC_CheckBox(x, y, plugin->config.bands[plugin->config.current_band].bypass, _("Bypass band"))
+{
+	this->plugin = plugin;
+}
+
+int ComprMultiBypass::handle_event()
+{
+	plugin->config.bands[plugin->config.current_band].bypass = get_value();
+	plugin->send_configure_change();
+	return 1;
+}
+
+
+ComprMultiBand::ComprMultiBand(ComprMultiWindow *window, 
+		ComprMultiEffect *plugin, int x, int y, int number,
+		char *text)
+ : BC_Radial(x, y, plugin->config.current_band == number, text)
+{
+	this->window = window;
+	this->plugin = plugin;
+	this->number = number;
+}
+
+int ComprMultiBand::handle_event()
+{
+	if( plugin->config.current_band != number ) {
+		plugin->config.current_band = number;
+		window->update();
+	}
+	return 1;
+}
+
+// dump envelope sum
+//	 printf("ComprMultiWindow::update_eqcanvas %d\n", __LINE__);
+//	 for( int i = 0; i < plugin->config.window_size / 2; i++ )
+//	 {
+//		 double sum = 0;
+//		 for( int band = 0; band < TOTAL_BANDS; band++ )
+//		 {
+//			 sum += plugin->engines[band]->envelope[i];
+//		 }
+//		 
+//		 printf("%f ", sum);
+//		 for( int band = 0; band < TOTAL_BANDS; band++ )
+//		 {
+//			 printf("%f ", plugin->engines[band]->envelope[i]);
+//		 }
+//		 printf("\n");
+//	 }
+
+
diff --git a/cinelerra-5.1/plugins/compressormulti/comprmultigui.h b/cinelerra-5.1/plugins/compressormulti/comprmultigui.h
new file mode 100644
index 00000000..b7722163
--- /dev/null
+++ b/cinelerra-5.1/plugins/compressormulti/comprmultigui.h
@@ -0,0 +1,220 @@
+/*
+ * CINELERRA
+ * 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
+ * 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
+ *
+ */
+
+
+
+#ifndef COMPRMULTIGUI_H
+#define COMPRMULTIGUI_H
+
+#include "bchash.inc"
+#include "comprmulti.h"
+#include "compressortools.h"
+#include "eqcanvas.inc"
+#include "guicast.h"
+
+class ComprMultiWindow;
+
+
+class ComprMultiCanvas : public CompressorCanvasBase
+{
+public:
+	ComprMultiCanvas(ComprMultiEffect *plugin,
+		ComprMultiWindow *window, int x, int y, int w, int h);
+	void update_window();
+};
+
+class ComprMultiBand : public BC_Radial
+{
+public:
+	ComprMultiBand(ComprMultiWindow *window,
+		ComprMultiEffect *plugin, int x, int y, int number, char *text);
+	int handle_event();
+
+	ComprMultiWindow *window;
+	ComprMultiEffect *plugin;
+// 0 - (TOTAL_BANDS-1)
+	int number;
+};
+
+
+class ComprMultiReaction : public BC_TumbleTextBox
+{
+public:
+	ComprMultiReaction(ComprMultiEffect *plugin,
+		ComprMultiWindow *window, int x, int y);
+	int handle_event();
+	ComprMultiEffect *plugin;
+};
+
+class ComprMultiX : public BC_TumbleTextBox
+{
+public:
+	ComprMultiX(ComprMultiEffect *plugin, ComprMultiWindow *window, int x, int y);
+	int handle_event();
+	ComprMultiEffect *plugin;
+};
+
+class ComprMultiY : public BC_TumbleTextBox
+{
+public:
+	ComprMultiY(ComprMultiEffect *plugin, ComprMultiWindow *window, int x, int y);
+	int handle_event();
+	ComprMultiEffect *plugin;
+};
+
+class ComprMultiTrigger : public BC_TumbleTextBox
+{
+public:
+	ComprMultiTrigger(ComprMultiEffect *plugin, ComprMultiWindow *window, int x, int y);
+	int handle_event();
+	ComprMultiEffect *plugin;
+};
+
+class ComprMultiDecay : public BC_TumbleTextBox
+{
+public:
+	ComprMultiDecay(ComprMultiEffect *plugin, ComprMultiWindow *window, int x, int y);
+	int handle_event();
+	ComprMultiEffect *plugin;
+};
+
+
+class ComprMultiClear : public BC_GenericButton
+{
+public:
+	ComprMultiClear(ComprMultiEffect *plugin, int x, int y);
+	int handle_event();
+	ComprMultiEffect *plugin;
+};
+
+class ComprMultiSmooth : public BC_CheckBox
+{
+public:
+	ComprMultiSmooth(ComprMultiEffect *plugin, int x, int y);
+	int handle_event();
+	ComprMultiEffect *plugin;
+};
+
+class ComprMultiSolo : public BC_CheckBox
+{
+public:
+	ComprMultiSolo(ComprMultiEffect *plugin, int x, int y);
+	int handle_event();
+	ComprMultiEffect *plugin;
+};
+
+class ComprMultiBypass : public BC_CheckBox
+{
+public:
+	ComprMultiBypass(ComprMultiEffect *plugin, int x, int y);
+	int handle_event();
+	ComprMultiEffect *plugin;
+};
+
+class ComprMultiInput : public BC_PopupMenu
+{
+public:
+	ComprMultiInput(ComprMultiEffect *plugin, int x, int y);
+	void create_objects();
+	int handle_event();
+	static const char* value_to_text(int value);
+	static int text_to_value(char *text);
+	ComprMultiEffect *plugin;
+};
+
+
+class ComprMultiFPot : public BC_FPot
+{
+public:
+	ComprMultiFPot(ComprMultiWindow *gui, ComprMultiEffect *plugin,
+			int x, int y, double *output, double min, double max);
+	int handle_event();
+	ComprMultiWindow *gui;
+	ComprMultiEffect *plugin;
+	double *output;
+};
+
+class ComprMultiQPot : public BC_QPot
+{
+public:
+	ComprMultiQPot(ComprMultiWindow *gui, ComprMultiEffect *plugin,
+		int x, int y, int *output);
+	int handle_event();
+	ComprMultiWindow *gui;
+	ComprMultiEffect *plugin;
+	int *output;
+};
+
+
+class ComprMultiSize : public BC_PopupMenu
+{
+public:
+	ComprMultiSize(ComprMultiWindow *gui,
+		ComprMultiEffect *plugin, int x, int y);
+	int handle_event();
+	void create_objects();		 // add initial items
+	void update(int size);
+	ComprMultiWindow *gui;
+	ComprMultiEffect *plugin;	
+};
+
+
+class ComprMultiWindow : public PluginClientWindow
+{
+public:
+	ComprMultiWindow(ComprMultiEffect *plugin);
+	~ComprMultiWindow();
+	void create_objects();
+	void update();
+// draw the dynamic range canvas
+	void update_canvas();
+// draw the bandpass canvas
+	void update_eqcanvas();
+	int resize_event(int w, int h);	
+
+	ComprMultiCanvas *canvas;
+	ComprMultiReaction *reaction;
+	ComprMultiClear *clear;
+	ComprMultiX *x_text;
+	ComprMultiY *y_text;
+	ComprMultiTrigger *trigger;
+	ComprMultiDecay *decay;
+	ComprMultiSmooth *smooth;
+	ComprMultiSolo *solo;
+	ComprMultiBypass *bypass;
+	ComprMultiInput *input;
+	BC_Meter *in;
+	BC_Meter *gain_change;
+	ComprMultiBand *band[TOTAL_BANDS];
+	CompressorGainFrame *gain_frame;
+	CompressorFreqFrame *freq_frame;
+
+	ComprMultiQPot *freq1;
+	ComprMultiQPot *freq2;
+	ComprMultiFPot *q;
+	ComprMultiSize *size;
+	EQCanvas *eqcanvas;
+
+	ComprMultiEffect *plugin;
+	BC_Hash *defaults;
+};
+
+#endif
+
diff --git a/cinelerra-5.1/plugins/denoisefft/denoisefft.C b/cinelerra-5.1/plugins/denoisefft/denoisefft.C
index 20b99005..5737d4ce 100644
--- a/cinelerra-5.1/plugins/denoisefft/denoisefft.C
+++ b/cinelerra-5.1/plugins/denoisefft/denoisefft.C
@@ -434,10 +434,7 @@ void DenoiseFFTEffect::collect_noise()
 	for(int i = 0; i < config.samples; i += WINDOW_SIZE)
 	{
 		collect_engine->process_buffer(collection_start,
-			WINDOW_SIZE,
-			0,
-			get_direction());
-
+			WINDOW_SIZE, (Samples*)0, get_direction());
 		collection_start += step * WINDOW_SIZE;
 		total_windows++;
 	}
diff --git a/cinelerra-5.1/plugins/graphic/graphic.C b/cinelerra-5.1/plugins/graphic/graphic.C
index 16c2f546..b7cf0722 100644
--- a/cinelerra-5.1/plugins/graphic/graphic.C
+++ b/cinelerra-5.1/plugins/graphic/graphic.C
@@ -320,29 +320,15 @@ void GraphicCanvas::process(int buttonpress, int motion, int draw)
 	if(draw)
 	{
 		clear_box(0, 0, get_w(), get_h());
-
-
 		int niquist = plugin->PluginAClient::project_sample_rate / 2;
-		int total_frames = plugin->get_gui_update_frames();
-		GraphicGUIFrame *frame = (GraphicGUIFrame*)plugin->get_gui_frame();
-
-		if(frame)
-		{
-			delete plugin->last_frame;
-			plugin->last_frame = frame;
-		}
-		else
-		{
-			frame = plugin->last_frame;
-		}
 
-// Draw most recent frame
-		if(frame)
-		{
+// delete frames up to current tracking position
+		double tracking_position = plugin->get_tracking_position();
+		GraphicGUIFrame *frame = (GraphicGUIFrame *)
+			plugin->get_gui_frame(tracking_position, 1);
+		if( frame ) {
+			int y1 = 0, y2 = 0;
 			set_color(MEGREY);
-			int y1 = 0;
-			int y2 = 0;
-
 
 			for(int i = 0; i < get_w(); i++)
 			{
@@ -366,23 +352,7 @@ void GraphicCanvas::process(int buttonpress, int motion, int draw)
 					y1 = y2;
 				}
 			}
-//printf( "\n");
-
-			total_frames--;
-		}
-
-
-
-
-
-
-// Delete remaining frames
-		while(total_frames > 0)
-		{
-			PluginClientFrame *frame = plugin->get_gui_frame();
-
-			if(frame) delete frame;
-			total_frames--;
+			delete frame;
 		}
 	}
 
@@ -1131,18 +1101,15 @@ void GraphicEQ::update_gui()
 {
 	if( !thread ) return;
 	GraphicGUI *window = (GraphicGUI *)thread->window;
+	window->lock_window("GraphicEQ::update_gui");
 //lock here for points, needed by window cursor_motion callback
 //  deleted in load_configuration by read_data
-	window->lock_window("GraphicEQ::update_gui");
 	if( load_configuration() &&
 	    window->canvas->state != GraphicCanvas::DRAG_POINT ) {
 		window->update_canvas();
 		window->update_textboxes();
 	}
-	else {
-		int total_frames = get_gui_update_frames();
-//printf("ParametricEQ::update_gui %d %d\n", __LINE__, total_frames);
-		if( total_frames )
+	else if( pending_gui_frames() ) {
 			window->update_canvas();
 	}
 	window->unlock_window();
@@ -1299,15 +1266,14 @@ void GraphicEQ::calculate_envelope(ArrayList<GraphicPoint*> *points,
 }
 
 
-
-
 GraphicGUIFrame::GraphicGUIFrame(int window_size, int sample_rate)
- : PluginClientFrame(window_size / 2, window_size / 2, sample_rate)
+ : PluginClientFrame()
 {
-	data = new double[window_size / 2];
+	this->window_size = window_size;
+	data_size = window_size / 2;
+	data = new double[data_size];
 	freq_max = 0;
 	time_max = 0;
-	this->window_size = window_size;
 }
 
 GraphicGUIFrame::~GraphicGUIFrame()
@@ -1316,10 +1282,6 @@ GraphicGUIFrame::~GraphicGUIFrame()
 }
 
 
-
-
-
-
 GraphicFFT::GraphicFFT(GraphicEQ *plugin)
  : CrossfadeFFT()
 {
diff --git a/cinelerra-5.1/plugins/graphic/graphic.h b/cinelerra-5.1/plugins/graphic/graphic.h
index 1517dfe4..ec55c476 100644
--- a/cinelerra-5.1/plugins/graphic/graphic.h
+++ b/cinelerra-5.1/plugins/graphic/graphic.h
@@ -211,6 +211,7 @@ public:
 	GraphicGUIFrame(int window_size, int sample_rate);
 	virtual ~GraphicGUIFrame();
 	double *data;
+	int data_size;
 // Maximum of window in frequency domain
 	double freq_max;
 // Maximum of window in time domain
diff --git a/cinelerra-5.1/plugins/parametric/parametric.C b/cinelerra-5.1/plugins/parametric/parametric.C
index ceb7b1d8..59bd8e5e 100644
--- a/cinelerra-5.1/plugins/parametric/parametric.C
+++ b/cinelerra-5.1/plugins/parametric/parametric.C
@@ -569,71 +569,33 @@ void ParametricWindow::update_canvas()
 
 	canvas->clear_box(0, 0, canvas->get_w(), canvas->get_h());
 
-
-
 // Draw spectrogram
-	int total_frames = plugin->get_gui_update_frames();
-	ParametricGUIFrame *frame = (ParametricGUIFrame*)plugin->get_gui_frame();
-
-	if(frame)
-	{
-		delete plugin->last_frame;
-		plugin->last_frame = frame;
-	}
-	else
-	{
-		frame = plugin->last_frame;
-	}
-
+	double tracking_position = plugin->get_tracking_position();
+	ParametricGUIFrame *frame = (ParametricGUIFrame *)
+		plugin->get_gui_frame(tracking_position, 1);
 // Draw most recent frame
-	if(frame)
-	{
+	if( frame ) {
+		int y1 = 0, y2 = 0;
 		canvas->set_color(MEGREY);
-		int y1 = 0;
-		int y2 = 0;
-		for(int i = 0; i < canvas->get_w(); i++)
-		{
+
+		for(int i = 0; i < canvas->get_w(); i++) {
 			int freq = Freq::tofreq(i * TOTALFREQS / canvas->get_w());
 			int index = (int64_t)freq * (int64_t)frame->window_size / 2 / niquist;
-			if(index < frame->window_size / 2)
-			{
+			if(index < frame->window_size / 2) {
 				double magnitude = frame->data[index] /
-					frame->freq_max *
-					frame->time_max;
+					frame->freq_max * frame->time_max;
 				y2 = (int)(canvas->get_h() -
 					(DB::todb(magnitude) - INFINITYGAIN) *
-					canvas->get_h() /
-					-INFINITYGAIN);
+					canvas->get_h() / -INFINITYGAIN);
 				CLAMP(y2, 0, canvas->get_h() - 1);
 				if(i > 0)
-				{
 					canvas->draw_line(i - 1, y1, i, y2);
-				}
 				y1 = y2;
 			}
 		}
-
-		total_frames--;
+		delete frame;
 	}
 
-
-
-
-
-
-// Delete remaining frames
-	while(total_frames > 0)
-	{
-		PluginClientFrame *frame = plugin->get_gui_frame();
-
-		if(frame) delete frame;
-		total_frames--;
-	}
-
-
-
-
-
 // 	canvas->set_color(GREEN);
 // 	canvas->draw_line(0, wetness, canvas->get_w(), wetness);
 // 	canvas->draw_line(0,
@@ -693,16 +655,12 @@ void ParametricWindow::update_canvas()
 }
 
 
-
-
-
-
-
 ParametricGUIFrame::ParametricGUIFrame(int window_size, int sample_rate)
- : PluginClientFrame(window_size / 2, window_size / 2, sample_rate)
+ : PluginClientFrame()
 {
 	this->window_size = window_size;
-	data = new double[window_size / 2];
+	data_size = window_size / 2;
+	data = new double[data_size];
 	freq_max = 0;
 	time_max = 0;
 }
@@ -713,14 +671,6 @@ ParametricGUIFrame::~ParametricGUIFrame()
 }
 
 
-
-
-
-
-
-
-
-
 ParametricFFT::ParametricFFT(ParametricEQ *plugin)
  : CrossfadeFFT()
 {
@@ -1029,16 +979,6 @@ int ParametricEQ::process_buffer(int64_t size,
 	return 0;
 }
 
-
-
-
-
-
-
-
-
-
-
 void ParametricEQ::reset()
 {
 	need_reconfigure = 1;
@@ -1048,28 +988,16 @@ void ParametricEQ::reset()
 
 void ParametricEQ::update_gui()
 {
-	if(thread)
-	{
-		if(load_configuration())
-		{
-			((ParametricWindow*)thread->window)->lock_window("ParametricEQ::update_gui");
-			calculate_envelope();
-			((ParametricWindow*)thread->window)->update_gui();
-			((ParametricWindow*)thread->window)->unlock_window();
-		}
-		else
-		{
-			int total_frames = get_gui_update_frames();
-//printf("ParametricEQ::update_gui %d %d\n", __LINE__, total_frames);
-			if(total_frames)
-			{
-				((ParametricWindow*)thread->window)->lock_window("ParametricEQ::update_gui");
-				((ParametricWindow*)thread->window)->update_canvas();
-				((ParametricWindow*)thread->window)->unlock_window();
-			}
-		}
+	if( !thread ) return;
+	ParametricWindow *window = (ParametricWindow*)thread->window;
+	window->lock_window("ParametricEQ::update_gui");
+	if( load_configuration() ) {
+		calculate_envelope();
+		window->update_gui();
 	}
+	else if(pending_gui_frames()) {
+		window->update_canvas();
+	}
+	window->unlock_window();
 }
 
-
-
diff --git a/cinelerra-5.1/plugins/parametric/parametric.h b/cinelerra-5.1/plugins/parametric/parametric.h
index 9626fb67..164028eb 100644
--- a/cinelerra-5.1/plugins/parametric/parametric.h
+++ b/cinelerra-5.1/plugins/parametric/parametric.h
@@ -232,6 +232,7 @@ public:
 	ParametricGUIFrame(int window_size, int sample_rate);
 	virtual ~ParametricGUIFrame();
 	double *data;
+	int data_size;
 // Maximum of window in frequency domain
 	double freq_max;
 // Maximum of window in time domain
diff --git a/cinelerra-5.1/plugins/reverb/reverb.C b/cinelerra-5.1/plugins/reverb/reverb.C
index b5bddf2a..4248a3cd 100644
--- a/cinelerra-5.1/plugins/reverb/reverb.C
+++ b/cinelerra-5.1/plugins/reverb/reverb.C
@@ -1,7 +1,6 @@
-
 /*
  * CINELERRA
- * Copyright (C) 2017 Adam Williams <broadcast at earthling dot net>
+ * Copyright (C) 2017-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
@@ -16,7 +15,6 @@
  * 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 "clip.h"
@@ -29,6 +27,7 @@
 #include "reverb.h"
 #include "reverbwindow.h"
 #include "samples.h"
+#include "transportque.inc"
 #include "units.h"
 
 #include "vframe.h"
@@ -39,63 +38,48 @@
 #include <unistd.h>
 
 
-
 PluginClient* new_plugin(PluginServer *server)
 {
 	return new Reverb(server);
 }
 
-
-
 Reverb::Reverb(PluginServer *server)
  : PluginAClient(server)
 {
 	srand(time(0));
-	redo_buffers = 1;       // set to redo buffers before the first render
-	dsp_in_length = 0;
 	ref_channels = 0;
 	ref_offsets = 0;
 	ref_levels = 0;
-	ref_lowpass = 0;
 	dsp_in = 0;
-	lowpass_in1 = 0;
-	lowpass_in2 = 0;
-	initialized = 0;
-
+	dsp_in_length = 0;
+	dsp_in_allocated = 0;
+	need_reconfigure = 1;
+	envelope = 0;
+	last_position = 0;
+	start_pos = 0;
+	dir = 0;
+	fft = 0;
 }
 
 Reverb::~Reverb()
 {
-
-
-	if(initialized)
-	{
-		for(int i = 0; i < total_in_buffers; i++)
-		{
+	if( fft ) {
+		for( int i = 0; i < total_in_buffers; i++ ) {
 			delete [] dsp_in[i];
 			delete [] ref_channels[i];
 			delete [] ref_offsets[i];
 			delete [] ref_levels[i];
-			delete [] ref_lowpass[i];
-			delete [] lowpass_in1[i];
-			delete [] lowpass_in2[i];
+			delete fft[i];
 		}
 
+		delete [] fft;
 		delete [] dsp_in;
 		delete [] ref_channels;
 		delete [] ref_offsets;
 		delete [] ref_levels;
-		delete [] ref_lowpass;
-		delete [] lowpass_in1;
-		delete [] lowpass_in2;
-
-		for(int i = 0; i < (smp + 1); i++)
-		{
-			delete engine[i];
-		}
-		delete [] engine;
-		initialized = 0;
+		delete engine;
 	}
+	delete [] envelope;
 }
 
 const char* Reverb::plugin_title() { return N_("Reverb"); }
@@ -103,184 +87,220 @@ int Reverb::is_realtime() { return 1; }
 int Reverb::is_multichannel() { return 1; }
 int Reverb::is_synthesis() { return 1; }
 
-int Reverb::process_realtime(int64_t size,
-	Samples **input_ptr,
-	Samples **output_ptr)
-{
-	int64_t new_dsp_length, i, j;
-	main_in = new double*[total_in_buffers];
-	main_out = new double*[total_in_buffers];
 
-	for(i = 0; i < total_in_buffers; i++)
-	{
-		main_in[i] = input_ptr[i]->get_data();
-		main_out[i] = output_ptr[i]->get_data();
+void Reverb::reset()
+{
+	dsp_in_length = 0;
+	if( fft ) {
+		for( int i = 0; i < PluginClient::total_in_buffers; i++ )
+			if( fft[i] ) fft[i]->delete_fft();
 	}
-
-//printf("Reverb::process_realtime 1\n");
-	redo_buffers |= load_configuration();
-
-//printf("Reverb::process_realtime 1\n");
-	if(!config.ref_total)
-	{
-		delete [] main_in;
-		delete [] main_out;
-		return 0;
+	if( dsp_in ) {
+		for( int i = 0; i < PluginClient::total_in_buffers; i++ ) {
+			if( !dsp_in[i] ) continue;
+			bzero(dsp_in[i], sizeof(double) * dsp_in_allocated);
+		}
 	}
+	need_reconfigure = 1;
+}
+
+void Reverb::render_stop()
+{
+	reset();
+}
 
 
-	if(!initialized)
-	{
-		dsp_in = new double*[total_in_buffers];
-		ref_channels = new int64_t*[total_in_buffers];
-		ref_offsets = new int64_t*[total_in_buffers];
-		ref_levels = new double*[total_in_buffers];
-		ref_lowpass = new int64_t*[total_in_buffers];
-		lowpass_in1 = new double*[total_in_buffers];
-		lowpass_in2 = new double*[total_in_buffers];
+int Reverb::process_buffer(int64_t size, Samples **buffer,
+		int64_t start_position, int sample_rate)
+{
+	start_pos = (double)start_position / sample_rate;
+	dir = get_direction() == PLAY_REVERSE ? -1 : 1;
+	need_reconfigure |= load_configuration();
 
+	if( need_reconfigure ) {
+		need_reconfigure = 0;
 
+		calculate_envelope();
 
-		for(i = 0; i < total_in_buffers; i++)
-		{
-			dsp_in[i] = new double[1];
-			ref_channels[i] = new int64_t[1];
-			ref_offsets[i] = new int64_t[1];
-			ref_levels[i] = new double[1];
-			ref_lowpass[i] = new int64_t[1];
-			lowpass_in1[i] = new double[1];
-			lowpass_in2[i] = new double[1];
+		if( fft && fft[0]->window_size != config.window_size ) {
+			for( int i = 0; i < PluginClient::total_in_buffers; i++ )
+				delete fft[i];
+			delete [] fft;  fft = 0;
 		}
 
-		engine = new ReverbEngine*[(smp + 1)];
-		for(i = 0; i < (smp + 1); i++)
-		{
-			engine[i] = new ReverbEngine(this);
-//printf("Reverb::start_realtime %d\n", Thread::calculate_realtime());
-// Realtime priority moved to sound driver
-//		engine[i]->set_realtime(realtime_priority);
-			engine[i]->start();
+		if( !fft ) {
+			fft = new ReverbFFT*[PluginClient::total_in_buffers];
+			for( int i = 0; i < PluginClient::total_in_buffers; i++ ) {
+				fft[i] = new ReverbFFT(this, i);
+				fft[i]->initialize(config.window_size);
+			}
 		}
-		initialized = 1;
-		redo_buffers = 1;
-	}
 
-	new_dsp_length = size +
-		((int64_t)config.delay_init + config.ref_length) * project_sample_rate / 1000 + 1;
+// allocate the stuff
+		if( !dsp_in ) {
+			dsp_in = new double*[PluginClient::total_in_buffers];
+			ref_channels = new int*[PluginClient::total_in_buffers];
+			ref_offsets = new int*[PluginClient::total_in_buffers];
+			ref_levels = new double*[PluginClient::total_in_buffers];
+			for( int i = 0; i < PluginClient::total_in_buffers; i++ ) {
+				dsp_in[i] = 0;
+				ref_channels[i] = 0;
+				ref_offsets[i] = 0;
+				ref_levels[i] = 0;
+			}
+			engine = new ReverbEngine(this);
+		}
 
-	if(redo_buffers || new_dsp_length != dsp_in_length)
-	{
-		for(i = 0; i < total_in_buffers; i++)
-		{
-			double *old_dsp = dsp_in[i];
-			double *new_dsp = new double[new_dsp_length];
-			for(j = 0; j < dsp_in_length && j < new_dsp_length; j++)
-				new_dsp[j] = old_dsp[j];
 
+		for( int i = 0; i < PluginClient::total_in_buffers; i++ ) {
+			delete [] ref_channels[i];  ref_channels[i] = new int[config.ref_total];
+			delete [] ref_offsets[i];   ref_offsets[i] = new int[config.ref_total];
+			delete [] ref_levels[i];    ref_levels[i] = new double[config.ref_total];
 
-			for( ; j < new_dsp_length; j++) new_dsp[j] = 0;
-			delete [] old_dsp;
-			dsp_in[i] = new_dsp;
-		}
+// 1st reflection is fixed by the user
+			ref_channels[i][0] = i;
+			ref_offsets[i][0] = config.delay_init * project_sample_rate / 1000;
+			ref_levels[i][0] = db.fromdb(config.ref_level1);
 
-		dsp_in_length = new_dsp_length;
-		redo_buffers = 1;
-	}
-//printf("Reverb::process_realtime 1\n");
-
-	if(redo_buffers)
-	{
-		for(i = 0; i < total_in_buffers; i++)
-		{
-			delete [] ref_channels[i];
-			delete [] ref_offsets[i];
-			delete [] ref_lowpass[i];
-			delete [] ref_levels[i];
-			delete [] lowpass_in1[i];
-			delete [] lowpass_in2[i];
-
-			ref_channels[i] = new int64_t[config.ref_total + 1];
-			ref_offsets[i] = new int64_t[config.ref_total + 1];
-			ref_lowpass[i] = new int64_t[config.ref_total + 1];
-			ref_levels[i] = new double[config.ref_total + 1];
-			lowpass_in1[i] = new double[config.ref_total + 1];
-			lowpass_in2[i] = new double[config.ref_total + 1];
-
-// set channels
-			ref_channels[i][0] = i;         // primary noise
-			ref_channels[i][1] = i;         // first reflection
-// set offsets
-			ref_offsets[i][0] = 0;
-			ref_offsets[i][1] = config.delay_init * project_sample_rate / 1000;
-// set levels
-			ref_levels[i][0] = db.fromdb(config.level_init);
-			ref_levels[i][1] = db.fromdb(config.ref_level1);
-// set lowpass
-			ref_lowpass[i][0] = -1;     // ignore first noise
-			ref_lowpass[i][1] = config.lowpass1;
-			lowpass_in1[i][0] = 0;
-			lowpass_in2[i][0] = 0;
-			lowpass_in1[i][1] = 0;
-			lowpass_in2[i][1] = 0;
-
-			int64_t ref_division = config.ref_length * project_sample_rate / 1000 / (config.ref_total + 1);
-			for(j = 2; j < config.ref_total + 1; j++)
-			{
+			int64_t ref_division = config.ref_length *
+				project_sample_rate / 1000 / (config.ref_total + 1);
+			for( int j = 1; j < config.ref_total; j++ ) {
 // set random channels for remaining reflections
 				ref_channels[i][j] = rand() % total_in_buffers;
 
 // set random offsets after first reflection
-				ref_offsets[i][j] = ref_offsets[i][1];
-				if( ref_division > 0 )
-					ref_offsets[i][j] += ref_division * j - (rand() % ref_division) / 2;
-
+				ref_offsets[i][j] = ref_offsets[i][0];
+				ref_offsets[i][j] += ref_division * j - (rand() % ref_division) / 2;
 // set changing levels
-				ref_levels[i][j] = db.fromdb(config.ref_level1 + (config.ref_level2 - config.ref_level1) / (config.ref_total - 1) * (j - 2));
-				//ref_levels[i][j] /= 100;
-
-// set changing lowpass as linear
-				ref_lowpass[i][j] = (int64_t)(config.lowpass1 + (double)(config.lowpass2 - config.lowpass1) / (config.ref_total - 1) * (j - 2));
-				lowpass_in1[i][j] = 0;
-				lowpass_in2[i][j] = 0;
+				double level_db = config.ref_level1 +
+					(config.ref_level2 - config.ref_level1) *
+					(j - 1) / (config.ref_total - 1);
+				ref_levels[i][j] = DB::fromdb(level_db);
 			}
 		}
+	}
 
-		redo_buffers = 0;
+// guess DSP allocation from the reflection time & requested samples
+	int new_dsp_allocated = size +
+		((int64_t)config.delay_init + config.ref_length) *
+		project_sample_rate / 1000 + 1;
+	reallocate_dsp(new_dsp_allocated);
+
+// Always read in the new samples & process the bandpass, even if there is no
+// bandpass.  This way the user can tweek the bandpass without causing glitches.
+	for( int i = 0; i < PluginClient::total_in_buffers; i++ ) {
+		new_dsp_length = dsp_in_length;
+		new_spectrogram_frames = 0;
+		fft[i]->process_buffer(start_position, size,
+			buffer[i],   // temporary storage for the bandpassed output
+			get_direction());
 	}
-//printf("Reverb::process_realtime 1\n");
 
-	for(i = 0; i < total_in_buffers; )
-	{
-		for(j = 0; j < (smp + 1) && (i + j) < total_in_buffers; j++)
-		{
-			engine[j]->process_overlays(i + j, size);
-		}
+// update the length with what the FFT reads appended
+	dsp_in_length = new_dsp_length;
 
-		for(j = 0; j < (smp + 1) && i < total_in_buffers; j++, i++)
-		{
-			engine[j]->wait_process_overlays();
-		}
+// now paint the reflections
+	engine->process_packages();
+
+// copy the DSP buffer to the output
+	for( int i = 0; i < PluginClient::total_in_buffers; i++ ) {
+		memcpy(buffer[i]->get_data(), dsp_in[i], size * sizeof(double));
 	}
-//printf("Reverb::process_realtime 2 %d %d\n", total_in_buffers, size);
 
-	for(i = 0; i < total_in_buffers; i++)
-	{
-		double *current_out = main_out[i];
-		double *current_in = dsp_in[i];
+// shift the DSP buffer forward
+	int remain = dsp_in_allocated - size;
+	for( int i = 0; i < PluginClient::total_in_buffers; i++ ) {
+		memcpy(dsp_in[i], dsp_in[i] + size, remain * sizeof(double));
+		bzero(dsp_in[i] + remain, size * sizeof(double));
+	}
+
+	dsp_in_length -= size;
+	last_position = start_position + dir * size;
+	return 0;
+}
 
-		for(j = 0; j < size; j++) current_out[j] = current_in[j];
 
-		int64_t k;
-		for(k = 0; j < dsp_in_length; j++, k++) current_in[k] = current_in[j];
+void Reverb::reallocate_dsp(int new_dsp_allocated)
+{
+	if( new_dsp_allocated > dsp_in_allocated ) {
+// copy samples already read into the new buffers
+		for( int i = 0; i < PluginClient::total_in_buffers; i++ ) {
+			double *old_dsp = dsp_in[i];
+			double *new_dsp = new double[new_dsp_allocated];
 
-		for(; k < dsp_in_length; k++) current_in[k] = 0;
+			if( old_dsp ) {
+				memcpy(new_dsp, old_dsp, sizeof(double) * dsp_in_length);
+				delete [] old_dsp;
+			}
+			bzero(new_dsp + dsp_in_allocated,
+				sizeof(double) * (new_dsp_allocated - dsp_in_allocated));
+			dsp_in[i] = new_dsp;
+		}
+		dsp_in_allocated = new_dsp_allocated;
 	}
-//printf("Reverb::process_realtime 2 %d %d\n", total_in_buffers, size);
 
+}
 
-	delete [] main_in;
-	delete [] main_out;
-	return 0;
+
+double Reverb::gauss(double sigma, double center, double x)
+{
+	if( EQUIV(sigma, 0) ) sigma = 0.01;
+
+	double result = 1.0 / sqrt(2 * M_PI * sigma * sigma) *
+		exp(-(x - center) * (x - center) / (2 * sigma * sigma));
+	return result;
+}
+
+void Reverb::calculate_envelope()
+{
+// assume the window size changed
+	delete [] envelope;
+	int window_size2 = config.window_size/2;
+	envelope = new double[window_size2];
+
+	int max_freq = Freq::tofreq_f(TOTALFREQS-1);
+	int nyquist = PluginAClient::project_sample_rate/2;
+	int low = config.low;
+	int high = config.high;
+
+// limit the frequencies
+	if( high >= max_freq ) high = nyquist;
+	if( low > high ) low = high;
+
+// frequency slots of the edge
+	double edge = (1.0 - config.q) * TOTALFREQS/2;
+	double low_slot = Freq::fromfreq_f(low);
+	double high_slot = Freq::fromfreq_f(high);
+	for( int i = 0; i < window_size2; i++ ) {
+		double freq = i * nyquist / window_size2;
+		double slot = Freq::fromfreq_f(freq);
+
+// printf("Reverb::calculate_envelope %d i=%d freq=%f slot=%f slot1=%f\n",
+// __LINE__, i, freq, slot, low_slot - edge);
+		if( slot < low_slot - edge ) {
+			envelope[i] = 0.0;
+		}
+		else if( slot < low_slot ) {
+#ifndef LOG_CROSSOVER
+			envelope[i] = 1.0 - (low_slot - slot) / edge;
+#else
+			envelope[i] = DB::fromdb((low_slot - slot) * INFINITYGAIN / edge);
+#endif
+		}
+		else if( slot < high_slot ) {
+			envelope[i] = 1.0;
+		}
+		else if( slot < high_slot + edge ) {
+#ifndef LOG_CROSSOVER
+			envelope[i] = 1.0 - (slot - high_slot) / edge;
+#else
+			envelope[i] = DB::fromdb((slot - high_slot) * INFINITYGAIN / edge);
+#endif
+		}
+		else {
+			envelope[i] = 0.0;
+		}
+	}
 }
 
 
@@ -292,54 +312,41 @@ LOAD_CONFIGURATION_MACRO(Reverb, ReverbConfig)
 
 void Reverb::save_data(KeyFrame *keyframe)
 {
-//printf("Reverb::save_data 1\n");
 	FileXML output;
-//printf("Reverb::save_data 1\n");
-
-// cause xml file to store data directly in text
 	output.set_shared_output(keyframe->xbuf);
-//printf("Reverb::save_data 1\n");
-
 	output.tag.set_title("REVERB");
 	output.tag.set_property("LEVELINIT", config.level_init);
 	output.tag.set_property("DELAY_INIT", config.delay_init);
 	output.tag.set_property("REF_LEVEL1", config.ref_level1);
 	output.tag.set_property("REF_LEVEL2", config.ref_level2);
 	output.tag.set_property("REF_TOTAL", config.ref_total);
-//printf("Reverb::save_data 1\n");
 	output.tag.set_property("REF_LENGTH", config.ref_length);
-	output.tag.set_property("LOWPASS1", config.lowpass1);
-	output.tag.set_property("LOWPASS2", config.lowpass2);
-//printf("Reverb::save_data config.ref_level2 %f\n", config.ref_level2);
-	output.append_tag();
-	output.tag.set_title("/REVERB");
+	output.tag.set_property("HIGH", config.high);
+	output.tag.set_property("LOW", config.low);
+	output.tag.set_property("Q", config.q);
+	output.tag.set_property("WINDOW_SIZE", config.window_size);
 	output.append_tag();
 	output.append_newline();
 	output.terminate_string();
-//printf("Reverb::save_data 2\n");
 }
 
 void Reverb::read_data(KeyFrame *keyframe)
 {
 	FileXML input;
-// cause xml file to read directly from text
 	input.set_shared_input(keyframe->xbuf);
 	int result = 0;
-
-	result = input.read_tag();
-
-	if(!result)
-	{
-		if(input.tag.title_is("REVERB"))
-		{
+	while( !(result = input.read_tag()) ) {
+		if( input.tag.title_is("REVERB") ) {
 			config.level_init = input.tag.get_property("LEVELINIT", config.level_init);
 			config.delay_init = input.tag.get_property("DELAY_INIT", config.delay_init);
 			config.ref_level1 = input.tag.get_property("REF_LEVEL1", config.ref_level1);
 			config.ref_level2 = input.tag.get_property("REF_LEVEL2", config.ref_level2);
 			config.ref_total = input.tag.get_property("REF_TOTAL", config.ref_total);
 			config.ref_length = input.tag.get_property("REF_LENGTH", config.ref_length);
-			config.lowpass1 = input.tag.get_property("LOWPASS1", config.lowpass1);
-			config.lowpass2 = input.tag.get_property("LOWPASS2", config.lowpass2);
+			config.high = input.tag.get_property("HIGH", config.high);
+			config.low = input.tag.get_property("LOW", config.low);
+			config.q = input.tag.get_property("Q", config.q);
+			config.window_size = input.tag.get_property("WINDOW_SIZE", config.window_size);
 		}
 	}
 
@@ -348,186 +355,182 @@ void Reverb::read_data(KeyFrame *keyframe)
 
 void Reverb::update_gui()
 {
-	if(thread)
-	{
-		if(load_configuration())
-		{
-//printf("Reverb::update_gui %d %d\n", __LINE__, config.ref_length);
-			thread->window->lock_window("Reverb::update_gui");
-			((ReverbWindow*)thread->window)->level_init->update(config.level_init);
-			((ReverbWindow*)thread->window)->delay_init->update(config.delay_init);
-			((ReverbWindow*)thread->window)->ref_level1->update(config.ref_level1);
-			((ReverbWindow*)thread->window)->ref_level2->update(config.ref_level2);
-			((ReverbWindow*)thread->window)->ref_total->update(config.ref_total);
-			((ReverbWindow*)thread->window)->ref_length->update(config.ref_length);
-			((ReverbWindow*)thread->window)->lowpass1->update(config.lowpass1);
-			((ReverbWindow*)thread->window)->lowpass2->update(config.lowpass2);
-			thread->window->unlock_window();
-		}
-	}
+	if( !thread ) return;
+	ReverbWindow *window = (ReverbWindow *)thread->window;
+	if( !window ) return;
+	int reconfigured = load_configuration();
+	int total_frames = pending_gui_frames();
+	if( !reconfigured && !total_frames ) return;
+	window->lock_window("Reverb::update_gui 1");
+	if( reconfigured )
+		window->update();
+	if( total_frames )
+		window->update_canvas();
+	window->unlock_window();
 }
 
+ReverbClientFrame::ReverbClientFrame(int size)
+ : CompressorFreqFrame()
+{
+	data = new double[size];
+	bzero(data, sizeof(double) * size);
+	data_size = size;
+}
 
+ReverbClientFrame::~ReverbClientFrame()
+{
+	delete [] data;  data = 0;
+}
 
-
-// int Reverb::load_from_file(char *path)
-// {
-// 	FILE *in;
-// 	int result = 0;
-// 	int length;
-// 	char string[1024];
-// 	
-// 	if(in = fopen(path, "rb"))
-// 	{
-// 		fseek(in, 0, SEEK_END);
-// 		length = ftell(in);
-// 		fseek(in, 0, SEEK_SET);
-// 		int temp = fread(string, length, 1, in);
-// 		fclose(in);
-// //		read_data(string);
-// 	}
-// 	else
-// 	{
-// 		perror("fopen:");
-// // failed
-// 		ErrorBox errorbox("");
-// 		char string[1024];
-// 		sprintf(string, _("Couldn't open %s."), path);
-// 		errorbox.create_objects(string);
-// 		errorbox.run_window();
-// 		result = 1;
-// 	}
-// 	
-// 	return result;
-// }
-// 
-// int Reverb::save_to_file(char *path)
-// {
-// 	FILE *out;
-// 	int result = 0;
-// 	char string[1024];
-// 	
-// 	{
-// // 		ConfirmSave confirm;
-// // 		result = confirm.test_file("", path);
-// 	}
-// 	
-// 	if(!result)
-// 	{
-// 		if(out = fopen(path, "wb"))
-// 		{
-// //			save_data(string);
-// 			fwrite(string, strlen(string), 1, out);
-// 			fclose(out);
-// 		}
-// 		else
-// 		{
-// 			result = 1;
-// // failed
-// 			ErrorBox errorbox("");
-// 			char string[1024];
-// 			sprintf(string, _("Couldn't save %s."), path);
-// 			errorbox.create_objects(string);
-// 			errorbox.run_window();
-// 			result = 1;
-// 		}
-// 	}
-// 	
-// 	return result;
-// }
-
-ReverbEngine::ReverbEngine(Reverb *plugin)
- : Thread(1, 0, 0)
+ReverbFFT::ReverbFFT(Reverb *plugin, int channel)
 {
 	this->plugin = plugin;
-	completed = 0;
-	input_lock.lock();
-	output_lock.lock();
+	this->channel = channel;
 }
 
-ReverbEngine::~ReverbEngine()
+ReverbFFT::~ReverbFFT()
 {
-	completed = 1;
-	input_lock.unlock();
-	join();
 }
 
-int ReverbEngine::process_overlays(int output_buffer, int64_t size)
+int ReverbFFT::signal_process()
 {
-	this->output_buffer = output_buffer;
-	this->size = size;
-	input_lock.unlock();
+// Create new spectrogram for updating the GUI
+	frame = new ReverbClientFrame(window_size/2);
+	frame->nyquist = plugin->PluginAClient::project_sample_rate/2;
+	frame->position = plugin->start_pos + plugin->dir *
+		(double)plugin->new_spectrogram_frames * frame->data_size /
+		plugin->get_samplerate();
+	plugin->add_gui_frame(frame);
+
+	for( int i=0; i<frame->data_size; i++ ) {
+		double env = plugin->envelope[i];
+		double fr = freq_real[i], fi = freq_imag[i];
+// scale complex signal by envelope
+		freq_real[i] = fr * env;
+		freq_imag[i] = fi * env;
+		double mag = sqrt(fr*fr + fi*fi);
+		double out = mag * env;
+// update the spectrogram with the output
+		if( frame->data[i] < out )
+			frame->data[i] = out;
+// get the maximum output in the frequency domain
+		if( frame->freq_max < out )
+			frame->freq_max = out;
+	}
+
+	symmetry(window_size, freq_real, freq_imag);
 	return 0;
 }
 
-int ReverbEngine::wait_process_overlays()
+int ReverbFFT::post_process()
 {
-	output_lock.lock();
+// get the maximum output in the time domain
+	double time_max = 0;
+	for( int i=0; i<window_size; ++i ) {
+		if( fabs(output_real[i]) > time_max )
+			time_max = fabs(output_real[i]);
+	}
+	if( time_max > frame->time_max )
+		frame->time_max = time_max;
+
+	++plugin->new_spectrogram_frames;
 	return 0;
 }
 
-int ReverbEngine::process_overlay(double *in, double *out, double &out1, double &out2, double level, int64_t lowpass, int64_t samplerate, int64_t size)
+
+int ReverbFFT::read_samples(int64_t output_sample, int samples, Samples *buffer)
 {
-// Modern niquist frequency is 44khz but pot limit is 20khz so can't use
-// niquist
-	if(lowpass == -1 || lowpass >= 20000)
-	{
-// no lowpass filter
-		for(int i = 0; i < size; i++) out[i] += in[i] * level;
+	int result = plugin->read_samples(buffer,
+		channel, plugin->get_samplerate(), output_sample, samples);
+
+// append original samples to the DSP buffer as the initial reflection
+	int new_dsp_allocation = plugin->new_dsp_length + samples;
+	plugin->reallocate_dsp(new_dsp_allocation);
+	double *dst = plugin->dsp_in[channel] + plugin->new_dsp_length;
+	double *src = buffer->get_data();
+	double level = DB::fromdb(plugin->config.level_init);
+	if( plugin->config.level_init <= INFINITYGAIN ) {
+		level = 0;
 	}
-	else
-	{
-		double coef = 0.25 * 2.0 * M_PI * (double)lowpass / (double)plugin->project_sample_rate;
-		double a = coef * 0.25;
-		double b = coef * 0.50;
-
-		for(int i = 0; i < size; i++)
-		{
-			out2 += a * (3 * out1 + in[i] - out2);
-			out2 += b * (out1 + in[i] - out2);
-			out2 += a * (out1 + 3 * in[i] - out2);
-			out2 += coef * (in[i] - out2);
-			out1 = in[i];
-			out[i] += out2 * level;
-		}
+
+	for( int i = 0; i < samples; i++ ) {
+		*dst++ += *src++ * level;
 	}
-	return 0;
+
+	plugin->new_dsp_length += samples;
+	return result;
 }
 
-void ReverbEngine::run()
-{
-	int j, i;
-//printf("ReverbEngine::run 1 %d\n", calculate_realtime());
-	while(1)
-	{
-		input_lock.lock();
-		if(completed) return;
-
-// Process reverb
-		for(i = 0; i < plugin->total_in_buffers; i++)
-		{
-			for(j = 0; j < plugin->config.ref_total + 1; j++)
-			{
-				if(plugin->ref_channels[i][j] == output_buffer)
-					process_overlay(plugin->main_in[i],
-								&(plugin->dsp_in[plugin->ref_channels[i][j]][plugin->ref_offsets[i][j]]),
-								plugin->lowpass_in1[i][j],
-								plugin->lowpass_in2[i][j],
-								plugin->ref_levels[i][j],
-								plugin->ref_lowpass[i][j],
-								plugin->project_sample_rate,
-								size);
-			}
+
+ReverbPackage::ReverbPackage()
+ : LoadPackage()
+{
+}
+
+
+ReverbUnit::ReverbUnit(ReverbEngine *engine, Reverb *plugin)
+ : LoadClient(engine)
+{
+	this->plugin = plugin;
+}
+
+ReverbUnit::~ReverbUnit()
+{
+}
+
+void ReverbUnit::process_package(LoadPackage *package)
+{
+	ReverbPackage *pkg = (ReverbPackage*)package;
+	int channel = pkg->channel;
+
+	for( int i = 0; i < plugin->config.ref_total; i++ ) {
+		int src_channel = plugin->ref_channels[channel][i];
+		int dst_offset = plugin->ref_offsets[channel][i];
+		double level = plugin->ref_levels[channel][i];
+		double *dst = plugin->dsp_in[channel] + dst_offset;
+		double *src = plugin->get_output(src_channel)->get_data();
+		int size = plugin->get_buffer_size();
+
+		if( size + dst_offset > plugin->dsp_in_allocated ) {
+			printf("ReverbUnit::process_package %d size=%d dst_offset=%d needed=%d allocated=%d\n",
+			__LINE__, size, dst_offset, size + dst_offset, plugin->dsp_in_allocated);
 		}
 
-		output_lock.unlock();
+		for( int j = 0; j < size; j++ )
+			*dst++ += *src++ * level;
 	}
 }
 
 
 
+ReverbEngine::ReverbEngine(Reverb *plugin)
+ : LoadServer(plugin->PluginClient::smp + 1, plugin->total_in_buffers)
+{
+	this->plugin = plugin;
+}
+
+ReverbEngine::~ReverbEngine()
+{
+}
+
+
+void ReverbEngine::init_packages()
+{
+	for( int i = 0; i < LoadServer::get_total_packages(); i++ ) {
+		ReverbPackage *package = (ReverbPackage*)LoadServer::get_package(i);
+		package->channel = i;
+	}
+}
 
+LoadClient* ReverbEngine::new_client()
+{
+	return new ReverbUnit(this, plugin);
+}
 
+LoadPackage* ReverbEngine::new_package()
+{
+	return new ReverbPackage;
+}
 
 
 ReverbConfig::ReverbConfig()
@@ -536,11 +539,12 @@ ReverbConfig::ReverbConfig()
 	delay_init = 0;
 	ref_level1 = -20;
 	ref_level2 = INFINITYGAIN;
-	ref_total = 100;
+	ref_total = 128;
 	ref_length = 600;
-	lowpass1 = Freq::tofreq(TOTALFREQS);
-	lowpass2 = Freq::tofreq(TOTALFREQS);
-
+	high = Freq::tofreq(TOTALFREQS);
+	low = Freq::tofreq(0);
+	q = 1.0;
+	window_size = 4096;
 }
 
 int ReverbConfig::equivalent(ReverbConfig &that)
@@ -551,8 +555,10 @@ int ReverbConfig::equivalent(ReverbConfig &that)
 		EQUIV(ref_level2, that.ref_level2) &&
 		ref_total == that.ref_total &&
 		ref_length == that.ref_length &&
-		lowpass1 == that.lowpass1 &&
-		lowpass2 == that.lowpass2);
+		high == that.high &&
+		low == that.low &&
+		EQUIV(q, that.q) &&
+		window_size == that.window_size);
 }
 
 void ReverbConfig::copy_from(ReverbConfig &that)
@@ -563,15 +569,14 @@ void ReverbConfig::copy_from(ReverbConfig &that)
 	ref_level2 = that.ref_level2;
 	ref_total = that.ref_total;
 	ref_length = that.ref_length;
-	lowpass1 = that.lowpass1;
-	lowpass2 = that.lowpass2;
+	high = that.high;
+	low = that.low;
+	q = that.q;
+	window_size = that.window_size;
 }
 
-void ReverbConfig::interpolate(ReverbConfig &prev,
-	ReverbConfig &next,
-	int64_t prev_frame,
-	int64_t next_frame,
-	int64_t current_frame)
+void ReverbConfig::interpolate(ReverbConfig &prev, ReverbConfig &next,
+	int64_t prev_frame, int64_t next_frame, int64_t current_frame)
 {
 	level_init = prev.level_init;
 	delay_init = prev.delay_init;
@@ -579,28 +584,30 @@ void ReverbConfig::interpolate(ReverbConfig &prev,
 	ref_level2 = prev.ref_level2;
 	ref_total = prev.ref_total;
 	ref_length = prev.ref_length;
-	lowpass1 = prev.lowpass1;
-	lowpass2 = prev.lowpass2;
+	high = prev.high;
+	low = prev.low;
+	q = prev.q;
+	window_size = prev.window_size;
 }
 
 void ReverbConfig::boundaries()
 {
-
 	CLAMP(level_init, INFINITYGAIN, 0);
 	CLAMP(delay_init, 0, MAX_DELAY_INIT);
 	CLAMP(ref_level1, INFINITYGAIN, 0);
 	CLAMP(ref_level2, INFINITYGAIN, 0);
 	CLAMP(ref_total, MIN_REFLECTIONS, MAX_REFLECTIONS);
-	CLAMP(ref_length, 0, MAX_REFLENGTH);
-	CLAMP(lowpass1, 0, Freq::tofreq(TOTALFREQS));
-	CLAMP(lowpass2, 0, Freq::tofreq(TOTALFREQS));
+	CLAMP(ref_length, MIN_REFLENGTH, MAX_REFLENGTH);
+	CLAMP(high, 0, Freq::tofreq(TOTALFREQS));
+	CLAMP(low, 0, Freq::tofreq(TOTALFREQS));
+	CLAMP(q, 0.0, 1.0);
 }
 
 void ReverbConfig::dump()
 {
-	printf("ReverbConfig::dump %f %jd %f %f %jd %jd %jd %jd\n",
-		level_init, delay_init, ref_level1, ref_level2,
-		ref_total, ref_length, lowpass1, lowpass2);
+	printf("ReverbConfig::dump %d level_init=%f delay_init=%d ref_level1=%f"
+		" ref_level2=%f ref_total=%d ref_length=%d high=%d low=%d q=%f\n",
+		__LINE__, level_init, (int)delay_init, ref_level1, ref_level2,
+		(int)ref_total, (int)ref_length, (int)high, (int)low, q);
 }
 
-
diff --git a/cinelerra-5.1/plugins/reverb/reverb.h b/cinelerra-5.1/plugins/reverb/reverb.h
index 629c74ce..2b889c89 100644
--- a/cinelerra-5.1/plugins/reverb/reverb.h
+++ b/cinelerra-5.1/plugins/reverb/reverb.h
@@ -1,7 +1,7 @@
 
 /*
  * 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
@@ -16,7 +16,6 @@
  * 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
- *
  */
 
 #ifndef REVERB_H
@@ -24,13 +23,18 @@
 
 class Reverb;
 class ReverbEngine;
+class ReverbFFT;
 
+#include "compressortools.h"
+#include "fourier.h"
 #include "reverbwindow.h"
+#include "loadbalance.h"
 #include "pluginaclient.h"
 
 #define MAX_DELAY_INIT 1000
 #define MIN_REFLECTIONS 1
 #define MAX_REFLECTIONS 255
+#define MIN_REFLENGTH 3
 #define MAX_REFLENGTH 5000
 
 class ReverbConfig
@@ -41,21 +45,23 @@ public:
 
 	int equivalent(ReverbConfig &that);
 	void copy_from(ReverbConfig &that);
-	void interpolate(ReverbConfig &prev,
-		ReverbConfig &next,
-		int64_t prev_frame,
-		int64_t next_frame,
-		int64_t current_frame);
+	void interpolate(ReverbConfig &prev, ReverbConfig &next,
+		int64_t prev_frame, int64_t next_frame, int64_t current_frame);
 	void dump();
 	void boundaries();
 
-	double level_init;
-	int64_t delay_init;
-	double ref_level1;
-	double ref_level2;
-	int64_t ref_total;
-	int64_t ref_length;
-	int64_t lowpass1, lowpass2;
+	float level_init;
+	int delay_init;
+	float ref_level1;
+	float ref_level2;
+	int ref_total;
+	int ref_length;
+// high frequency
+	int high;
+// low frequency
+	int low;
+	float q;
+	int window_size;
 };
 
 class Reverb : public PluginAClient
@@ -64,54 +70,110 @@ public:
 	Reverb(PluginServer *server);
 	~Reverb();
 
+	void reset();
+	void render_stop();
 	void update_gui();
-//	int load_from_file(char *data);
-//	int save_to_file(char *data);
-
-// data for reverb
-	char config_directory[1024];
 
-	double **main_in, **main_out;
-	double **dsp_in;
-	int64_t **ref_channels, **ref_offsets, **ref_lowpass;
-	double **ref_levels;
-	int64_t dsp_in_length;
-	int redo_buffers;
-// skirts for lowpass filter
-	double **lowpass_in1, **lowpass_in2;
-	DB db;
 // required for all realtime/multichannel plugins
-
 	PLUGIN_CLASS_MEMBERS(ReverbConfig);
-	int process_realtime(int64_t size, Samples **input_ptr, Samples **output_ptr);
+	int process_buffer(int64_t size, Samples **buffer,
+			int64_t start_position, int sample_rate);
+	double gauss(double sigma, double center, double x);
+	void calculate_envelope();
+	void reallocate_dsp(int new_dsp_allocated);
+
 	int is_realtime();
 	int is_synthesis();
 	int is_multichannel();
-	int show_gui();
-	int set_string();
 	void save_data(KeyFrame *keyframe);
 	void read_data(KeyFrame *keyframe);
-	void raise_window();
 
-	ReverbEngine **engine;
-	int initialized;
+// the output all reflections are painted on
+	double **dsp_in;
+// may have to expand it for fft windows larger than the reflected time
+	int dsp_in_allocated;
+// total samples read into dsp_in by the FFT
+	int dsp_in_length;
+// new value calculated by the FFT readers
+	int new_dsp_length;
+// total spectrogram frames generated by the FFT.  Each channel overwrites the same
+// spectrograms
+	int new_spectrogram_frames;
+
+// source channels of reflections
+	int **ref_channels;
+// destination offsets of reflections
+	int **ref_offsets;
+// levels of reflections
+	double **ref_levels;
+// detect seeking
+	int64_t last_position;
+// start_position / sample_rate
+	double start_pos;
+// get_direction fwd=1, rev=-1, stop=0
+	int dir;
+	DB db;
+
+	ReverbEngine *engine;
+	ReverbFFT **fft;
+	double *envelope;
+	int need_reconfigure;
+};
+
+class ReverbClientFrame : public CompressorFreqFrame
+{
+public:
+	ReverbClientFrame(int size);
+	~ReverbClientFrame();
+};
+
+
+class ReverbFFT :public CrossfadeFFT
+{
+public:
+	ReverbFFT(Reverb *plugin, int channel);
+	~ReverbFFT();
+
+	int signal_process();
+	int post_process();
+	int read_samples(int64_t output_sample, int samples, Samples *buffer);
+
+	Reverb *plugin;
+	ReverbClientFrame *frame;
+	int channel;
+};
+
+
+class ReverbPackage : public LoadPackage
+{
+public:
+	ReverbPackage();
+	int channel;
+};
+
+
+class ReverbUnit : public LoadClient
+{
+public:
+	ReverbUnit(ReverbEngine *engine, Reverb *plugin);
+	~ReverbUnit();
+	void process_package(LoadPackage *package);
+	ReverbEngine *engine;
+	Reverb *plugin;
 };
 
-class ReverbEngine : public Thread
+// This allocates 1 CPU for each output channel.
+// They simultaneously read from all the input FFT channels.
+class ReverbEngine : public LoadServer
 {
 public:
 	ReverbEngine(Reverb *plugin);
 	~ReverbEngine();
 
-	int process_overlay(double *in, double *out, double &out1, double &out2, double level, int64_t lowpass, int64_t samplerate, int64_t size);
-	int process_overlays(int output_buffer, int64_t size);
-	int wait_process_overlays();
-	void run();
+	void init_packages();
+	LoadClient* new_client();
+	LoadPackage* new_package();
 
-	Mutex input_lock, output_lock;
-	int completed;
-	int output_buffer;
-	int64_t size;
 	Reverb *plugin;
 };
 
diff --git a/cinelerra-5.1/plugins/reverb/reverb.inc b/cinelerra-5.1/plugins/reverb/reverb.inc
index f827f6c4..9683150a 100644
--- a/cinelerra-5.1/plugins/reverb/reverb.inc
+++ b/cinelerra-5.1/plugins/reverb/reverb.inc
@@ -16,13 +16,11 @@
  * 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
- *
  */
 
 #ifndef REVERB_INC
 #define REVERB_INC
 
-
 class Reverb;
 
 #endif
diff --git a/cinelerra-5.1/plugins/reverb/reverbwindow.C b/cinelerra-5.1/plugins/reverb/reverbwindow.C
index 86933363..63a21daf 100644
--- a/cinelerra-5.1/plugins/reverb/reverbwindow.C
+++ b/cinelerra-5.1/plugins/reverb/reverbwindow.C
@@ -1,7 +1,7 @@
 
 /*
  * 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
@@ -16,518 +16,387 @@
  * 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 "bchash.h"
 #include "bcsignals.h"
 #include "filesystem.h"
+#include "eqcanvas.h"
 #include "language.h"
 #include "reverb.h"
 #include "reverbwindow.h"
+#include "theme.h"
 
 #include <string.h>
 
-
+#define TEXT_W xS(90)
+#define WINDOW_W xS(400)
+#define WINDOW_H yS(450)
 
 ReverbWindow::ReverbWindow(Reverb *reverb)
  : PluginClientWindow(reverb,
-	xS(300),
-	yS(230),
-	xS(300),
-	yS(230),
-	0)
+	WINDOW_W, WINDOW_H, WINDOW_W, WINDOW_H, 0)
 {
 	this->reverb = reverb;
 }
 
 ReverbWindow::~ReverbWindow()
 {
+	for( int i = 0; i < TOTAL_PARAMS; i++ )
+		delete params[i];
+	delete canvas;
 }
 
 void ReverbWindow::create_objects()
 {
-	int xs5 = xS(5), xs35 = xS(35), xs200 = xS(200);
-	int ys10 = yS(10), ys25 = yS(25), ys40 = yS(40);
-	int x = xs200, y = ys10;
-	add_tool(new BC_Title(xs5, y + ys10, _("Initial signal level:")));
-	add_tool(level_init = new ReverbLevelInit(reverb, x, y)); y += ys25;
-	add_tool(new BC_Title(xs5, y + ys10, _("ms before reflections:")));
-	add_tool(delay_init = new ReverbDelayInit(reverb, x + xs35, y)); y += ys25;
-	add_tool(new BC_Title(xs5, y + ys10, _("First reflection level:")));
-	add_tool(ref_level1 = new ReverbRefLevel1(reverb, x, y)); y += ys25;
-	add_tool(new BC_Title(xs5, y + ys10, _("Last reflection level:")));
-	add_tool(ref_level2 = new ReverbRefLevel2(reverb, x + xs35, y)); y += ys25;
-	add_tool(new BC_Title(xs5, y + ys10, _("Number of reflections:")));
-	add_tool(ref_total = new ReverbRefTotal(reverb, x, y)); y += ys25;
-	add_tool(new BC_Title(xs5, y + ys10, _("ms of reflections:")));
-	add_tool(ref_length = new ReverbRefLength(reverb, x + xs35, y)); y += ys25;
-	add_tool(new BC_Title(xs5, y + ys10, _("Start band for lowpass:")));
-	add_tool(lowpass1 = new ReverbLowPass1(reverb, x, y)); y += ys25;
-	add_tool(new BC_Title(xs5, y + ys10, _("End band for lowpass:")));
-	add_tool(lowpass2 = new ReverbLowPass2(reverb, x + xs35, y)); y += ys40;
-	show_window();
-	flush();
-}
+	int margin = client->get_theme()->widget_border;
+	int x = xS(230), y = margin;
+	int x1 = x + BC_Pot::calculate_w();
+	int x2 = x1 + BC_Pot::calculate_w() + margin;
+	int text_w = get_w() - margin - x2;
+	int height = BC_TextBox::calculate_h(this, MEDIUMFONT, 1, 1) + margin;
+
+
+	int i = 0;
+	params[i] = new ReverbParam(reverb, this,
+		margin, x, x2, y, text_w,
+		0,  // output_i
+		&reverb->config.level_init, // output_f
+		0, // output_q
+		"Initial signal level (db):",
+		INFINITYGAIN, // min
+		0); // max
+	params[i]->initialize();
+	i++;
+	y += height;
+
+	params[i] = new ReverbParam(reverb, this,
+		margin, x1, x2, y, text_w,
+		&reverb->config.delay_init,  // output_i
+		0, // output_f
+		0, // output_q
+		"ms before reflections:",
+		0, // min
+		MAX_DELAY_INIT); // max
+	params[i]->initialize();
+	i++;
+	y += height;
 
+	params[i] = new ReverbParam(reverb, this,
+		margin, x, x2, y, text_w,
+		0,  // output_i
+		&reverb->config.ref_level1, // output_f
+		0, // output_q
+		"First reflection level (db):",
+		INFINITYGAIN, // min
+		0); // max
+	params[i]->initialize();
+	i++;
+	y += height;
 
+	params[i] = new ReverbParam(reverb, this,
+		margin, x1, x2, y, text_w,
+		0,  // output_i
+		&reverb->config.ref_level2, // output_f
+		0, // output_q
+		"Last reflection level (db):",
+		INFINITYGAIN, // min
+		0); // max
+	params[i]->initialize();
+	i++;
+	y += height;
 
+	params[i] = new ReverbParam(reverb, this,
+		margin, x, x2, y, text_w,
+		&reverb->config.ref_total,  // output_i
+		0, // output_f
+		0, // output_q
+		"Number of reflections:",
+		MIN_REFLECTIONS, // min
+		MAX_REFLECTIONS); // max
+	params[i]->initialize();
+	i++;
+	y += height;
 
+	params[i] = new ReverbParam(reverb, this,
+		margin, x1, x2, y, text_w,
+		&reverb->config.ref_length,  // output_i
+		0, // output_f
+		0, // output_q
+		"ms of reflections:",
+		0, // min
+		MAX_REFLENGTH); // max
+	params[i]->initialize();
+	i++;
+	y += height;
 
+	params[i] = new ReverbParam(reverb, this,
+		margin, x, x2, y, text_w,
+		0,  // output_i
+		0, // output_f
+		&reverb->config.low, // output_q
+		"Low freq of bandpass:",
+		0, // min
+		0); // max
+	params[i]->initialize();
+	i++;
+	y += height;
 
-ReverbLevelInit::ReverbLevelInit(Reverb *reverb, int x, int y)
- : BC_FPot(x,
- 	y,
-	reverb->config.level_init,
-	INFINITYGAIN,
-	0)
+	params[i] = new ReverbParam(reverb, this,
+		margin, x1, x2, y, text_w,
+		0,  // output_i
+		0, // output_f
+		&reverb->config.high, // output_q
+		"High freq of bandpass:",
+		0, // min
+		0); // max
+	params[i]->initialize();
+	i++;
+	y += height;
+
+	params[i] = new ReverbParam(reverb, this,
+		margin, x, x2, y, text_w,
+		0,  // output_i
+		&reverb->config.q, // output_f
+		0, // output_q
+		"Steepness of bandpass:",
+		0.0, // min
+		1.0); // max
+	params[i]->initialize();
+	params[i]->fpot->set_precision(0.01);
+	i++;
+	y += BC_Pot::calculate_h() + margin;
+
+
+	BC_Title *title;
+	add_subwindow(title = new BC_Title(margin, y, _("Window:")));
+	add_subwindow(size = new ReverbSize(this, reverb,
+		margin + title->get_w() + margin, y));
+	size->create_objects();
+	size->update(reverb->config.window_size);
+	y += size->get_h() + margin;
+
+	int canvas_x = 0;
+	int canvas_y = y;
+	int canvas_w = get_w() - margin;
+	int canvas_h = get_h() - canvas_y - margin;
+	canvas = new EQCanvas(this,
+		canvas_x, canvas_y, canvas_w, canvas_h,
+		INFINITYGAIN, 0.0);
+	canvas->initialize();
+	update();
+
+	show_window();
+}
+
+void ReverbWindow::update()
 {
-	this->reverb = reverb;
+	for( int i = 0; i < TOTAL_PARAMS; i++ )
+		params[i]->update(1, 1);
+	size->update(reverb->config.window_size);
+	update_canvas();
 }
-ReverbLevelInit::~ReverbLevelInit()
+
+
+void ReverbWindow::update_canvas()
 {
+	double tracking_position = reverb->get_tracking_position();
+	CompressorFreqFrame *frame = (CompressorFreqFrame *)
+		reverb->get_gui_frame(tracking_position, 1);
+	canvas->update_spectrogram(frame, -1, -1, -1);
+
+// draw the envelope
+	reverb->calculate_envelope();
+	canvas->draw_envelope(reverb->envelope,
+		reverb->PluginAClient::project_sample_rate,
+		reverb->config.window_size, 1, 0);
+	BC_SubWindow *gui = canvas->canvas;
+	gui->flash(1);
+//printf("ReverbWindow::update_canvas %d\n", __LINE__);
 }
-int ReverbLevelInit::handle_event()
+
+
+ReverbSize::ReverbSize(ReverbWindow *window, Reverb *plugin, int x, int y)
+ : BC_PopupMenu(x, y, xS(100), "4096", 1)
 {
-//printf("ReverbLevelInit::handle_event 1 %p\n", reverb);
-	reverb->config.level_init = get_value();
-//printf("ReverbLevelInit::handle_event 1\n");
-	reverb->send_configure_change();
-//printf("ReverbLevelInit::handle_event 2\n");
-	return 1;
+	this->plugin = plugin;
+	this->window = window;
 }
 
-ReverbDelayInit::ReverbDelayInit(Reverb *reverb, int x, int y)
- : BC_IPot(x,
- 	y,
-	reverb->config.delay_init,
-	0,
-	MAX_DELAY_INIT)
+int ReverbSize::handle_event()
 {
-	this->reverb = reverb;
+	plugin->config.window_size = atoi(get_text());
+	plugin->send_configure_change();
+
+	window->update_canvas();
+	return 1;
 }
-ReverbDelayInit::~ReverbDelayInit()
+
+void ReverbSize::create_objects()
 {
+	add_item(new BC_MenuItem("2048"));
+	add_item(new BC_MenuItem("4096"));
+	add_item(new BC_MenuItem("8192"));
+	add_item(new BC_MenuItem("16384"));
+	add_item(new BC_MenuItem("32768"));
+	add_item(new BC_MenuItem("65536"));
+	add_item(new BC_MenuItem("131072"));
+	add_item(new BC_MenuItem("262144"));
 }
-int ReverbDelayInit::handle_event()
+
+void ReverbSize::update(int size)
 {
-	reverb->config.delay_init = get_value();
-	reverb->send_configure_change();
-	return 1;
+	char string[BCTEXTLEN];
+	sprintf(string, "%d", size);
+	set_text(string);
 }
 
-ReverbRefLevel1::ReverbRefLevel1(Reverb *reverb, int x, int y)
- : BC_FPot(x,
- 	y,
-	reverb->config.ref_level1,
-	INFINITYGAIN,
-	0)
+
+ReverbParam::ReverbParam(Reverb *reverb, ReverbWindow *gui,
+	int x, int x2, int x3, int y, int text_w,
+	int *output_i,
+	float *output_f, // floating point output
+	int *output_q,
+	const char *title, float min, float max)
 {
+	this->output_i = output_i;
+	this->output_f = output_f;
+	this->output_q = output_q;
+	this->title = cstrdup(title);
 	this->reverb = reverb;
+	this->gui = gui;
+	this->x = x;
+	this->x2 = x2;
+	this->x3 = x3;
+	this->y = y;
+	this->text_w = text_w;
+	this->min = min;
+	this->max = max;
+	fpot = 0;
+	ipot = 0;
+	qpot = 0;
+	text = 0;
 }
-ReverbRefLevel1::~ReverbRefLevel1() {}
-int ReverbRefLevel1::handle_event()
+
+ReverbParam::~ReverbParam()
 {
-	reverb->config.ref_level1 = get_value();
-	reverb->send_configure_change();
-	return 1;
+	delete fpot;
+	delete ipot;
+	delete qpot;
+	delete text;
+	delete [] title;
 }
 
-
-ReverbRefLevel2::ReverbRefLevel2(Reverb *reverb, int x, int y)
- : BC_FPot(x,
- 	y,
-	reverb->config.ref_level2,
-	INFINITYGAIN,
-	0)
+void ReverbParam::initialize()
 {
-	this->reverb = reverb;
+	BC_Title *title_;
+	int y2 = y + (BC_Pot::calculate_h() -
+		BC_Title::calculate_h(gui, _(title), MEDIUMFONT)) / 2;
+	gui->add_tool(title_ = new BC_Title(x, y2, _(title)));
+
+	if( output_f ) gui->add_tool(fpot = new ReverbFPot(this, x2, y));
+	if( output_i ) gui->add_tool(ipot = new ReverbIPot(this, x2, y));
+	if( output_q ) gui->add_tool(qpot = new ReverbQPot(this, x2, y));
+
+	int y3 = y + (BC_Pot::calculate_h() -
+		BC_TextBox::calculate_h(gui, MEDIUMFONT, 1, 1)) / 2;
+	if( output_i )
+		gui->add_tool(text = new ReverbText(this, x3, y3, text_w, *output_i));
+	if( output_f )
+		gui->add_tool(text = new ReverbText(this, x3, y3, text_w, *output_f));
+	if( output_q )
+		gui->add_tool(text = new ReverbText(this, x3, y3, text_w, *output_q));
 }
-ReverbRefLevel2::~ReverbRefLevel2() {}
-int ReverbRefLevel2::handle_event()
+
+void ReverbParam::update(int skip_text, int skip_pot)
 {
-	reverb->config.ref_level2 = get_value();
-	reverb->send_configure_change();
-	return 1;
+	if( !skip_text ) {
+		if( output_i ) text->update((int64_t)*output_i);
+		if( output_q ) text->update((int64_t)*output_q);
+		if( output_f ) text->update((float)*output_f);
+	}
+
+	if( !skip_pot ) {
+		if( ipot ) ipot->update((int64_t)*output_i);
+		if( qpot ) ipot->update((int64_t)*output_q);
+		if( fpot ) ipot->update((float)*output_f);
+	}
 }
 
-ReverbRefTotal::ReverbRefTotal(Reverb *reverb, int x, int y)
- : BC_IPot(x,
- 	y,
-	reverb->config.ref_total,
-	MIN_REFLECTIONS,
-	MAX_REFLECTIONS)
+
+ReverbFPot::ReverbFPot(ReverbParam *param, int x, int y)
+ : BC_FPot(x, y, *param->output_f, param->min, param->max)
 {
-	this->reverb = reverb;
+	this->param = param;
+	set_use_caption(0);
 }
-ReverbRefTotal::~ReverbRefTotal() {}
-int ReverbRefTotal::handle_event()
+
+int ReverbFPot::handle_event()
 {
-	reverb->config.ref_total = get_value();
-	reverb->send_configure_change();
+	*param->output_f = get_value();
+	param->update(0, 1);
+	param->reverb->send_configure_change();
+	param->gui->update_canvas();
 	return 1;
 }
 
 
-ReverbRefLength::ReverbRefLength(Reverb *reverb, int x, int y)
- : BC_IPot(x,
- 	y,
-	reverb->config.ref_length,
-	0,
-	MAX_REFLENGTH)
+ReverbIPot::ReverbIPot(ReverbParam *param, int x, int y)
+ : BC_IPot(x, y, *param->output_i, (int)param->min, (int)param->max)
 {
-	this->reverb = reverb;
+	this->param = param;
+	set_use_caption(0);
 }
-ReverbRefLength::~ReverbRefLength() {}
-int ReverbRefLength::handle_event()
+
+int ReverbIPot::handle_event()
 {
-	reverb->config.ref_length = get_value();
-	reverb->send_configure_change();
+	*param->output_i = get_value();
+	param->update(0, 1);
+	param->reverb->send_configure_change();
+	param->gui->update_canvas();
 	return 1;
 }
 
-ReverbLowPass1::ReverbLowPass1(Reverb *reverb, int x, int y)
- : BC_QPot(x,
- 	y,
-	reverb->config.lowpass1)
+
+ReverbQPot::ReverbQPot(ReverbParam *param, int x, int y)
+ : BC_QPot(x, y, *param->output_q)
 {
-	this->reverb = reverb;
+	this->param = param;
+	set_use_caption(0);
 }
-ReverbLowPass1::~ReverbLowPass1() {}
-int ReverbLowPass1::handle_event()
+
+int ReverbQPot::handle_event()
 {
-	reverb->config.lowpass1 = get_value();
-	reverb->send_configure_change();
+	*param->output_q = get_value();
+	param->update(0, 1);
+	param->reverb->send_configure_change();
+	param->gui->update_canvas();
 	return 1;
 }
 
-ReverbLowPass2::ReverbLowPass2(Reverb *reverb, int x, int y)
- : BC_QPot(x,
- 	y,
-	reverb->config.lowpass2)
+
+ReverbText::ReverbText(ReverbParam *param, int x, int y, int text_w, int value)
+ : BC_TextBox(x, y, text_w, 1, (int64_t)value, 1, MEDIUMFONT)
 {
-	this->reverb = reverb;
+	this->param = param;
 }
-ReverbLowPass2::~ReverbLowPass2() {}
-int ReverbLowPass2::handle_event()
+
+ReverbText::ReverbText(ReverbParam *param, int x, int y, int text_w, float value)
+ : BC_TextBox(x, y, text_w, 1, (float)value, 1, MEDIUMFONT, 2)
 {
-	reverb->config.lowpass2 = get_value();
-	reverb->send_configure_change();
-	return 1;
+	this->param = param;
 }
 
-// ReverbMenu::ReverbMenu(Reverb *reverb, ReverbWindow *window)
-//  : BC_MenuBar(0, 0, window->get_w())
-// {
-// 	this->window = window;
-// 	this->reverb = reverb;
-// }
-// 
-// ReverbMenu::~ReverbMenu()
-// {
-// 	delete load;
-// 	delete save;
-// 	//delete set_default;
-// 	for(int i = 0; i < total_loads; i++)
-// 	{
-// 		delete prev_load[i];
-// 	}
-// 	delete prev_load_thread;
-// }
-// 
-// void ReverbMenu::create_objects(BC_Hash *defaults)
-// {
-// 	add_menu(filemenu = new BC_Menu(_("File")));
-// 	filemenu->add_item(load = new ReverbLoad(reverb, this));
-// 	filemenu->add_item(save = new ReverbSave(reverb, this));
-// 	//filemenu->add_item(set_default = new ReverbSetDefault);
-// 	load_defaults(defaults);
-// 	prev_load_thread = new ReverbLoadPrevThread(reverb, this);
-// }
-// 
-// int ReverbMenu::load_defaults(BC_Hash *defaults)
-// {
-// 	FileSystem fs;
-// 	total_loads = defaults->get("TOTAL_LOADS", 0);
-// 	if(total_loads > 0)
-// 	{
-// 		filemenu->add_item(new BC_MenuItem("-"));
-// 		char string[1024], path[1024], filename[1024];
-// 	
-// 		for(int i = 0; i < total_loads; i++)
-// 		{
-// 			sprintf(string, "LOADPREVIOUS%d", i);
-// 			defaults->get(string, path);
-// 			fs.extract_name(filename, path);
-// //printf("ReverbMenu::load_defaults %s\n", path);
-// 			filemenu->add_item(prev_load[i] = new ReverbLoadPrev(reverb, this, filename, path));
-// 		}
-// 	}
-// 	return 0;
-// }
-// 
-// int ReverbMenu::save_defaults(BC_Hash *defaults)
-// {
-// 	if(total_loads > 0)
-// 	{
-// 		defaults->update("TOTAL_LOADS",  total_loads);
-// 		char string[1024];
-// 		
-// 		for(int i = 0; i < total_loads; i++)
-// 		{
-// 			sprintf(string, "LOADPREVIOUS%d", i);
-// 			defaults->update(string, prev_load[i]->path);
-// 		}
-// 	}
-// 	return 0;
-// }
-// 
-// int ReverbMenu::add_load(char *path)
-// {
-// 	if(total_loads == 0)
-// 	{
-// 		filemenu->add_item(new BC_MenuItem("-"));
-// 	}
-// 	
-// // test for existing copy
-// 	FileSystem fs;
-// 	char text[1024], new_path[1024];      // get text and path
-// 	fs.extract_name(text, path);
-// 	strcpy(new_path, path);
-// 	
-// 	for(int i = 0; i < total_loads; i++)
-// 	{
-// 		if(!strcmp(prev_load[i]->get_text(), text))     // already exists
-// 		{                                // swap for top load
-// 			for(int j = i; j > 0; j--)   // move preceeding loads down
-// 			{
-// 				prev_load[j]->set_text(prev_load[j - 1]->get_text());
-// 				prev_load[j]->set_path(prev_load[j - 1]->path);
-// 			}
-// 			prev_load[0]->set_text(text);
-// 			prev_load[0]->set_path(new_path);
-// 			return 1;
-// 		}
-// 	}
-// 	
-// // add another load
-// 	if(total_loads < TOTAL_LOADS)
-// 	{
-// 		filemenu->add_item(prev_load[total_loads] = new ReverbLoadPrev(reverb, this));
-// 		total_loads++;
-// 	}
-// 	
-// // cycle loads down
-// 	for(int i = total_loads - 1; i > 0; i--)
-// 	{         
-// 	// set menu item text
-// 		prev_load[i]->set_text(prev_load[i - 1]->get_text());
-// 	// set filename
-// 		prev_load[i]->set_path(prev_load[i - 1]->path);
-// 	}
-// 
-// // set up the new load
-// 	prev_load[0]->set_text(text);
-// 	prev_load[0]->set_path(new_path);
-// 	return 0;
-// }
-// 
-// ReverbLoad::ReverbLoad(Reverb *reverb, ReverbMenu *menu)
-//  : BC_MenuItem(_("Load..."))
-// {
-// 	this->reverb = reverb;
-// 	this->menu = menu;
-// 	thread = new ReverbLoadThread(reverb, menu);
-// }
-// ReverbLoad::~ReverbLoad()
-// {
-// 	delete thread;
-// }
-// int ReverbLoad::handle_event()
-// {
-// 	thread->start();
-// 	return 0;
-// }
-// 
-// ReverbSave::ReverbSave(Reverb *reverb, ReverbMenu *menu)
-//  : BC_MenuItem(_("Save..."))
-// {
-// 	this->reverb = reverb;
-// 	this->menu = menu;
-// 	thread = new ReverbSaveThread(reverb, menu);
-// }
-// ReverbSave::~ReverbSave()
-// {
-// 	delete thread;
-// }
-// int ReverbSave::handle_event()
-// {
-// 	thread->start();
-// 	return 0;
-// }
-// 
-// ReverbSetDefault::ReverbSetDefault()
-//  : BC_MenuItem(_("Set default"))
-// {
-// }
-// int ReverbSetDefault::handle_event()
-// {
-// 	return 0;
-// }
-// 
-// ReverbLoadPrev::ReverbLoadPrev(Reverb *reverb, ReverbMenu *menu, char *filename, char *path)
-//  : BC_MenuItem(filename)
-// {
-// 	this->reverb = reverb;
-// 	this->menu = menu;
-// 	strcpy(this->path, path);
-// }
-// ReverbLoadPrev::ReverbLoadPrev(Reverb *reverb, ReverbMenu *menu)
-//  : BC_MenuItem("")
-// {
-// 	this->reverb = reverb;
-// 	this->menu = menu;
-// }
-// int ReverbLoadPrev::handle_event()
-// {
-// 	menu->prev_load_thread->set_path(path);
-// 	menu->prev_load_thread->start();
-// }
-// int ReverbLoadPrev::set_path(char *path)
-// {
-// 	strcpy(this->path, path);
-// }
-// 
-// 
-// ReverbSaveThread::ReverbSaveThread(Reverb *reverb, ReverbMenu *menu)
-//  : Thread()
-// {
-// 	this->reverb = reverb;
-// 	this->menu = menu;
-// }
-// ReverbSaveThread::~ReverbSaveThread()
-// {
-// }
-// void ReverbSaveThread::run()
-// {
-// 	int result = 0;
-// 	{
-// 		ReverbSaveDialog dialog(reverb);
-// 		dialog.create_objects();
-// 		result = dialog.run_window();
-// //		if(!result) strcpy(reverb->config_directory, dialog.get_path());
-// 	}
-// 	if(!result) 
-// 	{
-// 		result = reverb->save_to_file(reverb->config_directory);
-// 		menu->add_load(reverb->config_directory);
-// 	}
-// }
-// 
-// ReverbSaveDialog::ReverbSaveDialog(Reverb *reverb)
-//  : BC_FileBox(0,
-//  			0, 
-//  			reverb->config_directory, 
-//  			_("Save reverb"), 
-//  			_("Select the reverb file to save as"), 0, 0)
-// {
-// 	this->reverb = reverb;
-// }
-// ReverbSaveDialog::~ReverbSaveDialog()
-// {
-// }
-// int ReverbSaveDialog::ok_event()
-// {
-// 	set_done(0);
-// 	return 0;
-// }
-// int ReverbSaveDialog::cancel_event()
-// {
-// 	set_done(1);
-// 	return 0;
-// }
-// 
-// 
-// 
-// ReverbLoadThread::ReverbLoadThread(Reverb *reverb, ReverbMenu *menu)
-//  : Thread()
-// {
-// 	this->reverb = reverb;
-// 	this->menu = menu;
-// }
-// ReverbLoadThread::~ReverbLoadThread()
-// {
-// }
-// void ReverbLoadThread::run()
-// {
-// 	int result = 0;
-// 	{
-// 		ReverbLoadDialog dialog(reverb);
-// 		dialog.create_objects();
-// 		result = dialog.run_window();
-// //		if(!result) strcpy(reverb->config_directory, dialog.get_path());
-// 	}
-// 	if(!result) 
-// 	{
-// 		result = reverb->load_from_file(reverb->config_directory);
-// 		if(!result)
-// 		{
-// 			menu->add_load(reverb->config_directory);
-// 			reverb->send_configure_change();
-// 		}
-// 	}
-// }
-// 
-// ReverbLoadPrevThread::ReverbLoadPrevThread(Reverb *reverb, ReverbMenu *menu) : Thread()
-// {
-// 	this->reverb = reverb;
-// 	this->menu = menu;
-// }
-// ReverbLoadPrevThread::~ReverbLoadPrevThread()
-// {
-// }
-// void ReverbLoadPrevThread::run()
-// {
-// 	int result = 0;
-// 	strcpy(reverb->config_directory, path);
-// 	result = reverb->load_from_file(path);
-// 	if(!result)
-// 	{
-// 		menu->add_load(path);
-// 		reverb->send_configure_change();
-// 	}
-// }
-// int ReverbLoadPrevThread::set_path(char *path)
-// {
-// 	strcpy(this->path, path);
-// 	return 0;
-// }
-// 
-// 
-// 
-// 
-// 
-// ReverbLoadDialog::ReverbLoadDialog(Reverb *reverb)
-//  : BC_FileBox(0,
-//  			0, 
-//  			reverb->config_directory, 
-//  			_("Load reverb"), 
-//  			_("Select the reverb file to load from"), 0, 0)
-// {
-// 	this->reverb = reverb;
-// }
-// ReverbLoadDialog::~ReverbLoadDialog()
-// {
-// }
-// int ReverbLoadDialog::ok_event()
-// {
-// 	set_done(0);
-// 	return 0;
-// }
-// int ReverbLoadDialog::cancel_event()
-// {
-// 	set_done(1);
-// 	return 0;
-// }
+int ReverbText::handle_event()
+{
+	if( param->output_i ) *param->output_i = atoi(get_text());
+	if( param->output_f ) *param->output_f = atof(get_text());
+	if( param->output_q ) *param->output_q = atoi(get_text());
+
+	param->update(1, 0);
+	param->reverb->send_configure_change();
+	param->gui->update_canvas();
+	return 1;
+}
 
diff --git a/cinelerra-5.1/plugins/reverb/reverbwindow.h b/cinelerra-5.1/plugins/reverb/reverbwindow.h
index cc7eb265..b4e3968a 100644
--- a/cinelerra-5.1/plugins/reverb/reverbwindow.h
+++ b/cinelerra-5.1/plugins/reverb/reverbwindow.h
@@ -1,7 +1,7 @@
 
 /*
  * 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
@@ -16,33 +16,33 @@
  * 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
- *
  */
 
 #ifndef REVERBWINDOW_H
 #define REVERBWINDOW_H
 
-#define TOTAL_LOADS 5
+#define TOTAL_PARAMS 9
 
-class ReverbThread;
 class ReverbWindow;
+class ReverbParam;
 
+#include "eqcanvas.inc"
 #include "guicast.h"
 #include "mutex.h"
 #include "pluginclient.h"
 #include "reverb.inc"
 
 
-
 class ReverbLevelInit;
 class ReverbDelayInit;
 class ReverbRefLevel1;
 class ReverbRefLevel2;
 class ReverbRefTotal;
 class ReverbRefLength;
-class ReverbLowPass1;
-class ReverbLowPass2;
-class ReverbMenu;
+class ReverbHigh;
+class ReverbLow;
+class ReverbQ;
+class ReverbSize;
 
 class ReverbWindow : public PluginClientWindow
 {
@@ -51,225 +51,183 @@ public:
 	~ReverbWindow();
 
 	void create_objects();
+	void update();
+	void update_canvas();
 
 	Reverb *reverb;
-	ReverbLevelInit *level_init;
-	ReverbDelayInit *delay_init;
-	ReverbRefLevel1 *ref_level1;
-	ReverbRefLevel2 *ref_level2;
-	ReverbRefTotal *ref_total;
-	ReverbRefLength *ref_length;
-	ReverbLowPass1 *lowpass1;
-	ReverbLowPass2 *lowpass2;
-	ReverbMenu *menu;
-};
 
-class ReverbLevelInit : public BC_FPot
-{
-public:
-	ReverbLevelInit(Reverb *reverb, int x, int y);
-	~ReverbLevelInit();
-	int handle_event();
-	Reverb *reverb;
-};
+	ReverbParam *params[TOTAL_PARAMS];
 
-class ReverbDelayInit : public BC_IPot
-{
-public:
-	ReverbDelayInit(Reverb *reverb, int x, int y);
-	~ReverbDelayInit();
-	int handle_event();
-	Reverb *reverb;
+	ReverbParam *level_init;
+	ReverbParam *delay_init;
+	ReverbParam *ref_level1;
+	ReverbParam *ref_level2;
+	ReverbParam *ref_total;
+	ReverbParam *ref_length;
+	ReverbParam *high;
+	ReverbParam *low;
+	ReverbParam *q;
+	EQCanvas *canvas;
+	ReverbSize *size;
 };
 
-class ReverbRefLevel1 : public BC_FPot
+class ReverbSize : public BC_PopupMenu
 {
 public:
-	ReverbRefLevel1(Reverb *reverb, int x, int y);
-	~ReverbRefLevel1();
+	ReverbSize(ReverbWindow *window, Reverb *plugin, int x, int y);
+
 	int handle_event();
-	Reverb *reverb;
+	void create_objects();		 // add initial items
+	void update(int size);
+
+	ReverbWindow *window;
+	Reverb *plugin;
 };
 
-class ReverbRefLevel2 : public BC_FPot
+
+class ReverbFPot : public BC_FPot
 {
 public:
-	ReverbRefLevel2(Reverb *reverb, int x, int y);
-	~ReverbRefLevel2();
+	ReverbFPot(ReverbParam *param, int x, int y);
 	int handle_event();
-	Reverb *reverb;
+	ReverbParam *param;
 };
 
-class ReverbRefTotal : public BC_IPot
+class ReverbIPot : public BC_IPot
 {
 public:
-	ReverbRefTotal(Reverb *reverb, int x, int y);
-	~ReverbRefTotal();
+	ReverbIPot(ReverbParam *param, int x, int y);
 	int handle_event();
-	Reverb *reverb;
+	ReverbParam *param;
 };
 
-class ReverbRefLength : public BC_IPot
+class ReverbQPot : public BC_QPot
 {
 public:
-	ReverbRefLength(Reverb *reverb, int x, int y);
-	~ReverbRefLength();
+	ReverbQPot(ReverbParam *param, int x, int y);
 	int handle_event();
-	Reverb *reverb;
+	ReverbParam *param;
 };
 
-class ReverbLowPass1 : public BC_QPot
+class ReverbText : public BC_TextBox
 {
 public:
-	ReverbLowPass1(Reverb *reverb, int x, int y);
-	~ReverbLowPass1();
+	ReverbText(ReverbParam *param, int x, int y, int w, int value);
+	ReverbText(ReverbParam *param, int x, int y, int w, float value);
 	int handle_event();
-	Reverb *reverb;
+	ReverbParam *param;
 };
 
-class ReverbLowPass2 : public BC_QPot
+class ReverbParam
 {
 public:
-	ReverbLowPass2(Reverb *reverb, int x, int y);
-	~ReverbLowPass2();
-	int handle_event();
-	Reverb *reverb;
-};
+	ReverbParam(Reverb *reverb, ReverbWindow *gui,
+		int x, int x2, int x3, int y, int text_w,
+		int *output_i,
+		float *output_f, // floating point output
+		int *output_q, // frequency output
+		const char *title, float min, float max);
+	~ReverbParam();
 
+	void initialize();
+	void update(int skip_text, int skip_pot);
 
-class ReverbLoad;
-class ReverbSave;
-class ReverbSetDefault;
-class ReverbLoadPrev;
-class ReverbLoadPrevThread;
+	float *output_f;
+	ReverbFPot *fpot;
+	int *output_i;
+	ReverbIPot *ipot;
+	int *output_q;
+	ReverbQPot *qpot;
 
-class ReverbMenu : public BC_MenuBar
-{
-public:
-	ReverbMenu(Reverb *reverb, ReverbWindow *window);
-	~ReverbMenu();
-
-	void create_objects(BC_Hash *defaults);
-	int load_defaults(BC_Hash *defaults);
-	int save_defaults(BC_Hash *defaults);
-// most recent loads
-	int add_load(char *path);
-	ReverbLoadPrevThread *prev_load_thread;
-
-	int total_loads;
-	BC_Menu *filemenu;
-	ReverbWindow *window;
+	char *title;
+	ReverbText *text;
+	ReverbWindow *gui;
 	Reverb *reverb;
-	ReverbLoad *load;
-	ReverbSave *save;
-	ReverbSetDefault *set_default;
-	ReverbLoadPrev *prev_load[TOTAL_LOADS];
+	int x, y;
+	int x2, x3, text_w;
+	float min, max;
 };
 
-class ReverbSaveThread;
-class ReverbLoadThread;
 
-class ReverbLoad : public BC_MenuItem
+class ReverbLevelInit : public BC_FPot
 {
 public:
-	ReverbLoad(Reverb *reverb, ReverbMenu *menu);
-	~ReverbLoad();
+	ReverbLevelInit(Reverb *reverb, int x, int y);
+	~ReverbLevelInit();
 	int handle_event();
 	Reverb *reverb;
-	ReverbLoadThread *thread;
-	ReverbMenu *menu;
 };
 
-class ReverbSave : public BC_MenuItem
+class ReverbDelayInit : public BC_IPot
 {
 public:
-	ReverbSave(Reverb *reverb, ReverbMenu *menu);
-	~ReverbSave();
+	ReverbDelayInit(Reverb *reverb, int x, int y);
+	~ReverbDelayInit();
 	int handle_event();
 	Reverb *reverb;
-	ReverbSaveThread *thread;
-	ReverbMenu *menu;
 };
 
-class ReverbSetDefault : public BC_MenuItem
+class ReverbRefLevel1 : public BC_FPot
 {
 public:
-	ReverbSetDefault();
+	ReverbRefLevel1(Reverb *reverb, int x, int y);
+	~ReverbRefLevel1();
 	int handle_event();
+	Reverb *reverb;
 };
 
-class ReverbLoadPrev : public BC_MenuItem
+class ReverbRefLevel2 : public BC_FPot
 {
 public:
-	ReverbLoadPrev(Reverb *reverb, ReverbMenu *menu, char *filename, char *path);
-	ReverbLoadPrev(Reverb *reverb, ReverbMenu *menu);
+	ReverbRefLevel2(Reverb *reverb, int x, int y);
+	~ReverbRefLevel2();
 	int handle_event();
-	int set_path(char *path);
-	char path[1024];
 	Reverb *reverb;
-	ReverbMenu *menu;
 };
 
-
-class ReverbLoadPrevThread : public Thread
+class ReverbRefTotal : public BC_IPot
 {
 public:
-	ReverbLoadPrevThread(Reverb *reverb, ReverbMenu *menu);
-	~ReverbLoadPrevThread();
-	void run();
-	int set_path(char *path);
-	char path[1024];
+	ReverbRefTotal(Reverb *reverb, int x, int y);
+	~ReverbRefTotal();
+	int handle_event();
 	Reverb *reverb;
-	ReverbMenu *menu;
 };
 
-
-
-class ReverbSaveThread : public Thread
+class ReverbRefLength : public BC_IPot
 {
 public:
-	ReverbSaveThread(Reverb *reverb, ReverbMenu *menu);
-	~ReverbSaveThread();
-	void run();
+	ReverbRefLength(Reverb *reverb, int x, int y);
+	~ReverbRefLength();
+	int handle_event();
 	Reverb *reverb;
-	ReverbMenu *menu;
 };
 
-class ReverbSaveDialog : public BC_FileBox
+class ReverbHigh : public BC_QPot
 {
 public:
-	ReverbSaveDialog(Reverb *reverb);
-	~ReverbSaveDialog();
-
-	int ok_event();
-	int cancel_event();
+	ReverbHigh(Reverb *reverb, int x, int y);
+	~ReverbHigh();
+	int handle_event();
 	Reverb *reverb;
 };
 
-
-class ReverbLoadThread : public Thread
+class ReverbLow : public BC_QPot
 {
 public:
-	ReverbLoadThread(Reverb *reverb, ReverbMenu *menu);
-	~ReverbLoadThread();
-	void run();
+	ReverbLow(Reverb *reverb, int x, int y);
+	~ReverbLow();
+	int handle_event();
 	Reverb *reverb;
-	ReverbMenu *menu;
 };
 
-class ReverbLoadDialog : public BC_FileBox
+class ReverbQ: public BC_QPot
 {
 public:
-	ReverbLoadDialog(Reverb *reverb);
-	~ReverbLoadDialog();
-
-	int ok_event();
-	int cancel_event();
+	ReverbQ(Reverb *reverb, int x, int y);
+	~ReverbQ();
+	int handle_event();
 	Reverb *reverb;
 };
 
-
-
-
-
 #endif
diff --git a/cinelerra-5.1/plugins/reverb/reverbwindow.inc b/cinelerra-5.1/plugins/reverb/reverbwindow.inc
index a6e14921..512486df 100644
--- a/cinelerra-5.1/plugins/reverb/reverbwindow.inc
+++ b/cinelerra-5.1/plugins/reverb/reverbwindow.inc
@@ -16,13 +16,11 @@
  * 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
- *
  */
 
 #ifndef REVERBWINDOW_INC
 #define REVERBWINDOW_INC
 
-
 class ReverbThread;
 class ReverbWindow;
 
-- 
2.26.2