High-accuracy scheduling for MIDI only - so no audio interface, or process block callback?

Hi!

I’m working on writing a standalone MIDI clock source with Juce. No audio interface should be required since it’s pure MIDI.

The closest thing I know of that I can use is the highResolutionTimer, but that one “only” has millisecond accuracy, meaning that the 24 “pulses per quarter note”, if they’re scheduled with a callback, will have pretty low accuracy.

e.g, say I want a nice traditional bristolian 140 BPM:
(pseudocode)

singleBeatLength = 60000.0 / BPM;
int timerInterval = singleBeatLength / 24.0;
(…)
startTimer(timerInterval);
(…)
hiResTimerCallback {
_midiOutput->sendMessageNow(juce::MidiMessage::midiClock());
}

In the above, there’s a massive rounding error - the PPQN interval is 17,857, neither rounding to 17 nor 18 will give a good enough accuracy for MIDI clock, it’s either hitting 139, or 147 BPM.

I could compensate for the rounding by sending a “jittery” clock, but that’s not ideal.

I could require that an audio interface is set up, and use the processBlock to fill the midiBuffer with sample-accuracy, but I want to see if I can find a solution that doesn’t require an audio callback “just” for MIDI clock first if possible.

I’ll do some more reading on Juce’s MIDI API - in the meanwhile if you have any tips I’d love to know!

1 Like

Hm, replying to myself - I see I should be able to use sendBlockOfMessages, where the block takes a MidiBuffer containing messages that can take a double timestamp with sub-millisecond accuracy based on a sample-rate.

And without reading the MidiDevice class in detail, it seems to do the scheduling internally with higher accuracy, even without using an audio device / callback.

I’ll see if this works - but I’ll leave this post in any case, if you have any comments - and for “posterity” for that matter; I’ll post back if/when this works.

I’ve done something similar ages ago without JUCE. Typically the OS APIs do the precision scheduling, so you give them timestamps in samplerate accuracy and the drivers or whatever layers in between do the rest. That’s the only way it can be done reliably. So I’ll assume without looking the JUCE module just bridges to the respective macOS and Windows APIs.

Note though that with an actual MIDI wire, the highest accuracy you’ll get is 960µs anyway, if I’m not mistaken. That’s the time it takes to transmit one MIDI byte, and only if there’s not contention on the line, which means the clock is the only thing you’re sending. Anything else gets even more jittery.

Therefore, devices that sync to MIDI clock typically need to implement a PLL to smooth out the jitter anyway. Jitter is part of the game so to speak, and if the receiver is not botched, sending out a jittery clock is not a problem as long as it’s clean on average.

1 Like

That’s some very useful insight!

This is a good chance to learn a bit anyhow, so I’ll try to give it the least jittery signal possible.

I’m also reconsidering the internal handling of time in my software, sub-ms accuracy is a nice thing to have anyhow, even if it’s only for “control” signals rather than time-sensitive note on/offs.

I don’t think we need to/should think about old-school wire MIDI (restrictions) when implementing modern MIDI applications. USB MIDI capabilities on newer devices (I think) do not have the bandwidth limitations of classic MIDI. Which of course does not make the MIDI timing problems any easier. With the arrival of MIDI2.0 devices we will see the rise of bandwidth on MIDI data.

1 Like

Agreed! Meanwhile I’m using midi loopback while developing, and most of my hardware uses midi over usb, come to think of it.