# 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.