use nord_format::fields::Field;
use crate::strings::{self, Section};
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum Bars {
Nine(&'static str),
Tabs(&'static str),
Bass(&'static str, &'static str),
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Registration {
pub preset: u8,
pub live: bool,
pub bars: Bars,
pub vib: Option<&'static str>,
pub perc: Option<&'static str>,
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Organ {
pub selected: String,
pub known: bool,
pub preset_field: Option<&'static str>,
pub vib_type: Option<&'static str>,
pub perc: Option<(&'static str, &'static str)>,
pub registrations: Vec<Registration>,
}
impl Organ {
pub fn covers(&self, path: &str) -> bool {
let Some(leaf) = path.strip_prefix("organ_panel.") else {
return false;
};
self.known
&& ["b3_", "vox_", "farfisa_", "pipe_"]
.iter()
.any(|model| leaf.starts_with(model))
}
}
struct Paths {
presets: [&'static str; 2],
preset2_selected: &'static str,
vib_type: Option<&'static str>,
vib: [Option<&'static str>; 2],
tabs: bool,
perc: bool,
}
const B3: Paths = Paths {
presets: [
"organ_panel.b3_preset1_drawbars",
"organ_panel.b3_preset2_drawbars",
],
preset2_selected: "organ_panel.b3_preset2_selected",
vib_type: Some("organ_panel.b3_vib"),
vib: [
Some("organ_panel.b3_preset1_vib"),
Some("organ_panel.b3_preset2_vib"),
],
tabs: false,
perc: true,
};
const VOX: Paths = Paths {
presets: [
"organ_panel.vox_preset1_drawbars",
"organ_panel.vox_preset2_drawbars",
],
preset2_selected: "organ_panel.vox_preset2_selected",
vib_type: Some("organ_panel.vox_vib"),
vib: [
Some("organ_panel.vox_preset1_vib"),
Some("organ_panel.vox_preset2_vib"),
],
tabs: false,
perc: false,
};
const FARFISA: Paths = Paths {
presets: [
"organ_panel.farfisa_preset1_drawbars",
"organ_panel.farfisa_preset2_drawbars",
],
preset2_selected: "organ_panel.farfisa_preset2_selected",
vib_type: Some("organ_panel.farfisa_vib"),
vib: [
Some("organ_panel.farfisa_preset1_vib"),
Some("organ_panel.farfisa_preset2_vib"),
],
tabs: true,
perc: false,
};
const PIPE: Paths = Paths {
presets: [
"organ_panel.pipe_preset1_drawbars",
"organ_panel.pipe_preset2_drawbars",
],
preset2_selected: "organ_panel.pipe_preset2_selected",
vib_type: None,
vib: [None, None],
tabs: false,
perc: false,
};
const PERC_ON: [&str; 2] = ["organ_panel.b3_preset1_perc", "organ_panel.b3_preset2_perc"];
pub fn value_of<'a>(fields: &'a [Field], path: &str) -> Option<&'a str> {
fields
.iter()
.find(|field| field.path == path)
.map(|field| field.value.as_str())
}
fn flag(fields: &[Field], path: &str) -> bool {
value_of(fields, path) == Some("true")
}
pub fn organ(fields: &[Field]) -> Option<Organ> {
let selected = value_of(fields, "center_panel.organ_type")?.to_string();
let (paths, bass) = match selected.as_str() {
"B3" => (&B3, false),
"B3Bass" => (&B3, true),
"Vox" => (&VOX, false),
"Farfisa" => (&FARFISA, false),
"Pipe" => (&PIPE, false),
_ => {
return Some(Organ {
selected,
known: false,
preset_field: None,
vib_type: None,
perc: None,
registrations: Vec::new(),
})
}
};
let live = match flag(fields, paths.preset2_selected) {
true => 2,
false => 1,
};
let registrations = [1u8, 2]
.into_iter()
.map(|preset| {
let i = preset as usize - 1;
Registration {
preset,
live: preset == live,
bars: match (bass && preset == 1, paths.tabs) {
(true, _) => Bars::Bass("organ_panel.b3_bass_bar1", "organ_panel.b3_bass_bar2"),
(false, true) => Bars::Tabs(paths.presets[i]),
(false, false) => Bars::Nine(paths.presets[i]),
},
vib: paths.vib[i],
perc: paths.perc.then_some(PERC_ON[i]),
}
})
.collect();
Some(Organ {
selected,
known: true,
preset_field: Some(paths.preset2_selected),
vib_type: paths.vib_type,
perc: paths
.perc
.then_some(("organ_panel.b3_perc_third", "organ_panel.b3_perc_speed")),
registrations,
})
}
pub fn part_uses(fields: &[Field], instrument: &str) -> bool {
["center_panel.lower_part", "center_panel.upper_part"]
.iter()
.any(|path| value_of(fields, path) == Some(instrument))
}
pub fn shown(section: Section, fields: &[Field]) -> bool {
match section {
Section::Organ => part_uses(fields, "Organ"),
Section::Piano => part_uses(fields, "Piano"),
Section::Sample => part_uses(fields, "Sample"),
_ => true,
}
}
pub fn engineering_only(path: &str) -> bool {
const UNMAPPED: [&str; 2] = ["center_panel.lower_enabled", "center_panel.upper_enabled"];
const TRANSPOSE: [&str; 2] = ["center_panel.transpose", "center_panel.transpose_enabled"];
const IDS: [&str; 2] = ["piano_panel.id", "sample_panel.id"];
UNMAPPED.contains(&path)
|| TRANSPOSE.contains(&path)
|| IDS.contains(&path)
|| path.contains("unknown")
}
pub fn switches_first(section: Section) -> bool {
matches!(section, Section::Effects | Section::Eq)
}
pub fn choices(path: &str, legal: &[String], current: &str) -> Vec<String> {
let mut out: Vec<String> = legal
.iter()
.filter(|value| offerable(path, value))
.cloned()
.collect();
if !out.iter().any(|value| value == current) {
out.push(current.to_string());
}
out
}
fn offerable(path: &str, value: &str) -> bool {
if strings::unrecognised(value).is_some() {
return false;
}
!(value == "Unknown"
&& matches!(
path,
"effects_panel.fx1" | "effects_panel.fx2" | "effects_panel.fx3" | "effects_panel.fx4"
))
}
pub fn transpose(fields: &[Field]) -> Option<(bool, i64)> {
let on = flag(fields, "center_panel.transpose_enabled");
let semitones = value_of(fields, "center_panel.transpose")?
.trim_start_matches('+')
.parse()
.ok()?;
Some((on, semitones))
}
pub fn set_transpose(on: bool, semitones: i64) -> Vec<(String, String)> {
vec![
("center_panel.transpose_enabled".to_string(), on.to_string()),
("center_panel.transpose".to_string(), semitones.to_string()),
]
}
#[cfg(test)]
mod tests {
use super::*;
use nord_format::formats::ne5;
use nord_format::{Entity, Program};
use crate::fields::apply;
fn program(sets: &[(&str, &str)]) -> Vec<Field> {
let entity = Entity::Program(Program::Electro5(ne5::program::new(
(0, 0).try_into().unwrap(),
)));
let bytes = nord_format::to_bytes(&entity).unwrap();
let sets: Vec<(String, String)> = sets
.iter()
.map(|(p, v)| ((*p).to_string(), (*v).to_string()))
.collect();
apply(&bytes, &sets).expect("the sets are legal").0
}
fn organ_of(sets: &[(&str, &str)]) -> Organ {
organ(&program(sets)).expect("a program has an organ")
}
#[test]
fn a_b3_shows_two_registrations_with_vibrato_and_percussion() {
let organ = organ_of(&[("center_panel.organ_type", "B3")]);
assert!(organ.known);
assert_eq!(organ.vib_type, Some("organ_panel.b3_vib"));
assert!(organ.perc.is_some());
assert_eq!(
organ.registrations[0].bars,
Bars::Nine("organ_panel.b3_preset1_drawbars")
);
assert_eq!(
organ.registrations[1].bars,
Bars::Nine("organ_panel.b3_preset2_drawbars")
);
assert_eq!(
organ.registrations[0].perc,
Some("organ_panel.b3_preset1_perc")
);
}
#[test]
fn the_marked_preset_is_the_one_the_instrument_is_playing() {
let organ = organ_of(&[("center_panel.organ_type", "B3")]);
assert!(organ.registrations[0].live);
assert!(!organ.registrations[1].live);
let organ = organ_of(&[
("center_panel.organ_type", "B3"),
("organ_panel.b3_preset2_selected", "true"),
]);
assert!(!organ.registrations[0].live);
assert!(organ.registrations[1].live);
}
#[test]
fn a_vox_shows_its_own_bars_and_no_percussion() {
let organ = organ_of(&[("center_panel.organ_type", "Vox")]);
assert_eq!(organ.vib_type, Some("organ_panel.vox_vib"));
assert_eq!(organ.perc, None);
assert_eq!(
organ.registrations[0].bars,
Bars::Nine("organ_panel.vox_preset1_drawbars")
);
assert!(organ.registrations.iter().all(|r| r.perc.is_none()));
}
#[test]
fn a_farfisa_shows_registers_as_tabs() {
let organ = organ_of(&[("center_panel.organ_type", "Farfisa")]);
assert_eq!(
organ.registrations[0].bars,
Bars::Tabs("organ_panel.farfisa_preset1_drawbars")
);
assert_eq!(organ.vib_type, Some("organ_panel.farfisa_vib"));
}
#[test]
fn a_pipe_organ_has_no_vibrato_or_percussion() {
let organ = organ_of(&[("center_panel.organ_type", "Pipe")]);
assert_eq!(organ.vib_type, None);
assert_eq!(organ.perc, None);
assert!(organ.registrations.iter().all(|r| r.vib.is_none()));
assert_eq!(
organ.registrations[1].bars,
Bars::Nine("organ_panel.pipe_preset2_drawbars")
);
}
#[test]
fn b3_bass_replaces_preset_one_with_the_bass_manual() {
let organ = organ_of(&[("center_panel.organ_type", "B3Bass")]);
assert_eq!(
organ.registrations[0].bars,
Bars::Bass("organ_panel.b3_bass_bar1", "organ_panel.b3_bass_bar2")
);
assert_eq!(
organ.registrations[1].bars,
Bars::Nine("organ_panel.b3_preset2_drawbars")
);
assert!(organ.covers("organ_panel.b3_preset1_drawbars"));
assert!(!organ
.registrations
.iter()
.any(|r| r.bars == Bars::Nine("organ_panel.b3_preset1_drawbars")));
}
#[test]
fn an_unrecognised_organ_selection_shows_no_registration() {
let organ = organ_of(&[("center_panel.organ_type", "unknown (6)")]);
assert!(!organ.known);
assert!(organ.registrations.is_empty());
assert!(!organ.covers("organ_panel.b3_preset1_drawbars"));
assert_eq!(
strings::value_label("center_panel.organ_type", &organ.selected),
"unrecognized value (6)"
);
}
#[test]
fn the_organ_view_speaks_for_every_models_state() {
let organ = organ_of(&[("center_panel.organ_type", "B3")]);
for path in [
"organ_panel.b3_vib",
"organ_panel.vox_preset2_drawbars",
"organ_panel.farfisa_vib",
"organ_panel.pipe_preset1_drawbars",
] {
assert!(organ.covers(path), "{path}");
}
assert!(!organ.covers("organ_panel.something_new"));
assert!(!organ.covers("center_panel.gain"));
}
#[test]
fn only_the_engines_a_part_is_playing_are_shown() {
let fields = program(&[]);
assert!(
shown(Section::Organ, &fields),
"a fresh program plays organ"
);
assert!(!shown(Section::Piano, &fields));
assert!(!shown(Section::Sample, &fields));
for always in [Section::Effects, Section::Eq, Section::Keyboard] {
assert!(shown(always, &fields));
}
let fields = program(&[("center_panel.upper_part", "Piano")]);
assert!(shown(Section::Piano, &fields));
assert!(
shown(Section::Organ, &fields),
"the lower part still plays it"
);
let fields = program(&[
("center_panel.lower_part", "Sample"),
("center_panel.upper_part", "Piano"),
]);
assert!(!shown(Section::Organ, &fields));
assert_eq!(
strings::section("center_panel.lower_part"),
Section::Keyboard
);
assert_eq!(
strings::section("center_panel.upper_part"),
Section::Keyboard
);
}
#[test]
fn the_transpose_control_writes_both_halves() {
let fields = program(&[]);
assert_eq!(transpose(&fields), Some((false, 0)));
let sets = set_transpose(true, -5);
let paths: Vec<&str> = sets.iter().map(|(p, _)| p.as_str()).collect();
assert_eq!(
paths,
["center_panel.transpose_enabled", "center_panel.transpose"]
);
assert_eq!(sets[0].1, "true");
assert_eq!(sets[1].1, "-5");
let after = program(&[
("center_panel.transpose_enabled", "true"),
("center_panel.transpose", "-5"),
]);
assert_eq!(transpose(&after), Some((true, -5)));
}
#[test]
fn neither_half_of_the_transpose_pair_is_a_field_of_its_own() {
assert!(engineering_only("center_panel.transpose"));
assert!(engineering_only("center_panel.transpose_enabled"));
assert!(!engineering_only("center_panel.gain"));
assert!(engineering_only("center_panel.unknown_boolean1"));
assert!(engineering_only("piano_panel.id"));
}
#[test]
fn an_unrecognised_value_is_kept_but_never_offered() {
let legal: Vec<String> = ["B3", "B3Bass", "Pipe", "unknown (6)"]
.iter()
.map(|s| s.to_string())
.collect();
let ordinary = choices("center_panel.organ_type", &legal, "B3");
assert_eq!(ordinary, ["B3", "B3Bass", "Pipe"]);
let holding = choices("center_panel.organ_type", &legal, "unknown (6)");
assert_eq!(holding, ["B3", "B3Bass", "Pipe", "unknown (6)"]);
}
#[test]
fn the_older_spelling_of_off_is_not_offered_alongside_off() {
let legal: Vec<String> = ["Off", "Unknown", "Lower", "Upper"]
.iter()
.map(|s| s.to_string())
.collect();
assert_eq!(
choices("effects_panel.fx1", &legal, "Off"),
["Off", "Lower", "Upper"]
);
assert_eq!(
choices("effects_panel.fx1", &legal, "Unknown"),
["Off", "Lower", "Upper", "Unknown"]
);
assert!(offerable("some_other_field", "Unknown"));
}
}