closing in on pin management.
[ardour.git] / libs / ardour / route.cc
index a0dc130a7b697be1cb6701a66ed88cb1e0278083..2ce15a6149db7d3f08fc75bac0e600b85958ffbf 100644 (file)
@@ -46,6 +46,7 @@
 #include "ardour/capturing_processor.h"
 #include "ardour/debug.h"
 #include "ardour/delivery.h"
+#include "ardour/gain_control.h"
 #include "ardour/internal_return.h"
 #include "ardour/internal_send.h"
 #include "ardour/meter.h"
@@ -56,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"
@@ -113,6 +115,7 @@ Route::Route (Session& sess, string name, Flag flg, DataType default_type)
        , _track_number (0)
        , _in_configure_processors (false)
        , _initial_io_setup (false)
+       , _strict_io (false)
        , _custom_meter_position_noted (false)
 {
        processor_max_streams.reset();
@@ -137,6 +140,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));
@@ -171,11 +177,22 @@ Route::init ()
 
        /* add amp processor  */
 
-       _amp.reset (new Amp (_session));
+       _gain_control = boost::shared_ptr<GainControllable> (new GainControllable (_session, GainAutomation, shared_from_this ()));
+       add_control (_gain_control);
+
+       _amp.reset (new Amp (_session, X_("Fader"), _gain_control, true));
        add_processor (_amp, PostFader);
 
+       if (is_monitor ()) {
+               _amp->set_display_name (_("Monitor"));
+       }
+
        /* and input trim */
-       _trim.reset (new Amp (_session, "trim"));
+
+       _trim_control = boost::shared_ptr<GainControllable> (new GainControllable (_session, TrimAutomation, shared_from_this ()));
+       add_control (_trim_control);
+
+       _trim.reset (new Amp (_session, X_("Trim"), _trim_control, false));
        _trim->set_display_to_user (false);
 
        if (dynamic_cast<AudioTrack*>(this)) {
@@ -375,19 +392,30 @@ Route::ensure_track_or_route_name(string name, Session &session)
 }
 
 void
-Route::inc_gain (gain_t fraction, void *src)
+Route::inc_gain (gain_t factor)
 {
-       _amp->inc_gain (fraction, src);
+       /* To be used ONLY when doing group-relative gain adjustment, from
+        * ::set_gain()
+        */
+
+       float desired_gain = _gain_control->user_double();
+
+       if (fabsf (desired_gain) < GAIN_COEFF_SMALL) {
+               // really?! what's the idea here?
+               _gain_control->route_set_value (0.000001f + (0.000001f * factor));
+       } else {
+               _gain_control->route_set_value (desired_gain + (desired_gain * factor));
+       }
 }
 
 void
-Route::set_gain (gain_t val, void *src)
+Route::set_gain (gain_t val, Controllable::GroupControlDisposition group_override)
 {
-       if (src != 0 && _route_group && src != _route_group && _route_group->is_active() && _route_group->is_gain()) {
+       if (use_group (group_override, &RouteGroup::is_gain)) {
 
                if (_route_group->is_relative()) {
 
-                       gain_t usable_gain = _amp->gain();
+                       gain_t usable_gain = _gain_control->get_value();
                        if (usable_gain < 0.000001f) {
                                usable_gain = 0.000001f;
                        }
@@ -418,34 +446,28 @@ Route::set_gain (gain_t val, void *src)
                                }
                        }
 
-                       _route_group->foreach_route (boost::bind (&Route::inc_gain, _1, factor, _route_group));
+                       _route_group->foreach_route (boost::bind (&Route::inc_gain, _1, factor));
 
                } else {
 
-                       _route_group->foreach_route (boost::bind (&Route::set_gain, _1, val, _route_group));
+                       _route_group->foreach_route (boost::bind (&Route::set_gain, _1, val, Controllable::NoGroup));
                }
 
                return;
        }
 
-       if (val == _amp->gain()) {
+       if (val == _gain_control->get_value()) {
                return;
        }
 
-       _amp->set_gain (val, src);
-}
-
-void
-Route::inc_trim (gain_t fraction, void *src)
-{
-       _trim->inc_gain (fraction, src);
+       _gain_control->route_set_value (val);
 }
 
 void
-Route::set_trim (gain_t val, void * /* src */)
+Route::set_trim (gain_t val, Controllable::GroupControlDisposition /* group override */)
 {
        // TODO route group, see set_gain()
-       _trim->set_gain (val, 0);
+       _trim_control->route_set_value (val);
 }
 
 void
@@ -642,7 +664,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;
                }
@@ -650,8 +672,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);
@@ -767,19 +801,14 @@ Route::passthru_silence (framepos_t start_frame, framepos_t end_frame, pframes_t
 }
 
 void
