first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,53 @@
# 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 via `drain`.
- Honours running status across multiple `push` calls; 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.
- `decodeStream` convenience wrapper for one-shot decoding of a finished buffer.
## Usage: Basic
```ts
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
```ts
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 RunningStatusDecoder`
- `push(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.