Loading...
Searching...
No Matches
MIDISync.hpp
1#pragma once
2
3/* SPDX-License-Identifier: GPL-3.0-or-later */
4
5#include <ossia/dataflow/exec_state_facade.hpp>
6#include <ossia/detail/lockfree_queue.hpp>
7#include <ossia/detail/thread.hpp>
8#include <ossia/network/base/device.hpp>
9#include <ossia/network/base/protocol.hpp>
10#include <ossia/protocols/midi/midi_protocol.hpp>
11#include <ossia/protocols/midi/midi_stream.hpp>
12
13#include <halp/audio.hpp>
14#include <halp/controls.hpp>
15#include <halp/file_port.hpp>
16#include <halp/layout.hpp>
17#include <halp/meta.hpp>
18#include <halp/midi.hpp>
19#include <halp/midifile_port.hpp>
20#include <libremidi/message.hpp>
21
22#include <AvndProcesses/MIDIClockEngine.hpp>
23
24#include <algorithm>
25#include <array>
26#include <chrono>
27#include <cmath>
28#include <initializer_list>
29#include <span>
30#include <thread>
31
32namespace mtk
33{
34enum class MidiClockMode
35{
36 Disabled,
37 Enabled
38};
39enum class MidiStartStopMode
40{
41 Disabled,
42 Enabled
43};
44enum class MidiTimeCodeMode
45{
46 Disabled,
47 Enabled
48};
49enum class MidiTimeCodeFrameRate
50{
51 SMPTE_24 = 0b00,
52 SMPTE_25 = 0b01,
53 SMPTE_2997 = 0b10,
54 SMPTE_30 = 0b11
55};
56
57enum class MidiClockSource
58{
59 Score,
60 Free
61};
62
63enum class MidiClockRate
64{
65 x1,
66 quarter,
67 third,
68 half,
69 x2,
70 x3,
71 x4
72};
73
74// https://blat-blatnik.github.io/computerBear/making-accurate-sleep-function/
76{
77 double estimate = 5e-3;
78 double mean = 5e-3;
79 double m2 = 0;
80 int64_t count = 1;
81
82 void operator()(double seconds)
83 {
84 using namespace std;
85 using namespace std::chrono;
86
87 while(seconds > estimate)
88 {
89 auto start = high_resolution_clock::now();
90 this_thread::sleep_for(milliseconds(1));
91 auto end = high_resolution_clock::now();
92
93 double observed = (end - start).count() / 1e9;
94 seconds -= observed;
95
96 ++count;
97 double delta = observed - mean;
98 mean += delta / count;
99 m2 += delta * (observed - mean);
100 double stddev = std::sqrt(m2 / (count - 1));
101 estimate = mean + stddev;
102
103 // FIXME that's missing a rolling behaviour to be more
104 // precise for semi-large timescales, e.g.
105 // unplugging a laptop and powersave changing frequency
106 }
107
108 // spin lock
109 auto start = high_resolution_clock::now();
110 while((high_resolution_clock::now() - start).count() / 1e9 < seconds)
111 ;
112 }
113};
114
119{
120 halp_meta(name, "MIDI Sync Out")
121 halp_meta(author, "ossia team")
122 halp_meta(c_name, "avnd_helpers_midisync")
123 halp_meta(manual_url, "https://ossia.io/score-docs/processes/midi-sync.html")
124 halp_meta(uuid, "aa7c1ae5-495e-436e-a079-e3f1a19861bb")
125 halp_meta(category, "Timing/Midi")
126 halp_meta(description, "Send MIDI Clock, transport messages and MIDI Time Code synchronized to score playback.")
127 halp_flag(process_exec);
128
129 ossia::exec_state_facade ossia_state;
137 ossia::net::midi::midi_stream* midi_out{};
138
139 struct ins
140 {
141 halp::enum_t<MidiClockMode, "MIDI Clock"> clock;
142 halp::enum_t<MidiStartStopMode, "MIDI Start/Stop"> clock_startstop;
143 halp::enum_t<MidiTimeCodeMode, "MIDI TimeCode"> mtc;
144 //halp::spinbox_i32<"Channel", halp::irange{1, 16, 1}> channel;
145 halp::spinbox_i32<"MTC offset (s)", halp::irange{-128000, 128000, 0}> offset;
146 struct : halp::enum_t<MidiTimeCodeFrameRate, "MTC rate">
147 {
148 struct range
149 {
150 std::string_view values[4] = {"24", "25", "29.97", "30"};
151 MidiTimeCodeFrameRate init{};
152 };
153 } rate;
154
155 struct : halp::enum_t<MidiClockSource, "Clock source">
156 {
157 halp_meta(
158 description,
159 "Score: clock and transport follow score playback. Free: the clock runs at "
160 "its own tempo, driven by Start, Stop, Continue and Tap")
161 struct range
162 {
163 std::string_view values[2] = {"Score", "Free"};
164 MidiClockSource init{};
165 };
166 } source;
167
168 struct : halp::spinbox_f32<"BPM", halp::range{20., 300., 120.}>
169 {
170 halp_meta(description, "Tempo of the free clock, until a tempo is tapped")
171 } bpm;
172
173 struct : halp::impulse_button<"Start">
174 {
175 halp_meta(description, "Free clock: send Start and clock from the beginning")
176 } start_button;
177
178 struct : halp::impulse_button<"Stop">
179 {
180 halp_meta(description, "Free clock: send Stop and stop the clock")
181 } stop_button;
182
183 struct : halp::impulse_button<"Continue">
184 {
185 halp_meta(description, "Free clock: send Continue and clock from where it stopped")
186 } continue_button;
187
188 struct : halp::impulse_button<"Tap">
189 {
190 halp_meta(
191 description,
192 "Free clock: tap the tempo (average of the last 32 taps, a pause of 3 s "
193 "starts over). When stopped, 4 taps start the clock on the next beat")
194 } tap;
195
196 struct : halp::maintained_button<"Nudge down">
197 {
198 halp_meta(description, "Clock 10% slower while held, to move its phase back")
199 } nudge_down;
200
201 struct : halp::maintained_button<"Nudge up">
202 {
203 halp_meta(description, "Clock 10% faster while held, to move its phase forward")
204 } nudge_up;
205
206 struct : halp::enum_t<MidiClockRate, "Clock rate">
207 {
208 halp_meta(description, "Multiplies the number of clocks sent per beat")
209 struct range
210 {
211 std::string_view values[7] = {"x1", "x1/4", "x1/3", "x1/2", "x2", "x3", "x4"};
212 MidiClockRate init{};
213 };
214 } clock_rate;
215
216 struct : halp::spinbox_f32<"Clock offset (ms)", halp::range{-1000., 1000., 0.}>
217 {
218 halp_meta(
219 description,
220 "Positive values delay the clock and the transport messages. Negative values "
221 "send the score clock early by extrapolating the current tempo, on the output "
222 "port or to a MIDI device")
223 } clock_offset;
224 } inputs;
225
226 struct
227 {
228 struct : halp::midi_bus<"MIDI output">
229 {
230 ossia::net::node_base* ossia_node{};
231 } midi;
232
233 struct : halp::val_port<"Tempo", float>
234 {
235 halp_meta(description, "Tempo of the clock before nudge and rate")
236 } tempo;
237 } outputs;
238
239 struct ui
240 {
241 halp_meta(name, "MIDI Sync Out")
242 halp_meta(layout, halp::layouts::hbox)
243 halp_meta(background, halp::colors::background_dark)
244
245 struct
246 {
247 halp_meta(name, "Send")
248 halp_meta(layout, halp::layouts::section)
249 halp_meta(background, halp::colors::background_mid)
250 halp::item<&ins::clock> clock;
251 halp::item<&ins::clock_startstop> clock_startstop;
252 halp::item<&ins::mtc> mtc;
253 halp::item<&ins::offset> offset;
254 halp::item<&ins::rate> rate;
255 } send;
256
257 struct
258 {
259 halp_meta(name, "Clock")
260 halp_meta(layout, halp::layouts::section)
261 halp_meta(background, halp::colors::background_mid)
262 halp::item<&ins::source> source;
263 halp::item<&ins::clock_rate, halp::look{.widget = halp::control_widget::combo}>
264 clock_rate;
265 halp::item<&ins::clock_offset> clock_offset;
266 } clock;
267
268 struct
269 {
270 halp_meta(name, "Free clock")
271 halp_meta(layout, halp::layouts::section)
272 halp_meta(background, halp::colors::background_mid)
273 static constexpr auto condition()
274 {
275 return halp::enabled_when<&ins::source, MidiClockSource::Free>{};
276 }
277 halp::item<&ins::bpm> bpm;
278 struct
279 {
280 halp_meta(layout, halp::layouts::hbox)
281 halp::item<&ins::start_button> start;
282 halp::item<&ins::stop_button> stop;
283 halp::item<&ins::continue_button> cont;
284 } transport;
285 struct
286 {
287 halp_meta(layout, halp::layouts::hbox)
288 halp::item<&ins::tap> tap;
289 halp::item<&ins::nudge_down> nudge_down;
290 halp::item<&ins::nudge_up> nudge_up;
291 } tempo;
292 } free_clock;
293 };
294
295 // Free clock: written by operator(), read by the clock thread.
296 std::atomic_bool free_mode{false};
297 std::atomic_bool free_send_clock{false};
298 std::atomic_bool free_send_transport{false};
299 std::atomic<float> free_bpm{120.f};
300 std::atomic<float> clock_rate_factor{1.f};
301 std::atomic<int> nudge{0};
302 std::atomic<float> clock_offset_ms{0.f};
303 std::atomic<midi_clock::transport_command> free_command{
304 midi_clock::transport_command::None};
306 std::atomic<int64_t> pending_tap{0};
308 std::atomic<float> free_tempo{120.f};
313 ossia::spsc_queue<midi_clock::message> to_output_port{1024};
314
317 {
319 ossia::net::midi::midi_stream* device{};
320 bool rebind{};
321 };
322
330 ossia::spsc_queue<device_message> to_device{4096};
331
333 struct mtc_time
334 {
335 uint8_t frame_rate = 0b10;
336 uint8_t h = 0;
337 uint8_t m = 0;
338 uint8_t s = 0;
339 uint8_t f = 0;
340 };
341
342 using tick = halp::tick_flicks;
343 std::thread clock_thread;
344 std::atomic_bool clock_thread_running = true;
345
347 std::atomic_bool start_requested{false};
348 std::atomic_bool stop_requested{false};
349 std::atomic_bool pause_requested{false};
350
351 static void send_midi(
352 ossia::net::midi::midi_stream* device, std::span<const uint8_t> bytes)
353 {
354 if(device)
355 device->push_value(libremidi::message{{std::begin(bytes), std::end(bytes)}, 0});
356 }
357
358 [[nodiscard]]
359 static constexpr auto from_mtc_framerate(uint32_t frame_rate)
360 {
361 switch(frame_rate)
362 {
363 case 0b00:
364 return 24.;
365 case 0b01:
366 return 25.;
367 case 0b10:
368 return 29.97;
369 case 0b11:
370 return 30.;
371 }
372 return 30.;
373 }
374
375 static int64_t steady_now_ns() noexcept
376 {
377 return std::chrono::duration_cast<std::chrono::nanoseconds>(
378 std::chrono::steady_clock::now().time_since_epoch())
379 .count();
380 }
381
382 midi_clock::free_clock_params load_free_params() const noexcept
383 {
384 return midi_clock::free_clock_params{
385 .bpm = free_bpm.load(std::memory_order_relaxed),
386 .rate = clock_rate_factor.load(std::memory_order_relaxed),
387 .nudge = nudge.load(std::memory_order_relaxed),
388 .offset_ms = clock_offset_ms.load(std::memory_order_relaxed),
389 .send_clock = free_send_clock.load(std::memory_order_relaxed),
390 .send_transport = free_send_transport.load(std::memory_order_relaxed)};
391 }
392
393 void send_free_clock_message(
394 ossia::net::midi::midi_stream* device, const midi_clock::message& m)
395 {
396 if(device)
397 send_midi(device, std::span<const uint8_t>(m.bytes.data(), m.size));
398 else
399 to_output_port.try_enqueue(m);
400 }
401
408 {
409 using namespace std::chrono;
410 ossia::set_thread_name("ossia midi clock");
411 ossia::set_thread_pinned(ossia::thread_type::Midi, 0);
412
413 sleep_accurate precise_sleep;
414 midi_clock::free_clock free_clock;
415 bool was_free = false;
416 device_message pending;
417 bool has_pending = false;
418 ossia::net::midi::midi_stream* device{};
419 auto send = [this, &device](const midi_clock::message& m) {
420 send_free_clock_message(device, m);
421 };
422
423 while(clock_thread_running.load(std::memory_order_acquire))
424 {
425 const auto p = load_free_params();
426 const int64_t now = steady_now_ns();
427 int64_t deadline = midi_clock::free_clock::never;
428 if(free_mode.load(std::memory_order_acquire))
429 {
430 was_free = true;
431 if(auto c = free_command.exchange(midi_clock::transport_command::None);
432 c != midi_clock::transport_command::None)
433 free_clock.command(c, now, p, send);
434 if(auto t = pending_tap.exchange(0); t != 0)
435 free_clock.tap(t, p);
436
437 free_clock.process(now, p, send);
438 free_tempo.store(free_clock.tempo(p), std::memory_order_relaxed);
439 deadline = free_clock.next_deadline(p);
440 }
441 else
442 {
443 if(was_free)
444 {
445 was_free = false;
446 free_clock.command(midi_clock::transport_command::Stop, now, p, send);
447 }
448 free_command.exchange(midi_clock::transport_command::None);
449 pending_tap.exchange(0);
450 }
451
452 for(;;)
453 {
454 if(!has_pending)
455 has_pending = to_device.try_dequeue(pending);
456 if(!has_pending)
457 break;
458 if(pending.rebind)
459 {
460 device = pending.device;
461 has_pending = false;
462 continue;
463 }
464 if(pending.msg.at > steady_now_ns())
465 {
466 deadline = std::min(deadline, pending.msg.at);
467 break;
468 }
469 send_midi(device, std::span<const uint8_t>(pending.msg.bytes.data(), pending.msg.size));
470 has_pending = false;
471 }
472
473 // Sleep 1 ms at most, to see new commands and messages.
474 const int64_t wait = deadline - steady_now_ns();
475 if(wait > 3'000'000)
476 std::this_thread::sleep_for(milliseconds(1));
477 else if(wait > 0)
478 precise_sleep(wait / 1e9);
479 }
480 }
481
483 {
484 clock_thread = std::thread{[this] { clock_thread_main(); }};
485 }
486
487 ~MIDISyncOut()
488 {
489 clock_thread_running.store(false, std::memory_order_release);
490 clock_thread.join();
491 }
492
493 void start() { start_requested.store(true, std::memory_order_release); }
494 void stop() { stop_requested.store(true, std::memory_order_release); }
495 void pause() { pause_requested.store(true, std::memory_order_release); }
496 void resume() { }
497
498
499 [[nodiscard]]
500 mtc_time make_mtc_time(double total_seconds) const noexcept
501 {
502 mtc_time t;
503 t.frame_rate
504 = static_cast<std::underlying_type_t<MidiTimeCodeFrameRate>>(inputs.rate.value)
505 & 0b11;
506
507 total_seconds += this->inputs.offset;
508 if(total_seconds < 0.)
509 total_seconds = 0.;
510
511 auto h = std::div((long long)total_seconds, (long long)3600).quot;
512 total_seconds -= h * 3600;
513 auto m = std::div((long long)total_seconds, (long long)60).quot;
514 total_seconds -= m * 60;
515 double s;
516 auto frames = std::modf(total_seconds, &s);
517
518 t.h = h % 24;
519 t.m = m % 60;
520 t.s = uint8_t(s);
521 t.f = std::min(uint8_t(from_mtc_framerate(t.frame_rate) * frames), uint8_t(29));
522 return t;
523 }
524
525 halp::setup setup;
526 void prepare(halp::setup s) { setup = s; }
527
528 void operator()(halp::tick_flicks tk)
529 {
530 if(setup.rate <= 0)
531 return;
532
533 bind_output();
535 begin_tick(tk.frames);
536 const bool is_free = inputs.source.value == MidiClockSource::Free;
537
538 if(start_requested.exchange(false, std::memory_order_acq_rel))
539 main_state.running = false;
540
541 // The executor ticks once more, with no frame, after stop().
542 const bool stopping = stop_requested.exchange(false, std::memory_order_acq_rel);
543 const bool pausing = pause_requested.exchange(false, std::memory_order_acq_rel);
544 if(stopping || pausing)
545 {
546 // No tick may come to send them later: on the port, what was delayed
547 // goes out now, then Stop.
548 flush_delayed();
549 if(main_state.running && !is_free
550 && inputs.clock_startstop == MidiStartStopMode::Enabled)
551 {
552 if(main_state.to_device)
553 emit_transport({0xFC});
554 else
555 emit(0, {0xFC});
556 }
557 main_state.running = false;
558 }
559 else
560 {
561 release_delayed(tk.frames);
562 }
563
564 if(tk.frames > 0)
565 process_timeline(tk, is_free);
566
567 if(tk.frames > 0)
568 main_state.out_frame += tk.frames;
569
570 outputs.tempo.value = free_mode.load(std::memory_order_relaxed)
571 ? free_tempo.load(std::memory_order_relaxed)
572 : float(tk.tempo);
573
574 // Free clock messages arrive when they are due: they go out at the start
575 // of the tick, one tick of latency at most. Older ones were sent while
576 // the process was not ticked, and are dropped.
577 const int64_t stale_before
578 = steady_now_ns() - 2 * main_state.latency_ns - 2'000'000;
579 midi_clock::message m;
580 while(to_output_port.try_dequeue(m))
581 {
582 if(m.at < stale_before)
583 continue;
584 halp::midi_msg msg;
585 msg.bytes.assign(m.bytes.begin(), m.bytes.begin() + m.size);
586 msg.timestamp = 0;
587 outputs.midi.push_back(std::move(msg));
588 }
589 sort_output();
590
591 // No tick may come before the device is removed: the clock thread lets go
592 // of it once it has sent what this tick dated for it.
593 if(stopping || pausing)
594 set_device(nullptr);
595 }
596
599 void set_device(ossia::net::midi::midi_stream* stream) noexcept
600 {
601 if(stream == midi_out)
602 return;
604 d.device = stream;
605 d.rebind = true;
606 if(to_device.try_enqueue(d))
607 midi_out = stream;
608 }
609
611 void bind_output() noexcept
612 {
613 ossia::net::midi::midi_stream* stream{};
614 if(outputs.midi.ossia_node)
615 {
616 auto& proto = outputs.midi.ossia_node->get_device().get_protocol();
617 stream = dynamic_cast<ossia::net::midi::midi_stream*>(&proto);
618 }
619 set_device(stream);
620 }
621
624 {
625 const bool is_free = inputs.source.value == MidiClockSource::Free;
626 free_send_clock.store(inputs.clock == MidiClockMode::Enabled, std::memory_order_relaxed);
627 free_send_transport.store(
628 inputs.clock_startstop == MidiStartStopMode::Enabled, std::memory_order_relaxed);
629 free_bpm.store(inputs.bpm.value, std::memory_order_relaxed);
630 clock_rate_factor.store(
631 midi_clock::clock_rate_factor(int(inputs.clock_rate.value)),
632 std::memory_order_relaxed);
633 nudge.store(
634 int(inputs.nudge_up.value) - int(inputs.nudge_down.value),
635 std::memory_order_relaxed);
636 clock_offset_ms.store(inputs.clock_offset.value, std::memory_order_relaxed);
637 free_mode.store(is_free, std::memory_order_release);
638
639 if(!is_free)
640 return;
641
642 using enum midi_clock::transport_command;
643 if(inputs.stop_button)
644 free_command.store(Stop, std::memory_order_release);
645 else if(inputs.start_button)
646 free_command.store(Start, std::memory_order_release);
647 else if(inputs.continue_button)
648 free_command.store(Continue, std::memory_order_release);
649
650 if(inputs.tap)
651 {
652 const int64_t t = steady_now_ns();
653 pending_tap.store(t != 0 ? t : 1, std::memory_order_release);
654 }
655 }
656
664 void begin_tick(int frames) noexcept
665 {
666 const int64_t now = steady_now_ns();
667 const double ns_per_frame = 1e9 / setup.rate;
668 const int64_t buffer_ns = int64_t(std::max(setup.frames, frames) * ns_per_frame);
669 const int64_t drift = now - main_state.next_tick_ns;
670 if(main_state.next_tick_ns == 0 || std::abs(drift) > buffer_ns + 2'000'000)
671 main_state.tick_start_ns = now;
672 else
673 main_state.tick_start_ns = main_state.next_tick_ns + drift / 16;
674 main_state.ns_per_frame = ns_per_frame;
675 main_state.latency_ns = buffer_ns;
676 main_state.next_tick_ns
677 = main_state.tick_start_ns + int64_t(std::max(frames, 0) * ns_per_frame);
678 main_state.to_device = midi_out != nullptr;
679 main_state.tick_frames = std::max(frames, 0);
680 }
681
683 void emit(int64_t frame, std::initializer_list<uint8_t> bytes) noexcept
684 {
685 if(main_state.to_device)
686 {
688 auto& m = d.msg;
689 m.at = main_state.tick_start_ns + main_state.latency_ns
690 + int64_t(frame * main_state.ns_per_frame);
691 m.size = int(std::min(bytes.size(), m.bytes.size()));
692 std::copy_n(bytes.begin(), m.size, m.bytes.begin());
693 to_device.try_enqueue(d);
694 }
695 else
696 {
697 halp::midi_msg msg;
698 msg.bytes.assign(bytes.begin(), bytes.end());
699 msg.timestamp = frame;
700 outputs.midi.push_back(std::move(msg));
701 }
702 }
703
710 void emit_transport(std::initializer_list<uint8_t> bytes) noexcept
711 {
712 const double offset = inputs.clock_offset.value / 1000.;
713 const int64_t delay = offset > 0. ? int64_t(std::llround(offset * setup.rate)) : 0;
714 if(main_state.to_device || delay < main_state.tick_frames)
715 {
716 emit(delay, bytes);
717 return;
718 }
719
720 auto& d = main_state.delayed;
721 if(main_state.delayed_count == int(d.size()))
722 {
723 // Out of room: late rather than lost.
724 emit(std::max<int64_t>(main_state.tick_frames - 1, 0), bytes);
725 return;
726 }
727 auto& e = d[main_state.delayed_count++];
728 e.at = main_state.out_frame + delay;
729 e.size = int(std::min(bytes.size(), e.bytes.size()));
730 std::copy_n(bytes.begin(), e.size, e.bytes.begin());
731 }
732
733 void emit_message(int64_t frame, const midi_clock::message& m) noexcept
734 {
735 halp::midi_msg msg;
736 msg.bytes.assign(m.bytes.begin(), m.bytes.begin() + m.size);
737 msg.timestamp = frame;
738 outputs.midi.push_back(std::move(msg));
739 }
740
742 void release_delayed(int frames) noexcept
743 {
744 const int64_t end = main_state.out_frame + std::max(frames, 0);
745 int kept = 0;
746 for(int i = 0; i < main_state.delayed_count; i++)
747 {
748 auto& e = main_state.delayed[i];
749 if(e.at < end)
750 emit_message(std::max<int64_t>(e.at - main_state.out_frame, 0), e);
751 else
752 main_state.delayed[kept++] = e;
753 }
754 main_state.delayed_count = kept;
755 }
756
757 void flush_delayed() noexcept
758 {
759 for(int i = 0; i < main_state.delayed_count; i++)
760 emit_message(0, main_state.delayed[i]);
761 main_state.delayed_count = 0;
762 }
763
765 void sort_output() noexcept
766 {
767 auto& v = outputs.midi.midi_messages;
768 for(std::size_t i = 1; i < v.size(); i++)
769 for(std::size_t j = i; j > 0 && v[j - 1].timestamp > v[j].timestamp; j--)
770 std::swap(v[j - 1], v[j]);
771 }
772
773 void process_timeline(const halp::tick_flicks& tk, bool is_free)
774 {
775 const double current_time = tk.start_in_flicks / 705'600'000.0;
776 const double frame_duration
777 = std::abs(tk.end_in_flicks - tk.start_in_flicks) / 705'600'000.0;
778 const auto time = make_mtc_time(current_time);
779 const double factor
781 int(inputs.nudge_up.value) - int(inputs.nudge_down.value))
782 * midi_clock::clock_rate_factor(int(inputs.clock_rate.value));
783
784 // A tick that does not start where the previous one ended is a jump of
785 // the timeline: a seek, or a loop starting over.
786 const bool jumped
787 = main_state.running && tk.start_in_flicks != main_state.next_start_in_flicks;
788 if(!main_state.running || jumped)
789 locate(tk, time, current_time, factor, jumped, is_free);
790 main_state.running = true;
791 main_state.next_start_in_flicks = tk.end_in_flicks;
792
793 if(!is_free && inputs.clock == MidiClockMode::Enabled)
794 {
795 main_state.clock.process(
796 current_time, frame_duration, tk.frames, tk.tempo, factor,
797 inputs.clock_offset.value / 1000.,
798 [this](int64_t sample_offset) { emit(sample_offset, {0xF8}); });
799 }
800
801 if(inputs.mtc == MidiTimeCodeMode::Enabled)
802 send_quarter_frames(tk, time, current_time, frame_duration);
803 }
804
811 void locate(
812 const halp::tick_flicks& tk, const mtc_time& time, double current_time,
813 double factor, bool jumped, bool is_free)
814 {
815 const bool transport
816 = !is_free && inputs.clock_startstop == MidiStartStopMode::Enabled;
817 const bool clock = !is_free && inputs.clock == MidiClockMode::Enabled;
818 const double quarters = tk.start_position_in_quarters;
819 const bool at_start = std::abs(quarters) < 1e-9;
820
821 if(jumped && transport)
822 emit_transport({0xFC});
823
824 // The clock resumes on the receiver's 16th note that the Song Position
825 // names. Nudge is a passing change of tempo, not of the receiver's beat.
826 const int64_t beats = main_state.clock.locate(
827 current_time, quarters, tk.tempo,
828 midi_clock::clock_rate_factor(int(inputs.clock_rate.value)));
829
830 if(clock || transport)
831 {
832 // A MIDI beat is a 16th note, 14 bits: past 1024 bars there is no
833 // position to send.
834 if(!(transport && at_start) && beats >= 0 && beats < 16384)
835 emit_transport({0xF2, uint8_t(beats & 0x7F), uint8_t((beats >> 7) & 0x7F)});
836 if(transport)
837 emit_transport({uint8_t(at_start ? 0xFA : 0xFB)});
838 }
839
840 if(inputs.mtc == MidiTimeCodeMode::Enabled)
841 {
842 emit(
843 0, {0xF0, 0x7F, 0x7F, 0x01, 0x01, uint8_t(time.h | (time.frame_rate << 5)),
844 time.m, time.s, time.f, 0xF7});
845 }
846 const double fps = from_mtc_framerate(time.frame_rate);
847 main_state.mtc_quarter_frame_index = 0;
848 main_state.last_mtc_quarter_frame_time
849 = next_even_frame(current_time, fps) - 1.0 / (fps * 4.0);
850 }
851
857 double next_even_frame(double t, double fps) const noexcept
858 {
859 const double offset = this->inputs.offset;
860 const double u = t + offset;
861 const double second = std::floor(u);
862 // As make_mtc_time numbers them: from 0 in each second
863 const int per_second = int(std::ceil(fps - 1e-9));
864 int k = int(std::ceil((u - second) * fps - 1e-6));
865 if(k % 2 != 0)
866 k++;
867 const double frame = k < per_second ? second + k / fps : second + 1.;
868 return frame - offset;
869 }
870
877 const halp::tick_flicks& tk, const mtc_time& time, double current_time,
878 double frame_duration)
879 {
880 const double fps = from_mtc_framerate(time.frame_rate);
881 const double seconds_per_quarter_frame = 1.0 / (fps * 4.0);
882
883 double next_qf_time
884 = main_state.last_mtc_quarter_frame_time + seconds_per_quarter_frame;
885
886 while(next_qf_time < current_time + frame_duration)
887 {
888 // A piece due at the very start of the tick is not lost to rounding
889 if(next_qf_time >= current_time - 1e-9)
890 {
891 int64_t sample_offset
892 = ((next_qf_time - current_time) / frame_duration) * tk.frames;
893 sample_offset = std::clamp<int64_t>(sample_offset, 0, tk.frames - 1);
894
895 // Half a piece in, to stay clear of the frame boundary
896 if(main_state.mtc_quarter_frame_index == 0)
897 main_state.mtc_cycle
898 = make_mtc_time(next_qf_time + seconds_per_quarter_frame / 2.);
899 const mtc_time& cycle = main_state.mtc_cycle;
900
901 uint8_t qf_data = 0;
902 switch(main_state.mtc_quarter_frame_index)
903 {
904 case 0:
905 qf_data = 0x00 | (cycle.f & 0x0F);
906 break;
907 case 1:
908 qf_data = 0x10 | ((cycle.f >> 4) & 0x01);
909 break;
910 case 2:
911 qf_data = 0x20 | (cycle.s & 0x0F);
912 break;
913 case 3:
914 qf_data = 0x30 | ((cycle.s >> 4) & 0x03);
915 break;
916 case 4:
917 qf_data = 0x40 | (cycle.m & 0x0F);
918 break;
919 case 5:
920 qf_data = 0x50 | ((cycle.m >> 4) & 0x03);
921 break;
922 case 6:
923 qf_data = 0x60 | (cycle.h & 0x0F);
924 break;
925 case 7:
926 qf_data = 0x70 | (cycle.frame_rate << 1) | ((cycle.h >> 4) & 0x01);
927 break;
928 }
929
930 emit(sample_offset, {0xF1, qf_data});
931
932 main_state.mtc_quarter_frame_index
933 = (main_state.mtc_quarter_frame_index + 1) % 8;
934 }
935
936 main_state.last_mtc_quarter_frame_time = next_qf_time;
937 next_qf_time += seconds_per_quarter_frame;
938 }
939 }
940
943 {
945 double last_mtc_quarter_frame_time = 0.0;
946 int mtc_quarter_frame_index = 0;
948 mtc_time mtc_cycle{};
950 bool running = false;
951 int64_t next_start_in_flicks = 0;
952
954 int64_t out_frame = 0;
955 int tick_frames = 0;
956 std::array<midi_clock::message, 32> delayed{};
957 int delayed_count = 0;
958
959 bool to_device = false;
960 int64_t tick_start_ns = 0;
961 int64_t next_tick_ns = 0;
962 int64_t latency_ns = 0;
963 double ns_per_frame = 0.;
964 } main_state;
965};
966}
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
STL namespace.
State of the audio thread.
Definition MIDISync.hpp:943
int64_t out_frame
Frames of output since the object started, and in the current tick.
Definition MIDISync.hpp:954
bool running
Playing since the last start, stop or pause.
Definition MIDISync.hpp:950
A message for the device, or a change of the device itself.
Definition MIDISync.hpp:317
Definition MIDISync.hpp:140
Timecode fields of a timeline position.
Definition MIDISync.hpp:334
Definition MIDISync.hpp:240
Definition MIDISync.hpp:119
void release_delayed(int frames) noexcept
Sends the delayed transport messages due in this tick.
Definition MIDISync.hpp:742
void emit_transport(std::initializer_list< uint8_t > bytes) noexcept
Definition MIDISync.hpp:710
std::atomic< int64_t > pending_tap
steady_clock date of a tap not yet seen by the clock thread, or 0.
Definition MIDISync.hpp:306
void publish_clock_controls() noexcept
Hands the controls the clock thread reads over to it.
Definition MIDISync.hpp:623
void send_quarter_frames(const halp::tick_flicks &tk, const mtc_time &time, double current_time, double frame_duration)
Definition MIDISync.hpp:876
void set_device(ossia::net::midi::midi_stream *stream) noexcept
Definition MIDISync.hpp:599
void sort_output() noexcept
The port's messages in time order: delayed ones are added out of order.
Definition MIDISync.hpp:765
ossia::net::midi::midi_stream * midi_out
Definition MIDISync.hpp:137
void bind_output() noexcept
The MIDI device the output is bound to, if any.
Definition MIDISync.hpp:611
std::atomic< float > free_tempo
Written by the clock thread.
Definition MIDISync.hpp:308
void clock_thread_main()
Definition MIDISync.hpp:407
double next_even_frame(double t, double fps) const noexcept
Definition MIDISync.hpp:857
void locate(const halp::tick_flicks &tk, const mtc_time &time, double current_time, double factor, bool jumped, bool is_free)
Definition MIDISync.hpp:811
ossia::spsc_queue< device_message > to_device
Definition MIDISync.hpp:330
void begin_tick(int frames) noexcept
Definition MIDISync.hpp:664
std::atomic_bool start_requested
Set by start(), stop(), pause() and resume(); handled by the next tick.
Definition MIDISync.hpp:347
ossia::spsc_queue< midi_clock::message > to_output_port
Definition MIDISync.hpp:313
void emit(int64_t frame, std::initializer_list< uint8_t > bytes) noexcept
Sends a message at a frame of the tick, to the port or to the device.
Definition MIDISync.hpp:683
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
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
Definition MIDIClockEngine.hpp:334
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
Definition MIDISync.hpp:76
Definition MIDISync.hpp:149