Something went wrong. Try again.
YAML-based live configuration of MicroFreak datyoma.tngl.io/mfctl
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134use super::value::Percent;use super::value::integer_control;use crate::parameter::bin_enum;use crate::parameter::{DecodeError, RegisterCodec, StoredValue};use mfctl_derive::author;
#[author]#[serde(default, rename_all = "camelCase", deny_unknown_fields)]pub struct Arp { #[garde(skip)] /// true enables the arpeggiator when sequencer is false. pub enabled: bool, /// Octave range; 1 uses the held-note pitches only. pub range: ArpRange, /// Arpeggiator/sequencer step length as a tempo division. Active when sync is true; T means triplet. pub division: Division, /// Arpeggiator step rate in beats per minute; active when sync is false. pub rate_bpm: ArpRateBpm, /// Delays alternate steps: 50% gives straight timing, 75% gives maximum swing. pub swing: Swing, #[garde(skip)] /// true uses division for tempo-synchronized steps; false uses rateBpm. pub sync: bool, /// Intensity of Dice variation in gates, triggers, octaves, and velocity (0–100%). Requires an active arpeggio or sequence. Not saved with the preset. pub spice: Percent, /// Random variation of gates and triggers (0–100%); spice sets its intensity. Requires an active arpeggio or sequence. Not saved with the preset. pub dice: Percent, /// Pattern repeats a semi-random note order that changes when the held notes change. pub direction: Direction, #[garde(skip)] /// true selects the sequencer; false selects the arpeggiator. pub sequencer: bool,}
impl Default for Arp { fn default() -> Self { Self { enabled: false, range: ArpRange(1), division: Division::default(), rate_bpm: ArpRateBpm(120.0), swing: Swing(50), sync: false, spice: Percent::default(), dice: Percent::default(), direction: Direction::default(), sequencer: false, } }}
bin_enum! { pub enum Direction { #[default] Up, Order, Random, Pattern, }}
bin_enum! { pub enum Division { #[serde(rename = "1/1")] Whole, #[serde(rename = "1/2")] Half, #[serde(rename = "1/2T")] HalfTriplet, #[serde(rename = "1/4")] Quarter, #[serde(rename = "1/4T")] QuarterTriplet, #[serde(rename = "1/8")] Eighth, #[serde(rename = "1/8T")] EighthTriplet, #[serde(rename = "1/16")] #[default] Sixteenth, #[serde(rename = "1/16T")] SixteenthTriplet, #[serde(rename = "1/32")] ThirtySecond, #[serde(rename = "1/32T")] ThirtySecondTriplet, }}
integer_control!(ArpRange, u8, 1, 4);
#[author]#[serde(transparent)]#[garde(transparent)]pub struct ArpRateBpm(#[garde(range(min = 30.0, max = 240.0))] pub f64);
impl RegisterCodec for ArpRateBpm { fn encode(&self) -> StoredValue { StoredValue(((self.0 - 30.0) * 150.0).round() as u16) }
fn decode(raw: StoredValue) -> Result<Self, DecodeError> { // Higher registers exceed the supported 240 BPM author range. if raw.0 > 31500 { return Err(DecodeError::new::<Self>(raw)); } Ok(Self(30.0 + f64::from(raw.0) / 150.0)) }}
integer_control!(Swing, u8, 50, 75);
#[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!(ArpRange: 1 => 4, Swing: 50 => 75); }
#[test] fn rate_codec_matches_firmware() { for (bpm, raw) in [(30.0, 0), (120.0, 13500), (240.0, 31500)] { assert_eq!(ArpRateBpm(bpm).encode(), StoredValue(raw)); assert_eq!(ArpRateBpm::decode(StoredValue(raw)).unwrap().0, bpm); } assert!(ArpRateBpm::decode(StoredValue(31501)).is_err()); }}