2 * Copyright (C) 2019 Robin Gareus <robin@gareus.org>
3 * Copyright (C) 2019 Paul Davis <paul@linuxaudiosystems.com>
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version 2
8 * of the License, or (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22 #include <boost/none.hpp>
24 #include "pbd/error.h"
26 #include "pbd/pthread_utils.h"
27 #include "pbd/stacktrace.h"
29 #include "ardour/debug.h"
30 #include "ardour/session.h"
31 #include "ardour/transport_fsm.h"
33 using namespace ARDOUR;
36 Pool* TransportFSM::Event::pool = 0;
39 TransportFSM::Event::init_pool ()
41 pool = new Pool (X_("Events"), sizeof (Event), 128);
45 TransportFSM::Event::operator new (size_t)
51 TransportFSM::Event::operator delete (void *ptr, size_t /*size*/)
53 return pool->release (ptr);
56 TransportFSM::TransportFSM (TransportAPI& tapi)
57 : _last_locate (Locate, 0, MustRoll, false, false, false) /* all but first argument don't matter */
67 _motion_state = Stopped;
68 _butler_state = NotWaitingForButler;
69 _last_locate.target = max_samplepos;
73 TransportFSM::process_events ()
77 while (!queued_events.empty()) {
79 MotionState oms = _motion_state;
80 ButlerState obs = _butler_state;
82 Event* ev = &queued_events.front();
85 /* must remove from the queued_events list now, because
86 * process_event() may defer the event. This will lead to
87 * insertion into the deferred_events list, and its not possible
88 * with intrusive lists to be present in two lists at once
89 * (without additional hooks).
92 queued_events.pop_front ();
94 if (process_event (*ev, false, deferred)) { /* event processed successfully */
96 if (oms != _motion_state || obs != _butler_state) {
98 /* state changed, so now check deferred events
99 * to see if they can be processed now
102 if (!deferred_events.empty() ){
103 DEBUG_TRACE (DEBUG::TFSMEvents, string_compose ("processing %1 deferred events\n", deferred_events.size()));
105 for (EventList::iterator e = deferred_events.begin(); e != deferred_events.end(); ) {
106 Event* deferred_ev = &(*e);
108 if (process_event (*e, true, deferred2)) { /* event processed, remove from deferred */
109 e = deferred_events.erase (e);
127 /* This is the transition table from the original boost::msm
128 * implementation of this FSM. It is more easily readable and
129 * consultable. Please keep it updated as the FSM changes.
131 * Here's a hint about how to read each line of this table:
133 * "if the current state is Start and event Event arrives, new state is Next and we execute Action()"
137 * "if the current state is Start and event Event arrives, new state is Next and we execute Action() ***IF*** Guard() returns true"
139 * This new implementation, however, does not use metaprogramming to achieve all this,
140 * but just uses a large-ish switch() block.
145 Start Event Next Action Guard
146 +----------------------+----------------+------------------+---------------------+---------------------------------+
147 a_row < Stopped, start_transport, Rolling, &T::start_playback >,
148 _row < Stopped, stop_transport, Stopped >,
149 a_row < Stopped, locate, WaitingForLocate, &T::start_locate_while_stopped >,
150 g_row < WaitingForLocate, locate_done, Stopped, &T::should_not_roll_after_locate >,
151 _row < Rolling, butler_done, Rolling >,
152 _row < Rolling, start_transport, Rolling >,
153 a_row < Rolling, stop_transport, DeclickToStop, &T::stop_playback >,
154 a_row < DeclickToStop, declick_done, Stopped, >,
155 a_row < DeclickToStop, stop_transport, DeclickToStop >,
156 a_row < Rolling, locate, DeclickToLocate, &T::start_declick_for_locate >,
157 a_row < DeclickToLocate, declick_done, WaitingForLocate, &T::start_locate_after_declick >,
158 row < WaitingForLocate, locate_done, Rolling, &T::roll_after_locate, &T::should_roll_after_locate >,
159 a_row < NotWaitingForButler, butler_required, WaitingForButler, &T::schedule_butler_for_transport_work >,
160 a_row < WaitingForButler, butler_required, WaitingForButler, &T::schedule_butler_for_transport_work >,
161 _row < WaitingForButler, butler_done, NotWaitingForButler >,
162 a_row < WaitingForLocate, locate, WaitingForLocate, &T::interrupt_locate >,
163 a_row < DeclickToLocate, locate, DeclickToLocate, &T::interrupt_locate >,
167 #define defer(start_state,ev) boost::msm::front::Row<start_state, ev, start_state, boost::msm::front::Defer, boost::msm::front::none >
169 defer (DeclickToLocate, start_transport),
170 defer (DeclickToLocate, stop_transport),
171 defer (DeclickToStop, start_transport),
172 defer (WaitingForLocate, start_transport),
173 defer (WaitingForLocate, stop_transport)
179 TransportFSM::current_state () const
182 s << enum_2_string (_motion_state) << '/' << enum_2_string (_butler_state);
187 TransportFSM::bad_transition (Event const & ev)
189 error << "bad transition, current state = " << current_state() << " event = " << enum_2_string (ev.type) << endmsg;
190 std::cerr << "bad transition, current state = " << current_state() << " event = " << enum_2_string (ev.type) << std::endl;
191 PBD::stacktrace (std::cerr, 30);
195 TransportFSM::process_event (Event& ev, bool already_deferred, bool& deferred)
197 DEBUG_TRACE (DEBUG::TFSMEvents, string_compose ("process %1\n", enum_2_string (ev.type)));
204 switch (_motion_state) {
206 transition (Rolling);
211 case DeclickToLocate:
212 case WaitingForLocate:
213 if (!already_deferred) {
219 if (!already_deferred) {
225 bad_transition (ev); return false;
231 switch (_motion_state) {
233 transition (DeclickToStop);
238 case DeclickToLocate:
239 case WaitingForLocate:
240 if (!already_deferred) {
246 /* already doing it */
249 bad_transition (ev); return false;
255 DEBUG_TRACE (DEBUG::TFSMEvents, string_compose ("locate, ltd = %1 flush = %2 target = %3 loop %4 force %5\n",
256 enum_2_string (ev.ltd),
261 switch (_motion_state) {
263 transition (WaitingForLocate);
264 start_locate_while_stopped (ev);
267 if (ev.for_loop_end) {
268 /* we will finish the locate synchronously, so
269 * that after returning from
270 * ::locate_for_loop() we will already have
271 * received (and re-entrantly handled)
272 * LocateDone and returned back to Rolling.
274 * This happens because we only need to do a
275 * realtime locate and continue rolling. No
276 * disk I/O is required - the loop is
277 * automically present in buffers already.
279 * Note that ev.ltd is ignored and
280 * assumed to be true because we're looping.
282 transition (WaitingForLocate);
283 locate_for_loop (ev);
285 transition (DeclickToLocate);
286 start_declick_for_locate (ev);
289 case WaitingForLocate:
290 case DeclickToLocate:
291 interrupt_locate (ev);
294 bad_transition (ev); return false;
299 switch (_motion_state) {
300 case WaitingForLocate:
301 if (should_not_roll_after_locate()) {
302 transition (Stopped);
303 /* already stopped, nothing to do */
305 transition (Rolling);
306 roll_after_locate ();
310 bad_transition (ev); return false;
315 switch (_motion_state) {
316 case DeclickToLocate:
317 transition (WaitingForLocate);
318 start_locate_after_declick ();
321 transition (Stopped);
322 /* transport already stopped */
325 bad_transition (ev); return false;
330 switch (_butler_state) {
331 case NotWaitingForButler:
332 transition (WaitingForButler);
333 schedule_butler_for_transport_work ();
335 case WaitingForButler:
336 schedule_butler_for_transport_work ();
339 bad_transition (ev); return false;
344 switch (_butler_state) {
345 case WaitingForButler:
346 transition (NotWaitingForButler);
349 bad_transition (ev); return false;
357 /* transition actions */
360 TransportFSM::start_playback ()
362 DEBUG_TRACE (DEBUG::TFSMEvents, "start_playback\n");
364 _last_locate.target = max_samplepos;
365 current_roll_after_locate_status = boost::none;
367 api->start_transport();
371 TransportFSM::stop_playback (Event const & s)
373 DEBUG_TRACE (DEBUG::TFSMEvents, "stop_playback\n");
375 _last_locate.target = max_samplepos;
376 current_roll_after_locate_status = boost::none;
378 api->stop_transport (s.abort, s.clear_state);
382 TransportFSM::set_roll_after (bool with_roll) const
384 current_roll_after_locate_status = with_roll;
388 TransportFSM::start_declick_for_locate (Event const & l)
390 assert (l.type == Locate);
391 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,
392 enum_2_string (l.ltd), api->speed(), api->should_roll_after_locate()));
395 if (!current_roll_after_locate_status) {
396 set_roll_after (compute_should_roll (l.ltd));
398 api->stop_transport (false, false);
402 TransportFSM::start_locate_while_stopped (Event const & l) const
404 assert (l.type == Locate);
405 DEBUG_TRACE (DEBUG::TFSMEvents, "start_locate_while_stopped\n");
407 set_roll_after (compute_should_roll (l.ltd));
409 api->locate (l.target, current_roll_after_locate_status.get(), l.with_flush, l.for_loop_end, l.force);
413 TransportFSM::compute_should_roll (LocateTransportDisposition ltd) const
420 case RollIfAppropriate:
424 return api->should_roll_after_locate ();
433 TransportFSM::locate_for_loop (Event const & l)
435 assert (l.type == Locate);
436 DEBUG_TRACE (DEBUG::TFSMEvents, string_compose ("locate_for_loop, wl = %1\n", l.for_loop_end));
438 const bool should_roll = compute_should_roll (l.ltd);
441 api->locate (l.target, should_roll, l.with_flush, l.for_loop_end, l.force);
445 TransportFSM::start_locate_after_declick () const
447 DEBUG_TRACE (DEBUG::TFSMEvents, string_compose ("start_locate_after_declick, have crals ? %1 roll will be %2\n", (bool) current_roll_after_locate_status,
448 current_roll_after_locate_status ? current_roll_after_locate_status.get() : compute_should_roll (_last_locate.ltd)));
450 const bool roll = current_roll_after_locate_status ? current_roll_after_locate_status.get() : compute_should_roll (_last_locate.ltd);
451 api->locate (_last_locate.target, roll, _last_locate.with_flush, _last_locate.for_loop_end, _last_locate.force);
455 TransportFSM::interrupt_locate (Event const & l) const
457 assert (l.type == Locate);
458 DEBUG_TRACE (DEBUG::TFSMEvents, string_compose ("interrupt to %1 versus %2\n", l.target, _last_locate.target));
460 /* Because of snapping (e.g. of mouse position) we could be
461 * interrupting an existing locate to the same position. If we go ahead
462 * with this, the code in Session::do_locate() will notice that it's a
463 * repeat position, will do nothing, will queue a "locate_done" event
464 * that will arrive in the next process cycle. But this event may be
465 * processed before the original (real) locate has completed in the
466 * butler thread, and processing it may transition us back to Rolling
467 * before some (or even all) tracks are actually ready.
469 * So, we must avoid this from happening, and this seems like the
473 if (l.target == _last_locate.target && !l.force) {
476 /* maintain original "with-roll" choice of initial locate, even though
477 * we are interrupting the locate to start a new one.
479 api->locate (l.target, false, l.with_flush, l.for_loop_end, l.force);
483 TransportFSM::schedule_butler_for_transport_work () const
485 api->schedule_butler_for_transport_work ();
489 TransportFSM::should_roll_after_locate () const
493 if (current_roll_after_locate_status) {
494 roll = current_roll_after_locate_status.get();
495 current_roll_after_locate_status = boost::none; // used it
497 roll = api->should_roll_after_locate ();
500 DEBUG_TRACE (DEBUG::TFSMEvents, string_compose ("should_roll_after_locate() ? %1\n", roll));
505 TransportFSM::roll_after_locate () const
507 DEBUG_TRACE (DEBUG::TFSMEvents, string_compose ("rolling after locate, was for_loop ? %1\n", _last_locate.for_loop_end));
508 current_roll_after_locate_status = boost::none;
509 api->start_transport ();
513 TransportFSM::defer (Event& ev)
515 DEBUG_TRACE (DEBUG::TFSMEvents, string_compose ("Defer %1 during %2\n", enum_2_string (ev.type), current_state()));
516 deferred_events.push_back (ev);
520 TransportFSM::transition (MotionState ms)
522 const MotionState old = _motion_state;
524 DEBUG_TRACE (DEBUG::TFSMState, string_compose ("Leave %1, enter %2\n", enum_2_string (old), current_state()));
528 TransportFSM::transition (ButlerState bs)
530 const ButlerState old = _butler_state;
532 DEBUG_TRACE (DEBUG::TFSMState, string_compose ("Leave %1, enter %2\n", enum_2_string (old), current_state()));
536 TransportFSM::enqueue (Event* ev)
538 DEBUG_TRACE (DEBUG::TFSMState, string_compose ("queue tfsm event %1\n", enum_2_string (ev->type)));
539 queued_events.push_back (*ev);