prepare plugin configuration and replacing instruments
[ardour.git] / libs / ardour / plugin_insert.cc
index bf10a6595c214c2c221a92debb746b1815928443..a72412a86875fbc99d99520249287265802b2da1 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"
@@ -105,9 +106,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;
@@ -117,12 +123,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 */
@@ -139,6 +147,13 @@ PluginInsert::set_count (uint32_t num)
 }
 
 
+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)
 {
@@ -159,6 +174,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)
 {
@@ -258,7 +284,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;
@@ -300,7 +325,7 @@ PluginInsert::natural_output_streams() const
 {
 #ifdef MIXBUS
        if (is_channelstrip ()) {
-               return _configured_out;
+               return ChanCount::min (_configured_out, ChanCount (DataType::AUDIO, 2));
        }
 #endif
        return _plugins[0]->get_info()->n_outputs;
@@ -311,7 +336,7 @@ PluginInsert::natural_input_streams() const
 {
 #ifdef MIXBUS
        if (is_channelstrip ()) {
-               return _configured_in;
+               return ChanCount::min (_configured_in, ChanCount (DataType::AUDIO, 2));
        }
 #endif
        return _plugins[0]->get_info()->n_inputs;
@@ -341,15 +366,42 @@ PluginInsert::plugin_latency () const {
 }
 
 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
@@ -474,6 +526,39 @@ PluginInsert::flush ()
        }
 }
 
+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);
+                       }
+               }
+       }
+}
+
 void
 PluginInsert::connect_and_run (BufferSet& bufs, pframes_t nframes, framecnt_t offset, bool with_auto, framepos_t now)
 {
@@ -624,10 +709,18 @@ PluginInsert::connect_and_run (BufferSet& bufs, pframes_t nframes, framecnt_t of
                        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) {
-                                       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
+                               } 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);
+                                       }
                                }
                        }
                }
@@ -693,30 +786,8 @@ PluginInsert::connect_and_run (BufferSet& bufs, pframes_t nframes, framecnt_t of
                                deactivate ();
                        }
                }
-
-               // 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) {
-                                       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 == out) {
-                                                       mapped = true;
-                                                       break;
-                                               }
-                                       }
-                               }
-                               if (!mapped) {
-                                       bufs.get (*t, out).silence (nframes, offset);
-                               }
-                       }
-               }
+               // now silence unconnected outputs
+               inplace_silence_unconnected (bufs, _out_map, nframes, offset);
        }
 
        if (collect_signal_nframes > 0) {
@@ -746,6 +817,117 @@ PluginInsert::connect_and_run (BufferSet& bufs, pframes_t nframes, framecnt_t of
        }
 }
 
+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
+       ChanMapping in_map (input_map ());
+       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)
 {
@@ -753,12 +935,20 @@ PluginInsert::silence (framecnt_t nframes)
                return;
        }
 
+       _delaybuffers.flush ();
+
        ChanMapping in_map (natural_input_streams ());
        ChanMapping out_map (natural_output_streams ());
-
-       // TODO run sidechain (delaylines)
+       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), 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), in_map, out_map, nframes, 0);
        }
 }
 
@@ -781,35 +971,8 @@ PluginInsert::run (BufferSet& bufs, framepos_t start_frame, framepos_t end_frame
                }
 
        } else {
-               // TODO use mapping in bypassed mode ?!
-               // -> do we bypass the processor or the plugin
-
-               // TODO include sidechain??
-
-               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
-                        * 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;
@@ -1029,6 +1192,20 @@ PluginInsert::set_thru_map (ChanMapping m) {
        }
 }
 
+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
 {
@@ -1164,7 +1341,7 @@ PluginInsert::sanitize_maps ()
        /* strip dead wood */
        PinMappings new_ins;
        PinMappings new_outs;
-       ChanMapping new_thru (_thru_map);
+       ChanMapping new_thru;
 
        for (uint32_t pc = 0; pc < get_count(); ++pc) {
                ChanMapping new_in;
@@ -1208,6 +1385,17 @@ PluginInsert::sanitize_maps ()
                }
        }
 
+       /* 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) {
@@ -1244,49 +1432,73 @@ 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 ();
        _thru_map = ChanMapping ();
-       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));
 
-               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;
@@ -1306,12 +1518,12 @@ PluginInsert::configure_io (ChanCount in, ChanCount out)
        ChanCount old_in;
        ChanCount old_internal;
        ChanCount old_out;
+       ChanCount old_pins;
 
-       if (_configured) {
-               old_in = _configured_in;
-               old_internal = _configured_internal;
-               old_out = _configured_out;
-       }
+       old_pins = natural_input_streams();
+       old_in = _configured_in;
+       old_out = _configured_out;
+       old_internal = _configured_internal;
 
        _configured_in = in;
        _configured_internal = in;
@@ -1327,6 +1539,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 */
@@ -1334,7 +1549,7 @@ PluginInsert::configure_io (ChanCount in, ChanCount out)
 #ifndef NDEBUG
        if (DEBUG_ENABLED(DEBUG::ChanMapping)) {
                DEBUG_STR_DECL(a);
-               DEBUG_STR_APPEND(a, string_compose ("Match '%1': ",  name()));
+               DEBUG_STR_APPEND(a, string_compose ("%1: ",  name()));
                DEBUG_STR_APPEND(a, _match);
                DEBUG_TRACE (DEBUG::ChanMapping, DEBUG_STR(a).str());
        }
