Files
skills/docs/media/audio/midi/src/message/encode.md
2026-05-29 08:20:56 +01:00

2.7 KiB

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

import { encodeMessage } from "@suaveplan/midi";

const bytes = encodeMessage({
  type: "noteOn",
  channel: 0,
  note: 60,
  velocity: 100,
});
// Uint8Array(3) [0x90, 60, 100]

Usage: Advanced

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.