summaryrefslogtreecommitdiff
path: root/libs/ardour/session_process.cc
diff options
context:
space:
mode:
authorPaul Davis <paul@linuxaudiosystems.com>2020-03-20 17:38:23 -0600
committerPaul Davis <paul@linuxaudiosystems.com>2020-03-20 17:38:38 -0600
commit8296a030a582bd620479907ab17afc8fdc92609a (patch)
treeac94d91393b85b3d612e11fe5393575ee2cb89d5 /libs/ardour/session_process.cc
parente6e0edbe90cff679831c4d4f2dec8789702d3296 (diff)
redesign chasing the transport master
Substantive comments associated with code in Session::plan_master_strategy. Known not to work for reverse TC. Also, the JACK related code has not yet been tested
Diffstat (limited to 'libs/ardour/session_process.cc')
-rw-r--r--libs/ardour/session_process.cc381
1 files changed, 312 insertions, 69 deletions
diff --git a/libs/ardour/session_process.cc b/libs/ardour/session_process.cc
index b84b5c124c..c367ad858e 100644
--- a/libs/ardour/session_process.cc
+++ b/libs/ardour/session_process.cc
@@ -503,7 +503,8 @@ Session::process_with_events (pframes_t nframes)
}
if (!_exporting && config.get_external_sync()) {
- if (!follow_transport_master (nframes)) {
+ if (!implement_master_strategy ()) {
+ no_roll (nframes);
ltc_tx_send_time_code_for_cycle (_transport_sample, end_sample, _target_transport_speed, _transport_speed, nframes);
return;
}
@@ -641,7 +642,8 @@ Session::process_without_events (pframes_t nframes)
}
if (!_exporting && config.get_external_sync()) {
- if (!follow_transport_master (nframes)) {
+ if (!implement_master_strategy ()) {
+ no_roll (nframes);
ltc_tx_send_time_code_for_cycle (_transport_sample, _transport_sample, 0, 0 , nframes);
return;
}
@@ -1098,123 +1100,364 @@ Session::emit_thread_run ()
pthread_mutex_unlock (&_rt_emit_mutex);
}
-bool
-Session::follow_transport_master (pframes_t nframes)
+double
+Session::plan_master_strategy_engine (pframes_t nframes, double master_speed, samplepos_t master_transport_sample, double catch_speed)
{
+ /* JACK Transport. */
+
TransportMasterManager& tmm (TransportMasterManager::instance());
+ sampleoffset_t delta = _transport_sample - master_transport_sample;
- double master_speed;
- samplepos_t master_transport_sample;
- sampleoffset_t delta;
+ if (master_speed == 0) {
- if (tmm.master_invalid_this_cycle()) {
- DEBUG_TRACE (DEBUG::Slave, "session told not to use the transport master this cycle\n");
- goto noroll;
+ if (!actively_recording()) {
+
+ const samplecnt_t wlp = worst_latency_preroll_buffer_size_ceil ();
+
+ if (delta != wlp) {
+
+ /* if we're not aligned with the current JACK * time, then jump to it */
+
+ if (!locate_pending() && !declick_in_progress() && !tmm.current()->starting()) {
+
+ const samplepos_t locate_target = master_transport_sample + wlp;
+ DEBUG_TRACE (DEBUG::Slave, string_compose ("JACK transport: jump to master position %1 by locating to %2\n", master_transport_sample, locate_target));
+ /* for JACK transport always stop after the locate (2nd argument == false) */
+ TFSM_LOCATE (locate_target, MustStop, true, false, false);
+
+ } else {
+ DEBUG_TRACE (DEBUG::Slave, string_compose ("JACK Transport: locate already in process, sts = %1\n", master_transport_sample));
+ }
+ }
+ }
+
+ } else {
+
+ if (_transport_speed) {
+ /* master is rolling, and we're rolling ... with JACK we should always be perfectly in sync, so ... WTF? */
+ if (delta) {
+ if (remaining_latency_preroll() && worst_latency_preroll()) {
+ /* our transport position is not moving because we're doing latency alignment. Nothing in particular to do */
+ } else {
+ cerr << "\n\n\n IMPOSSIBLE! OUT OF SYNC WITH JACK TRANSPORT (rlp = " << remaining_latency_preroll() << " wlp " << worst_latency_preroll() << ")\n\n\n";
+ }
+ }
+ }
+ }
+
+
+ if (!locate_pending() && !declick_in_progress()) {
+
+ if (master_speed != 0.0) {
+
+ /* master rolling, we should be too */
+
+ if (_transport_speed == 0.0f) {
+ DEBUG_TRACE (DEBUG::Slave, string_compose ("slave starts transport: %1 sample %2 tf %3\n", master_speed, master_transport_sample, _transport_sample));
+ TFSM_EVENT (TransportFSM::StartTransport);
+ }
+
+ } else if (!tmm.current()->starting()) { /* master stopped, not in "starting" state */
+
+ if (_transport_speed != 0.0f) {
+ DEBUG_TRACE (DEBUG::Slave, string_compose ("slave stops transport: %1 sample %2 tf %3\n", master_speed, master_transport_sample, _transport_sample));
+ TFSM_STOP (false, false);
+ }
+ }
}
- master_speed = tmm.get_current_speed_in_process_context();
- master_transport_sample = tmm.get_current_position_in_process_context ();
- delta = _transport_sample - master_transport_sample;
+ return catch_speed;
+}
+
+double
+Session::plan_master_strategy (pframes_t nframes, double master_speed, samplepos_t master_transport_sample, double catch_speed)
+{
+ /* This is called from inside AudioEngine::process_callback(),
+ * immediately after the TransportMasterManager has run its
+ * ::pre_process_transport_masters() method to allow all transport
+ * masters to update their information on the speed and position
+ * indicated by their data sources.
+ *
+ * Our task here is to determine what the Session should do during its
+ * process() call in order to respond to the transport master (or to
+ * not respond at all, if we're not using external sync). We want to
+ * set transport_master_strategy.action, which will be used from within
+ * the Session process() callback (via ::implement_master_strategy())
+ * to determine what, if anything to do there.
+ *
+ * The return value is the speed (aka "ratio") to be used by the port
+ * resampler. If we're not chasing the master, the correct answer will
+ * be 1.0. This can occur in a number of scenarios. If we are synced
+ * and locked to the master, we want to use the "catch speed" given to
+ * us as a parameter. This was determined by the
+ * TransportMasterManager as the correct speed to use in order to
+ * reduce the delta between the master's position and the session
+ * transport position.
+ *
+ * In situations where we are not synced+locked, either temporarily or
+ * longer term, we return 1.0, which leads to no resampling, and the
+ * session will run at normal speed.
+ */
+
+ if (!config.get_external_sync()) {
+ return 1.0;
+ }
- DEBUG_TRACE (DEBUG::Slave, string_compose ("session at %1, master at %2, delta: %3 res: %4 TFSM state %5\n", _transport_sample, master_transport_sample, delta, tmm.current()->resolution(), _transport_fsm->current_state()));
+ TransportMasterManager& tmm (TransportMasterManager::instance());
+ const samplecnt_t locate_threshold = 5 * current_block_size;
+
+ if (tmm.master_invalid_this_cycle()) {
+ DEBUG_TRACE (DEBUG::Slave, "session told not to use the transport master this cycle\n");
+ transport_master_strategy.action = TransportMasterNoRoll;
+ return 1.0;
+ }
if (tmm.current()->type() == Engine) {
+ /* JACK is fundamentally different */
+ return plan_master_strategy_engine (nframes, master_speed, master_transport_sample, catch_speed);
+ }
- /* JACK Transport. */
+ const sampleoffset_t delta = _transport_sample - master_transport_sample;
- if (master_speed == 0) {
+ DEBUG_TRACE (DEBUG::Slave, string_compose ("\n\n\n\nsession at %1, master at %2, delta: %3 res: %4 TFSM state %5 action %6\n", _transport_sample, master_transport_sample, delta, tmm.current()->resolution(), _transport_fsm->current_state(), transport_master_strategy.action));
- if (!actively_recording()) {
+ const bool interesting_transport_state_change_underway = (locate_pending() || declick_in_progress());
- const samplecnt_t wlp = worst_latency_preroll_buffer_size_ceil ();
+ if ((transport_master_strategy.action == TransportMasterWait) || (transport_master_strategy.action == TransportMasterNoRoll)) {
- if (delta != wlp) {
+ /* We've either been:
+ *
+ * 1) waiting for the master to catch up with a position that
+ * we located to (Wait)
+ * 2) waiting to be able to use the master's speed & position
+ *
+ * The two cases are very similar, but differ in the conditions
+ * under which we need to initiate a (possibly successive)
+ * locate in response to the master's position
+ *
+ * This code is very similar to the non-wait case (the "else"
+ * that ends this scope). The big difference is that here we
+ * know that we've just finished a locate specifically in order
+ * to catch the master. This changes the logic a little bit.
+ */
- /* if we're not aligned with the current JACK * time, then jump to it */
+ DEBUG_TRACE (DEBUG::Slave, "had been waiting for locate-to-catch-master to finish\n");
- if (!locate_pending() && !declick_in_progress() && !tmm.current()->starting()) {
+ if (interesting_transport_state_change_underway) {
+ /* still waiting for the declick and/or locate to
+ finish ... nothing to do for now.
+ */
+ DEBUG_TRACE (DEBUG::Slave, "still waiting for the locate to finish\n");
+ return 1.0;
+ }
- const samplepos_t locate_target = master_transport_sample + wlp;
- DEBUG_TRACE (DEBUG::Slave, string_compose ("JACK transport: jump to master position %1 by locating to %2\n", master_transport_sample, locate_target));
- /* for JACK transport always stop after the locate (2nd argument == false) */
- TFSM_LOCATE (locate_target, MustStop, true, false, false);
+ const samplecnt_t wlp = worst_latency_preroll_buffer_size_ceil ();
+ bool should_locate;
- } else {
- DEBUG_TRACE (DEBUG::Slave, string_compose ("JACK Transport: locate already in process, sts = %1\n", master_transport_sample));
- }
- }
- }
+ if (transport_master_strategy.action == TransportMasterNoRoll) {
+ /* We've been waiting to be able to use the master's
+ * position (i.e to get a lock on the incoming data
+ * stream). We need to locate if we're either ahead or
+ * behind the master by <threshold>.
+ */
+
+ should_locate = abs (delta) > locate_threshold;
} else {
- if (_transport_speed) {
- /* master is rolling, and we're rolling ... with JACK we should always be perfectly in sync, so ... WTF? */
- if (delta) {
- if (remaining_latency_preroll() && worst_latency_preroll()) {
- /* our transport position is not moving because we're doing latency alignment. Nothing in particular to do */
- } else {
- cerr << "\n\n\n IMPOSSIBLE! OUT OF SYNC WITH JACK TRANSPORT (rlp = " << remaining_latency_preroll() << " wlp " << worst_latency_preroll() << ")\n\n\n";
- }
- }
- }
+ /* we located to be ahead of the master's position (see
+ * the locate call in the next "else" scope where we
+ * jump ahead by a significant distance).
+ *
+ * So, we should be ahead (or behind) the master's
+ * position, and waiting for it to get close to us.
+ *
+ * We only need to locate again if we are actually
+ * behind (or ahead, for reverse motion) of the master
+ * by more than <threshold>.
+ */
+
+ should_locate = delta < 0 && (abs (delta) > locate_threshold);
}
- } else {
+ if (should_locate) {
+
+ /* we're too far from the master to catch it via
+ * varispeed ... need to locate ahead of it, wait for
+ * it to get cose to us, then varispeed to sync.
+ *
+ * We assume that the transport state after the locate
+ * is always Stopped - we don't restart the transport
+ * until the master catches us, or at least gets close
+ * to our new position.
+ *
+ * Any time we locate, we need to reset the DLL used by
+ * the TransportMasterManager. Do that here, since the
+ * TMM will not need that again until after we start
+ * the locate (and hence the apparent transport
+ * position of the Session will reflect the target we
+ * set here). That is because the locate will be
+ * initiated in the Session::process() callback that is
+ * about to happen right after we return.
+ */
+
+ tmm.reinit (master_speed, master_transport_sample);
+
+ samplepos_t locate_target = master_transport_sample;
+
+ locate_target += wlp + lrintf (ntracks() * sample_rate() * 0.05);
+
+ DEBUG_TRACE (DEBUG::Slave, string_compose ("After locate-to-catch-master, still too far off (%1). Locate again to %2\n", delta, locate_target));
+
+ transport_master_strategy.action = TransportMasterLocate;
+ transport_master_strategy.target = locate_target;
+ transport_master_strategy.roll_disposition = MustStop;
+ transport_master_strategy.catch_speed = catch_speed;
+
+ return 1.0;
+ }
+
+ if (delta > wlp) {
+
+ /* We're close, but haven't reached the point where we
+ * need to start rolling for preroll latency yet.
+ */
+
+ DEBUG_TRACE (DEBUG::Slave, string_compose ("master @ %1 is not yet within %2 of our position %3 (delta is %4)\n", master_transport_sample, wlp, _transport_sample, delta));
+ return 1.0;
+ }
- /* This is a heuristic rather than a strictly provable rule. The idea
+ /* case #3: we should start rolling */
+
+ DEBUG_TRACE (DEBUG::Slave, string_compose ("master @ %1 is WITHIN %2 of our position %3 (delta is %4), so start\n", master_transport_sample, wlp, _transport_sample, delta));
+
+ transport_master_strategy.action = TransportMasterStart;
+ transport_master_strategy.catch_speed = catch_speed;
+ return catch_speed;
+
+ }
+
+ /* currently we're not waiting to sync with the master. So
+ * check if we're way out of alignment (case #1) or just a bit
+ * out of alignment (case #2)
+ */
+
+ if (abs (delta) > locate_threshold) {
+
+ /* CASE ONE
+ *
+ * This is a heuristic rather than a strictly provable rule. The idea
* is that if we're "far away" from the master, we should locate to its
* current position, and then varispeed to sync with it.
*
* On the other hand, if we're close to it, just varispeed.
*/
- if (!actively_recording() && abs (delta) > (5 * current_block_size)) {
+ tmm.reinit (master_speed, master_transport_sample);
- if (!locate_pending() && !declick_in_progress()) {
- DEBUG_TRACE (DEBUG::Slave, string_compose ("request locate to master position %1\n", master_transport_sample));
- /* note that for non-JACK transport masters, we assume that the transport state (rolling,stopped) after the locate
- * remains unchanged (2nd argument, "roll-after-locate")
- */
- tmm.reinit (master_speed, master_transport_sample);
- TFSM_LOCATE (master_transport_sample, (master_speed != 0) ? MustRoll : MustStop, true, false, false);
- }
+ samplepos_t locate_target = master_transport_sample;
+
+ locate_target += lrintf (ntracks() * sample_rate() * 0.05);
+
+ DEBUG_TRACE (DEBUG::Slave, string_compose ("request locate to master position %1\n", locate_target));
+
+ transport_master_strategy.action = TransportMasterLocate;
+ transport_master_strategy.target = locate_target;
+ transport_master_strategy.roll_disposition = (master_speed != 0) ? MustRoll : MustStop;
+ transport_master_strategy.catch_speed = catch_speed;
+
+ /* Session::process_with(out)_events() will take this
+ * up when called.
+ */
- return true;
+ return 1.0;
+
+ } else if (abs (delta) > tmm.current()->resolution()) {
+
+ /* CASE TWO
+ *
+ * If we're close, but not within the resolution of the
+ * master, just varispeed to chase the master, and be
+ * silent till we're synced
+ */
+
+ tmm.block_disk_output ();
+
+ } else {
+
+ /* speed is set, we're locked and synced and good to go */
+
+ if (!locate_pending() && !declick_in_progress()) {
+ DEBUG_TRACE (DEBUG::Slave, "master/slave synced & locked\n");
+ tmm.unblock_disk_output ();
}
}
if (master_speed != 0.0) {
+
+ /* master rolling, we should be too */
+
if (_transport_speed == 0.0f) {
DEBUG_TRACE (DEBUG::Slave, string_compose ("slave starts transport: %1 sample %2 tf %3\n", master_speed, master_transport_sample, _transport_sample));
- TFSM_EVENT (TransportFSM::StartTransport);
+ transport_master_strategy.action = TransportMasterStart;
+ transport_master_strategy.catch_speed = catch_speed;
+ return catch_speed;
}
} else if (!tmm.current()->starting()) { /* master stopped, not in "starting" state */
if (_transport_speed != 0.0f) {
DEBUG_TRACE (DEBUG::Slave, string_compose ("slave stops transport: %1 sample %2 tf %3\n", master_speed, master_transport_sample, _transport_sample));
- TFSM_STOP (false, false);
+ transport_master_strategy.action = TransportMasterStop;
+ return catch_speed;
}
}
- /* This is the second part of the "we're not synced yet" code. If we're
- * close, but not within the resolution of the master, silence disk
- * output but continue to varispeed to get in sync.
+ /* we were not waiting for the master, we're close enough to
+ * it, and our transport state already matched the master
+ * (stopped or rolling). We should just continue
+ * resampling/varispeeding at "catch_speed" in order to remain
+ * synced with the master.
*/
- if ((tmm.current()->type() != Engine) && !actively_recording() && abs (delta) > tmm.current()->resolution()) {
- /* just varispeed to chase the master, and be silent till we're synced */
- tmm.block_disk_output ();
- return true;
+ transport_master_strategy.action = TransportMasterRelax;
+ return catch_speed;
+}
+
+bool
+Session::implement_master_strategy ()
+{
+ /* This is called from within Session::process(), only if we are using
+ * external sync. The task here is simply to implement whatever actions
+ * where decided by ::plan_master_strategy (), from within the
+ * ::process() callback (the planning step is executed before
+ * Session::process() begins.
+ */
+
+ DEBUG_TRACE (DEBUG::Slave, string_compose ("Implementing master strategy: %1\n", transport_master_strategy.action));
+
+ switch (transport_master_strategy.action) {
+ case TransportMasterNoRoll:
+ /* This is the one case where we do not want the session to
+ call ::roll() under any circumstances. Returning false here
+ will do that.
+ */
+ return false;
+ case TransportMasterRelax:
+ break;
+ case TransportMasterWait:
+ break;
+ case TransportMasterLocate:
+ transport_master_strategy.action = TransportMasterWait;
+ TFSM_LOCATE(transport_master_strategy.target, transport_master_strategy.roll_disposition, true, false, false);
+ break;
+ case TransportMasterStart:
+ TFSM_EVENT (TransportFSM::StartTransport);
+ break;
+ case TransportMasterStop:
+ TFSM_STOP (false, false);
+ break;
}
- /* speed is set, we're locked, and good to go */
- tmm.unblock_disk_output ();
return true;
-
- noroll:
- /* don't move at all */
- DEBUG_TRACE (DEBUG::Slave, "no roll\n")
- no_roll (nframes);
- return false;
}