#include <cerrno>
#include <unistd.h>
-#include "pbd/undo.h"
+#include <boost/algorithm/string/erase.hpp>
+
#include "pbd/error.h"
#include "pbd/enumwriter.h"
-#include "pbd/pthread_utils.h"
+#include "pbd/i18n.h"
#include "pbd/memento_command.h"
+#include "pbd/pthread_utils.h"
#include "pbd/stacktrace.h"
+#include "pbd/undo.h"
#include "midi++/mmc.h"
#include "midi++/port.h"
#include "ardour/profile.h"
#include "ardour/scene_changer.h"
#include "ardour/session.h"
+#include "ardour/transport_fsm.h"
#include "ardour/transport_master.h"
#include "ardour/transport_master_manager.h"
#include "ardour/tempo.h"
#include "ardour/vca.h"
#include "ardour/vca_manager.h"
-#include "pbd/i18n.h"
-
using namespace std;
using namespace ARDOUR;
using namespace PBD;
# define ENSURE_PROCESS_THREAD \
do { \
if (!AudioEngine::instance()->in_process_thread()) { \
- PBD::stacktrace (std::cerr, 10); \
+ PBD::stacktrace (std::cerr, 30); \
} \
} while (0)
#endif
+#define TFSM_EVENT(evtype) { _transport_fsm->enqueue (new TransportFSM::Event (evtype)); }
+#define TFSM_STOP(abort,clear) { _transport_fsm->enqueue (new TransportFSM::Event (TransportFSM::StopTransport,abort,clear)); }
+#define TFSM_LOCATE(target,roll,flush,loop,force) { _transport_fsm->enqueue (new TransportFSM::Event (TransportFSM::Locate,target,roll,flush,loop,force)); }
+
/* *****************************************************************************
- * REALTIME ACTIONS (to be called on state transtion
+ * REALTIME ACTIONS (to be called on state transitions)
* ****************************************************************************/
void
{
ENSURE_PROCESS_THREAD;
- DEBUG_TRACE (DEBUG::Transport, string_compose ("realtime stop @ %1\n", _transport_sample));
+ DEBUG_TRACE (DEBUG::Transport, string_compose ("realtime stop @ %1 speed = %2\n", _transport_sample, _transport_speed));
PostTransportWork todo = PostTransportWork (0);
- /* assume that when we start, we'll be moving forwards */
-
- if (_transport_speed < 0.0f) {
+ if (_last_transport_speed < 0.0f) {
todo = (PostTransportWork (todo | PostTransportStop | PostTransportReverse));
_default_transport_speed = 1.0;
+ DiskReader::inc_no_disk_output (); // for the buffer reversal
} else {
todo = PostTransportWork (todo | PostTransportStop);
}
DEBUG_TRACE (DEBUG::Transport, string_compose ("stop complete, auto-return scheduled for return to %1\n", _requested_return_sample));
- /* the duration change is not guaranteed to have happened, but is likely */
-
- todo = PostTransportWork (todo | PostTransportDuration);
-
if (abort) {
todo = PostTransportWork (todo | PostTransportAbort);
}
add_post_transport_work (todo);
}
- _clear_event_type (SessionEvent::StopOnce);
_clear_event_type (SessionEvent::RangeStop);
_clear_event_type (SessionEvent::RangeLocate);
waiting_for_sync_offset = true;
}
- transport_sub_state = 0;
-}
-
-void
-Session::realtime_locate ()
-{
- ENSURE_PROCESS_THREAD;
-
- boost::shared_ptr<RouteList> r = routes.reader ();
- for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
- (*i)->realtime_locate ();
+ if (todo) {
+ TFSM_EVENT (TransportFSM::ButlerRequired);
}
}
void
-Session::start_locate (samplepos_t target_sample, bool with_roll, bool with_flush, bool for_loop_enabled, bool force)
+Session::locate (samplepos_t target_sample, bool with_roll, bool with_flush, bool for_loop_enabled, bool force, bool with_mmc)
{
ENSURE_PROCESS_THREAD;
will use the incorrect _transport_sample and report an old
and incorrect time to Jack transport
*/
- locate (target_sample, with_roll, with_flush, for_loop_enabled, force);
+ do_locate (target_sample, with_roll, with_flush, for_loop_enabled, force, with_mmc);
}
/* tell JACK to change transport position, and we will
}
} else {
- locate (target_sample, with_roll, with_flush, for_loop_enabled, force);
+ do_locate (target_sample, with_roll, with_flush, for_loop_enabled, force, with_mmc);
}
}
/** @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 with_flush, bool for_loop_enabled, bool force, bool with_mmc)
+Session::do_locate (samplepos_t target_sample, bool with_roll, bool with_flush, bool for_loop_enabled, bool force, bool with_mmc)
{
ENSURE_PROCESS_THREAD;
set_transport_speed (1.0, 0, false);
}
loop_changing = false;
+ TFSM_EVENT (TransportFSM::LocateDone);
Located (); /* EMIT SIGNAL */
return;
}
- cerr << "... now doing the actual locate to " << target_sample << " from " << _transport_sample << endl;
-
// Update Timecode time
_transport_sample = target_sample;
// Bump seek counter so that any in-process locate in the butler
* 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)
+ * !(playing a loop with JACK sync) AND
+ * we're not synced to an external transport master
*
*/
- bool transport_was_stopped = !transport_rolling();
- if (!transport_was_stopped && (!auto_play_legal || !config.get_auto_play()) && !with_roll && !(synced_to_engine() && play_loop) &&
+ /* 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();
+
+ if (!transport_was_stopped &&
+ (!auto_play_legal || !config.get_auto_play()) &&
+ !with_roll &&
+ !(synced_to_engine() && play_loop) &&
(!Profile->get_trx() || !(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 {
- /* otherwise tell the world that we located */
- realtime_locate ();
+
+ /* 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 ();
+ }
}
if (force || !for_loop_enabled || loop_changing) {
}
if (need_butler) {
- _butler->schedule_transport_work ();
+ TFSM_EVENT (TransportFSM::ButlerRequired);
+ } else {
+ TFSM_EVENT (TransportFSM::LocateDone);
}
loop_changing = false;
}
}
+void
+Session::post_locate ()
+{
+ if (transport_master_is_external() && !synced_to_engine()) {
+ const samplepos_t current_master_position = TransportMasterManager::instance().get_current_position_in_process_context();
+ if (abs (current_master_position - _transport_sample) > TransportMasterManager::instance().current()->resolution()) {
+ _last_roll_location = _last_roll_or_reversal_location = _transport_sample;
+ }
+ }
+}
+
/** Set the transport speed.
* Called from the process thread.
* @param speed New speed
_requested_return_sample = destination_sample;
}
- stop_transport (abort);
+ TFSM_STOP (abort, false);
}
} else if (transport_stopped() && speed == 1.0) {
_engine.transport_start ();
_count_in_once = false;
} else {
- start_transport ();
+ TFSM_EVENT (TransportFSM::StartTransport);
}
} else {
if ((_transport_speed && speed * _transport_speed < 0.0) || (_last_transport_speed * speed < 0.0) || (_last_transport_speed == 0.0 && speed < 0.0)) {
todo = PostTransportWork (todo | PostTransportReverse);
+ DiskReader::inc_no_disk_output (); // for the buffer reversal
_last_roll_or_reversal_location = _transport_sample;
}
if (todo) {
add_post_transport_work (todo);
- _butler->schedule_transport_work ();
+ TFSM_EVENT (TransportFSM::ButlerRequired);
}
DEBUG_TRACE (DEBUG::Transport, string_compose ("send TSC3 with speed = %1\n", _transport_speed));
ENSURE_PROCESS_THREAD;
_count_in_once = false;
- if (_transport_speed == 0.0f) {
- return;
- }
- DEBUG_TRACE (DEBUG::Transport, "time to actually stop\n");
+ DEBUG_TRACE (DEBUG::Transport, string_compose ("time to actually stop with TS @ %1\n", _transport_sample));
realtime_stop (abort, clear_state);
- _butler->schedule_transport_work ();
}
/** Called from the process thread */
TransportStateChange (); /* EMIT SIGNAL */
}
+bool
+Session::should_roll_after_locate () const
+{
+ /* a locate must previously have been requested and completed */
+
+ return ((!config.get_external_sync() && (auto_play_legal && config.get_auto_play())) && !_exporting) || (post_transport_work() & PostTransportRoll);
+
+}
+
/** Do any transport work in the audio thread that needs to be done after the
- * transport thread is finished. Audio thread, realtime safe.
+ * butler thread is finished. Audio thread, realtime safe.
*/
void
-Session::post_transport ()
+Session::butler_completed_transport_work ()
{
ENSURE_PROCESS_THREAD;
PostTransportWork ptw = post_transport_work ();
+ DEBUG_TRACE (DEBUG::Transport, string_compose ("Butler done, RT cleanup for %1\n", enum_2_string (ptw)));
+
if (ptw & PostTransportAudition) {
if (auditioner && auditioner->auditioning()) {
process_function = &Session::process_audition;
} else {
process_function = &Session::process_with_events;
}
+ ptw = PostTransportWork (ptw & ~PostTransportAudition);
+ set_post_transport_work (ptw);
}
- if (ptw & PostTransportStop) {
+ if (ptw & PostTransportLocate) {
+ post_locate ();
+ ptw = PostTransportWork (ptw & ~PostTransportLocate);
+ set_post_transport_work (ptw);
+ TFSM_EVENT (TransportFSM::LocateDone);
+ }
- transport_sub_state = 0;
+ if (ptw & PostTransportAdjustPlaybackBuffering) {
+ /* we blocked output while this happened */
+ DiskReader::dec_no_disk_output ();
+ ptw = PostTransportWork (ptw & ~PostTransportAdjustPlaybackBuffering);
+ set_post_transport_work (ptw);
}
- if (ptw & PostTransportLocate) {
+ ptw = PostTransportWork (ptw & ~(PostTransportAdjustCaptureBuffering|PostTransportOverWrite));
+ set_post_transport_work (ptw);
- if (((!config.get_external_sync() && (auto_play_legal && config.get_auto_play())) && !_exporting) || (ptw & PostTransportRoll)) {
- _count_in_once = false;
- start_transport ();
- } else {
- transport_sub_state = 0;
- }
+ set_next_event ();
+
+ if (_transport_fsm->waiting_for_butler()) {
+ TFSM_EVENT (TransportFSM::ButlerDone);
}
- set_next_event ();
- /* XXX is this really safe? shouldn't we just be unsetting the bits that we actually
- know were handled ?
- */
- set_post_transport_work (PostTransportWork (0));
+ DiskReader::dec_no_disk_output ();
+}
+
+void
+Session::schedule_butler_for_transport_work ()
+{
+ assert (_transport_fsm->waiting_for_butler ());
+ DEBUG_TRACE (DEBUG::Butler, "summon butler for transport work\n");
+ _butler->schedule_transport_work ();
}
bool
if (synced_to_engine () && config.get_jack_time_master ()) {
_engine.transport_stop ();
} else if (!synced_to_engine ()) {
- stop_transport ();
+ TFSM_EVENT (TransportFSM::StopTransport);
}
return true;
}
rolling, do not locate to loop start.
*/
if (!transport_rolling() && (speed != 0.0)) {
- start_locate (loc->start(), true, true, false, true);
+ TFSM_LOCATE (loc->start(), true, true, false, true);
}
} else {
if (speed != 0.0) {
- start_locate (loc->start(), true, true, false, true);
+ TFSM_LOCATE (loc->start(), true, true, false, true);
}
}
}
bool
Session::solo_selection_active ()
{
- if ( _soloSelection.empty() ) {
+ if (_soloSelection.empty()) {
return false;
}
return true;
}
void
-Session::solo_selection ( StripableList &list, bool new_state )
+Session::solo_selection (StripableList &list, bool new_state)
{
boost::shared_ptr<ControlList> solo_list (new ControlList);
boost::shared_ptr<ControlList> unsolo_list (new ControlList);
{
/* Note: this function executes in the butler thread context */
-restart:
- bool finished;
- PostTransportWork ptw;
+ restart:
boost::shared_ptr<RouteList> r = routes.reader ();
- uint64_t before;
-
int on_entry = g_atomic_int_get (&_butler->should_do_transport_work);
- finished = true;
- ptw = post_transport_work();
-
- DEBUG_TRACE (DEBUG::Transport, string_compose ("Butler transport work, todo = %1 at %2\n", enum_2_string (ptw), (before = g_get_monotonic_time())));
+ bool finished = true;
+ PostTransportWork ptw = post_transport_work();
+ uint64_t before;
+ DEBUG_TRACE (DEBUG::Transport, string_compose ("Butler transport work, todo = [%1] (0x%3%4%5) at %2\n", enum_2_string (ptw), (before = g_get_monotonic_time()), std::hex, ptw, std::dec));
if (ptw & PostTransportLocate) {
}
}
-bool
-Session::declick_in_progress () const
-{
- boost::shared_ptr<RouteList> rl = routes.reader();
- for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) {
- if ((*i)->declick_in_progress ()) {
- return true;
- }
- }
- return false;
-}
-
void
Session::non_realtime_locate ()
{
for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) {
(*i)->non_realtime_locate (tf);
if (sc != g_atomic_int_get (&_seek_counter)) {
+ std::cerr << "\n\nLOCATE INTERRUPTED BY LOCATE!!!\n\n";
goto restart;
}
}
clear_clicks ();
}
-#ifdef USE_TRACKS_CODE_FEATURES
-bool
-Session::select_playhead_priority_target (samplepos_t& jump_to)
-{
- jump_to = -1;
-
- AutoReturnTarget autoreturn = Config->get_auto_return_target_list ();
-
- if (!autoreturn) {
- return false;
- }
-
- if (Profile->get_trx() && transport_rolling() ) {
- // We're playing, so do nothing.
- // Next stop will put us where we need to be.
- return false;
- }
-
- /* Note that the order of checking each AutoReturnTarget flag defines
- the priority each flag.
-
- Ardour/Mixbus: Last Locate
- Range Selection
- Loop Range
- Region Selection
-
- Tracks: Range Selection
- Loop Range
- Region Selection
- Last Locate
- */
-
- if (autoreturn & RangeSelectionStart) {
- if (!_range_selection.empty()) {
- jump_to = _range_selection.from;
- } else {
- if (transport_rolling ()) {
- /* Range selection no longer exists, but we're playing,
- so do nothing. Next stop will put us where
- we need to be.
- */
- return false;
- }
- }
- }
-
- if (jump_to < 0 && (autoreturn & Loop) && get_play_loop()) {
- /* don't try to handle loop play when synced to JACK */
-
- if (!synced_to_engine()) {
- Location *location = _locations->auto_loop_location();
-
- if (location) {
- jump_to = location->start();
-
- if (Config->get_seamless_loop()) {
- /* need to get track buffers reloaded */
- set_track_loop (true);
- }
- }
- }
- }
-
- if (jump_to < 0 && (autoreturn & RegionSelectionStart)) {
- if (!_object_selection.empty()) {
- jump_to = _object_selection.from;
- }
- }
-
- if (jump_to < 0 && (autoreturn & LastLocate)) {
- jump_to = _last_roll_location;
- }
-
- return jump_to >= 0;
-}
-#else
-
bool
Session::select_playhead_priority_target (samplepos_t& jump_to)
{
return jump_to >= 0;
}
-#endif
-
void
Session::follow_playhead_priority ()
{
/* do this before seeking, because otherwise the tracks will do the wrong thing in seamless loop mode.
*/
- if (ptw & PostTransportClearSubstate) {
+ if (ptw & (PostTransportClearSubstate|PostTransportStop)) {
unset_play_range ();
if (!Config->get_loop_is_mode()) {
unset_play_loop ();
SaveSessionRequested (_current_snapshot_name);
}
- if (ptw & PostTransportStop) {
- unset_play_range ();
- if (!Config->get_loop_is_mode()) {
- unset_play_loop ();
- }
- }
-
PositionChanged (_transport_sample); /* EMIT SIGNAL */
DEBUG_TRACE (DEBUG::Transport, string_compose ("send TSC with speed = %1\n", _transport_speed));
TransportStateChange (); /* EMIT SIGNAL */
AutomationWatch::instance().transport_stop_automation_watches (_transport_sample);
- /* and start it up again if relevant */
-
- if ((ptw & PostTransportLocate) && !config.get_external_sync()) {
- request_transport_speed (1.0);
- }
+ ptw = PostTransportWork (ptw & ~(PostTransportStop|PostTransportClearSubstate));
+ set_post_transport_work (ptw);
}
void
if (Config->get_seamless_loop()) {
/* likely need to flush track buffers: this will locate us to wherever we are */
add_post_transport_work (PostTransportLocate);
- _butler->schedule_transport_work ();
+ TFSM_EVENT (TransportFSM::ButlerRequired);
}
TransportStateChange (); /* EMIT SIGNAL */
}
void
Session::engine_halted ()
{
- bool ignored;
-
/* there will be no more calls to process(), so
we'd better clean up for ourselves, right now.
- but first, make sure the butler is out of
- the picture.
+ We can't queue SessionEvents because they only get
+ handled from within a process callback.
*/
- if (_butler) {
- _butler->stop ();
- }
+ /* this just stops the FSM engine ... it doesn't change the state of
+ * the FSM directly or anything else ... but the FSM will be
+ * reinitialized when we call its ::start() method from
+ * ::engine_running() (if we ever get there)
+ */
- realtime_stop (false, true);
- non_realtime_stop (false, 0, ignored);
- transport_sub_state = 0;
+ _transport_fsm->stop ();
- DEBUG_TRACE (DEBUG::Transport, string_compose ("send TSC6 with speed = %1\n", _transport_speed));
- TransportStateChange (); /* EMIT SIGNAL */
+ /* Synchronously do the realtime part of a transport stop.
+ *
+ * Calling this will cause the butler to asynchronously run
+ * ::non_realtime_stop() where the rest of the "stop" work will be
+ * done.
+ */
+
+ realtime_stop (false, true);
}
+void
+Session::engine_running ()
+{
+ initialize_latencies ();
+ _transport_fsm->start ();
+}
void
Session::xrun_recovery ()
longer valid with a new slave.
*/
- DiskReader::set_no_disk_output (false);
+ DiskReader::dec_no_disk_output ();
#if 0
we should not be treating specific transport masters as special cases because there maybe > 1 of a particular type
set_dirty();
}
+
+bool
+Session::transport_stopped() const
+{
+ return _transport_fsm->stopped();
+}
+
+bool
+Session::transport_rolling() const
+{
+ return _transport_speed != 0.0 && _count_in_samples == 0 && _remaining_latency_preroll == 0;
+}
+
+bool
+Session::locate_pending () const
+{
+ return _transport_fsm->locating();
+}
+
+bool
+Session::declick_in_progress () const
+{
+ return _transport_fsm->declick_in_progress();
+}