-Route::set_listen (bool yn, void* src, bool group_override)
+Route::set_listen (bool yn, Controllable::GroupControlDisposition group_override)
 {
        if (_solo_safe) {
                return;
        }
 
-       bool group_active = _route_group && _route_group->is_active() && _route_group->is_solo();
-       if (group_override && _route_group) {
-               group_active = !group_active;
-       }
-
-       if (_route_group && src != _route_group && group_active) {
-               _route_group->foreach_route (boost::bind (&Route::set_listen, _1, yn, _route_group, group_override));
+       if (use_group (group_override, &RouteGroup::is_solo)) {
+               _route_group->foreach_route (boost::bind (&Route::set_listen, _1, yn, Controllable::ForGroup));
                return;
        }
 
@@ -794,7 +823,7 @@ Route::set_listen (bool yn, void* src, bool group_override)
                        }
                        _mute_master->set_soloed_by_others (false);
 
-                       listen_changed (src, group_override); /* EMIT SIGNAL */
+                       listen_changed (group_override); /* EMIT SIGNAL */
                }
        }
 }
@@ -810,11 +839,12 @@ Route::listening_via_monitor () const
 }
 
 void
-Route::set_solo_safe (bool yn, void *src)
+Route::set_solo_safe (bool yn, Controllable::GroupControlDisposition /* group_override */)
 {
        if (_solo_safe != yn) {
                _solo_safe = yn;
-               solo_safe_changed (src);
+               solo_safe_changed (); /* EMIT SIGNAL */
+               _solo_safe_control->Changed(); /* EMIT SIGNAL */
        }
 }
 
@@ -851,18 +881,21 @@ Route::clear_all_solo_state ()
 
        {
                PBD::Unwinder<bool> uw (_solo_safe, false);
-               set_solo (false, this);
+               set_solo (false, Controllable::NoGroup);
        }
 
        if (emit_changed) {
                set_mute_master_solo ();
-               solo_changed (false, this, false); /* EMIT SIGNAL */
+               solo_changed (false, Controllable::UseGroup); /* EMIT SIGNAL */
        }
 }
 
 void
-Route::set_solo (bool yn, void *src, bool group_override)
+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;
@@ -873,22 +906,14 @@ Route::set_solo (bool yn, void *src, bool group_override)
                return;
        }
 
-       bool group_active = _route_group && _route_group->is_active() && _route_group->is_solo();
-       if (group_override && _route_group) {
-               group_active = !group_active;
-       }
-       if (_route_group && src != _route_group && group_active) {
-               _route_group->foreach_route (boost::bind (&Route::set_solo, _1, yn, _route_group, group_override));
+       if (use_group (group_override, &RouteGroup::is_solo)) {
+               _route_group->foreach_route (boost::bind (&Route::set_solo, _1, yn, Controllable::ForGroup));
                return;
        }
 
-       DEBUG_TRACE (DEBUG::Solo, string_compose ("%1: set solo => %2, src: %3 grp ? %4 currently self-soloed ? %5\n",
-                                                 name(), yn, src, (src == _route_group), self_soloed()));
-
        if (self_soloed() != yn) {
                set_self_solo (yn);
-               set_mute_master_solo ();
-               solo_changed (true, src, group_override); /* EMIT SIGNAL */
+               solo_changed (true, group_override); /* EMIT SIGNAL */
                _solo_control->Changed (); /* EMIT SIGNAL */
        }
 
@@ -899,7 +924,7 @@ Route::set_solo (bool yn, void *src, bool group_override)
        */
 
        if (yn && Profile->get_trx()) {
-               set_mute (false, src);
+               set_mute (false, Controllable::UseGroup);
        }
 }
 
@@ -908,6 +933,7 @@ Route::set_self_solo (bool yn)
 {
        DEBUG_TRACE (DEBUG::Solo, string_compose ("%1: set SELF solo => %2\n", name(), yn));
        _self_solo = yn;
+       set_mute_master_solo ();
 }
 
 void
@@ -965,7 +991,7 @@ Route::mod_solo_by_others_upstream (int32_t delta)
        }
 
        set_mute_master_solo ();
-       solo_changed (false, this, false); /* EMIT SIGNAL */
+       solo_changed (false, Controllable::UseGroup); /* EMIT SIGNAL */
 }
 
 void
@@ -987,7 +1013,7 @@ Route::mod_solo_by_others_downstream (int32_t delta)
        DEBUG_TRACE (DEBUG::Solo, string_compose ("%1 SbD delta %2 = %3\n", name(), delta, _soloed_by_others_downstream));
 
        set_mute_master_solo ();
-       solo_changed (false, this, false); /* EMIT SIGNAL */
+       solo_changed (false, Controllable::UseGroup); /* EMIT SIGNAL */
 }
 
 void
@@ -998,7 +1024,7 @@ Route::set_mute_master_solo ()
 }
 
 void
