use std::fmt::{self, Debug, Display, Formatter};
use crate::bits::Packed;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FieldValue {
pub name: String,
pub placement: &'static str,
pub raw: u64,
pub bits: u64,
pub value: String,
}
impl Display for FieldValue {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(
f,
"{:<22} {:<12} raw {:<11} {}",
self.name, self.placement, self.raw, self.value
)
}
}
#[derive(Clone)]
pub struct FieldSpec {
pub name: String,
pub placement: &'static str,
pub width: u32,
pub legal: fn() -> Vec<String>,
pub control: ControlKind,
}
impl FieldSpec {
pub fn morph_parent(&self) -> Option<String> {
let ControlKind::Morph { of: Some(parent) } = self.control else {
return None;
};
Some(match self.name.rsplit_once('.') {
Some((prefix, _)) => format!("{prefix}.{parent}"),
None => parent.to_string(),
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ControlKind {
Toggle,
Selector,
Knob(Unit),
Bipolar(Unit),
Drawbar {
bars: u8,
rank: Option<u8>,
bits_per_bar: u8,
order: PackedOrder,
},
Morph {
of: Option<&'static str>,
},
Pattern {
steps: u8,
bits_per_step: u8,
order: PackedOrder,
},
Reference(Library),
Shift(Unit),
Number,
}
impl ControlKind {
pub const fn morphing(self, parent: &'static str) -> ControlKind {
match self {
ControlKind::Morph { .. } => ControlKind::Morph { of: Some(parent) },
other => other,
}
}
pub const fn ranked(self, rank: u8) -> ControlKind {
match self {
ControlKind::Drawbar {
bars,
bits_per_bar,
order,
..
} => ControlKind::Drawbar {
bars,
rank: Some(rank),
bits_per_bar,
order,
},
other => other,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum PackedOrder {
HighFirst,
LowFirst,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Library {
Piano,
Sample,
Program,
SetList,
}
impl Library {
pub const fn code(self) -> u8 {
match self {
Library::Piano => 1,
Library::Sample => 3,
Library::Program => 4,
Library::SetList => 5,
}
}
pub const fn from_code(code: u8) -> Option<Library> {
match code {
1 => Some(Library::Piano),
3 => Some(Library::Sample),
4 => Some(Library::Program),
5 => Some(Library::SetList),
_ => None,
}
}
pub const fn expect_code(code: u8) -> Library {
match Library::from_code(code) {
Some(library) => library,
None => panic!("no library has this code"),
}
}
pub fn label(&self) -> &'static str {
match self {
Library::Piano => "piano",
Library::Sample => "sample",
Library::Program => "program",
Library::SetList => "set list",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Unit {
Panel10,
Decibels,
Milliseconds,
Hertz,
Bpm,
ClockDivision,
Semitones,
Octaves,
Pan,
None,
}
impl Unit {
pub const fn code(self) -> u8 {
match self {
Unit::Panel10 => 0,
Unit::Decibels => 1,
Unit::Milliseconds => 2,
Unit::Hertz => 3,
Unit::Bpm => 4,
Unit::ClockDivision => 5,
Unit::Semitones => 6,
Unit::Octaves => 7,
Unit::Pan => 8,
Unit::None => 9,
}
}
pub const fn expect_code(code: u8) -> Unit {
match code {
0 => Unit::Panel10,
1 => Unit::Decibels,
2 => Unit::Milliseconds,
3 => Unit::Hertz,
4 => Unit::Bpm,
5 => Unit::ClockDivision,
6 => Unit::Semitones,
7 => Unit::Octaves,
8 => Unit::Pan,
9 => Unit::None,
_ => panic!("no unit has this code"),
}
}
pub fn describes_a_known_transform(&self) -> bool {
matches!(
self,
Unit::Panel10 | Unit::Decibels | Unit::Semitones | Unit::Octaves | Unit::Pan
)
}
}
pub const ENUMERABLE_BITS: u32 = 12;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FieldError {
UnknownField {
panel: &'static str,
name: String,
},
BadValue {
field: &'static str,
given: String,
legal: Vec<String>,
},
}
impl Display for FieldError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
FieldError::UnknownField { panel, name } => {
write!(f, "{panel} has no field {name:?}")
}
FieldError::BadValue {
field,
given,
legal,
} => {
write!(f, "{given:?} is not a value of {field}")?;
match legal.len() {
0 => write!(f, " (accepts the stored bits, decimal or 0x…)"),
n if n > 12 => write!(f, " (accepts {} .. {})", legal[0], legal[n - 1]),
_ => write!(f, " (accepts {})", legal.join(", ")),
}
}
}
}
}
impl std::error::Error for FieldError {}
pub trait Registry {
fn fields(&self) -> Vec<Field>;
fn field_values(&self) -> Vec<FieldValue>;
fn set_field(&mut self, path: &str, value: &str) -> Result<(), FieldError>;
}
pub struct Field {
pub path: String,
pub spec: FieldSpec,
pub value: String,
pub display: String,
}
pub fn legal_values<T: Packed + Debug>(width: u32) -> Vec<String> {
if width > ENUMERABLE_BITS {
return Vec::new();
}
let mut seen = Vec::new();
for bits in 0..(1u64 << width) {
if let Ok(v) = T::from_bits(bits) {
let rendered = format!("{v:?}");
if !seen.contains(&rendered) {
seen.push(rendered);
}
}
}
seen
}
pub fn parse_field<T: Packed + Debug>(width: u32, given: &str) -> Result<T, FieldError> {
let wanted = normalize(given);
let alias = match wanted.as_str() {
"on" | "yes" | "1" => Some("true"),
"off" | "no" | "0" => Some("false"),
_ => None,
};
if width <= ENUMERABLE_BITS {
for bits in 0..(1u64 << width) {
let Ok(v) = T::from_bits(bits) else { continue };
let rendered = normalize(&format!("{v:?}"));
if rendered == wanted || Some(rendered.as_str()) == alias {
return Ok(v);
}
}
} else if let Some(bits) = stored_value(&wanted) {
if width >= 64 || bits < (1u64 << width) {
if let Ok(v) = T::from_bits(bits) {
return Ok(v);
}
}
}
Err(FieldError::BadValue {
field: "",
given: given.to_string(),
legal: legal_values::<T>(width),
})
}
fn normalize(s: &str) -> String {
s.trim()
.trim_start_matches('+')
.to_ascii_lowercase()
.to_string()
}
fn stored_value(s: &str) -> Option<u64> {
match s.strip_prefix("0x") {
Some(hex) => u64::from_str_radix(hex, 16).ok(),
None => s.parse().ok(),
}
}
pub fn settable_form(width: u32, debug: &str, raw: u64) -> String {
if width <= ENUMERABLE_BITS {
debug.to_string()
} else {
format!("{raw:#x}")
}
}
impl FieldError {
pub fn at(self, field: &'static str) -> Self {
match self {
FieldError::BadValue { given, legal, .. } => FieldError::BadValue {
field,
given,
legal,
},
other => other,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::components::MorphTarget;
use crate::formats::ne5::{Level, Transpose};
#[test]
fn a_refinement_only_reaches_the_kind_it_is_for() {
assert_eq!(
ControlKind::Morph { of: None }.morphing("organ_a_volume"),
ControlKind::Morph {
of: Some("organ_a_volume")
}
);
let bar = |rank| ControlKind::Drawbar {
bars: 1,
rank,
bits_per_bar: 4,
order: PackedOrder::HighFirst,
};
assert_eq!(bar(None).ranked(7), bar(Some(7)));
let knob = ControlKind::Knob(Unit::Panel10);
assert_eq!(knob.morphing("delay_tempo"), knob);
assert_eq!(knob.ranked(2), knob);
}
#[test]
fn a_morph_slot_resolves_its_parents_full_path() {
let spec = |name: &str| FieldSpec {
name: name.to_string(),
placement: "0..=7",
width: 8,
legal: || Vec::new(),
control: <MorphTarget as Packed>::CONTROL.morphing("drawbar_1"),
};
assert_eq!(
spec("organ_a.drawbar_1_wheel").morph_parent().as_deref(),
Some("organ_a.drawbar_1"),
);
assert_eq!(
spec("drawbar_1_wheel").morph_parent().as_deref(),
Some("drawbar_1"),
);
let mut orphan = spec("drawbar_1_wheel");
orphan.control = <MorphTarget as Packed>::CONTROL;
assert_eq!(orphan.morph_parent(), None);
}
#[test]
fn a_value_is_parsed_out_of_the_way_it_prints() {
let v: Transpose = parse_field(4, "-5").unwrap();
assert_eq!(v.inner(), -5);
assert_eq!(<Transpose as Packed>::to_bits(&v), 1);
}
#[test]
fn a_leading_plus_and_stray_space_are_the_same_value() {
for spelling in ["+3", "3", " 3 "] {
assert_eq!(parse_field::<Transpose>(4, spelling).unwrap().inner(), 3);
}
}
#[test]
fn a_value_outside_the_types_range_is_refused() {
let err = parse_field::<Transpose>(4, "9")
.unwrap_err()
.at("transpose");
assert!(
err.to_string().contains("not a value of transpose"),
"{err}"
);
}
#[test]
fn a_bool_takes_the_words_people_actually_type() {
for yes in ["true", "on", "yes", "1"] {
assert!(parse_field::<bool>(1, yes).unwrap(), "{yes}");
}
for no in ["false", "off", "no", "0"] {
assert!(!parse_field::<bool>(1, no).unwrap(), "{no}");
}
}
#[test]
fn a_wide_numeric_value_must_fit_its_declared_width() {
assert!(parse_field::<u16>(16, "70000").is_err());
assert!(parse_field::<u32>(32, "4294967296").is_err());
assert_eq!(
parse_field::<u64>(64, "18446744073709551615").unwrap(),
u64::MAX
);
}
#[test]
fn legal_values_come_from_the_type() {
assert_eq!(legal_values::<bool>(1), vec!["false", "true"]);
let levels = legal_values::<Level>(7);
assert_eq!(levels.len(), 128);
assert_eq!(levels.last().unwrap(), "127");
}
#[test]
fn the_error_lists_a_short_value_set_and_ranges_a_long_one() {
let short = FieldError::BadValue {
field: "split",
given: "maybe".into(),
legal: vec!["false".into(), "true".into()],
};
assert!(short.to_string().contains("accepts false, true"));
let long = FieldError::BadValue {
field: "gain",
given: "200".into(),
legal: (0..128).map(|n| n.to_string()).collect(),
};
assert!(long.to_string().contains("accepts 0 .. 127"), "{long}");
}
}