The x0xb0x MIDI sync timing issue
Posted: Tue Aug 06, 2019 3:48 pm
Hi,
After looking at the x0xb0x source code I believe the "timing fix" in most firmwares is incorrect. I didn't check MarOS but I understand a lot of the sync code was rewritten there, so perhaps it's fixed there.
Solutions that "bump" the MIDI clock count on receipt of MIDI start are just masking the issue, and when you do that, you will find on some devices the x0xb0x ends up "ahead".
The actual issue is that the MIDI clock message is handled directly in the MIDI serial interrupt, and MIDI start is not. MIDI start is put onto a queue which is read off by the general processing thread at a later time.
The MIDI interrupt has the following code:
So, in a perfect world, this is the order of events:
(clock is stopped, playing=false)
MIDI start received
MIDI start message is added to the midi_q queue
the general thread runs, and works off the midi_q queue, sets playing to true.
MIDI clock received
playing = true
MIDI clock gets counted
So what happens if we receive a MIDI start message immediately followed by a MIDI clock?
(clock is stopped, playing is false)
MIDI start received
MIDI start message is added to the midi_q queue
MIDI clock received
playing is false
MIDI clock dropped
general thread runs, works midi_q off, sets playing to true.
So in this case the first MIDI clock was dropped.
So, what to do about it? The solution is to process MIDI start and clock in the same thread. I'm pretty sure the interrupt can handle processing MIDI start and MIDI clock since on the general case it'll just be an extra "if" statement, but I'm not yet sure exactly how much code needs to be moved into there.
A simpler solution may be to move MIDI clock OUT of the interrupt but that'll introduce some amount of jitter and lag to the response of the x0xb0x and generated DIN sync.
A quick fix is to just always increment the midi clock variable, and only reset it to 0 when receiving a MIDI stop. That way MIDI start does not reset the clock counter to 0. However I know some devices (like elektron) send MIDI clock even when stopped, so then restarting the sequencer would not reset the MIDI counter to 0.
So, in summary, the fix needs to process MIDI clock serially (not enqueue some messages and act on others in the interrupt).
Cheers,
Tom
After looking at the x0xb0x source code I believe the "timing fix" in most firmwares is incorrect. I didn't check MarOS but I understand a lot of the sync code was rewritten there, so perhaps it's fixed there.
Solutions that "bump" the MIDI clock count on receipt of MIDI start are just masking the issue, and when you do that, you will find on some devices the x0xb0x ends up "ahead".
The actual issue is that the MIDI clock message is handled directly in the MIDI serial interrupt, and MIDI start is not. MIDI start is put onto a queue which is read off by the general processing thread at a later time.
The MIDI interrupt has the following code:
Code: Select all
SIGNAL(SIG_USART0_RECV) {
char c = UDR0;
if (c == MIDI_CLOCK) {
// raise dinsync clk immediately, and also sched. to drop clock
// (MIDISYNC -> DINSYNC conversion);
if (sync != DIN_SYNC) {
sbi(DINSYNC_PORT, DINSYNC_CLK); // rising edge on note start
dinsync_clock_timeout = 5; // in 5ms drop the edge, is this enough?
}
if (!playing)
return;
midisync_clocked++;
return;
}
// putstring("0x"); putnum_uh(c); putstring(" ");
midi_q[tail_idx++] = c; // place at end of q
tail_idx %= MIDI_Q_SIZE;
if (tail_idx == head_idx) {
// i.e. there are too many msgs in the q
// drop the oldest msg?
head_idx++;
head_idx %= MIDI_Q_SIZE;
}
}
(clock is stopped, playing=false)
MIDI start received
MIDI start message is added to the midi_q queue
the general thread runs, and works off the midi_q queue, sets playing to true.
MIDI clock received
playing = true
MIDI clock gets counted
So what happens if we receive a MIDI start message immediately followed by a MIDI clock?
(clock is stopped, playing is false)
MIDI start received
MIDI start message is added to the midi_q queue
MIDI clock received
playing is false
MIDI clock dropped
general thread runs, works midi_q off, sets playing to true.
So in this case the first MIDI clock was dropped.
So, what to do about it? The solution is to process MIDI start and clock in the same thread. I'm pretty sure the interrupt can handle processing MIDI start and MIDI clock since on the general case it'll just be an extra "if" statement, but I'm not yet sure exactly how much code needs to be moved into there.
A simpler solution may be to move MIDI clock OUT of the interrupt but that'll introduce some amount of jitter and lag to the response of the x0xb0x and generated DIN sync.
A quick fix is to just always increment the midi clock variable, and only reset it to 0 when receiving a MIDI stop. That way MIDI start does not reset the clock counter to 0. However I know some devices (like elektron) send MIDI clock even when stopped, so then restarting the sequencer would not reset the MIDI counter to 0.
So, in summary, the fix needs to process MIDI clock serially (not enqueue some messages and act on others in the interrupt).
Cheers,
Tom