fix plugin silence runs (e.g. during audition)
[ardour.git] / libs / ardour / plugin_insert.cc
index 2f1169245cef993dd2dcb872ef81960d4fb7aefa..bb120d3fad89968035647a570289257857cd8fbb 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"
@@ -80,7 +81,7 @@ PluginInsert::PluginInsert (Session& s, boost::shared_ptr<Plugin> plug)
                add_plugin (plug);
                create_automatable_parameters ();
                const ChanCount& sc (sidechain_input_pins ());
-               if (sc.n_total () > 0) {
+               if (sc.n_audio () > 0) {
                        add_sidechain (sc.n_audio ());
                }
        }
@@ -117,12 +118,14 @@ 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 */
@@ -159,6 +162,17 @@ PluginInsert::set_custom_cfg (bool b)
        }
 }
 
+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)
 {
@@ -166,9 +180,13 @@ PluginInsert::add_sidechain (uint32_t n_audio)
        if (_sidechain) {
                return false;
        }
-       // TODO add route-name, plugin name and shorten.. (plugin name can be long and conatain odd chars)
-       std::string n = "Sidechain " + id().to_s(); /// XXX
-       SideChain *sc = new SideChain (_session, n);
+       std::ostringstream n;
+       if (n_audio > 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) {
@@ -254,7 +272,6 @@ PluginInsert::internal_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;
@@ -294,12 +311,22 @@ PluginInsert::internal_input_streams() const
 ChanCount
 PluginInsert::natural_output_streams() const
 {
+#ifdef MIXBUS
+       if (is_channelstrip ()) {
+               return _configured_out;
+       }
+#endif
        return _plugins[0]->get_info()->n_outputs;
 }
 
 ChanCount
 PluginInsert::natural_input_streams() const
 {
+#ifdef MIXBUS
+       if (is_channelstrip ()) {
+               return _configured_in;
+       }
+#endif
        return _plugins[0]->get_info()->n_inputs;
 }
 
@@ -321,16 +348,19 @@ 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();
-
-       return pi->n_inputs.n_midi() != 0 &&
-               pi->n_outputs.n_audio() > 0;
+       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;
 }
 
 void
@@ -458,29 +488,56 @@ PluginInsert::flush ()
 void
 PluginInsert::connect_and_run (BufferSet& bufs, pframes_t nframes, framecnt_t offset, bool with_auto, framepos_t now)
 {
+       // 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 ());
+       }
 
-#if 1
-       // TODO optimize special case.
-       // Currently this never triggers because the in_map for "Split" triggeres no_inplace.
        if (_match.method == Split && !_no_inplace) {
-               assert (in_map.size () == 1);
-               in_map[0] = ChanMapping (ChanCount::max (natural_input_streams (), _configured_in)); // no sidechain
-               ChanCount const in_streams = internal_input_streams ();
-               /* copy the first stream's audio buffer contents to the others */
-               bool valid;
-               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) {
-                               uint32_t idx = in_map[0].get (DataType::AUDIO, i, &valid);
+               // 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) {
-                                       bufs.get_audio(idx).read_from(bufs.get_audio(first_idx), nframes, offset, offset);
+                                       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 ());
        }
-#endif
 
        bufs.set_count(ChanCount::max(bufs.count(), _configured_internal));
        bufs.set_count(ChanCount::max(bufs.count(), _configured_out));
@@ -554,19 +611,46 @@ PluginInsert::connect_and_run (BufferSet& bufs, pframes_t nframes, framecnt_t of
        } 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;
 
-               uint32_t pc = 0;
-               // TODO optimize this flow. prepare during configure_io()
+               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);
+                               if (valid) {
+                                       uint32_t m = out + natural_input_streams ().get (*t);
+                                       _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
+                               }
+                       }
+               }
+
+               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;
+                       ARDOUR::ChanMapping i_out_map (out_map[pc]);
                        ARDOUR::ChanCount mapped;
