X-Git-Url: https://main.carlh.net/gitweb/?p=ardour.git;a=blobdiff_plain;f=libs%2Fardour%2Froute.cc;h=11db73241cbd304c629a556828f02611134678c9;hp=74b3a87fcf96985adacca1b9981f8236f1cac14e;hb=c8c6bca6587450ff64303dbc994a4cd28d6ce7aa;hpb=420cc9b4478059cfeabd8da5b2a25595a1de29fb diff --git a/libs/ardour/route.cc b/libs/ardour/route.cc index 74b3a87fcf..11db73241c 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,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" @@ -58,6 +58,7 @@ #include "ardour/panner.h" #include "ardour/panner_shell.h" #include "ardour/parameter_descriptor.h" +#include "ardour/phase_control.h" #include "ardour/plugin_insert.h" #include "ardour/port.h" #include "ardour/port_insert.h" @@ -67,22 +68,26 @@ #include "ardour/route_group.h" #include "ardour/send.h" #include "ardour/session.h" +#include "ardour/solo_control.h" +#include "ardour/solo_isolate_control.h" #include "ardour/unknown_processor.h" #include "ardour/utils.h" +#include "ardour/vca.h" -#include "i18n.h" +#include "pbd/i18n.h" using namespace std; using namespace ARDOUR; using namespace PBD; -PBD::Signal0 Route::SyncOrderKeys; -PBD::Signal0 Route::RemoteControlIDChange; +PBD::Signal3, boost::shared_ptr, Route::PluginSetupOptions > Route::PluginSetup; -Route::Route (Session& sess, string name, Flag flg, DataType default_type) - : SessionObject (sess, name) - , Automatable (sess) +/** Base class for all routable/mixable objects (tracks and busses) */ +Route::Route (Session& sess, string name, PresentationInfo::Flag flag, DataType default_type) + : Stripable (sess, name, PresentationInfo (flag)) , GraphNode (sess._process_graph) + , Muteable (sess, name) + , Automatable (sess) , _active (true) , _signal_latency (0) , _signal_latency_at_amp_position (0) @@ -91,31 +96,23 @@ Route::Route (Session& sess, string name, Flag flg, DataType default_type) , _roll_delay (0) , _pending_process_reorder (0) , _pending_signals (0) - , _flags (flg) , _pending_declick (true) , _meter_point (MeterPostFader) , _pending_meter_point (MeterPostFader) , _meter_type (MeterPeak) - , _self_solo (false) - , _soloed_by_others_upstream (0) - , _soloed_by_others_downstream (0) - , _solo_isolated (false) - , _solo_isolated_by_upstream (0) , _denormal_protection (false) , _recordable (true) , _silent (false) , _declickable (false) - , _mute_master (new MuteMaster (sess, name)) , _have_internal_generator (false) - , _solo_safe (false) , _default_type (default_type) - , _order_key (0) - , _has_order_key (false) - , _remote_control_id (0) , _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(); } @@ -135,24 +132,31 @@ Route::init () /* add standard controls */ - _solo_control.reset (new SoloControllable (X_("solo"), shared_from_this ())); - _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 ())); + _gain_control.reset (new GainControl (_session, GainAutomation)); + add_control (_gain_control); - _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)); + _trim_control.reset (new GainControl (_session, TrimAutomation)); + add_control (_trim_control); + _solo_control.reset (new SoloControl (_session, X_("solo"), *this, *this)); add_control (_solo_control); + _solo_control->Changed.connect_same_thread (*this, boost::bind (&Route::solo_control_changed, this, _1, _2)); + + _mute_control.reset (new MuteControl (_session, X_("mute"), *this)); add_control (_mute_control); + + _phase_control.reset (new PhaseControl (_session, X_("phase"))); add_control (_phase_control); + _solo_isolate_control.reset (new SoloIsolateControl (_session, X_("solo-iso"), *this, *this)); + add_control (_solo_isolate_control); + + _solo_safe_control.reset (new SoloSafeControl (_session, X_("solo-safe"))); + add_control (_solo_safe_control); + /* panning */ - if (!(_flags & Route::MonitorOut)) { + if (!(_presentation_info.flags() & PresentationInfo::MonitorOut)) { _pannable.reset (new Pannable (_session)); } @@ -167,17 +171,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 */ - - _gain_control = boost::shared_ptr (new GainControllable (_session, GainAutomation, shared_from_this ())); - add_control (_gain_control); + /* add the amp/fader processor. + * it should be the first processor to be added on every route. + */ _amp.reset (new Amp (_session, X_("Fader"), _gain_control, true)); add_processor (_amp, PostFader); @@ -186,10 +182,14 @@ Route::init () _amp->set_display_name (_("Monitor")); } - /* and input trim */ +#if 0 // not used - just yet + if (!is_master() && !is_monitor() && !is_auditioner()) { + _delayline.reset (new DelayLine (_session, _name)); + add_processor (_delayline, PreFader); + } +#endif - _trim_control = boost::shared_ptr (new GainControllable (_session, TrimAutomation, shared_from_this ())); - add_control (_trim_control); + /* and input trim */ _trim.reset (new Amp (_session, X_("Trim"), _trim_control, false)); _trim->set_display_to_user (false); @@ -229,10 +229,6 @@ Route::init () _monitor_control->activate (); } - if (is_master() || is_monitor() || is_auditioner()) { - _mute_master->set_solo_ignore (true); - } - /* now that we have _meter, its safe to connect to this */ { @@ -264,120 +260,6 @@ Route::~Route () _processors.clear (); } -void -Route::set_remote_control_id (uint32_t id, bool notify_class_listeners) -{ - if (Config->get_remote_model() != UserOrdered) { - return; - } - - set_remote_control_id_internal (id, notify_class_listeners); -} - -void -Route::set_remote_control_id_internal (uint32_t id, bool notify_class_listeners) -{ - /* force IDs for master/monitor busses and prevent - any other route from accidentally getting these IDs - (i.e. legacy sessions) - */ - - if (is_master() && id != MasterBusRemoteControlID) { - id = MasterBusRemoteControlID; - } - - if (is_monitor() && id != MonitorBusRemoteControlID) { - id = MonitorBusRemoteControlID; - } - - if (id < 1) { - return; - } - - /* don't allow it to collide */ - - if (!is_master () && !is_monitor() && - (id == MasterBusRemoteControlID || id == MonitorBusRemoteControlID)) { - id += MonitorBusRemoteControlID; - } - - if (id != remote_control_id()) { - _remote_control_id = id; - RemoteControlIDChanged (); - - if (notify_class_listeners) { - RemoteControlIDChange (); - } - } -} - -uint32_t -Route::remote_control_id() const -{ - if (is_master()) { - return MasterBusRemoteControlID; - } - - if (is_monitor()) { - return MonitorBusRemoteControlID; - } - - return _remote_control_id; -} - -bool -Route::has_order_key () const -{ - return _has_order_key; -} - -uint32_t -Route::order_key () const -{ - return _order_key; -} - -void -Route::set_remote_control_id_explicit (uint32_t rid) -{ - if (is_master() || is_monitor() || is_auditioner()) { - /* hard-coded remote IDs, or no remote ID */ - return; - } - - if (_remote_control_id != rid) { - DEBUG_TRACE (DEBUG::OrderKeys, string_compose ("%1: set edit-based RID to %2\n", name(), rid)); - _remote_control_id = rid; - RemoteControlIDChanged (); /* EMIT SIGNAL (per-route) */ - } - - /* don't emit the class-level RID signal RemoteControlIDChange here, - leave that to the entity that changed the order key, so that we - don't get lots of emissions for no good reasons (e.g. when changing - all route order keys). - - See Session::sync_remote_id_from_order_keys() for the (primary|only) - spot where that is emitted. - */ -} - -void -Route::set_order_key (uint32_t n) -{ - _has_order_key = true; - - if (_order_key == n) { - return; - } - - _order_key = n; - - DEBUG_TRACE (DEBUG::OrderKeys, string_compose ("%1 order key set to %2\n", - name(), order_key ())); - - _session.set_dirty (); -} - string Route::ensure_track_or_route_name(string name, Session &session) { @@ -390,83 +272,11 @@ Route::ensure_track_or_route_name(string name, Session &session) return newname; } -void -Route::inc_gain (gain_t factor) -{ - /* 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, Controllable::GroupControlDisposition group_override) -{ - if (use_group (group_override, &RouteGroup::is_gain)) { - - if (_route_group->is_relative()) { - - gain_t usable_gain = _gain_control->get_value(); - if (usable_gain < 0.000001f) { - usable_gain = 0.000001f; - } - - gain_t delta = val; - if (delta < 0.000001f) { - delta = 0.000001f; - } - - delta -= usable_gain; - - if (delta == 0.0f) - return; - - gain_t factor = delta / usable_gain; - - if (factor > 0.0f) { - factor = _route_group->get_max_factor(factor); - if (factor == 0.0f) { - _amp->gain_control()->Changed(); /* EMIT SIGNAL */ - return; - } - } else { - factor = _route_group->get_min_factor(factor); - if (factor == 0.0f) { - _amp->gain_control()->Changed(); /* EMIT SIGNAL */ - return; - } - } - - _route_group->foreach_route (boost::bind (&Route::inc_gain, _1, factor)); - - } else { - - _route_group->foreach_route (boost::bind (&Route::set_gain, _1, val, Controllable::NoGroup)); - } - - return; - } - - if (val == _gain_control->get_value()) { - return; - } - - _gain_control->route_set_value (val); -} - void Route::set_trim (gain_t val, Controllable::GroupControlDisposition /* group override */) { // TODO route group, see set_gain() - _trim_control->route_set_value (val); + // _trim_control->route_set_value (val); } void @@ -503,6 +313,8 @@ Route::process_output_buffers (BufferSet& bufs, return; } + _mute_control->automation_run (start_frame, nframes); + /* figure out if we're going to use gain automation */ if (gain_automation_ok) { _amp->set_gain_automation_buffer (_session.gain_automation_buffer ()); @@ -543,7 +355,7 @@ Route::process_output_buffers (BufferSet& bufs, DENORMAL CONTROL/PHASE INVERT ----------------------------------------------------------------------------------------- */ - if (_phase_invert.any ()) { + if (!_phase_control->none()) { int chn = 0; @@ -552,7 +364,7 @@ Route::process_output_buffers (BufferSet& bufs, for (BufferSet::audio_iterator i = bufs.audio_begin(); i != bufs.audio_end(); ++i, ++chn) { Sample* const sp = i->data(); - if (_phase_invert[chn]) { + if (_phase_control->inverted (chn)) { for (pframes_t nx = 0; nx < nframes; ++nx) { sp[nx] = -sp[nx]; sp[nx] += 1.0e-27f; @@ -569,7 +381,7 @@ Route::process_output_buffers (BufferSet& bufs, for (BufferSet::audio_iterator i = bufs.audio_begin(); i != bufs.audio_end(); ++i, ++chn) { Sample* const sp = i->data(); - if (_phase_invert[chn]) { + if (_phase_control->inverted (chn)) { for (pframes_t nx = 0; nx < nframes; ++nx) { sp[nx] = -sp[nx]; } @@ -599,6 +411,7 @@ Route::process_output_buffers (BufferSet& bufs, bool const meter_already_run = metering_state() == MeteringInput; framecnt_t latency = 0; + const double speed = _session.transport_speed (); for (ProcessorList::const_iterator i = _processors.begin(); i != _processors.end(); ++i) { @@ -628,8 +441,13 @@ Route::process_output_buffers (BufferSet& bufs, if (boost::dynamic_pointer_cast(*i) != 0) { boost::dynamic_pointer_cast(*i)->set_delay_in(_signal_latency - latency); } + if (boost::dynamic_pointer_cast(*i) != 0) { + const framecnt_t longest_session_latency = _initial_delay + _signal_latency; + boost::dynamic_pointer_cast(*i)->set_sidechain_latency ( + _initial_delay + latency, longest_session_latency - latency); + } - (*i)->run (bufs, start_frame - latency, end_frame - latency, nframes, *i != _processors.back()); + (*i)->run (bufs, start_frame - latency, end_frame - latency, speed, nframes, *i != _processors.back()); bufs.set_count ((*i)->output_streams()); if ((*i)->active ()) { @@ -657,6 +475,7 @@ Route::bounce_process (BufferSet& buffers, framepos_t start, framecnt_t nframes, _trim->setup_gain_automation (start, start + nframes, nframes); latency = 0; + const double speed = _session.transport_speed (); for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) { if (!include_endpoint && (*i) == endpoint) { @@ -679,7 +498,7 @@ Route::bounce_process (BufferSet& buffers, framepos_t start, framecnt_t nframes, */ if ((*i) == _main_outs) { assert ((*i)->does_routing()); - (*i)->run (buffers, start - latency, start - latency + nframes, nframes, true); + (*i)->run (buffers, start - latency, start - latency + nframes, speed, nframes, true); buffers.set_count ((*i)->output_streams()); } @@ -687,7 +506,7 @@ Route::bounce_process (BufferSet& buffers, framepos_t start, framecnt_t nframes, * 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); + (*i)->run (buffers, start - latency, start - latency + nframes, 1.0, nframes, true); buffers.set_count ((*i)->output_streams()); latency += (*i)->signal_latency (); } @@ -800,291 +619,38 @@ Route::passthru_silence (framepos_t start_frame, framepos_t end_frame, pframes_t } void -Route::set_listen (bool yn, Controllable::GroupControlDisposition group_override) -{ - if (_solo_safe) { - return; - } - - if (use_group (group_override, &RouteGroup::is_solo)) { - _route_group->foreach_route (boost::bind (&Route::set_listen, _1, yn, Controllable::ForGroup)); - return; - } - - if (_monitor_send) { - if (yn != _monitor_send->active()) { - if (yn) { - _monitor_send->activate (); - _mute_master->set_soloed_by_self (true); - } else { - _monitor_send->deactivate (); - _mute_master->set_soloed_by_self (false); - } - _mute_master->set_soloed_by_others (false); - - listen_changed (group_override); /* EMIT SIGNAL */ - } - } -} - -bool -Route::listening_via_monitor () const +Route::set_listen (bool yn) { if (_monitor_send) { - return _monitor_send->active (); - } else { - return false; - } -} - -void -Route::set_solo_safe (bool yn, Controllable::GroupControlDisposition /* group_override */) -{ - if (_solo_safe != yn) { - _solo_safe = yn; - solo_safe_changed (); /* EMIT SIGNAL */ - _solo_safe_control->Changed(); /* EMIT SIGNAL */ - } -} - -bool -Route::solo_safe() const -{ - return _solo_safe; -} - -void -Route::clear_all_solo_state () -{ - // ideally this function will never do anything, it only exists to forestall Murphy - bool emit_changed = false; - -#ifndef NDEBUG - // these are really debug messages, but of possible interest. - if (_self_solo) { - PBD::info << string_compose (_("Cleared Explicit solo: %1\n"), name()); - } - if (_soloed_by_others_upstream || _soloed_by_others_downstream) { - PBD::info << string_compose (_("Cleared Implicit solo: %1 up:%2 down:%3\n"), - name(), _soloed_by_others_upstream, _soloed_by_others_downstream); - } -#endif - - if (!_self_solo && (_soloed_by_others_upstream || _soloed_by_others_downstream)) { - // if self-soled, set_solo() will do signal emission - emit_changed = true; - } - - _soloed_by_others_upstream = 0; - _soloed_by_others_downstream = 0; - - { - PBD::Unwinder uw (_solo_safe, false); - set_solo (false, Controllable::NoGroup); - } - - if (emit_changed) { - set_mute_master_solo (); - solo_changed (false, Controllable::UseGroup); /* EMIT SIGNAL */ - } -} - -void -Route::set_solo (bool yn, Controllable::GroupControlDisposition group_override) -{ - DEBUG_TRACE (DEBUG::Solo, string_compose ("%1: set solo => %2, grp ? %3 currently self-soloed ? %4\n", - name(), yn, enum_2_string(group_override), self_soloed())); - - if (_solo_safe) { - DEBUG_TRACE (DEBUG::Solo, string_compose ("%1 ignore solo change due to solo-safe\n", name())); - return; - } - - if (is_master() || is_monitor() || is_auditioner()) { - DEBUG_TRACE (DEBUG::Solo, string_compose ("%1 ignore solo change (master, monitor or auditioner)\n", name())); - return; - } - - if (use_group (group_override, &RouteGroup::is_solo)) { - _route_group->foreach_route (boost::bind (&Route::set_solo, _1, yn, Controllable::ForGroup)); - return; - } - - if (self_soloed() != yn) { - set_self_solo (yn); - solo_changed (true, group_override); /* EMIT SIGNAL */ - _solo_control->Changed (); /* EMIT SIGNAL */ - } - - assert (Config->get_solo_control_is_listen_control() || !_monitor_send || !_monitor_send->active()); - - /* XXX TRACKS DEVELOPERS: THIS LOGIC SUGGESTS THAT YOU ARE NOT AWARE OF - Config->get_solo_mute_overrride(). - */ - - if (yn && Profile->get_trx()) { - set_mute (false, Controllable::UseGroup); - } -} - -void -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 -Route::mod_solo_by_others_upstream (int32_t delta) -{ - DEBUG_TRACE (DEBUG::Solo, string_compose ("%1 mod solo-by-upstream by %2, current up = %3 down = %4\n", - name(), delta, _soloed_by_others_upstream, _soloed_by_others_downstream)); - - uint32_t old_sbu = _soloed_by_others_upstream; - - if (delta < 0) { - if (_soloed_by_others_upstream >= (uint32_t) abs (delta)) { - _soloed_by_others_upstream += delta; + if (yn) { + _monitor_send->activate (); } else { - _soloed_by_others_upstream = 0; + _monitor_send->deactivate (); } - } else { - _soloed_by_others_upstream += delta; } - - DEBUG_TRACE (DEBUG::Solo, string_compose ( - "%1 SbU delta %2 = %3 old = %4 sbd %5 ss %6 exclusive %7\n", - name(), delta, _soloed_by_others_upstream, old_sbu, - _soloed_by_others_downstream, _self_solo, Config->get_exclusive_solo())); - - /* push the inverse solo change to everything that feeds us. - - This is important for solo-within-group. When we solo 1 track out of N that - feed a bus, that track will cause mod_solo_by_upstream (+1) to be called - on the bus. The bus then needs to call mod_solo_by_downstream (-1) on all - tracks that feed it. This will silence them if they were audible because - of a bus solo, but the newly soloed track will still be audible (because - it is self-soloed). - - but .. do this only when we are being told to solo-by-upstream (i.e delta = +1), - not in reverse. - */ - - if ((_self_solo || _soloed_by_others_downstream) && - ((old_sbu == 0 && _soloed_by_others_upstream > 0) || - (old_sbu > 0 && _soloed_by_others_upstream == 0))) { - - if (delta > 0 || !Config->get_exclusive_solo()) { - DEBUG_TRACE (DEBUG::Solo, string_compose("\t ... INVERT push from %1\n", _name)); - for (FedBy::iterator i = _fed_by.begin(); i != _fed_by.end(); ++i) { - if (i->sends_only) { - continue; - } - boost::shared_ptr sr = i->r.lock(); - if (sr) { - sr->mod_solo_by_others_downstream (-delta); - } - } - } - } - - set_mute_master_solo (); - solo_changed (false, Controllable::UseGroup); /* EMIT SIGNAL */ } void -Route::mod_solo_by_others_downstream (int32_t delta) +Route::solo_control_changed (bool, Controllable::GroupControlDisposition) { - DEBUG_TRACE (DEBUG::Solo, string_compose ("%1 mod solo-by-downstream by %2, current up = %3 down = %4\n", - name(), delta, _soloed_by_others_upstream, _soloed_by_others_downstream)); - - if (delta < 0) { - if (_soloed_by_others_downstream >= (uint32_t) abs (delta)) { - _soloed_by_others_downstream += delta; - } else { - _soloed_by_others_downstream = 0; - } - } else { - _soloed_by_others_downstream += 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, Controllable::UseGroup); /* EMIT SIGNAL */ -} - -void -Route::set_mute_master_solo () -{ - _mute_master->set_soloed_by_self (self_soloed()); - _mute_master->set_soloed_by_others (soloed_by_others_downstream() || soloed_by_others_upstream()); -} - -void -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", - name(), _solo_isolated_by_upstream, yn ? "+1" : "-1")); - - if (!yn) { - if (_solo_isolated_by_upstream >= 1) { - _solo_isolated_by_upstream--; - } else { - _solo_isolated_by_upstream = 0; - } - } else { - _solo_isolated_by_upstream++; - } + /* nothing to do if we're not using AFL/PFL. But if we are, we need + to alter the active state of the monitor send. + */ - if (solo_isolated() != old) { - /* solo isolated status changed */ - _mute_master->set_solo_ignore (solo_isolated()); - solo_isolated_changed (); /* EMIT SIGNAL */ + if (Config->get_solo_control_is_listen_control ()) { + set_listen (_solo_control->self_soloed() || _solo_control->get_masters_value()); } } void -Route::set_solo_isolated (bool yn, Controllable::GroupControlDisposition group_override) +Route::push_solo_isolate_upstream (int32_t delta) { - if (is_master() || is_monitor() || is_auditioner()) { - return; - } - - if (use_group (group_override, &RouteGroup::is_solo)) { - _route_group->foreach_route (boost::bind (&Route::set_solo_isolated, _1, yn, Controllable::ForGroup)); - return; - } - - bool changed = false; - - if (yn) { - if (_solo_isolated == false) { - _mute_master->set_solo_ignore (true); - changed = true; - } - _solo_isolated = true; - } else { - if (_solo_isolated == true) { - _solo_isolated = false; - _mute_master->set_solo_ignore (false); - changed = true; - } - } - - - if (!changed) { - return; - } - /* forward propagate solo-isolate status to everything fed by this route, but not those via sends only */ boost::shared_ptr routes = _session.get_routes (); for (RouteList::iterator i = routes->begin(); i != routes->end(); ++i) { - if ((*i).get() == this || (*i)->is_master() || (*i)->is_monitor() || (*i)->is_auditioner()) { + if ((*i).get() == this || !(*i)->can_solo()) { continue; } @@ -1092,75 +658,26 @@ Route::set_solo_isolated (bool yn, Controllable::GroupControlDisposition group_o bool does_feed = feeds (*i, &sends_only); if (does_feed && !sends_only) { - (*i)->mod_solo_isolated_by_upstream (yn); + (*i)->solo_isolate_control()->mod_solo_isolated_by_upstream (delta); } } - - /* XXX should we back-propagate as well? (April 2010: myself and chris goddard think not) */ - - solo_isolated_changed (); /* EMIT SIGNAL */ - _solo_isolate_control->Changed(); /* EMIT SIGNAL */ -} - -bool -Route::solo_isolated () const -{ - return (_solo_isolated == true) || (_solo_isolated_by_upstream > 0); } void -Route::set_mute_points (MuteMaster::MutePoint mp) +Route::push_solo_upstream (int delta) { - _mute_master->set_mute_points (mp); - mute_points_changed (); /* EMIT SIGNAL */ - - if (_mute_master->muted_by_self()) { - mute_changed (); /* EMIT SIGNAL */ - _mute_control->Changed (); /* EMIT SIGNAL */ - } -} - -void -Route::set_mute (bool yn, Controllable::GroupControlDisposition group_override) -{ - if (use_group (group_override, &RouteGroup::is_mute)) { - _route_group->foreach_route (boost::bind (&Route::set_mute, _1, yn, Controllable::ForGroup)); - return; - } - - if (muted() != yn) { - _mute_master->set_muted_by_self (yn); - /* allow any derived classes to respond to the mute change - before anybody else knows about it. - */ - act_on_mute (); - /* tell everyone else */ - mute_changed (); /* EMIT SIGNAL */ - _mute_control->Changed (); /* EMIT SIGNAL */ + DEBUG_TRACE (DEBUG::Solo, string_compose("\t ... INVERT push from %1\n", _name)); + for (FedBy::iterator i = _fed_by.begin(); i != _fed_by.end(); ++i) { + if (i->sends_only) { + continue; + } + boost::shared_ptr sr (i->r.lock()); + if (sr) { + sr->solo_control()->mod_solo_by_others_downstream (-delta); + } } } -bool -Route::muted () const -{ - return _mute_master->muted_by_self(); -} - -bool -Route::muted_by_others () const -{ - // This method is only used by route_ui for display state. - // The real thing is MuteMaster::muted_by_others_at() - - //master is never muted by others - if (is_master()) - return false; - - //now check to see if something is soloed (and I am not) - //see also MuteMaster::mute_gain_at() - return (_session.soloing() && !soloed() && !solo_isolated()); -} - #if 0 static void dump_processors(const string& name, const list >& procs) @@ -1253,89 +770,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; @@ -1373,6 +838,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 { @@ -1416,29 +882,89 @@ 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 (!AudioEngine::instance()->connected()) { + return 1; + } + + if (others.empty()) { + 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; + } - ProcessorList::iterator loc; + pi->set_strict_io (_strict_io); - if (before) { - loc = find(_processors.begin(), _processors.end(), before); - } else { - /* nothing specified - at end */ - loc = _processors.end (); - } + PluginSetupOptions mask = None; + if (Config->get_ask_replace_instrument ()) { + mask |= CanReplace; + } + if (Config->get_ask_setup_instrument ()) { + mask |= MultiOut; + } - if (!_session.engine().connected()) { - return 1; - } + flags &= mask; - if (others.empty()) { - return 0; + 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; + } + } } { @@ -1451,30 +977,59 @@ 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 (pi && pi->has_sidechain ()) { + pi->sidechain_input ()->changed.connect_same_thread (*this, boost::bind (&Route::sidechain_change_handler, this, _1, _2)); + } + + if ((*i)->active()) { + // emit ActiveChanged() and latency_changed() if needed + (*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) { @@ -1523,7 +1078,7 @@ Route::disable_processors (Placement p) placement_range(p, start, end); for (ProcessorList::iterator i = start; i != end; ++i) { - (*i)->deactivate (); + (*i)->enable (false); } _session.set_dirty (); @@ -1537,7 +1092,7 @@ Route::disable_processors () Glib::Threads::RWLock::ReaderLock lm (_processor_lock); for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) { - (*i)->deactivate (); + (*i)->enable (false); } _session.set_dirty (); @@ -1556,7 +1111,7 @@ Route::disable_plugins (Placement p) for (ProcessorList::iterator i = start; i != end; ++i) { if (boost::dynamic_pointer_cast (*i)) { - (*i)->deactivate (); + (*i)->enable (false); } } @@ -1572,7 +1127,7 @@ Route::disable_plugins () for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) { if (boost::dynamic_pointer_cast (*i)) { - (*i)->deactivate (); + (*i)->enable (false); } } @@ -1596,8 +1151,8 @@ Route::ab_plugins (bool forward) continue; } - if ((*i)->active()) { - (*i)->deactivate (); + if ((*i)->enabled ()) { + (*i)->enable (false); (*i)->set_next_ab_is_active (true); } else { (*i)->set_next_ab_is_active (false); @@ -1614,11 +1169,7 @@ Route::ab_plugins (bool forward) continue; } - if ((*i)->get_next_ab_is_active()) { - (*i)->activate (); - } else { - (*i)->deactivate (); - } + (*i)->enable ((*i)->get_next_ab_is_active ()); } } @@ -1687,7 +1238,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(); @@ -1759,9 +1310,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 (); } @@ -1781,10 +1338,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; } @@ -1813,6 +1370,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->enabled (); + + 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->enable (true); + } + + 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) { @@ -1855,9 +1510,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 (); } @@ -1872,10 +1532,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(); @@ -1926,7 +1586,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; @@ -1960,6 +1620,33 @@ 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_auditioner() + && (is_monitor() || _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 () && in.n_audio() > 0) { + /* ..but at least as many as there are master-inputs, if + * the delivery is dealing with audio */ + // 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)); @@ -2016,7 +1703,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()); @@ -2043,21 +1730,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; @@ -2074,6 +1794,9 @@ Route::configure_processors_unlocked (ProcessorStreams* err) } } + lr.release (); + lm->acquire (); + if (_meter) { _meter->set_max_channels (processor_max_streams); @@ -2106,12 +1829,7 @@ Route::all_visible_processors_active (bool state) if (!(*i)->display_to_user() || boost::dynamic_pointer_cast (*i)) { continue; } - - if (state) { - (*i)->activate (); - } else { - (*i)->deactivate (); - } + (*i)->enable (state); } _session.set_dirty (); @@ -2261,33 +1979,233 @@ Route::reorder_processors (const ProcessorList& new_order, ProcessorStreams* err if (processors_reorder_needs_configure (new_order) || !AudioEngine::instance()->running()) { - Glib::Threads::Mutex::Lock lx (AudioEngine::instance()->process_lock ()); - Glib::Threads::RWLock::WriterLock lm (_processor_lock); - ProcessorState pstate (this); + Glib::Threads::Mutex::Lock lx (AudioEngine::instance()->process_lock ()); + Glib::Threads::RWLock::WriterLock lm (_processor_lock); + ProcessorState pstate (this); + + apply_processor_order (new_order); + + if (configure_processors_unlocked (err, &lm)) { + pstate.restore (); + return -1; + } + + lm.release(); + lx.release(); + + processors_changed (RouteProcessorChange ()); /* EMIT SIGNAL */ + set_processor_positions (); + + } else { + DEBUG_TRACE (DEBUG::Processors, "Queue clickless processor re-order.\n"); + Glib::Threads::RWLock::ReaderLock lm (_processor_lock); + + // _pending_processor_order is protected by _processor_lock + _pending_processor_order = new_order; + g_atomic_int_set (&_pending_process_reorder, 1); + } + + return 0; +} + +bool +Route::add_remove_sidechain (boost::shared_ptr proc, bool add) +{ + boost::shared_ptr pi; + if ((pi = boost::dynamic_pointer_cast(proc)) == 0) { + return false; + } + + if (pi->has_sidechain () == add) { + return true; // ?? call failed, but result is as expected. + } + + { + Glib::Threads::RWLock::ReaderLock lm (_processor_lock); + ProcessorList::iterator i = find (_processors.begin(), _processors.end(), proc); + if (i == _processors.end ()) { + return false; + } + } + + { + Glib::Threads::Mutex::Lock lx (AudioEngine::instance()->process_lock ()); // take before Writerlock to avoid deadlock + Glib::Threads::RWLock::WriterLock lm (_processor_lock); + PBD::Unwinder 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; +} - apply_processor_order (new_order); +bool +Route::set_strict_io (const bool enable) +{ + Glib::Threads::Mutex::Lock lx (AudioEngine::instance()->process_lock ()); - if (configure_processors_unlocked (err)) { - pstate.restore (); - return -1; + 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); + } } - lm.release(); - lx.release(); + list > c = try_configure_processors_unlocked (n_inputs (), 0); - processors_changed (RouteProcessorChange ()); /* EMIT SIGNAL */ - set_processor_positions (); + 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 (); - } else { - DEBUG_TRACE (DEBUG::Processors, "Queue clickless processor re-order.\n"); - Glib::Threads::RWLock::ReaderLock lm (_processor_lock); + configure_processors (0); + lx.release (); - // _pending_processor_order is protected by _processor_lock - _pending_processor_order = new_order; - g_atomic_int_set (&_pending_process_reorder, 1); + processors_changed (RouteProcessorChange ()); /* EMIT SIGNAL */ + _session.set_dirty (); } - - return 0; + return true; } XMLNode& @@ -2305,6 +2223,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)); @@ -2318,15 +2237,12 @@ Route::state(bool full_state) node->add_property("id", buf); node->add_property ("name", _name); node->add_property("default-type", _default_type.to_string()); + node->add_property ("strict-io", _strict_io); - if (_flags) { - node->add_property("flags", enum_2_string (_flags)); - } + node->add_child_nocopy (_presentation_info.get_state()); node->add_property("active", _active?"yes":"no"); string p; - boost::to_string (_phase_invert, p); - node->add_property("phase-invert", p); node->add_property("denormal-protection", _denormal_protection?"yes":"no"); node->add_property("meter-point", enum_2_string (_meter_point)); @@ -2336,31 +2252,21 @@ Route::state(bool full_state) node->add_property("route-group", _route_group->name()); } - snprintf (buf, sizeof (buf), "%d", _order_key); - node->add_property ("order-key", buf); - node->add_property ("self-solo", (_self_solo ? "yes" : "no")); - snprintf (buf, sizeof (buf), "%d", _soloed_by_others_upstream); - node->add_property ("soloed-by-upstream", buf); - snprintf (buf, sizeof (buf), "%d", _soloed_by_others_downstream); - node->add_property ("soloed-by-downstream", buf); - node->add_property ("solo-isolated", solo_isolated() ? "yes" : "no"); - node->add_property ("solo-safe", _solo_safe ? "yes" : "no"); + node->add_child_nocopy (_solo_control->get_state ()); + node->add_child_nocopy (_solo_isolate_control->get_state ()); + node->add_child_nocopy (_solo_safe_control->get_state ()); node->add_child_nocopy (_input->state (full_state)); node->add_child_nocopy (_output->state (full_state)); - node->add_child_nocopy (_solo_control->get_state ()); - node->add_child_nocopy (_mute_control->get_state ()); node->add_child_nocopy (_mute_master->get_state ()); + node->add_child_nocopy (_mute_control->get_state ()); + node->add_child_nocopy (_phase_control->get_state ()); + if (full_state) { node->add_child_nocopy (Automatable::get_automation_xml_state ()); } - XMLNode* remote_control_node = new XMLNode (X_("RemoteControl")); - snprintf (buf, sizeof (buf), "%d", _remote_control_id); - remote_control_node->add_property (X_("id"), buf); - node->add_child_nocopy (*remote_control_node); - if (_comment.length()) { XMLNode *cmt = node->add_child ("Comment"); cmt->add_content (_comment); @@ -2370,26 +2276,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) { @@ -2408,6 +2317,8 @@ Route::state(bool full_state) foreach_processor (sigc::bind (sigc::mem_fun (*this, &Route::set_plugin_state_dir), "")); } + node->add_child_copy (Slavable::get_state()); + return *node; } @@ -2421,7 +2332,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; @@ -2435,14 +2346,10 @@ Route::set_state (const XMLNode& node, int version) set_id (node); _initial_io_setup = true; - if ((prop = node.property (X_("flags"))) != 0) { - _flags = Flag (string_2_enum (prop->value(), _flags)); - } else { - _flags = Flag (0); - } + Stripable::set_state (node, version); - if (is_master() || is_monitor() || is_auditioner()) { - _mute_master->set_solo_ignore (true); + if ((prop = node.property (X_("strict-io"))) != 0) { + _strict_io = string_is_affirmative (prop->value()); } if (is_monitor()) { @@ -2474,18 +2381,17 @@ Route::set_state (const XMLNode& node, int version) } else if (prop->value() == "Output") { _output->set_state (*child, version); } - } - if (child->name() == X_("Processor")) { + } else if (child->name() == X_("Processor")) { processor_state.add_child_copy (*child); - } - - if (child->name() == X_("Pannable")) { + } else if (child->name() == X_("Pannable")) { if (_pannable) { _pannable->set_state (*child, version); } else { warning << string_compose (_("Pannable state found for route (%1) without a panner!"), name()) << endmsg; } + } else if (child->name() == Slavable::xml_node_name) { + Slavable::set_state (*child, version); } } @@ -2508,82 +2414,15 @@ Route::set_state (const XMLNode& node, int version) // this looks up the internal instrument in processors reset_instrument_info(); - if ((prop = node.property ("self-solo")) != 0) { - set_self_solo (string_is_affirmative (prop->value())); - } - - if ((prop = node.property ("soloed-by-upstream")) != 0) { - _soloed_by_others_upstream = 0; // needed for mod_.... () to work - mod_solo_by_others_upstream (atoi (prop->value())); - } - - if ((prop = node.property ("soloed-by-downstream")) != 0) { - _soloed_by_others_downstream = 0; // needed for mod_.... () to work - mod_solo_by_others_downstream (atoi (prop->value())); - } - - if ((prop = node.property ("solo-isolated")) != 0) { - 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()), Controllable::NoGroup); - } - - if ((prop = node.property (X_("phase-invert"))) != 0) { - set_phase_invert (boost::dynamic_bitset<> (prop->value ())); - } - if ((prop = node.property (X_("denormal-protection"))) != 0) { set_denormal_protection (string_is_affirmative (prop->value())); } if ((prop = node.property (X_("active"))) != 0) { bool yn = string_is_affirmative (prop->value()); - _active = !yn; // force switch set_active (yn, this); } - if ((prop = node.property (X_("order-key"))) != 0) { // New order key (no separate mixer/editor ordering) - set_order_key (atoi(prop->value())); - } - - if ((prop = node.property (X_("order-keys"))) != 0) { // Deprecated order keys - - int32_t n; - - string::size_type colon, equal; - string remaining = prop->value(); - - while (remaining.length()) { - - if ((equal = remaining.find_first_of ('=')) == string::npos || equal == remaining.length()) { - error << string_compose (_("badly formed order key string in state file! [%1] ... ignored."), remaining) - << endmsg; - } else { - if (sscanf (remaining.substr (equal+1).c_str(), "%d", &n) != 1) { - error << string_compose (_("badly formed order key string in state file! [%1] ... ignored."), remaining) - << endmsg; - } else { - string keyname = remaining.substr (0, equal); - - if ((keyname == "EditorSort") || (keyname == "editor")) { - cerr << "Setting " << name() << " order key to " << n << " using saved Editor order." << endl; - set_order_key (n); - } - } - } - - colon = remaining.find_first_of (':'); - - if (colon != string::npos) { - remaining = remaining.substr (colon+1); - } else { - break; - } - } - } - if ((prop = node.property (X_("processor-after-last-custom-meter"))) != 0) { PBD::ID id (prop->value ()); Glib::Threads::RWLock::ReaderLock lm (_processor_lock); @@ -2608,21 +2447,25 @@ Route::set_state (const XMLNode& node, int version) XMLNode *cmt = *(child->children().begin()); _comment = cmt->content(); - } else if (child->name() == Controllable::xml_node_name && (prop = child->property("name")) != 0) { - if (prop->value() == "solo") { - _solo_control->set_state (*child, version); - } else if (prop->value() == "mute") { - _mute_control->set_state (*child, version); + } else if (child->name() == Controllable::xml_node_name) { + if ((prop = child->property (X_("name"))) == 0) { + continue; } - } else if (child->name() == X_("RemoteControl")) { - if ((prop = child->property (X_("id"))) != 0) { - int32_t x; - sscanf (prop->value().c_str(), "%d", &x); - set_remote_control_id_internal (x); + if (prop->value() == _gain_control->name()) { + _gain_control->set_state (*child, version); + } else if (prop->value() == _solo_control->name()) { + _solo_control->set_state (*child, version); + } else if (prop->value() == _solo_safe_control->name()) { + _solo_safe_control->set_state (*child, version); + } else if (prop->value() == _solo_isolate_control->name()) { + _solo_isolate_control->set_state (*child, version); + } else if (prop->value() == _solo_control->name()) { + _mute_control->set_state (*child, version); + } else if (prop->value() == _phase_control->name()) { + _phase_control->set_state (*child, version); } - - } else if (child->name() == X_("MuteMaster")) { + } else if (child->name() == MuteMaster::xml_node_name) { _mute_master->set_state (*child, version); } else if (child->name() == Automatable::xml_node_name) { @@ -2636,11 +2479,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 @@ -2653,38 +2496,12 @@ Route::set_state_2X (const XMLNode& node, int version) return -1; } - if ((prop = node.property (X_("flags"))) != 0) { - string f = prop->value (); - boost::replace_all (f, "ControlOut", "MonitorOut"); - _flags = Flag (string_2_enum (f, _flags)); - } else { - _flags = Flag (0); - } - - if (is_master() || is_monitor() || is_auditioner()) { - _mute_master->set_solo_ignore (true); - } - - if ((prop = node.property (X_("phase-invert"))) != 0) { - boost::dynamic_bitset<> p (_input->n_ports().n_audio ()); - if (string_is_affirmative (prop->value ())) { - p.set (); - } - set_phase_invert (p); - } + Stripable::set_state (node, version); if ((prop = node.property (X_("denormal-protection"))) != 0) { set_denormal_protection (string_is_affirmative (prop->value())); } - if ((prop = node.property (X_("soloed"))) != 0) { - bool yn = string_is_affirmative (prop->value()); - - /* XXX force reset of solo status */ - - set_solo (yn); - } - if ((prop = node.property (X_("muted"))) != 0) { bool first = true; @@ -2749,46 +2566,6 @@ Route::set_state_2X (const XMLNode& node, int version) _meter_point = MeterPoint (string_2_enum (prop->value (), _meter_point)); } - /* do not carry over edit/mix groups from 2.X because (a) its hard (b) they - don't mean the same thing. - */ - - if ((prop = node.property (X_("order-keys"))) != 0) { - - int32_t n; - - string::size_type colon, equal; - string remaining = prop->value(); - - while (remaining.length()) { - - if ((equal = remaining.find_first_of ('=')) == string::npos || equal == remaining.length()) { - error << string_compose (_("badly formed order key string in state file! [%1] ... ignored."), remaining) - << endmsg; - } else { - if (sscanf (remaining.substr (equal+1).c_str(), "%d", &n) != 1) { - error << string_compose (_("badly formed order key string in state file! [%1] ... ignored."), remaining) - << endmsg; - } else { - string keyname = remaining.substr (0, equal); - - if (keyname == "EditorSort" || keyname == "editor") { - info << string_compose(_("Converting deprecated order key for %1 using Editor order %2"), name (), n) << endmsg; - set_order_key (n); - } - } - } - - colon = remaining.find_first_of (':'); - - if (colon != string::npos) { - remaining = remaining.substr (colon+1); - } else { - break; - } - } - } - /* IOs */ nlist = node.children (); @@ -2878,13 +2655,6 @@ Route::set_state_2X (const XMLNode& node, int version) _mute_control->set_state (*child, version); } - } else if (child->name() == X_("RemoteControl")) { - if ((prop = child->property (X_("id"))) != 0) { - int32_t x; - sscanf (prop->value().c_str(), "%d", &x); - set_remote_control_id_internal (x); - } - } } @@ -2963,7 +2733,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); @@ -2992,6 +2762,12 @@ Route::set_processor_state (const XMLNode& node) 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") { @@ -3000,6 +2776,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; @@ -3011,6 +2790,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. */ @@ -3030,12 +2815,17 @@ Route::set_processor_state (const XMLNode& node) } { - Glib::Threads::Mutex::Lock lx (AudioEngine::instance()->process_lock ()); Glib::Threads::RWLock::WriterLock lm (_processor_lock); + /* re-assign _processors w/o process-lock. + * if there's an IO-processor present in _processors but + * not in new_order, it will be deleted and ~IO takes + * a process lock. + */ _processors = new_order; + Glib::Threads::Mutex::Lock lx (AudioEngine::instance()->process_lock ()); if (must_configure) { - configure_processors_unlocked (0); + configure_processors_unlocked (0, &lm); } for (ProcessorList::const_iterator i = _processors.begin(); i != _processors.end(); ++i) { @@ -3082,6 +2872,8 @@ Route::silence_unlocked (framecnt_t nframes) { /* Must be called with the processor lock held */ + const framepos_t now = _session.transport_frame (); + if (!_silent) { _output->silence (nframes); @@ -3094,7 +2886,7 @@ Route::silence_unlocked (framecnt_t nframes) continue; } - (*i)->silence (nframes); + (*i)->silence (nframes, now); } if (nframes == _session.get_block_size()) { @@ -3300,12 +3092,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; @@ -3314,13 +3147,25 @@ 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 (other.get() == this && iop_out && iop->input() && iop_out->connected_to (iop->input())) { + // TODO this needs a delaylines in the Insert to align connections (!) + DEBUG_TRACE (DEBUG::Graph, string_compose ("\tIOP %1 does feed its own return (%2)\n", iop->name(), other->name())); + continue; + } + 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; @@ -3345,6 +3190,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) @@ -3377,11 +3228,11 @@ Route::input_change_handler (IOChange change, void * /*src*/) contains ConfigurationChanged */ configure_processors (0); - _phase_invert.resize (_input->n_ports().n_audio ()); + _phase_control->resize (_input->n_ports().n_audio ()); io_changed (); /* EMIT SIGNAL */ } - if (_soloed_by_others_upstream || _solo_isolated_by_upstream) { + if (_solo_control->soloed_by_others_upstream() || _solo_isolate_control->solo_isolated_by_upstream()) { int sbou = 0; int ibou = 0; boost::shared_ptr routes = _session.get_routes (); @@ -3396,38 +3247,36 @@ Route::input_change_handler (IOChange change, void * /*src*/) if ((*i)->soloed()) { ++sbou; } - if ((*i)->solo_isolated()) { + if ((*i)->solo_isolate_control()->solo_isolated()) { ++ibou; } } } } - int delta = sbou - _soloed_by_others_upstream; - int idelta = ibou - _solo_isolated_by_upstream; + int delta = sbou - _solo_control->soloed_by_others_upstream(); + int idelta = ibou - _solo_isolate_control->solo_isolated_by_upstream(); if (idelta < -1) { PBD::warning << string_compose ( _("Invalid Solo-Isolate propagation: from:%1 new:%2 - old:%3 = delta:%4"), - _name, ibou, _solo_isolated_by_upstream, idelta) + _name, ibou, _solo_isolate_control->solo_isolated_by_upstream(), idelta) << endmsg; } - if (_soloed_by_others_upstream) { + if (_solo_control->soloed_by_others_upstream()) { // ignore new connections (they're not propagated) if (delta <= 0) { - mod_solo_by_others_upstream (delta); + _solo_control->mod_solo_by_others_upstream (delta); } } - if (_solo_isolated_by_upstream) { + if (_solo_isolate_control->solo_isolated_by_upstream()) { // solo-isolate currently only propagates downstream if (idelta < 0) { - mod_solo_isolated_by_upstream (false); + _solo_isolate_control->mod_solo_isolated_by_upstream (1); } - // TODO think: mod_solo_isolated_by_upstream() does not take delta arg, - // but idelta can't be smaller than -1, can it? //_solo_isolated_by_upstream = ibou; } @@ -3440,11 +3289,11 @@ Route::input_change_handler (IOChange change, void * /*src*/) bool sends_only; bool does_feed = feeds (*i, &sends_only); if (delta <= 0 && does_feed && !sends_only) { - (*i)->mod_solo_by_others_upstream (delta); + (*i)->solo_control()->mod_solo_by_others_upstream (delta); } if (idelta < 0 && does_feed && !sends_only) { - (*i)->mod_solo_isolated_by_upstream (false); + (*i)->solo_isolate_control()->mod_solo_isolated_by_upstream (-1); } } } @@ -3470,7 +3319,7 @@ Route::output_change_handler (IOChange change, void * /*src*/) io_changed (); /* EMIT SIGNAL */ } - if (_soloed_by_others_downstream) { + if (_solo_control->soloed_by_others_downstream()) { int sbod = 0; /* checking all all downstream routes for * explicit of implict solo is a rather drastic measure, @@ -3493,20 +3342,20 @@ Route::output_change_handler (IOChange change, void * /*src*/) } } } - int delta = sbod - _soloed_by_others_downstream; + int delta = sbod - _solo_control->soloed_by_others_downstream(); if (delta <= 0) { // do not allow new connections to change implicit solo (no propagation) - mod_solo_by_others_downstream (delta); + _solo_control->mod_solo_by_others_downstream (delta); // Session::route_solo_changed() does not propagate indirect solo-changes // propagate upstream to tracks for (RouteList::iterator i = routes->begin(); i != routes->end(); ++i) { - if ((*i).get() == this || (*i)->is_master() || (*i)->is_monitor() || (*i)->is_auditioner()) { + if ((*i).get() == this || !can_solo()) { continue; } bool sends_only; bool does_feed = (*i)->feeds (shared_from_this(), &sends_only); if (delta != 0 && does_feed && !sends_only) { - (*i)->mod_solo_by_others_downstream (delta); + (*i)->solo_control()->mod_solo_by_others_downstream (delta); } } @@ -3514,6 +3363,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 { @@ -3524,6 +3383,22 @@ Route::pans_required () const return max (n_inputs ().n_audio(), processor_max_streams.n_audio()); } +void +Route::flush_processor_buffers_locked (framecnt_t nframes) +{ + for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) { + boost::shared_ptr d = boost::dynamic_pointer_cast (*i); + if (d) { + d->flush_buffers (nframes); + } else { + boost::shared_ptr p = boost::dynamic_pointer_cast (*i); + if (p) { + p->flush_buffers (nframes); + } + } + } +} + int Route::no_roll (pframes_t nframes, framepos_t start_frame, framepos_t end_frame, bool session_state_changing) { @@ -3562,13 +3437,15 @@ Route::no_roll (pframes_t nframes, framepos_t start_frame, framepos_t end_frame, fill_buffers_with_input (bufs, _input, nframes); if (_meter_point == MeterInput) { - _meter->run (bufs, start_frame, end_frame, nframes, true); + _meter->run (bufs, start_frame, end_frame, 0.0, nframes, true); } _amp->apply_gain_automation (false); _trim->apply_gain_automation (false); passthru (bufs, start_frame, end_frame, nframes, 0); + flush_processor_buffers_locked (nframes); + return 0; } @@ -3602,11 +3479,13 @@ Route::roll (pframes_t nframes, framepos_t start_frame, framepos_t end_frame, in fill_buffers_with_input (bufs, _input, nframes); if (_meter_point == MeterInput) { - _meter->run (bufs, start_frame, end_frame, nframes, true); + _meter->run (bufs, start_frame, end_frame, 1.0, nframes, true); } passthru (bufs, start_frame, end_frame, nframes, declick); + flush_processor_buffers_locked (nframes); + return 0; } @@ -3614,6 +3493,7 @@ int Route::silent_roll (pframes_t nframes, framepos_t /*start_frame*/, framepos_t /*end_frame*/, bool& /* need_butler */) { silence (nframes); + flush_processor_buffers_locked (nframes); return 0; } @@ -3671,13 +3551,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(); @@ -3691,6 +3570,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 @@ -3799,10 +3696,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; } } @@ -3820,10 +3717,12 @@ Route::add_export_point() Glib::Threads::Mutex::Lock lx (AudioEngine::instance()->process_lock ()); Glib::Threads::RWLock::WriterLock lw (_processor_lock); - _capturing_processor.reset (new CapturingProcessor (_session)); + // this aligns all tracks; but not tracks + busses + assert (_session.worst_track_latency () >= _initial_delay); + _capturing_processor.reset (new CapturingProcessor (_session, _session.worst_track_latency () - _initial_delay)); _capturing_processor->activate (); - configure_processors_unlocked (0); + configure_processors_unlocked (0, &lw); } @@ -4085,7 +3984,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); } @@ -4119,42 +4018,6 @@ Route::internal_send_for (boost::shared_ptr target) const return boost::shared_ptr(); } -/** @param c Audio channel index. - * @param yn true to invert phase, otherwise false. - */ -void -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 (); - } -} - -void -Route::set_phase_invert (boost::dynamic_bitset<> p) -{ - if (_phase_invert != p) { - _phase_invert = p; - phase_invert_changed (); /* EMIT SIGNAL */ - _session.set_dirty (); - } -} - -bool -Route::phase_invert (uint32_t c) const -{ - return _phase_invert[c]; -} - -boost::dynamic_bitset<> -Route::phase_invert () const -{ - return _phase_invert; -} - void Route::set_denormal_protection (bool yn) { @@ -4222,14 +4085,10 @@ Route::trim_control() const return _trim_control; } -boost::shared_ptr +boost::shared_ptr Route::phase_control() const { - if (phase_invert().size()) { - return _phase_control; - } else { - return boost::shared_ptr(); - } + return _phase_control; } boost::shared_ptr @@ -4322,12 +4181,6 @@ Route::has_io_processor_named (const string& name) return false; } -MuteMaster::MutePoint -Route::mute_points () const -{ - return _mute_master->mute_points (); -} - void Route::set_processor_positions () { @@ -4362,6 +4215,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; @@ -4535,12 +4391,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 */ @@ -4640,10 +4497,17 @@ Route::setup_invisible_processors () new_processors.insert (amp, _monitor_control); } + /* TRIM CONTROL */ + + if (_trim && _trim->active()) { + assert (!_trim->display_to_user ()); + new_processors.push_front (_trim); + } + /* INTERNAL RETURN */ - /* doing this here means that any monitor control will come just after - the return. + /* doing this here means that any monitor control will come after + the return and trim. */ if (_intreturn) { @@ -4651,10 +4515,6 @@ Route::setup_invisible_processors () new_processors.push_front (_intreturn); } - if (_trim && _trim->active()) { - assert (!_trim->display_to_user ()); - new_processors.push_front (_trim); - } /* EXPORT PROCESSOR */ if (_capturing_processor) { @@ -4665,8 +4525,8 @@ Route::setup_invisible_processors () _processors = new_processors; for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) { - if (!(*i)->display_to_user () && !(*i)->active () && (*i) != _monitor_send) { - (*i)->activate (); + if (!(*i)->display_to_user () && !(*i)->enabled () && (*i) != _monitor_send) { + (*i)->enable (true); } } @@ -4794,11 +4654,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(); @@ -5366,3 +5224,47 @@ Route::master_send_enable_controllable () const return boost::shared_ptr(); #endif } + +bool +Route::slaved () const +{ + if (!_gain_control) { + return false; + } + /* just test one particular control, not all of them */ + return _gain_control->slaved (); +} + +bool +Route::slaved_to (boost::shared_ptr vca) const +{ + if (!vca || !_gain_control) { + return false; + } + + /* just test one particular control, not all of them */ + + return _gain_control->slaved_to (vca->gain_control()); +} + +bool +Route::muted_by_others_soloing () const +{ + if (!can_be_muted_by_others ()) { + return false; + } + + return _session.soloing() && !_solo_control->soloed() && !_solo_isolate_control->solo_isolated(); +} + +void +Route::clear_all_solo_state () +{ + double v = _solo_safe_control->get_value (); + + _solo_control->clear_all_solo_state (); + + if (v != 0.0) { + _solo_safe_control->set_value (v, Controllable::NoGroup); + } +}