2.9 KiB
dispatch/running-status
Purpose
RunningStatusDecoder is the streaming MIDI 1.0 decoder that honours running status — the wire convention where successive same-status messages omit redundant status bytes. The MIDI 1.0 spec mandates that consumers retain the last channel-voice status byte and apply it implicitly to following data-only frames; serial-MIDI hardware almost universally exploits this to save bandwidth.
This module also handles the orthogonal real-time interleaving rule: any byte in the range 0xF8..0xFF is a one-byte real-time message that can appear at any moment, even mid-frame, without disturbing the running status state. Implementing both rules correctly is the difference between a stable port adapter and one that drops one note in twenty.
Features
- Stateful streaming decoder — feed bytes incrementally via
push, drain complete messages viadrain. - Honours running status across multiple
pushcalls; partial data accumulates until a complete frame arrives. - Splits real-time messages out without affecting the running status pointer.
- Clears running status after every system-common message, matching the MIDI 1.0 spec.
decodeStreamconvenience wrapper for one-shot decoding of a finished buffer.
Usage: Basic
import { decodeStream } from "@suaveplan/midi";
const messages = decodeStream([0x91, 0x40, 0x64, 0x42, 0x55]);
// two noteOn events on channel 1: notes 64+100 and 66+85
Usage: Advanced
import { RunningStatusDecoder } from "@suaveplan/midi";
const decoder = new RunningStatusDecoder();
inputPort.onRawBytes((chunk) => {
decoder.push(chunk);
for (const msg of decoder.drain()) {
handleMessage(msg);
}
});
// When the port disconnects and reconnects, reset the implicit state.
inputPort.onDisconnect(() => decoder.reset());
API Reference
class RunningStatusDecoderpush(bytes: Uint8Array | readonly number[]): void— append bytes to the internal queue.drain(): MidiMessage[]— return every complete message available; partial frames remain queued for the next call.reset(): void— clear the buffer and running-status pointer (use on port reconnect).
decodeStream(bytes: Uint8Array | readonly number[]): MidiMessage[]— convenience for one-shot decoding when the entire stream is known up-front.
Implementation Notes
The decoder uses a number[] buffer rather than a Uint8Array because it needs shift / splice semantics that Uint8Array does not provide; the alternative is a ring buffer with explicit head/tail pointers, but for typical port chunk sizes (≤ 64 bytes) the array form is faster and far less code. SysEx accumulates until the F7 terminator is found; if the terminator never arrives the messages stay queued and drain returns the messages decoded so far, with the partial SysEx still pending — callers can reset to drop it on a port reset.