#endif
#include <cmath>
-#include <fstream>
#include <cassert>
#include <algorithm>
+#include <glibmm.h>
#include <boost/algorithm/string.hpp>
#include "pbd/xml++.h"
#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"
#include "ardour/audio_buffer.h"
+#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"
#include "ardour/pannable.h"
#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"
#include "ardour/processor.h"
+#include "ardour/profile.h"
#include "ardour/route.h"
#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<void> Route::SyncOrderKeys;
-PBD::Signal0<void> Route::RemoteControlIDChange;
+PBD::Signal3<int,boost::shared_ptr<Route>, boost::shared_ptr<PluginInsert>, 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)
+ , _signal_latency_at_trim_position (0)
, _initial_delay (0)
, _roll_delay (0)
- , _flags (flg)
+ , _pending_process_reorder (0)
+ , _pending_signals (0)
, _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 (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)
- , _last_custom_meter_was_at_end (false)
+ , _pinmgr_proxy (0)
{
- if (is_master()) {
- _meter_type = MeterK20;
- }
processor_max_streams.reset();
}
int
Route::init ()
{
+ /* set default meter type */
+ if (is_master()) {
+ _meter_type = Config->get_meter_type_master ();
+ }
+ else if (dynamic_cast<Track*>(this)) {
+ _meter_type = Config->get_meter_type_track ();
+ } else {
+ _meter_type = Config->get_meter_type_bus ();
+ }
+
/* add standard controls */
- _solo_control.reset (new SoloControllable (X_("solo"), shared_from_this ()));
- _mute_control.reset (new MuteControllable (X_("mute"), 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));
+ _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));
}
_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.
+ */
+
+ _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));
add_processor (_delayline, PreFader);
}
+#endif
- /* add amp processor */
+ /* and input trim */
- _amp.reset (new Amp (_session));
- add_processor (_amp, PostFader);
+ _trim.reset (new Amp (_session, X_("Trim"), _trim_control, false));
+ _trim->set_display_to_user (false);
+
+ if (dynamic_cast<AudioTrack*>(this)) {
+ /* we can't do this in the AudioTrack's constructor
+ * because _trim does not exit then
+ */
+ _trim->activate();
+ }
+ else if (!dynamic_cast<Track*>(this) && ! ( is_monitor() || is_auditioner() )) {
+ /* regular bus */
+ _trim->activate();
+ }
/* create standard processors: meter, main outs, monitor out;
they will be added to _processors by setup_invisible_processors ()
_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 */
- Metering::Meter.connect_same_thread (*this, (boost::bind (&Route::meter, this)));
-
{
Glib::Threads::Mutex::Lock lx (AudioEngine::instance()->process_lock ());
configure_processors (0);
_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)
{
string newname = name;
while (!session.io_name_is_legal (newname)) {
- newname = bump_name_once (newname, '.');
+ newname = bump_name_once (newname, ' ');
}
return newname;
}
-
-void
-Route::inc_gain (gain_t fraction, void *src)
-{
- _amp->inc_gain (fraction, src);
-}
-
void
-Route::set_gain (gain_t val, void *src)
+Route::set_trim (gain_t val, Controllable::GroupControlDisposition /* group override */)
{
- if (src != 0 && _route_group && src != _route_group && _route_group->is_active() && _route_group->is_gain()) {
-
- if (_route_group->is_relative()) {
-
- gain_t usable_gain = _amp->gain();
- 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, _route_group));
-
- } else {
-
- _route_group->foreach_route (boost::bind (&Route::set_gain, _1, val, _route_group));
- }
-
- return;
- }
-
- if (val == _amp->gain()) {
- return;
- }
-
- _amp->set_gain (val, src);
+ // TODO route group, see set_gain()
+ // _trim_control->route_set_value (val);
}
void
assert (!AudioEngine::instance()->process_lock().trylock());
Glib::Threads::RWLock::ReaderLock lm (_processor_lock, Glib::Threads::TRY_LOCK);
- assert(lm.locked());
+ if (!lm.locked()) {
+ // can this actually happen? functions calling process_output_buffers()
+ // already take a reader-lock.
+ bufs.silence (nframes, 0);
+ return;
+ }
+
+ _mute_control->automation_run (start_frame, nframes);
/* figure out if we're going to use gain automation */
if (gain_automation_ok) {
start_frame + _signal_latency_at_amp_position,
end_frame + _signal_latency_at_amp_position,
nframes);
+
+ _trim->set_gain_automation_buffer (_session.trim_automation_buffer ());
+ _trim->setup_gain_automation (
+ start_frame + _signal_latency_at_trim_position,
+ end_frame + _signal_latency_at_trim_position,
+ nframes);
} else {
_amp->apply_gain_automation (false);
+ _trim->apply_gain_automation (false);
}
/* Tell main outs what to do about monitoring. We do this so that
on a transition between monitoring states we get a de-clicking gain
- change in the _main_outs delivery.
+ change in the _main_outs delivery, if config.get_use_monitor_fades()
+ is true.
+
+ We override this in the case where we have an internal generator.
*/
+ bool silence = _have_internal_generator ? false : (monitoring_state () == MonitoringSilence);
- _main_outs->no_outs_cuz_we_no_monitor (monitoring_state () == MonitoringSilence);
+ _main_outs->no_outs_cuz_we_no_monitor (silence);
/* -------------------------------------------------------------------------------------------
GLOBAL DECLICK (for transport changes etc.)
DENORMAL CONTROL/PHASE INVERT
----------------------------------------------------------------------------------------- */
- if (_phase_invert.any ()) {
+ if (!_phase_control->none()) {
int chn = 0;
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;
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];
}
bool const meter_already_run = metering_state() == MeteringInput;
framecnt_t latency = 0;
+ const double speed = _session.transport_speed ();
- for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) {
+ for (ProcessorList::const_iterator i = _processors.begin(); i != _processors.end(); ++i) {
if (meter_already_run && boost::dynamic_pointer_cast<PeakMeter> (*i)) {
/* don't ::run() the meter, otherwise it will have its previous peak corrupted */
if (boost::dynamic_pointer_cast<Send>(*i) != 0) {
boost::dynamic_pointer_cast<Send>(*i)->set_delay_in(_signal_latency - latency);
}
+ if (boost::dynamic_pointer_cast<PluginInsert>(*i) != 0) {
+ const framecnt_t longest_session_latency = _initial_delay + _signal_latency;
+ boost::dynamic_pointer_cast<PluginInsert>(*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 ()) {
_amp->set_gain_automation_buffer (_session.gain_automation_buffer ());
_amp->setup_gain_automation (start - latency, start - latency + nframes, nframes);
+ /* trim is always at the top, for bounce no latency compensation is needed */
+ _trim->set_gain_automation_buffer (_session.trim_automation_buffer ());
+ _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) {
break;
}
- /* if we're not exporting, stop processing if we come across a routing processor. */
+ /* if we're *not* exporting, stop processing if we come across a routing processor. */
if (!for_export && boost::dynamic_pointer_cast<PortInsert>(*i)) {
break;
}
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, speed, nframes, true);
+ buffers.set_count ((*i)->output_streams());
+ }
+
+ /* don't run any processors that do routing.
+ * Also don't bother with metering.
*/
if (!(*i)->does_routing() && !boost::dynamic_pointer_cast<PeakMeter>(*i)) {
- (*i)->run (buffers, start - latency, start - latency + nframes, nframes, true);
+ (*i)->run (buffers, start - latency, start - latency + nframes, 1.0, nframes, true);
buffers.set_count ((*i)->output_streams());
latency += (*i)->signal_latency ();
}
}
void
-Route::set_listen (bool yn, void* src)
+Route::set_listen (bool yn)
{
- if (_solo_safe) {
- return;
- }
-
- if (_route_group && src != _route_group && _route_group->is_active() && _route_group->is_solo()) {
- _route_group->foreach_route (boost::bind (&Route::set_listen, _1, yn, _route_group));
- return;
- }
-
if (_monitor_send) {
- if (yn != _monitor_send->active()) {
- if (yn) {
- _monitor_send->activate ();
- _mute_master->set_soloed (true);
- } else {
- _monitor_send->deactivate ();
- _mute_master->set_soloed (false);
- }
-
- listen_changed (src); /* EMIT SIGNAL */
+ if (yn) {
+ _monitor_send->activate ();
+ } else {
+ _monitor_send->deactivate ();
}
}
}
-bool
-Route::listening_via_monitor () const
-{
- if (_monitor_send) {
- return _monitor_send->active ();
- } else {
- return false;
- }
-}
-
void
-Route::set_solo_safe (bool yn, void *src)
+Route::solo_control_changed (bool, Controllable::GroupControlDisposition)
{
- if (_solo_safe != yn) {
- _solo_safe = yn;
- solo_safe_changed (src);
- }
-}
+ /* 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.
+ */
-bool
-Route::solo_safe() const
-{
- return _solo_safe;
+ if (Config->get_solo_control_is_listen_control ()) {
+ set_listen (_solo_control->self_soloed() || _solo_control->get_masters_value());
+ }
}
void
-Route::set_solo (bool yn, void *src)
+Route::push_solo_isolate_upstream (int32_t delta)
{
- if (_solo_safe) {
- DEBUG_TRACE (DEBUG::Solo, string_compose ("%1 ignore solo change due to solo-safe\n", name()));
- return;
- }
+ /* forward propagate solo-isolate status to everything fed by this route, but not those via sends only */
- if (_route_group && src != _route_group && _route_group->is_active() && _route_group->is_solo()) {
- _route_group->foreach_route (boost::bind (&Route::set_solo, _1, yn, _route_group));
- return;
- }
+ boost::shared_ptr<RouteList> routes = _session.get_routes ();
+ for (RouteList::iterator i = routes->begin(); i != routes->end(); ++i) {
+
+ if ((*i).get() == this || !(*i)->can_solo()) {
+ continue;
+ }
- 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()));
+ bool sends_only;
+ bool does_feed = feeds (*i, &sends_only);
- if (self_soloed() != yn) {
- set_self_solo (yn);
- set_mute_master_solo ();
- solo_changed (true, src); /* EMIT SIGNAL */
- _solo_control->Changed (); /* EMIT SIGNAL */
+ if (does_feed && !sends_only) {
+ (*i)->solo_isolate_control()->mod_solo_isolated_by_upstream (delta);
+ }
}
}
void
-Route::set_self_solo (bool yn)
+Route::push_solo_upstream (int delta)
{
- DEBUG_TRACE (DEBUG::Solo, string_compose ("%1: set SELF solo => %2\n", name(), yn));
- _self_solo = yn;
+ 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<Route> sr (i->r.lock());
+ if (sr) {
+ sr->solo_control()->mod_solo_by_others_downstream (-delta);
+ }
+ }
}
-void
-Route::mod_solo_by_others_upstream (int32_t delta)
+#if 0
+static void
+dump_processors(const string& name, const list<boost::shared_ptr<Processor> >& procs)
{
- if (_solo_safe) {
- DEBUG_TRACE (DEBUG::Solo, string_compose ("%1 ignore solo-by-upstream due to solo-safe\n", name()));
- return;
+ cerr << name << " {" << endl;
+ for (list<boost::shared_ptr<Processor> >::const_iterator p = procs.begin();
+ p != procs.end(); ++p) {
+ cerr << "\t" << (*p)->name() << " ID = " << (*p)->id() << " @ " << (*p) << endl;
}
+ cerr << "}" << endl;
+}
+#endif
- 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));
+/** Supposing that we want to insert a Processor at a given Placement, return
+ * the processor to add the new one before (or 0 to add at the end).
+ */
+boost::shared_ptr<Processor>
+Route::before_processor_for_placement (Placement p)
+{
+ Glib::Threads::RWLock::ReaderLock lm (_processor_lock);
- uint32_t old_sbu = _soloed_by_others_upstream;
+ ProcessorList::iterator loc;
- if (delta < 0) {
- if (_soloed_by_others_upstream >= (uint32_t) abs (delta)) {
- _soloed_by_others_upstream += delta;
- } else {
- _soloed_by_others_upstream = 0;
- }
+ if (p == PreFader) {
+ /* generic pre-fader: insert immediately before the amp */
+ loc = find (_processors.begin(), _processors.end(), _amp);
} 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, "\t ... INVERT push\n");
- for (FedBy::iterator i = _fed_by.begin(); i != _fed_by.end(); ++i) {
- boost::shared_ptr<Route> sr = i->r.lock();
- if (sr) {
- sr->mod_solo_by_others_downstream (-delta);
- }
- }
- }
+ /* generic post-fader: insert right before the main outs */
+ loc = find (_processors.begin(), _processors.end(), _main_outs);
}
- set_mute_master_solo ();
- solo_changed (false, this);
+ return loc != _processors.end() ? *loc : boost::shared_ptr<Processor> ();
}
-void
-Route::mod_solo_by_others_downstream (int32_t delta)
+/** Supposing that we want to insert a Processor at a given index, return
+ * the processor to add the new one before (or 0 to add at the end).
+ */
+boost::shared_ptr<Processor>
+Route::before_processor_for_index (int index)
{
- if (_solo_safe) {
- DEBUG_TRACE (DEBUG::Solo, string_compose ("%1 ignore solo-by-downstream due to solo safe\n", name()));
- return;
+ if (index == -1) {
+ return boost::shared_ptr<Processor> ();
}
- 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));
+ Glib::Threads::RWLock::ReaderLock lm (_processor_lock);
- if (delta < 0) {
- if (_soloed_by_others_downstream >= (uint32_t) abs (delta)) {
- _soloed_by_others_downstream += delta;
- } else {
- _soloed_by_others_downstream = 0;
+ ProcessorList::iterator i = _processors.begin ();
+ int j = 0;
+ while (i != _processors.end() && j < index) {
+ if ((*i)->display_to_user()) {
+ ++j;
}
- } else {
- _soloed_by_others_downstream += delta;
- }
- DEBUG_TRACE (DEBUG::Solo, string_compose ("%1 SbD delta %2 = %3\n", name(), delta, _soloed_by_others_downstream));
+ ++i;
+ }
- set_mute_master_solo ();
- solo_changed (false, this);
+ return (i != _processors.end() ? *i : boost::shared_ptr<Processor> ());
}
-void
-Route::set_mute_master_solo ()
+/** Add a processor either pre- or post-fader
+ * @return 0 on success, non-0 on failure.
+ */
+int
+Route::add_processor (boost::shared_ptr<Processor> processor, Placement placement, ProcessorStreams* err, bool activation_allowed)
{
- _mute_master->set_soloed (self_soloed() || soloed_by_others_downstream() || soloed_by_others_upstream());
-}
-
-void
-Route::set_solo_isolated (bool yn, void *src)
-{
- 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));
- return;
- }
-
- /* forward propagate solo-isolate status to everything fed by this route, but not those via sends only */
-
- boost::shared_ptr<RouteList> 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()) {
- continue;
- }
-
- bool sends_only;
- bool does_feed = direct_feeds_according_to_graph (*i, &sends_only); // we will recurse anyway, so don't use ::feeds()
-
- if (does_feed && !sends_only) {
- (*i)->set_solo_isolated (yn, (*i)->route_group());
- }
- }
-
- /* XXX should we back-propagate as well? (April 2010: myself and chris goddard think not) */
-
- bool changed = false;
-
- if (yn) {
- if (_solo_isolated == 0) {
- _mute_master->set_solo_ignore (true);
- changed = true;
- }
- _solo_isolated++;
- } else {
- if (_solo_isolated > 0) {
- _solo_isolated--;
- if (_solo_isolated == 0) {
- _mute_master->set_solo_ignore (false);
- changed = true;
- }
- }
- }
-
- if (changed) {
- solo_isolated_changed (src);
- }
-}
-
-bool
-Route::solo_isolated () const
-{
- return _solo_isolated > 0;
-}
-
-void
-Route::set_mute_points (MuteMaster::MutePoint mp)
-{
- _mute_master->set_mute_points (mp);
- mute_points_changed (); /* EMIT SIGNAL */
-
- if (_mute_master->muted_by_self()) {
- mute_changed (this); /* EMIT SIGNAL */
- _mute_control->Changed (); /* EMIT SIGNAL */
- }
-}
-
-void
-Route::set_mute (bool yn, void *src)
-{
- 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));
- 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 (src); /* EMIT SIGNAL */
- _mute_control->Changed (); /* EMIT SIGNAL */
- }
-}
-
-bool
-Route::muted () const
-{
- return _mute_master->muted_by_self();
-}
-
-#if 0
-static void
-dump_processors(const string& name, const list<boost::shared_ptr<Processor> >& procs)
-{
- cerr << name << " {" << endl;
- for (list<boost::shared_ptr<Processor> >::const_iterator p = procs.begin();
- p != procs.end(); ++p) {
- cerr << "\t" << (*p)->name() << " ID = " << (*p)->id() << " @ " << (*p) << endl;
- }
- cerr << "}" << endl;
-}
-#endif
-
-/** Supposing that we want to insert a Processor at a given Placement, return
- * the processor to add the new one before (or 0 to add at the end).
- */
-boost::shared_ptr<Processor>
-Route::before_processor_for_placement (Placement p)
-{
- Glib::Threads::RWLock::ReaderLock lm (_processor_lock);
-
- ProcessorList::iterator loc;
-
- if (p == PreFader) {
- /* generic pre-fader: insert immediately before the amp */
- loc = find (_processors.begin(), _processors.end(), _amp);
- } else {
- /* generic post-fader: insert right before the main outs */
- loc = find (_processors.begin(), _processors.end(), _main_outs);
- }
-
- return loc != _processors.end() ? *loc : boost::shared_ptr<Processor> ();
-}
-
-/** Supposing that we want to insert a Processor at a given index, return
- * the processor to add the new one before (or 0 to add at the end).
- */
-boost::shared_ptr<Processor>
-Route::before_processor_for_index (int index)
-{
- if (index == -1) {
- return boost::shared_ptr<Processor> ();
- }
-
- Glib::Threads::RWLock::ReaderLock lm (_processor_lock);
-
- ProcessorList::iterator i = _processors.begin ();
- int j = 0;
- while (i != _processors.end() && j < index) {
- if ((*i)->display_to_user()) {
- ++j;
- }
-
- ++i;
- }
-
- return (i != _processors.end() ? *i : boost::shared_ptr<Processor> ());
-}
-
-/** Add a processor either pre- or post-fader
- * @return 0 on success, non-0 on failure.
- */
-int
-Route::add_processor (boost::shared_ptr<Processor> processor, Placement placement, ProcessorStreams* err, bool activation_allowed)
-{
- return add_processor (processor, before_processor_for_placement (placement), err, activation_allowed);
+ return add_processor (processor, before_processor_for_placement (placement), err, activation_allowed);
}
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<PluginInsert> pi;
- boost::shared_ptr<PortInsert> porti;
-
- if (processor == _amp) {
- /* Ensure that only one amp is in the list at any time */
- ProcessorList::iterator check = find (_processors.begin(), _processors.end(), processor);
- if (check != _processors.end()) {
- if (before == _amp) {
- /* Already in position; all is well */
- return 0;
- } else {
- _processors.erase (check);
- }
- }
- }
-
- assert (find (_processors.begin(), _processors.end(), processor) == _processors.end ());
-
- ProcessorList::iterator loc;
- if (before) {
- /* inserting before a processor; find it */
- loc = find (_processors.begin(), _processors.end(), before);
- if (loc == _processors.end ()) {
- /* Not found */
- return 1;
- }
- } else {
- /* inserting at end */
- loc = _processors.end ();
- }
-
- _processors.insert (loc, processor);
- processor->set_owner (this);
-
- // Set up processor list channels. This will set processor->[input|output]_streams(),
- // configure redirect ports properly, etc.
-
- {
- if (configure_processors_unlocked (err)) {
- pstate.restore ();
- configure_processors_unlocked (0); // it worked before we tried to add it ...
- return -1;
- }
- }
-
- if ((pi = boost::dynamic_pointer_cast<PluginInsert>(processor)) != 0) {
-
- if (pi->has_no_inputs ()) {
- /* generator plugin */
- _have_internal_generator = true;
- }
-
- }
-
- if (activation_allowed && !_session.get_disable_all_loaded_plugins()) {
- processor->activate ();
- }
+ ProcessorList pl;
- processor->ActiveChanged.connect_same_thread (*this, boost::bind (&Session::update_latency_compensation, &_session, false));
+ pl.push_back (processor);
+ int rv = add_processors (pl, before, err);
- _output->set_user_latency (0);
+ if (rv) {
+ return rv;
}
- reset_instrument_info ();
- processors_changed (RouteProcessorChange ()); /* EMIT SIGNAL */
- set_processor_positions ();
+ if (activation_allowed && (!_session.get_bypass_all_loaded_plugins () || !processor->display_to_user ())) {
+ processor->activate ();
+ }
return 0;
}
+void
+Route::processor_selfdestruct (boost::weak_ptr<Processor> wp)
+{
+ /* We cannot destruct the processor here (usually RT-thread
+ * with various locks held - in case of sends also io_locks).
+ * Queue for deletion in low-priority thread.
+ */
+ Glib::Threads::Mutex::Lock lx (selfdestruct_lock);
+ selfdestruct_sequence.push_back (wp);
+}
+
bool
Route::add_processor_from_xml_2X (const XMLNode& node, int version)
{
- const XMLProperty *prop;
+ XMLProperty const * prop;
try {
boost::shared_ptr<Processor> processor;
prop->value() == "lxvst" ||
prop->value() == "audiounit") {
- processor.reset (new PluginInsert (_session));
+ if (_session.get_disable_all_loaded_plugins ()) {
+ processor.reset (new UnknownProcessor (_session, node));
+ } else {
+ processor.reset (new PluginInsert (_session));
+ processor->set_owner (this);
+ }
} else {
return false;
}
- return (add_processor (processor, placement) == 0);
+ //A2 uses the "active" flag in the toplevel redirect node, not in the child plugin/IO
+ if (i != children.end()) {
+ if ((prop = (*i)->property (X_("active"))) != 0) {
+ if ( string_is_affirmative (prop->value()) && (!_session.get_bypass_all_loaded_plugins () || !processor->display_to_user () ) )
+ processor->activate();
+ else
+ processor->deactivate();
+ }
+ }
+
+ return (add_processor (processor, placement, 0, false) == 0);
}
catch (failed_constructor &err) {
}
}
+
+inline Route::PluginSetupOptions operator|= (Route::PluginSetupOptions& a, const Route::PluginSetupOptions& b) {
+ return a = static_cast<Route::PluginSetupOptions> (static_cast <int>(a) | static_cast<int> (b));
+}
+
+inline Route::PluginSetupOptions operator&= (Route::PluginSetupOptions& a, const Route::PluginSetupOptions& b) {
+ return a = static_cast<Route::PluginSetupOptions> (static_cast <int>(a) & static_cast<int> (b));
+}
+
int
Route::add_processors (const ProcessorList& others, boost::shared_ptr<Processor> before, ProcessorStreams* err)
{
- /* NOTE: this is intended to be used ONLY when copying
- processors from another Route. Hence the subtle
- differences between this and ::add_processor()
- */
-
ProcessorList::iterator loc;
if (before) {
loc = find(_processors.begin(), _processors.end(), before);
+ if (loc == _processors.end ()) {
+ return 1;
+ }
} else {
/* nothing specified - at end */
loc = _processors.end ();
}
- if (!_session.engine().connected()) {
+ if (!AudioEngine::instance()->connected()) {
return 1;
}
return 0;
}
+ ProcessorList to_skip;
+
+ // check if there's an instrument to replace or configure
+ for (ProcessorList::const_iterator i = others.begin(); i != others.end(); ++i) {
+ boost::shared_ptr<PluginInsert> pi;
+ if ((pi = boost::dynamic_pointer_cast<PluginInsert>(*i)) == 0) {
+ continue;
+ }
+ if (!pi->plugin ()->get_info ()->is_instrument ()) {
+ continue;
+ }
+ boost::shared_ptr<Processor> instrument = the_instrument ();
+ ChanCount in (DataType::MIDI, 1);
+ ChanCount out (DataType::AUDIO, 2); // XXX route's out?!
+
+ PluginSetupOptions flags = None;
+ if (instrument) {
+ flags |= CanReplace;
+ in = instrument->input_streams ();
+ out = instrument->output_streams ();
+ }
+ if (pi->has_output_presets (in, out)) {
+ flags |= MultiOut;
+ }
+
+ pi->set_strict_io (_strict_io);
+
+ PluginSetupOptions mask = None;
+ if (Config->get_ask_replace_instrument ()) {
+ mask |= CanReplace;
+ }
+ if (Config->get_ask_setup_instrument ()) {
+ mask |= MultiOut;
+ }
+
+ flags &= mask;
+
+ if (flags != None) {
+ boost::optional<int> rv = PluginSetup (shared_from_this (), pi, flags); /* EMIT SIGNAL */
+ switch (rv.get_value_or (0)) {
+ case 1:
+ to_skip.push_back (*i); // don't add this one;
+ break;
+ case 2:
+ replace_processor (instrument, *i, err);
+ to_skip.push_back (*i);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
{
Glib::Threads::Mutex::Lock lx (AudioEngine::instance()->process_lock ());
Glib::Threads::RWLock::WriterLock lm (_processor_lock);
if (*i == _meter) {
continue;
}
+ ProcessorList::iterator check = find (to_skip.begin(), to_skip.end(), *i);
+ if (check != to_skip.end()) {
+ continue;
+ }
boost::shared_ptr<PluginInsert> pi;
if ((pi = boost::dynamic_pointer_cast<PluginInsert>(*i)) != 0) {
- pi->set_count (1);
+ 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> send;
+ if ((send = boost::dynamic_pointer_cast<Send> (*i))) {
+ send->SelfDestruct.connect_same_thread (*this,
+ boost::bind (&Route::processor_selfdestruct, this, boost::weak_ptr<Processor> (*i)));
+ }
}
for (ProcessorList::const_iterator i = _processors.begin(); i != _processors.end(); ++i) {
placement_range(p, start, end);
for (ProcessorList::iterator i = start; i != end; ++i) {
- (*i)->deactivate ();
+ (*i)->enable (false);
}
_session.set_dirty ();
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 ();
for (ProcessorList::iterator i = start; i != end; ++i) {
if (boost::dynamic_pointer_cast<PluginInsert> (*i)) {
- (*i)->deactivate ();
+ (*i)->enable (false);
}
}
for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) {
if (boost::dynamic_pointer_cast<PluginInsert> (*i)) {
- (*i)->deactivate ();
+ (*i)->enable (false);
}
}
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);
continue;
}
- if ((*i)->get_next_ab_is_active()) {
- (*i)->activate ();
- } else {
- (*i)->deactivate ();
- }
+ (*i)->enable ((*i)->get_next_ab_is_active ());
}
}
seen_amp = true;
}
- if ((*i) == _amp || (*i) == _meter || (*i) == _main_outs || (*i) == _delayline) {
+ if ((*i) == _amp || (*i) == _meter || (*i) == _main_outs || (*i) == _delayline || (*i) == _trim) {
/* you can't remove these */
}
_processors = new_list;
- configure_processors_unlocked (&err); // this can't fail
+ configure_processors_unlocked (&err, &lm); // this can't fail
}
processor_max_streams.reset();
/* these can never be removed */
- if (processor == _amp || processor == _meter || processor == _main_outs || processor == _delayline) {
+ if (processor == _amp || processor == _meter || processor == _main_outs || processor == _delayline || processor == _trim) {
return 0;
}
run.
*/
- boost::shared_ptr<IOProcessor> iop;
+ boost::shared_ptr<IOProcessor> iop = boost::dynamic_pointer_cast<IOProcessor> (*i);
+ boost::shared_ptr<PluginInsert> pi = boost::dynamic_pointer_cast<PluginInsert>(*i);
+
+ if (pi != 0) {
+ assert (iop == 0);
+ iop = pi->sidechain();
+ }
- if ((iop = boost::dynamic_pointer_cast<IOProcessor> (*i)) != 0) {
+ if (iop != 0) {
iop->disconnect ();
}
if (!removed) {
/* what? */
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;
}
}
int
-Route::remove_processors (const ProcessorList& to_be_deleted, ProcessorStreams* err)
+Route::replace_processor (boost::shared_ptr<Processor> old, boost::shared_ptr<Processor> sub, ProcessorStreams* err)
{
- ProcessorList deleted;
+ /* 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;
+ }
- if (!_session.engine().connected()) {
+ /* I/Os are out, too */
+ if (boost::dynamic_pointer_cast<IOProcessor> (old) || boost::dynamic_pointer_cast<IOProcessor> (sub)) {
return 1;
}
- processor_max_streams.reset();
+ /* 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;
- boost::shared_ptr<Processor> processor;
+ bool replaced = false;
+ bool enable = old->enabled ();
for (i = _processors.begin(); i != _processors.end(); ) {
-
- processor = *i;
-
- /* these can never be removed */
-
- if (processor == _amp || processor == _meter || processor == _main_outs || processor == _delayline) {
+ if (*i == old) {
+ i = _processors.erase (i);
+ _processors.insert (i, sub);
+ sub->set_owner (this);
+ replaced = true;
+ break;
+ } else {
++i;
- continue;
}
+ }
- /* see if its in the list of processors to delete */
+ if (!replaced) {
+ return 1;
+ }
- if (find (to_be_deleted.begin(), to_be_deleted.end(), processor) == to_be_deleted.end()) {
- ++i;
+ if (_strict_io) {
+ boost::shared_ptr<PluginInsert> pi;
+ if ((pi = boost::dynamic_pointer_cast<PluginInsert>(sub)) != 0) {
+ pi->set_strict_io (true);
+ }
+ }
+
+ if (configure_processors_unlocked (err, &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<PluginInsert> pi;
+ if ((pi = boost::dynamic_pointer_cast<PluginInsert>(*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)
+{
+ ProcessorList deleted;
+
+ if (!_session.engine().connected()) {
+ return 1;
+ }
+
+ processor_max_streams.reset();
+
+ {
+ Glib::Threads::Mutex::Lock lx (AudioEngine::instance()->process_lock ());
+ Glib::Threads::RWLock::WriterLock lm (_processor_lock);
+ ProcessorState pstate (this);
+
+ ProcessorList::iterator i;
+ boost::shared_ptr<Processor> processor;
+
+ for (i = _processors.begin(); i != _processors.end(); ) {
+
+ processor = *i;
+
+ /* these can never be removed */
+
+ if (processor == _amp || processor == _meter || processor == _main_outs || processor == _delayline || processor == _trim) {
+ ++i;
+ continue;
+ }
+
+ /* see if its in the list of processors to delete */
+
+ if (find (to_be_deleted.begin(), to_be_deleted.end(), processor) == to_be_deleted.end()) {
+ ++i;
continue;
}
run.
*/
- boost::shared_ptr<IOProcessor> iop;
+ boost::shared_ptr<IOProcessor> iop = boost::dynamic_pointer_cast<IOProcessor>(processor);
+ boost::shared_ptr<PluginInsert> pi = boost::dynamic_pointer_cast<PluginInsert>(processor);
+ if (pi != 0) {
+ assert (iop == 0);
+ iop = pi->sidechain();
+ }
- if ((iop = boost::dynamic_pointer_cast<IOProcessor> (processor)) != 0) {
+ if (iop != 0) {
iop->disconnect ();
}
_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();
if (!_in_configure_processors) {
Glib::Threads::RWLock::WriterLock lm (_processor_lock);
- return configure_processors_unlocked (err);
+ return configure_processors_unlocked (err, &lm);
}
return 0;
for (ProcessorList::iterator p = _processors.begin(); p != _processors.end(); ++p, ++index) {
- if (boost::dynamic_pointer_cast<UnknownProcessor> (*p)) {
- DEBUG_TRACE (DEBUG::Processors, "--- CONFIGURE ABORTED due to unknown processor.\n");
- DEBUG_TRACE (DEBUG::Processors, "}\n");
- return list<pair<ChanCount, ChanCount> > ();
- }
-
if ((*p)->can_support_io_configuration(in, out)) {
+
+ if (boost::dynamic_pointer_cast<Delivery> (*p)
+ && boost::dynamic_pointer_cast<Delivery> (*p)->role() == Delivery::Main
+ && !is_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));
+
+ if (is_monitor()) {
+ // restriction for Monitor Section Processors
+ if (in.n_audio() != out.n_audio() || out.n_midi() > 0) {
+ /* do not allow to add/remove channels (for now)
+ * The Monitor follows the master-bus and has no panner (unpan)
+ * but do allow processors with midi-in to be added (e.g VSTs with control that
+ * will remain unconnected)
+ */
+ DEBUG_TRACE (DEBUG::Processors, "Monitor: Channel configuration not allowed.\n");
+ return list<pair<ChanCount, ChanCount> > ();
+ }
+ if (boost::dynamic_pointer_cast<InternalSend> (*p)) {
+ // internal sends make no sense, only feedback
+ DEBUG_TRACE (DEBUG::Processors, "Monitor: No Sends allowed.\n");
+ return list<pair<ChanCount, ChanCount> > ();
+ }
+ if (boost::dynamic_pointer_cast<PortInsert> (*p)) {
+ /* External Sends can be problematic. one can add/remove ports
+ * there signal leaves the DAW to external monitors anyway, so there's
+ * no real use for allowing them here anyway.
+ */
+ DEBUG_TRACE (DEBUG::Processors, "Monitor: No External Sends allowed.\n");
+ return list<pair<ChanCount, ChanCount> > ();
+ }
+ if (boost::dynamic_pointer_cast<Send> (*p)) {
+ // ditto
+ DEBUG_TRACE (DEBUG::Processors, "Monitor: No Sends allowed.\n");
+ return list<pair<ChanCount, ChanCount> > ();
+ }
+ }
in = out;
} else {
if (err) {
* 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());
processor_out_streams = _input->n_ports();
processor_max_streams.reset();
+ /* processor configure_io() may result in adding ports
+ * e.g. Delivery::configure_io -> ARDOUR::IO::ensure_io ()
+ *
+ * with jack2 adding ports results in a graph-order callback,
+ * which calls Session::resort_routes() and eventually
+ * Route::direct_feeds_according_to_reality()
+ * which takes a ReaderLock (_processor_lock).
+ *
+ * so we can't hold a WriterLock here until jack2 threading
+ * is fixed.
+ *
+ * NB. we still hold the process lock
+ *
+ * (ardour's own engines do call graph-order from the
+ * process-thread and hence do not have this issue; besides
+ * merely adding ports won't trigger a graph-order, only
+ * making connections does)
+ */
+ lm->release ();
+
+ // TODO check for a potential ReaderLock after ReaderLock ??
+ Glib::Threads::RWLock::ReaderLock lr (_processor_lock);
+
list< pair<ChanCount,ChanCount> >::iterator c = configuration.begin();
for (ProcessorList::iterator p = _processors.begin(); p != _processors.end(); ++p, ++c) {
- if (boost::dynamic_pointer_cast<UnknownProcessor> (*p)) {
- break;
+ 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;
}
-
- (*p)->configure_io(c->first, c->second);
processor_max_streams = ChanCount::max(processor_max_streams, c->first);
processor_max_streams = ChanCount::max(processor_max_streams, c->second);
+
+ boost::shared_ptr<IOProcessor> iop;
+ boost::shared_ptr<PluginInsert> pi;
+ if ((pi = boost::dynamic_pointer_cast<PluginInsert>(*p)) != 0) {
+ /* plugins connected via Split or Hide Match may have more channels.
+ * route/scratch buffers are needed for all of them
+ * The configuration may only be a subset (both input and output)
+ */
+ processor_max_streams = ChanCount::max(processor_max_streams, pi->required_buffers());
+ }
+ else if ((iop = boost::dynamic_pointer_cast<IOProcessor>(*p)) != 0) {
+ processor_max_streams = ChanCount::max(processor_max_streams, iop->natural_input_streams());
+ processor_max_streams = ChanCount::max(processor_max_streams, iop->natural_output_streams());
+ }
out = c->second;
if (boost::dynamic_pointer_cast<Delivery> (*p)
}
}
+ lr.release ();
+ lm->acquire ();
+
if (_meter) {
- _meter->reset_max_channels (processor_max_streams);
+ _meter->set_max_channels (processor_max_streams);
}
/* make sure we have sufficient scratch buffers to cope with the new processor
- configuration
+ configuration
*/
_session.ensure_buffers (n_process_buffers ());
if (_processors.empty()) {
return;
}
-
+
for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) {
if (!(*i)->display_to_user() || boost::dynamic_pointer_cast<Amp> (*i)) {
continue;
}
-
- if (state) {
- (*i)->activate ();
- } else {
- (*i)->deactivate ();
- }
+ (*i)->enable (state);
}
_session.set_dirty ();
}
-int
-Route::reorder_processors (const ProcessorList& new_order, ProcessorStreams* err)
+bool
+Route::processors_reorder_needs_configure (const ProcessorList& new_order)
{
- /* "new_order" is an ordered list of processors to be positioned according to "placement".
- NOTE: all processors in "new_order" MUST be marked as display_to_user(). There maybe additional
- processors in the current actual processor list that are hidden. Any visible processors
- in the current list but not in "new_order" will be assumed to be deleted.
- */
+ /* check if re-order requires re-configuration of any processors
+ * -> compare channel configuration for all processors
+ */
+ Glib::Threads::RWLock::ReaderLock lm (_processor_lock);
+ ChanCount c = input_streams ();
- {
- Glib::Threads::Mutex::Lock lx (AudioEngine::instance()->process_lock ());
- Glib::Threads::RWLock::WriterLock lm (_processor_lock);
- ProcessorState pstate (this);
+ for (ProcessorList::const_iterator j = new_order.begin(); j != new_order.end(); ++j) {
+ bool found = false;
+ if (c != (*j)->input_streams()) {
+ return true;
+ }
+ for (ProcessorList::const_iterator i = _processors.begin(); i != _processors.end(); ++i) {
+ if (*i == *j) {
+ found = true;
+ if ((*i)->input_streams() != c) {
+ return true;
+ }
+ c = (*i)->output_streams();
+ break;
+ }
+ }
+ if (!found) {
+ return true;
+ }
+ }
+ return false;
+}
- ProcessorList::iterator oiter;
- ProcessorList::const_iterator niter;
- ProcessorList as_it_will_be;
+#ifdef __clang__
+__attribute__((annotate("realtime")))
+#endif
+void
+Route::apply_processor_order (const ProcessorList& new_order)
+{
+ /* need to hold processor_lock; either read or write lock
+ * and the engine process_lock.
+ * Due to r/w lock ambiguity we can only assert the latter
+ */
+ assert (!AudioEngine::instance()->process_lock().trylock());
- oiter = _processors.begin();
- niter = new_order.begin();
- while (niter != new_order.end()) {
+ /* "new_order" is an ordered list of processors to be positioned according to "placement".
+ * NOTE: all processors in "new_order" MUST be marked as display_to_user(). There maybe additional
+ * processors in the current actual processor list that are hidden. Any visible processors
+ * in the current list but not in "new_order" will be assumed to be deleted.
+ */
- /* if the next processor in the old list is invisible (i.e. should not be in the new order)
- then append it to the temp list.
+ /* "as_it_will_be" and "_processors" are lists of shared pointers.
+ * actual memory usage is small, but insert/erase is not actually rt-safe :(
+ * (note though that ::processors_reorder_needs_configure() ensured that
+ * this function will only ever be called from the rt-thread if no processor were removed)
+ *
+ * either way, I can't proove it, but an x-run due to re-order here is less likley
+ * than an x-run-less 'ardour-silent cycle' both of which effectively "click".
+ */
- Otherwise, see if the next processor in the old list is in the new list. if not,
- its been deleted. If its there, append it to the temp list.
- */
+ ProcessorList as_it_will_be;
+ ProcessorList::iterator oiter;
+ ProcessorList::const_iterator niter;
- if (oiter == _processors.end()) {
+ oiter = _processors.begin();
+ niter = new_order.begin();
- /* no more elements in the old list, so just stick the rest of
- the new order onto the temp list.
- */
+ while (niter != new_order.end()) {
- as_it_will_be.insert (as_it_will_be.end(), niter, new_order.end());
- while (niter != new_order.end()) {
- ++niter;
- }
- break;
+ /* if the next processor in the old list is invisible (i.e. should not be in the new order)
+ then append it to the temp list.
- } else {
+ Otherwise, see if the next processor in the old list is in the new list. if not,
+ its been deleted. If its there, append it to the temp list.
+ */
- if (!(*oiter)->display_to_user()) {
+ if (oiter == _processors.end()) {
- as_it_will_be.push_back (*oiter);
+ /* no more elements in the old list, so just stick the rest of
+ the new order onto the temp list.
+ */
- } else {
+ as_it_will_be.insert (as_it_will_be.end(), niter, new_order.end());
+ while (niter != new_order.end()) {
+ ++niter;
+ }
+ break;
- /* visible processor: check that its in the new order */
+ } else {
- if (find (new_order.begin(), new_order.end(), (*oiter)) == new_order.end()) {
- /* deleted: do nothing, shared_ptr<> will clean up */
- } else {
- /* ignore this one, and add the next item from the new order instead */
- as_it_will_be.push_back (*niter);
- ++niter;
- }
- }
+ if (!(*oiter)->display_to_user()) {
- /* now remove from old order - its taken care of no matter what */
- oiter = _processors.erase (oiter);
- }
+ as_it_will_be.push_back (*oiter);
- }
+ } else {
- _processors.insert (oiter, as_it_will_be.begin(), as_it_will_be.end());
+ /* visible processor: check that its in the new order */
- /* If the meter is in a custom position, find it and make a rough note of its position */
- maybe_note_meter_position ();
+ if (find (new_order.begin(), new_order.end(), (*oiter)) == new_order.end()) {
+ /* deleted: do nothing, shared_ptr<> will clean up */
+ } else {
+ /* ignore this one, and add the next item from the new order instead */
+ as_it_will_be.push_back (*niter);
+ ++niter;
+ }
+ }
- if (configure_processors_unlocked (err)) {
- pstate.restore ();
- return -1;
+ /* now remove from old order - its taken care of no matter what */
+ oiter = _processors.erase (oiter);
}
- }
- processors_changed (RouteProcessorChange ()); /* EMIT SIGNAL */
- set_processor_positions ();
-
- return 0;
-}
+ }
+ _processors.insert (oiter, as_it_will_be.begin(), as_it_will_be.end());
-XMLNode&
-Route::get_state()
-{
- return state(true);
+ /* If the meter is in a custom position, find it and make a rough note of its position */
+ maybe_note_meter_position ();
}
-XMLNode&
-Route::get_template()
+int
+Route::reorder_processors (const ProcessorList& new_order, ProcessorStreams* err)
{
- return state(false);
-}
+ // it a change is already queued, wait for it
+ // (unless engine is stopped. apply immediately and proceed
+ while (g_atomic_int_get (&_pending_process_reorder)) {
+ if (!AudioEngine::instance()->running()) {
+ DEBUG_TRACE (DEBUG::Processors, "offline apply queued processor re-order.\n");
+ Glib::Threads::RWLock::WriterLock lm (_processor_lock);
-XMLNode&
-Route::state(bool full_state)
-{
- XMLNode *node = new XMLNode("Route");
- ProcessorList::iterator i;
- char buf[32];
+ apply_processor_order(_pending_processor_order);
+ setup_invisible_processors ();
- id().print (buf, sizeof (buf));
- node->add_property("id", buf);
- node->add_property ("name", _name);
- node->add_property("default-type", _default_type.to_string());
+ g_atomic_int_set (&_pending_process_reorder, 0);
- if (_flags) {
- node->add_property("flags", enum_2_string (_flags));
+ processors_changed (RouteProcessorChange ()); /* EMIT SIGNAL */
+ set_processor_positions ();
+ } else {
+ // TODO rather use a semaphore or something.
+ // but since ::reorder_processors() is called
+ // from the GUI thread, this is fine..
+ Glib::usleep(500);
+ }
}
- 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));
+ if (processors_reorder_needs_configure (new_order) || !AudioEngine::instance()->running()) {
- node->add_property("meter-type", enum_2_string (_meter_type));
+ Glib::Threads::Mutex::Lock lx (AudioEngine::instance()->process_lock ());
+ Glib::Threads::RWLock::WriterLock lm (_processor_lock);
+ ProcessorState pstate (this);
- if (_route_group) {
- node->add_property("route-group", _route_group->name());
- }
+ apply_processor_order (new_order);
- 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");
+ if (configure_processors_unlocked (err, &lm)) {
+ pstate.restore ();
+ return -1;
+ }
- 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 ());
+ lm.release();
+ lx.release();
- 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);
+ processors_changed (RouteProcessorChange ()); /* EMIT SIGNAL */
+ set_processor_positions ();
- if (_comment.length()) {
- XMLNode *cmt = node->add_child ("Comment");
- cmt->add_content (_comment);
+ } 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);
}
- if (_pannable) {
- node->add_child_nocopy (_pannable->state (full_state));
+ return 0;
+}
+
+bool
+Route::add_remove_sidechain (boost::shared_ptr<Processor> proc, bool add)
+{
+ boost::shared_ptr<PluginInsert> pi;
+ if ((pi = boost::dynamic_pointer_cast<PluginInsert>(proc)) == 0) {
+ return false;
}
- for (i = _processors.begin(); i != _processors.end(); ++i) {
- if (!full_state) {
- /* template save: do not include internal sends functioning as
- aux sends because the chance of the target ID
- in the session where this template is used
- is not very likely.
-
- similarly, do not save listen sends which connect to
- the monitor section, because these will always be
- added if necessary.
- */
- boost::shared_ptr<InternalSend> is;
+ if (pi->has_sidechain () == add) {
+ return true; // ?? call failed, but result is as expected.
+ }
- if ((is = boost::dynamic_pointer_cast<InternalSend> (*i)) != 0) {
- if (is->role() == Delivery::Listen) {
- continue;
- }
- }
+ {
+ Glib::Threads::RWLock::ReaderLock lm (_processor_lock);
+ ProcessorList::iterator i = find (_processors.begin(), _processors.end(), proc);
+ if (i == _processors.end ()) {
+ return false;
}
- node->add_child_nocopy((*i)->state (full_state));
}
- if (_extra_xml) {
- node->add_child_copy (*_extra_xml);
- }
+ {
+ Glib::Threads::Mutex::Lock lx (AudioEngine::instance()->process_lock ()); // take before Writerlock to avoid deadlock
+ Glib::Threads::RWLock::WriterLock lm (_processor_lock);
+ PBD::Unwinder<bool> uw (_in_sidechain_setup, true);
- if (_custom_meter_position_noted) {
- boost::shared_ptr<Processor> after = _processor_after_last_custom_meter.lock ();
- if (after) {
- after->id().print (buf, sizeof (buf));
- node->add_property (X_("processor-after-last-custom-meter"), buf);
+ lx.release (); // IO::add_port() and ~IO takes process lock - XXX check if this is safe
+ if (add) {
+ if (!pi->add_sidechain ()) {
+ return false;
+ }
+ } else {
+ if (!pi->del_sidechain ()) {
+ return false;
+ }
+ }
+
+ lx.acquire ();
+ list<pair<ChanCount, ChanCount> > c = try_configure_processors_unlocked (n_inputs (), 0);
+ lx.release ();
+
+ if (c.empty()) {
+ if (add) {
+ pi->del_sidechain ();
+ } else {
+ pi->add_sidechain ();
+ // TODO restore side-chain's state.
+ }
+ return false;
}
+ lx.acquire ();
+ configure_processors_unlocked (0, &lm);
+ }
- node->add_property (X_("last-custom-meter-was-at-end"), _last_custom_meter_was_at_end ? "yes" : "no");
+ if (pi->has_sidechain ()) {
+ pi->sidechain_input ()->changed.connect_same_thread (*this, boost::bind (&Route::sidechain_change_handler, this, _1, _2));
}
- return *node;
+ processors_changed (RouteProcessorChange ()); /* EMIT SIGNAL */
+ _session.set_dirty ();
+ return true;
}
-int
-Route::set_state (const XMLNode& node, int version)
+bool
+Route::plugin_preset_output (boost::shared_ptr<Processor> proc, ChanCount outs)
{
- if (version < 3000) {
- return set_state_2X (node, version);
+ boost::shared_ptr<PluginInsert> pi;
+ if ((pi = boost::dynamic_pointer_cast<PluginInsert>(proc)) == 0) {
+ return false;
}
- XMLNodeList nlist;
+ {
+ Glib::Threads::RWLock::ReaderLock lm (_processor_lock);
+ ProcessorList::iterator i = find (_processors.begin(), _processors.end(), proc);
+ if (i == _processors.end ()) {
+ return false;
+ }
+ }
+
+ {
+ Glib::Threads::Mutex::Lock lx (AudioEngine::instance()->process_lock ());
+ Glib::Threads::RWLock::WriterLock lm (_processor_lock);
+
+ const ChanCount& old (pi->preset_out ());
+ if (!pi->set_preset_out (outs)) {
+ return true; // no change, OK
+ }
+
+ list<pair<ChanCount, ChanCount> > c = try_configure_processors_unlocked (n_inputs (), 0);
+ if (c.empty()) {
+ /* not possible */
+ pi->set_preset_out (old);
+ return false;
+ }
+ configure_processors_unlocked (0, &lm);
+ }
+
+ processors_changed (RouteProcessorChange ()); /* EMIT SIGNAL */
+ _session.set_dirty ();
+ return true;
+}
+
+bool
+Route::reset_plugin_insert (boost::shared_ptr<Processor> proc)
+{
+ ChanCount unused;
+ return customize_plugin_insert (proc, 0, unused, unused);
+}
+
+bool
+Route::customize_plugin_insert (boost::shared_ptr<Processor> proc, uint32_t count, ChanCount outs, ChanCount sinks)
+{
+ boost::shared_ptr<PluginInsert> pi;
+ if ((pi = boost::dynamic_pointer_cast<PluginInsert>(proc)) == 0) {
+ 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<pair<ChanCount, ChanCount> > 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<PluginInsert> pi;
+ if ((pi = boost::dynamic_pointer_cast<PluginInsert>(*p)) != 0) {
+ pi->set_strict_io (_strict_io);
+ }
+ }
+
+ list<pair<ChanCount, ChanCount> > c = try_configure_processors_unlocked (n_inputs (), 0);
+
+ if (c.empty()) {
+ // not possible
+ _strict_io = !enable; // restore old value
+ for (ProcessorList::iterator p = _processors.begin(); p != _processors.end(); ++p) {
+ boost::shared_ptr<PluginInsert> pi;
+ if ((pi = boost::dynamic_pointer_cast<PluginInsert>(*p)) != 0) {
+ pi->set_strict_io (_strict_io);
+ }
+ }
+ return false;
+ }
+ lm.release ();
+
+ 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);
+
+ node->add_child_nocopy (_presentation_info.get_state());
+
+ node->add_property("active", _active?"yes":"no");
+ string p;
+ node->add_property("denormal-protection", _denormal_protection?"yes":"no");
+ node->add_property("meter-point", enum_2_string (_meter_point));
+
+ node->add_property("meter-type", enum_2_string (_meter_type));
+
+ if (_route_group) {
+ node->add_property("route-group", _route_group->name());
+ }
+
+ 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 (_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 ());
+ }
+
+ if (_comment.length()) {
+ XMLNode *cmt = node->add_child ("Comment");
+ cmt->add_content (_comment);
+ }
+
+ if (_pannable) {
+ node->add_child_nocopy (_pannable->state (full_state));
+ }
+
+ {
+ Glib::Threads::RWLock::ReaderLock lm (_processor_lock);
+ for (i = _processors.begin(); i != _processors.end(); ++i) {
+ if (!full_state) {
+ /* template save: do not include internal sends functioning as
+ aux sends because the chance of the target ID
+ in the session where this template is used
+ is not very likely.
+
+ similarly, do not save listen sends which connect to
+ the monitor section, because these will always be
+ added if necessary.
+ */
+ boost::shared_ptr<InternalSend> is;
+
+ if ((is = boost::dynamic_pointer_cast<InternalSend> (*i)) != 0) {
+ if (is->role() == Delivery::Listen) {
+ continue;
+ }
+ }
+ }
+ node->add_child_nocopy((*i)->state (full_state));
+ }
+ }
+
+ if (_extra_xml) {
+ node->add_child_copy (*_extra_xml);
+ }
+
+ if (_custom_meter_position_noted) {
+ boost::shared_ptr<Processor> after = _processor_after_last_custom_meter.lock ();
+ if (after) {
+ after->id().print (buf, sizeof (buf));
+ node->add_property (X_("processor-after-last-custom-meter"), buf);
+ }
+ }
+
+ if (!_session._template_state_dir.empty()) {
+ foreach_processor (sigc::bind (sigc::mem_fun (*this, &Route::set_plugin_state_dir), ""));
+ }
+
+ node->add_child_copy (Slavable::get_state());
+
+ return *node;
+}
+
+int
+Route::set_state (const XMLNode& node, int version)
+{
+ if (version < 3000) {
+ return set_state_2X (node, 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;
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()) {
} 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);
}
}
// 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()), this);
- }
-
- if ((prop = node.property ("solo-safe")) != 0) {
- set_solo_safe (string_is_affirmative (prop->value()), this);
- }
-
- 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);
}
}
- if ((prop = node.property (X_("last-custom-meter-was-at-end"))) != 0) {
- _last_custom_meter_was_at_end = string_is_affirmative (prop->value ());
- }
-
for (niter = nlist.begin(); niter != nlist.end(); ++niter){
child = *niter;
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) {
+ set_automation_xml_state (*child, Evoral::Parameter(NullAutomation));
}
}
int
Route::set_state_2X (const XMLNode& node, int version)
{
+ LocaleGuard lg;
XMLNodeList nlist;
XMLNodeConstIterator niter;
XMLNode *child;
- const XMLProperty *prop;
+ XMLProperty const * prop;
/* 2X things which still remain to be handled:
* default-type
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, this);
- }
-
if ((prop = node.property (X_("muted"))) != 0) {
bool first = true;
_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 ();
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);
}
}
_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);
- }
-
}
}
if (prop->value() == "amp") {
_amp->set_state (**niter, Stateful::current_state_version);
new_order.push_back (_amp);
+ } else if (prop->value() == "trim") {
+ _trim->set_state (**niter, Stateful::current_state_version);
+ new_order.push_back (_trim);
} else if (prop->value() == "meter") {
_meter->set_state (**niter, Stateful::current_state_version);
new_order.push_back (_meter);
} else if (prop->value() == "delay") {
- _delayline->set_state (**niter, Stateful::current_state_version);
- new_order.push_back (_delayline);
+ if (_delayline) {
+ _delayline->set_state (**niter, Stateful::current_state_version);
+ new_order.push_back (_delayline);
+ }
} else if (prop->value() == "main-outs") {
_main_outs->set_state (**niter, Stateful::current_state_version);
} else if (prop->value() == "intreturn") {
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);
if (prop->value() == "intsend") {
- processor.reset (new InternalSend (_session, _pannable, _mute_master, boost::shared_ptr<Route>(), Delivery::Aux, true));
+ processor.reset (new InternalSend (_session, _pannable, _mute_master, boost::dynamic_pointer_cast<ARDOUR::Route>(shared_from_this()), boost::shared_ptr<Route>(), Delivery::Aux, true));
} 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") {
- processor.reset (new PluginInsert(_session));
+ if (_session.get_disable_all_loaded_plugins ()) {
+ processor.reset (new UnknownProcessor (_session, **niter));
+ } else {
+ processor.reset (new PluginInsert (_session));
+ processor->set_owner (this);
+ if (_strict_io) {
+ boost::shared_ptr<PluginInsert> pi = boost::dynamic_pointer_cast<PluginInsert>(processor);
+ pi->set_strict_io (true);
+ }
+ }
} else if (prop->value() == "port") {
processor.reset (new PortInsert (_session, _pannable, _mute_master));
} else if (prop->value() == "send") {
processor.reset (new Send (_session, _pannable, _mute_master, Delivery::Send, true));
+ boost::shared_ptr<Send> send = boost::dynamic_pointer_cast<Send> (processor);
+ send->SelfDestruct.connect_same_thread (*this,
+ boost::bind (&Route::processor_selfdestruct, this, boost::weak_ptr<Processor> (processor)));
} else {
error << string_compose(_("unknown Processor type \"%1\"; ignored"), prop->value()) << endmsg;
processor.reset (new UnknownProcessor (_session, **niter));
}
+ /* subscribe to Sidechain IO changes */
+ boost::shared_ptr<PluginInsert> pi = boost::dynamic_pointer_cast<PluginInsert> (processor);
+ if (pi && pi->has_sidechain ()) {
+ pi->sidechain_input ()->changed.connect_same_thread (*this, boost::bind (&Route::sidechain_change_handler, this, _1, _2));
+ }
+
/* we have to note the monitor send here, otherwise a new one will be created
and the state of this one will be lost.
*/
}
{
- 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) {
}
reset_instrument_info ();
- processors_changed (RouteProcessorChange ());
+ processors_changed (RouteProcessorChange ()); /* EMIT SIGNAL */
set_processor_positions ();
}
{
/* Must be called with the processor lock held */
+ const framepos_t now = _session.transport_frame ();
+
if (!_silent) {
_output->silence (nframes);
continue;
}
- (*i)->silence (nframes);
+ (*i)->silence (nframes, now);
}
if (nframes == _session.get_block_size()) {
/* make sure we have one */
if (!_monitor_send) {
- _monitor_send.reset (new InternalSend (_session, _pannable, _mute_master, _session.monitor_out(), Delivery::Listen));
+ _monitor_send.reset (new InternalSend (_session, _pannable, _mute_master, boost::dynamic_pointer_cast<ARDOUR::Route>(shared_from_this()), _session.monitor_out(), Delivery::Listen));
_monitor_send->set_display_to_user (false);
}
{
Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock ());
boost::shared_ptr<Pannable> sendpan (new Pannable (_session));
- listener.reset (new InternalSend (_session, sendpan, _mute_master, route, Delivery::Aux));
+ listener.reset (new InternalSend (_session, sendpan, _mute_master, boost::dynamic_pointer_cast<ARDOUR::Route>(shared_from_this()), route, Delivery::Aux));
}
add_processor (listener, before);
again:
for (ProcessorList::iterator x = _processors.begin(); x != _processors.end(); ++x) {
-
+
boost::shared_ptr<InternalSend> d = boost::dynamic_pointer_cast<InternalSend>(*x);
-
+
if (d && d->target_route() == route) {
rl.release ();
- remove_processor (*x, &err, false);
+ if (remove_processor (*x, &err, false) > 0) {
+ rl.acquire ();
+ continue;
+ }
rl.acquire ();
/* list could have been demolished while we dropped the lock
so start over.
*/
-
- goto again;
+ if (_session.engine().connected()) {
+ /* i/o processors cannot be removed if the engine is not running
+ * so don't live-loop in case the engine is N/A or dies
+ */
+ goto again;
+ }
}
}
}
Route::set_comment (string cmt, void *src)
{
_comment = cmt;
- comment_changed (src);
+ comment_changed ();
_session.set_dirty ();
}
return false;
}
+IOVector
+Route::all_inputs () const
+{
+ /* TODO, if this works as expected,
+ * cache the IOVector and maintain it via
+ * input_change_handler(), sidechain_change_handler() etc
+ */
+ IOVector ios;
+ ios.push_back (_input);
+
+ Glib::Threads::RWLock::ReaderLock lm (_processor_lock);
+ for (ProcessorList::const_iterator r = _processors.begin(); r != _processors.end(); ++r) {
+
+ boost::shared_ptr<IOProcessor> iop = boost::dynamic_pointer_cast<IOProcessor>(*r);
+ boost::shared_ptr<PluginInsert> pi = boost::dynamic_pointer_cast<PluginInsert>(*r);
+ if (pi != 0) {
+ assert (iop == 0);
+ iop = pi->sidechain();
+ }
+
+ if (iop != 0 && iop->input()) {
+ ios.push_back (iop->input());
+ }
+ }
+ return ios;
+}
+
+IOVector
+Route::all_outputs () const
+{
+ IOVector ios;
+ // _output is included via Delivery
+ Glib::Threads::RWLock::ReaderLock lm (_processor_lock);
+ for (ProcessorList::const_iterator r = _processors.begin(); r != _processors.end(); ++r) {
+ boost::shared_ptr<IOProcessor> iop = boost::dynamic_pointer_cast<IOProcessor>(*r);
+ if (iop != 0 && iop->output()) {
+ ios.push_back (iop->output());
+ }
+ }
+ return ios;
+}
+
bool
Route::direct_feeds_according_to_reality (boost::shared_ptr<Route> other, bool* via_send_only)
{
DEBUG_TRACE (DEBUG::Graph, string_compose ("Feeds? %1\n", _name));
-
- if (_output->connected_to (other->input())) {
+ if (other->all_inputs().fed_by (_output)) {
DEBUG_TRACE (DEBUG::Graph, string_compose ("\tdirect FEEDS %2\n", other->name()));
if (via_send_only) {
*via_send_only = false;
return true;
}
+ Glib::Threads::RWLock::ReaderLock lm (_processor_lock);
for (ProcessorList::iterator r = _processors.begin(); r != _processors.end(); ++r) {
- boost::shared_ptr<IOProcessor> iop;
+ boost::shared_ptr<IOProcessor> iop = boost::dynamic_pointer_cast<IOProcessor>(*r);
+ boost::shared_ptr<PluginInsert> pi = boost::dynamic_pointer_cast<PluginInsert>(*r);
+ if (pi != 0) {
+ assert (iop == 0);
+ iop = pi->sidechain();
+ }
- if ((iop = boost::dynamic_pointer_cast<IOProcessor>(*r)) != 0) {
- if (iop->feeds (other)) {
+ if (iop != 0) {
+ boost::shared_ptr<const IO> iop_out = iop->output();
+ if (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;
return _session._current_route_graph.has (shared_from_this (), other, via_send_only);
}
+bool
+Route::feeds_according_to_graph (boost::shared_ptr<Route> other)
+{
+ return _session._current_route_graph.feeds (shared_from_this (), other);
+}
+
/** Called from the (non-realtime) butler thread when the transport is stopped */
void
Route::nonrealtime_handle_transport_stopped (bool /*abort_ignored*/, bool /*did_locate*/, bool can_flush_processors)
void
Route::input_change_handler (IOChange change, void * /*src*/)
{
- bool need_to_queue_solo_change = true;
-
if ((change.type & IOChange::ConfigurationChanged)) {
- /* This is called with the process lock held if change
- contains ConfigurationChanged
+ /* This is called with the process lock held if change
+ contains ConfigurationChanged
*/
- need_to_queue_solo_change = false;
configure_processors (0);
- _phase_invert.resize (_input->n_ports().n_audio ());
+ _phase_control->resize (_input->n_ports().n_audio ());
io_changed (); /* EMIT SIGNAL */
}
- if (!_input->connected() && _soloed_by_others_upstream) {
- if (need_to_queue_solo_change) {
- _session.cancel_solo_after_disconnect (shared_from_this(), true);
- } else {
- cancel_solo_after_disconnect (true);
+ if (_solo_control->soloed_by_others_upstream() || _solo_isolate_control->solo_isolated_by_upstream()) {
+ int sbou = 0;
+ int ibou = 0;
+ boost::shared_ptr<RouteList> routes = _session.get_routes ();
+ if (_input->connected()) {
+ for (RouteList::iterator i = routes->begin(); i != routes->end(); ++i) {
+ if ((*i).get() == this || (*i)->is_master() || (*i)->is_monitor() || (*i)->is_auditioner()) {
+ continue;
+ }
+ bool sends_only;
+ bool does_feed = (*i)->direct_feeds_according_to_reality (shared_from_this(), &sends_only);
+ if (does_feed && !sends_only) {
+ if ((*i)->soloed()) {
+ ++sbou;
+ }
+ if ((*i)->solo_isolate_control()->solo_isolated()) {
+ ++ibou;
+ }
+ }
+ }
+ }
+
+ 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_isolate_control->solo_isolated_by_upstream(), idelta)
+ << endmsg;
+
+ }
+
+ if (_solo_control->soloed_by_others_upstream()) {
+ // ignore new connections (they're not propagated)
+ if (delta <= 0) {
+ _solo_control->mod_solo_by_others_upstream (delta);
+ }
+ }
+
+ if (_solo_isolate_control->solo_isolated_by_upstream()) {
+ // solo-isolate currently only propagates downstream
+ if (idelta < 0) {
+ _solo_isolate_control->mod_solo_isolated_by_upstream (1);
+ }
+ //_solo_isolated_by_upstream = ibou;
+ }
+
+ // Session::route_solo_changed does not propagate indirect solo-changes
+ // propagate downstream 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()) {
+ continue;
+ }
+ bool sends_only;
+ bool does_feed = feeds (*i, &sends_only);
+ if (delta <= 0 && does_feed && !sends_only) {
+ (*i)->solo_control()->mod_solo_by_others_upstream (delta);
+ }
+
+ if (idelta < 0 && does_feed && !sends_only) {
+ (*i)->solo_isolate_control()->mod_solo_isolated_by_upstream (-1);
+ }
}
}
}
void
Route::output_change_handler (IOChange change, void * /*src*/)
{
- bool need_to_queue_solo_change = true;
if (_initial_io_setup) {
return;
}
if ((change.type & IOChange::ConfigurationChanged)) {
- /* This is called with the process lock held if change
- contains ConfigurationChanged
+ /* This is called with the process lock held if change
+ contains ConfigurationChanged
*/
- need_to_queue_solo_change = false;
configure_processors (0);
+
+ if (is_master()) {
+ _session.reset_monitor_section();
+ }
+
io_changed (); /* EMIT SIGNAL */
}
- if (!_output->connected() && _soloed_by_others_downstream) {
- if (need_to_queue_solo_change) {
- _session.cancel_solo_after_disconnect (shared_from_this(), false);
- } else {
- cancel_solo_after_disconnect (false);
+ 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,
+ * ideally the input_change_handler() of the other route
+ * would propagate the change to us.
+ */
+ boost::shared_ptr<RouteList> routes = _session.get_routes ();
+ if (_output->connected()) {
+ for (RouteList::iterator i = routes->begin(); i != routes->end(); ++i) {
+ if ((*i).get() == this || (*i)->is_master() || (*i)->is_monitor() || (*i)->is_auditioner()) {
+ continue;
+ }
+ bool sends_only;
+ bool does_feed = direct_feeds_according_to_reality (*i, &sends_only);
+ if (does_feed && !sends_only) {
+ if ((*i)->soloed()) {
+ ++sbod;
+ break;
+ }
+ }
+ }
+ }
+ int delta = sbod - _solo_control->soloed_by_others_downstream();
+ if (delta <= 0) {
+ // do not allow new connections to change implicit solo (no propagation)
+ _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 || !can_solo()) {
+ continue;
+ }
+ bool sends_only;
+ bool does_feed = (*i)->feeds (shared_from_this(), &sends_only);
+ if (delta != 0 && does_feed && !sends_only) {
+ (*i)->solo_control()->mod_solo_by_others_downstream (delta);
+ }
+ }
+
}
}
}
void
-Route::cancel_solo_after_disconnect (bool upstream)
+Route::sidechain_change_handler (IOChange change, void* src)
{
- if (upstream) {
- _soloed_by_others_upstream = 0;
- } else {
- _soloed_by_others_downstream = 0;
+ if (_initial_io_setup || _in_sidechain_setup) {
+ return;
}
- set_mute_master_solo ();
- solo_changed (false, this);
+
+ input_change_handler (change, src);
}
uint32_t
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<Delivery> d = boost::dynamic_pointer_cast<Delivery> (*i);
+ if (d) {
+ d->flush_buffers (nframes);
+ } else {
+ boost::shared_ptr<PortInsert> p = boost::dynamic_pointer_cast<PortInsert> (*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)
{
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;
}
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;
}
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;
}
}
}
+#ifdef __clang__
+__attribute__((annotate("realtime")))
+#endif
+bool
+Route::apply_processor_changes_rt ()
+{
+ int emissions = EmitNone;
+
+ if (_pending_meter_point != _meter_point) {
+ Glib::Threads::RWLock::WriterLock pwl (_processor_lock, Glib::Threads::TRY_LOCK);
+ if (pwl.locked()) {
+ /* meters always have buffers for 'processor_max_streams'
+ * they can be re-positioned without re-allocation */
+ if (set_meter_point_unlocked()) {
+ emissions |= EmitMeterChanged | EmitMeterVisibilityChange;;
+ } else {
+ emissions |= EmitMeterChanged;
+ }
+ }
+ }
+
+ bool changed = false;
+
+ if (g_atomic_int_get (&_pending_process_reorder)) {
+ Glib::Threads::RWLock::WriterLock pwl (_processor_lock, Glib::Threads::TRY_LOCK);
+ if (pwl.locked()) {
+ apply_processor_order (_pending_processor_order);
+ setup_invisible_processors ();
+ changed = true;
+ g_atomic_int_set (&_pending_process_reorder, 0);
+ emissions |= EmitRtProcessorChange;
+ }
+ }
+ if (changed) {
+ set_processor_positions ();
+ }
+ if (emissions != 0) {
+ g_atomic_int_set (&_pending_signals, emissions);
+ return true;
+ }
+ 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();
+ meter_change (); /* EMIT SIGNAL */
+ if (sig & EmitMeterVisibilityChange) {
+ processors_changed (RouteProcessorChange (RouteProcessorChange::MeterPointChange, true)); /* EMIT SIGNAL */
+ } else {
+ processors_changed (RouteProcessorChange (RouteProcessorChange::MeterPointChange, false)); /* EMIT SIGNAL */
+ }
+ }
+ if (sig & EmitRtProcessorChange) {
+ processors_changed (RouteProcessorChange (RouteProcessorChange::RealTimeChange)); /* EMIT SIGNAL */
+ }
+
+ /* this would be a job for the butler.
+ * Conceptually we should not take processe/processor locks here.
+ * OTOH its more efficient (less overhead for summoning the butler and
+ * telling her what do do) and signal emission is called
+ * directly after the process callback, which decreases the chance
+ * of x-runs when taking the locks.
+ */
+ while (!selfdestruct_sequence.empty ()) {
+ Glib::Threads::Mutex::Lock lx (selfdestruct_lock);
+ if (selfdestruct_sequence.empty ()) { break; } // re-check with lock
+ boost::shared_ptr<Processor> proc = selfdestruct_sequence.back ().lock ();
+ selfdestruct_sequence.pop_back ();
+ lx.release ();
+ if (proc) {
+ remove_processor (proc);
+ }
+ }
+}
+
void
Route::set_meter_point (MeterPoint p, bool force)
{
- if (_meter_point == p && !force) {
+ if (_pending_meter_point == p && !force) {
return;
}
- bool meter_was_visible_to_user = _meter->display_to_user ();
-
- {
+ if (force || !AudioEngine::instance()->running()) {
Glib::Threads::Mutex::Lock lx (AudioEngine::instance()->process_lock ());
Glib::Threads::RWLock::WriterLock lm (_processor_lock);
+ _pending_meter_point = p;
+ _meter->emit_configuration_changed();
+ meter_change (); /* EMIT SIGNAL */
+ if (set_meter_point_unlocked()) {
+ processors_changed (RouteProcessorChange (RouteProcessorChange::MeterPointChange, true)); /* EMIT SIGNAL */
+ } else {
+ processors_changed (RouteProcessorChange (RouteProcessorChange::MeterPointChange, false)); /* EMIT SIGNAL */
+ }
+ } else {
+ _pending_meter_point = p;
+ }
+}
- maybe_note_meter_position ();
- _meter_point = p;
+#ifdef __clang__
+__attribute__((annotate("realtime")))
+#endif
+bool
+Route::set_meter_point_unlocked ()
+{
+#ifndef NDEBUG
+ /* Caller must hold process and processor write lock */
+ assert (!AudioEngine::instance()->process_lock().trylock());
+ Glib::Threads::RWLock::WriterLock lm (_processor_lock, Glib::Threads::TRY_LOCK);
+ assert (!lm.locked ());
+#endif
- if (_meter_point != MeterCustom) {
+ _meter_point = _pending_meter_point;
- _meter->set_display_to_user (false);
+ bool meter_was_visible_to_user = _meter->display_to_user ();
- setup_invisible_processors ();
+ if (!_custom_meter_position_noted) {
+ maybe_note_meter_position ();
+ }
- } else {
+ if (_meter_point != MeterCustom) {
- _meter->set_display_to_user (true);
-
- /* If we have a previous position for the custom meter, try to put it there */
- if (_custom_meter_position_noted) {
- boost::shared_ptr<Processor> after = _processor_after_last_custom_meter.lock ();
-
- if (after) {
- ProcessorList::iterator i = find (_processors.begin(), _processors.end(), after);
- if (i != _processors.end ()) {
- _processors.remove (_meter);
- _processors.insert (i, _meter);
- }
- } else if (_last_custom_meter_was_at_end) {
- _processors.remove (_meter);
- _processors.push_back (_meter);
- }
+ _meter->set_display_to_user (false);
+
+ setup_invisible_processors ();
+
+ } else {
+ _meter->set_display_to_user (true);
+
+ /* If we have a previous position for the custom meter, try to put it there */
+ boost::shared_ptr<Processor> after = _processor_after_last_custom_meter.lock ();
+ if (after) {
+ ProcessorList::iterator i = find (_processors.begin(), _processors.end(), after);
+ if (i != _processors.end ()) {
+ _processors.remove (_meter);
+ _processors.insert (i, _meter);
}
+ } else {// at end, right before the mains_out/panner
+ _processors.remove (_meter);
+ ProcessorList::iterator main = _processors.end();
+ _processors.insert (--main, _meter);
}
+ }
- /* Set up the meter for its new position */
+ /* Set up the meter for its new position */
- ProcessorList::iterator loc = find (_processors.begin(), _processors.end(), _meter);
-
- ChanCount m_in;
-
- if (loc == _processors.begin()) {
- m_in = _input->n_ports();
- } else {
- ProcessorList::iterator before = loc;
- --before;
- m_in = (*before)->output_streams ();
- }
-
- _meter->reflect_inputs (m_in);
-
- /* we do not need to reconfigure the processors, because the meter
- (a) is always ready to handle processor_max_streams
- (b) is always an N-in/N-out processor, and thus moving
- it doesn't require any changes to the other processors.
- */
+ ProcessorList::iterator loc = find (_processors.begin(), _processors.end(), _meter);
+
+ ChanCount m_in;
+
+ if (loc == _processors.begin()) {
+ m_in = _input->n_ports();
+ } else {
+ ProcessorList::iterator before = loc;
+ --before;
+ m_in = (*before)->output_streams ();
}
- meter_change (); /* EMIT SIGNAL */
+ _meter->reflect_inputs (m_in);
- bool const meter_visibly_changed = (_meter->display_to_user() != meter_was_visible_to_user);
+ /* we do not need to reconfigure the processors, because the meter
+ (a) is always ready to handle processor_max_streams
+ (b) is always an N-in/N-out processor, and thus moving
+ it doesn't require any changes to the other processors.
+ */
- processors_changed (RouteProcessorChange (RouteProcessorChange::MeterPointChange, meter_visibly_changed)); /* EMIT SIGNAL */
+ /* these should really be done after releasing the lock
+ * but all those signals are subscribed to with gui_thread()
+ * so we're safe.
+ */
+ return (_meter->display_to_user() != meter_was_visible_to_user);
}
void
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;
}
}
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);
}
framecnt_t l = _output->user_latency();
framecnt_t lamp = 0;
bool before_amp = true;
+ framecnt_t ltrim = 0;
+ bool before_trim = true;
for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) {
if ((*i)->active ()) {
if ((*i) == _amp) {
before_amp = false;
}
+ if ((*i) == _trim) {
+ before_amp = false;
+ }
if (before_amp) {
lamp = l;
}
+ if (before_trim) {
+ lamp = l;
+ }
}
DEBUG_TRACE (DEBUG::Latency, string_compose ("%1: internal signal latency = %2\n", _name, l));
+ // TODO: (lamp - _signal_latency) to sync to output (read-ahed), currently _roll_delay shifts this around
_signal_latency_at_amp_position = lamp;
+ _signal_latency_at_trim_position = ltrim;
+
if (_signal_latency != l) {
_signal_latency = l;
signal_latency_changed (); /* EMIT SIGNAL */
}
}
-Route::SoloControllable::SoloControllable (std::string name, boost::shared_ptr<Route> r)
- : AutomationControl (r->session(), Evoral::Parameter (SoloAutomation),
- boost::shared_ptr<AutomationList>(), name)
- , _route (r)
-{
- boost::shared_ptr<AutomationList> gl(new AutomationList(Evoral::Parameter(SoloAutomation)));
- set_list (gl);
-}
-
-void
-Route::SoloControllable::set_value (double val)
-{
- bool bval = ((val >= 0.5f) ? true: false);
-
- boost::shared_ptr<RouteList> rl (new RouteList);
-
- boost::shared_ptr<Route> r = _route.lock ();
- if (!r) {
- return;
- }
-
- rl->push_back (r);
-
- if (Config->get_solo_control_is_listen_control()) {
- _session.set_listen (rl, bval);
- } else {
- _session.set_solo (rl, bval);
- }
-}
-
-double
-Route::SoloControllable::get_value () const
-{
- boost::shared_ptr<Route> r = _route.lock ();
- if (!r) {
- return 0;
- }
-
- if (Config->get_solo_control_is_listen_control()) {
- return r->listening_via_monitor() ? 1.0f : 0.0f;
- } else {
- return r->self_soloed() ? 1.0f : 0.0f;
- }
-}
-
-Route::MuteControllable::MuteControllable (std::string name, boost::shared_ptr<Route> r)
- : AutomationControl (r->session(), Evoral::Parameter (MuteAutomation),
- boost::shared_ptr<AutomationList>(), name)
- , _route (r)
-{
- boost::shared_ptr<AutomationList> gl(new AutomationList(Evoral::Parameter(MuteAutomation)));
- set_list (gl);
-}
-
-void
-Route::MuteControllable::set_value (double val)
-{
- bool bval = ((val >= 0.5f) ? true: false);
-
- boost::shared_ptr<RouteList> rl (new RouteList);
-
- boost::shared_ptr<Route> r = _route.lock ();
- if (!r) {
- return;
- }
-
- rl->push_back (r);
- _session.set_mute (rl, bval);
-}
-
-double
-Route::MuteControllable::get_value () const
-{
- boost::shared_ptr<Route> r = _route.lock ();
- if (!r) {
- return 0;
- }
-
- return r->muted() ? 1.0f : 0.0f;
-}
-
void
Route::set_block_size (pframes_t nframes)
{
_session.add_command (new MementoCommand<AutomationList> (*gc->alist().get(), &before, &after));
}
+ /* gain automation */
+ {
+ boost::shared_ptr<AutomationControl> gc = _trim->gain_control();
+
+ XMLNode &before = gc->alist()->get_state ();
+ gc->alist()->shift (pos, frames);
+ XMLNode &after = gc->alist()->get_state ();
+ _session.add_command (new MementoCommand<AutomationList> (*gc->alist().get(), &before, &after));
+ }
+
+ // TODO mute automation ??
+
/* pan automation */
if (_pannable) {
ControlSet::Controls& c (_pannable->controls());
}
}
+void
+Route::set_plugin_state_dir (boost::weak_ptr<Processor> p, const std::string& d)
+{
+ boost::shared_ptr<Processor> processor (p.lock ());
+ boost::shared_ptr<PluginInsert> pi = boost::dynamic_pointer_cast<PluginInsert> (processor);
+ if (!pi) {
+ return;
+ }
+ pi->set_state_dir (d);
+}
int
Route::save_as_template (const string& path, const string& name)
{
+ std::string state_dir = path.substr (0, path.find_last_of ('.')); // strip template_suffix
+ PBD::Unwinder<std::string> uw (_session._template_state_dir, state_dir);
+
XMLNode& node (state (false));
+
XMLTree tree;
IO::set_name_in_state (*node.children().front(), name);
tree.set_root (&node);
- return tree.write (path.c_str());
+
+ /* return zero on success, non-zero otherwise */
+ return !tree.write (path.c_str());
}
bool
Route::set_name (const string& str)
{
- bool ret;
- string ioproc_name;
- string name;
+ if (str == name()) {
+ return true;
+ }
- name = Route::ensure_track_or_route_name (str, _session);
+ string name = Route::ensure_track_or_route_name (str, _session);
SessionObject::set_name (name);
- ret = (_input->set_name(name) && _output->set_name(name));
+ bool ret = (_input->set_name(name) && _output->set_name(name));
if (ret) {
/* rename the main outs. Leave other IO processors
* @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);
XMLNodeList children = node.children();
for (XMLNodeIterator i = children.begin(); i != children.end(); ++i) {
-
+
if ((*i)->name() == X_("IO")) {
IO::set_name_in_state (**i, 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);
-
+
}
}
}
return boost::shared_ptr<Send>();
}
-/** @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 */
- _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)
{
}
}
-void
-Route::meter ()
+boost::shared_ptr<Pannable>
+Route::pannable() const
{
- Glib::Threads::RWLock::ReaderLock rm (_processor_lock, Glib::Threads::TRY_LOCK);
-
- assert (_meter);
-
- _meter->meter ();
-
- for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) {
-
- boost::shared_ptr<Send> s;
- boost::shared_ptr<Return> r;
-
- if ((s = boost::dynamic_pointer_cast<Send> (*i)) != 0) {
- s->meter()->meter();
- } else if ((r = boost::dynamic_pointer_cast<Return> (*i)) != 0) {
- r->meter()->meter ();
- }
- }
-}
-
-boost::shared_ptr<Pannable>
-Route::pannable() const
-{
- return _pannable;
-}
+ return _pannable;
+}
boost::shared_ptr<Panner>
Route::panner() const
return _main_outs->panner_shell();
}
-boost::shared_ptr<AutomationControl>
+boost::shared_ptr<GainControl>
Route::gain_control() const
{
- return _amp->gain_control();
+ return _gain_control;
+}
+
+boost::shared_ptr<GainControl>
+Route::trim_control() const
+{
+ return _trim_control;
+}
+
+boost::shared_ptr<PhaseControl>
+Route::phase_control() const
+{
+ return _phase_control;
}
boost::shared_ptr<AutomationControl>
/* maybe one of our processors does or ... */
- Glib::Threads::RWLock::ReaderLock rm (_processor_lock, Glib::Threads::TRY_LOCK);
+ Glib::Threads::RWLock::ReaderLock rm (_processor_lock);
for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) {
if ((c = boost::dynamic_pointer_cast<AutomationControl>((*i)->control (param))) != 0) {
break;
}
boost::shared_ptr<Processor>
-Route::nth_plugin (uint32_t n)
+Route::nth_plugin (uint32_t n) const
{
Glib::Threads::RWLock::ReaderLock lm (_processor_lock);
- ProcessorList::iterator i;
+ ProcessorList::const_iterator i;
for (i = _processors.begin(); i != _processors.end(); ++i) {
if (boost::dynamic_pointer_cast<PluginInsert> (*i)) {
}
boost::shared_ptr<Processor>
-Route::nth_send (uint32_t n)
+Route::nth_send (uint32_t n) const
{
Glib::Threads::RWLock::ReaderLock lm (_processor_lock);
- ProcessorList::iterator i;
+ ProcessorList::const_iterator i;
for (i = _processors.begin(); i != _processors.end(); ++i) {
if (boost::dynamic_pointer_cast<Send> (*i)) {
+
+ if ((*i)->name().find (_("Monitor")) == 0) {
+ /* send to monitor section is not considered
+ to be an accessible send.
+ */
+ continue;
+ }
+
if (n-- == 0) {
return *i;
}
return false;
}
-MuteMaster::MutePoint
-Route::mute_points () const
-{
- return _mute_master->mute_points ();
-}
-
void
Route::set_processor_positions ()
{
Glib::Threads::RWLock::ReaderLock lm (_processor_lock);
bool had_amp = false;
- for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) {
+ for (ProcessorList::const_iterator i = _processors.begin(); i != _processors.end(); ++i) {
(*i)->set_pre_fader (!had_amp);
- if (boost::dynamic_pointer_cast<Amp> (*i)) {
+ if (*i == _amp) {
had_amp = true;
}
}
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;
{
list<string> p;
+ if (_session.get_disable_all_loaded_plugins ()) {
+ // Do not list "missing plugins" if they are explicitly disabled
+ return p;
+ }
+
Glib::Threads::RWLock::ReaderLock lm (_processor_lock);
for (ProcessorList::const_iterator i = _processors.begin(); i != _processors.end(); ++i) {
if (boost::dynamic_pointer_cast<UnknownProcessor const> (*i)) {
} else {
all_connections.min = ~((pframes_t) 0);
all_connections.max = 0;
-
+
/* iterate over all "from" ports and determine the latency range for all of their
connections to the "outside" (outside of this Route).
*/
-
+
for (PortSet::iterator p = from.begin(); p != from.end(); ++p) {
-
+
LatencyRange range;
-
+
p->get_connected_latency_range (range, playback);
-
+
all_connections.min = min (all_connections.min, range.min);
all_connections.max = max (all_connections.max, range.max);
}
/** Put the invisible processors in the right place in _processors.
* Must be called with a writer lock on _processor_lock held.
*/
+#ifdef __clang__
+__attribute__((annotate("realtime")))
+#endif
void
Route::setup_invisible_processors ()
{
return;
}
- /* we'll build this new list here and then use it */
+ /* we'll build this new list here and then use it
+ *
+ * TODO put the ProcessorList is on the stack for RT-safety.
+ */
ProcessorList new_processors;
/* find the amp */
- ProcessorList::iterator amp = new_processors.begin ();
- while (amp != new_processors.end() && boost::dynamic_pointer_cast<Amp> (*amp) == 0) {
- ++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 */
if (_monitor_send && !is_monitor ()) {
assert (!_monitor_send->display_to_user ());
- if (Config->get_solo_control_is_listen_control()) {
- switch (Config->get_listen_position ()) {
- case PreFaderListen:
- switch (Config->get_pfl_position ()) {
- case PFLFromBeforeProcessors:
- new_processors.push_front (_monitor_send);
- break;
- case PFLFromAfterProcessors:
- new_processors.insert (amp, _monitor_send);
- break;
- }
- _monitor_send->set_can_pan (false);
+ switch (Config->get_listen_position ()) {
+ case PreFaderListen:
+ switch (Config->get_pfl_position ()) {
+ case PFLFromBeforeProcessors:
+ new_processors.push_front (_monitor_send);
break;
- case AfterFaderListen:
- switch (Config->get_afl_position ()) {
- case AFLFromBeforeProcessors:
- new_processors.insert (after_amp, _monitor_send);
- break;
- case AFLFromAfterProcessors:
- new_processors.insert (new_processors.end(), _monitor_send);
- break;
- }
- _monitor_send->set_can_pan (true);
+ case PFLFromAfterProcessors:
+ new_processors.insert (amp, _monitor_send);
break;
}
- } else {
- new_processors.insert (new_processors.end(), _monitor_send);
_monitor_send->set_can_pan (false);
+ break;
+ case AfterFaderListen:
+ switch (Config->get_afl_position ()) {
+ case AFLFromBeforeProcessors:
+ new_processors.insert (after_amp, _monitor_send);
+ break;
+ case AFLFromAfterProcessors:
+ new_processors.insert (new_processors.end(), _monitor_send);
+ break;
+ }
+ _monitor_send->set_can_pan (true);
+ break;
}
}
+#if 0 // not used - just yet
if (!is_master() && !is_monitor() && !is_auditioner()) {
new_processors.push_front (_delayline);
}
+#endif
/* MONITOR CONTROL */
if (_monitor_control && is_monitor ()) {
assert (!_monitor_control->display_to_user ());
- new_processors.push_front (_monitor_control);
+ 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) {
_processors = new_processors;
+ for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) {
+ if (!(*i)->display_to_user () && !(*i)->enabled () && (*i) != _monitor_send) {
+ (*i)->enable (true);
+ }
+ }
+
DEBUG_TRACE (DEBUG::Processors, string_compose ("%1: setup_invisible_processors\n", _name));
for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) {
DEBUG_TRACE (DEBUG::Processors, string_compose ("\t%1\n", (*i)->name ()));
if (_meter_point != MeterCustom) {
return;
}
-
+
_custom_meter_position_noted = true;
+ /* custom meter points range from after trim to before panner/main_outs
+ * this is a limitation by the current processor UI
+ */
+ bool seen_trim = false;
+ _processor_after_last_custom_meter.reset();
for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) {
+ if ((*i) == _trim) {
+ seen_trim = true;
+ }
+ if ((*i) == _main_outs) {
+ _processor_after_last_custom_meter = *i;
+ break;
+ }
if (boost::dynamic_pointer_cast<PeakMeter> (*i)) {
- ProcessorList::iterator j = i;
- ++j;
- if (j != _processors.end ()) {
- _processor_after_last_custom_meter = *j;
- _last_custom_meter_was_at_end = false;
+ if (!seen_trim) {
+ _processor_after_last_custom_meter = _trim;
} else {
- _last_custom_meter_was_at_end = true;
+ ProcessorList::iterator j = i;
+ ++j;
+ assert(j != _processors.end ()); // main_outs should be before
+ _processor_after_last_custom_meter = *j;
}
+ break;
}
}
+ assert(_processor_after_last_custom_meter.lock());
}
boost::shared_ptr<Processor>
/* ignore inactive processors and obviously ignore the main
* outs since everything has them and we don't care.
*/
-
+
if ((*i)->active() && (*i) != _main_outs && (*i)->does_routing()) {
return true;;
}
Route::the_instrument_unlocked () const
{
for (ProcessorList::const_iterator i = _processors.begin(); i != _processors.end(); ++i) {
- if (boost::dynamic_pointer_cast<PluginInsert>(*i)) {
- if ((*i)->input_streams().n_midi() > 0 &&
- (*i)->output_streams().n_audio() > 0) {
- return (*i);
- }
+ boost::shared_ptr<PluginInsert> pi = boost::dynamic_pointer_cast<PluginInsert>(*i);
+ if (pi && pi->plugin ()->get_info ()->is_instrument ()) {
+ return (*i);
}
}
return boost::shared_ptr<Processor>();
{
//Glib::Threads::Mutex::Lock lx (AudioEngine::instance()->process_lock ());
Glib::Threads::RWLock::ReaderLock lm (_processor_lock);
-
+
for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) {
(*i)->transport_located (pos);
}
}
+ _roll_delay = _initial_delay;
}
void
{
size_t n_buffers;
size_t i;
-
- /* MIDI
- *
+
+ /* MIDI
+ *
* We don't currently mix MIDI input together, so we don't need the
* complex logic of the audio case.
*/
boost::shared_ptr<MidiPort> source_port = io->midi (i);
MidiBuffer& buf (bufs.get_midi (i));
-
+
if (source_port) {
buf.copy (source_port->get_midi_buffer(nframes));
} else {
if (n_ports > n_buffers) {
scaling = ((float) n_buffers) / n_ports;
}
-
+
for (i = 0; i < n_ports; ++i) {
-
+
/* if there are more ports than buffers, map them onto buffers
* in a round-robin fashion
*/
boost::shared_ptr<AudioPort> source_port = io->audio (i);
AudioBuffer& buf (bufs.get_audio (i%n_buffers));
-
+
if (i < n_buffers) {
-
+
/* first time through just copy a channel into
the output buffer.
*/
if (scaling != 1.0f) {
buf.apply_gain (scaling, nframes);
}
-
+
} else {
-
+
/* on subsequent times around, merge data from
- * the port with what is already there
+ * the port with what is already there
*/
if (scaling != 1.0f) {
bufs.set_count (io->n_ports());
}
}
+
+boost::shared_ptr<AutomationControl>
+Route::pan_azimuth_control() const
+{
+#ifdef MIXBUS
+ boost::shared_ptr<ARDOUR::PluginInsert> plug = ch_post();
+ if (!plug) {
+ return boost::shared_ptr<AutomationControl>();
+ }
+ const uint32_t port_channel_post_pan = 2; // gtk2_ardour/mixbus_ports.h
+ return boost::dynamic_pointer_cast<ARDOUR::AutomationControl> (plug->control (Evoral::Parameter (ARDOUR::PluginAutomation, 0, port_channel_post_pan)));
+#else
+ if (!_pannable || !panner()) {
+ return boost::shared_ptr<AutomationControl>();
+ }
+ return _pannable->pan_azimuth_control;
+#endif
+}
+
+boost::shared_ptr<AutomationControl>
+Route::pan_elevation_control() const
+{
+ if (Profile->get_mixbus() || !_pannable || !panner()) {
+ return boost::shared_ptr<AutomationControl>();
+ }
+
+ set<Evoral::Parameter> c = panner()->what_can_be_automated ();
+
+ if (c.find (PanElevationAutomation) != c.end()) {
+ return _pannable->pan_elevation_control;
+ } else {
+ return boost::shared_ptr<AutomationControl>();
+ }
+}
+boost::shared_ptr<AutomationControl>
+Route::pan_width_control() const
+{
+ if (Profile->get_mixbus() || !_pannable || !panner()) {
+ return boost::shared_ptr<AutomationControl>();
+ }
+
+ set<Evoral::Parameter> c = panner()->what_can_be_automated ();
+
+ if (c.find (PanWidthAutomation) != c.end()) {
+ return _pannable->pan_width_control;
+ } else {
+ return boost::shared_ptr<AutomationControl>();
+ }
+}
+boost::shared_ptr<AutomationControl>
+Route::pan_frontback_control() const
+{
+ if (Profile->get_mixbus() || !_pannable || !panner()) {
+ return boost::shared_ptr<AutomationControl>();
+ }
+
+ set<Evoral::Parameter> c = panner()->what_can_be_automated ();
+
+ if (c.find (PanFrontBackAutomation) != c.end()) {
+ return _pannable->pan_frontback_control;
+ } else {
+ return boost::shared_ptr<AutomationControl>();
+ }
+}
+boost::shared_ptr<AutomationControl>
+Route::pan_lfe_control() const
+{
+ if (Profile->get_mixbus() || !_pannable || !panner()) {
+ return boost::shared_ptr<AutomationControl>();
+ }
+
+ set<Evoral::Parameter> c = panner()->what_can_be_automated ();
+
+ if (c.find (PanLFEAutomation) != c.end()) {
+ return _pannable->pan_lfe_control;
+ } else {
+ return boost::shared_ptr<AutomationControl>();
+ }
+}
+
+uint32_t
+Route::eq_band_cnt () const
+{
+ if (Profile->get_mixbus()) {
+ return 3;
+ } else {
+ /* Ardour has no well-known EQ object */
+ return 0;
+ }
+}
+
+boost::shared_ptr<AutomationControl>
+Route::eq_gain_controllable (uint32_t band) const
+{
+#ifdef MIXBUS
+ boost::shared_ptr<PluginInsert> eq = ch_eq();
+
+ if (!eq) {
+ return boost::shared_ptr<AutomationControl>();
+ }
+
+ uint32_t port_number;
+ switch (band) {
+ case 0:
+ if (is_master() || mixbus()) {
+ port_number = 4;
+ } else {
+ port_number = 8;
+ }
+ break;
+ case 1:
+ if (is_master() || mixbus()) {
+ port_number = 3;
+ } else {
+ port_number = 6;
+ }
+ break;
+ case 2:
+ if (is_master() || mixbus()) {
+ port_number = 2;
+ } else {
+ port_number = 4;
+ }
+ break;
+ default:
+ return boost::shared_ptr<AutomationControl>();
+ }
+
+ return boost::dynamic_pointer_cast<ARDOUR::AutomationControl> (eq->control (Evoral::Parameter (ARDOUR::PluginAutomation, 0, port_number)));
+#else
+ return boost::shared_ptr<AutomationControl>();
+#endif
+}
+boost::shared_ptr<AutomationControl>
+Route::eq_freq_controllable (uint32_t band) const
+{
+#ifdef MIXBUS
+
+ if (mixbus() || is_master()) {
+ /* no frequency controls for mixbusses or master */
+ return boost::shared_ptr<AutomationControl>();
+ }
+
+ boost::shared_ptr<PluginInsert> eq = ch_eq();
+
+ if (!eq) {
+ return boost::shared_ptr<AutomationControl>();
+ }
+
+ uint32_t port_number;
+ switch (band) {
+ case 0:
+ port_number = 7;
+ break;
+ case 1:
+ port_number = 5;
+ break;
+ case 2:
+ port_number = 3;
+ break;
+ default:
+ return boost::shared_ptr<AutomationControl>();
+ }
+
+ return boost::dynamic_pointer_cast<ARDOUR::AutomationControl> (eq->control (Evoral::Parameter (ARDOUR::PluginAutomation, 0, port_number)));
+#else
+ return boost::shared_ptr<AutomationControl>();
+#endif
+}
+
+boost::shared_ptr<AutomationControl>
+Route::eq_q_controllable (uint32_t band) const
+{
+ return boost::shared_ptr<AutomationControl>();
+}
+
+boost::shared_ptr<AutomationControl>
+Route::eq_shape_controllable (uint32_t band) const
+{
+ return boost::shared_ptr<AutomationControl>();
+}
+
+boost::shared_ptr<AutomationControl>
+Route::eq_enable_controllable () const
+{
+#ifdef MIXBUS
+ boost::shared_ptr<PluginInsert> eq = ch_eq();
+
+ if (!eq) {
+ return boost::shared_ptr<AutomationControl>();
+ }
+
+ return boost::dynamic_pointer_cast<ARDOUR::AutomationControl> (eq->control (Evoral::Parameter (ARDOUR::PluginAutomation, 0, 1)));
+#else
+ return boost::shared_ptr<AutomationControl>();
+#endif
+}
+
+boost::shared_ptr<AutomationControl>
+Route::eq_hpf_controllable () const
+{
+#ifdef MIXBUS
+ boost::shared_ptr<PluginInsert> eq = ch_eq();
+
+ if (!eq) {
+ return boost::shared_ptr<AutomationControl>();
+ }
+
+ return boost::dynamic_pointer_cast<ARDOUR::AutomationControl> (eq->control (Evoral::Parameter (ARDOUR::PluginAutomation, 0, 2)));
+#else
+ return boost::shared_ptr<AutomationControl>();
+#endif
+}
+
+string
+Route::eq_band_name (uint32_t band) const
+{
+ if (Profile->get_mixbus()) {
+ switch (band) {
+ case 0:
+ return _("lo");
+ case 1:
+ return _("mid");
+ case 2:
+ return _("hi");
+ default:
+ return string();
+ }
+ } else {
+ return string ();
+ }
+}
+
+boost::shared_ptr<AutomationControl>
+Route::comp_enable_controllable () const
+{
+#ifdef MIXBUS
+ boost::shared_ptr<PluginInsert> comp = ch_comp();
+
+ if (!comp) {
+ return boost::shared_ptr<AutomationControl>();
+ }
+
+ return boost::dynamic_pointer_cast<ARDOUR::AutomationControl> (comp->control (Evoral::Parameter (ARDOUR::PluginAutomation, 0, 1)));
+#else
+ return boost::shared_ptr<AutomationControl>();
+#endif
+}
+boost::shared_ptr<AutomationControl>
+Route::comp_threshold_controllable () const
+{
+#ifdef MIXBUS
+ boost::shared_ptr<PluginInsert> comp = ch_comp();
+
+ if (!comp) {
+ return boost::shared_ptr<AutomationControl>();
+ }
+
+ return boost::dynamic_pointer_cast<ARDOUR::AutomationControl> (comp->control (Evoral::Parameter (ARDOUR::PluginAutomation, 0, 2)));
+
+#else
+ return boost::shared_ptr<AutomationControl>();
+#endif
+}
+boost::shared_ptr<AutomationControl>
+Route::comp_speed_controllable () const
+{
+#ifdef MIXBUS
+ boost::shared_ptr<PluginInsert> comp = ch_comp();
+
+ if (!comp) {
+ return boost::shared_ptr<AutomationControl>();
+ }
+
+ return boost::dynamic_pointer_cast<ARDOUR::AutomationControl> (comp->control (Evoral::Parameter (ARDOUR::PluginAutomation, 0, 3)));
+#else
+ return boost::shared_ptr<AutomationControl>();
+#endif
+}
+boost::shared_ptr<AutomationControl>
+Route::comp_mode_controllable () const
+{
+#ifdef MIXBUS
+ boost::shared_ptr<PluginInsert> comp = ch_comp();
+
+ if (!comp) {
+ return boost::shared_ptr<AutomationControl>();
+ }
+
+ return boost::dynamic_pointer_cast<ARDOUR::AutomationControl> (comp->control (Evoral::Parameter (ARDOUR::PluginAutomation, 0, 4)));
+#else
+ return boost::shared_ptr<AutomationControl>();
+#endif
+}
+boost::shared_ptr<AutomationControl>
+Route::comp_makeup_controllable () const
+{
+#ifdef MIXBUS
+ boost::shared_ptr<PluginInsert> comp = ch_comp();
+
+ if (!comp) {
+ return boost::shared_ptr<AutomationControl>();
+ }
+
+ return boost::dynamic_pointer_cast<ARDOUR::AutomationControl> (comp->control (Evoral::Parameter (ARDOUR::PluginAutomation, 0, 5)));
+#else
+ return boost::shared_ptr<AutomationControl>();
+#endif
+}
+boost::shared_ptr<AutomationControl>
+Route::comp_redux_controllable () const
+{
+#ifdef MIXBUS
+ boost::shared_ptr<PluginInsert> comp = ch_comp();
+
+ if (!comp) {
+ return boost::shared_ptr<AutomationControl>();
+ }
+
+ return boost::dynamic_pointer_cast<ARDOUR::AutomationControl> (comp->control (Evoral::Parameter (ARDOUR::PluginAutomation, 0, 6)));
+#else
+ return boost::shared_ptr<AutomationControl>();
+#endif
+}
+
+string
+Route::comp_mode_name (uint32_t mode) const
+{
+#ifdef MIXBUS
+ switch (mode) {
+ case 0:
+ return _("Leveler");
+ case 1:
+ return _("Compressor");
+ case 2:
+ return _("Limiter");
+ case 3:
+ return mixbus() ? _("Sidechain") : _("Limiter");
+ }
+
+ return _("???");
+#else
+ return _("???");
+#endif
+}
+
+string
+Route::comp_speed_name (uint32_t mode) const
+{
+#ifdef MIXBUS
+ switch (mode) {
+ case 0:
+ return _("Attk");
+ case 1:
+ return _("Ratio");
+ case 2:
+ case 3:
+ return _("Rels");
+ }
+ return _("???");
+#else
+ return _("???");
+#endif
+}
+
+boost::shared_ptr<AutomationControl>
+Route::send_level_controllable (uint32_t n) const
+{
+#ifdef MIXBUS
+ boost::shared_ptr<ARDOUR::PluginInsert> plug = ch_post();
+ if (!plug) {
+ return boost::shared_ptr<AutomationControl>();
+ }
+
+ if (n >= 8) {
+ /* no such bus */
+ return boost::shared_ptr<AutomationControl>();
+ }
+
+ const uint32_t port_id = port_channel_post_aux1_level + (2*n); // gtk2_ardour/mixbus_ports.h
+ return boost::dynamic_pointer_cast<ARDOUR::AutomationControl> (plug->control (Evoral::Parameter (ARDOUR::PluginAutomation, 0, port_id)));
+#else
+ boost::shared_ptr<Send> s = boost::dynamic_pointer_cast<Send>(nth_send (n));
+ if (!s) {
+ return boost::shared_ptr<AutomationControl>();
+ }
+ return s->gain_control ();
+#endif
+}
+
+boost::shared_ptr<AutomationControl>
+Route::send_enable_controllable (uint32_t n) const
+{
+#ifdef MIXBUS
+ boost::shared_ptr<ARDOUR::PluginInsert> plug = ch_post();
+ if (!plug) {
+ return boost::shared_ptr<AutomationControl>();
+ }
+
+ if (n >= 8) {
+ /* no such bus */
+ return boost::shared_ptr<AutomationControl>();
+ }
+
+ const uint32_t port_id = port_channel_post_aux1_asgn + (2*n); // gtk2_ardour/mixbus_ports.h
+ return boost::dynamic_pointer_cast<ARDOUR::AutomationControl> (plug->control (Evoral::Parameter (ARDOUR::PluginAutomation, 0, port_id)));
+#else
+ /* although Ardour sends have enable/disable as part of the Processor
+ API, it is not exposed as a controllable.
+
+ XXX: we should fix this.
+ */
+ return boost::shared_ptr<AutomationControl>();
+#endif
+}
+
+string
+Route::send_name (uint32_t n) const
+{
+#ifdef MIXBUS
+ if (n >= 8) {
+ return string();
+ }
+ boost::shared_ptr<Route> r = _session.get_mixbus (n);
+ assert (r);
+ return r->name();
+#else
+ boost::shared_ptr<Processor> p = nth_send (n);
+ if (p) {
+ return p->name();
+ } else {
+ return string();
+ }
+#endif
+}
+
+boost::shared_ptr<AutomationControl>
+Route::master_send_enable_controllable () const
+{
+#ifdef MIXBUS
+ boost::shared_ptr<ARDOUR::PluginInsert> plug = ch_post();
+ if (!plug) {
+ return boost::shared_ptr<AutomationControl>();
+ }
+ return boost::dynamic_pointer_cast<ARDOUR::AutomationControl> (plug->control (Evoral::Parameter (ARDOUR::PluginAutomation, 0, port_channel_post_mstr_assign)));
+#else
+ return boost::shared_ptr<AutomationControl>();
+#endif
+}
+
+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> 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);
+ }
+}