# message/encode ## Purpose `encodeMessage` converts a strongly-typed `MidiMessage` value into the byte sequence a MIDI 1.0 wire format expects. It is the canonical serialisation function for the package and is shared by the streaming dispatcher, the UMP bridge (for status-byte derivation), and any consumer writing to a `MidiPortLike`. The encoder validates every payload field at the type-boundary: channels must be 0..15, 7-bit data must be 0..127, 14-bit values (pitch bend, song position) must be 0..16383. Out-of-range values raise `MidiError("MIDI.INVALID_MESSAGE")` rather than silently masking bits, because silent masking is a leading cause of "the synth jumped an octave when I sent the wrong velocity" bugs. ## Features - Pure function returning a freshly allocated `Uint8Array`; no shared buffers, safe to call concurrently. - Validates channel, 7-bit, and 14-bit ranges; rejects non-integer values. - Exposes the `STATUS` byte-constant table for the decoder and UMP bridge. - SysEx framing automatically inserts the `F0` prefix and `F7` terminator. ## Usage: Basic ```ts import { encodeMessage } from "@suaveplan/midi"; const bytes = encodeMessage({ type: "noteOn", channel: 0, note: 60, velocity: 100, }); // Uint8Array(3) [0x90, 60, 100] ``` ## Usage: Advanced ```ts import { encodeMessage, STATUS } from "@suaveplan/midi"; // Compose a series of messages into a single buffer for batched send. const messages = [ { type: "controlChange" as const, channel: 0, controller: 7, value: 100 }, { type: "pitchBend" as const, channel: 0, value: 8192 }, { type: "noteOn" as const, channel: 0, note: 64, velocity: 127 }, ]; const chunks = messages.map(encodeMessage); const total = chunks.reduce((n, c) => n + c.length, 0); const out = new Uint8Array(total); let offset = 0; for (const c of chunks) { out.set(c, offset); offset += c.length; } port.send(out); expect(STATUS.NOTE_ON).toBe(0x90); ``` ## API Reference - `encodeMessage(msg: MidiMessage): Uint8Array` — returns the bytes for any of the 18 message variants. Throws `MidiError("MIDI.INVALID_MESSAGE")` on out-of-range fields. - `STATUS` — frozen object exposing every status-byte constant the encoder writes (`NOTE_OFF`, `NOTE_ON`, `POLY_PRESSURE`, ..., `SYSEX_START`, `SYSEX_END`, `CLOCK`, etc.). ## Implementation Notes The encoder uses `Uint8Array.of(...)` rather than `new Uint8Array(n)` + index assignments because `of` accepts the elements directly and lets V8/JSCore use the inline cache for small arrays. Field assertions are inlined helpers (`assertChannel`, `assert7`, `assert14`) to keep the branch density low — every variant pays at most three boundary checks plus the `Uint8Array.of` call. SysEx allocates `payload.length + 2` to leave room for the F0/F7 endpoints.