MIDI Running Status: Is the Missing Byte an Error?
A missing repeated MIDI status byte can be valid running status, rather than a damaged note. The parser reuses the preceding channel-message status when the next event supplies only data bytes. It must still read each event's time and data correctly, and update that saved status when the message type or channel changes.
The useful first question is whether you are inspecting raw file bytes or already decoded messages. Our four downloadable fixtures show the difference between an intentionally omitted repeated status, a channel change that needs a new status, and a genuinely missing first status. They test parsing, not audio quality or interface speed.
Same note events, fewer stored bytes
On October 27, 2026, we generated four original format-0 MIDI files with one track, 480 ticks per quarter note and a 500,000-microsecond tempo event. The first two contain key 60 on at tick 0, off at 120, key 64 on at 240 and off at 360. Both use channel 1 and note-on velocity 80; their release events use note-on velocity zero.
| Download | File bytes | Stored note-status bytes | Parsed result |
|---|---|---|---|
| explicit.mid | 49 | 4 | Four channel-1 events |
| running.mid | 46 | 1 | Same four events and ticks |
| channel-change.mid | 47 | 2 | First pair on channel 1; second on channel 2 |
| missing-first-status.mid | 45 | 0 | Rejected: no prior channel status |
The running-status version is three bytes shorter than the explicit version, but its decoded notes, channels, velocities and tick positions are identical. The script checks that equality after removing only its diagnostic “status stored” flag. This is a measured file-size result for this tiny example, not a general compression percentage.
The channel-change file adds an explicit 91 status before the second note. That changes the saved status from channel 1 to channel 2. Its following zero-velocity release reuses 91. The final file deliberately removes the initial 90 from the compressed version, leaving no channel status to inherit; the supplied parser rejects it. That last file is intentionally malformed and is for byte inspection, not a playback demonstration.
Read delta-time before looking for the status
Here are the note-event portions of the first two track chunks. The byte before each separator is the file event's delta-time, not a note number:
Explicit:
00 | 90 3C 50
78 | 90 3C 00
78 | 90 40 50
78 | 90 40 00
Running status:
00 | 90 3C 50
78 | 3C 00
78 | 40 50
78 | 40 00
Values are hexadecimal: 78 means 120 ticks, 3C means key 60, and 40 means key 64. Tempo, track headers and end-of-track bytes are omitted from this display. A raw live MIDI stream has different framing from a Standard MIDI File; do not feed the displayed delta-time bytes into a live-message decoder as though they were musical data.
Microsoft's running-status documentation confirms that Standard MIDI Files permit omitting subsequent status bytes when the status is unchanged. For this file-level example, “unchanged” includes the channel: 90 and 91 are different status bytes even though both represent note-on messages.
Why a MIDI monitor may show complete messages anyway
A monitor can operate after a layer has expanded running status. The same Microsoft documentation explains that Windows MIDI input drivers deliver complete messages after expansion. Therefore, seeing a status byte on every displayed message does not establish that the original file or wire representation stored one every time.
The JUCE MIDI message API also exposes a parsing constructor with a lastStatusByte parameter. That is useful documentation when identifying which part of an application deals with retained parsing context. We did not call JUCE, capture a Windows driver, or test a particular monitor in this experiment; our results come from the downloadable Python parser and the generated files.
What the experiment does not establish
Saving three bytes in a file does not establish an audible timing improvement. We did not send these messages over DIN, USB or a network, render sound, benchmark a driver, or import the files into a DAW. The ticks are unchanged by construction. A decoder that misreads the abbreviated form can create wrong events, but that is a parsing fault rather than evidence that running status inherently changes a melody.
Our parser supports only the generated note messages and their initial tempo/end meta events. It deliberately does not implement System Exclusive, interleaved real-time messages, arbitrary intervening meta events, general status cancellation rules, multiple tracks, or all channel-message lengths. Do not turn this small fixture parser into a general importer without handling those cases against the specification.
The MIDI Association's file overview describes timed MIDI data and track structures. Keep those file containers separate from the decoded messages. A parser must know which representation it is reading before it can decide that a missing status byte is valid or corrupt.
A practical check for a suspicious MIDI file
- Keep the original bytes. Use a copy for diagnosis so an editor's resave does not remove the representation you wanted to inspect.
- Confirm the boundary. Read the track chunk and delta-time, then decide whether the event starts with an explicit status or inherits a valid prior one.
- Retain the whole status. Message type and channel both matter. Do not carry channel 1 forward after an explicit channel-2 status arrives.
- Compare decoded events. Expand running status, then compare tick, channel, key, velocity and event meaning. Byte-for-byte inequality alone does not establish different notes.
- Reject missing context. If the data starts without a valid prior status, report it rather than guessing a channel or silently manufacturing a note.
If an apparent release disappears in your event list, also inspect zero-velocity note-ons and matched note-offs. If track number and channel are being confused, use the separate track/channel comparison. Those are related checks, but neither explains omitted status bytes by itself.
Reproduce the result
Save experiment.py in an empty folder and run python experiment.py with Python 3. It writes the four MIDI files, the full decoded traces and the comparison CSV. Assertions check byte lengths 49/46/47/45, identical semantic events for the first two, channels 1/1/2/2 for the channel-change fixture, and the expected rejection of the malformed fixture.
The output is reproducible without packages or an account. Passing these assertions validates this controlled comparison; it is not a claim that every MIDI file is valid, that every receiving device was tested, or that a MIDI export faithfully transcribed its source recording.