fix crash when copy'ing latent plugins
[ardour.git] / libs / ardour / plugin_insert.cc
index cb386b940c06720d8279f89c3336ce9a5ec4d124..9a0e6c55c40574e32cd001bf5e3df688593e625c 100644 (file)
@@ -36,6 +36,7 @@
 #include "ardour/luaproc.h"
 #include "ardour/plugin.h"
 #include "ardour/plugin_insert.h"
+#include "ardour/port.h"
 
 #ifdef LV2_SUPPORT
 #include "ardour/lv2_plugin.h"
@@ -56,7 +57,7 @@
 #include "ardour/session.h"
 #include "ardour/types.h"
 
-#include "i18n.h"
+#include "pbd/i18n.h"
 
 using namespace std;
 using namespace ARDOUR;
@@ -66,16 +67,41 @@ const string PluginInsert::port_automation_node_name = "PortAutomation";
 
 PluginInsert::PluginInsert (Session& s, boost::shared_ptr<Plugin> plug)
        : Processor (s, (plug ? plug->name() : string ("toBeRenamed")))
+       , _sc_playback_latency (0)
+       , _sc_capture_latency (0)
+       , _plugin_signal_latency (0)
        , _signal_analysis_collected_nframes(0)
        , _signal_analysis_collect_nframes_max(0)
+       , _configured (false)
+       , _no_inplace (false)
        , _strict_io (false)
-       , _strict_io_configured (false)
+       , _custom_cfg (false)
+       , _maps_from_state (false)
+       , _bypass_port (UINT32_MAX)
 {
        /* the first is the master */
 
        if (plug) {
                add_plugin (plug);
                create_automatable_parameters ();
+               const ChanCount& sc (sidechain_input_pins ());
+               if (sc.n_audio () > 0 || sc.n_midi () > 0) {
+                       add_sidechain (sc.n_audio (), sc.n_midi ());
+               }
+       }
+}
+
+PluginInsert::~PluginInsert ()
+{
+}
+
+void
+PluginInsert::set_strict_io (bool b)
+{
+       bool changed = _strict_io != b;
+       _strict_io = b;
+       if (changed) {
+               PluginConfigChanged (); /* EMIT SIGNAL */
        }
 }
 
@@ -84,9 +110,14 @@ PluginInsert::set_count (uint32_t num)
 {
        bool require_state = !_plugins.empty();
 
+       if (require_state && num > 1 && plugin (0)->get_info ()->type == ARDOUR::AudioUnit) {
+               // we don't allow to replicate AUs
+               return false;
+       }
+
        /* this is a bad idea.... we shouldn't do this while active.
-          only a route holding their redirect_lock should be calling this
-       */
+        * only a route holding their redirect_lock should be calling this
+        */
 
        if (num == 0) {
                return false;
@@ -96,27 +127,123 @@ PluginInsert::set_count (uint32_t num)
                for (uint32_t n = 0; n < diff; ++n) {
                        boost::shared_ptr<Plugin> p = plugin_factory (_plugins[0]);
                        add_plugin (p);
-                       if (active ()) {
-                               p->activate ();
-                       }
 
                        if (require_state) {
-                               /* XXX do something */
+                               XMLNode& state = _plugins[0]->get_state ();
+                               p->set_state (state, Stateful::loading_state_version);
+                       }
+
+                       if (active ()) {
+                               p->activate ();
                        }
                }
+               PluginConfigChanged (); /* EMIT SIGNAL */
 
        } else if (num < _plugins.size()) {
                uint32_t diff = _plugins.size() - num;
                for (uint32_t n= 0; n < diff; ++n) {
                        _plugins.pop_back();
                }
+               PluginConfigChanged (); /* EMIT SIGNAL */
        }
 
        return true;
 }
 
-PluginInsert::~PluginInsert ()
+
+void
+PluginInsert::set_sinks (const ChanCount& c)
+{
+       _custom_sinks = c;
+       /* no signal, change will only be visible after re-config */
+}
+
+void
+PluginInsert::set_outputs (const ChanCount& c)
+{
+       bool changed = (_custom_out != c) && _custom_cfg;
+       _custom_out = c;
+       if (changed) {
+               PluginConfigChanged (); /* EMIT SIGNAL */
+       }
+}
+
+void
+PluginInsert::set_custom_cfg (bool b)
+{
+       bool changed = _custom_cfg != b;
+       _custom_cfg = b;
+       if (changed) {
+               PluginConfigChanged (); /* EMIT SIGNAL */
+       }
+}
+
+bool
+PluginInsert::set_preset_out (const ChanCount& c)
+{
+       bool changed = _preset_out != c;
+       _preset_out = c;
+       if (changed && !_custom_cfg) {
+               PluginConfigChanged (); /* EMIT SIGNAL */
+       }
+       return changed;
+}
+
+bool
+PluginInsert::add_sidechain (uint32_t n_audio, uint32_t n_midi)
+{
+       // caller must hold process lock
+       if (_sidechain) {
+               return false;
+       }
+       std::ostringstream n;
+       if (n_audio > 0 || n_midi > 0) {
+               n << "Sidechain " << Session::next_name_id ();
+       } else {
+               n << "TO BE RESET FROM XML";
+       }
+       SideChain *sc = new SideChain (_session, n.str ());
+       _sidechain = boost::shared_ptr<SideChain> (sc);
+       _sidechain->activate ();
+       for (uint32_t n = 0; n < n_audio; ++n) {
+               _sidechain->input()->add_port ("", owner(), DataType::AUDIO); // add a port, don't connect.
+       }
+       for (uint32_t n = 0; n < n_midi; ++n) {
+               _sidechain->input()->add_port ("", owner(), DataType::MIDI); // add a port, don't connect.
+       }
+       PluginConfigChanged (); /* EMIT SIGNAL */
+       return true;
+}
+
+bool
+PluginInsert::del_sidechain ()
 {
+       if (!_sidechain) {
+               return false;
+       }
+       _sidechain.reset ();
+       _sc_playback_latency = 0;
+       _sc_capture_latency = 0;
+       PluginConfigChanged (); /* EMIT SIGNAL */
+       return true;
+}
+
+void
+PluginInsert::set_sidechain_latency (uint32_t capture, uint32_t playback)
+{
+       if (_sidechain &&
+                       (_sc_playback_latency != playback || _sc_capture_latency != capture)) {
+               _sc_capture_latency = capture;
+               _sc_playback_latency = playback;
+               LatencyRange pl; pl.min = pl.max = playback;
+               LatencyRange cl; cl.min = cl.max = capture;
+               DEBUG_TRACE (DEBUG::Latency, string_compose ("%1: capture %2 playback; %3\n", _sidechain->name (), capture, playback));
+               PortSet& ps (_sidechain->input ()->ports ());
+               for (PortSet::iterator p = ps.begin(); p != ps.end(); ++p) {
+                       p->set_private_latency_range (pl, true);
+                       p->set_private_latency_range (cl, false);
+               }
+       }
 }
 
 void
