diff options
Diffstat (limited to 'libs/ardour/transport_master_manager.cc')
-rw-r--r-- | libs/ardour/transport_master_manager.cc | 537 |
1 files changed, 537 insertions, 0 deletions
diff --git a/libs/ardour/transport_master_manager.cc b/libs/ardour/transport_master_manager.cc new file mode 100644 index 0000000000..e69aebe77d --- /dev/null +++ b/libs/ardour/transport_master_manager.cc @@ -0,0 +1,537 @@ +/* + * Copyright (C) 2018 Paul Davis (paul@linuxaudiosystems.com) + * + * 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, 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "pbd/i18n.h" + +#include "ardour/audioengine.h" +#include "ardour/debug.h" +#include "ardour/disk_reader.h" +#include "ardour/session.h" +#include "ardour/transport_master_manager.h" + +#if __cplusplus > 199711L +#define local_signbit(x) std::signbit (x) +#else +#define local_signbit(x) ((((__int64*)(&z))*) & 0x8000000000000000) +#endif + +using namespace ARDOUR; +using namespace PBD; + +const std::string TransportMasterManager::state_node_name = X_("TransportMasters"); +TransportMasterManager* TransportMasterManager::_instance = 0; + +TransportMasterManager::TransportMasterManager() + : _master_speed (0) + , _master_position (0) + , _current_master (0) + , _session (0) + , _master_invalid_this_cycle (false) + , master_dll_initstate (0) +{ +} + +TransportMasterManager::~TransportMasterManager () +{ + clear (); +} + +int +TransportMasterManager::init () +{ + try { + /* setup default transport masters. Most people will never need any + others + */ + add (Engine, X_("JACK Transport")); + add (MTC, X_("MTC")); + add (LTC, X_("LTC")); + add (MIDIClock, X_("MIDI Clock")); + } catch (...) { + return -1; + } + + _current_master = _transport_masters.back(); + + return 0; +} + +void +TransportMasterManager::set_session (Session* s) +{ + /* Called by AudioEngine in process context, synchronously with it's + * own "adoption" of the Session. The call will occur before the first + * call to ::pre_process_transport_masters(). + */ + + Glib::Threads::RWLock::ReaderLock lm (lock); + + config_connection.disconnect (); + + _session = s; + + for (TransportMasters::iterator tm = _transport_masters.begin(); tm != _transport_masters.end(); ++tm) { + (*tm)->set_session (s); + } + + if (_session) { + _session->config.ParameterChanged.connect_same_thread (config_connection, boost::bind (&TransportMasterManager::parameter_changed, this, _1)); + } + +} + +void +TransportMasterManager::parameter_changed (std::string const & what) +{ + if (what == "external-sync") { + if (!_session->config.get_external_sync()) { + /* disabled */ + DiskReader::set_no_disk_output (false); + } + } +} + +TransportMasterManager& +TransportMasterManager::instance() +{ + if (!_instance) { + _instance = new TransportMasterManager(); + } + return *_instance; +} + +// Called from AudioEngine::process_callback() BEFORE Session::process() is called. Each transport master has processed any incoming data for this cycle, +// and this method computes the transport speed that Ardour should use to get into and remain in sync with the master. +// +double +TransportMasterManager::pre_process_transport_masters (pframes_t nframes, samplepos_t now) +{ + Glib::Threads::RWLock::ReaderLock lm (lock, Glib::Threads::TRY_LOCK); + + if (!lm.locked()) { + return 1.0; + } + + boost::optional<samplepos_t> session_pos; + + if (_session) { + session_pos = _session->audible_sample(); + } + + if (Config->get_run_all_transport_masters_always()) { + for (TransportMasters::iterator tm = _transport_masters.begin(); tm != _transport_masters.end(); ++tm) { + if ((*tm)->check_collect()) { + (*tm)->pre_process (nframes, now, session_pos); + } + } + } + + if (!_session) { + return 1.0; + } + + /* if we're not running ALL transport masters, but still have a current + * one, then we should run that one all the time so that we know + * precisely where it is when we starting chasing it ... + */ + + if (!Config->get_run_all_transport_masters_always() && _current_master) { + _current_master->pre_process (nframes, now, session_pos); + } + + if (!_session->config.get_external_sync()) { + DEBUG_TRACE (DEBUG::Slave, string_compose ("no external sync, use session actual speed of %1\n", _session->actual_speed() ? _session->actual_speed() : 1.0)); + return _session->actual_speed () ? _session->actual_speed() : 1.0; + } + + /* --- NOT REACHED UNLESS CHASING (i.e. _session->config.get_external_sync() is true ------*/ + + if (!_current_master->ok()) { + /* stop */ + _session->request_transport_speed (0.0, false, _current_master->request_type()); + DEBUG_TRACE (DEBUG::Slave, "no roll2 - master has failed\n"); + _master_invalid_this_cycle = true; + return 1.0; + } + + if (!_current_master->locked()) { + DEBUG_TRACE (DEBUG::Slave, "no roll4 - not locked\n"); + _master_invalid_this_cycle = true; + return 1.0; + } + + double engine_speed; + + if (!_current_master->speed_and_position (_master_speed, _master_position, now)) { + return 1.0; + } + + if (_master_speed != 0.0) { + + samplepos_t delta = _master_position; + + if (_session->compute_audible_delta (delta)) { + + if (master_dll_initstate == 0) { + + init_transport_master_dll (_master_speed, _master_position); + // _master_invalid_this_cycle = true; + DEBUG_TRACE (DEBUG::Slave, "no roll3 - still initializing master DLL\n"); + master_dll_initstate = _master_speed > 0.0 ? 1 : -1; + + return 1.0; + } + + /* compute delta or "error" between the computed master_position for + * this cycle and the current session position. + * + * Remember: ::speed_and_position() is being called in process context + * but returns the predicted speed+position for the start of this process cycle, + * not just the most recent timestamp received by the current master object. + */ + + DEBUG_TRACE (DEBUG::Slave, string_compose ("master DLL: delta = %1 (%2 vs %3) res: %4\n", delta, _master_position, _session->transport_sample(), _current_master->resolution())); + + if (delta > _current_master->resolution()) { + + // init_transport_master_dll (_master_speed, _master_position); + + if (!_session->actively_recording()) { + DEBUG_TRACE (DEBUG::Slave, string_compose ("slave delta %1 greater than slave resolution %2 => no disk output\n", delta, _current_master->resolution())); + /* run routes as normal, but no disk output */ + DiskReader::set_no_disk_output (true); + } else { + DiskReader::set_no_disk_output (false); + } + } else { + DiskReader::set_no_disk_output (false); + } + + /* inject DLL with new data */ + + DEBUG_TRACE (DEBUG::Slave, string_compose ("feed master DLL t0 %1 t1 %2 e %3 %4 e2 %5 sess %6\n", t0, t1, delta, _master_position, e2, _session->transport_sample())); + + const double e = delta; + + t0 = t1; + t1 += b * e + e2; + e2 += c * e; + + engine_speed = (t1 - t0) / nframes; + + DEBUG_TRACE (DEBUG::Slave, string_compose ("slave @ %1 speed %2 cur delta %3 matching speed %4\n", _master_position, _master_speed, delta, engine_speed)); + + /* provide a .1% deadzone to lock the speed */ + if (fabs (engine_speed - 1.0) <= 0.001) { + engine_speed = 1.0; + } + + if (_current_master->sample_clock_synced() && engine_speed != 0.0f) { + + /* if the master is synced to our audio interface via word-clock or similar, then we assume that its speed is binary: 0.0 or 1.0 + (since our sample clock cannot change with respect to it). + */ + if (engine_speed > 0.0) { + engine_speed = 1.0; + } else if (engine_speed < 0.0) { + engine_speed = -1.0; + } + } + + /* speed is set, we're locked, and good to go */ + DEBUG_TRACE (DEBUG::Slave, string_compose ("%1: computed speed-to-follow-master as %2\n", _current_master->name(), engine_speed)); + + } else { + + /* session has not finished with latency compensation yet, so we cannot compute the + difference between the master and the session. + */ + engine_speed = 1.0; + } + + } else { + + engine_speed = 1.0; + } + + _master_invalid_this_cycle = false; + + DEBUG_TRACE (DEBUG::Slave, string_compose ("computed resampling ratio as %1 with position = %2 and speed = %3\n", engine_speed, _master_position, _master_speed)); + + return engine_speed; +} + + +void +TransportMasterManager::init_transport_master_dll (double speed, samplepos_t pos) +{ + /* the bandwidth of the DLL is a trade-off, + * because the max-speed of the transport in ardour is + * limited to +-8.0, a larger bandwidth would cause oscillations + * + * But this is only really a problem if the user performs manual + * seeks while transport is running and slaved to some timecode-y master. + */ + + AudioEngine* ae = AudioEngine::instance(); + + double const omega = 2.0 * M_PI * double(ae->samples_per_cycle()) / 2.0 / double(ae->sample_rate()); + b = 1.4142135623730950488 * omega; + c = omega * omega; + + const int direction = (speed >= 0.0 ? 1 : -1); + + master_dll_initstate = direction; + + e2 = double (direction * ae->samples_per_cycle()); + t0 = double (pos); + t1 = t0 + e2; + + DEBUG_TRACE (DEBUG::Slave, string_compose ("[re-]init ENGINE DLL %1 %2 %3 from %4 %5\n", t0, t1, e2, speed, pos)); +} + +int +TransportMasterManager::add (SyncSource type, std::string const & name) +{ + int ret = 0; + boost::shared_ptr<TransportMaster> tm; + + { + Glib::Threads::RWLock::WriterLock lm (lock); + tm = TransportMaster::factory (type, name); + ret = add_locked (tm); + } + + if (ret == 0) { + Added (tm); + } + + return ret; +} + +int +TransportMasterManager::add_locked (boost::shared_ptr<TransportMaster> tm) +{ + if (!tm) { + return -1; + } + + for (TransportMasters::const_iterator t = _transport_masters.begin(); t != _transport_masters.end(); ++t) { + if ((*t)->name() == tm->name()) { + error << string_compose (_("There is already a transport master named \"%1\" - not duplicated"), tm->name()) << endmsg; + return -1; + } + } + + _transport_masters.push_back (tm); + return 0; +} + +int +TransportMasterManager::remove (std::string const & name) +{ + int ret = -1; + boost::shared_ptr<TransportMaster> tm; + + { + Glib::Threads::RWLock::WriterLock lm (lock); + + for (TransportMasters::iterator t = _transport_masters.begin(); t != _transport_masters.end(); ++t) { + if ((*t)->name() == name) { + tm = *t; + _transport_masters.erase (t); + ret = 0; + break; + } + } + } + + if (ret == 0 && tm) { + Removed (tm); + } + + return -1; +} + +int +TransportMasterManager::set_current_locked (boost::shared_ptr<TransportMaster> c) +{ + if (find (_transport_masters.begin(), _transport_masters.end(), c) == _transport_masters.end()) { + warning << string_compose (X_("programming error: attempt to use unknown transport master named \"%1\"\n"), c->name()); + return -1; + } + + _current_master = c; + _master_speed = 0; + _master_position = 0; + + master_dll_initstate = 0; + + DEBUG_TRACE (DEBUG::Slave, string_compose ("current transport master set to %1\n", c->name())); + + return 0; +} + +int +TransportMasterManager::set_current (boost::shared_ptr<TransportMaster> c) +{ + int ret = -1; + boost::shared_ptr<TransportMaster> old (_current_master); + + { + Glib::Threads::RWLock::WriterLock lm (lock); + ret = set_current_locked (c); + } + + if (ret == 0) { + CurrentChanged (old, _current_master); // EMIT SIGNAL + } + + return ret; +} + +int +TransportMasterManager::set_current (SyncSource ss) +{ + int ret = -1; + boost::shared_ptr<TransportMaster> old (_current_master); + + { + Glib::Threads::RWLock::WriterLock lm (lock); + + for (TransportMasters::iterator t = _transport_masters.begin(); t != _transport_masters.end(); ++t) { + if ((*t)->type() == ss) { + ret = set_current_locked (*t); + break; + } + } + } + + if (ret == 0) { + CurrentChanged (old, _current_master); // EMIT SIGNAL + } + + return ret; +} + + +int +TransportMasterManager::set_current (std::string const & str) +{ + int ret = -1; + boost::shared_ptr<TransportMaster> old (_current_master); + + { + Glib::Threads::RWLock::WriterLock lm (lock); + + for (TransportMasters::iterator t = _transport_masters.begin(); t != _transport_masters.end(); ++t) { + if ((*t)->name() == str) { + ret = set_current_locked (*t); + break; + } + } + } + + if (ret == 0) { + CurrentChanged (old, _current_master); // EMIT SIGNAL + } + + return ret; +} + + +void +TransportMasterManager::clear () +{ + { + Glib::Threads::RWLock::WriterLock lm (lock); + _transport_masters.clear (); + } + + Removed (boost::shared_ptr<TransportMaster>()); +} + +int +TransportMasterManager::set_state (XMLNode const & node, int version) +{ + assert (node.name() == state_node_name); + + XMLNodeList const & children = node.children(); + + + if (!children.empty()) { + _transport_masters.clear (); + } + + { + Glib::Threads::RWLock::WriterLock lm (lock); + + + for (XMLNodeList::const_iterator c = children.begin(); c != children.end(); ++c) { + + boost::shared_ptr<TransportMaster> tm = TransportMaster::factory (**c); + + if (add_locked (tm)) { + continue; + } + + /* we know it is the last thing added to the list of masters */ + + _transport_masters.back()->set_state (**c, version); + } + } + + std::string current_master; + if (node.get_property (X_("current"), current_master)) { + set_current (current_master); + } else { + set_current (MTC); + } + + return 0; +} + +XMLNode& +TransportMasterManager::get_state () +{ + XMLNode* node = new XMLNode (state_node_name); + + node->set_property (X_("current"), _current_master->name()); + + Glib::Threads::RWLock::ReaderLock lm (lock); + + for (TransportMasters::iterator t = _transport_masters.begin(); t != _transport_masters.end(); ++t) { + node->add_child_nocopy ((*t)->get_state()); + } + + return *node; +} + +boost::shared_ptr<TransportMaster> +TransportMasterManager::master_by_type (SyncSource src) const +{ + Glib::Threads::RWLock::ReaderLock lm (lock); + + for (TransportMasters::const_iterator tm = _transport_masters.begin(); tm != _transport_masters.end(); ++tm) { + if ((*tm)->type() == src) { + return *tm; + } + } + + return boost::shared_ptr<TransportMaster> (); +} |