fix bug in MidiClock that sent MIDI Clock messages with negative offsets after a...
[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++/manager.h"
24
25 #include "evoral/midi_events.h"
26
27 #include "ardour/audioengine.h"
28 #include "ardour/ticker.h"
29 #include "ardour/session.h"
30 #include "ardour/tempo.h"
31 #include "ardour/debug.h"
32
33 using namespace ARDOUR;
34
35 MidiClockTicker::MidiClockTicker ()
36         : _midi_port (0)
37         , _ppqn (24)
38         , _last_tick (0.0)
39 {
40 }
41
42 void MidiClockTicker::set_session (Session* s)
43 {
44         SessionHandlePtr::set_session (s);
45         
46          if (_session) {
47                  _session->TransportStateChange.connect_same_thread (_session_connections, boost::bind (&MidiClockTicker::transport_state_changed, this));
48                  _session->PositionChanged.connect_same_thread (_session_connections, boost::bind (&MidiClockTicker::position_changed, this, _1));
49                  _session->TransportLooped.connect_same_thread (_session_connections, boost::bind (&MidiClockTicker::transport_looped, this));
50                  update_midi_clock_port();
51          }
52 }
53
54 void
55 MidiClockTicker::session_going_away ()
56 {
57         SessionHandlePtr::session_going_away();
58         _midi_port = 0;
59 }
60
61 void MidiClockTicker::update_midi_clock_port()
62 {
63         _midi_port = MIDI::Manager::instance()->midi_clock_output_port();
64 }
65
66 void MidiClockTicker::transport_state_changed()
67 {
68         if (_session->exporting()) {
69                 /* no midi clock during export, for now */
70                 return;
71         }
72
73         if (!_session->engine().running()) {
74                 /* Engine stopped, we can't do anything */
75                 return;
76         }
77
78         float      speed    = _session->transport_speed();
79         framepos_t position = _session->transport_frame();
80
81         DEBUG_TRACE (PBD::DEBUG::MidiClock,
82                      string_compose ("Transport state change @ %4, speed: %1 position: %2 play loop: %3\n", speed, position, _session->get_play_loop(), position)
83                 );
84
85         if (speed == 1.0f) {
86                 _last_tick = position;
87
88                 if (!Config->get_send_midi_clock())
89                         return;
90
91                 if (_session->get_play_loop()) {
92                         assert(_session->locations()->auto_loop_location());
93                         if (position == _session->locations()->auto_loop_location()->start()) {
94                                 send_start_event(0);
95                         } else {
96                                 send_continue_event(0);
97                         }
98                 } else if (position == 0) {
99                         send_start_event(0);
100                 } else {
101                         send_continue_event(0);
102                 }
103
104                 send_midi_clock_event(0);
105
106         } else if (speed == 0.0f) {
107                 if (!Config->get_send_midi_clock())
108                         return;
109
110                 send_stop_event(0);
111         }
112
113         tick (position);
114 }
115
116 void MidiClockTicker::position_changed (framepos_t position)
117 {
118         DEBUG_TRACE (PBD::DEBUG::MidiClock, string_compose ("Position change: %1\n", position));
119
120         _last_tick = position;
121 }
122
123 void MidiClockTicker::transport_looped()
124 {
125         Location* loop_location = _session->locations()->auto_loop_location();
126         assert(loop_location);
127
128         DEBUG_TRACE (PBD::DEBUG::MidiClock,
129                      string_compose ("Transport looped, position: %1, loop start: %2, loop end: %3, play loop: %4\n",
130                                      _session->transport_frame(), loop_location->start(), loop_location->end(), _session->get_play_loop())
131                 );
132
133         // adjust _last_tick, so that the next MIDI clock message is sent
134         // in due time (and the tick interval is still constant)
135
136         framecnt_t elapsed_since_last_tick = loop_location->end() - _last_tick;
137
138         if (loop_location->start() > elapsed_since_last_tick) {
139                 _last_tick = loop_location->start() - elapsed_since_last_tick;
140         } else {
141                 _last_tick = 0;
142         }
143 }
144
145 void MidiClockTicker::tick (const framepos_t& transport_frame)
146 {
147         if (!Config->get_send_midi_clock() || _session == 0 || _session->transport_speed() != 1.0f || _midi_port == 0) {
148                 return;
149         }
150
151         while (true) {
152                 double next_tick = _last_tick + one_ppqn_in_frames (transport_frame);
153                 frameoffset_t next_tick_offset = llrint (next_tick) - transport_frame;
154
155                 DEBUG_TRACE (PBD::DEBUG::MidiClock,
156                              string_compose ("Transport: %1, last tick time: %2, next tick time: %3, offset: %4, cycle length: %5\n",
157                                              transport_frame, _last_tick, next_tick, next_tick_offset, _midi_port->nframes_this_cycle()
158                                      )
159                         );
160
161                 if (next_tick_offset >= _midi_port->nframes_this_cycle()) {
162                         break;
163                 }
164
165                 if (next_tick_offset >= 0) {
166                         send_midi_clock_event (next_tick_offset);
167                 }
168
169                 _last_tick = next_tick;
170         }
171 }
172
173 double MidiClockTicker::one_ppqn_in_frames (framepos_t transport_position)
174 {
175         const Tempo& current_tempo = _session->tempo_map().tempo_at (transport_position);
176         double frames_per_beat = current_tempo.frames_per_beat (_session->nominal_frame_rate());
177
178         double quarter_notes_per_beat = 4.0 / current_tempo.note_type();
179         double frames_per_quarter_note = frames_per_beat / quarter_notes_per_beat;
180
181         return frames_per_quarter_note / double (_ppqn);
182 }
183
184 void MidiClockTicker::send_midi_clock_event (pframes_t offset)
185 {
186         if (!_midi_port) {
187                 return;
188         }
189
190         DEBUG_TRACE (PBD::DEBUG::MidiClock, string_compose ("Tick with offset %1\n", offset));
191
192         static uint8_t _midi_clock_tick[1] = { MIDI_CMD_COMMON_CLOCK };
193         _midi_port->write (_midi_clock_tick, 1, offset);
194 }
195
196 void MidiClockTicker::send_start_event (pframes_t offset)
197 {
198         if (!_midi_port) {
199                 return;
200         }
201
202         static uint8_t _midi_clock_tick[1] = { MIDI_CMD_COMMON_START };
203         _midi_port->write (_midi_clock_tick, 1, offset);
204 }
205
206 void MidiClockTicker::send_continue_event (pframes_t offset)
207 {
208         if (!_midi_port) {
209                 return;
210         }
211
212         static uint8_t _midi_clock_tick[1] = { MIDI_CMD_COMMON_CONTINUE };
213         _midi_port->write (_midi_clock_tick, 1, offset);
214 }
215
216 void MidiClockTicker::send_stop_event (pframes_t offset)
217 {
218         if (!_midi_port) {
219                 return;
220         }
221
222         static uint8_t _midi_clock_tick[1] = { MIDI_CMD_COMMON_STOP };
223         _midi_port->write (_midi_clock_tick, 1, offset);
224 }
225
226
227