-                       ARDOUR::ChanCount backmap;
 
-                       // map inputs sequentially
+                       /* 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;
@@ -581,53 +665,39 @@ PluginInsert::connect_and_run (BufferSet& bufs, pframes_t nframes, framecnt_t of
                                }
                        }
 
-                       // TODO use map_offset_to()  instead ??
-                       backmap = mapped;
-
-                       // map outputs
+                       /* outputs are mapped to inplace_bufs after the inputs */
                        for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
-                               for (uint32_t out = 0; out < natural_output_streams().get (*t); ++out) {
-                                       uint32_t m = mapped.get (*t);
-                                       inplace_bufs.get (*t, m).silence (nframes, offset);
-                                       i_out_map.set (*t, out, m);
-                                       mapped.set (*t, m + 1);
-                               }
+                               i_out_map.offset_to (*t, natural_input_streams ().get (*t));
                        }
 
-                       if ((*i)->connect_and_run(inplace_bufs, i_in_map, i_out_map, nframes, offset)) {
+                       if ((*i)->connect_and_run (inplace_bufs, i_in_map, i_out_map, nframes, offset)) {
                                deactivate ();
                        }
-
-                       // copy back outputs
-                       for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
-                               for (uint32_t out = 0; out < natural_output_streams().get (*t); ++out) {
-                                       uint32_t m = backmap.get (*t);
-                                       bool valid;
-                                       uint32_t out_idx = out_map[pc].get (*t, out, &valid);
-                                       if (valid) {
-                                               bufs.get (*t, out_idx).read_from (inplace_bufs.get (*t, m), nframes, offset, offset);
-                                               used_outputs.set (*t, out_idx, 1); // mark as used
-                                       }
-                                       backmap.set (*t, m + 1);
-                               }
-                       }
                }
-               /* all instances have completed, now clear outputs that have not been written to.
-                * (except midi bypass)
-                */
+
+               /* all instances have completed, now copy data that was written
+                * and zero unconnected buffers */
+               ARDOUR::ChanMapping nonzero_out (used_outputs);
                if (has_midi_bypass ()) {
-                       used_outputs.set (DataType::MIDI, 0, 1); // 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) { continue; }
-                               bufs.get (*t, out).silence (nframes, offset);
+                               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, in_map[pc], out_map[pc], nframes, offset)) {
@@ -694,12 +764,25 @@ PluginInsert::silence (framecnt_t nframes)
                return;
        }
 
-       ChanMapping in_map (natural_input_streams ());
-       ChanMapping out_map (natural_output_streams ());
+       _delaybuffers.flush ();
 
-       // TODO run sidechain (delaylines)
-       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);
+#ifdef MIXBUS
+       if (is_channelstrip ()) {
+               if (_configured_in.n_audio() > 0) {
+                       ChanCount maxbuf = ChanCount::min (_configured_in, ChanCount (DataType::AUDIO, 2));
+                       ChanMapping mb_in_map (ChanCount::min (_configured_in, ChanCount (DataType::AUDIO, 2)));
+                       ChanMapping mb_out_map (ChanCount::min (_configured_out, ChanCount (DataType::AUDIO, 2)));
+                       _plugins.front()->connect_and_run (_session.get_scratch_buffers (maxbuf, true), mb_in_map, mb_out_map, nframes, 0);
+               }
+       } else
+#endif
+       {
+               ChanMapping in_map (natural_input_streams ());
+               ChanMapping out_map (natural_output_streams ());
+               ChanCount maxbuf = ChanCount::max (natural_input_streams (), natural_output_streams());
+               for (Plugins::iterator i = _plugins.begin(); i != _plugins.end(); ++i) {
+                       (*i)->connect_and_run (_session.get_scratch_buffers (maxbuf, true), in_map, out_map, nframes, 0);
+               }
        }
 }
 
