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

52 lines
2.3 KiB
Markdown

# gm/level2
## Purpose
General MIDI Level 2 added a bank-select-driven variation layer on top of GM1. Each GM1 program (0..127) gains optional variation banks selected by Bank-Select LSB (CC 32). Bank 0 is always the GM1 default; banks 1..127 are reserved for GM2 variations. This module exposes the variation-name lookup so engines can render `(program, bankLsb)` pairs into a user-visible patch name.
The table here mirrors the GM2 v1.2 *mandatory* variation set. Full-coverage engines (hundreds of additional variations) layer their own tables on top via `Object.assign({}, GM_LEVEL2_VARIATIONS, …)` — keeping the base set small means importing this module costs negligible bundle size for consumers that only need the GM1 fallback path.
## Features
- `GM_LEVEL2_VARIATIONS` frozen map keyed by `program * 256 + bankLsb`.
- `gm2Key(program, bankLsb)` helper for composing the lookup key.
- Bank 0 entries are deliberately omitted; consumers fall back to `GM_LEVEL1_PATCHES`.
- Composable with downstream extension tables via the frozen-object idiom.
## Usage: Basic
```ts
import { GM_LEVEL2_VARIATIONS, gm2Key } from "@suaveplan/midi";
const name = GM_LEVEL2_VARIATIONS[gm2Key(4, 1)];
// "Detuned Electric Piano 1"
```
## Usage: Advanced
```ts
import {
GM_LEVEL1_PATCHES,
GM_LEVEL2_VARIATIONS,
gm2Key,
} from "@suaveplan/midi";
function resolvePatchName(program: number, bankLsb: number): string {
if (bankLsb === 0) return GM_LEVEL1_PATCHES[program] ?? `Program ${program}`;
return (
GM_LEVEL2_VARIATIONS[gm2Key(program, bankLsb)] ??
GM_LEVEL1_PATCHES[program] ??
`Program ${program}`
);
}
```
## API Reference
- `GM_LEVEL2_VARIATIONS: Readonly<Record<number, string>>` — frozen map keyed by `program * 256 + bankLsb`.
- `gm2Key(program: number, bankLsb: number): number` — pure function returning the composite lookup key.
## Implementation Notes
The composite key encoding (`program * 256 + bankLsb`) is preferred over a nested `Record<number, Record<number, string>>` because a flat key has constant-time lookup with no intermediate object allocation, and because both forms have the same `Object.freeze` cost. Variations are added selectively — full GM2 / GS / XG coverage is huge and varies by vendor, so consumers can choose which dialect tables to spread on top.