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,63 @@
# ump/ump
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## 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
```ts
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
```ts
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.