start shaping up VCA assign process
[ardour.git] / libs / ardour / route.cc
index 5132c275ac0b160991c16c9a546cb7d01ee10f1b..8b1f47ec4ec22a6409472fe16266dba077523ac5 100644 (file)
@@ -33,7 +33,6 @@
 #include "pbd/memento_command.h"
 #include "pbd/stacktrace.h"
 #include "pbd/convert.h"
-#include "pbd/boost_debug.h"
 #include "pbd/unwind.h"
 
 #include "ardour/amp.h"
@@ -41,6 +40,7 @@
 #include "ardour/audio_track.h"
 #include "ardour/audio_port.h"
 #include "ardour/audioengine.h"
+#include "ardour/boost_debug.h"
 #include "ardour/buffer.h"
 #include "ardour/buffer_set.h"
 #include "ardour/capturing_processor.h"
@@ -69,6 +69,7 @@
 #include "ardour/session.h"
 #include "ardour/unknown_processor.h"
 #include "ardour/utils.h"
+#include "ardour/vca.h"
 
 #include "i18n.h"
 
@@ -78,6 +79,7 @@ using namespace PBD;
 
 PBD::Signal0<void> Route::SyncOrderKeys;
 PBD::Signal0<void> Route::RemoteControlIDChange;
+PBD::Signal3<int,boost::shared_ptr<Route>, boost::shared_ptr<PluginInsert>, Route::PluginSetupOptions > Route::PluginSetup;
 
 /** Base class for all routable/mixable objects (tracks and busses) */
 Route::Route (Session& sess, string name, Flag flg, DataType default_type)
@@ -119,6 +121,7 @@ Route::Route (Session& sess, string name, Flag flg, DataType default_type)
        , _in_sidechain_setup (false)
        , _strict_io (false)
        , _custom_meter_position_noted (false)
+       , _pinmgr_proxy (0)
 {
        processor_max_streams.reset();
 }
@@ -170,14 +173,9 @@ Route::init ()
        _output->changed.connect_same_thread (*this, boost::bind (&Route::output_change_handler, this, _1, _2));
        _output->PortCountChanging.connect_same_thread (*this, boost::bind (&Route::output_port_count_changing, this, _1));
 
-#if 0 // not used - just yet
-       if (!is_master() && !is_monitor() && !is_auditioner()) {
-               _delayline.reset (new DelayLine (_session, _name));
-               add_processor (_delayline, PreFader);
-       }
-#endif
-
-       /* add amp processor  */
+       /* add the amp/fader processor.
+        * it should be the first processor to be added on every route.
+        */
 
        _gain_control = boost::shared_ptr<GainControllable> (new GainControllable (_session, GainAutomation, shared_from_this ()));
        add_control (_gain_control);
@@ -189,6 +187,13 @@ Route::init ()
                _amp->set_display_name (_("Monitor"));
        }
 
+#if 0 // not used - just yet
+       if (!is_master() && !is_monitor() && !is_auditioner()) {
+               _delayline.reset (new DelayLine (_session, _name));
+               add_processor (_delayline, PreFader);
+       }
+#endif
+
        /* and input trim */
 
        _trim_control = boost::shared_ptr<GainControllable> (new GainControllable (_session, TrimAutomation, shared_from_this ()));
