102 halp_meta(name,
"Midi sync")
103 halp_meta(author,
"ossia team")
104 halp_meta(c_name,
"avnd_helpers_midisync")
105 halp_meta(manual_url,
"https://ossia.io/score-docs/processes/midi-sync.html")
106 halp_meta(uuid,
"aa7c1ae5-495e-436e-a079-e3f1a19861bb")
107 halp_meta(category,
"Midi")
108 halp_flag(process_exec);
110 ossia::exec_state_facade ossia_state;
111 std::atomic<ossia::net::midi::midi_protocol*> midi_out{};
112 std::atomic<MidiStartStopEvent> next_event_midiclock{};
113 std::atomic<MidiStartStopEvent> next_event_mtc{};
114 std::atomic<double> current_song_pos{};
118 halp::enum_t<MidiClockMode,
"MIDI Clock"> clock;
119 halp::enum_t<MidiStartStopMode,
"MIDI Start/Stop"> clock_startstop;
120 halp::enum_t<MidiTimeCodeMode,
"MIDI TimeCode"> mtc;
122 halp::spinbox_i32<
"MTC offset (s)", halp::irange{-128000, 128000, 0}> offset;
123 struct : halp::enum_t<MidiTimeCodeFrameRate,
"MTC rate">
127 std::string_view values[4] = {
"24",
"25",
"29.97",
"30"};
128 MidiTimeCodeFrameRate init{};
135 struct : halp::midi_bus<
"MIDI output">
137 ossia::net::node_base* ossia_node{};
147 uint32_t has_clock : 1 = 0;
148 uint32_t has_startstop : 1 = 0;
149 uint32_t has_mtc : 1 = 0;
151 uint32_t frame_rate : 2 = 0b10;
157 static_assert(
sizeof(
impl) == 8);
160 using tick = halp::tick_flicks;
161 std::thread clock_thread;
162 std::thread mtc_thread;
163 std::atomic_bool clock_thread_running =
true;
164 std::atomic_bool mtc_thread_running =
true;
166 std::atomic<uint64_t> current_state = 0;
168 template <
typename... T>
169 void send_midi(T... bytes)
170 requires(!(std::is_pointer_v<T> || ...))
172 if(
auto proto = midi_out.load())
173 proto->push_value(libremidi::message{
174 libremidi::midi_bytes{
static_cast<unsigned char>(bytes)...}, 0});
177 void send_midi(std::span<const uint8_t> bytes)
179 if(
auto proto = midi_out.load())
180 proto->push_value(libremidi::message{{std::begin(bytes), std::end(bytes)}, 0});
184 auto load_state() noexcept
186 auto u = this->current_state.load(std::memory_order_acquire);
187 auto state = std::bit_cast<storage::impl>(u);
188 if(state.tempo <= 0.)
194 static auto compute_time_between_ticks(storage::impl state)
noexcept
196 const double duration_of_quarter_note_in_seconds = 60. / state.tempo;
197 const std::chrono::nanoseconds time_between_ticks = std::chrono::nanoseconds(
198 int64_t(1e9 * duration_of_quarter_note_in_seconds / 24.));
199 return time_between_ticks;
202 void full_songpos_message(
double quarters)
209 double midi_beats = quarters * 4.;
211 uint64_t res = std::floor(midi_beats);
215 uint8_t message[3] = {0xF2, 0x00, 0x00};
216 message[1] = (res & 0b0011'1111'1000'0000) >> 7;
217 message[2] = (res & 0b0000'0000'0111'1111);
222 void full_mtc_message(storage::impl state)
224 static_assert(0b0000'0011 << 5 == 0b01100000);
225 const uint8_t h = state.h | (state.frame_rate << 5);
226 const uint8_t m = state.m;
227 const uint8_t s = state.s;
228 const uint8_t f = state.f;
230 const uint8_t bytes[10]{0xF0, 0x7F, 0x7F, 0x01, 0x00, h, m, s, f, 0xF7};
234 void current_mtc_message(
int& index, storage::impl state)
236 uint8_t bytes[2]{0xF1, 0};
249 bytes[1] = 0b0000'0000 | (0b1111 & state.f);
253 bytes[1] = 0b0001'0000 | (0b0001 & (state.f >> 4));
257 bytes[1] = 0b0010'0000 | (0b1111 & state.s);
261 bytes[1] = 0b0011'0000 | (0b0011 & (state.s >> 4));
265 bytes[1] = 0b0100'0000 | (0b1111 & state.m);
269 bytes[1] = 0b0101'0000 | (0b0011 & (state.m >> 4));
273 bytes[1] = 0b0110'0000 | (0b0011 & state.h);
277 bytes[1] = 0b0111'0000 | (state.frame_rate << 1) | (0b0001 & (state.h >> 4));
285 static constexpr auto from_mtc_framerate(uint32_t frame_rate)
308 clock_thread = std::thread{[
this] {
309 ossia::set_thread_name(
"ossia midi clock");
310 ossia::set_thread_pinned(ossia::thread_type::Midi, 0);
312 sleep_accurate precise_sleep;
314 std::chrono::steady_clock::time_point last_tick_sent{}, now{};
316 last_tick_sent = std::chrono::steady_clock::now();
317 now = last_tick_sent;
322 while(clock_thread_running.load(std::memory_order_acquire))
324 auto state = load_state();
325 auto msg_to_send = this->next_event_midiclock.exchange(MidiStartStopEvent::None);
326 if(state.has_startstop)
330 case MidiStartStopEvent::None:
332 case MidiStartStopEvent::Start:
333 full_songpos_message(0.);
338 last_tick_sent = std::chrono::steady_clock::now();
339 now = last_tick_sent;
342 case MidiStartStopEvent::Continue:
343 full_songpos_message(
344 this->current_song_pos.load(std::memory_order_acquire));
347 case MidiStartStopEvent::Stop:
348 full_songpos_message(0.);
356 auto time_between_ticks = compute_time_between_ticks(state);
357 auto elapsed_nsecs = std::chrono::duration_cast<std::chrono::nanoseconds>(
358 now - last_tick_sent);
360 if(elapsed_nsecs < time_between_ticks)
361 precise_sleep((time_between_ticks - elapsed_nsecs).count() / 1e9);
365 last_tick_sent = now;
369 std::this_thread::sleep_for(std::chrono::milliseconds(3));
375 mtc_thread = std::thread{[
this] {
376 ossia::set_thread_name(
"ossia midi mtc");
377 ossia::set_thread_pinned(ossia::thread_type::Midi, 0);
379 sleep_accurate precise_sleep;
381 std::chrono::steady_clock::time_point last_tick_sent{}, now{};
383 last_tick_sent = std::chrono::steady_clock::now();
384 now = last_tick_sent;
387 int current_index = 0;
388 if(state = load_state(); state.has_mtc)
389 current_mtc_message(current_index, state);
391 while(mtc_thread_running.load(std::memory_order_acquire))
393 auto new_state = load_state();
394 auto msg_to_send = this->next_event_mtc.exchange(MidiStartStopEvent::None);
395 if(state.has_startstop)
399 case MidiStartStopEvent::None:
401 case MidiStartStopEvent::Start:
402 full_mtc_message(make_state(state.tempo, 0.));
404 case MidiStartStopEvent::Continue:
405 full_mtc_message(state);
407 case MidiStartStopEvent::Stop:
408 full_mtc_message(make_state(state.tempo, 0.));
413 if(new_state.has_mtc)
417 if(current_index == 0)
420 double frame_rate = from_mtc_framerate(state.frame_rate);
424 auto time_between_ticks = std::chrono::nanoseconds(int64_t(1e9 / frame_rate));
425 auto elapsed_nsecs = std::chrono::duration_cast<std::chrono::nanoseconds>(
426 now - last_tick_sent);
428 if(elapsed_nsecs < time_between_ticks)
429 precise_sleep((time_between_ticks - elapsed_nsecs).count() / 1e9);
431 current_mtc_message(current_index, state);
433 last_tick_sent = now;
439 std::this_thread::sleep_for(std::chrono::milliseconds(3));
447 clock_thread_running.store(
false, std::memory_order_release);
448 mtc_thread_running.store(
false, std::memory_order_release);
455 next_event_midiclock.store(MidiStartStopEvent::Start, std::memory_order_release);
456 current_song_pos.store(0., std::memory_order_release);
461 if(inputs.clock_startstop == MidiStartStopMode::Enabled)
464 next_event_midiclock.store(MidiStartStopEvent::Stop, std::memory_order_release);
465 current_song_pos.store(0., std::memory_order_release);
470 auto u = this->current_state.load(std::memory_order_acquire);
471 auto state = std::bit_cast<storage::impl>(u);
472 state.has_clock =
false;
473 state.has_mtc =
false;
474 this->current_state.store(std::bit_cast<uint64_t>(state), std::memory_order_release);
479 next_event_midiclock.store(MidiStartStopEvent::Continue, std::memory_order_release);
481 auto u = this->current_state.load(std::memory_order_acquire);
482 auto state = std::bit_cast<storage::impl>(u);
483 state.has_clock = inputs.clock == MidiClockMode::Enabled;
484 state.has_mtc = inputs.mtc == MidiTimeCodeMode::Enabled;
485 this->current_state.store(std::bit_cast<uint64_t>(state), std::memory_order_release);
488 void transport(
auto time)
503 storage::impl make_state(
double tempo,
double total_seconds)
508 total_seconds += this->inputs.offset;
510 auto h = std::div((
long long)total_seconds, (
long long)3600).quot;
511 total_seconds -= h * 3600;
512 auto m = std::div((
long long)total_seconds, (
long long)60).quot;
513 total_seconds -= m * 60;
515 auto frames = std::modf(total_seconds, &s);
517 state.has_clock = inputs.clock == MidiClockMode::Enabled;
518 state.has_startstop = inputs.clock_startstop == MidiStartStopMode::Enabled;
519 state.has_mtc = inputs.mtc == MidiTimeCodeMode::Enabled;
521 =
static_cast<std::underlying_type_t<MidiTimeCodeFrameRate>
>(inputs.rate.value);
524 state.s = std::floor(s);
525 state.f = from_mtc_framerate(state.frame_rate) * frames;
531 void prepare(halp::setup s) { setup = s; }
532 void operator()(halp::tick_flicks tk)
537 if(outputs.midi.ossia_node)
539 auto& proto = outputs.midi.ossia_node->get_device().get_protocol();
540 if(
auto mp =
dynamic_cast<ossia::net::midi::midi_protocol*
>(&proto))
544 auto state = make_state(tk.tempo, tk.start_in_flicks / 705'600'000.);
548 this->current_state.store(std::bit_cast<uint64_t>(state), std::memory_order_release);
549 this->current_song_pos.store(tk.start_position_in_quarters);