use crate::bits::Packed;
use crate::components::sparse_enum;
use crate::components::{EqBand, Frequency, Rate, RotorSpeed, Time};
use crate::formats::ne5::Level;
use crate::types::RangedU8;
use nord_bits_derive::bitbody;
use std::fmt::{self, Display, Formatter};
#[bitbody(18)]
#[derive(Default)]
pub struct EffectsPanel {
#[bits(0..=1)]
pub fx1: Routing,
#[bits(2..=5)]
pub fx1_type: Fx1Type,
#[bits(6..=12)]
pub fx1_rate: Rate,
#[bits(13..=14)]
pub fx2: Routing,
#[bits(15..=18)]
pub fx2_type: Fx2Type,
#[bits(19..=25)]
pub fx2_rate: Rate,
#[bits(26..=27)]
pub fx4: Routing,
#[bits(28..=29)]
pub fx4_feedback: RangedU8<3>,
#[bits(30..=36)]
pub fx4_tempo: Time,
#[bits(37..=43)]
pub fx4_moisture: Level,
#[bits(44..=44)]
pub fx4_ping_pong: bool,
#[bits(45..=45)]
pub equalizer_on: bool,
#[bits(117..=118)]
pub equalizer_part: EqualizerPart,
#[bits(47..=53)]
pub equalizer_freq: Frequency,
#[bits(54..=60)]
pub equalizer_treble: EqBand,
#[bits(61..=67)]
pub equalizer_freq_gain: EqBand,
#[bits(68..=74)]
pub equalizer_bass: EqBand,
#[bits(75..=76)]
pub fx3: Routing,
#[bits(77..=79)]
pub fx3_type: Fx3Type,
#[bits(80..=86)]
pub fx3_compression: Level,
#[bits(87..=87)]
pub fx5: bool,
#[bits(88..=90)]
pub fx5_type: Fx5Type,
#[bits(91..=97)]
pub fx5_moisture: Level,
#[bits(98..=98)]
pub rotary_stop: bool,
#[bits(99..=99)]
pub rotary_speed: RotorSpeed,
#[bits(115..=115)]
pub fx1_control: bool,
#[bits(116..=116)]
pub fx2_deep: bool,
}
#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Routing {
#[default]
Off = 0,
Unknown = 1,
Lower = 2,
Upper = 3,
}
impl Routing {
pub fn from_panel(position: u8) -> Option<Routing> {
match position {
0 => Some(Routing::Off),
1 => Some(Routing::Lower),
2 => Some(Routing::Upper),
_ => None,
}
}
pub fn part(&self) -> Option<&'static str> {
match self {
Routing::Lower => Some("lower"),
Routing::Upper => Some("upper"),
Routing::Off | Routing::Unknown => None,
}
}
pub fn is_unknown(&self) -> bool {
matches!(self, Routing::Unknown)
}
}
impl Packed for Routing {
const MAX_BITS: u32 = 2;
const CONTROL: crate::fields::ControlKind = crate::fields::ControlKind::Selector;
type Error = std::convert::Infallible;
fn from_bits(bits: u64) -> Result<Self, Self::Error> {
Ok(match bits & 0b11 {
0 => Routing::Off,
1 => Routing::Unknown,
2 => Routing::Lower,
_ => Routing::Upper,
})
}
fn to_bits(&self) -> u64 {
*self as u64
}
}
impl Display for Routing {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
Routing::Off => f.write_str("off"),
Routing::Unknown => f.write_str("unknown (1)"),
Routing::Lower => f.write_str("lower"),
Routing::Upper => f.write_str("upper"),
}
}
}
sparse_enum!(
Fx1Type, 4, {
0 => Trem1, "trem 1";
1 => Trem2, "trem 2";
2 => Trem1And2, "trem 1&2";
3 => Pan1, "pan 1";
4 => Pan2, "pan 2";
5 => Pan1And2, "pan 1&2";
6 => Wah, "wah";
7 => Rm, "rm";
}
);
sparse_enum!(
Fx2Type, 4, {
0 => Phaser1, "phaser 1";
1 => Phaser2, "phaser 2";
2 => Flanger, "flanger";
3 => Chorus1, "chorus 1";
4 => Chorus2, "chorus 2";
5 => Vibe, "vibe";
}
);
sparse_enum!(
Fx3Type, 3, {
0 => None_, "none";
1 => Small, "small";
2 => Jc, "jc";
3 => Twin, "twin";
4 => Rotary, "rotary";
5 => Comp, "comp";
}
);
sparse_enum!(
Fx5Type, 3, {
0 => Room, "room";
1 => StageSoft, "stage soft";
2 => Stage, "stage";
3 => HallSoft, "hall soft";
4 => Hall, "hall";
}
);
sparse_enum!(
EqualizerPart, 2, {
0 => Lower, "lower";
1 => Upper, "upper";
2 => Both, "lower+upper";
}
);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn an_unrecognized_value_survives_and_announces_itself() {
let unknown = Fx1Type::from_bits(9).unwrap();
assert_eq!(unknown, Fx1Type::Unknown(9));
assert!(unknown.is_unknown());
assert_eq!(unknown.label(), None);
assert_eq!(unknown.to_string(), "unknown (9)");
assert_eq!(unknown.to_bits(), 9, "an unknown value must round-trip");
}
#[test]
fn recovered_values_round_trip() {
for bits in 0..8u64 {
let t = Fx1Type::from_bits(bits).unwrap();
assert!(!t.is_unknown(), "{bits} should be recovered");
assert_eq!(t.to_bits(), bits);
}
for bits in 0..3u64 {
assert_eq!(EqualizerPart::from_bits(bits).unwrap().to_bits(), bits);
}
}
#[test]
fn routing_matches_what_the_instrument_stores() {
assert_eq!(Routing::from_bits(0).unwrap(), Routing::Off);
assert_eq!(Routing::from_bits(1).unwrap(), Routing::Unknown);
assert_eq!(Routing::from_bits(2).unwrap(), Routing::Lower);
assert_eq!(Routing::from_bits(3).unwrap(), Routing::Upper);
for bits in 0..4u64 {
assert_eq!(Routing::from_bits(bits).unwrap().to_bits(), bits);
}
assert_eq!(Routing::from_panel(0), Some(Routing::Off));
assert_eq!(Routing::from_panel(1), Some(Routing::Lower));
assert_eq!(Routing::from_panel(2), Some(Routing::Upper));
assert_eq!(Routing::from_panel(3), None);
assert_eq!(Routing::Lower.to_bits(), 2);
assert_eq!(Routing::Upper.to_bits(), 3);
assert_eq!(Routing::Off.to_string(), "off");
assert_eq!(Routing::Unknown.to_string(), "unknown (1)");
assert!(Routing::Unknown.is_unknown());
assert!(!Routing::Off.is_unknown());
assert_eq!(Routing::Unknown.part(), None);
}
}