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)
58 , _last_stop (StopTransport)
68 _motion_state = Stopped;
69 _butler_state = NotWaitingForButler;
70 _last_locate.target = max_samplepos;
74 TransportFSM::process_events ()
78 while (!queued_events.empty()) {
80 MotionState oms = _motion_state;
81 ButlerState obs = _butler_state;
83 if (process_event (queued_events.front())) { /* event processed successfully */
85 if (oms != _motion_state || obs != _butler_state) {
87 /* state changed, so now check deferred events
88 * to see if they can be processed now
91 if (!deferred_events.empty() ){
92 DEBUG_TRACE (DEBUG::TFSMEvents, string_compose ("processing %1 deferred events\n", deferred_events.size()));
94 for (EventList::iterator e = deferred_events.begin(); e != deferred_events.end(); ) {
95 Event* deferred_ev = &(*e);
96 if (process_event (*e)) { /* event processed, remove from deferred */
97 e = deferred_events.erase (e);
107 Event* ev = &queued_events.front();
108 queued_events.pop_front ();
115 /* This is the transition table from the original boost::msm
116 * implementation of this FSM. It is more easily readable and
117 * consultable. Please keep it updated as the FSM changes.
119 * Here's a hint about how to read each line of this table:
121 * "if the current state is Start and event Event arrives, new state is Next and we execute Action()"
125 * "if the current state is Start and event Event arrives, new state is Next and we execute Action() ***IF*** Guard() returns true"
127 * This new implementation, however, does not use metaprogramming to achieve all this,
128 * but just uses a large-ish switch() block.
133 Start Event Next Action Guard
134 +----------------------+----------------+------------------+---------------------+---------------------------------+
135 a_row < Stopped, start_transport, Rolling, &T::start_playback >,
136 _row < Stopped, stop_transport, Stopped >,
137 a_row < Stopped, locate, WaitingForLocate, &T::start_locate_while_stopped >,
138 g_row < WaitingForLocate, locate_done, Stopped, &T::should_not_roll_after_locate >,
139 _row < Rolling, butler_done, Rolling >,
140 _row < Rolling, start_transport, Rolling >,
141 a_row < Rolling, stop_transport, DeclickToStop, &T::start_declick_for_stop >,
142 a_row < DeclickToStop, declick_done, Stopped, &T::stop_playback >,
143 a_row < Rolling, locate, DeclickToLocate, &T::start_declick_for_locate >,
144 a_row < DeclickToLocate, declick_done, WaitingForLocate, &T::start_locate_after_declick >,
145 row < WaitingForLocate, locate_done, Rolling, &T::roll_after_locate, &T::should_roll_after_locate >,
146 a_row < NotWaitingForButler, butler_required, WaitingForButler, &T::schedule_butler_for_transport_work >,
147 a_row < WaitingForButler, butler_required, WaitingForButler, &T::schedule_butler_for_transport_work >,
148 _row < WaitingForButler, butler_done, NotWaitingForButler >,
149 a_row < WaitingForLocate, locate, WaitingForLocate, &T::interrupt_locate >,
150 a_row < DeclickToLocate, locate, DeclickToLocate, &T::interrupt_locate >,
154 #define defer(start_state,ev) boost::msm::front::Row<start_state, ev, start_state, boost::msm::front::Defer, boost::msm::front::none >
156 defer (DeclickToLocate, start_transport),
157 defer (DeclickToLocate, stop_transport),
158 defer (DeclickToStop, start_transport),
159 defer (WaitingForLocate, start_transport),
160 defer (WaitingForLocate, stop_transport)
166 TransportFSM::current_state () const
169 s << enum_2_string (_motion_state) << '/' << enum_2_string (_butler_state);
174 TransportFSM::bad_transition (Event const & ev)
176 error << "bad transition, current state = " << current_state() << " event = " << enum_2_string (ev.type) << endmsg;
177 std::cerr << "bad transition, current state = " << current_state() << " event = " << enum_2_string (ev.type) << std::endl;
181 TransportFSM::process_event (Event& ev)
183 DEBUG_TRACE (DEBUG::TFSMEvents, string_compose ("process %1\n", enum_2_string (ev.type)));
188 switch (_motion_state) {
190 transition (Rolling);
195 case DeclickToLocate:
196 case WaitingForLocate:
203 bad_transition (ev); return false;
209 switch (_motion_state) {
211 transition (DeclickToStop);
212 start_declick_for_stop (ev);
216 case DeclickToLocate:
217 case WaitingForLocate:
221 bad_transition (ev); return false;
227 switch (_motion_state) {
229 transition (WaitingForLocate);
230 start_locate_while_stopped (ev);
233 transition (DeclickToLocate);
234 start_declick_for_locate (ev);
236 case WaitingForLocate:
237 case DeclickToLocate:
238 interrupt_locate (ev);
241 bad_transition (ev); return false;
246 switch (_motion_state) {
247 case WaitingForLocate:
248 if (should_not_roll_after_locate()) {
249 transition (Stopped);
250 /* already stopped, nothing to do */
252 transition (Rolling);
253 roll_after_locate ();
257 bad_transition (ev); return false;
262 switch (_motion_state) {
263 case DeclickToLocate:
264 transition (WaitingForLocate);
265 start_locate_after_declick ();
268 transition (Stopped);
272 bad_transition (ev); return false;
277 switch (_butler_state) {
278 case NotWaitingForButler:
279 transition (WaitingForButler);
280 schedule_butler_for_transport_work ();
282 case WaitingForButler:
283 schedule_butler_for_transport_work ();
286 bad_transition (ev); return false;
291 switch (_butler_state) {
292 case WaitingForButler:
293 transition (NotWaitingForButler);
296 bad_transition (ev); return false;
304 /* transition actions */
307 TransportFSM::start_playback ()
309 DEBUG_TRACE (DEBUG::TFSMEvents, "start_playback\n");
311 _last_locate.target = max_samplepos;
312 current_roll_after_locate_status = boost::none;
314 api->start_transport();
318 TransportFSM::stop_playback ()
320 DEBUG_TRACE (DEBUG::TFSMEvents, "stop_playback\n");
322 _last_locate.target = max_samplepos;
323 current_roll_after_locate_status = boost::none;
325 api->stop_transport (_last_stop.abort, _last_stop.clear_state);
329 TransportFSM::start_declick_for_stop (Event const & s)
331 assert (s.type == StopTransport);
332 DEBUG_TRACE (DEBUG::TFSMEvents, "start_declick_for_stop\n");
337 TransportFSM::start_declick_for_locate (Event const & l)
339 assert (l.type == Locate);
340 DEBUG_TRACE (DEBUG::TFSMEvents, "start_declick_for_locate\n");
343 if (!current_roll_after_locate_status) {
345 if (api->speed() != 0.) {
346 current_roll_after_locate_status = true;
348 current_roll_after_locate_status = api->should_roll_after_locate();
351 current_roll_after_locate_status = (api->speed() != 0.);
355 _last_stop = Event (StopTransport, false, false);
359 TransportFSM::start_locate_while_stopped (Event const & l) const
361 assert (l.type == Locate);
362 DEBUG_TRACE (DEBUG::TFSMEvents, "start_locate_while_stopped\n");
364 current_roll_after_locate_status = api->should_roll_after_locate();
365 api->locate (l.target, current_roll_after_locate_status.get(), l.with_flush, l.with_loop, l.force);
369 TransportFSM::start_locate_after_declick () const
371 DEBUG_TRACE (DEBUG::TFSMEvents, "start_locate_after_declick\n");
372 const bool roll = current_roll_after_locate_status ? current_roll_after_locate_status.get() : _last_locate.with_roll;
373 api->locate (_last_locate.target, roll, _last_locate.with_flush, _last_locate.with_loop, _last_locate.force);
377 TransportFSM::interrupt_locate (Event const & l) const
379 assert (l.type == Locate);
380 DEBUG_TRACE (DEBUG::TFSMEvents, string_compose ("interrupt to %1 versus %2\n", l.target, _last_locate.target));
382 /* Because of snapping (e.g. of mouse position) we could be
383 * interrupting an existing locate to the same position. If we go ahead
384 * with this, the code in Session::do_locate() will notice that it's a
385 * repeat position, will do nothing, will queue a "locate_done" event
386 * that will arrive in the next process cycle. But this event may be
387 * processed before the original (real) locate has completed in the
388 * butler thread, and processing it may transition us back to Rolling
389 * before some (or even all) tracks are actually ready.
391 * So, we must avoid this from happening, and this seems like the
395 if (l.target == _last_locate.target && !l.force) {
398 /* maintain original "with-roll" choice of initial locate, even though
399 * we are interrupting the locate to start a new one.
401 api->locate (l.target, false, l.with_flush, l.with_loop, l.force);
405 TransportFSM::schedule_butler_for_transport_work () const
407 api->schedule_butler_for_transport_work ();
411 TransportFSM::should_roll_after_locate () const
415 if (current_roll_after_locate_status) {
416 roll = current_roll_after_locate_status.get();
418 roll = api->should_roll_after_locate ();
421 DEBUG_TRACE (DEBUG::TFSMEvents, string_compose ("should_roll_after_locate() ? %1\n", roll));
426 TransportFSM::roll_after_locate () const
428 DEBUG_TRACE (DEBUG::TFSMEvents, "rolling after locate\n");
429 current_roll_after_locate_status = boost::none;
430 api->start_transport ();
434 TransportFSM::defer (Event& ev)
436 DEBUG_TRACE (DEBUG::TFSMEvents, string_compose ("Defer %1 during %2\n", enum_2_string (ev.type), current_state()));
437 deferred_events.push_back (ev);
441 TransportFSM::transition (MotionState ms)
443 const MotionState old = _motion_state;
445 DEBUG_TRACE (DEBUG::TFSMState, string_compose ("Leave %1, enter %2\n", enum_2_string (old), current_state()));
449 TransportFSM::transition (ButlerState bs)
451 const ButlerState old = _butler_state;
453 DEBUG_TRACE (DEBUG::TFSMState, string_compose ("Leave %1, enter %2\n", enum_2_string (old), current_state()));
457 TransportFSM::enqueue (Event* ev)
459 DEBUG_TRACE (DEBUG::TFSMState, string_compose ("queue tfsm event %1\n", enum_2_string (ev->type)));
460 queued_events.push_back (*ev);