-Route::mod_solo_isolated_by_upstream (bool yn, void* src)
+Route::mod_solo_isolated_by_upstream (bool yn)
 {
        bool old = solo_isolated ();
        DEBUG_TRACE (DEBUG::Solo, string_compose ("%1 mod_solo_isolated_by_upstream cur: %2 d: %3\n",
@@ -1017,19 +1043,19 @@ Route::mod_solo_isolated_by_upstream (bool yn, void* src)
        if (solo_isolated() != old) {
                /* solo isolated status changed */
                _mute_master->set_solo_ignore (solo_isolated());
-               solo_isolated_changed (src); /* EMIT SIGNAL */
+               solo_isolated_changed (); /* EMIT SIGNAL */
        }
 }
 
 void
-Route::set_solo_isolated (bool yn, void *src)
+Route::set_solo_isolated (bool yn, Controllable::GroupControlDisposition group_override)
 {
        if (is_master() || is_monitor() || is_auditioner()) {
                return;
        }
 
-       if (_route_group && src != _route_group && _route_group->is_active() && _route_group->is_solo()) {
-               _route_group->foreach_route (boost::bind (&Route::set_solo_isolated, _1, yn, _route_group));
+       if (use_group (group_override, &RouteGroup::is_solo)) {
+               _route_group->foreach_route (boost::bind (&Route::set_solo_isolated, _1, yn, Controllable::ForGroup));
                return;
        }
 
@@ -1067,13 +1093,14 @@ Route::set_solo_isolated (bool yn, void *src)
                bool does_feed = feeds (*i, &sends_only);
 
                if (does_feed && !sends_only) {
-                       (*i)->mod_solo_isolated_by_upstream (yn, src);
+                       (*i)->mod_solo_isolated_by_upstream (yn);
                }
        }
 
        /* XXX should we back-propagate as well? (April 2010: myself and chris goddard think not) */
 
-       solo_isolated_changed (src); /* EMIT SIGNAL */
+       solo_isolated_changed (); /* EMIT SIGNAL */
+       _solo_isolate_control->Changed(); /* EMIT SIGNAL */
 }
 
 bool
@@ -1089,16 +1116,16 @@ Route::set_mute_points (MuteMaster::MutePoint mp)
        mute_points_changed (); /* EMIT SIGNAL */
 
        if (_mute_master->muted_by_self()) {
-               mute_changed (this); /* EMIT SIGNAL */
+               mute_changed (); /* EMIT SIGNAL */
                _mute_control->Changed (); /* EMIT SIGNAL */
        }
 }
 
 void
-Route::set_mute (bool yn, void *src)
+Route::set_mute (bool yn, Controllable::GroupControlDisposition group_override)
 {
-       if (_route_group && src != _route_group && _route_group->is_active() && _route_group->is_mute()) {
-               _route_group->foreach_route (boost::bind (&Route::set_mute, _1, yn, _route_group));
+       if (use_group (group_override, &RouteGroup::is_mute)) {
+               _route_group->foreach_route (boost::bind (&Route::set_mute, _1, yn, Controllable::ForGroup));
                return;
        }
 
@@ -1109,7 +1136,7 @@ Route::set_mute (bool yn, void *src)
                */
                act_on_mute ();
                /* tell everyone else */
-               mute_changed (src); /* EMIT SIGNAL */
+               mute_changed (); /* EMIT SIGNAL */
                _mute_control->Changed (); /* EMIT SIGNAL */
        }
 }
@@ -1231,6 +1258,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);
@@ -1429,7 +1463,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);
@@ -1787,6 +1822,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)
 {
@@ -1936,6 +2069,37 @@ Route::try_configure_processors_unlocked (ChanCount in, ProcessorStreams* err)
                if ((*p)->can_support_io_configuration(in, out)) {
                        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));