@@ -1256,96 +1261,37 @@ Route::add_processor (boost::shared_ptr<Processor> processor, boost::shared_ptr<
        DEBUG_TRACE (DEBUG::Processors, string_compose (
                             "%1 adding processor %2\n", name(), processor->name()));
 
-       if (!AudioEngine::instance()->connected() || !processor) {
-               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);
-               ProcessorState pstate (this);
-
-               boost::shared_ptr<PluginInsert> pi;
-               boost::shared_ptr<PortInsert> porti;
-
-               if (processor == _amp) {
-                       /* Ensure that only one amp is in the list at any time */
-                       ProcessorList::iterator check = find (_processors.begin(), _processors.end(), processor);
-                       if (check != _processors.end()) {
-                               if (before == _amp) {
-                                       /* Already in position; all is well */
-                                       return 0;
-                               } else {
-                                       _processors.erase (check);
-                               }
-                       }
-               }
-
-               assert (find (_processors.begin(), _processors.end(), processor) == _processors.end ());
-
-               ProcessorList::iterator loc;
-               if (before) {
-                       /* inserting before a processor; find it */
-                       loc = find (_processors.begin(), _processors.end(), before);
-                       if (loc == _processors.end ()) {
-                               /* Not found */
-                               return 1;
-                       }
-               } else {
-                       /* inserting at end */
-                       loc = _processors.end ();
-               }
-
-               _processors.insert (loc, processor);
-               processor->set_owner (this);
-
-               // Set up processor list channels.  This will set processor->[input|output]_streams(),
-               // configure redirect ports properly, etc.
-
-               {
-                       if (configure_processors_unlocked (err)) {
-                               pstate.restore ();
-                               configure_processors_unlocked (0); // it worked before we tried to add it ...
-                               return -1;
-                       }
-               }
-
-               if ((pi = boost::dynamic_pointer_cast<PluginInsert>(processor)) != 0) {
-
-                       if (pi->has_no_inputs ()) {
-                               /* generator plugin */
-                               _have_internal_generator = true;
-                       }
-
-               }
-
-               if (activation_allowed && (!_session.get_bypass_all_loaded_plugins () || !processor->display_to_user ())) {
-                       processor->activate ();
-               }
+       ProcessorList pl;
 
-               processor->ActiveChanged.connect_same_thread (*this, boost::bind (&Session::update_latency_compensation, &_session, false));
+       pl.push_back (processor);
+       int rv = add_processors (pl, before, err);
 
-               _output->set_user_latency (0);
+       if (rv) {
+               return rv;
        }
 
-       reset_instrument_info ();
-       processors_changed (RouteProcessorChange ()); /* EMIT SIGNAL */
-       set_processor_positions ();
+       if (activation_allowed && (!_session.get_bypass_all_loaded_plugins () || !processor->display_to_user ())) {
+               processor->activate ();
+       }
 
        return 0;
 }
 
+void
+Route::processor_selfdestruct (boost::weak_ptr<Processor> wp)
+{
+       /* We cannot destruct the processor here (usually RT-thread
+        * with various locks held - in case of sends also io_locks).
+        * Queue for deletion in low-priority thread.
+        */
+       Glib::Threads::Mutex::Lock lx (selfdestruct_lock);
+       selfdestruct_sequence.push_back (wp);
+}
+
 bool
 Route::add_processor_from_xml_2X (const XMLNode& node, int version)
 {
-       const XMLProperty *prop;
+       XMLProperty const * prop;
 
        try {
                boost::shared_ptr<Processor> processor;
@@ -1427,24 +1373,31 @@ Route::add_processor_from_xml_2X (const XMLNode& node, int version)
        }
 }
 
