update processor in-place mode when pin-mapping changes
[ardour.git] / libs / ardour / plugin_insert.cc
index 0206941235e127b49cb154be0094b3fa4b31e014..cdf1e600a8451bfbf6f6e93e4b0a19b2347a29f1 100644 (file)
@@ -72,13 +72,17 @@ PluginInsert::PluginInsert (Session& s, boost::shared_ptr<Plugin> plug)
        , _no_inplace (false)
        , _strict_io (false)
        , _custom_cfg (false)
-       , _pending_no_inplace (false)
+       , _maps_from_state (false)
 {
        /* the first is the master */
 
        if (plug) {
                add_plugin (plug);
                create_automatable_parameters ();
+               const ChanCount& sc (sidechain_input_pins ());
+               if (sc.n_total () > 0) {
+                       add_sidechain (sc.n_audio ());
+               }
        }
 }
 
@@ -86,6 +90,16 @@ PluginInsert::~PluginInsert ()
 {
 }
 
+void
+PluginInsert::set_strict_io (bool b)
+{
+       bool changed = _strict_io != b;
+       _strict_io = b;
+       if (changed) {
+               PluginConfigChanged (); /* EMIT SIGNAL */
+       }
+}
+
 bool
 PluginInsert::set_count (uint32_t num)
 {
@@ -111,12 +125,14 @@ PluginInsert::set_count (uint32_t num)
                                /* XXX do something */
                        }
                }
+               PluginConfigChanged (); /* EMIT SIGNAL */
 
        } else if (num < _plugins.size()) {
                uint32_t diff = _plugins.size() - num;
                for (uint32_t n= 0; n < diff; ++n) {
                        _plugins.pop_back();
                }
+               PluginConfigChanged (); /* EMIT SIGNAL */
        }
 
        return true;
@@ -126,7 +142,51 @@ PluginInsert::set_count (uint32_t num)
 void
 PluginInsert::set_outputs (const ChanCount& c)
 {
+       bool changed = (_custom_out != c) && _custom_cfg;
        _custom_out = c;
+       if (changed) {
+               PluginConfigChanged (); /* EMIT SIGNAL */
+       }
+}
+
+void
+PluginInsert::set_custom_cfg (bool b)
+{
+       bool changed = _custom_cfg != b;
+       _custom_cfg = b;
+       if (changed) {
+               PluginConfigChanged (); /* EMIT SIGNAL */
+       }
+}
+
+bool
+PluginInsert::add_sidechain (uint32_t n_audio)
+{
+       // caller must hold process lock
+       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);
+       _sidechain = boost::shared_ptr<SideChain> (sc);
+       _sidechain->activate ();
+       for (uint32_t n = 0; n < n_audio; ++n) {
+               _sidechain->input()->add_port ("", owner()); // add a port, don't connect.
+       }
+       PluginConfigChanged (); /* EMIT SIGNAL */
+       return true;
+}
+
+bool
+PluginInsert::del_sidechain ()
+{
+       if (!_sidechain) {
+               return false;
+       }
+       _sidechain.reset ();
+       PluginConfigChanged (); /* EMIT SIGNAL */
+       return true;
 }
 
 void
@@ -157,6 +217,13 @@ PluginInsert::input_streams() const
        return _configured_in;
 }
 
+ChanCount
+PluginInsert::internal_streams() const
+{
+       assert (_configured);
+       return _configured_internal;
+}
+
 ChanCount
 PluginInsert::internal_output_streams() const
 {
@@ -227,15 +294,31 @@ 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;
 }
 
+ChanCount
+PluginInsert::sidechain_input_pins() const
+{
+       return _cached_sidechain_pins;
+}
+
 bool
 PluginInsert::has_no_inputs() const
 {
@@ -385,40 +468,20 @@ 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);
-
-#if 1
-       // auto-detect if inplace processing is possible
-       // TODO: do this once. during configure_io and every time the
-       // plugin-count or mapping changes.
-       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;
-               }
+       if (_mapping_changed) {
+               _no_inplace = check_inplace ();
+               _mapping_changed = false;
        }
 
