nord_format/fields.rs
1//! Field-level introspection over a `#[bitbody]`'s fields.
2//!
3//! Generated registries let callers inspect and edit declared fields without a
4//! second, manually synchronized list of names.
5
6use std::fmt::{self, Debug, Display, Formatter};
7
8use crate::bits::Packed;
9
10/// One decoded field of a panel.
11#[derive(Debug, Clone, PartialEq, Eq)]
12pub struct FieldValue {
13 /// The field's full registry path, e.g. `center_panel.transpose`.
14 pub name: String,
15 /// Where the bits sit, as `LO..=HI` over the declaring body's bytes.
16 pub placement: &'static str,
17 /// The field's bits as they were *read*, shifted down to bit 0. Carries no type, so
18 /// it stays comparable across a retype.
19 pub raw: u64,
20 /// The bits the field's current value would *write*.
21 ///
22 /// Equal to [`raw`](Self::raw) on a panel decoded from bytes and not edited since —
23 /// decode and encode are inverses — so the two diverging is exactly the set of
24 /// pending changes. A `Default`-built panel has all-zero raw bytes, so every default
25 /// that encodes non-zero reads as pending.
26 pub bits: u64,
27 /// The decoded value's `Debug` rendering.
28 pub value: String,
29}
30
31impl Display for FieldValue {
32 /// `lower_part 0..=2 raw 0 Organ`
33 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
34 write!(
35 f,
36 "{:<22} {:<12} raw {:<11} {}",
37 self.name, self.placement, self.raw, self.value
38 )
39 }
40}
41
42/// What a field is, without an instance of the panel to read it from.
43#[derive(Clone)]
44pub struct FieldSpec {
45 /// The field's full registry path, e.g. `center_panel.transpose`.
46 pub name: String,
47 pub placement: &'static str,
48 /// Width of the field in bits.
49 pub width: u32,
50 /// Every value the field's type accepts, rendered as `set_field` spells them. Empty for a field too wide to enumerate — see [`ENUMERABLE_BITS`].
51 pub legal: fn() -> Vec<String>,
52 /// Which panel control this field is, from its type's
53 /// [`CONTROL`](crate::bits::Packed::CONTROL).
54 pub control: ControlKind,
55}
56
57impl FieldSpec {
58 /// The full path of the parameter this field morphs, for a [`ControlKind::Morph`]
59 /// that names one.
60 ///
61 /// The kind carries the parent's *sibling name*, since that is all the declaring body
62 /// knows; the path is this field's path with its last segment replaced, so a nested
63 /// body's prefix rides along.
64 pub fn morph_parent(&self) -> Option<String> {
65 let ControlKind::Morph { of: Some(parent) } = self.control else {
66 return None;
67 };
68 Some(match self.name.rsplit_once('.') {
69 Some((prefix, _)) => format!("{prefix}.{parent}"),
70 None => parent.to_string(),
71 })
72 }
73}
74
75/// What the panel puts under a reader's finger.
76///
77/// The registry already says where a field sits and which values it takes; this says what
78/// *kind* of thing it is, so a caller can choose a widget without a table of field names
79/// beside it. It comes from the field's type, so a field gets it right by being declared
80/// with the type that matches the control — a `bool` is a button, a [`Level`] is a knob.
81///
82/// [`Level`]: crate::components::Level
83#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
84pub enum ControlKind {
85 /// A two-state button. Its two states may have names — see the field's `legal` values.
86 Toggle,
87 /// A selector over a fixed set of named values.
88 Selector,
89 /// A continuous knob or slider, reading in `unit`.
90 Knob(Unit),
91 /// A knob whose musical zero is its centre, reading in `unit` either side.
92 Bipolar(Unit),
93 /// One or more drawbars, each `0..=8`, drawn as bars rather than numbers.
94 Drawbar {
95 /// How many bars the field holds, in register order. The Stage models give each
96 /// bar its own field and the Electro 5 packs a whole register into one, so this
97 /// is what tells a caller which it is holding.
98 bars: u8,
99 /// Where the field's **first** bar sits in the register: 1 is the leftmost bar,
100 /// 9 the rightmost of a nine-bar manual. A whole register starts at 1 and a
101 /// single Stage bar carries its own position.
102 ///
103 /// ⚠️ A position, not a pitch. Which harmonic each position draws is the organ
104 /// model's business — the B3's 16'/5⅓'/8' series is not the Vox's or the
105 /// Farfisa's, and the same nine positions serve all of them here — so labelling
106 /// them is for a caller that knows which model the field belongs to.
107 ///
108 /// `None` where the declaration does not place the bar in a register at all: the
109 /// Electro 5's bass manual, whose two bars nothing establishes the position of.
110 rank: Option<u8>,
111 /// Bits one bar occupies.
112 bits_per_bar: u8,
113 /// Which end of the field the first bar sits at. Only meaningful above one bar,
114 /// and the reason it is here: the Electro 5 packs its nine nibbles high-first
115 /// while the arpeggiator packs its steps low-first, so a caller reading one by
116 /// the other's convention draws the register mirrored.
117 order: PackedOrder,
118 },
119 /// The value a performance control morphs its parent parameter *to*. Belongs on that
120 /// parent's control, not on one of its own.
121 Morph {
122 /// The parent parameter's field name, as a sibling of this field — the full path
123 /// is this field's path with its last segment replaced, which is what
124 /// [`FieldSpec::morph_parent`] does.
125 ///
126 /// `None` where the body declares no parameter under the name this slot's own
127 /// name implies, so the slot stands alone until one is placed beside it.
128 of: Option<&'static str>,
129 },
130 /// A per-step pattern grid: `steps` steps of `bits_per_step` bits, the first step at
131 /// the `order` end.
132 Pattern {
133 steps: u8,
134 bits_per_step: u8,
135 order: PackedOrder,
136 },
137 /// An opaque id into one of the instrument's libraries.
138 Reference(Library),
139 /// A signed shift, reading in `unit`.
140 Shift(Unit),
141 /// An integer nothing has been claimed about — the default, and a standing invitation
142 /// to give the field a type that says more.
143 Number,
144}
145
146impl ControlKind {
147 /// Name the parent a morph slot morphs — the sibling field, not a path.
148 ///
149 /// `#[bitbody]` applies this from the field's own name, and only where the body
150 /// really declares that sibling. Every other kind is returned unchanged, so a field
151 /// named like a morph slot but typed as something else keeps what its type said.
152 pub const fn morphing(self, parent: &'static str) -> ControlKind {
153 match self {
154 ControlKind::Morph { .. } => ControlKind::Morph { of: Some(parent) },
155 other => other,
156 }
157 }
158
159 /// Place a drawbar in its register: `rank` 1 is the leftmost bar.
160 ///
161 /// Applied by `#[bitbody]` from a `…_N` field name, and ignored by every other kind
162 /// — a field whose name happens to end in a digit is not a drawbar unless its type
163 /// says so.
164 pub const fn ranked(self, rank: u8) -> ControlKind {
165 match self {
166 ControlKind::Drawbar {
167 bars,
168 bits_per_bar,
169 order,
170 ..
171 } => ControlKind::Drawbar {
172 bars,
173 rank: Some(rank),
174 bits_per_bar,
175 order,
176 },
177 other => other,
178 }
179 }
180}
181
182/// Which end of a field the first of its packed values sits at.
183///
184/// Only a field holding several values in one slot needs it — a drawbar register, a
185/// pattern row — and such a field cannot be drawn without it: read from the wrong end,
186/// the register comes out mirrored and looks like a plausible registration.
187#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
188pub enum PackedOrder {
189 /// The first value occupies the most significant bits.
190 HighFirst,
191 /// The first value occupies the least significant bits.
192 LowFirst,
193}
194
195/// One of the instrument's stored libraries — what a [`ControlKind::Reference`] id is an
196/// id *into*.
197///
198/// A file carries the id alone, so nothing but this says which catalogue resolves it.
199/// Listed here are the libraries something in a decoded body actually refers to; the
200/// instruments hold others (the live slots, the settings singleton) that no reference
201/// points at, and they are not here.
202#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
203pub enum Library {
204 /// Piano instruments (`.npno`).
205 Piano,
206 /// Sample instruments (`.nsmp`).
207 Sample,
208 /// The instrument's own programs.
209 Program,
210 /// Set lists, which name programs in turn.
211 SetList,
212}
213
214impl Library {
215 /// The library's numeric code.
216 ///
217 /// It exists because a const generic parameter cannot be an enum: a type that carries
218 /// its library — [`LibraryRefOf`](crate::components::LibraryRefOf) — carries this
219 /// instead and turns it back with [`from_code`](Self::from_code).
220 ///
221 /// The numbers are the object-class codes the instruments use on the wire, and
222 /// `nord-usb`'s `ObjectClass` takes its library codes from here, so a caller holding
223 /// both has one table.
224 pub const fn code(self) -> u8 {
225 match self {
226 Library::Piano => 1,
227 Library::Sample => 3,
228 Library::Program => 4,
229 Library::SetList => 5,
230 }
231 }
232
233 /// The library a [`code`](Self::code) names, or `None` — most bytes name none.
234 pub const fn from_code(code: u8) -> Option<Library> {
235 match code {
236 1 => Some(Library::Piano),
237 3 => Some(Library::Sample),
238 4 => Some(Library::Program),
239 5 => Some(Library::SetList),
240 _ => None,
241 }
242 }
243
244 /// The library a [`code`](Self::code) names, for the type-level parameter this
245 /// vocabulary exists to carry.
246 ///
247 /// ⚠️ Panics on a code naming none. That is a build failure only where the value is
248 /// *forced* at compile time, which the aliases in [`components`](crate::components)
249 /// are — a `LibraryRefOf<7>` nobody places compiles clean and fails when a field
250 /// declared with it asks for its control kind. Use [`from_code`](Self::from_code)
251 /// anywhere a code arrives at runtime.
252 pub const fn expect_code(code: u8) -> Library {
253 match Library::from_code(code) {
254 Some(library) => library,
255 None => panic!("no library has this code"),
256 }
257 }
258
259 /// The catalogue's name, singular, as a caller would put it in front of "id".
260 pub fn label(&self) -> &'static str {
261 match self {
262 Library::Piano => "piano",
263 Library::Sample => "sample",
264 Library::Program => "program",
265 Library::SetList => "set list",
266 }
267 }
268}
269
270/// What a control's reading is *in*.
271///
272/// ⚠️ Naming a unit is not a promise that the stored value converts to it. Several Nord
273/// knobs read in milliseconds or hertz over a curve no manual publishes; the unit says
274/// what the panel shows, and the type's own `Display` prints a converted reading only
275/// where the transform is known. See [`Unit::describes_a_known_transform`].
276#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
277pub enum Unit {
278 /// The panel's own `0..10`, which most Nord knobs read in.
279 Panel10,
280 Decibels,
281 Milliseconds,
282 Hertz,
283 /// Beats per minute, quarter-note.
284 Bpm,
285 /// A subdivision of the master clock — `1/8`, `1/4 T`.
286 ClockDivision,
287 Semitones,
288 Octaves,
289 /// A stereo position, left through centre to right.
290 Pan,
291 /// No unit: a count, an index, or a raw byte.
292 None,
293}
294
295impl Unit {
296 /// The unit's numeric code.
297 ///
298 /// It exists for the same reason [`Library::code`] does: a const generic parameter
299 /// cannot be an enum, so a type that carries its unit —
300 /// [`BipolarOf`](crate::components::BipolarOf) — carries this and turns it back with
301 /// [`expect_code`](Self::expect_code).
302 pub const fn code(self) -> u8 {
303 match self {
304 Unit::Panel10 => 0,
305 Unit::Decibels => 1,
306 Unit::Milliseconds => 2,
307 Unit::Hertz => 3,
308 Unit::Bpm => 4,
309 Unit::ClockDivision => 5,
310 Unit::Semitones => 6,
311 Unit::Octaves => 7,
312 Unit::Pan => 8,
313 Unit::None => 9,
314 }
315 }
316
317 /// The unit a [`code`](Self::code) names, for the type-level parameter this
318 /// vocabulary exists to carry.
319 ///
320 /// ⚠️ Panics on a code naming none, which is a build failure where the value is
321 /// forced at compile time — as the aliases in [`components`](crate::components) are.
322 pub const fn expect_code(code: u8) -> Unit {
323 match code {
324 0 => Unit::Panel10,
325 1 => Unit::Decibels,
326 2 => Unit::Milliseconds,
327 3 => Unit::Hertz,
328 4 => Unit::Bpm,
329 5 => Unit::ClockDivision,
330 6 => Unit::Semitones,
331 7 => Unit::Octaves,
332 8 => Unit::Pan,
333 9 => Unit::None,
334 _ => panic!("no unit has this code"),
335 }
336 }
337
338 /// Whether a value in this unit can be *computed* from the stored one.
339 ///
340 /// False for the units where the panel's curve is not published — a caller that wants
341 /// to label an axis may still use the unit, but must print the stored value.
342 pub fn describes_a_known_transform(&self) -> bool {
343 matches!(
344 self,
345 Unit::Panel10 | Unit::Decibels | Unit::Semitones | Unit::Octaves | Unit::Pan
346 )
347 }
348}
349
350/// The widest field whose legal values are enumerated. Above it a field is spelled by its
351/// stored bits, since walking every pattern would mean millions of strings.
352pub const ENUMERABLE_BITS: u32 = 12;
353
354/// Why a field could not be set.
355#[derive(Debug, Clone, PartialEq, Eq)]
356pub enum FieldError {
357 UnknownField {
358 panel: &'static str,
359 name: String,
360 },
361 BadValue {
362 field: &'static str,
363 given: String,
364 legal: Vec<String>,
365 },
366}
367
368impl Display for FieldError {
369 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
370 match self {
371 FieldError::UnknownField { panel, name } => {
372 write!(f, "{panel} has no field {name:?}")
373 }
374 FieldError::BadValue {
375 field,
376 given,
377 legal,
378 } => {
379 write!(f, "{given:?} is not a value of {field}")?;
380 match legal.len() {
381 // Too wide to have named values; the stored bits are its only
382 // spelling.
383 0 => write!(f, " (accepts the stored bits, decimal or 0x…)"),
384 n if n > 12 => write!(f, " (accepts {} .. {})", legal[0], legal[n - 1]),
385 _ => write!(f, " (accepts {})", legal.join(", ")),
386 }
387 }
388 }
389 }
390}
391
392impl std::error::Error for FieldError {}
393
394/// The generated field registry behind an entity, where its body declares one.
395///
396/// `#[bitbody]` generates these three methods on every body with public
397/// fields; this trait is the same surface behind one name, so a caller can
398/// list and set fields without naming the body type.
399/// [`Entity::registry`](crate::Entity::registry) is where one comes from —
400/// a body joins by being declared there, and every consumer sees it at once.
401pub trait Registry {
402 /// Every settable field, described under its full path.
403 fn fields(&self) -> Vec<Field>;
404 /// Every registered field's current value, in declaration order.
405 fn field_values(&self) -> Vec<FieldValue>;
406 /// Set one field by its full path.
407 fn set_field(&mut self, path: &str, value: &str) -> Result<(), FieldError>;
408}
409
410/// One settable field of a body, addressed the way `--set` addresses it.
411pub struct Field {
412 /// The field's full registry path, e.g. `center_panel.transpose`.
413 pub path: String,
414 pub spec: FieldSpec,
415 /// What the field currently holds, spelled the way `set_field` takes it.
416 /// Feeding this straight back is always a no-op.
417 pub value: String,
418 /// The same value as `nord inspect` renders it. Differs from `value` only for a
419 /// field too wide to have named values, where the rendering is a list and the
420 /// spelling is the stored bits.
421 pub display: String,
422}
423
424/// Every value of `T` that fits a `LO..=HI` field, in stored order, asked of the type
425/// itself rather than kept in a second list beside it.
426pub fn legal_values<T: Packed + Debug>(width: u32) -> Vec<String> {
427 if width > ENUMERABLE_BITS {
428 return Vec::new();
429 }
430 let mut seen = Vec::new();
431 for bits in 0..(1u64 << width) {
432 if let Ok(v) = T::from_bits(bits) {
433 let rendered = format!("{v:?}");
434 if !seen.contains(&rendered) {
435 seen.push(rendered);
436 }
437 }
438 }
439 seen
440}
441
442/// Parse a field's value out of the way the field prints it.
443///
444/// **The rendering is the vocabulary**: this walks the field's own bit patterns and takes
445/// the one whose `Debug` matches, so a type gets string parsing from its `Debug` alone,
446/// and a value outside its range has no pattern to match and fails here rather than being
447/// clamped.
448///
449/// ⚠️ An unexplained value can therefore only be written by *naming* it as unexplained: a
450/// sparse enum renders an unrecognized `9` as `Unknown(9)`, so a bare `9` matches nothing
451/// and `Unknown(9)` is the only spelling.
452pub fn parse_field<T: Packed + Debug>(width: u32, given: &str) -> Result<T, FieldError> {
453 let wanted = normalize(given);
454 // A truth word for a `bool` field: its `Debug` is `true`/`false`, which no numeric
455 // field renders, so trying the canonical spelling second cannot collide.
456 let alias = match wanted.as_str() {
457 "on" | "yes" | "1" => Some("true"),
458 "off" | "no" | "0" => Some("false"),
459 _ => None,
460 };
461
462 if width <= ENUMERABLE_BITS {
463 for bits in 0..(1u64 << width) {
464 let Ok(v) = T::from_bits(bits) else { continue };
465 let rendered = normalize(&format!("{v:?}"));
466 if rendered == wanted || Some(rendered.as_str()) == alias {
467 return Ok(v);
468 }
469 }
470 } else if let Some(bits) = stored_value(&wanted) {
471 // Wide fields use stored bits; for a drawbar block the hex digits are its bars.
472 // Check before decoding: storage-backed implementations may cast and truncate.
473 if width >= 64 || bits < (1u64 << width) {
474 if let Ok(v) = T::from_bits(bits) {
475 return Ok(v);
476 }
477 }
478 }
479 Err(FieldError::BadValue {
480 // Filled in by the caller, which knows the field's name.
481 field: "",
482 given: given.to_string(),
483 legal: legal_values::<T>(width),
484 })
485}
486
487/// Case-folded, `+`-stripped, whitespace-trimmed: `+5`, `5` and ` 5 ` are one value, and
488/// so are `Organ` and `organ`.
489fn normalize(s: &str) -> String {
490 s.trim()
491 .trim_start_matches('+')
492 .to_ascii_lowercase()
493 .to_string()
494}
495
496/// A field's stored bits, written decimal or `0x`-prefixed. Already normalized.
497fn stored_value(s: &str) -> Option<u64> {
498 match s.strip_prefix("0x") {
499 Some(hex) => u64::from_str_radix(hex, 16).ok(),
500 None => s.parse().ok(),
501 }
502}
503
504/// How a field of this width spells its current value back to a caller.
505///
506/// Narrow fields are named — `Organ`, `-5`, `true` — and that name is what `--set` takes.
507/// A field too wide to enumerate has no name, so its stored bits are the spelling, and
508/// `raw` is exactly those bits.
509pub fn settable_form(width: u32, debug: &str, raw: u64) -> String {
510 if width <= ENUMERABLE_BITS {
511 debug.to_string()
512 } else {
513 format!("{raw:#x}")
514 }
515}
516
517impl FieldError {
518 /// Attach the field's name to an error raised before it was known.
519 pub fn at(self, field: &'static str) -> Self {
520 match self {
521 FieldError::BadValue { given, legal, .. } => FieldError::BadValue {
522 field,
523 given,
524 legal,
525 },
526 other => other,
527 }
528 }
529}
530
531#[cfg(test)]
532mod tests {
533 use super::*;
534 use crate::components::MorphTarget;
535 use crate::formats::ne5::{Level, Transpose};
536
537 /// A refinement carries what the declaration site knows and the type cannot. It is
538 /// keyed on the kind, so a field whose *name* looks like a morph slot or a drawbar
539 /// but whose type says otherwise keeps what its type said.
540 #[test]
541 fn a_refinement_only_reaches_the_kind_it_is_for() {
542 assert_eq!(
543 ControlKind::Morph { of: None }.morphing("organ_a_volume"),
544 ControlKind::Morph {
545 of: Some("organ_a_volume")
546 }
547 );
548 let bar = |rank| ControlKind::Drawbar {
549 bars: 1,
550 rank,
551 bits_per_bar: 4,
552 order: PackedOrder::HighFirst,
553 };
554 assert_eq!(bar(None).ranked(7), bar(Some(7)));
555
556 let knob = ControlKind::Knob(Unit::Panel10);
557 assert_eq!(knob.morphing("delay_tempo"), knob);
558 assert_eq!(knob.ranked(2), knob);
559 }
560
561 /// The kind names the parent as a sibling; the path is the field's own, one segment
562 /// swapped, so a nested body's prefix rides along.
563 #[test]
564 fn a_morph_slot_resolves_its_parents_full_path() {
565 let spec = |name: &str| FieldSpec {
566 name: name.to_string(),
567 placement: "0..=7",
568 width: 8,
569 legal: || Vec::new(),
570 control: <MorphTarget as Packed>::CONTROL.morphing("drawbar_1"),
571 };
572 assert_eq!(
573 spec("organ_a.drawbar_1_wheel").morph_parent().as_deref(),
574 Some("organ_a.drawbar_1"),
575 );
576 assert_eq!(
577 spec("drawbar_1_wheel").morph_parent().as_deref(),
578 Some("drawbar_1"),
579 );
580 // A slot with no parameter beside it stands alone.
581 let mut orphan = spec("drawbar_1_wheel");
582 orphan.control = <MorphTarget as Packed>::CONTROL;
583 assert_eq!(orphan.morph_parent(), None);
584 }
585
586 #[test]
587 fn a_value_is_parsed_out_of_the_way_it_prints() {
588 let v: Transpose = parse_field(4, "-5").unwrap();
589 assert_eq!(v.inner(), -5);
590 // The bias is the type's business, not the caller's: -5 stores as 1.
591 assert_eq!(<Transpose as Packed>::to_bits(&v), 1);
592 }
593
594 #[test]
595 fn a_leading_plus_and_stray_space_are_the_same_value() {
596 for spelling in ["+3", "3", " 3 "] {
597 assert_eq!(parse_field::<Transpose>(4, spelling).unwrap().inner(), 3);
598 }
599 }
600
601 /// Out of range has no bit pattern to match, so it cannot reach an encode.
602 #[test]
603 fn a_value_outside_the_types_range_is_refused() {
604 let err = parse_field::<Transpose>(4, "9")
605 .unwrap_err()
606 .at("transpose");
607 assert!(
608 err.to_string().contains("not a value of transpose"),
609 "{err}"
610 );
611 }
612
613 #[test]
614 fn a_bool_takes_the_words_people_actually_type() {
615 for yes in ["true", "on", "yes", "1"] {
616 assert!(parse_field::<bool>(1, yes).unwrap(), "{yes}");
617 }
618 for no in ["false", "off", "no", "0"] {
619 assert!(!parse_field::<bool>(1, no).unwrap(), "{no}");
620 }
621 }
622
623 #[test]
624 fn a_wide_numeric_value_must_fit_its_declared_width() {
625 assert!(parse_field::<u16>(16, "70000").is_err());
626 assert!(parse_field::<u32>(32, "4294967296").is_err());
627 assert_eq!(
628 parse_field::<u64>(64, "18446744073709551615").unwrap(),
629 u64::MAX
630 );
631 }
632
633 /// A wide numeric field enumerates, so `--fields` can still say what it takes.
634 #[test]
635 fn legal_values_come_from_the_type() {
636 assert_eq!(legal_values::<bool>(1), vec!["false", "true"]);
637 let levels = legal_values::<Level>(7);
638 assert_eq!(levels.len(), 128);
639 assert_eq!(levels.last().unwrap(), "127");
640 }
641
642 /// The message has to name a way forward, or it is just a rejection.
643 #[test]
644 fn the_error_lists_a_short_value_set_and_ranges_a_long_one() {
645 let short = FieldError::BadValue {
646 field: "split",
647 given: "maybe".into(),
648 legal: vec!["false".into(), "true".into()],
649 };
650 assert!(short.to_string().contains("accepts false, true"));
651
652 let long = FieldError::BadValue {
653 field: "gain",
654 given: "200".into(),
655 legal: (0..128).map(|n| n.to_string()).collect(),
656 };
657 assert!(long.to_string().contains("accepts 0 .. 127"), "{long}");
658 }
659}