@@ -1359,19 +1574,36 @@ PluginInsert::configure_io (ChanCount in, ChanCount out)
                break;
        case Delegate:
                {
-                       ChanCount dout;
+                       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;
-                       bool const r = _plugins.front()->can_support_io_configuration (in, dout, &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);
-                       assert (_match.strict_io || dout.n_audio() == out.n_audio()); // sans midi bypass
                        if (useins.n_audio() == 0) {
-                               useins = in;
+                               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:
@@ -1383,31 +1615,62 @@ PluginInsert::configure_io (ChanCount in, ChanCount out)
                break;
        }
 
+       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 */
-               if (old_internal != _configured_internal) {
-                       mapping_changed = sanitize_maps ();
-               }
+               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 ()) { _maps_from_state = false; }
+               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) {
-                       _maps_from_state = false;
+               if (_maps_from_state && old_in == in && old_out == out) {
                        mapping_changed = true;
                        sanitize_maps ();
                } else {
                        /* generate a new mapping */
                        mapping_changed = reset_map (false);
                }
+               _maps_from_state = false;
        }
 
        if (mapping_changed) {
@@ -1439,13 +1702,37 @@ PluginInsert::configure_io (ChanCount in, ChanCount out)
        _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_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 ());
+       _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
@@ -1481,12 +1768,29 @@ 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 ("%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) const
+PluginInsert::internal_can_support_io_configuration (ChanCount const & inx, ChanCount& out) const
 {
        if (_plugins.empty()) {
                return Match();
@@ -1501,8 +1805,13 @@ PluginInsert::private_can_support_io_configuration (ChanCount const & inx, ChanC
 
        /* 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, 1, _strict_io, true);
+               } else {
+                       return Match (ExactMatch, get_count(), _strict_io, true);
+               }
        }
 
        /* try automatic configuration */
@@ -1517,12 +1826,12 @@ 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));
                        out = ChanCount::min (out, max_out);
-                       DEBUG_TRACE (DEBUG::ChanMapping, string_compose ("special case strict-i/o instrument: %1\n", name()));
+                       DEBUG_TRACE (DEBUG::ChanMapping, string_compose ("%1: special case strict-i/o instrument\n", name()));
                        return m;
                }
 
@@ -1538,7 +1847,7 @@ PluginInsert::private_can_support_io_configuration (ChanCount const & inx, ChanC
                                                f = max (f, (uint32_t) ceil (inx.get(*t) / (float)nout));
                                        }
                                        out = inx;
-                                       DEBUG_TRACE (DEBUG::ChanMapping, string_compose ("special case strict-i/o generator: %1\n", name()));
+                                       DEBUG_TRACE (DEBUG::ChanMapping, string_compose ("%1: special case strict-i/o for generator\n", name()));
                                        return Match (Replicate, f, _strict_io);
                                }
                                break;
@@ -1556,12 +1865,14 @@ PluginInsert::private_can_support_io_configuration (ChanCount const & inx, ChanC
 
        ChanCount ns_inputs  = inputs - sidechain_input_pins ();
 
-       DEBUG_TRACE (DEBUG::ChanMapping, string_compose ("resolving 'Impossible' match for %1\n", name()));
+       DEBUG_TRACE (DEBUG::ChanMapping, string_compose ("%1: resolving 'Impossible' match...\n", name()));
 
        if (info->reconfigurable_io()) {
                ChanCount useins;
-               // TODO add sidechains here
-               bool const r = _plugins.front()->can_support_io_configuration (inx, out, &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);
@@ -1624,8 +1935,13 @@ 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 */
-               bool const r = _plugins.front()->can_support_io_configuration (in, out);
+               /* 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);
                }
@@ -1637,11 +1953,11 @@ PluginInsert::automatic_can_support_io_configuration (ChanCount const & inx, Cha
        ChanCount ns_inputs  = inputs - sidechain_input_pins ();
 
        if (in.get(DataType::MIDI) == 1 && outputs.get(DataType::MIDI) == 0) {
-               DEBUG_TRACE (DEBUG::ChanMapping, string_compose ("bypassing midi-data around %1\n", name()));
+               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::ChanMapping, 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);
        }
 
@@ -1787,7 +2103,9 @@ 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");
@@ -1826,7 +2144,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
@@ -1867,7 +2185,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) {
@@ -1939,6 +2257,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"
@@ -1948,15 +2279,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;
 
@@ -2054,25 +2376,33 @@ 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 ();
        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 <= get_count()) {
+                       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 <= get_count()) {
+                       if (pc >= 0 && pc <= (long) get_count()) {
                                _out_map[pc] = ChanMapping (**i);
                                ++out_maps;
                        }
@@ -2080,6 +2410,8 @@ PluginInsert::set_state(const XMLNode& node, int version)
                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);
@@ -2100,6 +2432,7 @@ PluginInsert::set_state(const XMLNode& node, int version)
                }
        }
 
+       PluginConfigChanged (); /* EMIT SIGNAL */
        return 0;
 }
 
@@ -2134,7 +2467,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;
@@ -2390,6 +2723,9 @@ 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 (internal_input_streams (), internal_output_streams ());
                _impulseAnalysisPlugin = ret;
@@ -2425,7 +2761,8 @@ PluginInsert::add_plugin (boost::shared_ptr<Plugin> plugin)
                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->LatencyChanged.connect_same_thread (*this, boost::bind (&PluginInsert::latency_changed, this, _1, _2));
-               // cache sidechain ports
+               _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) {
@@ -2446,6 +2783,18 @@ PluginInsert::add_plugin (boost::shared_ptr<Plugin> plugin)
        _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 ()
 {