Skip to content

Fix: MIDI parser — System Real-Time no longer corrupts running status#693

Open
sauloverissimo wants to merge 1 commit intoelectro-smith:masterfrom
sauloverissimo:fix/midi-parser-realtime
Open

Fix: MIDI parser — System Real-Time no longer corrupts running status#693
sauloverissimo wants to merge 1 commit intoelectro-smith:masterfrom
sauloverissimo:fix/midi-parser-realtime

Conversation

@sauloverissimo
Copy link

Summary

System Real-Time bytes (0xF8–0xFF) can legally appear anywhere in a MIDI byte stream — even between data bytes of a multi-byte message — and must not affect the parser's running status. The current parser handles them inside the ParserEmpty switch case, which means they first trigger a state reset (pstate_ = ParserEmpty) and overwrite incoming_message_ fields. This causes:

  1. Channel corruption — a Timing Clock (0xF8) between NoteOn bytes shifts the channel to 8 (since 0xF8 & 0x0F = 8)
  2. Lost messages — the in-progress multi-byte message is destroyed
  3. SysEx corruption — RT bytes inside SysEx are stored as data instead of being dispatched

This is the root cause of #666 and #665.

The fix

Move System Real-Time handling to the very top of Parse(), before any state modification:

if(byte >= 0xF8)
{
    if(event_out != nullptr)
    {
        MidiEvent rt_event;
        rt_event.type    = SystemRealTime;
        rt_event.channel = 0;
        rt_event.srt_type = static_cast<SystemRealTimeType>(byte & kSystemRealTimeMask);
        *event_out = rt_event;
    }
    return true;
}

Uses a stack-local MidiEvent so pstate_, incoming_message_, and running_status_ are completely untouched. The early return means RT bytes are transparent to the rest of the parser — exactly as the MIDI 1.0 spec requires.

Tests

Added 4 new unit tests covering the scenarios that the existing runningStatSysRealtime test does not:

Test What it covers
sysRealtimeMidMessage Clock bytes interleaved within a NoteOn (between status/data0/data1)
sysRealtimeDuringSysEx Clock byte inside a SysEx message — must not corrupt SysEx data
allSysRealtimeTypes All 8 RT types (0xF8–0xFF) parse correctly with channel=0
runningStatusPreservedAfterRealtime Running status NoteOn + RT + channel verification across multiple notes

All 154 tests pass (including the existing runningStatSysRealtime test).

Related

System Real-Time bytes (0xF8–0xFF) can legally appear anywhere in a MIDI
stream, even between data bytes of a multi-byte message. The parser was
handling them inside the state machine's ParserEmpty case, which meant
they first triggered a state reset and overwrote incoming_message_ fields
(channel, type). This caused channel corruption — e.g. a Timing Clock
(0xF8) between NoteOn bytes shifted the channel to 8 (0xF8 & 0x0F).

The fix intercepts RT bytes at the very top of Parse(), before any state
modification, using a stack-local MidiEvent. This leaves pstate_,
incoming_message_, and running_status_ completely untouched, which is
the correct behavior per the MIDI 1.0 specification.

This also fixes RT bytes inside SysEx — they were previously stored as
SysEx data instead of being dispatched as separate events.

Added 4 new unit tests:
- sysRealtimeMidMessage: clock bytes interleaved within a NoteOn
- sysRealtimeDuringSysEx: clock byte inside a SysEx message
- allSysRealtimeTypes: all 8 RT types parse with channel=0
- runningStatusPreservedAfterRealtime: running status + RT + channel check

All 154 tests pass (including the existing runningStatSysRealtime test).

Closes electro-smith#666
Closes electro-smith#665
@github-actions
Copy link

Test Results

154 tests  +4   154 ✅ +4   0s ⏱️ ±0s
  1 suites ±0     0 💤 ±0 
  1 files   ±0     0 ❌ ±0 

Results for commit 4ed3992. ± Comparison against base commit 9498417.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Midi In Clock not supported Midi NoteOn Velocity 0 wrong channel MIDI - Clock messages may interfere with running status implementation

1 participant