Following the discussion on the communityboard:
https://community.vcvrack.com/t/dedicated-midi-clock-sync-module-idea-programmer-needed/6856/25?u=alasdairmoons
Would it be possible to include or set a TIMESTAMP to the midi::message for the special case of 0xF messages to improve/stabilize the Midi-Clock/BPM calculation in Rack v2.x.x.?
RtMidi provides a timestamp relative to the last MIDI message received. I think this is fine. But how do we then associate each message with an engine frame? Using floor(absoluteTime * sampleRate) has the problem of accumulating error and slewing towards the past or future.
The cause of this issue is that MIDI messages are processed as soon as the engine runs VCV MIDI-CV's process() method after the message is received by the RtMidi driver.
blockSize / sampleRate.blockSize / sampleRate (if we're near the audio buffer deadline) to 2 * blockSize / sampleRate (if we're well before the audio buffer deadline).0 (if near the deadline) to blockSize / sampleRate (if well before the deadline).As you can see, the maximum jitter is 2 * blockSize / sampleRate, but of course is usually blockSize / sampleRate since it's rare for the engine to be near the audio buffer deadline for one buffer and finish stepping well before the deadline for another buffer.
Right now a module (such as VCV MIDI-CV) has no knowledge of the block size or when the block was requested, so even if we have MIDI message timestamps, we couldn't do anything with the information. I propose adding double Engine::getStepTimestamp() and int Engine::getStepFrame() which returns the timestamp and engine frame number the last time Engine::step(int frames) was called. (This is a new Rack v2 method.)
VCV MIDI-CV can then compute message timestamp - engine timestamp to see how long we need to wait before consuming the MIDI message. When message timestamp - engine timestamp < (current frame - last step frame) / sampleRate, then we're ready to process.
The only problem is that RtMidi annoyingly gives the delta-timestamp instead of the timestamp. I suggest ignoring this value and computing the timestamp based on when the RtMidiCallback is called. Might not be perfect, but it has fewer problems than trying to accumulate delta-timestamp values.
Before I finalize the above solution proposal, I need to quantify the amount of jitter before and after implementing a possible fix. I'll use a stable MIDI hardware clock.
Most helpful comment
Problem
The cause of this issue is that MIDI messages are processed as soon as the engine runs VCV MIDI-CV's
process()method after the message is received by the RtMidi driver.blockSize / sampleRate.blockSize / sampleRate(if we're near the audio buffer deadline) to2 * blockSize / sampleRate(if we're well before the audio buffer deadline).0(if near the deadline) toblockSize / sampleRate(if well before the deadline).As you can see, the maximum jitter is
2 * blockSize / sampleRate, but of course is usuallyblockSize / sampleRatesince it's rare for the engine to be near the audio buffer deadline for one buffer and finish stepping well before the deadline for another buffer.Approach at a solution
Right now a module (such as VCV MIDI-CV) has no knowledge of the block size or when the block was requested, so even if we have MIDI message timestamps, we couldn't do anything with the information. I propose adding
double Engine::getStepTimestamp()andint Engine::getStepFrame()which returns the timestamp and engine frame number the last timeEngine::step(int frames)was called. (This is a new Rack v2 method.)VCV MIDI-CV can then compute
message timestamp - engine timestampto see how long we need to wait before consuming the MIDI message. Whenmessage timestamp - engine timestamp < (current frame - last step frame) / sampleRate, then we're ready to process.The only problem is that RtMidi annoyingly gives the delta-timestamp instead of the timestamp. I suggest ignoring this value and computing the timestamp based on when the
RtMidiCallbackis called. Might not be perfect, but it has fewer problems than trying to accumulate delta-timestamp values.Reproducing
Before I finalize the above solution proposal, I need to quantify the amount of jitter before and after implementing a possible fix. I'll use a stable MIDI hardware clock.