#include <cassert>
#include <algorithm>
+#include <glibmm.h>
#include <boost/algorithm/string.hpp>
#include "pbd/xml++.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/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/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"
, 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)
, _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_by_upstream (0)
, _denormal_protection (false)
, _recordable (true)
, _silent (false)
, _order_key (0)
, _has_order_key (false)
, _remote_control_id (0)
+ , _track_number (0)
, _in_configure_processors (false)
, _initial_io_setup (false)
, _custom_meter_position_noted (false)
- , _last_custom_meter_was_at_end (false)
{
- if (is_master()) {
- _meter_type = MeterK20;
- }
processor_max_streams.reset();
}
int
Route::init ()
{
+ /* set default meter type */
+ if (is_master()) {
+ _meter_type = Config->get_meter_type_master ();
+ }
+ else if (dynamic_cast<Track*>(this)) {
+ _meter_type = Config->get_meter_type_track ();
+ } else {
+ _meter_type = Config->get_meter_type_bus ();
+ }
+
/* add standard controls */
_solo_control.reset (new SoloControllable (X_("solo"), shared_from_this ()));
_output->changed.connect_same_thread (*this, boost::bind (&Route::output_change_handler, this, _1, _2));
_output->PortCountChanging.connect_same_thread (*this, boost::bind (&Route::output_port_count_changing, this, _1));
+#if 0 // not used - just yet
+ if (!is_master() && !is_monitor() && !is_auditioner()) {
+ _delayline.reset (new DelayLine (_session, _name));
+ add_processor (_delayline, PreFader);
+ }
+#endif
+
/* add amp processor */
_amp.reset (new Amp (_session));
add_processor (_amp, PostFader);
+ /* and input trim */
+ _trim.reset (new Amp (_session, "trim"));
+ _trim->set_display_to_user (false);
+
+ if (dynamic_cast<AudioTrack*>(this)) {
+ /* we can't do this in the AudioTrack's constructor
+ * because _trim does not exit then
+ */
+ _trim->activate();
+ }
+ else if (!dynamic_cast<Track*>(this) && ! ( is_monitor() || is_auditioner() )) {
+ /* regular bus */
+ _trim->activate();
+ }
+
/* create standard processors: meter, main outs, monitor out;
they will be added to _processors by setup_invisible_processors ()
*/
/* now that we have _meter, its safe to connect to this */
- Metering::Meter.connect_same_thread (*this, (boost::bind (&Route::meter, this)));
-
{
Glib::Threads::Mutex::Lock lx (AudioEngine::instance()->process_lock ());
configure_processors (0);
return newname;
}
-
void
Route::inc_gain (gain_t fraction, void *src)
{
_amp->set_gain (val, src);
}
+void
+Route::inc_trim (gain_t fraction, void *src)
+{
+ _trim->inc_gain (fraction, src);
+}
+
+void
+Route::set_trim (gain_t val, void * /* src */)
+{
+ // TODO route group, see set_gain()
+ _trim->set_gain (val, 0);
+}
+
void
Route::maybe_declick (BufferSet&, framecnt_t, int)
{
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.
*/
+ bool silence = monitoring_state () == MonitoringSilence;
+
+ //but we override this in the case where we have an internal generator
+ if ( _have_internal_generator )
+ silence = false;
- _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;
+ }
+
+ /* don't run any processors that does routing.
+ * oh, and don't bother with the peak meter either.
+ */
+ 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);
}
return;
}
+ if (is_master() || is_monitor() || is_auditioner()) {
+ DEBUG_TRACE (DEBUG::Solo, string_compose ("%1 ignore solo change (master, monitor or auditioner)\n", name()));
+ return;
+ }
+
if (_route_group && src != _route_group && _route_group->is_active() && _route_group->is_solo()) {
_route_group->foreach_route (boost::bind (&Route::set_solo, _1, yn, _route_group));
return;
solo_changed (true, src); /* EMIT SIGNAL */
_solo_control->Changed (); /* EMIT SIGNAL */
}
+
+ /* XXX TRACKS DEVELOPERS: THIS LOGIC SUGGESTS THAT YOU ARE NOT AWARE OF
+ Config->get_solo_mute_overrride().
+ */
+
+ if (yn && Profile->get_trx()) {
+ set_mute (false, src);
+ }
}
void
_mute_master->set_soloed (self_soloed() || soloed_by_others_downstream() || soloed_by_others_upstream());
}
+void
+Route::mod_solo_isolated_by_upstream (bool yn, void* src)
+{
+ bool old = solo_isolated ();
+
+ 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 (yn);
+ solo_isolated_changed (src);
+ }
+}
+
void
Route::set_solo_isolated (bool yn, void *src)
{
return;
}
- /* forward propagate solo-isolate status to everything fed by this route, but not those via sends only */
-
- boost::shared_ptr<RouteList> routes = _session.get_routes ();
- for (RouteList::iterator i = routes->begin(); i != routes->end(); ++i) {
-
- if ((*i).get() == this || (*i)->is_master() || (*i)->is_monitor() || (*i)->is_auditioner()) {
- continue;
- }
-
- bool sends_only;
- bool does_feed = direct_feeds_according_to_graph (*i, &sends_only); // we will recurse anyway, so don't use ::feeds()
-
- if (does_feed && !sends_only) {
- (*i)->set_solo_isolated (yn, (*i)->route_group());
- }
- }
-
- /* XXX should we back-propagate as well? (April 2010: myself and chris goddard think not) */
-
bool changed = false;
if (yn) {
}
}
- if (changed) {
- solo_isolated_changed (src);
+
+ if (!changed) {
+ return;
+ }
+
+ /* forward propagate solo-isolate status to everything fed by this route, but not those via sends only */
+
+ boost::shared_ptr<RouteList> routes = _session.get_routes ();
+ 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 (does_feed && !sends_only) {
+ (*i)->mod_solo_isolated_by_upstream (yn, src);
+ }
}
+
+ /* XXX should we back-propagate as well? (April 2010: myself and chris goddard think not) */
+
+ solo_isolated_changed (src);
}
bool
Route::solo_isolated () const
{
- return _solo_isolated > 0;
+ return (_solo_isolated > 0) || (_solo_isolated_by_upstream > 0);
}
void
return _mute_master->muted_by_self();
}
+bool
+Route::muted_by_others () const
+{
+ //master is never muted by others
+ if (is_master())
+ return false;
+
+ //now check to see if something is soloed (and I am not)
+ return (_session.soloing() && !self_soloed() && !solo_isolated());
+}
+
#if 0
static void
dump_processors(const string& name, const list<boost::shared_ptr<Processor> >& procs)
}
- if (activation_allowed && !_session.get_disable_all_loaded_plugins()) {
+ if (activation_allowed && (!_session.get_disable_all_loaded_plugins () || !processor->display_to_user ())) {
processor->activate ();
}
if (processor->set_state (node, version)) {
return false;
}
+
+ //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_disable_all_loaded_plugins () || !processor->display_to_user () ) )
+ processor->activate();
+ else
+ processor->deactivate();
+ }
+ }
- return (add_processor (processor, placement) == 0);
+ return (add_processor (processor, placement, 0, false) == 0);
}
catch (failed_constructor &err) {
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;
/* 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) {
++i;
continue;
}
return 0;
}
-#if 0
-/* currently unused (again) -- but will come in handy soon (again)
- * once there is an option to link route + delivery panner settings
- */
-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 ();
-}
-#endif
-
void
Route::reset_instrument_info ()
{
(*p)->configure_io(c->first, c->second);
processor_max_streams = ChanCount::max(processor_max_streams, c->first);
processor_max_streams = ChanCount::max(processor_max_streams, c->second);
+
+ boost::shared_ptr<PluginInsert> pi;
+ if ((pi = boost::dynamic_pointer_cast<PluginInsert>(*p)) != 0) {
+ /* plugins connected via Split Match may have more channels.
+ * route/scratch buffers are needed for all of them*/
+ processor_max_streams = ChanCount::max(processor_max_streams, pi->input_streams());
+ processor_max_streams = ChanCount::max(processor_max_streams, pi->natural_input_streams());
+ }
out = c->second;
if (boost::dynamic_pointer_cast<Delivery> (*p)
if (_meter) {
- _meter->reset_max_channels (processor_max_streams);
+ _meter->set_max_channels (processor_max_streams);
}
/* make sure we have sufficient scratch buffers to cope with the new processor
_session.set_dirty ();
}
-int
-Route::reorder_processors (const ProcessorList& new_order, ProcessorStreams* err)
+bool
+Route::processors_reorder_needs_configure (const ProcessorList& new_order)
{
+ /* check if re-order requires re-configuration of any processors
+ * -> compare channel configuration for all processors
+ */
+ Glib::Threads::RWLock::ReaderLock lm (_processor_lock);
+ ChanCount c = input_streams ();
+
+ for (ProcessorList::const_iterator j = new_order.begin(); j != new_order.end(); ++j) {
+ bool found = false;
+ if (c != (*j)->input_streams()) {
+ return true;
+ }
+ for (ProcessorList::const_iterator i = _processors.begin(); i != _processors.end(); ++i) {
+ if (*i == *j) {
+ found = true;
+ if ((*i)->input_streams() != c) {
+ return true;
+ }
+ c = (*i)->output_streams();
+ break;
+ }
+ }
+ if (!found) {
+ return true;
+ }
+ }
+ return false;
+}
+
+#ifdef __clang__
+__attribute__((annotate("realtime")))
+#endif
+void
+Route::apply_processor_order (const ProcessorList& new_order)
+{
+ /* need to hold processor_lock; either read or write lock
+ * and the engine process_lock.
+ * Due to r/w lock ambiguity we can only assert the latter
+ */
+ assert (!AudioEngine::instance()->process_lock().trylock());
+
+
/* "new_order" is an ordered list of processors to be positioned according to "placement".
- NOTE: all processors in "new_order" MUST be marked as display_to_user(). There maybe additional
- processors in the current actual processor list that are hidden. Any visible processors
- in the current list but not in "new_order" will be assumed to be deleted.
- */
+ * NOTE: all processors in "new_order" MUST be marked as display_to_user(). There maybe additional
+ * processors in the current actual processor list that are hidden. Any visible processors
+ * in the current list but not in "new_order" will be assumed to be deleted.
+ */
- {
- Glib::Threads::Mutex::Lock lx (AudioEngine::instance()->process_lock ());
- Glib::Threads::RWLock::WriterLock lm (_processor_lock);
- ProcessorState pstate (this);
+ /* "as_it_will_be" and "_processors" are lists of shared pointers.
+ * actual memory usage is small, but insert/erase is not actually rt-safe :(
+ * (note though that ::processors_reorder_needs_configure() ensured that
+ * this function will only ever be called from the rt-thread if no processor were removed)
+ *
+ * either way, I can't proove it, but an x-run due to re-order here is less likley
+ * than an x-run-less 'ardour-silent cycle' both of which effectively "click".
+ */
- ProcessorList::iterator oiter;
- ProcessorList::const_iterator niter;
- ProcessorList as_it_will_be;
+ ProcessorList as_it_will_be;
+ ProcessorList::iterator oiter;
+ ProcessorList::const_iterator niter;
- oiter = _processors.begin();
- niter = new_order.begin();
+ oiter = _processors.begin();
+ niter = new_order.begin();
- while (niter != new_order.end()) {
+ while (niter != new_order.end()) {
- /* if the next processor in the old list is invisible (i.e. should not be in the new order)
- then append it to the temp list.
+ /* if the next processor in the old list is invisible (i.e. should not be in the new order)
+ then append it to the temp list.
- Otherwise, see if the next processor in the old list is in the new list. if not,
- its been deleted. If its there, append it to the temp list.
- */
+ Otherwise, see if the next processor in the old list is in the new list. if not,
+ its been deleted. If its there, append it to the temp list.
+ */
- if (oiter == _processors.end()) {
+ if (oiter == _processors.end()) {
- /* no more elements in the old list, so just stick the rest of
- the new order onto the temp list.
- */
+ /* no more elements in the old list, so just stick the rest of
+ the new order onto the temp list.
+ */
- as_it_will_be.insert (as_it_will_be.end(), niter, new_order.end());
- while (niter != new_order.end()) {
- ++niter;
- }
- break;
+ as_it_will_be.insert (as_it_will_be.end(), niter, new_order.end());
+ while (niter != new_order.end()) {
+ ++niter;
+ }
+ break;
- } else {
+ } else {
- if (!(*oiter)->display_to_user()) {
+ if (!(*oiter)->display_to_user()) {
- as_it_will_be.push_back (*oiter);
+ as_it_will_be.push_back (*oiter);
- } else {
+ } else {
- /* visible processor: check that its in the new order */
+ /* visible processor: check that its in the new order */
- if (find (new_order.begin(), new_order.end(), (*oiter)) == new_order.end()) {
- /* deleted: do nothing, shared_ptr<> will clean up */
- } else {
- /* ignore this one, and add the next item from the new order instead */
- as_it_will_be.push_back (*niter);
- ++niter;
- }
+ if (find (new_order.begin(), new_order.end(), (*oiter)) == new_order.end()) {
+ /* deleted: do nothing, shared_ptr<> will clean up */
+ } else {
+ /* ignore this one, and add the next item from the new order instead */
+ as_it_will_be.push_back (*niter);
+ ++niter;
}
-
- /* now remove from old order - its taken care of no matter what */
- oiter = _processors.erase (oiter);
}
+ /* now remove from old order - its taken care of no matter what */
+ oiter = _processors.erase (oiter);
}
- _processors.insert (oiter, as_it_will_be.begin(), as_it_will_be.end());
+ }
+ _processors.insert (oiter, as_it_will_be.begin(), as_it_will_be.end());
- /* If the meter is in a custom position, find it and make a rough note of its position */
- maybe_note_meter_position ();
+ /* If the meter is in a custom position, find it and make a rough note of its position */
+ maybe_note_meter_position ();
+}
+
+int
+Route::reorder_processors (const ProcessorList& new_order, ProcessorStreams* err)
+{
+ // it a change is already queued, wait for it
+ // (unless engine is stopped. apply immediately and proceed
+ while (g_atomic_int_get (&_pending_process_reorder)) {
+ if (!AudioEngine::instance()->running()) {
+ DEBUG_TRACE (DEBUG::Processors, "offline apply queued processor re-order.\n");
+ Glib::Threads::RWLock::WriterLock lm (_processor_lock);
+
+ apply_processor_order(_pending_processor_order);
+ setup_invisible_processors ();
+
+ g_atomic_int_set (&_pending_process_reorder, 0);
+
+ processors_changed (RouteProcessorChange ()); /* EMIT SIGNAL */
+ set_processor_positions ();
+ } else {
+ // TODO rather use a semaphore or something.
+ // but since ::reorder_processors() is called
+ // from the GUI thread, this is fine..
+ Glib::usleep(500);
+ }
+ }
+
+ if (processors_reorder_needs_configure (new_order) || !AudioEngine::instance()->running()) {
+
+ Glib::Threads::Mutex::Lock lx (AudioEngine::instance()->process_lock ());
+ Glib::Threads::RWLock::WriterLock lm (_processor_lock);
+ ProcessorState pstate (this);
+
+ apply_processor_order (new_order);
if (configure_processors_unlocked (err)) {
pstate.restore ();
return -1;
}
- }
- processors_changed (RouteProcessorChange ()); /* EMIT SIGNAL */
- set_processor_positions ();
+ lm.release();
+ lx.release();
+
+ processors_changed (RouteProcessorChange ()); /* EMIT SIGNAL */
+ set_processor_positions ();
+
+ } else {
+ DEBUG_TRACE (DEBUG::Processors, "Queue clickless processor re-order.\n");
+ Glib::Threads::RWLock::ReaderLock lm (_processor_lock);
+
+ // _pending_processor_order is protected by _processor_lock
+ _pending_processor_order = new_order;
+ g_atomic_int_set (&_pending_process_reorder, 1);
+ }
return 0;
}
node->add_child_nocopy (_mute_control->get_state ());
node->add_child_nocopy (_mute_master->get_state ());
+ if (full_state) {
+ node->add_child_nocopy (Automatable::get_automation_xml_state ());
+ }
+
XMLNode* remote_control_node = new XMLNode (X_("RemoteControl"));
snprintf (buf, sizeof (buf), "%d", _remote_control_id);
remote_control_node->add_property (X_("id"), buf);
after->id().print (buf, sizeof (buf));
node->add_property (X_("processor-after-last-custom-meter"), buf);
}
-
- node->add_property (X_("last-custom-meter-was-at-end"), _last_custom_meter_was_at_end ? "yes" : "no");
}
return *node;
}
}
- if ((prop = node.property (X_("last-custom-meter-was-at-end"))) != 0) {
- _last_custom_meter_was_at_end = string_is_affirmative (prop->value ());
- }
-
for (niter = nlist.begin(); niter != nlist.end(); ++niter){
child = *niter;
} else if (child->name() == X_("MuteMaster")) {
_mute_master->set_state (*child, version);
+
+ } else if (child->name() == Automatable::xml_node_name) {
+ set_automation_xml_state (*child, Evoral::Parameter(NullAutomation));
}
}
int
Route::set_state_2X (const XMLNode& node, int version)
{
+ LocaleGuard lg (X_("C"));
XMLNodeList nlist;
XMLNodeConstIterator niter;
XMLNode *child;
if (prop->value() == "amp") {
_amp->set_state (**niter, Stateful::current_state_version);
new_order.push_back (_amp);
+ } else if (prop->value() == "trim") {
+ _trim->set_state (**niter, Stateful::current_state_version);
+ new_order.push_back (_trim);
} else if (prop->value() == "meter") {
_meter->set_state (**niter, Stateful::current_state_version);
new_order.push_back (_meter);
+ } else if (prop->value() == "delay") {
+ if (_delayline) {
+ _delayline->set_state (**niter, Stateful::current_state_version);
+ new_order.push_back (_delayline);
+ }
} else if (prop->value() == "main-outs") {
_main_outs->set_state (**niter, Stateful::current_state_version);
} else if (prop->value() == "intreturn") {
if (prop->value() == "intsend") {
- processor.reset (new InternalSend (_session, _pannable, _mute_master, boost::shared_ptr<Route>(), Delivery::Aux));
+ 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" ||
} else if (prop->value() == "send") {
- processor.reset (new Send (_session, _pannable, _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;
}
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);
*/
need_to_queue_solo_change = false;
configure_processors (0);
+
+ if (is_master()) {
+ _session.reset_monitor_section();
+ }
+
io_changed (); /* EMIT SIGNAL */
}
}
_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);
+ _meter->set_display_to_user (false);
- /* 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);
- }
+ 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
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)
+ : AutomationControl (r->session(),
+ Evoral::Parameter (SoloAutomation),
+ ParameterDescriptor(Evoral::Parameter (SoloAutomation)),
+ boost::shared_ptr<AutomationList>(), name)
, _route (r)
{
boost::shared_ptr<AutomationList> gl(new AutomationList(Evoral::Parameter(SoloAutomation)));
+ gl->set_interpolation(Evoral::ControlList::Discrete);
set_list (gl);
}
void
Route::SoloControllable::set_value (double val)
{
- bool bval = ((val >= 0.5f) ? true: false);
+ const bool bval = ((val >= 0.5) ? true : false);
boost::shared_ptr<RouteList> rl (new RouteList);
}
if (Config->get_solo_control_is_listen_control()) {
- return r->listening_via_monitor() ? 1.0f : 0.0f;
+ return r->listening_via_monitor() ? GAIN_COEFF_UNITY : GAIN_COEFF_ZERO;
} else {
- return r->self_soloed() ? 1.0f : 0.0f;
+ return r->self_soloed() ? GAIN_COEFF_UNITY : GAIN_COEFF_ZERO;
}
}
Route::MuteControllable::MuteControllable (std::string name, boost::shared_ptr<Route> r)
- : AutomationControl (r->session(), Evoral::Parameter (MuteAutomation),
- boost::shared_ptr<AutomationList>(), name)
+ : AutomationControl (r->session(),
+ Evoral::Parameter (MuteAutomation),
+ ParameterDescriptor (Evoral::Parameter (MuteAutomation)),
+ boost::shared_ptr<AutomationList>(),
+ name)
, _route (r)
{
boost::shared_ptr<AutomationList> gl(new AutomationList(Evoral::Parameter(MuteAutomation)));
+ gl->set_interpolation(Evoral::ControlList::Discrete);
set_list (gl);
}
void
-Route::MuteControllable::set_value (double val)
+Route::MuteControllable::set_superficial_value(bool muted)
{
- bool bval = ((val >= 0.5f) ? true: false);
+ /* Note we can not use AutomationControl::set_value here since it will emit
+ Changed(), but the value will not be correct to the observer. */
- boost::shared_ptr<RouteList> rl (new RouteList);
+ bool to_list = _list && ((AutomationList*)_list.get())->automation_write();
+
+ Control::set_double (muted, _session.transport_frame(), to_list);
+}
+
+void
+Route::MuteControllable::set_value (double val)
+{
+ const bool bval = ((val >= 0.5) ? true : false);
boost::shared_ptr<Route> r = _route.lock ();
if (!r) {
return;
}
- rl->push_back (r);
- _session.set_mute (rl, bval);
+ if (_list && ((AutomationList*)_list.get())->automation_playback()) {
+ // Playing back automation, set route mute directly
+ r->set_mute (bval, this);
+ } else {
+ // Set from user, queue mute event
+ boost::shared_ptr<RouteList> rl (new RouteList);
+ rl->push_back (r);
+ _session.set_mute (rl, bval, Session::rt_cleanup);
+ }
+
+ // Set superficial/automation value to drive controller (and possibly record)
+ set_superficial_value(bval);
}
double
Route::MuteControllable::get_value () const
{
- boost::shared_ptr<Route> r = _route.lock ();
- if (!r) {
- return 0;
+ if (_list && ((AutomationList*)_list.get())->automation_playback()) {
+ // Playing back automation, get the value from the list
+ return AutomationControl::get_value();
}
- return r->muted() ? 1.0f : 0.0f;
+ // Not playing back automation, get the actual route mute value
+ boost::shared_ptr<Route> r = _route.lock ();
+ return (r && r->muted()) ? GAIN_COEFF_UNITY : GAIN_COEFF_ZERO;
}
void
_session.add_command (new MementoCommand<AutomationList> (*gc->alist().get(), &before, &after));
}
+ /* gain automation */
+ {
+ boost::shared_ptr<AutomationControl> gc = _trim->gain_control();
+
+ XMLNode &before = gc->alist()->get_state ();
+ gc->alist()->shift (pos, frames);
+ XMLNode &after = gc->alist()->get_state ();
+ _session.add_command (new MementoCommand<AutomationList> (*gc->alist().get(), &before, &after));
+ }
+
+ // TODO mute automation ??
+
/* pan automation */
if (_pannable) {
ControlSet::Controls& c (_pannable->controls());
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
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
{
/* maybe one of our processors does or ... */
- Glib::Threads::RWLock::ReaderLock rm (_processor_lock, Glib::Threads::TRY_LOCK);
+ Glib::Threads::RWLock::ReaderLock rm (_processor_lock);
for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) {
if ((c = boost::dynamic_pointer_cast<AutomationControl>((*i)->control (param))) != 0) {
break;
Glib::Threads::RWLock::ReaderLock lm (_processor_lock);
bool had_amp = false;
- for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) {
+ for (ProcessorList::const_iterator i = _processors.begin(); i != _processors.end(); ++i) {
(*i)->set_pre_fader (!had_amp);
- if (boost::dynamic_pointer_cast<Amp> (*i)) {
+ if (*i == _amp) {
had_amp = true;
}
}
/** 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 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 ()) {
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)->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 ()));
}
_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>
_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);