better comments
[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                 if (process_event (queued_events.front())) { /* event processed successfully */
84
85                         if (oms != _motion_state || obs != _butler_state) {
86
87                                 /* state changed, so now check deferred events
88                                  * to see if they can be processed now
89                                  */
90
91                                 if (!deferred_events.empty() ){
92                                         DEBUG_TRACE (DEBUG::TFSMEvents, string_compose ("processing %1 deferred events\n", deferred_events.size()));
93
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);
98                                                         delete deferred_ev;
99                                                 } else {
100                                                         ++e;
101                                                 }
102                                         }
103                                 }
104                         }
105                 }
106
107                 Event* ev = &queued_events.front();
108                 queued_events.pop_front ();
109                 delete ev;
110         }
111
112         processing--;
113 }
114
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.
118  *
119  * Here's a hint about how to read each line of this table:
120  *
121  * "if the current state is Start and event Event arrives, new state is Next and we execute Action()"
122  *
123  * with a variant:
124  *
125  * "if the current state is Start and event Event arrives, new state is Next and we execute Action() ***IF*** Guard() returns true"
126  *
127  * This new implementation, however, does not use metaprogramming to achieve all this,
128  * but just uses a large-ish switch() block.
129  *
130  */
131
132 /*
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                                    >,
151
152 // Deferrals
153
154 #define defer(start_state,ev) boost::msm::front::Row<start_state, ev, start_state, boost::msm::front::Defer, boost::msm::front::none >
155
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)
161
162 #undef defer
163 */
164
165 std::string
166 TransportFSM::current_state () const
167 {
168         std::stringstream s;
169         s << enum_2_string (_motion_state) << '/' << enum_2_string (_butler_state);
170         return s.str();
171 }
172
173 void
174 TransportFSM::bad_transition (Event const & ev)
175 {
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;
178 }
179
180 bool
181 TransportFSM::process_event (Event& ev)
182 {
183         DEBUG_TRACE (DEBUG::TFSMEvents, string_compose ("process %1\n", enum_2_string (ev.type)));
184
185         switch (ev.type) {
186
187         case StartTransport:
188                 switch (_motion_state) {
189                 case Stopped:
190                         transition (Rolling);
191                         start_playback ();
192                         break;
193                 case Rolling:
194                         break;
195                 case DeclickToLocate:
196                 case WaitingForLocate:
197                         defer (ev);
198                         break;
199                 case DeclickToStop:
200                         defer (ev);
201                         break;
202                 default:
203                         bad_transition (ev); return false;
204                         break;
205                 }
206                 break;
207
208         case StopTransport:
209                 switch (_motion_state) {
210                 case Rolling:
211                         transition (DeclickToStop);
212                         start_declick_for_stop (ev);
213                         break;
214                 case Stopped:
215                         break;
216                 case DeclickToLocate:
217                 case WaitingForLocate:
218                         defer (ev);
219                         break;
220                 default:
221                         bad_transition (ev); return false;
222                         break;
223                 }
224                 break;
225
226         case Locate:
227                 switch (_motion_state) {
228                 case Stopped:
229                         transition (WaitingForLocate);
230                         start_locate_while_stopped (ev);
231                         break;
232                 case Rolling:
233                         transition (DeclickToLocate);
234                         start_declick_for_locate (ev);
235                         break;
236                 case WaitingForLocate:
237                 case DeclickToLocate:
238                         interrupt_locate (ev);
239                         break;
240                 default:
241                         bad_transition (ev); return false;
242                 }
243                 break;
244
245         case LocateDone:
246                 switch (_motion_state) {
247                 case WaitingForLocate:
248                         if (should_not_roll_after_locate()) {
249                                 transition (Stopped);
250                                 /* already stopped, nothing to do */
251                         } else {
252                                 transition (Rolling);
253                                 roll_after_locate ();
254                         }
255                         break;
256                 default:
257                         bad_transition (ev); return false;
258                 }
259                 break;
260
261         case DeclickDone:
262                 switch (_motion_state) {
263                 case DeclickToLocate:
264                         transition (WaitingForLocate);
265                         start_locate_after_declick ();
266                         break;
267                 case DeclickToStop:
268                         transition (Stopped);
269                         stop_playback ();
270                         break;
271                 default:
272                         bad_transition (ev); return false;
273                 }
274                 break;
275
276         case ButlerRequired:
277                 switch (_butler_state) {
278                 case NotWaitingForButler:
279                         transition (WaitingForButler);
280                         schedule_butler_for_transport_work ();
281                         break;
282                 case WaitingForButler:
283                         schedule_butler_for_transport_work ();
284                         break;
285                 default:
286                         bad_transition (ev); return false;
287                 }
288                 break;
289
290         case ButlerDone:
291                 switch (_butler_state) {
292                 case WaitingForButler:
293                         transition (NotWaitingForButler);
294                         break;
295                 default:
296                         bad_transition (ev); return false;
297                 }
298                 break;
299         }
300
301         return true;
302 }
303
304 /* transition actions */
305
306 void
307 TransportFSM::start_playback ()
308 {
309         DEBUG_TRACE (DEBUG::TFSMEvents, "start_playback\n");
310
311         _last_locate.target = max_samplepos;
312         current_roll_after_locate_status = boost::none;
313
314         api->start_transport();
315 }
316
317 void
318 TransportFSM::stop_playback ()
319 {
320         DEBUG_TRACE (DEBUG::TFSMEvents, "stop_playback\n");
321
322         _last_locate.target = max_samplepos;
323         current_roll_after_locate_status = boost::none;
324
325         api->stop_transport (_last_stop.abort, _last_stop.clear_state);
326 }
327
328 void
329 TransportFSM::start_declick_for_stop (Event const & s)
330 {
331         assert (s.type == StopTransport);
332         DEBUG_TRACE (DEBUG::TFSMEvents, "start_declick_for_stop\n");
333         _last_stop = s;
334 }
335
336 void
337 TransportFSM::start_declick_for_locate (Event const & l)
338 {
339         assert (l.type == Locate);
340         DEBUG_TRACE (DEBUG::TFSMEvents, "start_declick_for_locate\n");
341         _last_locate = l;
342
343         if (!current_roll_after_locate_status) {
344                 if (l.with_roll) {
345                         if (api->speed() != 0.) {
346                                 current_roll_after_locate_status = true;
347                         } else {
348                                 current_roll_after_locate_status = api->should_roll_after_locate();
349                         }
350                 } else {
351                         current_roll_after_locate_status = (api->speed() != 0.);
352                 }
353         }
354
355         _last_stop = Event (StopTransport, false, false);
356 }
357
358 void
359 TransportFSM::start_locate_while_stopped (Event const & l) const
360 {
361         assert (l.type == Locate);
362         DEBUG_TRACE (DEBUG::TFSMEvents, "start_locate_while_stopped\n");
363
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);
366 }
367
368 void
369 TransportFSM::start_locate_after_declick () const
370 {
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);
374 }
375
376 void
377 TransportFSM::interrupt_locate (Event const & l) const
378 {
379         assert (l.type == Locate);
380         DEBUG_TRACE (DEBUG::TFSMEvents, string_compose ("interrupt to %1 versus %2\n", l.target, _last_locate.target));
381
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.
390          *
391          * So, we must avoid this from happening, and this seems like the
392          * simplest way.
393          */
394
395         if (l.target == _last_locate.target && !l.force) {
396                 return;
397         }
398         /* maintain original "with-roll" choice of initial locate, even though
399          * we are interrupting the locate to start a new one.
400          */
401         api->locate (l.target, false, l.with_flush, l.with_loop, l.force);
402 }
403
404 void
405 TransportFSM::schedule_butler_for_transport_work () const
406 {
407         api->schedule_butler_for_transport_work ();
408 }
409
410 bool
411 TransportFSM::should_roll_after_locate () const
412 {
413         bool roll;
414
415         if (current_roll_after_locate_status) {
416                 roll = current_roll_after_locate_status.get();
417         } else {
418                 roll = api->should_roll_after_locate ();
419         }
420
421         DEBUG_TRACE (DEBUG::TFSMEvents, string_compose ("should_roll_after_locate() ? %1\n", roll));
422         return roll;
423 }
424
425 void
426 TransportFSM::roll_after_locate () const
427 {
428         DEBUG_TRACE (DEBUG::TFSMEvents, "rolling after locate\n");
429         current_roll_after_locate_status = boost::none;
430         api->start_transport ();
431 }
432
433 void
434 TransportFSM::defer (Event& ev)
435 {
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);
438 }
439
440 void
441 TransportFSM::transition (MotionState ms)
442 {
443         const MotionState old = _motion_state;
444         _motion_state = ms;
445         DEBUG_TRACE (DEBUG::TFSMState, string_compose ("Leave %1, enter %2\n", enum_2_string (old), current_state()));
446 }
447
448 void
449 TransportFSM::transition (ButlerState bs)
450 {
451         const ButlerState old = _butler_state;
452         _butler_state = bs;
453         DEBUG_TRACE (DEBUG::TFSMState, string_compose ("Leave %1, enter %2\n", enum_2_string (old), current_state()));
454 }
455
456 void
457 TransportFSM::enqueue (Event* ev)
458 {
459         DEBUG_TRACE (DEBUG::TFSMState, string_compose ("queue tfsm event %1\n", enum_2_string (ev->type)));
460         queued_events.push_back (*ev);
461         if (!processing) {
462                 process_events ();
463         }
464 }