a06e2159c9c8e6585fb53391ff85acee539af90b
[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)
58         , _last_stop (StopTransport)
59         , api (&tapi)
60         , processing (0)
61 {
62         init ();
63 }
64
65 void
66 TransportFSM::init ()
67 {
68         _motion_state = Stopped;
69         _butler_state = NotWaitingForButler;
70         _last_locate.target = max_samplepos;
71 }
72
73 void
74 TransportFSM::process_events ()
75 {
76         processing++;
77
78         while (!queued_events.empty()) {
79
80                 MotionState oms = _motion_state;
81                 ButlerState obs = _butler_state;
82
83                 Event* ev = &queued_events.front();
84                 bool deferred;
85
86                 /* must remove from the queued_events list now, because
87                  * process_event() may defer the event. This will lead to
88                  * insertion into the deferred_events list, and its not possible
89                  * with intrusive lists to be present in two lists at once
90                  * (without additional hooks).
91                  */
92
93                 queued_events.pop_front ();
94
95                 if (process_event (*ev, false, deferred)) { /* event processed successfully */
96
97                         if (oms != _motion_state || obs != _butler_state) {
98
99                                 /* state changed, so now check deferred events
100                                  * to see if they can be processed now
101                                  */
102
103                                 if (!deferred_events.empty() ){
104                                         DEBUG_TRACE (DEBUG::TFSMEvents, string_compose ("processing %1 deferred events\n", deferred_events.size()));
105
106                                         for (EventList::iterator e = deferred_events.begin(); e != deferred_events.end(); ) {
107                                                 Event* deferred_ev = &(*e);
108                                                 bool deferred2;
109                                                 if (process_event (*e, true, deferred2)) { /* event processed, remove from deferred */
110                                                         e = deferred_events.erase (e);
111                                                         delete deferred_ev;
112                                                 } else {
113                                                         ++e;
114                                                 }
115                                         }
116                                 }
117                         }
118                 }
119
120                 if (!deferred) {
121                         delete ev;
122                 }
123         }
124
125         processing--;
126 }
127
128 /* This is the transition table from the original boost::msm
129  * implementation of this FSM. It is more easily readable and
130  * consultable. Please keep it updated as the FSM changes.
131  *
132  * Here's a hint about how to read each line of this table:
133  *
134  * "if the current state is Start and event Event arrives, new state is Next and we execute Action()"
135  *
136  * with a variant:
137  *
138  * "if the current state is Start and event Event arrives, new state is Next and we execute Action() ***IF*** Guard() returns true"
139  *
140  * This new implementation, however, does not use metaprogramming to achieve all this,
141  * but just uses a large-ish switch() block.
142  *
143  */
144
145 /*
146         Start                Event            Next               Action                Guard
147       +----------------------+----------------+------------------+---------------------+---------------------------------+
148 a_row < Stopped,             start_transport, Rolling,           &T::start_playback                                      >,
149 _row  < Stopped,             stop_transport,  Stopped                                                                    >,
150 a_row < Stopped,             locate,          WaitingForLocate,  &T::start_locate_while_stopped                          >,
151 g_row < WaitingForLocate,    locate_done,     Stopped,                                  &T::should_not_roll_after_locate >,
152 _row  < Rolling,             butler_done,     Rolling                                                                    >,
153 _row  < Rolling,             start_transport, Rolling                                                                    >,
154 a_row < Rolling,             stop_transport,  DeclickToStop,     &T::stop_playback                                       >,
155 a_row < DeclickToStop,       declick_done,    Stopped,                                                                   >,
156 a_row < DeclickToStop,       stop_transport,  DeclickToStop                                                              >,
157 a_row < Rolling,             locate,          DeclickToLocate,   &T::start_declick_for_locate                            >,
158 a_row < DeclickToLocate,     declick_done,    WaitingForLocate,  &T::start_locate_after_declick                          >,
159 row   < WaitingForLocate,    locate_done,     Rolling,           &T::roll_after_locate, &T::should_roll_after_locate     >,
160 a_row < NotWaitingForButler, butler_required, WaitingForButler,  &T::schedule_butler_for_transport_work                  >,
161 a_row < WaitingForButler,    butler_required, WaitingForButler,  &T::schedule_butler_for_transport_work                  >,
162 _row  < WaitingForButler,    butler_done,     NotWaitingForButler                                                        >,
163 a_row < WaitingForLocate,    locate,          WaitingForLocate,  &T::interrupt_locate                                    >,
164 a_row < DeclickToLocate,     locate,          DeclickToLocate,   &T::interrupt_locate                                    >,
165
166 // Deferrals
167
168 #define defer(start_state,ev) boost::msm::front::Row<start_state, ev, start_state, boost::msm::front::Defer, boost::msm::front::none >
169
170 defer (DeclickToLocate, start_transport),
171 defer (DeclickToLocate, stop_transport),
172 defer (DeclickToStop, start_transport),
173 defer (WaitingForLocate, start_transport),
174 defer (WaitingForLocate, stop_transport)
175
176 #undef defer
177 */
178
179 std::string
180 TransportFSM::current_state () const
181 {
182         std::stringstream s;
183         s << enum_2_string (_motion_state) << '/' << enum_2_string (_butler_state);
184         return s.str();
185 }
186
187 void
188 TransportFSM::bad_transition (Event const & ev)
189 {
190         error << "bad transition, current state = " << current_state() << " event = " << enum_2_string (ev.type) << endmsg;
191         std::cerr << "bad transition, current state = " << current_state() << " event = " << enum_2_string (ev.type) << std::endl;
192         PBD::stacktrace (std::cerr, 30);
193 }
194
195 bool
196 TransportFSM::process_event (Event& ev, bool already_deferred, bool& deferred)
197 {
198         DEBUG_TRACE (DEBUG::TFSMEvents, string_compose ("process %1\n", enum_2_string (ev.type)));
199
200         deferred = false;
201
202         switch (ev.type) {
203
204         case StartTransport:
205                 switch (_motion_state) {
206                 case Stopped:
207                         transition (Rolling);
208                         start_playback ();
209                         break;
210                 case Rolling:
211                         break;
212                 case DeclickToLocate:
213                 case WaitingForLocate:
214                         if (!already_deferred) {
215                                 defer (ev);
216                                 deferred = true;
217                         }
218                         break;
219                 case DeclickToStop:
220                         if (!already_deferred) {
221                                 defer (ev);
222                                 deferred = true;
223                         }
224                         break;
225                 default:
226                         bad_transition (ev); return false;
227                         break;
228                 }
229                 break;
230
231         case StopTransport:
232                 switch (_motion_state) {
233                 case Rolling:
234                         transition (DeclickToStop);
235                         stop_playback ();
236                         break;
237                 case Stopped:
238                         break;
239                 case DeclickToLocate:
240                 case WaitingForLocate:
241                         if (!already_deferred) {
242                                 defer (ev);
243                                 deferred = true;
244                         }
245                         break;
246                 case DeclickToStop:
247                         /* already doing it */
248                         break;
249                 default:
250                         bad_transition (ev); return false;
251                         break;
252                 }
253                 break;
254
255         case Locate:
256                 DEBUG_TRACE (DEBUG::TFSMEvents, string_compose ("locate, ltd = %1 flush = %2 target = %3 loop %4 force %5\n",
257                                                                 enum_2_string (ev.ltd),
258                                                                 ev.with_flush,
259                                                                 ev.target,
260                                                                 ev.for_loop_end,
261                                                                 ev.force));
262                 switch (_motion_state) {
263                 case Stopped:
264                         transition (WaitingForLocate);
265                         start_locate_while_stopped (ev);
266                         break;
267                 case Rolling:
268                         if (ev.for_loop_end) {
269                                 /* we will finish the locate synchronously, so
270                                  * that after returning from
271                                  * ::locate_for_loop() we will already have
272                                  * received (and re-entrantly handled)
273                                  * LocateDone and returned back to Rolling.
274                                  *
275                                  * This happens because we only need to do a
276                                  * realtime locate and continue rolling. No
277                                  * disk I/O is required - the loop is
278                                  * automically present in buffers already.
279                                  *
280                                  * Note that ev.ltd is ignored and
281                                  * assumed to be true because we're looping.
282                                  */
283                                 transition (WaitingForLocate);
284                                 locate_for_loop (ev);
285                         } else {
286                                 transition (DeclickToLocate);
287                                 start_declick_for_locate (ev);
288                         }
289                         break;
290                 case WaitingForLocate:
291                 case DeclickToLocate:
292                         interrupt_locate (ev);
293                         break;
294                 default:
295                         bad_transition (ev); return false;
296                 }
297                 break;
298
299         case LocateDone:
300                 switch (_motion_state) {
301                 case WaitingForLocate:
302                         if (should_not_roll_after_locate()) {
303                                 transition (Stopped);
304                                 /* already stopped, nothing to do */
305                         } else {
306                                 transition (Rolling);
307                                 roll_after_locate ();
308                         }
309                         break;
310                 default:
311                         bad_transition (ev); return false;
312                 }
313                 break;
314
315         case DeclickDone:
316                 switch (_motion_state) {
317                 case DeclickToLocate:
318                         transition (WaitingForLocate);
319                         start_locate_after_declick ();
320                         break;
321                 case DeclickToStop:
322                         transition (Stopped);
323                         /* transport already stopped */
324                         break;
325                 default:
326                         bad_transition (ev); return false;
327                 }
328                 break;
329
330         case ButlerRequired:
331                 switch (_butler_state) {
332                 case NotWaitingForButler:
333                         transition (WaitingForButler);
334                         schedule_butler_for_transport_work ();
335                         break;
336                 case WaitingForButler:
337                         schedule_butler_for_transport_work ();
338                         break;
339                 default:
340                         bad_transition (ev); return false;
341                 }
342                 break;
343
344         case ButlerDone:
345                 switch (_butler_state) {
346                 case WaitingForButler:
347                         transition (NotWaitingForButler);
348                         break;
349                 default:
350                         bad_transition (ev); return false;
351                 }
352                 break;
353         }
354
355         return true;
356 }
357
358 /* transition actions */
359
360 void
361 TransportFSM::start_playback ()
362 {
363         DEBUG_TRACE (DEBUG::TFSMEvents, "start_playback\n");
364
365         _last_locate.target = max_samplepos;
366         current_roll_after_locate_status = boost::none;
367
368         api->start_transport();
369 }
370
371 void
372 TransportFSM::stop_playback ()
373 {
374         DEBUG_TRACE (DEBUG::TFSMEvents, "stop_playback\n");
375
376         _last_locate.target = max_samplepos;
377         current_roll_after_locate_status = boost::none;
378
379         api->stop_transport (_last_stop.abort, _last_stop.clear_state);
380 }
381
382 void
383 TransportFSM::set_roll_after (bool with_roll) const
384 {
385         current_roll_after_locate_status = with_roll;
386 }
387
388 void
389 TransportFSM::start_declick_for_locate (Event const & l)
390 {
391         assert (l.type == Locate);
392         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,
393                                                         enum_2_string (l.ltd), api->speed(), api->should_roll_after_locate()));
394         _last_locate = l;
395
396         if (!current_roll_after_locate_status) {
397                 set_roll_after (compute_should_roll (l.ltd));
398         }
399         api->stop_transport (false, false);
400 }
401
402 void
403 TransportFSM::start_locate_while_stopped (Event const & l) const
404 {
405         assert (l.type == Locate);
406         DEBUG_TRACE (DEBUG::TFSMEvents, "start_locate_while_stopped\n");
407
408         set_roll_after (compute_should_roll (l.ltd));
409
410         api->locate (l.target, current_roll_after_locate_status.get(), l.with_flush, l.for_loop_end, l.force);
411 }
412
413 bool
414 TransportFSM::compute_should_roll (LocateTransportDisposition ltd) const
415 {
416         switch (ltd) {
417         case MustRoll:
418                 return true;
419         case MustStop:
420                 return false;
421         case DoTheRightThing:
422                 if (rolling()) {
423                         return true;
424                 } else {
425                         return api->should_roll_after_locate ();
426                 }
427                 break;
428         }
429         /*NOTREACHED*/
430         return true;
431 }
432
433 void
434 TransportFSM::locate_for_loop (Event const & l)
435 {
436         assert (l.type == Locate);
437         DEBUG_TRACE (DEBUG::TFSMEvents, string_compose ("locate_for_loop, wl = %1\n", l.for_loop_end));
438
439         const bool should_roll = compute_should_roll (l.ltd);
440
441         _last_locate = l;
442         api->locate (l.target, should_roll, l.with_flush, l.for_loop_end, l.force);
443 }
444
445 void
446 TransportFSM::start_locate_after_declick () const
447 {
448         DEBUG_TRACE (DEBUG::TFSMEvents, string_compose ("start_locate_after_declick, have crals ? %1 roll will be %2\n", (bool) current_roll_after_locate_status,
449                                                         current_roll_after_locate_status ? current_roll_after_locate_status.get() : compute_should_roll (_last_locate.ltd)));
450
451         const bool roll = current_roll_after_locate_status ? current_roll_after_locate_status.get() : compute_should_roll (_last_locate.ltd);
452         api->locate (_last_locate.target, roll, _last_locate.with_flush, _last_locate.for_loop_end, _last_locate.force);
453 }
454
455 void
456 TransportFSM::interrupt_locate (Event const & l) const
457 {
458         assert (l.type == Locate);
459         DEBUG_TRACE (DEBUG::TFSMEvents, string_compose ("interrupt to %1 versus %2\n", l.target, _last_locate.target));
460
461         /* Because of snapping (e.g. of mouse position) we could be
462          * interrupting an existing locate to the same position. If we go ahead
463          * with this, the code in Session::do_locate() will notice that it's a
464          * repeat position, will do nothing, will queue a "locate_done" event
465          * that will arrive in the next process cycle. But this event may be
466          * processed before the original (real) locate has completed in the
467          * butler thread, and processing it may transition us back to Rolling
468          * before some (or even all) tracks are actually ready.
469          *
470          * So, we must avoid this from happening, and this seems like the
471          * simplest way.
472          */
473
474         if (l.target == _last_locate.target && !l.force) {
475                 return;
476         }
477         /* maintain original "with-roll" choice of initial locate, even though
478          * we are interrupting the locate to start a new one.
479          */
480         api->locate (l.target, false, l.with_flush, l.for_loop_end, l.force);
481 }
482
483 void
484 TransportFSM::schedule_butler_for_transport_work () const
485 {
486         api->schedule_butler_for_transport_work ();
487 }
488
489 bool
490 TransportFSM::should_roll_after_locate () const
491 {
492         bool roll;
493
494         if (current_roll_after_locate_status) {
495                 roll = current_roll_after_locate_status.get();
496                 current_roll_after_locate_status = boost::none; // used it
497         } else {
498                 roll = api->should_roll_after_locate ();
499         }
500
501         DEBUG_TRACE (DEBUG::TFSMEvents, string_compose ("should_roll_after_locate() ? %1\n", roll));
502         return roll;
503 }
504
505 void
506 TransportFSM::roll_after_locate () const
507 {
508         DEBUG_TRACE (DEBUG::TFSMEvents, string_compose ("rolling after locate, was for_loop ? %1\n", _last_locate.for_loop_end));
509         current_roll_after_locate_status = boost::none;
510         api->start_transport ();
511 }
512
513 void
514 TransportFSM::defer (Event& ev)
515 {
516         DEBUG_TRACE (DEBUG::TFSMEvents, string_compose ("Defer %1 during %2\n", enum_2_string (ev.type), current_state()));
517         deferred_events.push_back (ev);
518 }
519
520 void
521 TransportFSM::transition (MotionState ms)
522 {
523         const MotionState old = _motion_state;
524         _motion_state = ms;
525         DEBUG_TRACE (DEBUG::TFSMState, string_compose ("Leave %1, enter %2\n", enum_2_string (old), current_state()));
526 }
527
528 void
529 TransportFSM::transition (ButlerState bs)
530 {
531         const ButlerState old = _butler_state;
532         _butler_state = bs;
533         DEBUG_TRACE (DEBUG::TFSMState, string_compose ("Leave %1, enter %2\n", enum_2_string (old), current_state()));
534 }
535
536 void
537 TransportFSM::enqueue (Event* ev)
538 {
539         DEBUG_TRACE (DEBUG::TFSMState, string_compose ("queue tfsm event %1\n", enum_2_string (ev->type)));
540         queued_events.push_back (*ev);
541         if (!processing) {
542                 process_events ();
543         }
544 }