+
+                       if (is_monitor()) {
+                               // restriction for Monitor Section Processors
+                               if (in.n_audio() != out.n_audio() || out.n_midi() > 0) {
+                                       /* do not allow to add/remove channels (for now)
+                                        * The Monitor follows the master-bus and has no panner (unpan)
+                                        * but do allow processors with midi-in to be added (e.g VSTs with control that
+                                        * will remain unconnected)
+                                        */
+                                       DEBUG_TRACE (DEBUG::Processors, "Monitor: Channel configuration not allowed.\n");
+                                       return list<pair<ChanCount, ChanCount> > ();
+                               }
+                               if (boost::dynamic_pointer_cast<InternalSend> (*p)) {
+                                       // internal sends make no sense, only feedback
+                                       DEBUG_TRACE (DEBUG::Processors, "Monitor: No Sends allowed.\n");
+                                       return list<pair<ChanCount, ChanCount> > ();
+                               }
+                               if (boost::dynamic_pointer_cast<PortInsert> (*p)) {
+                                       /* External Sends can be problematic. one can add/remove ports
+                                        * there signal leaves the DAW to external monitors anyway, so there's
+                                        * no real use for allowing them here anyway.
+                                        */
+                                       DEBUG_TRACE (DEBUG::Processors, "Monitor: No External Sends allowed.\n");
+                                       return list<pair<ChanCount, ChanCount> > ();
+                               }
+                               if (boost::dynamic_pointer_cast<Send> (*p)) {
+                                       // ditto
+                                       DEBUG_TRACE (DEBUG::Processors, "Monitor: No Sends allowed.\n");
+                                       return list<pair<ChanCount, ChanCount> > ();
+                               }
+                       }
                        in = out;
                } else {
                        if (err) {
@@ -1997,10 +2161,14 @@ Route::configure_processors_unlocked (ProcessorStreams* err)
 
                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->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;
 
@@ -2233,6 +2401,113 @@ Route::reorder_processors (const ProcessorList& new_order, ProcessorStreams* err
        return 0;
 }
 
+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)
+{
+       if (_strict_io) {
+               return false;
+       }
+
+       boost::shared_ptr<PluginInsert> pi;
+       if ((pi = boost::dynamic_pointer_cast<PluginInsert>(proc)) == 0) {
+               return false;
+       }
+
+       {
+               bool found = false;
+               Glib::Threads::RWLock::ReaderLock lm (_processor_lock);
+               for (ProcessorList::iterator p = _processors.begin(); p != _processors.end(); ++p) {
+                       if (*p == proc) {
+                               found = true;
+                               break;
+                       }
+               }
+               if (!found) {
+                       return false;
+               }
+       }
+
+       {
+               Glib::Threads::Mutex::Lock lx (AudioEngine::instance()->process_lock ());
+               Glib::Threads::RWLock::WriterLock lm (_processor_lock);
+               ProcessorState pstate (this);
+
+               assert (!pi->strict_io ());
+               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()
 {
@@ -2248,6 +2523,11 @@ Route::get_template()
 XMLNode&
 Route::state(bool full_state)
 {
+       if (!_session._template_state_dir.empty()) {
+               assert (!full_state); // only for templates
+               foreach_processor (sigc::bind (sigc::mem_fun (*this, &Route::set_plugin_state_dir), _session._template_state_dir));
+       }
+
        XMLNode *node = new XMLNode("Route");
        ProcessorList::iterator i;
        char buf[32];
@@ -2256,6 +2536,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));
@@ -2342,6 +2623,10 @@ Route::state(bool full_state)
                }
        }
 
+       if (!_session._template_state_dir.empty()) {
+               foreach_processor (sigc::bind (sigc::mem_fun (*this, &Route::set_plugin_state_dir), ""));
+       }
+
        return *node;
 }
 
@@ -2375,6 +2660,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);
        }
@@ -2457,11 +2746,11 @@ Route::set_state (const XMLNode& node, int version)
        }
 
        if ((prop = node.property ("solo-isolated")) != 0) {
-               set_solo_isolated (string_is_affirmative (prop->value()), this);
+               set_solo_isolated (string_is_affirmative (prop->value()), Controllable::NoGroup);
        }
 
        if ((prop = node.property ("solo-safe")) != 0) {
-               set_solo_safe (string_is_affirmative (prop->value()), this);
+               set_solo_safe (string_is_affirmative (prop->value()), Controllable::NoGroup);
        }
 
        if ((prop = node.property (X_("phase-invert"))) != 0) {
@@ -2616,7 +2905,7 @@ Route::set_state_2X (const XMLNode& node, int version)
 
                /* XXX force reset of solo status */
 
-               set_solo (yn, this);
+               set_solo (yn);
        }
 
        if ((prop = node.property (X_("muted"))) != 0) {
@@ -2755,7 +3044,7 @@ Route::set_state_2X (const XMLNode& node, int version)
                                gain_t val;
 
                                if (sscanf (prop->value().c_str(), "%f", &val) == 1) {
-                                       _amp->gain_control()->set_value (val);
+                                       _amp->gain_control()->set_value (val, Controllable::NoGroup);
                                }
                        }
 
@@ -2918,13 +3207,19 @@ 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));
+                                               if (_strict_io) {
+                                                       boost::shared_ptr<PluginInsert> pi = boost::dynamic_pointer_cast<PluginInsert>(processor);
+                                                       pi->set_strict_io (true);
+                                               }
+
                                        }
                                } else if (prop->value() == "port") {
 
@@ -3183,7 +3478,7 @@ void
 Route::set_comment (string cmt, void *src)
 {
        _comment = cmt;
-       comment_changed (src);
+       comment_changed ();
        _session.set_dirty ();
 }
 
@@ -3357,7 +3652,7 @@ Route::input_change_handler (IOChange change, void * /*src*/)
                if (_solo_isolated_by_upstream) {
                        // solo-isolate currently only propagates downstream
                        if (idelta < 0) {
-                               mod_solo_isolated_by_upstream (false, this);
+                               mod_solo_isolated_by_upstream (false);
                        }
                        // TODO think: mod_solo_isolated_by_upstream() does not take delta arg,
                        // but idelta can't be smaller than -1, can it?
@@ -3377,7 +3672,7 @@ Route::input_change_handler (IOChange change, void * /*src*/)
                        }
 
                        if (idelta < 0 && does_feed && !sends_only) {
-                               (*i)->mod_solo_isolated_by_upstream (false, this);
+                               (*i)->mod_solo_isolated_by_upstream (false);
                        }
                }
        }
