#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/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/delayline.h"
#include "ardour/midi_buffer.h"
#include "ardour/midi_port.h"
#include "ardour/monitor_processor.h"
#include "ardour/pannable.h"
#include "ardour/panner.h"
#include "ardour/panner_shell.h"
+#include "ardour/parameter_descriptor.h"
#include "ardour/plugin_insert.h"
#include "ardour/port.h"
#include "ardour/port_insert.h"
#include "ardour/processor.h"
+#include "ardour/profile.h"
#include "ardour/route.h"
#include "ardour/route_group.h"
#include "ardour/send.h"
PBD::Signal0<void> Route::SyncOrderKeys;
PBD::Signal0<void> Route::RemoteControlIDChange;
+/** Base class for all routable/mixable objects (tracks and busses) */
Route::Route (Session& sess, string name, Flag flg, DataType default_type)
: SessionObject (sess, name)
, Automatable (sess)
, GraphNode (sess._process_graph)
, _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)
+ , _in_sidechain_setup (false)
+ , _strict_io (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_isolate_control.reset (new SoloIsolateControllable (X_("solo-iso"), shared_from_this ()));
+ _solo_safe_control.reset (new SoloSafeControllable (X_("solo-safe"), shared_from_this ()));
_solo_control->set_flags (Controllable::Flag (_solo_control->flags() | Controllable::Toggle));
_mute_control->set_flags (Controllable::Flag (_mute_control->flags() | Controllable::Toggle));
+ _phase_control->set_flags (Controllable::Flag (_phase_control->flags() | Controllable::Toggle));
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));
+ _gain_control = boost::shared_ptr<GainControllable> (new GainControllable (_session, GainAutomation, shared_from_this ()));
+ add_control (_gain_control);
+
+ _amp.reset (new Amp (_session, X_("Fader"), _gain_control, true));
add_processor (_amp, PostFader);
+ if (is_monitor ()) {
+ _amp->set_display_name (_("Monitor"));
+ }
+
+ /* and input trim */
+
+ _trim_control = boost::shared_ptr<GainControllable> (new GainControllable (_session, TrimAutomation, shared_from_this ()));
+ add_control (_trim_control);
+
+ _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 ()
*/
/* 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)
+Route::inc_gain (gain_t factor)
{
- _amp->inc_gain (fraction, src);
+ /* To be used ONLY when doing group-relative gain adjustment, from
+ * ::set_gain()
+ */
+
+ float desired_gain = _gain_control->user_double();
+
+ if (fabsf (desired_gain) < GAIN_COEFF_SMALL) {
+ // really?! what's the idea here?
+ _gain_control->route_set_value (0.000001f + (0.000001f * factor));
+ } else {
+ _gain_control->route_set_value (desired_gain + (desired_gain * factor));
+ }
}
void
-Route::set_gain (gain_t val, void *src)
+Route::set_gain (gain_t val, Controllable::GroupControlDisposition group_override)
{
- if (src != 0 && _route_group && src != _route_group && _route_group->is_active() && _route_group->is_gain()) {
+ if (use_group (group_override, &RouteGroup::is_gain)) {
if (_route_group->is_relative()) {
- gain_t usable_gain = _amp->gain();
+ gain_t usable_gain = _gain_control->get_value();
if (usable_gain < 0.000001f) {
usable_gain = 0.000001f;
}
}
}
- _route_group->foreach_route (boost::bind (&Route::inc_gain, _1, factor, _route_group));
+ _route_group->foreach_route (boost::bind (&Route::inc_gain, _1, factor));
} else {
- _route_group->foreach_route (boost::bind (&Route::set_gain, _1, val, _route_group));
+ _route_group->foreach_route (boost::bind (&Route::set_gain, _1, val, Controllable::NoGroup));
}
return;
}
- if (val == _amp->gain()) {
+ if (val == _gain_control->get_value()) {
return;
}
- _amp->set_gain (val, src);
+ _gain_control->route_set_value (val);
+}
+
+void
+Route::set_trim (gain_t val, Controllable::GroupControlDisposition /* group override */)
+{
+ // TODO route group, see set_gain()
+ _trim_control->route_set_value (val);
}
void
framepos_t start_frame, framepos_t end_frame, pframes_t nframes,
int declick, bool gain_automation_ok)
{
+ /* Caller must hold process lock */
+ 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) {
_amp->set_gain_automation_buffer (_session.gain_automation_buffer ());
- _amp->setup_gain_automation (start_frame, end_frame, nframes);
+ _amp->setup_gain_automation (
+ 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.)
/* set this to be true if the meter will already have been ::run() earlier */
bool const meter_already_run = metering_state() == MeteringInput;
- for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) {
+ framecnt_t latency = 0;
+
+ 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 */
do we catch route != active somewhere higher?
*/
- (*i)->run (bufs, start_frame, end_frame, nframes, *i != _processors.back());
+ if (boost::dynamic_pointer_cast<Send>(*i) != 0) {
+ boost::dynamic_pointer_cast<Send>(*i)->set_delay_in(_signal_latency - latency);
+ }
+
+ (*i)->run (bufs, start_frame - latency, end_frame - latency, nframes, *i != _processors.back());
bufs.set_count ((*i)->output_streams());
+
+ if ((*i)->active ()) {
+ latency += (*i)->signal_latency ();
+ }
+ }
+}
+
+void
+Route::bounce_process (BufferSet& buffers, framepos_t start, framecnt_t nframes,
+ boost::shared_ptr<Processor> endpoint,
+ bool include_endpoint, bool for_export, bool for_freeze)
+{
+ /* If no processing is required, there's no need to go any further. */
+ if (!endpoint && !include_endpoint) {
+ return;
+ }
+
+ framecnt_t latency = bounce_get_latency(_amp, false, for_export, for_freeze);
+ _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) {
+
+ if (!include_endpoint && (*i) == endpoint) {
+ break;
+ }
+
+ /* if we're *not* exporting, stop processing if we come across a routing processor. */
+ if (!for_export && boost::dynamic_pointer_cast<PortInsert>(*i)) {
+ break;
+ }
+ if (!for_export && for_freeze && (*i)->does_routing() && (*i)->active()) {
+ break;
+ }
+
+ /* special case the panner (export outputs)
+ * Ideally we'd only run the panner, not the delivery itself...
+ * but panners need separate input/output buffers and some context
+ * (panshell, panner type, etc). AFAICT there is no ill side effect
+ * of re-using the main delivery when freewheeling/exporting a region.
+ */
+ if ((*i) == _main_outs) {
+ assert ((*i)->does_routing());
+ (*i)->run (buffers, start - latency, start - latency + nframes, nframes, true);
+ buffers.set_count ((*i)->output_streams());
+ }
+
+ /* don't run any processors that do routing.
+ * Also don't bother with metering.
+ */
+ if (!(*i)->does_routing() && !boost::dynamic_pointer_cast<PeakMeter>(*i)) {
+ (*i)->run (buffers, start - latency, start - latency + nframes, nframes, true);
+ buffers.set_count ((*i)->output_streams());
+ latency += (*i)->signal_latency ();
+ }
+
+ if ((*i) == endpoint) {
+ break;
+ }
+ }
+}
+
+framecnt_t
+Route::bounce_get_latency (boost::shared_ptr<Processor> endpoint,
+ bool include_endpoint, bool for_export, bool for_freeze) const
+{
+ framecnt_t latency = 0;
+ if (!endpoint && !include_endpoint) {
+ return latency;
+ }
+
+ for (ProcessorList::const_iterator i = _processors.begin(); i != _processors.end(); ++i) {
+ if (!include_endpoint && (*i) == endpoint) {
+ break;
+ }
+ if (!for_export && boost::dynamic_pointer_cast<PortInsert>(*i)) {
+ break;
+ }
+ if (!for_export && for_freeze && (*i)->does_routing() && (*i)->active()) {
+ break;
+ }
+ if (!(*i)->does_routing() && !boost::dynamic_pointer_cast<PeakMeter>(*i)) {
+ latency += (*i)->signal_latency ();
+ }
+ if ((*i) == endpoint) {
+ break;
+ }
+ }
+ return latency;
+}
+
+ChanCount
+Route::bounce_get_output_streams (ChanCount &cc, boost::shared_ptr<Processor> endpoint,
+ bool include_endpoint, bool for_export, bool for_freeze) const
+{
+ if (!endpoint && !include_endpoint) {
+ return cc;
+ }
+
+ for (ProcessorList::const_iterator i = _processors.begin(); i != _processors.end(); ++i) {
+ if (!include_endpoint && (*i) == endpoint) {
+ break;
+ }
+ if (!for_export && boost::dynamic_pointer_cast<PortInsert>(*i)) {
+ break;
+ }
+ if (!for_export && for_freeze && (*i)->does_routing() && (*i)->active()) {
+ break;
+ }
+ if (!(*i)->does_routing() && !boost::dynamic_pointer_cast<PeakMeter>(*i)) {
+ cc = (*i)->output_streams();
+ }
+ if ((*i) == endpoint) {
+ break;
+ }
}
+ return cc;
}
ChanCount
{
assert (is_monitor());
BufferSet& bufs (_session.get_route_buffers (n_process_buffers()));
+ fill_buffers_with_input (bufs, _input, nframes);
passthru (bufs, start_frame, end_frame, nframes, declick);
}
}
void
-Route::set_listen (bool yn, void* src)
+Route::set_listen (bool yn, Controllable::GroupControlDisposition 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));
+ if (use_group (group_override, &RouteGroup::is_solo)) {
+ _route_group->foreach_route (boost::bind (&Route::set_listen, _1, yn, Controllable::ForGroup));
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 (group_override); /* EMIT SIGNAL */
}
}
}
}
void
-Route::set_solo_safe (bool yn, void *src)
+Route::set_solo_safe (bool yn, Controllable::GroupControlDisposition /* group_override */)
{
if (_solo_safe != yn) {
_solo_safe = yn;
- solo_safe_changed (src);
+ solo_safe_changed (); /* EMIT SIGNAL */
+ _solo_safe_control->Changed(); /* 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, Controllable::NoGroup);
+ }
+
+ if (emit_changed) {
+ set_mute_master_solo ();
+ solo_changed (false, Controllable::UseGroup); /* EMIT SIGNAL */
+ }
+}
+
+void
+Route::set_solo (bool yn, Controllable::GroupControlDisposition group_override)
{
+ DEBUG_TRACE (DEBUG::Solo, string_compose ("%1: set solo => %2, grp ? %3 currently self-soloed ? %4\n",
+ name(), yn, enum_2_string(group_override), self_soloed()));
+
if (_solo_safe) {
DEBUG_TRACE (DEBUG::Solo, string_compose ("%1 ignore solo change due to solo-safe\n", name()));
return;
}
- if (_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;
}
- 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 (use_group (group_override, &RouteGroup::is_solo)) {
+ _route_group->foreach_route (boost::bind (&Route::set_solo, _1, yn, Controllable::ForGroup));
+ return;
+ }
if (self_soloed() != yn) {
set_self_solo (yn);
- set_mute_master_solo ();
- solo_changed (true, src); /* EMIT SIGNAL */
+ solo_changed (true, group_override); /* EMIT SIGNAL */
_solo_control->Changed (); /* EMIT SIGNAL */
}
+
+ assert (Config->get_solo_control_is_listen_control() || !_monitor_send || !_monitor_send->active());
+
+ /* XXX TRACKS DEVELOPERS: THIS LOGIC SUGGESTS THAT YOU ARE NOT AWARE OF
+ Config->get_solo_mute_overrride().
+ */
+
+ if (yn && Profile->get_trx()) {
+ set_mute (false, Controllable::UseGroup);
+ }
}
void
{
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, Controllable::UseGroup); /* 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, Controllable::UseGroup); /* 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)
+{
+ 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 (); /* EMIT SIGNAL */
+ }
}
void
-Route::set_solo_isolated (bool yn, void *src)
+Route::set_solo_isolated (bool yn, Controllable::GroupControlDisposition group_override)
{
if (is_master() || is_monitor() || is_auditioner()) {
return;
}
- if (_route_group && src != _route_group && _route_group->is_active() && _route_group->is_solo()) {
- _route_group->foreach_route (boost::bind (&Route::set_solo_isolated, _1, yn, _route_group));
+ if (use_group (group_override, &RouteGroup::is_solo)) {
+ _route_group->foreach_route (boost::bind (&Route::set_solo_isolated, _1, yn, Controllable::ForGroup));
+ return;
+ }
+
+ 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;
}
}
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);
}
}
/* 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 (); /* EMIT SIGNAL */
+ _solo_isolate_control->Changed(); /* EMIT SIGNAL */
}
bool
Route::solo_isolated () const
{
- return _solo_isolated > 0;
+ return (_solo_isolated == true) || (_solo_isolated_by_upstream > 0);
}
void
mute_points_changed (); /* EMIT SIGNAL */
if (_mute_master->muted_by_self()) {
- mute_changed (this); /* EMIT SIGNAL */
+ mute_changed (); /* EMIT SIGNAL */
_mute_control->Changed (); /* EMIT SIGNAL */
}
}
void
-Route::set_mute (bool yn, void *src)
+Route::set_mute (bool yn, Controllable::GroupControlDisposition group_override)
{
- if (_route_group && src != _route_group && _route_group->is_active() && _route_group->is_mute()) {
- _route_group->foreach_route (boost::bind (&Route::set_mute, _1, yn, _route_group));
+ if (use_group (group_override, &RouteGroup::is_mute)) {
+ _route_group->foreach_route (boost::bind (&Route::set_mute, _1, yn, Controllable::ForGroup));
return;
}
*/
act_on_mute ();
/* tell everyone else */
- mute_changed (src); /* EMIT SIGNAL */
+ mute_changed (); /* EMIT SIGNAL */
_mute_control->Changed (); /* EMIT SIGNAL */
}
}
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;
}
return 1;
}
+ if (_strict_io) {
+ boost::shared_ptr<PluginInsert> pi;
+ if ((pi = boost::dynamic_pointer_cast<PluginInsert>(processor)) != 0) {
+ pi->set_strict_io (true);
+ }
+ }
+
{
Glib::Threads::Mutex::Lock lx (AudioEngine::instance()->process_lock ());
Glib::Threads::RWLock::WriterLock lm (_processor_lock);
}
- 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));
+ 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) {
boost::shared_ptr<PluginInsert> pi;
if ((pi = boost::dynamic_pointer_cast<PluginInsert>(*i)) != 0) {
- pi->set_count (1);
+ pi->set_strict_io (_strict_io);
}
_processors.insert (loc, *i);
seen_amp = true;
}
- if ((*i) == _amp || (*i) == _meter || (*i) == _main_outs) {
+ if ((*i) == _amp || (*i) == _meter || (*i) == _main_outs || (*i) == _delayline || (*i) == _trim) {
/* you can't remove these */
{
// TODO once the export point can be configured properly, do something smarter here
if (processor == _capturing_processor) {
+ Glib::Threads::Mutex::Lock lx (AudioEngine::instance()->process_lock (), Glib::Threads::NOT_LOCK);
+ if (need_process_lock) {
+ lx.acquire();
+ }
+
_capturing_processor.reset();
+
+ if (need_process_lock) {
+ lx.release();
+ }
}
/* these can never be removed */
- if (processor == _amp || processor == _meter || processor == _main_outs) {
+ if (processor == _amp || processor == _meter || processor == _main_outs || processor == _delayline || processor == _trim) {
return 0;
}
if (need_process_lock) {
lx.acquire();
}
- Glib::Threads::RWLock::WriterLock lm (_processor_lock);
+
+ /* Caller must hold process lock */
+ assert (!AudioEngine::instance()->process_lock().trylock());
+
+ Glib::Threads::RWLock::WriterLock lm (_processor_lock); // XXX deadlock after export
+
ProcessorState pstate (this);
ProcessorList::iterator i;
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 ((iop = boost::dynamic_pointer_cast<IOProcessor> (*i)) != 0) {
+ if (pi != 0) {
+ assert (iop == 0);
+ iop = pi->sidechain();
+ }
+
+ if (iop != 0) {
iop->disconnect ();
}
if (!removed) {
/* what? */
return 1;
- }
+ }
if (configure_processors_unlocked (err)) {
pstate.restore ();
}
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->active ();
for (i = _processors.begin(); i != _processors.end(); ) {
-
- processor = *i;
-
- /* these can never be removed */
-
- if (processor == _amp || processor == _meter || processor == _main_outs) {
+ if (*i == old) {
+ i = _processors.erase (i);
+ _processors.insert (i, sub);
+ sub->set_owner (this);
+ replaced = true;
+ break;
+ } else {
++i;
- continue;
+ }
+ }
+
+ if (!replaced) {
+ return 1;
+ }
+
+ 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)) {
+ pstate.restore ();
+ configure_processors_unlocked (0);
+ 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->activate ();
+ }
+
+ sub->ActiveChanged.connect_same_thread (*this, boost::bind (&Session::update_latency_compensation, &_session, false));
+ _output->set_user_latency (0);
+ }
+
+ reset_instrument_info ();
+ old->drop_references ();
+ processors_changed (RouteProcessorChange ()); /* EMIT SIGNAL */
+ set_processor_positions ();
+ return 0;
+}
+
+int
+Route::remove_processors (const ProcessorList& to_be_deleted, ProcessorStreams* err)
+{
+ 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 */
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 ();
}
return 0;
}
-void
-Route::set_custom_panner_uri (std::string const panner_uri)
-{
- if (_in_configure_processors) {
- DEBUG_TRACE (DEBUG::Panning, string_compose (_("Route::set_custom_panner_uri '%1' -- called while in_configure_processors\n"), name()));
- return;
- }
-
- if (!_main_outs->panner_shell()->set_user_selected_panner_uri(panner_uri)) {
- DEBUG_TRACE (DEBUG::Panning, string_compose (_("Route::set_custom_panner_uri '%1 '%2' -- no change needed\n"), name(), panner_uri));
- /* no change needed */
- return;
- }
-
- DEBUG_TRACE (DEBUG::Panning, string_compose (_("Route::set_custom_panner_uri '%1 '%2' -- reconfigure I/O\n"), name(), panner_uri));
-
- /* reconfigure I/O -- re-initialize panner modules */
- {
- Glib::Threads::Mutex::Lock lx (AudioEngine::instance()->process_lock ());
- Glib::Threads::RWLock::WriterLock lm (_processor_lock);
-
- for (ProcessorList::iterator p = _processors.begin(); p != _processors.end(); ++p) {
- boost::shared_ptr<Delivery> dl;
- boost::shared_ptr<Panner> panner;
- if ((dl = boost::dynamic_pointer_cast<Delivery> (*p)) == 0) {
- continue;
- }
- if (!dl->panner_shell()) {
- continue;
- }
- if (!(panner = dl->panner_shell()->panner())) {
- continue;
- }
- /* _main_outs has already been set before the loop.
- * Ignore the return status here. It need reconfiguration */
- if (dl->panner_shell() != _main_outs->panner_shell()) {
- if (!dl->panner_shell()->set_user_selected_panner_uri(panner_uri)) {
- continue;
- }
- }
-
- ChanCount in = panner->in();
- ChanCount out = panner->out();
- dl->panner_shell()->configure_io(in, out);
- dl->panner_shell()->pannable()->set_panner(dl->panner_shell()->panner());
- }
- }
-
- processors_changed (RouteProcessorChange ()); /* EMIT SIGNAL */
- _session.set_dirty ();
-}
-
void
Route::reset_instrument_info ()
{
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
+ && _strict_io) {
+ /* with strict I/O the panner + output are forced to
+ * follow the last processor's output.
+ *
+ * Delivery::can_support_io_configuration() will only add ports,
+ * but not remove excess ports.
+ *
+ * This works because the delivery only requires
+ * as many outputs as there are inputs.
+ * Delivery::configure_io() will do the actual removal
+ * by calling _output->ensure_io()
+ */
+ if (!is_master() && _session.master_out ()) {
+ /* ..but at least as many as there are master-inputs */
+ // XXX this may need special-casing for mixbus (master-outputs)
+ // and should maybe be a preference anyway ?!
+ out = ChanCount::max (in, _session.master_out ()->n_inputs ());
+ } else {
+ out = in;
+ }
+ }
+
DEBUG_TRACE (DEBUG::Processors, string_compose ("\t%1 ID=%2 in=%3 out=%4\n",(*p)->name(), (*p)->id(), in, out));
configuration.push_back(make_pair(in, out));
+
+ if (is_monitor()) {
+ // restriction for Monitor Section Processors
+ if (in.n_audio() != out.n_audio() || out.n_midi() > 0) {
+ /* do not allow to add/remove channels (for now)
+ * The Monitor follows the master-bus and has no panner (unpan)
+ * but do allow processors with midi-in to be added (e.g VSTs with control that
+ * will remain unconnected)
+ */
+ DEBUG_TRACE (DEBUG::Processors, "Monitor: Channel configuration not allowed.\n");
+ return list<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) {
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;
+ 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<PluginInsert> pi;
+ if ((pi = boost::dynamic_pointer_cast<PluginInsert>(*p)) != 0) {
+ /* plugins connected via Split or Hide Match may have more channels.
+ * route/scratch buffers are needed for all of them
+ * The configuration may only be a subset (both input and output)
+ */
+ processor_max_streams = ChanCount::max(processor_max_streams, pi->input_streams());
+ processor_max_streams = ChanCount::max(processor_max_streams, pi->internal_streams());
+ processor_max_streams = ChanCount::max(processor_max_streams, pi->output_streams());
+ processor_max_streams = ChanCount::max(processor_max_streams, pi->natural_input_streams() * pi->get_count());
+ processor_max_streams = ChanCount::max(processor_max_streams, pi->natural_output_streams() * pi->get_count());
+ }
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 ();
}
+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.
+ */
+
+ /* "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 as_it_will_be;
+ ProcessorList::iterator oiter;
+ ProcessorList::const_iterator niter;
+
+ oiter = _processors.begin();
+ niter = new_order.begin();
+
+ 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.
+
+ 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()) {
+
+ /* 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;
+
+ } else {
+
+ if (!(*oiter)->display_to_user()) {
+
+ as_it_will_be.push_back (*oiter);
+
+ } else {
+
+ /* 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;
+ }
+ }
+
+ /* 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());
+
+ /* 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)
{
- /* "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.
- */
+ // 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);
- ProcessorList::iterator oiter;
- ProcessorList::const_iterator niter;
- ProcessorList as_it_will_be;
+ apply_processor_order (new_order);
- oiter = _processors.begin();
- niter = new_order.begin();
+ if (configure_processors_unlocked (err)) {
+ pstate.restore ();
+ return -1;
+ }
- while (niter != new_order.end()) {
+ lm.release();
+ lx.release();
- /* 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.
+ processors_changed (RouteProcessorChange ()); /* EMIT SIGNAL */
+ set_processor_positions ();
- 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.
- */
+ } else {
+ DEBUG_TRACE (DEBUG::Processors, "Queue clickless processor re-order.\n");
+ Glib::Threads::RWLock::ReaderLock lm (_processor_lock);
- if (oiter == _processors.end()) {
+ // _pending_processor_order is protected by _processor_lock
+ _pending_processor_order = new_order;
+ g_atomic_int_set (&_pending_process_reorder, 1);
+ }
- /* no more elements in the old list, so just stick the rest of
- the new order onto the temp list.
- */
+ return 0;
+}
- as_it_will_be.insert (as_it_will_be.end(), niter, new_order.end());
- while (niter != new_order.end()) {
- ++niter;
- }
- break;
+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;
+ }
+ if (pi->has_sidechain () == add) {
+ return true; // ?? call failed, but result is as expected.
+ }
+
+ {
+ Glib::Threads::RWLock::ReaderLock lm (_processor_lock);
+ ProcessorList::iterator i = find (_processors.begin(), _processors.end(), proc);
+ if (i == _processors.end ()) {
+ return false;
+ }
+ }
+
+ {
+ Glib::Threads::Mutex::Lock lx (AudioEngine::instance()->process_lock ()); // take before Writerlock to avoid deadlock
+ Glib::Threads::RWLock::WriterLock lm (_processor_lock);
+ PBD::Unwinder<bool> uw (_in_sidechain_setup, true);
+
+ lx.release (); // IO::add_port() and ~IO takes process lock - XXX check if this is safe
+ if (add) {
+ if (!pi->add_sidechain ()) {
+ return false;
+ }
+ } else {
+ if (!pi->del_sidechain ()) {
+ return false;
+ }
+ }
+
+ lx.acquire ();
+ list<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);
+ }
- if (!(*oiter)->display_to_user()) {
+ if (pi->has_sidechain ()) {
+ pi->sidechain_input ()->changed.connect_same_thread (*this, boost::bind (&Route::sidechain_change_handler, this, _1, _2));
+ }
- as_it_will_be.push_back (*oiter);
+ processors_changed (RouteProcessorChange ()); /* EMIT SIGNAL */
+ _session.set_dirty ();
+ return true;
+}
- } else {
+bool
+Route::reset_plugin_insert (boost::shared_ptr<Processor> proc)
+{
+ ChanCount unused;
+ return customize_plugin_insert (proc, 0, unused);
+}
- /* visible processor: check that its in the new order */
+bool
+Route::customize_plugin_insert (boost::shared_ptr<Processor> proc, uint32_t count, ChanCount outs)
+{
+ boost::shared_ptr<PluginInsert> pi;
+ if ((pi = boost::dynamic_pointer_cast<PluginInsert>(proc)) == 0) {
+ return false;
+ }
- 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;
- }
- }
+ {
+ Glib::Threads::RWLock::ReaderLock lm (_processor_lock);
+ ProcessorList::iterator i = find (_processors.begin(), _processors.end(), proc);
+ if (i == _processors.end ()) {
+ return false;
+ }
+ }
- /* now remove from old order - its taken care of no matter what */
- oiter = _processors.erase (oiter);
- }
+ {
+ 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 ();
+ if (count == 0) {
+ pi->set_custom_cfg (false);
+ } else {
+ pi->set_custom_cfg (true);
+ pi->set_count (count);
+ pi->set_outputs (outs);
}
- _processors.insert (oiter, as_it_will_be.begin(), as_it_will_be.end());
+ list<pair<ChanCount, ChanCount> > c = try_configure_processors_unlocked (n_inputs (), 0);
+ if (c.empty()) {
+ /* not possible */
- /* If the meter is in a custom position, find it and make a rough note of its position */
- maybe_note_meter_position ();
+ pi->set_count (old_cnt);
+ pi->set_outputs (old_chan);
+ pi->set_custom_cfg (old_cust);
- if (configure_processors_unlocked (err)) {
- pstate.restore ();
- return -1;
+ return false;
}
+ configure_processors_unlocked (0);
}
processors_changed (RouteProcessorChange ()); /* EMIT SIGNAL */
- set_processor_positions ();
+ _session.set_dirty ();
+ return true;
+}
- return 0;
+bool
+Route::set_strict_io (const bool enable)
+{
+ 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 ();
+
+ {
+ Glib::Threads::Mutex::Lock lx (AudioEngine::instance()->process_lock ());
+ configure_processors (0);
+ }
+ processors_changed (RouteProcessorChange ()); /* EMIT SIGNAL */
+ _session.set_dirty ();
+ }
+ return true;
}
XMLNode&
XMLNode&
Route::state(bool full_state)
{
+ 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];
node->add_property("id", buf);
node->add_property ("name", _name);
node->add_property("default-type", _default_type.to_string());
+ node->add_property ("strict-io", _strict_io);
if (_flags) {
node->add_property("flags", enum_2_string (_flags));
node->add_child_nocopy (_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");
+ if (!_session._template_state_dir.empty()) {
+ foreach_processor (sigc::bind (sigc::mem_fun (*this, &Route::set_plugin_state_dir), ""));
}
return *node;
_flags = Flag (0);
}
+ if ((prop = node.property (X_("strict-io"))) != 0) {
+ _strict_io = string_is_affirmative (prop->value());
+ }
+
if (is_master() || is_monitor() || is_auditioner()) {
_mute_master->set_solo_ignore (true);
}
}
if ((prop = node.property ("solo-isolated")) != 0) {
- set_solo_isolated (string_is_affirmative (prop->value()), this);
+ set_solo_isolated (string_is_affirmative (prop->value()), Controllable::NoGroup);
}
if ((prop = node.property ("solo-safe")) != 0) {
- set_solo_safe (string_is_affirmative (prop->value()), this);
+ set_solo_safe (string_is_affirmative (prop->value()), Controllable::NoGroup);
}
if ((prop = node.property (X_("phase-invert"))) != 0) {
}
}
- 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;
/* XXX force reset of solo status */
- set_solo (yn, this);
+ set_solo (yn);
}
if ((prop = node.property (X_("muted"))) != 0) {
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);
}
}
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") {
+ 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") {
- boost::shared_ptr<Pannable> sendpan (new Pannable (_session));
- processor.reset (new InternalSend (_session, sendpan, _mute_master, boost::shared_ptr<Route>(), Delivery::Role (0)));
+ 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") {
- boost::shared_ptr<Pannable> sendpan (new Pannable (_session));
- processor.reset (new Send (_session, sendpan, _mute_master));
+ processor.reset (new Send (_session, _pannable, _mute_master, Delivery::Send, true));
} 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.
*/
}
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;
+ }
}
}
}
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;
}
+ Glib::Threads::RWLock::ReaderLock lm (_processor_lock); // XXX
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 ((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 ());
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;
+ }
+ }
+ }
+ }
+
+ 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);
+ }
+ // 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);
+ }
}
}
}
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 (_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)
+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;
+ }
+
+ if ((change.type & IOChange::ConfigurationChanged)) {
+ /* This is called with the process lock held if change
+ contains ConfigurationChanged
+ */
+ configure_processors (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;
boost::shared_ptr<CapturingProcessor>
Route::add_export_point()
{
+ Glib::Threads::RWLock::ReaderLock lm (_processor_lock);
if (!_capturing_processor) {
+ lm.release();
+ Glib::Threads::Mutex::Lock lx (AudioEngine::instance()->process_lock ());
+ Glib::Threads::RWLock::WriterLock lw (_processor_lock);
_capturing_processor.reset (new CapturingProcessor (_session));
_capturing_processor->activate ();
- configure_processors (0);
+ configure_processors_unlocked (0);
}
Route::update_signal_latency ()
{
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 ()) {
l += (*i)->signal_latency ();
}
+ 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);
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);
-
+
}
}
}
if (_phase_invert[c] != yn) {
_phase_invert[c] = yn;
phase_invert_changed (); /* EMIT SIGNAL */
+ _phase_control->Changed(); /* EMIT SIGNAL */
_session.set_dirty ();
}
}
void
Route::set_active (bool yn, void* src)
{
+ if (_session.transport_rolling()) {
+ return;
+ }
+
if (_route_group && src != _route_group && _route_group->is_active() && _route_group->is_route_active()) {
_route_group->foreach_route (boost::bind (&Route::set_active, _1, yn, _route_group));
return;
}
}
-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 _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<Route::PhaseControllable>
+Route::phase_control() const
+{
+ if (phase_invert().size()) {
+ return _phase_control;
+ } else {
+ return boost::shared_ptr<PhaseControllable>();
+ }
}
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;
}
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;
}
- assert (amp != _processors.end ());
+ assert (amp != new_processors.end ());
/* 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);
}
/* INTERNAL RETURN */
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;;
}
_pannable->transport_located (pos);
}
+ if (_delayline.get()) {
+ _delayline.get()->flush();
+ }
+
{
//Glib::Threads::Mutex::Lock lx (AudioEngine::instance()->process_lock ());
- Glib::Threads::RWLock::WriterLock lm (_processor_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
+}