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

2.9 KiB

ump/ump

(no submodules)

Purpose

MIDI 2.0 introduces the Universal MIDI Packet (UMP) format — fixed-width 32, 64, 96, or 128 bit packets that carry MIDI messages alongside high-resolution per-note controllers, per-note pitch, and 32-bit velocity. This module exposes the four packet types, the message-type nibble constants, and a lossless bridge between MIDI 1.0 messages and their UMP equivalents.

The bridge is deliberately one-way for any MIDI 2.0 feature that exceeds MIDI 1.0's expressivity: a MIDI 2.0 channel-voice packet with 32-bit velocity cannot be downgraded to MIDI 1.0 without losing precision, so umpToMidi1 rejects it with MidiError("MIDI.UNSUPPORTED_UMP_VARIANT") rather than silently truncating. Engines that want to "best-effort downgrade" wrap this bridge with their own approximation policy.

Features

  • UMP32 / UMP64 / UMP96 / UMP128 shape types — each carries a Uint32Array of words.
  • midi1ToUmp lifts any MIDI 1.0 MidiMessage to its UMP equivalent.
  • umpToMidi1 reverses the lift when lossless; throws for MIDI 2.0-only features.
  • Short SysEx (≤ 6 bytes) bridges through a single UMP64 data-64 packet.
  • UMP_MESSAGE_TYPE constant exposed for downstream dispatchers.

Usage: Basic

import { midi1ToUmp, umpToMidi1 } from "@suaveplan/midi";

const packet = midi1ToUmp({
  type: "noteOn",
  channel: 0,
  note: 60,
  velocity: 100,
});
const back = umpToMidi1(packet);
// back is identical to the input.

Usage: Advanced

import { midi1ToUmp, type UmpPacket } from "@suaveplan/midi";

// Lift an entire stream onto group 3.
function liftToGroup(stream: MidiMessage[], group: number): UmpPacket[] {
  return stream.map((m) => midi1ToUmp(m, group));
}

// Filter packets by message-type nibble.
function isChannelVoice(packet: UmpPacket): boolean {
  const mt = (packet.words[0]! >>> 28) & 0xf;
  return mt === 0x2 || mt === 0x4;
}

API Reference

  • UMP32 / UMP64 / UMP96 / UMP128 — discriminated shape types; words is Uint32Array of length 1/2/3/4.
  • UmpPacket = UMP32 | UMP64 | UMP96 | UMP128.
  • UMP_MESSAGE_TYPE — frozen object exposing the MT nibble constants.
  • midi1ToUmp(msg: MidiMessage, group?: number): UmpPacket — defaults group to 0.
  • umpToMidi1(packet: UmpPacket): MidiMessage — throws MidiError("MIDI.UNSUPPORTED_UMP_VARIANT") for MIDI 2.0-only packets.

Implementation Notes

Channel-voice packets pack the full status byte (statusHigh | channel) into bits 16..23 of word 0; the encoder builds the byte first and shifts it as a unit rather than ORing the two nibbles separately at different shift counts (which was an earlier source of mis-encoding on channel != 0). The SysEx bridge supports only the single-packet "complete" form (status nibble 0) because multi-packet sysex-7 / sysex-8 streams require an explicit codec with start/continue/end framing — that codec lives in a dedicated downstream package.