synthahol_phase_plant/modulator/
envelope.rs

1//! [Envelope Modulator](https://kilohearts.com/docs/modulation#envelope)
2
3use std::any::Any;
4
5use uom::si::ratio::{percent, ratio};
6
7use super::*;
8
9#[derive(Debug, PartialEq)]
10pub struct EnvelopeModulator {
11    pub envelope: Envelope,
12    pub depth: Ratio,
13
14    /// Ranges from -1.0..=1.0
15    pub trigger_threshold: Ratio,
16    pub note_trigger_mode: NoteTriggerMode,
17    pub seamless: bool,
18}
19
20impl Default for EnvelopeModulator {
21    fn default() -> Self {
22        Self {
23            envelope: Default::default(),
24            depth: Ratio::new::<percent>(100.0),
25            note_trigger_mode: NoteTriggerMode::Auto,
26            trigger_threshold: Ratio::new::<ratio>(0.5),
27            seamless: false,
28        }
29    }
30}
31
32impl Modulator for EnvelopeModulator {
33    fn as_block(&self) -> ModulatorBlock {
34        self.into()
35    }
36
37    fn box_eq(&self, other: &dyn Any) -> bool {
38        other
39            .downcast_ref::<Self>()
40            .map_or(false, |other| self == other)
41    }
42
43    fn mode(&self) -> ModulatorMode {
44        ModulatorMode::Envelope
45    }
46}
47
48#[cfg(test)]
49mod test {
50    use approx::assert_relative_eq;
51    use uom::si::ratio::percent;
52    use uom::si::time::second;
53
54    use crate::test::read_modulator_preset;
55
56    use super::*;
57
58    #[test]
59    fn init() {
60        for file in &["envelope-1.8.13.phaseplant", "envelope-2.1.0.phaseplant"] {
61            let preset = read_modulator_preset("envelope", file).unwrap();
62            assert_eq!(preset.modulator_containers.len(), 1);
63            let container = preset.modulator_container(0).unwrap();
64            assert_eq!(container.id, 0);
65            assert!(container.enabled);
66            assert!(!container.minimized);
67            let modulator: &EnvelopeModulator = preset.modulator(0).unwrap();
68            assert_relative_eq!(modulator.depth.get::<percent>(), 100.0);
69            assert_eq!(modulator.trigger_threshold.get::<percent>(), 50.0);
70            assert_eq!(modulator.note_trigger_mode, NoteTriggerMode::Auto);
71            assert!(!modulator.seamless);
72            let envelope = &modulator.envelope;
73            assert_relative_eq!(envelope.delay.get::<second>(), 0.0);
74            assert_relative_eq!(envelope.attack.get::<second>(), 0.010, epsilon = 0.00001);
75            assert_relative_eq!(envelope.attack_curve, 0.0);
76            assert_relative_eq!(envelope.hold.get::<second>(), 0.0);
77            assert_relative_eq!(envelope.decay.get::<second>(), 0.100, epsilon = 0.0001);
78            assert_relative_eq!(envelope.decay_falloff, 0.0);
79            assert_relative_eq!(envelope.sustain.get::<percent>(), 1.0);
80            assert_relative_eq!(envelope.release.get::<second>(), 0.100, epsilon = 0.0001);
81            assert_relative_eq!(envelope.release_falloff, 0.0);
82        }
83    }
84
85    #[test]
86    fn eleven_to_sixteen() {
87        let preset =
88            read_modulator_preset("envelope", "envelope-11to16-1.8.13.phaseplant").unwrap();
89        let modulator: &EnvelopeModulator = preset.modulator(0).unwrap();
90        assert_relative_eq!(modulator.depth.get::<percent>(), 100.0);
91        let envelope = &modulator.envelope;
92        assert_relative_eq!(envelope.delay.get::<second>(), 0.011, epsilon = 0.0001);
93        assert_relative_eq!(envelope.attack.get::<second>(), 0.012, epsilon = 0.0001);
94        assert_relative_eq!(envelope.attack_curve, 0.0);
95        assert_relative_eq!(envelope.hold.get::<second>(), 0.013, epsilon = 0.0001);
96        assert_relative_eq!(envelope.decay.get::<second>(), 0.014, epsilon = 0.0001);
97        assert_relative_eq!(envelope.decay_falloff, 0.0);
98        assert_relative_eq!(envelope.sustain.get::<percent>(), 0.15, epsilon = 0.0001);
99        assert_relative_eq!(envelope.release.get::<second>(), 0.016, epsilon = 0.0001);
100        assert_relative_eq!(envelope.release_falloff, 0.0);
101    }
102
103    #[test]
104    fn curves() {
105        let preset =
106            read_modulator_preset("envelope", "envelope-curves25-50-75-1.8.13.phaseplant").unwrap();
107        let modulator: &EnvelopeModulator = preset.modulator(0).unwrap();
108        assert_relative_eq!(modulator.envelope.attack_curve, 0.25);
109        assert_relative_eq!(modulator.envelope.decay_falloff, 0.50);
110        assert_relative_eq!(modulator.envelope.release_falloff, 0.75);
111    }
112
113    #[test]
114    fn disabled() {
115        let preset =
116            read_modulator_preset("envelope", "envelope-disabled-1.8.13.phaseplant").unwrap();
117        let container = preset.modulator_container(0).unwrap();
118        assert!(!container.enabled);
119    }
120
121    #[test]
122    fn depth() {
123        let preset =
124            read_modulator_preset("envelope", "envelope-minimized-depth50-1.8.13.phaseplant")
125                .unwrap();
126        let container = preset.modulator_container(0).unwrap();
127        assert!(container.enabled);
128        let modulator: &EnvelopeModulator = preset.modulator(0).unwrap();
129        assert_relative_eq!(modulator.depth.get::<percent>(), 50.0);
130    }
131
132    #[test]
133    fn note_trigger() {
134        let preset =
135            read_modulator_preset("envelope", "envelope-note_trigger_always-2.1.0.phaseplant")
136                .unwrap();
137        let modulator: &EnvelopeModulator = preset.modulator(0).unwrap();
138        assert_eq!(modulator.note_trigger_mode, NoteTriggerMode::Always);
139    }
140
141    #[test]
142    fn seamless() {
143        let preset =
144            read_modulator_preset("envelope", "envelope-seamless-2.1.0.phaseplant").unwrap();
145        let modulator: &EnvelopeModulator = preset.modulator(0).unwrap();
146        assert!(modulator.seamless);
147    }
148
149    #[test]
150    fn trigger_threshold() {
151        let preset =
152            read_modulator_preset("envelope", "envelope-trigger_threshold25-2.1.0.phaseplant")
153                .unwrap();
154        let modulator: &EnvelopeModulator = preset.modulator(0).unwrap();
155        assert_eq!(modulator.trigger_threshold.get::<percent>(), 25.0);
156    }
157}