MIDI Clock - Shuffling locate code (not actually used yet)
[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
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), clocks_till_locate(-1) { }
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
61                         s->bbt_time (fr, *this);
62
63                         const TempoMap& tempo = s->tempo_map();
64
65                         const double divisions   = tempo.meter_at(frame).divisions_per_bar();
66                         const double divisor     = tempo.meter_at(frame).note_divisor();
67                         const double qnote_scale = divisor * 0.25f;
68
69                         frame = fr;
70
71                         /* Midi Beats in terms of Song Position Pointer is equivalent to total
72                            sixteenth notes at 'time' */
73
74                         midi_beats  = (((bars - 1) * divisions) + beats - 1);
75                         midi_beats += (double)ticks / (double)Position::ticks_per_beat * qnote_scale;
76                         midi_beats *= 16.0f / divisor;
77
78                         midi_clocks = midi_beats * 6.0f;
79
80                         didit = true;
81                 }
82
83                 print (std::clog);
84
85                 return didit;
86         }
87
88         double      speed;
89         framecnt_t  frame;
90         double      midi_beats;
91         double      midi_clocks;
92
93         void print (std::ostream& s) {
94                 s << "MCLK Position: frames: " << frame << " midi beats: " << midi_beats << " speed: " << speed << std::endl;
95         }
96 };
97
98
99 MidiClockTicker::MidiClockTicker ()
100         : _midi_port (0)
101         , _ppqn (24)
102         , _last_tick (0.0)
103 {
104         _pos = new Position();
105 }
106
107 MidiClockTicker::~MidiClockTicker()
108 {
109         _midi_port = 0;
110         if (_pos) {
111                 delete _pos;
112                 _pos = 0;
113         }
114 }
115
116 void
117 MidiClockTicker::set_session (Session* s)
118 {
119         SessionHandlePtr::set_session (s);
120         
121          if (_session) {
122                  _session->TransportStateChange.connect_same_thread (_session_connections, boost::bind (&MidiClockTicker::transport_state_changed, this));
123                  _session->PositionChanged.connect_same_thread (_session_connections, boost::bind (&MidiClockTicker::position_changed, this, _1));
124                  _session->TransportLooped.connect_same_thread (_session_connections, boost::bind (&MidiClockTicker::transport_looped, this));
125                  _session->Located.connect_same_thread (_session_connections, boost::bind (&MidiClockTicker::session_located, this));
126
127                  update_midi_clock_port();
128                  _pos->sync (_session);
129          }
130 }
131
132 void
133 MidiClockTicker::session_located()
134 {
135         if (0 == _session || ! _pos->sync (_session)) {
136                 return;
137         }
138
139         _last_tick = _pos->frame;
140
141         // WIP - Testing code
142         if (0 == _pos->frame) {
143                 std::clog << "zero frame\n";
144                 if (1.0f == _pos->speed) {
145                         std::clog << "normal speed:\n";
146                         _pos->clocks_till_locate = 0;
147                 }
148         }
149 }
150
151 void
152 MidiClockTicker::session_going_away ()
153 {
154         SessionHandlePtr::session_going_away();
155         _midi_port = 0;
156 }
157
158 void
159 MidiClockTicker::update_midi_clock_port()
160 {
161         _midi_port = MIDI::Manager::instance()->midi_clock_output_port();
162 }
163
164 void
165 MidiClockTicker::transport_state_changed()
166 {
167         if (_session->exporting()) {
168                 /* no midi clock during export, for now */
169                 return;
170         }
171
172         if (!_session->engine().running()) {
173                 /* Engine stopped, we can't do anything */
174                 return;
175         }
176
177         float      speed    = _session->transport_speed();
178         framepos_t position = _session->transport_frame();
179
180         DEBUG_TRACE (PBD::DEBUG::MidiClock,
181                  string_compose ("Transport state change @ %4, speed: %1 position: %2 play loop: %3\n", speed, position, _session->get_play_loop(), position)
182         );
183
184         if (speed == 1.0f) {
185                 _last_tick = position;
186
187                 if (!Config->get_send_midi_clock())
188                         return;
189
190                 if (_session->get_play_loop()) {
191                         assert(_session->locations()->auto_loop_location());
192                         if (position == _session->locations()->auto_loop_location()->start()) {
193                                 send_start_event(0);
194                         } else {
195                                 send_continue_event(0);
196                         }
197                 } else if (position == 0) {
198                         send_start_event(0);
199                 } else {
200                         send_continue_event(0);
201                 }
202
203                 send_midi_clock_event(0);
204
205         } else if (speed == 0.0f) {
206                 if (!Config->get_send_midi_clock())
207                         return;
208
209                 send_stop_event(0);
210                 send_position_event (position, 0);
211         }
212
213         tick (position);
214 }
215
216 void
217 MidiClockTicker::position_changed (framepos_t position)
218 {
219         const double speed = _session->transport_speed();
220         DEBUG_TRACE (PBD::DEBUG::MidiClock, string_compose ("Transport Position Change: %1, speed: %2\n", position, speed));
221
222         if (speed == 0.0f && Config->get_send_midi_clock()) {
223                 send_position_event (position, 0);
224         }
225
226         _last_tick = position;
227 }
228
229 void
230 MidiClockTicker::transport_looped()
231 {
232         Location* loop_location = _session->locations()->auto_loop_location();
233         assert(loop_location);
234
235         DEBUG_TRACE (PBD::DEBUG::MidiClock,
236                      string_compose ("Transport looped, position: %1, loop start: %2, loop end: %3, play loop: %4\n",
237                                      _session->transport_frame(), loop_location->start(), loop_location->end(), _session->get_play_loop())
238                 );
239
240         // adjust _last_tick, so that the next MIDI clock message is sent
241         // in due time (and the tick interval is still constant)
242
243         framecnt_t elapsed_since_last_tick = loop_location->end() - _last_tick;
244
245         if (loop_location->start() > elapsed_since_last_tick) {
246                 _last_tick = loop_location->start() - elapsed_since_last_tick;
247         } else {
248                 _last_tick = 0;
249         }
250 }
251
252 void
253 MidiClockTicker::tick (const framepos_t& transport_frame)
254 {
255         if (_pos->clocks_till_locate == 0) {
256                 std::clog << "Locate: " << transport_frame << std::endl;
257         }
258
259         --_pos->clocks_till_locate;
260
261         if (!Config->get_send_midi_clock() || _session == 0 || _session->transport_speed() != 1.0f || _midi_port == 0) {
262                 return;
263         }
264
265         while (true) {
266                 double next_tick = _last_tick + one_ppqn_in_frames (transport_frame);
267                 frameoffset_t next_tick_offset = llrint (next_tick) - transport_frame;
268
269
270                 MIDI::JackMIDIPort* mp = dynamic_cast<MIDI::JackMIDIPort*> (_midi_port);
271                 
272
273                 DEBUG_TRACE (PBD::DEBUG::MidiClock,
274                              string_compose ("Transport: %1, last tick time: %2, next tick time: %3, offset: %4, cycle length: %5\n",
275                                              transport_frame, _last_tick, next_tick, next_tick_offset, mp ? mp->nframes_this_cycle() : 0));
276
277
278                 if (!mp || (next_tick_offset >= mp->nframes_this_cycle())) {
279                         break;
280                 }
281
282                 if (next_tick_offset >= 0) {
283                         send_midi_clock_event (next_tick_offset);
284                 }
285
286                 _pos->frame = _last_tick = next_tick;
287         }
288 }
289
290 double
291 MidiClockTicker::one_ppqn_in_frames (framepos_t transport_position)
292 {
293         const Tempo& current_tempo = _session->tempo_map().tempo_at (transport_position);
294         double frames_per_beat = current_tempo.frames_per_beat (_session->nominal_frame_rate());
295
296         double quarter_notes_per_beat = 4.0 / current_tempo.note_type();
297         double frames_per_quarter_note = frames_per_beat / quarter_notes_per_beat;
298
299         return frames_per_quarter_note / double (_ppqn);
300 }
301
302 void
303 MidiClockTicker::send_midi_clock_event (pframes_t offset)
304 {
305         if (!_midi_port) {
306                 return;
307         }
308
309         // DEBUG_TRACE (PBD::DEBUG::MidiClock, string_compose ("Tick with offset %1\n", offset));
310
311         static uint8_t _midi_clock_tick[1] = { MIDI_CMD_COMMON_CLOCK };
312         _midi_port->write (_midi_clock_tick, 1, offset);
313 }
314
315 void
316 MidiClockTicker::send_start_event (pframes_t offset)
317 {
318         if (!_midi_port) {
319                 return;
320         }
321
322         DEBUG_TRACE (PBD::DEBUG::MidiClock, string_compose ("Start %1\n", _last_tick));
323
324         static uint8_t _midi_clock_tick[1] = { MIDI_CMD_COMMON_START };
325         _midi_port->write (_midi_clock_tick, 1, offset);
326 }
327
328 void
329 MidiClockTicker::send_continue_event (pframes_t offset)
330 {
331         if (!_midi_port) {
332                 return;
333         }
334
335         DEBUG_TRACE (PBD::DEBUG::MidiClock, string_compose ("Continue %1\n", _last_tick));
336
337         static uint8_t _midi_clock_tick[1] = { MIDI_CMD_COMMON_CONTINUE };
338         _midi_port->write (_midi_clock_tick, 1, offset);
339 }
340
341 void
342 MidiClockTicker::send_stop_event (pframes_t offset)
343 {
344         if (!_midi_port) {
345                 return;
346         }
347
348         DEBUG_TRACE (PBD::DEBUG::MidiClock, string_compose ("Stop %1\n", _last_tick));
349
350         static uint8_t _midi_clock_tick[1] = { MIDI_CMD_COMMON_STOP };
351         _midi_port->write (_midi_clock_tick, 1, offset);
352 }
353
354 void
355 MidiClockTicker::send_position_event (framepos_t transport_position, pframes_t offset)
356 {
357         if (_midi_port == 0 || _session == 0 || _session->engine().freewheeling()) {
358                 return;
359         }
360
361         const TempoMap& tempo = _session->tempo_map();
362
363         Timecode::BBT_Time time;
364         _session->bbt_time (transport_position, time);
365         const double beats_per_bar = tempo.meter_at(transport_position).divisions_per_bar();
366
367         /* Midi Beats in terms of Song Position Pointer is equivalent to total
368            sixteenth notes at 'time' */
369         const uint32_t midi_beats = 4 * (((time.bars - 1) * beats_per_bar) + time.beats - 1);
370
371         /* can only use 14bits worth */
372         if (midi_beats > 0x3fff) {
373                 return;
374         }
375
376         /* split midi beats into a 14bit value */
377         MIDI::byte msg[3] = {
378                 MIDI_CMD_COMMON_SONG_POS,
379                 midi_beats & 0x007f,
380                 midi_beats & 0x3f80
381         };
382
383         DEBUG_TRACE (PBD::DEBUG::MidiClock, string_compose ("Song Position: %1\n", midi_beats));
384
385         _midi_port->midimsg (msg, sizeof (msg), offset);
386 }