5f394f9bd73cbfd45a6a509c513502738b4c20ad
[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 "pbd/error.h"
23 #include "pbd/i18n.h"
24
25 #include "ardour/debug.h"
26 #include "ardour/session.h"
27 #include "ardour/transport_fsm.h"
28
29 using namespace ARDOUR;
30 using namespace PBD;
31
32 Pool* TransportFSM::Event::pool = 0;
33
34 void
35 TransportFSM::Event::init_pool ()
36 {
37         pool = new Pool (X_("Events"), sizeof (Event), 128);
38 }
39
40 void*
41 TransportFSM::Event::operator new (size_t)
42 {
43         return pool->alloc();
44  }
45
46 void
47 TransportFSM::Event::operator delete (void *ptr, size_t /*size*/)
48 {
49         return pool->release (ptr);
50 }
51
52 TransportFSM::TransportFSM (TransportAPI& tapi)
53         : _last_locate (Locate)
54         , _last_stop (StopTransport)
55         , api (&tapi)
56         , processing (0)
57 {
58         init ();
59 }
60
61 void
62 TransportFSM::init ()
63 {
64         _motion_state = Stopped;
65         _butler_state = NotWaitingForButler;
66 }
67
68 void
69 TransportFSM::process_events ()
70 {
71         processing++;
72
73         while (!queued_events.empty()) {
74
75                 MotionState oms = _motion_state;
76                 ButlerState obs = _butler_state;
77
78                 if (process_event (queued_events.front())) { /* event processed successfully */
79
80                         if (oms != _motion_state || obs != _butler_state) {
81
82                                 /* state changed, so now check deferred events
83                                  * to see if they can be processed now
84                                  */
85
86                                 if (!deferred_events.empty() ){
87                                         DEBUG_TRACE (DEBUG::TFSMEvents, string_compose ("processing %1 deferred events\n", deferred_events.size()));
88
89                                         for (EventList::iterator e = deferred_events.begin(); e != deferred_events.end(); ) {
90                                                 Event* deferred_ev = &(*e);
91                                                 if (process_event (*e)) { /* event processed, remove from deferred */
92                                                         e = deferred_events.erase (e);
93                                                         delete deferred_ev;
94                                                 } else {
95                                                         ++e;
96                                                 }
97                                         }
98                                 }
99                         }
100                 }
101
102                 Event* ev = &queued_events.front();
103                 queued_events.pop_front ();
104                 delete ev;
105         }
106
107         processing--;
108 }
109
110 /* This is the transition table from the original boost::msm
111  * implementation of this FSM. It is more easily readable and
112  * consultable. Please keep it updated as the FSM changes.
113  *
114  * Here's a hint about how to read each line of this table:
115  *
116  * "if the current state is Start and event Event arrives, new state is Next and we execute Action()"
117  *
118  * with a variant:
119  *
120  * "if the current state is Start and event Event arrives, new state is Next and we execute Action() ***IF*** Guard() returns true"
121  *
122  * This new implementation, however, does not use metaprogramming to achieve all this,
123  * but just uses a large-ish switch() block.
124  *
125  */
126
127 /*
128         Start                Event            Next               Action                Guard
129       +----------------------+----------------+------------------+---------------------+---------------------------------+
130 a_row < Stopped,             start_transport, Rolling,           &T::start_playback                                      >,
131 _row  < Stopped,             stop_transport,  Stopped                                                                    >,
132 a_row < Stopped,             locate,          WaitingForLocate,  &T::start_locate                                        >,
133 g_row < WaitingForLocate,    locate_done,     Stopped,                                  &T::should_not_roll_after_locate >,
134 _row  < Rolling,             butler_done,     Rolling                                                                    >,
135 _row  < Rolling,             start_transport, Rolling                                                                    >,
136 a_row < Rolling,             stop_transport,  DeclickToStop,     &T::start_declick                                       >,
137 a_row < DeclickToStop,       declick_done,    Stopped,           &T::stop_playback                                       >,
138 a_row < Rolling,             locate,          DeclickToLocate,   &T::save_locate_and_start_declick                       >,
139 a_row < DeclickToLocate,     declick_done,    WaitingForLocate,  &T::start_saved_locate                                  >,
140 row   < WaitingForLocate,    locate_done,     Rolling,           &T::roll_after_locate, &T::should_roll_after_locate     >,
141 a_row < NotWaitingForButler, butler_required, WaitingForButler,  &T::schedule_butler_for_transport_work                  >,
142 a_row < WaitingForButler,    butler_required, WaitingForButler,  &T::schedule_butler_for_transport_work                  >,
143 _row  < WaitingForButler,    butler_done,     NotWaitingForButler                                                        >,
144 a_row < WaitingForLocate,    locate,          WaitingForLocate,  &T::interrupt_locate                                    >,
145 a_row < DeclickToLocate,     locate,          DeclickToLocate,   &T::interrupt_locate                                    >,
146
147 // Deferrals
148
149 #define defer(start_state,ev) boost::msm::front::Row<start_state, ev, start_state, boost::msm::front::Defer, boost::msm::front::none >
150
151 defer (DeclickToLocate, start_transport),
152 defer (DeclickToLocate, stop_transport),
153 defer (DeclickToStop, start_transport),
154 defer (WaitingForLocate, start_transport),
155 defer (WaitingForLocate, stop_transport)
156
157 #undef defer
158 */
159
160 std::string
161 TransportFSM::current_state () const
162 {
163         std::stringstream s;
164         s << enum_2_string (_motion_state) << '/' << enum_2_string (_butler_state);
165         return s.str();
166 }
167
168 void
169 TransportFSM::bad_transition (Event const & ev)
170 {
171         error << "bad transition, current state = " << current_state() << " event = " << enum_2_string (ev.type) << endmsg;
172         std::cerr << "bad transition, current state = " << current_state() << " event = " << enum_2_string (ev.type) << std::endl;
173 }
174
175 bool
176 TransportFSM::process_event (Event& ev)
177 {
178         DEBUG_TRACE (DEBUG::TFSMEvents, string_compose ("process %1\n", enum_2_string (ev.type)));
179
180         switch (ev.type) {
181
182         case StartTransport:
183                 switch (_motion_state) {
184                 case Stopped:
185                         transition (Rolling);
186                         start_playback ();
187                         break;
188                 case Rolling:
189                         break;
190                 case DeclickToLocate:
191                 case WaitingForLocate:
192                         defer (ev);
193                         break;
194                 case DeclickToStop:
195                         defer (ev);
196                         break;
197                 default:
198                         bad_transition (ev); return false;
199                         break;
200                 }
201                 break;
202
203         case StopTransport:
204                 switch (_motion_state) {
205                 case Rolling:
206                         transition (DeclickToStop);
207                         start_declick (ev);
208                         break;
209                 case Stopped:
210                         break;
211                 case DeclickToLocate:
212                 case WaitingForLocate:
213                         defer (ev);
214                         break;
215                 default:
216                         bad_transition (ev); return false;
217                         break;
218                 }
219                 break;
220
221         case Locate:
222                 switch (_motion_state) {
223                 case Stopped:
224                         transition (WaitingForLocate);
225                         start_locate (ev);
226                         break;
227                 case Rolling:
228                         transition (DeclickToLocate);
229                         save_locate_and_start_declick (ev);
230                         break;
231                 case WaitingForLocate:
232                 case DeclickToLocate:
233                         interrupt_locate (ev);
234                         break;
235                 default:
236                         bad_transition (ev); return false;
237                 }
238                 break;
239
240         case LocateDone:
241                 switch (_motion_state) {
242                 case WaitingForLocate:
243                         if (should_not_roll_after_locate()) {
244                                 transition (Stopped);
245                         } else {
246                                 transition (Rolling);
247                                 roll_after_locate ();
248                         }
249                         break;
250                 default:
251                         bad_transition (ev); return false;
252                 }
253                 break;
254
255         case DeclickDone:
256                 switch (_motion_state) {
257                 case DeclickToLocate:
258                         transition (WaitingForLocate);
259                         start_saved_locate ();
260                         break;
261                 case DeclickToStop:
262                         transition (Stopped);
263                         stop_playback ();
264                         break;
265                 default:
266                         bad_transition (ev); return false;
267                 }
268                 break;
269
270         case ButlerRequired:
271                 switch (_butler_state) {
272                 case NotWaitingForButler:
273                         transition (WaitingForButler);
274                         schedule_butler_for_transport_work ();
275                         break;
276                 case WaitingForButler:
277                         schedule_butler_for_transport_work ();
278                         break;
279                 default:
280                         bad_transition (ev); return false;
281                 }
282                 break;
283
284         case ButlerDone:
285                 switch (_butler_state) {
286                 case WaitingForButler:
287                         transition (NotWaitingForButler);
288                         break;
289                 default:
290                         bad_transition (ev); return false;
291                 }
292                 break;
293         }
294
295         return true;
296 }
297
298 /* transition actions */
299
300 void
301 TransportFSM::start_playback ()
302 {
303         DEBUG_TRACE (DEBUG::TFSMEvents, "tfsm::start_playback\n");
304         api->start_transport();
305 }
306
307 void
308 TransportFSM::start_declick (Event const & s)
309 {
310         assert (s.type == StopTransport);
311         DEBUG_TRACE (DEBUG::TFSMEvents, "tfsm::start_declick\n");
312         _last_stop = s;
313 }
314
315 void
316 TransportFSM::stop_playback ()
317 {
318         DEBUG_TRACE (DEBUG::TFSMEvents, "tfsm::stop_playback\n");
319         api->stop_transport (_last_stop.abort, _last_stop.clear_state);
320 }
321
322 void
323 TransportFSM::save_locate_and_start_declick (Event const & l)
324 {
325         assert (l.type == Locate);
326         DEBUG_TRACE (DEBUG::TFSMEvents, "tfsm::save_locate_and_stop\n");
327         _last_locate = l;
328         _last_stop = Event (StopTransport, false, false);
329 }
330
331 void
332 TransportFSM::start_locate (Event const & l)
333 {
334         assert (l.type == Locate);
335         DEBUG_TRACE (DEBUG::TFSMEvents, "tfsm::start_locate\n");
336         api->locate (l.target, l.with_roll, l.with_flush, l.with_loop, l.force);
337 }
338
339 void
340 TransportFSM::start_saved_locate ()
341 {
342         DEBUG_TRACE (DEBUG::TFSMEvents, "tfsm::start_save\n");
343         api->locate (_last_locate.target, _last_locate.with_roll, _last_locate.with_flush, _last_locate.with_loop, _last_locate.force);
344 }
345
346 void
347 TransportFSM::interrupt_locate (Event const & l)
348 {
349         assert (l.type == Locate);
350         DEBUG_TRACE (DEBUG::TFSMEvents, "tfsm::interrupt\n");
351         /* maintain original "with-roll" choice of initial locate, even though
352          * we are interrupting the locate to start a new one.
353          */
354         api->locate (l.target, _last_locate.with_roll, l.with_flush, l.with_loop, l.force);
355 }
356
357 void
358 TransportFSM::schedule_butler_for_transport_work ()
359 {
360         api->schedule_butler_for_transport_work ();
361 }
362
363 bool
364 TransportFSM::should_roll_after_locate ()
365 {
366         bool ret = api->should_roll_after_locate ();
367         DEBUG_TRACE (DEBUG::TFSMEvents, string_compose ("tfsm::should_roll_after_locate() ? %1\n", ret));
368         return ret;
369 }
370
371 void
372 TransportFSM::roll_after_locate ()
373 {
374         DEBUG_TRACE (DEBUG::TFSMEvents, "rolling after locate\n");
375         api->start_transport ();
376 }
377
378 void
379 TransportFSM::defer (Event& ev)
380 {
381         DEBUG_TRACE (DEBUG::TFSMEvents, string_compose ("Defer %1 during %2\n", enum_2_string (ev.type), current_state()));
382         deferred_events.push_back (ev);
383 }
384
385 void
386 TransportFSM::transition (MotionState ms)
387 {
388         DEBUG_TRACE (DEBUG::TFSMState, string_compose ("Leave %1, enter %2\n", enum_2_string (_motion_state), enum_2_string (ms)));
389         _motion_state = ms;
390 }
391
392 void
393 TransportFSM::transition (ButlerState bs)
394 {
395         DEBUG_TRACE (DEBUG::TFSMState, string_compose ("Leave %1, enter %2\n", enum_2_string (_butler_state), enum_2_string (bs)));
396         _butler_state = bs;
397 }