X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=libs%2Fardour%2Froute.cc;h=569f00fbc770c020e8d2798f0994dbfd3695491f;hb=1c0c9b40b73180537da7630b6a219baf85886da6;hp=65f4531ac88e7cd158c2538b322ffc5380eb2d5c;hpb=f5e71fd817248c1c1123590951fb9837c3c4f7eb;p=ardour.git diff --git a/libs/ardour/route.cc b/libs/ardour/route.cc index 65f4531ac8..569f00fbc7 100644 --- a/libs/ardour/route.cc +++ b/libs/ardour/route.cc @@ -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,11 +40,13 @@ #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" #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" @@ -67,6 +69,7 @@ #include "ardour/session.h" #include "ardour/unknown_processor.h" #include "ardour/utils.h" +#include "ardour/vca.h" #include "i18n.h" @@ -76,9 +79,11 @@ using namespace PBD; PBD::Signal0 Route::SyncOrderKeys; PBD::Signal0 Route::RemoteControlIDChange; +PBD::Signal3, boost::shared_ptr, 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) - : SessionObject (sess, name) + : Stripable (sess, name) , Automatable (sess) , GraphNode (sess._process_graph) , _active (true) @@ -113,7 +118,10 @@ Route::Route (Session& sess, string name, Flag flg, DataType default_type) , _track_number (0) , _in_configure_processors (false) , _initial_io_setup (false) + , _in_sidechain_setup (false) + , _strict_io (false) , _custom_meter_position_noted (false) + , _pinmgr_proxy (0) { processor_max_streams.reset(); } @@ -137,6 +145,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)); @@ -162,6 +173,20 @@ 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)); + /* add the amp/fader processor. + * it should be the first processor to be added on every route. + */ + + _gain_control = boost::shared_ptr (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")); + } + #if 0 // not used - just yet if (!is_master() && !is_monitor() && !is_auditioner()) { _delayline.reset (new DelayLine (_session, _name)); @@ -169,13 +194,12 @@ Route::init () } #endif - /* add amp processor */ + /* and input trim */ - _amp.reset (new Amp (_session)); - add_processor (_amp, PostFader); + _trim_control = boost::shared_ptr (new GainControllable (_session, TrimAutomation, shared_from_this ())); + add_control (_trim_control); - /* and input trim */ - _trim.reset (new Amp (_session, "trim")); + _trim.reset (new Amp (_session, X_("Trim"), _trim_control, false)); _trim->set_display_to_user (false); if (dynamic_cast(this)) { @@ -375,19 +399,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 +453,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); + _gain_control->route_set_value (val); } void -Route::inc_trim (gain_t fraction, void *src) -{ - _trim->inc_gain (fraction, src); -} - -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 +671,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(*i)) { break; } @@ -650,8 +679,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(*i)) { (*i)->run (buffers, start - latency, start - latency + nframes, nframes, true); @@ -767,19 +808,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 +830,8 @@ Route::set_listen (bool yn, void* src, bool group_override) } _mute_master->set_soloed_by_others (false); - listen_changed (src, group_override); /* EMIT SIGNAL */ + _session.listen_changed (group_override, shared_from_this()); + _solo_control->Changed(); /* EMIT SIGNAL */ } } } @@ -810,11 +847,11 @@ 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_control->Changed(); /* EMIT SIGNAL */ } } @@ -851,18 +888,22 @@ Route::clear_all_solo_state () { PBD::Unwinder 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 */ + _session.solo_changed (false, Controllable::UseGroup, shared_from_this()); + _solo_control->Changed (); /* 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 +914,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 */ + _session.solo_changed (true, group_override, shared_from_this()); _solo_control->Changed (); /* EMIT SIGNAL */ } @@ -899,7 +932,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 +941,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 +999,8 @@ Route::mod_solo_by_others_upstream (int32_t delta) } set_mute_master_solo (); - solo_changed (false, this, false); /* EMIT SIGNAL */ + _session.solo_changed (false, Controllable::UseGroup, shared_from_this()); + _solo_control->Changed (); /* EMIT SIGNAL */ } void @@ -987,7 +1022,8 @@ 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 */ + _session.solo_changed (false, Controllable::UseGroup, shared_from_this()); + _solo_control->Changed (); /* EMIT SIGNAL */ } void @@ -998,7 +1034,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 +1053,20 @@ 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 */ + _session.solo_isolated_changed (shared_from_this()); + _solo_isolate_control->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 +1104,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 */ + _session.solo_isolated_changed (shared_from_this()); + _solo_isolate_control->Changed(); /* EMIT SIGNAL */ } bool @@ -1089,16 +1127,16 @@ Route::set_mute_points (MuteMaster::MutePoint mp) mute_points_changed (); /* EMIT SIGNAL */ if (_mute_master->muted_by_self()) { - mute_changed (this); /* EMIT SIGNAL */ + _session.mute_changed (); _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 +1147,7 @@ Route::set_mute (bool yn, void *src) */ act_on_mute (); /* tell everyone else */ - mute_changed (src); /* EMIT SIGNAL */ + _session.mute_changed (); _mute_control->Changed (); /* EMIT SIGNAL */ } } @@ -1227,89 +1265,37 @@ Route::add_processor (boost::shared_ptr 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; - } - - { - Glib::Threads::Mutex::Lock lx (AudioEngine::instance()->process_lock ()); - Glib::Threads::RWLock::WriterLock lm (_processor_lock); - ProcessorState pstate (this); - - boost::shared_ptr pi; - boost::shared_ptr 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(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 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; @@ -1347,6 +1333,7 @@ Route::add_processor_from_xml_2X (const XMLNode& node, int version) processor.reset (new UnknownProcessor (_session, node)); } else { processor.reset (new PluginInsert (_session)); + processor->set_owner (this); } } else { @@ -1390,24 +1377,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 (static_cast (a) | static_cast (b)); +} + +inline Route::PluginSetupOptions operator&= (Route::PluginSetupOptions& a, const Route::PluginSetupOptions& b) { + return a = static_cast (static_cast (a) & static_cast (b)); +} + int Route::add_processors (const ProcessorList& others, boost::shared_ptr 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; } @@ -1415,6 +1409,59 @@ Route::add_processors (const ProcessorList& others, boost::shared_ptr 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 pi; + if ((pi = boost::dynamic_pointer_cast(*i)) == 0) { + continue; + } + if (!pi->plugin ()->get_info ()->is_instrument ()) { + continue; + } + boost::shared_ptr 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 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); @@ -1425,30 +1472,54 @@ Route::add_processors (const ProcessorList& others, boost::shared_ptr if (*i == _meter) { continue; } + ProcessorList::iterator check = find (to_skip.begin(), to_skip.end(), *i); + if (check != to_skip.end()) { + continue; + } boost::shared_ptr pi; if ((pi = boost::dynamic_pointer_cast(*i)) != 0) { - pi->set_count (1); + 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; + if ((send = boost::dynamic_pointer_cast (*i))) { + send->SelfDestruct.connect_same_thread (*this, + boost::bind (&Route::processor_selfdestruct, this, boost::weak_ptr (*i))); + } } for (ProcessorList::const_iterator i = _processors.begin(); i != _processors.end(); ++i) { @@ -1661,7 +1732,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(); @@ -1733,9 +1804,15 @@ Route::remove_processor (boost::shared_ptr processor, ProcessorStream run. */ - boost::shared_ptr iop; + boost::shared_ptr iop = boost::dynamic_pointer_cast (*i); + boost::shared_ptr pi = boost::dynamic_pointer_cast(*i); + + if (pi != 0) { + assert (iop == 0); + iop = pi->sidechain(); + } - if ((iop = boost::dynamic_pointer_cast (*i)) != 0) { + if (iop != 0) { iop->disconnect (); } @@ -1755,10 +1832,10 @@ Route::remove_processor (boost::shared_ptr 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; } @@ -1787,6 +1864,104 @@ Route::remove_processor (boost::shared_ptr processor, ProcessorStream return 0; } +int +Route::replace_processor (boost::shared_ptr old, boost::shared_ptr 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 (old) || boost::dynamic_pointer_cast (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 pi; + if ((pi = boost::dynamic_pointer_cast(sub)) != 0) { + pi->set_strict_io (true); + } + } + + if (configure_processors_unlocked (err, &lm)) { + pstate.restore (); + configure_processors_unlocked (0, &lm); + return -1; + } + + _have_internal_generator = false; + + for (i = _processors.begin(); i != _processors.end(); ++i) { + boost::shared_ptr pi; + if ((pi = boost::dynamic_pointer_cast(*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) { @@ -1829,9 +2004,14 @@ Route::remove_processors (const ProcessorList& to_be_deleted, ProcessorStreams* run. */ - boost::shared_ptr iop; + boost::shared_ptr iop = boost::dynamic_pointer_cast(processor); + boost::shared_ptr pi = boost::dynamic_pointer_cast(processor); + if (pi != 0) { + assert (iop == 0); + iop = pi->sidechain(); + } - if ((iop = boost::dynamic_pointer_cast (processor)) != 0) { + if (iop != 0) { iop->disconnect (); } @@ -1846,10 +2026,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(); @@ -1900,7 +2080,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; @@ -1934,8 +2114,65 @@ 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 (*p) + && boost::dynamic_pointer_cast (*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)); + + 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 > (); + } + if (boost::dynamic_pointer_cast (*p)) { + // internal sends make no sense, only feedback + DEBUG_TRACE (DEBUG::Processors, "Monitor: No Sends allowed.\n"); + return list > (); + } + if (boost::dynamic_pointer_cast (*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 > (); + } + if (boost::dynamic_pointer_cast (*p)) { + // ditto + DEBUG_TRACE (DEBUG::Processors, "Monitor: No Sends allowed.\n"); + return list > (); + } + } in = out; } else { if (err) { @@ -1959,7 +2196,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()); @@ -1986,21 +2223,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 >::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 iop; boost::shared_ptr pi; if ((pi = boost::dynamic_pointer_cast(*p)) != 0) { - /* plugins connected via Split Match may have more channels. - * route/scratch buffers are needed for all of them*/ - processor_max_streams = ChanCount::max(processor_max_streams, pi->input_streams()); - processor_max_streams = ChanCount::max(processor_max_streams, pi->natural_input_streams()); + /* 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->required_buffers()); + } + else if ((iop = boost::dynamic_pointer_cast(*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; @@ -2017,6 +2287,9 @@ Route::configure_processors_unlocked (ProcessorStreams* err) } } + lr.release (); + lm->acquire (); + if (_meter) { _meter->set_max_channels (processor_max_streams); @@ -2210,7 +2483,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; } @@ -2233,35 +2506,242 @@ Route::reorder_processors (const ProcessorList& new_order, ProcessorStreams* err return 0; } -XMLNode& -Route::get_state() -{ - return state(true); -} - -XMLNode& -Route::get_template() -{ - return state(false); -} - -XMLNode& -Route::state(bool full_state) +bool +Route::add_remove_sidechain (boost::shared_ptr proc, bool add) { - XMLNode *node = new XMLNode("Route"); - ProcessorList::iterator i; - char buf[32]; + boost::shared_ptr pi; + if ((pi = boost::dynamic_pointer_cast(proc)) == 0) { + return false; + } - id().print (buf, sizeof (buf)); - node->add_property("id", buf); - node->add_property ("name", _name); - node->add_property("default-type", _default_type.to_string()); + if (pi->has_sidechain () == add) { + return true; // ?? call failed, but result is as expected. + } - if (_flags) { - node->add_property("flags", enum_2_string (_flags)); + { + Glib::Threads::RWLock::ReaderLock lm (_processor_lock); + ProcessorList::iterator i = find (_processors.begin(), _processors.end(), proc); + if (i == _processors.end ()) { + return false; + } } - node->add_property("active", _active?"yes":"no"); + { + Glib::Threads::Mutex::Lock lx (AudioEngine::instance()->process_lock ()); // take before Writerlock to avoid deadlock + Glib::Threads::RWLock::WriterLock lm (_processor_lock); + PBD::Unwinder uw (_in_sidechain_setup, true); + + lx.release (); // IO::add_port() and ~IO takes process lock - XXX check if this is safe + if (add) { + if (!pi->add_sidechain ()) { + return false; + } + } else { + if (!pi->del_sidechain ()) { + return false; + } + } + + lx.acquire (); + list > c = try_configure_processors_unlocked (n_inputs (), 0); + lx.release (); + + if (c.empty()) { + if (add) { + pi->del_sidechain (); + } else { + pi->add_sidechain (); + // TODO restore side-chain's state. + } + return false; + } + lx.acquire (); + configure_processors_unlocked (0, &lm); + } + + if (pi->has_sidechain ()) { + pi->sidechain_input ()->changed.connect_same_thread (*this, boost::bind (&Route::sidechain_change_handler, this, _1, _2)); + } + + processors_changed (RouteProcessorChange ()); /* EMIT SIGNAL */ + _session.set_dirty (); + return true; +} + +bool +Route::plugin_preset_output (boost::shared_ptr proc, ChanCount outs) +{ + boost::shared_ptr pi; + if ((pi = boost::dynamic_pointer_cast(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 > 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 proc) +{ + ChanCount unused; + return customize_plugin_insert (proc, 0, unused, unused); +} + +bool +Route::customize_plugin_insert (boost::shared_ptr proc, uint32_t count, ChanCount outs, ChanCount sinks) +{ + boost::shared_ptr pi; + if ((pi = boost::dynamic_pointer_cast(proc)) == 0) { + return false; + } + + { + Glib::Threads::RWLock::ReaderLock lm (_processor_lock); + ProcessorList::iterator i = find (_processors.begin(), _processors.end(), proc); + if (i == _processors.end ()) { + return false; + } + } + + { + Glib::Threads::Mutex::Lock lx (AudioEngine::instance()->process_lock ()); + Glib::Threads::RWLock::WriterLock lm (_processor_lock); + + bool old_cust = pi->custom_cfg (); + uint32_t old_cnt = pi->get_count (); + ChanCount old_chan = pi->output_streams (); + ChanCount old_sinks = pi->natural_input_streams (); + + if (count == 0) { + pi->set_custom_cfg (false); + } else { + pi->set_custom_cfg (true); + pi->set_count (count); + pi->set_outputs (outs); + pi->set_sinks (sinks); + } + + list > c = try_configure_processors_unlocked (n_inputs (), 0); + if (c.empty()) { + /* 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, &lm); + } + + processors_changed (RouteProcessorChange ()); /* EMIT SIGNAL */ + _session.set_dirty (); + return true; +} + +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); + for (ProcessorList::iterator p = _processors.begin(); p != _processors.end(); ++p) { + boost::shared_ptr pi; + if ((pi = boost::dynamic_pointer_cast(*p)) != 0) { + pi->set_strict_io (_strict_io); + } + } + + list > 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 pi; + if ((pi = boost::dynamic_pointer_cast(*p)) != 0) { + pi->set_strict_io (_strict_io); + } + } + return false; + } + lm.release (); + + configure_processors (0); + lx.release (); + + processors_changed (RouteProcessorChange ()); /* EMIT SIGNAL */ + _session.set_dirty (); + } + return true; +} + +XMLNode& +Route::get_state() +{ + return state(true); +} + +XMLNode& +Route::get_template() +{ + return state(false); +} + +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)); + } + + XMLNode *node = new XMLNode("Route"); + ProcessorList::iterator i; + char buf[32]; + + id().print (buf, sizeof (buf)); + 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)); + } + + node->add_property("active", _active?"yes":"no"); string p; boost::to_string (_phase_invert, p); node->add_property("phase-invert", p); @@ -2308,26 +2788,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 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 is; - if ((is = boost::dynamic_pointer_cast (*i)) != 0) { - if (is->role() == Delivery::Listen) { - continue; + if ((is = boost::dynamic_pointer_cast (*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) { @@ -2342,6 +2825,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; } @@ -2355,7 +2842,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; @@ -2375,6 +2862,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 +2948,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) { @@ -2474,7 +2965,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); } @@ -2570,11 +3060,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 @@ -2616,7 +3106,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 +3245,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); } } @@ -2897,7 +3387,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); @@ -2918,13 +3408,20 @@ Route::set_processor_state (const XMLNode& node) } else if (prop->value() == "ladspa" || prop->value() == "Ladspa" || prop->value() == "lv2" || prop->value() == "windows-vst" || - prop->value() == "lxvst" || + prop->value() == "lxvst" || + prop->value() == "luaproc" || prop->value() == "audiounit") { if (_session.get_disable_all_loaded_plugins ()) { processor.reset (new UnknownProcessor (_session, **niter)); } else { processor.reset (new PluginInsert (_session)); + processor->set_owner (this); + if (_strict_io) { + boost::shared_ptr pi = boost::dynamic_pointer_cast(processor); + pi->set_strict_io (true); + } + } } else if (prop->value() == "port") { @@ -2933,6 +3430,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 = boost::dynamic_pointer_cast (processor); + send->SelfDestruct.connect_same_thread (*this, + boost::bind (&Route::processor_selfdestruct, this, boost::weak_ptr (processor))); } else { error << string_compose(_("unknown Processor type \"%1\"; ignored"), prop->value()) << endmsg; @@ -2944,6 +3444,12 @@ Route::set_processor_state (const XMLNode& node) processor.reset (new UnknownProcessor (_session, **niter)); } + /* subscribe to Sidechain IO changes */ + boost::shared_ptr pi = boost::dynamic_pointer_cast (processor); + if (pi && pi->has_sidechain ()) { + pi->sidechain_input ()->changed.connect_same_thread (*this, boost::bind (&Route::sidechain_change_handler, this, _1, _2)); + } + /* we have to note the monitor send here, otherwise a new one will be created and the state of this one will be lost. */ @@ -2968,7 +3474,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) { @@ -3183,7 +3689,7 @@ void Route::set_comment (string cmt, void *src) { _comment = cmt; - comment_changed (src); + comment_changed (); _session.set_dirty (); } @@ -3233,12 +3739,53 @@ Route::feeds (boost::shared_ptr other, bool* via_sends_only) return false; } +IOVector +Route::all_inputs () const +{ + /* TODO, if this works as expected, + * cache the IOVector and maintain it via + * input_change_handler(), sidechain_change_handler() etc + */ + IOVector ios; + ios.push_back (_input); + + Glib::Threads::RWLock::ReaderLock lm (_processor_lock); + for (ProcessorList::const_iterator r = _processors.begin(); r != _processors.end(); ++r) { + + boost::shared_ptr iop = boost::dynamic_pointer_cast(*r); + boost::shared_ptr pi = boost::dynamic_pointer_cast(*r); + if (pi != 0) { + assert (iop == 0); + iop = pi->sidechain(); + } + + if (iop != 0 && iop->input()) { + ios.push_back (iop->input()); + } + } + return ios; +} + +IOVector +Route::all_outputs () const +{ + IOVector ios; + // _output is included via Delivery + Glib::Threads::RWLock::ReaderLock lm (_processor_lock); + for (ProcessorList::const_iterator r = _processors.begin(); r != _processors.end(); ++r) { + boost::shared_ptr iop = boost::dynamic_pointer_cast(*r); + if (iop != 0 && iop->output()) { + ios.push_back (iop->output()); + } + } + return ios; +} + bool Route::direct_feeds_according_to_reality (boost::shared_ptr other, bool* via_send_only) { DEBUG_TRACE (DEBUG::Graph, string_compose ("Feeds? %1\n", _name)); - - if (_output->connected_to (other->input())) { + if (other->all_inputs().fed_by (_output)) { DEBUG_TRACE (DEBUG::Graph, string_compose ("\tdirect FEEDS %2\n", other->name())); if (via_send_only) { *via_send_only = false; @@ -3247,13 +3794,20 @@ Route::direct_feeds_according_to_reality (boost::shared_ptr other, bool* return true; } + Glib::Threads::RWLock::ReaderLock lm (_processor_lock); for (ProcessorList::iterator r = _processors.begin(); r != _processors.end(); ++r) { - boost::shared_ptr iop; + boost::shared_ptr iop = boost::dynamic_pointer_cast(*r); + boost::shared_ptr pi = boost::dynamic_pointer_cast(*r); + if (pi != 0) { + assert (iop == 0); + iop = pi->sidechain(); + } - if ((iop = boost::dynamic_pointer_cast(*r)) != 0) { - if (iop->feeds (other)) { + if (iop != 0) { + boost::shared_ptr iop_out = iop->output(); + if ((iop_out && other->all_inputs().fed_by (iop_out)) || iop->feeds (other)) { DEBUG_TRACE (DEBUG::Graph, string_compose ("\tIOP %1 does feed %2\n", iop->name(), other->name())); if (via_send_only) { *via_send_only = true; @@ -3278,6 +3832,12 @@ Route::direct_feeds_according_to_graph (boost::shared_ptr other, bool* vi return _session._current_route_graph.has (shared_from_this (), other, via_send_only); } +bool +Route::feeds_according_to_graph (boost::shared_ptr other) +{ + return _session._current_route_graph.feeds (shared_from_this (), other); +} + /** Called from the (non-realtime) butler thread when the transport is stopped */ void Route::nonrealtime_handle_transport_stopped (bool /*abort_ignored*/, bool /*did_locate*/, bool can_flush_processors) @@ -3357,7 +3917,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 +3937,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); } } } @@ -3447,6 +4007,16 @@ Route::output_change_handler (IOChange change, void * /*src*/) } } +void +Route::sidechain_change_handler (IOChange change, void* src) +{ + if (_initial_io_setup || _in_sidechain_setup) { + return; + } + + input_change_handler (change, src); +} + uint32_t Route::pans_required () const { @@ -3604,13 +4174,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(); @@ -3624,6 +4193,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 proc = selfdestruct_sequence.back ().lock (); + selfdestruct_sequence.pop_back (); + lx.release (); + if (proc) { + remove_processor (proc); + } + } } void @@ -3732,10 +4319,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; } } @@ -3756,7 +4343,7 @@ Route::add_export_point() _capturing_processor.reset (new CapturingProcessor (_session)); _capturing_processor->activate (); - configure_processors_unlocked (0); + configure_processors_unlocked (0, &lw); } @@ -3836,188 +4423,6 @@ Route::set_latency_compensation (framecnt_t longest_session_latency) } } -Route::SoloControllable::SoloControllable (std::string name, boost::shared_ptr r) - : AutomationControl (r->session(), - Evoral::Parameter (SoloAutomation), - ParameterDescriptor(Evoral::Parameter (SoloAutomation)), - boost::shared_ptr(), name) - , _route (r) -{ - boost::shared_ptr 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 rl (new RouteList); - - boost::shared_ptr 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 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 r) - : AutomationControl (r->session(), - Evoral::Parameter (MuteAutomation), - ParameterDescriptor (Evoral::Parameter (MuteAutomation)), - boost::shared_ptr(), - name) - , _route (r) -{ - boost::shared_ptr 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 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 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 r = _route.lock (); - return (r && r->muted()) ? GAIN_COEFF_UNITY : GAIN_COEFF_ZERO; -} - -Route::PhaseControllable::PhaseControllable (std::string name, boost::shared_ptr r) - : AutomationControl (r->session(), - Evoral::Parameter (PhaseAutomation), - ParameterDescriptor (Evoral::Parameter (PhaseAutomation)), - boost::shared_ptr(), - name) - , _route (r) -{ - boost::shared_ptr 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 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 r = _route.lock (); - return (double) r->phase_invert (_current_phase); -} - -void -Route::PhaseControllable::set_channel (uint32_t c) -{ - _current_phase = c; -} - -uint32_t -Route::PhaseControllable::channel () const -{ - return _current_phase; -} - void Route::set_block_size (pframes_t nframes) { @@ -4120,16 +4525,31 @@ Route::shift (framepos_t pos, framecnt_t frames) } } +void +Route::set_plugin_state_dir (boost::weak_ptr p, const std::string& d) +{ + boost::shared_ptr processor (p.lock ()); + boost::shared_ptr pi = boost::dynamic_pointer_cast (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 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 zero on success, non-zero otherwise */ return !tree.write (path.c_str()); } @@ -4172,7 +4592,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); @@ -4185,14 +4605,16 @@ Route::set_name_in_state (XMLNode& node, string const & name) } 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); } } 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,7 +4647,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 (); } } @@ -4235,7 +4657,7 @@ Route::set_phase_invert (boost::dynamic_bitset<> p) { if (_phase_invert != p) { _phase_invert = p; - phase_invert_changed (); /* EMIT SIGNAL */ + _phase_control->Changed (); /* EMIT SIGNAL */ _session.set_dirty (); } } @@ -4307,16 +4729,26 @@ Route::panner_shell() const return _main_outs->panner_shell(); } -boost::shared_ptr +boost::shared_ptr Route::gain_control() const { - return _amp->gain_control(); + return _gain_control; } boost::shared_ptr Route::trim_control() const { - return _trim->gain_control(); + return _trim_control; +} + +boost::shared_ptr +Route::phase_control() const +{ + if (phase_invert().size()) { + return _phase_control; + } else { + return boost::shared_ptr(); + } } boost::shared_ptr @@ -4350,10 +4782,10 @@ Route::get_control (const Evoral::Parameter& param) } boost::shared_ptr -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 (*i)) { @@ -4367,13 +4799,21 @@ Route::nth_plugin (uint32_t n) } boost::shared_ptr -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 (*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; } @@ -4441,6 +4881,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; @@ -4614,12 +5057,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 */ @@ -4716,7 +5160,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 */ @@ -4873,11 +5317,9 @@ boost::shared_ptr Route::the_instrument_unlocked () const { for (ProcessorList::const_iterator i = _processors.begin(); i != _processors.end(); ++i) { - if (boost::dynamic_pointer_cast(*i)) { - if ((*i)->input_streams().n_midi() > 0 && - (*i)->output_streams().n_audio() > 0) { - return (*i); - } + boost::shared_ptr pi = boost::dynamic_pointer_cast(*i); + if (pi && pi->plugin ()->get_info ()->is_instrument ()) { + return (*i); } } return boost::shared_ptr(); @@ -4996,3 +5438,486 @@ Route::fill_buffers_with_input (BufferSet& bufs, boost::shared_ptr io, pfram bufs.set_count (io->n_ports()); } } + +boost::shared_ptr +Route::pan_azimuth_control() const +{ +#ifdef MIXBUS + boost::shared_ptr plug = ch_post(); + if (!plug) { + return boost::shared_ptr(); + } + const uint32_t port_channel_post_pan = 2; // gtk2_ardour/mixbus_ports.h + return boost::dynamic_pointer_cast (plug->control (Evoral::Parameter (ARDOUR::PluginAutomation, 0, port_channel_post_pan))); +#else + if (!_pannable || !panner()) { + return boost::shared_ptr(); + } + return _pannable->pan_azimuth_control; +#endif +} + +boost::shared_ptr +Route::pan_elevation_control() const +{ + if (Profile->get_mixbus() || !_pannable || !panner()) { + return boost::shared_ptr(); + } + + set c = panner()->what_can_be_automated (); + + if (c.find (PanElevationAutomation) != c.end()) { + return _pannable->pan_elevation_control; + } else { + return boost::shared_ptr(); + } +} +boost::shared_ptr +Route::pan_width_control() const +{ + if (Profile->get_mixbus() || !_pannable || !panner()) { + return boost::shared_ptr(); + } + + set c = panner()->what_can_be_automated (); + + if (c.find (PanWidthAutomation) != c.end()) { + return _pannable->pan_width_control; + } else { + return boost::shared_ptr(); + } +} +boost::shared_ptr +Route::pan_frontback_control() const +{ + if (Profile->get_mixbus() || !_pannable || !panner()) { + return boost::shared_ptr(); + } + + set c = panner()->what_can_be_automated (); + + if (c.find (PanFrontBackAutomation) != c.end()) { + return _pannable->pan_frontback_control; + } else { + return boost::shared_ptr(); + } +} +boost::shared_ptr +Route::pan_lfe_control() const +{ + if (Profile->get_mixbus() || !_pannable || !panner()) { + return boost::shared_ptr(); + } + + set c = panner()->what_can_be_automated (); + + if (c.find (PanLFEAutomation) != c.end()) { + return _pannable->pan_lfe_control; + } else { + return boost::shared_ptr(); + } +} + +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 +Route::eq_gain_controllable (uint32_t band) const +{ +#ifdef MIXBUS + boost::shared_ptr eq = ch_eq(); + + if (!eq) { + return boost::shared_ptr(); + } + + 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(); + } + + return boost::dynamic_pointer_cast (eq->control (Evoral::Parameter (ARDOUR::PluginAutomation, 0, port_number))); +#else + return boost::shared_ptr(); +#endif +} +boost::shared_ptr +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(); + } + + boost::shared_ptr eq = ch_eq(); + + if (!eq) { + return boost::shared_ptr(); + } + + 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(); + } + + return boost::dynamic_pointer_cast (eq->control (Evoral::Parameter (ARDOUR::PluginAutomation, 0, port_number))); +#else + return boost::shared_ptr(); +#endif +} + +boost::shared_ptr +Route::eq_q_controllable (uint32_t band) const +{ + return boost::shared_ptr(); +} + +boost::shared_ptr +Route::eq_shape_controllable (uint32_t band) const +{ + return boost::shared_ptr(); +} + +boost::shared_ptr +Route::eq_enable_controllable () const +{ +#ifdef MIXBUS + boost::shared_ptr eq = ch_eq(); + + if (!eq) { + return boost::shared_ptr(); + } + + return boost::dynamic_pointer_cast (eq->control (Evoral::Parameter (ARDOUR::PluginAutomation, 0, 1))); +#else + return boost::shared_ptr(); +#endif +} + +boost::shared_ptr +Route::eq_hpf_controllable () const +{ +#ifdef MIXBUS + boost::shared_ptr eq = ch_eq(); + + if (!eq) { + return boost::shared_ptr(); + } + + return boost::dynamic_pointer_cast (eq->control (Evoral::Parameter (ARDOUR::PluginAutomation, 0, 2))); +#else + return boost::shared_ptr(); +#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 +Route::comp_enable_controllable () const +{ +#ifdef MIXBUS + boost::shared_ptr comp = ch_comp(); + + if (!comp) { + return boost::shared_ptr(); + } + + return boost::dynamic_pointer_cast (comp->control (Evoral::Parameter (ARDOUR::PluginAutomation, 0, 1))); +#else + return boost::shared_ptr(); +#endif +} +boost::shared_ptr +Route::comp_threshold_controllable () const +{ +#ifdef MIXBUS + boost::shared_ptr comp = ch_comp(); + + if (!comp) { + return boost::shared_ptr(); + } + + return boost::dynamic_pointer_cast (comp->control (Evoral::Parameter (ARDOUR::PluginAutomation, 0, 2))); + +#else + return boost::shared_ptr(); +#endif +} +boost::shared_ptr +Route::comp_speed_controllable () const +{ +#ifdef MIXBUS + boost::shared_ptr comp = ch_comp(); + + if (!comp) { + return boost::shared_ptr(); + } + + return boost::dynamic_pointer_cast (comp->control (Evoral::Parameter (ARDOUR::PluginAutomation, 0, 3))); +#else + return boost::shared_ptr(); +#endif +} +boost::shared_ptr +Route::comp_mode_controllable () const +{ +#ifdef MIXBUS + boost::shared_ptr comp = ch_comp(); + + if (!comp) { + return boost::shared_ptr(); + } + + return boost::dynamic_pointer_cast (comp->control (Evoral::Parameter (ARDOUR::PluginAutomation, 0, 4))); +#else + return boost::shared_ptr(); +#endif +} +boost::shared_ptr +Route::comp_makeup_controllable () const +{ +#ifdef MIXBUS + boost::shared_ptr comp = ch_comp(); + + if (!comp) { + return boost::shared_ptr(); + } + + return boost::dynamic_pointer_cast (comp->control (Evoral::Parameter (ARDOUR::PluginAutomation, 0, 5))); +#else + return boost::shared_ptr(); +#endif +} +boost::shared_ptr +Route::comp_redux_controllable () const +{ +#ifdef MIXBUS + boost::shared_ptr comp = ch_comp(); + + if (!comp) { + return boost::shared_ptr(); + } + + return boost::dynamic_pointer_cast (comp->control (Evoral::Parameter (ARDOUR::PluginAutomation, 0, 6))); +#else + return boost::shared_ptr(); +#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 +Route::send_level_controllable (uint32_t n) const +{ +#ifdef MIXBUS + boost::shared_ptr plug = ch_post(); + if (!plug) { + return boost::shared_ptr(); + } + + if (n >= 8) { + /* no such bus */ + return boost::shared_ptr(); + } + + const uint32_t port_id = port_channel_post_aux1_level + (2*n); // gtk2_ardour/mixbus_ports.h + return boost::dynamic_pointer_cast (plug->control (Evoral::Parameter (ARDOUR::PluginAutomation, 0, port_id))); +#else + boost::shared_ptr s = boost::dynamic_pointer_cast(nth_send (n)); + if (!s) { + return boost::shared_ptr(); + } + return s->gain_control (); +#endif +} + +boost::shared_ptr +Route::send_enable_controllable (uint32_t n) const +{ +#ifdef MIXBUS + boost::shared_ptr plug = ch_post(); + if (!plug) { + return boost::shared_ptr(); + } + + if (n >= 8) { + /* no such bus */ + return boost::shared_ptr(); + } + + const uint32_t port_id = port_channel_post_aux1_asgn + (2*n); // gtk2_ardour/mixbus_ports.h + return boost::dynamic_pointer_cast (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(); +#endif +} + +string +Route::send_name (uint32_t n) const +{ +#ifdef MIXBUS + if (n >= 8) { + return string(); + } + boost::shared_ptr r = _session.get_mixbus (n); + assert (r); + return r->name(); +#else + boost::shared_ptr p = nth_send (n); + if (p) { + return p->name(); + } else { + return string(); + } +#endif +} + +boost::shared_ptr +Route::master_send_enable_controllable () const +{ +#ifdef MIXBUS + boost::shared_ptr plug = ch_post(); + if (!plug) { + return boost::shared_ptr(); + } + return boost::dynamic_pointer_cast (plug->control (Evoral::Parameter (ARDOUR::PluginAutomation, 0, port_channel_post_mstr_assign))); +#else + return boost::shared_ptr(); +#endif +} + +bool +Route::slaved_to (boost::shared_ptr vca) const +{ + if (!vca || !_gain_control) { + return false; + } + + return _gain_control->slaved_to (vca->gain_control()); +} + +void +Route::vca_assign (boost::shared_ptr vca) +{ + _gain_control->add_master (vca->gain_control()); + _solo_control->add_master (vca->solo_control()); + _mute_control->add_master (vca->mute_control()); +} + +void +Route::vca_unassign (boost::shared_ptr vca) +{ + if (!vca) { + /* unassign from all */ + _gain_control->clear_masters (); + _solo_control->clear_masters (); + _mute_control->clear_masters (); + } else { + _gain_control->remove_master (vca->gain_control()); + _solo_control->remove_master (vca->solo_control()); + _mute_control->remove_master (vca->mute_control()); + + } +}