nord-format 0.6.0

Read and write Nord keyboard files from Rust, byte for byte
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
//! The organ panel, `0x4e..=0x92`.
//!
//! 69 bytes of per-model, per-preset state: the instrument keeps the full drawbar and
//! vib/perc registration for every model and both presets, so switching model or preset
//! is lossless.
//!
//! Fields are storage; the methods below are meaning. Which block a model reads, whether
//! a Farfisa nibble counts as on, where the b3-bass bars really live — none of that is
//! expressible as a placement, so it lives in an accessor.

use crate::bits::Packed;
use crate::components::{Drawbar, PercSpeed, VibChorus};
use crate::error::ParseError;
use nord_bits_derive::bitbody;

use std::fmt::{self, Debug, Display, Formatter};

/// Length of the organ panel block, 0x4e..=0x92.
const ORGAN_LEN: usize = 0x92 - 0x4d;

/// The Electro 5's four organ models. (B3-bass shares the B3 storage slots, so
/// it isn't a separate model here.)
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OrganModel {
    B3,
    Vox,
    Farfisa,
    Pipe,
}

/// Which of a model's two stored registrations the instrument plays.
///
/// The panel numbers them 1 and 2; the file spells the choice as each model's
/// `…_preset2_selected` flag, which is what the conversions here are.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Preset {
    One,
    Two,
}

impl From<bool> for Preset {
    fn from(preset2_selected: bool) -> Preset {
        match preset2_selected {
            true => Preset::Two,
            false => Preset::One,
        }
    }
}

impl From<Preset> for bool {
    /// The `…_preset2_selected` flag as the file stores it.
    fn from(preset: Preset) -> bool {
        matches!(preset, Preset::Two)
    }
}

impl Display for Preset {
    /// The panel's own number.
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        f.write_str(match self {
            Preset::One => "1",
            Preset::Two => "2",
        })
    }
}

/// The organ panel: drawbar and vib/perc registration for every model and
/// both presets, stored in full so switching either is lossless.
#[bitbody(69)]
pub struct OrganPanel {
    // ── B3 ─────────────────────────────────────────────────────────────────────
    /// Shared across presets.
    #[bits(24..=26)]
    pub b3_vib: B3Vib,
    /// Percussion third harmonic, shared across presets.
    #[bits(27..=27)]
    pub b3_perc_third: bool,
    /// Shared across presets.
    #[bits(28..=29)]
    pub b3_perc_speed: B3PercSpeed,
    #[bits(41..=41)]
    pub b3_preset2_selected: bool,
    #[bits(56..=91)]
    pub b3_preset1_drawbars: Drawbars,
    #[bits(92..=92)]
    pub b3_preset1_vib: bool,
    #[bits(93..=93)]
    pub b3_preset1_perc: bool,
    /// First bass drawbar of **b3+bass preset 1** — see [`OrganPanel::b3_bass_drawbars`].
    /// It is not in the nine-nibble block, and the two nibbles it shadows there hold
    /// stale leftovers.
    #[bits(94..=97)]
    pub b3_bass_bar1: Drawbar,
    /// Second bass drawbar of b3+bass preset 1. The four bits after it are unused.
    #[bits(98..=101)]
    pub b3_bass_bar2: Drawbar,
    #[bits(112..=147)]
    pub b3_preset2_drawbars: Drawbars,
    #[bits(148..=148)]
    pub b3_preset2_vib: bool,
    #[bits(149..=149)]
    pub b3_preset2_perc: bool,
    // Bits 150..=157 survive panel stores but do not affect preset-2 bass, and are
    // left unclaimed so they round-trip verbatim. Confirmed on hardware.

    // ── Vox ────────────────────────────────────────────────────────────────────
    /// Shared across presets.
    #[bits(168..=170)]
    pub vox_vib: VoxVib,
    #[bits(185..=185)]
    pub vox_preset2_selected: bool,
    #[bits(200..=235)]
    pub vox_preset1_drawbars: Drawbars,
    #[bits(236..=236)]
    pub vox_preset1_vib: bool,
    #[bits(248..=283)]
    pub vox_preset2_drawbars: Drawbars,
    #[bits(284..=284)]
    pub vox_preset2_vib: bool,