@@ -135,15 +262,33 @@ PluginInsert::control_list_automation_state_changed (Evoral::Parameter which, Au
 
 ChanCount
 PluginInsert::output_streams() const
+{
+       assert (_configured);
+       return _configured_out;
+}
+
+ChanCount
+PluginInsert::input_streams() const
+{
+       assert (_configured);
+       return _configured_in;
+}
+
+ChanCount
+PluginInsert::internal_streams() const
+{
+       assert (_configured);
+       return _configured_internal;
+}
+
+ChanCount
+PluginInsert::internal_output_streams() const
 {
        assert (!_plugins.empty());
 
        PluginInfoPtr info = _plugins.front()->get_info();
 
-       if (_strict_io_configured) {
-               return _configured_in; // XXX, check initial configuration
-       }
-       else if (info->reconfigurable_io()) {
+       if (info->reconfigurable_io()) {
                ChanCount out = _plugins.front()->output_streams ();
                // DEBUG_TRACE (DEBUG::Processors, string_compose ("Plugin insert, reconfigur(able) output streams = %1\n", out));
                return out;
@@ -151,13 +296,13 @@ PluginInsert::output_streams() const
                ChanCount out = info->n_outputs;
                // DEBUG_TRACE (DEBUG::Processors, string_compose ("Plugin insert, static output streams = %1 for %2 plugins\n", out, _plugins.size()));
                out.set_audio (out.n_audio() * _plugins.size());
-               out.set_midi (out.n_midi() * _plugins.size() + midi_bypass.n_midi());
+               out.set_midi (out.n_midi() * _plugins.size());
                return out;
        }
 }
 
 ChanCount
-PluginInsert::input_streams() const
+PluginInsert::internal_input_streams() const
 {
        assert (!_plugins.empty());
 
@@ -166,7 +311,6 @@ PluginInsert::input_streams() const
        PluginInfoPtr info = _plugins.front()->get_info();
 
        if (info->reconfigurable_io()) {
-               assert (_plugins.size() == 1);
                in = _plugins.front()->input_streams();
        } else {
                in = info->n_inputs;
@@ -206,15 +350,31 @@ PluginInsert::input_streams() const
 ChanCount
 PluginInsert::natural_output_streams() const
 {
+#ifdef MIXBUS
+       if (is_channelstrip ()) {
+               return ChanCount::min (_configured_out, ChanCount (DataType::AUDIO, 2));
+       }
+#endif
        return _plugins[0]->get_info()->n_outputs;
 }
 
 ChanCount
 PluginInsert::natural_input_streams() const
 {
+#ifdef MIXBUS
+       if (is_channelstrip ()) {
+               return ChanCount::min (_configured_in, ChanCount (DataType::AUDIO, 2));
+       }
+#endif
        return _plugins[0]->get_info()->n_inputs;
 }
 
+ChanCount
+PluginInsert::sidechain_input_pins() const
+{
+       return _cached_sidechain_pins;
+}
+
 bool
 PluginInsert::has_no_inputs() const
 {
@@ -227,16 +387,48 @@ PluginInsert::has_no_audio_inputs() const
        return _plugins[0]->get_info()->n_inputs.n_audio() == 0;
 }
 
+framecnt_t
+PluginInsert::plugin_latency () const {
+       return _plugins.front()->signal_latency ();
+}
+
 bool
-PluginInsert::is_midi_instrument() const
+PluginInsert::needs_midi_input() const
 {
-       /* XXX more finesse is possible here. VST plugins have a
-          a specific "instrument" flag, for example.
-        */
-       PluginInfoPtr pi = _plugins[0]->get_info();
+       PluginInfoPtr pip = _plugins[0]->get_info();
+       if (pip->needs_midi_input ()) {
+               return true;
+       }
+       return pip->n_inputs.n_midi() != 0 && pip->n_outputs.n_audio() != 0;
+}
+
+bool
+PluginInsert::has_output_presets (ChanCount in, ChanCount out)
+{
+       if (!_configured && _plugins[0]->get_info ()->reconfigurable_io ()) {
+               // collect possible configurations, prefer given in/out
+               _plugins[0]->can_support_io_configuration (in, out);
+       }
+
+       PluginOutputConfiguration ppc (_plugins[0]->possible_output ());
 
-       return pi->n_inputs.n_midi() != 0 &&
-               pi->n_outputs.n_audio() > 0;
+       if (ppc.size () == 0) {
+               return false;
+       }
+       if (!strict_io () && ppc.size () == 1) {
+               return false;
+       }
+
+       if (strict_io () && ppc.size () == 1) {
+               // "stereo" is currently preferred default for instruments
+               if (ppc.find (2) != ppc.end ()) {
+                       return false;
+               }
+       }
+       if (!needs_midi_input ()) {
+                       return false;
+       }
+       return true;
 }
 
 void
@@ -244,31 +436,53 @@ PluginInsert::create_automatable_parameters ()
 {
        assert (!_plugins.empty());
 
+       boost::shared_ptr<Plugin> plugin = _plugins.front();
        set<Evoral::Parameter> a = _plugins.front()->automatable ();
 
-       for (set<Evoral::Parameter>::iterator i = a.begin(); i != a.end(); ++i) {
-               if (i->type() == PluginAutomation) {
+       for (uint32_t i = 0; i < plugin->parameter_count(); ++i) {
+               if (!plugin->parameter_is_control (i) || !plugin->parameter_is_input (i)) {
+                       continue;
+               }
+               Evoral::Parameter param (PluginAutomation, 0, i);
 
-                       Evoral::Parameter param(*i);
+               ParameterDescriptor desc;
+               plugin->get_parameter_descriptor(i, desc);
 
-                       ParameterDescriptor desc;
-                       _plugins.front()->get_parameter_descriptor(i->id(), desc);
+               const bool automatable = a.find(param) != a.end();
 
+               if (automatable) {
                        can_automate (param);
-                       boost::shared_ptr<AutomationList> list(new AutomationList(param, desc));
-                       boost::shared_ptr<AutomationControl> c (new PluginControl(this, param, desc, list));
-                       add_control (c);
-                       _plugins.front()->set_automation_control (i->id(), c);
-               } else if (i->type() == PluginPropertyAutomation) {
-                       Evoral::Parameter param(*i);
-                       const ParameterDescriptor& desc = _plugins.front()->get_property_descriptor(param.id());
-                       if (desc.datatype != Variant::NOTHING) {
-                               boost::shared_ptr<AutomationList> list;
-                               if (Variant::type_is_numeric(desc.datatype)) {
-                                       list = boost::shared_ptr<AutomationList>(new AutomationList(param, desc));
-                               }
-                               add_control (boost::shared_ptr<AutomationControl> (new PluginPropertyControl(this, param, desc, list)));
+               }
+               boost::shared_ptr<AutomationList> list(new AutomationList(param, desc));
+               boost::shared_ptr<AutomationControl> c (new PluginControl(this, param, desc, list));
+               if (!automatable) {
+                       c->set_flags (Controllable::Flag ((int)c->flags() | Controllable::NotAutomatable));
+               }
+               add_control (c);
+               plugin->set_automation_control (i, c);
+       }
+
+
+       const Plugin::PropertyDescriptors& pdl (plugin->get_supported_properties ());
+       for (Plugin::PropertyDescriptors::const_iterator p = pdl.begin(); p != pdl.end(); ++p) {
+               Evoral::Parameter param (PluginPropertyAutomation, 0, p->first);
+               const ParameterDescriptor& desc = plugin->get_property_descriptor(param.id());
+               if (desc.datatype != Variant::NOTHING) {
+                       boost::shared_ptr<AutomationList> list;
+                       if (Variant::type_is_numeric(desc.datatype)) {
+                               list = boost::shared_ptr<AutomationList>(new AutomationList(param, desc));
                        }
+                       add_control (boost::shared_ptr<AutomationControl> (new PluginPropertyControl(this, param, desc, list)));
+               }
+       }
+
+       _bypass_port = plugin->designated_bypass_port ();
+
+       if (_bypass_port != UINT32_MAX) {
+               boost::shared_ptr<AutomationControl> ac = automation_control (Evoral::Parameter (PluginAutomation, 0, _bypass_port));
+               if (0 == (ac->flags () & Controllable::NotAutomatable)) {
+                       ac->alist()->automation_state_changed.connect_same_thread (*this, boost::bind (&PluginInsert::bypassable_changed, this));
+                       ac->Changed.connect_same_thread (*this, boost::bind (&PluginInsert::enable_changed, this));
                }
        }
 }
@@ -341,6 +555,17 @@ PluginInsert::activate ()
        }
 
        Processor::activate ();
+       /* when setting state e.g ProcessorBox::paste_processor_state ()
+        * the plugin is not yet owned by a route.
+        * but no matter.  Route::add_processors() will call activate () again
+        */
+       if (!owner ()) {
+               return;
+       }
+       if (_plugin_signal_latency != signal_latency ()) {
+               _plugin_signal_latency = signal_latency ();
+               latency_changed ();
+       }
 }
 
 void
@@ -351,6 +576,10 @@ PluginInsert::deactivate ()
        for (Plugins::iterator i = _plugins.begin(); i != _plugins.end(); ++i) {
                (*i)->deactivate ();
        }
+       if (_plugin_signal_latency != signal_latency ()) {
+               _plugin_signal_latency = signal_latency ();
+               latency_changed ();
+       }
 }
 
 void
@@ -362,39 +591,148 @@ PluginInsert::flush ()
 }
 
 void
-PluginInsert::connect_and_run (BufferSet& bufs, pframes_t nframes, framecnt_t offset, bool with_auto, framepos_t now)
+PluginInsert::enable (bool yn)
 {
-       // Calculate if, and how many frames we need to collect for analysis
-       framecnt_t collect_signal_nframes = (_signal_analysis_collect_nframes_max -
-                                            _signal_analysis_collected_nframes);
-       if (nframes < collect_signal_nframes) { // we might not get all frames now
-               collect_signal_nframes = nframes;
+       if (_bypass_port == UINT32_MAX) {
+               if (yn) {
+                       activate ();
+               } else {
+                       deactivate ();
+               }
+       } else {
+               if (!_pending_active) {
+                       activate ();
+               }
+               boost::shared_ptr<AutomationControl> ac = automation_control (Evoral::Parameter (PluginAutomation, 0, _bypass_port));
+               ac->set_value (yn ? 1.0 : 0.0, Controllable::NoGroup);
+               ActiveChanged ();
        }
+}
 
-       ChanCount const in_streams = input_streams ();
-       ChanCount const out_streams = output_streams ();
+bool
+PluginInsert::enabled () const
+{
+       if (_bypass_port == UINT32_MAX) {
+               return Processor::enabled ();
+       } else {
+               boost::shared_ptr<const AutomationControl> ac = boost::const_pointer_cast<AutomationControl> (automation_control (Evoral::Parameter (PluginAutomation, 0, _bypass_port)));
+               return (ac->get_value () > 0 && _pending_active);
+       }
+}
 
-       bool valid;
-       if (_match.method == Split) {
-               assert (_in_map.size () == 1);
-               /* fix the input mapping so that we have maps for each of the plugin's inputs */
-
-               /* copy the first stream's buffer contents to the others */
-               /* XXX: audio only */
-               uint32_t first_idx = _in_map[0].get (DataType::AUDIO, 0, &valid);
-               if (valid) {
-                       for (uint32_t i = in_streams.n_audio(); i < natural_input_streams().n_audio(); ++i) {
-                               bufs.get_audio(_in_map[0].get (DataType::AUDIO, i, &valid)).read_from(bufs.get_audio(first_idx), nframes, offset, offset);
+bool
+PluginInsert::bypassable () const
+{
+       if (_bypass_port == UINT32_MAX) {
+               return true;
+       } else {
+               boost::shared_ptr<const AutomationControl> ac = boost::const_pointer_cast<AutomationControl> (automation_control (Evoral::Parameter (PluginAutomation, 0, _bypass_port)));
+
+               return !ac->automation_playback ();
+       }
+}
+
+void
+PluginInsert::enable_changed ()
+{
+       ActiveChanged ();
+}
+
+void
+PluginInsert::bypassable_changed ()
+{
+       BypassableChanged ();
+}
+
+void
+PluginInsert::inplace_silence_unconnected (BufferSet& bufs, const PinMappings& out_map, framecnt_t nframes, framecnt_t offset) const
+{
+       // TODO optimize: store "unconnected" in a fixed set.
+       // it only changes on reconfiguration.
+       for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
+               for (uint32_t out = 0; out < bufs.count().get (*t); ++out) {
+                       bool mapped = false;
+                       if (*t == DataType::MIDI && out == 0 && has_midi_bypass ()) {
+                               mapped = true; // in-place Midi bypass
+                       }
+                       for (uint32_t pc = 0; pc < get_count() && !mapped; ++pc) {
+                               PinMappings::const_iterator i = out_map.find (pc);
+                               if (i == out_map.end ()) {
+                                       continue;
+                               }
+                               const ChanMapping& outmap (i->second);
+                               for (uint32_t o = 0; o < natural_output_streams().get (*t); ++o) {
+                                       bool valid;
+                                       uint32_t idx = outmap.get (*t, o, &valid);
+                                       if (valid && idx == out) {
+                                               mapped = true;
+                                               break;
+                                       }
+                               }
+                       }
+                       if (!mapped) {
+                               bufs.get (*t, out).silence (nframes, offset);
                        }
                }
        }
+}
 
-       bufs.set_count(ChanCount::max(bufs.count(), in_streams));
-       bufs.set_count(ChanCount::max(bufs.count(), out_streams));
+void
+PluginInsert::connect_and_run (BufferSet& bufs, framepos_t start, framepos_t end, double speed, pframes_t nframes, framecnt_t offset, bool with_auto)
+{
+       // TODO: atomically copy maps & _no_inplace
+       PinMappings in_map (_in_map);
+       PinMappings out_map (_out_map);
+       ChanMapping thru_map (_thru_map);
+       if (_mapping_changed) { // ToDo use a counters, increment until match.
+               _no_inplace = check_inplace ();
+               _mapping_changed = false;
+       }
+
+       if (_latency_changed) {
+               /* delaylines are configured with the max possible latency (as reported by the plugin)
+                * so this won't allocate memory (unless the plugin lied about its max latency)
+                * It may still 'click' though, since the fixed delaylines are not de-clicked.
+                * Then again plugin-latency changes are not click-free to begin with.
+                *
+                * This is also worst case, there is currently no concept of per-stream latency.
+                *
+                * e.g.  Two identical latent plugins:
+                *   1st plugin: process left (latent), bypass right.
+                *   2nd plugin: bypass left, process right (latent).
+                * -> currently this yields 2 times latency of the plugin,
+                */
+               _latency_changed = false;
+               _delaybuffers.set (ChanCount::max(bufs.count(), _configured_out), plugin_latency ());
+       }
 
-       /* Note that we've already required that plugins
-          be able to handle in-place processing.
-       */
+       if (_match.method == Split && !_no_inplace) {
+               // TODO: also use this optimization if one source-buffer
+               // feeds _all_ *connected* inputs.
+               // currently this is *first* buffer to all only --
+               // see PluginInsert::check_inplace
+               for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
+                       if (_configured_internal.get (*t) == 0) {
+                               continue;
+                       }
+                       bool valid;
+                       uint32_t first_idx = in_map[0].get (*t, 0, &valid);
+                       assert (valid && first_idx == 0); // check_inplace ensures this
+                       /* copy the first stream's buffer contents to the others */
+                       for (uint32_t i = 1; i < natural_input_streams ().get (*t); ++i) {
+                               uint32_t idx = in_map[0].get (*t, i, &valid);
+                               if (valid) {
+                                       assert (idx == 0);
+                                       bufs.get (*t, i).read_from (bufs.get (*t, first_idx), nframes, offset, offset);
+                               }
+                       }
+               }
+               /* the copy operation produces a linear monotonic input map */
+               in_map[0] = ChanMapping (natural_input_streams ());
+       }
+
+       bufs.set_count(ChanCount::max(bufs.count(), _configured_internal));
+       bufs.set_count(ChanCount::max(bufs.count(), _configured_out));
 
        if (with_auto) {
 
@@ -408,7 +746,7 @@ PluginInsert::connect_and_run (BufferSet& bufs, pframes_t nframes, framecnt_t of
                        if (c->list() && c->automation_playback()) {
                                bool valid;
 
-                               const float val = c->list()->rt_safe_eval (now, valid);
+                               const float val = c->list()->rt_safe_eval (start, valid);
 
                                if (valid) {
                                        /* This is the ONLY place where we are
@@ -427,34 +765,153 @@ PluginInsert::connect_and_run (BufferSet& bufs, pframes_t nframes, framecnt_t of
                }
        }
 
+       /* Calculate if, and how many frames we need to collect for analysis */
+       framecnt_t collect_signal_nframes = (_signal_analysis_collect_nframes_max -
+                                            _signal_analysis_collected_nframes);
+       if (nframes < collect_signal_nframes) { // we might not get all frames now
+               collect_signal_nframes = nframes;
+       }
+
        if (collect_signal_nframes > 0) {
                // collect input
                //std::cerr << "collect input, bufs " << bufs.count().n_audio() << " count,  " << bufs.available().n_audio() << " available" << std::endl;
-               //std::cerr << "               streams " << input_streams().n_audio() << std::endl;
+               //std::cerr << "               streams " << internal_input_streams().n_audio() << std::endl;
                //std::cerr << "filling buffer with " << collect_signal_nframes << " frames at " << _signal_analysis_collected_nframes << std::endl;
 
                _signal_analysis_inputs.set_count(input_streams());
 
                for (uint32_t i = 0; i < input_streams().n_audio(); ++i) {
-                       _signal_analysis_inputs.get_audio(i).read_from(
+                       _signal_analysis_inputs.get_audio(i).read_from (
                                bufs.get_audio(i),
                                collect_signal_nframes,
                                _signal_analysis_collected_nframes); // offset is for target buffer
                }
 
        }
+#ifdef MIXBUS
+       if (is_channelstrip ()) {
+               if (_configured_in.n_audio() > 0) {
+                       ChanMapping mb_in_map (ChanCount::min (_configured_in, ChanCount (DataType::AUDIO, 2)));
+                       ChanMapping mb_out_map (ChanCount::min (_configured_out, ChanCount (DataType::AUDIO, 2)));
 
-       uint32_t pc = 0;
-       for (Plugins::iterator i = _plugins.begin(); i != _plugins.end(); ++i, ++pc) {
-               if ((*i)->connect_and_run(bufs, _in_map[pc], _out_map[pc], nframes, offset)) {
-                       deactivate ();
+                       _plugins.front()->connect_and_run (bufs, start, end, speed, mb_in_map, mb_out_map, nframes, offset);
+
+                       for (uint32_t out = _configured_in.n_audio (); out < bufs.count().get (DataType::AUDIO); ++out) {
+                               bufs.get (DataType::AUDIO, out).silence (nframes, offset);
+                       }
+               }
+       } else
+#endif
+       if (_no_inplace) {
+               // TODO optimize -- build maps once.
+               uint32_t pc = 0;
+               BufferSet& inplace_bufs  = _session.get_noinplace_buffers();
+               ARDOUR::ChanMapping used_outputs;
+
+               assert (inplace_bufs.count () >= natural_input_streams () + _configured_out);
+
+               /* build used-output map */
+               for (Plugins::iterator i = _plugins.begin(); i != _plugins.end(); ++i, ++pc) {
+                       for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
+                               for (uint32_t out = 0; out < natural_output_streams().get (*t); ++out) {
+                                       bool valid;
+                                       uint32_t out_idx = out_map[pc].get (*t, out, &valid);
+                                       if (valid) {
+                                               used_outputs.set (*t, out_idx, 1); // mark as used
+                                       }
+                               }
+                       }
                }
+               /* copy thru data to outputs before processing in-place */
+               for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
+                       for (uint32_t out = 0; out < bufs.count().get (*t); ++out) {
+                               bool valid;
+                               uint32_t in_idx = thru_map.get (*t, out, &valid);
+                               uint32_t m = out + natural_input_streams ().get (*t);
+                               if (valid) {
+                                       _delaybuffers.delay (*t, out, inplace_bufs.get (*t, m), bufs.get (*t, in_idx), nframes, offset, offset);
+                                       used_outputs.set (*t, out, 1); // mark as used
+                               } else {
+                                       used_outputs.get (*t, out, &valid);
+                                       if (valid) {
+                                               /* the plugin is expected to write here, but may not :(
+                                                * (e.g. drumgizmo w/o kit loaded)
+                                                */
+                                               inplace_bufs.get (*t, m).silence (nframes);
+                                       }
+                               }
+                       }
+               }
+
+               pc = 0;
+               for (Plugins::iterator i = _plugins.begin(); i != _plugins.end(); ++i, ++pc) {
+
+                       ARDOUR::ChanMapping i_in_map (natural_input_streams());
+                       ARDOUR::ChanMapping i_out_map (out_map[pc]);
+                       ARDOUR::ChanCount mapped;
+
+                       /* map inputs sequentially */
+                       for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
+                               for (uint32_t in = 0; in < natural_input_streams().get (*t); ++in) {
+                                       bool valid;
+                                       uint32_t in_idx = in_map[pc].get (*t, in, &valid);
+                                       uint32_t m = mapped.get (*t);
+                                       if (valid) {
+                                               inplace_bufs.get (*t, m).read_from (bufs.get (*t, in_idx), nframes, offset, offset);
+                                       } else {
+                                               inplace_bufs.get (*t, m).silence (nframes, offset);
+                                       }
+                                       mapped.set (*t, m + 1);
+                               }
+                       }
+
+                       /* outputs are mapped to inplace_bufs after the inputs */
+                       for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
+                               i_out_map.offset_to (*t, natural_input_streams ().get (*t));
+                       }
+
+                       if ((*i)->connect_and_run (inplace_bufs, start, end, speed, i_in_map, i_out_map, nframes, offset)) {
+                               deactivate ();
+                       }
+               }
+
+               /* all instances have completed, now copy data that was written
+                * and zero unconnected buffers */
+               ARDOUR::ChanMapping nonzero_out (used_outputs);
+               if (has_midi_bypass ()) {
+                       nonzero_out.set (DataType::MIDI, 0, 1); // Midi bypass.
+               }
+               for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
+                       for (uint32_t out = 0; out < bufs.count().get (*t); ++out) {
+                               bool valid;
+                               used_outputs.get (*t, out, &valid);
+                               if (!valid) {
+                                       nonzero_out.get (*t, out, &valid);
+                                       if (!valid) {
+                                               bufs.get (*t, out).silence (nframes, offset);
+                                       }
+                               } else {
+                                       uint32_t m = out + natural_input_streams ().get (*t);
+                                       bufs.get (*t, out).read_from (inplace_bufs.get (*t, m), nframes, offset, offset);
+                               }
+                       }
+               }
+       } else {
+               /* in-place processing */
+               uint32_t pc = 0;
+               for (Plugins::iterator i = _plugins.begin(); i != _plugins.end(); ++i, ++pc) {
+                       if ((*i)->connect_and_run(bufs, start, end, speed, in_map[pc], out_map[pc], nframes, offset)) {
+                               deactivate ();
+                       }
+               }
+               // now silence unconnected outputs
+               inplace_silence_unconnected (bufs, _out_map, nframes, offset);
        }
 
        if (collect_signal_nframes > 0) {
                // collect output
                //std::cerr << "       output, bufs " << bufs.count().n_audio() << " count,  " << bufs.available().n_audio() << " available" << std::endl;
-               //std::cerr << "               streams " << output_streams().n_audio() << std::endl;
+               //std::cerr << "               streams " << internal_output_streams().n_audio() << std::endl;
 
                _signal_analysis_outputs.set_count(output_streams());
 
@@ -476,62 +933,169 @@ PluginInsert::connect_and_run (BufferSet& bufs, pframes_t nframes, framecnt_t of
                                             &_signal_analysis_outputs);
                }
        }
-       /* leave remaining channel buffers alone */
+
+       if (_plugin_signal_latency != signal_latency ()) {
+               _plugin_signal_latency = signal_latency ();
+               latency_changed ();
+       }
+}
+
+void
+PluginInsert::bypass (BufferSet& bufs, pframes_t nframes)
+{
+       /* bypass the plugin(s) not the whole processor.
+        * -> use mappings just like connect_and_run
+        */
+
+       // TODO: atomically copy maps & _no_inplace
+       const ChanMapping in_map (no_sc_input_map ());
+       const ChanMapping out_map (output_map ());
+       if (_mapping_changed) {
+               _no_inplace = check_inplace ();
+               _mapping_changed = false;
+       }
+
+       bufs.set_count(ChanCount::max(bufs.count(), _configured_internal));
+       bufs.set_count(ChanCount::max(bufs.count(), _configured_out));
+
+       if (_no_inplace) {
+               ChanMapping thru_map (_thru_map);
+
+               BufferSet& inplace_bufs  = _session.get_noinplace_buffers();
+               // copy all inputs
+               for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
+                       for (uint32_t in = 0; in < _configured_internal.get (*t); ++in) {
+                               inplace_bufs.get (*t, in).read_from (bufs.get (*t, in), nframes, 0, 0);
+                       }
+               }
+               ARDOUR::ChanMapping used_outputs;
+               // copy thru
+               for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
+                       for (uint32_t out = 0; out < _configured_out.get (*t); ++out) {
+                               bool valid;
+                               uint32_t in_idx = thru_map.get (*t, out, &valid);
+                               if (valid) {
+                                       bufs.get (*t, out).read_from (inplace_bufs.get (*t, in_idx), nframes, 0, 0);
+                                       used_outputs.set (*t, out, 1); // mark as used
+                               }
+                       }
+               }
+               // plugin no-op: assume every plugin has an internal identity map
+               for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
+                       for (uint32_t out = 0; out < _configured_out.get (*t); ++out) {
+                               bool valid;
+                               uint32_t src_idx = out_map.get_src (*t, out, &valid);
+                               if (!valid) {
+                                       continue;
+                               }
+                               uint32_t in_idx = in_map.get (*t, src_idx, &valid);
+                               if (!valid) {
+                                       continue;
+                               }
+                               bufs.get (*t, out).read_from (inplace_bufs.get (*t, in_idx), nframes, 0, 0);
+                               used_outputs.set (*t, out, 1); // mark as used
+                       }
+               }
+               // now silence all unused outputs
+               if (has_midi_bypass ()) {
+                       used_outputs.set (DataType::MIDI, 0, 1); // Midi bypass.
+               }
+               for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
+                       for (uint32_t out = 0; out < _configured_out.get (*t); ++out) {
+                               bool valid;
+                               used_outputs.get (*t, out, &valid);
+                               if (!valid) {
+                                               bufs.get (*t, out).silence (nframes, 0);
+                               }
+                       }
+               }
+       } else {
+               if (_match.method == Split) {
+                       for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
+                               if (_configured_internal.get (*t) == 0) {
+                                       continue;
+                               }
+                               // copy/feeds _all_ *connected* inputs, copy the first buffer
+                               bool valid;
+                               uint32_t first_idx = in_map.get (*t, 0, &valid);
+                               assert (valid && first_idx == 0); // check_inplace ensures this
+                               for (uint32_t i = 1; i < natural_input_streams ().get (*t); ++i) {
+                                       uint32_t idx = in_map.get (*t, i, &valid);
+                                       if (valid) {
+                                               assert (idx == 0);
+                                               bufs.get (*t, i).read_from (bufs.get (*t, first_idx), nframes, 0, 0);
+                                       }
+                               }
+                       }
+               }
+
+               // apply output map and/or monotonic but not identity i/o mappings
+               for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
+                       for (uint32_t out = 0; out < _configured_out.get (*t); ++out) {
+                               bool valid;
+                               uint32_t src_idx = out_map.get_src (*t, out, &valid);
+                               if (!valid) {
+                                       bufs.get (*t, out).silence (nframes, 0);
+                                       continue;
+                               }
+                               uint32_t in_idx = in_map.get (*t, src_idx, &valid);
+                               if (!valid) {
+                                       bufs.get (*t, out).silence (nframes, 0);
+                                       continue;
+                               }
+                               if (in_idx != src_idx) {
+                                       bufs.get (*t, out).read_from (bufs.get (*t, in_idx), nframes, 0, 0);
+                               }
+                       }
+               }
+       }
 }
 
 void