@@ -935,9 +1018,11 @@ PluginInsert::set_input_map (uint32_t num, ChanMapping m) {
        if (num < _in_map.size()) {
                bool changed = _in_map[num] != m;
                _in_map[num] = m;
-               sanitize_maps ();
+               changed |= sanitize_maps ();
                if (changed) {
                        PluginMapChanged (); /* EMIT SIGNAL */
+                       _mapping_changed = true;
+                       _session.set_dirty();
                }
        }
 }
@@ -947,13 +1032,27 @@ PluginInsert::set_output_map (uint32_t num, ChanMapping m) {
        if (num < _out_map.size()) {
                bool changed = _out_map[num] != m;
                _out_map[num] = m;
-               sanitize_maps ();
+               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();
+       }
+}
+
 ChanMapping
 PluginInsert::input_map () const
 {
@@ -1019,6 +1118,69 @@ PluginInsert::is_channelstrip () const {
 }
 #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;
+               }
+       }
+       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 ()
 {
@@ -1026,6 +1188,8 @@ PluginInsert::sanitize_maps ()
        /* 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;
@@ -1051,6 +1215,7 @@ PluginInsert::sanitize_maps ()
                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) {
@@ -1067,11 +1232,46 @@ PluginInsert::sanitize_maps ()
                }
        }
 
-       if (_in_map != new_ins || _out_map != new_outs) {
+       /* 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);
+                       }
+               }
+       }
+
+       if (has_midi_bypass ()) {
+               // TODO: include midi-bypass in the thru set,
+               // remove dedicated handling.
+               new_thru.unset (DataType::MIDI, 0);
+       }
+
+       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;
 }
@@ -1079,54 +1279,81 @@ PluginInsert::sanitize_maps ()
 bool
 PluginInsert::reset_map (bool emit)
 {
-       uint32_t pc = 0;
        const PinMappings old_in (_in_map);
        const PinMappings old_out (_out_map);
 
        _in_map.clear ();
        _out_map.clear ();
-       for (Plugins::iterator i = _plugins.begin(); i != _plugins.end(); ++i, ++pc) {
-               ChanCount ns_inputs  = natural_input_streams() - sidechain_input_pins ();
-               if (_match.method == Split) {
-                       _in_map[pc] = ChanMapping ();
-                       /* connect no sidechain sinks in round-robin fashion */
-                       for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
-                               const uint32_t cend = _configured_in.get (*t);
-                               if (cend == 0) { continue; }
-                               uint32_t c = 0;
-                               for (uint32_t in = 0; in < ns_inputs.get (*t); ++in) {
-                                       _in_map[pc].set (*t, in, c);
-                                       c = (c + 1) % cend;
-                               }
-                       }
-               } else {
-                       _in_map[pc] = ChanMapping (ChanCount::min (ns_inputs, _configured_in));
-               }
-               _out_map[pc] = ChanMapping (ChanCount::min (natural_output_streams(), _configured_out));
+       _thru_map = ChanMapping ();
 
-               for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
+       /* 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);
-                       _in_map[pc].offset_to(*t, stride * pc);
-                       _out_map[pc].offset_to(*t, pc * natural_output_streams().get(*t));
 
-                       // connect side-chains
+                       /* 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;
-                       if (sc_len == 0) { continue; }
-                       uint32_t c = 0;
-                       for (uint32_t in = ns_inputs.get (*t); in < nis; ++in) {
-                               _in_map[pc].set (*t, in, sc_start + c);
-                               c = (c + 1) % sc_len;
+                       /* ...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);
+                                               }
+                                       }
+                               }
                        }
                }
        }
+
+       /* 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;
 }
@@ -1159,6 +1386,9 @@ PluginInsert::configure_io (ChanCount in, ChanCount out)
                        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 */
