use super::value::integer_control; use crate::parameter::unsigned; use crate::parameter::{self, DecodeError, RegisterCodec, StoredValue}; use mfctl_derive::author; #[author(no_copy)] #[serde(default, rename_all = "camelCase", deny_unknown_fields)] pub struct Global { /// Semitones of pitch bend in either direction. pub bend_range: BendRange, #[garde(skip)] /// Enables up to four independently pitched voices that share the analog filter. pub paraphonic: bool, /// Undocumented voice-mode setting (0 or 1). The effect of each value is unknown. pub para_voice_mode: ParaVoiceMode, /// Loudness adjustment in dB. pub preset_volume: PresetVolume, /// Master output level (0–100%). pub master_volume: MasterVolume, /// Detuning between unison voices. pub unison_spread: UnisonSpread, /// Number of voices in unison. pub unison_count: UnisonCount, /// Off plays normal notes; Chord adds the saved chord intervals to each root note; Unison stacks detuned voices on each note. pub chord_mode: ChordMode, /// Up to three additional chord notes in semitones relative to the implicit root. /// Empty means root only; omission uses [0, 0, 0]. Saved even outside Chord mode. pub chord_offsets: ChordOffsets, } impl Default for Global { fn default() -> Self { Self { bend_range: BendRange::default(), paraphonic: false, para_voice_mode: ParaVoiceMode(1), preset_volume: PresetVolume(0), master_volume: MasterVolume(100.0), unison_spread: UnisonSpread(0.123), unison_count: UnisonCount(4), chord_mode: ChordMode::default(), chord_offsets: ChordOffsets::default(), } } } #[author] #[derive(Default)] #[schemars(extend("default" = Self::default()))] pub enum ChordMode { #[default] Off, Chord = 2, Unison = 3, } impl RegisterCodec for ChordMode { fn encode(&self) -> StoredValue { parameter::encode_bin(*self as u16, Self::Unison as u16) } fn decode(raw: StoredValue) -> Result { match parameter::decode_bin::(raw, Self::Unison as u16)? { 0 => Ok(Self::Off), 2 => Ok(Self::Chord), 3 => Ok(Self::Unison), _ => Err(DecodeError::new::(raw)), } } } impl Default for BendRange { fn default() -> Self { Self(12) } } integer_control!(BendRange, u8, 0, 24); integer_control!(ParaVoiceMode, u8, 0, 1); integer_control!(PresetVolume, i8, -12, 12); #[author] #[serde(transparent)] #[garde(transparent)] pub struct MasterVolume(#[garde(range(min = 0.0, max = 100.0))] pub f64); impl RegisterCodec for MasterVolume { fn encode(&self) -> StoredValue { let step = (self.0 * 127.0 / 100.0).round() as u16; StoredValue(step * 256 + step / 64) } fn decode(raw: StoredValue) -> Result { Ok(Self( f64::from(unsigned::(raw)? / 256) * 100.0 / 127.0, )) } } integer_control!(UnisonCount, u8, 2, 4); #[author] #[serde(transparent)] #[garde(transparent)] pub struct UnisonSpread(#[garde(range(min = 0.001, max = 12.0))] pub f64); impl RegisterCodec for UnisonSpread { fn encode(&self) -> StoredValue { let step = if self.0 <= 1.0 { ((self.0 - 0.001) * 511.0 / 0.999).round() } else { (511.0 + (self.0 - 1.0) * 512.0 / 11.0).round() } as u16; StoredValue(step * 32 + step / 32) } fn decode(raw: StoredValue) -> Result { let step = u32::from(unsigned::(raw)? / 32); let millist = if step < 512 { (step + 1) * 1000 / 512 } else { 1000 + (step - 511) * 11000 / 512 }; Ok(Self(f64::from(millist) / 1000.0)) } } #[author(no_copy)] #[serde(transparent)] #[garde(transparent)] pub struct ChordOffsets( #[garde(length(max = 3), inner(range(min = -128, max = 126)))] #[schemars(length(max = 3), inner(range(min = -128, max = 126)))] pub Vec, ); impl Default for ChordOffsets { fn default() -> Self { Self(vec![0, 0, 0]) } } #[cfg(test)] mod tests { use super::*; use crate::parameter::{RegisterCodec, StoredValue}; use crate::patch::value::tests::check_integer_controls; use garde::Validate; #[test] fn integer_controls_round_trip_their_entire_author_ranges() { check_integer_controls!(BendRange: 0 => 24, ParaVoiceMode: 0 => 1, PresetVolume: -12 => 12, UnisonCount: 2 => 4); } #[test] fn quantized_volume_and_spread_follow_firmware_steps() { for step in 0..=127 { let value = MasterVolume(f64::from(step) * 100.0 / 127.0); let raw = StoredValue(step * 256 + step / 64); assert_eq!(value.encode(), raw); assert_eq!(MasterVolume::decode(raw).unwrap(), value); } for (value, raw) in [(0.001, 0), (1.0, 16367), (12.0, 32767)] { assert_eq!(UnisonSpread(value).encode(), StoredValue(raw)); } assert_eq!(UnisonSpread::decode(StoredValue(16367)).unwrap().0, 1.0); assert_eq!(UnisonSpread::decode(StoredValue(32767)).unwrap().0, 12.0); } }