8e3ef98cfac9bdbe9c66194a50f6ae95c70570cf
[ardour.git] / libs / ardour / transport_fsm.cc
1 /*
2  * Copyright (C) 2019 Robin Gareus <robin@gareus.org>
3  * Copyright (C) 2019 Paul Davis <paul@linuxaudiosystems.com>
4  *
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.
9  *
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.
14  *
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.
18  */
19
20 #include <sstream>
21
22 #include <boost/none.hpp>
23
24 #include "pbd/error.h"
25 #include "pbd/i18n.h"
26 #include "pbd/pthread_utils.h"
27 #include "pbd/stacktrace.h"
28
29 #include "ardour/debug.h"
30 #include "ardour/session.h"
31 #include "ardour/transport_fsm.h"
32
33 using namespace ARDOUR;
34 using namespace PBD;
35
36 Pool* TransportFSM::Event::pool = 0;
37
38 void
39 TransportFSM::Event::init_pool ()
40 {
41         pool = new Pool (X_("Events"), sizeof (Event), 128);
42 }
43
44 void*
45 TransportFSM::Event::operator new (size_t)
46 {
47         return pool->alloc();
48  }
49
50 void
51 TransportFSM::Event::operator delete (void *ptr, size_t /*size*/)
52 {
53         return pool->release (ptr);
54 }
55
56 TransportFSM::TransportFSM (TransportAPI& tapi)
57         : _last_locate (Locate, 0, MustRoll, false, false, false) /* all but first argument don't matter */
58         , api (&tapi)
59         , processing (0)
60 {
61         init ();
62 }
63
64 void
65 TransportFSM::init ()
66 {
67         _motion_state = Stopped;
68         _butler_state = NotWaitingForButler;
69         _last_locate.target = max_samplepos;
70 }
71
72 void
73 TransportFSM::process_events ()
74 {
75         processing++;
76
77         while (!queued_events.empty()) {
78
79                 MotionState oms = _motion_state;
80                 ButlerState obs = _butler_state;
81
82                 Event* ev = &queued_events.front();
83                 bool deferred;
84
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).
90                  */
91
92                 queued_events.pop_front ();
93
94                 if (process_event (*ev, false, deferred)) { /* event processed successfully */
95
96                         if (oms != _motion_state || obs != _butler_state) {
97
98                                 /* state changed, so now check deferred events
99                                  * to see if they can be processed now
100                                  */
101
102                                 if (!deferred_events.empty() ){
103                                         DEBUG_TRACE (DEBUG::TFSMEvents, string_compose ("processing %1 deferred events\n", deferred_events.size()));
104
105                                         for (EventList::iterator e = deferred_events.begin(); e != deferred_events.end(); ) {
106                                                 Event* deferred_ev = &(*e);
107                                                 bool deferred2;
108                                                 if (process_event (*e, true, deferred2)) { /* event processed, remove from deferred */
109                                                         e = deferred_events.erase (e);
110                                                         delete deferred_ev;
111                                                 } else {
112                                                         ++e;
113                                                 }
114                                         }
115                                 }
116                         }
117                 }
118
119                 if (!deferred) {
120                         delete ev;
121                 }
122         }
123
124         processing--;
125 }
126
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.
130  *
131  * Here's a hint about how to read each line of this table:
132  *
133  * "if the current state is Start and event Event arrives, new state is Next and we execute Action()"
134  *
135  * with a variant:
136  *
137  * "if the current state is Start and event Event arrives, new state is Next and we execute Action() ***IF*** Guard() returns true"
138  *
139  * This new implementation, however, does not use metaprogramming to achieve all this,
140  * but just uses a large-ish switch() block.
141  *
142  */
143
144 /*
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                                    >,
164
165 // Deferrals
166
167 #define defer(start_state,ev) boost::msm::front::Row<start_state, ev, start_state, boost::msm::front::Defer, boost::msm::front::none >
168
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)
174
175 #undef defer
176 */
177
178 std::string
179 TransportFSM::current_state () const
180 {
181         std::stringstream s;
182         s << enum_2_string (_motion_state) << '/' << enum_2_string (_butler_state);
183         return s.str();
184 }
185
186 void
187 TransportFSM::bad_transition (Event const & ev)
188 {
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);
192 }
193
194 bool
195 TransportFSM::process_event (Event& ev, bool already_deferred, bool& deferred)
196 {
197         DEBUG_TRACE (DEBUG::TFSMEvents, string_compose ("process %1\n", enum_2_string (ev.type)));
198
199         deferred = false;
200
201         switch (ev.type) {
202
203         case StartTransport:
204                 switch (_motion_state) {
205                 case Stopped:
206                         transition (Rolling);
207                         start_playback ();
208                         break;
209                 case Rolling:
210                         break;
211                 case DeclickToLocate:
212                 case WaitingForLocate:
213                         if (!already_deferred) {
214                                 defer (ev);
215                                 deferred = true;
216                         }
217                         break;
218                 case DeclickToStop:
219                         if (!already_deferred) {
220                                 defer (ev);
221                                 deferred = true;
222                         }
223                         break;
224                 default:
225                         bad_transition (ev); return false;
226                         break;
227                 }
228                 break;
229
230         case StopTransport:
231                 switch (_motion_state) {
232                 case Rolling:
233                         transition (DeclickToStop);
234                         stop_playback (ev);
235                         break;
236                 case Stopped:
237                         break;
238                 case DeclickToLocate:
239                 case WaitingForLocate:
240                         if (!already_deferred) {
241                                 defer (ev);
242                                 deferred = true;
243                         }
244                         break;
245                 case DeclickToStop:
246                         /* already doing it */
247                         break;
248                 default:
249                         bad_transition (ev); return false;
250                         break;
251                 }
252                 break;
253
254         case Locate:
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),
257                                                                 ev.with_flush,
258                                                                 ev.target,
259                                                                 ev.for_loop_end,
260                                                                 ev.force));
261                 switch (_motion_state) {
262                 case Stopped:
263                         transition (WaitingForLocate);
264                         start_locate_while_stopped (ev);
265                         break;
266                 case Rolling:
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.
273                                  *
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.
278                                  *
279                                  * Note that ev.ltd is ignored and
280                                  * assumed to be true because we're looping.
281                                  */
282                                 transition (WaitingForLocate);
283                                 locate_for_loop (ev);
284                         } else {
285                                 transition (DeclickToLocate);
286                                 start_declick_for_locate (ev);
287                         }
288                         break;
289                 case WaitingForLocate:
290                 case DeclickToLocate:
291                         interrupt_locate (ev);
292                         break;
293                 default:
294                         bad_transition (ev); return false;
295                 }
296                 break;
297
298         case LocateDone:
299                 switch (_motion_state) {
300                 case WaitingForLocate:
301                         if (should_not_roll_after_locate()) {
302                                 transition (Stopped);
303                                 /* already stopped, nothing to do */
304                         } else {
305                                 transition (Rolling);
306                                 roll_after_locate ();
307                         }
308                         break;
309                 default:
310                         bad_transition (ev); return false;
311                 }
312                 break;
313
314         case DeclickDone:
315                 switch (_motion_state) {
316                 case DeclickToLocate:
317                         transition (WaitingForLocate);
318                         start_locate_after_declick ();
319                         break;
320                 case DeclickToStop:
321                         transition (Stopped);
322                         /* transport already stopped */
323                         break;
324                 default:
325                         bad_transition (ev); return false;
326                 }
327                 break;
328
329         case ButlerRequired:
330                 switch (_butler_state) {
331                 case NotWaitingForButler:
332                         transition (WaitingForButler);
333                         schedule_butler_for_transport_work ();
334                         break;
335                 case WaitingForButler:
336                         schedule_butler_for_transport_work ();
337                         break;
338                 default:
339                         bad_transition (ev); return false;
340                 }
341                 break;
342
343         case ButlerDone:
344                 switch (_butler_state) {
345                 case WaitingForButler:
346                         transition (NotWaitingForButler);
347                         break;
348                 default:
349                         bad_transition (ev); return false;
350                 }
351                 break;
352         }
353
354         return true;
355 }
356
357 /* transition actions */
358
359 void
360 TransportFSM::start_playback ()
361 {
362         DEBUG_TRACE (DEBUG::TFSMEvents, "start_playback\n");
363
364         _last_locate.target = max_samplepos;
365         current_roll_after_locate_status = boost::none;
366
367         api->start_transport();
368 }
369
370 void
371 TransportFSM::stop_playback (Event const & s)
372 {
373         DEBUG_TRACE (DEBUG::TFSMEvents, "stop_playback\n");
374
375         _last_locate.target = max_samplepos;
376         current_roll_after_locate_status = boost::none;
377
378         api->stop_transport (s.abort, s.clear_state);
379 }
380
381 void
382 TransportFSM::set_roll_after (bool with_roll) const
383 {
384         current_roll_after_locate_status = with_roll;
385 }
386
387 void
388 TransportFSM::start_declick_for_locate (Event const & l)
389 {
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()));
393         _last_locate = l;
394
395         if (!current_roll_after_locate_status) {
396                 set_roll_after (compute_should_roll (l.ltd));
397         }
398         api->stop_transport (false, false);
399 }
400
401 void
402 TransportFSM::start_locate_while_stopped (Event const & l) const
403 {
404         assert (l.type == Locate);
405         DEBUG_TRACE (DEBUG::TFSMEvents, "start_locate_while_stopped\n");
406
407         set_roll_after (compute_should_roll (l.ltd));
408
409         api->locate (l.target, current_roll_after_locate_status.get(), l.with_flush, l.for_loop_end, l.force);
410 }
411
412 bool
413 TransportFSM::compute_should_roll (LocateTransportDisposition ltd) const
414 {
415         switch (ltd) {
416         case MustRoll:
417                 return true;
418         case MustStop:
419                 return false;
420         case RollIfAppropriate:
421                 if (rolling()) {
422                         return true;
423                 } else {
424                         return api->should_roll_after_locate ();
425                 }
426                 break;
427         }
428         /*NOTREACHED*/
429         return true;
430 }
431
432 void
433 TransportFSM::locate_for_loop (Event const & l)
434 {
435         assert (l.type == Locate);
436         DEBUG_TRACE (DEBUG::TFSMEvents, string_compose ("locate_for_loop, wl = %1\n", l.for_loop_end));
437
438         const bool should_roll = compute_should_roll (l.ltd);
439
440         _last_locate = l;
441         api->locate (l.target, should_roll, l.with_flush, l.for_loop_end, l.force);
442 }
443
444 void
445 TransportFSM::start_locate_after_declick () const
446 {
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)));
449
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);
452 }
453
454 void
455 TransportFSM::interrupt_locate (Event const & l) const
456 {
457         assert (l.type == Locate);
458         DEBUG_TRACE (DEBUG::TFSMEvents, string_compose ("interrupt to %1 versus %2\n", l.target, _last_locate.target));
459
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.
468          *
469          * So, we must avoid this from happening, and this seems like the
470          * simplest way.
471          */
472
473         if (l.target == _last_locate.target && !l.force) {
474                 return;
475         }
476         /* maintain original "with-roll" choice of initial locate, even though
477          * we are interrupting the locate to start a new one.
478          */
479         api->locate (l.target, false, l.with_flush, l.for_loop_end, l.force);
480 }
481
482 void
483 TransportFSM::schedule_butler_for_transport_work () const
484 {
485         api->schedule_butler_for_transport_work ();
486 }
487
488 bool
489 TransportFSM::should_roll_after_locate () const
490 {
491         bool roll;
492
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
496         } else {
497                 roll = api->should_roll_after_locate ();
498         }
499
500         DEBUG_TRACE (DEBUG::TFSMEvents, string_compose ("should_roll_after_locate() ? %1\n", roll));
501         return roll;
502 }
503
504 void
505 TransportFSM::roll_after_locate () const
506 {
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 ();
510 }
511
512 void
513 TransportFSM::defer (Event& ev)
514 {
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);
517 }
518
519 void
520 TransportFSM::transition (MotionState ms)
521 {
522         const MotionState old = _motion_state;
523         _motion_state = ms;
524         DEBUG_TRACE (DEBUG::TFSMState, string_compose ("Leave %1, enter %2\n", enum_2_string (old), current_state()));
525 }
526
527 void
528 TransportFSM::transition (ButlerState bs)
529 {
530         const ButlerState old = _butler_state;
531         _butler_state = bs;
532         DEBUG_TRACE (DEBUG::TFSMState, string_compose ("Leave %1, enter %2\n", enum_2_string (old), current_state()));
533 }
534
535 void
536 TransportFSM::enqueue (Event* ev)
537 {
538         DEBUG_TRACE (DEBUG::TFSMState, string_compose ("queue tfsm event %1\n", enum_2_string (ev->type)));
539         queued_events.push_back (*ev);
540         if (!processing) {
541                 process_events ();
542         }
543 }