use super::value::Percent; use super::value::time_control; use mfctl_derive::author; #[author] #[serde(default, rename_all = "camelCase", deny_unknown_fields)] pub struct Envelope { #[garde(skip)] /// true lets the main envelope shape loudness; false switches sound on and off with the gate. pub amp_mod: bool, /// Time for the main envelope to reach its peak, in milliseconds. pub attack_ms: AttackMs, /// Decay and release time, in milliseconds. pub decay_release_ms: DecayReleaseMs, /// Main envelope sustain level (0–100%). pub sustain: Percent, #[garde(skip)] /// true prevents envelope retriggering when notes overlap. pub legato: bool, #[garde(skip)] /// true sharpens the envelope's decay. pub snap: bool, } impl Default for Envelope { fn default() -> Self { Self { amp_mod: true, attack_ms: AttackMs(0), decay_release_ms: DecayReleaseMs(1663), sustain: Percent(100.0), legato: false, snap: false, } } } time_control!( AttackMs, 10000, [0, 25, 75, 200, 470, 940, 1830, 3100, 5700, 10000] ); time_control!( DecayReleaseMs, 25000, [0, 900, 1400, 2200, 3200, 4900, 6800, 10200, 15400, 25000] ); #[cfg(test)] mod tests { use super::*; use crate::parameter::{RegisterCodec, StoredValue}; use crate::patch::value::tests::check_time_control; #[test] fn attack_follows_firmware_knots() { check_time_control!( AttackMs, [0, 25, 75, 200, 470, 940, 1830, 3100, 5700, 10000] ); } #[test] fn decay_release_follows_firmware_knots() { check_time_control!( DecayReleaseMs, [0, 900, 1400, 2200, 3200, 4900, 6800, 10200, 15400, 25000] ); } #[test] fn attack_interpolates_between_knots() { assert_eq!(AttackMs(50).encode(), StoredValue(5460)); assert_eq!(AttackMs::decode(StoredValue(5460)).unwrap(), AttackMs(50)); } }