X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=libs%2Fardour%2Froute.cc;h=be7dfb846973aa67c1c49016d84e8d4f3e420d0d;hb=1059b3f48e568b60d5e248d94fbb9110a2dd1a16;hp=318989bb60908c548e056a73a4b1704edcf0c5f7;hpb=0d0f71ee92fb7ce53fbcb8c7b0cd93b1cdf3529f;p=ardour.git diff --git a/libs/ardour/route.cc b/libs/ardour/route.cc index 318989bb60..be7dfb8469 100644 --- a/libs/ardour/route.cc +++ b/libs/ardour/route.cc @@ -15,7 +15,6 @@ along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - $Id$ */ #include @@ -24,12 +23,15 @@ #include #include +#include #include -#include #include #include -#include +#include +#include +#include +#include #include #include #include @@ -37,59 +39,62 @@ #include #include #include +#include #include #include #include -#include - +#include +#include +#include #include "i18n.h" using namespace std; using namespace ARDOUR; using namespace PBD; - uint32_t Route::order_key_cnt = 0; - +sigc::signal Route::SyncOrderKeys; Route::Route (Session& sess, string name, int input_min, int input_max, int output_min, int output_max, Flag flg, DataType default_type) : IO (sess, name, input_min, input_max, output_min, output_max, default_type), _flags (flg), - _solo_control (X_("solo"), *this, ToggleControllable::SoloControl), - _mute_control (X_("mute"), *this, ToggleControllable::MuteControl) + _solo_control (new ToggleControllable (X_("solo"), *this, ToggleControllable::SoloControl)), + _mute_control (new ToggleControllable (X_("mute"), *this, ToggleControllable::MuteControl)) { init (); } -Route::Route (Session& sess, const XMLNode& node) - : IO (sess, "route"), - _solo_control (X_("solo"), *this, ToggleControllable::SoloControl), - _mute_control (X_("mute"), *this, ToggleControllable::MuteControl) +Route::Route (Session& sess, const XMLNode& node, DataType default_type) + : IO (sess, *node.child ("IO"), default_type), + _solo_control (new ToggleControllable (X_("solo"), *this, ToggleControllable::SoloControl)), + _mute_control (new ToggleControllable (X_("mute"), *this, ToggleControllable::MuteControl)) { init (); - set_state (node); + _set_state (node, false); } void Route::init () { - redirect_max_outs = 0; + processor_max_outs.reset(); _muted = false; _soloed = false; _solo_safe = false; _phase_invert = false; - order_keys[N_("signal")] = order_key_cnt++; + _denormal_protection = false; + order_keys[strdup (N_("signal"))] = order_key_cnt++; _active = true; _silent = false; _meter_point = MeterPostFader; _initial_delay = 0; _roll_delay = 0; _own_latency = 0; + _user_latency = 0; _have_internal_generator = false; _declickable = false; _pending_declick = true; _remote_control_id = 0; - + _edit_group = 0; _mix_group = 0; @@ -111,7 +116,12 @@ Route::init () Route::~Route () { - clear_redirects (this); + clear_processors (PreFader); + clear_processors (PostFader); + + for (OrderKeys::iterator i = order_keys.begin(); i != order_keys.end(); ++i) { + free ((void*)(i->first)); + } if (_control_outs) { delete _control_outs; @@ -134,24 +144,51 @@ Route::remote_control_id() const } long -Route::order_key (string name) const +Route::order_key (const char* name) const { OrderKeys::const_iterator i; - if ((i = order_keys.find (name)) == order_keys.end()) { - return -1; + for (i = order_keys.begin(); i != order_keys.end(); ++i) { + if (!strcmp (name, i->first)) { + return i->second; + } } - return (*i).second; + return -1; } void -Route::set_order_key (string name, long n) +Route::set_order_key (const char* name, long n) { - order_keys[name] = n; + order_keys[strdup(name)] = n; + + if (Config->get_sync_all_route_ordering()) { + for (OrderKeys::iterator x = order_keys.begin(); x != order_keys.end(); ++x) { + x->second = n; + } + } + _session.set_dirty (); } +void +Route::sync_order_keys () +{ + uint32_t key; + + if (order_keys.empty()) { + return; + } + + OrderKeys::iterator x = order_keys.begin(); + key = x->second; + ++x; + + for (; x != order_keys.end(); ++x) { + x->second = key; + } +} + void Route::inc_gain (gain_t fraction, void *src) { @@ -165,33 +202,33 @@ Route::set_gain (gain_t val, void *src) if (_mix_group->is_relative()) { - - gain_t usable_gain = gain(); + gain_t usable_gain = gain(); if (usable_gain < 0.000001f) { - usable_gain=0.000001f; + usable_gain = 0.000001f; } gain_t delta = val; if (delta < 0.000001f) { - delta=0.000001f; + delta = 0.000001f; } delta -= usable_gain; - if (delta == 0.0f) return; + if (delta == 0.0f) + return; gain_t factor = delta / usable_gain; if (factor > 0.0f) { factor = _mix_group->get_max_factor(factor); if (factor == 0.0f) { - gain_changed (src); + _gain_control->Changed(); /* EMIT SIGNAL */ return; } } else { factor = _mix_group->get_min_factor(factor); if (factor == 0.0f) { - gain_changed (src); + _gain_control->Changed(); /* EMIT SIGNAL */ return; } } @@ -213,18 +250,30 @@ Route::set_gain (gain_t val, void *src) IO::set_gain (val, src); } +/** Process this route for one (sub) cycle (process thread) + * + * @param bufs Scratch buffers to use for the signal path + * @param start_frame Initial transport frame + * @param end_frame Final transport frame + * @param nframes Number of frames to output (to ports) + * @param offset Output offset (of port buffers, for split cycles) + * + * Note that (end_frame - start_frame) may not be equal to nframes when the + * transport speed isn't 1.0 (eg varispeed). + */ void -Route::process_output_buffers (vector& bufs, uint32_t nbufs, +Route::process_output_buffers (BufferSet& bufs, nframes_t start_frame, nframes_t end_frame, - nframes_t nframes, nframes_t offset, bool with_redirects, int declick, + nframes_t nframes, nframes_t offset, bool with_processors, int declick, bool meter) { - uint32_t n; - RedirectList::iterator i; + // This is definitely very audio-only for now + assert(_default_type == DataType::AUDIO); + + ProcessorList::iterator i; bool post_fader_work = false; bool mute_declick_applied = false; gain_t dmg, dsg, dg; - vector::iterator bufiter; IO *co; bool mute_audible; bool solo_audible; @@ -243,7 +292,7 @@ Route::process_output_buffers (vector& bufs, uint32_t nbufs, declick = _pending_declick; { - Glib::Mutex::Lock cm (control_outs_lock, Glib::TRY_LOCK); + Glib::Mutex::Lock cm (_control_outs_lock, Glib::TRY_LOCK); if (cm.locked()) { co = _control_outs; @@ -271,17 +320,17 @@ Route::process_output_buffers (vector& bufs, uint32_t nbufs, -------------------------------------------------------------------------------------------------- */ if (declick > 0) { - apply_declick (bufs, nbufs, nframes, 0.0, 1.0, false); + Amp::run_in_place (bufs, nframes, 0.0, 1.0, false); _pending_declick = 0; } else if (declick < 0) { - apply_declick (bufs, nbufs, nframes, 1.0, 0.0, false); + Amp::run_in_place (bufs, nframes, 1.0, 0.0, false); _pending_declick = 0; } else { /* no global declick */ if (solo_gain != dsg) { - apply_declick (bufs, nbufs, nframes, solo_gain, dsg, false); + Amp::run_in_place (bufs, nframes, solo_gain, dsg, false); solo_gain = dsg; } } @@ -292,13 +341,11 @@ Route::process_output_buffers (vector& bufs, uint32_t nbufs, -------------------------------------------------------------------------------------------------- */ if (meter && (_meter_point == MeterInput)) { - for (n = 0; n < nbufs; ++n) { - _peak_power[n] = Session::compute_peak (bufs[n], nframes, _peak_power[n]); - } + _meter->run(bufs, start_frame, end_frame, nframes, offset); } if (!_soloed && _mute_affects_pre_fader && (mute_gain != dmg)) { - apply_declick (bufs, nbufs, nframes, mute_gain, dmg, false); + Amp::run_in_place (bufs, nframes, mute_gain, dmg, false); mute_gain = dmg; mute_declick_applied = true; } @@ -328,23 +375,38 @@ Route::process_output_buffers (vector& bufs, uint32_t nbufs, } else { - co->deliver_output (bufs, nbufs, nframes, offset); + co->deliver_output (bufs, start_frame, end_frame, nframes, offset); } } + /* ----------------------------------------------------------------------------------------------------- + DENORMAL CONTROL + -------------------------------------------------------------------------------------------------- */ + + if (_denormal_protection || Config->get_denormal_protection()) { + + for (BufferSet::audio_iterator i = bufs.audio_begin(); i != bufs.audio_end(); ++i) { + Sample* const sp = i->data(); + + for (nframes_t nx = offset; nx < nframes + offset; ++nx) { + sp[nx] += 1.0e-27f; + } + } + } + /* ---------------------------------------------------------------------------------------------------- PRE-FADER REDIRECTS -------------------------------------------------------------------------------------------------- */ - if (with_redirects) { - Glib::RWLock::ReaderLock rm (redirect_lock, Glib::TRY_LOCK); + if (with_processors) { + Glib::RWLock::ReaderLock rm (_processor_lock, Glib::TRY_LOCK); if (rm.locked()) { if (mute_gain > 0 || !_mute_affects_pre_fader) { - for (i = _redirects.begin(); i != _redirects.end(); ++i) { + for (i = _processors.begin(); i != _processors.end(); ++i) { switch ((*i)->placement()) { case PreFader: - (*i)->run (bufs, nbufs, nframes, offset); + (*i)->run_in_place (bufs, start_frame, end_frame, nframes, offset); break; case PostFader: post_fader_work = true; @@ -352,7 +414,7 @@ Route::process_output_buffers (vector& bufs, uint32_t nbufs, } } } else { - for (i = _redirects.begin(); i != _redirects.end(); ++i) { + for (i = _processors.begin(); i != _processors.end(); ++i) { switch ((*i)->placement()) { case PreFader: (*i)->silence (nframes, offset); @@ -366,9 +428,11 @@ Route::process_output_buffers (vector& bufs, uint32_t nbufs, } } - + // This really should already be true... + bufs.set_count(pre_fader_streams()); + if (!_soloed && (mute_gain != dmg) && !mute_declick_applied && _mute_affects_post_fader) { - apply_declick (bufs, nbufs, nframes, mute_gain, dmg, false); + Amp::run_in_place (bufs, nframes, mute_gain, dmg, false); mute_gain = dmg; mute_declick_applied = true; } @@ -378,9 +442,7 @@ Route::process_output_buffers (vector& bufs, uint32_t nbufs, -------------------------------------------------------------------------------------------------- */ if (meter && (_meter_point == MeterPreFader)) { - for (n = 0; n < nbufs; ++n) { - _peak_power[n] = Session::compute_peak (bufs[n], nframes, _peak_power[n]); - } + _meter->run_in_place(bufs, start_frame, end_frame, nframes, offset); } @@ -407,8 +469,7 @@ Route::process_output_buffers (vector& bufs, uint32_t nbufs, } else { - co->deliver_output (bufs, nbufs, nframes, offset); - + co->deliver_output (bufs, start_frame, end_frame, nframes, offset); } } @@ -424,27 +485,23 @@ Route::process_output_buffers (vector& bufs, uint32_t nbufs, // OR recording - // h/w monitoring not in use - - (!Config->get_monitoring_model() == HardwareMonitoring && - // AND software monitoring required - Config->get_monitoring_model() == SoftwareMonitoring)) { + Config->get_monitoring_model() == SoftwareMonitoring) { if (apply_gain_automation) { if (_phase_invert) { - for (n = 0; n < nbufs; ++n) { - Sample *sp = bufs[n]; + for (BufferSet::audio_iterator i = bufs.audio_begin(); i != bufs.audio_end(); ++i) { + Sample* const sp = i->data(); for (nframes_t nx = 0; nx < nframes; ++nx) { sp[nx] *= -gab[nx]; } } } else { - for (n = 0; n < nbufs; ++n) { - Sample *sp = bufs[n]; + for (BufferSet::audio_iterator i = bufs.audio_begin(); i != bufs.audio_end(); ++i) { + Sample* const sp = i->data(); for (nframes_t nx = 0; nx < nframes; ++nx) { sp[nx] *= gab[nx]; @@ -462,7 +519,7 @@ Route::process_output_buffers (vector& bufs, uint32_t nbufs, if (_gain != dg) { - apply_declick (bufs, nbufs, nframes, _gain, dg, _phase_invert); + Amp::run_in_place (bufs, nframes, _gain, dg, _phase_invert); _gain = dg; } else if (_gain != 0 && (_phase_invert || _gain != 1.0)) { @@ -481,14 +538,14 @@ Route::process_output_buffers (vector& bufs, uint32_t nbufs, this_gain = _gain; } - for (n = 0; n < nbufs; ++n) { - Sample *sp = bufs[n]; + for (BufferSet::audio_iterator i = bufs.audio_begin(); i != bufs.audio_end(); ++i) { + Sample* const sp = i->data(); apply_gain_to_buffer(sp,nframes,this_gain); } } else if (_gain == 0) { - for (n = 0; n < nbufs; ++n) { - memset (bufs[n], 0, sizeof (Sample) * nframes); + for (BufferSet::audio_iterator i = bufs.audio_begin(); i != bufs.audio_end(); ++i) { + i->clear(); } } } @@ -503,24 +560,24 @@ Route::process_output_buffers (vector& bufs, uint32_t nbufs, POST-FADER REDIRECTS -------------------------------------------------------------------------------------------------- */ - /* note that post_fader_work cannot be true unless with_redirects was also true, so don't test both */ + /* note that post_fader_work cannot be true unless with_processors was also true, so don't test both */ if (post_fader_work) { - Glib::RWLock::ReaderLock rm (redirect_lock, Glib::TRY_LOCK); + Glib::RWLock::ReaderLock rm (_processor_lock, Glib::TRY_LOCK); if (rm.locked()) { if (mute_gain > 0 || !_mute_affects_post_fader) { - for (i = _redirects.begin(); i != _redirects.end(); ++i) { + for (i = _processors.begin(); i != _processors.end(); ++i) { switch ((*i)->placement()) { case PreFader: break; case PostFader: - (*i)->run (bufs, nbufs, nframes, offset); + (*i)->run_in_place (bufs, start_frame, end_frame, nframes, offset); break; } } } else { - for (i = _redirects.begin(); i != _redirects.end(); ++i) { + for (i = _processors.begin(); i != _processors.end(); ++i) { switch ((*i)->placement()) { case PreFader: break; @@ -534,7 +591,7 @@ Route::process_output_buffers (vector& bufs, uint32_t nbufs, } if (!_soloed && (mute_gain != dmg) && !mute_declick_applied && _mute_affects_control_outs) { - apply_declick (bufs, nbufs, nframes, mute_gain, dmg, false); + Amp::run_in_place (bufs, nframes, mute_gain, dmg, false); mute_gain = dmg; mute_declick_applied = true; } @@ -565,12 +622,12 @@ Route::process_output_buffers (vector& bufs, uint32_t nbufs, (no_monitor && record_enabled() && (!Config->get_auto_input() || _session.actively_recording())) ) { - + co->silence (nframes, offset); } else { - co->deliver_output_no_pan (bufs, nbufs, nframes, offset); + co->deliver_output (bufs, start_frame, end_frame, nframes, offset); } } @@ -579,7 +636,7 @@ Route::process_output_buffers (vector& bufs, uint32_t nbufs, ----------------------------------------------------------------------*/ if (!_soloed && (mute_gain != dmg) && !mute_declick_applied && _mute_affects_main_outs) { - apply_declick (bufs, nbufs, nframes, mute_gain, dmg, false); + Amp::run_in_place (bufs, nframes, mute_gain, dmg, false); mute_gain = dmg; mute_declick_applied = true; } @@ -591,7 +648,7 @@ Route::process_output_buffers (vector& bufs, uint32_t nbufs, solo_audible = dsg > 0; mute_audible = dmg > 0 || !_mute_affects_main_outs; - if (n_outputs() == 0) { + if (n_outputs().get(_default_type) == 0) { /* relax */ @@ -616,31 +673,19 @@ Route::process_output_buffers (vector& bufs, uint32_t nbufs, ) { /* don't use Route::silence() here, because that causes - all outputs (sends, port inserts, etc. to be silent). + all outputs (sends, port processors, etc. to be silent). */ if (_meter_point == MeterPostFader) { - reset_peak_meters (); + peak_meter().reset(); } - + IO::silence (nframes, offset); } else { - if ((_session.transport_speed() > 1.5f || - _session.transport_speed() < -1.5f) && - Config->get_quieten_at_speed()) { - pan (bufs, nbufs, nframes, offset, speed_quietning); - } else { - // cerr << _name << " panner state = " << _panner->automation_state() << endl; - if (!_panner->empty() && - (_panner->automation_state() & Play || - ((_panner->automation_state() & Touch) && !_panner->touching()))) { - pan_automated (bufs, nbufs, start_frame, end_frame, nframes, offset); - } else { - pan (bufs, nbufs, nframes, offset, 1.0); - } - } + deliver_output(bufs, start_frame, end_frame, nframes, offset); + } } @@ -650,62 +695,41 @@ Route::process_output_buffers (vector& bufs, uint32_t nbufs, -------------------------------------------------------------------------------------------------- */ if (meter && (_meter_point == MeterPostFader)) { -// cerr << "meter post" << endl; - if ((_gain == 0 && !apply_gain_automation) || dmg == 0) { - uint32_t no = n_outputs(); - for (n = 0; n < no; ++n) { - _peak_power[n] = 0; - } + _meter->reset(); } else { - uint32_t no = n_outputs(); - for (n = 0; n < no; ++n) { - _peak_power[n] = Session::compute_peak (output(n)->get_buffer (nframes) + offset, nframes, _peak_power[n]); - } + _meter->run_in_place(output_buffers(), start_frame, end_frame, nframes, offset); } } } -uint32_t +ChanCount Route::n_process_buffers () { - return max (n_inputs(), redirect_max_outs); + return max (n_inputs(), processor_max_outs); } void - Route::passthru (nframes_t start_frame, nframes_t end_frame, nframes_t nframes, nframes_t offset, int declick, bool meter_first) { - vector& bufs = _session.get_passthru_buffers(); - uint32_t limit = n_process_buffers (); + BufferSet& bufs = _session.get_scratch_buffers(n_process_buffers()); _silent = false; - collect_input (bufs, limit, nframes, offset); - -#define meter_stream meter_first + collect_input (bufs, nframes, offset); if (meter_first) { - for (uint32_t n = 0; n < limit; ++n) { - _peak_power[n] = Session::compute_peak (bufs[n], nframes, _peak_power[n]); - } - meter_stream = false; - } else { - meter_stream = true; + _meter->run_in_place(bufs, start_frame, end_frame, nframes, offset); + meter_first = false; } - process_output_buffers (bufs, limit, start_frame, end_frame, nframes, offset, true, declick, meter_stream); - -#undef meter_stream + process_output_buffers (bufs, start_frame, end_frame, nframes, offset, true, declick, meter_first); } void -Route::set_phase_invert (bool yn, void *src) +Route::passthru_silence (nframes_t start_frame, nframes_t end_frame, nframes_t nframes, nframes_t offset, int declick, bool meter) { - if (_phase_invert != yn) { - _phase_invert = yn; - } - // phase_invert_changed (src); /* EMIT SIGNAL */ + process_output_buffers (_session.get_silent_buffers (n_process_buffers()), start_frame, end_frame, nframes, offset, true, declick, meter); } void @@ -723,7 +747,7 @@ Route::set_solo (bool yn, void *src) if (_soloed != yn) { _soloed = yn; solo_changed (src); /* EMIT SIGNAL */ - _solo_control.Changed (); /* EMIT SIGNAL */ + _solo_control->Changed (); /* EMIT SIGNAL */ } } @@ -760,7 +784,7 @@ Route::set_mute (bool yn, void *src) _muted = yn; mute_changed (src); /* EMIT SIGNAL */ - _mute_control.Changed (); /* EMIT SIGNAL */ + _mute_control->Changed (); /* EMIT SIGNAL */ Glib::Mutex::Lock lm (declick_lock); desired_mute_gain = (yn?0.0f:1.0f); @@ -768,183 +792,315 @@ Route::set_mute (bool yn, void *src) } int -Route::add_redirect (boost::shared_ptr redirect, void *src, uint32_t* err_streams) +Route::add_processor (boost::shared_ptr processor, ProcessorStreams* err) { - uint32_t old_rmo = redirect_max_outs; + ChanCount old_rmo = processor_max_outs; if (!_session.engine().connected()) { return 1; } { - Glib::RWLock::WriterLock lm (redirect_lock); + Glib::RWLock::WriterLock lm (_processor_lock); boost::shared_ptr pi; boost::shared_ptr porti; - uint32_t potential_max_streams = 0; + //processor->set_default_type(_default_type); - if ((pi = boost::dynamic_pointer_cast(redirect)) != 0) { + if ((pi = boost::dynamic_pointer_cast(processor)) != 0) { pi->set_count (1); - if (pi->input_streams() == 0) { - /* instrument plugin */ + if (pi->natural_input_streams() == ChanCount::ZERO) { + /* generator plugin */ _have_internal_generator = true; } - - potential_max_streams = max(pi->input_streams(), pi->output_streams()); - } else if ((porti = boost::dynamic_pointer_cast(redirect)) != 0) { - - /* force new port inserts to start out with an i/o configuration - that matches this route's i/o configuration. - - the "inputs" for the port are supposed to match the output - of this route. - - the "outputs" of the route should match the inputs of this - route. XXX shouldn't they match the number of active signal - streams at the point of insertion? - - */ - - porti->ensure_io (n_outputs (), n_inputs(), false, this); } + + _processors.push_back (processor); - // Ensure peak vector sizes before the plugin is activated - while (_peak_power.size() < potential_max_streams) { - _peak_power.push_back(0); - } - while (_visible_peak_power.size() < potential_max_streams) { - _visible_peak_power.push_back(0); - } - - _redirects.push_back (redirect); - - if (_reset_plugin_counts (err_streams)) { - _redirects.pop_back (); + // Set up processor list channels. This will set processor->[input|output]_streams(), + // configure redirect ports properly, etc. + if (_reset_plugin_counts (err)) { + _processors.pop_back (); _reset_plugin_counts (0); // it worked before we tried to add it ... return -1; } - redirect->activate (); - redirect->active_changed.connect (mem_fun (*this, &Route::redirect_active_proxy)); + // Ensure peak vector sizes before the plugin is activated + ChanCount potential_max_streams = max(processor->input_streams(), processor->output_streams()); + _meter->configure_io(potential_max_streams, potential_max_streams); + + processor->activate (); + processor->ActiveChanged.connect (bind (mem_fun (_session, &Session::update_latency_compensation), false, false)); + + _user_latency = 0; } - if (redirect_max_outs != old_rmo || old_rmo == 0) { + if (processor_max_outs != old_rmo || old_rmo == ChanCount::ZERO) { reset_panner (); } - redirects_changed (src); /* EMIT SIGNAL */ + processors_changed (); /* EMIT SIGNAL */ return 0; } int -Route::add_redirects (const RedirectList& others, void *src, uint32_t* err_streams) +Route::add_processors (const ProcessorList& others, ProcessorStreams* err) { - uint32_t old_rmo = redirect_max_outs; + ChanCount old_rmo = processor_max_outs; if (!_session.engine().connected()) { return 1; } { - Glib::RWLock::WriterLock lm (redirect_lock); + Glib::RWLock::WriterLock lm (_processor_lock); - RedirectList::iterator existing_end = _redirects.end(); + ProcessorList::iterator existing_end = _processors.end(); --existing_end; - uint32_t potential_max_streams = 0; + ChanCount potential_max_streams; - for (RedirectList::const_iterator i = others.begin(); i != others.end(); ++i) { + for (ProcessorList::const_iterator i = others.begin(); i != others.end(); ++i) { boost::shared_ptr pi; if ((pi = boost::dynamic_pointer_cast(*i)) != 0) { pi->set_count (1); - uint32_t m = max(pi->input_streams(), pi->output_streams()); + ChanCount m = max(pi->input_streams(), pi->output_streams()); if (m > potential_max_streams) potential_max_streams = m; } // Ensure peak vector sizes before the plugin is activated - while (_peak_power.size() < potential_max_streams) { - _peak_power.push_back(0); - } - while (_visible_peak_power.size() < potential_max_streams) { - _visible_peak_power.push_back(0); - } + _meter->configure_io(potential_max_streams, potential_max_streams); - _redirects.push_back (*i); + _processors.push_back (*i); - if (_reset_plugin_counts (err_streams)) { + if (_reset_plugin_counts (err)) { ++existing_end; - _redirects.erase (existing_end, _redirects.end()); + _processors.erase (existing_end, _processors.end()); _reset_plugin_counts (0); // it worked before we tried to add it ... return -1; } (*i)->activate (); - (*i)->active_changed.connect (mem_fun (*this, &Route::redirect_active_proxy)); + (*i)->ActiveChanged.connect (bind (mem_fun (_session, &Session::update_latency_compensation), false, false)); } + + _user_latency = 0; } - if (redirect_max_outs != old_rmo || old_rmo == 0) { + if (processor_max_outs != old_rmo || old_rmo == ChanCount::ZERO) { reset_panner (); } - redirects_changed (src); /* EMIT SIGNAL */ + processors_changed (); /* EMIT SIGNAL */ return 0; } +/** Turn off all processors with a given placement + * @param p Placement of processors to disable + */ + +void +Route::disable_processors (Placement p) +{ + Glib::RWLock::ReaderLock lm (_processor_lock); + + for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) { + if ((*i)->placement() == p) { + (*i)->set_active (false); + } + } + + _session.set_dirty (); +} + +/** Turn off all redirects + */ + +void +Route::disable_processors () +{ + Glib::RWLock::ReaderLock lm (_processor_lock); + + for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) { + (*i)->set_active (false); + } + + _session.set_dirty (); +} + +/** Turn off all redirects with a given placement + * @param p Placement of redirects to disable + */ + +void +Route::disable_plugins (Placement p) +{ + Glib::RWLock::ReaderLock lm (_processor_lock); + + for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) { + if (boost::dynamic_pointer_cast (*i) && (*i)->placement() == p) { + (*i)->set_active (false); + } + } + + _session.set_dirty (); +} + +/** Turn off all plugins + */ + +void +Route::disable_plugins () +{ + Glib::RWLock::ReaderLock lm (_processor_lock); + + for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) { + if (boost::dynamic_pointer_cast (*i)) { + (*i)->set_active (false); + } + } + + _session.set_dirty (); +} + + void -Route::clear_redirects (void *src) +Route::ab_plugins (bool forward) { - uint32_t old_rmo = redirect_max_outs; + Glib::RWLock::ReaderLock lm (_processor_lock); + + if (forward) { + + /* forward = turn off all active redirects, and mark them so that the next time + we go the other way, we will revert them + */ + + for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) { + if (!boost::dynamic_pointer_cast (*i)) { + continue; + } + + if ((*i)->active()) { + (*i)->set_active (false); + (*i)->set_next_ab_is_active (true); + } else { + (*i)->set_next_ab_is_active (false); + } + } + + } else { + + /* backward = if the redirect was marked to go active on the next ab, do so */ + + for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) { + + if (!boost::dynamic_pointer_cast (*i)) { + continue; + } + + if ((*i)->get_next_ab_is_active()) { + (*i)->set_active (true); + } else { + (*i)->set_active (false); + } + } + } + + _session.set_dirty (); +} + + +/* Figure out the streams that will feed into PreFader */ +ChanCount +Route::pre_fader_streams() const +{ + boost::shared_ptr processor; + + // Find the last pre-fader redirect + for (ProcessorList::const_iterator i = _processors.begin(); i != _processors.end(); ++i) { + if ((*i)->placement() == PreFader) { + processor = *i; + } + } + + if (processor) { + return processor->output_streams(); + } else { + return n_inputs (); + } +} + + +/** Remove processors with a given placement. + * @param p Placement of processors to remove. + */ +void +Route::clear_processors (Placement p) +{ + const ChanCount old_rmo = processor_max_outs; if (!_session.engine().connected()) { return; } { - Glib::RWLock::WriterLock lm (redirect_lock); - _redirects.clear (); + Glib::RWLock::WriterLock lm (_processor_lock); + ProcessorList new_list; + + for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) { + if ((*i)->placement() == p) { + /* it's the placement we want to get rid of */ + (*i)->drop_references (); + } else { + /* it's a different placement, so keep it */ + new_list.push_back (*i); + } + } + + _processors = new_list; } - if (redirect_max_outs != old_rmo) { + /* FIXME: can't see how this test can ever fire */ + if (processor_max_outs != old_rmo) { reset_panner (); } - redirect_max_outs = 0; + processor_max_outs.reset(); _have_internal_generator = false; - redirects_changed (src); /* EMIT SIGNAL */ + processors_changed (); /* EMIT SIGNAL */ } int -Route::remove_redirect (boost::shared_ptr redirect, void *src, uint32_t* err_streams) +Route::remove_processor (boost::shared_ptr processor, ProcessorStreams* err) { - uint32_t old_rmo = redirect_max_outs; + ChanCount old_rmo = processor_max_outs; if (!_session.engine().connected()) { return 1; } - redirect_max_outs = 0; + processor_max_outs.reset(); { - Glib::RWLock::WriterLock lm (redirect_lock); - RedirectList::iterator i; + Glib::RWLock::WriterLock lm (_processor_lock); + ProcessorList::iterator i; bool removed = false; - for (i = _redirects.begin(); i != _redirects.end(); ++i) { - if (*i == redirect) { + for (i = _processors.begin(); i != _processors.end(); ++i) { + if (*i == processor) { - RedirectList::iterator tmp; + ProcessorList::iterator tmp; /* move along, see failure case for reset_plugin_counts() - where we may need to reinsert the redirect. + where we may need to reprocessor the processor. */ tmp = i; @@ -955,23 +1111,21 @@ Route::remove_redirect (boost::shared_ptr redirect, void *src, uint32_ run. */ - boost::shared_ptr send; - boost::shared_ptr port_insert; + boost::shared_ptr redirect; - if ((send = boost::dynamic_pointer_cast (*i)) != 0) { - send->disconnect_inputs (this); - send->disconnect_outputs (this); - } else if ((port_insert = boost::dynamic_pointer_cast (*i)) != 0) { - port_insert->disconnect_inputs (this); - port_insert->disconnect_outputs (this); + if ((redirect = boost::dynamic_pointer_cast (*i)) != 0) { + redirect->io()->disconnect_inputs (this); + redirect->io()->disconnect_outputs (this); } - _redirects.erase (i); + _processors.erase (i); i = tmp; removed = true; break; } + + _user_latency = 0; } if (!removed) { @@ -979,9 +1133,9 @@ Route::remove_redirect (boost::shared_ptr redirect, void *src, uint32_ return 1; } - if (_reset_plugin_counts (err_streams)) { + if (_reset_plugin_counts (err)) { /* get back to where we where */ - _redirects.insert (i, redirect); + _processors.insert (i, processor); /* we know this will work, because it worked before :) */ _reset_plugin_counts (0); return -1; @@ -989,7 +1143,7 @@ Route::remove_redirect (boost::shared_ptr redirect, void *src, uint32_ bool foo = false; - for (i = _redirects.begin(); i != _redirects.end(); ++i) { + for (i = _processors.begin(); i != _processors.end(); ++i) { boost::shared_ptr pi; if ((pi = boost::dynamic_pointer_cast(*i)) != 0) { @@ -1002,128 +1156,78 @@ Route::remove_redirect (boost::shared_ptr redirect, void *src, uint32_ _have_internal_generator = foo; } - if (old_rmo != redirect_max_outs) { + if (old_rmo != processor_max_outs) { reset_panner (); } - redirects_changed (src); /* EMIT SIGNAL */ + processor->drop_references (); + + processors_changed (); /* EMIT SIGNAL */ return 0; } int -Route::reset_plugin_counts (uint32_t* lpc) +Route::reset_plugin_counts (ProcessorStreams* err) { - Glib::RWLock::WriterLock lm (redirect_lock); - return _reset_plugin_counts (lpc); + Glib::RWLock::WriterLock lm (_processor_lock); + return _reset_plugin_counts (err); } int -Route::_reset_plugin_counts (uint32_t* err_streams) -{ - RedirectList::iterator r; - uint32_t i_cnt; - uint32_t s_cnt; - map > insert_map; - nframes_t initial_streams; - - redirect_max_outs = 0; - i_cnt = 0; - s_cnt = 0; - - /* divide inserts up by placement so we get the signal flow - properly modelled. we need to do this because the _redirects - list is not sorted by placement, and because other reasons may - exist now or in the future for this separate treatment. +Route::_reset_plugin_counts (ProcessorStreams* err) +{ + ProcessorList::iterator r; + map > processor_map; + ChanCount initial_streams; + + /* Process each placement in order, checking to see if we + can really do what has been requested. */ - for (r = _redirects.begin(); r != _redirects.end(); ++r) { - - boost::shared_ptr insert; - - /* do this here in case we bomb out before we get to the end of - this function. - */ - - redirect_max_outs = max ((*r)->output_streams (), redirect_max_outs); - - if ((insert = boost::dynamic_pointer_cast(*r)) != 0) { - ++i_cnt; - insert_map[insert->placement()].push_back (InsertCount (insert)); + /* divide processors up by placement so we get the signal flow + properly modelled. we need to do this because the _processors + list is not sorted by placement + */ - /* reset plugin counts back to one for now so - that we have a predictable, controlled - state to try to configure. - */ + /* ... but it should/will be... */ + + for (r = _processors.begin(); r != _processors.end(); ++r) { - boost::shared_ptr pi; - - if ((pi = boost::dynamic_pointer_cast(insert)) != 0) { - pi->set_count (1); - } + boost::shared_ptr processor; - } else if (boost::dynamic_pointer_cast (*r) != 0) { - ++s_cnt; + if ((processor = boost::dynamic_pointer_cast(*r)) != 0) { + processor_map[processor->placement()].push_back (ProcessorCount (processor)); } } - if (i_cnt == 0) { - if (s_cnt) { - goto recompute; - } else { - return 0; - } - } - - /* Now process each placement in order, checking to see if we - can really do what has been requested. - */ /* A: PreFader */ - if (check_some_plugin_counts (insert_map[PreFader], n_inputs (), err_streams)) { + if ( ! check_some_plugin_counts (processor_map[PreFader], n_inputs (), err)) { return -1; } - /* figure out the streams that will feed into PreFader */ - - if (!insert_map[PreFader].empty()) { - InsertCount& ic (insert_map[PreFader].back()); - initial_streams = ic.insert->compute_output_streams (ic.cnt); - } else { - initial_streams = n_inputs (); - } + ChanCount post_fader_input = (err ? err->count : n_inputs()); /* B: PostFader */ - if (check_some_plugin_counts (insert_map[PostFader], initial_streams, err_streams)) { + if ( ! check_some_plugin_counts (processor_map[PostFader], post_fader_input, err)) { return -1; } /* OK, everything can be set up correctly, so lets do it */ - apply_some_plugin_counts (insert_map[PreFader]); - apply_some_plugin_counts (insert_map[PostFader]); - - /* recompute max outs of any redirect */ - - recompute: + apply_some_plugin_counts (processor_map[PreFader]); + apply_some_plugin_counts (processor_map[PostFader]); - redirect_max_outs = 0; - RedirectList::iterator prev = _redirects.end(); + /* recompute max outs of any processor */ - for (r = _redirects.begin(); r != _redirects.end(); prev = r, ++r) { - boost::shared_ptr s; + processor_max_outs.reset(); + ProcessorList::iterator prev = _processors.end(); - if ((s = boost::dynamic_pointer_cast (*r)) != 0) { - if (r == _redirects.begin()) { - s->expect_inputs (n_inputs()); - } else { - s->expect_inputs ((*prev)->output_streams()); - } - } - - redirect_max_outs = max ((*r)->output_streams (), redirect_max_outs); + for (r = _processors.begin(); r != _processors.end(); prev = r, ++r) { + processor_max_outs = max ((*r)->output_streams (), processor_max_outs); } /* we're done */ @@ -1132,72 +1236,96 @@ Route::_reset_plugin_counts (uint32_t* err_streams) } int32_t -Route::apply_some_plugin_counts (list& iclist) +Route::apply_some_plugin_counts (list& iclist) { - list::iterator i; + list::iterator i; for (i = iclist.begin(); i != iclist.end(); ++i) { - if ((*i).insert->configure_io ((*i).cnt, (*i).in, (*i).out)) { + cerr << "now applying for " << (*i).processor->name() << " in = " << (*i).in.n_audio() << " out = " << (*i).out.n_audio() << endl; + + if ((*i).processor->configure_io ((*i).in, (*i).out)) { return -1; } /* make sure that however many we have, they are all active */ - (*i).insert->activate (); + (*i).processor->activate (); } return 0; } -int32_t -Route::check_some_plugin_counts (list& iclist, int32_t required_inputs, uint32_t* err_streams) +/** Returns whether \a iclist can be configured and run starting with + * \a required_inputs at the first processor's inputs. + * If false is returned, \a iclist can not be run with \a required_inputs, and \a err is set. + * Otherwise, \a err is set to the output of the list. + */ +bool +Route::check_some_plugin_counts (list& iclist, ChanCount required_inputs, ProcessorStreams* err) { - list::iterator i; - + list::iterator i; + size_t index = 0; + + if (err) { + err->index = 0; + err->count = required_inputs; + } + for (i = iclist.begin(); i != iclist.end(); ++i) { - if (((*i).cnt = (*i).insert->can_support_input_configuration (required_inputs)) < 0) { - if (err_streams) { - *err_streams = required_inputs; + + cerr << "Checking whether " << (*i).processor->name() << " can support " << required_inputs.n_audio() << " inputs\n"; + + if ((*i).processor->can_support_input_configuration (required_inputs) < 0) { + if (err) { + err->index = index; + err->count = required_inputs; } - return -1; + return false; } (*i).in = required_inputs; - (*i).out = (*i).insert->compute_output_streams ((*i).cnt); + (*i).out = (*i).processor->output_for_input_configuration (required_inputs); + + cerr << "config looks like " << (*i).processor->name() << " in = " << (*i).in.n_audio() << " out = " << (*i).out.n_audio() << endl; required_inputs = (*i).out; + + ++index; + } + + if (err) { + if (!iclist.empty()) { + err->index = index; + err->count = iclist.back().processor->output_for_input_configuration(required_inputs); + } } - return 0; + return true; } int -Route::copy_redirects (const Route& other, Placement placement, uint32_t* err_streams) +Route::copy_processors (const Route& other, Placement placement, ProcessorStreams* err) { - uint32_t old_rmo = redirect_max_outs; - - if (err_streams) { - *err_streams = 0; - } + ChanCount old_rmo = processor_max_outs; - RedirectList to_be_deleted; + ProcessorList to_be_deleted; { - Glib::RWLock::WriterLock lm (redirect_lock); - RedirectList::iterator tmp; - RedirectList the_copy; + Glib::RWLock::WriterLock lm (_processor_lock); + ProcessorList::iterator tmp; + ProcessorList the_copy; - the_copy = _redirects; + the_copy = _processors; - /* remove all relevant redirects */ + /* remove all relevant processors */ - for (RedirectList::iterator i = _redirects.begin(); i != _redirects.end(); ) { + for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ) { tmp = i; ++tmp; if ((*i)->placement() == placement) { to_be_deleted.push_back (*i); - _redirects.erase (i); + _processors.erase (i); } i = tmp; @@ -1205,27 +1333,27 @@ Route::copy_redirects (const Route& other, Placement placement, uint32_t* err_st /* now copy the relevant ones from "other" */ - for (RedirectList::const_iterator i = other._redirects.begin(); i != other._redirects.end(); ++i) { + for (ProcessorList::const_iterator i = other._processors.begin(); i != other._processors.end(); ++i) { if ((*i)->placement() == placement) { - _redirects.push_back (Redirect::clone (*i)); + _processors.push_back (IOProcessor::clone (*i)); } } /* reset plugin stream handling */ - if (_reset_plugin_counts (err_streams)) { + if (_reset_plugin_counts (err)) { /* FAILED COPY ATTEMPT: we have to restore order */ - /* delete all cloned redirects */ + /* delete all cloned processors */ - for (RedirectList::iterator i = _redirects.begin(); i != _redirects.end(); ) { + for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ) { tmp = i; ++tmp; if ((*i)->placement() == placement) { - _redirects.erase (i); + _processors.erase (i); } i = tmp; @@ -1233,8 +1361,8 @@ Route::copy_redirects (const Route& other, Placement placement, uint32_t* err_st /* restore the natural order */ - _redirects = the_copy; - redirect_max_outs = old_rmo; + _processors = the_copy; + processor_max_outs = old_rmo; /* we failed, even though things are OK again */ @@ -1242,78 +1370,89 @@ Route::copy_redirects (const Route& other, Placement placement, uint32_t* err_st } else { - /* SUCCESSFUL COPY ATTEMPT: delete the redirects we removed pre-copy */ + /* SUCCESSFUL COPY ATTEMPT: delete the processors we removed pre-copy */ to_be_deleted.clear (); + _user_latency = 0; } } - if (redirect_max_outs != old_rmo || old_rmo == 0) { + if (processor_max_outs != old_rmo || old_rmo == ChanCount::ZERO) { reset_panner (); } - redirects_changed (this); /* EMIT SIGNAL */ + processors_changed (); /* EMIT SIGNAL */ return 0; } void -Route::all_redirects_flip () +Route::all_processors_flip () { - Glib::RWLock::ReaderLock lm (redirect_lock); + Glib::RWLock::ReaderLock lm (_processor_lock); - if (_redirects.empty()) { + if (_processors.empty()) { return; } - bool first_is_on = _redirects.front()->active(); + bool first_is_on = _processors.front()->active(); - for (RedirectList::iterator i = _redirects.begin(); i != _redirects.end(); ++i) { - (*i)->set_active (!first_is_on, this); + for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) { + (*i)->set_active (!first_is_on); } + + _session.set_dirty (); } +/** Set all processors with a given placement to a given active state. + * @param p Placement of processors to change. + * @param state New active state for those processors. + */ void -Route::all_redirects_active (bool state) +Route::all_processors_active (Placement p, bool state) { - Glib::RWLock::ReaderLock lm (redirect_lock); + Glib::RWLock::ReaderLock lm (_processor_lock); - if (_redirects.empty()) { + if (_processors.empty()) { return; } - for (RedirectList::iterator i = _redirects.begin(); i != _redirects.end(); ++i) { - (*i)->set_active (state, this); + for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) { + if ((*i)->placement() == p) { + (*i)->set_active (state); + } } + + _session.set_dirty (); } -struct RedirectSorter { - bool operator() (boost::shared_ptr a, boost::shared_ptr b) { +struct ProcessorSorter { + bool operator() (boost::shared_ptr a, boost::shared_ptr b) { return a->sort_key() < b->sort_key(); } }; int -Route::sort_redirects (uint32_t* err_streams) +Route::sort_processors (ProcessorStreams* err) { { - RedirectSorter comparator; - Glib::RWLock::WriterLock lm (redirect_lock); - uint32_t old_rmo = redirect_max_outs; + ProcessorSorter comparator; + Glib::RWLock::WriterLock lm (_processor_lock); + ChanCount old_rmo = processor_max_outs; /* the sweet power of C++ ... */ - RedirectList as_it_was_before = _redirects; + ProcessorList as_it_was_before = _processors; - _redirects.sort (comparator); + _processors.sort (comparator); - if (_reset_plugin_counts (err_streams)) { - _redirects = as_it_was_before; - redirect_max_outs = old_rmo; + if (_reset_plugin_counts (err)) { + _processors = as_it_was_before; + processor_max_outs = old_rmo; return -1; } } reset_panner (); - redirects_changed (this); /* EMIT SIGNAL */ + processors_changed (); /* EMIT SIGNAL */ return 0; } @@ -1334,13 +1473,11 @@ XMLNode& Route::state(bool full_state) { XMLNode *node = new XMLNode("Route"); - XMLNode *aevents; - RedirectList:: iterator i; + ProcessorList::iterator i; char buf[32]; if (_flags) { - snprintf (buf, sizeof (buf), "0x%x", _flags); - node->add_property("flags", buf); + node->add_property("flags", enum_2_string (_flags)); } node->add_property("default-type", _default_type.to_string()); @@ -1349,6 +1486,7 @@ Route::state(bool full_state) node->add_property("muted", _muted?"yes":"no"); node->add_property("soloed", _soloed?"yes":"no"); node->add_property("phase-invert", _phase_invert?"yes":"no"); + node->add_property("denormal-protection", _denormal_protection?"yes":"no"); node->add_property("mute-affects-pre-fader", _mute_affects_pre_fader?"yes":"no"); node->add_property("mute-affects-post-fader", _mute_affects_post_fader?"yes":"no"); node->add_property("mute-affects-control-outs", _mute_affects_control_outs?"yes":"no"); @@ -1365,7 +1503,7 @@ Route::state(bool full_state) OrderKeys::iterator x = order_keys.begin(); while (x != order_keys.end()) { - order_string += (*x).first; + order_string += string ((*x).first); order_string += '='; snprintf (buf, sizeof(buf), "%ld", (*x).second); order_string += buf; @@ -1381,8 +1519,13 @@ Route::state(bool full_state) node->add_property ("order-keys", order_string); node->add_child_nocopy (IO::state (full_state)); - node->add_child_nocopy (_solo_control.get_state ()); - node->add_child_nocopy (_mute_control.get_state ()); + node->add_child_nocopy (_solo_control->get_state ()); + node->add_child_nocopy (_mute_control->get_state ()); + + XMLNode* remote_control_node = new XMLNode (X_("remote_control")); + snprintf (buf, sizeof (buf), "%d", _remote_control_id); + remote_control_node->add_property (X_("id"), buf); + node->add_child_nocopy (*remote_control_node); if (_control_outs) { XMLNode* cnode = new XMLNode (X_("ControlOuts")); @@ -1395,27 +1538,7 @@ Route::state(bool full_state) cmt->add_content (_comment); } - if (full_state) { - string path; - - path = _session.snap_name(); - path += "-gain-"; - path += legalize_for_path (_name); - path += ".automation"; - - /* XXX we didn't ask for a state save, we asked for the current state. - FIX ME! - */ - - if (save_automation (path)) { - error << _("Could not get state of route. Problem with save_automation") << endmsg; - } - - aevents = node->add_child ("Automation"); - aevents->add_property ("path", path); - } - - for (i = _redirects.begin(); i != _redirects.end(); ++i) { + for (i = _processors.begin(); i != _processors.end(); ++i) { node->add_child_nocopy((*i)->state (full_state)); } @@ -1426,6 +1549,74 @@ Route::state(bool full_state) return *node; } +XMLNode& +Route::get_processor_state () +{ + XMLNode* root = new XMLNode (X_("redirects")); + for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) { + root->add_child_nocopy ((*i)->state (true)); + } + + return *root; +} + +int +Route::set_processor_state (const XMLNode& root) +{ + if (root.name() != X_("redirects")) { + return -1; + } + + XMLNodeList nlist; + XMLNodeList nnlist; + XMLNodeConstIterator iter; + XMLNodeConstIterator niter; + Glib::RWLock::ReaderLock lm (_processor_lock); + + nlist = root.children(); + + for (iter = nlist.begin(); iter != nlist.end(); ++iter){ + + /* iter now points to a IOProcessor state node */ + + nnlist = (*iter)->children (); + + for (niter = nnlist.begin(); niter != nnlist.end(); ++niter) { + + /* find the IO child node, since it contains the ID we need */ + + /* XXX OOP encapsulation violation, ugh */ + + if ((*niter)->name() == IO::state_node_name) { + + XMLProperty* prop = (*niter)->property (X_("id")); + + if (!prop) { + warning << _("IOProcessor node has no ID, ignored") << endmsg; + break; + } + + ID id = prop->value (); + + /* now look for a processor with that ID */ + + for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) { + if ((*i)->id() == id) { + (*i)->set_state (**iter); + break; + } + } + + break; + + } + } + + } + + return 0; +} + void Route::set_deferred_state () { @@ -1439,7 +1630,7 @@ Route::set_deferred_state () nlist = deferred_state->children(); for (niter = nlist.begin(); niter != nlist.end(); ++niter){ - add_redirect_from_xml (**niter); + add_processor_from_xml (**niter); } delete deferred_state; @@ -1447,16 +1638,16 @@ Route::set_deferred_state () } void -Route::add_redirect_from_xml (const XMLNode& node) +Route::add_processor_from_xml (const XMLNode& node) { const XMLProperty *prop; + // legacy sessions use a different node name for sends if (node.name() == "Send") { - - + try { boost::shared_ptr send (new Send (_session, node)); - add_redirect (send, this); + add_processor (send); } catch (failed_constructor &err) { @@ -1464,36 +1655,40 @@ Route::add_redirect_from_xml (const XMLNode& node) return; } - } else if (node.name() == "Insert") { + // use "Processor" in XML? + } else if (node.name() == "Processor") { try { if ((prop = node.property ("type")) != 0) { - boost::shared_ptr insert; + boost::shared_ptr processor; if (prop->value() == "ladspa" || prop->value() == "Ladspa" || prop->value() == "vst") { - insert.reset (new PluginInsert(_session, node)); + processor.reset (new PluginInsert(_session, node)); } else if (prop->value() == "port") { + processor.reset (new PortInsert (_session, node)); + + } else if (prop->value() == "send") { - insert.reset (new PortInsert (_session, node)); + processor.reset (new Send (_session, node)); } else { - error << string_compose(_("unknown Insert type \"%1\"; ignored"), prop->value()) << endmsg; + error << string_compose(_("unknown Processor type \"%1\"; ignored"), prop->value()) << endmsg; } - add_redirect (insert, this); + add_processor (processor); } else { - error << _("Insert XML node has no type property") << endmsg; + error << _("Processor XML node has no type property") << endmsg; } } catch (failed_constructor &err) { - warning << _("insert could not be created. Ignored.") << endmsg; + warning << _("processor could not be created. Ignored.") << endmsg; return; } } @@ -1501,6 +1696,12 @@ Route::add_redirect_from_xml (const XMLNode& node) int Route::set_state (const XMLNode& node) +{ + return _set_state (node, true); +} + +int +Route::_set_state (const XMLNode& node, bool call_base) { XMLNodeList nlist; XMLNodeConstIterator niter; @@ -1513,28 +1714,30 @@ Route::set_state (const XMLNode& node) return -1; } - if ((prop = node.property ("flags")) != 0) { - int x; - sscanf (prop->value().c_str(), "0x%x", &x); - _flags = Flag (x); + if ((prop = node.property (X_("flags"))) != 0) { + _flags = Flag (string_2_enum (prop->value(), _flags)); } else { _flags = Flag (0); } - if ((prop = node.property ("default-type")) != 0) { + if ((prop = node.property (X_("default-type"))) != 0) { _default_type = DataType(prop->value()); assert(_default_type != DataType::NIL); } - if ((prop = node.property ("phase-invert")) != 0) { - set_phase_invert(prop->value()=="yes"?true:false, this); + if ((prop = node.property (X_("phase-invert"))) != 0) { + set_phase_invert (prop->value()=="yes"?true:false, this); + } + + if ((prop = node.property (X_("denormal-protection"))) != 0) { + set_denormal_protection (prop->value()=="yes"?true:false, this); } - if ((prop = node.property ("active")) != 0) { + if ((prop = node.property (X_("active"))) != 0) { set_active (prop->value() == "yes"); } - if ((prop = node.property ("muted")) != 0) { + if ((prop = node.property (X_("muted"))) != 0) { bool yn = prop->value()=="yes"?true:false; /* force reset of mute status */ @@ -1544,7 +1747,7 @@ Route::set_state (const XMLNode& node) mute_gain = desired_mute_gain; } - if ((prop = node.property ("soloed")) != 0) { + if ((prop = node.property (X_("soloed"))) != 0) { bool yn = prop->value()=="yes"?true:false; /* force reset of solo status */ @@ -1554,23 +1757,23 @@ Route::set_state (const XMLNode& node) solo_gain = desired_solo_gain; } - if ((prop = node.property ("mute-affects-pre-fader")) != 0) { + if ((prop = node.property (X_("mute-affects-pre-fader"))) != 0) { _mute_affects_pre_fader = (prop->value()=="yes")?true:false; } - if ((prop = node.property ("mute-affects-post-fader")) != 0) { + if ((prop = node.property (X_("mute-affects-post-fader"))) != 0) { _mute_affects_post_fader = (prop->value()=="yes")?true:false; } - if ((prop = node.property ("mute-affects-control-outs")) != 0) { + if ((prop = node.property (X_("mute-affects-control-outs"))) != 0) { _mute_affects_control_outs = (prop->value()=="yes")?true:false; } - if ((prop = node.property ("mute-affects-main-outs")) != 0) { + if ((prop = node.property (X_("mute-affects-main-outs"))) != 0) { _mute_affects_main_outs = (prop->value()=="yes")?true:false; } - if ((prop = node.property ("edit-group")) != 0) { + if ((prop = node.property (X_("edit-group"))) != 0) { RouteGroup* edit_group = _session.edit_group_by_name(prop->value()); if(edit_group == 0) { error << string_compose(_("Route %1: unknown edit group \"%2 in saved state (ignored)"), _name, prop->value()) << endmsg; @@ -1579,7 +1782,7 @@ Route::set_state (const XMLNode& node) } } - if ((prop = node.property ("order-keys")) != 0) { + if ((prop = node.property (X_("order-keys"))) != 0) { long n; @@ -1596,7 +1799,7 @@ Route::set_state (const XMLNode& node) error << string_compose (_("badly formed order key string in state file! [%1] ... ignored."), remaining) << endmsg; } else { - set_order_key (remaining.substr (0, equal), n); + set_order_key (remaining.substr (0, equal).c_str(), n); } } @@ -1616,7 +1819,7 @@ Route::set_state (const XMLNode& node) delete deferred_state; } - deferred_state = new XMLNode("deferred state"); + deferred_state = new XMLNode(X_("deferred state")); /* set parent class properties before anything else */ @@ -1624,54 +1827,39 @@ Route::set_state (const XMLNode& node) child = *niter; - if (child->name() == IO::state_node_name) { + if (child->name() == IO::state_node_name && call_base) { IO::set_state (*child); break; } } + + XMLNodeList processor_nodes; for (niter = nlist.begin(); niter != nlist.end(); ++niter){ child = *niter; - if (child->name() == "Send") { - + if (child->name() == X_("Send") || child->name() == X_("Processor")) { + processor_nodes.push_back(child); + } - if (!IO::ports_legal) { + } - deferred_state->add_child_copy (*child); + _set_processor_states(processor_nodes); - } else { - add_redirect_from_xml (*child); - } - - } else if (child->name() == "Insert") { - - if (!IO::ports_legal) { - - deferred_state->add_child_copy (*child); - } else { - - add_redirect_from_xml (*child); - } - - } else if (child->name() == "Automation") { + for (niter = nlist.begin(); niter != nlist.end(); ++niter){ + child = *niter; + // All processors have been applied already - XMLPropertyList plist; - XMLPropertyConstIterator piter; - XMLProperty *prop; + if (child->name() == X_("Automation")) { - plist = child->properties(); - for (piter = plist.begin(); piter != plist.end(); ++piter) { - prop = *piter; - if (prop->name() == "path") { - load_automation (prop->value()); - } + if ((prop = child->property (X_("path"))) != 0) { + load_automation (prop->value()); } - } else if (child->name() == "ControlOuts") { + } else if (child->name() == X_("ControlOuts")) { string coutname = _name; coutname += _("[control]"); @@ -1679,25 +1867,38 @@ Route::set_state (const XMLNode& node) _control_outs = new IO (_session, coutname); _control_outs->set_state (**(child->children().begin())); - } else if (child->name() == "Comment") { + } else if (child->name() == X_("Comment")) { /* XXX this is a terrible API design in libxml++ */ XMLNode *cmt = *(child->children().begin()); _comment = cmt->content(); - } else if (child->name() == "extra") { + } else if (child->name() == X_("extra")) { + _extra_xml = new XMLNode (*child); - } else if (child->name() == "solo") { - _solo_control.set_state (*child); - _session.add_controllable (&_solo_control); - } else if (child->name() == "mute") { - _mute_control.set_state (*child); - _session.add_controllable (&_mute_control); + + } else if (child->name() == X_("controllable") && (prop = child->property("name")) != 0) { + + if (prop->value() == "solo") { + _solo_control->set_state (*child); + _session.add_controllable (_solo_control); + } + else if (prop->value() == "mute") { + _mute_control->set_state (*child); + _session.add_controllable (_mute_control); + } + } + else if (child->name() == X_("remote_control")) { + if ((prop = child->property (X_("id"))) != 0) { + int32_t x; + sscanf (prop->value().c_str(), "%d", &x); + set_remote_control_id (x); + } } } - if ((prop = node.property ("mix-group")) != 0) { + if ((prop = node.property (X_("mix-group"))) != 0) { RouteGroup* mix_group = _session.mix_group_by_name(prop->value()); if (mix_group == 0) { error << string_compose(_("Route %1: unknown mix group \"%2 in saved state (ignored)"), _name, prop->value()) << endmsg; @@ -1709,6 +1910,109 @@ Route::set_state (const XMLNode& node) return 0; } +void +Route::_set_processor_states(const XMLNodeList &nlist) +{ + XMLNodeConstIterator niter; + char buf[64]; + + ProcessorList::iterator i, o; + + // Iterate through existing processors, remove those which are not in the state list + for (i = _processors.begin(); i != _processors.end(); ) { + ProcessorList::iterator tmp = i; + ++tmp; + + bool processorInStateList = false; + + (*i)->id().print (buf, sizeof (buf)); + + + for (niter = nlist.begin(); niter != nlist.end(); ++niter) { + + // legacy sessions (IOProcessor as a child of Processor, both is-a IO) + if (strncmp(buf,(*niter)->child(X_("IOProcessor"))->child(X_("IO"))->property(X_("id"))->value().c_str(), sizeof(buf)) == 0) { + processorInStateList = true; + break; + } else if (strncmp(buf,(*niter)->property(X_("id"))->value().c_str(), sizeof(buf)) == 0) { + processorInStateList = true; + break; + } + } + + if (!processorInStateList) { + remove_processor (*i); + } + + + i = tmp; + } + + + // Iterate through state list and make sure all processors are on the track and in the correct order, + // set the state of existing processors according to the new state on the same go + i = _processors.begin(); + for (niter = nlist.begin(); niter != nlist.end(); ++niter, ++i) { + + // Check whether the next processor in the list + o = i; + + while (o != _processors.end()) { + (*o)->id().print (buf, sizeof (buf)); + if ( strncmp(buf, (*niter)->child(X_("IOProcessor"))->child(X_("IO"))->property(X_("id"))->value().c_str(), sizeof(buf)) == 0) + break; + else if (strncmp(buf,(*niter)->property(X_("id"))->value().c_str(), sizeof(buf)) == 0) + break; + + ++o; + } + + if (o == _processors.end()) { + // If the processor (*niter) is not on the route, we need to create it + // and move it to the correct location + + ProcessorList::iterator prev_last = _processors.end(); + --prev_last; // We need this to check whether adding succeeded + + add_processor_from_xml (**niter); + + ProcessorList::iterator last = _processors.end(); + --last; + + if (prev_last == last) { + cerr << "Could not fully restore state as some processors were not possible to create" << endl; + continue; + + } + + boost::shared_ptr tmp = (*last); + // remove the processor from the wrong location + _processors.erase(last); + // processor the new processor at the current location + _processors.insert(i, tmp); + + --i; // move pointer to the newly processored processor + continue; + } + + // We found the processor (*niter) on the route, first we must make sure the processor + // is at the location provided in the XML state + if (i != o) { + boost::shared_ptr tmp = (*o); + // remove the old copy + _processors.erase(o); + // processor the processor at the correct location + _processors.insert(i, tmp); + + --i; // move pointer so it points to the right processor + } + + (*i)->set_state( (**niter) ); + } + + processors_changed (); +} + void Route::curve_reallocate () { @@ -1721,8 +2025,6 @@ Route::silence (nframes_t nframes, nframes_t offset) { if (!_silent) { - // reset_peak_meters (); - IO::silence (nframes, offset); if (_control_outs) { @@ -1730,10 +2032,10 @@ Route::silence (nframes_t nframes, nframes_t offset) } { - Glib::RWLock::ReaderLock lm (redirect_lock, Glib::TRY_LOCK); + Glib::RWLock::ReaderLock lm (_processor_lock, Glib::TRY_LOCK); if (lm.locked()) { - for (RedirectList::iterator i = _redirects.begin(); i != _redirects.end(); ++i) { + for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) { boost::shared_ptr pi; if (!_active && (pi = boost::dynamic_pointer_cast (*i)) != 0) { // skip plugins, they don't need anything when we're not active @@ -1755,13 +2057,19 @@ Route::silence (nframes_t nframes, nframes_t offset) int Route::set_control_outs (const vector& ports) { - Glib::Mutex::Lock lm (control_outs_lock); + Glib::Mutex::Lock lm (_control_outs_lock); vector::const_iterator i; - + size_t limit; + if (_control_outs) { delete _control_outs; _control_outs = 0; } + + if (is_control() || is_master()) { + /* no control outs for these two special busses */ + return 0; + } if (ports.empty()) { return 0; @@ -1773,10 +2081,25 @@ Route::set_control_outs (const vector& ports) _control_outs = new IO (_session, coutname); /* our control outs need as many outputs as we - have outputs. we track the changes in ::output_change_handler(). + have audio outputs. we track the changes in ::output_change_handler(). */ + + // XXX its stupid that we have to get this value twice - _control_outs->ensure_io (0, n_outputs(), true, this); + limit = n_outputs().n_audio(); + + if (_control_outs->ensure_io (ChanCount::ZERO, ChanCount (DataType::AUDIO, n_outputs().get (DataType::AUDIO)), true, this)) { + return -1; + } + + /* now connect to the named ports */ + + for (size_t n = 0; n < limit; ++n) { + if (_control_outs->connect_output (_control_outs->output (n), ports[n % ports.size()], this)) { + error << string_compose (_("could not connect %1 to %2"), _control_outs->output(n)->name(), ports[n]) << endmsg; + return -1; + } + } return 0; } @@ -1851,8 +2174,8 @@ Route::feeds (boost::shared_ptr other) uint32_t i, j; IO& self = *this; - uint32_t no = self.n_outputs(); - uint32_t ni = other->n_inputs (); + uint32_t no = self.n_outputs().n_total(); + uint32_t ni = other->n_inputs ().n_total(); for (i = 0; i < no; ++i) { for (j = 0; j < ni; ++j) { @@ -1862,15 +2185,22 @@ Route::feeds (boost::shared_ptr other) } } - /* check Redirects which may also interconnect Routes */ + /* check IOProcessors which may also interconnect Routes */ + + for (ProcessorList::iterator r = _processors.begin(); r != _processors.end(); r++) { - for (RedirectList::iterator r = _redirects.begin(); r != _redirects.end(); r++) { + boost::shared_ptr redirect = boost::dynamic_pointer_cast(*r); - no = (*r)->n_outputs(); + if ( ! redirect) + continue; + + // TODO: support internal redirects here + + no = redirect->io()->n_outputs().n_total(); for (i = 0; i < no; ++i) { for (j = 0; j < ni; ++j) { - if ((*r)->output(i)->connected_to (other->input (j)->name())) { + if (redirect->io()->output(i)->connected_to (other->input (j)->name())) { return true; } } @@ -1881,7 +2211,7 @@ Route::feeds (boost::shared_ptr other) if (_control_outs) { - no = _control_outs->n_outputs(); + no = _control_outs->n_outputs().n_total(); for (i = 0; i < no; ++i) { for (j = 0; j < ni; ++j) { @@ -1948,20 +2278,24 @@ void Route::set_active (bool yn) { _active = yn; - active_changed(); /* EMIT SIGNAL */ + active_changed(); /* EMIT SIGNAL */ } void -Route::handle_transport_stopped (bool abort_ignored, bool did_locate, bool can_flush_redirects) +Route::handle_transport_stopped (bool abort_ignored, bool did_locate, bool can_flush_processors) { nframes_t now = _session.transport_frame(); { - Glib::RWLock::ReaderLock lm (redirect_lock); + Glib::RWLock::ReaderLock lm (_processor_lock); - for (RedirectList::iterator i = _redirects.begin(); i != _redirects.end(); ++i) { + if (!did_locate) { + automation_snapshot (now); + } + + for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) { - if (Config->get_plugins_stop_with_transport() && can_flush_redirects) { + if (Config->get_plugins_stop_with_transport() && can_flush_processors) { (*i)->deactivate (); (*i)->activate (); } @@ -1975,19 +2309,6 @@ Route::handle_transport_stopped (bool abort_ignored, bool did_locate, bool can_f _roll_delay = _initial_delay; } -UndoAction -Route::get_memento() const -{ - void (Route::*pmf)(state_id_t) = &Route::set_state; - return sigc::bind (mem_fun (*(const_cast(this)), pmf), _current_state_id); -} - -void -Route::set_state (state_id_t id) -{ - return; -} - void Route::input_change_handler (IOChange change, void *ignored) { @@ -2001,7 +2322,7 @@ Route::output_change_handler (IOChange change, void *ignored) { if (change & ConfigurationChanged) { if (_control_outs) { - _control_outs->ensure_io (0, n_outputs(), true, this); + _control_outs->ensure_io (ChanCount::ZERO, ChanCount(DataType::AUDIO, n_outputs().n_audio()), true, this); } reset_plugin_counts (0); @@ -2011,18 +2332,18 @@ Route::output_change_handler (IOChange change, void *ignored) uint32_t Route::pans_required () const { - if (n_outputs() < 2) { + if (n_outputs().n_audio() < 2) { return 0; } - return max (n_inputs (), redirect_max_outs); + return max (n_inputs ().n_audio(), processor_max_outs.n_audio()); } int Route::no_roll (nframes_t nframes, nframes_t start_frame, nframes_t end_frame, nframes_t offset, bool session_state_changing, bool can_record, bool rec_monitors_input) { - if (n_outputs() == 0) { + if (n_outputs().n_total() == 0) { return 0; } @@ -2033,7 +2354,7 @@ Route::no_roll (nframes_t nframes, nframes_t start_frame, nframes_t end_frame, n apply_gain_automation = false; - if (n_inputs()) { + if (n_inputs().n_total()) { passthru (start_frame, end_frame, nframes, offset, 0, false); } else { silence (nframes, offset); @@ -2071,7 +2392,16 @@ int Route::roll (nframes_t nframes, nframes_t start_frame, nframes_t end_frame, nframes_t offset, int declick, bool can_record, bool rec_monitors_input) { - if ((n_outputs() == 0 && _redirects.empty()) || n_inputs() == 0 || !_active) { + { + Glib::RWLock::ReaderLock lm (_processor_lock, Glib::TRY_LOCK); + if (lm.locked()) { + // automation snapshot can also be called from the non-rt context + // and it uses the processor list, so we take the lock out here + automation_snapshot (_session.transport_frame()); + } + } + + if ((n_outputs().n_total() == 0 && _processors.empty()) || n_inputs().n_total() == 0 || !_active) { silence (nframes, offset); return 0; } @@ -2087,14 +2417,13 @@ Route::roll (nframes_t nframes, nframes_t start_frame, nframes_t end_frame, nfra apply_gain_automation = false; { - Glib::Mutex::Lock am (automation_lock, Glib::TRY_LOCK); + Glib::Mutex::Lock am (_automation_lock, Glib::TRY_LOCK); if (am.locked() && _session.transport_rolling()) { - nframes_t start_frame = end_frame - nframes; - - if (gain_automation_playback()) { - apply_gain_automation = _gain_automation_curve.rt_safe_get_vector (start_frame, end_frame, _session.gain_automation_buffer(), nframes); + if (_gain_control->list()->automation_playback()) { + apply_gain_automation = _gain_control->list()->curve().rt_safe_get_vector ( + start_frame, end_frame, _session.gain_automation_buffer(), nframes); } } } @@ -2115,24 +2444,25 @@ Route::silent_roll (nframes_t nframes, nframes_t start_frame, nframes_t end_fram void Route::toggle_monitor_input () { - for (vector::iterator i = _inputs.begin(); i != _inputs.end(); ++i) { - (*i)->ensure_monitor_input(!(*i)->monitoring_input()); + for (PortSet::iterator i = _inputs.begin(); i != _inputs.end(); ++i) { + i->ensure_monitor_input( ! i->monitoring_input()); } } bool Route::has_external_redirects () const { + // FIXME: what about sends? + boost::shared_ptr pi; - for (RedirectList::const_iterator i = _redirects.begin(); i != _redirects.end(); ++i) { + for (ProcessorList::const_iterator i = _processors.begin(); i != _processors.end(); ++i) { if ((pi = boost::dynamic_pointer_cast(*i)) != 0) { - uint32_t no = pi->n_outputs(); - - for (uint32_t n = 0; n < no; ++n) { + for (PortSet::const_iterator port = pi->io()->outputs().begin(); + port != pi->io()->outputs().end(); ++port) { - string port_name = pi->output(n)->name(); + string port_name = port->name(); string client_name = port_name.substr (0, port_name.find(':')); /* only say "yes" if the redirect is actually in use */ @@ -2148,15 +2478,15 @@ Route::has_external_redirects () const } void -Route::flush_redirects () +Route::flush_processors () { /* XXX shouldn't really try to take this lock, since this is called from the RT audio thread. */ - Glib::RWLock::ReaderLock lm (redirect_lock); + Glib::RWLock::ReaderLock lm (_processor_lock); - for (RedirectList::iterator i = _redirects.begin(); i != _redirects.end(); ++i) { + for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) { (*i)->deactivate (); (*i)->activate (); } @@ -2175,38 +2505,78 @@ Route::set_meter_point (MeterPoint p, void *src) nframes_t Route::update_total_latency () { - _own_latency = 0; + nframes_t old = _own_latency; - for (RedirectList::iterator i = _redirects.begin(); i != _redirects.end(); ++i) { - if ((*i)->active ()) { - _own_latency += (*i)->latency (); + if (_user_latency) { + _own_latency = _user_latency; + } else { + _own_latency = 0; + + for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) { + if ((*i)->active ()) { + _own_latency += (*i)->signal_latency (); + } } } set_port_latency (_own_latency); + + if (!_user_latency) { + /* this (virtual) function is used for pure Routes, + not derived classes like AudioTrack. this means + that the data processed here comes from an input + port, not prerecorded material, and therefore we + have to take into account any input latency. + */ - /* this (virtual) function is used for pure Routes, - not derived classes like AudioTrack. this means - that the data processed here comes from an input - port, not prerecorded material, and therefore we - have to take into account any input latency. - */ - _own_latency += input_latency (); + _own_latency += input_latency (); + } + if (old != _own_latency) { + signal_latency_changed (); /* EMIT SIGNAL */ + } + return _own_latency; } +void +Route::set_user_latency (nframes_t nframes) +{ + Latent::set_user_latency (nframes); + _session.update_latency_compensation (false, false); +} + void Route::set_latency_delay (nframes_t longest_session_latency) { - _initial_delay = longest_session_latency - _own_latency; + nframes_t old = _initial_delay; + + if (_own_latency < longest_session_latency) { + _initial_delay = longest_session_latency - _own_latency; + } else { + _initial_delay = 0; + } + + if (_initial_delay != old) { + initial_delay_changed (); /* EMIT SIGNAL */ + } if (_session.transport_stopped()) { _roll_delay = _initial_delay; } } +void +Route::automation_snapshot (nframes_t now) +{ + IO::automation_snapshot (now); + + for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) { + (*i)->automation_snapshot (now); + } +} + Route::ToggleControllable::ToggleControllable (std::string name, Route& s, ToggleType tp) : Controllable (name), route (s), type(tp) { @@ -2252,44 +2622,18 @@ Route::ToggleControllable::get_value (void) const void Route::set_block_size (nframes_t nframes) { - for (RedirectList::iterator i = _redirects.begin(); i != _redirects.end(); ++i) { + for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) { (*i)->set_block_size (nframes); } } -void -Route::redirect_active_proxy (Redirect* ignored, void* ignored_src) -{ - _session.update_latency_compensation (false, false); -} - void Route::protect_automation () { - switch (gain_automation_state()) { - case Write: - case Touch: - set_gain_automation_state (Off); - break; - default: - break; - } - - switch (panner().automation_state ()) { - case Write: - case Touch: - panner().set_automation_state (Off); - break; - default: - break; - } + Automatable::protect_automation(); - for (RedirectList::iterator i = _redirects.begin(); i != _redirects.end(); ++i) { - boost::shared_ptr pi; - if ((pi = boost::dynamic_pointer_cast (*i)) != 0) { - pi->protect_automation (); - } - } + for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) + (*i)->protect_automation(); } void