    // ── Farfisa ────────────────────────────────────────────────────────────────
    /// Shared across presets.
    #[bits(296..=298)]
    pub farfisa_vib: FarfisaVib,
    #[bits(313..=313)]
    pub farfisa_preset2_selected: bool,
    /// Stored as positions, read by the instrument as tabs — see
    /// [`OrganPanel::farfisa_tabs`].
    #[bits(328..=363)]
    pub farfisa_preset1_drawbars: Drawbars,
    #[bits(364..=364)]
    pub farfisa_preset1_vib: bool,
    #[bits(376..=411)]
    pub farfisa_preset2_drawbars: Drawbars,
    #[bits(412..=412)]
    pub farfisa_preset2_vib: bool,

    // Pipe has no vibrato or percussion. Bit 492 survives panel stores but is
    // unreachable while Pipe is selected, and is left unclaimed. Confirmed on hardware.
    #[bits(441..=441)]
    pub pipe_preset2_selected: bool,
    #[bits(456..=491)]
    pub pipe_preset1_drawbars: Drawbars,
    #[bits(504..=539)]
    pub pipe_preset2_drawbars: Drawbars,
}

/// `[u8; 69]` has no `Default` — the std impls stop at 32 — so this one goes through the
/// decode, which every organ field is total over.
impl Default for OrganPanel {
    fn default() -> Self {
        OrganPanel::try_from([0; ORGAN_LEN]).expect("every organ field decodes totally")
    }
}

impl OrganPanel {
    /// The registration `model` is playing.
    pub fn preset(&self, model: OrganModel) -> Preset {
        (*self.preset_selected(model)).into()
    }

    /// The nine drawbar positions (physical, 0..=8) stored for `model`'s `preset`. This
    /// is the on-disk value; per-model display transforms (Farfisa on/off, Vox's ignored
    /// 8th bar, B3-bass bass-bar remap) are not applied.
    pub fn drawbars(&self, model: OrganModel, preset: Preset) -> [u8; 9] {
        self.drawbar_block(model, preset).positions()
    }

    /// The two bass drawbars of **B3-with-bass, preset 1** — the bass manual.
    ///
    /// These are *not* in the nine-nibble block. In b3+bass mode preset 1 is the bass
    /// manual (only bars 1–2 are live) and preset 2 is the ordinary B3; the bass
    /// registration sits in its own four-bit pair after the block, which is why it does
    /// not move when the drawbars do.
    ///
    /// ⚠️ Do **not** read bars 1–2 from [`Self::drawbars`] in this mode — those two
    /// nibbles hold stale leftovers, not zero and not the bass values.
    ///
    /// Confirmed on hardware. The captures are `1100_400000000` and `1100_040000000`.
    pub fn b3_bass_drawbars(&self) -> [u8; 2] {
        [self.b3_bass_bar1.raw(), self.b3_bass_bar2.raw()]
    }

    /// Farfisa drawbars as the instrument actually treats them: **on/off tabs**, not
    /// continuous positions.
    ///
    /// A stored nibble of **≥5 reads as on**, anything lower as off. Use this rather
    /// than [`Self::drawbars`] for Farfisa — the raw 0..=8 value is stored faithfully
    /// but has no meaning beyond which side of the threshold it falls.
    pub fn farfisa_tabs(&self, preset: Preset) -> [bool; 9] {
        self.drawbars(OrganModel::Farfisa, preset)
            .map(|bar| bar >= 5)
    }

    /// Whether vibrato/chorus is on for `model`'s `preset`. Pipe has none.
    pub fn vib_on(&self, model: OrganModel, preset: Preset) -> bool {
        self.vib_flag(model, preset).is_some_and(|on| *on)
    }

    /// The vibrato/chorus mode selected for `model` (shared across presets), or `None`
    /// for Pipe and for an index the model does not use. Each model offers a different
    /// subset of the six modes at a different index, so the stored value is only
    /// meaningful alongside the model.
    pub fn vib_type(&self, model: OrganModel) -> Option<VibChorus> {
        match model {
            OrganModel::B3 => self.b3_vib.get(),
            OrganModel::Vox => self.vox_vib.get(),
            OrganModel::Farfisa => self.farfisa_vib.get(),
            OrganModel::Pipe => None,
        }
    }

    /// Whether B3 percussion is on for `preset` (B3 only).
    pub fn b3_perc_on(&self, preset: Preset) -> bool {
        match preset {
            Preset::One => self.b3_preset1_perc,
            Preset::Two => self.b3_preset2_perc,
        }
    }

