#endif
#include <cmath>
-#include <fstream>
#include <cassert>
#include <algorithm>
+#include <glibmm.h>
#include <boost/algorithm/string.hpp>
#include "pbd/xml++.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/buffer.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"
, _active (true)
, _signal_latency (0)
, _signal_latency_at_amp_position (0)
+ , _signal_latency_at_trim_position (0)
, _initial_delay (0)
, _roll_delay (0)
+ , _pending_process_reorder (0)
+ , _pending_signals (0)
, _flags (flg)
, _pending_declick (true)
, _meter_point (MeterPostFader)
+ , _pending_meter_point (MeterPostFader)
, _meter_type (MeterPeak)
, _self_solo (false)
, _soloed_by_others_upstream (0)
, _soloed_by_others_downstream (0)
- , _solo_isolated (0)
+ , _solo_isolated (false)
+ , _solo_isolated_by_upstream (0)
, _denormal_protection (false)
, _recordable (true)
, _silent (false)
, _order_key (0)
, _has_order_key (false)
, _remote_control_id (0)
+ , _track_number (0)
, _in_configure_processors (false)
, _initial_io_setup (false)
, _custom_meter_position_noted (false)
- , _last_custom_meter_was_at_end (false)
{
- 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 ()));
+ _phase_control.reset (new PhaseControllable (X_("phase"), shared_from_this ()));
_solo_control->set_flags (Controllable::Flag (_solo_control->flags() | Controllable::Toggle));
_mute_control->set_flags (Controllable::Flag (_mute_control->flags() | Controllable::Toggle));
+ _phase_control->set_flags (Controllable::Flag (_phase_control->flags() | Controllable::Toggle));
add_control (_solo_control);
add_control (_mute_control);
+ add_control (_phase_control);
/* panning */
_output->changed.connect_same_thread (*this, boost::bind (&Route::output_change_handler, this, _1, _2));
_output->PortCountChanging.connect_same_thread (*this, boost::bind (&Route::output_port_count_changing, this, _1));
+#if 0 // not used - just yet
if (!is_master() && !is_monitor() && !is_auditioner()) {
_delayline.reset (new DelayLine (_session, _name));
add_processor (_delayline, PreFader);
}
+#endif
/* add amp processor */
_amp.reset (new Amp (_session));
add_processor (_amp, PostFader);
+ // amp should exist before amp controls
+ _group_gain_control.reset (new GroupGainControllable (X_("groupgain"), shared_from_this ()));
+ _group_gain_control->set_flags (Controllable::Flag (_group_gain_control->flags() | Controllable::GainLike));
+ add_control (_group_gain_control);
+
+ /* and input trim */
+ _trim.reset (new Amp (_session, "trim"));
+ _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 ()
*/
/* 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);
void
Route::set_remote_control_id_internal (uint32_t id, bool notify_class_listeners)
{
- /* force IDs for master/monitor busses and prevent
+ /* force IDs for master/monitor busses and prevent
any other route from accidentally getting these IDs
(i.e. legacy sessions)
*/
/* don't allow it to collide */
- if (!is_master () && !is_monitor() &&
+ if (!is_master () && !is_monitor() &&
(id == MasterBusRemoteControlID || id == MonitorBusRemoteControlID)) {
id += MonitorBusRemoteControlID;
}
{
if (is_master()) {
return MasterBusRemoteControlID;
- }
+ }
if (is_monitor()) {
return MonitorBusRemoteControlID;
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->set_gain (val, src);
}
+void
+Route::inc_trim (gain_t fraction, void *src)
+{
+ _trim->inc_gain (fraction, src);
+}
+
+void
+Route::set_trim (gain_t val, void * /* src */)
+{
+ // TODO route group, see set_gain()
+ _trim->set_gain (val, 0);
+}
+
void
Route::maybe_declick (BufferSet&, framecnt_t, int)
{
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;
+ }
/* 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.)
framecnt_t latency = 0;
- 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 */
_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;
for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) {
}
void
-Route::set_listen (bool yn, void* src)
+Route::set_listen (bool yn, void* src, bool group_override)
{
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));
+ bool group_active = _route_group && _route_group->is_active() && _route_group->is_solo();
+ if (group_override && _route_group) {
+ group_active = !group_active;
+ }
+
+ if (_route_group && src != _route_group && group_active) {
+ _route_group->foreach_route (boost::bind (&Route::set_listen, _1, yn, _route_group, group_override));
return;
}
if (yn != _monitor_send->active()) {
if (yn) {
_monitor_send->activate ();
- _mute_master->set_soloed (true);
+ _mute_master->set_soloed_by_self (true);
} else {
_monitor_send->deactivate ();
- _mute_master->set_soloed (false);
+ _mute_master->set_soloed_by_self (false);
}
+ _mute_master->set_soloed_by_others (false);
- listen_changed (src); /* EMIT SIGNAL */
+ listen_changed (src, group_override); /* EMIT SIGNAL */
}
}
}
}
void
-Route::set_solo (bool yn, void *src)
+Route::clear_all_solo_state ()
+{
+ // ideally this function will never do anything, it only exists to forestall Murphy
+ bool emit_changed = false;
+
+#ifndef NDEBUG
+ // these are really debug messages, but of possible interest.
+ if (_self_solo) {
+ PBD::info << string_compose (_("Cleared Explicit solo: %1\n"), name());
+ }
+ if (_soloed_by_others_upstream || _soloed_by_others_downstream) {
+ PBD::info << string_compose (_("Cleared Implicit solo: %1 up:%2 down:%3\n"),
+ name(), _soloed_by_others_upstream, _soloed_by_others_downstream);
+ }
+#endif
+
+ if (!_self_solo && (_soloed_by_others_upstream || _soloed_by_others_downstream)) {
+ // if self-soled, set_solo() will do signal emission
+ emit_changed = true;
+ }
+
+ _soloed_by_others_upstream = 0;
+ _soloed_by_others_downstream = 0;
+
+ {
+ PBD::Unwinder<bool> uw (_solo_safe, false);
+ set_solo (false, this);
+ }
+
+ if (emit_changed) {
+ set_mute_master_solo ();
+ solo_changed (false, this, false); /* EMIT SIGNAL */
+ }
+}
+
+void
+Route::set_solo (bool yn, void *src, bool group_override)
{
if (_solo_safe) {
DEBUG_TRACE (DEBUG::Solo, string_compose ("%1 ignore solo change due to solo-safe\n", name()));
return;
}
- 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));
+ if (is_master() || is_monitor() || is_auditioner()) {
+ DEBUG_TRACE (DEBUG::Solo, string_compose ("%1 ignore solo change (master, monitor or auditioner)\n", name()));
+ return;
+ }
+
+ bool group_active = _route_group && _route_group->is_active() && _route_group->is_solo();
+ if (group_override && _route_group) {
+ group_active = !group_active;
+ }
+ if (_route_group && src != _route_group && group_active) {
+ _route_group->foreach_route (boost::bind (&Route::set_solo, _1, yn, _route_group, group_override));
return;
}
- DEBUG_TRACE (DEBUG::Solo, string_compose ("%1: set solo => %2, src: %3 grp ? %4 currently self-soloed ? %5\n",
+ DEBUG_TRACE (DEBUG::Solo, string_compose ("%1: set solo => %2, src: %3 grp ? %4 currently self-soloed ? %5\n",
name(), yn, src, (src == _route_group), self_soloed()));
if (self_soloed() != yn) {
set_self_solo (yn);
- set_mute_master_solo ();
- solo_changed (true, src); /* EMIT SIGNAL */
+ solo_changed (true, src, group_override); /* EMIT SIGNAL */
_solo_control->Changed (); /* EMIT SIGNAL */
}
+
+ assert (Config->get_solo_control_is_listen_control() || !_monitor_send || !_monitor_send->active());
+
+ /* XXX TRACKS DEVELOPERS: THIS LOGIC SUGGESTS THAT YOU ARE NOT AWARE OF
+ Config->get_solo_mute_overrride().
+ */
+
+ if (yn && Profile->get_trx()) {
+ set_mute (false, src);
+ }
}
void
{
DEBUG_TRACE (DEBUG::Solo, string_compose ("%1: set SELF solo => %2\n", name(), yn));
_self_solo = yn;
+ set_mute_master_solo ();
}
void
Route::mod_solo_by_others_upstream (int32_t delta)
{
- if (_solo_safe) {
- DEBUG_TRACE (DEBUG::Solo, string_compose ("%1 ignore solo-by-upstream due to solo-safe\n", name()));
- return;
- }
-
- DEBUG_TRACE (DEBUG::Solo, string_compose ("%1 mod solo-by-upstream by %2, current up = %3 down = %4\n",
+ DEBUG_TRACE (DEBUG::Solo, string_compose ("%1 mod solo-by-upstream by %2, current up = %3 down = %4\n",
name(), delta, _soloed_by_others_upstream, _soloed_by_others_downstream));
uint32_t old_sbu = _soloed_by_others_upstream;
(old_sbu > 0 && _soloed_by_others_upstream == 0))) {
if (delta > 0 || !Config->get_exclusive_solo()) {
- DEBUG_TRACE (DEBUG::Solo, "\t ... INVERT push\n");
+ 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->mod_solo_by_others_downstream (-delta);
}
set_mute_master_solo ();
- solo_changed (false, this);
+ solo_changed (false, this, false); /* EMIT SIGNAL */
}
void
Route::mod_solo_by_others_downstream (int32_t delta)
{
- if (_solo_safe) {
- DEBUG_TRACE (DEBUG::Solo, string_compose ("%1 ignore solo-by-downstream due to solo safe\n", name()));
- return;
- }
-
- DEBUG_TRACE (DEBUG::Solo, string_compose ("%1 mod solo-by-downstream by %2, current up = %3 down = %4\n",
+ DEBUG_TRACE (DEBUG::Solo, string_compose ("%1 mod solo-by-downstream by %2, current up = %3 down = %4\n",
name(), delta, _soloed_by_others_upstream, _soloed_by_others_downstream));
if (delta < 0) {
DEBUG_TRACE (DEBUG::Solo, string_compose ("%1 SbD delta %2 = %3\n", name(), delta, _soloed_by_others_downstream));
set_mute_master_solo ();
- solo_changed (false, this);
+ solo_changed (false, this, false); /* EMIT SIGNAL */
}
void
Route::set_mute_master_solo ()
{
- _mute_master->set_soloed (self_soloed() || soloed_by_others_downstream() || soloed_by_others_upstream());
+ _mute_master->set_soloed_by_self (self_soloed());
+ _mute_master->set_soloed_by_others (soloed_by_others_downstream() || soloed_by_others_upstream());
+}
+
+void
+Route::mod_solo_isolated_by_upstream (bool yn, void* src)
+{
+ bool old = solo_isolated ();
+ DEBUG_TRACE (DEBUG::Solo, string_compose ("%1 mod_solo_isolated_by_upstream cur: %2 d: %3\n",
+ name(), _solo_isolated_by_upstream, yn ? "+1" : "-1"));
+
+ if (!yn) {
+ if (_solo_isolated_by_upstream >= 1) {
+ _solo_isolated_by_upstream--;
+ } else {
+ _solo_isolated_by_upstream = 0;
+ }
+ } else {
+ _solo_isolated_by_upstream++;
+ }
+
+ if (solo_isolated() != old) {
+ /* solo isolated status changed */
+ _mute_master->set_solo_ignore (solo_isolated());
+ solo_isolated_changed (src); /* EMIT SIGNAL */
+ }
}
void
return;
}
+ bool changed = false;
+
+ if (yn) {
+ if (_solo_isolated == false) {
+ _mute_master->set_solo_ignore (true);
+ changed = true;
+ }
+ _solo_isolated = true;
+ } else {
+ if (_solo_isolated == true) {
+ _solo_isolated = false;
+ _mute_master->set_solo_ignore (false);
+ changed = true;
+ }
+ }
+
+
+ if (!changed) {
+ return;
+ }
+
/* forward propagate solo-isolate status to everything fed by this route, but not those via sends only */
boost::shared_ptr<RouteList> routes = _session.get_routes ();
}
bool sends_only;
- bool does_feed = direct_feeds_according_to_graph (*i, &sends_only); // we will recurse anyway, so don't use ::feeds()
+ bool does_feed = feeds (*i, &sends_only);
if (does_feed && !sends_only) {
- (*i)->set_solo_isolated (yn, (*i)->route_group());
+ (*i)->mod_solo_isolated_by_upstream (yn, src);
}
}
/* 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);
- }
+ solo_isolated_changed (src); /* EMIT SIGNAL */
}
bool
Route::solo_isolated () const
{
- return _solo_isolated > 0;
+ return (_solo_isolated == true) || (_solo_isolated_by_upstream > 0);
}
void
return _mute_master->muted_by_self();
}
+bool
+Route::muted_by_others () const
+{
+ // This method is only used by route_ui for display state.
+ // The real thing is MuteMaster::muted_by_others_at()
+
+ //master is never muted by others
+ if (is_master())
+ return false;
+
+ //now check to see if something is soloed (and I am not)
+ //see also MuteMaster::mute_gain_at()
+ return (_session.soloing() && !soloed() && !solo_isolated());
+}
+
#if 0
static void
dump_processors(const string& name, const list<boost::shared_ptr<Processor> >& procs)
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);
}
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;
}
}
- if (activation_allowed && !_session.get_disable_all_loaded_plugins()) {
+ if (activation_allowed && (!_session.get_bypass_all_loaded_plugins () || !processor->display_to_user ())) {
processor->activate ();
}
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));
+ }
} 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) {
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 */
/* 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;
}
if (!removed) {
/* what? */
return 1;
- }
+ }
if (configure_processors_unlocked (err)) {
pstate.restore ();
/* 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) {
++i;
continue;
}
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)) {
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));
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));
}
-
- (*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<PluginInsert> pi;
+ if ((pi = boost::dynamic_pointer_cast<PluginInsert>(*p)) != 0) {
+ /* plugins connected via Split Match may have more channels.
+ * route/scratch buffers are needed for all of them*/
+ processor_max_streams = ChanCount::max(processor_max_streams, pi->input_streams());
+ processor_max_streams = ChanCount::max(processor_max_streams, pi->natural_input_streams());
+ }
out = c->second;
if (boost::dynamic_pointer_cast<Delivery> (*p)
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 {
_session.set_dirty ();
}
-int
-Route::reorder_processors (const ProcessorList& new_order, ProcessorStreams* err)
+bool
+Route::processors_reorder_needs_configure (const ProcessorList& new_order)
+{
+ /* 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 ();
+
+ 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;
+}
+
+#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());
+
+
/* "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.
- */
+ * 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.
+ */
- {
- Glib::Threads::Mutex::Lock lx (AudioEngine::instance()->process_lock ());
- Glib::Threads::RWLock::WriterLock lm (_processor_lock);
- ProcessorState pstate (this);
+ /* "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".
+ */
- ProcessorList::iterator oiter;
- ProcessorList::const_iterator niter;
- ProcessorList as_it_will_be;
+ ProcessorList as_it_will_be;
+ ProcessorList::iterator oiter;
+ ProcessorList::const_iterator niter;
- oiter = _processors.begin();
- niter = new_order.begin();
+ oiter = _processors.begin();
+ niter = new_order.begin();
- while (niter != new_order.end()) {
+ while (niter != new_order.end()) {
- /* 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.
+ /* 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.
- 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.
- */
+ 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 == _processors.end()) {
+ if (oiter == _processors.end()) {
- /* no more elements in the old list, so just stick the rest of
- the new order onto the temp list.
- */
+ /* no more elements in the old list, so just stick the rest of
+ the new order onto the temp list.
+ */
- as_it_will_be.insert (as_it_will_be.end(), niter, new_order.end());
- while (niter != new_order.end()) {
- ++niter;
- }
- break;
+ as_it_will_be.insert (as_it_will_be.end(), niter, new_order.end());
+ while (niter != new_order.end()) {
+ ++niter;
+ }
+ break;
- } else {
+ } else {
- if (!(*oiter)->display_to_user()) {
+ if (!(*oiter)->display_to_user()) {
- as_it_will_be.push_back (*oiter);
+ as_it_will_be.push_back (*oiter);
- } else {
+ } else {
- /* visible processor: check that its in the new order */
+ /* visible processor: check that its in the new order */
- 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 (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;
}
-
- /* now remove from old order - its taken care of no matter what */
- oiter = _processors.erase (oiter);
}
+ /* now remove from old order - its taken care of no matter what */
+ oiter = _processors.erase (oiter);
}
- _processors.insert (oiter, as_it_will_be.begin(), as_it_will_be.end());
+ }
+ _processors.insert (oiter, as_it_will_be.begin(), as_it_will_be.end());
- /* If the meter is in a custom position, find it and make a rough note of its position */
- maybe_note_meter_position ();
+ /* If the meter is in a custom position, find it and make a rough note of its position */
+ maybe_note_meter_position ();
+}
+
+int
+Route::reorder_processors (const ProcessorList& new_order, ProcessorStreams* err)
+{
+ // 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);
+
+ apply_processor_order(_pending_processor_order);
+ setup_invisible_processors ();
+
+ g_atomic_int_set (&_pending_process_reorder, 0);
+
+ 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);
+ }
+ }
+
+ if (processors_reorder_needs_configure (new_order) || !AudioEngine::instance()->running()) {
+
+ Glib::Threads::Mutex::Lock lx (AudioEngine::instance()->process_lock ());
+ Glib::Threads::RWLock::WriterLock lm (_processor_lock);
+ ProcessorState pstate (this);
+
+ apply_processor_order (new_order);
if (configure_processors_unlocked (err)) {
pstate.restore ();
return -1;
}
- }
- processors_changed (RouteProcessorChange ()); /* EMIT SIGNAL */
- set_processor_positions ();
+ lm.release();
+ lx.release();
+
+ processors_changed (RouteProcessorChange ()); /* EMIT SIGNAL */
+ set_processor_positions ();
+
+ } else {
+ DEBUG_TRACE (DEBUG::Processors, "Queue clickless processor re-order.\n");
+ Glib::Threads::RWLock::ReaderLock lm (_processor_lock);
+
+ // _pending_processor_order is protected by _processor_lock
+ _pending_processor_order = new_order;
+ g_atomic_int_set (&_pending_process_reorder, 1);
+ }
return 0;
}
node->add_child_nocopy (_mute_control->get_state ());
node->add_child_nocopy (_mute_master->get_state ());
+ if (full_state) {
+ node->add_child_nocopy (Automatable::get_automation_xml_state ());
+ }
+
XMLNode* remote_control_node = new XMLNode (X_("RemoteControl"));
snprintf (buf, sizeof (buf), "%d", _remote_control_id);
remote_control_node->add_property (X_("id"), buf);
for (i = _processors.begin(); i != _processors.end(); ++i) {
if (!full_state) {
- /* template save: do not include internal sends functioning as
+ /* 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.
after->id().print (buf, sizeof (buf));
node->add_property (X_("processor-after-last-custom-meter"), buf);
}
-
- node->add_property (X_("last-custom-meter-was-at-end"), _last_custom_meter_was_at_end ? "yes" : "no");
}
return *node;
}
}
- 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;
} else if (child->name() == X_("MuteMaster")) {
_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 (X_("C"));
XMLNodeList nlist;
XMLNodeConstIterator niter;
XMLNode *child;
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") {
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() == "lxvst" ||
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));
+ }
} else if (prop->value() == "port") {
processor.reset (new PortInsert (_session, _pannable, _mute_master));
}
reset_instrument_info ();
- processors_changed (RouteProcessorChange ());
+ processors_changed (RouteProcessorChange ()); /* EMIT SIGNAL */
set_processor_positions ();
}
/* 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;
+ }
}
}
}
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 ());
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 (_soloed_by_others_upstream || _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_isolated()) {
+ ++ibou;
+ }
+ }
+ }
}
- }
-}
-void
-Route::output_change_handler (IOChange change, void * /*src*/)
-{
- bool need_to_queue_solo_change = true;
- if (_initial_io_setup) {
+ int delta = sbou - _soloed_by_others_upstream;
+ int idelta = ibou - _solo_isolated_by_upstream;
+
+ if (idelta < -1) {
+ PBD::warning << string_compose (
+ _("Invalid Solo-Isolate propagation: from:%1 new:%2 - old:%3 = delta:%4"),
+ _name, ibou, _solo_isolated_by_upstream, idelta)
+ << endmsg;
+
+ }
+
+ if (_soloed_by_others_upstream) {
+ // ignore new connections (they're not propagated)
+ if (delta <= 0) {
+ mod_solo_by_others_upstream (delta);
+ }
+ }
+
+ if (_solo_isolated_by_upstream) {
+ // solo-isolate currently only propagates downstream
+ if (idelta < 0) {
+ mod_solo_isolated_by_upstream (false, this);
+ }
+ // TODO think: mod_solo_isolated_by_upstream() does not take delta arg,
+ // but idelta can't be smaller than -1, can it?
+ //_solo_isolated_by_upstream = ibou;
+ }
+
+ // 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)->mod_solo_by_others_upstream (delta);
+ }
+
+ if (idelta < 0 && does_feed && !sends_only) {
+ (*i)->mod_solo_isolated_by_upstream (false, this);
+ }
+ }
+ }
+}
+
+void
+Route::output_change_handler (IOChange change, void * /*src*/)
+{
+ 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 (_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 - _soloed_by_others_downstream;
+ if (delta <= 0) {
+ // do not allow new connections to change implicit solo (no propagation)
+ mod_solo_by_others_downstream (delta);
+ // Session::route_solo_changed() does not propagate indirect solo-changes
+ // propagate upstream to tracks
+ for (RouteList::iterator i = routes->begin(); i != routes->end(); ++i) {
+ if ((*i).get() == this || (*i)->is_master() || (*i)->is_monitor() || (*i)->is_auditioner()) {
+ continue;
+ }
+ bool sends_only;
+ bool does_feed = (*i)->feeds (shared_from_this(), &sends_only);
+ if (delta != 0 && does_feed && !sends_only) {
+ (*i)->mod_solo_by_others_downstream (delta);
+ }
+ }
-void
-Route::cancel_solo_after_disconnect (bool upstream)
-{
- if (upstream) {
- _soloed_by_others_upstream = 0;
- } else {
- _soloed_by_others_downstream = 0;
+ }
}
- set_mute_master_solo ();
- solo_changed (false, this);
}
uint32_t
}
_amp->apply_gain_automation (false);
+ _trim->apply_gain_automation (false);
passthru (bufs, start_frame, end_frame, nframes, 0);
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 false;
+}
+
+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 */
+ }
+}
+
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
ProcessorState pstate (this);
if (configure_processors_unlocked (0)) {
+ DEBUG_TRACE (DEBUG::Processors, "---- CONFIGURATION FAILED.\n");
pstate.restore ();
configure_processors_unlocked (0); // it worked before we tried to add it ...
return;
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)
+ : AutomationControl (r->session(),
+ Evoral::Parameter (SoloAutomation),
+ ParameterDescriptor(Evoral::Parameter (SoloAutomation)),
+ boost::shared_ptr<AutomationList>(), name)
, _route (r)
{
boost::shared_ptr<AutomationList> gl(new AutomationList(Evoral::Parameter(SoloAutomation)));
+ gl->set_interpolation(Evoral::ControlList::Discrete);
set_list (gl);
}
void
Route::SoloControllable::set_value (double val)
{
- bool bval = ((val >= 0.5f) ? true: false);
+ if (writable()) {
+ set_value_unchecked (val);
+ }
+}
+
+void
+Route::SoloControllable::set_value_unchecked (double val)
+{
+ const bool bval = ((val >= 0.5) ? true : false);
boost::shared_ptr<RouteList> rl (new RouteList);
}
if (Config->get_solo_control_is_listen_control()) {
- return r->listening_via_monitor() ? 1.0f : 0.0f;
+ return r->listening_via_monitor() ? GAIN_COEFF_UNITY : GAIN_COEFF_ZERO;
} else {
- return r->self_soloed() ? 1.0f : 0.0f;
+ return r->self_soloed() ? GAIN_COEFF_UNITY : GAIN_COEFF_ZERO;
}
}
Route::MuteControllable::MuteControllable (std::string name, boost::shared_ptr<Route> r)
- : AutomationControl (r->session(), Evoral::Parameter (MuteAutomation),
- boost::shared_ptr<AutomationList>(), name)
+ : AutomationControl (r->session(),
+ Evoral::Parameter (MuteAutomation),
+ ParameterDescriptor (Evoral::Parameter (MuteAutomation)),
+ boost::shared_ptr<AutomationList>(),
+ name)
, _route (r)
{
boost::shared_ptr<AutomationList> gl(new AutomationList(Evoral::Parameter(MuteAutomation)));
+ gl->set_interpolation(Evoral::ControlList::Discrete);
set_list (gl);
}
+void
+Route::MuteControllable::set_superficial_value(bool muted)
+{
+ /* Note we can not use AutomationControl::set_value here since it will emit
+ Changed(), but the value will not be correct to the observer. */
+
+ const bool to_list = _list && ((AutomationList*)_list.get ())->automation_write ();
+ const double where = _session.audible_frame ();
+ if (to_list) {
+ /* Note that we really need this:
+ * if (as == Touch && _list->in_new_write_pass ()) {
+ * alist->start_write_pass (_session.audible_frame ());
+ * }
+ * here in the case of the user calling from a GUI or whatever.
+ * Without the ability to distinguish between user and
+ * automation-initiated changes, we lose the "touch mute"
+ * behaviour we have in AutomationController::toggled ().
+ */
+ _list->set_in_write_pass (true, false, where);
+ }
+
+ Control::set_double (muted, where, to_list);
+}
+
void
Route::MuteControllable::set_value (double val)
{
- bool bval = ((val >= 0.5f) ? true: false);
+ if (writable()) {
+ set_value_unchecked (val);
+ }
+}
- boost::shared_ptr<RouteList> rl (new RouteList);
+void
+Route::MuteControllable::set_value_unchecked (double val)
+{
+ const bool bval = ((val >= 0.5) ? true : false);
boost::shared_ptr<Route> r = _route.lock ();
if (!r) {
return;
}
- rl->push_back (r);
- _session.set_mute (rl, bval);
+ if (_list && ((AutomationList*)_list.get())->automation_playback()) {
+ // Set superficial/automation value to drive controller (and possibly record)
+ set_superficial_value (bval);
+ // Playing back automation, set route mute directly
+ r->set_mute (bval, this);
+ } else {
+ // Set from user, queue mute event
+ boost::shared_ptr<RouteList> rl (new RouteList);
+ rl->push_back (r);
+ _session.set_mute (rl, bval, Session::rt_cleanup);
+ }
}
double
Route::MuteControllable::get_value () const
{
+ if (_list && ((AutomationList*)_list.get())->automation_playback()) {
+ // Playing back automation, get the value from the list
+ return AutomationControl::get_value();
+ }
+
+ // Not playing back automation, get the actual route mute value
boost::shared_ptr<Route> r = _route.lock ();
- if (!r) {
- return 0;
+ return (r && r->muted()) ? GAIN_COEFF_UNITY : GAIN_COEFF_ZERO;
+}
+
+Route::GroupGainControllable::GroupGainControllable (std::string name, boost::shared_ptr<Route> r)
+ : AutomationControl (r->session(),
+ Evoral::Parameter (GainAutomation),
+ ParameterDescriptor (Evoral::Parameter (GainAutomation)),
+ boost::shared_ptr<AutomationList>(),
+ name)
+ , _route (r)
+{
+ boost::shared_ptr<AutomationList> gl(new AutomationList(Evoral::Parameter(GainAutomation)));
+ gl->set_interpolation(Evoral::ControlList::Discrete);
+ set_list (gl);
+}
+
+void
+Route::GroupGainControllable::set_value (double val)
+{
+ boost::shared_ptr<Route> r = _route.lock ();
+ // I am not sure why I need the * .5 to make this work
+ float normalized_position = r->gain_control()->interface_to_internal (val * 0.5);
+ r->set_gain ((gain_t)normalized_position, this);
+}
+
+double
+Route::GroupGainControllable::get_value () const
+{
+ boost::shared_ptr<Route> r = _route.lock ();
+ return 2.0 * r->gain_control()->internal_to_interface (r->gain_control()->get_value ());
+}
+
+Route::PhaseControllable::PhaseControllable (std::string name, boost::shared_ptr<Route> r)
+ : AutomationControl (r->session(),
+ Evoral::Parameter (PhaseAutomation),
+ ParameterDescriptor (Evoral::Parameter (PhaseAutomation)),
+ boost::shared_ptr<AutomationList>(),
+ name)
+ , _route (r)
+{
+ boost::shared_ptr<AutomationList> gl(new AutomationList(Evoral::Parameter(PhaseAutomation)));
+ gl->set_interpolation(Evoral::ControlList::Discrete);
+ set_list (gl);
+}
+
+void
+Route::PhaseControllable::set_value (double v)
+{
+ boost::shared_ptr<Route> r = _route.lock ();
+ if (r->phase_invert().size()) {
+ if (v == 0 || (v < 1 && v > 0.9) ) {
+ r->set_phase_invert (_current_phase, false);
+ } else {
+ r->set_phase_invert (_current_phase, true);
+ }
}
+}
+
+double
+Route::PhaseControllable::get_value () const
+{
+ boost::shared_ptr<Route> r = _route.lock ();
+ return (double) r->phase_invert (_current_phase);
+}
+
+void
+Route::PhaseControllable::set_channel (uint32_t c)
+{
+ _current_phase = c;
+}
- return r->muted() ? 1.0f : 0.0f;
+uint32_t
+Route::PhaseControllable::channel () const
+{
+ return _current_phase;
}
void
_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());
int
Route::save_as_template (const string& path, const string& name)
{
+ {
+ // would be nice to use foreach_processor()
+ Glib::Threads::RWLock::ReaderLock lm (_processor_lock);
+ std::string state_dir = path.substr (0, path.find_last_of ('.')); // strip template_suffix
+ for (ProcessorList::const_iterator i = _processors.begin(); i != _processors.end(); ++i) {
+ boost::shared_ptr<PluginInsert> pi = boost::dynamic_pointer_cast<PluginInsert> (*i);
+ if (pi) {
+ pi->set_state_dir (state_dir);
+ }
+ }
+ }
+
XMLNode& node (state (false));
+
+ {
+ Glib::Threads::RWLock::ReaderLock lm (_processor_lock);
+ for (ProcessorList::const_iterator i = _processors.begin(); i != _processors.end(); ++i) {
+ boost::shared_ptr<PluginInsert> pi = boost::dynamic_pointer_cast<PluginInsert> (*i);
+ if (pi) {
+ pi->set_state_dir ();
+ }
+ }
+ }
+
XMLTree tree;
IO::set_name_in_state (*node.children().front(), name);
tree.set_root (&node);
- return tree.write (path.c_str());
+ // TODO: special case LV2 plugin state
+ // copy of serialize it. Alternatively:
+ // create a plugin-preset (which can be loaded separately)
+
+ /* 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);
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);
-
+
}
}
}
}
}
-void
-Route::meter ()
-{
- 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 _amp->gain_control();
}
+boost::shared_ptr<AutomationControl>
+Route::trim_control() const
+{
+ return _trim->gain_control();
+}
+
boost::shared_ptr<AutomationControl>
Route::get_control (const Evoral::Parameter& param)
{
/* 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;
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;
}
}
{
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) {
+ while (amp != new_processors.end() && *amp != _amp) {
++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 */
new_processors.push_front (_intreturn);
}
+ if (_trim && _trim->active()) {
+ assert (!_trim->display_to_user ());
+ new_processors.push_front (_trim);
+ }
/* EXPORT PROCESSOR */
if (_capturing_processor) {
_processors = new_processors;
+ for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) {
+ if (!(*i)->display_to_user () && !(*i)->active () && (*i) != _monitor_send) {
+ (*i)->activate ();
+ }
+ }
+
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;;
}
{
//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();
+ assert (plug);
+ 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");
+ }
+
+ 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:
+ return _("Rels");
+ }
+ return _("???");
+#else
+ return _("???");
+#endif
+}