Initial Commit
This commit is contained in:
58
packages/media/audio/midi/src/dispatch/rpn.md
Normal file
58
packages/media/audio/midi/src/dispatch/rpn.md
Normal file
@@ -0,0 +1,58 @@
|
||||
# dispatch/rpn
|
||||
|
||||
## Purpose
|
||||
|
||||
`RPNReducer` is the sister of `NRPNReducer` for Registered Parameter Numbers — the parameter MSB and LSB CC numbers move from 99/98 to 101/100, but the rest of the four-CC dance is identical. RPN is the MIDI Manufacturers Association's way of standardising a handful of common parameters (pitch-bend range, fine/coarse tuning, modulation-depth range, MPE configuration) that every spec-compliant synth interprets identically.
|
||||
|
||||
Separating RPN from NRPN avoids cross-contamination: an `RPNReducer` will never accidentally interpret CC 99 as a parameter MSB, and vice versa. Consumers typically run both reducers in parallel on the same control-change stream.
|
||||
|
||||
## Features
|
||||
|
||||
- Per-channel partial state, exactly mirroring `NRPNReducer`.
|
||||
- Emits one `RPNChangeEvent` on CC 38 (data LSB) once the parameter pair is set.
|
||||
- Throws `MidiError("MIDI.NRPN_INCOMPLETE")` when data arrives before the parameter MSB/LSB.
|
||||
- Ignores unrelated CCs cleanly — composes with other CC-aware reducers without interference.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { RPNReducer } from "@suaveplan/midi";
|
||||
|
||||
const r = new RPNReducer();
|
||||
r.consume({ type: "controlChange", channel: 0, controller: 101, value: 0 });
|
||||
r.consume({ type: "controlChange", channel: 0, controller: 100, value: 0 });
|
||||
r.consume({ type: "controlChange", channel: 0, controller: 6, value: 2 });
|
||||
const evt = r.consume({ type: "controlChange", channel: 0, controller: 38, value: 0 });
|
||||
// evt.value = 0x100 (pitch-bend range = 2 semitones, coarse)
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import { NRPNReducer, RPNReducer } from "@suaveplan/midi";
|
||||
|
||||
const nrpn = new NRPNReducer();
|
||||
const rpn = new RPNReducer();
|
||||
|
||||
decoder.onMessage((m) => {
|
||||
if (m.type !== "controlChange") return;
|
||||
const rpnEvt = rpn.consume(m);
|
||||
if (rpnEvt) {
|
||||
handleRpn(rpnEvt);
|
||||
return; // RPN takes priority
|
||||
}
|
||||
const nrpnEvt = nrpn.consume(m);
|
||||
if (nrpnEvt) handleNrpn(nrpnEvt);
|
||||
});
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `class RPNReducer`
|
||||
- `consume(msg: ControlChangeMessage): RPNChangeEvent | undefined`
|
||||
- `reset(): void`
|
||||
- `RPNChangeEvent { type: "rpnChange"; channel; paramHigh; paramLow; value }`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
Because RPN does not specify increment/decrement shortcuts in the same canonical form as NRPN (the MMA's recommendation is that vendors generally avoid them for RPN), this reducer omits CC 96/97 handling — adding them would invite confusion when both reducers run on the same stream. Vendors that do support RPN inc/dec can layer their own behaviour on top by inspecting CC 96/97 outside this class.
|
||||
Reference in New Issue
Block a user