X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=libs%2Fardour%2Froute.cc;h=4d473eb7406d2a945c981a830e69f2a4bc49744c;hb=4e5423b3487771220cdca4c2b1a79c4d74b6afa5;hp=a449540c6fbf82e64b5bc1d0e910c73f0f58fb54;hpb=f485cfa324717f57b9f820f43f1b53307b96a8b9;p=ardour.git diff --git a/libs/ardour/route.cc b/libs/ardour/route.cc index a449540c6f..4d473eb740 100644 --- a/libs/ardour/route.cc +++ b/libs/ardour/route.cc @@ -30,9 +30,10 @@ #include "pbd/xml++.h" #include "pbd/enumwriter.h" +#include "pbd/locale_guard.h" #include "pbd/memento_command.h" #include "pbd/stacktrace.h" -#include "pbd/convert.h" +#include "pbd/types_convert.h" #include "pbd/unwind.h" #include "ardour/amp.h" @@ -46,6 +47,9 @@ #include "ardour/capturing_processor.h" #include "ardour/debug.h" #include "ardour/delivery.h" +#include "ardour/disk_reader.h" +#include "ardour/disk_writer.h" +#include "ardour/event_type_map.h" #include "ardour/gain_control.h" #include "ardour/internal_return.h" #include "ardour/internal_send.h" @@ -53,6 +57,7 @@ #include "ardour/delayline.h" #include "ardour/midi_buffer.h" #include "ardour/midi_port.h" +#include "ardour/monitor_control.h" #include "ardour/monitor_processor.h" #include "ardour/pannable.h" #include "ardour/panner.h" @@ -70,35 +75,30 @@ #include "ardour/session.h" #include "ardour/solo_control.h" #include "ardour/solo_isolate_control.h" +#include "ardour/types_convert.h" #include "ardour/unknown_processor.h" #include "ardour/utils.h" #include "ardour/vca.h" -#include "i18n.h" +#include "pbd/i18n.h" using namespace std; using namespace ARDOUR; using namespace PBD; -PBD::Signal0 Route::SyncOrderKeys; -PBD::Signal0 Route::RemoteControlIDChange; PBD::Signal3, boost::shared_ptr, Route::PluginSetupOptions > Route::PluginSetup; /** Base class for all routable/mixable objects (tracks and busses) */ -Route::Route (Session& sess, string name, Flag flg, DataType default_type) - : GraphNode (sess._process_graph) - , Stripable (sess, name) +Route::Route (Session& sess, string name, PresentationInfo::Flag flag, DataType default_type) + : Stripable (sess, name, PresentationInfo (flag)) + , GraphNode (sess._process_graph) , Muteable (sess, name) - , Automatable (sess) , _active (true) , _signal_latency (0) - , _signal_latency_at_amp_position (0) - , _signal_latency_at_trim_position (0) , _initial_delay (0) - , _roll_delay (0) + , _disk_io_point (DiskIOPreFader) , _pending_process_reorder (0) , _pending_signals (0) - , _flags (flg) , _pending_declick (true) , _meter_point (MeterPostFader) , _pending_meter_point (MeterPostFader) @@ -109,9 +109,6 @@ Route::Route (Session& sess, string name, Flag flg, DataType default_type) , _declickable (false) , _have_internal_generator (false) , _default_type (default_type) - , _order_key (0) - , _has_order_key (false) - , _remote_control_id (0) , _track_number (0) , _in_configure_processors (false) , _initial_io_setup (false) @@ -119,10 +116,16 @@ Route::Route (Session& sess, string name, Flag flg, DataType default_type) , _strict_io (false) , _custom_meter_position_noted (false) , _pinmgr_proxy (0) + , _patch_selector_dialog (0) { processor_max_streams.reset(); } +boost::weak_ptr +Route::weakroute () { + return boost::weak_ptr (boost::dynamic_pointer_cast (shared_from_this ())); +} + int Route::init () { @@ -139,10 +142,12 @@ Route::init () /* add standard controls */ _gain_control.reset (new GainControl (_session, GainAutomation)); - add_control (_gain_control); - _trim_control.reset (new GainControl (_session, TrimAutomation)); - add_control (_trim_control); + /* While the route has-a gain-control for consistency with Stripable and VCA + * ownership is handed over to the Amp Processor which manages the + * state of the Control and AutomationList as part of its + * Automatable API. -- Don't call add_control () here. + */ _solo_control.reset (new SoloControl (_session, X_("solo"), *this, *this)); add_control (_solo_control); @@ -162,7 +167,7 @@ Route::init () /* panning */ - if (!(_flags & Route::MonitorOut)) { + if (!(_presentation_info.flags() & PresentationInfo::MonitorOut)) { _pannable.reset (new Pannable (_session)); } @@ -235,10 +240,6 @@ Route::init () _monitor_control->activate (); } - if (is_master() || is_monitor() || is_auditioner()) { - _mute_master->set_solo_ignore (true); - } - /* now that we have _meter, its safe to connect to this */ { @@ -270,120 +271,6 @@ Route::~Route () _processors.clear (); } -void -Route::set_remote_control_id (uint32_t id, bool notify_class_listeners) -{ - if (Config->get_remote_model() != UserOrdered) { - return; - } - - set_remote_control_id_internal (id, notify_class_listeners); -} - -void -Route::set_remote_control_id_internal (uint32_t id, bool notify_class_listeners) -{ - /* force IDs for master/monitor busses and prevent - any other route from accidentally getting these IDs - (i.e. legacy sessions) - */ - - if (is_master() && id != MasterBusRemoteControlID) { - id = MasterBusRemoteControlID; - } - - if (is_monitor() && id != MonitorBusRemoteControlID) { - id = MonitorBusRemoteControlID; - } - - if (id < 1) { - return; - } - - /* don't allow it to collide */ - - if (!is_master () && !is_monitor() && - (id == MasterBusRemoteControlID || id == MonitorBusRemoteControlID)) { - id += MonitorBusRemoteControlID; - } - - if (id != remote_control_id()) { - _remote_control_id = id; - RemoteControlIDChanged (); - - if (notify_class_listeners) { - RemoteControlIDChange (); - } - } -} - -uint32_t -Route::remote_control_id() const -{ - if (is_master()) { - return MasterBusRemoteControlID; - } - - if (is_monitor()) { - return MonitorBusRemoteControlID; - } - - return _remote_control_id; -} - -bool -Route::has_order_key () const -{ - return _has_order_key; -} - -uint32_t -Route::order_key () const -{ - return _order_key; -} - -void -Route::set_remote_control_id_explicit (uint32_t rid) -{ - if (is_master() || is_monitor() || is_auditioner()) { - /* hard-coded remote IDs, or no remote ID */ - return; - } - - if (_remote_control_id != rid) { - DEBUG_TRACE (DEBUG::OrderKeys, string_compose ("%1: set edit-based RID to %2\n", name(), rid)); - _remote_control_id = rid; - RemoteControlIDChanged (); /* EMIT SIGNAL (per-route) */ - } - - /* don't emit the class-level RID signal RemoteControlIDChange here, - leave that to the entity that changed the order key, so that we - don't get lots of emissions for no good reasons (e.g. when changing - all route order keys). - - See Session::sync_remote_id_from_order_keys() for the (primary|only) - spot where that is emitted. - */ -} - -void -Route::set_order_key (uint32_t n) -{ - _has_order_key = true; - - if (_order_key == n) { - return; - } - - _order_key = n; - - DEBUG_TRACE (DEBUG::OrderKeys, string_compose ("%1 order key set to %2\n", - name(), order_key ())); - - _session.set_dirty (); -} - string Route::ensure_track_or_route_name(string name, Session &session) { @@ -404,7 +291,7 @@ Route::set_trim (gain_t val, Controllable::GroupControlDisposition /* group over } void -Route::maybe_declick (BufferSet&, framecnt_t, int) +Route::maybe_declick (BufferSet&, samplecnt_t, int) { /* this is the "bus" implementation and they never declick. */ @@ -414,16 +301,16 @@ Route::maybe_declick (BufferSet&, framecnt_t, int) /** Process this route for one (sub) cycle (process thread) * * @param bufs Scratch buffers to use for the signal path - * @param start_frame Initial transport frame - * @param end_frame Final transport frame - * @param nframes Number of frames to output (to ports) + * @param start_sample Initial transport sample + * @param end_sample Final transport sample + * @param nframes Number of samples to output (to ports) * - * Note that (end_frame - start_frame) may not be equal to nframes when the + * Note that (end_sample - start_sample) may not be equal to nframes when the * transport speed isn't 1.0 (eg varispeed). */ void Route::process_output_buffers (BufferSet& bufs, - framepos_t start_frame, framepos_t end_frame, pframes_t nframes, + samplepos_t start_sample, samplepos_t end_sample, pframes_t nframes, int declick, bool gain_automation_ok) { /* Caller must hold process lock */ @@ -437,22 +324,21 @@ Route::process_output_buffers (BufferSet& bufs, return; } + automation_run (start_sample, nframes); + /* figure out if we're going to use gain automation */ if (gain_automation_ok) { _amp->set_gain_automation_buffer (_session.gain_automation_buffer ()); _amp->setup_gain_automation ( - start_frame + _signal_latency_at_amp_position, - end_frame + _signal_latency_at_amp_position, + start_sample + _amp->output_latency (), + end_sample + _amp->output_latency (), nframes); _trim->set_gain_automation_buffer (_session.trim_automation_buffer ()); _trim->setup_gain_automation ( - start_frame + _signal_latency_at_trim_position, - end_frame + _signal_latency_at_trim_position, + start_sample + _trim->output_latency (), + end_sample + _trim->output_latency (), nframes); - } else { - _amp->apply_gain_automation (false); - _trim->apply_gain_automation (false); } /* Tell main outs what to do about monitoring. We do this so that @@ -532,7 +418,8 @@ Route::process_output_buffers (BufferSet& bufs, /* set this to be true if the meter will already have been ::run() earlier */ bool const meter_already_run = metering_state() == MeteringInput; - framecnt_t latency = 0; + samplecnt_t latency = 0; + const double speed = (is_auditioner() ? 1.0 : _session.transport_speed ()); for (ProcessorList::const_iterator i = _processors.begin(); i != _processors.end(); ++i) { @@ -562,8 +449,14 @@ Route::process_output_buffers (BufferSet& bufs, if (boost::dynamic_pointer_cast(*i) != 0) { boost::dynamic_pointer_cast(*i)->set_delay_in(_signal_latency - latency); } + if (boost::dynamic_pointer_cast(*i) != 0) { + const samplecnt_t longest_session_latency = _initial_delay + _signal_latency; + boost::dynamic_pointer_cast(*i)->set_sidechain_latency ( + _initial_delay + latency, longest_session_latency - latency); + } - (*i)->run (bufs, start_frame - latency, end_frame - latency, nframes, *i != _processors.back()); + //cerr << name() << " run " << (*i)->name() << endl; + (*i)->run (bufs, start_sample - latency, end_sample - latency, speed, nframes, *i != _processors.back()); bufs.set_count ((*i)->output_streams()); if ((*i)->active ()) { @@ -573,7 +466,7 @@ Route::process_output_buffers (BufferSet& bufs, } void -Route::bounce_process (BufferSet& buffers, framepos_t start, framecnt_t nframes, +Route::bounce_process (BufferSet& buffers, samplepos_t start, samplecnt_t nframes, boost::shared_ptr endpoint, bool include_endpoint, bool for_export, bool for_freeze) { @@ -582,7 +475,7 @@ Route::bounce_process (BufferSet& buffers, framepos_t start, framecnt_t nframes, return; } - framecnt_t latency = bounce_get_latency(_amp, false, for_export, for_freeze); + samplecnt_t latency = bounce_get_latency(_amp, false, for_export, for_freeze); _amp->set_gain_automation_buffer (_session.gain_automation_buffer ()); _amp->setup_gain_automation (start - latency, start - latency + nframes, nframes); @@ -591,6 +484,7 @@ Route::bounce_process (BufferSet& buffers, framepos_t start, framecnt_t nframes, _trim->setup_gain_automation (start, start + nframes, nframes); latency = 0; + const double speed = _session.transport_speed (); for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) { if (!include_endpoint && (*i) == endpoint) { @@ -613,7 +507,7 @@ Route::bounce_process (BufferSet& buffers, framepos_t start, framecnt_t nframes, */ if ((*i) == _main_outs) { assert ((*i)->does_routing()); - (*i)->run (buffers, start - latency, start - latency + nframes, nframes, true); + (*i)->run (buffers, start - latency, start - latency + nframes, speed, nframes, true); buffers.set_count ((*i)->output_streams()); } @@ -621,7 +515,7 @@ Route::bounce_process (BufferSet& buffers, framepos_t start, framecnt_t nframes, * Also don't bother with metering. */ if (!(*i)->does_routing() && !boost::dynamic_pointer_cast(*i)) { - (*i)->run (buffers, start - latency, start - latency + nframes, nframes, true); + (*i)->run (buffers, start - latency, start - latency + nframes, 1.0, nframes, true); buffers.set_count ((*i)->output_streams()); latency += (*i)->signal_latency (); } @@ -632,11 +526,11 @@ Route::bounce_process (BufferSet& buffers, framepos_t start, framecnt_t nframes, } } -framecnt_t +samplecnt_t Route::bounce_get_latency (boost::shared_ptr endpoint, bool include_endpoint, bool for_export, bool for_freeze) const { - framecnt_t latency = 0; + samplecnt_t latency = 0; if (!endpoint && !include_endpoint) { return latency; } @@ -696,16 +590,16 @@ Route::n_process_buffers () } void -Route::monitor_run (framepos_t start_frame, framepos_t end_frame, pframes_t nframes, int declick) +Route::monitor_run (samplepos_t start_sample, samplepos_t end_sample, pframes_t nframes, int declick) { assert (is_monitor()); BufferSet& bufs (_session.get_route_buffers (n_process_buffers())); fill_buffers_with_input (bufs, _input, nframes); - passthru (bufs, start_frame, end_frame, nframes, declick); + passthru (bufs, start_sample, end_sample, nframes, declick, true); } void -Route::passthru (BufferSet& bufs, framepos_t start_frame, framepos_t end_frame, pframes_t nframes, int declick) +Route::passthru (BufferSet& bufs, samplepos_t start_sample, samplepos_t end_sample, pframes_t nframes, int declick, bool gain_automation_ok) { _silent = false; @@ -719,24 +613,32 @@ Route::passthru (BufferSet& bufs, framepos_t start_frame, framepos_t end_frame, bufs.silence (nframes, 0); } - write_out_of_band_data (bufs, start_frame, end_frame, nframes); - process_output_buffers (bufs, start_frame, end_frame, nframes, declick, true); + /* append immediate messages to the first MIDI buffer (thus sending it to the first output port) */ + + write_out_of_band_data (bufs, start_sample, end_sample, nframes); + + /* run processor chain */ + + process_output_buffers (bufs, start_sample, end_sample, nframes, declick, gain_automation_ok); } void -Route::passthru_silence (framepos_t start_frame, framepos_t end_frame, pframes_t nframes, int declick) +Route::passthru_silence (samplepos_t start_sample, samplepos_t end_sample, pframes_t nframes, int declick) { BufferSet& bufs (_session.get_route_buffers (n_process_buffers(), true)); 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, declick, false); + write_out_of_band_data (bufs, start_sample, end_sample, nframes); + process_output_buffers (bufs, start_sample, end_sample, nframes, declick, false); } void Route::set_listen (bool yn) { if (_monitor_send) { + if (_monitor_send->active() == yn) { + return; + } if (yn) { _monitor_send->activate (); } else { @@ -946,6 +848,7 @@ Route::add_processor_from_xml_2X (const XMLNode& node, int version) if (prop->value() == "ladspa" || prop->value() == "Ladspa" || prop->value() == "lv2" || prop->value() == "windows-vst" || + prop->value() == "mac-vst" || prop->value() == "lxvst" || prop->value() == "audiounit") { @@ -981,7 +884,7 @@ Route::add_processor_from_xml_2X (const XMLNode& node, int version) //A2 uses the "active" flag in the toplevel redirect node, not in the child plugin/IO if (i != children.end()) { if ((prop = (*i)->property (X_("active"))) != 0) { - if ( string_is_affirmative (prop->value()) && (!_session.get_bypass_all_loaded_plugins () || !processor->display_to_user () ) ) + if ( string_to (prop->value()) && (!_session.get_bypass_all_loaded_plugins () || !processor->display_to_user () ) ) processor->activate(); else processor->deactivate(); @@ -1010,6 +913,13 @@ int Route::add_processors (const ProcessorList& others, boost::shared_ptr before, ProcessorStreams* err) { ProcessorList::iterator loc; + boost::shared_ptr fanout; + + if (g_atomic_int_get (&_pending_process_reorder)) { + /* we need to flush any pending re-order changes */ + Glib::Threads::Mutex::Lock lx (AudioEngine::instance()->process_lock ()); + apply_processor_changes_rt (); + } if (before) { loc = find(_processors.begin(), _processors.end(), before); @@ -1021,10 +931,6 @@ Route::add_processors (const ProcessorList& others, boost::shared_ptr loc = _processors.end (); } - if (!AudioEngine::instance()->connected()) { - return 1; - } - if (others.empty()) { return 0; } @@ -1067,8 +973,9 @@ Route::add_processors (const ProcessorList& others, boost::shared_ptr flags &= mask; if (flags != None) { - boost::optional rv = PluginSetup (shared_from_this (), pi, flags); /* EMIT SIGNAL */ - switch (rv.get_value_or (0)) { + boost::optional rv = PluginSetup (boost::dynamic_pointer_cast(shared_from_this ()), pi, flags); /* EMIT SIGNAL */ + int mode = rv.get_value_or (0); + switch (mode & 3) { case 1: to_skip.push_back (*i); // don't add this one; break; @@ -1079,6 +986,9 @@ Route::add_processors (const ProcessorList& others, boost::shared_ptr default: break; } + if ((mode & 5) == 4) { + fanout = pi; + } } } @@ -1129,7 +1039,12 @@ Route::add_processors (const ProcessorList& others, boost::shared_ptr } } + if (pi && pi->has_sidechain ()) { + pi->sidechain_input ()->changed.connect_same_thread (*this, boost::bind (&Route::sidechain_change_handler, this, _1, _2)); + } + if ((*i)->active()) { + // emit ActiveChanged() and latency_changed() if needed (*i)->activate (); } @@ -1160,6 +1075,11 @@ Route::add_processors (const ProcessorList& others, boost::shared_ptr processors_changed (RouteProcessorChange ()); /* EMIT SIGNAL */ set_processor_positions (); + if (fanout && fanout->configured () + && fanout->output_streams().n_audio() > 2 + && boost::dynamic_pointer_cast (the_instrument ()) == fanout) { + fan_out (); /* EMIT SIGNAL */ + } return 0; } @@ -1188,7 +1108,7 @@ Route::disable_processors (Placement p) placement_range(p, start, end); for (ProcessorList::iterator i = start; i != end; ++i) { - (*i)->deactivate (); + (*i)->enable (false); } _session.set_dirty (); @@ -1202,7 +1122,7 @@ Route::disable_processors () Glib::Threads::RWLock::ReaderLock lm (_processor_lock); for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) { - (*i)->deactivate (); + (*i)->enable (false); } _session.set_dirty (); @@ -1221,7 +1141,7 @@ Route::disable_plugins (Placement p) for (ProcessorList::iterator i = start; i != end; ++i) { if (boost::dynamic_pointer_cast (*i)) { - (*i)->deactivate (); + (*i)->enable (false); } } @@ -1237,7 +1157,7 @@ Route::disable_plugins () for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) { if (boost::dynamic_pointer_cast (*i)) { - (*i)->deactivate (); + (*i)->enable (false); } } @@ -1260,9 +1180,17 @@ Route::ab_plugins (bool forward) if (!boost::dynamic_pointer_cast (*i)) { continue; } + if (!(*i)->display_to_user ()) { + continue; + } +#ifdef MIXBUS + if (boost::dynamic_pointer_cast (*i)->is_channelstrip()) { + continue; + } +#endif - if ((*i)->active()) { - (*i)->deactivate (); + if ((*i)->enabled ()) { + (*i)->enable (false); (*i)->set_next_ab_is_active (true); } else { (*i)->set_next_ab_is_active (false); @@ -1274,16 +1202,19 @@ Route::ab_plugins (bool forward) /* backward = if the redirect was marked to go active on the next ab, do so */ for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) { - if (!boost::dynamic_pointer_cast (*i)) { continue; } - - if ((*i)->get_next_ab_is_active()) { - (*i)->activate (); - } else { - (*i)->deactivate (); + if (!(*i)->display_to_user ()) { + continue; + } +#ifdef MIXBUS + if (boost::dynamic_pointer_cast (*i)->is_channelstrip()) { + continue; } +#endif + + (*i)->enable ((*i)->get_next_ab_is_active ()); } } @@ -1306,6 +1237,7 @@ Route::clear_processors (Placement p) _session.set_deletion_in_progress(); } + ProcessorList old_list = _processors; { Glib::Threads::Mutex::Lock lx (AudioEngine::instance()->process_lock ()); Glib::Threads::RWLock::WriterLock lm (_processor_lock); @@ -1354,13 +1286,15 @@ Route::clear_processors (Placement p) _processors = new_list; configure_processors_unlocked (&err, &lm); // this can't fail } + /* drop references w/o process-lock (I/O procs may re-take it in ~IO() */ + old_list.clear (); processor_max_streams.reset(); _have_internal_generator = false; - processors_changed (RouteProcessorChange ()); /* EMIT SIGNAL */ + reset_instrument_info (); set_processor_positions (); - reset_instrument_info (); + processors_changed (RouteProcessorChange ()); /* EMIT SIGNAL */ if (!already_deleting) { _session.clear_deletion_in_progress(); @@ -1524,7 +1458,7 @@ Route::replace_processor (boost::shared_ptr old, boost::shared_ptractive (); + bool enable = old->enabled (); for (i = _processors.begin(); i != _processors.end(); ) { if (*i == old) { @@ -1568,7 +1502,7 @@ Route::replace_processor (boost::shared_ptr old, boost::shared_ptractivate (); + sub->enable (true); } sub->ActiveChanged.connect_same_thread (*this, boost::bind (&Session::update_latency_compensation, &_session, false)); @@ -1737,8 +1671,8 @@ Route::try_configure_processors_unlocked (ChanCount in, ProcessorStreams* err) if (boost::dynamic_pointer_cast (*p) && boost::dynamic_pointer_cast (*p)->role() == Delivery::Main - && !(is_monitor() || is_auditioner()) - && ( _strict_io || Profile->get_mixbus ())) { + && !is_auditioner() + && (is_monitor() || _strict_io || Profile->get_mixbus ())) { /* with strict I/O the panner + output are forced to * follow the last processor's output. * @@ -1750,8 +1684,9 @@ Route::try_configure_processors_unlocked (ChanCount in, ProcessorStreams* err) * Delivery::configure_io() will do the actual removal * by calling _output->ensure_io() */ - if (!is_master() && _session.master_out ()) { - /* ..but at least as many as there are master-inputs */ + if (!is_master() && _session.master_out () && in.n_audio() > 0) { + /* ..but at least as many as there are master-inputs, if + * the delivery is dealing with audio */ // XXX this may need special-casing for mixbus (master-outputs) // and should maybe be a preference anyway ?! out = ChanCount::max (in, _session.master_out ()->n_inputs ()); @@ -1766,13 +1701,21 @@ Route::try_configure_processors_unlocked (ChanCount in, ProcessorStreams* err) if (is_monitor()) { // restriction for Monitor Section Processors if (in.n_audio() != out.n_audio() || out.n_midi() > 0) { - /* do not allow to add/remove channels (for now) - * The Monitor follows the master-bus and has no panner (unpan) - * but do allow processors with midi-in to be added (e.g VSTs with control that - * will remain unconnected) + /* Note: The Monitor follows the master-bus and has no panner. + * + * The general idea is to only allow plugins that retain the channel-count + * and plugins with MIDI in (e.g VSTs with control that will remain unconnected). + * Then again 5.1 in, monitor stereo is a valid use-case. + * + * and worse: we only refuse adding plugins *here*. + * + * 1) stereo-master, stereo-mon, add a stereo-plugin, OK + * 2) change master-bus, add a channel + * 2a) monitor-secion follows + * 3) monitor processors fail to re-reconfigure (stereo plugin) + * 4) re-load session, monitor-processor remains unconfigured, crash. */ - DEBUG_TRACE (DEBUG::Processors, "Monitor: Channel configuration not allowed.\n"); - return list > (); + DEBUG_TRACE (DEBUG::Processors, "Monitor: Channel configuration change.\n"); } if (boost::dynamic_pointer_cast (*p)) { // internal sends make no sense, only feedback @@ -1876,6 +1819,7 @@ Route::configure_processors_unlocked (ProcessorStreams* err, Glib::Threads::RWLo lm->acquire (); return -1; } + processor_max_streams = ChanCount::max(processor_max_streams, c->first); processor_max_streams = ChanCount::max(processor_max_streams, c->second); @@ -1942,12 +1886,15 @@ Route::all_visible_processors_active (bool state) if (!(*i)->display_to_user() || boost::dynamic_pointer_cast (*i)) { continue; } - - if (state) { - (*i)->activate (); - } else { - (*i)->deactivate (); +#ifdef MIXBUS + boost::shared_ptr pi; + if (0 != (pi = boost::dynamic_pointer_cast(*i))) { + if (pi->is_channelstrip ()) { + continue; + } } +#endif + (*i)->enable (state); } _session.set_dirty (); @@ -2070,6 +2017,35 @@ Route::apply_processor_order (const ProcessorList& new_order) maybe_note_meter_position (); } +void +Route::move_instrument_down (bool postfader) +{ + Glib::Threads::RWLock::ReaderLock lm (_processor_lock); + ProcessorList new_order; + boost::shared_ptr instrument; + for (ProcessorList::const_iterator i = _processors.begin(); i != _processors.end(); ++i) { + boost::shared_ptr pi = boost::dynamic_pointer_cast(*i); + if (pi && pi->plugin ()->get_info ()->is_instrument ()) { + instrument = *i; + } else if (instrument && *i == _amp) { + if (postfader) { + new_order.push_back (*i); + new_order.push_back (instrument); + } else { + new_order.push_back (instrument); + new_order.push_back (*i); + } + } else { + new_order.push_back (*i); + } + } + if (!instrument) { + return; + } + lm.release (); + reorder_processors (new_order, 0); +} + int Route::reorder_processors (const ProcessorList& new_order, ProcessorStreams* err) { @@ -2123,6 +2099,11 @@ Route::reorder_processors (const ProcessorList& new_order, ProcessorStreams* err g_atomic_int_set (&_pending_process_reorder, 1); } + /* update processor input/output latency + * (total signal_latency does not change) + */ + update_signal_latency (true); + return 0; } @@ -2341,40 +2322,31 @@ Route::get_template() XMLNode& Route::state(bool full_state) { - LocaleGuard lg; if (!_session._template_state_dir.empty()) { - assert (!full_state); // only for templates foreach_processor (sigc::bind (sigc::mem_fun (*this, &Route::set_plugin_state_dir), _session._template_state_dir)); } XMLNode *node = new XMLNode("Route"); ProcessorList::iterator i; - char buf[32]; - id().print (buf, sizeof (buf)); - node->add_property("id", buf); - node->add_property ("name", _name); - node->add_property("default-type", _default_type.to_string()); - node->add_property ("strict-io", _strict_io); + node->set_property (X_("id"), id ()); + node->set_property (X_("name"), name()); + node->set_property (X_("default-type"), _default_type); + node->set_property (X_("strict-io"), _strict_io); - if (_flags) { - node->add_property("flags", enum_2_string (_flags)); - } + node->add_child_nocopy (_presentation_info.get_state()); - node->add_property("active", _active?"yes":"no"); - string p; - node->add_property("denormal-protection", _denormal_protection?"yes":"no"); - node->add_property("meter-point", enum_2_string (_meter_point)); + node->set_property (X_("active"), _active); + node->set_property (X_("denormal-protection"), _denormal_protection); + node->set_property (X_("meter-point"), _meter_point); + node->set_property (X_("disk-io-point"), _disk_io_point); - node->add_property("meter-type", enum_2_string (_meter_type)); + node->set_property (X_("meter-type"), _meter_type); if (_route_group) { - node->add_property("route-group", _route_group->name()); + node->set_property (X_("route-group"), _route_group->name()); } - snprintf (buf, sizeof (buf), "%d", _order_key); - node->add_property ("order-key", buf); - node->add_child_nocopy (_solo_control->get_state ()); node->add_child_nocopy (_solo_isolate_control->get_state ()); node->add_child_nocopy (_solo_safe_control->get_state ()); @@ -2390,11 +2362,6 @@ Route::state(bool full_state) node->add_child_nocopy (Automatable::get_automation_xml_state ()); } - XMLNode* remote_control_node = new XMLNode (X_("RemoteControl")); - snprintf (buf, sizeof (buf), "%d", _remote_control_id); - remote_control_node->add_property (X_("id"), buf); - node->add_child_nocopy (*remote_control_node); - if (_comment.length()) { XMLNode *cmt = node->add_child ("Comment"); cmt->add_content (_comment); @@ -2436,8 +2403,7 @@ Route::state(bool full_state) if (_custom_meter_position_noted) { boost::shared_ptr after = _processor_after_last_custom_meter.lock (); if (after) { - after->id().print (buf, sizeof (buf)); - node->add_property (X_("processor-after-last-custom-meter"), buf); + node->set_property (X_("processor-after-last-custom-meter"), after->id()); } } @@ -2460,33 +2426,23 @@ Route::set_state (const XMLNode& node, int version) XMLNodeList nlist; XMLNodeConstIterator niter; XMLNode *child; - XMLProperty const * prop; 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()); + std::string route_name; + if (node.get_property (X_("name"), route_name)) { + Route::set_name (route_name); } set_id (node); _initial_io_setup = true; - if ((prop = node.property (X_("flags"))) != 0) { - _flags = Flag (string_2_enum (prop->value(), _flags)); - } else { - _flags = Flag (0); - } - - if ((prop = node.property (X_("strict-io"))) != 0) { - _strict_io = string_is_affirmative (prop->value()); - } + Stripable::set_state (node, version); - if (!can_solo()) { - _mute_master->set_solo_ignore (true); - } + node.get_property (X_("strict-io"), _strict_io); if (is_monitor()) { /* monitor bus does not get a panner, but if (re)created @@ -2508,13 +2464,14 @@ Route::set_state (const XMLNode& node, int version) child = *niter; if (child->name() == IO::state_node_name) { - if ((prop = child->property (X_("direction"))) == 0) { + std::string direction; + if (!child->get_property (X_("direction"), direction)) { continue; } - if (prop->value() == "Input") { + if (direction == "Input") { _input->set_state (*child, version); - } else if (prop->value() == "Output") { + } else if (direction == "Output") { _output->set_state (*child, version); } @@ -2526,39 +2483,32 @@ Route::set_state (const XMLNode& node, int version) } else { warning << string_compose (_("Pannable state found for route (%1) without a panner!"), name()) << endmsg; } - } else if (child->name() == Controllable::xml_node_name) { - if ((prop = child->property (X_("name"))) == 0) { - continue; - } - - if (prop->value() == _gain_control->name()) { - _gain_control->set_state (*child, version); - } else if (prop->value() == _solo_control->name()) { - _solo_control->set_state (*child, version); - } else if (prop->value() == _solo_safe_control->name()) { - _solo_safe_control->set_state (*child, version); - } else if (prop->value() == _solo_isolate_control->name()) { - _solo_isolate_control->set_state (*child, version); - } else if (prop->value() == _solo_control->name()) { - _mute_control->set_state (*child, version); - } } else if (child->name() == Slavable::xml_node_name) { Slavable::set_state (*child, version); } } - if ((prop = node.property (X_("meter-point"))) != 0) { - MeterPoint mp = MeterPoint (string_2_enum (prop->value (), _meter_point)); + MeterPoint mp; + if (node.get_property (X_("meter-point"), mp)) { set_meter_point (mp, true); if (_meter) { _meter->set_display_to_user (_meter_point == MeterCustom); } } - if ((prop = node.property (X_("meter-type"))) != 0) { - _meter_type = MeterType (string_2_enum (prop->value (), _meter_type)); + DiskIOPoint diop; + if (node.get_property (X_("disk-io-point"), diop)) { + if (_disk_writer) { + _disk_writer->set_display_to_user (diop == DiskIOCustom); + } + if (_disk_reader) { + _disk_reader->set_display_to_user (diop == DiskIOCustom); + } + set_disk_io_point (diop); } + node.get_property (X_("meter-type"), _meter_type); + _initial_io_setup = false; set_processor_state (processor_state); @@ -2566,57 +2516,25 @@ Route::set_state (const XMLNode& node, int version) // this looks up the internal instrument in processors reset_instrument_info(); - 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()); - set_active (yn, this); + bool denormal_protection; + if (node.get_property (X_("denormal-protection"), denormal_protection)) { + set_denormal_protection (denormal_protection); } - if ((prop = node.property (X_("order-key"))) != 0) { // New order key (no separate mixer/editor ordering) - set_order_key (atoi(prop->value())); + /* convert old 3001 state */ + std::string phase_invert_str; + if (node.get_property (X_("phase-invert"), phase_invert_str)) { + _phase_control->set_phase_invert (boost::dynamic_bitset<> (phase_invert_str)); } - if ((prop = node.property (X_("order-keys"))) != 0) { // Deprecated order keys - - int32_t 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 << string_compose (_("badly formed order key string in state file! [%1] ... ignored."), remaining) - << endmsg; - } else { - if (sscanf (remaining.substr (equal+1).c_str(), "%d", &n) != 1) { - error << string_compose (_("badly formed order key string in state file! [%1] ... ignored."), remaining) - << endmsg; - } else { - string keyname = remaining.substr (0, equal); - - if ((keyname == "EditorSort") || (keyname == "editor")) { - cerr << "Setting " << name() << " order key to " << n << " using saved Editor order." << endl; - set_order_key (n); - } - } - } - - colon = remaining.find_first_of (':'); - - if (colon != string::npos) { - remaining = remaining.substr (colon+1); - } else { - break; - } - } + bool is_active; + if (node.get_property (X_("active"), is_active)) { + set_active (is_active, this); } - if ((prop = node.property (X_("processor-after-last-custom-meter"))) != 0) { - PBD::ID id (prop->value ()); + std::string id_string; + if (node.get_property (X_("processor-after-last-custom-meter"), id_string)) { + PBD::ID id (id_string); Glib::Threads::RWLock::ReaderLock lm (_processor_lock); ProcessorList::const_iterator i = _processors.begin (); while (i != _processors.end() && (*i)->id() != id) { @@ -2639,20 +2557,31 @@ Route::set_state (const XMLNode& node, int version) XMLNode *cmt = *(child->children().begin()); _comment = cmt->content(); - } else if (child->name() == Controllable::xml_node_name && (prop = child->property("name")) != 0) { - if (prop->value() == "solo") { - _solo_control->set_state (*child, version); - } else if (prop->value() == "mute") { - _mute_control->set_state (*child, version); + } else if (child->name() == Controllable::xml_node_name) { + std::string control_name; + if (!child->get_property (X_("name"), control_name)) { + continue; } - } 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_internal (x); + if (control_name == _solo_control->name()) { + _solo_control->set_state (*child, version); + } else if (control_name == _solo_safe_control->name()) { + _solo_safe_control->set_state (*child, version); + } else if (control_name == _solo_isolate_control->name()) { + _solo_isolate_control->set_state (*child, version); + } else if (control_name == _mute_control->name()) { + _mute_control->set_state (*child, version); + } else if (control_name == _phase_control->name()) { + _phase_control->set_state (*child, version); + } else { + Evoral::Parameter p = EventTypeMap::instance().from_symbol (control_name); + if (p.type () >= MidiCCAutomation && p.type () < MidiSystemExclusiveAutomation) { + boost::shared_ptr ac = automation_control (p, true); + if (ac) { + ac->set_state (*child, version); + } + } } - } else if (child->name() == MuteMaster::xml_node_name) { _mute_master->set_state (*child, version); @@ -2684,26 +2613,16 @@ Route::set_state_2X (const XMLNode& node, int version) return -1; } - if ((prop = node.property (X_("flags"))) != 0) { - string f = prop->value (); - boost::replace_all (f, "ControlOut", "MonitorOut"); - _flags = Flag (string_2_enum (f, _flags)); - } else { - _flags = Flag (0); - } - - if (is_master() || is_monitor() || is_auditioner()) { - _mute_master->set_solo_ignore (true); - } + Stripable::set_state (node, version); if ((prop = node.property (X_("denormal-protection"))) != 0) { - set_denormal_protection (string_is_affirmative (prop->value())); + set_denormal_protection (string_to (prop->value())); } if ((prop = node.property (X_("muted"))) != 0) { bool first = true; - bool muted = string_is_affirmative (prop->value()); + bool muted = string_to (prop->value()); if (muted) { @@ -2711,7 +2630,7 @@ Route::set_state_2X (const XMLNode& node, int version) if ((prop = node.property (X_("mute-affects-pre-fader"))) != 0) { - if (string_is_affirmative (prop->value())){ + if (string_to (prop->value())){ mute_point = mute_point + "PreFader"; first = false; } @@ -2719,7 +2638,7 @@ Route::set_state_2X (const XMLNode& node, int version) if ((prop = node.property (X_("mute-affects-post-fader"))) != 0) { - if (string_is_affirmative (prop->value())){ + if (string_to (prop->value())){ if (!first) { mute_point = mute_point + ","; @@ -2732,7 +2651,7 @@ Route::set_state_2X (const XMLNode& node, int version) if ((prop = node.property (X_("mute-affects-control-outs"))) != 0) { - if (string_is_affirmative (prop->value())){ + if (string_to (prop->value())){ if (!first) { mute_point = mute_point + ","; @@ -2745,7 +2664,7 @@ Route::set_state_2X (const XMLNode& node, int version) if ((prop = node.property (X_("mute-affects-main-outs"))) != 0) { - if (string_is_affirmative (prop->value())){ + if (string_to (prop->value())){ if (!first) { mute_point = mute_point + ","; @@ -2764,46 +2683,6 @@ Route::set_state_2X (const XMLNode& node, int version) _meter_point = MeterPoint (string_2_enum (prop->value (), _meter_point)); } - /* do not carry over edit/mix groups from 2.X because (a) its hard (b) they - don't mean the same thing. - */ - - if ((prop = node.property (X_("order-keys"))) != 0) { - - int32_t 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 << string_compose (_("badly formed order key string in state file! [%1] ... ignored."), remaining) - << endmsg; - } else { - if (sscanf (remaining.substr (equal+1).c_str(), "%d", &n) != 1) { - error << string_compose (_("badly formed order key string in state file! [%1] ... ignored."), remaining) - << endmsg; - } else { - string keyname = remaining.substr (0, equal); - - if (keyname == "EditorSort" || keyname == "editor") { - info << string_compose(_("Converting deprecated order key for %1 using Editor order %2"), name (), n) << endmsg; - set_order_key (n); - } - } - } - - colon = remaining.find_first_of (':'); - - if (colon != string::npos) { - remaining = remaining.substr (colon+1); - } else { - break; - } - } - } - /* IOs */ nlist = node.children (); @@ -2827,7 +2706,7 @@ Route::set_state_2X (const XMLNode& node, int version) set_id (*child); if ((prop = child->property (X_("active"))) != 0) { - bool yn = string_is_affirmative (prop->value()); + bool yn = string_to (prop->value()); _active = !yn; // force switch set_active (yn, this); } @@ -2893,13 +2772,6 @@ Route::set_state_2X (const XMLNode& node, int version) _mute_control->set_state (*child, version); } - } 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_internal (x); - } - } } @@ -2974,119 +2846,140 @@ Route::set_processor_state (const XMLNode& node) } else if (prop->value() == "capture") { /* CapturingProcessor should never be restored, it's always added explicitly when needed */ + } else if (prop->value() == "diskreader" && _disk_reader) { + _disk_reader->set_state (**niter, Stateful::current_state_version); + new_order.push_back (_disk_reader); + } else if (prop->value() == "diskwriter" && _disk_writer) { + _disk_writer->set_state (**niter, Stateful::current_state_version); + new_order.push_back (_disk_writer); } else { - ProcessorList::iterator o; + set_processor_state (**niter, prop, new_order, must_configure); + } + } + + ProcessorList old_list = _processors; // keep a copy + { + Glib::Threads::Mutex::Lock lx (AudioEngine::instance()->process_lock ()); + Glib::Threads::RWLock::WriterLock lm (_processor_lock); + /* re-assign _processors w/o process-lock. + * if there's an IO-processor present in _processors but + * not in new_order, it will be deleted and ~IO takes + * a process lock. + */ + _processors = new_order; + + if (must_configure) { + configure_processors_unlocked (0, &lm); + } - for (o = _processors.begin(); o != _processors.end(); ++o) { - XMLProperty const * id_prop = (*niter)->property(X_("id")); - if (id_prop && (*o)->id() == id_prop->value()) { - (*o)->set_state (**niter, Stateful::current_state_version); - new_order.push_back (*o); + for (ProcessorList::const_iterator i = _processors.begin(); i != _processors.end(); ++i) { + + (*i)->set_owner (this); + (*i)->ActiveChanged.connect_same_thread (*this, boost::bind (&Session::update_latency_compensation, &_session, false)); + + boost::shared_ptr pi; + + if ((pi = boost::dynamic_pointer_cast(*i)) != 0) { + if (pi->has_no_inputs ()) { + _have_internal_generator = true; break; } } + } + } + /* drop references w/o process-lock (I/O procs may re-take it in ~IO() */ + old_list.clear (); - // If the processor (*niter) is not on the route then create it + reset_instrument_info (); + processors_changed (RouteProcessorChange ()); /* EMIT SIGNAL */ + set_processor_positions (); +} - if (o == _processors.end()) { +bool +Route::set_processor_state (XMLNode const & node, XMLProperty const* prop, ProcessorList& new_order, bool& must_configure) +{ + ProcessorList::iterator o; - boost::shared_ptr processor; + for (o = _processors.begin(); o != _processors.end(); ++o) { + XMLProperty const * id_prop = node.property(X_("id")); + if (id_prop && (*o)->id() == id_prop->value()) { + (*o)->set_state (node, Stateful::current_state_version); + new_order.push_back (*o); + break; + } + } - if (prop->value() == "intsend") { + // If the processor (node) is not on the route then create it - processor.reset (new InternalSend (_session, _pannable, _mute_master, boost::dynamic_pointer_cast(shared_from_this()), boost::shared_ptr(), Delivery::Aux, true)); + if (o == _processors.end()) { - } else if (prop->value() == "ladspa" || prop->value() == "Ladspa" || - prop->value() == "lv2" || - prop->value() == "windows-vst" || - prop->value() == "lxvst" || - prop->value() == "luaproc" || - prop->value() == "audiounit") { + boost::shared_ptr processor; - if (_session.get_disable_all_loaded_plugins ()) { - processor.reset (new UnknownProcessor (_session, **niter)); - } else { - processor.reset (new PluginInsert (_session)); - processor->set_owner (this); - if (_strict_io) { - boost::shared_ptr pi = boost::dynamic_pointer_cast(processor); - pi->set_strict_io (true); - } - - } - } else if (prop->value() == "port") { - - processor.reset (new PortInsert (_session, _pannable, _mute_master)); + if (prop->value() == "intsend") { - } else if (prop->value() == "send") { + processor.reset (new InternalSend (_session, _pannable, _mute_master, boost::dynamic_pointer_cast(shared_from_this()), boost::shared_ptr(), Delivery::Aux, true)); - processor.reset (new Send (_session, _pannable, _mute_master, Delivery::Send, true)); - boost::shared_ptr send = boost::dynamic_pointer_cast (processor); - send->SelfDestruct.connect_same_thread (*this, - boost::bind (&Route::processor_selfdestruct, this, boost::weak_ptr (processor))); + } else if (prop->value() == "ladspa" || prop->value() == "Ladspa" || + prop->value() == "lv2" || + prop->value() == "windows-vst" || + prop->value() == "mac-vst" || + prop->value() == "lxvst" || + prop->value() == "luaproc" || + prop->value() == "audiounit") { - } else { - error << string_compose(_("unknown Processor type \"%1\"; ignored"), prop->value()) << endmsg; - continue; + if (_session.get_disable_all_loaded_plugins ()) { + processor.reset (new UnknownProcessor (_session, node)); + } else { + processor.reset (new PluginInsert (_session)); + processor->set_owner (this); + if (_strict_io) { + boost::shared_ptr pi = boost::dynamic_pointer_cast(processor); + pi->set_strict_io (true); } - if (processor->set_state (**niter, Stateful::current_state_version) != 0) { - /* This processor could not be configured. Turn it into a UnknownProcessor */ - processor.reset (new UnknownProcessor (_session, **niter)); - } + } + } else if (prop->value() == "port") { - /* subscribe to Sidechain IO changes */ - boost::shared_ptr pi = boost::dynamic_pointer_cast (processor); - if (pi && pi->has_sidechain ()) { - pi->sidechain_input ()->changed.connect_same_thread (*this, boost::bind (&Route::sidechain_change_handler, this, _1, _2)); - } + processor.reset (new PortInsert (_session, _pannable, _mute_master)); - /* we have to note the monitor send here, otherwise a new one will be created - and the state of this one will be lost. - */ - boost::shared_ptr isend = boost::dynamic_pointer_cast (processor); - if (isend && isend->role() == Delivery::Listen) { - _monitor_send = isend; - } + } else if (prop->value() == "send") { - /* it doesn't matter if invisible processors are added here, as they - will be sorted out by setup_invisible_processors () shortly. - */ + processor.reset (new Send (_session, _pannable, _mute_master, Delivery::Send, true)); + boost::shared_ptr send = boost::dynamic_pointer_cast (processor); + send->SelfDestruct.connect_same_thread (*this, + boost::bind (&Route::processor_selfdestruct, this, boost::weak_ptr (processor))); - new_order.push_back (processor); - must_configure = true; - } + } else { + return false; } - } - { - Glib::Threads::Mutex::Lock lx (AudioEngine::instance()->process_lock ()); - Glib::Threads::RWLock::WriterLock lm (_processor_lock); - _processors = new_order; - - if (must_configure) { - configure_processors_unlocked (0, &lm); + if (processor->set_state (node, Stateful::current_state_version) != 0) { + /* This processor could not be configured. Turn it into a UnknownProcessor */ + processor.reset (new UnknownProcessor (_session, node)); } - for (ProcessorList::const_iterator i = _processors.begin(); i != _processors.end(); ++i) { + /* subscribe to Sidechain IO changes */ + boost::shared_ptr pi = boost::dynamic_pointer_cast (processor); + if (pi && pi->has_sidechain ()) { + pi->sidechain_input ()->changed.connect_same_thread (*this, boost::bind (&Route::sidechain_change_handler, this, _1, _2)); + } - (*i)->set_owner (this); - (*i)->ActiveChanged.connect_same_thread (*this, boost::bind (&Session::update_latency_compensation, &_session, false)); + /* we have to note the monitor send here, otherwise a new one will be created + and the state of this one will be lost. + */ + boost::shared_ptr isend = boost::dynamic_pointer_cast (processor); + if (isend && isend->role() == Delivery::Listen) { + _monitor_send = isend; + } - boost::shared_ptr pi; + /* it doesn't matter if invisible processors are added here, as they + will be sorted out by setup_invisible_processors () shortly. + */ - if ((pi = boost::dynamic_pointer_cast(*i)) != 0) { - if (pi->has_no_inputs ()) { - _have_internal_generator = true; - break; - } - } - } + new_order.push_back (processor); + must_configure = true; } - - reset_instrument_info (); - processors_changed (RouteProcessorChange ()); /* EMIT SIGNAL */ - set_processor_positions (); + return true; } void @@ -3097,7 +2990,7 @@ Route::curve_reallocate () } void -Route::silence (framecnt_t nframes) +Route::silence (samplecnt_t nframes) { Glib::Threads::RWLock::ReaderLock lm (_processor_lock, Glib::Threads::TRY_LOCK); if (!lm.locked()) { @@ -3108,23 +3001,30 @@ Route::silence (framecnt_t nframes) } void -Route::silence_unlocked (framecnt_t nframes) +Route::silence_unlocked (samplecnt_t nframes) { /* Must be called with the processor lock held */ + const samplepos_t now = _session.transport_sample (); + if (!_silent) { _output->silence (nframes); + // update owned automated controllables + automation_run (now, nframes); + 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 + /* evaluate automated automation controls */ + pi->automation_run (now, nframes); + /* skip plugins, they don't need anything when we're not active */ continue; } - (*i)->silence (nframes); + (*i)->silence (nframes, now); } if (nframes == _session.get_block_size()) { @@ -3219,8 +3119,7 @@ Route::add_aux_send (boost::shared_ptr route, boost::shared_ptrprocess_lock ()); - boost::shared_ptr sendpan (new Pannable (_session)); - listener.reset (new InternalSend (_session, sendpan, _mute_master, boost::dynamic_pointer_cast(shared_from_this()), route, Delivery::Aux)); + listener.reset (new InternalSend (_session, _pannable, _mute_master, boost::dynamic_pointer_cast(shared_from_this()), route, Delivery::Aux)); } add_processor (listener, before); @@ -3398,6 +3297,11 @@ Route::direct_feeds_according_to_reality (boost::shared_ptr other, bool* if (iop != 0) { boost::shared_ptr iop_out = iop->output(); + if (other.get() == this && iop_out && iop->input() && iop_out->connected_to (iop->input())) { + // TODO this needs a delaylines in the Insert to align connections (!) + DEBUG_TRACE (DEBUG::Graph, string_compose ("\tIOP %1 does feed its own return (%2)\n", iop->name(), other->name())); + continue; + } if ((iop_out && other->all_inputs().fed_by (iop_out)) || iop->feeds (other)) { DEBUG_TRACE (DEBUG::Graph, string_compose ("\tIOP %1 does feed %2\n", iop->name(), other->name())); if (via_send_only) { @@ -3420,37 +3324,37 @@ Route::direct_feeds_according_to_reality (boost::shared_ptr other, bool* bool Route::direct_feeds_according_to_graph (boost::shared_ptr other, bool* via_send_only) { - return _session._current_route_graph.has (shared_from_this (), other, via_send_only); + return _session._current_route_graph.has (boost::dynamic_pointer_cast (shared_from_this ()), other, via_send_only); } bool Route::feeds_according_to_graph (boost::shared_ptr other) { - return _session._current_route_graph.feeds (shared_from_this (), other); + return _session._current_route_graph.feeds (boost::dynamic_pointer_cast (shared_from_this ()), other); } /** Called from the (non-realtime) butler thread when the transport is stopped */ void -Route::nonrealtime_handle_transport_stopped (bool /*abort_ignored*/, bool /*did_locate*/, bool can_flush_processors) +Route::non_realtime_transport_stop (samplepos_t now, bool flush) { - framepos_t now = _session.transport_frame(); - { Glib::Threads::RWLock::ReaderLock lm (_processor_lock); - Automatable::transport_stopped (now); + Automatable::non_realtime_transport_stop (now, flush); for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) { - if (!_have_internal_generator && (Config->get_plugins_stop_with_transport() && can_flush_processors)) { + if (!_have_internal_generator && (Config->get_plugins_stop_with_transport() && flush)) { (*i)->flush (); } - (*i)->transport_stopped (now); + (*i)->non_realtime_transport_stop (now, flush); } } - _roll_delay = _initial_delay; + if (_disk_reader) { + _disk_reader->set_roll_delay (_initial_delay); + } } void @@ -3475,7 +3379,7 @@ Route::input_change_handler (IOChange change, void * /*src*/) continue; } bool sends_only; - bool does_feed = (*i)->direct_feeds_according_to_reality (shared_from_this(), &sends_only); + bool does_feed = (*i)->direct_feeds_according_to_reality (boost::dynamic_pointer_cast (shared_from_this()), &sends_only); if (does_feed && !sends_only) { if ((*i)->soloed()) { ++sbou; @@ -3552,46 +3456,56 @@ Route::output_change_handler (IOChange change, void * /*src*/) io_changed (); /* EMIT SIGNAL */ } - if (_solo_control->soloed_by_others_downstream()) { - int sbod = 0; - /* checking all all downstream routes for - * explicit of implict solo is a rather drastic measure, - * ideally the input_change_handler() of the other route - * would propagate the change to us. + if ((change.type & IOChange::ConnectionsChanged)) { + + /* do this ONLY if connections have changed. Configuration + * changes do not, by themselves alter solo upstream or + * downstream status. */ - boost::shared_ptr routes = _session.get_routes (); - if (_output->connected()) { - for (RouteList::iterator i = routes->begin(); i != routes->end(); ++i) { - if ((*i).get() == this || (*i)->is_master() || (*i)->is_monitor() || (*i)->is_auditioner()) { - continue; - } - bool sends_only; - bool does_feed = direct_feeds_according_to_reality (*i, &sends_only); - if (does_feed && !sends_only) { - if ((*i)->soloed()) { - ++sbod; - break; + + if (_solo_control->soloed_by_others_downstream()) { + int sbod = 0; + /* checking all all downstream routes for + * explicit of implict solo is a rather drastic measure, + * ideally the input_change_handler() of the other route + * would propagate the change to us. + */ + boost::shared_ptr routes = _session.get_routes (); + if (_output->connected()) { + for (RouteList::iterator i = routes->begin(); i != routes->end(); ++i) { + if ((*i).get() == this || (*i)->is_master() || (*i)->is_monitor() || (*i)->is_auditioner()) { + continue; + } + bool sends_only; + bool does_feed = direct_feeds_according_to_reality (*i, &sends_only); + if (does_feed && !sends_only) { + if ((*i)->soloed()) { + ++sbod; + break; + } } } } - } - int delta = sbod - _solo_control->soloed_by_others_downstream(); - if (delta <= 0) { - // do not allow new connections to change implicit solo (no propagation) - _solo_control->mod_solo_by_others_downstream (delta); - // Session::route_solo_changed() does not propagate indirect solo-changes - // propagate upstream to tracks - for (RouteList::iterator i = routes->begin(); i != routes->end(); ++i) { - if ((*i).get() == this || !can_solo()) { - continue; - } - bool sends_only; - bool does_feed = (*i)->feeds (shared_from_this(), &sends_only); - if (delta != 0 && does_feed && !sends_only) { - (*i)->solo_control()->mod_solo_by_others_downstream (delta); + + int delta = sbod - _solo_control->soloed_by_others_downstream(); + if (delta <= 0) { + // do not allow new connections to change implicit solo (no propagation) + _solo_control->mod_solo_by_others_downstream (delta); + // Session::route_solo_changed() does not propagate indirect solo-changes + // propagate upstream to tracks + boost::shared_ptr shared_this = boost::dynamic_pointer_cast (shared_from_this()); + for (RouteList::iterator i = routes->begin(); i != routes->end(); ++i) { + if ((*i).get() == this || !can_solo()) { + continue; + } + bool sends_only; + bool does_feed = (*i)->feeds (shared_this, &sends_only); + if (delta != 0 && does_feed && !sends_only) { + (*i)->solo_control()->mod_solo_by_others_downstream (delta); + } } - } + } } } } @@ -3616,8 +3530,24 @@ Route::pans_required () const return max (n_inputs ().n_audio(), processor_max_streams.n_audio()); } +void +Route::flush_processor_buffers_locked (samplecnt_t nframes) +{ + for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) { + boost::shared_ptr d = boost::dynamic_pointer_cast (*i); + if (d) { + d->flush_buffers (nframes); + } else { + boost::shared_ptr p = boost::dynamic_pointer_cast (*i); + if (p) { + p->flush_buffers (nframes); + } + } + } +} + int -Route::no_roll (pframes_t nframes, framepos_t start_frame, framepos_t end_frame, bool session_state_changing) +Route::no_roll (pframes_t nframes, samplepos_t start_sample, samplepos_t end_sample, bool session_state_changing) { Glib::Threads::RWLock::ReaderLock lm (_processor_lock, Glib::Threads::TRY_LOCK); @@ -3625,11 +3555,7 @@ Route::no_roll (pframes_t nframes, framepos_t start_frame, framepos_t end_frame, return 0; } - if (n_outputs().n_total() == 0) { - return 0; - } - - if (!_active || n_inputs() == ChanCount::ZERO) { + if (!_active) { silence_unlocked (nframes); return 0; } @@ -3654,68 +3580,69 @@ Route::no_roll (pframes_t nframes, framepos_t start_frame, framepos_t end_frame, fill_buffers_with_input (bufs, _input, nframes); if (_meter_point == MeterInput) { - _meter->run (bufs, start_frame, end_frame, nframes, true); + _meter->run (bufs, start_sample, end_sample, 0.0, nframes, true); } - _amp->apply_gain_automation (false); - _trim->apply_gain_automation (false); - passthru (bufs, start_frame, end_frame, nframes, 0); + passthru (bufs, start_sample, end_sample, nframes, 0, true); + + flush_processor_buffers_locked (nframes); return 0; } int -Route::roll (pframes_t nframes, framepos_t start_frame, framepos_t end_frame, int declick, bool& /* need_butler */) +Route::roll (pframes_t nframes, samplepos_t start_sample, samplepos_t end_sample, int declick, bool& need_butler) { Glib::Threads::RWLock::ReaderLock lm (_processor_lock, Glib::Threads::TRY_LOCK); - if (!lm.locked()) { - return 0; - } - if (n_outputs().n_total() == 0) { + if (!lm.locked()) { return 0; } - if (!_active || n_inputs().n_total() == 0) { + if (!_active) { silence_unlocked (nframes); - return 0; - } - - framepos_t unused = 0; - - if ((nframes = check_initial_delay (nframes, unused)) == 0) { + if (_meter_point == MeterInput && ((_monitoring_control->monitoring_choice() & MonitorInput) || (!_disk_writer || _disk_writer->record_enabled()))) { + _meter->reset(); + } return 0; } _silent = false; - BufferSet& bufs = _session.get_route_buffers (n_process_buffers()); + BufferSet& bufs = _session.get_route_buffers (n_process_buffers ()); fill_buffers_with_input (bufs, _input, nframes); - if (_meter_point == MeterInput) { - _meter->run (bufs, start_frame, end_frame, nframes, true); + /* filter captured data before meter sees it */ + filter_input (bufs); + + if (_meter_point == MeterInput && + ((_monitoring_control->monitoring_choice() & MonitorInput) || (_disk_writer && _disk_writer->record_enabled()))) { + _meter->run (bufs, start_sample, end_sample, 1.0 /*speed()*/, nframes, true); } - passthru (bufs, start_frame, end_frame, nframes, declick); + passthru (bufs, start_sample, end_sample, nframes, declick, (!_disk_writer || !_disk_writer->record_enabled()) && _session.transport_rolling()); + + if ((_disk_reader && _disk_reader->need_butler()) || (_disk_writer && _disk_writer->need_butler())) { + need_butler = true; + } + + flush_processor_buffers_locked (nframes); return 0; } int -Route::silent_roll (pframes_t nframes, framepos_t /*start_frame*/, framepos_t /*end_frame*/, bool& /* need_butler */) +Route::silent_roll (pframes_t nframes, samplepos_t /*start_sample*/, samplepos_t /*end_sample*/, bool& /* need_butler */) { silence (nframes); + flush_processor_buffers_locked (nframes); return 0; } void Route::flush_processors () { - /* XXX shouldn't really try to take this lock, since - this is called from the RT audio thread. - */ - Glib::Threads::RWLock::ReaderLock lm (_processor_lock); for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) { @@ -3758,6 +3685,10 @@ Route::apply_processor_changes_rt () } if (changed) { set_processor_positions (); + /* update processor input/output latency + * (total signal_latency does not change) + */ + update_signal_latency (true); } if (emissions != 0) { g_atomic_int_set (&_pending_signals, emissions); @@ -3929,7 +3860,10 @@ Route::add_export_point() Glib::Threads::Mutex::Lock lx (AudioEngine::instance()->process_lock ()); Glib::Threads::RWLock::WriterLock lw (_processor_lock); - _capturing_processor.reset (new CapturingProcessor (_session)); + // this aligns all tracks; but not tracks + busses + samplecnt_t latency = _session.worst_track_roll_delay (); + assert (latency >= _initial_delay); + _capturing_processor.reset (new CapturingProcessor (_session, latency - _initial_delay)); _capturing_processor->activate (); configure_processors_unlocked (0, &lw); @@ -3939,41 +3873,40 @@ Route::add_export_point() return _capturing_processor; } -framecnt_t -Route::update_signal_latency () +samplecnt_t +Route::update_signal_latency (bool set_initial_delay) { - framecnt_t l = _output->user_latency(); - framecnt_t lamp = 0; - bool before_amp = true; - framecnt_t ltrim = 0; - bool before_trim = true; + Glib::Threads::RWLock::ReaderLock lm (_processor_lock); + + samplecnt_t l_in = _input->latency (); + samplecnt_t l_out = _output->user_latency(); for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) { if ((*i)->active ()) { - l += (*i)->signal_latency (); - } - if ((*i) == _amp) { - before_amp = false; - } - if ((*i) == _trim) { - before_amp = false; - } - if (before_amp) { - lamp = l; + l_in += (*i)->signal_latency (); } - if (before_trim) { - lamp = l; + (*i)->set_input_latency (l_in); + } + + for (ProcessorList::reverse_iterator i = _processors.rbegin(); i != _processors.rend(); ++i) { + (*i)->set_output_latency (l_out); + if ((*i)->active ()) { + l_out += (*i)->signal_latency (); } } - DEBUG_TRACE (DEBUG::Latency, string_compose ("%1: internal signal latency = %2\n", _name, l)); + DEBUG_TRACE (DEBUG::Latency, string_compose ("%1: internal signal latency = %2\n", _name, l_out)); + + _signal_latency = l_out; - // TODO: (lamp - _signal_latency) to sync to output (read-ahed), currently _roll_delay shifts this around - _signal_latency_at_amp_position = lamp; - _signal_latency_at_trim_position = ltrim; + lm.release (); - if (_signal_latency != l) { - _signal_latency = l; + if (set_initial_delay) { + /* see also Session::post_playback_latency() */ + set_latency_compensation (_session.worst_track_latency () + _session.worst_track_out_latency () - output ()->latency ()); + } + + if (_signal_latency != l_out) { signal_latency_changed (); /* EMIT SIGNAL */ } @@ -3981,19 +3914,20 @@ Route::update_signal_latency () } void -Route::set_user_latency (framecnt_t nframes) +Route::set_user_latency (samplecnt_t nframes) { _output->set_user_latency (nframes); _session.update_latency_compensation (); } void -Route::set_latency_compensation (framecnt_t longest_session_latency) +Route::set_latency_compensation (samplecnt_t longest_session_latency) { - framecnt_t old = _initial_delay; + samplecnt_t old = _initial_delay; + assert (!_disk_reader || _disk_reader->output_latency () <= _signal_latency); - if (_signal_latency < longest_session_latency) { - _initial_delay = longest_session_latency - _signal_latency; + if (_disk_reader && _disk_reader->output_latency () < longest_session_latency) { + _initial_delay = longest_session_latency - _disk_reader->output_latency (); } else { _initial_delay = 0; } @@ -4008,7 +3942,9 @@ Route::set_latency_compensation (framecnt_t longest_session_latency) } if (_session.transport_stopped()) { - _roll_delay = _initial_delay; + if (_disk_reader) { + _disk_reader->set_roll_delay (_initial_delay); + } } } @@ -4049,18 +3985,18 @@ Route::set_pending_declick (int declick) * Adds undo commands for any shifts that are performed. * * @param pos Position to start shifting from. - * @param frames Amount to shift forwards by. + * @param samples Amount to shift forwards by. */ void -Route::shift (framepos_t pos, framecnt_t frames) +Route::shift (samplepos_t pos, samplecnt_t samples) { /* gain automation */ { boost::shared_ptr gc = _amp->gain_control(); XMLNode &before = gc->alist()->get_state (); - gc->alist()->shift (pos, frames); + gc->alist()->shift (pos, samples); XMLNode &after = gc->alist()->get_state (); _session.add_command (new MementoCommand (*gc->alist().get(), &before, &after)); } @@ -4070,7 +4006,7 @@ Route::shift (framepos_t pos, framecnt_t frames) boost::shared_ptr gc = _trim->gain_control(); XMLNode &before = gc->alist()->get_state (); - gc->alist()->shift (pos, frames); + gc->alist()->shift (pos, samples); XMLNode &after = gc->alist()->get_state (); _session.add_command (new MementoCommand (*gc->alist().get(), &before, &after)); } @@ -4086,7 +4022,7 @@ Route::shift (framepos_t pos, framecnt_t frames) if (pc) { boost::shared_ptr al = pc->alist(); XMLNode& before = al->get_state (); - al->shift (pos, frames); + al->shift (pos, samples); XMLNode& after = al->get_state (); _session.add_command (new MementoCommand (*al.get(), &before, &after)); } @@ -4105,7 +4041,7 @@ Route::shift (framepos_t pos, framecnt_t frames) if (ac) { boost::shared_ptr al = ac->alist(); XMLNode &before = al->get_state (); - al->shift (pos, frames); + al->shift (pos, samples); XMLNode &after = al->get_state (); _session.add_command (new MementoCommand (*al.get(), &before, &after)); } @@ -4126,12 +4062,22 @@ Route::set_plugin_state_dir (boost::weak_ptr p, const std::string& d) } int -Route::save_as_template (const string& path, const string& name) +Route::save_as_template (const string& path, const string& name, const string& description) { std::string state_dir = path.substr (0, path.find_last_of ('.')); // strip template_suffix PBD::Unwinder uw (_session._template_state_dir, state_dir); XMLNode& node (state (false)); + node.set_property (X_("name"), name); + + node.remove_nodes (X_("description")); + if (!description.empty()) { + XMLNode* desc = new XMLNode(X_("description")); + XMLNode* desc_cont = new XMLNode(X_("content"), description); + desc->add_child_nocopy (*desc_cont); + + node.add_child_nocopy (*desc); + } XMLTree tree; @@ -4147,6 +4093,10 @@ Route::save_as_template (const string& path, const string& name) bool Route::set_name (const string& str) { + if (str.empty ()) { + return false; + } + if (str == name()) { return true; } @@ -4183,7 +4133,7 @@ Route::set_name (const string& str) void Route::set_name_in_state (XMLNode& node, string const & name, bool rename_playlist) { - node.add_property (X_("name"), name); + node.set_property (X_("name"), name); XMLNodeList children = node.children(); for (XMLNodeIterator i = children.begin(); i != children.end(); ++i) { @@ -4194,17 +4144,17 @@ Route::set_name_in_state (XMLNode& node, string const & name, bool rename_playli } else if ((*i)->name() == X_("Processor")) { - XMLProperty const * role = (*i)->property (X_("role")); - if (role && role->value() == X_("Main")) { - (*i)->add_property (X_("name"), name); + std::string str; + if ((*i)->get_property (X_("role"), str) && str == X_("Main")) { + (*i)->set_property (X_("name"), name); } } else if ((*i)->name() == X_("Diskstream")) { if (rename_playlist) { - (*i)->add_property (X_("playlist"), string_compose ("%1.1", name).c_str()); + (*i)->set_property (X_("playlist"), name + ".1"); } - (*i)->add_property (X_("name"), name); + (*i)->set_property (X_("name"), name); } } @@ -4259,6 +4209,7 @@ Route::set_active (bool yn, void* src) _active = yn; _input->set_active (yn); _output->set_active (yn); + flush_processors (); active_changed (); // EMIT SIGNAL _session.set_dirty (); } @@ -4458,8 +4409,8 @@ Route::unknown_processors () const } -framecnt_t -Route::update_port_latencies (PortSet& from, PortSet& to, bool playback, framecnt_t our_latency) const +samplecnt_t +Route::update_port_latencies (PortSet& from, PortSet& to, bool playback, samplecnt_t our_latency) const { /* we assume that all our input ports feed all our output ports. its not universally true, but the alternative is way too corner-case to worry about. @@ -4507,10 +4458,10 @@ Route::update_port_latencies (PortSet& from, PortSet& to, bool playback, framecn return all_connections.max; } -framecnt_t +samplecnt_t Route::set_private_port_latencies (bool playback) const { - framecnt_t own_latency = 0; + samplecnt_t own_latency = 0; /* Processor list not protected by lock: MUST BE CALLED FROM PROCESS THREAD OR LATENCY CALLBACK. @@ -4539,7 +4490,7 @@ Route::set_private_port_latencies (bool playback) const } void -Route::set_public_port_latencies (framecnt_t value, bool playback) const +Route::set_public_port_latencies (samplecnt_t value, bool playback) const { /* this is called to set the JACK-visible port latencies, which take latency compensation into account. @@ -4590,6 +4541,8 @@ Route::setup_invisible_processors () */ ProcessorList new_processors; + ProcessorList::iterator dr; + ProcessorList::iterator dw; /* find visible processors */ @@ -4707,10 +4660,20 @@ Route::setup_invisible_processors () new_processors.insert (amp, _monitor_control); } + /* TRIM CONTROL */ + + ProcessorList::iterator trim = new_processors.end(); + + if (_trim->active()) { + assert (!_trim->display_to_user ()); + new_processors.push_front (_trim); + trim = new_processors.begin(); + } + /* INTERNAL RETURN */ - /* doing this here means that any monitor control will come just after - the return. + /* doing this here means that any monitor control will come after + the return and trim. */ if (_intreturn) { @@ -4718,22 +4681,64 @@ Route::setup_invisible_processors () new_processors.push_front (_intreturn); } - if (_trim && _trim->active()) { - assert (!_trim->display_to_user ()); - new_processors.push_front (_trim); - } /* EXPORT PROCESSOR */ + /* DISK READER & WRITER (for Track objects) */ + + if (_disk_reader || _disk_writer) { + switch (_disk_io_point) { + case DiskIOPreFader: + if (trim != new_processors.end()) { + /* insert BEFORE TRIM */ + if (_disk_writer) { + new_processors.insert (trim, _disk_writer); + } + if (_disk_reader) { + new_processors.insert (trim, _disk_reader); + } + } else { + if (_disk_writer) { + new_processors.push_front (_disk_writer); + } + if (_disk_reader) { + new_processors.push_front (_disk_reader); + } + } + break; + case DiskIOPostFader: + /* insert BEFORE main outs */ + if (_disk_writer) { + new_processors.insert (main, _disk_writer); + } + if (_disk_reader) { + new_processors.insert (main, _disk_reader); + } + break; + case DiskIOCustom: + /* reader and writer are visible under this condition, so they + * are not invisible and thus not handled here. + */ + break; + } + } + if (_capturing_processor) { assert (!_capturing_processor->display_to_user ()); - new_processors.push_front (_capturing_processor); + ProcessorList::iterator reader_pos = find (new_processors.begin(), new_processors.end(), _disk_reader); + if (reader_pos != new_processors.end()) { + /* insert after disk-reader */ + new_processors.insert (++reader_pos, _capturing_processor); + } else { + new_processors.push_front (_capturing_processor); + } } + _processors = new_processors; for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) { - if (!(*i)->display_to_user () && !(*i)->active () && (*i) != _monitor_send) { - (*i)->activate (); + if (!(*i)->display_to_user () && !(*i)->enabled () && (*i) != _monitor_send) { + (*i)->enable (true); } } @@ -4815,15 +4820,6 @@ Route::processor_by_id (PBD::ID id) const return boost::shared_ptr (); } -/** @return the monitoring state, or in other words what data we are pushing - * into the route (data from the inputs, data from disk or silence) - */ -MonitorState -Route::monitoring_state () const -{ - return MonitoringInput; -} - /** @return what we should be metering; either the data coming from the input * IO or the data that is flowing through the route. */ @@ -4872,10 +4868,12 @@ Route::the_instrument_unlocked () const void -Route::non_realtime_locate (framepos_t pos) +Route::non_realtime_locate (samplepos_t pos) { + Automatable::non_realtime_locate (pos); + if (_pannable) { - _pannable->transport_located (pos); + _pannable->non_realtime_locate (pos); } if (_delayline.get()) { @@ -4887,10 +4885,13 @@ Route::non_realtime_locate (framepos_t pos) Glib::Threads::RWLock::ReaderLock lm (_processor_lock); for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) { - (*i)->transport_located (pos); + (*i)->non_realtime_locate (pos); } } - _roll_delay = _initial_delay; + + if (_disk_reader) { + _disk_reader->set_roll_delay (_initial_delay); + } } void @@ -4939,7 +4940,6 @@ Route::fill_buffers_with_input (BufferSet& bufs, boost::shared_ptr io, pfram boost::shared_ptr source_port = io->audio (i); AudioBuffer& buf (bufs.get_audio (i%n_buffers)); - if (i < n_buffers) { /* first time through just copy a channel into @@ -4987,11 +4987,12 @@ boost::shared_ptr Route::pan_azimuth_control() const { #ifdef MIXBUS +# undef MIXBUS_PORTS_H +# include "../../gtk2_ardour/mixbus_ports.h" boost::shared_ptr plug = ch_post(); if (!plug) { return boost::shared_ptr(); } - const uint32_t port_channel_post_pan = 2; // gtk2_ardour/mixbus_ports.h return boost::dynamic_pointer_cast (plug->control (Evoral::Parameter (ARDOUR::PluginAutomation, 0, port_channel_post_pan))); #else if (!_pannable || !panner()) { @@ -5019,6 +5020,12 @@ Route::pan_elevation_control() const boost::shared_ptr Route::pan_width_control() const { +#ifdef MIXBUS + if (mixbus() && _ch_pre) { + //mono blend + return boost::dynamic_pointer_cast(_ch_pre->control(Evoral::Parameter(PluginAutomation, 0, 5))); + } +#endif if (Profile->get_mixbus() || !_pannable || !panner()) { return boost::shared_ptr(); } @@ -5066,7 +5073,15 @@ uint32_t Route::eq_band_cnt () const { if (Profile->get_mixbus()) { +#ifdef MIXBUS32C + if (is_master() || mixbus()) { + return 3; + } else { + return 4; + } +#else return 3; +#endif } else { /* Ardour has no well-known EQ object */ return 0; @@ -5084,30 +5099,33 @@ Route::eq_gain_controllable (uint32_t band) const } uint32_t port_number; - switch (band) { - case 0: - if (is_master() || mixbus()) { - port_number = 4; - } else { - port_number = 8; + if (is_master() || mixbus()) { + switch (band) { + case 0: port_number = 4; break; + case 1: port_number = 3; break; + case 2: port_number = 2; break; + default: + return boost::shared_ptr(); } - break; - case 1: - if (is_master() || mixbus()) { - port_number = 3; - } else { - port_number = 6; + } else { +#ifdef MIXBUS32C + switch (band) { + case 0: port_number = 14; break; + case 1: port_number = 12; break; + case 2: port_number = 10; break; + case 3: port_number = 8; break; + default: + return boost::shared_ptr(); } - break; - case 2: - if (is_master() || mixbus()) { - port_number = 2; - } else { - port_number = 4; +#else + switch (band) { + case 0: port_number = 8; break; + case 1: port_number = 6; break; + case 2: port_number = 4; break; + default: + return boost::shared_ptr(); } - break; - default: - return boost::shared_ptr(); +#endif } return boost::dynamic_pointer_cast (eq->control (Evoral::Parameter (ARDOUR::PluginAutomation, 0, port_number))); @@ -5119,7 +5137,6 @@ boost::shared_ptr Route::eq_freq_controllable (uint32_t band) const { #ifdef MIXBUS - if (mixbus() || is_master()) { /* no frequency controls for mixbusses or master */ return boost::shared_ptr(); @@ -5132,19 +5149,24 @@ Route::eq_freq_controllable (uint32_t band) const } uint32_t port_number; +#ifdef MIXBUS32C switch (band) { - case 0: - port_number = 7; - break; - case 1: - port_number = 5; - break; - case 2: - port_number = 3; - break; - default: - return boost::shared_ptr(); + case 0: port_number = 13; break; // lo + case 1: port_number = 11; break; // lo mid + case 2: port_number = 9; break; // hi mid + case 3: port_number = 7; break; // hi + default: + return boost::shared_ptr(); } +#else + switch (band) { + case 0: port_number = 7; break; + case 1: port_number = 5; break; + case 2: port_number = 3; break; + default: + return boost::shared_ptr(); + } +#endif return boost::dynamic_pointer_cast (eq->control (Evoral::Parameter (ARDOUR::PluginAutomation, 0, port_number))); #else @@ -5161,6 +5183,22 @@ Route::eq_q_controllable (uint32_t band) const boost::shared_ptr Route::eq_shape_controllable (uint32_t band) const { +#ifdef MIXBUS32C + boost::shared_ptr eq = ch_eq(); + if (is_master() || mixbus() || !eq) { + return boost::shared_ptr(); + } + switch (band) { + case 0: + return boost::dynamic_pointer_cast (eq->control (Evoral::Parameter (ARDOUR::PluginAutomation, 0, 4))); // lo bell + break; + case 3: + return boost::dynamic_pointer_cast (eq->control (Evoral::Parameter (ARDOUR::PluginAutomation, 0, 3))); // hi bell + break; + default: + break; + } +#endif return boost::shared_ptr(); } @@ -5181,12 +5219,46 @@ Route::eq_enable_controllable () const } boost::shared_ptr -Route::eq_hpf_controllable () const +Route::filter_freq_controllable (bool hpf) const { #ifdef MIXBUS boost::shared_ptr eq = ch_eq(); - if (!eq) { + if (is_master() || mixbus() || !eq) { + return boost::shared_ptr(); + } + if (hpf) { +#ifdef MIXBUS32C + return boost::dynamic_pointer_cast (eq->control (Evoral::Parameter (ARDOUR::PluginAutomation, 0, 5))); // HPF freq +#else + return boost::dynamic_pointer_cast (eq->control (Evoral::Parameter (ARDOUR::PluginAutomation, 0, 2))); +#endif + } else { +#ifdef MIXBUS32C + return boost::dynamic_pointer_cast (eq->control (Evoral::Parameter (ARDOUR::PluginAutomation, 0, 6))); // LPF freq +#else + return boost::shared_ptr(); +#endif + } + +#else + return boost::shared_ptr(); +#endif +} + +boost::shared_ptr +Route::filter_slope_controllable (bool) const +{ + return boost::shared_ptr(); +} + +boost::shared_ptr +Route::filter_enable_controllable (bool) const +{ +#ifdef MIXBUS32C + boost::shared_ptr eq = ch_eq(); + + if (is_master() || mixbus() || !eq) { return boost::shared_ptr(); } @@ -5199,20 +5271,30 @@ Route::eq_hpf_controllable () const string Route::eq_band_name (uint32_t band) const { +#ifdef MIXBUS32C + if (is_master() || mixbus()) { +#endif if (Profile->get_mixbus()) { switch (band) { - case 0: - return _("lo"); - case 1: - return _("mid"); - case 2: - return _("hi"); - default: - return string(); + case 0: return _("lo"); + case 1: return _("mid"); + case 2: return _("hi"); + default: return string(); } } else { return string (); } +#ifdef MIXBUS32C + } else { + switch (band) { + case 0: return _("lo"); + case 1: return _("lo mid"); + case 2: return _("hi mid"); + case 3: return _("hi"); + default: return string(); + } + } +#endif } boost::shared_ptr @@ -5291,19 +5373,23 @@ Route::comp_makeup_controllable () const return boost::shared_ptr(); #endif } -boost::shared_ptr +boost::shared_ptr Route::comp_redux_controllable () const { #ifdef MIXBUS boost::shared_ptr comp = ch_comp(); if (!comp) { - return boost::shared_ptr(); + return boost::shared_ptr(); + } + if (is_master()) { + return comp->control_output (2); + } else { + return comp->control_output (6); } - return boost::dynamic_pointer_cast (comp->control (Evoral::Parameter (ARDOUR::PluginAutomation, 0, 6))); #else - return boost::shared_ptr(); + return boost::shared_ptr(); #endif } @@ -5351,71 +5437,122 @@ boost::shared_ptr Route::send_level_controllable (uint32_t n) const { #ifdef MIXBUS +# undef MIXBUS_PORTS_H +# include "../../gtk2_ardour/mixbus_ports.h" boost::shared_ptr plug = ch_post(); - if (!plug) { - return boost::shared_ptr(); - } + if (plug && !mixbus()) { + uint32_t port_id = 0; + switch (n) { + case 0: port_id = port_channel_post_aux1_level; break; + case 1: port_id = port_channel_post_aux2_level; break; + case 2: port_id = port_channel_post_aux3_level; break; + case 3: port_id = port_channel_post_aux4_level; break; + case 4: port_id = port_channel_post_aux5_level; break; + case 5: port_id = port_channel_post_aux6_level; break; + case 6: port_id = port_channel_post_aux7_level; break; + case 7: port_id = port_channel_post_aux8_level; break; +# ifdef MIXBUS32C + case 8: port_id = port_channel_post_aux9_level; break; + case 9: port_id = port_channel_post_aux10_level; break; + case 10: port_id = port_channel_post_aux11_level; break; + case 11: port_id = port_channel_post_aux12_level; break; +# endif + default: + break; + } - if (n >= 8) { - /* no such bus */ - return boost::shared_ptr(); + if (port_id > 0) { + return boost::dynamic_pointer_cast (plug->control (Evoral::Parameter (ARDOUR::PluginAutomation, 0, port_id))); + } +# ifdef MIXBUS32C + assert (n > 11); + n -= 12; +# else + assert (n > 7); + n -= 8; +# endif } - - const uint32_t port_id = port_channel_post_aux1_level + (2*n); // gtk2_ardour/mixbus_ports.h - return boost::dynamic_pointer_cast (plug->control (Evoral::Parameter (ARDOUR::PluginAutomation, 0, port_id))); -#else +#endif boost::shared_ptr s = boost::dynamic_pointer_cast(nth_send (n)); if (!s) { return boost::shared_ptr(); } return s->gain_control (); -#endif } boost::shared_ptr Route::send_enable_controllable (uint32_t n) const { #ifdef MIXBUS +# undef MIXBUS_PORTS_H +# include "../../gtk2_ardour/mixbus_ports.h" boost::shared_ptr plug = ch_post(); - if (!plug) { - return boost::shared_ptr(); - } + if (plug && !mixbus()) { + uint32_t port_id = 0; + switch (n) { + case 0: port_id = port_channel_post_aux1_asgn; break; + case 1: port_id = port_channel_post_aux2_asgn; break; + case 2: port_id = port_channel_post_aux3_asgn; break; + case 3: port_id = port_channel_post_aux4_asgn; break; + case 4: port_id = port_channel_post_aux5_asgn; break; + case 5: port_id = port_channel_post_aux6_asgn; break; + case 6: port_id = port_channel_post_aux7_asgn; break; + case 7: port_id = port_channel_post_aux8_asgn; break; +# ifdef MIXBUS32C + case 8: port_id = port_channel_post_aux9_asgn; break; + case 9: port_id = port_channel_post_aux10_asgn; break; + case 10: port_id = port_channel_post_aux11_asgn; break; + case 11: port_id = port_channel_post_aux12_asgn; break; +# endif + default: + break; + } - if (n >= 8) { - /* no such bus */ - return boost::shared_ptr(); + if (port_id > 0) { + return boost::dynamic_pointer_cast (plug->control (Evoral::Parameter (ARDOUR::PluginAutomation, 0, port_id))); + } +# ifdef MIXBUS32C + assert (n > 11); + n -= 12; +# else + assert (n > 7); + n -= 8; +# endif } - - const uint32_t port_id = port_channel_post_aux1_asgn + (2*n); // gtk2_ardour/mixbus_ports.h - return boost::dynamic_pointer_cast (plug->control (Evoral::Parameter (ARDOUR::PluginAutomation, 0, port_id))); -#else +#endif /* although Ardour sends have enable/disable as part of the Processor - API, it is not exposed as a controllable. - - XXX: we should fix this. - */ + * API, it is not exposed as a controllable. + * + * XXX: we should fix this (make it click-free, automatable enable-control) + */ return boost::shared_ptr(); -#endif } string Route::send_name (uint32_t n) const { #ifdef MIXBUS - if (n >= 8) { - return string(); - } - boost::shared_ptr r = _session.get_mixbus (n); - assert (r); - return r->name(); + boost::shared_ptr plug = ch_post(); + if (plug && !mixbus()) { +# ifdef MIXBUS32C + if (n < 12) { + return _session.get_mixbus (n)->name(); + } + n -= 12; #else + if (n < 8) { + return _session.get_mixbus (n)->name(); + } + n -= 8; +# endif + } +#endif boost::shared_ptr p = nth_send (n); if (p) { return p->name(); } else { return string(); } -#endif } boost::shared_ptr @@ -5426,12 +5563,24 @@ Route::master_send_enable_controllable () const if (!plug) { return boost::shared_ptr(); } +# undef MIXBUS_PORTS_H +# include "../../gtk2_ardour/mixbus_ports.h" return boost::dynamic_pointer_cast (plug->control (Evoral::Parameter (ARDOUR::PluginAutomation, 0, port_channel_post_mstr_assign))); #else return boost::shared_ptr(); #endif } +bool +Route::slaved () const +{ + if (!_gain_control) { + return false; + } + /* just test one particular control, not all of them */ + return _gain_control->slaved (); +} + bool Route::slaved_to (boost::shared_ptr vca) const { @@ -5451,17 +5600,227 @@ Route::muted_by_others_soloing () const return false; } - return _session.soloing() && !_solo_control->soloed() && !_solo_isolate_control->solo_isolated(); + return _session.soloing() && !_solo_control->soloed() && !_solo_isolate_control->solo_isolated(); } void Route::clear_all_solo_state () { - double v = _solo_safe_control->get_value (); - _solo_control->clear_all_solo_state (); +} - if (v != 0.0) { - _solo_safe_control->set_value (v, Controllable::NoGroup); +boost::shared_ptr +Route::automation_control_recurse (PBD::ID const & id) const +{ + boost::shared_ptr ac = Automatable::automation_control (id); + + if (ac) { + return ac; } + + Glib::Threads::RWLock::ReaderLock lm (_processor_lock); + + for (ProcessorList::const_iterator i = _processors.begin(); i != _processors.end(); ++i) { + if ((ac = (*i)->automation_control (id))) { + return ac; + } + } + + return boost::shared_ptr (); } + +SlavableControlList +Route::slavables () const +{ + SlavableControlList rv; + rv.push_back (_gain_control); + rv.push_back (_mute_control); + rv.push_back (_solo_control); + return rv; +} + +void +Route::set_disk_io_point (DiskIOPoint diop) +{ + bool display = false; + + cerr << "set disk io to " << enum_2_string (diop) << endl; + + switch (diop) { + case DiskIOCustom: + display = true; + break; + default: + display = false; + } + + if (_disk_writer) { + _disk_writer->set_display_to_user (display); + } + + if (_disk_reader) { + _disk_reader->set_display_to_user (display); + } + + const bool changed = (diop != _disk_io_point); + + _disk_io_point = diop; + + if (changed) { + Glib::Threads::Mutex::Lock lx (AudioEngine::instance()->process_lock ()); + configure_processors (0); + } + + processors_changed (RouteProcessorChange ()); /* EMIT SIGNAL */ +} + +#ifdef USE_TRACKS_CODE_FEATURES + +/* This is the Tracks version of Track::monitoring_state(). + * + * Ardour developers: try to flag or fix issues if parts of the libardour API + * change in ways that invalidate this + */ + +MonitorState +Route::monitoring_state () const +{ + /* Explicit requests */ + + if (_monitoring != MonitorInput) { + return MonitoringInput; + } + + if (_monitoring & MonitorDisk) { + return MonitoringDisk; + } + + /* This is an implementation of the truth table in doc/monitor_modes.pdf; + I don't think it's ever going to be too pretty too look at. + */ + + // GZ: NOT USED IN TRACKS + //bool const auto_input = _session.config.get_auto_input (); + //bool const software_monitor = Config->get_monitoring_model() == SoftwareMonitoring; + //bool const tape_machine_mode = Config->get_tape_machine_mode (); + + bool const roll = _session.transport_rolling (); + bool const track_rec = _diskstream->record_enabled (); + bool session_rec = _session.actively_recording (); + + if (track_rec) { + + if (!session_rec && roll) { + return MonitoringDisk; + } else { + return MonitoringInput; + } + + } else { + + if (roll) { + return MonitoringDisk; + } + } + + return MonitoringSilence; +} + +#else + +/* This is the Ardour/Mixbus version of Track::monitoring_state(). + * + * Tracks developers: do NOT modify this method under any circumstances. + */ + +MonitorState +Route::monitoring_state () const +{ + if (!_disk_reader) { + return MonitoringInput; + } + + /* Explicit requests */ + MonitorChoice m (_monitoring_control->monitoring_choice()); + + if (m != MonitorAuto) { + + MonitorState ms ((MonitorState) 0); + + if (m & MonitorInput) { + ms = MonitoringInput; + } + + if (m & MonitorDisk) { + ms = MonitorState (ms | MonitoringDisk); + } + + return ms; + } + + switch (_session.config.get_session_monitoring ()) { + case MonitorDisk: + return MonitoringDisk; + break; + case MonitorInput: + return MonitoringInput; + break; + default: + break; + } + + /* This is an implementation of the truth table in doc/monitor_modes.pdf; + I don't think it's ever going to be too pretty too look at. + */ + + bool const roll = _session.transport_rolling (); + bool const track_rec = _disk_writer->record_enabled (); + bool const auto_input = _session.config.get_auto_input (); + bool const software_monitor = Config->get_monitoring_model() == SoftwareMonitoring; + bool const tape_machine_mode = Config->get_tape_machine_mode (); + bool session_rec; + + /* I suspect that just use actively_recording() is good enough all the + * time, but just to keep the semantics the same as they were before + * sept 26th 2012, we differentiate between the cases where punch is + * enabled and those where it is not. + * + * rg: I suspect this is not the case: monitoring may differ + */ + + if (_session.config.get_punch_in() || _session.config.get_punch_out() || _session.preroll_record_punch_enabled ()) { + session_rec = _session.actively_recording (); + } else { + session_rec = _session.get_record_enabled(); + } + + if (track_rec) { + + if (!session_rec && roll && auto_input) { + return MonitoringDisk; + } else { + return software_monitor ? MonitoringInput : MonitoringSilence; + } + + } else { + + if (tape_machine_mode) { + + return MonitoringDisk; + + } else { + + if (!roll && auto_input) { + return software_monitor ? MonitoringInput : MonitoringSilence; + } else { + return MonitoringDisk; + } + + } + } + + abort(); /* NOTREACHED */ + return MonitoringSilence; +} + +#endif