    /// Whether B3 percussion uses the third harmonic (shared across presets).
    pub fn b3_perc_third(&self) -> bool {
        self.b3_perc_third
    }

    /// B3 percussion decay speed (shared across presets). The on-disk encoding is not
    /// monotonic — soft, fast and both store 2, 1 and 3.
    pub fn b3_perc_speed(&self) -> PercSpeed {
        self.b3_perc_speed
            .get()
            .expect("all four two-bit indices are named")
    }

    // ── writes ──────────────────────────────────────────────────────────────────

    /// Play `preset` on `model`.
    pub fn set_preset(&mut self, model: OrganModel, preset: Preset) {
        *self.preset_selected_mut(model) = preset.into();
    }

    /// Store nine drawbar positions, `0..=8`. A higher one is refused rather than
    /// truncated, since two bars share a byte.
    pub fn set_drawbars(
        &mut self,
        model: OrganModel,
        preset: Preset,
        bars: [u8; 9],
    ) -> Result<(), ParseError> {
        *self.drawbar_block_mut(model, preset) = Drawbars::new(bars)?;
        Ok(())
    }

    /// Set the Farfisa tabs: on stores `8`, off stores `0`. Any other stored value is
    /// lost — the instrument only reads which side of the ≥5 threshold it falls on, but
    /// the byte does change, so this will not round-trip a program you only meant to
    /// read.
    pub fn set_farfisa_tabs(&mut self, preset: Preset, tabs: [bool; 9]) {
        let bars = tabs.map(|on| if on { Drawbars::MAX } else { 0 });
        self.set_drawbars(OrganModel::Farfisa, preset, bars)
            .expect("0 and 8 are both in range");
    }

    /// Turn vibrato/chorus on or off for `model`'s `preset`. No-op for Pipe, which has
    /// none.
    pub fn set_vib_on(&mut self, model: OrganModel, preset: Preset, on: bool) {
        if let Some(flag) = self.vib_flag_mut(model, preset) {
            *flag = on;
        }
    }

    /// Select the vibrato/chorus mode for `model`, shared across presets. Fails for a
    /// mode the model does not offer; Pipe has none.
    pub fn set_vib_type(&mut self, model: OrganModel, vib: VibChorus) -> Result<(), ParseError> {
        let refuse = |why: &str| ParseError::OutOfBounds {
            value: format!("{vib:?}"),
            bound: format!("{model:?} {why}"),
        };
        match model {
            OrganModel::B3 => {
                self.b3_vib = B3Vib::select(vib).ok_or_else(|| refuse("does not offer it"))?
            }
            OrganModel::Vox => {
                self.vox_vib = VoxVib::select(vib).ok_or_else(|| refuse("does not offer it"))?
            }
            OrganModel::Farfisa => {
                self.farfisa_vib =
                    FarfisaVib::select(vib).ok_or_else(|| refuse("does not offer it"))?
            }
            OrganModel::Pipe => return Err(refuse("has no vibrato/chorus")),
        }
        Ok(())
    }

    /// Turn B3 percussion on or off for `preset`.
    pub fn set_b3_perc_on(&mut self, preset: Preset, on: bool) {
        *match preset {
            Preset::One => &mut self.b3_preset1_perc,
            Preset::Two => &mut self.b3_preset2_perc,
        } = on;
    }

    /// Percussion third harmonic (shared across presets).
    pub fn set_b3_perc_third(&mut self, on: bool) {
        self.b3_perc_third = on;
    }

    /// Percussion decay speed (shared across presets). Note the encoding is not
    /// monotonic — see [`Self::b3_perc_speed`].
    pub fn set_b3_perc_speed(&mut self, speed: PercSpeed) {
        self.b3_perc_speed =
            B3PercSpeed::select(speed).expect("all four speeds have a two-bit index");
    }

    /// Set the two bass drawbars of b3+bass preset 1, `0..=8`.
    pub fn set_b3_bass_drawbars(&mut self, bars: [u8; 2]) -> Result<(), ParseError> {
        self.b3_bass_bar1 = Drawbar::new(bars[0])?;
        self.b3_bass_bar2 = Drawbar::new(bars[1])?;
        Ok(())
    }

    // ── which field a model and preset name ─────────────────────────────────────

