complete removal of Session::_transport_speed, _default_transport_speed and takeover of transport state mgmt by TransportFSM

The TransportFSM is now responsible for deciding what to do at all transport state transitions. The Session (via the TransportAPI) merely
provides mechanism (locate, start, stop, set_speed). Default and most recent speed requests are managed by the TransportFSM too
This commit is contained in:
Paul Davis 2021-05-03 17:35:34 -06:00
parent 3d10f44b30
commit 4b87c3a6b1
9 changed files with 290 additions and 245 deletions

View File

@ -797,12 +797,8 @@ public:
bool synced_to_engine() const;
double engine_speed() const { return _engine_speed; }
double actual_speed() const {
if (_transport_speed > 0) return _engine_speed;
if (_transport_speed < 0) return - _engine_speed;
return 0;
}
double transport_speed() const { return _count_in_samples > 0 ? 0. : _transport_speed; }
double actual_speed() const;
double transport_speed() const;
/** @return true if the transport state (TFSM) is stopped */
bool transport_stopped() const;
/** @return true if the transport state (TFSM) is stopped or stopping */
@ -1336,16 +1332,17 @@ protected:
/* transport API */
void locate (samplepos_t, bool with_roll, bool for_loop_end=false, bool force=false, bool with_mmc=true);
void locate (samplepos_t, bool for_loop_end=false, bool force=false, bool with_mmc=true);
void stop_transport (bool abort = false, bool clear_state = false);
void start_transport ();
void butler_completed_transport_work ();
void post_locate ();
void schedule_butler_for_transport_work ();
bool should_roll_after_locate () const;
double speed() const { return _transport_speed; }
bool user_roll_after_locate () const;
bool should_stop_before_locate () const;
samplepos_t position() const { return _transport_sample; }
void set_transport_speed (double speed, bool as_default, bool at_next_start);
void set_transport_speed (double speed);
bool need_declick_before_locate () const;
private:
@ -1382,7 +1379,6 @@ private:
// varispeed playback -- TODO: move out of session to backend.
double _engine_speed;
double _transport_speed; // only: -1, 0, +1
double _default_transport_speed;
double _default_engine_speed;
double _last_transport_speed;

View File

@ -34,15 +34,15 @@ class LIBARDOUR_API TransportAPI
private:
friend struct TransportFSM;
virtual void locate (samplepos_t, bool with_roll, bool with_loop=false, bool force=false, bool with_mmc=true) = 0;
virtual void locate (samplepos_t, bool with_loop=false, bool force=false, bool with_mmc=true) = 0;
virtual bool should_stop_before_locate () const = 0;
virtual void stop_transport (bool abort = false, bool clear_state = false) = 0;
virtual void start_transport () = 0;
virtual void butler_completed_transport_work () = 0;
virtual void schedule_butler_for_transport_work () = 0;
virtual bool should_roll_after_locate () const = 0;
virtual double speed() const = 0;
virtual void set_transport_speed (double speed, bool as_default, bool at_next_start) = 0;
virtual bool user_roll_after_locate() const = 0;
virtual void set_transport_speed (double speed) = 0;
virtual samplepos_t position() const = 0;
virtual bool need_declick_before_locate () const = 0;
};

View File

@ -147,12 +147,13 @@ struct TransportFSM
std::string current_state () const;
int transport_speed () const;
double transport_speed() const { return _transport_speed; }
private:
MotionState _motion_state;
ButlerState _butler_state;
DirectionState _direction_state;
double _transport_speed;
void init();
@ -161,7 +162,7 @@ struct TransportFSM
void schedule_butler_for_transport_work () const;
void start_playback ();
void stop_playback (Event const &);
void start_locate_after_declick () const;
void start_locate_after_declick ();
void locate_for_loop (Event const &);
void roll_after_locate () const;
void start_locate_while_stopped (Event const &) const;
@ -199,7 +200,6 @@ struct TransportFSM
bool process_event (Event&, bool was_deferred, bool& deferred);
mutable Event _last_locate;
Event last_speed_request;
TransportAPI* api;
typedef boost::intrusive::list<Event> EventList;
@ -207,12 +207,14 @@ struct TransportFSM
EventList deferred_events;
int processing;
mutable boost::optional<bool> current_roll_after_locate_status;
double most_recently_requested_speed;
mutable double most_recently_requested_speed;
mutable double default_speed;
void defer (Event& ev);
void bad_transition (Event const &);
void set_roll_after (bool) const;
bool compute_should_roll (LocateTransportDisposition) const;
int compute_transport_speed () const;
};
} /* end namespace ARDOUR */

View File

