Skip to main content

nord_format/
components.rs

1//! Typed values shared across models.
2//!
3//! A component owns its encoding, its validation and its `Display`, and knows nothing
4//! about which panel or offset holds it — so the same impl serves every `#[bits(...)]`
5//! placement of that value.
6//!
7//! Only what more than one model uses belongs here. A component with a single consumer
8//! lives beside that consumer, in the panel module that names it.
9
10use std::fmt::{self, Debug, Display, Formatter};
11
12use crate::bits::{bits_for, Packed};
13use crate::error::ParseError;
14use crate::fields::{ControlKind, Library, PackedOrder, Unit};
15use crate::types::RangedI8;
16
17/// Octave shift. The range and the storage bias are the model's business, so each
18/// names its own alias.
19pub type OctaveShift<const OFFSET: u8, const MIN: i8, const MAX: i8> = RangedI8<OFFSET, MIN, MAX>;
20
21/// Half-step transposition. As with [`OctaveShift`], the model fixes the parameters.
22pub type Transpose<const OFFSET: u8, const MIN: i8, const MAX: i8> = RangedI8<OFFSET, MIN, MAX>;
23
24/// A continuous control on the panel's own `0..10` — level, compression, gain, tone.
25///
26/// `FULL` is the stored value the panel reads as 10, and so also fixes the slot's width.
27/// Use the [`Level`] and [`Level6`] aliases rather than naming it — nearly every one of
28/// these is the seven-bit `0..=127`, and the Stage 4 puts a few of the same knobs in six
29/// bits.
30///
31/// ⚠️ **A `0..=127` slot is not automatically one of these.** An envelope stage reads in
32/// milliseconds, a filter cutoff in hertz, an equalizer band in decibels either side of a
33/// centre — see [`Time`], [`Frequency`], [`Rate`] and [`Bipolar`]. Typing one of those as a
34/// `Level` makes the panel reading wrong rather than merely absent.
35#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
36pub struct LevelOf<const FULL: u8> {
37    inner: u8,
38}
39
40impl<const FULL: u8> LevelOf<FULL> {
41    const VALID: () = assert!(FULL > 0, "a level needs a nonzero full-scale value");
42
43    pub const MAX: u8 = {
44        let () = Self::VALID;
45        FULL
46    };
47
48    pub fn new(value: u8) -> Result<Self, ParseError> {
49        value.try_into()
50    }
51
52    /// The stored value, `0..=FULL`.
53    pub fn as_u8(&self) -> u8 {
54        let () = Self::VALID;
55        self.inner
56    }
57
58    /// The panel's 0..10 reading.
59    ///
60    /// Confirmed on hardware. Reverb wet reads `43` in the file and the panel shows
61    /// 3.4, and `43 / 127 * 10 = 3.39`.
62    pub fn as_panel(&self) -> f32 {
63        let () = Self::VALID;
64        f32::from(self.inner) / f32::from(FULL) * 10.0
65    }
66}
67
68impl<const FULL: u8> Default for LevelOf<FULL> {
69    fn default() -> Self {
70        let () = Self::VALID;
71        Self { inner: 0 }
72    }
73}
74
75impl<const FULL: u8> TryFrom<u8> for LevelOf<FULL> {
76    type Error = ParseError;
77
78    fn try_from(value: u8) -> Result<Self, ParseError> {
79        let () = Self::VALID;
80        if value > FULL {
81            return Err(ParseError::OutOfBounds {
82                value: format!("{value}"),
83                bound: format!("0..={FULL}"),
84            });
85        }
86        Ok(LevelOf { inner: value })
87    }
88}
89
90impl<const FULL: u8> Packed for LevelOf<FULL> {
91    const MAX_BITS: u32 = {
92        let () = Self::VALID;
93        bits_for(FULL as u64)
94    };
95    const DECODE_BITS: u32 = u8::BITS;
96    const CONTROL: ControlKind = ControlKind::Knob(Unit::Panel10);
97    type Error = ParseError;
98
99    fn from_bits(bits: u64) -> Result<Self, ParseError> {
100        (bits as u8).try_into()
101    }
102
103    fn to_bits(&self) -> u64 {
104        self.inner as u64
105    }
106}
107
108impl<const FULL: u8> Display for LevelOf<FULL> {
109    /// Stored byte and panel reading: `96 (7.6)`.
110    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
111        write!(f, "{} ({:.1})", self.inner, self.as_panel())
112    }
113}
114
115impl<const FULL: u8> Debug for LevelOf<FULL> {
116    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
117        write!(f, "{}", self.inner)
118    }
119}
120
121impl<const FULL: u8> PartialEq<u8> for LevelOf<FULL> {
122    fn eq(&self, other: &u8) -> bool {
123        self.inner == *other
124    }
125}
126
127/// The seven-bit panel knob, which is nearly every one of them.
128pub type Level = LevelOf<127>;
129
130/// The same knob in a six-bit slot, as a few Stage 4 parameters store it.
131pub type Level6 = LevelOf<63>;
132
133/// Declare a 0..=127 knob whose panel reading is in `$unit` over a curve no manual
134/// publishes.
135///
136/// [`Level`] is the same slot on the panel's own `0..10`, where the transform *is* known.
137/// These are the ones where it is not: an envelope stage reads in milliseconds and a
138/// filter cutoff in hertz, but no published table converts the stored byte, so the byte
139/// is what they print. The unit is still worth carrying — it is what lets an interface
140/// label the control and pick a taper without a table of field names beside it.
141macro_rules! knob {
142    ($(#[$meta:meta])* $name:ident, $unit:expr) => {
143        knob!($(#[$meta])* $name, 127, 7, ControlKind::Knob($unit));
144    };
145    ($(#[$meta:meta])* $name:ident, $max:expr, $bits:expr, $control:expr) => {
146        $(#[$meta])*
147        #[derive(Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
148        pub struct $name {
149            inner: u8,
150        }
151
152        impl $name {
153            pub const MAX: u8 = $max;
154
155            pub fn new(value: u8) -> Result<Self, ParseError> {
156                value.try_into()
157            }
158
159            #[doc = concat!("The stored value, 0..=", stringify!($max), ".")]
160            pub fn as_u8(&self) -> u8 {
161                self.inner
162            }
163        }
164
165        impl TryFrom<u8> for $name {
166            type Error = ParseError;
167
168            fn try_from(value: u8) -> Result<Self, ParseError> {
169                if value > Self::MAX {
170                    return Err(ParseError::OutOfBounds {
171                        value: format!("{value}"),
172                        bound: format!("0..={}", Self::MAX),
173                    });
174                }
175                Ok($name { inner: value })
176            }
177        }
178
179        impl Packed for $name {
180            const MAX_BITS: u32 = $bits;
181            const DECODE_BITS: u32 = u8::BITS;
182            const CONTROL: ControlKind = $control;
183            type Error = ParseError;
184
185            fn from_bits(bits: u64) -> Result<Self, ParseError> {
186                (bits as u8).try_into()
187            }
188
189            fn to_bits(&self) -> u64 {
190                self.inner as u64
191            }
192        }
193
194        /// The stored byte. There is no published transform to the unit, so printing one
195        /// would invent precision the file does not carry.
196        impl Debug for $name {
197            fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
198                write!(f, "{}", self.inner)
199            }
200        }
201
202        impl Display for $name {
203            fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
204                write!(f, "{}", self.inner)
205            }
206        }
207
208        impl PartialEq<u8> for $name {
209            fn eq(&self, other: &u8) -> bool {
210                self.inner == *other
211            }
212        }
213    };
214}
215
216knob!(
217    /// An envelope stage or a delay time. The panel reads it in milliseconds through
218    /// seconds, over a curve no manual publishes.
219    Time,
220    Unit::Milliseconds
221);
222
223knob!(
224    /// A filter cutoff or an equalizer sweep. The panel reads it in hertz — the Stage
225    /// manuals give the endpoints of the mid sweep (200 Hz to 8 kHz) but not the taper.
226    Frequency,
227    Unit::Hertz
228);
229
230knob!(
231    /// A modulation or LFO rate, read in hertz.
232    ///
233    /// ⚠️ Under a live master clock the same slot reads as a subdivision instead — see
234    /// [`ClockDivision`]. The flag that switches it is a sibling field, so neither field
235    /// answers alone.
236    Rate,
237    Unit::Hertz
238);
239
240knob!(
241    /// A stereo position in a six-bit slot.
242    ///
243    /// ⚠️ The mapping is not established: over the Stage 4 factory programs the slot's
244    /// mode is 0 rather than the mid-scale 32 a centre-encoded pan would show, so this
245    /// makes no claim about where centre sits and prints the stored value. It carries
246    /// only that the control is a pan. Inferred from specimens; not confirmed on
247    /// hardware.
248    Pan,
249    63,
250    6,
251    ControlKind::Knob(Unit::Pan)
252);
253
254knob!(
255    /// A pitch offset in semitones.
256    ///
257    /// The Stage 4's coarse oscillator pitch holds 0, 7, 12, 24 and 40 — unison, a
258    /// fifth, an octave, two octaves — which is what makes the unit readable. Inferred
259    /// from specimens; not confirmed on hardware. The Stage 3 manual gives the same
260    /// control as "semitone steps, ranging from 0 to 48".
261    Interval,
262    63,
263    6,
264    ControlKind::Shift(Unit::Semitones)
265);
266
267/// A 0..=127 slot whose musical zero is its centre, reading `±LIMIT` of the unit
268/// `UNIT` codes either side.
269///
270/// The Stage equalizer bands are the clearest case: the manuals give "the boost/cut range
271/// is +/- 15 dB" for all three models, and rendering those on [`Level`]'s `0..10` reads a
272/// cut as a small boost.
273///
274/// ⚠️ The unit is the declaration's, not the shape's: a `±10` modulation amount is not
275/// decibels because an equalizer band is. Name it through [`EqBand`] or [`Bipolar`]
276/// rather than writing the code out.
277///
278/// ⚠️ The centre is taken as 64 — the midpoint of the slot. Inferred from specimens; not
279/// confirmed on hardware. The corpus does not distinguish 63 from 64, and no manual
280/// states it. A reading is therefore accurate at the endpoints and approximate in
281/// between.
282#[derive(Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
283pub struct BipolarOf<const LIMIT: i16, const UNIT: u8> {
284    inner: u8,
285}
286
287impl<const LIMIT: i16, const UNIT: u8> BipolarOf<LIMIT, UNIT> {
288    pub const MAX: u8 = 127;
289    /// The stored value that reads as zero.
290    pub const CENTER: u8 = 64;
291    /// What the reading is in.
292    pub const UNIT: Unit = Unit::expect_code(UNIT);
293
294    pub fn new(value: u8) -> Result<Self, ParseError> {
295        value.try_into()
296    }
297
298    /// The stored value, 0..=127.
299    pub fn as_u8(&self) -> u8 {
300        self.inner
301    }
302
303    /// The panel's signed reading, `-LIMIT..=+LIMIT`.
304    pub fn reading(&self) -> f32 {
305        let from_center = f32::from(self.inner) - f32::from(Self::CENTER);
306        let span = if from_center < 0.0 {
307            f32::from(Self::CENTER)
308        } else {
309            f32::from(Self::MAX - Self::CENTER)
310        };
311        from_center / span * f32::from(LIMIT)
312    }
313}
314
315impl<const LIMIT: i16, const UNIT: u8> TryFrom<u8> for BipolarOf<LIMIT, UNIT> {
316    type Error = ParseError;
317
318    fn try_from(value: u8) -> Result<Self, ParseError> {
319        if value > Self::MAX {
320            return Err(ParseError::OutOfBounds {
321                value: format!("{value}"),
322                bound: format!("0..={}", Self::MAX),
323            });
324        }
325        Ok(BipolarOf { inner: value })
326    }
327}
328
329impl<const LIMIT: i16, const UNIT: u8> Packed for BipolarOf<LIMIT, UNIT> {
330    const MAX_BITS: u32 = 7;
331    const DECODE_BITS: u32 = u8::BITS;
332    const CONTROL: ControlKind = ControlKind::Bipolar(Unit::expect_code(UNIT));
333    type Error = ParseError;
334
335    fn from_bits(bits: u64) -> Result<Self, ParseError> {
336        (bits as u8).try_into()
337    }
338
339    fn to_bits(&self) -> u64 {
340        self.inner as u64
341    }
342}
343
344/// The stored byte, so a retype from a plain integer leaves the field dumps alone.
345impl<const LIMIT: i16, const UNIT: u8> Debug for BipolarOf<LIMIT, UNIT> {
346    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
347        write!(f, "{}", self.inner)
348    }
349}
350
351impl<const LIMIT: i16, const UNIT: u8> Display for BipolarOf<LIMIT, UNIT> {
352    /// Stored byte and signed reading: `96 (+7.5)`.
353    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
354        write!(f, "{} ({:+.1})", self.inner, self.reading())
355    }
356}
357
358impl<const LIMIT: i16, const UNIT: u8> PartialEq<u8> for BipolarOf<LIMIT, UNIT> {
359    fn eq(&self, other: &u8) -> bool {
360        self.inner == *other
361    }
362}
363
364/// An equalizer band, `±15 dB` — the range all three Stage manuals give.
365pub type EqBand = BipolarOf<15, { Unit::Decibels.code() }>;
366
367/// A bipolar amount with no unit: a modulation depth the panel reads as a bare
368/// `±LIMIT`, such as the Stage 2's filter modulation.
369pub type Bipolar<const LIMIT: i16> = BipolarOf<LIMIT, { Unit::None.code() }>;
370
371/// The value a performance control morphs its parent parameter *to*.
372///
373/// Every morphable parameter has three of these beside it — `_wheel`, `_aftertouch` and
374/// `_ctrl_pedal` — and together they are half of every Stage body's field count. They are
375/// not controls of their own: an interface shows them **on the parent's knob**, as a
376/// second handle, which is what [`ControlKind::Morph`] tells it to do.
377///
378/// `BITS` is the slot's width, which tracks the parent's: eight beside a `0..=127` knob,
379/// five beside a drawbar, three beside a switch.
380///
381/// ⚠️ **The encoding is not established.** Stage 4 specimens use the whole byte,
382/// with 127 predominant. It may be a signed delta biased by 127 or a destination at
383/// twice the parent's resolution. Inferred from specimens; not confirmed on hardware.
384/// [`Self::is_neutral`] is the only interpretation exposed.
385///
386/// The experiment that settles it: assign one morph at a known depth, store, and diff the
387/// slot against its parent's value.
388#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
389pub struct MorphOf<const BITS: u32> {
390    inner: u8,
391}
392
393impl<const BITS: u32> MorphOf<BITS> {
394    const VALID: () = assert!(BITS > 0 && BITS <= 8, "a morph must fit in a byte");
395
396    /// The slot's midpoint. Only the eight-bit value is confirmed by specimens.
397    pub const NEUTRAL: u8 = {
398        let () = Self::VALID;
399        ((1u16 << BITS) / 2 - 1) as u8
400    };
401
402    pub fn as_u8(&self) -> u8 {
403        let () = Self::VALID;
404        self.inner
405    }
406
407    /// Whether the slot holds [`NEUTRAL`](Self::NEUTRAL).
408    pub fn is_neutral(&self) -> bool {
409        self.inner == Self::NEUTRAL
410    }
411}
412
413impl<const BITS: u32> Default for MorphOf<BITS> {
414    fn default() -> Self {
415        let () = Self::VALID;
416        Self { inner: 0 }
417    }
418}
419
420impl<const BITS: u32> Packed for MorphOf<BITS> {
421    const MAX_BITS: u32 = {
422        let () = Self::VALID;
423        BITS
424    };
425    const DECODE_BITS: u32 = u8::BITS;
426    /// The parent is the declaration site's business, not the type's — every morph slot
427    /// shares this type and each names a different parameter — so `#[bitbody]` fills it
428    /// in from the field's name.
429    const CONTROL: ControlKind = ControlKind::Morph { of: None };
430    type Error = ::core::convert::Infallible;
431
432    fn from_bits(bits: u64) -> Result<Self, Self::Error> {
433        let () = Self::VALID;
434        Ok(MorphOf { inner: bits as u8 })
435    }
436
437    fn to_bits(&self) -> u64 {
438        self.inner as u64
439    }
440}
441
442/// The stored value — the encoding is unconfirmed, so this prints what is there.
443impl<const BITS: u32> Debug for MorphOf<BITS> {
444    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
445        write!(f, "{}", self.inner)
446    }
447}
448
449impl<const BITS: u32> Display for MorphOf<BITS> {
450    /// A neutral slot as `—`, anything else as the stored value.
451    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
452        if self.is_neutral() {
453            f.write_str("—")
454        } else {
455            write!(f, "{}", self.inner)
456        }
457    }
458}
459
460impl<const BITS: u32> PartialEq<u8> for MorphOf<BITS> {
461    fn eq(&self, other: &u8) -> bool {
462        self.inner == *other
463    }
464}
465
466/// The morph slot beside a `0..=127` knob.
467pub type MorphTarget = MorphOf<8>;
468
469/// The morph slot beside a drawbar.
470pub type DrawbarMorph = MorphOf<5>;
471
472/// The morph slot beside a three-position switch — the Stage 3's rotary speed.
473pub type SwitchMorph = MorphOf<3>;
474
475/// A [`Selector`] over a list too long for a byte — a waveform, a sample slot.
476#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
477pub struct WideSelector<const BITS: u32> {
478    inner: u16,
479}
480
481impl<const BITS: u32> WideSelector<BITS> {
482    const VALID: () = assert!(BITS > 0 && BITS <= 16, "a wide selector must fit in a u16");
483
484    /// The stored index.
485    pub fn raw(&self) -> u16 {
486        let () = Self::VALID;
487        self.inner
488    }
489}
490
491impl<const BITS: u32> Default for WideSelector<BITS> {
492    fn default() -> Self {
493        let () = Self::VALID;
494        Self { inner: 0 }
495    }
496}
497
498impl<const BITS: u32> Packed for WideSelector<BITS> {
499    const MAX_BITS: u32 = {
500        let () = Self::VALID;
501        BITS
502    };
503    const DECODE_BITS: u32 = u16::BITS;
504    const CONTROL: ControlKind = ControlKind::Selector;
505    type Error = ::core::convert::Infallible;
506
507    fn from_bits(bits: u64) -> Result<Self, Self::Error> {
508        let () = Self::VALID;
509        Ok(WideSelector { inner: bits as u16 })
510    }
511
512    fn to_bits(&self) -> u64 {
513        self.inner as u64
514    }
515}
516
517impl<const BITS: u32> Debug for WideSelector<BITS> {
518    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
519        write!(f, "{}", self.inner)
520    }
521}
522
523impl<const BITS: u32> Display for WideSelector<BITS> {
524    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
525        write!(f, "{}", self.inner)
526    }
527}
528
529impl<const BITS: u32> PartialEq<u16> for WideSelector<BITS> {
530    fn eq(&self, other: &u16) -> bool {
531        self.inner == *other
532    }
533}
534
535/// One drawbar, in the four-bit slot the Stage models give it.
536///
537/// Positions are physical, `0..=8`. The slot holds four bits, so decoding is total: a
538/// nibble above 8 is preserved and reported by [`Self::position`] as `None` rather than
539/// refused, on the same rule as [`crate::types::RangedU8`] — the bound is the slot's, not
540/// the instrument's.
541///
542/// ⚠️ The two constructors therefore disagree on purpose. [`Self::new`] takes a
543/// *position* and refuses 9 and above; `from_bits` — and so `set_field`, which goes
544/// through the type's own parse — takes a *nibble* and accepts all sixteen, because a
545/// file holding one has to round-trip. A caller offering a bar to a player wants the
546/// former.
547///
548/// ⚠️ On the Stage 2's Farfisa the register is a *tab*, and the file stores a bit rather
549/// than a nibble, so those fields are `bool` and not this type.
550#[derive(Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
551pub struct Drawbar {
552    inner: u8,
553}
554
555impl Drawbar {
556    /// The highest position a drawbar can be pulled to.
557    pub const MAX: u8 = 8;
558
559    /// A bar at `position`, `0..=8`. A higher one is refused — this takes a position,
560    /// where decoding takes a nibble.
561    pub fn new(position: u8) -> Result<Self, ParseError> {
562        if position > Self::MAX {
563            return Err(ParseError::OutOfBounds {
564                value: format!("{position}"),
565                bound: format!("0..={}", Self::MAX),
566            });
567        }
568        Ok(Drawbar { inner: position })
569    }
570
571    /// The stored nibble, whatever it holds.
572    pub fn raw(&self) -> u8 {
573        self.inner
574    }
575
576    /// The position, or `None` for a nibble past the drawbar's travel.
577    pub fn position(&self) -> Option<u8> {
578        (self.inner <= Self::MAX).then_some(self.inner)
579    }
580}
581
582impl Packed for Drawbar {
583    const MAX_BITS: u32 = 4;
584    const DECODE_BITS: u32 = u8::BITS;
585    /// Which bar of the register this is comes from the declaration site — every bar
586    /// shares this type — so `#[bitbody]` fills the rank in from a `…_N` field name.
587    const CONTROL: ControlKind = ControlKind::Drawbar {
588        bars: 1,
589        rank: None,
590        bits_per_bar: Self::MAX_BITS as u8,
591        // One bar: there is no second value for the order to place.
592        order: PackedOrder::HighFirst,
593    };
594    type Error = ::core::convert::Infallible;
595
596    fn from_bits(bits: u64) -> Result<Self, Self::Error> {
597        Ok(Drawbar { inner: bits as u8 })
598    }
599
600    fn to_bits(&self) -> u64 {
601        self.inner as u64
602    }
603}
604
605impl Debug for Drawbar {
606    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
607        write!(f, "{}", self.inner)
608    }
609}
610
611impl Display for Drawbar {
612    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
613        write!(f, "{}", self.inner)
614    }
615}
616
617impl PartialEq<u8> for Drawbar {
618    fn eq(&self, other: &u8) -> bool {
619        self.inner == *other
620    }
621}
622
623/// A four-bit octave shift stored in two's complement, as the Stage 4 stores it.
624///
625/// Inferred from specimens; not confirmed on hardware. Over the Stage 4 factory programs
626/// the slot holds only 0, 1, 2, 14 and 15 — a distribution centred on zero with the
627/// negative side wrapping, where the Stage 2 and 3 instead centre on a stored 7 and 6.
628/// Those two are [`OctaveShift`] aliases; this is the third encoding.
629#[derive(Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
630pub struct OctaveShiftNibble {
631    /// The signed reading, -8..=7.
632    inner: i8,
633}
634
635impl OctaveShiftNibble {
636    /// The shift in octaves, `-8..=7`.
637    pub fn octaves(&self) -> i8 {
638        self.inner
639    }
640}
641
642impl Packed for OctaveShiftNibble {
643    const MAX_BITS: u32 = 4;
644    const DECODE_BITS: u32 = 4;
645    const CONTROL: ControlKind = ControlKind::Shift(Unit::Octaves);
646    type Error = ::core::convert::Infallible;
647
648    fn from_bits(bits: u64) -> Result<Self, Self::Error> {
649        let nibble = (bits & 0xf) as i8;
650        Ok(OctaveShiftNibble {
651            inner: if nibble >= 8 { nibble - 16 } else { nibble },
652        })
653    }
654
655    fn to_bits(&self) -> u64 {
656        (self.inner as u8 & 0xf) as u64
657    }
658}
659
660impl Debug for OctaveShiftNibble {
661    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
662        write!(f, "{}", self.inner)
663    }
664}
665
666impl Display for OctaveShiftNibble {
667    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
668        write!(f, "{:+}", self.inner)
669    }
670}
671
672impl PartialEq<i8> for OctaveShiftNibble {
673    fn eq(&self, other: &i8) -> bool {
674        self.inner == *other
675    }
676}
677
678/// A selector whose positions are known to be a fixed set, but whose table is not.
679///
680/// This preserves the control shape without inventing labels for positions that are not
681/// yet identified. Use `sparse_enum!` once the value table is known.
682#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
683pub struct Selector<const BITS: u32> {
684    inner: u8,
685}
686
687impl<const BITS: u32> Selector<BITS> {
688    const VALID: () = assert!(BITS > 0 && BITS <= 8, "a selector must fit in a byte");
689
690    /// The stored index.
691    pub fn raw(&self) -> u8 {
692        let () = Self::VALID;
693        self.inner
694    }
695}
696
697impl<const BITS: u32> Default for Selector<BITS> {
698    fn default() -> Self {
699        let () = Self::VALID;
700        Self { inner: 0 }
701    }
702}
703
704impl<const BITS: u32> Packed for Selector<BITS> {
705    const MAX_BITS: u32 = {
706        let () = Self::VALID;
707        BITS
708    };
709    const DECODE_BITS: u32 = u8::BITS;
710    const CONTROL: ControlKind = ControlKind::Selector;
711    type Error = ::core::convert::Infallible;
712
713    fn from_bits(bits: u64) -> Result<Self, Self::Error> {
714        let () = Self::VALID;
715        Ok(Selector { inner: bits as u8 })
716    }
717
718    fn to_bits(&self) -> u64 {
719        self.inner as u64
720    }
721}
722
723/// The stored index — this type exists precisely because there is no name to print.
724impl<const BITS: u32> Debug for Selector<BITS> {
725    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
726        write!(f, "{}", self.inner)
727    }
728}
729
730impl<const BITS: u32> Display for Selector<BITS> {
731    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
732        write!(f, "{}", self.inner)
733    }
734}
735
736impl<const BITS: u32> PartialEq<u8> for Selector<BITS> {
737    fn eq(&self, other: &u8) -> bool {
738        self.inner == *other
739    }
740}
741
742/// A subdivision of the master clock, as a rate slot reads when its clock flag is set.
743///
744/// The manuals give the vocabulary — "subdivisions of the Master Clock tempo, ranging
745/// from 1/2 to 1/32 notes. Apart from straight subdivisions there are also swing (S),
746/// triplet (T) and dotted (D) options" — which is sixteen readings for a four-bit slot.
747///
748/// ⚠️ Which index carries which subdivision is not established, so this names none of
749/// them. The experiment that settles it: store one specimen per detent of a clocked rate
750/// knob.
751pub type ClockDivision = Selector<4>;
752
753/// The balance between a split's lower and upper parts, as a 0..=127 crossfade.
754///
755/// ⚠️ Each side is clamped at 50, so the pair does not sum to 100 — a stored 16 reads
756/// as `50.0/12.6`.
757#[derive(Copy, Default, Clone, PartialEq, Eq)]
758pub struct PartMix {
759    inner: u8,
760}
761
762impl PartMix {
763    pub fn inner(&self) -> u8 {
764        self.inner
765    }
766
767    pub fn lower(&self) -> f32 {
768        let lower = 100_f32 - ((self.inner() as f32) / 127.0) * 100_f32;
769
770        if lower > 50_f32 {
771            50_f32
772        } else {
773            lower
774        }
775    }
776
777    pub fn upper(&self) -> f32 {
778        let upper = ((self.inner() as f32) / 127.0) * 100_f32;
779
780        if upper > 50_f32 {
781            50_f32
782        } else {
783            upper
784        }
785    }
786}
787
788impl Display for PartMix {
789    /// The two sides as the panel reads them: `50.0/12.6`.
790    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
791        write!(f, "{:.1}/{:.1}", self.lower(), self.upper())
792    }
793}
794
795impl Debug for PartMix {
796    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
797        write!(f, "{self}")
798    }
799}
800
801impl Packed for PartMix {
802    const MAX_BITS: u32 = 7;
803    const DECODE_BITS: u32 = u8::BITS;
804    const CONTROL: ControlKind = ControlKind::Bipolar(Unit::None);
805    type Error = ParseError;
806
807    fn from_bits(bits: u64) -> Result<Self, ParseError> {
808        (bits as u8).try_into()
809    }
810
811    fn to_bits(&self) -> u64 {
812        self.inner() as u64
813    }
814}
815
816impl TryFrom<u8> for PartMix {
817    type Error = ParseError;
818
819    fn try_from(value: u8) -> Result<Self, Self::Error> {
820        if value > 127 {
821            return Err(ParseError::OutOfBounds {
822                value: format!("{value}"),
823                bound: "0..=127".to_string(),
824            });
825        }
826
827        Ok(PartMix { inner: value })
828    }
829}
830
831/// Percussion decay speed. How it is stored is per-model; the Electro 5's B3 does not
832/// store it in this order.
833#[derive(Debug, Clone, Copy, PartialEq, Eq)]
834pub enum PercSpeed {
835    Off,
836    Soft,
837    Fast,
838    Both,
839}
840
841/// A keyboard split point as the 73-key models store it: one of six keys, or
842/// the whole keyboard as Upper / Lower.
843#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
844pub enum SplitPoint73 {
845    #[default]
846    C3,
847    F3,
848    C4,
849    F4,
850    C5,
851    F5,
852    Upper,
853    Lower,
854}
855
856impl TryFrom<u8> for SplitPoint73 {
857    type Error = ParseError;
858
859    fn try_from(value: u8) -> Result<SplitPoint73, ParseError> {
860        match value {
861            0 => Ok(SplitPoint73::C3),
862            1 => Ok(SplitPoint73::F3),
863            2 => Ok(SplitPoint73::C4),
864            3 => Ok(SplitPoint73::F4),
865            4 => Ok(SplitPoint73::C5),
866            5 => Ok(SplitPoint73::F5),
867            6 => Ok(SplitPoint73::Upper),
868            7 => Ok(SplitPoint73::Lower),
869            _ => Err(ParseError::OutOfBounds {
870                value: format!("{value}"),
871                bound: "0..=7 (SplitPoint73)".to_string(),
872            }),
873        }
874    }
875}
876
877impl Packed for SplitPoint73 {
878    const MAX_BITS: u32 = 3;
879    const DECODE_BITS: u32 = u8::BITS;
880    const CONTROL: ControlKind = ControlKind::Selector;
881    type Error = ParseError;
882
883    fn from_bits(bits: u64) -> Result<Self, ParseError> {
884        (bits as u8).try_into()
885    }
886
887    fn to_bits(&self) -> u64 {
888        *self as u64
889    }
890}
891
892/// A vibrato (`V`) or chorus (`C`) organ modulation at one of three depths.
893///
894/// Which subset an organ offers is the model's business, and so is the index each sits
895/// at — see the per-model tables beside the organ panel.
896#[derive(Debug, Clone, Copy, PartialEq, Eq)]
897pub enum VibChorus {
898    V1,
899    C1,
900    V2,
901    C2,
902    V3,
903    C3,
904}
905
906/// A Stage program's transpose slot: stored `0..=12`, biased by 6, reading
907/// `-6..=+6` semitones.
908///
909/// ⚠️ Not a [`RangedI8`]: the Stage 2 EX factory live
910/// buffers hold 15 in this slot — an untouched buffer stores an out-of-table
911/// pattern — so the unknown patterns are preserved rather than refused.
912#[derive(Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
913pub struct StageTranspose {
914    raw: u8,
915}
916
917impl StageTranspose {
918    /// The stored 4-bit pattern.
919    pub fn raw(&self) -> u8 {
920        self.raw
921    }
922
923    /// The semitone reading, or `None` for a pattern past the panel's `+6`.
924    pub fn semitones(&self) -> Option<i8> {
925        (self.raw <= 12).then(|| self.raw as i8 - 6)
926    }
927}
928
929impl Packed for StageTranspose {
930    const MAX_BITS: u32 = 4;
931    const DECODE_BITS: u32 = u8::BITS;
932    const CONTROL: ControlKind = ControlKind::Shift(Unit::Semitones);
933    type Error = ::core::convert::Infallible;
934
935    fn from_bits(bits: u64) -> Result<Self, Self::Error> {
936        Ok(StageTranspose { raw: bits as u8 })
937    }
938
939    fn to_bits(&self) -> u64 {
940        self.raw as u64
941    }
942}
943
944impl Debug for StageTranspose {
945    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
946        match self.semitones() {
947            Some(s) => write!(f, "{s}"),
948            None => write!(f, "unknown ({})", self.raw),
949        }
950    }
951}
952
953/// The master clock rate the Stage 2 and 3 store in a program: `stored + 30` BPM.
954///
955/// Reported by public documentation; not confirmed on hardware.
956#[derive(Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
957pub struct MasterTempo {
958    inner: u8,
959}
960
961impl MasterTempo {
962    /// The stored byte.
963    pub fn as_u8(&self) -> u8 {
964        self.inner
965    }
966
967    /// The panel's BPM reading.
968    pub fn bpm(&self) -> u16 {
969        self.inner as u16 + 30
970    }
971}
972
973impl Packed for MasterTempo {
974    const MAX_BITS: u32 = 8;
975    const DECODE_BITS: u32 = u8::BITS;
976    const CONTROL: ControlKind = ControlKind::Knob(Unit::Bpm);
977    type Error = ::core::convert::Infallible;
978
979    fn from_bits(bits: u64) -> Result<Self, Self::Error> {
980        Ok(MasterTempo { inner: bits as u8 })
981    }
982
983    fn to_bits(&self) -> u64 {
984        self.inner as u64
985    }
986}
987
988impl Debug for MasterTempo {
989    /// The BPM reading — the stored byte is recoverable as `bpm - 30`.
990    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
991        write!(f, "{}", self.bpm())
992    }
993}
994
995/// Declare a sparse enumeration: known values, plus `Unknown` for the rest of the slot.
996///
997/// The slot is wider than the set of values we have names for, so anything unrecognized
998/// decodes to `Unknown`, round-trips byte-exactly, and displays as `unknown (9)` — never
999/// coerced to the nearest label. Match on it, or call `is_unknown()`, to find them.
1000macro_rules! sparse_enum {
1001    (
1002        $(#[$meta:meta])*
1003        $name:ident, $bits:expr, { $($value:expr => $variant:ident, $label:expr;)+ }
1004    ) => {
1005        $(#[$meta])*
1006        #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1007        pub enum $name {
1008            $($variant,)+
1009            /// A stored value with no known meaning.
1010            Unknown(u8),
1011        }
1012
1013        /// Named variants as their names; an unknown as `unknown (raw)`. ⚠️ The corpus
1014        /// tripwires match the lowercase spelling — a derived `Unknown(raw)` slips past
1015        /// them.
1016        impl ::core::fmt::Debug for $name {
1017            fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
1018                match self {
1019                    $($name::$variant => f.write_str(stringify!($variant)),)+
1020                    $name::Unknown(raw) => write!(f, "unknown ({raw})"),
1021                }
1022            }
1023        }
1024
1025        impl $name {
1026            /// The label, or `None` for a value with no known meaning.
1027            pub fn label(&self) -> Option<&'static str> {
1028                match self {
1029                    $($name::$variant => Some($label),)+
1030                    $name::Unknown(_) => None,
1031                }
1032            }
1033
1034            /// Whether the stored value has no known meaning.
1035            pub fn is_unknown(&self) -> bool {
1036                matches!(self, $name::Unknown(_))
1037            }
1038
1039            /// The stored value, named or not.
1040            pub fn raw(&self) -> u8 {
1041                <Self as $crate::bits::Packed>::to_bits(self) as u8
1042            }
1043        }
1044
1045        impl Default for $name {
1046            fn default() -> Self {
1047                match <Self as $crate::bits::Packed>::from_bits(0) {
1048                    Ok(v) => v,
1049                    Err(never) => match never {},
1050                }
1051            }
1052        }
1053
1054        impl $crate::bits::Packed for $name {
1055            const MAX_BITS: u32 = $bits;
1056            const DECODE_BITS: u32 = u8::BITS;
1057            const CONTROL: $crate::fields::ControlKind = $crate::fields::ControlKind::Selector;
1058            type Error = ::core::convert::Infallible;
1059
1060            fn from_bits(bits: u64) -> Result<Self, Self::Error> {
1061                Ok(match bits as u8 {
1062                    $($value => $name::$variant,)+
1063                    other => $name::Unknown(other),
1064                })
1065            }
1066
1067            fn to_bits(&self) -> u64 {
1068                match self {
1069                    $($name::$variant => $value as u64,)+
1070                    $name::Unknown(raw) => *raw as u64,
1071                }
1072            }
1073        }
1074
1075        impl ::core::fmt::Display for $name {
1076            fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
1077                match self.label() {
1078                    Some(label) => f.write_str(label),
1079                    None => write!(f, "unknown ({})", self.raw()),
1080                }
1081            }
1082        }
1083    };
1084}
1085
1086pub(crate) use sparse_enum;
1087
1088/// Declare a one-bit field whose two states have names.
1089///
1090/// A `bool` is the right shape for on/off, and the wrong one for a switch between two
1091/// *named* positions: `false` is not a reading anyone can act on when the panel says
1092/// Normal and Analog. This keeps the single bit and gives both states their word.
1093macro_rules! switch {
1094    (
1095        $(#[$meta:meta])*
1096        $name:ident, $clear:ident = $clear_label:expr, $set:ident = $set_label:expr
1097    ) => {
1098        $(#[$meta])*
1099        #[derive(Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
1100        pub enum $name {
1101            /// The state stored as a clear bit.
1102            #[default]
1103            $clear,
1104            /// The state stored as a set bit.
1105            $set,
1106        }
1107
1108        impl $name {
1109            /// The panel's word for this state.
1110            pub fn label(&self) -> &'static str {
1111                match self {
1112                    $name::$clear => $clear_label,
1113                    $name::$set => $set_label,
1114                }
1115            }
1116
1117            /// Whether the bit is set.
1118            pub fn is_set(&self) -> bool {
1119                matches!(self, $name::$set)
1120            }
1121        }
1122
1123        impl $crate::bits::Packed for $name {
1124            const MAX_BITS: u32 = 1;
1125            const DECODE_BITS: u32 = 1;
1126            const CONTROL: $crate::fields::ControlKind = $crate::fields::ControlKind::Toggle;
1127            type Error = ::core::convert::Infallible;
1128
1129            fn from_bits(bits: u64) -> Result<Self, Self::Error> {
1130                Ok(if bits != 0 { $name::$set } else { $name::$clear })
1131            }
1132
1133            fn to_bits(&self) -> u64 {
1134                self.is_set() as u64
1135            }
1136        }
1137
1138        /// The variant name, which is what `--set` takes. ⚠️ Not [`Display`], which is
1139        /// the panel's own word for the state and may differ.
1140        impl ::core::fmt::Debug for $name {
1141            fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
1142                match self {
1143                    $name::$clear => f.write_str(stringify!($clear)),
1144                    $name::$set => f.write_str(stringify!($set)),
1145                }
1146            }
1147        }
1148
1149        impl ::core::fmt::Display for $name {
1150            fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
1151                f.write_str(self.label())
1152            }
1153        }
1154    };
1155}
1156
1157/// Sixteen pattern steps, two bits each — the Stage 4 arpeggiator's accent, gate and pan
1158/// rows.
1159///
1160/// The panel edits these as a grid: the manual's Pattern Edit page moves a cursor with a
1161/// Position dial and sets the step under it, and the Pattern Pan page moves a step
1162/// "between Left, Center and Right". Three values per step is exactly two bits, and a
1163/// pattern runs to sixteen steps, which is exactly the 32-bit slot.
1164///
1165/// ⚠️ **Step order is inferred, not established.** Read low-bits-first the corpus values
1166/// fall out as music — `0x01010101` is an accent every fourth step, `0x55aa5500` is four
1167/// left then four right then four left — but correlating the highest non-zero step
1168/// against the sibling `arp_pattern_length` fails in both directions, so either the word
1169/// keeps all sixteen steps regardless of the active length or that field is not a step
1170/// count. Inferred from specimens; not confirmed on hardware.
1171///
1172/// The slot is wider than [`crate::fields::ENUMERABLE_BITS`], so `--set` spells it by its
1173/// stored bits — `0x55aa5500` is the readable form for a pattern anyway.
1174#[derive(Copy, Clone, Default, PartialEq, Eq, Hash)]
1175pub struct ArpPattern {
1176    inner: u32,
1177}
1178
1179impl ArpPattern {
1180    /// Steps a pattern can hold.
1181    pub const STEPS: usize = 16;
1182
1183    /// The stored word.
1184    pub fn raw(&self) -> u32 {
1185        self.inner
1186    }
1187
1188    /// The sixteen steps, `0..=3` each, lowest bits first.
1189    pub fn steps(&self) -> [u8; Self::STEPS] {
1190        std::array::from_fn(|n| ((self.inner >> (2 * n)) & 0b11) as u8)
1191    }
1192
1193    /// Whether every step is zero — an unset row.
1194    pub fn is_empty(&self) -> bool {
1195        self.inner == 0
1196    }
1197}
1198
1199impl Packed for ArpPattern {
1200    const MAX_BITS: u32 = 32;
1201    const DECODE_BITS: u32 = u32::BITS;
1202    const CONTROL: ControlKind = ControlKind::Pattern {
1203        steps: Self::STEPS as u8,
1204        // The slot divided by its steps, so the two cannot drift apart.
1205        bits_per_step: (Self::MAX_BITS / Self::STEPS as u32) as u8,
1206        order: PackedOrder::LowFirst,
1207    };
1208    type Error = ::core::convert::Infallible;
1209
1210    fn from_bits(bits: u64) -> Result<Self, Self::Error> {
1211        Ok(ArpPattern { inner: bits as u32 })
1212    }
1213
1214    fn to_bits(&self) -> u64 {
1215        self.inner as u64
1216    }
1217}
1218
1219impl Debug for ArpPattern {
1220    /// The stored word in hex, which is what `--set` takes back.
1221    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1222        write!(f, "{:#010x}", self.inner)
1223    }
1224}
1225
1226impl Display for ArpPattern {
1227    /// The steps as a row: `1010 1010 ....` — a dot for a zero step.
1228    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1229        for (n, step) in self.steps().into_iter().enumerate() {
1230            if n > 0 && n % 4 == 0 {
1231                f.write_str(" ")?;
1232            }
1233            match step {
1234                0 => f.write_str(".")?,
1235                s => write!(f, "{s}")?,
1236            }
1237        }
1238        Ok(())
1239    }
1240}
1241
1242impl PartialEq<u32> for ArpPattern {
1243    fn eq(&self, other: &u32) -> bool {
1244        self.inner == *other
1245    }
1246}
1247
1248/// An opaque id into one of the instrument's libraries — a piano model, a sample.
1249///
1250/// The id is only meaningful against the library that holds it, so the type names which:
1251/// `LIBRARY` is a [`Library`] code, and the aliases below are the spellings to use.
1252/// The file carries the reference and nothing else, which is what
1253/// [`ControlKind::Reference`] tells a caller.
1254#[derive(Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
1255pub struct LibraryRefOf<const LIBRARY: u8> {
1256    inner: u32,
1257}
1258
1259impl<const LIBRARY: u8> LibraryRefOf<LIBRARY> {
1260    /// Which catalogue resolves this id.
1261    pub const LIBRARY: Library = Library::expect_code(LIBRARY);
1262
1263    /// The stored id. Zero is "nothing referenced" on every model in the corpus.
1264    pub fn id(&self) -> u32 {
1265        self.inner
1266    }
1267
1268    pub fn is_none(&self) -> bool {
1269        self.inner == 0
1270    }
1271}
1272
1273impl<const LIBRARY: u8> Packed for LibraryRefOf<LIBRARY> {
1274    const MAX_BITS: u32 = 32;
1275    const DECODE_BITS: u32 = u32::BITS;
1276    const CONTROL: ControlKind = ControlKind::Reference(Library::expect_code(LIBRARY));
1277    type Error = ::core::convert::Infallible;
1278
1279    fn from_bits(bits: u64) -> Result<Self, Self::Error> {
1280        Ok(LibraryRefOf { inner: bits as u32 })
1281    }
1282
1283    fn to_bits(&self) -> u64 {
1284        self.inner as u64
1285    }
1286}
1287
1288impl<const LIBRARY: u8> Debug for LibraryRefOf<LIBRARY> {
1289    /// Hex, matching how `nord program deps` reports the same id.
1290    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1291        write!(f, "{:#010x}", self.inner)
1292    }
1293}
1294
1295impl<const LIBRARY: u8> Display for LibraryRefOf<LIBRARY> {
1296    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1297        if self.is_none() {
1298            f.write_str("none")
1299        } else {
1300            write!(f, "{:#010x}", self.inner)
1301        }
1302    }
1303}
1304
1305impl<const LIBRARY: u8> PartialEq<u32> for LibraryRefOf<LIBRARY> {
1306    fn eq(&self, other: &u32) -> bool {
1307        self.inner == *other
1308    }
1309}
1310
1311/// An id into the piano library (`.npno`).
1312pub type PianoRef = LibraryRefOf<{ Library::Piano.code() }>;
1313
1314/// An id into the sample library (`.nsmp`).
1315pub type SampleRef = LibraryRefOf<{ Library::Sample.code() }>;
1316
1317switch!(
1318    /// Which of the two delay lines is running.
1319    ///
1320    /// Both Stage manuals give the pair by name: "There are two different delay modes, the
1321    /// normal ('non-analog') mode, and the Analog Mode … In Analog Mode the pitch of any
1322    /// sounding repeats is altered if the tempo is changed."
1323    DelayCharacter, Normal = "normal", Analog = "analog"
1324);
1325
1326switch!(
1327    /// How quickly the compressor recovers. Manual: "The FAST mode … makes the Compressor
1328    /// recover quicker after being triggered."
1329    CompressorResponse, Normal = "normal", Fast = "fast"
1330);
1331
1332switch!(
1333    /// The rotary speaker's rotor speed. Manual: "Switch between fast and slow rotor
1334    /// speeds."
1335    ///
1336    /// ⚠️ Stopped is not one of these — it is a separate flag, so neither field answers
1337    /// on its own.
1338    RotorSpeed, Slow = "slow", Fast = "fast"
1339);
1340
1341sparse_enum!(
1342    /// Which of the four keyboard zones a section occupies, as the Stage 3 and 4 store it.
1343    ///
1344    /// The Stage 3 byte-map docs give the table as an occupancy picture — `o---` is the
1345    /// leftmost zone alone, `oooo` the whole keyboard. The Stage 3's piano and synth
1346    /// zone slots hold only values inside this table, with `oooo` dominating, so all
1347    /// three sections share it. Inferred from specimens; not confirmed on hardware.
1348    ///
1349    /// Unexplained: Stage 4 specimens reach stored value 10. It decodes as `Unknown(10)`
1350    /// and survives verbatim.
1351    KbZone4, 4, {
1352        0 => V0, "o---";
1353        1 => V1, "-o--";
1354        2 => V2, "--o-";
1355        3 => V3, "---o";
1356        4 => V4, "oo--";
1357        5 => V5, "-oo-";
1358        6 => V6, "--oo";
1359        7 => V7, "ooo-";
1360        8 => V8, "-ooo";
1361        9 => V9, "oooo";
1362    }
1363);
1364
1365sparse_enum!(
1366    /// Which of the three keyboard zones a section occupies, as the Stage 2 stores it.
1367    ///
1368    /// The Stage 2 splits into two or three zones rather than four, and its panel spells
1369    /// them in words. From the `ns2-*-kb-zone` tables in the Stage byte-map docs.
1370    KbZone3, 3, {
1371        0 => Lo, "LO";
1372        1 => LoUp, "LO UP";
1373        2 => Up, "UP";
1374        3 => UpHi, "UP HI";
1375        4 => Hi, "HI";
1376        5 => LoUpHi, "LO UP HI";
1377    }
1378);
1379
1380sparse_enum!(
1381    /// A Stage split boundary, one of the ten notes the panel offers.
1382    ///
1383    /// The Stage 2 and 3 store the same ten-note table. Reported by public
1384    /// documentation; not confirmed on hardware.
1385    SplitNote, 4, {
1386        0 => F2, "F2";
1387        1 => C3, "C3";
1388        2 => F3, "F3";
1389        3 => C4, "C4";
1390        4 => F4, "F4";
1391        5 => C5, "C5";
1392        6 => F5, "F5";
1393        7 => C6, "C6";
1394        8 => F6, "F6";
1395        9 => C7, "C7";
1396    }
1397);
1398
1399sparse_enum!(
1400    /// A Stage 3 split crossfade width, in semitones.
1401    ///
1402    /// Reported by public documentation; not confirmed on hardware.
1403    SplitWidth, 2, {
1404        0 => One, "1";
1405        1 => Six, "6";
1406        2 => Twelve, "12";
1407    }
1408);
1409
1410sparse_enum!(
1411    /// The program category byte the Stage 2 and 3 keep in the header's `aux` word.
1412    ///
1413    /// Reported by public documentation; not confirmed on hardware.
1414    /// The gaps are real: no name is known for the values between these.
1415    ProgramCategory, 8, {
1416        0x00 => Acoustic, "Acoustic";
1417        0x01 => Bass, "Bass";
1418        0x02 => Wind, "Wind";
1419        0x04 => Fantasy, "Fantasy";
1420        0x05 => Fx, "FX";
1421        0x06 => Lead, "Lead";
1422        0x07 => Organ, "Organ";
1423        0x08 => Pad, "Pad";
1424        0x0a => Pluck, "Pluck";
1425        0x0b => String, "String";
1426        0x0c => Synth, "Synth";
1427        0x0d => Vocal, "Vocal";
1428        0x0e => User, "User";
1429        0x11 => None_, "None";
1430        0x15 => Grand, "Grand";
1431        0x16 => Upright, "Upright";
1432        0x17 => EPiano1, "EPiano1";
1433        0x18 => EPiano2, "EPiano2";
1434        0x1b => Clavinet, "Clavinet";
1435        0x1c => Harpsi, "Harpsi";
1436        0x1e => Arpeggio, "Arpeggio";
1437        0xff => Undefined, "Undefined";
1438    }
1439);
1440
1441impl ProgramCategory {
1442    /// The category a Stage 2 or 3 header names, or `None` where the `aux` word carries
1443    /// no category id at all or one too wide for this byte-sized table.
1444    ///
1445    /// The whole id is examined: a value above `0xff` names no category here rather than
1446    /// being truncated into one.
1447    pub fn of(header: &crate::cbin::Header) -> Option<ProgramCategory> {
1448        let id = u8::try_from(header.category()?).ok()?;
1449        match Self::from_bits(id as u64) {
1450            Ok(category) => Some(category),
1451            Err(never) => match never {},
1452        }
1453    }
1454}
1455
1456sparse_enum!(
1457    /// From the `ns2-effect-1-type` table in the Stage byte-map docs.
1458    Effect1Type, 3, {
1459        0 => APan, "A-Pan";
1460        1 => Trem, "Trem";
1461        2 => Rm, "RM";
1462        3 => WaWa, "WA-WA";
1463        4 => AWa1, "A-WA1";
1464        5 => AWa2, "A-WA2";
1465    }
1466);
1467
1468sparse_enum!(
1469    /// From the `ns2-effect-2-type` table in the Stage byte-map docs.
1470    Effect2Type, 3, {
1471        0 => Phas1, "PHAS1";
1472        1 => Phas2, "PHAS2";
1473        2 => Flang, "FLANG";
1474        3 => Vibe, "VIBE";
1475        4 => Chor1, "CHOR1";
1476        5 => Chor2, "CHOR2";
1477    }
1478);
1479
1480sparse_enum!(
1481    /// From the `ns2-reverb-type` table in the Stage byte-map docs.
1482    ReverbType, 3, {
1483        0 => Room1, "Room 1";
1484        1 => Room2, "Room 2";
1485        2 => Stage1, "Stage 1";
1486        3 => Stage2, "Stage 2";
1487        4 => Hall1, "Hall 1";
1488        5 => Hall2, "Hall 2";
1489    }
1490);
1491
1492#[cfg(test)]
1493mod tests {
1494    use super::*;
1495    use crate::fields::{ControlKind, Library, PackedOrder, Unit};
1496
1497    /// The whole point of the vocabulary: a field gets its control kind by choosing a
1498    /// type, so an interface never needs a table of field names of its own.
1499    #[test]
1500    fn a_type_says_what_kind_of_control_it_is() {
1501        assert_eq!(<Level as Packed>::CONTROL, ControlKind::Knob(Unit::Panel10));
1502        assert_eq!(
1503            <Time as Packed>::CONTROL,
1504            ControlKind::Knob(Unit::Milliseconds)
1505        );
1506        assert_eq!(
1507            <EqBand as Packed>::CONTROL,
1508            ControlKind::Bipolar(Unit::Decibels)
1509        );
1510        // The shape a caller needs to draw the control is on the kind: how many bars,
1511        // how many steps, which catalogue. What the *type* cannot know — which bar of
1512        // the register, which parameter a morph slot belongs to — is left open here and
1513        // filled in by `#[bitbody]` from the field's name.
1514        assert_eq!(
1515            <MorphTarget as Packed>::CONTROL,
1516            ControlKind::Morph { of: None }
1517        );
1518        assert_eq!(
1519            <Drawbar as Packed>::CONTROL,
1520            ControlKind::Drawbar {
1521                bars: 1,
1522                rank: None,
1523                bits_per_bar: 4,
1524                order: PackedOrder::HighFirst,
1525            }
1526        );
1527        // ⚠️ The two multi-value kinds pack from opposite ends, which is why each says
1528        // so: a pattern's first step is in the lowest bits and an Electro 5 register's
1529        // first bar is in the highest.
1530        assert_eq!(
1531            <ArpPattern as Packed>::CONTROL,
1532            ControlKind::Pattern {
1533                steps: 16,
1534                bits_per_step: 2,
1535                order: PackedOrder::LowFirst,
1536            }
1537        );
1538        assert_eq!(
1539            <PianoRef as Packed>::CONTROL,
1540            ControlKind::Reference(Library::Piano)
1541        );
1542        assert_eq!(
1543            <SampleRef as Packed>::CONTROL,
1544            ControlKind::Reference(Library::Sample)
1545        );
1546        assert_eq!(<KbZone4 as Packed>::CONTROL, ControlKind::Selector);
1547        assert_eq!(<bool as Packed>::CONTROL, ControlKind::Toggle);
1548        assert_eq!(
1549            <OctaveShiftNibble as Packed>::CONTROL,
1550            ControlKind::Shift(Unit::Octaves)
1551        );
1552        // The default, and the standing invitation to give a field a better type.
1553        assert_eq!(<u8 as Packed>::CONTROL, ControlKind::Number);
1554    }
1555
1556    /// A unit is a label, not a promise. Printing a millisecond reading off a curve no
1557    /// manual publishes would be inventing precision the file does not carry.
1558    #[test]
1559    fn a_unit_says_whether_it_can_be_computed() {
1560        assert!(Unit::Panel10.describes_a_known_transform());
1561        assert!(Unit::Decibels.describes_a_known_transform());
1562        assert!(!Unit::Milliseconds.describes_a_known_transform());
1563        assert!(!Unit::Hertz.describes_a_known_transform());
1564        // So the type prints the stored byte rather than a converted one.
1565        assert_eq!(Time::new(96).unwrap().to_string(), "96");
1566        assert_eq!(Level::new(96).unwrap().to_string(), "96 (7.6)");
1567    }
1568
1569    /// Stage 4 octave shift is two's complement; Stage 2 and 3 use biased values.
1570    #[test]
1571    fn the_stage4_octave_shift_wraps_where_the_others_bias() {
1572        let read = |bits| OctaveShiftNibble::from_bits(bits).unwrap().octaves();
1573        assert_eq!(read(0), 0);
1574        assert_eq!(read(1), 1);
1575        assert_eq!(read(2), 2);
1576        assert_eq!(read(15), -1);
1577        assert_eq!(read(14), -2);
1578        // Every pattern round-trips, so an unreached value rides through a re-encode.
1579        for bits in 0..16u64 {
1580            assert_eq!(OctaveShiftNibble::from_bits(bits).unwrap().to_bits(), bits);
1581        }
1582    }
1583
1584    /// Every pattern survives; the specimen mode at 127 is displayed as neutral.
1585    #[test]
1586    fn a_morph_slot_names_its_neutral_and_keeps_the_rest() {
1587        assert_eq!(MorphTarget::NEUTRAL, 127);
1588        let neutral = MorphTarget::from_bits(127).unwrap();
1589        assert!(neutral.is_neutral());
1590        assert_eq!(neutral.to_string(), "—");
1591        assert_eq!(format!("{neutral:?}"), "127");
1592
1593        let moved = MorphTarget::from_bits(254).unwrap();
1594        assert!(!moved.is_neutral());
1595        assert_eq!(moved.to_string(), "254");
1596        // The whole byte is in use, so nothing may be refused or clamped.
1597        for bits in 0..256u64 {
1598            assert_eq!(MorphTarget::from_bits(bits).unwrap().to_bits(), bits);
1599        }
1600    }
1601
1602    /// Sixteen two-bit steps decode lowest bits first.
1603    #[test]
1604    fn an_arp_pattern_reads_as_steps() {
1605        // Accent on every fourth step.
1606        let accent = ArpPattern::from_bits(0x0101_0101).unwrap();
1607        assert_eq!(
1608            accent.steps(),
1609            [1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0]
1610        );
1611        assert_eq!(accent.to_string(), "1... 1... 1... 1...");
1612
1613        // Four left, four right, four left — a pan row.
1614        let pan = ArpPattern::from_bits(0x55aa_5500).unwrap();
1615        assert_eq!(&pan.steps()[4..12], &[1, 1, 1, 1, 2, 2, 2, 2]);
1616
1617        assert!(ArpPattern::default().is_empty());
1618        // The slot is wider than the enumerable ceiling, so `--set` spells it in hex.
1619        assert_eq!(format!("{pan:?}"), "0x55aa5500");
1620    }
1621
1622    /// An equalizer band reads +/- 15 dB either side of the slot's centre.
1623    #[test]
1624    fn a_bipolar_band_reads_signed() {
1625        assert_eq!(EqBand::new(64).unwrap().reading(), 0.0);
1626        assert_eq!(EqBand::new(0).unwrap().to_string(), "0 (-15.0)");
1627        assert_eq!(EqBand::new(127).unwrap().to_string(), "127 (+15.0)");
1628        // `Debug` is the stored byte, so retyping a plain integer leaves field dumps alone.
1629        assert_eq!(format!("{:?}", EqBand::new(96).unwrap()), "96");
1630    }
1631
1632    /// The unit comes from the declaration, so a bipolar slot that is not a decibel
1633    /// reading does not claim to be one.
1634    #[test]
1635    fn a_bipolar_slot_carries_the_unit_it_was_declared_with() {
1636        assert_eq!(
1637            <EqBand as Packed>::CONTROL,
1638            ControlKind::Bipolar(Unit::Decibels)
1639        );
1640        assert_eq!(
1641            <Bipolar<10> as Packed>::CONTROL,
1642            ControlKind::Bipolar(Unit::None)
1643        );
1644        // ±10 of nothing is still ±10.
1645        assert_eq!(Bipolar::<10>::new(127).unwrap().reading(), 10.0);
1646    }
1647
1648    /// The code is only a way to carry a unit through a const generic, so it has to come
1649    /// back as the unit it went in as.
1650    #[test]
1651    fn a_unit_survives_the_code_that_carries_it() {
1652        for unit in [
1653            Unit::Panel10,
1654            Unit::Decibels,
1655            Unit::Milliseconds,
1656            Unit::Hertz,
1657            Unit::Bpm,
1658            Unit::ClockDivision,
1659            Unit::Semitones,
1660            Unit::Octaves,
1661            Unit::Pan,
1662            Unit::None,
1663        ] {
1664            assert_eq!(Unit::expect_code(unit.code()), unit, "{unit:?}");
1665        }
1666    }
1667
1668    /// A switch keeps its single bit and gives both states a word. `Debug` is the
1669    /// variant, which is what `--set` takes; `Display` is the panel's own wording.
1670    #[test]
1671    fn a_switch_names_both_of_its_states() {
1672        let normal = DelayCharacter::from_bits(0).unwrap();
1673        let analog = DelayCharacter::from_bits(1).unwrap();
1674        assert_eq!(format!("{normal:?}"), "Normal");
1675        assert_eq!(analog.to_string(), "analog");
1676        assert_eq!(analog.to_bits(), 1);
1677        assert!(analog.is_set());
1678        assert_eq!(<DelayCharacter as Packed>::MAX_BITS, 1);
1679    }
1680
1681    /// A drawbar is total over its nibble: a position past the bar's travel is preserved
1682    /// and reported as unnamed rather than refused.
1683    #[test]
1684    fn a_drawbar_keeps_a_nibble_past_its_travel() {
1685        assert_eq!(Drawbar::from_bits(8).unwrap().position(), Some(8));
1686        assert_eq!(Drawbar::from_bits(9).unwrap().position(), None);
1687        assert_eq!(Drawbar::from_bits(9).unwrap().raw(), 9);
1688        for bits in 0..16u64 {
1689            assert_eq!(Drawbar::from_bits(bits).unwrap().to_bits(), bits);
1690        }
1691    }
1692
1693    #[test]
1694    fn a_level_carries_the_panel_transform() {
1695        assert_eq!(Level::new(0).unwrap().to_string(), "0 (0.0)");
1696        assert_eq!(Level::new(127).unwrap().to_string(), "127 (10.0)");
1697        assert_eq!(Level::new(96).unwrap().to_string(), "96 (7.6)");
1698        assert!(Level::new(128).is_err(), "128 does not fit seven bits");
1699    }
1700}