    fn drawbar_block(&self, model: OrganModel, preset: Preset) -> &Drawbars {
        match (model, preset) {
            (OrganModel::B3, Preset::One) => &self.b3_preset1_drawbars,
            (OrganModel::B3, Preset::Two) => &self.b3_preset2_drawbars,
            (OrganModel::Vox, Preset::One) => &self.vox_preset1_drawbars,
            (OrganModel::Vox, Preset::Two) => &self.vox_preset2_drawbars,
            (OrganModel::Farfisa, Preset::One) => &self.farfisa_preset1_drawbars,
            (OrganModel::Farfisa, Preset::Two) => &self.farfisa_preset2_drawbars,
            (OrganModel::Pipe, Preset::One) => &self.pipe_preset1_drawbars,
            (OrganModel::Pipe, Preset::Two) => &self.pipe_preset2_drawbars,
        }
    }

    fn drawbar_block_mut(&mut self, model: OrganModel, preset: Preset) -> &mut Drawbars {
        match (model, preset) {
            (OrganModel::B3, Preset::One) => &mut self.b3_preset1_drawbars,
            (OrganModel::B3, Preset::Two) => &mut self.b3_preset2_drawbars,
            (OrganModel::Vox, Preset::One) => &mut self.vox_preset1_drawbars,
            (OrganModel::Vox, Preset::Two) => &mut self.vox_preset2_drawbars,
            (OrganModel::Farfisa, Preset::One) => &mut self.farfisa_preset1_drawbars,
            (OrganModel::Farfisa, Preset::Two) => &mut self.farfisa_preset2_drawbars,
            (OrganModel::Pipe, Preset::One) => &mut self.pipe_preset1_drawbars,
            (OrganModel::Pipe, Preset::Two) => &mut self.pipe_preset2_drawbars,
        }
    }

    fn preset_selected(&self, model: OrganModel) -> &bool {
        match model {
            OrganModel::B3 => &self.b3_preset2_selected,
            OrganModel::Vox => &self.vox_preset2_selected,
            OrganModel::Farfisa => &self.farfisa_preset2_selected,
            OrganModel::Pipe => &self.pipe_preset2_selected,
        }
    }

    fn preset_selected_mut(&mut self, model: OrganModel) -> &mut bool {
        match model {
            OrganModel::B3 => &mut self.b3_preset2_selected,
            OrganModel::Vox => &mut self.vox_preset2_selected,
            OrganModel::Farfisa => &mut self.farfisa_preset2_selected,
            OrganModel::Pipe => &mut self.pipe_preset2_selected,
        }
    }

    fn vib_flag(&self, model: OrganModel, preset: Preset) -> Option<&bool> {
        Some(match (model, preset) {
            (OrganModel::B3, Preset::One) => &self.b3_preset1_vib,
            (OrganModel::B3, Preset::Two) => &self.b3_preset2_vib,
            (OrganModel::Vox, Preset::One) => &self.vox_preset1_vib,
            (OrganModel::Vox, Preset::Two) => &self.vox_preset2_vib,
            (OrganModel::Farfisa, Preset::One) => &self.farfisa_preset1_vib,
            (OrganModel::Farfisa, Preset::Two) => &self.farfisa_preset2_vib,
            (OrganModel::Pipe, _) => return None,
        })
    }

