goodbye USE_TRACKS_CODE_FEATURES and is_tracks_build
[ardour.git] / libs / ardour / session_transport.cc
index 40a0b7e3582b6810c02451c457ad61a738f0a094..dbda0aabb3a3ebe2e6be24443d9ca2d6e31901ab 100644 (file)
 #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"
@@ -54,6 +57,7 @@
 #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"
@@ -61,8 +65,6 @@
 #include "ardour/vca.h"
 #include "ardour/vca_manager.h"
 
-#include "pbd/i18n.h"
-
 using namespace std;
 using namespace ARDOUR;
 using namespace PBD;
@@ -74,14 +76,18 @@ 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
@@ -89,14 +95,13 @@ Session::realtime_stop (bool abort, bool clear_state)
 {
        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);
        }
@@ -111,10 +116,6 @@ Session::realtime_stop (bool abort, bool clear_state)
 
        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);
        }
@@ -127,7 +128,6 @@ Session::realtime_stop (bool abort, bool clear_state)
                add_post_transport_work (todo);
        }
 
-       _clear_event_type (SessionEvent::StopOnce);
        _clear_event_type (SessionEvent::RangeStop);
        _clear_event_type (SessionEvent::RangeLocate);
 
@@ -156,22 +156,13 @@ Session::realtime_stop (bool abort, bool clear_state)
                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;
 
@@ -195,7 +186,7 @@ Session::start_locate (samplepos_t target_sample, bool with_roll, bool with_flus
                                   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
@@ -211,13 +202,13 @@ Session::start_locate (samplepos_t target_sample, bool with_roll, bool with_flus
                }
 
        } 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;
 
@@ -246,12 +237,11 @@ Session::locate (samplepos_t target_sample, bool with_roll, bool with_flush, boo
                        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
@@ -266,19 +256,34 @@ Session::locate (samplepos_t target_sample, bool with_roll, bool with_flush, boo
         * 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) {
@@ -379,7 +384,9 @@ Session::locate (samplepos_t target_sample, bool with_roll, bool with_flush, boo
        }
 
        if (need_butler) {
-               _butler->schedule_transport_work ();
+               TFSM_EVENT (TransportFSM::ButlerRequired);
+       } else {
+               TFSM_EVENT (TransportFSM::LocateDone);
        }
 
        loop_changing = false;
@@ -396,6 +403,17 @@ Session::locate (samplepos_t target_sample, bool with_roll, bool with_flush, boo
        }
 }
 
+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
@@ -468,7 +486,7 @@ Session::set_transport_speed (double speed, samplepos_t destination_sample, bool
                                _requested_return_sample = destination_sample;
                        }
 
-                       stop_transport (abort);
+                       TFSM_STOP (abort, false);
                }
 
        } else if (transport_stopped() && speed == 1.0) {
@@ -505,7 +523,7 @@ Session::set_transport_speed (double speed, samplepos_t destination_sample, bool
                        _engine.transport_start ();
                        _count_in_once = false;
                } else {
-                       start_transport ();
+                       TFSM_EVENT (TransportFSM::StartTransport);
                }
 
        } else {
@@ -547,6 +565,7 @@ Session::set_transport_speed (double speed, samplepos_t destination_sample, bool
 
                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;
                }
 
@@ -559,7 +578,7 @@ Session::set_transport_speed (double speed, samplepos_t destination_sample, bool
 
                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));
@@ -595,14 +614,10 @@ Session::stop_transport (bool abort, bool clear_state)
        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 */
@@ -705,43 +720,68 @@ Session::start_transport ()
        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
@@ -752,7 +792,7 @@ Session::maybe_stop (samplepos_t limit)
                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;
        }
@@ -861,11 +901,11 @@ Session::set_play_loop (bool yn, double speed)
                                   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);
                                }
                        }
                }
@@ -1114,14 +1154,14 @@ Session::request_cancel_play_range ()
 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);
@@ -1169,18 +1209,14 @@ Session::butler_transport_work ()
 {
        /* 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) {
 
@@ -1315,18 +1351,6 @@ Session::non_realtime_overwrite (int on_entry, bool& finished)
        }
 }
 
-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 ()
 {
@@ -1374,6 +1398,7 @@ 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;
                        }
                }
@@ -1400,83 +1425,6 @@ Session::non_realtime_locate ()
        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)
 {
@@ -1488,8 +1436,6 @@ Session::select_playhead_priority_target (samplepos_t& jump_to)
        return jump_to >= 0;
 }
 
-#endif
-
 void
 Session::follow_playhead_priority ()
 {
@@ -1656,7 +1602,7 @@ Session::non_realtime_stop (bool abort, int on_entry, bool& finished)
        /* 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 ();
@@ -1730,23 +1676,13 @@ Session::non_realtime_stop (bool abort, int on_entry, bool& finished)
                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
@@ -1761,7 +1697,7 @@ Session::unset_play_loop ()
                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 */
        }
@@ -1905,27 +1841,37 @@ Session::request_roll_at_and_return (samplepos_t start, samplepos_t return_to)
 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 ()
@@ -2058,7 +2004,7 @@ Session::sync_source_changed (SyncSource type, samplepos_t pos, pframes_t cycle_
           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
@@ -2105,3 +2051,27 @@ Session::sync_source_changed (SyncSource type, samplepos_t pos, pframes_t cycle_
 
        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();
+}