-       if (_pending_no_inplace != !inplace_ok) {
-#ifndef NDEBUG // this 'cerr' needs to go ASAP.
-               cerr << name () << " automatically set : " << (inplace_ok ? "Use Inplace" : "No Inplace") << "\n"; // XXX
-#endif
-               _pending_no_inplace = !inplace_ok;
-       }
-#endif
-
-       _no_inplace = _pending_no_inplace || _plugins.front()->inplace_broken ();
-
-
 #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 (max (natural_input_streams (), _configured_in));
+               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;
@@ -434,7 +497,7 @@ PluginInsert::connect_and_run (BufferSet& bufs, pframes_t nframes, framecnt_t of
        }
 #endif
 
-       bufs.set_count(ChanCount::max(bufs.count(), _configured_in));
+       bufs.set_count(ChanCount::max(bufs.count(), _configured_internal));
        bufs.set_count(ChanCount::max(bufs.count(), _configured_out));
 
        if (with_auto) {
@@ -492,14 +555,14 @@ PluginInsert::connect_and_run (BufferSet& bufs, pframes_t nframes, framecnt_t of
 
        }
 #ifdef MIXBUS
-       if (_plugins.front()->is_channelstrip() ) {
+       if (is_channelstrip ()) {
                if (_configured_in.n_audio() > 0) {
-                       ChanMapping mb_in_map (min (_configured_in, ChanCount (DataType::AUDIO, 2)));
-                       ChanMapping mb_out_map (min (_configured_out, 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 (bufs, mb_in_map, mb_out_map, nframes, offset);
 
-                       for (uint32_t out = _configured_in.n_audio; out < bufs.count().get (DataType::AUDIO); ++out) {
+                       for (uint32_t out = _configured_in.n_audio (); out < bufs.count().get (DataType::AUDIO); ++out) {
                                bufs.get (DataType::AUDIO, out).silence (nframes, offset);
                        }
                }
@@ -567,7 +630,7 @@ PluginInsert::connect_and_run (BufferSet& bufs, pframes_t nframes, framecnt_t of
                /* all instances have completed, now clear outputs that have not been written to.
                 * (except midi bypass)
                 */
-               if (bufs.count().n_midi() == 1 && natural_output_streams().get(DataType::MIDI) == 0) {
+               if (has_midi_bypass ()) {
                        used_outputs.set (DataType::MIDI, 0, 1); // Midi bypass.
                }
                for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
@@ -592,6 +655,9 @@ PluginInsert::connect_and_run (BufferSet& bufs, pframes_t nframes, framecnt_t of
                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;
@@ -646,17 +712,24 @@ PluginInsert::silence (framecnt_t nframes)
        ChanMapping in_map (natural_input_streams ());
        ChanMapping out_map (natural_output_streams ());
 
+       // 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);
        }
 }
 
 void
-PluginInsert::run (BufferSet& bufs, framepos_t start_frame, framepos_t /*end_frame*/, pframes_t nframes, bool)
+PluginInsert::run (BufferSet& bufs, framepos_t start_frame, framepos_t end_frame, pframes_t nframes, bool)
 {
        if (_pending_active) {
                /* run as normal if we are active or moving from inactive to active */
 
+               if (_sidechain) {
+                       // collect sidechain input for complete cycle (!)
+                       // TODO we need delaylines here for latency compensation
+                       _sidechain->run (bufs, start_frame, end_frame, nframes, true);
+               }
+
                if (_session.transport_rolling() || _session.bounce_processing()) {
                        automation_run (bufs, start_frame, nframes);
                } else {
@@ -667,6 +740,8 @@ PluginInsert::run (BufferSet& bufs, framepos_t start_frame, framepos_t /*end_fra
                // 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 ();
 
@@ -875,8 +950,10 @@ 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 ();
                if (changed) {
                        PluginMapChanged (); /* EMIT SIGNAL */
+                       _mapping_changed = true;
                }
        }
 }
@@ -886,31 +963,249 @@ 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 ();
                if (changed) {
                        PluginMapChanged (); /* EMIT SIGNAL */
+                       _mapping_changed = true;
                }
        }
 }
 
+ChanMapping
+PluginInsert::input_map () const
+{
+       ChanMapping rv;
+       uint32_t pc = 0;
+       for (PinMappings::const_iterator i = _in_map.begin (); i != _in_map.end (); ++i, ++pc) {
+               ChanMapping m (i->second);
+               const ChanMapping::Mappings& mp ((*i).second.mappings());
+               for (ChanMapping::Mappings::const_iterator tm = mp.begin(); tm != mp.end(); ++tm) {
+                       for (ChanMapping::TypeMapping::const_iterator i = tm->second.begin(); i != tm->second.end(); ++i) {
+                               rv.set (tm->first, i->first + pc * natural_input_streams().get(tm->first), i->second);
+                       }
+               }
+       }
+       return rv;
+}
+
+ChanMapping
+PluginInsert::output_map () const
+{
+       ChanMapping rv;
+       uint32_t pc = 0;
+       for (PinMappings::const_iterator i = _out_map.begin (); i != _out_map.end (); ++i, ++pc) {
+               ChanMapping m (i->second);
+               const ChanMapping::Mappings& mp ((*i).second.mappings());
+               for (ChanMapping::Mappings::const_iterator tm = mp.begin(); tm != mp.end(); ++tm) {
+                       for (ChanMapping::TypeMapping::const_iterator i = tm->second.begin(); i != tm->second.end(); ++i) {
+                               rv.set (tm->first, i->first + pc * natural_output_streams().get(tm->first), i->second);
+                       }
+               }
+       }
+       if (has_midi_bypass ()) {
+               rv.set (DataType::MIDI, 0, 0);
+       }
+
+       return rv;
+}
+
+bool
+PluginInsert::has_midi_bypass () const
+{
+       if (_configured_in.n_midi () == 1 && _configured_out.n_midi () == 1
+                       && natural_output_streams ().n_midi () == 0) {
+               return true;
+       }
+       return false;
+}
+
+bool
+PluginInsert::has_midi_thru () const
+{
+       if (_configured_in.n_midi () == 1 && _configured_out.n_midi () == 1
+                       && natural_input_streams ().n_midi () == 0 && natural_output_streams ().n_midi () == 0) {
+               return true;
+       }
+       return false;
+}
+
+#ifdef MIXBUS
+bool
+PluginInsert::is_channelstrip () const {
+       return _plugins.front()->is_channelstrip();
+}
+#endif
+
+bool
+PluginInsert::check_inplace ()
+{
+       // 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;
+               }
+       }
+       bool no_inplace = !inplace_ok || _plugins.front()->inplace_broken ();
+       DEBUG_TRACE (DEBUG::ChanMapping, string_compose ("%1 %2\n", name(), no_inplace ? "No Inplace Processing" : "In-Place"));
+       return no_inplace;
+}
+
+bool
+PluginInsert::sanitize_maps ()
+{
+       bool changed = false;
+       /* strip dead wood */
+       PinMappings new_ins;
+       PinMappings new_outs;
+       for (uint32_t pc = 0; pc < get_count(); ++pc) {
+               ChanMapping new_in;
+               ChanMapping new_out;
+               for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
+                       for (uint32_t i = 0; i < natural_input_streams().get (*t); ++i) {
+                               bool valid;
+                               uint32_t idx = _in_map[pc].get (*t, i, &valid);
+                               if (valid && idx < _configured_internal.get (*t)) {
+                                       new_in.set (*t, i, idx);
+                               }
+                       }
+                       for (uint32_t o = 0; o < natural_output_streams().get (*t); ++o) {
+                               bool valid;
+                               uint32_t idx = _out_map[pc].get (*t, o, &valid);
+                               if (valid && idx < _configured_out.get (*t)) {
+                                       new_out.set (*t, o, idx);
+                               }
+                       }
+               }
+               if (_in_map[pc] != new_in || _out_map[pc] != new_out) {
+                       changed = true;
+               }
+               new_ins[pc] = new_in;
+               new_outs[pc] = new_out;
+       }
+       /* prevent dup output assignments */
+       for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
+               for (uint32_t o = 0; o < _configured_out.get (*t); ++o) {
+                       bool mapped = false;
+                       for (uint32_t pc = 0; pc < get_count(); ++pc) {
+                               bool valid;
+                               uint32_t idx = new_outs[pc].get_src (*t, o, &valid);
+                               if (valid && mapped) {
+                                       new_outs[pc].unset (*t, idx);
+                               } else if (valid) {
+                                       mapped = true;
+                               }
+                       }
+               }
+       }
+
+       if (_in_map != new_ins || _out_map != new_outs) {
+               changed = true;
+       }
+       _in_map = new_ins;
+       _out_map = new_outs;
+
+       return changed;
+}
+
+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));
+
+               for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
+                       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
+                       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;
+                       }
+               }
+       }
+       sanitize_maps ();
+       if (old_in == _in_map && old_out == _out_map) {
+               return false;
+       }
+       if (emit) {
+               PluginMapChanged (); /* EMIT SIGNAL */
+               _mapping_changed = true;
+       }
+       return true;
+}
+
 bool
 PluginInsert::configure_io (ChanCount in, ChanCount out)
 {
        Match old_match = _match;
        ChanCount old_in;
+       ChanCount old_internal;
        ChanCount old_out;
 
        if (_configured) {
                old_in = _configured_in;
+               old_internal = _configured_internal;
                old_out = _configured_out;
        }
 
        _configured_in = in;
+       _configured_internal = in;
        _configured_out = out;
 
+       if (_sidechain) {
+               /* TODO hide midi-bypass, and custom outs. Best /fake/ "out" here.
+                * (currently _sidechain->configure_io always succeeds
+                *  since Processor::configure_io() succeeds)
+                */
+               if (!_sidechain->configure_io (in, out)) {
+                       DEBUG_TRACE (DEBUG::ChanMapping, "Sidechain configuration failed\n");
+                       return false;
+               }
+               _configured_internal += _sidechain->input()->n_ports();
+       }
+
        /* get plugin configuration */
        _match = private_can_support_io_configuration (in, out);
-#ifndef NDEBUG // XXX
-       cout << "Match '" << name() << "': " << _match;
+#ifndef NDEBUG
+       if (DEBUG_ENABLED(DEBUG::ChanMapping)) {
+               DEBUG_STR_DECL(a);
+               DEBUG_STR_APPEND(a, string_compose ("Match '%1': ",  name()));
+               DEBUG_STR_APPEND(a, _match);
+               DEBUG_TRACE (DEBUG::ChanMapping, DEBUG_STR(a).str());
+       }
 #endif
 
        /* set the matching method and number of plugins that we will use to meet this configuration */
@@ -957,85 +1252,61 @@ PluginInsert::configure_io (ChanCount in, ChanCount out)
        }
 
        bool mapping_changed = false;