    fn vib_flag_mut(&mut self, model: OrganModel, preset: Preset) -> Option<&mut bool> {
        Some(match (model, preset) {
            (OrganModel::B3, Preset::One) => &mut self.b3_preset1_vib,
            (OrganModel::B3, Preset::Two) => &mut self.b3_preset2_vib,
            (OrganModel::Vox, Preset::One) => &mut self.vox_preset1_vib,
            (OrganModel::Vox, Preset::Two) => &mut self.vox_preset2_vib,
            (OrganModel::Farfisa, Preset::One) => &mut self.farfisa_preset1_vib,
            (OrganModel::Farfisa, Preset::Two) => &mut self.farfisa_preset2_vib,
            (OrganModel::Pipe, _) => return None,
        })
    }
}
/// Declare a model's index into a shared enumeration.
///
/// The slot holds an index, not the value: which modes an organ offers, and in what
/// order, differs per model. An index the model does not use decodes to `None` rather
/// than being coerced to a neighbor, and round-trips whatever it held.
macro_rules! model_index {
    ($(#[$meta:meta])* $name:ident, $bits:expr, $of:ty, [$($variant:ident),+ $(,)?]) => {
        $(#[$meta])*
        #[derive(Copy, Clone, Default, PartialEq, Eq)]
        pub struct $name(u8);

        impl $name {
            /// What this model offers at the stored index, or `None` if it offers
            /// nothing there.
            pub fn get(&self) -> Option<$of> {
                Self::TABLE.get(self.0 as usize).copied()
            }

            /// The index this model stores `value` at, or `None` if it does not offer it.
            pub fn select(value: $of) -> Option<Self> {
                Self::TABLE.iter().position(|&v| v == value).map(|i| $name(i as u8))
            }

            /// The stored index, named or not.
            pub fn raw(&self) -> u8 {
                self.0
            }

            const TABLE: &'static [$of] = &[$(<$of>::$variant),+];
        }

        impl Packed for $name {
            const MAX_BITS: u32 = $bits;
            const DECODE_BITS: u32 = u8::BITS;
            const CONTROL: $crate::fields::ControlKind = $crate::fields::ControlKind::Selector;
            type Error = ::core::convert::Infallible;

            fn from_bits(bits: u64) -> Result<Self, Self::Error> {
                Ok($name(bits as u8))
            }

            fn to_bits(&self) -> u64 {
                self.0 as u64
            }
        }

        impl Debug for $name {
            fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
                match self.get() {
                    Some(value) => write!(f, "{value:?}"),
                    None => write!(f, "unknown ({})", self.0),
                }
            }
        }

        impl Display for $name {
            fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
                write!(f, "{self:?}")
            }
        }
    };
}

model_index!(
    /// The B3's vibrato/chorus selection. It offers all six modes.
    B3Vib, 3, VibChorus, [V1, C1, V2, C2, V3, C3]
);

model_index!(
    /// The Vox's vibrato selection: three depths, no chorus.
    VoxVib, 3, VibChorus, [V1, V2, V3]
);

model_index!(
    /// The Farfisa's vibrato/chorus selection.
    FarfisaVib, 3, VibChorus, [V1, V2, C2, C3]
);

model_index!(
    /// The B3's percussion decay speed. The stored order is not the panel's: soft, fast
    /// and both are 2, 1 and 3.
    B3PercSpeed, 2, PercSpeed, [Off, Fast, Soft, Both]
);

/// Nine drawbar positions, nibble-packed high-nibble first — the on-disk form every
/// organ model shares.
///
/// Positions are physical, `0..=8`, stored identity. Decoding is total: a nibble outside
/// that range is preserved rather than refused. [`Drawbars::new`] refuses one on the way
/// in.
///
/// Per-model display transforms — Farfisa's on/off threshold, Vox's ignored 8th bar,
/// the b3-bass remap — are not applied here.
#[derive(Copy, Clone, Default, PartialEq, Eq)]
pub struct Drawbars([u8; 9]);

impl Drawbars {
    /// The number of bars, and so the nibbles this occupies.
    pub const BARS: usize = 9;
    /// The highest position a drawbar can be pulled to.
    pub const MAX: u8 = 8;

    /// Nine positions, each `0..=8`.
    pub fn new(bars: [u8; Self::BARS]) -> Result<Self, ParseError> {
        match bars.iter().find(|&&b| b > Self::MAX) {
            Some(&bad) => Err(ParseError::OutOfBounds {
                value: format!("{bad}"),
                bound: format!("0..={}", Self::MAX),
            }),
            None => Ok(Drawbars(bars)),
        }
    }

    /// The nine positions as stored.
    pub fn positions(&self) -> [u8; Self::BARS] {
        self.0
    }
}

impl Packed for Drawbars {
    const MAX_BITS: u32 = 4 * Drawbars::BARS as u32;
    const DECODE_BITS: u32 = Self::MAX_BITS;
    /// The whole register in one field, so the first bar is the leftmost one and the
    /// nibbles run down from the top of the slot.
    const CONTROL: crate::fields::ControlKind = crate::fields::ControlKind::Drawbar {
        bars: Drawbars::BARS as u8,
        rank: Some(1),
        bits_per_bar: (Self::MAX_BITS / Drawbars::BARS as u32) as u8,
        order: crate::fields::PackedOrder::HighFirst,
    };
    type Error = ::core::convert::Infallible;

