X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=libs%2Fardour%2Froute.cc;h=053b56512804b56512bbdfc3ce07462ca2c72387;hb=c35eae34c39c9962ac93110f67e9ea40f24834b6;hp=d77dc46b0c60f91a82512178f86c312f9a3367ae;hpb=8ae580427987b4eefc102f3e801c1b76fdc74d48;p=ardour.git diff --git a/libs/ardour/route.cc b/libs/ardour/route.cc index d77dc46b0c..053b565128 100644 --- a/libs/ardour/route.cc +++ b/libs/ardour/route.cc @@ -1,5 +1,5 @@ /* - Copyright (C) 2000 Paul Davis + Copyright (C) 2000 Paul Davis This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -20,30 +20,43 @@ #include #include #include +#include #include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "pbd/xml++.h" +#include "pbd/enumwriter.h" +#include "pbd/stacktrace.h" +#include "pbd/memento_command.h" + +#include "evoral/Curve.hpp" + +#include "ardour/amp.h" +#include "ardour/audio_port.h" +#include "ardour/audioengine.h" +#include "ardour/buffer.h" +#include "ardour/buffer_set.h" +#include "ardour/configuration.h" +#include "ardour/cycle_timer.h" +#include "ardour/delivery.h" +#include "ardour/dB.h" +#include "ardour/internal_send.h" +#include "ardour/internal_return.h" +#include "ardour/ladspa_plugin.h" +#include "ardour/meter.h" +#include "ardour/mix.h" +#include "ardour/panner.h" +#include "ardour/plugin_insert.h" +#include "ardour/port.h" +#include "ardour/port_insert.h" +#include "ardour/processor.h" +#include "ardour/profile.h" +#include "ardour/route.h" +#include "ardour/route_group.h" +#include "ardour/send.h" +#include "ardour/session.h" +#include "ardour/timestamps.h" +#include "ardour/utils.h" + #include "i18n.h" using namespace std; @@ -51,78 +64,104 @@ using namespace ARDOUR; using namespace PBD; uint32_t Route::order_key_cnt = 0; +sigc::signal Route::SyncOrderKeys; +Route::Route (Session& sess, string name, Flag flg, DataType default_type) + : SessionObject (sess, name) + , AutomatableControls (sess) + , _flags (flg) + , _solo_control (new SoloControllable (X_("solo"), *this)) + , _mute_master (new MuteMaster (sess, name)) + , _default_type (default_type) -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) { init (); + + /* add standard processors other than amp (added by ::init()) */ + + _meter.reset (new PeakMeter (_session)); + add_processor (_meter, PreFader); + + if (_flags & ControlOut) { + /* where we listen to tracks */ + _intreturn.reset (new InternalReturn (_session)); + add_processor (_intreturn, PreFader); + } + + _main_outs.reset (new Delivery (_session, _output, _mute_master, _name, Delivery::Main)); + add_processor (_main_outs, PostFader); + + /* now that we have _meter, its safe to connect to this */ + + _meter_connection = Metering::connect (mem_fun (*this, &Route::meter)); } Route::Route (Session& sess, const XMLNode& node, DataType default_type) - : IO (sess, *node.child ("IO"), default_type), - _solo_control (X_("solo"), *this, ToggleControllable::SoloControl), - _mute_control (X_("mute"), *this, ToggleControllable::MuteControl) + : SessionObject (sess, "toBeReset") + , AutomatableControls (sess) + , _solo_control (new SoloControllable (X_("solo"), *this)) + , _mute_master (new MuteMaster (sess, "toBeReset")) + , _default_type (default_type) { init (); - _set_state (node, false); + + _set_state (node, Stateful::loading_state_version, false); + + /* now that we have _meter, its safe to connect to this */ + + _meter_connection = Metering::connect (mem_fun (*this, &Route::meter)); } void Route::init () { - redirect_max_outs.reset(); - _muted = false; - _soloed = false; - _solo_safe = false; - _phase_invert = false; - _denormal_protection = false; - order_keys[strdup (N_("signal"))] = order_key_cnt++; + _solo_level = 0; + _solo_isolated = false; _active = true; + processor_max_streams.reset(); + _solo_safe = false; + _recordable = true; + order_keys[N_("signal")] = order_key_cnt++; _silent = false; _meter_point = MeterPostFader; _initial_delay = 0; _roll_delay = 0; - _own_latency = 0; _have_internal_generator = false; _declickable = false; _pending_declick = true; _remote_control_id = 0; - - _edit_group = 0; - _mix_group = 0; + _in_configure_processors = false; + + _route_group = 0; + + _phase_invert = 0; + _denormal_protection = false; + + /* add standard controls */ + + add_control (_solo_control); + add_control (_mute_master); + + /* input and output objects */ - _mute_affects_pre_fader = Config->get_mute_affects_pre_fader(); - _mute_affects_post_fader = Config->get_mute_affects_post_fader(); - _mute_affects_control_outs = Config->get_mute_affects_control_outs(); - _mute_affects_main_outs = Config->get_mute_affects_main_outs(); - - solo_gain = 1.0; - desired_solo_gain = 1.0; - mute_gain = 1.0; - desired_mute_gain = 1.0; + _input.reset (new IO (_session, _name, IO::Input, _default_type)); + _output.reset (new IO (_session, _name, IO::Output, _default_type)); - _control_outs = 0; + _input->changed.connect (mem_fun (this, &Route::input_change_handler)); + _output->changed.connect (mem_fun (this, &Route::output_change_handler)); - input_changed.connect (mem_fun (this, &Route::input_change_handler)); - output_changed.connect (mem_fun (this, &Route::output_change_handler)); + /* add amp processor */ + + _amp.reset (new Amp (_session, _mute_master)); + add_processor (_amp, PostFader); } Route::~Route () { - clear_redirects (PreFader, this); - clear_redirects (PostFader, this); - - for (OrderKeys::iterator i = order_keys.begin(); i != order_keys.end(); ++i) { - free ((void*)(i->first)); - } + Metering::disconnect (_meter_connection); - if (_control_outs) { - delete _control_outs; - } + clear_processors (PreFader); + clear_processors (PostFader); } void @@ -141,838 +180,896 @@ Route::remote_control_id() const } long -Route::order_key (const char* name) const +Route::order_key (std::string const & name) const { - OrderKeys::const_iterator i; - - for (i = order_keys.begin(); i != order_keys.end(); ++i) { - if (!strcmp (name, i->first)) { - return i->second; - } + OrderKeys::const_iterator i = order_keys.find (name); + if (i == order_keys.end()) { + return -1; } - return -1; + return i->second; } void -Route::set_order_key (const char* name, long n) +Route::set_order_key (std::string const & name, long n) { - order_keys[strdup(name)] = n; + order_keys[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 (); } +/** Set all order keys to be the same as that for `base', if such a key + * exists in this route. + * @param base Base key. + */ +void +Route::sync_order_keys (std::string const & base) +{ + if (order_keys.empty()) { + return; + } + + OrderKeys::iterator i; + uint32_t key; + + if ((i = order_keys.find (base)) == order_keys.end()) { + /* key doesn't exist, use the first existing key (during session initialization) */ + i = order_keys.begin(); + key = i->second; + ++i; + } else { + /* key exists - use it and reset all others (actually, itself included) */ + key = i->second; + i = order_keys.begin(); + } + + for (; i != order_keys.end(); ++i) { + i->second = key; + } +} + +string +Route::ensure_track_or_route_name(string name, Session &session) +{ + string newname = name; + + while (session.route_by_name (newname) != NULL) { + newname = bump_name_once (newname); + } + + return newname; +} + + void Route::inc_gain (gain_t fraction, void *src) { - IO::inc_gain (fraction, src); + _amp->inc_gain (fraction, src); } void Route::set_gain (gain_t val, void *src) { - if (src != 0 && _mix_group && src != _mix_group && _mix_group->is_active()) { - - if (_mix_group->is_relative()) { - - - gain_t usable_gain = gain(); + if (src != 0 && _route_group && src != _route_group && _route_group->active_property (RouteGroup::Gain)) { + + if (_route_group->is_relative()) { + + gain_t usable_gain = _amp->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); + factor = _route_group->get_max_factor(factor); if (factor == 0.0f) { - gain_changed (src); + _amp->gain_control()->Changed(); /* EMIT SIGNAL */ return; } } else { - factor = _mix_group->get_min_factor(factor); + factor = _route_group->get_min_factor(factor); if (factor == 0.0f) { - gain_changed (src); + _amp->gain_control()->Changed(); /* EMIT SIGNAL */ return; } } - - _mix_group->apply (&Route::inc_gain, factor, _mix_group); + + _route_group->apply (&Route::inc_gain, factor, _route_group); } else { - - _mix_group->apply (&Route::set_gain, val, _mix_group); + + _route_group->apply (&Route::set_gain, val, _route_group); } return; - } + } - if (val == gain()) { + if (val == _amp->gain()) { return; } - IO::set_gain (val, src); + _amp->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 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 (BufferSet& bufs, - nframes_t start_frame, nframes_t end_frame, - nframes_t nframes, nframes_t offset, bool with_redirects, int declick, - bool meter) -{ - // This is definitely very audio-only for now - assert(_default_type == DataType::AUDIO); - - RedirectList::iterator i; - bool post_fader_work = false; - bool mute_declick_applied = false; - gain_t dmg, dsg, dg; - IO *co; - bool mute_audible; - bool solo_audible; - bool no_monitor; - gain_t* gab = _session.gain_automation_buffer(); + sframes_t start_frame, sframes_t end_frame, nframes_t nframes, + bool /*with_processors*/, int declick) +{ + bool monitor; + + bufs.is_silent (false); switch (Config->get_monitoring_model()) { case HardwareMonitoring: case ExternalMonitoring: - no_monitor = true; + monitor = !record_enabled() || (_session.config.get_auto_input() && !_session.actively_recording()); break; default: - no_monitor = false; + monitor = true; } - declick = _pending_declick; - - { - Glib::Mutex::Lock cm (control_outs_lock, Glib::TRY_LOCK); - - if (cm.locked()) { - co = _control_outs; - } else { - co = 0; - } - } - - { - Glib::Mutex::Lock dm (declick_lock, Glib::TRY_LOCK); - - if (dm.locked()) { - dmg = desired_mute_gain; - dsg = desired_solo_gain; - dg = _desired_gain; - } else { - dmg = mute_gain; - dsg = solo_gain; - dg = _gain; - } + if (!declick) { + declick = _pending_declick; } - /* ---------------------------------------------------------------------------------------------------- - GLOBAL DECLICK (for transport changes etc.) - -------------------------------------------------------------------------------------------------- */ - - if (declick > 0) { - Amp::run (bufs, nframes, 0.0, 1.0, false); - _pending_declick = 0; - } else if (declick < 0) { - Amp::run (bufs, nframes, 1.0, 0.0, false); - _pending_declick = 0; - } else { - - /* no global declick */ + /* figure out if we're going to use gain automation */ + _amp->setup_gain_automation (start_frame, end_frame, nframes); - if (solo_gain != dsg) { - Amp::run (bufs, nframes, solo_gain, dsg, false); - solo_gain = dsg; - } - } + /* tell main outs what to do about monitoring */ + _main_outs->no_outs_cuz_we_no_monitor (!monitor); - /* ---------------------------------------------------------------------------------------------------- - INPUT METERING & MONITORING - -------------------------------------------------------------------------------------------------- */ - if (meter && (_meter_point == MeterInput)) { - _meter->run(bufs, nframes); - } + /* ------------------------------------------------------------------------------------------- + GLOBAL DECLICK (for transport changes etc.) + ----------------------------------------------------------------------------------------- */ - if (!_soloed && _mute_affects_pre_fader && (mute_gain != dmg)) { - Amp::run (bufs, nframes, mute_gain, dmg, false); - mute_gain = dmg; - mute_declick_applied = true; + if (declick > 0) { + Amp::apply_gain (bufs, nframes, 0.0, 1.0); + } else if (declick < 0) { + Amp::apply_gain (bufs, nframes, 1.0, 0.0); } - if ((_meter_point == MeterInput) && co) { - - solo_audible = dsg > 0; - mute_audible = dmg > 0;// || !_mute_affects_pre_fader; - - if ( // muted by solo of another track - - !solo_audible || - - // muted by mute of this track - - !mute_audible || - - // rec-enabled but not s/w monitoring - - // TODO: this is probably wrong + _pending_declick = 0; - (no_monitor && record_enabled() && (!Config->get_auto_input() || _session.actively_recording())) + /* ------------------------------------------------------------------------------------------- + DENORMAL CONTROL/PHASE INVERT + ----------------------------------------------------------------------------------------- */ - ) { - - co->silence (nframes, offset); - - } else { - - 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 (_phase_invert) { - /* FIXME: Somewhere in these loops is where bufs.count() should go from n_inputs() to redirect_max_outs() - * (if they differ). Something explicit needs to be done here to make sure the list of redirects will - * give us what we need (possibly by inserting transparent 'translators' into the list to make it work) */ + int chn = 0; - if (with_redirects) { - Glib::RWLock::ReaderLock rm (redirect_lock, Glib::TRY_LOCK); - if (rm.locked()) { - if (mute_gain > 0 || !_mute_affects_pre_fader) { - for (i = _redirects.begin(); i != _redirects.end(); ++i) { - switch ((*i)->placement()) { - case PreFader: - (*i)->run (bufs, start_frame, end_frame, nframes, offset); - break; - case PostFader: - post_fader_work = true; - break; - } - } - } else { - for (i = _redirects.begin(); i != _redirects.end(); ++i) { - switch ((*i)->placement()) { - case PreFader: - (*i)->silence (nframes, offset); - break; - case PostFader: - post_fader_work = true; - break; - } - } - } - } - } + if (_denormal_protection || Config->get_denormal_protection()) { - // FIXME: for now, just hope the redirects list did what it was supposed to - bufs.set_count(n_process_buffers()); - - - if (!_soloed && (mute_gain != dmg) && !mute_declick_applied && _mute_affects_post_fader) { - Amp::run (bufs, nframes, mute_gain, dmg, false); - mute_gain = dmg; - mute_declick_applied = true; - } - - /* ---------------------------------------------------------------------------------------------------- - PRE-FADER METERING & MONITORING - -------------------------------------------------------------------------------------------------- */ - - if (meter && (_meter_point == MeterPreFader)) { - _meter->run(bufs, nframes); - } - - - if ((_meter_point == MeterPreFader) && co) { - - solo_audible = dsg > 0; - mute_audible = dmg > 0 || !_mute_affects_pre_fader; - - if ( // muted by solo of another track - - !solo_audible || - - // muted by mute of this track - - !mute_audible || - - // rec-enabled but not s/w monitoring - - (no_monitor && record_enabled() && (!Config->get_auto_input() || _session.actively_recording())) - - ) { - - co->silence (nframes, offset); - - } else { + for (BufferSet::audio_iterator i = bufs.audio_begin(); i != bufs.audio_end(); ++i, ++chn) { + Sample* const sp = i->data(); - co->deliver_output (bufs, start_frame, end_frame, nframes, offset); - } - } - - /* ---------------------------------------------------------------------------------------------------- - GAIN STAGE - -------------------------------------------------------------------------------------------------- */ - - /* if not recording or recording and requiring any monitor signal, then apply gain */ - - if ( // not recording - - !(record_enabled() && _session.actively_recording()) || - - // OR recording - - // h/w monitoring not in use - - (!Config->get_monitoring_model() == HardwareMonitoring && - - // AND software monitoring required - - Config->get_monitoring_model() == SoftwareMonitoring)) { - - if (apply_gain_automation) { - - if (_phase_invert) { - for (BufferSet::audio_iterator i = bufs.audio_begin(); i != bufs.audio_end(); ++i) { - Sample* const sp = i->data(); - + if (_phase_invert & chn) { for (nframes_t nx = 0; nx < nframes; ++nx) { - sp[nx] *= -gab[nx]; + sp[nx] = -sp[nx]; + sp[nx] += 1.0e-27f; } - } - } else { - for (BufferSet::audio_iterator i = bufs.audio_begin(); i != bufs.audio_end(); ++i) { - Sample* const sp = i->data(); - + } else { for (nframes_t nx = 0; nx < nframes; ++nx) { - sp[nx] *= gab[nx]; + sp[nx] += 1.0e-27f; } } } - - if (apply_gain_automation && _session.transport_rolling() && nframes > 0) { - _effective_gain = gab[nframes-1]; - } - + } else { - - /* manual (scalar) gain */ - - if (_gain != dg) { - - Amp::run (bufs, nframes, _gain, dg, _phase_invert); - _gain = dg; - - } else if (_gain != 0 && (_phase_invert || _gain != 1.0)) { - - /* no need to interpolate current gain value, - but its non-unity, so apply it. if the gain - is zero, do nothing because we'll ship silence - below. - */ - gain_t this_gain; - - if (_phase_invert) { - this_gain = -_gain; - } else { - this_gain = _gain; - } - - 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); - } + for (BufferSet::audio_iterator i = bufs.audio_begin(); i != bufs.audio_end(); ++i, ++chn) { + Sample* const sp = i->data(); - } else if (_gain == 0) { - for (BufferSet::audio_iterator i = bufs.audio_begin(); i != bufs.audio_end(); ++i) { - i->clear(); + if (_phase_invert & chn) { + for (nframes_t nx = 0; nx < nframes; ++nx) { + sp[nx] = -sp[nx]; + } } } } } else { - /* actively recording, no monitoring required; leave buffers as-is to save CPU cycles */ - - } - - /* ---------------------------------------------------------------------------------------------------- - POST-FADER REDIRECTS - -------------------------------------------------------------------------------------------------- */ - - /* note that post_fader_work cannot be true unless with_redirects was also true, so don't test both */ - - if (post_fader_work) { + if (_denormal_protection || Config->get_denormal_protection()) { - Glib::RWLock::ReaderLock rm (redirect_lock, Glib::TRY_LOCK); - if (rm.locked()) { - if (mute_gain > 0 || !_mute_affects_post_fader) { - for (i = _redirects.begin(); i != _redirects.end(); ++i) { - switch ((*i)->placement()) { - case PreFader: - break; - case PostFader: - (*i)->run (bufs, start_frame, end_frame, nframes, offset); - break; - } - } - } else { - for (i = _redirects.begin(); i != _redirects.end(); ++i) { - switch ((*i)->placement()) { - case PreFader: - break; - case PostFader: - (*i)->silence (nframes, offset); - break; - } + 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] += 1.0e-27f; } } - } - } - if (!_soloed && (mute_gain != dmg) && !mute_declick_applied && _mute_affects_control_outs) { - Amp::run (bufs, nframes, mute_gain, dmg, false); - mute_gain = dmg; - mute_declick_applied = true; + } } - /* ---------------------------------------------------------------------------------------------------- - CONTROL OUTPUT STAGE - -------------------------------------------------------------------------------------------------- */ + /* ------------------------------------------------------------------------------------------- + and go .... + ----------------------------------------------------------------------------------------- */ - if ((_meter_point == MeterPostFader) && co) { - - solo_audible = solo_gain > 0; - mute_audible = dmg > 0 || !_mute_affects_control_outs; + Glib::RWLock::ReaderLock rm (_processor_lock, Glib::TRY_LOCK); - if ( // silent anyway + if (rm.locked()) { + for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) { + if (bufs.count() != (*i)->input_streams()) { + cerr << _name << " bufs = " << bufs.count() + << " input for " << (*i)->name() << " = " << (*i)->input_streams() + << endl; + } + assert (bufs.count() == (*i)->input_streams()); + (*i)->run (bufs, start_frame, end_frame, nframes); + bufs.set_count (ChanCount::max(bufs.count(), (*i)->output_streams())); + } - (_gain == 0 && !apply_gain_automation) || - - // muted by solo of another track + if (!_processors.empty()) { + bufs.set_count (ChanCount::max (bufs.count(), _processors.back()->output_streams())); + } + } +} - !solo_audible || - - // muted by mute of this track +ChanCount +Route::n_process_buffers () +{ + return max (_input->n_ports(), processor_max_streams); +} - !mute_audible || +void +Route::passthru (sframes_t start_frame, sframes_t end_frame, nframes_t nframes, int declick) +{ + BufferSet& bufs = _session.get_scratch_buffers (n_process_buffers()); - // recording but not s/w monitoring - - (no_monitor && record_enabled() && (!Config->get_auto_input() || _session.actively_recording())) + _silent = false; - ) { - - co->silence (nframes, offset); - - } else { + assert (bufs.available() >= _input->n_ports()); - co->deliver_output (bufs, start_frame, end_frame, nframes, offset); - } - } - - /* ---------------------------------------------------------------------- - GLOBAL MUTE - ----------------------------------------------------------------------*/ - - if (!_soloed && (mute_gain != dmg) && !mute_declick_applied && _mute_affects_main_outs) { - Amp::run (bufs, nframes, mute_gain, dmg, false); - mute_gain = dmg; - mute_declick_applied = true; - } - - /* ---------------------------------------------------------------------------------------------------- - MAIN OUTPUT STAGE - -------------------------------------------------------------------------------------------------- */ - - solo_audible = dsg > 0; - mute_audible = dmg > 0 || !_mute_affects_main_outs; - - if (n_outputs().get(_default_type) == 0) { - - /* relax */ - - } else if (no_monitor && record_enabled() && (!Config->get_auto_input() || _session.actively_recording())) { - - IO::silence (nframes, offset); - - } else { + if (_input->n_ports() == ChanCount::ZERO) { + silence (nframes); + } - if ( // silent anyway + bufs.set_count (_input->n_ports()); - (_gain == 0 && !apply_gain_automation) || - - // muted by solo of another track, but not using control outs for solo + if (is_control() && _session.listening()) { - (!solo_audible && (Config->get_solo_model() != SoloBus)) || - - // muted by mute of this track + /* control/monitor bus ignores input ports when something is + feeding the listen "stream". data will "arrive" into the + route from the intreturn processor element. + */ - !mute_audible + bufs.silence (nframes, 0); - ) { + } else { - /* don't use Route::silence() here, because that causes - all outputs (sends, port inserts, etc. to be silent). - */ - - if (_meter_point == MeterPostFader) { - peak_meter().reset(); - } + for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) { - IO::silence (nframes, offset); - - } else { - - deliver_output(bufs, start_frame, end_frame, nframes, offset); + BufferSet::iterator o = bufs.begin(*t); + PortSet& ports (_input->ports()); + for (PortSet::iterator i = ports.begin(*t); i != ports.end(*t); ++i, ++o) { + o->read_from (i->get_buffer(nframes), nframes); + } } - } - /* ---------------------------------------------------------------------------------------------------- - POST-FADER METERING - -------------------------------------------------------------------------------------------------- */ - - if (meter && (_meter_point == MeterPostFader)) { - if ((_gain == 0 && !apply_gain_automation) || dmg == 0) { - _meter->reset(); - } else { - _meter->run(output_buffers(), nframes, offset); - } - } + write_out_of_band_data (bufs, start_frame, end_frame, nframes); + process_output_buffers (bufs, start_frame, end_frame, nframes, true, declick); } -ChanCount -Route::n_process_buffers () +void +Route::passthru_silence (sframes_t start_frame, sframes_t end_frame, nframes_t nframes, int declick) { - return max (n_inputs(), redirect_max_outs); + BufferSet& bufs (_session.get_silent_buffers (n_process_buffers())); + bufs.set_count (_input->n_ports()); + write_out_of_band_data (bufs, start_frame, end_frame, nframes); + process_output_buffers (bufs, start_frame, end_frame, nframes, true, declick); } void -Route::passthru (nframes_t start_frame, nframes_t end_frame, nframes_t nframes, nframes_t offset, int declick, bool meter_first) +Route::set_listen (bool yn, void* src) { - BufferSet& bufs = _session.get_scratch_buffers(n_process_buffers()); - - _silent = false; - - collect_input (bufs, nframes, offset); + if (_control_outs) { + if (yn != _control_outs->active()) { + if (yn) { + _control_outs->activate (); + } else { + _control_outs->deactivate (); + } - if (meter_first) { - _meter->run(bufs, nframes); - meter_first = false; + listen_changed (src); /* EMIT SIGNAL */ + } } - - process_output_buffers (bufs, start_frame, end_frame, nframes, offset, true, declick, meter_first); } -void -Route::passthru_silence (nframes_t start_frame, nframes_t end_frame, nframes_t nframes, nframes_t offset, int declick, bool meter) +bool +Route::listening () const { - process_output_buffers (_session.get_silent_buffers (n_process_buffers()), start_frame, end_frame, nframes, offset, true, declick, meter); + if (_control_outs) { + return _control_outs->active (); + } else { + return false; + } } void Route::set_solo (bool yn, void *src) { - if (_solo_safe) { + if (_solo_safe || _solo_isolated) { return; } - if (_mix_group && src != _mix_group && _mix_group->is_active()) { - _mix_group->apply (&Route::set_solo, yn, _mix_group); + if (_route_group && src != _route_group && _route_group->active_property (RouteGroup::Solo)) { + _route_group->apply (&Route::set_solo, yn, _route_group); return; } - if (_soloed != yn) { - _soloed = yn; + if (soloed() != yn) { + mod_solo_level (yn ? 1 : -1); solo_changed (src); /* EMIT SIGNAL */ - _solo_control.Changed (); /* EMIT SIGNAL */ + _solo_control->Changed (); /* EMIT SIGNAL */ } } void -Route::set_solo_mute (bool yn) +Route::mod_solo_level (int32_t delta) { - Glib::Mutex::Lock lm (declick_lock); + if (delta < 0) { + if (_solo_level >= (uint32_t) delta) { + _solo_level += delta; + } else { + _solo_level = 0; + } + } else { + _solo_level += delta; + } - /* Called by Session in response to another Route being soloed. + /* tell main outs what the solo situation is */ - - desired_solo_gain = (yn?0.0:1.0); + + _main_outs->set_solo_level (_solo_level); + _main_outs->set_solo_isolated (_solo_isolated); } void -Route::set_solo_safe (bool yn, void *src) +Route::set_solo_isolated (bool yn, void *src) { - if (_solo_safe != yn) { - _solo_safe = yn; - solo_safe_changed (src); /* EMIT SIGNAL */ + if (_route_group && src != _route_group && _route_group->active_property (RouteGroup::Solo)) { + _route_group->apply (&Route::set_solo_isolated, yn, _route_group); + return; + } + + if (yn != _solo_isolated) { + _solo_isolated = yn; + + /* tell main outs what the solo situation is + */ + + _main_outs->set_solo_level (_solo_level); + _main_outs->set_solo_isolated (_solo_isolated); + + solo_isolated_changed (src); } } +bool +Route::solo_isolated () const +{ + return _solo_isolated; +} + void Route::set_mute (bool yn, void *src) - { - if (_mix_group && src != _mix_group && _mix_group->is_active()) { - _mix_group->apply (&Route::set_mute, yn, _mix_group); + if (_route_group && src != _route_group && _route_group->active_property (RouteGroup::Mute)) { + _route_group->apply (&Route::set_mute, yn, _route_group); return; } - if (_muted != yn) { - _muted = yn; - mute_changed (src); /* EMIT SIGNAL */ - - _mute_control.Changed (); /* EMIT SIGNAL */ - - Glib::Mutex::Lock lm (declick_lock); - desired_mute_gain = (yn?0.0f:1.0f); + if (muted() != yn) { + _mute_master->mute (yn); + mute_changed (src); + } +} + +bool +Route::muted() const +{ + return _mute_master->muted (); +} + +#if 0 +static void +dump_processors(const string& name, const list >& procs) +{ + cerr << name << " {" << endl; + for (list >::const_iterator p = procs.begin(); + p != procs.end(); ++p) { + cerr << "\t" << (*p)->name() << " ID = " << (*p)->id() << endl; } + cerr << "}" << endl; } +#endif int -Route::add_redirect (boost::shared_ptr redirect, void *src, InsertStreams* err) +Route::add_processor (boost::shared_ptr processor, Placement placement, ProcessorStreams* err) { - ChanCount old_rmo = redirect_max_outs; + ProcessorList::iterator loc; - if (!_session.engine().connected()) { + /* XXX this is not thread safe - we don't hold the lock across determining the iter + to add before and actually doing the insertion. dammit. + */ + + if (placement == PreFader) { + /* generic pre-fader: insert immediately before the amp */ + loc = find (_processors.begin(), _processors.end(), _amp); + } else { + /* generic post-fader: insert right before the main outs */ + loc = find (_processors.begin(), _processors.end(), _main_outs); + } + + return add_processor (processor, loc, err); +} + + +/** Add a processor to the route. + * If @a iter is not NULL, it must point to an iterator in _processors and the new + * processor will be inserted immediately before this location. Otherwise, + * @a position is used. + */ +int +Route::add_processor (boost::shared_ptr processor, ProcessorList::iterator iter, ProcessorStreams* err) +{ + ChanCount old_pms = processor_max_streams; + + if (!_session.engine().connected() || !processor) { return 1; } { - Glib::RWLock::WriterLock lm (redirect_lock); + Glib::RWLock::WriterLock lm (_processor_lock); boost::shared_ptr pi; boost::shared_ptr porti; - redirect->set_default_type(_default_type); + ProcessorList::iterator loc = find(_processors.begin(), _processors.end(), processor); - if ((pi = boost::dynamic_pointer_cast(redirect)) != 0) { - pi->set_count (1); + if (processor == _amp || processor == _meter || processor == _main_outs) { + // Ensure only one of these are in the list at any time + if (loc != _processors.end()) { + if (iter == loc) { // Already in place, do nothing + return 0; + } else { // New position given, relocate + _processors.erase (loc); + } + } - if (pi->natural_input_streams() == ChanCount::ZERO) { - /* generator plugin */ - _have_internal_generator = true; + } else { + if (loc != _processors.end()) { + cerr << "ERROR: Processor added to route twice!" << endl; + return 1; } - - } 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. + loc = iter; + } - the "inputs" for the port are supposed to match the output - of this route. + _processors.insert (loc, processor); - 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? - */ - // FIXME: (yes, they should) + // Set up processor list channels. This will set processor->[input|output]_streams(), + // configure redirect ports properly, etc. - porti->ensure_io (n_outputs (), n_inputs(), false, this); + if (configure_processors_unlocked (err)) { + ProcessorList::iterator ploc = loc; + --ploc; + _processors.erase(ploc); + configure_processors_unlocked (0); // it worked before we tried to add it ... + cerr << "configure failed\n"; + return -1; } - - _redirects.push_back (redirect); - // Set up redirect list channels. This will set redirect->[input|output]_streams() - if (_reset_plugin_counts (err)) { - _redirects.pop_back (); - _reset_plugin_counts (0); // it worked before we tried to add it ... - return -1; + if ((pi = boost::dynamic_pointer_cast(processor)) != 0) { + + if (pi->natural_input_streams() == ChanCount::ZERO) { + /* generator plugin */ + _have_internal_generator = true; + } + } - // Ensure peak vector sizes before the plugin is activated - ChanCount potential_max_streams = max(redirect->input_streams(), redirect->output_streams()); - _meter->setup(potential_max_streams); + if (_control_outs != processor) { + // XXX: do we want to emit the signal here ? change call order. + processor->activate (); + } + processor->ActiveChanged.connect (bind (mem_fun (_session, &Session::update_latency_compensation), false, false)); - redirect->activate (); - redirect->active_changed.connect (mem_fun (*this, &Route::redirect_active_proxy)); - } - - if (redirect_max_outs != old_rmo || old_rmo == ChanCount::ZERO) { - reset_panner (); + _output->set_user_latency (0); } + processors_changed (); /* EMIT SIGNAL */ - redirects_changed (src); /* EMIT SIGNAL */ return 0; } -int -Route::add_redirects (const RedirectList& others, void *src, InsertStreams* err) +bool +Route::add_processor_from_xml (const XMLNode& node, ProcessorList::iterator iter) { - ChanCount old_rmo = redirect_max_outs; + const XMLProperty *prop; - if (!_session.engine().connected()) { - return 1; + if (node.name() != "Processor") { + return false; } - { - Glib::RWLock::WriterLock lm (redirect_lock); + try { + if ((prop = node.property ("type")) != 0) { - RedirectList::iterator existing_end = _redirects.end(); - --existing_end; + boost::shared_ptr processor; - ChanCount potential_max_streams; + if (prop->value() == "ladspa" || prop->value() == "Ladspa" || + prop->value() == "lv2" || + prop->value() == "vst" || + prop->value() == "audiounit") { - for (RedirectList::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); - - ChanCount m = max(pi->input_streams(), pi->output_streams()); - if (m > potential_max_streams) - potential_max_streams = m; - } + processor.reset (new PluginInsert(_session, node)); - // Ensure peak vector sizes before the plugin is activated - _meter->setup(potential_max_streams); + } else if (prop->value() == "port") { - _redirects.push_back (*i); - - if (_reset_plugin_counts (err)) { - ++existing_end; - _redirects.erase (existing_end, _redirects.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)); - } - } - - if (redirect_max_outs != old_rmo || old_rmo == ChanCount::ZERO) { - reset_panner (); - } + processor.reset (new PortInsert (_session, _mute_master, node)); - redirects_changed (src); /* EMIT SIGNAL */ - return 0; + } else if (prop->value() == "send") { + + processor.reset (new Send (_session, _mute_master, node)); + + } else if (prop->value() == "meter") { + + if (_meter) { + if (_meter->set_state (node, Stateful::loading_state_version)) { + return false; + } else { + return true; + } + } + + _meter.reset (new PeakMeter (_session, node)); + processor = _meter; + + } else if (prop->value() == "amp") { + + /* amp always exists */ + + processor = _amp; + if (processor->set_state (node, Stateful::loading_state_version)) { + return false; + } else { + /* never any reason to add it */ + return true; + } + + } else if (prop->value() == "intsend") { + + processor.reset (new InternalSend (_session, _mute_master, node)); + + } else if (prop->value() == "intreturn") { + + if (_intreturn) { + if (_intreturn->set_state (node, Stateful::loading_state_version)) { + return false; + } else { + return true; + } + } + _intreturn.reset (new InternalReturn (_session, node)); + processor = _intreturn; + + } else if (prop->value() == "main-outs") { + + if (_main_outs) { + if (_main_outs->set_state (node, Stateful::loading_state_version)) { + return false; + } else { + return true; + } + } + + _main_outs.reset (new Delivery (_session, _output, _mute_master, node)); + processor = _main_outs; + + } else { + error << string_compose(_("unknown Processor type \"%1\"; ignored"), prop->value()) << endmsg; + return false; + } + + if (iter == _processors.end() && processor->visible() && !_processors.empty()) { + /* check for invisible processors stacked at the end and leave them there */ + ProcessorList::iterator p; + p = _processors.end(); + --p; + while (!(*p)->visible() && p != _processors.begin()) { + --p; + } + ++p; + iter = p; + } + + return (add_processor (processor, iter) == 0); + + } else { + error << _("Processor XML node has no type property") << endmsg; + return false; + } + } + + catch (failed_constructor &err) { + warning << _("processor could not be created. Ignored.") << endmsg; + return false; + } } -/** Turn off all redirects with a given placement - * @param p Placement of redirects to disable - */ -void -Route::disable_redirects (Placement p) +bool +Route::add_processor_from_xml_2X (const XMLNode& node, int version, ProcessorList::iterator iter) { - Glib::RWLock::ReaderLock lm (redirect_lock); - - for (RedirectList::iterator i = _redirects.begin(); i != _redirects.end(); ++i) { - if ((*i)->placement() == p) { - (*i)->set_active (false, this); + const XMLProperty *prop; + + try { + boost::shared_ptr processor; + + if (node.name() == "Insert") { + + if ((prop = node.property ("type")) != 0) { + + if (prop->value() == "ladspa" || prop->value() == "Ladspa" || + prop->value() == "lv2" || + prop->value() == "vst" || + prop->value() == "audiounit") { + + processor.reset (new PluginInsert (_session, node)); + + } else { + + processor.reset (new PortInsert (_session, _mute_master, node)); + } + + } + + } else if (node.name() == "Send") { + + processor.reset (new Send (_session, _mute_master, node, version)); + + } else { + + error << string_compose(_("unknown Processor type \"%1\"; ignored"), node.name()) << endmsg; + return false; + } + + if (iter == _processors.end() && processor->visible() && !_processors.empty()) { + /* check for invisible processors stacked at the end and leave them there */ + ProcessorList::iterator p; + p = _processors.end(); + --p; + while (!(*p)->visible() && p != _processors.begin()) { + --p; + } + ++p; + iter = p; + } + + return (add_processor (processor, iter) == 0); + } + + catch (failed_constructor &err) { + warning << _("processor could not be created. Ignored.") << endmsg; + return false; + } +} + +int +Route::add_processors (const ProcessorList& others, boost::shared_ptr before, ProcessorStreams* err) +{ + ProcessorList::iterator loc; + + if (before) { + loc = find(_processors.begin(), _processors.end(), before); + } else { + /* nothing specified - at end but before main outs */ + loc = find (_processors.begin(), _processors.end(), _main_outs); + } + + return add_processors (others, loc, err); +} + +int +Route::add_processors (const ProcessorList& others, ProcessorList::iterator iter, ProcessorStreams* err) +{ + /* NOTE: this is intended to be used ONLY when copying + processors from another Route. Hence the subtle + differences between this and ::add_processor() + */ + + ChanCount old_pms = processor_max_streams; + + if (!_session.engine().connected()) { + return 1; + } + + if (others.empty()) { + return 0; + } + + { + Glib::RWLock::WriterLock lm (_processor_lock); + ProcessorList::iterator existing_end = _processors.end(); + --existing_end; + + ChanCount potential_max_streams = ChanCount::max (_input->n_ports(), _output->n_ports()); + + for (ProcessorList::const_iterator i = others.begin(); i != others.end(); ++i) { + + // Ensure meter only appears in the list once + if (*i == _meter) { + ProcessorList::iterator m = find(_processors.begin(), _processors.end(), *i); + if (m != _processors.end()) { + _processors.erase(m); + } + } + + boost::shared_ptr pi; + + if ((pi = boost::dynamic_pointer_cast(*i)) != 0) { + pi->set_count (1); + + ChanCount m = max (pi->input_streams(), pi->output_streams()); + + if (m > potential_max_streams) { + potential_max_streams = m; + } + } + + _processors.insert (iter, *i); + + if (configure_processors_unlocked (err)) { + ++existing_end; + _processors.erase (existing_end, _processors.end()); + configure_processors_unlocked (0); // it worked before we tried to add it ... + return -1; + } + + (*i)->ActiveChanged.connect (bind (mem_fun (_session, &Session::update_latency_compensation), false, false)); } + + _output->set_user_latency (0); } + + processors_changed (); /* EMIT SIGNAL */ + + return 0; } -/** Turn off all redirects +void +Route::placement_range(Placement p, ProcessorList::iterator& start, ProcessorList::iterator& end) +{ + if (p == PreFader) { + start = _processors.begin(); + end = find(_processors.begin(), _processors.end(), _amp); + } else { + start = find(_processors.begin(), _processors.end(), _amp); + ++start; + end = _processors.end(); + } +} + +/** 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); + ProcessorList::iterator start, end; + placement_range(p, start, end); + + for (ProcessorList::iterator i = start; i != end; ++i) { + (*i)->deactivate (); + } + + _session.set_dirty (); +} + +/** Turn off all redirects + */ void -Route::disable_redirects () +Route::disable_processors () { - Glib::RWLock::ReaderLock lm (redirect_lock); - - for (RedirectList::iterator i = _redirects.begin(); i != _redirects.end(); ++i) { - (*i)->set_active (false, this); + Glib::RWLock::ReaderLock lm (_processor_lock); + + for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) { + (*i)->deactivate (); } + + _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 (redirect_lock); - - for (RedirectList::iterator i = _redirects.begin(); i != _redirects.end(); ++i) { - if (boost::dynamic_pointer_cast (*i) && (*i)->placement() == p) { - (*i)->set_active (false, this); + Glib::RWLock::ReaderLock lm (_processor_lock); + + ProcessorList::iterator start, end; + placement_range(p, start, end); + + for (ProcessorList::iterator i = start; i != end; ++i) { + if (boost::dynamic_pointer_cast (*i)) { + (*i)->deactivate (); } } + + _session.set_dirty (); } /** Turn off all plugins */ - void Route::disable_plugins () { - Glib::RWLock::ReaderLock lm (redirect_lock); - - for (RedirectList::iterator i = _redirects.begin(); i != _redirects.end(); ++i) { + 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, this); + (*i)->deactivate (); } } + + _session.set_dirty (); } void Route::ab_plugins (bool forward) { - Glib::RWLock::ReaderLock lm (redirect_lock); - + 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 (RedirectList::iterator i = _redirects.begin(); i != _redirects.end(); ++i) { + for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) { if (!boost::dynamic_pointer_cast (*i)) { continue; } if ((*i)->active()) { - (*i)->set_active (false, this); + (*i)->deactivate (); (*i)->set_next_ab_is_active (true); } else { (*i)->set_next_ab_is_active (false); @@ -983,132 +1080,152 @@ Route::ab_plugins (bool forward) /* backward = if the redirect was marked to go active on the next ab, do so */ - for (RedirectList::iterator i = _redirects.begin(); i != _redirects.end(); ++i) { + 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, this); + (*i)->activate (); } else { - (*i)->set_active (false, this); + (*i)->deactivate (); } } } -} - - -/* Figure out the streams that will feed into PreFader */ -ChanCount -Route::pre_fader_streams() const -{ - boost::shared_ptr redirect; - // Find the last pre-fader redirect - for (RedirectList::const_iterator r = _redirects.begin(); r != _redirects.end(); ++r) { - if ((*r)->placement() == PreFader) { - redirect = *r; - } - } - - if (redirect) { - return redirect->output_streams(); - } else { - return n_inputs (); - } + _session.set_dirty (); } -/** Remove redirects with a given placement. - * @param p Placement of redirects to remove. +/** Remove processors with a given placement. + * @param p Placement of processors to remove. */ void -Route::clear_redirects (Placement p, void *src) +Route::clear_processors (Placement p) { - const ChanCount old_rmo = redirect_max_outs; + const ChanCount old_pms = processor_max_streams; if (!_session.engine().connected()) { return; } + bool already_deleting = _session.deletion_in_progress(); + if (!already_deleting) { + _session.set_deletion_in_progress(); + } + { - Glib::RWLock::WriterLock lm (redirect_lock); - RedirectList new_list; - - for (RedirectList::iterator i = _redirects.begin(); i != _redirects.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 */ + Glib::RWLock::WriterLock lm (_processor_lock); + ProcessorList new_list; + ProcessorStreams err; + bool seen_amp = false; + + for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) { + + if (*i == _amp) { + seen_amp = true; + } + + if ((*i) == _amp || (*i) == _meter || (*i) == _main_outs) { + + /* you can't remove these */ + new_list.push_back (*i); + + } else { + if (seen_amp) { + + switch (p) { + case PreFader: + new_list.push_back (*i); + break; + case PostFader: + (*i)->drop_references (); + break; + } + + } else { + + switch (p) { + case PreFader: + (*i)->drop_references (); + break; + case PostFader: + new_list.push_back (*i); + break; + } + } } } - - _redirects = new_list; - } - /* FIXME: can't see how this test can ever fire */ - if (redirect_max_outs != old_rmo) { - reset_panner (); + _processors = new_list; + configure_processors_unlocked (&err); // this can't fail } - - redirect_max_outs.reset(); + + processor_max_streams.reset(); _have_internal_generator = false; - redirects_changed (src); /* EMIT SIGNAL */ + processors_changed (); /* EMIT SIGNAL */ + + if (!already_deleting) { + _session.clear_deletion_in_progress(); + } } int -Route::remove_redirect (boost::shared_ptr redirect, void *src, InsertStreams* err) +Route::remove_processor (boost::shared_ptr processor, ProcessorStreams* err) { - ChanCount old_rmo = redirect_max_outs; + /* these can never be removed */ + + if (processor == _amp || processor == _meter || processor == _main_outs) { + return 0; + } + + ChanCount old_pms = processor_max_streams; if (!_session.engine().connected()) { return 1; } - redirect_max_outs.reset(); + processor_max_streams.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(); ) { + if (*i == processor) { - RedirectList::iterator tmp; - - /* move along, see failure case for reset_plugin_counts() - where we may need to reinsert the redirect. + /* move along, see failure case for configure_processors() + where we may need to reconfigure the processor. */ - tmp = i; - ++tmp; - /* stop redirects that send signals to JACK ports from causing noise as a result of no longer being run. */ - boost::shared_ptr send; - boost::shared_ptr port_insert; - - 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); - } + boost::shared_ptr iop; - _redirects.erase (i); + if ((iop = boost::dynamic_pointer_cast (*i)) != 0) { + if (iop->input()) { + iop->input()->disconnect (this); + } + if (iop->output()) { + iop->output()->disconnect (this); + } + } - i = tmp; + i = _processors.erase (i); removed = true; break; + + } else { + ++i; } + + _output->set_user_latency (0); } if (!removed) { @@ -1116,331 +1233,344 @@ Route::remove_redirect (boost::shared_ptr redirect, void *src, InsertS return 1; } - if (_reset_plugin_counts (err)) { + if (configure_processors_unlocked (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); + configure_processors_unlocked (0); return -1; } - bool foo = false; + _have_internal_generator = 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) { if (pi->is_generator()) { - foo = true; + _have_internal_generator = true; + break; } } } - - _have_internal_generator = foo; - } - - if (old_rmo != redirect_max_outs) { - reset_panner (); } - redirect->drop_references (); + processor->drop_references (); + processors_changed (); /* EMIT SIGNAL */ - redirects_changed (src); /* EMIT SIGNAL */ return 0; } int -Route::reset_plugin_counts (InsertStreams* err) +Route::remove_processors (const ProcessorList& to_be_deleted, ProcessorStreams* err) { - Glib::RWLock::WriterLock lm (redirect_lock); - return _reset_plugin_counts (err); -} + ProcessorList deleted; + ProcessorList as_we_were; + + if (!_session.engine().connected()) { + return 1; + } + processor_max_streams.reset(); -int -Route::_reset_plugin_counts (InsertStreams* err) -{ - RedirectList::iterator r; - map > insert_map; - ChanCount initial_streams; + { + Glib::RWLock::WriterLock lm (_processor_lock); + ProcessorList::iterator i; + boost::shared_ptr processor; - /* Process each placement in order, checking to see if we - can really do what has been requested. - */ - - /* 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 - */ + as_we_were = _processors; - /* ... but it should/will be... */ - - for (r = _redirects.begin(); r != _redirects.end(); ++r) { + for (i = _processors.begin(); i != _processors.end(); ) { - boost::shared_ptr insert; + processor = *i; - if ((insert = boost::dynamic_pointer_cast(*r)) != 0) { - insert_map[insert->placement()].push_back (InsertCount (insert)); - } - } - + /* these can never be removed */ - /* A: PreFader */ - - if ( ! check_some_plugin_counts (insert_map[PreFader], n_inputs (), err)) { - return -1; - } + if (processor == _amp || processor == _meter || processor == _main_outs) { + ++i; + continue; + } - ChanCount post_fader_input = (err ? err->count : n_inputs()); + /* see if its in the list of processors to delete */ - /* B: PostFader */ + if (find (to_be_deleted.begin(), to_be_deleted.end(), processor) == to_be_deleted.end()) { + ++i; + continue; + } - if ( ! check_some_plugin_counts (insert_map[PostFader], post_fader_input, err)) { - return -1; - } + /* stop IOProcessors that send to JACK ports + from causing noise as a result of no longer being + run. + */ - /* OK, everything can be set up correctly, so lets do it */ + boost::shared_ptr iop; - apply_some_plugin_counts (insert_map[PreFader]); - apply_some_plugin_counts (insert_map[PostFader]); + if ((iop = boost::dynamic_pointer_cast (processor)) != 0) { + iop->disconnect (); + } - /* recompute max outs of any redirect */ + deleted.push_back (processor); + i = _processors.erase (i); + } - redirect_max_outs.reset(); - RedirectList::iterator prev = _redirects.end(); + if (deleted.empty()) { + /* none of those in the requested list were found */ + return 0; + } - for (r = _redirects.begin(); r != _redirects.end(); prev = r, ++r) { - boost::shared_ptr s; + _output->set_user_latency (0); - if ((s = boost::dynamic_pointer_cast (*r)) != 0) { - if (r == _redirects.begin()) { - s->expect_inputs (n_inputs()); - } else { - s->expect_inputs ((*prev)->output_streams()); - } + if (configure_processors_unlocked (err)) { + /* get back to where we where */ + _processors = as_we_were; + /* we know this will work, because it worked before :) */ + configure_processors_unlocked (0); + return -1; + } - } else { - - /* don't pay any attention to send output configuration, since it doesn't - affect the route. - */ + _have_internal_generator = false; - redirect_max_outs = max ((*r)->output_streams (), redirect_max_outs); - + for (i = _processors.begin(); i != _processors.end(); ++i) { + boost::shared_ptr pi; + + if ((pi = boost::dynamic_pointer_cast(*i)) != 0) { + if (pi->is_generator()) { + _have_internal_generator = true; + break; + } + } } } - /* we're done */ + /* now try to do what we need to so that those that were removed will be deleted */ + + for (ProcessorList::iterator i = deleted.begin(); i != deleted.end(); ++i) { + (*i)->drop_references (); + } + + processors_changed (); /* EMIT SIGNAL */ return 0; -} +} -int32_t -Route::apply_some_plugin_counts (list& iclist) -{ - list::iterator i; - for (i = iclist.begin(); i != iclist.end(); ++i) { - - if ((*i).insert->configure_io ((*i).in, (*i).out)) { - return -1; - } - /* make sure that however many we have, they are all active */ - (*i).insert->activate (); +int +Route::configure_processors (ProcessorStreams* err) +{ + if (!_in_configure_processors) { + Glib::RWLock::WriterLock lm (_processor_lock); + return configure_processors_unlocked (err); } - return 0; } -/** Returns whether \a iclist can be configured and run starting with - * \a required_inputs at the first insert'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. +/** Configure the input/output configuration of each processor in the processors list. + * Return 0 on success, otherwise configuration is impossible. */ -bool -Route::check_some_plugin_counts (list& iclist, ChanCount required_inputs, InsertStreams* err) +int +Route::configure_processors_unlocked (ProcessorStreams* err) { - list::iterator i; - size_t index = 0; - - if (err) { - err->index = 0; - err->count = required_inputs; + if (_in_configure_processors) { + return 0; } - for (i = iclist.begin(); i != iclist.end(); ++i) { + _in_configure_processors = true; + + // Check each processor in order to see if we can configure as requested + ChanCount in = _input->n_ports (); + ChanCount out; + list< pair > configuration; + uint32_t index = 0; - if ((*i).insert->can_support_input_configuration (required_inputs) < 0) { + for (ProcessorList::iterator p = _processors.begin(); p != _processors.end(); ++p, ++index) { + + if ((*p)->can_support_io_configuration(in, out)) { + configuration.push_back(make_pair(in, out)); + in = out; + } else { if (err) { err->index = index; - err->count = required_inputs; + err->count = in; } - return false; + _in_configure_processors = false; + return -1; } - - (*i).in = required_inputs; - (*i).out = (*i).insert->output_for_input_configuration (required_inputs); + } - required_inputs = (*i).out; - - ++index; + // We can, so configure everything + list< pair >::iterator c = configuration.begin(); + for (ProcessorList::iterator p = _processors.begin(); p != _processors.end(); ++p, ++c) { + (*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); + out = c->second; } - - if (err) { - if (!iclist.empty()) { - err->index = index; - err->count = iclist.back().insert->output_for_input_configuration(required_inputs); - } + + // Ensure route outputs match last processor's outputs + if (out != _output->n_ports ()) { + _output->ensure_io (out, false, this); } - return true; + _in_configure_processors = false; + return 0; } -int -Route::copy_redirects (const Route& other, Placement placement, InsertStreams* err) +void +Route::all_processors_flip () { - ChanCount old_rmo = redirect_max_outs; + Glib::RWLock::ReaderLock lm (_processor_lock); - RedirectList to_be_deleted; + if (_processors.empty()) { + return; + } - { - Glib::RWLock::WriterLock lm (redirect_lock); - RedirectList::iterator tmp; - RedirectList the_copy; + bool first_is_on = _processors.front()->active(); + + for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) { + if (first_is_on) { + (*i)->deactivate (); + } else { + (*i)->activate (); + } + } - the_copy = _redirects; - - /* remove all relevant redirects */ + _session.set_dirty (); +} - for (RedirectList::iterator i = _redirects.begin(); i != _redirects.end(); ) { - tmp = i; - ++tmp; +/** 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_processors_active (Placement p, bool state) +{ + Glib::RWLock::ReaderLock lm (_processor_lock); - if ((*i)->placement() == placement) { - to_be_deleted.push_back (*i); - _redirects.erase (i); - } + if (_processors.empty()) { + return; + } + ProcessorList::iterator start, end; + placement_range(p, start, end); - i = tmp; + bool before_amp = true; + for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) { + if ((*i) == _amp) { + before_amp = false; + continue; } - - /* now copy the relevant ones from "other" */ - - for (RedirectList::const_iterator i = other._redirects.begin(); i != other._redirects.end(); ++i) { - if ((*i)->placement() == placement) { - _redirects.push_back (Redirect::clone (*i)); + if (p == PreFader && before_amp) { + if (state) { + (*i)->activate (); + } else { + (*i)->deactivate (); } } + } - /* reset plugin stream handling */ + _session.set_dirty (); +} - if (_reset_plugin_counts (err)) { +bool +Route::processor_is_prefader (boost::shared_ptr p) +{ + bool pre_fader = true; + Glib::RWLock::ReaderLock lm (_processor_lock); - /* FAILED COPY ATTEMPT: we have to restore order */ + for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) { - /* delete all cloned redirects */ + /* semantic note: if p == amp, we want to return true, so test + for equality before checking if this is the amp + */ - for (RedirectList::iterator i = _redirects.begin(); i != _redirects.end(); ) { + if ((*i) == p) { + break; + } - tmp = i; - ++tmp; + if ((*i) == _amp) { + pre_fader = false; + break; + } + } - if ((*i)->placement() == placement) { - _redirects.erase (i); - } - - i = tmp; - } + return pre_fader; +} - /* restore the natural order */ +int +Route::reorder_processors (const ProcessorList& new_order, ProcessorStreams* err) +{ + /* "new_order" is an ordered list of processors to be positioned according to "placement". + NOTE: all processors in "new_order" MUST be marked as visible. 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. + */ - _redirects = the_copy; - redirect_max_outs = old_rmo; + { + Glib::RWLock::WriterLock lm (_processor_lock); + ChanCount old_pms = processor_max_streams; + ProcessorList::iterator oiter; + ProcessorList::const_iterator niter; + ProcessorList as_it_was_before = _processors; + ProcessorList as_it_will_be; - /* we failed, even though things are OK again */ + oiter = _processors.begin(); + niter = new_order.begin(); - return -1; + while (niter != new_order.end()) { - } else { - - /* SUCCESSFUL COPY ATTEMPT: delete the redirects we removed pre-copy */ - to_be_deleted.clear (); - } - } + /* 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 (redirect_max_outs != old_rmo || old_rmo == ChanCount::ZERO) { - reset_panner (); - } + 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. + */ - redirects_changed (this); /* EMIT SIGNAL */ - return 0; -} + if (oiter == _processors.end()) { -void -Route::all_redirects_flip () -{ - Glib::RWLock::ReaderLock lm (redirect_lock); + /* no more elements in the old list, so just stick the rest of + the new order onto the temp list. + */ - if (_redirects.empty()) { - return; - } + as_it_will_be.insert (as_it_will_be.end(), niter, new_order.end()); + while (niter != new_order.end()) { + ++niter; + } + break; - bool first_is_on = _redirects.front()->active(); - - for (RedirectList::iterator i = _redirects.begin(); i != _redirects.end(); ++i) { - (*i)->set_active (!first_is_on, this); - } -} + } else { -/** Set all redirects with a given placement to a given active state. - * @param p Placement of redirects to change. - * @param state New active state for those redirects. - */ -void -Route::all_redirects_active (Placement p, bool state) -{ - Glib::RWLock::ReaderLock lm (redirect_lock); + if (!(*oiter)->visible()) { - if (_redirects.empty()) { - return; - } + as_it_will_be.push_back (*oiter); - for (RedirectList::iterator i = _redirects.begin(); i != _redirects.end(); ++i) { - if ((*i)->placement() == p) { - (*i)->set_active (state, this); - } - } -} + } else { -struct RedirectSorter { - bool operator() (boost::shared_ptr a, boost::shared_ptr b) { - return a->sort_key() < b->sort_key(); - } -}; + /* visible processor: check that its in the new order */ -int -Route::sort_redirects (InsertStreams* err) -{ - { - RedirectSorter comparator; - Glib::RWLock::WriterLock lm (redirect_lock); - ChanCount old_rmo = redirect_max_outs; + 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; + } + } - /* the sweet power of C++ ... */ + /* now remove from old order - its taken care of no matter what */ + oiter = _processors.erase (oiter); + } - RedirectList as_it_was_before = _redirects; + } + + _processors.insert (oiter, as_it_will_be.begin(), as_it_will_be.end()); - _redirects.sort (comparator); - - if (_reset_plugin_counts (err)) { - _redirects = as_it_was_before; - redirect_max_outs = old_rmo; + if (configure_processors_unlocked (err)) { + _processors = as_it_was_before; + processor_max_streams = old_pms; return -1; - } - } + } + } - reset_panner (); - redirects_changed (this); /* EMIT SIGNAL */ + processors_changed (); /* EMIT SIGNAL */ return 0; } @@ -1461,41 +1591,36 @@ XMLNode& Route::state(bool full_state) { XMLNode *node = new XMLNode("Route"); - RedirectList:: iterator i; + ProcessorList::iterator i; char buf[32]; + id().print (buf, sizeof (buf)); + node->add_property("id", buf); + node->add_property ("name", _name); + node->add_property("default-type", _default_type.to_string()); + if (_flags) { node->add_property("flags", enum_2_string (_flags)); } - - node->add_property("default-type", _default_type.to_string()); node->add_property("active", _active?"yes":"no"); - 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"); - node->add_property("mute-affects-main-outs", _mute_affects_main_outs?"yes":"no"); + node->add_property("meter-point", enum_2_string (_meter_point)); - if (_edit_group) { - node->add_property("edit-group", _edit_group->name()); - } - if (_mix_group) { - node->add_property("mix-group", _mix_group->name()); + if (_route_group) { + node->add_property("route-group", _route_group->name()); } string order_string; - OrderKeys::iterator x = order_keys.begin(); + OrderKeys::iterator x = order_keys.begin(); while (x != order_keys.end()) { order_string += string ((*x).first); order_string += '='; snprintf (buf, sizeof(buf), "%ld", (*x).second); order_string += buf; - + ++x; if (x == order_keys.end()) { @@ -1506,186 +1631,211 @@ 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 (_input->state (full_state)); + node->add_child_nocopy (_output->state (full_state)); + node->add_child_nocopy (_solo_control->get_state ()); + node->add_child_nocopy (_mute_master->get_state ()); - XMLNode* remote_control_node = new XMLNode (X_("remote_control")); + XMLNode* remote_control_node = new XMLNode (X_("RemoteControl")); snprintf (buf, sizeof (buf), "%d", _remote_control_id); remote_control_node->add_property (X_("id"), buf); node->add_child_nocopy (*remote_control_node); - if (_control_outs) { - XMLNode* cnode = new XMLNode (X_("ControlOuts")); - cnode->add_child_nocopy (_control_outs->state (full_state)); - node->add_child_nocopy (*cnode); - } - if (_comment.length()) { XMLNode *cmt = node->add_child ("Comment"); cmt->add_content (_comment); } - for (i = _redirects.begin(); i != _redirects.end(); ++i) { + for (i = _processors.begin(); i != _processors.end(); ++i) { node->add_child_nocopy((*i)->state (full_state)); } if (_extra_xml){ node->add_child_copy (*_extra_xml); } - + return *node; } -XMLNode& -Route::get_redirect_state () +int +Route::set_state (const XMLNode& node, int version) { - XMLNode* root = new XMLNode (X_("redirects")); - for (RedirectList::iterator i = _redirects.begin(); i != _redirects.end(); ++i) { - root->add_child_nocopy ((*i)->state (true)); - } - - return *root; + return _set_state (node, version, true); } int -Route::set_redirect_state (const XMLNode& root) +Route::_set_state (const XMLNode& node, int version, bool /*call_base*/) { - if (root.name() != X_("redirects")) { - return -1; + if (version < 3000) { + return _set_state_2X (node, version); } XMLNodeList nlist; - XMLNodeList nnlist; - XMLNodeConstIterator iter; XMLNodeConstIterator niter; - Glib::RWLock::ReaderLock lm (redirect_lock); + XMLNode *child; + XMLPropertyList plist; + const XMLProperty *prop; - nlist = root.children(); - - for (iter = nlist.begin(); iter != nlist.end(); ++iter){ + if (node.name() != "Route"){ + error << string_compose(_("Bad node sent to Route::set_state() [%1]"), node.name()) << endmsg; + return -1; + } + + if ((prop = node.property (X_("name"))) != 0) { + Route::set_name (prop->value()); + } + + if ((prop = node.property ("id")) != 0) { + _id = prop->value (); + } + + if ((prop = node.property (X_("flags"))) != 0) { + _flags = Flag (string_2_enum (prop->value(), _flags)); + } else { + _flags = Flag (0); + } + + /* add all processors (except amp, which is always present) */ + + nlist = node.children(); + XMLNode processor_state (X_("processor_state")); + + for (niter = nlist.begin(); niter != nlist.end(); ++niter){ + + child = *niter; + + if (child->name() == IO::state_node_name) { + if ((prop = child->property (X_("direction"))) == 0) { + continue; + } + + if (prop->value() == "Input") { + _input->set_state (*child, version); + } else if (prop->value() == "Output") { + _output->set_state (*child, version); + } + } - /* iter now points to a Redirect state node */ - - nnlist = (*iter)->children (); + if (child->name() == X_("Processor")) { + processor_state.add_child_copy (*child); + } + } + + set_processor_state (processor_state); + + if ((prop = node.property ("solo_level")) != 0) { + _solo_level = 0; // needed for mod_solo_level() to work + mod_solo_level (atoi (prop->value())); + } + + if ((prop = node.property ("solo-isolated")) != 0) { + set_solo_isolated (string_is_affirmative (prop->value()), this); + } + + if ((prop = node.property (X_("phase-invert"))) != 0) { + set_phase_invert (string_is_affirmative (prop->value())); + } + + if ((prop = node.property (X_("denormal-protection"))) != 0) { + set_denormal_protection (string_is_affirmative (prop->value())); + } + + if ((prop = node.property (X_("active"))) != 0) { + bool yn = string_is_affirmative (prop->value()); + _active = !yn; // force switch + set_active (yn); + } + + if ((prop = node.property (X_("soloed"))) != 0) { + bool yn = string_is_affirmative (prop->value()); + + /* XXX force reset of solo status */ + + set_solo (yn, this); + } + + if ((prop = node.property (X_("meter-point"))) != 0) { + _meter_point = MeterPoint (string_2_enum (prop->value (), _meter_point)); + } + + if ((prop = node.property (X_("route-group"))) != 0) { + RouteGroup* route_group = _session.route_group_by_name(prop->value()); + if (route_group == 0) { + error << string_compose(_("Route %1: unknown route group \"%2 in saved state (ignored)"), _name, prop->value()) << endmsg; + } else { + set_route_group (route_group, this); + } + } - for (niter = nnlist.begin(); niter != nnlist.end(); ++niter) { + if ((prop = node.property (X_("order-keys"))) != 0) { - /* find the IO child node, since it contains the ID we need */ + long n; - /* XXX OOP encapsulation violation, ugh */ + string::size_type colon, equal; + string remaining = prop->value(); - if ((*niter)->name() == IO::state_node_name) { + while (remaining.length()) { - XMLProperty* prop = (*niter)->property (X_("id")); - - if (!prop) { - warning << _("Redirect node has no ID, ignored") << endmsg; - break; + if ((equal = remaining.find_first_of ('=')) == string::npos || equal == remaining.length()) { + error << string_compose (_("badly formed order key string in state file! [%1] ... ignored."), remaining) + << endmsg; + } else { + if (sscanf (remaining.substr (equal+1).c_str(), "%ld", &n) != 1) { + error << string_compose (_("badly formed order key string in state file! [%1] ... ignored."), remaining) + << endmsg; + } else { + set_order_key (remaining.substr (0, equal), n); } + } - ID id = prop->value (); + colon = remaining.find_first_of (':'); - /* now look for a redirect with that ID */ - - for (RedirectList::iterator i = _redirects.begin(); i != _redirects.end(); ++i) { - if ((*i)->id() == id) { - (*i)->set_state (**iter); - break; - } - } - + if (colon != string::npos) { + remaining = remaining.substr (colon+1); + } else { break; - } } - - } - - return 0; -} - -void -Route::set_deferred_state () -{ - XMLNodeList nlist; - XMLNodeConstIterator niter; - - if (!deferred_state) { - return; } - nlist = deferred_state->children(); - for (niter = nlist.begin(); niter != nlist.end(); ++niter){ - add_redirect_from_xml (**niter); - } - - delete deferred_state; - deferred_state = 0; -} - -void -Route::add_redirect_from_xml (const XMLNode& node) -{ - const XMLProperty *prop; - - if (node.name() == "Send") { - - - try { - boost::shared_ptr send (new Send (_session, node)); - add_redirect (send, this); - } - - catch (failed_constructor &err) { - error << _("Send construction failed") << endmsg; - return; - } - - } else if (node.name() == "Insert") { - - try { - if ((prop = node.property ("type")) != 0) { + child = *niter; - boost::shared_ptr insert; + if (child->name() == X_("Comment")) { - if (prop->value() == "ladspa" || prop->value() == "Ladspa" || prop->value() == "vst") { + /* XXX this is a terrible API design in libxml++ */ - insert.reset (new PluginInsert(_session, node)); - - } else if (prop->value() == "port") { + XMLNode *cmt = *(child->children().begin()); + _comment = cmt->content(); + } else if (child->name() == X_("Extra")) { - insert.reset (new PortInsert (_session, node)); + _extra_xml = new XMLNode (*child); - } else { + } else if (child->name() == X_("Controllable") && (prop = child->property("name")) != 0) { - error << string_compose(_("unknown Insert type \"%1\"; ignored"), prop->value()) << endmsg; - } + if (prop->value() == "solo") { + _solo_control->set_state (*child, version); + _session.add_controllable (_solo_control); + } - add_redirect (insert, this); - - } else { - error << _("Insert XML node has no type property") << endmsg; + } else if (child->name() == X_("RemoteControl")) { + if ((prop = child->property (X_("id"))) != 0) { + int32_t x; + sscanf (prop->value().c_str(), "%d", &x); + set_remote_control_id (x); } - } - - catch (failed_constructor &err) { - warning << _("insert could not be created. Ignored.") << endmsg; - return; + + } else if (child->name() == X_("MuteMaster")) { + _mute_master->set_state (*child, version); } } -} -int -Route::set_state (const XMLNode& node) -{ - return _set_state (node, true); + return 0; } int -Route::_set_state (const XMLNode& node, bool call_base) +Route::_set_state_2X (const XMLNode& node, int version) { XMLNodeList nlist; XMLNodeConstIterator niter; @@ -1693,7 +1843,18 @@ Route::_set_state (const XMLNode& node, bool call_base) XMLPropertyList plist; const XMLProperty *prop; - if (node.name() != "Route"){ + /* 2X things which still remain to be handled: + * default-type + * muted + * mute-affects-pre-fader + * mute-affects-post-fader + * mute-affects-control-outs + * mute-affects-main-outs + * automation + * controlouts + */ + + if (node.name() != "Route") { error << string_compose(_("Bad node sent to Route::set_state() [%1]"), node.name()) << endmsg; return -1; } @@ -1703,66 +1864,93 @@ Route::_set_state (const XMLNode& node, bool call_base) } else { _flags = Flag (0); } - - if ((prop = node.property (X_("default-type"))) != 0) { - _default_type = DataType(prop->value()); - assert(_default_type != DataType::NIL); - } - if ((prop = node.property (X_("phase-invert"))) != 0) { - set_phase_invert (prop->value()=="yes"?true:false, this); - } + /* add standard processors */ - if ((prop = node.property (X_("denormal-protection"))) != 0) { - set_denormal_protection (prop->value()=="yes"?true:false, this); - } + _meter.reset (new PeakMeter (_session)); + add_processor (_meter, PreFader); - if ((prop = node.property (X_("active"))) != 0) { - set_active (prop->value() == "yes"); + if (_flags & ControlOut) { + /* where we listen to tracks */ + _intreturn.reset (new InternalReturn (_session)); + add_processor (_intreturn, PreFader); } - if ((prop = node.property (X_("muted"))) != 0) { - bool yn = prop->value()=="yes"?true:false; + _main_outs.reset (new Delivery (_session, _output, _mute_master, _name, Delivery::Main)); + add_processor (_main_outs, PostFader); - /* force reset of mute status */ + /* IOs */ - _muted = !yn; - set_mute(yn, this); - mute_gain = desired_mute_gain; - } + nlist = node.children (); + for (niter = nlist.begin(); niter != nlist.end(); ++niter) { - if ((prop = node.property (X_("soloed"))) != 0) { - bool yn = prop->value()=="yes"?true:false; + child = *niter; - /* force reset of solo status */ + if (child->name() == IO::state_node_name) { - _soloed = !yn; - set_solo (yn, this); - solo_gain = desired_solo_gain; + /* there is a note in IO::set_state_2X() about why we have to call + this directly. + */ + + _input->set_state_2X (*child, version, true); + _output->set_state_2X (*child, version, false); + + if ((prop = child->property (X_("name"))) != 0) { + set_name (prop->value ()); + } + + if ((prop = child->property (X_("id"))) != 0) { + _id = prop->value (); + } + + if ((prop = child->property (X_("active"))) != 0) { + bool yn = string_is_affirmative (prop->value()); + _active = !yn; // force switch + set_active (yn); + } + } + + /* XXX: panners? */ + } + + if ((prop = node.property (X_("phase-invert"))) != 0) { + set_phase_invert (string_is_affirmative (prop->value())); } - if ((prop = node.property (X_("mute-affects-pre-fader"))) != 0) { - _mute_affects_pre_fader = (prop->value()=="yes")?true:false; + if ((prop = node.property (X_("denormal-protection"))) != 0) { + set_denormal_protection (string_is_affirmative (prop->value())); } - if ((prop = node.property (X_("mute-affects-post-fader"))) != 0) { - _mute_affects_post_fader = (prop->value()=="yes")?true:false; + if ((prop = node.property (X_("soloed"))) != 0) { + bool yn = string_is_affirmative (prop->value()); + + /* XXX force reset of solo status */ + + set_solo (yn, this); } - if ((prop = node.property (X_("mute-affects-control-outs"))) != 0) { - _mute_affects_control_outs = (prop->value()=="yes")?true:false; + if ((prop = node.property (X_("meter-point"))) != 0) { + _meter_point = MeterPoint (string_2_enum (prop->value (), _meter_point)); } - if ((prop = node.property (X_("mute-affects-main-outs"))) != 0) { - _mute_affects_main_outs = (prop->value()=="yes")?true:false; + /* XXX: if the route was in both a mix group and an edit group, it'll end up + just in the edit group. */ + + if ((prop = node.property (X_("mix-group"))) != 0) { + RouteGroup* route_group = _session.route_group_by_name(prop->value()); + if (route_group == 0) { + error << string_compose(_("Route %1: unknown route group \"%2 in saved state (ignored)"), _name, prop->value()) << endmsg; + } else { + set_route_group (route_group, this); + } } 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; + RouteGroup* route_group = _session.route_group_by_name(prop->value()); + if (route_group == 0) { + error << string_compose(_("Route %1: unknown route group \"%2 in saved state (ignored)"), _name, prop->value()) << endmsg; } else { - set_edit_group(edit_group, this); + set_route_group (route_group, this); } } @@ -1777,13 +1965,13 @@ Route::_set_state (const XMLNode& node, bool call_base) if ((equal = remaining.find_first_of ('=')) == string::npos || equal == remaining.length()) { error << string_compose (_("badly formed order key string in state file! [%1] ... ignored."), remaining) - << endmsg; + << endmsg; } else { if (sscanf (remaining.substr (equal+1).c_str(), "%ld", &n) != 1) { error << string_compose (_("badly formed order key string in state file! [%1] ... ignored."), remaining) - << endmsg; + << endmsg; } else { - set_order_key (remaining.substr (0, equal).c_str(), n); + set_order_key (remaining.substr (0, equal), n); } } @@ -1797,197 +1985,179 @@ Route::_set_state (const XMLNode& node, bool call_base) } } - nlist = node.children(); - - if (deferred_state) { - delete deferred_state; - } - - deferred_state = new XMLNode(X_("deferred state")); - - /* set parent class properties before anything else */ + XMLNodeList redirect_nodes; for (niter = nlist.begin(); niter != nlist.end(); ++niter){ child = *niter; - if (child->name() == IO::state_node_name && call_base) { - - IO::set_state (*child); - break; - } - } - - XMLNodeList redirect_nodes; - - for (niter = nlist.begin(); niter != nlist.end(); ++niter){ - - child = *niter; - if (child->name() == X_("Send") || child->name() == X_("Insert")) { redirect_nodes.push_back(child); } } - _set_redirect_states(redirect_nodes); - + set_processor_state_2X (redirect_nodes, version); for (niter = nlist.begin(); niter != nlist.end(); ++niter){ child = *niter; - // All redirects (sends and inserts) have been applied already - - if (child->name() == X_("Automation")) { - - if ((prop = child->property (X_("path"))) != 0) { - load_automation (prop->value()); - } - - } else if (child->name() == X_("ControlOuts")) { - - string coutname = _name; - coutname += _("[control]"); - _control_outs = new IO (_session, coutname); - _control_outs->set_state (**(child->children().begin())); - - } else if (child->name() == X_("Comment")) { + 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() == X_("extra")) { + } else if (child->name() == X_("Extra")) { _extra_xml = new XMLNode (*child); - } else if (child->name() == X_("controllable") && (prop = child->property("name")) != 0) { - + } 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); + _solo_control->set_state (*child, version); + _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")) { + + } else if (child->name() == X_("RemoteControl")) { if ((prop = child->property (X_("id"))) != 0) { int32_t x; sscanf (prop->value().c_str(), "%d", &x); set_remote_control_id (x); } - } - } - 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; - } else { - set_mix_group(mix_group, this); - } + } } return 0; } +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; +} + +void +Route::set_processor_state_2X (XMLNodeList const & nList, int version) +{ + /* We don't bother removing existing processors not in nList, as this + method will only be called when creating a Route from scratch, not + for undo purposes. Just put processors in at the appropriate place + in the list. + */ + + for (XMLNodeConstIterator i = nList.begin(); i != nList.end(); ++i) { + add_processor_from_xml_2X (**i, version, _processors.begin ()); + } +} + void -Route::_set_redirect_states(const XMLNodeList &nlist) +Route::set_processor_state (const XMLNode& node) { + const XMLNodeList &nlist = node.children(); XMLNodeConstIterator niter; - char buf[64]; + ProcessorList::iterator i, o; - RedirectList::iterator i, o; + // Iterate through existing processors, remove those which are not in the state list - // Iterate through existing redirects, remove those which are not in the state list - for (i = _redirects.begin(); i != _redirects.end(); ) { - RedirectList::iterator tmp = i; - ++tmp; + for (i = _processors.begin(); i != _processors.end(); ) { + + /* leave amp alone, always */ - bool redirectInStateList = false; - - (*i)->id().print (buf, sizeof (buf)); + if ((*i) == _amp) { + ++i; + continue; + } + + ProcessorList::iterator tmp = i; + ++tmp; + bool processorInStateList = false; for (niter = nlist.begin(); niter != nlist.end(); ++niter) { - if (strncmp(buf,(*niter)->child(X_("Redirect"))->child(X_("IO"))->property(X_("id"))->value().c_str(), sizeof(buf)) == 0) { - redirectInStateList = true; + XMLProperty* id_prop = (*niter)->property(X_("id")); + + if (id_prop && (*i)->id() == id_prop->value()) { + processorInStateList = true; break; } } - - if (!redirectInStateList) { - remove_redirect ( *i, this); - } + 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(); - // Iterate through state list and make sure all redirects are on the track and in the correct order, - // set the state of existing redirects according to the new state on the same go - i = _redirects.begin(); for (niter = nlist.begin(); niter != nlist.end(); ++niter, ++i) { - // Check whether the next redirect in the list + XMLProperty* prop = (*niter)->property ("type"); + o = i; - while (o != _redirects.end()) { - (*o)->id().print (buf, sizeof (buf)); - if ( strncmp(buf, (*niter)->child(X_("Redirect"))->child(X_("IO"))->property(X_("id"))->value().c_str(), sizeof(buf)) == 0) - break; - ++o; - } + // Check whether the next processor in the list is the right one, + // except for "amp" which is always there and may not have the + // old ID since it is always created anew in every Route - if (o == _redirects.end()) { - // If the redirect (*niter) is not on the route, we need to create it - // and move it to the correct location + if (prop->value() != "amp") { + while (o != _processors.end()) { + XMLProperty* id_prop = (*niter)->property(X_("id")); + if (id_prop && (*o)->id() == id_prop->value()) { + break; + } - RedirectList::iterator prev_last = _redirects.end(); - --prev_last; // We need this to check whether adding succeeded - - add_redirect_from_xml (**niter); + ++o; + } + } - RedirectList::iterator last = _redirects.end(); - --last; + // If the processor (*niter) is not on the route, + // create it and move it to the correct location - if (prev_last == last) { - cerr << "Could not fully restore state as some redirects were not possible to create" << endl; - continue; + if (o == _processors.end()) { + if (add_processor_from_xml (**niter, i)) { + --i; // move iterator to the newly inserted processor + } else { + cerr << "Error restoring route: unable to restore processor" << endl; } - boost::shared_ptr tmp = (*last); - // remove the redirect from the wrong location - _redirects.erase(last); - // insert the new redirect at the current location - _redirects.insert(i, tmp); + } else { - --i; // move pointer to the newly inserted redirect - continue; - } + // Otherwise, the processor already exists; just + // ensure it is at the location provided in the XML state - // We found the redirect (*niter) on the route, first we must make sure the redirect - // is at the location provided in the XML state - if (i != o) { - boost::shared_ptr tmp = (*o); - // remove the old copy - _redirects.erase(o); - // insert the redirect at the correct location - _redirects.insert(i, tmp); + if (i != o) { + boost::shared_ptr tmp = (*o); + _processors.erase (o); // remove the old copy + _processors.insert (i, tmp); // insert the processor at the correct location + --i; // move iterator to the correct processor + } - --i; // move pointer so it points to the right redirect - } + // and make it (just) so - (*i)->set_state( (**niter) ); + (*i)->set_state (**niter, Stateful::current_state_version); + } } - - redirects_changed(this); + + /* note: there is no configure_processors() call because we figure that + the XML state represents a working signal route. + */ + + processors_changed (); } void @@ -1998,304 +2168,263 @@ Route::curve_reallocate () } void -Route::silence (nframes_t nframes, nframes_t offset) +Route::silence (nframes_t nframes) { if (!_silent) { - IO::silence (nframes, offset); + _output->silence (nframes); - if (_control_outs) { - _control_outs->silence (nframes, offset); - } + { + Glib::RWLock::ReaderLock lm (_processor_lock, Glib::TRY_LOCK); - { - Glib::RWLock::ReaderLock lm (redirect_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 continue; } - (*i)->silence (nframes, offset); + (*i)->silence (nframes); } - if (nframes == _session.get_block_size() && offset == 0) { + if (nframes == _session.get_block_size()) { // _silent = true; } } } - - } -} - -int -Route::set_control_outs (const vector& ports) -{ - Glib::Mutex::Lock lm (control_outs_lock); - vector::const_iterator i; - size_t limit; - - if (_control_outs) { - delete _control_outs; - _control_outs = 0; - } - - if (control() || master()) { - /* no control outs for these two special busses */ - return 0; - } - - if (ports.empty()) { - return 0; - } - - string coutname = _name; - coutname += _("[control]"); - - _control_outs = new IO (_session, coutname); - - /* our control outs need as many outputs as we - have audio outputs. we track the changes in ::output_change_handler(). - */ - - // XXX its stupid that we have to get this value twice - 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], this)) { - error << string_compose (_("could not connect %1 to %2"), _control_outs->output(n)->name(), ports[n]) << endmsg; - return -1; - } } - - return 0; -} +} void -Route::set_edit_group (RouteGroup *eg, void *src) - +Route::add_internal_return () { - if (eg == _edit_group) { - return; + if (!_intreturn) { + _intreturn.reset (new InternalReturn (_session)); + add_processor (_intreturn, PreFader); } +} - if (_edit_group) { - _edit_group->remove (this); - } +BufferSet* +Route::get_return_buffer () const +{ + Glib::RWLock::ReaderLock rm (_processor_lock); - if ((_edit_group = eg) != 0) { - _edit_group->add (this); + for (ProcessorList::const_iterator x = _processors.begin(); x != _processors.end(); ++x) { + boost::shared_ptr d = boost::dynamic_pointer_cast(*x); + + if (d) { + BufferSet* bs = d->get_buffers (); + return bs; + } } - _session.set_dirty (); - edit_group_changed (src); /* EMIT SIGNAL */ + return 0; } void -Route::drop_edit_group (void *src) +Route::release_return_buffer () const { - _edit_group = 0; - _session.set_dirty (); - edit_group_changed (src); /* EMIT SIGNAL */ -} + Glib::RWLock::ReaderLock rm (_processor_lock); -void -Route::set_mix_group (RouteGroup *mg, void *src) + for (ProcessorList::const_iterator x = _processors.begin(); x != _processors.end(); ++x) { + boost::shared_ptr d = boost::dynamic_pointer_cast(*x); -{ - if (mg == _mix_group) { - return; + if (d) { + return d->release_buffers (); + } } +} - if (_mix_group) { - _mix_group->remove (this); - } +int +Route::listen_via (boost::shared_ptr route, Placement placement, bool /*active*/, bool aux) +{ + vector ports; + vector::const_iterator i; - if ((_mix_group = mg) != 0) { - _mix_group->add (this); - } + { + Glib::RWLock::ReaderLock rm (_processor_lock); - _session.set_dirty (); - mix_group_changed (src); /* EMIT SIGNAL */ -} + for (ProcessorList::iterator x = _processors.begin(); x != _processors.end(); ++x) { -void -Route::drop_mix_group (void *src) -{ - _mix_group = 0; - _session.set_dirty (); - mix_group_changed (src); /* EMIT SIGNAL */ -} + boost::shared_ptr d = boost::dynamic_pointer_cast(*x); -void -Route::set_comment (string cmt, void *src) -{ - _comment = cmt; - comment_changed (src); - _session.set_dirty (); -} + if (d && d->target_route() == route) { -bool -Route::feeds (boost::shared_ptr other) -{ - uint32_t i, j; + /* if the target is the control outs, then make sure + we take note of which i-send is doing that. + */ - IO& self = *this; - uint32_t no = self.n_outputs().n_total(); - uint32_t ni = other->n_inputs ().n_total(); + if (route == _session.control_out()) { + _control_outs = boost::dynamic_pointer_cast(d); + } - for (i = 0; i < no; ++i) { - for (j = 0; j < ni; ++j) { - if (self.output(i)->connected_to (other->input(j)->name())) { - return true; + /* already listening via the specified IO: do nothing */ + + return 0; } } } - /* check Redirects which may also interconnect Routes */ + boost::shared_ptr listener; - for (RedirectList::iterator r = _redirects.begin(); r != _redirects.end(); r++) { + try { + listener.reset (new InternalSend (_session, _mute_master, route, (aux ? Delivery::Aux : Delivery::Listen))); - no = (*r)->n_outputs().n_total(); + } catch (failed_constructor& err) { + return -1; + } - for (i = 0; i < no; ++i) { - for (j = 0; j < ni; ++j) { - if ((*r)->output(i)->connected_to (other->input (j)->name())) { - return true; - } - } - } + if (route == _session.control_out()) { + _control_outs = listener; } - /* check for control room outputs which may also interconnect Routes */ + add_processor (listener, placement); - if (_control_outs) { + return 0; +} - no = _control_outs->n_outputs().n_total(); - - for (i = 0; i < no; ++i) { - for (j = 0; j < ni; ++j) { - if (_control_outs->output(i)->connected_to (other->input (j)->name())) { - return true; - } - } +void +Route::drop_listen (boost::shared_ptr route) +{ + ProcessorStreams err; + ProcessorList::iterator tmp; + + Glib::RWLock::ReaderLock rl(_processor_lock); + rl.acquire (); + + again: + for (ProcessorList::iterator x = _processors.begin(); x != _processors.end(); ) { + + boost::shared_ptr d = boost::dynamic_pointer_cast(*x); + + if (d && d->target_route() == route) { + rl.release (); + remove_processor (*x, &err); + rl.acquire (); + + /* list could have been demolished while we dropped the lock + so start over. + */ + + goto again; } } - return false; + rl.release (); + + if (route == _session.control_out()) { + _control_outs.reset (); + } } void -Route::set_mute_config (mute_type t, bool onoff, void *src) +Route::set_route_group (RouteGroup *rg, void *src) { - switch (t) { - case PRE_FADER: - _mute_affects_pre_fader = onoff; - pre_fader_changed(src); /* EMIT SIGNAL */ - break; - - case POST_FADER: - _mute_affects_post_fader = onoff; - post_fader_changed(src); /* EMIT SIGNAL */ - break; + if (rg == _route_group) { + return; + } - case CONTROL_OUTS: - _mute_affects_control_outs = onoff; - control_outs_changed(src); /* EMIT SIGNAL */ - break; + if (_route_group) { + _route_group->remove (this); + } - case MAIN_OUTS: - _mute_affects_main_outs = onoff; - main_outs_changed(src); /* EMIT SIGNAL */ - break; + if ((_route_group = rg) != 0) { + _route_group->add (this); } + + _session.set_dirty (); + route_group_changed (src); /* EMIT SIGNAL */ } -bool -Route::get_mute_config (mute_type t) +void +Route::drop_route_group (void *src) { - bool onoff = false; - - switch (t){ - case PRE_FADER: - onoff = _mute_affects_pre_fader; - break; - case POST_FADER: - onoff = _mute_affects_post_fader; - break; - case CONTROL_OUTS: - onoff = _mute_affects_control_outs; - break; - case MAIN_OUTS: - onoff = _mute_affects_main_outs; - break; - } - - return onoff; + _route_group = 0; + _session.set_dirty (); + route_group_changed (src); /* EMIT SIGNAL */ } void -Route::set_active (bool yn) +Route::set_comment (string cmt, void *src) +{ + _comment = cmt; + comment_changed (src); + _session.set_dirty (); +} + +bool +Route::feeds (boost::shared_ptr other) { - _active = yn; - active_changed(); /* EMIT SIGNAL */ + // cerr << _name << endl; + + if (_output->connected_to (other->input())) { + // cerr << "\tdirect FEEDS " << other->name() << endl; + return true; + } + + for (ProcessorList::iterator r = _processors.begin(); r != _processors.end(); r++) { + + boost::shared_ptr iop; + + if ((iop = boost::dynamic_pointer_cast(*r)) != 0) { + if (iop->feeds (other)) { + // cerr << "\tIOP " << iop->name() << " feeds " << other->name() << endl; + return true; + } else { + // cerr << "\tIOP " << iop->name() << " does NOT feeds " << other->name() << endl; + } + } + } + + // cerr << "\tdoes NOT FEED " << other->name() << endl; + return false; } 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); if (!did_locate) { - automation_snapshot (now); + automation_snapshot (now, true); } - for (RedirectList::iterator i = _redirects.begin(); i != _redirects.end(); ++i) { - - if (Config->get_plugins_stop_with_transport() && can_flush_redirects) { + for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) { + + if (Config->get_plugins_stop_with_transport() && can_flush_processors) { (*i)->deactivate (); (*i)->activate (); } - + (*i)->transport_stopped (now); } } - IO::transport_stopped (now); - _roll_delay = _initial_delay; } void -Route::input_change_handler (IOChange change, void *ignored) +Route::input_change_handler (IOChange change, void * /*src*/) { - if (change & ConfigurationChanged) { - reset_plugin_counts (0); + if ((change & ConfigurationChanged)) { + configure_processors (0); } } void -Route::output_change_handler (IOChange change, void *ignored) +Route::output_change_handler (IOChange change, void * /*src*/) { - if (change & ConfigurationChanged) { - if (_control_outs) { - _control_outs->ensure_io (ChanCount::ZERO, ChanCount(DataType::AUDIO, n_outputs().n_audio()), true, this); - } - - reset_plugin_counts (0); + if ((change & ConfigurationChanged)) { + + /* XXX resize all listeners to match _main_outs? */ + + // configure_processors (0); } } @@ -2305,51 +2434,48 @@ Route::pans_required () const if (n_outputs().n_audio() < 2) { return 0; } - - return max (n_inputs ().n_audio(), static_cast(redirect_max_outs.n_audio())); + + return max (n_inputs ().n_audio(), processor_max_streams.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) +int +Route::no_roll (nframes_t nframes, sframes_t start_frame, sframes_t end_frame, + bool session_state_changing, bool /*can_record*/, bool /*rec_monitors_input*/) { if (n_outputs().n_total() == 0) { return 0; } - if (session_state_changing || !_active) { - silence (nframes, offset); + if (session_state_changing || !_active || n_inputs() == ChanCount::ZERO) { + silence (nframes); return 0; } - apply_gain_automation = false; - - if (n_inputs().n_total()) { - passthru (start_frame, end_frame, nframes, offset, 0, false); - } else { - silence (nframes, offset); - } + _amp->apply_gain_automation (false); + passthru (start_frame, end_frame, nframes, 0); return 0; } nframes_t -Route::check_initial_delay (nframes_t nframes, nframes_t& offset, nframes_t& transport_frame) +Route::check_initial_delay (nframes_t nframes, nframes_t& transport_frame) { if (_roll_delay > nframes) { _roll_delay -= nframes; - silence (nframes, offset); + silence (nframes); /* transport frame is not legal for caller to use */ return 0; } else if (_roll_delay > 0) { nframes -= _roll_delay; - - silence (_roll_delay, offset); - - offset += _roll_delay; + silence (_roll_delay); + /* we've written _roll_delay of samples into the + output ports, so make a note of that for + future reference. + */ + _main_outs->increment_output_offset (_roll_delay); transport_frame += _roll_delay; _roll_delay = 0; @@ -2359,61 +2485,53 @@ Route::check_initial_delay (nframes_t nframes, nframes_t& offset, nframes_t& tra } 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) +Route::roll (nframes_t nframes, sframes_t start_frame, sframes_t end_frame, int declick, + bool /*can_record*/, bool /*rec_monitors_input*/) { { - Glib::RWLock::ReaderLock lm (redirect_lock, Glib::TRY_LOCK); + // automation snapshot can also be called from the non-rt context + // and it uses the processor list, so we try to acquire the lock here + 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 redirect list, so we take the lock out here - automation_snapshot (_session.transport_frame()); + automation_snapshot (_session.transport_frame(), false); } } - if ((n_outputs().n_total() == 0 && _redirects.empty()) || n_inputs().n_total() == 0 || !_active) { - silence (nframes, offset); + if (n_outputs().n_total() == 0) { return 0; } - - nframes_t unused = 0; - if ((nframes = check_initial_delay (nframes, offset, unused)) == 0) { + if (!_active || n_inputs().n_total() == 0) { + silence (nframes); return 0; } - _silent = false; - - apply_gain_automation = false; + nframes_t unused = 0; - { - Glib::Mutex::Lock am (automation_lock, Glib::TRY_LOCK); - - if (am.locked() && _session.transport_rolling()) { - - if (gain_automation_playback()) { - apply_gain_automation = _gain_automation_curve.rt_safe_get_vector (start_frame, end_frame, _session.gain_automation_buffer(), nframes); - } - } + if ((nframes = check_initial_delay (nframes, unused)) == 0) { + return 0; } - passthru (start_frame, end_frame, nframes, offset, declick, false); + _silent = false; + + passthru (start_frame, end_frame, nframes, declick); return 0; } int -Route::silent_roll (nframes_t nframes, nframes_t start_frame, nframes_t end_frame, nframes_t offset, - bool can_record, bool rec_monitors_input) +Route::silent_roll (nframes_t nframes, sframes_t /*start_frame*/, sframes_t /*end_frame*/, + bool /*can_record*/, bool /*rec_monitors_input*/) { - silence (nframes, offset); + silence (nframes); return 0; } void Route::toggle_monitor_input () { - for (PortSet::iterator i = _inputs.begin(); i != _inputs.end(); ++i) { + for (PortSet::iterator i = _input->ports().begin(); i != _input->ports().end(); ++i) { i->ensure_monitor_input( ! i->monitoring_input()); } } @@ -2421,19 +2539,21 @@ Route::toggle_monitor_input () bool Route::has_external_redirects () const { + // FIXME: what about sends? - they don't return a signal back to ardour? + 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) { - for (PortSet::const_iterator port = pi->outputs().begin(); - port != pi->outputs().end(); ++port) { - + for (PortSet::const_iterator port = pi->output()->ports().begin(); port != pi->output()->ports().end(); ++port) { + 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 */ - + if (client_name != "ardour" && pi->active()) { return true; } @@ -2445,15 +2565,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 (); } @@ -2464,40 +2584,124 @@ Route::set_meter_point (MeterPoint p, void *src) { if (_meter_point != p) { _meter_point = p; + + // Move meter in the processors list + ProcessorList::iterator loc = find(_processors.begin(), _processors.end(), _meter); + _processors.erase(loc); + switch (p) { + case MeterInput: + loc = _processors.begin(); + break; + case MeterPreFader: + loc = find(_processors.begin(), _processors.end(), _amp); + break; + case MeterPostFader: + loc = _processors.end(); + break; + } + _processors.insert(loc, _meter); + meter_change (src); /* EMIT SIGNAL */ + processors_changed (); /* EMIT SIGNAL */ _session.set_dirty (); } } +void +Route::put_control_outs_at (Placement p) +{ + if (!_control_outs) { + return; + } + + // Move meter in the processors list + ProcessorList::iterator loc = find(_processors.begin(), _processors.end(), _control_outs); + _processors.erase(loc); + + switch (p) { + case PreFader: + loc = find(_processors.begin(), _processors.end(), _amp); + if (loc != _processors.begin()) { + --loc; + } + break; + case PostFader: + loc = find(_processors.begin(), _processors.end(), _amp); + assert (loc != _processors.end()); + loc++; + break; + } + + _processors.insert(loc, _control_outs); + + processors_changed (); /* EMIT SIGNAL */ + _session.set_dirty (); +} nframes_t Route::update_total_latency () { - _own_latency = 0; + nframes_t old = _output->effective_latency(); + nframes_t own_latency = _output->user_latency(); - for (RedirectList::iterator i = _redirects.begin(); i != _redirects.end(); ++i) { + for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) { if ((*i)->active ()) { - _own_latency += (*i)->latency (); + own_latency += (*i)->signal_latency (); } } - set_port_latency (_own_latency); +#undef DEBUG_LATENCY +#ifdef DEBUG_LATENCY + cerr << _name << ": internal redirect latency = " << own_latency << endl; +#endif - /* 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. - */ + _output->set_port_latency (own_latency); + + if (_output->user_latency() == 0) { + + /* 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->signal_latency (); + } + + if (old != own_latency) { + _output->set_latency_delay (own_latency); + signal_latency_changed (); /* EMIT SIGNAL */ + } + +#ifdef DEBUG_LATENCY + cerr << _name << ": input latency = " << _input->signal_latency() << " total = " + << own_latency << endl; +#endif - return _own_latency; + return _output->effective_latency (); +} + +void +Route::set_user_latency (nframes_t nframes) +{ + _output->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 (_output->effective_latency() < longest_session_latency) { + _initial_delay = longest_session_latency - _output->effective_latency(); + } else { + _initial_delay = 0; + } + + if (_initial_delay != old) { + initial_delay_changed (); /* EMIT SIGNAL */ + } if (_session.transport_stopped()) { _roll_delay = _initial_delay; @@ -2505,101 +2709,50 @@ Route::set_latency_delay (nframes_t longest_session_latency) } void -Route::automation_snapshot (nframes_t now) +Route::automation_snapshot (nframes_t now, bool force) { - IO::automation_snapshot (now); - - for (RedirectList::iterator i = _redirects.begin(); i != _redirects.end(); ++i) { - (*i)->automation_snapshot (now); + for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) { + (*i)->automation_snapshot (now, force); } } -Route::ToggleControllable::ToggleControllable (std::string name, Route& s, ToggleType tp) - : Controllable (name), route (s), type(tp) +Route::SoloControllable::SoloControllable (std::string name, Route& r) + : AutomationControl (r.session(), Evoral::Parameter (SoloAutomation), + boost::shared_ptr(), name) + , route (r) { - + boost::shared_ptr gl(new AutomationList(Evoral::Parameter(SoloAutomation))); + set_list (gl); } void -Route::ToggleControllable::set_value (float val) +Route::SoloControllable::set_value (float val) { bool bval = ((val >= 0.5f) ? true: false); - - switch (type) { - case MuteControl: - route.set_mute (bval, this); - break; - case SoloControl: - route.set_solo (bval, this); - break; - default: - break; - } + + route.set_solo (bval, this); } float -Route::ToggleControllable::get_value (void) const +Route::SoloControllable::get_value (void) const { - float val = 0.0f; - - switch (type) { - case MuteControl: - val = route.muted() ? 1.0f : 0.0f; - break; - case SoloControl: - val = route.soloed() ? 1.0f : 0.0f; - break; - default: - break; - } - - return val; + return route.soloed() ? 1.0f : 0.0f; } -void +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); + _session.ensure_buffers(processor_max_streams); } void Route::protect_automation () { - switch (gain_automation_state()) { - case Write: - set_gain_automation_state (Off); - case Touch: - set_gain_automation_state (Play); - break; - default: - break; - } - - switch (panner().automation_state ()) { - case Write: - panner().set_automation_state (Off); - break; - case Touch: - panner().set_automation_state (Play); - break; - default: - break; - } - - 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 @@ -2616,3 +2769,224 @@ Route::set_pending_declick (int declick) } } + +/** Shift automation forwards from a particular place, thereby inserting time. + * Adds undo commands for any shifts that are performed. + * + * @param pos Position to start shifting from. + * @param frames Amount to shift forwards by. + */ + +void +Route::shift (nframes64_t /*pos*/, nframes64_t /*frames*/) +{ +#ifdef THIS_NEEDS_FIXING_FOR_V3 + + /* gain automation */ + XMLNode &before = _gain_control->get_state (); + _gain_control->shift (pos, frames); + XMLNode &after = _gain_control->get_state (); + _session.add_command (new MementoCommand (_gain_automation_curve, &before, &after)); + + /* pan automation */ + for (std::vector::iterator i = _panner->begin (); i != _panner->end (); ++i) { + Curve & c = (*i)->automation (); + XMLNode &before = c.get_state (); + c.shift (pos, frames); + XMLNode &after = c.get_state (); + _session.add_command (new MementoCommand (c, &before, &after)); + } + + /* redirect automation */ + { + Glib::RWLock::ReaderLock lm (redirect_lock); + for (RedirectList::iterator i = _redirects.begin (); i != _redirects.end (); ++i) { + + set a; + (*i)->what_has_automation (a); + + for (set::const_iterator j = a.begin (); j != a.end (); ++j) { + AutomationList & al = (*i)->automation_list (*j); + XMLNode &before = al.get_state (); + al.shift (pos, frames); + XMLNode &after = al.get_state (); + _session.add_command (new MementoCommand (al, &before, &after)); + } + } + } +#endif + +} + + +int +Route::save_as_template (const string& path, const string& name) +{ + XMLNode& node (state (false)); + XMLTree tree; + + IO::set_name_in_state (*node.children().front(), name); + + tree.set_root (&node); + return tree.write (path.c_str()); +} + + +bool +Route::set_name (const string& str) +{ + bool ret; + string ioproc_name; + string name; + + name = Route::ensure_track_or_route_name (str, _session); + SessionObject::set_name (name); + + ret = (_input->set_name(name) && _output->set_name(name)); + + if (ret) { + + Glib::RWLock::ReaderLock lm (_processor_lock); + + for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) { + + /* rename all I/O processors that have inputs or outputs */ + + boost::shared_ptr iop = boost::dynamic_pointer_cast (*i); + + if (iop && (iop->output() || iop->input())) { + if (!iop->set_name (name)) { + ret = false; + } + } + } + + } + + return ret; +} + +boost::shared_ptr +Route::internal_send_for (boost::shared_ptr target) const +{ + Glib::RWLock::ReaderLock lm (_processor_lock); + + for (ProcessorList::const_iterator i = _processors.begin(); i != _processors.end(); ++i) { + boost::shared_ptr send; + + if ((send = boost::dynamic_pointer_cast(*i)) != 0) { + if (send->target_route() == target) { + return send; + } + } + } + + return boost::shared_ptr(); +} + +void +Route::set_phase_invert (bool yn) +{ + if (_phase_invert != yn) { + _phase_invert = 0xffff; // XXX all channels + phase_invert_changed (); /* EMIT SIGNAL */ + } +} + +bool +Route::phase_invert () const +{ + return _phase_invert != 0; +} + +void +Route::set_denormal_protection (bool yn) +{ + if (_denormal_protection != yn) { + _denormal_protection = yn; + denormal_protection_changed (); /* EMIT SIGNAL */ + } +} + +bool +Route::denormal_protection () const +{ + return _denormal_protection; +} + +void +Route::set_active (bool yn) +{ + if (_active != yn) { + _active = yn; + _input->set_active (yn); + _output->set_active (yn); + active_changed (); // EMIT SIGNAL + } +} + +void +Route::meter () +{ + Glib::RWLock::ReaderLock rm (_processor_lock, Glib::TRY_LOCK); + + assert (_meter); + + _meter->meter (); + + for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) { + + boost::shared_ptr s; + boost::shared_ptr r; + + if ((s = boost::dynamic_pointer_cast (*i)) != 0) { + s->meter()->meter(); + } else if ((r = boost::dynamic_pointer_cast (*i)) != 0) { + r->meter()->meter (); + } + } +} + +boost::shared_ptr +Route::panner() const +{ + + return _main_outs->panner(); +} + +boost::shared_ptr +Route::gain_control() const +{ + + return _amp->gain_control(); +} + +boost::shared_ptr +Route::get_control (const Evoral::Parameter& param) +{ + /* either we own the control or .... */ + + boost::shared_ptr c = boost::dynamic_pointer_cast(data().control (param)); + + if (!c) { + + /* maybe one of our processors does or ... */ + + Glib::RWLock::ReaderLock rm (_processor_lock, Glib::TRY_LOCK); + for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) { + if ((c = boost::dynamic_pointer_cast((*i)->data().control (param))) != 0) { + break; + } + } + } + + if (!c) { + + /* nobody does so we'll make a new one */ + + c = boost::dynamic_pointer_cast(control_factory(param)); + add_control(c); + } + + return c; +}