@@ -3836,188 +4131,6 @@ Route::set_latency_compensation (framecnt_t longest_session_latency)
        }
 }
 
-Route::SoloControllable::SoloControllable (std::string name, boost::shared_ptr<Route> r)
-       : AutomationControl (r->session(),
-                            Evoral::Parameter (SoloAutomation),
-                            ParameterDescriptor(Evoral::Parameter (SoloAutomation)),
-                            boost::shared_ptr<AutomationList>(), name)
-       , _route (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)
-{
-       if (writable()) {
-               set_value_unchecked (val);
-       }
-}
-
-void
-Route::SoloControllable::set_value_unchecked (double val)
-{
-       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);
-       } else {
-               _session.set_solo (rl, bval);
-       }
-}
-
-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)
-       : AutomationControl (r->session(),
-                            Evoral::Parameter (MuteAutomation),
-                            ParameterDescriptor (Evoral::Parameter (MuteAutomation)),
-                            boost::shared_ptr<AutomationList>(),
-                            name)
-       , _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)
-{
-       if (writable()) {
-               set_value_unchecked (val);
-       }
-}
-
-void
-Route::MuteControllable::set_value_unchecked (double val)
-{
-       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, this);
-       } 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);
-       }
-}
-
-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)
-       : AutomationControl (r->session(),
-                            Evoral::Parameter (PhaseAutomation),
-                            ParameterDescriptor (Evoral::Parameter (PhaseAutomation)),
-                            boost::shared_ptr<AutomationList>(),
-                            name)
-       , _route (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)
-{
-       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 (int c)
-{
-       _current_phase = c;
-}
-
-int
-Route::PhaseControllable::channel () const
-{
-       return _current_phase;
-}
-
 void
 Route::set_block_size (pframes_t nframes)
 {
@@ -4120,17 +4233,33 @@ Route::shift (framepos_t pos, framecnt_t frames)
        }
 }
 
+void
+Route::set_plugin_state_dir (boost::weak_ptr<Processor> p, const std::string& d)
+{
+       boost::shared_ptr<Processor> processor (p.lock ());
+       boost::shared_ptr<PluginInsert> pi  = boost::dynamic_pointer_cast<PluginInsert> (processor);
+       if (!pi) {
+               return;
+       }
+       pi->set_state_dir (d);
+}
 
 int
 Route::save_as_template (const string& path, const string& name)
 {
+       std::string state_dir = path.substr (0, path.find_last_of ('.')); // strip template_suffix
+       PBD::Unwinder<std::string> uw (_session._template_state_dir, state_dir);
+
        XMLNode& node (state (false));
+
        XMLTree tree;
 
        IO::set_name_in_state (*node.children().front(), name);
 
        tree.set_root (&node);
-       return tree.write (path.c_str());
+
+       /* return zero on success, non-zero otherwise */
+       return !tree.write (path.c_str());
 }
 
 
@@ -4171,7 +4300,7 @@ Route::set_name (const string& str)
  *  @param name New name.
  */
 void
-Route::set_name_in_state (XMLNode& node, string const & name)
+Route::set_name_in_state (XMLNode& node, string const & name, bool rename_playlist)
 {
        node.add_property (X_("name"), name);
 
@@ -4191,7 +4320,9 @@ Route::set_name_in_state (XMLNode& node, string const & name)
 
                } else if ((*i)->name() == X_("Diskstream")) {
 
-                       (*i)->add_property (X_("playlist"), string_compose ("%1.1", name).c_str());
+                       if (rename_playlist) {
+                               (*i)->add_property (X_("playlist"), string_compose ("%1.1", name).c_str());
+                       }
                        (*i)->add_property (X_("name"), name);
 
                }
@@ -4225,6 +4356,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 ();
        }
 }
@@ -4306,16 +4438,26 @@ Route::panner_shell() const
        return _main_outs->panner_shell();
 }
 
-boost::shared_ptr<AutomationControl>
+boost::shared_ptr<GainControl>
 Route::gain_control() const
 {
-       return _amp->gain_control();
+       return _gain_control;
 }
 
-boost::shared_ptr<AutomationControl>
+boost::shared_ptr<GainControl>
 Route::trim_control() const
 {
-       return _trim->gain_control();
+       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>
@@ -4349,10 +4491,10 @@ Route::get_control (const Evoral::Parameter& param)
 }
 
 boost::shared_ptr<Processor>
