extend strict-i/o to include route outputs.
[ardour.git] / libs / ardour / route.cc
index 6effb27fc47b237680839a8fd2e791a4ef4b61eb..0441928c0a1f7786e8de07132faa2d918b9bda91 100644 (file)
@@ -57,6 +57,7 @@
 #include "ardour/pannable.h"
 #include "ardour/panner.h"
 #include "ardour/panner_shell.h"
+#include "ardour/parameter_descriptor.h"
 #include "ardour/plugin_insert.h"
 #include "ardour/port.h"
 #include "ardour/port_insert.h"
@@ -78,6 +79,7 @@ using namespace PBD;
 PBD::Signal0<void> Route::SyncOrderKeys;
 PBD::Signal0<void> Route::RemoteControlIDChange;
 
+/** Base class for all routable/mixable objects (tracks and busses) */
 Route::Route (Session& sess, string name, Flag flg, DataType default_type)
        : SessionObject (sess, name)
        , Automatable (sess)
@@ -114,6 +116,8 @@ Route::Route (Session& sess, string name, Flag flg, DataType default_type)
        , _track_number (0)
        , _in_configure_processors (false)
        , _initial_io_setup (false)
+       , _in_sidechain_setup (false)
+       , _strict_io (false)
        , _custom_meter_position_noted (false)
 {
        processor_max_streams.reset();
@@ -138,6 +142,9 @@ Route::init ()
        _mute_control.reset (new MuteControllable (X_("mute"), shared_from_this ()));
        _phase_control.reset (new PhaseControllable (X_("phase"), shared_from_this ()));
 
+       _solo_isolate_control.reset (new SoloIsolateControllable (X_("solo-iso"), shared_from_this ()));
+       _solo_safe_control.reset (new SoloSafeControllable (X_("solo-safe"), shared_from_this ()));
+
        _solo_control->set_flags (Controllable::Flag (_solo_control->flags() | Controllable::Toggle));
        _mute_control->set_flags (Controllable::Flag (_mute_control->flags() | Controllable::Toggle));
        _phase_control->set_flags (Controllable::Flag (_phase_control->flags() | Controllable::Toggle));
@@ -659,7 +666,7 @@ Route::bounce_process (BufferSet& buffers, framepos_t start, framecnt_t nframes,
                        break;
                }
 
-               /* if we're not exporting, stop processing if we come across a routing processor. */
+               /* if we're *not* exporting, stop processing if we come across a routing processor. */
                if (!for_export && boost::dynamic_pointer_cast<PortInsert>(*i)) {
                        break;
                }
@@ -667,8 +674,20 @@ Route::bounce_process (BufferSet& buffers, framepos_t start, framecnt_t nframes,
                        break;
                }
 
-               /* don't run any processors that does routing.
-                * oh, and don't bother with the peak meter either.
+               /* special case the panner (export outputs)
+                * Ideally we'd only run the panner, not the delivery itself...
+                * but panners need separate input/output buffers and some context
+                * (panshell, panner type, etc). AFAICT there is no ill side effect
+                * of re-using the main delivery when freewheeling/exporting a region.
+                */
+               if ((*i) == _main_outs) {
+                       assert ((*i)->does_routing());
+                       (*i)->run (buffers, start - latency, start - latency + nframes, nframes, true);
+                       buffers.set_count ((*i)->output_streams());
+               }
+
+               /* don't run any processors that do routing.
+                * Also don't bother with metering.
                 */
                if (!(*i)->does_routing() && !boost::dynamic_pointer_cast<PeakMeter>(*i)) {
                        (*i)->run (buffers, start - latency, start - latency + nframes, nframes, true);
@@ -791,7 +810,7 @@ Route::set_listen (bool yn, Controllable::GroupControlDisposition group_override
        }
 
        if (use_group (group_override, &RouteGroup::is_solo)) {
-               _route_group->foreach_route (boost::bind (&Route::set_listen, _1, yn, Controllable::NoGroup));
+               _route_group->foreach_route (boost::bind (&Route::set_listen, _1, yn, Controllable::ForGroup));
                return;
        }
 
@@ -826,7 +845,8 @@ Route::set_solo_safe (bool yn, Controllable::GroupControlDisposition /* group_ov
 {
        if (_solo_safe != yn) {
                _solo_safe = yn;
-               solo_safe_changed ();
+               solo_safe_changed (); /* EMIT SIGNAL */
+               _solo_safe_control->Changed(); /* EMIT SIGNAL */
        }
 }
 
@@ -875,6 +895,9 @@ Route::clear_all_solo_state ()
 void
 Route::set_solo (bool yn, Controllable::GroupControlDisposition group_override)
 {
+       DEBUG_TRACE (DEBUG::Solo, string_compose ("%1: set solo => %2, grp ? %3 currently self-soloed ? %4\n",
+                                                 name(), yn, enum_2_string(group_override), self_soloed()));
+
        if (_solo_safe) {
                DEBUG_TRACE (DEBUG::Solo, string_compose ("%1 ignore solo change due to solo-safe\n", name()));
                return;
@@ -886,13 +909,10 @@ Route::set_solo (bool yn, Controllable::GroupControlDisposition group_override)
        }
 
        if (use_group (group_override, &RouteGroup::is_solo)) {
-               _route_group->foreach_route (boost::bind (&Route::set_solo, _1, yn, Controllable::NoGroup));
+               _route_group->foreach_route (boost::bind (&Route::set_solo, _1, yn, Controllable::ForGroup));
                return;
        }
 
-       DEBUG_TRACE (DEBUG::Solo, string_compose ("%1: set solo => %2, grp ? %3 currently self-soloed ? %4\n",
-                                                 name(), yn, enum_2_string(group_override), self_soloed()));
-
        if (self_soloed() != yn) {
                set_self_solo (yn);
                solo_changed (true, group_override); /* EMIT SIGNAL */
@@ -1037,7 +1057,7 @@ Route::set_solo_isolated (bool yn, Controllable::GroupControlDisposition group_o
        }
 
        if (use_group (group_override, &RouteGroup::is_solo)) {
-               _route_group->foreach_route (boost::bind (&Route::set_solo_isolated, _1, yn, Controllable::NoGroup));
+               _route_group->foreach_route (boost::bind (&Route::set_solo_isolated, _1, yn, Controllable::ForGroup));
                return;
        }
 
@@ -1082,6 +1102,7 @@ Route::set_solo_isolated (bool yn, Controllable::GroupControlDisposition group_o
        /* XXX should we back-propagate as well? (April 2010: myself and chris goddard think not) */
 
        solo_isolated_changed (); /* EMIT SIGNAL */
+       _solo_isolate_control->Changed(); /* EMIT SIGNAL */
 }
 
 bool
@@ -1106,7 +1127,7 @@ void
 Route::set_mute (bool yn, Controllable::GroupControlDisposition group_override)
 {
        if (use_group (group_override, &RouteGroup::is_mute)) {
-               _route_group->foreach_route (boost::bind (&Route::set_mute, _1, yn, Controllable::NoGroup));
+               _route_group->foreach_route (boost::bind (&Route::set_mute, _1, yn, Controllable::ForGroup));
                return;
        }
 
@@ -1239,6 +1260,13 @@ Route::add_processor (boost::shared_ptr<Processor> processor, boost::shared_ptr<
                return 1;
        }
 
+       if (_strict_io) {
+               boost::shared_ptr<PluginInsert> pi;
+               if ((pi = boost::dynamic_pointer_cast<PluginInsert>(processor)) != 0) {
+                       pi->set_strict_io (true);
+               }
+       }
+
        {
                Glib::Threads::Mutex::Lock lx (AudioEngine::instance()->process_lock ());
                Glib::Threads::RWLock::WriterLock lm (_processor_lock);
@@ -1355,6 +1383,7 @@ Route::add_processor_from_xml_2X (const XMLNode& node, int version)
                                                processor.reset (new UnknownProcessor (_session, node));
                                        } else {
                                                processor.reset (new PluginInsert (_session));
+                                               processor->set_owner (this);
                                        }
 
                                } else {
@@ -1437,7 +1466,8 @@ Route::add_processors (const ProcessorList& others, boost::shared_ptr<Processor>
                        boost::shared_ptr<PluginInsert> pi;
 
                        if ((pi = boost::dynamic_pointer_cast<PluginInsert>(*i)) != 0) {
-                               pi->set_count (1);
+                               pi->set_count (1); // why? configure_processors_unlocked() will re-do this
+                               pi->set_strict_io (_strict_io);
                        }
 
                        _processors.insert (loc, *i);
@@ -1741,9 +1771,15 @@ Route::remove_processor (boost::shared_ptr<Processor> processor, ProcessorStream
                                   run.
                                */
 
-                               boost::shared_ptr<IOProcessor> iop;
+                               boost::shared_ptr<IOProcessor> iop = boost::dynamic_pointer_cast<IOProcessor> (*i);
+                               boost::shared_ptr<PluginInsert> pi = boost::dynamic_pointer_cast<PluginInsert>(*i);
 
-                               if ((iop = boost::dynamic_pointer_cast<IOProcessor> (*i)) != 0) {
+                               if (pi != 0) {
+                                       assert (iop == 0);
+                                       iop = pi->sidechain();
+                               }
+
+                               if (iop != 0) {
                                        iop->disconnect ();
                                }
 
@@ -1795,6 +1831,104 @@ Route::remove_processor (boost::shared_ptr<Processor> processor, ProcessorStream
        return 0;
 }
 
+int
+Route::replace_processor (boost::shared_ptr<Processor> old, boost::shared_ptr<Processor> sub, ProcessorStreams* err)
+{
+       /* these can never be removed */
+       if (old == _amp || old == _meter || old == _main_outs || old == _delayline || old == _trim) {
+               return 1;
+       }
+       /* and can't be used as substitute, either */
+       if (sub == _amp || sub == _meter || sub == _main_outs || sub == _delayline || sub == _trim) {
+               return 1;
+       }
+
+       /* I/Os are out, too */
+       if (boost::dynamic_pointer_cast<IOProcessor> (old) || boost::dynamic_pointer_cast<IOProcessor> (sub)) {
+               return 1;
+       }
+
+       /* this function cannot be used to swap/reorder processors */
+       if (find (_processors.begin(), _processors.end(), sub) != _processors.end ()) {
+               return 1;
+       }
+
+       if (!AudioEngine::instance()->connected() || !old || !sub) {
+               return 1;
+       }
+
+       /* ensure that sub is not owned by another route */
+       if (sub->owner ()) {
+               return 1;
+       }
+
+       {
+               Glib::Threads::Mutex::Lock lx (AudioEngine::instance()->process_lock ());
+               Glib::Threads::RWLock::WriterLock lm (_processor_lock);
+               ProcessorState pstate (this);
+
+               assert (find (_processors.begin(), _processors.end(), sub) == _processors.end ());
+
+               ProcessorList::iterator i;
+               bool replaced = false;
+               bool enable = old->active ();
+
+               for (i = _processors.begin(); i != _processors.end(); ) {
+                       if (*i == old) {
+                               i = _processors.erase (i);
+                               _processors.insert (i, sub);
+                               sub->set_owner (this);
+                               replaced = true;
+                               break;
+                       } else {
+                               ++i;
+                       }
+               }
+
+               if (!replaced) {
+                       return 1;
+               }
+
+               if (_strict_io) {
+                       boost::shared_ptr<PluginInsert> pi;
+                       if ((pi = boost::dynamic_pointer_cast<PluginInsert>(sub)) != 0) {
+                               pi->set_strict_io (true);
+                       }
+               }
+
+               if (configure_processors_unlocked (err)) {
+                       pstate.restore ();
+                       configure_processors_unlocked (0);
+                       return -1;
+               }
+
+               _have_internal_generator = false;
+
+               for (i = _processors.begin(); i != _processors.end(); ++i) {
+                       boost::shared_ptr<PluginInsert> pi;
+                       if ((pi = boost::dynamic_pointer_cast<PluginInsert>(*i)) != 0) {
+                               if (pi->has_no_inputs ()) {
+                                       _have_internal_generator = true;
+                                       break;
+                               }
+                       }
+               }
+
+               if (enable) {
+                       sub->activate ();
+               }
+
+               sub->ActiveChanged.connect_same_thread (*this, boost::bind (&Session::update_latency_compensation, &_session, false));
+               _output->set_user_latency (0);
+       }
+
+       reset_instrument_info ();
+       old->drop_references ();
+       processors_changed (RouteProcessorChange ()); /* EMIT SIGNAL */
+       set_processor_positions ();
+       return 0;
+}
+
 int
 Route::remove_processors (const ProcessorList& to_be_deleted, ProcessorStreams* err)
 {
@@ -1837,9 +1971,14 @@ Route::remove_processors (const ProcessorList& to_be_deleted, ProcessorStreams*
                           run.
                        */
 
-                       boost::shared_ptr<IOProcessor> iop;
+                       boost::shared_ptr<IOProcessor> iop = boost::dynamic_pointer_cast<IOProcessor>(processor);
+                       boost::shared_ptr<PluginInsert> pi = boost::dynamic_pointer_cast<PluginInsert>(processor);
+                       if (pi != 0) {
+                               assert (iop == 0);
+                               iop = pi->sidechain();
+                       }
 
-                       if ((iop = boost::dynamic_pointer_cast<IOProcessor> (processor)) != 0) {
+                       if (iop != 0) {
                                iop->disconnect ();
                        }
 
@@ -1942,6 +2081,31 @@ Route::try_configure_processors_unlocked (ChanCount in, ProcessorStreams* err)
        for (ProcessorList::iterator p = _processors.begin(); p != _processors.end(); ++p, ++index) {
 
                if ((*p)->can_support_io_configuration(in, out)) {
+
+                       if (boost::dynamic_pointer_cast<Delivery> (*p)
+                                       && boost::dynamic_pointer_cast<Delivery> (*p)->role() == Delivery::Main
+                                       && _strict_io) {
+                               /* with strict I/O the panner + output are forced to
+                                * follow the last processor's output.
+                                *
+                                * Delivery::can_support_io_configuration() will only add ports,
+                                * but not remove excess ports.
+                                *
+                                * This works because the delivery only requires
+                                * as many outputs as there are inputs.
+                                * Delivery::configure_io() will do the actual removal
+                                * by calling _output->ensure_io()
+                                */
+                               if (_session.master_out ()) {
+                                       /* ..but at least as many as there are master-inputs */
+                                       // XXX this may need special-casing for mixbus (master-outputs)
+                                       // and should maybe be a preference anyway ?!
+                                       out = ChanCount::max (in, _session.master_out ()->n_inputs ());
+                               } else {
+                                       out = in;
+                               }
+                       }
+
                        DEBUG_TRACE (DEBUG::Processors, string_compose ("\t%1 ID=%2 in=%3 out=%4\n",(*p)->name(), (*p)->id(), in, out));
                        configuration.push_back(make_pair(in, out));
 
@@ -2030,16 +2194,23 @@ Route::configure_processors_unlocked (ProcessorStreams* err)
 
                if (!(*p)->configure_io(c->first, c->second)) {
                        DEBUG_TRACE (DEBUG::Processors, string_compose ("%1: configuration failed\n", _name));
+                       _in_configure_processors = false;
+                       return -1;
                }
                processor_max_streams = ChanCount::max(processor_max_streams, c->first);
                processor_max_streams = ChanCount::max(processor_max_streams, c->second);
 
                boost::shared_ptr<PluginInsert> pi;
                if ((pi = boost::dynamic_pointer_cast<PluginInsert>(*p)) != 0) {
-                       /* plugins connected via Split Match may have more channels.
-                        * route/scratch buffers are needed for all of them*/
+                       /* plugins connected via Split or Hide Match may have more channels.
+                        * route/scratch buffers are needed for all of them
+                        * The configuration may only be a subset (both input and output)
+                        */
                        processor_max_streams = ChanCount::max(processor_max_streams, pi->input_streams());
-                       processor_max_streams = ChanCount::max(processor_max_streams, pi->natural_input_streams());
+                       processor_max_streams = ChanCount::max(processor_max_streams, pi->internal_streams());
+                       processor_max_streams = ChanCount::max(processor_max_streams, pi->output_streams());
+                       processor_max_streams = ChanCount::max(processor_max_streams, pi->natural_input_streams() * pi->get_count());
+                       processor_max_streams = ChanCount::max(processor_max_streams, pi->natural_output_streams() * pi->get_count());
                }
                out = c->second;
 
@@ -2272,6 +2443,163 @@ Route::reorder_processors (const ProcessorList& new_order, ProcessorStreams* err
        return 0;
 }
 
+bool
+Route::add_remove_sidechain (boost::shared_ptr<Processor> proc, bool add)
+{
+       boost::shared_ptr<PluginInsert> pi;
+       if ((pi = boost::dynamic_pointer_cast<PluginInsert>(proc)) == 0) {
+               return false;
+       }
+
+       if (pi->has_sidechain () == add) {
+               return true; // ?? call failed, but result is as expected.
+       }
+
+       {
+               Glib::Threads::RWLock::ReaderLock lm (_processor_lock);
+               ProcessorList::iterator i = find (_processors.begin(), _processors.end(), proc);
+               if (i == _processors.end ()) {
+                       return false;
+               }
+       }
+
+       {
+               Glib::Threads::Mutex::Lock lx (AudioEngine::instance()->process_lock ()); // take before Writerlock to avoid deadlock
+               Glib::Threads::RWLock::WriterLock lm (_processor_lock);
+               PBD::Unwinder<bool> uw (_in_sidechain_setup, true);
+
+               lx.release (); // IO::add_port() and ~IO takes process lock  - XXX check if this is safe
+               if (add) {
+                       if (!pi->add_sidechain ()) {
+                               return false;
+                       }
+               } else {
+                       if (!pi->del_sidechain ()) {
+                               return false;
+                       }
+               }
+
+               lx.acquire ();
+               list<pair<ChanCount, ChanCount> > c = try_configure_processors_unlocked (n_inputs (), 0);
+               lx.release ();
+
+               if (c.empty()) {
+                       if (add) {
+                               pi->del_sidechain ();
+                       } else {
+                               pi->add_sidechain ();
+                               // TODO restore side-chain's state.
+                       }
+                       return false;
+               }
+               lx.acquire ();
+               configure_processors_unlocked (0);
+       }
+
+       if (pi->has_sidechain ()) {
+               pi->sidechain_input ()->changed.connect_same_thread (*this, boost::bind (&Route::sidechain_change_handler, this, _1, _2));
+       }
+
+       processors_changed (RouteProcessorChange ()); /* EMIT SIGNAL */
+       _session.set_dirty ();
+       return true;
+}
+
+bool
+Route::reset_plugin_insert (boost::shared_ptr<Processor> proc)
+{
+       ChanCount unused;
+       return customize_plugin_insert (proc, 0, unused);
+}
+
+bool
+Route::customize_plugin_insert (boost::shared_ptr<Processor> proc, uint32_t count, ChanCount outs)
+{
+       boost::shared_ptr<PluginInsert> pi;
+       if ((pi = boost::dynamic_pointer_cast<PluginInsert>(proc)) == 0) {
+               return false;
+       }
+
+       {
+               Glib::Threads::RWLock::ReaderLock lm (_processor_lock);
+               ProcessorList::iterator i = find (_processors.begin(), _processors.end(), proc);
+               if (i == _processors.end ()) {
+                       return false;
+               }
+       }
+
+       {
+               Glib::Threads::Mutex::Lock lx (AudioEngine::instance()->process_lock ());
+               Glib::Threads::RWLock::WriterLock lm (_processor_lock);
+
+               bool      old_cust = pi->custom_cfg ();
+               uint32_t  old_cnt  = pi->get_count ();
+               ChanCount old_chan = pi->output_streams ();
+
+               if (count == 0) {
+                       pi->set_custom_cfg (false);
+               } else {
+                       pi->set_custom_cfg (true);
+                       pi->set_count (count);
+                       pi->set_outputs (outs);
+               }
+
+               list<pair<ChanCount, ChanCount> > c = try_configure_processors_unlocked (n_inputs (), 0);
+               if (c.empty()) {
+                       /* not possible */
+
+                       pi->set_count (old_cnt);
+                       pi->set_outputs (old_chan);
+                       pi->set_custom_cfg (old_cust);
+
+                       return false;
+               }
+               configure_processors_unlocked (0);
+       }
+
+       processors_changed (RouteProcessorChange ()); /* EMIT SIGNAL */
+       _session.set_dirty ();
+       return true;
+}
+
+bool
+Route::set_strict_io (const bool enable)
+{
+       if (_strict_io != enable) {
+               _strict_io = enable;
+               Glib::Threads::RWLock::ReaderLock lm (_processor_lock);
+               for (ProcessorList::iterator p = _processors.begin(); p != _processors.end(); ++p) {
+                       boost::shared_ptr<PluginInsert> pi;
+                       if ((pi = boost::dynamic_pointer_cast<PluginInsert>(*p)) != 0) {
+                               pi->set_strict_io (_strict_io);
+                       }
+               }
+
+               list<pair<ChanCount, ChanCount> > c = try_configure_processors_unlocked (n_inputs (), 0);
+
+               if (c.empty()) {
+                       // not possible
+                       _strict_io = !enable; // restore old value
+                       for (ProcessorList::iterator p = _processors.begin(); p != _processors.end(); ++p) {
+                               boost::shared_ptr<PluginInsert> pi;
+                               if ((pi = boost::dynamic_pointer_cast<PluginInsert>(*p)) != 0) {
+                                       pi->set_strict_io (_strict_io);
+                               }
+                       }
+                       return false;
+               }
+               lm.release ();
+
+               {
+                       Glib::Threads::Mutex::Lock lx (AudioEngine::instance()->process_lock ());
+                       configure_processors (0);
+               }
+               processors_changed (RouteProcessorChange ()); /* EMIT SIGNAL */
+               _session.set_dirty ();
+       }
+       return true;
+}
+
 XMLNode&
 Route::get_state()
 {
@@ -2300,6 +2628,7 @@ Route::state(bool full_state)
        node->add_property("id", buf);
        node->add_property ("name", _name);
        node->add_property("default-type", _default_type.to_string());
+       node->add_property ("strict-io", _strict_io);
 
        if (_flags) {
                node->add_property("flags", enum_2_string (_flags));
@@ -2423,6 +2752,10 @@ Route::set_state (const XMLNode& node, int version)
                _flags = Flag (0);
        }
 
+       if ((prop = node.property (X_("strict-io"))) != 0) {
+               _strict_io = string_is_affirmative (prop->value());
+       }
+
        if (is_master() || is_monitor() || is_auditioner()) {
                _mute_master->set_solo_ignore (true);
        }
@@ -2966,13 +3299,20 @@ Route::set_processor_state (const XMLNode& node)
                                } else if (prop->value() == "ladspa" || prop->value() == "Ladspa" ||
                                           prop->value() == "lv2" ||
                                           prop->value() == "windows-vst" ||
-                                          prop->value() == "lxvst" ||
+                                          prop->value() == "lxvst" ||
+                                          prop->value() == "luaproc" ||
                                           prop->value() == "audiounit") {
 
                                        if (_session.get_disable_all_loaded_plugins ()) {
                                                processor.reset (new UnknownProcessor (_session, **niter));
                                        } else {
                                                processor.reset (new PluginInsert (_session));
+                                               processor->set_owner (this);
+                                               if (_strict_io) {
+                                                       boost::shared_ptr<PluginInsert> pi = boost::dynamic_pointer_cast<PluginInsert>(processor);
+                                                       pi->set_strict_io (true);
+                                               }
+
                                        }
                                } else if (prop->value() == "port") {
 
@@ -2992,6 +3332,12 @@ Route::set_processor_state (const XMLNode& node)
                                        processor.reset (new UnknownProcessor (_session, **niter));
                                }
 
+                               /* subscribe to Sidechain IO changes */
+                               boost::shared_ptr<PluginInsert> pi = boost::dynamic_pointer_cast<PluginInsert> (processor);
+                               if (pi && pi->has_sidechain ()) {
+                                       pi->sidechain_input ()->changed.connect_same_thread (*this, boost::bind (&Route::sidechain_change_handler, this, _1, _2));
+                               }
+
                                /* we have to note the monitor send here, otherwise a new one will be created
                                   and the state of this one will be lost.
                                */
@@ -3281,12 +3627,53 @@ Route::feeds (boost::shared_ptr<Route> other, bool* via_sends_only)
        return false;
 }
 
+IOVector
+Route::all_inputs () const
+{
+       /* TODO, if this works as expected,
+        * cache the IOVector and maintain it via
+        * input_change_handler(), sidechain_change_handler() etc
+        */
+       IOVector ios;
+       ios.push_back (_input);
+
+       Glib::Threads::RWLock::ReaderLock lm (_processor_lock);
+       for (ProcessorList::const_iterator r = _processors.begin(); r != _processors.end(); ++r) {
+
+               boost::shared_ptr<IOProcessor> iop = boost::dynamic_pointer_cast<IOProcessor>(*r);
+               boost::shared_ptr<PluginInsert> pi = boost::dynamic_pointer_cast<PluginInsert>(*r);
+               if (pi != 0) {
+                       assert (iop == 0);
+                       iop = pi->sidechain();
+               }
+
+               if (iop != 0 && iop->input()) {
+                       ios.push_back (iop->input());
+               }
+       }
+       return ios;
+}
+
+IOVector
+Route::all_outputs () const
+{
+       IOVector ios;
+       // _output is included via Delivery
+       Glib::Threads::RWLock::ReaderLock lm (_processor_lock);
+       for (ProcessorList::const_iterator r = _processors.begin(); r != _processors.end(); ++r) {
+               boost::shared_ptr<IOProcessor> iop = boost::dynamic_pointer_cast<IOProcessor>(*r);
+               if (iop != 0 && iop->output()) {
+                       ios.push_back (iop->output());
+               }
+       }
+       return ios;
+}
+
 bool
 Route::direct_feeds_according_to_reality (boost::shared_ptr<Route> other, bool* via_send_only)
 {
        DEBUG_TRACE (DEBUG::Graph, string_compose ("Feeds? %1\n", _name));
-
-       if (_output->connected_to (other->input())) {
+       if (other->all_inputs().fed_by (_output)) {
                DEBUG_TRACE (DEBUG::Graph, string_compose ("\tdirect FEEDS %2\n", other->name()));
                if (via_send_only) {
                        *via_send_only = false;
@@ -3296,12 +3683,19 @@ Route::direct_feeds_according_to_reality (boost::shared_ptr<Route> other, bool*
        }
 
 
+       Glib::Threads::RWLock::ReaderLock lm (_processor_lock); // XXX
        for (ProcessorList::iterator r = _processors.begin(); r != _processors.end(); ++r) {
 
-               boost::shared_ptr<IOProcessor> iop;
+               boost::shared_ptr<IOProcessor> iop = boost::dynamic_pointer_cast<IOProcessor>(*r);
+               boost::shared_ptr<PluginInsert> pi = boost::dynamic_pointer_cast<PluginInsert>(*r);
+               if (pi != 0) {
+                       assert (iop == 0);
+                       iop = pi->sidechain();
+               }
 
-               if ((iop = boost::dynamic_pointer_cast<IOProcessor>(*r)) != 0) {
-                       if (iop->feeds (other)) {
+               if (iop != 0) {
+                       boost::shared_ptr<const IO> iop_out = iop->output();
+                       if ((iop_out && other->all_inputs().fed_by (iop_out)) || iop->feeds (other)) {
                                DEBUG_TRACE (DEBUG::Graph,  string_compose ("\tIOP %1 does feed %2\n", iop->name(), other->name()));
                                if (via_send_only) {
                                        *via_send_only = true;
@@ -3326,6 +3720,12 @@ Route::direct_feeds_according_to_graph (boost::shared_ptr<Route> other, bool* vi
        return _session._current_route_graph.has (shared_from_this (), other, via_send_only);
 }
 
+bool
+Route::feeds_according_to_graph (boost::shared_ptr<Route> other)
+{
+       return _session._current_route_graph.feeds (shared_from_this (), other);
+}
+
 /** Called from the (non-realtime) butler thread when the transport is stopped */
 void
 Route::nonrealtime_handle_transport_stopped (bool /*abort_ignored*/, bool /*did_locate*/, bool can_flush_processors)
@@ -3495,6 +3895,21 @@ Route::output_change_handler (IOChange change, void * /*src*/)
        }
 }
 
+void
+Route::sidechain_change_handler (IOChange change, void * /*src*/)
+{
+       if (_initial_io_setup || _in_sidechain_setup) {
+               return;
+       }
+
+       if ((change.type & IOChange::ConfigurationChanged)) {
+               /* This is called with the process lock held if change
+                  contains ConfigurationChanged
+               */
+               configure_processors (0);
+       }
+}
+
 uint32_t
 Route::pans_required () const
 {
@@ -3884,264 +4299,6 @@ Route::set_latency_compensation (framecnt_t longest_session_latency)
        }
 }
 
-void
-Route::set_control (AutomationType type, double val, PBD::Controllable::GroupControlDisposition group_override)
-{
-       boost::shared_ptr<RouteList> rl;
-
-       switch (type) {
-       case GainAutomation:
-               /* route must mediate group control */
-               set_gain (val, group_override);
-               return;
-               break;
-
-       case TrimAutomation:
-               /* route must mediate group control */
-               set_trim (val, group_override);
-               return;
-               break;
-
-       case RecEnableAutomation:
-               /* session must mediate group control */
-               rl.reset (new RouteList);
-               rl->push_back (shared_from_this());
-               _session.set_record_enabled (rl, val >= 0.5 ? true : false, Session::rt_cleanup, group_override);
-               return;
-               break;
-
-       case SoloAutomation:
-               /* session must mediate group control */
-               rl.reset (new RouteList);
-               rl->push_back (shared_from_this());
-               if (Config->get_solo_control_is_listen_control()) {
-                       _session.set_listen (rl, val >= 0.5 ? true : false, Session::rt_cleanup, group_override);
-               } else {
-                       _session.set_solo (rl, val >= 0.5 ? true : false);
-               }
-
-               return;
-               break;
-
-       case MuteAutomation:
-               /* session must mediate group control */
-               rl.reset (new RouteList);
-               rl->push_back (shared_from_this());
-               _session.set_mute (rl, !muted(), Session::rt_cleanup, group_override);
-               return;
-               break;
-
-       default:
-               /* Not a route automation control */
-               fatal << string_compose (_("programming error: %1%2\n"), X_("illegal type of route automation control passed to Route::set_control(): "), enum_2_string(type)) << endmsg;
-               /*NOTREACHED*/
-               return;
-       }
-}
-
-
-Route::RouteAutomationControl::RouteAutomationControl (const std::string& name,
-                                                       AutomationType atype,
-                                                       boost::shared_ptr<AutomationList> alist,
-                                                       boost::shared_ptr<Route> r)
-       : AutomationControl (r->session(), Evoral::Parameter (atype),
-                            ParameterDescriptor (Evoral::Parameter (atype)),
-                            alist, name)
-       , _route (r)
-{
-}
-
-Route::GainControllable::GainControllable (Session& s, AutomationType atype, boost::shared_ptr<Route> r)
-       : GainControl (s, Evoral::Parameter(atype))
-       , _route (r)
-{
-
-}
-
-Route::SoloControllable::SoloControllable (std::string name, boost::shared_ptr<Route> r)
-       : RouteAutomationControl (name, SoloAutomation, boost::shared_ptr<AutomationList>(), r)
-{
-       boost::shared_ptr<AutomationList> gl(new AutomationList(Evoral::Parameter(SoloAutomation)));
-       gl->set_interpolation(Evoral::ControlList::Discrete);
-       set_list (gl);
-}
-
-void
-Route::SoloControllable::set_value (double val, PBD::Controllable::GroupControlDisposition group_override)
-{
-       if (writable()) {
-               _set_value (val, group_override);
-       }
-}
-
-void
-Route::SoloControllable::_set_value (double val, PBD::Controllable::GroupControlDisposition group_override)
-{
-       const bool bval = ((val >= 0.5) ? true : false);
-
-       boost::shared_ptr<RouteList> rl (new RouteList);
-
-       boost::shared_ptr<Route> r = _route.lock ();
-       if (!r) {
-               return;
-       }
-
-       rl->push_back (r);
-
-       if (Config->get_solo_control_is_listen_control()) {
-               _session.set_listen (rl, bval, Session::rt_cleanup, group_override);
-       } else {
-               _session.set_solo (rl, bval, Session::rt_cleanup, group_override);
-       }
-}
-
-void
-Route::SoloControllable::set_value_unchecked (double val)
-{
-       /* Used only by automation playback */
-
-       _set_value (val, Controllable::NoGroup);
-}
-
-double
-Route::SoloControllable::get_value () const
-{
-       boost::shared_ptr<Route> r = _route.lock ();
-       if (!r) {
-               return 0;
-       }
-
-       if (Config->get_solo_control_is_listen_control()) {
-               return r->listening_via_monitor() ? GAIN_COEFF_UNITY : GAIN_COEFF_ZERO;
-       } else {
-               return r->self_soloed() ? GAIN_COEFF_UNITY : GAIN_COEFF_ZERO;
-       }
-}
-
-Route::MuteControllable::MuteControllable (std::string name, boost::shared_ptr<Route> r)
-       : RouteAutomationControl (name, MuteAutomation, boost::shared_ptr<AutomationList>(), r)
-       , _route (r)
-{
-       boost::shared_ptr<AutomationList> gl(new AutomationList(Evoral::Parameter(MuteAutomation)));
-       gl->set_interpolation(Evoral::ControlList::Discrete);
-       set_list (gl);
-}
-
-void
-Route::MuteControllable::set_superficial_value(bool muted)
-{
-       /* Note we can not use AutomationControl::set_value here since it will emit
-          Changed(), but the value will not be correct to the observer. */
-
-       const bool to_list = _list && ((AutomationList*)_list.get ())->automation_write ();
-       const double where = _session.audible_frame ();
-       if (to_list) {
-               /* Note that we really need this:
-                *  if (as == Touch && _list->in_new_write_pass ()) {
-                *       alist->start_write_pass (_session.audible_frame ());
-                *  }
-                * here in the case of the user calling from a GUI or whatever.
-                * Without the ability to distinguish between user and
-                * automation-initiated changes, we lose the "touch mute"
-                * behaviour we have in AutomationController::toggled ().
-                */
-               _list->set_in_write_pass (true, false, where);
-       }
-
-       Control::set_double (muted, where, to_list);
-}
-
-void
-Route::MuteControllable::set_value (double val, PBD::Controllable::GroupControlDisposition group_override)
-{
-       if (writable()) {
-               _set_value (val, group_override);
-       }
-}
-
-void
-Route::MuteControllable::set_value_unchecked (double val)
-{
-       /* used only automation playback */
-       _set_value (val, Controllable::NoGroup);
-}
-
-void
-Route::MuteControllable::_set_value (double val, Controllable::GroupControlDisposition group_override)
-{
-       const bool bval = ((val >= 0.5) ? true : false);
-
-       boost::shared_ptr<Route> r = _route.lock ();
-       if (!r) {
-               return;
-       }
-
-       if (_list && ((AutomationList*)_list.get())->automation_playback()) {
-               // Set superficial/automation value to drive controller (and possibly record)
-               set_superficial_value (bval);
-               // Playing back automation, set route mute directly
-               r->set_mute (bval, Controllable::NoGroup);
-       } else {
-               // Set from user, queue mute event
-               boost::shared_ptr<RouteList> rl (new RouteList);
-               rl->push_back (r);
-               _session.set_mute (rl, bval, Session::rt_cleanup, group_override);
-       }
-}
-
-double
-Route::MuteControllable::get_value () const
-{
-       if (_list && ((AutomationList*)_list.get())->automation_playback()) {
-               // Playing back automation, get the value from the list
-               return AutomationControl::get_value();
-       }
-
-       // Not playing back automation, get the actual route mute value
-       boost::shared_ptr<Route> r = _route.lock ();
-       return (r && r->muted()) ? GAIN_COEFF_UNITY : GAIN_COEFF_ZERO;
-}
-
-Route::PhaseControllable::PhaseControllable (std::string name, boost::shared_ptr<Route> r)
-       : RouteAutomationControl (name, PhaseAutomation, boost::shared_ptr<AutomationList>(), r)
-{
-       boost::shared_ptr<AutomationList> gl(new AutomationList(Evoral::Parameter(PhaseAutomation)));
-       gl->set_interpolation(Evoral::ControlList::Discrete);
-       set_list (gl);
-}
-
-void
-Route::PhaseControllable::set_value (double v, PBD::Controllable::GroupControlDisposition /* group_override */)
-{
-       boost::shared_ptr<Route> r = _route.lock ();
-       if (r->phase_invert().size()) {
-               if (v == 0 || (v < 1 && v > 0.9) ) {
-                       r->set_phase_invert (_current_phase, false);
-               } else {
-                       r->set_phase_invert (_current_phase, true);
-               }
-       }
-}
-
-double
-Route::PhaseControllable::get_value () const
-{
-       boost::shared_ptr<Route> r = _route.lock ();
-       return (double) r->phase_invert (_current_phase);
-}
-
-void
-Route::PhaseControllable::set_channel (uint32_t c)
-{
-       _current_phase = c;
-}
-
-uint32_t
-Route::PhaseControllable::channel () const
-{
-       return _current_phase;
-}
-
 void
 Route::set_block_size (pframes_t nframes)
 {
@@ -4367,6 +4524,7 @@ Route::set_phase_invert (uint32_t c, bool yn)
        if (_phase_invert[c] != yn) {
                _phase_invert[c] = yn;
                phase_invert_changed (); /* EMIT SIGNAL */
+               _phase_control->Changed(); /* EMIT SIGNAL */
                _session.set_dirty ();
        }
 }
@@ -4460,6 +4618,16 @@ Route::trim_control() const
        return _trim_control;
 }
 
+boost::shared_ptr<Route::PhaseControllable>
+Route::phase_control() const
+{
+       if (phase_invert().size()) {
+               return _phase_control;
+       } else {
+               return boost::shared_ptr<PhaseControllable>();
+       }
+}
+
 boost::shared_ptr<AutomationControl>
 Route::get_control (const Evoral::Parameter& param)
 {
@@ -4516,7 +4684,7 @@ Route::nth_send (uint32_t n) const
        for (i = _processors.begin(); i != _processors.end(); ++i) {
                if (boost::dynamic_pointer_cast<Send> (*i)) {
 
-                       if ((*i)->name() == _("Monitor")) {
+                       if ((*i)->name().find (_("Monitor")) == 0) {
                                /* send to monitor section is not considered
                                   to be an accessible send.
                                */
@@ -5151,7 +5319,9 @@ Route::pan_azimuth_control() const
 {
 #ifdef MIXBUS
        boost::shared_ptr<ARDOUR::PluginInsert> plug = ch_post();
-       assert (plug);
+       if (!plug) {
+               return boost::shared_ptr<AutomationControl>();
+       }
        const uint32_t port_channel_post_pan = 2; // gtk2_ardour/mixbus_ports.h
        return boost::dynamic_pointer_cast<ARDOUR::AutomationControl> (plug->control (Evoral::Parameter (ARDOUR::PluginAutomation, 0, port_channel_post_pan)));
 #else
@@ -5479,6 +5649,8 @@ Route::comp_mode_name (uint32_t mode) const
                return _("Compressor");
        case 2:
                return _("Limiter");
+       case 3:
+               return mixbus() ? _("Sidechain") : _("Limiter");
        }
 
        return _("???");
@@ -5497,6 +5669,7 @@ Route::comp_speed_name (uint32_t mode) const
        case 1:
                return _("Ratio");
        case 2:
+       case 3:
                return _("Rels");
        }
        return _("???");
@@ -5510,7 +5683,9 @@ Route::send_level_controllable (uint32_t n) const
 {
 #ifdef  MIXBUS
        boost::shared_ptr<ARDOUR::PluginInsert> plug = ch_post();
-       assert (plug);
+       if (!plug) {
+               return boost::shared_ptr<AutomationControl>();
+       }
 
        if (n >= 8) {
                /* no such bus */
@@ -5533,7 +5708,9 @@ Route::send_enable_controllable (uint32_t n) const
 {
 #ifdef  MIXBUS
        boost::shared_ptr<ARDOUR::PluginInsert> plug = ch_post();
-       assert (plug);
+       if (!plug) {
+               return boost::shared_ptr<AutomationControl>();
+       }
 
        if (n >= 8) {
                /* no such bus */
@@ -5571,3 +5748,17 @@ Route::send_name (uint32_t n) const
        }
 #endif
 }
+
+boost::shared_ptr<AutomationControl>
+Route::master_send_enable_controllable () const
+{
+#ifdef  MIXBUS
+       boost::shared_ptr<ARDOUR::PluginInsert> plug = ch_post();
+       if (!plug) {
+               return boost::shared_ptr<AutomationControl>();
+       }
+       return boost::dynamic_pointer_cast<ARDOUR::AutomationControl> (plug->control (Evoral::Parameter (ARDOUR::PluginAutomation, 0, port_channel_post_mstr_assign)));
+#else
+       return boost::shared_ptr<AutomationControl>();
+#endif
+}