    fn from_bits(bits: u64) -> Result<Self, Self::Error> {
        Ok(Drawbars(std::array::from_fn(|n| {
            let shift = Self::MAX_BITS - 4 * (n as u32 + 1);
            ((bits >> shift) & 0xf) as u8
        })))
    }

    fn to_bits(&self) -> u64 {
        self.0
            .iter()
            .fold(0, |bits, &bar| (bits << 4) | (bar as u64 & 0xf))
    }
}

impl Debug for Drawbars {
    /// The positions alone, so a panel's `Debug` reads as the array it is.
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        write!(f, "{:?}", self.0)
    }
}

impl Display for Drawbars {
    /// `888000000` — the form the corpus filenames use.
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        for bar in self.0 {
            write!(f, "{bar}")?;
        }
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    /// Panel-relative index of the byte at absolute Electro 5 file offset `abs`.
    const fn org(abs: usize) -> usize {
        abs - 0x4e
    }

    /// Build an organ panel from `(absolute offset, byte)` pairs; everything else 0.
    fn panel(bytes: &[(usize, u8)]) -> OrganPanel {
        let mut raw = [0u8; ORGAN_LEN];
        for &(at, b) in bytes {
            raw[org(at)] = b;
        }
        OrganPanel::try_from(raw).expect("every organ field decodes totally")
    }

    /// The bass drawbars of b3+bass preset 1 live outside the nine-nibble block, in a
    /// 12-bit field across `0x59`'s low nibble and `0x5a`. Values are from real
    /// specimens: `1100_400000000`, `1100_040000000`, `1100_87gfedcba`.
    #[test]
    fn b3_bass_drawbars_decode_from_the_packed_field() {
        // bar1 = 4, bar2 = 0  ->  field 0x100
        assert_eq!(
            panel(&[(0x59, 0x01), (0x5a, 0x00)]).b3_bass_drawbars(),
            [4, 0]
        );
        // bar1 = 0, bar2 = 4  ->  field 0x010
        assert_eq!(
            panel(&[(0x59, 0x00), (0x5a, 0x10)]).b3_bass_drawbars(),
            [0, 4]
        );
        // bar1 = 8, bar2 = 7  ->  field 0x21c
        assert_eq!(
            panel(&[(0x59, 0x02), (0x5a, 0x1c)]).b3_bass_drawbars(),
            [8, 7]
        );
        // bar1 = 8, bar2 = 8  ->  field 0x220
        assert_eq!(
            panel(&[(0x59, 0x02), (0x5a, 0x20)]).b3_bass_drawbars(),
            [8, 8]
        );
        // all down
        assert_eq!(panel(&[]).b3_bass_drawbars(), [0, 0]);
    }

    /// `0x59` is shared. Its high nibble is bar 9 of the main block and bits 3/2 are
    /// vibrato/percussion — none of which may leak into the bass drawbars. Regression
    /// guard for the placement.
    #[test]
    fn b3_bass_drawbars_ignore_the_flags_sharing_that_byte() {
        // Same field as `1100_88iiiiiii`: bar 9 = 8 in the high nibble, bars still 8,8.
        assert_eq!(
            panel(&[(0x59, 0x82), (0x5a, 0x20)]).b3_bass_drawbars(),
            [8, 8]
        );
        // Vibrato (0x08) and percussion (0x04) on must not disturb the reading.
        assert_eq!(
            panel(&[(0x59, 0x0e), (0x5a, 0x20)]).b3_bass_drawbars(),
            [8, 8]
        );
        assert_eq!(
            panel(&[(0x59, 0xfe), (0x5a, 0x20)]).b3_bass_drawbars(),
            [8, 8]
        );
    }

    /// Farfisa's drawbars are on/off tabs: >= 5 is on. The raw nibble is still stored
    /// faithfully, it just carries no meaning beyond the threshold.
    #[test]
    fn farfisa_drawbars_are_on_off_tabs() {
        // 0x77 is Farfisa preset 1: nine nibbles, high-nibble first.
        // bars 0..8 = 8,7,6,5,4,3,2,1,0 -> on for >= 5.
        let p = panel(&[
            (0x77, 0x87),
            (0x78, 0x65),
            (0x79, 0x43),
            (0x7a, 0x21),
            (0x7b, 0x00),
        ]);
        assert_eq!(
            p.drawbars(OrganModel::Farfisa, Preset::One),
            [8, 7, 6, 5, 4, 3, 2, 1, 0]
        );
        assert_eq!(
            p.farfisa_tabs(Preset::One),
            [true, true, true, true, false, false, false, false, false]
        );
        // The threshold sits between 4 and 5.
        let edge = panel(&[
            (0x77, 0x54),
            (0x78, 0x00),
            (0x79, 0x00),
            (0x7a, 0x00),
            (0x7b, 0x00),
        ]);
        assert!(edge.farfisa_tabs(Preset::One)[0], "5 should read as on");
        assert!(!edge.farfisa_tabs(Preset::One)[1], "4 should read as off");
    }

