summaryrefslogtreecommitdiff
path: root/libs/ardour/route.cc
diff options
context:
space:
mode:
Diffstat (limited to 'libs/ardour/route.cc')
-rw-r--r--libs/ardour/route.cc2421
1 files changed, 2421 insertions, 0 deletions
diff --git a/libs/ardour/route.cc b/libs/ardour/route.cc
new file mode 100644
index 0000000000..cd925e90b5
--- /dev/null
+++ b/libs/ardour/route.cc
@@ -0,0 +1,2421 @@
+/*
+ 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
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ $Id$
+*/
+
+#include <cmath>
+#include <fstream>
+
+#include <sigc++/bind.h>
+#include <pbd/xml++.h>
+
+#include <ardour/timestamps.h>
+#include <ardour/audioengine.h>
+#include <ardour/route.h>
+#include <ardour/insert.h>
+#include <ardour/send.h>
+#include <ardour/session.h>
+#include <ardour/utils.h>
+#include <ardour/configuration.h>
+#include <ardour/cycle_timer.h>
+#include <ardour/route_group.h>
+#include <ardour/port.h>
+#include <ardour/ladspa_plugin.h>
+#include <ardour/panner.h>
+#include <ardour/dB.h>
+#include <ardour/mix.h>
+
+#include "i18n.h"
+
+using namespace std;
+using namespace ARDOUR;
+//using namespace sigc;
+
+
+uint32_t Route::order_key_cnt = 0;
+
+
+Route::Route (Session& sess, string name, int input_min, int input_max, int output_min, int output_max, Flag flg)
+ : IO (sess, name, input_min, input_max, output_min, output_max),
+ _flags (flg),
+ _midi_solo_control (*this, MIDIToggleControl::SoloControl, _session.midi_port()),
+ _midi_mute_control (*this, MIDIToggleControl::MuteControl, _session.midi_port())
+{
+ init ();
+}
+
+Route::Route (Session& sess, const XMLNode& node)
+ : IO (sess, "route"),
+ _midi_solo_control (*this, MIDIToggleControl::SoloControl, _session.midi_port()),
+ _midi_mute_control (*this, MIDIToggleControl::MuteControl, _session.midi_port())
+{
+ init ();
+ set_state (node);
+}
+
+void
+Route::init ()
+{
+ redirect_max_outs = 0;
+ _muted = false;
+ _soloed = false;
+ _solo_safe = false;
+ _phase_invert = false;
+ order_keys[N_("signal")] = order_key_cnt++;
+ _active = true;
+ _silent = false;
+ _meter_point = MeterPostFader;
+ _initial_delay = 0;
+ _roll_delay = 0;
+ _own_latency = 0;
+ _have_internal_generator = false;
+ _declickable = false;
+ _pending_declick = true;
+
+ _edit_group = 0;
+ _mix_group = 0;
+
+ _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;
+
+ _control_outs = 0;
+
+ input_changed.connect (mem_fun (this, &Route::input_change_handler));
+ output_changed.connect (mem_fun (this, &Route::output_change_handler));
+
+ reset_midi_control (_session.midi_port(), _session.get_midi_control());
+}
+
+Route::~Route ()
+{
+ GoingAway (); /* EMIT SIGNAL */
+ clear_redirects (this);
+
+ if (_control_outs) {
+ delete _control_outs;
+ }
+}
+
+long
+Route::order_key (string name) const
+{
+ OrderKeys::const_iterator i;
+
+ if ((i = order_keys.find (name)) == order_keys.end()) {
+ return -1;
+ }
+
+ return (*i).second;
+}
+
+void
+Route::set_order_key (string name, long n)
+{
+ order_keys[name] = n;
+ _session.set_dirty ();
+}
+
+void
+Route::inc_gain (gain_t fraction, void *src)
+{
+ IO::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 (usable_gain < 0.000001f) {
+ usable_gain=0.000001f;
+ }
+
+ gain_t delta = val;
+ if (delta < 0.000001f) {
+ delta=0.000001f;
+ }
+
+ delta -= usable_gain;
+
+ if (delta == 0.0f) return;
+
+ gain_t factor = delta / usable_gain;
+
+ if (factor > 0.0f) {
+ factor = _mix_group->get_max_factor(factor);
+ if (factor == 0.0f) {
+ gain_changed (src);
+ return;
+ }
+ } else {
+ factor = _mix_group->get_min_factor(factor);
+ if (factor == 0.0f) {
+ gain_changed (src);
+ return;
+ }
+ }
+
+ _mix_group->apply (&Route::inc_gain, factor, _mix_group);
+
+ } else {
+
+ _mix_group->apply (&Route::set_gain, val, _mix_group);
+ }
+
+ return;
+ }
+
+ if (val == gain()) {
+ return;
+ }
+
+ IO::set_gain (val, src);
+}
+
+void
+Route::process_output_buffers (vector<Sample*>& bufs, uint32_t nbufs,
+ jack_nframes_t start_frame, jack_nframes_t end_frame,
+ jack_nframes_t nframes, jack_nframes_t offset, bool with_redirects, int declick,
+ bool meter)
+{
+ uint32_t n;
+ RedirectList::iterator i;
+ bool post_fader_work = false;
+ bool mute_declick_applied = false;
+ gain_t dmg, dsg, dg;
+ vector<Sample*>::iterator bufiter;
+ IO *co;
+ bool mute_audible;
+ bool solo_audible;
+ bool no_monitor = (Config->get_use_hardware_monitoring() || Config->get_no_sw_monitoring ());
+ gain_t* gab = _session.gain_automation_buffer();
+
+ declick = _pending_declick;
+
+ {
+ TentativeLockMonitor cm (control_outs_lock, __LINE__, __FILE__);
+
+ if (cm.locked()) {
+ co = _control_outs;
+ } else {
+ co = 0;
+ }
+ }
+
+ {
+ TentativeLockMonitor dm (declick_lock, __LINE__, __FILE__);
+
+ if (dm.locked()) {
+ dmg = desired_mute_gain;
+ dsg = desired_solo_gain;
+ dg = _desired_gain;
+ } else {
+ dmg = mute_gain;
+ dsg = solo_gain;
+ dg = _gain;
+ }
+ }
+
+ /* ----------------------------------------------------------------------------------------------------
+ GLOBAL DECLICK (for transport changes etc.)
+ -------------------------------------------------------------------------------------------------- */
+
+ if (declick > 0) {
+ apply_declick (bufs, nbufs, nframes, 0.0, 1.0, _phase_invert);
+ _pending_declick = 0;
+ } else if (declick < 0) {
+ apply_declick (bufs, nbufs, nframes, 1.0, 0.0, _phase_invert);
+ _pending_declick = 0;
+ } else {
+
+ /* no global declick */
+
+ if (solo_gain != dsg) {
+ apply_declick (bufs, nbufs, nframes, solo_gain, dsg, _phase_invert);
+ solo_gain = dsg;
+ }
+ }
+
+
+ /* ----------------------------------------------------------------------------------------------------
+ INPUT METERING & MONITORING
+ -------------------------------------------------------------------------------------------------- */
+
+ if (meter && (_meter_point == MeterInput)) {
+ for (n = 0; n < nbufs; ++n) {
+ _peak_power[n] = Session::compute_peak (bufs[n], nframes, _peak_power[n]);
+ }
+ }
+
+ if (!_soloed && _mute_affects_pre_fader && (mute_gain != dmg)) {
+ apply_declick (bufs, nbufs, nframes, mute_gain, dmg, _phase_invert);
+ mute_gain = dmg;
+ mute_declick_applied = true;
+ }
+
+ 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
+
+ (no_monitor && record_enabled() && (!_session.get_auto_input() || _session.actively_recording()))
+
+ ) {
+
+ co->silence (nframes, offset);
+
+ } else {
+
+ co->deliver_output (bufs, nbufs, nframes, offset);
+
+ }
+ }
+
+ /* ----------------------------------------------------------------------------------------------------
+ PRE-FADER REDIRECTS
+ -------------------------------------------------------------------------------------------------- */
+
+ if (with_redirects) {
+ TentativeLockMonitor rm (redirect_lock, __LINE__, __FILE__);
+ 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, nbufs, 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 (!_soloed && (mute_gain != dmg) && !mute_declick_applied && _mute_affects_post_fader) {
+ apply_declick (bufs, nbufs, nframes, mute_gain, dmg, _phase_invert);
+ mute_gain = dmg;
+ mute_declick_applied = true;
+ }
+
+ /* ----------------------------------------------------------------------------------------------------
+ PRE-FADER METERING & MONITORING
+ -------------------------------------------------------------------------------------------------- */
+
+ if (meter && (_meter_point == MeterPreFader)) {
+ for (n = 0; n < nbufs; ++n) {
+ _peak_power[n] = Session::compute_peak (bufs[n], nframes, _peak_power[n]);
+ }
+ }
+
+
+ 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() && (!_session.get_auto_input() || _session.actively_recording()))
+
+ ) {
+
+ co->silence (nframes, offset);
+
+ } else {
+
+ co->deliver_output (bufs, nbufs, 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_use_hardware_monitoring() &&
+
+ // AND software monitoring required
+
+ !Config->get_no_sw_monitoring())) {
+
+ if (apply_gain_automation) {
+
+ if (_phase_invert) {
+ for (n = 0; n < nbufs; ++n) {
+ Sample *sp = bufs[n];
+
+ for (jack_nframes_t nx = 0; nx < nframes; ++nx) {
+ sp[nx] *= -gab[nx];
+ }
+ }
+ } else {
+ for (n = 0; n < nbufs; ++n) {
+ Sample *sp = bufs[n];
+
+ for (jack_nframes_t nx = 0; nx < nframes; ++nx) {
+ sp[nx] *= gab[nx];
+ }
+ }
+ }
+
+ if (apply_gain_automation) {
+ _effective_gain = gab[nframes-1];
+ }
+
+ } else {
+
+ /* manual (scalar) gain */
+
+ if (_gain != dg) {
+
+ apply_declick (bufs, nbufs, 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 (n = 0; n < nbufs; ++n) {
+ Sample *sp = bufs[n];
+ apply_gain_to_buffer(sp,nframes,this_gain);
+ }
+
+ } else if (_gain == 0) {
+ for (n = 0; n < nbufs; ++n) {
+ memset (bufs[n], 0, sizeof (Sample) * nframes);
+ }
+ }
+ }
+
+ } 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) {
+
+ TentativeLockMonitor rm (redirect_lock, __LINE__, __FILE__);
+ 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, nbufs, 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;
+ }
+ }
+ }
+ }
+ }
+
+ if (!_soloed && (mute_gain != dmg) && !mute_declick_applied && _mute_affects_control_outs) {
+ apply_declick (bufs, nbufs, nframes, mute_gain, dmg, _phase_invert);
+ mute_gain = dmg;
+ mute_declick_applied = true;
+ }
+
+ /* ----------------------------------------------------------------------------------------------------
+ CONTROL OUTPUT STAGE
+ -------------------------------------------------------------------------------------------------- */
+
+ if ((_meter_point == MeterPostFader) && co) {
+
+ solo_audible = solo_gain > 0;
+ mute_audible = dmg > 0 || !_mute_affects_control_outs;
+
+ if ( // silent anyway
+
+ (_gain == 0 && !apply_gain_automation) ||
+
+ // muted by solo of another track
+
+ !solo_audible ||
+
+ // muted by mute of this track
+
+ !mute_audible ||
+
+ // recording but not s/w monitoring
+
+ (no_monitor && record_enabled() && (!_session.get_auto_input() || _session.actively_recording()))
+
+ ) {
+
+ co->silence (nframes, offset);
+
+ } else {
+
+ co->deliver_output_no_pan (bufs, nbufs, nframes, offset);
+ }
+ }
+
+ /* ----------------------------------------------------------------------
+ GLOBAL MUTE
+ ----------------------------------------------------------------------*/
+
+ if (!_soloed && (mute_gain != dmg) && !mute_declick_applied && _mute_affects_main_outs) {
+ apply_declick (bufs, nbufs, nframes, mute_gain, dmg, _phase_invert);
+ 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() == 0) {
+
+ /* relax */
+
+ } else if (no_monitor && record_enabled() && (!_session.get_auto_input() || _session.actively_recording())) {
+
+ IO::silence (nframes, offset);
+
+ } else {
+
+ if ( // silent anyway
+
+ (_gain == 0 && !apply_gain_automation) ||
+
+ // muted by solo of another track, but not using control outs for solo
+
+ (!solo_audible && (_session.solo_model() != Session::SoloBus)) ||
+
+ // muted by mute of this track
+
+ !mute_audible
+
+ ) {
+
+ /* don't use Route::silence() here, because that causes
+ all outputs (sends, port inserts, etc. to be silent).
+ */
+
+ if (_meter_point == MeterPostFader) {
+ reset_peak_meters ();
+ }
+
+ IO::silence (nframes, offset);
+
+ } else {
+
+ if (_session.transport_speed() > 1.5f || _session.transport_speed() < -1.5f) {
+ pan (bufs, nbufs, nframes, offset, speed_quietning);
+ } else {
+ if (!_panner->empty() &&
+ (_panner->automation_state() & Play ||
+ ((_panner->automation_state() & Touch) && !_panner->touching()))) {
+ pan_automated (bufs, nbufs, start_frame, end_frame, nframes, offset);
+ } else {
+ pan (bufs, nbufs, nframes, offset, 1.0);
+ }
+ }
+ }
+
+ }
+
+ /* ----------------------------------------------------------------------------------------------------
+ POST-FADER METERING
+ -------------------------------------------------------------------------------------------------- */
+
+ if (meter && (_meter_point == MeterPostFader)) {
+// cerr << "meter post" << endl;
+
+ if ((_gain == 0 && !apply_gain_automation) || dmg == 0) {
+ uint32_t no = n_outputs();
+ for (n = 0; n < no; ++n) {
+ _peak_power[n] = 0;
+ }
+ } else {
+ uint32_t no = n_outputs();
+ for (n = 0; n < no; ++n) {
+ _peak_power[n] = Session::compute_peak (output(n)->get_buffer (nframes) + offset, nframes, _peak_power[n]);
+ }
+ }
+ }
+}
+
+uint32_t
+Route::n_process_buffers ()
+{
+ return max (n_inputs(), redirect_max_outs);
+}
+
+void
+
+Route::passthru (jack_nframes_t start_frame, jack_nframes_t end_frame, jack_nframes_t nframes, jack_nframes_t offset, int declick, bool meter_first)
+{
+ vector<Sample*>& bufs = _session.get_passthru_buffers();
+ uint32_t limit = n_process_buffers ();
+
+ _silent = false;
+
+ collect_input (bufs, limit, nframes, offset);
+
+#define meter_stream meter_first
+
+ if (meter_first) {
+ for (uint32_t n = 0; n < limit; ++n) {
+ _peak_power[n] = Session::compute_peak (bufs[n], nframes, _peak_power[n]);
+ }
+ meter_stream = false;
+ } else {
+ meter_stream = true;
+ }
+
+ process_output_buffers (bufs, limit, start_frame, end_frame, nframes, offset, true, declick, meter_stream);
+
+#undef meter_stream
+}
+
+void
+Route::set_phase_invert (bool yn, void *src)
+{
+ if (_phase_invert != yn) {
+ _phase_invert = yn;
+ }
+ // phase_invert_changed (src); /* EMIT SIGNAL */
+}
+
+void
+Route::set_solo (bool yn, void *src)
+{
+ if (_solo_safe) {
+ return;
+ }
+
+ if (_mix_group && src != _mix_group && _mix_group->is_active()) {
+ _mix_group->apply (&Route::set_solo, yn, _mix_group);
+ return;
+ }
+
+ if (_soloed != yn) {
+ _soloed = yn;
+ solo_changed (src); /* EMIT SIGNAL */
+
+ if (_session.get_midi_feedback()) {
+ _midi_solo_control.send_feedback (_soloed);
+ }
+ }
+}
+
+void
+Route::set_solo_mute (bool yn)
+{
+ LockMonitor lm (declick_lock, __LINE__, __FILE__);
+
+ /* Called by Session in response to another Route being soloed.
+ */
+
+ desired_solo_gain = (yn?0.0:1.0);
+}
+
+void
+Route::set_solo_safe (bool yn, void *src)
+{
+ if (_solo_safe != yn) {
+ _solo_safe = yn;
+ solo_safe_changed (src); /* EMIT SIGNAL */
+ }
+}
+
+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);
+ return;
+ }
+
+ if (_muted != yn) {
+ _muted = yn;
+ mute_changed (src); /* EMIT SIGNAL */
+
+ if (_session.get_midi_feedback()) {
+ _midi_mute_control.send_feedback (_muted);
+ }
+
+ LockMonitor lm (declick_lock, __LINE__, __FILE__);
+ desired_mute_gain = (yn?0.0f:1.0f);
+ }
+}
+
+int
+Route::add_redirect (Redirect *redirect, void *src, uint32_t* err_streams)
+{
+ uint32_t old_rmo = redirect_max_outs;
+
+ if (!_session.engine().connected()) {
+ return 1;
+ }
+
+ {
+ LockMonitor lm (redirect_lock, __LINE__, __FILE__);
+
+ PluginInsert* pi;
+ PortInsert* porti;
+
+ if ((pi = dynamic_cast<PluginInsert*>(redirect)) != 0) {
+ pi->set_count (1);
+
+ if (pi->input_streams() == 0) {
+ /* instrument plugin */
+ _have_internal_generator = true;
+ }
+
+ } else if ((porti = dynamic_cast<PortInsert*>(redirect)) != 0) {
+
+ /* force new port inserts to start out with an i/o configuration
+ that matches this route's i/o configuration.
+
+ the "inputs" for the port are supposed to match the output
+ of this route.
+
+ the "outputs" of the route should match the inputs of this
+ route. XXX shouldn't they match the number of active signal
+ streams at the point of insertion?
+
+ */
+
+ porti->ensure_io (n_outputs (), n_inputs(), false, this);
+ }
+
+ _redirects.push_back (redirect);
+
+ if (_reset_plugin_counts (err_streams)) {
+ _redirects.pop_back ();
+ _reset_plugin_counts (0); // it worked before we tried to add it ...
+ return -1;
+ }
+
+ redirect->activate ();
+ redirect->active_changed.connect (mem_fun (*this, &Route::redirect_active_proxy));
+ }
+
+ if (redirect_max_outs != old_rmo || old_rmo == 0) {
+ reset_panner ();
+ }
+
+
+ redirects_changed (src); /* EMIT SIGNAL */
+ return 0;
+}
+
+int
+Route::add_redirects (const RedirectList& others, void *src, uint32_t* err_streams)
+{
+ uint32_t old_rmo = redirect_max_outs;
+
+ if (!_session.engine().connected()) {
+ return 1;
+ }
+
+ {
+ LockMonitor lm (redirect_lock, __LINE__, __FILE__);
+
+ RedirectList::iterator existing_end = _redirects.end();
+ --existing_end;
+
+ for (RedirectList::const_iterator i = others.begin(); i != others.end(); ++i) {
+
+ PluginInsert* pi;
+
+ if ((pi = dynamic_cast<PluginInsert*>(*i)) != 0) {
+ pi->set_count (1);
+ }
+
+ _redirects.push_back (*i);
+
+ if (_reset_plugin_counts (err_streams)) {
+ ++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 == 0) {
+ reset_panner ();
+ }
+
+ redirects_changed (src); /* EMIT SIGNAL */
+ return 0;
+}
+
+void
+Route::clear_redirects (void *src)
+{
+ uint32_t old_rmo = redirect_max_outs;
+
+ if (!_session.engine().connected()) {
+ return;
+ }
+
+ {
+ LockMonitor lm (redirect_lock, __LINE__, __FILE__);
+
+ for (RedirectList::iterator i = _redirects.begin(); i != _redirects.end(); ++i) {
+ delete *i;
+ }
+
+ _redirects.clear ();
+ }
+
+ if (redirect_max_outs != old_rmo) {
+ reset_panner ();
+ }
+
+ redirect_max_outs = 0;
+ _have_internal_generator = false;
+ redirects_changed (src); /* EMIT SIGNAL */
+}
+
+int
+Route::remove_redirect (Redirect *redirect, void *src, uint32_t* err_streams)
+{
+ uint32_t old_rmo = redirect_max_outs;
+
+ if (!_session.engine().connected()) {
+ return 1;
+ }
+
+ redirect_max_outs = 0;
+
+ {
+ LockMonitor lm (redirect_lock, __LINE__, __FILE__);
+ RedirectList::iterator i;
+ bool removed = false;
+
+ for (i = _redirects.begin(); i != _redirects.end(); ++i) {
+ if (*i == redirect) {
+
+ RedirectList::iterator tmp;
+
+ /* move along, see failure case for reset_plugin_counts()
+ where we may need to reinsert the redirect.
+ */
+
+ tmp = i;
+ ++tmp;
+
+ /* stop redirects that send signals to JACK ports
+ from causing noise as a result of no longer being
+ run.
+ */
+
+ Send* send;
+ PortInsert* port_insert;
+
+ if ((send = dynamic_cast<Send*> (*i)) != 0) {
+ send->disconnect_inputs (this);
+ send->disconnect_outputs (this);
+ } else if ((port_insert = dynamic_cast<PortInsert*> (*i)) != 0) {
+ port_insert->disconnect_inputs (this);
+ port_insert->disconnect_outputs (this);
+ }
+
+ _redirects.erase (i);
+
+ i = tmp;
+ removed = true;
+ break;
+ }
+ }
+
+ if (!removed) {
+ /* what? */
+ return 1;
+ }
+
+ if (_reset_plugin_counts (err_streams)) {
+ /* get back to where we where */
+ _redirects.insert (i, redirect);
+ /* we know this will work, because it worked before :) */
+ _reset_plugin_counts (0);
+ return -1;
+ }
+
+ bool foo = false;
+
+ for (i = _redirects.begin(); i != _redirects.end(); ++i) {
+ PluginInsert* pi;
+
+ if ((pi = dynamic_cast<PluginInsert*>(*i)) != 0) {
+ if (pi->is_generator()) {
+ foo = true;
+ }
+ }
+ }
+
+ _have_internal_generator = foo;
+ }
+
+ if (old_rmo != redirect_max_outs) {
+ reset_panner ();
+ }
+
+ redirects_changed (src); /* EMIT SIGNAL */
+ return 0;
+}
+
+int
+Route::reset_plugin_counts (uint32_t* lpc)
+{
+ LockMonitor lm (redirect_lock, __LINE__, __FILE__);
+ return _reset_plugin_counts (lpc);
+}
+
+
+int
+Route::_reset_plugin_counts (uint32_t* err_streams)
+{
+ RedirectList::iterator r;
+ uint32_t i_cnt;
+ uint32_t s_cnt;
+ map<Placement,list<InsertCount> > insert_map;
+ jack_nframes_t initial_streams;
+
+ redirect_max_outs = 0;
+ i_cnt = 0;
+ s_cnt = 0;
+
+ /* divide inserts up by placement so we get the signal flow
+ properly modelled. we need to do this because the _redirects
+ list is not sorted by placement, and because other reasons may
+ exist now or in the future for this separate treatment.
+ */
+
+ for (r = _redirects.begin(); r != _redirects.end(); ++r) {
+
+ Insert *insert;
+
+ /* do this here in case we bomb out before we get to the end of
+ this function.
+ */
+
+ redirect_max_outs = max ((*r)->output_streams (), redirect_max_outs);
+
+ if ((insert = dynamic_cast<Insert*>(*r)) != 0) {
+ ++i_cnt;
+ insert_map[insert->placement()].push_back (InsertCount (*insert));
+
+ /* reset plugin counts back to one for now so
+ that we have a predictable, controlled
+ state to try to configure.
+ */
+
+ PluginInsert* pi;
+
+ if ((pi = dynamic_cast<PluginInsert*>(insert)) != 0) {
+ pi->set_count (1);
+ }
+
+ } else if (dynamic_cast<Send*> (*r) != 0) {
+ ++s_cnt;
+ }
+ }
+
+ if (i_cnt == 0) {
+ if (s_cnt) {
+ goto recompute;
+ } else {
+ return 0;
+ }
+ }
+
+ /* Now process each placement in order, checking to see if we
+ can really do what has been requested.
+ */
+
+ /* A: PreFader */
+
+ if (check_some_plugin_counts (insert_map[PreFader], n_inputs (), err_streams)) {
+ return -1;
+ }
+
+ /* figure out the streams that will feed into PreFader */
+
+ if (!insert_map[PreFader].empty()) {
+ InsertCount& ic (insert_map[PreFader].back());
+ initial_streams = ic.insert.compute_output_streams (ic.cnt);
+ } else {
+ initial_streams = n_inputs ();
+ }
+
+ /* B: PostFader */
+
+ if (check_some_plugin_counts (insert_map[PostFader], initial_streams, err_streams)) {
+ return -1;
+ }
+
+ /* OK, everything can be set up correctly, so lets do it */
+
+ apply_some_plugin_counts (insert_map[PreFader]);
+ apply_some_plugin_counts (insert_map[PostFader]);
+
+ /* recompute max outs of any redirect */
+
+ recompute:
+
+ redirect_max_outs = 0;
+ RedirectList::iterator prev = _redirects.end();
+
+ for (r = _redirects.begin(); r != _redirects.end(); prev = r, ++r) {
+ Send* s;
+
+ if ((s = dynamic_cast<Send*> (*r)) != 0) {
+ if (r == _redirects.begin()) {
+ s->expect_inputs (n_inputs());
+ } else {
+ s->expect_inputs ((*prev)->output_streams());
+ }
+ }
+
+ redirect_max_outs = max ((*r)->output_streams (), redirect_max_outs);
+ }
+
+ /* we're done */
+
+ return 0;
+}
+
+int32_t
+Route::apply_some_plugin_counts (list<InsertCount>& iclist)
+{
+ list<InsertCount>::iterator i;
+
+ for (i = iclist.begin(); i != iclist.end(); ++i) {
+
+ if ((*i).insert.configure_io ((*i).cnt, (*i).in, (*i).out)) {
+ return -1;
+ }
+ /* make sure that however many we have, they are all active */
+ (*i).insert.activate ();
+ }
+
+ return 0;
+}
+
+int32_t
+Route::check_some_plugin_counts (list<InsertCount>& iclist, int32_t required_inputs, uint32_t* err_streams)
+{
+ list<InsertCount>::iterator i;
+
+ for (i = iclist.begin(); i != iclist.end(); ++i) {
+
+ if (((*i).cnt = (*i).insert.can_support_input_configuration (required_inputs)) < 0) {
+ if (err_streams) {
+ *err_streams = required_inputs;
+ }
+ return -1;
+ }
+
+ (*i).in = required_inputs;
+ (*i).out = (*i).insert.compute_output_streams ((*i).cnt);
+
+ required_inputs = (*i).out;
+ }
+
+ return 0;
+}
+
+int
+Route::copy_redirects (const Route& other, Placement placement, uint32_t* err_streams)
+{
+ uint32_t old_rmo = redirect_max_outs;
+
+ if (err_streams) {
+ *err_streams = 0;
+ }
+
+ RedirectList to_be_deleted;
+
+ {
+ LockMonitor lm (redirect_lock, __LINE__, __FILE__);
+ RedirectList::iterator tmp;
+ RedirectList the_copy;
+
+ the_copy = _redirects;
+
+ /* remove all relevant redirects */
+
+ for (RedirectList::iterator i = _redirects.begin(); i != _redirects.end(); ) {
+ tmp = i;
+ ++tmp;
+
+ if ((*i)->placement() == placement) {
+ to_be_deleted.push_back (*i);
+ _redirects.erase (i);
+ }
+
+ i = tmp;
+ }
+
+ /* 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));
+ }
+ }
+
+ /* reset plugin stream handling */
+
+ if (_reset_plugin_counts (err_streams)) {
+
+ /* FAILED COPY ATTEMPT: we have to restore order */
+
+ /* delete all cloned redirects */
+
+ for (RedirectList::iterator i = _redirects.begin(); i != _redirects.end(); ) {
+
+ tmp = i;
+ ++tmp;
+
+ if ((*i)->placement() == placement) {
+ delete *i;
+ _redirects.erase (i);
+ }
+
+ i = tmp;
+ }
+
+ /* restore the natural order */
+
+ _redirects = the_copy;
+ redirect_max_outs = old_rmo;
+
+ /* we failed, even though things are OK again */
+
+ return -1;
+
+ } else {
+
+ /* SUCCESSFUL COPY ATTEMPT: delete the redirects we removed pre-copy */
+
+ for (RedirectList::iterator i = to_be_deleted.begin(); i != to_be_deleted.end(); ++i) {
+ delete *i;
+ }
+ }
+ }
+
+ if (redirect_max_outs != old_rmo || old_rmo == 0) {
+ reset_panner ();
+ }
+
+ redirects_changed (this); /* EMIT SIGNAL */
+ return 0;
+}
+
+void
+Route::all_redirects_flip ()
+{
+ LockMonitor lm (redirect_lock, __LINE__, __FILE__);
+
+ if (_redirects.empty()) {
+ return;
+ }
+
+ bool first_is_on = _redirects.front()->active();
+
+ for (RedirectList::iterator i = _redirects.begin(); i != _redirects.end(); ++i) {
+ (*i)->set_active (!first_is_on, this);
+ }
+}
+
+void
+Route::all_redirects_active (bool state)
+{
+ LockMonitor lm (redirect_lock, __LINE__, __FILE__);
+
+ if (_redirects.empty()) {
+ return;
+ }
+
+ for (RedirectList::iterator i = _redirects.begin(); i != _redirects.end(); ++i) {
+ (*i)->set_active (state, this);
+ }
+}
+
+struct RedirectSorter {
+ bool operator() (const Redirect *a, const Redirect *b) {
+ return a->sort_key() < b->sort_key();
+ }
+};
+
+int
+Route::sort_redirects (uint32_t* err_streams)
+{
+ {
+ RedirectSorter comparator;
+ LockMonitor lm (redirect_lock, __LINE__, __FILE__);
+ uint32_t old_rmo = redirect_max_outs;
+
+ /* the sweet power of C++ ... */
+
+ RedirectList as_it_was_before = _redirects;
+
+ _redirects.sort (comparator);
+
+ if (_reset_plugin_counts (err_streams)) {
+ _redirects = as_it_was_before;
+ redirect_max_outs = old_rmo;
+ return -1;
+ }
+ }
+
+ reset_panner ();
+ redirects_changed (this); /* EMIT SIGNAL */
+
+ return 0;
+}
+
+XMLNode&
+Route::get_state()
+{
+ return state(true);
+}
+
+XMLNode&
+Route::get_template()
+{
+ return state(false);
+}
+
+XMLNode&
+Route::state(bool full_state)
+{
+ XMLNode *node = new XMLNode("Route");
+ XMLNode *aevents;
+ RedirectList:: iterator i;
+ char buf[32];
+
+ if (_flags) {
+ snprintf (buf, sizeof (buf), "0x%x", _flags);
+ node->add_property("flags", buf);
+ }
+ 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("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");
+
+ if (_edit_group) {
+ node->add_property("edit-group", _edit_group->name());
+ }
+ if (_mix_group) {
+ node->add_property("mix-group", _mix_group->name());
+ }
+
+ /* MIDI control */
+
+ MIDI::channel_t chn;
+ MIDI::eventType ev;
+ MIDI::byte additional;
+ XMLNode* midi_node = 0;
+ XMLNode* child;
+
+ midi_node = node->add_child ("MIDI");
+
+ if (_midi_mute_control.get_control_info (chn, ev, additional)) {
+ child = midi_node->add_child ("mute");
+ set_midi_node_info (child, ev, chn, additional);
+ }
+ if (_midi_solo_control.get_control_info (chn, ev, additional)) {
+ child = midi_node->add_child ("solo");
+ set_midi_node_info (child, ev, chn, additional);
+ }
+
+
+ string order_string;
+ OrderKeys::iterator x = order_keys.begin();
+
+ while (x != order_keys.end()) {
+ order_string += (*x).first;
+ order_string += '=';
+ snprintf (buf, sizeof(buf), "%ld", (*x).second);
+ order_string += buf;
+
+ ++x;
+
+ if (x == order_keys.end()) {
+ break;
+ }
+
+ order_string += ':';
+ }
+ node->add_property ("order-keys", order_string);
+
+ node->add_child_nocopy (IO::state (full_state));
+
+ 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);
+ }
+
+ if (full_state) {
+ string path;
+
+ path = _session.snap_name();
+ path += "-gain-";
+ path += legalize_for_path (_name);
+ path += ".automation";
+
+ /* XXX we didn't ask for a state save, we asked for the current state.
+ FIX ME!
+ */
+
+ if (save_automation (path)) {
+ error << _("Could not get state of route. Problem with save_automation") << endmsg;
+ }
+
+ aevents = node->add_child ("Automation");
+ aevents->add_property ("path", path);
+ }
+
+ for (i = _redirects.begin(); i != _redirects.end(); ++i) {
+ node->add_child_nocopy((*i)->state (full_state));
+ }
+
+ if (_extra_xml){
+ node->add_child_copy (*_extra_xml);
+ }
+
+ return *node;
+}
+
+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;
+ Insert *insert = 0;
+ Send *send = 0;
+
+ if (node.name() == "Send") {
+
+ try {
+ send = new Send (_session, node);
+ }
+
+ catch (failed_constructor &err) {
+ error << _("Send construction failed") << endmsg;
+ return;
+ }
+
+ add_redirect (send, this);
+
+ } else if (node.name() == "Insert") {
+
+ try {
+ if ((prop = node.property ("type")) != 0) {
+
+ if (prop->value() == "ladspa" || prop->value() == "Ladspa" || prop->value() == "vst") {
+
+ insert = new PluginInsert(_session, node);
+
+ } else if (prop->value() == "port") {
+
+
+ insert = new PortInsert (_session, node);
+
+ } else {
+
+ error << compose(_("unknown Insert type \"%1\"; ignored"), prop->value()) << endmsg;
+ }
+
+ add_redirect (insert, this);
+
+ } else {
+ error << _("Insert XML node has no type property") << endmsg;
+ }
+ }
+
+ catch (failed_constructor &err) {
+ warning << _("insert could not be created. Ignored.") << endmsg;
+ return;
+ }
+ }
+}
+
+int
+Route::set_state (const XMLNode& node)
+{
+ XMLNodeList nlist;
+ XMLNodeConstIterator niter;
+ XMLNode *child;
+ XMLPropertyList plist;
+ const XMLProperty *prop;
+ XMLNodeList midi_kids;
+
+
+ if (node.name() != "Route"){
+ error << compose(_("Bad node sent to Route::set_state() [%1]"), node.name()) << endmsg;
+ return -1;
+ }
+
+ if ((prop = node.property ("flags")) != 0) {
+ int x;
+ sscanf (prop->value().c_str(), "0x%x", &x);
+ _flags = Flag (x);
+ } else {
+ _flags = Flag (0);
+ }
+
+ if ((prop = node.property ("phase-invert")) != 0) {
+ set_phase_invert(prop->value()=="yes"?true:false, this);
+ }
+
+ if ((prop = node.property ("active")) != 0) {
+ set_active (prop->value() == "yes");
+ }
+
+ if ((prop = node.property ("muted")) != 0) {
+ bool yn = prop->value()=="yes"?true:false;
+
+ /* force reset of mute status */
+
+ _muted = !yn;
+ set_mute(yn, this);
+ mute_gain = desired_mute_gain;
+ }
+
+ if ((prop = node.property ("soloed")) != 0) {
+ bool yn = prop->value()=="yes"?true:false;
+
+ /* force reset of solo status */
+
+ _soloed = !yn;
+ set_solo (yn, this);
+ solo_gain = desired_solo_gain;
+ }
+
+ if ((prop = node.property ("mute-affects-pre-fader")) != 0) {
+ _mute_affects_pre_fader = (prop->value()=="yes")?true:false;
+ }
+
+ if ((prop = node.property ("mute-affects-post-fader")) != 0) {
+ _mute_affects_post_fader = (prop->value()=="yes")?true:false;
+ }
+
+ if ((prop = node.property ("mute-affects-control-outs")) != 0) {
+ _mute_affects_control_outs = (prop->value()=="yes")?true:false;
+ }
+
+ if ((prop = node.property ("mute-affects-main-outs")) != 0) {
+ _mute_affects_main_outs = (prop->value()=="yes")?true:false;
+ }
+
+ if ((prop = node.property ("edit-group")) != 0) {
+ RouteGroup* edit_group = _session.edit_group_by_name(prop->value());
+ if(edit_group == 0) {
+ error << compose(_("Route %1: unknown edit group \"%2 in saved state (ignored)"), _name, prop->value()) << endmsg;
+ } else {
+ set_edit_group(edit_group, this);
+ }
+ }
+
+ if ((prop = node.property ("order-keys")) != 0) {
+
+ long n;
+
+ string::size_type colon, equal;
+ string remaining = prop->value();
+
+ while (remaining.length()) {
+
+ if ((equal = remaining.find_first_of ('=')) == string::npos || equal == remaining.length()) {
+ error << 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 << compose (_("badly formed order key string in state file! [%1] ... ignored."), remaining)
+ << endmsg;
+ } else {
+ set_order_key (remaining.substr (0, equal), n);
+ }
+ }
+
+ colon = remaining.find_first_of (':');
+
+ if (colon != string::npos) {
+ remaining = remaining.substr (colon+1);
+ } else {
+ break;
+ }
+ }
+ }
+
+ nlist = node.children();
+
+ if (deferred_state) {
+ delete deferred_state;
+ }
+
+ deferred_state = new XMLNode("deferred state");
+
+ /* set parent class properties before anything else */
+
+ for (niter = nlist.begin(); niter != nlist.end(); ++niter){
+
+ child = *niter;
+
+ if (child->name() == IO::state_node_name) {
+
+ IO::set_state (*child);
+ break;
+ }
+ }
+
+ for (niter = nlist.begin(); niter != nlist.end(); ++niter){
+
+ child = *niter;
+
+ if (child->name() == "Send") {
+
+
+ if (!IO::ports_legal) {
+
+ deferred_state->add_child_copy (*child);
+
+ } else {
+ add_redirect_from_xml (*child);
+ }
+
+ } else if (child->name() == "Insert") {
+
+ if (!IO::ports_legal) {
+
+ deferred_state->add_child_copy (*child);
+
+ } else {
+
+ add_redirect_from_xml (*child);
+ }
+
+ } else if (child->name() == "Automation") {
+
+ XMLPropertyList plist;
+ XMLPropertyConstIterator piter;
+ XMLProperty *prop;
+
+ plist = child->properties();
+ for (piter = plist.begin(); piter != plist.end(); ++piter) {
+ prop = *piter;
+ if (prop->name() == "path") {
+ load_automation (prop->value());
+ }
+ }
+
+ } else if (child->name() == "ControlOuts") {
+
+ string coutname = _name;
+ coutname += _("[control]");
+
+ _control_outs = new IO (_session, coutname);
+ _control_outs->set_state (**(child->children().begin()));
+
+ } else if (child->name() == "Comment") {
+
+ /* XXX this is a terrible API design in libxml++ */
+
+ XMLNode *cmt = *(child->children().begin());
+ _comment = cmt->content();
+
+ } else if (child->name() == "extra") {
+ _extra_xml = new XMLNode (*child);
+ }
+ }
+
+ if ((prop = node.property ("mix-group")) != 0) {
+ RouteGroup* mix_group = _session.mix_group_by_name(prop->value());
+ if (mix_group == 0) {
+ error << compose(_("Route %1: unknown mix group \"%2 in saved state (ignored)"), _name, prop->value()) << endmsg;
+ } else {
+ set_mix_group(mix_group, this);
+ }
+ }
+
+ midi_kids = node.children ("MIDI");
+
+ for (niter = midi_kids.begin(); niter != midi_kids.end(); ++niter) {
+
+ XMLNodeList kids;
+ XMLNodeConstIterator miter;
+ XMLNode* child;
+
+ kids = (*niter)->children ();
+
+ for (miter = kids.begin(); miter != kids.end(); ++miter) {
+
+ child =* miter;
+
+ MIDI::eventType ev = MIDI::on; /* initialize to keep gcc happy */
+ MIDI::byte additional = 0; /* ditto */
+ MIDI::channel_t chn = 0; /* ditto */
+
+ if (child->name() == "mute") {
+
+ if (get_midi_node_info (child, ev, chn, additional)) {
+ _midi_mute_control.set_control_type (chn, ev, additional);
+ } else {
+ error << compose(_("MIDI mute control specification for %1 is incomplete, so it has been ignored"), _name) << endmsg;
+ }
+ }
+ else if (child->name() == "solo") {
+
+ if (get_midi_node_info (child, ev, chn, additional)) {
+ _midi_solo_control.set_control_type (chn, ev, additional);
+ } else {
+ error << compose(_("MIDI mute control specification for %1 is incomplete, so it has been ignored"), _name) << endmsg;
+ }
+ }
+
+ }
+ }
+
+
+ return 0;
+}
+
+void
+Route::curve_reallocate ()
+{
+// _gain_automation_curve.finish_resize ();
+// _pan_automation_curve.finish_resize ();
+}
+
+void
+Route::silence (jack_nframes_t nframes, jack_nframes_t offset)
+{
+ if (!_silent) {
+
+ // reset_peak_meters ();
+
+ IO::silence (nframes, offset);
+
+ if (_control_outs) {
+ _control_outs->silence (nframes, offset);
+ }
+
+ {
+ TentativeLockMonitor lm (redirect_lock, __LINE__, __FILE__);
+
+ if (lm.locked()) {
+ for (RedirectList::iterator i = _redirects.begin(); i != _redirects.end(); ++i) {
+ (*i)->silence (nframes, offset);
+ }
+
+ if (nframes == _session.get_block_size() && offset == 0) {
+ // _silent = true;
+ }
+ }
+ }
+
+ }
+}
+
+int
+Route::set_control_outs (const vector<string>& ports)
+{
+ LockMonitor lm (control_outs_lock, __LINE__, __FILE__);
+ vector<string>::const_iterator i;
+
+ if (_control_outs) {
+ delete _control_outs;
+ _control_outs = 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 outputs. we track the changes in ::output_change_handler().
+ */
+
+ _control_outs->ensure_io (0, n_outputs(), true, this);
+
+ return 0;
+}
+
+void
+Route::set_edit_group (RouteGroup *eg, void *src)
+
+{
+ if (_edit_group) {
+ _edit_group->remove (this);
+ }
+
+ if ((_edit_group = eg)) {
+ _edit_group->add (this);
+ }
+
+ _session.set_dirty ();
+
+ edit_group_changed (src); /* EMIT SIGNAL */
+}
+
+void
+Route::set_mix_group (RouteGroup *mg, void *src)
+
+{
+ if (_mix_group) {
+ _mix_group->remove (this);
+ }
+
+ if ((_mix_group = mg)) {
+ _mix_group->add (this);
+ }
+
+ _session.set_dirty ();
+
+ mix_group_changed (src); /* EMIT SIGNAL */
+}
+
+void
+Route::set_comment (string cmt, void *src)
+{
+ _comment = cmt;
+ comment_changed (src);
+ _session.set_dirty ();
+}
+
+bool
+Route::feeds (Route *o)
+{
+ uint32_t i, j;
+
+ IO& other = *o;
+ IO& self = *this;
+ uint32_t no = self.n_outputs();
+ uint32_t ni = other.n_inputs ();
+
+ for (i = 0; i < no; ++i) {
+ for (j = 0; j < ni; ++j) {
+ if (self.output(i)->connected_to (other.input(j)->name())) {
+ return true;
+ }
+ }
+ }
+
+ /* check Redirects which may also interconnect Routes */
+
+ for (RedirectList::iterator r = _redirects.begin(); r != _redirects.end(); r++) {
+
+ no = (*r)->n_outputs();
+
+ for (i = 0; i < no; ++i) {
+ for (j = 0; j < ni; ++j) {
+ if ((*r)->output(i)->connected_to (other.input (j)->name())) {
+ return true;
+ }
+ }
+ }
+ }
+
+ /* check for control room outputs which may also interconnect Routes */
+
+ if (_control_outs) {
+
+ no = _control_outs->n_outputs();
+
+ 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;
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+void
+Route::set_mute_config (mute_type t, bool onoff, 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;
+
+ case CONTROL_OUTS:
+ _mute_affects_control_outs = onoff;
+ control_outs_changed(src); /* EMIT SIGNAL */
+ break;
+
+ case MAIN_OUTS:
+ _mute_affects_main_outs = onoff;
+ main_outs_changed(src); /* EMIT SIGNAL */
+ break;
+ }
+}
+
+bool
+Route::get_mute_config (mute_type t)
+{
+ 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;
+}
+
+void
+Route::set_active (bool yn)
+{
+ _active = yn;
+ active_changed(); /* EMIT SIGNAL */
+}
+
+void
+Route::transport_stopped (bool abort_ignored, bool did_locate, bool can_flush_redirects)
+{
+ jack_nframes_t now = _session.transport_frame();
+
+ if (!did_locate) {
+ automation_snapshot (now);
+ }
+
+ {
+ LockMonitor lm (redirect_lock, __LINE__, __FILE__);
+ for (RedirectList::iterator i = _redirects.begin(); i != _redirects.end(); ++i) {
+
+ if (Config->get_plugins_stop_with_transport() && can_flush_redirects) {
+ (*i)->deactivate ();
+ (*i)->activate ();
+ }
+
+ (*i)->transport_stopped (now);
+ }
+ }
+
+ IO::transport_stopped (now);
+
+ _roll_delay = _initial_delay;
+}
+
+UndoAction
+Route::get_memento() const
+{
+ void (Route::*pmf)(state_id_t) = &Route::set_state;
+ return sigc::bind (mem_fun (*(const_cast<Route *>(this)), pmf), _current_state_id);
+}
+
+void
+Route::set_state (state_id_t id)
+{
+ return;
+}
+
+void
+Route::input_change_handler (IOChange change, void *ignored)
+{
+ if (change & ConfigurationChanged) {
+ reset_plugin_counts (0);
+ }
+}
+
+void
+Route::output_change_handler (IOChange change, void *ignored)
+{
+ if (change & ConfigurationChanged) {
+ if (_control_outs) {
+ _control_outs->ensure_io (0, n_outputs(), true, this);
+ }
+
+ reset_plugin_counts (0);
+ }
+}
+
+uint32_t
+Route::pans_required () const
+{
+ if (n_outputs() < 2) {
+ return 0;
+ }
+
+ return max (n_inputs (), redirect_max_outs);
+}
+
+int
+Route::no_roll (jack_nframes_t nframes, jack_nframes_t start_frame, jack_nframes_t end_frame, jack_nframes_t offset,
+ bool session_state_changing, bool can_record, bool rec_monitors_input)
+{
+ if (n_outputs() == 0) {
+ return 0;
+ }
+
+ if (session_state_changing || !_active) {
+ silence (nframes, offset);
+ return 0;
+ }
+
+ apply_gain_automation = false;
+
+ if (n_inputs()) {
+ passthru (start_frame, end_frame, nframes, offset, 0, false);
+ } else {
+ silence (nframes, offset);
+ }
+
+ return 0;
+}
+
+jack_nframes_t
+Route::check_initial_delay (jack_nframes_t nframes, jack_nframes_t& offset, jack_nframes_t& transport_frame)
+{
+ if (_roll_delay > nframes) {
+
+ _roll_delay -= nframes;
+ silence (nframes, offset);
+ /* 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;
+ transport_frame += _roll_delay;
+
+ _roll_delay = 0;
+ }
+
+ return nframes;
+}
+
+int
+Route::roll (jack_nframes_t nframes, jack_nframes_t start_frame, jack_nframes_t end_frame, jack_nframes_t offset, int declick,
+ bool can_record, bool rec_monitors_input)
+{
+ automation_snapshot (_session.transport_frame());
+
+ if ((n_outputs() == 0 && _redirects.empty()) || n_inputs() == 0 || !_active) {
+ silence (nframes, offset);
+ return 0;
+ }
+
+ jack_nframes_t unused;
+
+ if ((nframes = check_initial_delay (nframes, offset, unused)) == 0) {
+ return 0;
+ }
+
+ _silent = false;
+ apply_gain_automation = false;
+
+ {
+ TentativeLockMonitor am (automation_lock, __LINE__, __FILE__);
+
+ if (am.locked()) {
+
+ jack_nframes_t start_frame = end_frame - nframes;
+
+ if (gain_automation_playback()) {
+ apply_gain_automation = _gain_automation_curve.rt_safe_get_vector (start_frame, end_frame, _session.gain_automation_buffer(), nframes);
+ }
+ }
+ }
+
+ passthru (start_frame, end_frame, nframes, offset, declick, false);
+
+ return 0;
+}
+
+int
+Route::silent_roll (jack_nframes_t nframes, jack_nframes_t start_frame, jack_nframes_t end_frame, jack_nframes_t offset,
+ bool can_record, bool rec_monitors_input)
+{
+ silence (nframes, offset);
+ return 0;
+}
+
+void
+Route::toggle_monitor_input ()
+{
+ for (vector<Port*>::iterator i = _inputs.begin(); i != _inputs.end(); ++i) {
+ (*i)->request_monitor_input(!(*i)->monitoring_input());
+ }
+}
+
+bool
+Route::has_external_redirects () const
+{
+ const PortInsert* pi;
+
+ for (RedirectList::const_iterator i = _redirects.begin(); i != _redirects.end(); ++i) {
+ if ((pi = dynamic_cast<const PortInsert*>(*i)) != 0) {
+
+ uint32_t no = pi->n_outputs();
+
+ for (uint32_t n = 0; n < no; ++n) {
+
+ string port_name = pi->output(n)->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;
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+void
+Route::reset_midi_control (MIDI::Port* port, bool on)
+{
+ MIDI::channel_t chn;
+ MIDI::eventType ev;
+ MIDI::byte extra;
+
+ for (RedirectList::iterator i = _redirects.begin(); i != _redirects.end(); ++i) {
+ (*i)->reset_midi_control (port, on);
+ }
+
+ IO::reset_midi_control (port, on);
+
+ _midi_solo_control.get_control_info (chn, ev, extra);
+ if (!on) {
+ chn = -1;
+ }
+ _midi_solo_control.midi_rebind (port, chn);
+
+ _midi_mute_control.get_control_info (chn, ev, extra);
+ if (!on) {
+ chn = -1;
+ }
+ _midi_mute_control.midi_rebind (port, chn);
+}
+
+void
+Route::send_all_midi_feedback ()
+{
+ if (_session.get_midi_feedback()) {
+
+ {
+ LockMonitor lm (redirect_lock, __LINE__, __FILE__);
+ for (RedirectList::iterator i = _redirects.begin(); i != _redirects.end(); ++i) {
+ (*i)->send_all_midi_feedback ();
+ }
+ }
+
+ IO::send_all_midi_feedback();
+
+ _midi_solo_control.send_feedback (_soloed);
+ _midi_mute_control.send_feedback (_muted);
+ }
+}
+
+MIDI::byte*
+Route::write_midi_feedback (MIDI::byte* buf, int32_t& bufsize)
+{
+ buf = _midi_solo_control.write_feedback (buf, bufsize, _soloed);
+ buf = _midi_mute_control.write_feedback (buf, bufsize, _muted);
+
+ {
+ LockMonitor lm (redirect_lock, __LINE__, __FILE__);
+ for (RedirectList::iterator i = _redirects.begin(); i != _redirects.end(); ++i) {
+ buf = (*i)->write_midi_feedback (buf, bufsize);
+ }
+ }
+
+ return IO::write_midi_feedback (buf, bufsize);
+}
+
+void
+Route::flush_redirects ()
+{
+ /* XXX shouldn't really try to take this lock, since
+ this is called from the RT audio thread.
+ */
+
+ LockMonitor lm (redirect_lock, __LINE__, __FILE__);
+
+ for (RedirectList::iterator i = _redirects.begin(); i != _redirects.end(); ++i) {
+ (*i)->deactivate ();
+ (*i)->activate ();
+ }
+}
+
+void
+Route::set_meter_point (MeterPoint p, void *src)
+{
+ if (_meter_point != p) {
+ _meter_point = p;
+ meter_change (src); /* EMIT SIGNAL */
+ _session.set_dirty ();
+ }
+}
+
+jack_nframes_t
+Route::update_total_latency ()
+{
+ _own_latency = 0;
+
+ for (RedirectList::iterator i = _redirects.begin(); i != _redirects.end(); ++i) {
+ if ((*i)->active ()) {
+ _own_latency += (*i)->latency ();
+ }
+ }
+
+ set_port_latency (_own_latency);
+
+ /* this (virtual) function is used for pure Routes,
+ not derived classes like AudioTrack. this means
+ that the data processed here comes from an input
+ port, not prerecorded material, and therefore we
+ have to take into account any input latency.
+ */
+
+ _own_latency += input_latency ();
+
+ return _own_latency;
+}
+
+void
+Route::set_latency_delay (jack_nframes_t longest_session_latency)
+{
+ _initial_delay = longest_session_latency - _own_latency;
+
+ if (_session.transport_stopped()) {
+ _roll_delay = _initial_delay;
+ }
+}
+
+void
+Route::automation_snapshot (jack_nframes_t now)
+{
+ IO::automation_snapshot (now);
+
+ for (RedirectList::iterator i = _redirects.begin(); i != _redirects.end(); ++i) {
+ (*i)->automation_snapshot (now);
+ }
+}
+
+Route::MIDIToggleControl::MIDIToggleControl (Route& s, ToggleType tp, MIDI::Port* port)
+ : MIDI::Controllable (port, true), route (s), type(tp), setting(false)
+{
+ last_written = false; /* XXX need a good out-of-bound-value */
+}
+
+void
+Route::MIDIToggleControl::set_value (float val)
+{
+ MIDI::eventType et;
+ MIDI::channel_t chn;
+ MIDI::byte additional;
+
+ get_control_info (chn, et, additional);
+
+ setting = true;
+
+#ifdef HOLD_TOGGLE_VALUES
+ if (et == MIDI::off || et == MIDI::on) {
+
+ /* literal toggle */
+
+ switch (type) {
+ case MuteControl:
+ route.set_mute (!route.muted(), this);
+ break;
+ case SoloControl:
+ route.set_solo (!route.soloed(), this);
+ break;
+ default:
+ break;
+ }
+
+ } else {
+#endif
+
+ /* map full control range to a boolean */
+
+ 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;
+ }
+
+#ifdef HOLD_TOGGLE_VALUES
+ }
+#endif
+
+ setting = false;
+}
+
+void
+Route::MIDIToggleControl::send_feedback (bool value)
+{
+
+ if (!setting && get_midi_feedback()) {
+ MIDI::byte val = (MIDI::byte) (value ? 127: 0);
+ MIDI::channel_t ch = 0;
+ MIDI::eventType ev = MIDI::none;
+ MIDI::byte additional = 0;
+ MIDI::EventTwoBytes data;
+
+ if (get_control_info (ch, ev, additional)) {
+ data.controller_number = additional;
+ data.value = val;
+
+ route._session.send_midi_message (get_port(), ev, ch, data);
+ }
+ }
+
+}
+
+MIDI::byte*
+Route::MIDIToggleControl::write_feedback (MIDI::byte* buf, int32_t& bufsize, bool val, bool force)
+{
+ if (get_midi_feedback() && bufsize > 2) {
+ MIDI::channel_t ch = 0;
+ MIDI::eventType ev = MIDI::none;
+ MIDI::byte additional = 0;
+
+ if (get_control_info (ch, ev, additional)) {
+ if (val != last_written || force) {
+ *buf++ = (0xF0 & ev) | (0xF & ch);
+ *buf++ = additional; /* controller number */
+ *buf++ = (MIDI::byte) (val ? 127 : 0);
+ bufsize -= 3;
+ last_written = val;
+ }
+ }
+ }
+
+ return buf;
+}
+
+void
+Route::set_block_size (jack_nframes_t nframes)
+{
+ for (RedirectList::iterator i = _redirects.begin(); i != _redirects.end(); ++i) {
+ (*i)->set_block_size (nframes);
+ }
+}
+
+void
+Route::redirect_active_proxy (Redirect* ignored, void* ignored_src)
+{
+ _session.update_latency_compensation (false, false);
+}
+
+void
+Route::protect_automation ()
+{
+ switch (gain_automation_state()) {
+ case Write:
+ case Touch:
+ set_gain_automation_state (Off);
+ break;
+ default:
+ break;
+ }
+
+ switch (panner().automation_state ()) {
+ case Write:
+ case Touch:
+ panner().set_automation_state (Off);
+ break;
+ default:
+ break;
+ }
+
+ for (RedirectList::iterator i = _redirects.begin(); i != _redirects.end(); ++i) {
+ PluginInsert* pi;
+ if ((pi = dynamic_cast<PluginInsert*> (*i)) != 0) {
+ pi->protect_automation ();
+ }
+ }
+}
+
+void
+Route::set_pending_declick (int declick)
+{
+ if (_declickable) {
+ /* this call is not allowed to turn off a pending declick unless "force" is true */
+ if (declick) {
+ _pending_declick = declick;
+ }
+ // cerr << _name << ": after setting to " << declick << " pending declick = " << _pending_declick << endl;
+ } else {
+ _pending_declick = 0;
+ }
+
+}