+
+inline Route::PluginSetupOptions operator|= (Route::PluginSetupOptions& a, const Route::PluginSetupOptions& b) {
+       return a = static_cast<Route::PluginSetupOptions> (static_cast <int>(a) | static_cast<int> (b));
+}
+
+inline Route::PluginSetupOptions operator&= (Route::PluginSetupOptions& a, const Route::PluginSetupOptions& b) {
+       return a = static_cast<Route::PluginSetupOptions> (static_cast <int>(a) & static_cast<int> (b));
+}
+
 int
 Route::add_processors (const ProcessorList& others, boost::shared_ptr<Processor> before, ProcessorStreams* err)
 {
-       /* NOTE: this is intended to be used ONLY when copying
-          processors from another Route. Hence the subtle
-          differences between this and ::add_processor()
-       */
-
        ProcessorList::iterator loc;
 
        if (before) {
                loc = find(_processors.begin(), _processors.end(), before);
+               if (loc == _processors.end ()) {
+                       return 1;
+               }
        } else {
                /* nothing specified - at end */
                loc = _processors.end ();
        }
 
-       if (!_session.engine().connected()) {
+       if (!AudioEngine::instance()->connected()) {
                return 1;
        }
 
@@ -1452,6 +1405,59 @@ Route::add_processors (const ProcessorList& others, boost::shared_ptr<Processor>
                return 0;
        }
 
+       ProcessorList to_skip;
+
+       // check if there's an instrument to replace or configure
+       for (ProcessorList::const_iterator i = others.begin(); i != others.end(); ++i) {
+               boost::shared_ptr<PluginInsert> pi;
+               if ((pi = boost::dynamic_pointer_cast<PluginInsert>(*i)) == 0) {
+                       continue;
+               }
+               if (!pi->plugin ()->get_info ()->is_instrument ()) {
+                       continue;
+               }
+               boost::shared_ptr<Processor> instrument = the_instrument ();
+               ChanCount in (DataType::MIDI, 1);
+               ChanCount out (DataType::AUDIO, 2); // XXX route's out?!
+
+               PluginSetupOptions flags = None;
+               if (instrument) {
+                       flags |= CanReplace;
+                       in = instrument->input_streams ();
+                       out = instrument->output_streams ();
+               }
+               if (pi->has_output_presets (in, out)) {
+                       flags |= MultiOut;
+               }
+
+               pi->set_strict_io (_strict_io);
+
+               PluginSetupOptions mask = None;
+               if (Config->get_ask_replace_instrument ()) {
+                       mask |= CanReplace;
+               }
+               if (Config->get_ask_setup_instrument ()) {
+                       mask |= MultiOut;
+               }
+
+               flags &= mask;
+
+               if (flags != None) {
+                       boost::optional<int> rv = PluginSetup (shared_from_this (), pi, flags);  /* EMIT SIGNAL */
+                       switch (rv.get_value_or (0)) {
+                               case 1:
+                                       to_skip.push_back (*i); // don't add this one;
+                                       break;
+                               case 2:
+                                       replace_processor (instrument, *i, err);
+                                       to_skip.push_back (*i);
+                                       break;
+                               default:
+                                       break;
+                       }
+               }
+       }
+
        {
                Glib::Threads::Mutex::Lock lx (AudioEngine::instance()->process_lock ());
                Glib::Threads::RWLock::WriterLock lm (_processor_lock);
@@ -1462,31 +1468,54 @@ Route::add_processors (const ProcessorList& others, boost::shared_ptr<Processor>
                        if (*i == _meter) {
                                continue;
                        }
+                       ProcessorList::iterator check = find (to_skip.begin(), to_skip.end(), *i);
+                       if (check != to_skip.end()) {
+                               continue;
+                       }
 
                        boost::shared_ptr<PluginInsert> pi;
 
                        if ((pi = boost::dynamic_pointer_cast<PluginInsert>(*i)) != 0) {
-                               pi->set_count (1); // why? configure_processors_unlocked() will re-do this
                                pi->set_strict_io (_strict_io);
                        }
 
+                       if (*i == _amp) {
+                               /* Ensure that only one amp is in the list at any time */
+                               ProcessorList::iterator check = find (_processors.begin(), _processors.end(), *i);
+                               if (check != _processors.end()) {
+                                       if (before == _amp) {
+                                               /* Already in position; all is well */
+                                               continue;
+                                       } else {
+                                               _processors.erase (check);
+                                       }
+                               }
+                       }
+
+                       assert (find (_processors.begin(), _processors.end(), *i) == _processors.end ());
+
                        _processors.insert (loc, *i);
                        (*i)->set_owner (this);
 
-                       if ((*i)->active()) {
-                               (*i)->activate ();
-                       }
-
-                       /* Think: does this really need to be called for every processor in the loop? */
                        {
-                               if (configure_processors_unlocked (err)) {
+                               if (configure_processors_unlocked (err, &lm)) {
                                        pstate.restore ();
-                                       configure_processors_unlocked (0); // it worked before we tried to add it ...
+                                       configure_processors_unlocked (0, &lm); // it worked before we tried to add it ...
                                        return -1;
                                }
                        }
 
+                       if ((*i)->active()) {
+                               (*i)->activate ();
+                       }
+
                        (*i)->ActiveChanged.connect_same_thread (*this, boost::bind (&Session::update_latency_compensation, &_session, false));
+
+                       boost::shared_ptr<Send> send;
+                       if ((send = boost::dynamic_pointer_cast<Send> (*i))) {
+                               send->SelfDestruct.connect_same_thread (*this,
+                                               boost::bind (&Route::processor_selfdestruct, this, boost::weak_ptr<Processor> (*i)));
+                       }
                }
 
                for (ProcessorList::const_iterator i = _processors.begin(); i != _processors.end(); ++i) {
@@ -1699,7 +1728,7 @@ Route::clear_processors (Placement p)
                }
 
                _processors = new_list;
-               configure_processors_unlocked (&err); // this can't fail
+               configure_processors_unlocked (&err, &lm); // this can't fail
        }
 
        processor_max_streams.reset();
@@ -1799,10 +1828,10 @@ Route::remove_processor (boost::shared_ptr<Processor> processor, ProcessorStream
                        return 1;
                }
 
-               if (configure_processors_unlocked (err)) {
+               if (configure_processors_unlocked (err, &lm)) {
                        pstate.restore ();
                        /* we know this will work, because it worked before :) */
-                       configure_processors_unlocked (0);
+                       configure_processors_unlocked (0, &lm);
                        return -1;
                }
 
@@ -1896,9 +1925,9 @@ Route::replace_processor (boost::shared_ptr<Processor> old, boost::shared_ptr<Pr
                        }
                }
 
-               if (configure_processors_unlocked (err)) {
+               if (configure_processors_unlocked (err, &lm)) {
                        pstate.restore ();
-                       configure_processors_unlocked (0);
+                       configure_processors_unlocked (0, &lm);
                        return -1;
                }
 
@@ -1993,10 +2022,10 @@ Route::remove_processors (const ProcessorList& to_be_deleted, ProcessorStreams*
 
                _output->set_user_latency (0);
 
-               if (configure_processors_unlocked (err)) {
+               if (configure_processors_unlocked (err, &lm)) {
                        pstate.restore ();
                        /* we know this will work, because it worked before :) */
-                       configure_processors_unlocked (0);
+                       configure_processors_unlocked (0, &lm);
                        return -1;
                }
                //lx.unlock();
@@ -2047,7 +2076,7 @@ Route::configure_processors (ProcessorStreams* err)
 
        if (!_in_configure_processors) {
                Glib::Threads::RWLock::WriterLock lm (_processor_lock);
-               return configure_processors_unlocked (err);
+               return configure_processors_unlocked (err, &lm);
        }
 
        return 0;
@@ -2081,6 +2110,32 @@ 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
+                                       && !(is_monitor() || is_auditioner())
+                                       && ( _strict_io || Profile->get_mixbus ())) {
+                               /* 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 (!is_master() && _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));
 
@@ -2137,7 +2192,7 @@ Route::try_configure_processors_unlocked (ChanCount in, ProcessorStreams* err)
  *  Return 0 on success, otherwise configuration is impossible.
  */
 int
-Route::configure_processors_unlocked (ProcessorStreams* err)
+Route::configure_processors_unlocked (ProcessorStreams* err, Glib::Threads::RWLock::WriterLock* lm)
 {
 #ifndef PLATFORM_WINDOWS
        assert (!AudioEngine::instance()->process_lock().trylock());
@@ -2164,28 +2219,54 @@ Route::configure_processors_unlocked (ProcessorStreams* err)
        processor_out_streams = _input->n_ports();
        processor_max_streams.reset();
 
+       /* processor configure_io() may result in adding ports
+        * e.g. Delivery::configure_io -> ARDOUR::IO::ensure_io ()
+        *
+        * with jack2 adding ports results in a graph-order callback,
+        * which calls Session::resort_routes() and eventually
+        * Route::direct_feeds_according_to_reality()
+        * which takes a ReaderLock (_processor_lock).
+        *
+        * so we can't hold a WriterLock here until jack2 threading
+        * is fixed.
+        *
+        * NB. we still hold the process lock
+        *
+        * (ardour's own engines do call graph-order from the
+        * process-thread and hence do not have this issue; besides
+        * merely adding ports won't trigger a graph-order, only
+        * making connections does)
+        */
+       lm->release ();
+
+       // TODO check for a potential ReaderLock after ReaderLock ??
+       Glib::Threads::RWLock::ReaderLock lr (_processor_lock);
+
        list< pair<ChanCount,ChanCount> >::iterator c = configuration.begin();
        for (ProcessorList::iterator p = _processors.begin(); p != _processors.end(); ++p, ++c) {
 
                if (!(*p)->configure_io(c->first, c->second)) {
                        DEBUG_TRACE (DEBUG::Processors, string_compose ("%1: configuration failed\n", _name));
                        _in_configure_processors = false;
+                       lr.release ();
+                       lm->acquire ();
                        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<IOProcessor> iop;
                boost::shared_ptr<PluginInsert> pi;
                if ((pi = boost::dynamic_pointer_cast<PluginInsert>(*p)) != 0) {
                        /* 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->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());
+                       processor_max_streams = ChanCount::max(processor_max_streams, pi->required_buffers());
+               }
+               else if ((iop = boost::dynamic_pointer_cast<IOProcessor>(*p)) != 0) {
+                       processor_max_streams = ChanCount::max(processor_max_streams, iop->natural_input_streams());
+                       processor_max_streams = ChanCount::max(processor_max_streams, iop->natural_output_streams());
                }
                out = c->second;
 
@@ -2202,6 +2283,9 @@ Route::configure_processors_unlocked (ProcessorStreams* err)
                }
        }
 
+       lr.release ();
+       lm->acquire ();
+
 
        if (_meter) {
                _meter->set_max_channels (processor_max_streams);
@@ -2395,7 +2479,7 @@ Route::reorder_processors (const ProcessorList& new_order, ProcessorStreams* err
 
                apply_processor_order (new_order);
 
-               if (configure_processors_unlocked (err)) {
+               if (configure_processors_unlocked (err, &lm)) {
                        pstate.restore ();
                        return -1;
                }
@@ -2468,7 +2552,7 @@ Route::add_remove_sidechain (boost::shared_ptr<Processor> proc, bool add)
                        return false;
                }
                lx.acquire ();
-               configure_processors_unlocked (0);
+               configure_processors_unlocked (0, &lm);
        }
 
        if (pi->has_sidechain ()) {
@@ -2480,15 +2564,54 @@ Route::add_remove_sidechain (boost::shared_ptr<Processor> proc, bool add)
        return true;
 }
 
+bool
+Route::plugin_preset_output (boost::shared_ptr<Processor> proc, 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);
+
+               const ChanCount& old (pi->preset_out ());
+               if (!pi->set_preset_out (outs)) {
+                       return true; // no change, OK
+               }
+
+               list<pair<ChanCount, ChanCount> > c = try_configure_processors_unlocked (n_inputs (), 0);
+               if (c.empty()) {
+                       /* not possible */
+                       pi->set_preset_out (old);
+                       return false;
+               }
+               configure_processors_unlocked (0, &lm);
+       }
+
+       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);
+       return customize_plugin_insert (proc, 0, unused, unused);
 }
 
 bool
-Route::customize_plugin_insert (boost::shared_ptr<Processor> proc, uint32_t count, ChanCount outs)
+Route::customize_plugin_insert (boost::shared_ptr<Processor> proc, uint32_t count, ChanCount outs, ChanCount sinks)
 {
        boost::shared_ptr<PluginInsert> pi;
        if ((pi = boost::dynamic_pointer_cast<PluginInsert>(proc)) == 0) {
@@ -2507,9 +2630,10 @@ Route::customize_plugin_insert (boost::shared_ptr<Processor> proc, uint32_t coun
                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 ();
+               bool      old_cust  = pi->custom_cfg ();
+               uint32_t  old_cnt   = pi->get_count ();
+               ChanCount old_chan  = pi->output_streams ();
+               ChanCount old_sinks = pi->natural_input_streams ();
 
                if (count == 0) {
                        pi->set_custom_cfg (false);
@@ -2517,6 +2641,7 @@ Route::customize_plugin_insert (boost::shared_ptr<Processor> proc, uint32_t coun
                        pi->set_custom_cfg (true);
                        pi->set_count (count);
                        pi->set_outputs (outs);
+                       pi->set_sinks (sinks);
                }
 
                list<pair<ChanCount, ChanCount> > c = try_configure_processors_unlocked (n_inputs (), 0);
@@ -2524,12 +2649,13 @@ Route::customize_plugin_insert (boost::shared_ptr<Processor> proc, uint32_t coun
                        /* not possible */
 
                        pi->set_count (old_cnt);
+                       pi->set_sinks (old_sinks);
                        pi->set_outputs (old_chan);
                        pi->set_custom_cfg (old_cust);
 
                        return false;
                }
-               configure_processors_unlocked (0);
+               configure_processors_unlocked (0, &lm);
        }
 
        processors_changed (RouteProcessorChange ()); /* EMIT SIGNAL */
@@ -2540,6 +2666,8 @@ Route::customize_plugin_insert (boost::shared_ptr<Processor> proc, uint32_t coun
 bool
 Route::set_strict_io (const bool enable)
 {
+       Glib::Threads::Mutex::Lock lx (AudioEngine::instance()->process_lock ());
+
        if (_strict_io != enable) {
                _strict_io = enable;
                Glib::Threads::RWLock::ReaderLock lm (_processor_lock);
@@ -2565,10 +2693,9 @@ Route::set_strict_io (const bool enable)
                }
                lm.release ();
 
-               {
-                       Glib::Threads::Mutex::Lock lx (AudioEngine::instance()->process_lock ());
-                       configure_processors (0);
-               }
+               configure_processors (0);
+               lx.release ();
+
                processors_changed (RouteProcessorChange ()); /* EMIT SIGNAL */
                _session.set_dirty ();
        }
@@ -2590,6 +2717,7 @@ Route::get_template()
 XMLNode&
 Route::state(bool full_state)
 {
+       LocaleGuard lg;
        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));
@@ -2656,26 +2784,29 @@ Route::state(bool full_state)
                node->add_child_nocopy (_pannable->state (full_state));
        }
 
-       for (i = _processors.begin(); i != _processors.end(); ++i) {
-               if (!full_state) {
-                       /* template save: do not include internal sends functioning as
-                          aux sends because the chance of the target ID
-                          in the session where this template is used
-                          is not very likely.
-
-                          similarly, do not save listen sends which connect to
-                          the monitor section, because these will always be
-                          added if necessary.
-                       */
-                       boost::shared_ptr<InternalSend> is;
+       {
+               Glib::Threads::RWLock::ReaderLock lm (_processor_lock);
+               for (i = _processors.begin(); i != _processors.end(); ++i) {
+                       if (!full_state) {
+                               /* template save: do not include internal sends functioning as
+                                        aux sends because the chance of the target ID
+                                        in the session where this template is used
+                                        is not very likely.
+
+                                        similarly, do not save listen sends which connect to
+                                        the monitor section, because these will always be
+                                        added if necessary.
+                                        */
+                               boost::shared_ptr<InternalSend> is;
 
-                       if ((is = boost::dynamic_pointer_cast<InternalSend> (*i)) != 0) {
-                               if (is->role() == Delivery::Listen) {
-                                       continue;
+                               if ((is = boost::dynamic_pointer_cast<InternalSend> (*i)) != 0) {
+                                       if (is->role() == Delivery::Listen) {
+                                               continue;
+                                       }
                                }
                        }
+                       node->add_child_nocopy((*i)->state (full_state));
                }
-               node->add_child_nocopy((*i)->state (full_state));
        }
 
        if (_extra_xml) {
@@ -2707,7 +2838,7 @@ Route::set_state (const XMLNode& node, int version)
        XMLNodeList nlist;
        XMLNodeConstIterator niter;
        XMLNode *child;
-       const XMLProperty *prop;
+       XMLProperty const * prop;
 
        if (node.name() != "Route"){
                error << string_compose(_("Bad node sent to Route::set_state() [%1]"), node.name()) << endmsg;
@@ -2830,7 +2961,6 @@ Route::set_state (const XMLNode& node, int version)
 
        if ((prop = node.property (X_("active"))) != 0) {
                bool yn = string_is_affirmative (prop->value());
-               _active = !yn; // force switch
                set_active (yn, this);
        }
 
@@ -2926,11 +3056,11 @@ Route::set_state (const XMLNode& node, int version)
 int
 Route::set_state_2X (const XMLNode& node, int version)
 {
-       LocaleGuard lg (X_("C"));
+       LocaleGuard lg;
        XMLNodeList nlist;
        XMLNodeConstIterator niter;
        XMLNode *child;
-       const XMLProperty *prop;
+       XMLProperty const * prop;
 
        /* 2X things which still remain to be handled:
         * default-type
@@ -3253,7 +3383,7 @@ Route::set_processor_state (const XMLNode& node)
                        ProcessorList::iterator o;
 
                        for (o = _processors.begin(); o != _processors.end(); ++o) {
-                               XMLProperty* id_prop = (*niter)->property(X_("id"));
+                               XMLProperty const * id_prop = (*niter)->property(X_("id"));
                                if (id_prop && (*o)->id() == id_prop->value()) {
                                        (*o)->set_state (**niter, Stateful::current_state_version);
                                        new_order.push_back (*o);
@@ -3296,6 +3426,9 @@ Route::set_processor_state (const XMLNode& node)
                                } else if (prop->value() == "send") {
 
                                        processor.reset (new Send (_session, _pannable, _mute_master, Delivery::Send, true));
+                                       boost::shared_ptr<Send> send = boost::dynamic_pointer_cast<Send> (processor);
+                                       send->SelfDestruct.connect_same_thread (*this,
+                                                       boost::bind (&Route::processor_selfdestruct, this, boost::weak_ptr<Processor> (processor)));
 
                                } else {
                                        error << string_compose(_("unknown Processor type \"%1\"; ignored"), prop->value()) << endmsg;
@@ -3337,7 +3470,7 @@ Route::set_processor_state (const XMLNode& node)
                _processors = new_order;
 
                if (must_configure) {
-                       configure_processors_unlocked (0);
+                       configure_processors_unlocked (0, &lm);
                }
 
                for (ProcessorList::const_iterator i = _processors.begin(); i != _processors.end(); ++i) {
@@ -3657,8 +3790,8 @@ Route::direct_feeds_according_to_reality (boost::shared_ptr<Route> other, bool*
                return true;
        }
 
+       Glib::Threads::RWLock::ReaderLock lm (_processor_lock);
 
-       Glib::Threads::RWLock::ReaderLock lm (_processor_lock); // XXX
        for (ProcessorList::iterator r = _processors.begin(); r != _processors.end(); ++r) {
 
                boost::shared_ptr<IOProcessor> iop = boost::dynamic_pointer_cast<IOProcessor>(*r);
@@ -3871,18 +4004,13 @@ Route::output_change_handler (IOChange change, void * /*src*/)
 }
 
 void
-Route::sidechain_change_handler (IOChange change, void * /*src*/)
+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);
-       }
+       input_change_handler (change, src);
 }
 
 uint32_t
@@ -4042,13 +4170,12 @@ Route::apply_processor_changes_rt ()
                g_atomic_int_set (&_pending_signals, emissions);
                return true;
        }
-       return false;
+       return (!selfdestruct_sequence.empty ());
 }
 
 void
 Route::emit_pending_signals ()
 {
-
        int sig = g_atomic_int_and (&_pending_signals, 0);
        if (sig & EmitMeterChanged) {
                _meter->emit_configuration_changed();
@@ -4062,6 +4189,24 @@ Route::emit_pending_signals ()
        if (sig & EmitRtProcessorChange) {
                processors_changed (RouteProcessorChange (RouteProcessorChange::RealTimeChange)); /* EMIT SIGNAL */
        }
+
+       /* this would be a job for the butler.
+        * Conceptually we should not take processe/processor locks here.
+        * OTOH its more efficient (less overhead for summoning the butler and
+        * telling her what do do) and signal emission is called
+        * directly after the process callback, which decreases the chance
+        * of x-runs when taking the locks.
+        */
+       while (!selfdestruct_sequence.empty ()) {
+               Glib::Threads::Mutex::Lock lx (selfdestruct_lock);
+               if (selfdestruct_sequence.empty ()) { break; } // re-check with lock
+               boost::shared_ptr<Processor> proc = selfdestruct_sequence.back ().lock ();
+               selfdestruct_sequence.pop_back ();
+               lx.release ();
+               if (proc) {
+                       remove_processor (proc);
+               }
+       }
 }
 
 void
@@ -4170,10 +4315,10 @@ Route::listen_position_changed ()
                Glib::Threads::RWLock::WriterLock lm (_processor_lock);
                ProcessorState pstate (this);
 
-               if (configure_processors_unlocked (0)) {
+               if (configure_processors_unlocked (0, &lm)) {
                        DEBUG_TRACE (DEBUG::Processors, "---- CONFIGURATION FAILED.\n");
                        pstate.restore ();
-                       configure_processors_unlocked (0); // it worked before we tried to add it ...
+                       configure_processors_unlocked (0, &lm); // it worked before we tried to add it ...
                        return;
                }
        }
@@ -4194,7 +4339,7 @@ Route::add_export_point()
                _capturing_processor.reset (new CapturingProcessor (_session));
                _capturing_processor->activate ();
 
-               configure_processors_unlocked (0);
+               configure_processors_unlocked (0, &lw);
 
        }
 
@@ -4456,7 +4601,7 @@ Route::set_name_in_state (XMLNode& node, string const & name, bool rename_playli
 
                } else if ((*i)->name() == X_("Processor")) {
 
-                       XMLProperty* role = (*i)->property (X_("role"));
+                       XMLProperty const * role = (*i)->property (X_("role"));
                        if (role && role->value() == X_("Main")) {
                                (*i)->add_property (X_("name"), name);
                        }
@@ -4733,6 +4878,9 @@ Route::input_port_count_changing (ChanCount to)
 bool
 Route::output_port_count_changing (ChanCount to)
 {
+       if (_strict_io && !_in_configure_processors) {
+               return true;
+       }
        for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
                if (processor_out_streams.get(*t) > to.get(*t)) {
                        return true;
@@ -4906,12 +5054,13 @@ Route::setup_invisible_processors ()
 
        /* find the amp */
 
-       ProcessorList::iterator amp = new_processors.begin ();
-       while (amp != new_processors.end() && *amp != _amp) {
-               ++amp;
-       }
+       ProcessorList::iterator amp = find (new_processors.begin(), new_processors.end(), _amp);
 
-       assert (amp != new_processors.end ());
+       if (amp == new_processors.end ()) {
+               error << string_compose (_("Amp/Fader on Route '%1' went AWOL. Re-added."), name()) << endmsg;
+               new_processors.push_front (_amp);
+               amp = find (new_processors.begin(), new_processors.end(), _amp);
+       }
 
        /* and the processor after the amp */
 
@@ -5165,11 +5314,9 @@ boost::shared_ptr<Processor>
 Route::the_instrument_unlocked () const
 {
        for (ProcessorList::const_iterator i = _processors.begin(); i != _processors.end(); ++i) {
-               if (boost::dynamic_pointer_cast<PluginInsert>(*i)) {
-                       if ((*i)->input_streams().n_midi() > 0 &&
-                           (*i)->output_streams().n_audio() > 0) {
-                               return (*i);
-                       }
+               boost::shared_ptr<PluginInsert> pi = boost::dynamic_pointer_cast<PluginInsert>(*i);
+               if (pi && pi->plugin ()->get_info ()->is_instrument ()) {
+                       return (*i);
                }
        }
        return boost::shared_ptr<Processor>();
@@ -5737,3 +5884,13 @@ Route::master_send_enable_controllable () const
        return boost::shared_ptr<AutomationControl>();
 #endif
 }
+
+bool
+Route::slaved_to (boost::shared_ptr<VCA> vca) const
+{
+       if (!vca || !_gain_control) {
+               return false;
+       }
+
+       return _gain_control->slaved_to (vca->control());
+}