-PluginInsert::silence (framecnt_t nframes)
+PluginInsert::silence (framecnt_t nframes, framepos_t start_frame)
 {
        if (!active ()) {
                return;
        }
 
+       _delaybuffers.flush ();
+
        ChanMapping in_map (natural_input_streams ());
        ChanMapping out_map (natural_output_streams ());
-
+       ChanCount maxbuf = ChanCount::max (natural_input_streams (), natural_output_streams());
+#ifdef MIXBUS
+       if (is_channelstrip ()) {
+               if (_configured_in.n_audio() > 0) {
+                       _plugins.front()->connect_and_run (_session.get_scratch_buffers (maxbuf, true), start_frame, start_frame + nframes, 1.0, in_map, out_map, nframes, 0);
+               }
+       } else
+#endif
        for (Plugins::iterator i = _plugins.begin(); i != _plugins.end(); ++i) {
-               (*i)->connect_and_run (_session.get_scratch_buffers ((*i)->get_info()->n_inputs, true), in_map, out_map, nframes, 0);
+               (*i)->connect_and_run (_session.get_scratch_buffers (maxbuf, true), start_frame, start_frame + nframes, 1.0, in_map, out_map, nframes, 0);
        }
 }
 
 void
-PluginInsert::run (BufferSet& bufs, framepos_t start_frame, framepos_t /*end_frame*/, pframes_t nframes, bool)
+PluginInsert::run (BufferSet& bufs, framepos_t start_frame, framepos_t end_frame, double speed, pframes_t nframes, bool)
 {
+       if (_sidechain) {
+               // collect sidechain input for complete cycle (!)
+               // TODO we need delaylines here for latency compensation
+               _sidechain->run (bufs, start_frame, end_frame, speed, nframes, true);
+       }
+
        if (_pending_active) {
                /* run as normal if we are active or moving from inactive to active */
 
                if (_session.transport_rolling() || _session.bounce_processing()) {
-                       automation_run (bufs, start_frame, nframes);
+                       automation_run (bufs, start_frame, end_frame, speed, nframes);
                } else {
-                       connect_and_run (bufs, nframes, 0, false);
+                       connect_and_run (bufs, start_frame, end_frame, speed, nframes, 0, false);
                }
 
        } else {
-               uint32_t in = input_streams ().n_audio ();
-               uint32_t out = output_streams().n_audio ();
-
-               if (has_no_audio_inputs() || in == 0) {
-
-                       /* silence all (audio) outputs. Should really declick
-                        * at the transitions of "active"
-                        */
-
-                       for (uint32_t n = 0; n < out; ++n) {
-                               bufs.get_audio (n).silence (nframes);
-                       }
-
-               } else if (out > in) {
-
-                       /* not active, but something has make up for any channel count increase */
-
-                       // TODO: option round-robin (n % in) or silence additional buffers ??
-                       // for now , simply replicate last buffer
-                       for (uint32_t n = in; n < out; ++n) {
-                               bufs.get_audio(n).read_from(bufs.get_audio(in - 1), nframes);
-                       }
-               }
-
-               bufs.count().set_audio (out);
+               bypass (bufs, nframes);
+               _delaybuffers.flush ();
        }
 
        _active = _pending_active;
@@ -539,43 +1103,40 @@ PluginInsert::run (BufferSet& bufs, framepos_t start_frame, framepos_t /*end_fra
        /* we have no idea whether the plugin generated silence or not, so mark
         * all buffers appropriately.
         */
-
 }
 
 void
-PluginInsert::automation_run (BufferSet& bufs, framepos_t start, pframes_t nframes)
+PluginInsert::automation_run (BufferSet& bufs, framepos_t start, framepos_t end, double speed, pframes_t nframes)
 {
        Evoral::ControlEvent next_event (0, 0.0f);
-       framepos_t now = start;
-       framepos_t end = now + nframes;
        framecnt_t offset = 0;
 
        Glib::Threads::Mutex::Lock lm (control_lock(), Glib::Threads::TRY_LOCK);
 
        if (!lm.locked()) {
-               connect_and_run (bufs, nframes, offset, false);
+               connect_and_run (bufs, start, end, speed, nframes, offset, false);
                return;
        }
 
-       if (!find_next_event (now, end, next_event) || _plugins.front()->requires_fixed_sized_buffers()) {
+       if (!find_next_event (start, end, next_event) || _plugins.front()->requires_fixed_sized_buffers()) {
 
                /* no events have a time within the relevant range */
 
-               connect_and_run (bufs, nframes, offset, true, now);
+               connect_and_run (bufs, start, end, speed, nframes, offset, true);
                return;
        }
 
        while (nframes) {
 
-               framecnt_t cnt = min (((framecnt_t) ceil (next_event.when) - now), (framecnt_t) nframes);
+               framecnt_t cnt = min (((framecnt_t) ceil (next_event.when) - start), (framecnt_t) nframes);
 
-               connect_and_run (bufs, cnt, offset, true, now);
+               connect_and_run (bufs, start, start + cnt, speed, cnt, offset, true); // XXX (start + cnt) * speed
 
                nframes -= cnt;
                offset += cnt;
-               now += cnt;
+               start += cnt;
 
-               if (!find_next_event (now, end, next_event)) {
+               if (!find_next_event (start, end, next_event)) {
                        break;
                }
        }
@@ -583,7 +1144,7 @@ PluginInsert::automation_run (BufferSet& bufs, framepos_t start, pframes_t nfram
        /* cleanup anything that is left to do */
 
        if (nframes) {
-               connect_and_run (bufs, nframes, offset, true, now);
+               connect_and_run (bufs, start, start + nframes, speed, nframes, offset, true);
        }
 }
 
@@ -655,77 +1216,515 @@ PluginInsert::reset_parameters_to_default ()
                        continue;
                }
 
-               if (ac->automation_state() & Play) {
-                       all = false;
-                       continue;
+               if (ac->automation_state() & Play) {
+                       all = false;
+                       continue;
+               }
+
+               ac->set_value (dflt, Controllable::NoGroup);
+       }
+       return all;
+}
+
+boost::shared_ptr<Plugin>
+PluginInsert::plugin_factory (boost::shared_ptr<Plugin> other)
+{
+       boost::shared_ptr<LadspaPlugin> lp;
+       boost::shared_ptr<LuaProc> lua;
+#ifdef LV2_SUPPORT
+       boost::shared_ptr<LV2Plugin> lv2p;
+#endif
+#ifdef WINDOWS_VST_SUPPORT
+       boost::shared_ptr<WindowsVSTPlugin> vp;
+#endif
+#ifdef LXVST_SUPPORT
+       boost::shared_ptr<LXVSTPlugin> lxvp;
+#endif
+#ifdef AUDIOUNIT_SUPPORT
+       boost::shared_ptr<AUPlugin> ap;
+#endif
+
+       if ((lp = boost::dynamic_pointer_cast<LadspaPlugin> (other)) != 0) {
+               return boost::shared_ptr<Plugin> (new LadspaPlugin (*lp));
+       } else if ((lua = boost::dynamic_pointer_cast<LuaProc> (other)) != 0) {
+               return boost::shared_ptr<Plugin> (new LuaProc (*lua));
+#ifdef LV2_SUPPORT
+       } else if ((lv2p = boost::dynamic_pointer_cast<LV2Plugin> (other)) != 0) {
+               return boost::shared_ptr<Plugin> (new LV2Plugin (*lv2p));
+#endif
+#ifdef WINDOWS_VST_SUPPORT
+       } else if ((vp = boost::dynamic_pointer_cast<WindowsVSTPlugin> (other)) != 0) {
+               return boost::shared_ptr<Plugin> (new WindowsVSTPlugin (*vp));
+#endif
+#ifdef LXVST_SUPPORT
+       } else if ((lxvp = boost::dynamic_pointer_cast<LXVSTPlugin> (other)) != 0) {
+               return boost::shared_ptr<Plugin> (new LXVSTPlugin (*lxvp));
+#endif
+#ifdef AUDIOUNIT_SUPPORT
+       } else if ((ap = boost::dynamic_pointer_cast<AUPlugin> (other)) != 0) {
+               return boost::shared_ptr<Plugin> (new AUPlugin (*ap));
+#endif
+       }
+
+       fatal << string_compose (_("programming error: %1"),
+                         X_("unknown plugin type in PluginInsert::plugin_factory"))
+             << endmsg;
+       abort(); /*NOTREACHED*/
+       return boost::shared_ptr<Plugin> ((Plugin*) 0);
+}
+
+void
+PluginInsert::set_input_map (uint32_t num, ChanMapping m) {
+       if (num < _in_map.size()) {
+               bool changed = _in_map[num] != m;
+               _in_map[num] = m;
+               changed |= sanitize_maps ();
+               if (changed) {
+                       PluginMapChanged (); /* EMIT SIGNAL */
+                       _mapping_changed = true;
+                       _session.set_dirty();
+               }
+       }
+}
+
+void
+PluginInsert::set_output_map (uint32_t num, ChanMapping m) {
+       if (num < _out_map.size()) {
+               bool changed = _out_map[num] != m;
+               _out_map[num] = m;
+               changed |= sanitize_maps ();
+               if (changed) {
+                       PluginMapChanged (); /* EMIT SIGNAL */
+                       _mapping_changed = true;
+                       _session.set_dirty();
+               }
+       }
+}
+
+void
+PluginInsert::set_thru_map (ChanMapping m) {
+       bool changed = _thru_map != m;
+       _thru_map = m;
+       changed |= sanitize_maps ();
+       if (changed) {
+               PluginMapChanged (); /* EMIT SIGNAL */
+               _mapping_changed = true;
+               _session.set_dirty();
+       }
+}
+
+bool
+PluginInsert::pre_seed (const ChanCount& in, const ChanCount& out,
+               const ChanMapping& im, const ChanMapping& om, const ChanMapping& tm)
+{
+       if (_configured) { return false; }
+       _configured_in = in;
+       _configured_out = out;
+       _in_map[0] = im;
+       _out_map[0] = om;
+       _thru_map = tm;
+       _maps_from_state = in.n_total () > 0 && out.n_total () > 0;
+       return true;
+}
+
+ChanMapping
+PluginInsert::input_map () const
+{
+       ChanMapping rv;
+       uint32_t pc = 0;
+       for (PinMappings::const_iterator i = _in_map.begin (); i != _in_map.end (); ++i, ++pc) {
+               ChanMapping m (i->second);
+               const ChanMapping::Mappings& mp ((*i).second.mappings());
+               for (ChanMapping::Mappings::const_iterator tm = mp.begin(); tm != mp.end(); ++tm) {
+                       for (ChanMapping::TypeMapping::const_iterator i = tm->second.begin(); i != tm->second.end(); ++i) {
+                               rv.set (tm->first, i->first + pc * natural_input_streams().get(tm->first), i->second);
+                       }
+               }
+       }
+       return rv;
+}
+
+
+ChanMapping
+PluginInsert::no_sc_input_map () const
+{
+       ChanMapping rv;
+       uint32_t pc = 0;
+       for (PinMappings::const_iterator i = _in_map.begin (); i != _in_map.end (); ++i, ++pc) {
+               ChanMapping m (i->second);
+               const ChanMapping::Mappings& mp ((*i).second.mappings());
+               for (ChanMapping::Mappings::const_iterator tm = mp.begin(); tm != mp.end(); ++tm) {
+                       uint32_t ins = natural_input_streams().get(tm->first) - _cached_sidechain_pins.get(tm->first);
+                       for (ChanMapping::TypeMapping::const_iterator i = tm->second.begin(); i != tm->second.end(); ++i) {
+                               if (i->first < ins) {
+                                       rv.set (tm->first, i->first + pc * ins, i->second);
+                               }
+                       }
+               }
+       }
+       return rv;
+}
+
+ChanMapping
+PluginInsert::output_map () const
+{
+       ChanMapping rv;
+       uint32_t pc = 0;
+       for (PinMappings::const_iterator i = _out_map.begin (); i != _out_map.end (); ++i, ++pc) {
+               ChanMapping m (i->second);
+               const ChanMapping::Mappings& mp ((*i).second.mappings());
+               for (ChanMapping::Mappings::const_iterator tm = mp.begin(); tm != mp.end(); ++tm) {
+                       for (ChanMapping::TypeMapping::const_iterator i = tm->second.begin(); i != tm->second.end(); ++i) {
+                               rv.set (tm->first, i->first + pc * natural_output_streams().get(tm->first), i->second);
+                       }
+               }
+       }
+       if (has_midi_bypass ()) {
+               rv.set (DataType::MIDI, 0, 0);
+       }
+
+       return rv;
+}
+
+bool
+PluginInsert::has_midi_bypass () const
+{
+       if (_configured_in.n_midi () == 1 && _configured_out.n_midi () == 1
+                       && natural_output_streams ().n_midi () == 0) {
+               return true;
+       }
+       return false;
+}
+
+bool
+PluginInsert::has_midi_thru () const
+{
+       if (_configured_in.n_midi () == 1 && _configured_out.n_midi () == 1
+                       && natural_input_streams ().n_midi () == 0 && natural_output_streams ().n_midi () == 0) {
+               return true;
+       }
+       return false;
+}
+
+#ifdef MIXBUS
+bool
+PluginInsert::is_channelstrip () const {
+       return _plugins.front()->is_channelstrip();
+}
+#endif
+
+bool
+PluginInsert::check_inplace ()
+{
+       bool inplace_ok = !_plugins.front()->inplace_broken ();
+
+       if (_thru_map.n_total () > 0) {
+               // TODO once midi-bypass is part of the mapping, ignore it
+               inplace_ok = false;
+       }
+
+       if (_match.method == Split && inplace_ok) {
+               assert (get_count() == 1);
+               assert (_in_map.size () == 1);
+               if (!_out_map[0].is_monotonic ()) {
+                       inplace_ok = false;
+               }
+               if (_configured_internal != _configured_in) {
+                       /* no sidechain -- TODO we could allow this with
+                        * some more logic in PluginInsert::connect_and_run().
+                        *
+                        * PluginInsert::reset_map() already maps it.
+                        */
+                       inplace_ok = false;
+               }
+               /* check mapping */
+               for (DataType::iterator t = DataType::begin(); t != DataType::end() && inplace_ok; ++t) {
+                       if (_configured_internal.get (*t) == 0) {
+                               continue;
+                       }
+                       bool valid;
+                       uint32_t first_idx = _in_map[0].get (*t, 0, &valid);
+                       if (!valid || first_idx != 0) {
+                               // so far only allow to copy the *first* stream's buffer to others
+                               inplace_ok = false;
+                       } else {
+                               for (uint32_t i = 1; i < natural_input_streams ().get (*t); ++i) {
+                                       uint32_t idx = _in_map[0].get (*t, i, &valid);
+                                       if (valid && idx != first_idx) {
+                                               inplace_ok = false;
+                                               break;
+                                       }
+                               }
+                       }
+               }
+
+               if (inplace_ok) {
+                       DEBUG_TRACE (DEBUG::ChanMapping, string_compose ("%1: In Place Split Map\n", name()));
+                       return false;
+               }
+       }
+
+       for (uint32_t pc = 0; pc < get_count() && inplace_ok ; ++pc) {
+               if (!_in_map[pc].is_monotonic ()) {
+                       inplace_ok = false;
+               }
+               if (!_out_map[pc].is_monotonic ()) {
+                       inplace_ok = false;
+               }
+       }
+
+       if (inplace_ok) {
+               /* check if every output is fed by the corresponding input
+                *
+                * this prevents  in-port 1 -> sink-pin 2  ||  source-pin 1 -> out port 1, source-pin 2 -> out port 2
+                * (with in-place,  source-pin 1 -> out port 1 overwrites in-port 1)
+                *
+                * but allows     in-port 1 -> sink-pin 2  ||  source-pin 2 -> out port 1
+                */
+               ChanMapping in_map (input_map ());
+               const ChanMapping::Mappings out_m (output_map ().mappings ());
+               for (ChanMapping::Mappings::const_iterator t = out_m.begin (); t != out_m.end () && inplace_ok; ++t) {
+                       for (ChanMapping::TypeMapping::const_iterator c = (*t).second.begin (); c != (*t).second.end () ; ++c) {
+                               /* src-pin: c->first, out-port: c->second */
+                               bool valid;
+                               uint32_t in_port = in_map.get (t->first, c->first, &valid);
+                               if (valid && in_port != c->second) {
+                                       inplace_ok = false;
+                                       break;
+                               }
+                       }
+               }
+       }
+
+       DEBUG_TRACE (DEBUG::ChanMapping, string_compose ("%1: %2\n", name(), inplace_ok ? "In-Place" : "No Inplace Processing"));
+       return !inplace_ok; // no-inplace
+}
+
+bool
+PluginInsert::sanitize_maps ()
+{
+       bool changed = false;
+       /* strip dead wood */
+       PinMappings new_ins;
+       PinMappings new_outs;
+       ChanMapping new_thru;
+
+       for (uint32_t pc = 0; pc < get_count(); ++pc) {
+               ChanMapping new_in;
+               ChanMapping new_out;
+               for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
+                       for (uint32_t i = 0; i < natural_input_streams().get (*t); ++i) {
+                               bool valid;
+                               uint32_t idx = _in_map[pc].get (*t, i, &valid);
+                               if (valid && idx < _configured_internal.get (*t)) {
+                                       new_in.set (*t, i, idx);
+                               }
+                       }
+                       for (uint32_t o = 0; o < natural_output_streams().get (*t); ++o) {
+                               bool valid;
+                               uint32_t idx = _out_map[pc].get (*t, o, &valid);
+                               if (valid && idx < _configured_out.get (*t)) {
+                                       new_out.set (*t, o, idx);
+                               }
+                       }
+               }
+               if (_in_map[pc] != new_in || _out_map[pc] != new_out) {
+                       changed = true;
+               }
+               new_ins[pc] = new_in;
+               new_outs[pc] = new_out;
+       }
+
+       /* prevent dup output assignments */
+       for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
+               for (uint32_t o = 0; o < _configured_out.get (*t); ++o) {
+                       bool mapped = false;
+                       for (uint32_t pc = 0; pc < get_count(); ++pc) {
+                               bool valid;
+                               uint32_t idx = new_outs[pc].get_src (*t, o, &valid);
+                               if (valid && mapped) {
+                                       new_outs[pc].unset (*t, idx);
+                               } else if (valid) {
+                                       mapped = true;
+                               }
+                       }
+               }
+       }
+
+       /* remove excess thru */
+       for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
+               for (uint32_t o = 0; o < _configured_out.get (*t); ++o) {
+                       bool valid;
+                       uint32_t idx = _thru_map.get (*t, o, &valid);
+                       if (valid && idx < _configured_internal.get (*t)) {
+                               new_thru.set (*t, o, idx);
+                       }
+               }
+       }
+
+       /* prevent out + thru,  existing plugin outputs override thru */
+       for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
+               for (uint32_t o = 0; o < _configured_out.get (*t); ++o) {
+                       bool mapped = false;
+                       bool valid;
+                       for (uint32_t pc = 0; pc < get_count(); ++pc) {
+                               new_outs[pc].get_src (*t, o, &mapped);
+                               if (mapped) { break; }
+                       }
+                       if (!mapped) { continue; }
+                       uint32_t idx = new_thru.get (*t, o, &valid);
+                       if (mapped) {
+                               new_thru.unset (*t, idx);
+                       }
                }
