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

2.5 KiB
Raw Blame History

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 CC14ChangeEvent on 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 CC14Reducer
    • consume(msg: ControlChangeMessage): CC14ChangeEvent | undefined
    • reset(): void
  • CC14ChangeEvent { type: "cc14Change"; channel; controller; value } — controller is 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.