@@ -1191,14 +1421,23 @@ PluginInsert::configure_io (ChanCount in, ChanCount out)
                break;
        case Delegate:
                {
-                       ChanCount dout;
+                       ChanCount dout (in); // hint
+                       if (_custom_cfg) { 
+                               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;
                        bool const r = _plugins.front()->can_support_io_configuration (in, dout, &useins);
                        assert (r);
-                       assert (_match.strict_io || dout.n_audio() == out.n_audio()); // sans midi bypass
                        if (useins.n_audio() == 0) {
                                useins = in;
                        }
+                       DEBUG_TRACE (DEBUG::ChanMapping, string_compose ("Delegate configuration: %1 %2 %3\n", name(), useins, dout));
+
                        if (_plugins.front()->configure_io (useins, dout) == false) {
                                PluginIoReConfigure (); /* EMIT SIGNAL */
                                _configured = false;
@@ -1222,13 +1461,18 @@ PluginInsert::configure_io (ChanCount in, ChanCount out)
                        && _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 */
                if (old_internal != _configured_internal) {
                        mapping_changed = sanitize_maps ();
                }
        } else if (_match.custom_cfg && _configured) {
+               assert (_maps_from_state == false);
                mapping_changed = sanitize_maps ();
        } else {
+#ifdef MIXBUS
+               if (is_channelstrip ()) { _maps_from_state = false; }
+#endif
                if (_maps_from_state) {
                        _maps_from_state = false;
                        mapping_changed = true;
@@ -1257,23 +1501,35 @@ PluginInsert::configure_io (ChanCount in, ChanCount out)
                                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
        }
 
-       // auto-detect if inplace processing is possible
-       bool inplace_ok = true;
-       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;
-               }
-       }
-       _no_inplace = !inplace_ok || _plugins.front()->inplace_broken ();
+       _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)
+        */
+       _required_buffers =ChanCount::max (_configured_internal,
+                       natural_input_streams () + ChanCount::max (_configured_out, natural_output_streams () * get_count ()));
 
        if (old_in != in || old_out != out || old_internal != _configured_internal
                        || (old_match.method != _match.method && (old_match.method == Split || _match.method == Split))
@@ -1281,6 +1537,9 @@ PluginInsert::configure_io (ChanCount in, ChanCount out)
                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
@@ -1314,21 +1573,50 @@ PluginInsert::can_support_io_configuration (const ChanCount& in, ChanCount& 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 ("using output preset: %1 %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) const
+PluginInsert::internal_can_support_io_configuration (ChanCount const & inx, ChanCount& out) const
 {
        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;
-               return Match (ExactMatch, get_count(), _strict_io, true); // XXX
+               if (info->reconfigurable_io()) {
+                       return Match (Delegate, get_count(), _strict_io, true);
+               } else {
+                       return Match (ExactMatch, get_count(), _strict_io, true);
+               }
        }
 
        /* try automatic configuration */
@@ -1343,7 +1631,7 @@ PluginInsert::private_can_support_io_configuration (ChanCount const & inx, ChanC
                m.strict_io = true;
 
                /* special case MIDI instruments */
-               if (is_midi_instrument()) {
+               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));
@@ -1368,8 +1656,6 @@ PluginInsert::private_can_support_io_configuration (ChanCount const & inx, ChanC
                                        return Match (Replicate, f, _strict_io);
                                }
                                break;
-                       case Split:
-                               break;
                        default:
                                break;
                }
@@ -1388,13 +1674,15 @@ PluginInsert::private_can_support_io_configuration (ChanCount const & inx, ChanC
 
        if (info->reconfigurable_io()) {
                ChanCount useins;
-               // TODO add sidechains here
+               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, out, &useins);
                if (!r) {
                        // houston, we have a problem.
                        return Match (Impossible, 0);
                }
-               return Match (Delegate, 1);
+               return Match (Delegate, 1, _strict_io);
        }
 
        ChanCount midi_bypass;
@@ -1413,7 +1701,7 @@ PluginInsert::private_can_support_io_configuration (ChanCount const & inx, ChanC
        }
        if (f > 0 && outputs * f >= _configured_out) {
                out = outputs * f + midi_bypass;
-               return Match (Replicate, f);
+               return Match (Replicate, f, _strict_io);
        }
 
        // add at least as many plugins needed to connect all inputs (w/o sidechain pins)
@@ -1425,7 +1713,7 @@ PluginInsert::private_can_support_io_configuration (ChanCount const & inx, ChanC
        }
        if (f > 0) {
                out = outputs * f + midi_bypass;
-               return Match (Replicate, f);
+               return Match (Replicate, f, _strict_io);
        }
 
        // add at least as many plugins needed to connect all inputs
@@ -1436,7 +1724,7 @@ PluginInsert::private_can_support_io_configuration (ChanCount const & inx, ChanC
                f = max (f, (uint32_t) ceil (inx.get(*t) / (float)nin));
        }
        out = outputs * f + midi_bypass;
-       return Match (Replicate, f);
+       return Match (Replicate, f, _strict_io);
 }
 
 /* this is the original Ardour 3/4 behavior, mainly for backwards compatibility */
@@ -1452,7 +1740,12 @@ PluginInsert::automatic_can_support_io_configuration (ChanCount const & inx, Cha
        ChanCount midi_bypass;
 
        if (info->reconfigurable_io()) {
-               /* Plugin has flexible I/O, so delegate to it */
+               /* 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, out);
                if (!r) {
                        return Match (Impossible, 0);
@@ -1510,7 +1803,7 @@ PluginInsert::automatic_can_support_io_configuration (ChanCount const & inx, Cha
 
        uint32_t f             = 0;
        bool     can_replicate = true;
-       for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
+       for (DataType::iterator t = DataType::begin(); t != DataType::end() && can_replicate; ++t) {
 
                // ignore side-chains
                uint32_t nin = ns_inputs.get (*t);
@@ -1616,6 +1909,7 @@ PluginInsert::state (bool full)
         * in case the plugin goes missing) */
        node.add_child_nocopy (* _configured_in.state (X_("ConfiguredInput")));
        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");
@@ -1626,6 +1920,7 @@ PluginInsert::state (bool full)
                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));
@@ -1881,8 +2176,6 @@ PluginInsert::set_state(const XMLNode& node, int version)
                _custom_cfg = string_is_affirmative (prop->value());
        }
 
