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
This commit is contained in:
parent
e6e0edbe90
commit
8296a030a5
@ -1369,7 +1369,34 @@ private:
|
||||
};
|
||||
|
||||
samplepos_t master_wait_end;
|
||||
void track_transport_master (float slave_speed, samplepos_t slave_transport_sample);
|
||||
|
||||
enum TransportMasterAction {
|
||||
TransportMasterRelax,
|
||||
TransportMasterNoRoll,
|
||||
TransportMasterLocate,
|
||||
TransportMasterStart,
|
||||
TransportMasterStop,
|
||||
TransportMasterWait,
|
||||
};
|
||||
|
||||
struct TransportMasterStrategy {
|
||||
TransportMasterAction action;
|
||||
samplepos_t target;
|
||||
LocateTransportDisposition roll_disposition;
|
||||
double catch_speed;
|
||||
|
||||
TransportMasterStrategy ()
|
||||
: action (TransportMasterRelax)
|
||||
, target (0)
|
||||
, roll_disposition (MustStop)
|
||||
, catch_speed (0.) {}
|
||||
};
|
||||
|
||||
TransportMasterStrategy transport_master_strategy;
|
||||
double plan_master_strategy (pframes_t nframes, double master_speed, samplepos_t master_transport_sample, double catch_speed);
|
||||
double plan_master_strategy_engine (pframes_t nframes, double master_speed, samplepos_t master_transport_sample, double catch_speed);
|
||||
bool implement_master_strategy ();
|
||||
|
||||
bool follow_transport_master (pframes_t nframes);
|
||||
|
||||
void sync_source_changed (SyncSource, samplepos_t pos, pframes_t cycle_nframes);
|
||||
|
@ -421,9 +421,11 @@ AudioEngine::process_callback (pframes_t nframes)
|
||||
}
|
||||
|
||||
if (!_freewheeling || Freewheel.empty()) {
|
||||
const double engine_speed = tmm.pre_process_transport_masters (nframes, sample_time_at_cycle_start());
|
||||
double engine_speed = tmm.pre_process_transport_masters (nframes, sample_time_at_cycle_start());
|
||||
engine_speed = _session->plan_master_strategy (nframes, tmm.get_current_position_in_process_context(), tmm.get_current_position_in_process_context(), engine_speed);
|
||||
Port::set_speed_ratio (engine_speed);
|
||||
DEBUG_TRACE (DEBUG::Slave, string_compose ("transport master (current=%1) gives speed %2 (ports using %3)\n", tmm.current() ? tmm.current()->name() : string("[]"), engine_speed, Port::speed_ratio()));
|
||||
|
||||
#if 0 // USE FOR DEBUG ONLY
|
||||
/* use with Dummy backend, engine pulse and
|
||||
* scripts/_find_nonzero_sample.lua
|
||||
|
@ -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,30 +1100,14 @@ 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)
|
||||
{
|
||||
TransportMasterManager& tmm (TransportMasterManager::instance());
|
||||
|
||||
double master_speed;
|
||||
samplepos_t master_transport_sample;
|
||||
sampleoffset_t delta;
|
||||
|
||||
if (tmm.master_invalid_this_cycle()) {
|
||||
DEBUG_TRACE (DEBUG::Slave, "session told not to use the transport master this cycle\n");
|
||||
goto noroll;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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()));
|
||||
|
||||
if (tmm.current()->type() == Engine) {
|
||||
|
||||
/* JACK Transport. */
|
||||
|
||||
TransportMasterManager& tmm (TransportMasterManager::instance());
|
||||
sampleoffset_t delta = _transport_sample - master_transport_sample;
|
||||
|
||||
if (master_speed == 0) {
|
||||
|
||||
if (!actively_recording()) {
|
||||
@ -1159,31 +1145,13 @@ Session::follow_transport_master (pframes_t nframes)
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
/* 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)) {
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
@ -1196,25 +1164,300 @@ Session::follow_transport_master (pframes_t nframes)
|
||||
TFSM_STOP (false, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 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.
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
const sampleoffset_t delta = _transport_sample - master_transport_sample;
|
||||
|
||||
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));
|
||||
|
||||
const bool interesting_transport_state_change_underway = (locate_pending() || declick_in_progress());
|
||||
|
||||
if ((transport_master_strategy.action == TransportMasterWait) || (transport_master_strategy.action == TransportMasterNoRoll)) {
|
||||
|
||||
/* 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.
|
||||
*/
|
||||
|
||||
DEBUG_TRACE (DEBUG::Slave, "had been waiting for locate-to-catch-master to finish\n");
|
||||
|
||||
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 samplecnt_t wlp = worst_latency_preroll_buffer_size_ceil ();
|
||||
bool should_locate;
|
||||
|
||||
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 {
|
||||
|
||||
/* 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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/* 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.
|
||||
*/
|
||||
|
||||
tmm.reinit (master_speed, master_transport_sample);
|
||||
|
||||
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 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
|
||||
*/
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/* speed is set, we're locked, and good to go */
|
||||
} 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 ();
|
||||
return true;
|
||||
|
||||
noroll:
|
||||
/* don't move at all */
|
||||
DEBUG_TRACE (DEBUG::Slave, "no roll\n")
|
||||
no_roll (nframes);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
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));
|
||||
transport_master_strategy.action = TransportMasterStop;
|
||||
return catch_speed;
|
||||
}
|
||||
}
|
||||
|
||||
/* 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.
|
||||
*/
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -439,6 +439,8 @@ TransportMasterManager::set_current_locked (boost::shared_ptr<TransportMaster> c
|
||||
|
||||
master_dll_initstate = 0;
|
||||
|
||||
unblock_disk_output ();
|
||||
|
||||
DEBUG_TRACE (DEBUG::Slave, string_compose ("current transport master set to %1\n", (c ? c->name() : string ("none"))));
|
||||
|
||||
return 0;
|
||||
|
Loading…
Reference in New Issue
Block a user