Midi clock housekeeping
[ardour.git] / libs / ardour / ticker.cc
1 /*
2     Copyright (C) 2008 Hans Baier
3
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 of the License, or
7     (at your option) any later version.
8
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13
14     You should have received a copy of the GNU General Public License
15     along with this program; if not, write to the Free Software
16     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17 */
18
19 #include "pbd/compose.h"
20 #include "pbd/stacktrace.h"
21
22 #include "midi++/port.h"
23 #include "midi++/jack_midi_port.h"
24 #include "midi++/manager.h"
25
26 #include "evoral/midi_events.h"
27
28 #include "ardour/audioengine.h"
29 #include "ardour/ticker.h"
30 #include "ardour/session.h"
31 #include "ardour/tempo.h"
32 #include "ardour/debug.h"
33
34 using namespace ARDOUR;
35 using namespace PBD;
36
37 /** MIDI Clock Position tracking */
38 class MidiClockTicker::Position : public Timecode::BBT_Time
39 {
40 public:
41
42         Position() : speed(0.0f), frame(0) { }
43         ~Position() { }
44
45         /** Sync timing information taken from the given Session
46                 @return True if timings differed */
47         bool sync (Session* s) {
48
49                 bool didit = false;
50
51                 double     sp = s->transport_speed();
52                 framecnt_t fr = s->transport_frame();
53
54                 if (speed != sp) {
55                         speed = sp;
56                         didit = true;
57                 }
58
59                 if (frame != fr) {
60                         frame = fr;
61                         didit = true;
62                 }
63
64                 /* Midi beats and clocks always gets updated for now */
65
66                 s->bbt_time (this->frame, *this);
67
68                 const TempoMap& tempo = s->tempo_map();
69
70                 const double divisions   = tempo.meter_at(frame).divisions_per_bar();
71                 const double divisor     = tempo.meter_at(frame).note_divisor();
72                 const double qnote_scale = divisor * 0.25f;
73
74                 /** Midi Beats in terms of Song Position Pointer is equivalent to total
75                         sixteenth notes at 'time' */
76
77                 midi_beats  = (((bars - 1) * divisions) + beats - 1);
78                 midi_beats += (double)ticks / (double)Position::ticks_per_beat * qnote_scale;
79                 midi_beats *= 16.0f / divisor;
80
81                 midi_clocks = midi_beats * 6.0f;
82
83                 return didit;
84         }
85
86         double      speed;
87         framecnt_t  frame;
88         double      midi_beats;
89         double      midi_clocks;
90
91         void print (std::ostream& s) {
92                 s << "frames: " << frame << " midi beats: " << midi_beats << " speed: " << speed;
93         }
94 };
95
96
97 MidiClockTicker::MidiClockTicker ()
98         : _midi_port (0)
99         , _ppqn (24)
100         , _last_tick (0.0)
101 {
102         _pos.reset (new Position());
103 }
104
105 MidiClockTicker::~MidiClockTicker()
106 {
107         _midi_port = 0;
108         _pos.reset (0);
109 }
110
111 void
112 MidiClockTicker::set_session (Session* s)
113 {
114         SessionHandlePtr::set_session (s);
115         
116          if (_session) {
117                  _session->TransportStateChange.connect_same_thread (_session_connections, boost::bind (&MidiClockTicker::transport_state_changed, this));
118                  _session->PositionChanged.connect_same_thread (_session_connections, boost::bind (&MidiClockTicker::position_changed, this, _1));
119                  _session->TransportLooped.connect_same_thread (_session_connections, boost::bind (&MidiClockTicker::transport_looped, this));
120                  _session->Located.connect_same_thread (_session_connections, boost::bind (&MidiClockTicker::session_located, this));
121
122                  update_midi_clock_port();
123                  _pos->sync (_session);
124          }
125 }
126
127 void
128 MidiClockTicker::session_located()
129 {
130         DEBUG_TRACE (DEBUG::MidiClock, string_compose ("Session Located: %1, speed: %2\n", _session->transport_frame(), _session->transport_speed()));
131
132         if (0 == _session || ! _pos->sync (_session)) {
133                 return;
134         }
135
136         _last_tick = _pos->frame;
137
138         if (!Config->get_send_midi_clock()) {
139                 return;
140         }
141
142         if (_pos->speed == 0.0f) {
143                 uint32_t where = llrint (_pos->midi_beats);
144                 send_position_event (where, 0);
145         } else if (_pos->speed == 1.0f) {
146 #if 1
147                 /* Experimental.  To really do this and have accuracy, the
148                    stop/locate/continue sequence would need queued to send immediately
149                    before the next midi clock. */
150
151                 send_stop_event (0);
152
153                 if (_pos->frame == 0) {
154                         send_start_event (0);
155                 } else {
156                         uint32_t where = llrint (_pos->midi_beats);
157                         send_position_event (where, 0);
158                         send_continue_event (0);
159                 }
160 #endif
161         } else {
162                 /* Varispeed not supported */
163         }
164 }
165
166 void
167 MidiClockTicker::session_going_away ()
168 {
169         SessionHandlePtr::session_going_away();
170         _midi_port = 0;
171 }
172
173 void
174 MidiClockTicker::update_midi_clock_port()
175 {
176         _midi_port = MIDI::Manager::instance()->midi_clock_output_port();
177 }
178
179 void
180 MidiClockTicker::transport_state_changed()
181 {
182         if (_session->exporting()) {
183                 /* no midi clock during export, for now */
184                 return;
185         }
186
187         if (!_session->engine().running()) {
188                 /* Engine stopped, we can't do anything */
189                 return;
190         }
191
192         if (! _pos->sync (_session)) {
193                 return;
194         }
195
196         DEBUG_TRACE (DEBUG::MidiClock,
197                  string_compose ("Transport state change @ %4, speed: %1 position: %2 play loop: %3\n",
198                                                         _pos->speed, _pos->frame, _session->get_play_loop(), _pos->frame)
199         );
200
201         _last_tick = _pos->frame;
202
203         if (! Config->get_send_midi_clock()) {
204                 return;
205         }
206
207         if (_pos->speed == 1.0f) {
208
209                 if (_session->get_play_loop()) {
210                         assert(_session->locations()->auto_loop_location());
211
212                         if (_pos->frame == _session->locations()->auto_loop_location()->start()) {
213                                 send_start_event(0);
214                         } else {
215                                 send_continue_event(0);
216                         }
217
218                 } else if (_pos->frame == 0) {
219                         send_start_event(0);
220                 } else {
221                         send_continue_event(0);
222                 }
223
224                 // send_midi_clock_event (0);
225
226         } else if (_pos->speed == 0.0f) {
227                 send_stop_event (0);
228                 send_position_event (llrint (_pos->midi_beats), 0);
229         }
230
231         // tick (_pos->frame);
232 }
233
234 void
235 MidiClockTicker::position_changed (framepos_t)
236 {
237 #if 0
238         const double speed = _session->transport_speed();
239         DEBUG_TRACE (DEBUG::MidiClock, string_compose ("Transport Position Change: %1, speed: %2\n", position, speed));
240
241         if (speed == 0.0f && Config->get_send_midi_clock()) {
242                 send_position_event (position, 0);
243         }
244
245         _last_tick = position;
246 #endif
247 }
248
249 void
250 MidiClockTicker::transport_looped()
251 {
252         Location* loop_location = _session->locations()->auto_loop_location();
253         assert(loop_location);
254
255         DEBUG_TRACE (DEBUG::MidiClock,
256                      string_compose ("Transport looped, position: %1, loop start: %2, loop end: %3, play loop: %4\n",
257                                      _session->transport_frame(), loop_location->start(), loop_location->end(), _session->get_play_loop())
258                 );
259
260         // adjust _last_tick, so that the next MIDI clock message is sent
261         // in due time (and the tick interval is still constant)
262
263         framecnt_t elapsed_since_last_tick = loop_location->end() - _last_tick;
264
265         if (loop_location->start() > elapsed_since_last_tick) {
266                 _last_tick = loop_location->start() - elapsed_since_last_tick;
267         } else {
268                 _last_tick = 0;
269         }
270 }
271
272 void
273 MidiClockTicker::tick (const framepos_t& /* transport_frame */)
274 {
275         if (!Config->get_send_midi_clock() || _session == 0 || _session->transport_speed() != 1.0f || _midi_port == 0) {
276                 return;
277         }
278
279         MIDI::JackMIDIPort* mp = dynamic_cast<MIDI::JackMIDIPort*> (_midi_port);
280         if (! mp) {
281                 return;
282         }
283
284         const framepos_t end = _pos->frame + mp->nframes_this_cycle();
285         double iter = _last_tick;
286
287         while (true) {
288                 double clock_delta = one_ppqn_in_frames (llrint (iter));
289                 double next_tick   = iter + clock_delta;
290                 frameoffset_t next_tick_offset = llrint (next_tick) - end;
291
292                 DEBUG_TRACE (DEBUG::MidiClock,
293                                  string_compose ("Tick: iter: %1, last tick time: %2, next tick time: %3, offset: %4, cycle length: %5\n",
294                                                  iter, _last_tick, next_tick, next_tick_offset, mp ? mp->nframes_this_cycle() : 0));
295
296                 if (!mp || (next_tick_offset >= mp->nframes_this_cycle())) {
297                         break;
298                 }
299
300                 if (next_tick_offset >= 0) {
301                         send_midi_clock_event (next_tick_offset);
302                 }
303
304                 iter = next_tick;
305         }
306
307         _last_tick  = iter;
308         _pos->frame = end;
309 }
310
311
312 double
313 MidiClockTicker::one_ppqn_in_frames (framepos_t transport_position)
314 {
315         const Tempo& current_tempo = _session->tempo_map().tempo_at (transport_position);
316         double frames_per_beat = current_tempo.frames_per_beat (_session->nominal_frame_rate());
317
318         double quarter_notes_per_beat = 4.0 / current_tempo.note_type();
319         double frames_per_quarter_note = frames_per_beat / quarter_notes_per_beat;
320
321         return frames_per_quarter_note / double (_ppqn);
322 }
323
324 void
325 MidiClockTicker::send_midi_clock_event (pframes_t offset)
326 {
327         if (!_midi_port) {
328                 return;
329         }
330
331         DEBUG_TRACE (DEBUG::MidiClock, string_compose ("Tick with offset %1\n", offset));
332
333         static uint8_t _midi_clock_tick[1] = { MIDI_CMD_COMMON_CLOCK };
334         _midi_port->write (_midi_clock_tick, 1, offset);
335 }
336
337 void
338 MidiClockTicker::send_start_event (pframes_t offset)
339 {
340         if (!_midi_port) {
341                 return;
342         }
343
344         DEBUG_TRACE (DEBUG::MidiClock, string_compose ("Start %1\n", _last_tick));
345
346         static uint8_t _midi_clock_tick[1] = { MIDI_CMD_COMMON_START };
347         _midi_port->write (_midi_clock_tick, 1, offset);
348 }
349
350 void
351 MidiClockTicker::send_continue_event (pframes_t offset)
352 {
353         if (!_midi_port) {
354                 return;
355         }
356
357         DEBUG_TRACE (DEBUG::MidiClock, string_compose ("Continue %1\n", _last_tick));
358
359         static uint8_t _midi_clock_tick[1] = { MIDI_CMD_COMMON_CONTINUE };
360         _midi_port->write (_midi_clock_tick, 1, offset);
361 }
362
363 void
364 MidiClockTicker::send_stop_event (pframes_t offset)
365 {
366         if (!_midi_port) {
367                 return;
368         }
369
370         DEBUG_TRACE (DEBUG::MidiClock, string_compose ("Stop %1\n", _last_tick));
371
372         static uint8_t _midi_clock_tick[1] = { MIDI_CMD_COMMON_STOP };
373         _midi_port->write (_midi_clock_tick, 1, offset);
374 }
375
376 void
377 MidiClockTicker::send_position_event (uint32_t midi_beats, pframes_t offset)
378 {
379         if (!_midi_port) {
380                 return;
381         }
382
383         /* can only use 14bits worth */
384         if (midi_beats > 0x3fff) {
385                 return;
386         }
387
388         /* split midi beats into a 14bit value */
389         MIDI::byte msg[3] = {
390                 MIDI_CMD_COMMON_SONG_POS,
391                 midi_beats & 0x007f,
392                 midi_beats & 0x3f80
393         };
394
395         _midi_port->midimsg (msg, sizeof (msg), offset);
396
397         DEBUG_TRACE (DEBUG::MidiClock, string_compose ("Song Position Sent: %1\n", midi_beats));
398 }