-       // TODO load/add sidechain
-
        uint32_t in_maps = 0;
        uint32_t out_maps = 0;
        XMLNodeList kids = node.children ();
@@ -1890,6 +2183,9 @@ PluginInsert::set_state(const XMLNode& node, int version)
                if ((*i)->name() == X_("ConfiguredOutput")) {
                        _custom_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 <= get_count()) {
@@ -1904,6 +2200,11 @@ PluginInsert::set_state(const XMLNode& node, int version)
                                ++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);
@@ -1924,6 +2225,7 @@ PluginInsert::set_state(const XMLNode& node, int version)
                }
        }
 
+       PluginConfigChanged (); /* EMIT SIGNAL */
        return 0;
 }
 
@@ -2248,7 +2550,8 @@ PluginInsert::add_plugin (boost::shared_ptr<Plugin> plugin)
                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));
-               // cache sidechain ports
+               plugin->LatencyChanged.connect_same_thread (*this, boost::bind (&PluginInsert::latency_changed, this, _1, _2));
+               // 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) {
@@ -2260,6 +2563,12 @@ PluginInsert::add_plugin (boost::shared_ptr<Plugin> plugin)
                        }
                }
        }
+#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);
 }
 
@@ -2287,6 +2596,13 @@ PluginInsert::monitoring_changed ()
        }
 }
 
+void
+PluginInsert::latency_changed (framecnt_t, framecnt_t)
+{
+       // this is called in RT context, LatencyChanged is emitted after run()
+       _latency_changed = true;
+}
+
 void
 PluginInsert::start_touch (uint32_t param_id)
 {