-Route::nth_plugin (uint32_t n)
+Route::nth_plugin (uint32_t n) const
 {
        Glib::Threads::RWLock::ReaderLock lm (_processor_lock);
-       ProcessorList::iterator i;
+       ProcessorList::const_iterator i;
 
        for (i = _processors.begin(); i != _processors.end(); ++i) {
                if (boost::dynamic_pointer_cast<PluginInsert> (*i)) {
@@ -4366,13 +4508,21 @@ Route::nth_plugin (uint32_t n)
 }
 
 boost::shared_ptr<Processor>
-Route::nth_send (uint32_t n)
+Route::nth_send (uint32_t n) const
 {
        Glib::Threads::RWLock::ReaderLock lm (_processor_lock);
-       ProcessorList::iterator i;
+       ProcessorList::const_iterator i;
 
        for (i = _processors.begin(); i != _processors.end(); ++i) {
                if (boost::dynamic_pointer_cast<Send> (*i)) {
+
+                       if ((*i)->name().find (_("Monitor")) == 0) {
+                               /* send to monitor section is not considered
+                                  to be an accessible send.
+                               */
+                               continue;
+                       }
+
                        if (n-- == 0) {
                                return *i;
                        }
@@ -4715,7 +4865,7 @@ Route::setup_invisible_processors ()
 
        if (_monitor_control && is_monitor ()) {
                assert (!_monitor_control->display_to_user ());
-               new_processors.push_front (_monitor_control);
+               new_processors.insert (amp, _monitor_control);
        }
 
        /* INTERNAL RETURN */
@@ -4995,3 +5145,452 @@ Route::fill_buffers_with_input (BufferSet& bufs, boost::shared_ptr<IO> io, pfram
                bufs.set_count (io->n_ports());
        }
 }
+
+boost::shared_ptr<AutomationControl>
+Route::pan_azimuth_control() const
+{
+#ifdef MIXBUS
+       boost::shared_ptr<ARDOUR::PluginInsert> plug = ch_post();
+       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
+       if (!_pannable || !panner()) {
+               return boost::shared_ptr<AutomationControl>();
+       }
+       return _pannable->pan_azimuth_control;
+#endif
+}
+
+boost::shared_ptr<AutomationControl>
+Route::pan_elevation_control() const
+{
+       if (Profile->get_mixbus() || !_pannable || !panner()) {
+               return boost::shared_ptr<AutomationControl>();
+       }
+
+       set<Evoral::Parameter> c = panner()->what_can_be_automated ();
+
+       if (c.find (PanElevationAutomation) != c.end()) {
+               return _pannable->pan_elevation_control;
+       } else {
+               return boost::shared_ptr<AutomationControl>();
+       }
+}
+boost::shared_ptr<AutomationControl>
+Route::pan_width_control() const
+{
+       if (Profile->get_mixbus() || !_pannable || !panner()) {
+               return boost::shared_ptr<AutomationControl>();
+       }
+
+       set<Evoral::Parameter> c = panner()->what_can_be_automated ();
+
+       if (c.find (PanWidthAutomation) != c.end()) {
+               return _pannable->pan_width_control;
+       } else {
+               return boost::shared_ptr<AutomationControl>();
+       }
+}
+boost::shared_ptr<AutomationControl>
+Route::pan_frontback_control() const
+{
+       if (Profile->get_mixbus() || !_pannable || !panner()) {
+               return boost::shared_ptr<AutomationControl>();
+       }
+
+       set<Evoral::Parameter> c = panner()->what_can_be_automated ();
+
+       if (c.find (PanFrontBackAutomation) != c.end()) {
+               return _pannable->pan_frontback_control;
+       } else {
+               return boost::shared_ptr<AutomationControl>();
+       }
+}
+boost::shared_ptr<AutomationControl>
+Route::pan_lfe_control() const
+{
+       if (Profile->get_mixbus() || !_pannable || !panner()) {
+               return boost::shared_ptr<AutomationControl>();
+       }
+
+       set<Evoral::Parameter> c = panner()->what_can_be_automated ();
+
+       if (c.find (PanLFEAutomation) != c.end()) {
+               return _pannable->pan_lfe_control;
+       } else {
+               return boost::shared_ptr<AutomationControl>();
+       }
+}
+
+uint32_t
+Route::eq_band_cnt () const
+{
+       if (Profile->get_mixbus()) {
+               return 3;
+       } else {
+               /* Ardour has no well-known EQ object */
+               return 0;
+       }
+}
+
+boost::shared_ptr<AutomationControl>
+Route::eq_gain_controllable (uint32_t band) const
+{
+#ifdef MIXBUS
+       boost::shared_ptr<PluginInsert> eq = ch_eq();
+
+       if (!eq) {
+               return boost::shared_ptr<AutomationControl>();
+       }
+
+       uint32_t port_number;
+       switch (band) {
+       case 0:
+               if (is_master() || mixbus()) {
+                       port_number = 4;
+               } else {
+                       port_number = 8;
+               }
+               break;
+       case 1:
+               if (is_master() || mixbus()) {
+                       port_number = 3;
+               } else {
+                       port_number = 6;
+               }
+               break;
+       case 2:
+               if (is_master() || mixbus()) {
+                       port_number = 2;
+               } else {
+                       port_number = 4;
+               }
+               break;
+       default:
+               return boost::shared_ptr<AutomationControl>();
+       }
+
+       return boost::dynamic_pointer_cast<ARDOUR::AutomationControl> (eq->control (Evoral::Parameter (ARDOUR::PluginAutomation, 0, port_number)));
+#else
+       return boost::shared_ptr<AutomationControl>();
+#endif
+}
+boost::shared_ptr<AutomationControl>
+Route::eq_freq_controllable (uint32_t band) const
+{
+#ifdef MIXBUS
+
+       if (mixbus() || is_master()) {
+               /* no frequency controls for mixbusses or master */
+               return boost::shared_ptr<AutomationControl>();
+       }
+
+       boost::shared_ptr<PluginInsert> eq = ch_eq();
+
+       if (!eq) {
+               return boost::shared_ptr<AutomationControl>();
+       }
+
+       uint32_t port_number;
+       switch (band) {
+       case 0:
+               port_number = 7;
+               break;
+       case 1:
+               port_number = 5;
+               break;
+       case 2:
+               port_number = 3;
+               break;
+       default:
+               return boost::shared_ptr<AutomationControl>();
+       }
+
+       return boost::dynamic_pointer_cast<ARDOUR::AutomationControl> (eq->control (Evoral::Parameter (ARDOUR::PluginAutomation, 0, port_number)));
+#else
+       return boost::shared_ptr<AutomationControl>();
+#endif
+}
+
+boost::shared_ptr<AutomationControl>
+Route::eq_q_controllable (uint32_t band) const
+{
+       return boost::shared_ptr<AutomationControl>();
+}
+
+boost::shared_ptr<AutomationControl>
+Route::eq_shape_controllable (uint32_t band) const
+{
+       return boost::shared_ptr<AutomationControl>();
+}
+
+boost::shared_ptr<AutomationControl>
+Route::eq_enable_controllable () const
+{
+#ifdef MIXBUS
+       boost::shared_ptr<PluginInsert> eq = ch_eq();
+
+       if (!eq) {
+               return boost::shared_ptr<AutomationControl>();
+       }
+
+       return boost::dynamic_pointer_cast<ARDOUR::AutomationControl> (eq->control (Evoral::Parameter (ARDOUR::PluginAutomation, 0, 1)));
+#else
+       return boost::shared_ptr<AutomationControl>();
+#endif
+}
+
+boost::shared_ptr<AutomationControl>
+Route::eq_hpf_controllable () const
+{
+#ifdef MIXBUS
+       boost::shared_ptr<PluginInsert> eq = ch_eq();
+
+       if (!eq) {
+               return boost::shared_ptr<AutomationControl>();
+       }
+
+       return boost::dynamic_pointer_cast<ARDOUR::AutomationControl> (eq->control (Evoral::Parameter (ARDOUR::PluginAutomation, 0, 2)));
+#else
+       return boost::shared_ptr<AutomationControl>();
+#endif
+}
+
+string
+Route::eq_band_name (uint32_t band) const
+{
+       if (Profile->get_mixbus()) {
+               switch (band) {
+               case 0:
+                       return _("lo");
+               case 1:
+                       return _("mid");
+               case 2:
+                       return _("hi");
+               default:
+                       return string();
+               }
+       } else {
+               return string ();
+       }
+}
+
+boost::shared_ptr<AutomationControl>
+Route::comp_enable_controllable () const
+{
+#ifdef MIXBUS
+       boost::shared_ptr<PluginInsert> comp = ch_comp();
+
+       if (!comp) {
+               return boost::shared_ptr<AutomationControl>();
+       }
+
+       return boost::dynamic_pointer_cast<ARDOUR::AutomationControl> (comp->control (Evoral::Parameter (ARDOUR::PluginAutomation, 0, 1)));
+#else
+       return boost::shared_ptr<AutomationControl>();
+#endif
+}
+boost::shared_ptr<AutomationControl>
+Route::comp_threshold_controllable () const
+{
+#ifdef MIXBUS
+       boost::shared_ptr<PluginInsert> comp = ch_comp();
+
+       if (!comp) {
+               return boost::shared_ptr<AutomationControl>();
+       }
+
+       return boost::dynamic_pointer_cast<ARDOUR::AutomationControl> (comp->control (Evoral::Parameter (ARDOUR::PluginAutomation, 0, 2)));
+
+#else
+       return boost::shared_ptr<AutomationControl>();
+#endif
+}
+boost::shared_ptr<AutomationControl>
+Route::comp_speed_controllable () const
+{
+#ifdef MIXBUS
+       boost::shared_ptr<PluginInsert> comp = ch_comp();
+
+       if (!comp) {
+               return boost::shared_ptr<AutomationControl>();
+       }
+
+       return boost::dynamic_pointer_cast<ARDOUR::AutomationControl> (comp->control (Evoral::Parameter (ARDOUR::PluginAutomation, 0, 3)));
+#else
+       return boost::shared_ptr<AutomationControl>();
+#endif
+}
+boost::shared_ptr<AutomationControl>
+Route::comp_mode_controllable () const
+{
+#ifdef MIXBUS
+       boost::shared_ptr<PluginInsert> comp = ch_comp();
+
+       if (!comp) {
+               return boost::shared_ptr<AutomationControl>();
+       }
+
+       return boost::dynamic_pointer_cast<ARDOUR::AutomationControl> (comp->control (Evoral::Parameter (ARDOUR::PluginAutomation, 0, 4)));
+#else
+       return boost::shared_ptr<AutomationControl>();
+#endif
+}
+boost::shared_ptr<AutomationControl>
+Route::comp_makeup_controllable () const
+{
+#ifdef MIXBUS
+       boost::shared_ptr<PluginInsert> comp = ch_comp();
+
+       if (!comp) {
+               return boost::shared_ptr<AutomationControl>();
+       }
+
+       return boost::dynamic_pointer_cast<ARDOUR::AutomationControl> (comp->control (Evoral::Parameter (ARDOUR::PluginAutomation, 0, 5)));
+#else
+       return boost::shared_ptr<AutomationControl>();
+#endif
+}
+boost::shared_ptr<AutomationControl>
+Route::comp_redux_controllable () const
+{
+#ifdef MIXBUS
+       boost::shared_ptr<PluginInsert> comp = ch_comp();
+
+       if (!comp) {
+               return boost::shared_ptr<AutomationControl>();
+       }
+
+       return boost::dynamic_pointer_cast<ARDOUR::AutomationControl> (comp->control (Evoral::Parameter (ARDOUR::PluginAutomation, 0, 6)));
+#else
+       return boost::shared_ptr<AutomationControl>();
+#endif
+}
+
+string
+Route::comp_mode_name (uint32_t mode) const
+{
+#ifdef MIXBUS
+       switch (mode) {
+       case 0:
+               return _("Leveler");
+       case 1:
+               return _("Compressor");
+       case 2:
+               return _("Limiter");
+       case 3:
+               return mixbus() ? _("Sidechain") : _("Limiter");
+       }
+
+       return _("???");
+#else
+       return _("???");
+#endif
+}
+
+string
+Route::comp_speed_name (uint32_t mode) const
+{
+#ifdef MIXBUS
+       switch (mode) {
+       case 0:
+               return _("Attk");
+       case 1:
+               return _("Ratio");
+       case 2:
+       case 3:
+               return _("Rels");
+       }
+       return _("???");
+#else
+       return _("???");
+#endif
+}
+
+boost::shared_ptr<AutomationControl>
+Route::send_level_controllable (uint32_t n) const
+{
+#ifdef  MIXBUS
+       boost::shared_ptr<ARDOUR::PluginInsert> plug = ch_post();
+       if (!plug) {
+               return boost::shared_ptr<AutomationControl>();
+       }
+
+       if (n >= 8) {
+               /* no such bus */
+               return boost::shared_ptr<AutomationControl>();
+       }
+
+       const uint32_t port_id = port_channel_post_aux1_level + (2*n); // gtk2_ardour/mixbus_ports.h
+       return boost::dynamic_pointer_cast<ARDOUR::AutomationControl> (plug->control (Evoral::Parameter (ARDOUR::PluginAutomation, 0, port_id)));
+#else
+       boost::shared_ptr<Send> s = boost::dynamic_pointer_cast<Send>(nth_send (n));
+       if (!s) {
+               return boost::shared_ptr<AutomationControl>();
+       }
+       return s->gain_control ();
+#endif
+}
+
+boost::shared_ptr<AutomationControl>
+Route::send_enable_controllable (uint32_t n) const
+{
+#ifdef  MIXBUS
+       boost::shared_ptr<ARDOUR::PluginInsert> plug = ch_post();
+       if (!plug) {
+               return boost::shared_ptr<AutomationControl>();
+       }
+
+       if (n >= 8) {
+               /* no such bus */
+               return boost::shared_ptr<AutomationControl>();
+       }
+
+       const uint32_t port_id = port_channel_post_aux1_asgn + (2*n); // gtk2_ardour/mixbus_ports.h
+       return boost::dynamic_pointer_cast<ARDOUR::AutomationControl> (plug->control (Evoral::Parameter (ARDOUR::PluginAutomation, 0, port_id)));
+#else
+       /* although Ardour sends have enable/disable as part of the Processor
+          API, it is not exposed as a controllable.
+
+          XXX: we should fix this.
+       */
+       return boost::shared_ptr<AutomationControl>();
+#endif
+}
+
+string
+Route::send_name (uint32_t n) const
+{
+#ifdef MIXBUS
+       if (n >= 8) {
+               return string();
+       }
+       boost::shared_ptr<Route> r = _session.get_mixbus (n);
+       assert (r);
+       return r->name();
+#else
+       boost::shared_ptr<Processor> p = nth_send (n);
+       if (p) {
+               return p->name();
+       } else {
+               return string();
+       }
+#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
+}