+       }
 
-               ac->set_value (dflt, Controllable::NoGroup);
+       if (has_midi_bypass ()) {
+               // TODO: include midi-bypass in the thru set,
+               // remove dedicated handling.
+               new_thru.unset (DataType::MIDI, 0);
        }
-       return all;
+
+       if (_in_map != new_ins || _out_map != new_outs || _thru_map != new_thru) {
+               changed = true;
+       }
+       _in_map = new_ins;
+       _out_map = new_outs;
+       _thru_map = new_thru;
+
+       return changed;
 }
 
-boost::shared_ptr<Plugin>
-PluginInsert::plugin_factory (boost::shared_ptr<Plugin> other)
+bool
+PluginInsert::reset_map (bool emit)
 {
-       boost::shared_ptr<LadspaPlugin> lp;
-       boost::shared_ptr<LuaProc> lua;
-#ifdef LV2_SUPPORT
-       boost::shared_ptr<LV2Plugin> lv2p;
-#endif
-#ifdef WINDOWS_VST_SUPPORT
-       boost::shared_ptr<WindowsVSTPlugin> vp;
-#endif
-#ifdef LXVST_SUPPORT
-       boost::shared_ptr<LXVSTPlugin> lxvp;
-#endif
-#ifdef AUDIOUNIT_SUPPORT
-       boost::shared_ptr<AUPlugin> ap;
-#endif
+       const PinMappings old_in (_in_map);
+       const PinMappings old_out (_out_map);
 
-       if ((lp = boost::dynamic_pointer_cast<LadspaPlugin> (other)) != 0) {
-               return boost::shared_ptr<Plugin> (new LadspaPlugin (*lp));
-       } else if ((lua = boost::dynamic_pointer_cast<LuaProc> (other)) != 0) {
-               return boost::shared_ptr<Plugin> (new LuaProc (*lua));
-#ifdef LV2_SUPPORT
-       } else if ((lv2p = boost::dynamic_pointer_cast<LV2Plugin> (other)) != 0) {
-               return boost::shared_ptr<Plugin> (new LV2Plugin (*lv2p));
-#endif
-#ifdef WINDOWS_VST_SUPPORT
-       } else if ((vp = boost::dynamic_pointer_cast<WindowsVSTPlugin> (other)) != 0) {
-               return boost::shared_ptr<Plugin> (new WindowsVSTPlugin (*vp));
-#endif
-#ifdef LXVST_SUPPORT
-       } else if ((lxvp = boost::dynamic_pointer_cast<LXVSTPlugin> (other)) != 0) {
-               return boost::shared_ptr<Plugin> (new LXVSTPlugin (*lxvp));
-#endif
-#ifdef AUDIOUNIT_SUPPORT
-       } else if ((ap = boost::dynamic_pointer_cast<AUPlugin> (other)) != 0) {
-               return boost::shared_ptr<Plugin> (new AUPlugin (*ap));
-#endif
+       _in_map.clear ();
+       _out_map.clear ();
+       _thru_map = ChanMapping ();
+
+       /* build input map */
+       for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
+               uint32_t sc = 0; // side-chain round-robin (all instances)
+               uint32_t pc = 0;
+               for (Plugins::iterator i = _plugins.begin(); i != _plugins.end(); ++i, ++pc) {
+                       const uint32_t nis = natural_input_streams ().get(*t);
+                       const uint32_t stride = nis - sidechain_input_pins().get (*t);
+
+                       /* SC inputs are last in the plugin-insert.. */
+                       const uint32_t sc_start = _configured_in.get (*t);
+                       const uint32_t sc_len = _configured_internal.get (*t) - sc_start;
+                       /* ...but may not be at the end of the plugin ports.
+                        * in case the side-chain is not the last port, shift connections back.
+                        * and connect to side-chain
+                        */
+                       uint32_t shift = 0;
+                       uint32_t ic = 0; // split inputs
+                       const uint32_t cend = _configured_in.get (*t);
+
+                       for (uint32_t in = 0; in < nis; ++in) {
+                               const Plugin::IOPortDescription& iod (_plugins[pc]->describe_io_port (*t, true, in));
+                               if (iod.is_sidechain) {
+                                       /* connect sidechain sinks to sidechain inputs in round-robin fashion */
+                                       if (sc_len > 0) {// side-chain may be hidden
+                                               _in_map[pc].set (*t, in, sc_start + sc);
+                                               sc = (sc + 1) % sc_len;
+                                       }
+                                       ++shift;
+                               } else {
+                                       if (_match.method == Split) {
+                                               if (cend == 0) { continue; }
+                                               if (_strict_io && ic + stride * pc >= cend) {
+                                                       break;
+                                               }
+                                               /* connect *no* sidechain sinks in round-robin fashion */
+                                               _in_map[pc].set (*t, in, ic + stride * pc);
+                                               if (_strict_io && (ic + 1) == cend) {
+                                                       break;
+                                               }
+                                               ic = (ic + 1) % cend;
+                                       } else {
+                                               uint32_t s = in - shift;
+                                               if (stride * pc + s < cend) {
+                                                       _in_map[pc].set (*t, in, s + stride * pc);
+                                               }
+                                       }
+                               }
+                       }
+               }
        }
 
-       fatal << string_compose (_("programming error: %1"),
-                         X_("unknown plugin type in PluginInsert::plugin_factory"))
-             << endmsg;
-       abort(); /*NOTREACHED*/
-       return boost::shared_ptr<Plugin> ((Plugin*) 0);
+       /* build output map */
+       uint32_t pc = 0;
+       for (Plugins::iterator i = _plugins.begin(); i != _plugins.end(); ++i, ++pc) {
+               _out_map[pc] = ChanMapping (ChanCount::min (natural_output_streams(), _configured_out));
+               for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
+                       _out_map[pc].offset_to(*t, pc * natural_output_streams().get(*t));
+               }
+       }
+
+       sanitize_maps ();
+       if (old_in == _in_map && old_out == _out_map) {
+               return false;
+       }
+       if (emit) {
+               PluginMapChanged (); /* EMIT SIGNAL */
+               _mapping_changed = true;
+               _session.set_dirty();
+       }
+       return true;
 }
 
 bool
 PluginInsert::configure_io (ChanCount in, ChanCount out)
 {
        Match old_match = _match;
-       ChanCount old_in = input_streams ();
-       ChanCount old_out = output_streams ();
+       ChanCount old_in;
+       ChanCount old_internal;
+       ChanCount old_out;
+       ChanCount old_pins;
+
+       old_pins = natural_input_streams();
+       old_in = _configured_in;
+       old_out = _configured_out;
+       old_internal = _configured_internal;
 
        _configured_in = in;
+       _configured_internal = in;
        _configured_out = out;
 
-       /* set the matching method and number of plugins that we will use to meet this configuration */
+       if (_sidechain) {
+               /* TODO hide midi-bypass, and custom outs. Best /fake/ "out" here.
+                * (currently _sidechain->configure_io always succeeds
+                *  since Processor::configure_io() succeeds)
+                */
+               if (!_sidechain->configure_io (in, out)) {
+                       DEBUG_TRACE (DEBUG::ChanMapping, "Sidechain configuration failed\n");
+                       return false;
+               }
+               _configured_internal += _sidechain->input()->n_ports();
+
+               // include (static_cast<Route*>owner())->name() ??
+               _sidechain->input ()-> set_pretty_name (string_compose (_("SC %1"), name ()));
+       }
+
+       /* get plugin configuration */
        _match = private_can_support_io_configuration (in, out);
+#ifndef NDEBUG
+       if (DEBUG_ENABLED(DEBUG::ChanMapping)) {
+               DEBUG_STR_DECL(a);
+               DEBUG_STR_APPEND(a, string_compose ("%1: ",  name()));
+               DEBUG_STR_APPEND(a, _match);
+               DEBUG_TRACE (DEBUG::ChanMapping, DEBUG_STR(a).str());
+       }
+#endif
+
+       /* set the matching method and number of plugins that we will use to meet this configuration */
        if (set_count (_match.plugins) == false) {
                PluginIoReConfigure (); /* EMIT SIGNAL */
+               _configured = false;
                return false;
        }
 
@@ -733,58 +1732,188 @@ PluginInsert::configure_io (ChanCount in, ChanCount out)
        switch (_match.method) {
        case Split:
        case Hide:
-               if (_plugins.front()->configure_io (_plugins.front()->get_info()->n_inputs, out) == false) {
+               if (_plugins.front()->configure_io (natural_input_streams(), out) == false) {
                        PluginIoReConfigure (); /* EMIT SIGNAL */
+                       _configured = false;
                        return false;
                }
                break;
+       case Delegate:
+               {
+                       ChanCount din (_configured_internal);
+                       ChanCount dout (din); // hint
+                       if (_custom_cfg) {
+                               if (_custom_sinks.n_total () > 0) {
+                                       din = _custom_sinks;
+                               }
+                               dout = _custom_out;
+                       } else if (_preset_out.n_audio () > 0) {
+                               dout.set (DataType::AUDIO, _preset_out.n_audio ());
+                       } else if (dout.n_midi () > 0 && dout.n_audio () == 0) {
+                               dout.set (DataType::AUDIO, 2);
+                       }
+                       if (out.n_audio () == 0) { out.set (DataType::AUDIO, 1); }
+                       ChanCount useins;
+                       DEBUG_TRACE (DEBUG::ChanMapping, string_compose ("%1: Delegate lookup : %2 %3\n", name(), din, dout));
+                       bool const r = _plugins.front()->can_support_io_configuration (din, dout, &useins);
+                       assert (r);
+                       if (useins.n_audio() == 0) {
+                               useins = din;
+                       }
+                       DEBUG_TRACE (DEBUG::ChanMapping, string_compose ("%1: Delegate configuration: %2 %3\n", name(), useins, dout));
 
+                       if (_plugins.front()->configure_io (useins, dout) == false) {
+                               PluginIoReConfigure (); /* EMIT SIGNAL */
+                               _configured = false;
+                               return false;
+                       }
+                       if (!_custom_cfg) {
+                               _custom_sinks = din;
+                       }
+               }
+               break;
        default:
                if (_plugins.front()->configure_io (in, out) == false) {
                        PluginIoReConfigure (); /* EMIT SIGNAL */
+                       _configured = false;
                        return false;
                }
                break;
        }
 
-       // TODO make configurable
-       uint32_t pc = 0;
-       for (Plugins::iterator i = _plugins.begin(); i != _plugins.end(); ++i, ++pc) {
-               if (_match.method == Split) {
-                       /* TODO see PluginInsert::connect_and_run, channel replication */
-                       _in_map[pc] = ChanMapping (natural_input_streams ());
+       DEBUG_TRACE (DEBUG::ChanMapping, string_compose ("%1: cfg:%2 state:%3 chn-in:%4 chn-out:%5 inpin:%6 match:%7 cust:%8 size-in:%9 size-out:%10\n",
+                               name (),
+                               _configured ? "Y" : "N",
+                               _maps_from_state ? "Y" : "N",
+                               old_in == in ? "==" : "!=",
+                               old_out == out ? "==" : "!=",
+                               old_pins == natural_input_streams () ? "==" : "!=",
+                               old_match.method == _match.method ? "==" : "!=",
+                               old_match.custom_cfg == _match.custom_cfg ? "==" : "!=",
+                               _in_map.size() == get_count () ? "==" : "!=",
+                               _out_map.size() == get_count () ? "==" : "!="
+                               ));
+
+       bool mapping_changed = false;
+       if (old_in == in && old_out == out
+                       && _configured
+                       && old_pins == natural_input_streams ()
+                       && old_match.method == _match.method
+                       && old_match.custom_cfg == _match.custom_cfg
+                       && _in_map.size() == _out_map.size()
+                       && _in_map.size() == get_count ()
+                ) {
+               assert (_maps_from_state == false);
+               /* If the configuration has not changed, keep the mapping */
+               mapping_changed = sanitize_maps ();
+       } else if (_match.custom_cfg && _configured) {
+               assert (_maps_from_state == false);
+               /* don't touch the map in manual mode */
+               mapping_changed = sanitize_maps ();
+       } else {
+#ifdef MIXBUS
+               if (is_channelstrip ()) {
+                       /* fake channel map - for wire display */
+                       _in_map.clear ();
+                       _out_map.clear ();
+                       _thru_map = ChanMapping ();
+                       _in_map[0] = ChanMapping (ChanCount::min (_configured_in, ChanCount (DataType::AUDIO, 2)));
+                       _out_map[0] = ChanMapping (ChanCount::min (_configured_out, ChanCount (DataType::AUDIO, 2)));
+                       /* set "thru" map for in-place forward of audio */
+                       for (uint32_t i = 2; i < _configured_in.n_audio(); ++i) {
+                               _thru_map.set (DataType::AUDIO, i, i);
+                       }
+                       /* and midi (after implicit 1st channel bypass) */
+                       for (uint32_t i = 1; i < _configured_in.n_midi(); ++i) {
+                               _thru_map.set (DataType::MIDI, i, i);
+                       }
+               } else
+#endif
+               if (_maps_from_state && old_in == in && old_out == out) {
+                       mapping_changed = true;
+                       sanitize_maps ();
                } else {
-                       _in_map[pc] = ChanMapping (input_streams ());
+                       /* generate a new mapping */
+                       mapping_changed = reset_map (false);
                }
-               _out_map[pc] = ChanMapping (output_streams());
+               _maps_from_state = false;
+       }
 
-               for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
-                       _in_map[pc].offset_to(*t, pc * natural_input_streams().get(*t));
-                       _out_map[pc].offset_to(*t, pc * natural_output_streams().get(*t));
+       if (mapping_changed) {
+               PluginMapChanged (); /* EMIT SIGNAL */
+
+#ifndef NDEBUG
+               if (DEBUG_ENABLED(DEBUG::ChanMapping)) {
+                       uint32_t pc = 0;
+                       DEBUG_STR_DECL(a);
+                       DEBUG_STR_APPEND(a, "\n--------<<--------\n");
+                       for (Plugins::iterator i = _plugins.begin(); i != _plugins.end(); ++i, ++pc) {
+                               if (pc > 0) {
+                       DEBUG_STR_APPEND(a, "----><----\n");
+                               }
+                               DEBUG_STR_APPEND(a, string_compose ("Channel Map for %1 plugin %2\n", name(), pc));
+                               DEBUG_STR_APPEND(a, " * Inputs:\n");
+                               DEBUG_STR_APPEND(a, _in_map[pc]);
+                               DEBUG_STR_APPEND(a, " * Outputs:\n");
+                               DEBUG_STR_APPEND(a, _out_map[pc]);
+                       }
+                       DEBUG_STR_APPEND(a, " * Thru:\n");
+                       DEBUG_STR_APPEND(a, _thru_map);
+                       DEBUG_STR_APPEND(a, "-------->>--------\n");
+                       DEBUG_TRACE (DEBUG::ChanMapping, DEBUG_STR(a).str());
                }
+#endif
        }
 
+       _no_inplace = check_inplace ();
+       _mapping_changed = false;
+
+       /* only the "noinplace_buffers" thread buffers need to be this large,
+        * this can be optimized. other buffers are fine with
+        * ChanCount::max (natural_input_streams (), natural_output_streams())
+        * and route.cc's max (configured_in, configured_out)
+        *
+        * no-inplace copies "thru" outputs (to emulate in-place) for
+        * all outputs (to prevent overwrite) into a temporary space
+        * which also holds input buffers (in case the plugin does process
+        * in-place and overwrites those).
+        *
+        * this buffers need to be at least as
+        *   natural_input_streams () + possible outputs.
+        *
+        * sidechain inputs add a constraint on the input:
+        * configured input + sidechain (=_configured_internal)
+        *
+        * NB. this also satisfies
+        * max (natural_input_streams(), natural_output_streams())
+        * which is needed for silence runs
+        */
+       _required_buffers = ChanCount::max (_configured_internal,
+                       natural_input_streams () + ChanCount::max (_configured_out, natural_output_streams () * get_count ()));
 
-       if (  (old_match.method != _match.method && (old_match.method == Split || _match.method == Split))
-                       || old_in != in
-                       || old_out != out
-                       )
-       {
+       if (old_in != in || old_out != out || old_internal != _configured_internal
+                       || old_pins != natural_input_streams ()
+                       || (old_match.method != _match.method && (old_match.method == Split || _match.method == Split))
+                ) {
                PluginIoReConfigure (); /* EMIT SIGNAL */
        }
 
+       _delaybuffers.configure (_configured_out, _plugins.front ()->max_latency ());
+       _latency_changed = true;
+
        // we don't know the analysis window size, so we must work with the
        // current buffer size here. each request for data fills in these
        // buffers and the analyser makes sure it gets enough data for the
        // analysis window
        session().ensure_buffer_set (_signal_analysis_inputs, in);
-       //_signal_analysis_inputs.set_count (in);
+       _signal_analysis_inputs.set_count (in);
 
        session().ensure_buffer_set (_signal_analysis_outputs, out);
-       //_signal_analysis_outputs.set_count (out);
+       _signal_analysis_outputs.set_count (out);
 
        // std::cerr << "set counts to i" << in.n_audio() << "/o" << out.n_audio() << std::endl;
 
+       _configured = true;
        return Processor::configure_io (in, out);
 }
 
@@ -799,52 +1928,212 @@ PluginInsert::configure_io (ChanCount in, ChanCount out)
 bool
 PluginInsert::can_support_io_configuration (const ChanCount& in, ChanCount& out)
 {
+       if (_sidechain) {
+               _sidechain->can_support_io_configuration (in, out); // never fails, sets "out"
+       }
        return private_can_support_io_configuration (in, out).method != Impossible;
 }
 
+PluginInsert::Match
+PluginInsert::private_can_support_io_configuration (ChanCount const& in, ChanCount& out) const
+{
+       if (!_custom_cfg && _preset_out.n_audio () > 0) {
+               // preseed hint (for variable i/o)
+               out.set (DataType::AUDIO, _preset_out.n_audio ());
+       }
+
+       Match rv = internal_can_support_io_configuration (in, out);
+
+       if (!_custom_cfg && _preset_out.n_audio () > 0) {
+               DEBUG_TRACE (DEBUG::ChanMapping, string_compose ("%1: using output preset: %2\n", name(), _preset_out));
+               out.set (DataType::AUDIO, _preset_out.n_audio ());
+       }
+       return rv;
+}
+
 /** A private version of can_support_io_configuration which returns the method
  *  by which the configuration can be matched, rather than just whether or not
  *  it can be.
  */
 PluginInsert::Match
-PluginInsert::private_can_support_io_configuration (ChanCount const & inx, ChanCount& out)
+PluginInsert::internal_can_support_io_configuration (ChanCount const & inx, ChanCount& out) const
 {
-       _strict_io_configured = false;
        if (_plugins.empty()) {
                return Match();
        }
 
+#ifdef MIXBUS
+       if (is_channelstrip ()) {
+               out = inx;
+               return Match (ExactMatch, 1);
+       }
+#endif
+
+       /* if a user specified a custom cfg, so be it. */
+       if (_custom_cfg) {
+               PluginInfoPtr info = _plugins.front()->get_info();
+               out = _custom_out;
+               if (info->reconfigurable_io()) {
+                       return Match (Delegate, 1, _strict_io, true);
+               } else {
+                       return Match (ExactMatch, get_count(), _strict_io, true);
+               }
+       }
+
+       /* try automatic configuration */
+       Match m = PluginInsert::automatic_can_support_io_configuration (inx, out);
+
        PluginInfoPtr info = _plugins.front()->get_info();
-       ChanCount in; in += inx;
-       midi_bypass.reset();
+       ChanCount inputs  = info->n_inputs;
+       ChanCount outputs = info->n_outputs;
+
+       /* handle case strict-i/o */
+       if (_strict_io && m.method != Impossible) {
+               m.strict_io = true;
+
+               /* special case MIDI instruments */
+               if (needs_midi_input ()) {
+                       // output = midi-bypass + at most master-out channels.
+                       ChanCount max_out (DataType::AUDIO, 2); // TODO use master-out
+                       max_out.set (DataType::MIDI, out.get(DataType::MIDI));
+                       out = ChanCount::min (out, max_out);
+                       DEBUG_TRACE (DEBUG::ChanMapping, string_compose ("%1: special case strict-i/o instrument\n", name()));
+                       return m;
+               }
+
+               switch (m.method) {
+                       case NoInputs:
+                               if (inx.n_audio () != out.n_audio ()) { // ignore midi bypass
+                                       /* replicate processor to match output count (generators and such)
+                                        * at least enough to feed every output port. */
+                                       uint32_t f = 1; // at least one. e.g. control data filters, no in, no out.
+                                       for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
+                                               uint32_t nout = outputs.get (*t);
+                                               if (nout == 0 || inx.get(*t) == 0) { continue; }
+                                               f = max (f, (uint32_t) ceil (inx.get(*t) / (float)nout));
+                                       }
+                                       out = inx;
+                                       DEBUG_TRACE (DEBUG::ChanMapping, string_compose ("%1: special case strict-i/o for generator\n", name()));
+                                       return Match (Replicate, f, _strict_io);
+                               }
+                               break;
+                       default:
+                               break;
+               }
+
+               out = inx;
+               return m;
+       }
+
+       if (m.method != Impossible) {
+               return m;
+       }
+
+       ChanCount ns_inputs  = inputs - sidechain_input_pins ();
+
+       DEBUG_TRACE (DEBUG::ChanMapping, string_compose ("%1: resolving 'Impossible' match...\n", name()));
 
        if (info->reconfigurable_io()) {
-               /* Plugin has flexible I/O, so delegate to it */
-               bool const r = _plugins.front()->can_support_io_configuration (in, out);
+               ChanCount useins;
+               out = inx; // hint
+               if (out.n_midi () > 0 && out.n_audio () == 0) { out.set (DataType::AUDIO, 2); }
+               if (out.n_audio () == 0) { out.set (DataType::AUDIO, 1); }
+               bool const r = _plugins.front()->can_support_io_configuration (inx + sidechain_input_pins (), out, &useins);
                if (!r) {
+                       // houston, we have a problem.
                        return Match (Impossible, 0);
                }
+               // midi bypass
+               if (inx.n_midi () > 0 && out.n_midi () == 0) { out.set (DataType::MIDI, 1); }
+               return Match (Delegate, 1, _strict_io);
+       }
 
-               if (_strict_io && in.n_audio() < out.n_audio()) {
-                       DEBUG_TRACE (DEBUG::Processors, string_compose ("hiding output ports of reconfigurable %1\n", name()));
-                       out.set (DataType::AUDIO, in.get (DataType::AUDIO));
-               }
+       ChanCount midi_bypass;
+       if (inx.get(DataType::MIDI) == 1 && outputs.get(DataType::MIDI) == 0) {
+               midi_bypass.set (DataType::MIDI, 1);
+       }
+
+       // add at least as many plugins so that output count matches input count (w/o sidechain pins)
+       uint32_t f = 0;
+       for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
+               uint32_t nin = ns_inputs.get (*t);
+               uint32_t nout = outputs.get (*t);
+               if (nin == 0 || inx.get(*t) == 0) { continue; }
+               // prefer floor() so the count won't overly increase IFF (nin < nout)
+               f = max (f, (uint32_t) floor (inx.get(*t) / (float)nout));
+       }
+       if (f > 0 && outputs * f >= _configured_out) {
+               out = outputs * f + midi_bypass;
+               return Match (Replicate, f, _strict_io);
+       }
+
+       // add at least as many plugins needed to connect all inputs (w/o sidechain pins)
+       f = 0;
+       for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
+               uint32_t nin = ns_inputs.get (*t);
+               if (nin == 0 || inx.get(*t) == 0) { continue; }
+               f = max (f, (uint32_t) ceil (inx.get(*t) / (float)nin));
+       }
+       if (f > 0) {
+               out = outputs * f + midi_bypass;
+               return Match (Replicate, f, _strict_io);
+       }
+
+       // add at least as many plugins needed to connect all inputs
+       f = 1;
+       for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
+               uint32_t nin = inputs.get (*t);
+               if (nin == 0 || inx.get(*t) == 0) { continue; }
+               f = max (f, (uint32_t) ceil (inx.get(*t) / (float)nin));
+       }
+       out = outputs * f + midi_bypass;
+       return Match (Replicate, f, _strict_io);
+}
 
