Files
suaveplan/packages/ui/react/react-midi/src/midi-contract/reducers.md
2026-05-27 23:47:53 +01:00

2.4 KiB

midi-contract/reducers

Purpose

Three small reducers that fold the four-CC NRPN, RPN, and 14-bit CC "dances" into single consolidated events. The Web MIDI runtime delivers each CC as an isolated message; a knob that maps to a 14-bit parameter therefore arrives as two CC messages (MSB then LSB), and an NRPN edit arrives as four (CC 99/98 for parameter MSB/LSB then CC 6/38 for value MSB/LSB). The reducers track per-channel partial state and emit one NRPNChange / RPNChange / CC14Change per completed dance.

This logic lives here so the useNRPN / useRPN / useCC14 hooks have a deterministic, pure target to wrap with useSyncExternalStore. Once @suaveplan/midi ships its canonical reducers, these classes SHALL be replaced one-for-one without behavioural change.

Features

  • NRPNReducer — CC 99/98 parameter MSB/LSB, CC 6/38 data MSB/LSB.
  • RPNReducer — CC 101/100 parameter MSB/LSB, CC 6/38 data MSB/LSB.
  • CC14Reducer — pairs CC 0-31 (MSB) with CC 32-63 (LSB).
  • Per-channel partial state — concurrent edits on different channels do not interleave.

Usage: Basic

import { NRPNReducer } from "@suaveplan/react-midi/midi-contract";

const reducer = new NRPNReducer();
const out = reducer.feed({
    type: "controlChange",
    channel: 0,
    controller: 99,
    value: 0x20,
});
// out === null (waiting for CC 98 / 6 / 38)

Usage: Advanced

import { CC14Reducer } from "@suaveplan/react-midi/midi-contract";

const r = new CC14Reducer();
// 14-bit "bank select" on channel 0:
const a = r.feed({ type: "controlChange", channel: 0, controller: 0, value: 1 });
const b = r.feed({ type: "controlChange", channel: 0, controller: 32, value: 64 });
// b === { type: "cc14Change", channel: 0, controller: 0, value: (1<<7)|64 }

API Reference

  • class NRPNReducer { feed(msg: ControlChangeMessage): NRPNChange | null }
  • class RPNReducer { feed(msg: ControlChangeMessage): RPNChange | null }
  • class CC14Reducer { feed(msg: ControlChangeMessage): CC14Change | null }

Implementation Notes

Reception of a fresh paramHigh (CC 99 / CC 101) resets all four partial slots for that channel — devices commonly start a fresh NRPN edit without an explicit "null param" sentinel, so the reducer treats a new paramHigh as the implicit "start of new dance" marker. The 14-bit CC reducer keys per (channel, baseController) so two simultaneous 14-bit edits on different channels do not corrupt each other's pending MSB.