22 constexpr double factors[] = {1., 0.25, 1. / 3., 0.5, 2., 3., 4.};
23 return (index >= 0 && index <
int(std::size(factors))) ? factors[index] : 1.;
29 return nudge > 0 ? 1.1 : nudge < 0 ? 0.9 : 1.;
32inline constexpr double min_tempo = 20.;
33inline constexpr double max_tempo = 300.;
41 static constexpr int max_taps = 32;
42 static constexpr int64_t timeout_ns = 3'000'000'000;
44 void tap(int64_t now)
noexcept
48 const int64_t since = now - last();
49 if(since > timeout_ns || since <= 0)
53 m_head = (m_head + 1) % max_taps;
55 m_count = std::min(m_count + 1, max_taps);
58 void reset()
noexcept { m_count = 0; }
60 int count()
const noexcept {
return m_count; }
61 int64_t last()
const noexcept {
return m_taps[m_head]; }
68 const int first = (m_head - (m_count - 1) + max_taps) % max_taps;
69 return double(m_taps[m_head] - m_taps[first]) / double(m_count - 1);
76 return iv > 0. ? std::clamp(60e9 / iv, min_tempo, max_tempo) : 0.;
80 std::array<int64_t, max_taps> m_taps{};
81 int m_head{max_taps - 1};
90 std::array<uint8_t, 10>
bytes{};
94enum class transport_command :
int
108 double offset_ms = 0.;
109 bool send_clock =
true;
110 bool send_transport =
true;
133 static constexpr int count_in_taps = 4;
134 static constexpr int64_t never = std::numeric_limits<int64_t>::max();
139 if(m_tapped_tempo > 0.)
140 return m_tapped_tempo;
141 return std::clamp(p.bpm, min_tempo, max_tempo);
146 const double rate = p.rate > 0. ? p.rate : 1.;
150 bool running() const noexcept {
return m_state == state::running; }
151 bool starting() const noexcept {
return m_state == state::starting; }
152 bool count_in_pending() const noexcept {
return m_starting_from_taps; }
157 const tap_tempo& taps() const noexcept {
return m_taps; }
164 case state::starting:
167 return m_last_tick + period_ns(p);
173 template <
typename Send>
174 void command(transport_command c, int64_t now,
const free_clock_params& p, Send&& send)
179 case transport_command::None:
181 case transport_command::Start:
182 m_state = state::starting;
183 m_start_is_continue =
false;
184 m_starting_from_taps =
false;
185 m_start_at = now + delay_ns(p);
187 case transport_command::Continue:
188 if(m_state == state::stopped)
190 m_state = state::starting;
191 m_start_is_continue =
true;
192 m_starting_from_taps =
false;
193 m_start_at = now + delay_ns(p);
196 case transport_command::Stop:
197 if(m_state != state::stopped)
199 m_state = state::stopped;
200 m_starting_from_taps =
false;
202 send(message{now, {0xFC}, 1});
209 void tap(int64_t now,
const free_clock_params& p)
noexcept
213 if(m_taps.count() >= 2)
214 m_tapped_tempo = m_taps.
tempo();
217 = m_state == state::stopped || (m_state == state::starting && m_starting_from_taps);
218 if(idle && m_taps.count() >= count_in_taps)
220 m_state = state::starting;
221 m_start_is_continue =
false;
222 m_starting_from_taps =
true;
223 m_start_at = m_taps.last() + int64_t(m_taps.
interval_ns()) + delay_ns(p);
228 template <
typename Send>
233 if(m_state == state::starting && now >= m_start_at)
235 const int64_t t = m_start_at;
238 m_clocks = m_start_is_continue ? (m_clocks + 5) / 6 * 6 : 0;
241 const int64_t pos = std::clamp<int64_t>(
song_position(), 0, 16383);
242 send(
message{t, {0xF2, uint8_t(pos & 0x7F), uint8_t((pos >> 7) & 0x7F)}, 3});
243 send(
message{t, {uint8_t(m_start_is_continue ? 0xFB : 0xFA)}, 1});
249 m_state = state::running;
250 m_starting_from_taps =
false;
253 if(m_state == state::running)
255 for(int64_t next = m_last_tick + period_ns(p); next <= now;
256 next = m_last_tick + period_ns(p))
259 send(
message{next, {0xF8}, 1});
274 static int64_t delay_ns(
const free_clock_params& p)
noexcept
276 return p.offset_ms > 0. ? int64_t(p.offset_ms * 1e6) : 0;
279 void update_params(
const free_clock_params& p)
noexcept
281 if(p.
bpm != m_bpm_control)
283 if(m_has_bpm_control)
285 m_bpm_control = p.
bpm;
286 m_has_bpm_control =
true;
289 const int64_t delay = delay_ns(p);
292 const int64_t delta = delay - m_delay;
294 if(m_state == state::starting)
296 else if(m_state == state::running)
297 m_last_tick += delta;
302 double m_tapped_tempo{};
303 double m_bpm_control{};
304 bool m_has_bpm_control{};
307 state m_state{state::stopped};
308 bool m_start_is_continue{};
309 bool m_starting_from_taps{};
310 int64_t m_start_at{};
311 int64_t m_last_tick{};
335 double last_clock_time = 0.0;
336 uint32_t clock_tick_count = 0;
337 double previous_offset = 0.0;
351 int64_t
locate(
double time,
double quarters,
double tempo,
double rate)
noexcept
353 const double sixteenths_per_quarter = 4.0 * rate;
354 const double position = std::ceil(quarters * sixteenths_per_quarter - 1e-9);
355 const double ahead = position / sixteenths_per_quarter - quarters;
356 last_clock_time = time + ahead * 60.0 / tempo;
357 previous_offset = 0.0;
359 return int64_t(position);
363 template <
typename Emit>
365 double current_time,
double frame_duration, int64_t frames,
double tempo,
366 double factor,
double offset, Emit&& emit)
368 const double seconds_per_tick = 60.0 / (tempo * 24.0 * factor);
370 const double buffer_start_time = current_time - offset;
371 const double buffer_end_time = buffer_start_time + frame_duration;
372 double gap_start_time = buffer_start_time;
375 if(offset < previous_offset)
376 gap_start_time -= previous_offset - offset;
377 previous_offset = offset;
380 double next_tick_time = last_clock_time + seconds_per_tick;
383 next_tick_time = std::max(last_clock_time, gap_start_time);
385 while(next_tick_time < buffer_end_time)
388 if(next_tick_time >= buffer_start_time)
390 int64_t sample_offset
391 = ((next_tick_time - buffer_start_time) / frame_duration) * frames;
392 sample_offset = std::clamp<int64_t>(sample_offset, 0, frames - 1);
397 else if(next_tick_time >= gap_start_time)
403 last_clock_time = next_tick_time;
404 next_tick_time += seconds_per_tick;
Definition MIDIClockEngine.hpp:18
constexpr double nudge_factor(int nudge) noexcept
Tempo factor while a nudge button is held: -1 is down, +1 is up.
Definition MIDIClockEngine.hpp:27
constexpr double clock_rate_factor(int index) noexcept
Clock rate factors, in the order of the "Clock rate" control's values.
Definition MIDIClockEngine.hpp:20
Definition MIDIClockEngine.hpp:103
double bpm
The BPM control. A tapped tempo replaces it until it changes.
Definition MIDIClockEngine.hpp:105
Definition MIDIClockEngine.hpp:132
void process(int64_t now, const free_clock_params &p, Send &&send)
Sends what is due at or before now.
Definition MIDIClockEngine.hpp:229
int64_t next_deadline(const free_clock_params &p) const noexcept
When process() next has something to send.
Definition MIDIClockEngine.hpp:160
int64_t song_position() const noexcept
Position in MIDI beats (16th notes) of the receiver: 6 clocks each.
Definition MIDIClockEngine.hpp:155
double tempo(const free_clock_params &p) const noexcept
The tempo before nudge and rate: the tapped one, or the BPM control.
Definition MIDIClockEngine.hpp:137
Definition MIDIClockEngine.hpp:86
int64_t at
Time at which the message is due, in the caller's clock.
Definition MIDIClockEngine.hpp:88
std::array< uint8_t, 10 > bytes
Large enough for a full MTC frame.
Definition MIDIClockEngine.hpp:90
Definition MIDIClockEngine.hpp:40
double tempo() const noexcept
Tempo of the series in BPM, 0 with less than 2 taps.
Definition MIDIClockEngine.hpp:73
double interval_ns() const noexcept
Average interval between the taps of the series, 0 with less than 2 taps.
Definition MIDIClockEngine.hpp:64
Definition MIDIClockEngine.hpp:334
bool clock_at_locate
The clock at last_clock_time is still to send.
Definition MIDIClockEngine.hpp:339
int64_t locate(double time, double quarters, double tempo, double rate) noexcept
Definition MIDIClockEngine.hpp:351
void process(double current_time, double frame_duration, int64_t frames, double tempo, double factor, double offset, Emit &&emit)
factor: nudge times clock rate; offset in seconds.
Definition MIDIClockEngine.hpp:364