    /// Preset 2 reads from its own block, so the two presets never alias.
    #[test]
    fn farfisa_presets_are_independent() {
        let p = panel(&[(0x77, 0x80), (0x7d, 0x08)]);
        assert!(p.farfisa_tabs(Preset::One)[0]);
        assert!(!p.farfisa_tabs(Preset::One)[1]);
        assert!(!p.farfisa_tabs(Preset::Two)[0]);
        assert!(p.farfisa_tabs(Preset::Two)[1]);
    }

    /// Every model and preset reads its own nine nibbles and writes them back where it
    /// found them — no placement lands on a neighbor's block.
    #[test]
    fn every_model_and_preset_has_its_own_block() {
        for (n, (model, preset)) in [
            OrganModel::B3,
            OrganModel::Vox,
            OrganModel::Farfisa,
            OrganModel::Pipe,
        ]
        .into_iter()
        .flat_map(|m| [(m, Preset::One), (m, Preset::Two)])
        .enumerate()
        {
            let bars = [(n as u8) % 9; 9];
            let mut p = OrganPanel::default();
            p.set_drawbars(model, preset, bars).unwrap();

            let raw = <[u8; ORGAN_LEN]>::from(&p);
            let back = OrganPanel::try_from(raw).unwrap();
            assert_eq!(
                back.drawbars(model, preset),
                bars,
                "{model:?} preset {preset}"
            );

            let others: Vec<_> = [
                OrganModel::B3,
                OrganModel::Vox,
                OrganModel::Farfisa,
                OrganModel::Pipe,
            ]
            .into_iter()
            .flat_map(|m| [(m, Preset::One), (m, Preset::Two)])
            .filter(|&(m, p)| !(m == model && p == preset))
            .filter(|&(m, p)| back.drawbars(m, p) != [0; 9])
            .collect();
            assert!(
                others.is_empty(),
                "{model:?} preset {preset} also wrote {others:?}"
            );
        }
    }

    /// A mode the model does not offer is refused rather than stored at some free index.
    #[test]
    fn a_model_only_accepts_the_modes_it_has() {
        let mut p = OrganPanel::default();
        p.set_vib_type(OrganModel::Vox, VibChorus::V3).unwrap();
        assert_eq!(p.vib_type(OrganModel::Vox), Some(VibChorus::V3));
        assert!(p.set_vib_type(OrganModel::Vox, VibChorus::C1).is_err());
        assert!(p.set_vib_type(OrganModel::Pipe, VibChorus::V1).is_err());

        p.set_vib_type(OrganModel::B3, VibChorus::C3).unwrap();
        assert_eq!(p.vib_type(OrganModel::B3), Some(VibChorus::C3));
        // The Vox selection is at the same index in its own table and must not move.
        assert_eq!(p.vib_type(OrganModel::Vox), Some(VibChorus::V3));
    }

    /// The two speed bits are not in panel order.
    #[test]
    fn perc_speed_stores_soft_fast_and_both_as_2_1_and_3() {
        let bits = |speed| {
            let mut p = OrganPanel::default();
            p.set_b3_perc_speed(speed);
            (<[u8; ORGAN_LEN]>::from(&p)[org(0x51)] >> 2) & 0b11
        };
        assert_eq!(bits(PercSpeed::Off), 0);
        assert_eq!(bits(PercSpeed::Fast), 1);
        assert_eq!(bits(PercSpeed::Soft), 2);
        assert_eq!(bits(PercSpeed::Both), 3);

        let mut p = OrganPanel::default();
        p.set_b3_perc_speed(PercSpeed::Soft);
        assert_eq!(p.b3_perc_speed(), PercSpeed::Soft);
    }
}