+/* this is the original Ardour 3/4 behavior, mainly for backwards compatibility */
+PluginInsert::Match
+PluginInsert::automatic_can_support_io_configuration (ChanCount const & inx, ChanCount& out) const
+{
+       if (_plugins.empty()) {
+               return Match();
+       }
+
+       PluginInfoPtr info = _plugins.front()->get_info();
+       ChanCount in; in += inx;
+       ChanCount midi_bypass;
+
+       if (info->reconfigurable_io()) {
+               /* Plugin has flexible I/O, so delegate to it
+                * pre-seed outputs, plugin tries closest match
+                */
+               out = in; // hint
+               if (out.n_midi () > 0 && out.n_audio () == 0) { out.set (DataType::AUDIO, 2); }
+               if (out.n_audio () == 0) { out.set (DataType::AUDIO, 1); }
+               bool const r = _plugins.front()->can_support_io_configuration (in + sidechain_input_pins (), out);
+               if (!r) {
+                       return Match (Impossible, 0);
+               }
+               // midi bypass
+               if (in.n_midi () > 0 && out.n_midi () == 0) { out.set (DataType::MIDI, 1); }
                return Match (Delegate, 1);
        }
 
        ChanCount inputs  = info->n_inputs;
        ChanCount outputs = info->n_outputs;
+       ChanCount ns_inputs  = inputs - sidechain_input_pins ();
 
        if (in.get(DataType::MIDI) == 1 && outputs.get(DataType::MIDI) == 0) {
-               DEBUG_TRACE ( DEBUG::Processors, string_compose ("bypassing midi-data around %1\n", name()));
-               midi_bypass.set(DataType::MIDI, 1);
+               DEBUG_TRACE (DEBUG::ChanMapping, string_compose ("%1: bypassing midi-data\n", name()));
+               midi_bypass.set (DataType::MIDI, 1);
        }
        if (in.get(DataType::MIDI) == 1 && inputs.get(DataType::MIDI) == 0) {
-               DEBUG_TRACE ( DEBUG::Processors, string_compose ("hiding midi-port from plugin %1\n", name()));
+               DEBUG_TRACE (DEBUG::ChanMapping, string_compose ("%1: hiding midi-port from plugin\n", name()));
                in.set(DataType::MIDI, 0);
        }
 
+       // add internally provided sidechain ports
+       ChanCount insc = in + sidechain_input_ports ();
+
        bool no_inputs = true;
        for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
                if (inputs.get (*t) != 0) {
@@ -859,8 +2148,14 @@ PluginInsert::private_can_support_io_configuration (ChanCount const & inx, ChanC
                return Match (NoInputs, 1);
        }
 
-       /* Plugin inputs match requested inputs exactly */
-       if (inputs == in  && (!_strict_io || outputs.n_audio() == inputs.n_audio())) {
+       /* Plugin inputs match requested inputs + side-chain-ports exactly */
+       if (inputs == insc) {
+               out = outputs + midi_bypass;
+               return Match (ExactMatch, 1);
+       }
+
+       /* Plugin inputs matches without side-chain-pins */
+       if (ns_inputs == in) {
                out = outputs + midi_bypass;
                return Match (ExactMatch, 1);
        }
@@ -875,7 +2170,8 @@ PluginInsert::private_can_support_io_configuration (ChanCount const & inx, ChanC
        bool     can_replicate = true;
        for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
 
-               uint32_t nin = inputs.get (*t);
+               // ignore side-chains
+               uint32_t nin = ns_inputs.get (*t);
 
                // No inputs of this type
                if (nin == 0 && in.get(*t) == 0) {
@@ -899,7 +2195,7 @@ PluginInsert::private_can_support_io_configuration (ChanCount const & inx, ChanC
                }
        }
 
-       if (can_replicate) {
+       if (can_replicate && f > 0) {
                for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
                        out.set (*t, outputs.get(*t) * f);
                }
@@ -915,10 +2211,10 @@ PluginInsert::private_can_support_io_configuration (ChanCount const & inx, ChanC
           plugin inputs?  Let me count the ways ...
        */
 
-       bool can_split = !_strict_io;
+       bool can_split = true;
        for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
 
-               bool const can_split_type = (in.get (*t) == 1 && inputs.get (*t) > 1);
+               bool const can_split_type = (in.get (*t) == 1 && ns_inputs.get (*t) > 1);
                bool const nothing_to_do_for_type = (in.get (*t) == 0 && inputs.get (*t) == 0);
 
                if (!can_split_type && !nothing_to_do_for_type) {
@@ -951,19 +2247,14 @@ PluginInsert::private_can_support_io_configuration (ChanCount const & inx, ChanC
        }
 
        if (could_hide && !cannot_hide) {
-               if (_strict_io && inputs.get (DataType::AUDIO) == outputs.get (DataType::AUDIO)) {
-                       _strict_io_configured = true;
-                       outputs = inputs;
-               } else {
-                       out = outputs + midi_bypass;
-               }
-               return Match (Hide, 1, hide_channels);
+               out = outputs + midi_bypass;
+               return Match (Hide, 1, false, false, hide_channels);
        }
 
-       midi_bypass.reset();
        return Match (Impossible, 0);
 }
 
+
 XMLNode&
 PluginInsert::get_state ()
 {
@@ -982,7 +2273,24 @@ PluginInsert::state (bool full)
        /* remember actual i/o configuration (for later placeholder
         * in case the plugin goes missing) */
        node.add_child_nocopy (* _configured_in.state (X_("ConfiguredInput")));
+       node.add_child_nocopy (* _custom_sinks.state (X_("CustomSinks")));
        node.add_child_nocopy (* _configured_out.state (X_("ConfiguredOutput")));
+       node.add_child_nocopy (* _preset_out.state (X_("PresetOutput")));
+
+       /* save custom i/o config */
+       node.add_property("custom", _custom_cfg ? "yes" : "no");
+       for (uint32_t pc = 0; pc < get_count(); ++pc) {
+               char tmp[128];
+               snprintf (tmp, sizeof(tmp), "InputMap-%d", pc);
+               node.add_child_nocopy (* _in_map[pc].state (tmp));
+               snprintf (tmp, sizeof(tmp), "OutputMap-%d", pc);
+               node.add_child_nocopy (* _out_map[pc].state (tmp));
+       }
+       node.add_child_nocopy (* _thru_map.state ("ThruMap"));
+
+       if (_sidechain) {
+               node.add_child_nocopy (_sidechain->state (full));
+       }
 
        _plugins[0]->set_insert_id(this->id());
        node.add_child_nocopy (_plugins[0]->get_state());
@@ -1006,7 +2314,7 @@ PluginInsert::set_control_ids (const XMLNode& node, int version)
 
        for (iter = nlist.begin(); iter != nlist.end(); ++iter) {
                if ((*iter)->name() == Controllable::xml_node_name) {
-                       const XMLProperty* prop;
+                       XMLProperty const * prop;
 
                        uint32_t p = (uint32_t)-1;
 #ifdef LV2_SUPPORT
@@ -1047,7 +2355,7 @@ PluginInsert::set_state(const XMLNode& node, int version)
        XMLNodeList nlist = node.children();
        XMLNodeIterator niter;
        XMLPropertyList plist;
-       const XMLProperty *prop;
+       XMLProperty const * prop;
        ARDOUR::PluginType type;
 
        if ((prop = node.property ("type")) == 0) {
@@ -1119,6 +2427,19 @@ PluginInsert::set_state(const XMLNode& node, int version)
        }
 #endif
 
+       if (plugin == 0 && type == ARDOUR::Lua) {
+               /* unique ID (sha1 of script) was not found,
+                * load the plugin from the serialized version in the
+                * session-file instead.
+                */
+               boost::shared_ptr<LuaProc> lp (new LuaProc (_session.engine(), _session, ""));
+               XMLNode *ls = node.child (lp->state_node_name().c_str());
+               if (ls && lp) {
+                       lp->set_script_from_state (*ls);
+                       plugin = lp;
+               }
+       }
+
        if (plugin == 0) {
                error << string_compose(
                        _("Found a reference to a plugin (\"%1\") that is unknown.\n"
@@ -1128,15 +2449,6 @@ PluginInsert::set_state(const XMLNode& node, int version)
                return -1;
        }
 
-       if (type == ARDOUR::Lua) {
-               XMLNode *ls = node.child (plugin->state_node_name().c_str());
-               // we need to load the script to set the name and parameters.
-               boost::shared_ptr<LuaProc> lp = boost::dynamic_pointer_cast<LuaProc>(plugin);
-               if (ls && lp) {
-                       lp->set_script_from_state (*ls);
-               }
-       }
-
        // The name of the PluginInsert comes from the plugin, nothing else
        _name = plugin->get_info()->name;
 
@@ -1230,6 +2542,58 @@ PluginInsert::set_state(const XMLNode& node, int version)
                set_parameter_state_2X (node, version);
        }
 
+       if ((prop = node.property (X_("custom"))) != 0) {
+               _custom_cfg = string_is_affirmative (prop->value());
+       }
+
+       uint32_t in_maps = 0;
+       uint32_t out_maps = 0;
+       XMLNodeList kids = node.children ();
+       for (XMLNodeIterator i = kids.begin(); i != kids.end(); ++i) {
+               if ((*i)->name() == X_("ConfiguredInput")) {
+                       _configured_in = ChanCount(**i);
+               }
+               if ((*i)->name() == X_("CustomSinks")) {
+                       _custom_sinks = ChanCount(**i);
+               }
+               if ((*i)->name() == X_("ConfiguredOutput")) {
+                       _custom_out = ChanCount(**i);
+                       _configured_out = ChanCount(**i);
+               }
+               if ((*i)->name() == X_("PresetOutput")) {
+                       _preset_out = ChanCount(**i);
+               }
+               if (strncmp ((*i)->name ().c_str(), X_("InputMap-"), 9) == 0) {
+                       long pc = atol (&((*i)->name().c_str()[9]));
+                       if (pc >= 0 && pc <= (long) get_count()) {
+                               _in_map[pc] = ChanMapping (**i);
+                               ++in_maps;
+                       }
+               }
+               if (strncmp ((*i)->name ().c_str(), X_("OutputMap-"), 10) == 0) {
+                       long pc = atol (&((*i)->name().c_str()[10]));
+                       if (pc >= 0 && pc <= (long) get_count()) {
+                               _out_map[pc] = ChanMapping (**i);
+                               ++out_maps;
+                       }
+               }
+               if ((*i)->name () ==  "ThruMap") {
+                               _thru_map = ChanMapping (**i);
+               }
+
+               // sidechain is a Processor (IO)
+               if ((*i)->name () ==  Processor::state_node_name) {
+                       if (!_sidechain) {
+                               add_sidechain (0);
+                       }
+                       _sidechain->set_state (**i, version);
+               }
+       }
+
+       if (in_maps == out_maps && out_maps >0 && out_maps == get_count()) {
+               _maps_from_state = true;
+       }
+
        for (Plugins::iterator i = _plugins.begin(); i != _plugins.end(); ++i) {
                if (active()) {
                        (*i)->activate ();
@@ -1238,6 +2602,7 @@ PluginInsert::set_state(const XMLNode& node, int version)
                }
        }
 
+       PluginConfigChanged (); /* EMIT SIGNAL */
        return 0;
 }
 
@@ -1272,7 +2637,7 @@ PluginInsert::set_parameter_state_2X (const XMLNode& node, int version)
                }
 
                XMLNodeList cnodes;
-               XMLProperty *cprop;
+               XMLProperty const * cprop;
                XMLNodeConstIterator iter;
                XMLNode *child;
                const char *port;
@@ -1358,6 +2723,9 @@ PluginInsert::describe_parameter (Evoral::Parameter param)
 ARDOUR::framecnt_t
 PluginInsert::signal_latency() const
 {
+       if (!_pending_active) {
+               return 0;
+       }
        if (_user_latency) {
                return _user_latency;
        }
@@ -1384,29 +2752,12 @@ PluginInsert::PluginControl::PluginControl (PluginInsert*                     p,
                        list->set_interpolation(Evoral::ControlList::Discrete);
                }
        }
-
-       if (desc.toggled) {
-               set_flags(Controllable::Toggle);
-       }
 }
 
 /** @param val `user' value */
-void
-PluginInsert::PluginControl::set_value (double user_val, PBD::Controllable::GroupControlDisposition group_override)
-{
-       if (writable()) {
-               _set_value (user_val, group_override);
-       }
-}
-void
-PluginInsert::PluginControl::set_value_unchecked (double user_val)
-{
-       /* used only by automation playback */
-       _set_value (user_val, Controllable::NoGroup);
-}
 
 void
-PluginInsert::PluginControl::_set_value (double user_val, PBD::Controllable::GroupControlDisposition group_override)
+PluginInsert::PluginControl::actually_set_value (double user_val, PBD::Controllable::GroupControlDisposition group_override)
 {
        /* FIXME: probably should be taking out some lock here.. */
 
@@ -1419,13 +2770,13 @@ PluginInsert::PluginControl::_set_value (double user_val, PBD::Controllable::Gro
                iasp->set_parameter (_list->parameter().id(), user_val);
        }
 
-       AutomationControl::set_value (user_val, group_override);
+       AutomationControl::actually_set_value (user_val, group_override);
 }
 
 void
 PluginInsert::PluginControl::catch_up_with_external_value (double user_val)
 {
-       AutomationControl::set_value (user_val, Controllable::NoGroup);
+       AutomationControl::actually_set_value (user_val, Controllable::NoGroup);
 }
 
 XMLNode&
@@ -1470,22 +2821,10 @@ PluginInsert::PluginPropertyControl::PluginPropertyControl (PluginInsert*
                alist()->set_yrange (desc.lower, desc.upper);
                alist()->reset_default (desc.normal);
        }
-
-       if (desc.toggled) {
-               set_flags(Controllable::Toggle);
-       }
-}
-
-void
-PluginInsert::PluginPropertyControl::set_value (double user_val, PBD::Controllable::GroupControlDisposition /* group_override*/)
-{
-       if (writable()) {
-               set_value_unchecked (user_val);
-       }
 }
 
 void
-PluginInsert::PluginPropertyControl::set_value_unchecked (double user_val)
+PluginInsert::PluginPropertyControl::actually_set_value (double user_val, Controllable::GroupControlDisposition gcd)
 {
        /* Old numeric set_value(), coerce to appropriate datatype if possible.
           This is lossy, but better than nothing until Ardour's automation system
@@ -1501,7 +2840,8 @@ PluginInsert::PluginPropertyControl::set_value_unchecked (double user_val)
        }
 
        _value = value;
-       AutomationControl::set_value (user_val, Controllable::NoGroup);
+
+       AutomationControl::actually_set_value (user_val, gcd);
 }
 
 XMLNode&
@@ -1528,8 +2868,18 @@ PluginInsert::get_impulse_analysis_plugin()
 {
        boost::shared_ptr<Plugin> ret;
        if (_impulseAnalysisPlugin.expired()) {
+               // LV2 in particular uses various _session params
+               // during init() -- most notably block_size..
+               // not great.
                ret = plugin_factory(_plugins[0]);
-               ret->configure_io (input_streams (), output_streams ());
+               ChanCount out (internal_output_streams ());
+               if (ret->get_info ()->reconfigurable_io ()) {
+                       // populate get_info ()->n_inputs and ->n_outputs
+                       ChanCount useins;
+                       ret->can_support_io_configuration (internal_input_streams (), out, &useins);
+                       assert (out == internal_output_streams ());
+               }
+               ret->configure_io (internal_input_streams (), out);
                _impulseAnalysisPlugin = ret;
        } else {
                ret = _impulseAnalysisPlugin.lock();
@@ -1543,8 +2893,8 @@ PluginInsert::collect_signal_for_analysis (framecnt_t nframes)
 {
        // called from outside the audio thread, so this should be safe
        // only do audio as analysis is (currently) only for audio plugins
-       _signal_analysis_inputs.ensure_buffers(  DataType::AUDIO, input_streams().n_audio(),  nframes);
-       _signal_analysis_outputs.ensure_buffersDataType::AUDIO, output_streams().n_audio(), nframes);
+       _signal_analysis_inputs.ensure_buffers (DataType::AUDIO, input_streams().n_audio(),  nframes);
+       _signal_analysis_outputs.ensure_buffers (DataType::AUDIO, output_streams().n_audio(), nframes);
 
        _signal_analysis_collected_nframes   = 0;
        _signal_analysis_collect_nframes_max = nframes;
@@ -1557,17 +2907,46 @@ PluginInsert::add_plugin (boost::shared_ptr<Plugin> plugin)
        plugin->set_insert_id (this->id());
 
        if (_plugins.empty()) {
-                /* first (and probably only) plugin instance - connect to relevant signals
-                 */
+               /* first (and probably only) plugin instance - connect to relevant signals */
 
                plugin->ParameterChangedExternally.connect_same_thread (*this, boost::bind (&PluginInsert::parameter_changed_externally, this, _1, _2));
-                plugin->StartTouch.connect_same_thread (*this, boost::bind (&PluginInsert::start_touch, this, _1));
-                plugin->EndTouch.connect_same_thread (*this, boost::bind (&PluginInsert::end_touch, this, _1));
+               plugin->StartTouch.connect_same_thread (*this, boost::bind (&PluginInsert::start_touch, this, _1));
+               plugin->EndTouch.connect_same_thread (*this, boost::bind (&PluginInsert::end_touch, this, _1));
+               _custom_sinks = plugin->get_info()->n_inputs;
+               // cache sidechain port count
+               _cached_sidechain_pins.reset ();
+               const ChanCount& nis (plugin->get_info()->n_inputs);
+               for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
+                       for (uint32_t in = 0; in < nis.get (*t); ++in) {
+                               const Plugin::IOPortDescription& iod (plugin->describe_io_port (*t, true, in));
+                               if (iod.is_sidechain) {
+                                       _cached_sidechain_pins.set (*t, 1 + _cached_sidechain_pins.n(*t));
+                               }
+                       }
+               }
        }
+#if (defined WINDOWS_VST_SUPPORT || defined LXVST_SUPPORT)
+       boost::shared_ptr<VSTPlugin> vst = boost::dynamic_pointer_cast<VSTPlugin> (plugin);
+       if (vst) {
+               vst->set_insert (this, _plugins.size ());
+       }
+#endif
 
        _plugins.push_back (plugin);
 }
 
+bool
+PluginInsert::load_preset (ARDOUR::Plugin::PresetRecord pr)
+{
+       bool ok = true;
+       for (Plugins::iterator i = _plugins.begin(); i != _plugins.end(); ++i) {
+               if (! (*i)->load_preset (pr)) {
+                       ok = false;
+               }
+       }
+       return ok;
+}
+
 void
 PluginInsert::realtime_handle_transport_stopped ()
 {
@@ -1592,6 +2971,16 @@ PluginInsert::monitoring_changed ()
        }
 }
 
+void
+PluginInsert::latency_changed ()
+{
+       // this is called in RT context, LatencyChanged is emitted after run()
+       _latency_changed = true;
+       // XXX This also needs a proper API not an owner() hack.
+       assert (owner ());
+       static_cast<Route*>(owner ())->processor_latency_changed (); /* EMIT SIGNAL */
+}
+
 void
 PluginInsert::start_touch (uint32_t param_id)
 {
@@ -1609,3 +2998,24 @@ PluginInsert::end_touch (uint32_t param_id)
                 ac->stop_touch (true, session().audible_frame());
         }
 }
+
+std::ostream& operator<<(std::ostream& o, const ARDOUR::PluginInsert::Match& m)
+{
+       switch (m.method) {
+               case PluginInsert::Impossible: o << "Impossible"; break;
+               case PluginInsert::Delegate:   o << "Delegate"; break;
+               case PluginInsert::NoInputs:   o << "NoInputs"; break;
+               case PluginInsert::ExactMatch: o << "ExactMatch"; break;
+               case PluginInsert::Replicate:  o << "Replicate"; break;
+               case PluginInsert::Split:      o << "Split"; break;
+               case PluginInsert::Hide:       o << "Hide"; break;
+       }
+       o << " cnt: " << m.plugins
+               << (m.strict_io ? " strict-io" : "")
+               << (m.custom_cfg ? " custom-cfg" : "");
+       if (m.method == PluginInsert::Hide) {
+               o << " hide: " << m.hide;
+       }
+       o << "\n";
+       return o;
+}