Something went wrong. Try again.
YAML-based live configuration of MicroFreak datyoma.tngl.io/mfctl
microfreak
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172use 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<Self, DecodeError> { match parameter::decode_bin::<Self>(raw, Self::Unison as u16)? { 0 => Ok(Self::Off), 2 => Ok(Self::Chord), 3 => Ok(Self::Unison), _ => Err(DecodeError::new::<Self>(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<Self, DecodeError> { Ok(Self( f64::from(unsigned::<Self>(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<Self, DecodeError> { let step = u32::from(unsigned::<Self>(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<i8>,);
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); }}