Initial Commit

This commit is contained in:
2026-05-27 23:47:53 +01:00
parent fbc70f96e6
commit 9b28db724f
18103 changed files with 1600554 additions and 0 deletions

View File

@@ -0,0 +1,67 @@
# `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
```ts
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
```ts
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.