-       if (old_in == in && old_out == out && _configured
+       if (old_in == in && old_out == out
+                       && _configured
                        && old_match.method == _match.method
                        && _in_map.size() == _out_map.size()
                        && _in_map.size() == get_count ()
                 ) {
-               /* If the configuraton has not changed, keep the mapping */
-       } else if (_match.custom_cfg && _configured) {
-               /* strip dead wood */
-               for (uint32_t pc = 0; pc < get_count(); ++pc) {
-                       ChanMapping new_in;
-                       ChanMapping new_out;
-                       for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
-                               for (uint32_t i = 0; i < natural_input_streams().get (*t); ++i) {
-                                       bool valid;
-                                       uint32_t idx = _in_map[pc].get (*t, i, &valid);
-                                       if (valid && idx <= in.get (*t)) {
-                                               new_in.set (*t, i, idx);
-                                       }
-                               }
-                               for (uint32_t o = 0; o < natural_output_streams().get (*t); ++o) {
-                                       bool valid;
-                                       uint32_t idx = _out_map[pc].get (*t, o, &valid);
-                                       if (valid && idx <= out.get (*t)) {
-                                               new_out.set (*t, o, idx);
-                                       }
-                               }
-                       }
-                       if (_in_map[pc] != new_in || _out_map[pc] != new_out) {
-                               mapping_changed = true;
-                       }
-                       _in_map[pc] = new_in;
-                       _out_map[pc] = new_out;
+               /* If the configuration has not changed, keep the mapping */
+               if (old_internal != _configured_internal) {
+                       mapping_changed = sanitize_maps ();
                }
+       } else if (_match.custom_cfg && _configured) {
+               mapping_changed = sanitize_maps ();
        } else {
-               /* generate a new mapping */
-               uint32_t pc = 0;
-               _in_map.clear ();
-               _out_map.clear ();
-               for (Plugins::iterator i = _plugins.begin(); i != _plugins.end(); ++i, ++pc) {
-                       if (_match.method == Split) {
-                               _in_map[pc] = ChanMapping ();
-                               /* connect inputs 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 < natural_input_streams().get (*t); ++in) {
-                                               _in_map[pc].set (*t, in, c);
-                                               c = c + 1 % cend;
-                                       }
-                               }
-                       } else {
-                               _in_map[pc] = ChanMapping (min (natural_input_streams (), in));
-                       }
-                       _out_map[pc] = ChanMapping (min (natural_output_streams(), out));
-
-                       for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
-                               _in_map[pc].offset_to(*t, pc * natural_input_streams().get(*t));
-                               _out_map[pc].offset_to(*t, pc * natural_output_streams().get(*t));
-                       }
+#ifdef MIXBUS
+               if (is_channelstrip ()) { _maps_from_state = false; }
+#endif
+               if (_maps_from_state) {
+                       _maps_from_state = false;
                        mapping_changed = true;
+                       sanitize_maps ();
+               } else {
+                       /* generate a new mapping */
+                       mapping_changed = reset_map (false);
                }
        }
 
        if (mapping_changed) {
                PluginMapChanged (); /* EMIT SIGNAL */
-#ifndef NDEBUG // XXX
-               uint32_t pc = 0;
-               cout << "----<<----\n";
-               for (Plugins::iterator i = _plugins.begin(); i != _plugins.end(); ++i, ++pc) {
-                       cout << "Channel Map for " << name() << " plugin " << pc << "\n";
-                       cout << " * Inputs:\n" << _in_map[pc];
-                       cout << " * Outputs:\n" << _out_map[pc];
+
+#ifndef NDEBUG
+               if (DEBUG_ENABLED(DEBUG::ChanMapping)) {
+                       uint32_t pc = 0;
+                       DEBUG_STR_DECL(a);
+                       DEBUG_STR_APPEND(a, "\n--------<<--------\n");
+                       for (Plugins::iterator i = _plugins.begin(); i != _plugins.end(); ++i, ++pc) {
+                               if (pc > 0) {
+                       DEBUG_STR_APPEND(a, "----><----\n");
+                               }
+                               DEBUG_STR_APPEND(a, string_compose ("Channel Map for %1 plugin %2\n", name(), pc));
+                               DEBUG_STR_APPEND(a, " * Inputs:\n");
+                               DEBUG_STR_APPEND(a, _in_map[pc]);
+                               DEBUG_STR_APPEND(a, " * Outputs:\n");
+                               DEBUG_STR_APPEND(a, _out_map[pc]);
+                       }
+                       DEBUG_STR_APPEND(a, "-------->>--------\n");
+                       DEBUG_TRACE (DEBUG::ChanMapping, DEBUG_STR(a).str());
                }
-               cout << "---->>----\n";
 #endif
        }
 
-       if (old_in != in || old_out != out
+       _no_inplace = check_inplace ();
+       _mapping_changed = false;
+       DEBUG_TRACE (DEBUG::ChanMapping, string_compose ("%1 %2\n", name(), _no_inplace ? "No Inplace Processing" : "In-Place"));
+
+       if (old_in != in || old_out != out || old_internal != _configured_internal
                        || (old_match.method != _match.method && (old_match.method == Split || _match.method == Split))
                 ) {
                PluginIoReConfigure (); /* EMIT SIGNAL */
@@ -1068,6 +1339,9 @@ PluginInsert::configure_io (ChanCount in, ChanCount out)
 bool
 PluginInsert::can_support_io_configuration (const ChanCount& in, ChanCount& out)
 {
+       if (_sidechain) {
+               _sidechain->can_support_io_configuration (in, out); // never fails, sets "out"
+       }
        return private_can_support_io_configuration (in, out).method != Impossible;
 }
 
@@ -1082,20 +1356,25 @@ PluginInsert::private_can_support_io_configuration (ChanCount const & inx, ChanC
                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) {
                out = _custom_out;
-               return Match (ExactMatch, get_count(), false, true); // XXX
+               return Match (ExactMatch, get_count(), _strict_io, true); // XXX
        }
 
-       /* try automatic configuration next */
+       /* try automatic configuration */
        Match m = PluginInsert::automatic_can_support_io_configuration (inx, out);
 
-
        PluginInfoPtr info = _plugins.front()->get_info();
        ChanCount inputs  = info->n_inputs;
        ChanCount outputs = info->n_outputs;
-       ChanCount midi_bypass;
 
        /* handle case strict-i/o */
        if (_strict_io && m.method != Impossible) {
@@ -1106,38 +1385,32 @@ PluginInsert::private_can_support_io_configuration (ChanCount const & inx, ChanC
                        // 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 = min (out, max_out);
+                       out = ChanCount::min (out, max_out);
+                       DEBUG_TRACE (DEBUG::ChanMapping, string_compose ("special case strict-i/o instrument: %1\n", name()));
                        return m;
                }
 
                switch (m.method) {
                        case NoInputs:
-                               if (inx != out) {
+                               if (inx.n_audio () != out.n_audio ()) { // ignore midi bypass
                                        /* replicate processor to match output count (generators and such)
                                         * at least enough to feed every output port. */
                                        uint32_t f = 1; // at least one. e.g. control data filters, no in, no out.
                                        for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
-                                               uint32_t nin = inputs.get (*t);
-                                               if (nin == 0 || inx.get(*t) == 0) { continue; }
-                                               f = max (f, (uint32_t) ceil (inx.get(*t) / (float)nin));
+                                               uint32_t nout = outputs.get (*t);
+                                               if (nout == 0 || inx.get(*t) == 0) { continue; }
+                                               f = max (f, (uint32_t) ceil (inx.get(*t) / (float)nout));
                                        }
                                        out = inx;
-                                       return Match (Replicate, f);
+                                       DEBUG_TRACE (DEBUG::ChanMapping, string_compose ("special case strict-i/o generator: %1\n", name()));
+                                       return Match (Replicate, f, _strict_io);
                                }
                                break;
-                       case Split:
-                               break;
                        default:
                                break;
                }
-               if (inx == out) { return m; }
 
                out = inx;
-               if (inx.get(DataType::MIDI) == 1
-                               && out.get (DataType::MIDI) == 0
-                               && outputs.get(DataType::MIDI) == 0) {
-                       out += ChanCount (DataType::MIDI, 1);
-               }
                return m;
        }
 
@@ -1145,28 +1418,50 @@ PluginInsert::private_can_support_io_configuration (ChanCount const & inx, ChanC
                return m;
        }
 
+       ChanCount ns_inputs  = inputs - sidechain_input_pins ();
+
+       DEBUG_TRACE (DEBUG::ChanMapping, string_compose ("resolving 'Impossible' match for %1\n", name()));
+
        if (info->reconfigurable_io()) {
                ChanCount useins;
+               // TODO add sidechains here
                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;
+       if (inx.get(DataType::MIDI) == 1 && outputs.get(DataType::MIDI) == 0) {
+               midi_bypass.set (DataType::MIDI, 1);
        }
 
-       // add at least as many plugins so that output count matches input count
+       // add at least as many plugins so that output count matches input count (w/o sidechain pins)
        uint32_t f = 0;
        for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
-               uint32_t nin = inputs.get (*t);
+               uint32_t nin = ns_inputs.get (*t);
                uint32_t nout = outputs.get (*t);
                if (nin == 0 || inx.get(*t) == 0) { continue; }
                // prefer floor() so the count won't overly increase IFF (nin < nout)
                f = max (f, (uint32_t) floor (inx.get(*t) / (float)nout));
        }
        if (f > 0 && outputs * f >= _configured_out) {
-               out = outputs * f;
-               return Match (Replicate, f);
+               out = outputs * f + midi_bypass;
+               return Match (Replicate, f, _strict_io);
+       }
+
+       // add at least as many plugins needed to connect all inputs (w/o sidechain pins)
+       f = 0;
+       for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
+               uint32_t nin = ns_inputs.get (*t);
+               if (nin == 0 || inx.get(*t) == 0) { continue; }
+               f = max (f, (uint32_t) ceil (inx.get(*t) / (float)nin));
+       }
+       if (f > 0) {
+               out = outputs * f + midi_bypass;
+               return Match (Replicate, f, _strict_io);
        }
 
        // add at least as many plugins needed to connect all inputs
@@ -1176,8 +1471,8 @@ PluginInsert::private_can_support_io_configuration (ChanCount const & inx, ChanC
                if (nin == 0 || inx.get(*t) == 0) { continue; }
                f = max (f, (uint32_t) ceil (inx.get(*t) / (float)nin));
        }
-       out = outputs * f;
-       return Match (Replicate, f);
+       out = outputs * f + midi_bypass;
+       return Match (Replicate, f, _strict_io);
 }
 
 /* this is the original Ardour 3/4 behavior, mainly for backwards compatibility */
@@ -1203,16 +1498,20 @@ PluginInsert::automatic_can_support_io_configuration (ChanCount const & inx, Cha
 
        ChanCount inputs  = info->n_inputs;
        ChanCount outputs = info->n_outputs;
+       ChanCount ns_inputs  = inputs - sidechain_input_pins ();
 
        if (in.get(DataType::MIDI) == 1 && outputs.get(DataType::MIDI) == 0) {
-               DEBUG_TRACE ( DEBUG::Processors, string_compose ("bypassing midi-data around %1\n", name()));
-               midi_bypass.set(DataType::MIDI, 1);
+               DEBUG_TRACE (DEBUG::ChanMapping, string_compose ("bypassing midi-data around %1\n", name()));
+               midi_bypass.set (DataType::MIDI, 1);
        }
        if (in.get(DataType::MIDI) == 1 && inputs.get(DataType::MIDI) == 0) {
-               DEBUG_TRACE ( DEBUG::Processors, string_compose ("hiding midi-port from plugin %1\n", name()));
+               DEBUG_TRACE (DEBUG::ChanMapping, string_compose ("hiding midi-port from plugin %1\n", name()));
                in.set(DataType::MIDI, 0);
        }
 
+       // add internally provided sidechain ports
+       ChanCount insc = in + sidechain_input_ports ();
+
        bool no_inputs = true;
        for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
                if (inputs.get (*t) != 0) {
@@ -1227,8 +1526,14 @@ PluginInsert::automatic_can_support_io_configuration (ChanCount const & inx, Cha
                return Match (NoInputs, 1);
        }
 
-       /* Plugin inputs match requested inputs exactly */
-       if (inputs == in) {
+       /* Plugin inputs match requested inputs + side-chain-ports exactly */
+       if (inputs == insc) {
+               out = outputs + midi_bypass;
+               return Match (ExactMatch, 1);
+       }
+
+       /* Plugin inputs matches without side-chain-pins */
+       if (ns_inputs == in) {
                out = outputs + midi_bypass;
                return Match (ExactMatch, 1);
        }
@@ -1241,9 +1546,10 @@ 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) {
 
-               uint32_t nin = inputs.get (*t);
+               // ignore side-chains
+               uint32_t nin = ns_inputs.get (*t);
 
                // No inputs of this type
                if (nin == 0 && in.get(*t) == 0) {
@@ -1267,7 +1573,7 @@ PluginInsert::automatic_can_support_io_configuration (ChanCount const & inx, Cha
                }
        }
 
-       if (can_replicate) {
+       if (can_replicate && f > 0) {
                for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
                        out.set (*t, outputs.get(*t) * f);
                }
@@ -1286,7 +1592,7 @@ PluginInsert::automatic_can_support_io_configuration (ChanCount const & inx, Cha
        bool can_split = true;
        for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
 
-               bool const can_split_type = (in.get (*t) == 1 && inputs.get (*t) > 1);
+               bool const can_split_type = (in.get (*t) == 1 && ns_inputs.get (*t) > 1);
                bool const nothing_to_do_for_type = (in.get (*t) == 0 && inputs.get (*t) == 0);
 
                if (!can_split_type && !nothing_to_do_for_type) {
@@ -1323,7 +1629,6 @@ PluginInsert::automatic_can_support_io_configuration (ChanCount const & inx, Cha
                return Match (Hide, 1, false, false, hide_channels);
        }
 
-       midi_bypass.reset();
        return Match (Impossible, 0);
 }
 
@@ -1350,11 +1655,16 @@ PluginInsert::state (bool full)
 
        /* save custom i/o config */
        node.add_property("custom", _custom_cfg ? "yes" : "no");
-       if (_custom_cfg) {
-               assert (_custom_out == _configured_out); // redundant
-               for (uint32_t pc = 0; pc < get_count(); ++pc) {
-                       // TODO save _in_map[pc], _out_map[pc]
-               }
+       for (uint32_t pc = 0; pc < get_count(); ++pc) {
+               char tmp[128];
+               snprintf (tmp, sizeof(tmp), "InputMap-%d", pc);
+               node.add_child_nocopy (* _in_map[pc].state (tmp));
+               snprintf (tmp, sizeof(tmp), "OutputMap-%d", pc);
+               node.add_child_nocopy (* _out_map[pc].state (tmp));
+       }
+
+       if (_sidechain) {
+               node.add_child_nocopy (_sidechain->state (full));
        }
 
        _plugins[0]->set_insert_id(this->id());
@@ -1607,15 +1917,40 @@ 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_("ConfiguredOutput")) {
                        _custom_out = ChanCount(**i);
                }
-               // TODO restore mappings for all 0 .. count.
+               if (strncmp ((*i)->name ().c_str(), X_("InputMap-"), 9) == 0) {
+                       long pc = atol (&((*i)->name().c_str()[9]));
+                       if (pc >=0 && pc <= 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()) {
+                               _out_map[pc] = ChanMapping (**i);
+                               ++out_maps;
+                       }
+               }
+               if ((*i)->name () ==  Processor::state_node_name) {
+                       if (!_sidechain) {
+                               add_sidechain (0);
+                       }
+                       _sidechain->set_state (**i, version);
+               }
        }
 
-
+       if (in_maps == out_maps && out_maps >0 && out_maps == get_count()) {
+               _maps_from_state = true;
+       }
 
        for (Plugins::iterator i = _plugins.begin(); i != _plugins.end(); ++i) {
                if (active()) {
@@ -1944,14 +2279,23 @@ PluginInsert::add_plugin (boost::shared_ptr<Plugin> plugin)
        plugin->set_insert_id (this->id());
 
        if (_plugins.empty()) {
-                /* first (and probably only) plugin instance - connect to relevant signals
-                 */
+               /* first (and probably only) plugin instance - connect to relevant signals */
 
                plugin->ParameterChangedExternally.connect_same_thread (*this, boost::bind (&PluginInsert::parameter_changed_externally, this, _1, _2));
-                plugin->StartTouch.connect_same_thread (*this, boost::bind (&PluginInsert::start_touch, this, _1));
-                plugin->EndTouch.connect_same_thread (*this, boost::bind (&PluginInsert::end_touch, this, _1));
+               plugin->StartTouch.connect_same_thread (*this, boost::bind (&PluginInsert::start_touch, this, _1));
+               plugin->EndTouch.connect_same_thread (*this, boost::bind (&PluginInsert::end_touch, this, _1));
+               // cache sidechain ports
+               _cached_sidechain_pins.reset ();
+               const ChanCount& nis (plugin->get_info()->n_inputs);
+               for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
+                       for (uint32_t in = 0; in < nis.get (*t); ++in) {
+                               const Plugin::IOPortDescription& iod (plugin->describe_io_port (*t, true, in));
+                               if (iod.is_sidechain) {
+                                       _cached_sidechain_pins.set (*t, 1 + _cached_sidechain_pins.n(*t));
+                               }
+                       }
+               }
        }
-
        _plugins.push_back (plugin);
 }