2.5 KiB
dispatch/cc14
Purpose
CC14Reducer accumulates paired MIDI 1.0 control-change messages into 14-bit values. The wire convention is: CCs 0..31 carry the most-significant byte of a controller, CCs 32..63 carry the least-significant byte of the same logical controller (lsbController = msbController + 32). Sending MSB then LSB lets the receiver follow value changes smoothly across a 14-bit range — far more useful than the 7-bit single-CC form for parameters like volume, expression, and pan.
The reducer maintains per-channel, per-controller state and emits one CC14ChangeEvent on every LSB arrival, regardless of whether the MSB has been seen yet (controllers that send LSB-only updates simply use the last seen MSB, or zero on the first message).
Features
- Per-channel, per-MSB-controller partial state.
- Emits a consolidated
CC14ChangeEventon every CC 32..63 arrival. - Ignores controllers outside 0..63 — they belong to NRPN/RPN dispatch or are 7-bit-only.
reset()to clear state on port reset.
Usage: Basic
import { CC14Reducer } from "@suaveplan/midi";
const r = new CC14Reducer();
r.consume({ type: "controlChange", channel: 0, controller: 7, value: 0x40 }); // MSB
const evt = r.consume({ type: "controlChange", channel: 0, controller: 39, value: 0x05 }); // LSB
// evt = { type: "cc14Change", channel: 0, controller: 7, value: (0x40 << 7) | 0x05 }
Usage: Advanced
import { CC14Reducer } from "@suaveplan/midi";
const reducer = new CC14Reducer();
// Combine with the running-status decoder to extract smooth volume curves.
decoder.onMessage((m) => {
if (m.type !== "controlChange") return;
const evt = reducer.consume(m);
if (evt && evt.controller === 7) {
mixer.setVolume(evt.channel, evt.value / 16383);
}
});
API Reference
class CC14Reducerconsume(msg: ControlChangeMessage): CC14ChangeEvent | undefinedreset(): void
CC14ChangeEvent { type: "cc14Change"; channel; controller; value }—controlleris the MSB CC number (0..31).
Implementation Notes
State is keyed by a composite ${channel}:${msbController} string so a single Map covers all 16 channels × 32 controllers without nested maps. The reducer treats an LSB without a preceding MSB as msb = 0, matching the behaviour of every consumer-grade synth tested against the spec. Out-of-range controllers (≥ 64) are silently ignored rather than throwing — they have legitimate uses (sustain pedal, sostenuto, NRPN parameter MSB) that this reducer should not interfere with.