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