Something went wrong. Try again.
YAML-based live configuration of MicroFreak datyoma.tngl.io/mfctl
microfreak
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188//! Mode-dependent glide values and firmware register codecs.use crate::parameter::{self, DecodeError, RegisterCodec, StoredValue};use mfctl_derive::author;use serde::Deserialize;use std::fmt;
#[author]#[schemars(extend("default" = Self::default()))]pub enum Glide { /// Fixed glide duration in milliseconds, from zero (Off) to 10000. #[serde(rename = "glideTimeMs")] Time(DurationMs), /// Tempo-synchronized glide division, or off while retaining Sync mode. #[serde(rename = "glideDivision")] Sync(Division), /// Glide duration per octave in milliseconds, from zero (Off) to 10000. #[serde(rename = "glideMsPerOctave")] Rate(DurationMs),}
impl Default for Glide { fn default() -> Self { Self::Time(DurationMs::default()) }}
impl Glide { pub(crate) fn from_mode_register(raw: StoredValue) -> Result<Self, DecodeError> { Ok(match parameter::decode_bin::<Self>(raw, 2)? { 0 => Self::default(), 1 => Self::Sync(Division::default()), 2 => Self::Rate(DurationMs::default()), _ => unreachable!(), }) }
// As with LFO rate, omission defaults but explicit invalid/multiple choices do not. pub(super) fn deserialize_or_default<'de, D: serde::Deserializer<'de>>( deserializer: D, ) -> Result<Self, D::Error> { struct GlideVisitor; impl<'de> serde::de::Visitor<'de> for GlideVisitor { type Value = Glide;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("glideTimeMs, glideDivision or glideMsPerOctave") }
fn visit_map<M: serde::de::MapAccess<'de>>(self, map: M) -> Result<Glide, M::Error> { let fields = serde_yaml::Mapping::deserialize( serde::de::value::MapAccessDeserializer::new(map), )?; if fields.is_empty() { Ok(Glide::default()) } else if fields.len() == 1 { Glide::deserialize(serde::de::value::MapAccessDeserializer::new( serde::de::value::MapDeserializer::new(fields.into_iter()), )) .map_err(serde::de::Error::custom) } else { Err(serde::de::Error::custom( "choose only one of glideTimeMs, glideDivision or glideMsPerOctave", )) } } } deserializer.deserialize_struct( "Glide", &["glideTimeMs", "glideDivision", "glideMsPerOctave"], GlideVisitor, ) }}
#[author]#[derive(Default)]#[serde(transparent)]#[garde(transparent)]pub struct DurationMs(#[garde(range(min = 0.0, max = 10000.0))] pub f64);
impl RegisterCodec for DurationMs { fn encode(&self) -> StoredValue { StoredValue((32767.0 * (self.0 / 10000.0).cbrt()).round() as u16) }
fn decode(raw: StoredValue) -> Result<Self, DecodeError> { Ok(Self( 10000.0 * (f64::from(parameter::unsigned::<Self>(raw)?) / 32767.0).powi(3), )) }}
#[author]#[derive(Default, strum_macros::FromRepr)]#[repr(u16)]#[schemars(extend("default" = Self::default()))]pub enum Division { #[default] #[serde(rename = "off")] Off, #[serde(rename = "1/32T")] ThirtySecondTriplet, #[serde(rename = "1/32")] ThirtySecond, #[serde(rename = "1/16T")] SixteenthTriplet, #[serde(rename = "1/16")] Sixteenth, #[serde(rename = "1/8T")] EighthTriplet, #[serde(rename = "1/8")] Eighth, #[serde(rename = "1/4T")] QuarterTriplet, #[serde(rename = "1/4")] Quarter, #[serde(rename = "1/2T")] HalfTriplet, #[serde(rename = "1/2")] Half, #[serde(rename = "1/1")] Whole,}
impl RegisterCodec for Division { fn encode(&self) -> StoredValue { if matches!(self, Self::Off) { StoredValue(0) } else { // Use bin interiors, except Whole, whose bin is only 32760..32767. StoredValue(((*self as u16 - 1) * 3276 + 1638).min(32767)) } }
fn decode(raw: StoredValue) -> Result<Self, DecodeError> { let raw = parameter::unsigned::<Self>(raw)?; Ok(if raw == 0 { Self::Off } else { Self::from_repr((raw / 3276).min(10) + 1).unwrap() }) }}
#[cfg(test)]mod tests { use super::*;
#[test] fn durations_follow_the_cubic_curve_and_preserve_registers() { for (raw, expected) in [ (0, 0.0), (8192, 156.2643059879054), (16384, 1250.1144479032432), (32767, 10000.0), ] { let duration = DurationMs::decode(StoredValue(raw)).unwrap(); assert!((duration.0 - expected).abs() < 1e-9); assert_eq!(duration.encode(), StoredValue(raw)); } for raw in 0..=32767 { assert_eq!( DurationMs::decode(StoredValue(raw)).unwrap().encode(), StoredValue(raw) ); } assert!(DurationMs::decode(StoredValue(32768)).is_err()); }
#[test] fn divisions_use_firmware_boundaries_and_round_trip() { assert_eq!(Division::decode(StoredValue(0)).unwrap(), Division::Off); assert_eq!(Division::Off.encode(), StoredValue(0)); for index in 0..=10 { let division = Division::from_repr(index + 1).unwrap(); let lower = (index * 3276).max(1); let upper = ((index + 1) * 3276 - 1).min(32767); for raw in [lower, upper] { assert_eq!(Division::decode(StoredValue(raw)).unwrap(), division); } assert!((lower..=upper).contains(&division.encode().0)); assert_eq!(Division::decode(division.encode()).unwrap(), division); } assert_eq!(Division::Whole.encode(), StoredValue(32767)); assert!(Division::decode(StoredValue(32768)).is_err()); }}