@ -191,9 +191,6 @@ Session::Session (AudioEngine &eng,
, _silent (false)
, _remaining_latency_preroll (0)
, _engine_speed (1.0)
, _transport_speed (0)
, _default_transport_speed (1.0)
, _default_engine_speed (1.0)
, _last_transport_speed (1.0)
, _requested_transport_speed (std::numeric_limits<double>::max())
, _signalled_varispeed (0)
@ -1872,7 +1869,7 @@ Session::_locations_changed (const Locations::LocationList& locations)
void
Session::enable_record ()
{
if (_transport_speed != 0.0 && _transport_speed != 1.0) {
if (_transport_fsm->transport_speed() != 0.0 && _transport_fsm->transport_speed() != 1.0) {
/* no recording at anything except normal speed */
return;
}
@ -1964,11 +1961,11 @@ Session::maybe_enable_record (bool rt_context)
/* Save pending state of which sources the next record will use,
* which gives us some chance of recovering from a crash during the record.
*/
if (!rt_context && (!quick_start || _transport_speed == 0)) {
if (!rt_context && (!quick_start || _transport_fsm->transport_speed() == 0)) {
save_state ("", true);
}
if (_transport_speed != 0) {
if (_transport_fsm->transport_speed() != 0) {
maybe_allow_only_punch ();
if (!config.get_punch_in()) {
enable_record ();
@ -2020,7 +2017,7 @@ Session::audible_sample (bool* latent_locate) const
}
#if 0 // TODO looping
if (_transport_speed > 0.0f) {
if (_transport_fsm->transport_speed() > 0.0f) {
if (play_loop && have_looped) {
/* the play-position wrapped at the loop-point
* ardour is already playing the beginning of the loop,
@ -2036,7 +2033,7 @@ Session::audible_sample (bool* latent_locate) const
}
}
}
} else if (_transport_speed < 0.0f) {
} else if (_transport_fsm->transport_speed() < 0.0f) {
/* XXX wot? no backward looping? */
}
#endif

View File

@ -42,6 +42,9 @@ using namespace std;
using namespace ARDOUR;
using namespace PBD;
#define TFSM_ROLL() { _transport_fsm->enqueue (new TransportFSM::Event (TransportFSM::StartTransport)); }
#define TFSM_SPEED(speed,as_default) { _transport_fsm->enqueue (new TransportFSM::Event (speed,as_default)); }
boost::shared_ptr<ExportHandler>
Session::get_export_handler ()
{
@ -116,7 +119,7 @@ Session::start_audio_export (samplepos_t position, bool realtime, bool region_ex
if (!_exporting) {
pre_export ();
} else if (_transport_speed != 0) {
} else if (_transport_fsm->transport_speed() != 0) {
realtime_stop (true, true);
}
@ -302,13 +305,13 @@ Session::process_export_fw (pframes_t nframes)
return;
}
set_transport_speed (1.0, false, true);
start_transport ();
TFSM_SPEED (1.0, false);
TFSM_ROLL ();
butler_transport_work (true);
g_atomic_int_set (&_butler->should_do_transport_work, 0);
butler_completed_transport_work ();
/* Session::process_with_events () sets _remaining_latency_preroll = 0
* when being called with _transport_speed == 0.0.
* when being called with _transport_fsm->transport_speed() == 0.
*
* This can happen wit JACK, there is a process-callback before
* freewheeling becomes active, after Session::start_audio_export().

View File

@ -52,6 +52,7 @@
#include "ardour/profile.h"
#include "ardour/session.h"
#include "ardour/transport_master.h"
#include "ardour/transport_fsm.h"
#include "ardour/ticker.h"
#include "pbd/i18n.h"
@ -142,7 +143,7 @@ Session::mmc_record_strobe (MIDI::MachineControl &/*mmc*/)
/* record strobe does an implicit "Play" command */
if (_transport_speed != 1.0) {
if (_transport_fsm->transport_speed() != 1.0) {
/* start_transport() will move from Enabled->Recording, so we
don't need to do anything here except enable recording.
@ -222,7 +223,7 @@ Session::mmc_step (MIDI::MachineControl &/*mmc*/, int steps)
double diff_secs = diff.tv_sec + (diff.tv_usec / 1000000.0);
double cur_speed = (((steps * 0.5) * timecode_frames_per_second()) / diff_secs) / timecode_frames_per_second();
if (_transport_speed == 0 || cur_speed * _transport_speed < 0) {
if (_transport_fsm->transport_speed() == 0 || cur_speed * _transport_fsm->transport_speed() < 0) {
/* change direction */
step_speed = cur_speed;
} else {
@ -233,7 +234,7 @@ Session::mmc_step (MIDI::MachineControl &/*mmc*/, int steps)
#if 0
cerr << "delta = " << diff_secs
<< " ct = " << _transport_speed
<< " ct = " << _transport_fsm->transport_speed()
<< " steps = " << steps
<< " new speed = " << cur_speed
<< " speed = " << step_speed
@ -493,7 +494,7 @@ Session::send_midi_time_code_for_cycle (samplepos_t start_sample, samplepos_t en
return 0;
}
if (_transport_speed < 0) {
if (_transport_fsm->transport_speed() < 0) {
// we don't support rolling backwards
return 0;
}
@ -565,7 +566,7 @@ Session::send_midi_time_code_for_cycle (samplepos_t start_sample, samplepos_t en
assert (msg_time < end_sample);
/* convert from session samples back to JACK samples using the transport speed */
ARDOUR::pframes_t const out_stamp = (msg_time - start_sample) / _transport_speed;
ARDOUR::pframes_t const out_stamp = (msg_time - start_sample) / _transport_fsm->transport_speed();
assert (out_stamp < nframes);
MidiBuffer& mb (_midi_ports->mtc_output_port()->get_midi_buffer(nframes));
@ -623,7 +624,7 @@ Session::mmc_step_timeout ()
timersub (&now, &last_mmc_step, &diff);
diff_usecs = diff.tv_sec * 1000000 + diff.tv_usec;
if (diff_usecs > 1000000.0 || fabs (_transport_speed) < 0.0000001) {
if (diff_usecs > 1000000.0 || fabs (_transport_fsm->transport_speed()) < 0.0000001) {
/* too long or too slow, stop transport */
request_stop ();
step_queued = false;
@ -637,7 +638,7 @@ Session::mmc_step_timeout ()
/* slow it down */
request_transport_speed_nonzero (_transport_speed * 0.75);
request_transport_speed_nonzero (actual_speed() * 0.75);
return true;
}

View File

@ -165,9 +165,11 @@ Session::fail_roll (pframes_t nframes)
int
Session::no_roll (pframes_t nframes)
{
assert (_transport_fsm->transport_speed() == 0);
PT_TIMING_CHECK (4);
samplepos_t end_sample = _transport_sample + floor (nframes * _transport_speed);
samplepos_t end_sample = _transport_sample + floor (nframes * _transport_fsm->transport_speed());
int ret = 0;
boost::shared_ptr<RouteList> r = routes.reader ();
@ -215,7 +217,7 @@ Session::process_routes (pframes_t nframes, bool& need_butler)
boost::shared_ptr<RouteList> r = routes.reader ();
const samplepos_t start_sample = _transport_sample;
const samplepos_t end_sample = _transport_sample + floor (nframes * _transport_speed);
const samplepos_t end_sample = _transport_sample + floor (nframes * _transport_fsm->transport_speed());
if (actively_recording ()) {
_capture_duration += nframes;
@ -291,13 +293,13 @@ Session::get_track_statistics ()
bool
Session::compute_audible_delta (samplepos_t& pos_and_delta) const
{
if (_transport_speed == 0.0 || _count_in_samples > 0 || _remaining_latency_preroll > 0) {
if (_transport_fsm->transport_speed() == 0.0 || _count_in_samples > 0 || _remaining_latency_preroll > 0) {
/* cannot compute audible delta, because the session is
generating silence that does not correspond to the timeline,
but is instead filling playback buffers to manage latency
alignment.
*/
DEBUG_TRACE (DEBUG::Slave, string_compose ("still adjusting for latency (%1) and/or count-in (%2) or stopped %1\n", _remaining_latency_preroll, _count_in_samples, _transport_speed));
DEBUG_TRACE (DEBUG::Slave, string_compose ("still adjusting for latency (%1) and/or count-in (%2) or stopped %1\n", _remaining_latency_preroll, _count_in_samples, _transport_fsm->transport_speed()));
return false;
}
@ -361,10 +363,10 @@ Session::process_with_events (pframes_t nframes)
process_event (ev);
}
/* only count-in when going to roll at speed 1.0 */
if (_transport_speed != 1.0 && _count_in_samples > 0) {
if (_transport_fsm->transport_speed() != 1.0 && _count_in_samples > 0) {
_count_in_samples = 0;
}
if (_transport_speed == 0.0) {
if (_transport_fsm->transport_speed() == 0.0) {
_remaining_latency_preroll = 0;
}
@ -438,11 +440,11 @@ Session::process_with_events (pframes_t nframes)
bool const was_sending_qf_mtc = _send_qf_mtc;
double const tolerance = Config->get_mtc_qf_speed_tolerance() / 100.0;
if (_transport_speed != 0) {
if (_transport_fsm->transport_speed() != 0) {
_send_qf_mtc = (
Config->get_send_mtc () &&
_transport_speed >= (1 - tolerance) &&
_transport_speed <= (1 + tolerance)
_transport_fsm->transport_speed() >= (1 - tolerance) &&
_transport_fsm->transport_speed() <= (1 + tolerance)
);
if (_send_qf_mtc && !was_sending_qf_mtc) {
@ -488,10 +490,10 @@ Session::process_with_events (pframes_t nframes)
}
}
assert (_transport_speed == 0 || _transport_speed == 1.0 || _transport_speed == -1.0);
assert (_transport_fsm->transport_speed() == 0 || _transport_fsm->transport_speed() == 1.0 || _transport_fsm->transport_speed() == -1.0);
samples_moved = (samplecnt_t) nframes * _transport_speed;
// DEBUG_TRACE (DEBUG::Transport, string_compose ("plan to move transport by %1 (%2 @ %3)\n", samples_moved, nframes, _transport_speed));
samples_moved = (samplecnt_t) nframes * _transport_fsm->transport_speed();
// DEBUG_TRACE (DEBUG::Transport, string_compose ("plan to move transport by %1 (%2 @ %3)\n", samples_moved, nframes, _transport_fsm->transport_speed()));
end_sample = _transport_sample + samples_moved;
@ -511,7 +513,7 @@ Session::process_with_events (pframes_t nframes)
}
}
if (_transport_speed == 0) {
if (_transport_fsm->transport_speed() == 0) {
no_roll (nframes);
return;
}
@ -539,15 +541,15 @@ Session::process_with_events (pframes_t nframes)
while (nframes) {
this_nframes = nframes; /* real (jack) time relative */
samples_moved = (samplecnt_t) floor (_transport_speed * nframes); /* transport relative */
// DEBUG_TRACE (DEBUG::Transport, string_compose ("sub-loop plan to move transport by %1 (%2 @ %3)\n", samples_moved, nframes, _transport_speed));
samples_moved = (samplecnt_t) floor (_transport_fsm->transport_speed() * nframes); /* transport relative */
// DEBUG_TRACE (DEBUG::Transport, string_compose ("sub-loop plan to move transport by %1 (%2 @ %3)\n", samples_moved, nframes, _transport_fsm->transport_speed()));
/* running an event, position transport precisely to its time */
if (this_event && this_event->action_sample <= end_sample && this_event->action_sample >= _transport_sample) {
/* this isn't quite right for reverse play */
samples_moved = (samplecnt_t) (this_event->action_sample - _transport_sample);
// DEBUG_TRACE (DEBUG::Transport, string_compose ("sub-loop2 (for %4)plan to move transport by %1 (%2 @ %3)\n", samples_moved, nframes, _transport_speed, enum_2_string (this_event->type)));
this_nframes = abs (floor(samples_moved / _transport_speed));
// DEBUG_TRACE (DEBUG::Transport, string_compose ("sub-loop2 (for %4)plan to move transport by %1 (%2 @ %3)\n", samples_moved, nframes, _transport_fsm->transport_speed(), enum_2_string (this_event->type)));
this_nframes = abs (floor(samples_moved / _transport_fsm->transport_speed()));
}
try_run_lua (this_nframes);
@ -605,7 +607,7 @@ Session::process_with_events (pframes_t nframes)
}
/* this is necessary to handle the case of seamless looping */
end_sample = _transport_sample + floor (nframes * _transport_speed);
end_sample = _transport_sample + floor (nframes * _transport_fsm->transport_speed());
}
set_next_event ();
@ -646,15 +648,15 @@ Session::process_without_events (pframes_t nframes)
}
}
assert (_transport_speed == 0 || _transport_speed == 1.0 || _transport_speed == -1.0);
assert (_transport_fsm->transport_speed() == 0 || _transport_fsm->transport_speed() == 1.0 || _transport_fsm->transport_speed() == -1.0);
if (_transport_speed == 0) {
if (_transport_fsm->transport_speed() == 0) {
// DEBUG_TRACE (DEBUG::Transport, string_compose ("transport not moving @ %1\n", _transport_sample));
no_roll (nframes);
return;
} else {
samples_moved = (samplecnt_t) nframes * _transport_speed;
// DEBUG_TRACE (DEBUG::Transport, string_compose ("plan to move transport by %1 (%2 @ %3)\n", samples_moved, nframes, _transport_speed));
samples_moved = (samplecnt_t) nframes * _transport_fsm->transport_speed();
// DEBUG_TRACE (DEBUG::Transport, string_compose ("plan to move transport by %1 (%2 @ %3)\n", samples_moved, nframes, _transport_fsm->transport_speed()));
}
if (!_exporting && !timecode_transmission_suspended()) {
@ -683,10 +685,10 @@ Session::process_without_events (pframes_t nframes)
if (samples_moved < 0) {
decrement_transport_position (-samples_moved);
//DEBUG_TRACE (DEBUG::Transport, string_compose ("DEcrement transport by %1 to %2\n", samples_moved, _transport_sample));
// DEBUG_TRACE (DEBUG::Transport, string_compose ("DEcrement transport by %1 to %2\n", samples_moved, _transport_sample));
} else if (samples_moved) {
increment_transport_position (samples_moved);
//DEBUG_TRACE (DEBUG::Transport, string_compose ("INcrement transport by %1 to %2\n", samples_moved, _transport_sample));
// DEBUG_TRACE (DEBUG::Transport, string_compose ("INcrement transport by %1 to %2\n", samples_moved, _transport_sample));
} else {
DEBUG_TRACE (DEBUG::Transport, "no transport motion\n");
}
@ -1193,7 +1195,7 @@ Session::plan_master_strategy_engine (pframes_t nframes, double master_speed, sa
/* master rolling, we should be too */
if (_transport_speed == 0.0f) {
if (_transport_fsm->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;
return 1.0;
@ -1201,7 +1203,7 @@ Session::plan_master_strategy_engine (pframes_t nframes, double master_speed, sa
} else if (!tmm.current()->starting()) { /* master stopped, not in "starting" state */
if (_transport_speed != 0.0f) {
if (_transport_fsm->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 1.0;
@ -1468,7 +1470,7 @@ Session::plan_master_strategy (pframes_t nframes, double master_speed, samplepos
/* master rolling, we should be too */
if (_transport_speed == 0.0f) {
if (_transport_fsm->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;
@ -1477,7 +1479,7 @@ Session::plan_master_strategy (pframes_t nframes, double master_speed, samplepos
} else if (!tmm.current()->starting()) { /* master stopped, not in "starting" state */
if (_transport_speed != 0.0f) {
if (_transport_fsm->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;

View File

@ -97,7 +97,7 @@ Session::realtime_stop (bool abort, bool clear_state)
{
ENSURE_PROCESS_THREAD;
DEBUG_TRACE (DEBUG::Transport, string_compose ("realtime stop @ %1 speed = %2\n", _transport_sample, _transport_speed));
DEBUG_TRACE (DEBUG::Transport, string_compose ("realtime stop @ %1 speed = %2\n", _transport_sample, _transport_fsm->transport_speed()));
PostTransportWork todo = PostTransportStop;
/* we are rolling and we want to stop */
@ -117,34 +117,6 @@ Session::realtime_stop (bool abort, bool clear_state)
}
}
if (!_transport_fsm->declicking_for_locate()) {
if (Config->get_reset_default_speed_on_stop()) {
if (_engine_speed != 1.0) {
TFSM_SPEED (1.0, true);
}
} else {
/* We're not resetting back to 1.0, but we may need to handle a
* speed change from whatever we have been rolling at to
* whatever the current default is. We could have been
* rewinding at -4.5 ... when we restart, we need to play at
* the current _default_transport_speed
*
* don't do this is there's already a requested transport speed waiting
*/
if (_requested_transport_speed == std::numeric_limits<double>::max()) {
/* no pending speed request */
const double dts = (_default_transport_speed < 0) ? -1 : 1;
if (_engine_speed != _default_engine_speed || dts != _transport_speed) {
TFSM_SPEED (_default_transport_speed, false);
}
}
}
}
/* call routes */
boost::shared_ptr<RouteList> r = routes.reader ();
@ -179,11 +151,13 @@ Session::realtime_stop (bool abort, bool clear_state)
reset_punch_loop_constraint ();
_transport_speed = 0;
g_atomic_int_set (&_playback_load, 100);
g_atomic_int_set (&_capture_load, 100);
if (Config->get_monitoring_model() == HardwareMonitoring) {
set_track_monitor_input_status (true);
}
if (config.get_use_video_sync()) {
waiting_for_sync_offset = true;
}
@ -195,7 +169,7 @@ Session::realtime_stop (bool abort, bool clear_state)
/** @param with_mmc true to send a MMC locate command when the locate is done */
void
Session::locate (samplepos_t target_sample, bool with_roll, bool for_loop_end, bool force, bool with_mmc)
Session::locate (samplepos_t target_sample, bool for_loop_end, bool force, bool with_mmc)
{
ENSURE_PROCESS_THREAD;
@ -214,19 +188,10 @@ Session::locate (samplepos_t target_sample, bool with_roll, bool for_loop_end, b
* changes in the value of _transport_sample.
*/
DEBUG_TRACE (DEBUG::Transport, string_compose ("rt-locate to %1 ts = %7, roll %2 for loop end %3 force %4 mmc %5\n",
target_sample, with_roll, for_loop_end, force, with_mmc, _transport_sample));
DEBUG_TRACE (DEBUG::Transport, string_compose ("rt-locate to %1 ts = %7, for loop end %2 force %3 mmc %4\n",
target_sample, for_loop_end, force, with_mmc, _transport_sample));
if (!force && (_transport_sample == target_sample) && !for_loop_end) {
/* already at the desired position. Not forced to locate, so
unless we're told to start rolling also, there's nothing to
do but tell the world where we are (again).
*/
if (with_roll) {
start_transport ();
}
TFSM_EVENT (TransportFSM::LocateDone);
Located (); /* EMIT SIGNAL */
return;
@ -244,38 +209,13 @@ Session::locate (samplepos_t target_sample, bool with_roll, bool for_loop_end, b
}
timecode_time(_transport_sample, transmitting_timecode_time); // XXX here?
/* do "stopped" stuff if:
*
* we are rolling AND
* no autoplay in effect AND
* we're not going to keep rolling after the locate AND
* !(playing a loop with JACK sync) AND
* we're not synced to an external transport master
*
*/
assert (_transport_fsm->locating() || _transport_fsm->declicking_for_locate());
/* it is important here that we use the internal state of the transport
FSM, not the public facing result of ::transport_rolling()
*/
bool transport_was_stopped = _transport_fsm->stopped();
/* Tell all routes to do the RT part of locate */
if (!transport_was_stopped &&
(!auto_play_legal || !config.get_auto_play()) &&
!with_roll &&
!(synced_to_engine() && get_play_loop ()) &&
!(config.get_external_sync() && !synced_to_engine())) {
realtime_stop (false, true); // XXX paul - check if the 2nd arg is really correct
transport_was_stopped = true;
} else {
/* Tell all routes to do the RT part of locate */
boost::shared_ptr<RouteList> r = routes.reader ();
for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
(*i)->realtime_locate (for_loop_end);
}
boost::shared_ptr<RouteList> r = routes.reader ();
for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
(*i)->realtime_locate (for_loop_end);
}
if (force || !for_loop_end) {
@ -300,18 +240,6 @@ Session::locate (samplepos_t target_sample, bool with_roll, bool for_loop_end, b
}
}
if (with_roll) {
/* switch from input if we're going to roll */
if (Config->get_monitoring_model() == HardwareMonitoring) {
set_track_monitor_input_status (!config.get_auto_input());
}
} else {
/* otherwise we're going to stop, so do the opposite */
if (Config->get_monitoring_model() == HardwareMonitoring) {
set_track_monitor_input_status (true);
}
}
/* cancel looped playback if transport pos outside of loop range */
if (get_play_loop ()) {
@ -390,10 +318,10 @@ Session::post_locate ()
* @param speed New speed
*/
void
Session::set_transport_speed (double speed, bool as_default, bool at_next_start)
Session::set_transport_speed (double speed)
{
ENSURE_PROCESS_THREAD;
DEBUG_TRACE (DEBUG::Transport, string_compose ("@ %1 Set transport speed to %2 from %3 (es = %4) (def %5), as_default %6\n", _transport_sample, speed, _transport_speed, _engine_speed, _default_transport_speed, as_default));
DEBUG_TRACE (DEBUG::Transport, string_compose ("@ %1 Set transport speed to %2 from %3 (es = %4)\n", _transport_sample, speed, _transport_fsm->transport_speed(), _engine_speed));
assert (speed != 0.0);
@ -411,7 +339,7 @@ Session::set_transport_speed (double speed, bool as_default, bool at_next_start)
*/
if ((_engine_speed != 1) && (_engine_speed == fabs (speed)) && ((speed * _transport_speed) > 0)) {
if ((_engine_speed != 1) && (_engine_speed == fabs (speed)) && ((speed * _transport_fsm->transport_speed()) > 0)) {
/* engine speed is not changing and no direction change, do nothing */
DEBUG_TRACE (DEBUG::Transport, "no reason to change speed, do nothing\n");
return;
@ -428,7 +356,7 @@ Session::set_transport_speed (double speed, bool as_default, bool at_next_start)
}
double new_engine_speed = fabs (speed);
double new_transport_speed = (speed < 0) ? -1 : 1;
// double new_transport_speed = (speed < 0) ? -1 : 1;
if ((synced_to_engine()) && speed != 0.0 && speed != 1.0) {
warning << string_compose (
@ -438,27 +366,17 @@ Session::set_transport_speed (double speed, bool as_default, bool at_next_start)
return;
}
if (at_next_start) {
_requested_transport_speed = speed;
} else {
clear_clicks ();
_transport_speed = new_transport_speed;
_engine_speed = new_engine_speed;
}
clear_clicks ();
_engine_speed = new_engine_speed;
if (as_default) {
_default_engine_speed = new_engine_speed;
_default_transport_speed = new_transport_speed;
}
DEBUG_TRACE (DEBUG::Transport, string_compose ("send TSC3 with speed = %1\n", _transport_speed));
DEBUG_TRACE (DEBUG::Transport, string_compose ("send TSC3 with speed = %1\n", _transport_fsm->transport_speed()));
/* throttle signal emissions.
* when slaved [_last]_transport_speed
* when slaved [_last]_transport_fsm->transport_speed()
* usually changes every cycle (tiny amounts due to DLL).
* Emitting a signal every cycle is overkill and unwarranted.
*
* Using _transport_speed is not acceptable,
* Using _transport_fsm->transport_speed() is not acceptable,
* since it allows for large changes over a long period
* of time. Hence we introduce a dedicated variable to keep track
*
@ -517,8 +435,8 @@ Session::start_transport ()
}
}
if (Config->get_monitoring_model() == HardwareMonitoring && config.get_auto_input()) {
set_track_monitor_input_status (false);
if (Config->get_monitoring_model() == HardwareMonitoring) {
set_track_monitor_input_status (!config.get_auto_input());
}
_last_roll_location = _transport_sample;
@ -560,15 +478,6 @@ Session::start_transport ()
maybe_allow_only_loop ();
maybe_allow_only_punch ();
if (_requested_transport_speed != std::numeric_limits<double>::max()) {
_engine_speed = fabs (_requested_transport_speed);
_transport_speed = _requested_transport_speed > 0 ? 1 : -1;
_requested_transport_speed = std::numeric_limits<double>::max();
} else {
_transport_speed = _default_transport_speed;
_engine_speed = _default_engine_speed;
}
clear_clicks ();
if (!_engine.freewheeling()) {
@ -625,7 +534,7 @@ Session::start_transport ()
}
}
DEBUG_TRACE (DEBUG::Transport, string_compose ("send TSC4 with speed = %1\n", _transport_speed));
DEBUG_TRACE (DEBUG::Transport, string_compose ("send TSC4 with speed = %1\n", transport_speed()));
TransportStateChange (); /* EMIT SIGNAL */
}
@ -638,6 +547,31 @@ Session::need_declick_before_locate () const
return naudiotracks() > 0;
}
bool
Session::should_stop_before_locate () const
{
/* do "stopped" stuff if:
*
* we are rolling AND
* no autoplay in effect AND
* we're not synced to an external transport master
*
*/
if ((!auto_play_legal || !config.get_auto_play()) &&
!(config.get_external_sync() && !synced_to_engine())) {
return true;
}
return false;
}
bool
Session::user_roll_after_locate () const
{
return auto_play_legal && config.get_auto_play();
}
bool
Session::should_roll_after_locate () const
{
@ -688,13 +622,6 @@ Session::butler_completed_transport_work ()
if (_transport_fsm->waiting_for_butler()) {
TFSM_EVENT (TransportFSM::ButlerDone);
}
if (start_after_butler_done_msg) {
if (_transport_speed) {
/* reversal is done ... tell TFSM that it is time to start*/
TFSM_EVENT (TransportFSM::StartTransport);
}
}
}
void
@ -709,7 +636,7 @@ bool
Session::maybe_stop (samplepos_t limit)
{
ENSURE_PROCESS_THREAD;
if ((_transport_speed > 0.0f && _transport_sample >= limit) || (_transport_speed < 0.0f && _transport_sample == 0)) {
if ((_transport_fsm->transport_speed() > 0.0f && _transport_sample >= limit) || (_transport_fsm->transport_speed() < 0.0f && _transport_sample == 0)) {
if (synced_to_engine ()) {
_engine.transport_stop ();
} else {
@ -868,8 +795,8 @@ Session::request_roll (TransportRequestSource origin)
return;
}
SessionEvent* ev = new SessionEvent (SessionEvent::StartRoll, SessionEvent::Add, SessionEvent::Immediate, audible_sample(), _default_engine_speed * _default_transport_speed);
DEBUG_TRACE (DEBUG::Transport, string_compose ("Request transport roll, requested %1 from %2 * %3 transport @ %4\n", _default_engine_speed * _default_transport_speed, _default_engine_speed, _default_transport_speed, _transport_sample));
SessionEvent* ev = new SessionEvent (SessionEvent::StartRoll, SessionEvent::Add, SessionEvent::Immediate, 0, false); /* final 2 argumment do not matter */
DEBUG_TRACE (DEBUG::Transport, "Request transport roll\n");
queue_event (ev);
}
@ -1580,7 +1507,7 @@ Session::non_realtime_stop (bool abort, int on_entry, bool& finished)
}
PositionChanged (_transport_sample); /* EMIT SIGNAL */
DEBUG_TRACE (DEBUG::Transport, string_compose ("send TSC with speed = %1\n", _transport_speed));
DEBUG_TRACE (DEBUG::Transport, string_compose ("send TSC with speed = %1\n", _transport_fsm->transport_speed()));
TransportStateChange (); /* EMIT SIGNAL */
AutomationWatch::instance().transport_stop_automation_watches (_transport_sample);
}
@ -1641,7 +1568,7 @@ Session::set_play_loop (bool yn, bool change_transport_state)
unset_play_loop ();
}
DEBUG_TRACE (DEBUG::Transport, string_compose ("send TSC2 with speed = %1\n", _transport_speed));
DEBUG_TRACE (DEBUG::Transport, string_compose ("send TSC2 with speed = %1\n", _transport_fsm->transport_speed()));
}
void
@ -1780,7 +1707,7 @@ Session::set_play_range (list<AudioRange>& range, bool leave_rolling)
ev = new SessionEvent (SessionEvent::LocateRoll, SessionEvent::Add, SessionEvent::Immediate, range.front().start, 0.0f, false);
merge_event (ev);
DEBUG_TRACE (DEBUG::Transport, string_compose ("send TSC5 with speed = %1\n", _transport_speed));
DEBUG_TRACE (DEBUG::Transport, string_compose ("send TSC5 with speed = %1\n", _transport_fsm->transport_speed()));
TransportStateChange (); /* EMIT SIGNAL */
}
@ -2053,7 +1980,7 @@ Session::transport_state_rolling() const
bool
Session::transport_rolling() const
{
return _transport_speed != 0.0 && _count_in_samples == 0 && _remaining_latency_preroll == 0;
return _transport_fsm->transport_speed() != 0.0 && _count_in_samples == 0 && _remaining_latency_preroll == 0;
}
bool
@ -2079,3 +2006,20 @@ Session::transport_will_roll_forwards () const
{
return _transport_fsm->will_roll_fowards ();
}
double
Session::transport_speed() const
{
if (_transport_fsm->transport_speed() != _transport_fsm->transport_speed()) {
// cerr << "\n\n!!TS " << _transport_fsm->transport_speed() << " TFSM::speed " << _transport_fsm->transport_speed() << " via " << _transport_fsm->current_state() << endl;
}
return _count_in_samples > 0 ? 0. : _transport_fsm->transport_speed();
}
double
Session::actual_speed() const
{
if (_transport_fsm->transport_speed() > 0) return _engine_speed;
if (_transport_fsm->transport_speed() < 0) return - _engine_speed;
return 0;
}

View File

@ -37,6 +37,29 @@ using namespace PBD;
Pool* TransportFSM::Event::pool = 0;
/*
autoplay off (hint: state remains the same):
[STOPPED] -> LOCATE -> [STOPPED]
[ROLLING] -> LOCATE -> [ROLLING]
[RWD|FFWD] -> LOCATE -> [RWD|FFWD]
autoplay on (hint: state is always rolling):
[STOPPED] -> LOCATE -> [ROLLING]
[ROLLING] -> LOCATE -> [ROLLING]
[RWD|FFWD] -> LOCATE -> [ROLLING]
the problem child is the last one. The final ROLLING state is intended to be
forwards at the default speed. But if we were rewinding, we need a reverse.
if autoplay is the determining factor in differentiating these two, we must
make it available via TransportAPI. but let's rename it as something slightly
more on-topic.
*/
void
TransportFSM::Event::init_pool ()
{
@ -56,11 +79,15 @@ TransportFSM::Event::operator delete (void *ptr, size_t /*size*/)
}
TransportFSM::TransportFSM (TransportAPI& tapi)
: _last_locate (Locate, 0, MustRoll, false, false) /* all but first argument don't matter */
, last_speed_request (0, false) /* SetSpeed request */
: _motion_state (Stopped)
, _butler_state (NotWaitingForButler)
, _direction_state (Forwards)
, _transport_speed (0)
, _last_locate (Locate, max_samplepos, MustRoll, false, false) /* all but first two argument don't matter */
, api (&tapi)
, processing (0)
, most_recently_requested_speed (std::numeric_limits<double>::max())
, default_speed (1.0)
{
init ();
}
@ -68,10 +95,6 @@ TransportFSM::TransportFSM (TransportAPI& tapi)
void
TransportFSM::init ()
{
_motion_state = Stopped;
_butler_state = NotWaitingForButler;
_direction_state = Forwards;
_last_locate.target = max_samplepos;
}
void
@ -119,7 +142,7 @@ TransportFSM::process_events ()
e = deferred_events.erase (e);
delete deferred_ev;
} else {
DEBUG_TRACE (DEBUG::TFSMEvents, string_compose ("deferred event %1 re-deferred\n", enum_2_string (deferred_ev->type)));
DEBUG_TRACE (DEBUG::TFSMEvents, string_compose ("Re-Defer deferred event %1\n", enum_2_string (deferred_ev->type)));
++e;
}
} else { /* process error */
@ -203,7 +226,6 @@ void
TransportFSM::bad_transition (Event const & ev)
{
error << "bad transition, current state = " << current_state() << " event = " << enum_2_string (ev.type) << endmsg;
std::cerr << "bad transition, current state = " << current_state() << " event = " << enum_2_string (ev.type) << std::endl;
PBD::stacktrace (std::cerr, 30);
}
@ -292,7 +314,7 @@ TransportFSM::process_event (Event& ev, bool already_deferred, bool& deferred)
break;
case Locate:
DEBUG_TRACE (DEBUG::TFSMEvents, string_compose ("locate, ltd = %1 target = %2 loop %3 force %4\n",
DEBUG_TRACE (DEBUG::TFSMEvents, string_compose ("locate, ltd = %1 target = %2 for loop end %3 force %4\n",
enum_2_string (ev.ltd),
ev.target,
ev.for_loop_end,
@ -447,6 +469,15 @@ TransportFSM::start_playback ()
_last_locate.target = max_samplepos;
current_roll_after_locate_status = boost::none;
if (most_recently_requested_speed == std::numeric_limits<double>::max()) {
/* we started rolling without ever setting speed; that's an implicit
* call to set_speed (1.0)
*/
most_recently_requested_speed = 1.0;
} else {
api->set_transport_speed (most_recently_requested_speed);
}
api->start_transport();
}
@ -458,21 +489,40 @@ TransportFSM::stop_playback (Event const & s)
_last_locate.target = max_samplepos;
current_roll_after_locate_status = boost::none;
api->stop_transport (s.abort_capture, s.clear_state);
}
if (!declicking_for_locate()) {
void
TransportFSM::set_roll_after (bool with_roll) const
{
current_roll_after_locate_status = with_roll;
if (Config->get_reset_default_speed_on_stop()) {
if (most_recently_requested_speed != 1.0) {
set_speed (Event (1.0, false));
}
} else {
/* We're not resetting back to 1.0, but we may need to handle a
* speed change from whatever we have been rolling at to
* whatever the current default is. We could have been
* rewinding at -4.5 ... when we restart, we need to play at
* the current _default_transport_speed
*/
if (most_recently_requested_speed != default_speed) {
set_speed (Event (default_speed, false));
}
}
}
api->stop_transport (s.abort_capture, s.clear_state);
}
void
TransportFSM::start_declick_for_locate (Event const & l)
{
assert (l.type == Locate);
DEBUG_TRACE (DEBUG::TFSMEvents, string_compose ("start_declick_for_locate, crals %1 ltd %2 speed %3 sral %4\n", (bool) current_roll_after_locate_status,
enum_2_string (l.ltd), api->speed(), api->should_roll_after_locate()));
DEBUG_TRACE (DEBUG::TFSMEvents, string_compose ("start_declick_for_locate, crals %1 ltd %2 sral %3\n", (bool) current_roll_after_locate_status,
enum_2_string (l.ltd), api->should_roll_after_locate()));
_last_locate = l;
if (!current_roll_after_locate_status) {
@ -481,17 +531,6 @@ TransportFSM::start_declick_for_locate (Event const & l)
api->stop_transport (false, false);
}
void
TransportFSM::start_locate_while_stopped (Event const & l) const
{
assert (l.type == Locate);
DEBUG_TRACE (DEBUG::TFSMEvents, "start_locate_while_stopped\n");
set_roll_after (compute_should_roll (l.ltd));
api->locate (l.target, current_roll_after_locate_status.get(), l.for_loop_end, l.force);
}
bool
TransportFSM::compute_should_roll (LocateTransportDisposition ltd) const
{
@ -500,19 +539,37 @@ TransportFSM::compute_should_roll (LocateTransportDisposition ltd) const
return true;
case MustStop:
return false;
case RollIfAppropriate:
/* by the time we call this, if we were rolling before the
locate, we've already transitioned into DeclickToLocate
*/
if (_motion_state == DeclickToLocate) {
return true;
} else {
return api->should_roll_after_locate ();
}
default:
break;
}
/*NOTREACHED*/
return true;
/* case RollIfAppropriate */
/* by the time we call this, if we were rolling before the
locate, we've already transitioned into DeclickToLocate,
so DeclickToLocate is essentially a synonym for "was Rolling".
*/
if (_motion_state == DeclickToLocate) {
return true;
}
return api->should_roll_after_locate ();
}
void
TransportFSM::set_roll_after (bool with_roll) const
{
current_roll_after_locate_status = with_roll;
}
void
TransportFSM::start_locate_while_stopped (Event const & l) const
{
assert (l.type == Locate);
DEBUG_TRACE (DEBUG::TFSMEvents, "start_locate_while_stopped\n");
set_roll_after (compute_should_roll (l.ltd));
api->locate (l.target, l.for_loop_end, l.force);
}
void
@ -521,20 +578,43 @@ TransportFSM::locate_for_loop (Event const & l)
assert (l.type == Locate);
DEBUG_TRACE (DEBUG::TFSMEvents, string_compose ("locate_for_loop, wl = %1\n", l.for_loop_end));
const bool should_roll = compute_should_roll (l.ltd);
current_roll_after_locate_status = should_roll;
_last_locate = l;
api->locate (l.target, should_roll, l.for_loop_end, l.force);
set_roll_after (compute_should_roll (l.ltd));
api->locate (l.target, l.for_loop_end, l.force);
}
void
TransportFSM::start_locate_after_declick () const
TransportFSM::start_locate_after_declick ()
{
DEBUG_TRACE (DEBUG::TFSMEvents, string_compose ("start_locate_after_declick, have crals ? %1 roll will be %2\n", (bool) current_roll_after_locate_status,
current_roll_after_locate_status ? current_roll_after_locate_status.get() : compute_should_roll (_last_locate.ltd)));
const bool roll = current_roll_after_locate_status ? current_roll_after_locate_status.get() : compute_should_roll (_last_locate.ltd);
api->locate (_last_locate.target, roll, _last_locate.for_loop_end, _last_locate.force);
/* we only get here because a locate request arrived while we were rolling. We declicked and that is now finished */
/* Special case: we were rolling. If the user has set auto-play, then
post-locate, we should roll at the default speed, which may involve
a reversal and that needs to be setup before we actually locate.
*/
double post_locate_speed;
if (api->user_roll_after_locate()) {
post_locate_speed = default_speed;
} else {
post_locate_speed = most_recently_requested_speed;
}
if (post_locate_speed * most_recently_requested_speed < 0) {
/* different directions */
transition (Reversing);
}
if (api->user_roll_after_locate()) {
most_recently_requested_speed = post_locate_speed;
}
api->locate (_last_locate.target, _last_locate.for_loop_end, _last_locate.force);
}
void
@ -563,7 +643,7 @@ TransportFSM::interrupt_locate (Event const & l) const
* we are interrupting the locate to start a new one.
*/
_last_locate = l;
api->locate (l.target, false, l.for_loop_end, l.force);
api->locate (l.target, l.for_loop_end, l.force);
}
void
@ -593,6 +673,15 @@ TransportFSM::roll_after_locate () const
{
DEBUG_TRACE (DEBUG::TFSMEvents, string_compose ("rolling after locate, was for_loop ? %1\n", _last_locate.for_loop_end));
current_roll_after_locate_status = boost::none;
if (most_recently_requested_speed == std::numeric_limits<double>::max()) {
/* we started rolling without ever setting speed; that's an implicit
* call to set_speed (1.0)
*/
most_recently_requested_speed = 1.0;
}
api->set_transport_speed (most_recently_requested_speed);
api->start_transport ();
}
@ -608,6 +697,7 @@ TransportFSM::transition (MotionState ms)
{
const MotionState old = _motion_state;
_motion_state = ms;
_transport_speed = compute_transport_speed ();
DEBUG_TRACE (DEBUG::TFSMState, string_compose ("Leave %1, enter %2\n", enum_2_string (old), current_state()));
}
@ -616,6 +706,7 @@ TransportFSM::transition (ButlerState bs)
{
const ButlerState old = _butler_state;
_butler_state = bs;
_transport_speed = compute_transport_speed ();
DEBUG_TRACE (DEBUG::TFSMState, string_compose ("Leave %1, enter %2\n", enum_2_string (old), current_state()));
}
@ -624,6 +715,7 @@ TransportFSM::transition (DirectionState ds)
{
const DirectionState old = _direction_state;
_direction_state = ds;
_transport_speed = compute_transport_speed ();
DEBUG_TRACE (DEBUG::TFSMState, string_compose ("Leave %1, enter %2\n", enum_2_string (old), current_state()));
}
@ -638,7 +730,7 @@ TransportFSM::enqueue (Event* ev)
}
int
TransportFSM::transport_speed() const
TransportFSM::compute_transport_speed () const
{
if (_motion_state != Rolling || _direction_state == Reversing) {
return 0;
@ -687,7 +779,7 @@ TransportFSM::set_speed (Event const & ev)
must_reverse = true;
}
api->set_transport_speed (ev.speed, ev.as_default, !rolling() || must_reverse);
api->set_transport_speed (ev.speed);
/* corner case: first call to ::set_speed() has a negative
* speed
@ -695,19 +787,27 @@ TransportFSM::set_speed (Event const & ev)
most_recently_requested_speed = ev.speed;
if (ev.as_default) {
default_speed = ev.speed;
}
if (must_reverse) {
/* direction change */
DEBUG_TRACE (DEBUG::TFSMState, string_compose ("switch-directions, target speed %1 state %2 IR %3\n", ev.speed, current_state(), initial_reverse));
last_speed_request = ev;
transition (Reversing);
Event lev (Locate, api->position(), must_roll ? MustRoll : MustStop, false, true);
transition (DeclickToLocate);
start_declick_for_locate (lev);
if (_transport_speed) {
transition (DeclickToLocate);
start_declick_for_locate (lev);
} else {
transition (WaitingForLocate);
start_locate_while_stopped (lev);
}
}
}