synfx_dsp/
env.rs

1// Copyright (c) 2022 Weird Constructor <weirdconstructor@gmail.com>
2// This file is a part of synfx-dsp. Released under GPL-3.0-or-later.
3// See README.md and COPYING for details.
4
5/*! Provides you with all the tools for building ADSR or any other kind of envelopes.
6
7See also:
8
9- [EnvState] which holds the state of the envelope.
10- [EnvRetrigAD] is a complete implementation of an attack decay envelope.
11- [crate::env_hold_stage] for a hold stage piece
12- [crate::env_target_stage] for an attack/decay/release stage piece
13- [crate::env_sustain_stage] for a sustain stage piece
14*/
15
16use crate::sqrt4_to_pow4;
17use crate::{TrigSignal, Trigger};
18
19/// Envelope state structure for the macros [crate::env_hold_stage],
20/// [crate::env_target_stage] and [crate::env_sustain_stage].
21///
22///```
23/// use synfx_dsp::{EnvState, env_hold_stage, env_target_stage, assert_decimated_slope_feq};
24/// let mut state = EnvState::new();
25/// state.set_sample_rate(48000.0);
26///
27/// let attack_ms = 1.0;
28/// let hold_ms = 2.0;
29/// let delay_ms = 2.0;
30///
31/// state.trigger();
32/// let mut env_samples = vec![];
33/// for _ in 0..(((48000.0 * (attack_ms + hold_ms + delay_ms)) / 1000.0) as usize) {
34///     env_target_stage!(state, 0, attack_ms, 1.0, |x| x, {
35///         env_hold_stage!(state, 2, hold_ms, {
36///             env_target_stage!(state, 4, delay_ms, 0.0, |x| x, {});
37///         });
38///     });
39///     env_samples.push(state.current);
40/// }
41///
42/// assert_decimated_slope_feq!(env_samples[0..48], 4, vec![0.02083; 100]);
43///```
44#[derive(Debug, Clone)]
45pub struct EnvState {
46    pub srate_ms: f32,
47    pub stage: u32,
48    pub phase: f32,
49    pub start: f32,
50    pub current: f32,
51}
52
53impl EnvState {
54    /// Create a new envelope state structure.
55    pub fn new() -> Self {
56        Self {
57            srate_ms: 44100.0 / 1000.0,
58            stage: std::u32::MAX,
59            phase: 0.0,
60            start: 0.0,
61            current: 0.0,
62        }
63    }
64
65    #[inline]
66    pub fn set_sample_rate(&mut self, srate: f32) {
67        self.srate_ms = srate / 1000.0;
68    }
69
70    #[inline]
71    pub fn trigger(&mut self) {
72        self.stage = 0;
73    }
74
75    #[inline]
76    pub fn is_running(&self) -> bool {
77        self.stage != std::u32::MAX
78    }
79
80    #[inline]
81    pub fn stop_immediately(&mut self) {
82        self.stage = std::u32::MAX;
83    }
84
85    pub fn reset(&mut self) {
86        self.stage = std::u32::MAX;
87        self.phase = 0.0;
88        self.start = 0.0;
89        self.current = 0.0;
90    }
91}
92
93/// Holds the previous `state.current` value for `$time_ms`.
94///
95/// See also [EnvState] about the first argument `$state`.
96/// `$stage_idx` is `$stage_idx + 2` after this stage is finished.
97#[macro_export]
98macro_rules! env_hold_stage {
99    ($state: expr, $stage_idx: expr, $time_ms: expr, $else: block) => {
100        if $state.stage == $stage_idx || $state.stage == ($stage_idx + 1) {
101            if $state.stage == $stage_idx {
102                $state.phase = 0.0;
103                $state.stage += 1;
104                $state.start = $state.current;
105            }
106
107            let inc = 1.0 / ($time_ms * $state.srate_ms);
108            $state.phase += inc;
109            if $state.phase >= 1.0 {
110                $state.stage += 1;
111            }
112            $state.current = $state.start;
113        } else $else
114    };
115}
116
117/// Increases/Decreases `state.current` value until you are at `$value` within `$time_ms`.
118///
119/// This envelope part is great for a release stage. Or an fixed time attack stage.
120/// See also [crate::env_target_stage_lin_time_adj].
121///
122/// See also [EnvState] about the first argument `$state`.
123/// `$shape_fn` can be used to shape the line of this envelope stage. Use `|x| x` for a linear envelope.
124/// `$stage_idx` is `$stage_idx + 2` after this stage is finished.
125#[macro_export]
126macro_rules! env_target_stage {
127    ($state: expr, $stage_idx: expr, $time_ms: expr, $value: expr, $shape_fn: expr, $else: block) => {
128        if $state.stage == $stage_idx || $state.stage == ($stage_idx + 1) {
129            if $state.stage == $stage_idx {
130                $state.phase = 0.0;
131                $state.start = $state.current;
132                $state.stage += 1;
133            }
134
135            let inc = 1.0 / ($time_ms * $state.srate_ms).max(1.0);
136            $state.phase += inc;
137            if $state.phase >= 1.0 {
138                $state.stage += 1;
139                $state.current = $value;
140            } else {
141                let phase_shped = ($shape_fn)($state.phase);
142                $state.current = $state.start * (1.0 - phase_shped) + phase_shped * $value;
143            }
144        } else $else
145    };
146}
147
148/// Increases/Decreases `state.current` value until you are at `$value` within an adjusted `$time_ms`.
149/// Depending on how close `state.start` is to `$value`, `$time_ms` is linearily shortened.
150/// For this to work, you need to supply the supposed starting value of the envelope.
151///
152/// This envelope part is great for a retriggerable envelope.
153///
154/// See also [EnvState] about the first argument `$state`.
155/// `$shape_fn` can be used to shape the line of this envelope stage. Use `|x| x` for a linear envelope.
156/// `$stage_idx` is `$stage_idx + 2` after this stage is finished.
157#[macro_export]
158macro_rules! env_target_stage_lin_time_adj {
159    ($state: expr, $stage_idx: expr, $time_ms: expr, $src_value: expr, $value: expr, $shape_fn: expr, $else: block) => {
160        if $state.stage == $stage_idx || $state.stage == ($stage_idx + 1) {
161            if $state.stage == $stage_idx {
162                $state.phase = 0.0;
163                $state.start = $state.current;
164                $state.stage += 1;
165            }
166
167            let time_adj_factor = 1.0 - ($state.start - $src_value) / ($value - $src_value);
168            let inc = 1.0 / (time_adj_factor * $time_ms * $state.srate_ms).max(1.0);
169            $state.phase += inc;
170            if $state.phase >= 1.0 {
171                $state.stage += 1;
172                $state.current = $value;
173            } else {
174                let phase_shped = ($shape_fn)($state.phase);
175                $state.current = $state.start * (1.0 - phase_shped) + phase_shped * $value;
176            }
177        } else $else
178    };
179}
180
181/// Holds the previous `state.current` value until `$gate` drops below [crate::TRIG_LOW_THRES].
182///
183/// See also [EnvState] about the first argument `$state`.
184/// `$stage_idx` is `$stage_idx + 1` after this stage is finished.
185#[macro_export]
186macro_rules! env_sustain_stage {
187    ($state: expr, $stage_idx: expr, $sustain_value: expr, $gate: expr, $else: block) => {
188        if $state.stage == $stage_idx {
189            if $gate < $crate::TRIG_LOW_THRES {
190                $state.stage += 1;
191            }
192
193            $state.current = $sustain_value;
194        } else $else
195    };
196}
197
198/// A retriggerable AD (Attack & Decay) envelope with modifyable shapes for the attack and decay.
199///
200/// For a more elaborate example see [EnvRetrigAD::tick].
201///
202///```
203/// use synfx_dsp::EnvRetrigAD;
204///
205/// let mut env = EnvRetrigAD::new();
206/// // ..
207/// env.set_sample_rate(44100.0);
208/// // ..
209/// let attack_ms = 3.0;
210/// let decay_ms  = 10.0;
211/// let attack_shape = 0.5; // 0.5 == linear
212/// let decay_shape = 0.5; // 0.5 == linear
213/// let trigger_signal = 0.0; // Raise to 1.0 for trigger.
214///
215/// let (value, retrig) = env.tick(trigger_signal, attack_ms, attack_shape, decay_ms, decay_shape);
216/// // ..
217///```
218///
219/// Note: The code for this envelope is used and tested by the `Ad` node of HexoDSP.
220#[derive(Debug, Clone)]
221pub struct EnvRetrigAD {
222    state: EnvState,
223    trig: Trigger,
224    trig_sig: TrigSignal,
225}
226
227impl EnvRetrigAD {
228    /// Creates a new instance of the envelope.
229    pub fn new() -> Self {
230        Self { state: EnvState::new(), trig: Trigger::new(), trig_sig: TrigSignal::new() }
231    }
232
233    /// Set the sample rate of the envelope. Unit in samples per second.
234    pub fn set_sample_rate(&mut self, srate: f32) {
235        self.state.set_sample_rate(srate);
236        self.trig_sig.set_sample_rate(srate);
237    }
238
239    /// Reset the internal state of the envelope.
240    pub fn reset(&mut self) {
241        self.state.reset();
242        self.trig_sig.reset();
243        self.trig.reset();
244    }
245
246    /// Computes the next tick for this envelope.
247    /// The inputs can be changed on each tick.
248    ///
249    /// * `trigger` - Trigger input signal, will trigger like [crate::Trigger].
250    /// * `attack_ms` - The milliseconds for the attack stage.
251    /// * `attack_shape` - The shape for the attack stage.
252    ///   Value in the range [[0.0, 1.0]]. 0.5 is linear. See also [crate::sqrt4_to_pow4].
253    /// * `decay_ms` - The milliseconds for the decay stage.
254    /// * `decay_shape` - The shape for the decay stage.
255    ///   Value in the range [[0.0, 1.0]]. 0.5 is linear. See also [crate::sqrt4_to_pow4].
256    ///
257    /// Returned are two values:
258    /// * First the envelope value
259    /// * Second a trigger signal at the end of the envelope.
260    ///
261    ///```
262    /// use synfx_dsp::EnvRetrigAD;
263    /// let mut env = EnvRetrigAD::new();
264    /// env.set_sample_rate(10.0); // Yes, 10 samples per second for testing here :-)
265    ///
266    /// for _ in 0..2 {
267    ///     env.tick(1.0, 500.0, 0.5, 500.0, 0.5);
268    /// }
269    ///
270    /// let (value, _retrig) = env.tick(1.0, 500.0, 0.5, 500.0, 0.5);
271    /// assert!((value - 0.6).abs() < 0.0001);
272    ///
273    /// for _ in 0..5 {
274    ///     env.tick(1.0, 500.0, 0.5, 500.0, 0.5);
275    /// }
276    ///
277    /// let (value, _retrig) = env.tick(1.0, 500.0, 0.5, 500.0, 0.5);
278    /// assert!((value - 0.2).abs() < 0.0001);
279    ///```
280    #[inline]
281    pub fn tick(
282        &mut self,
283        trigger: f32,
284        attack_ms: f32,
285        attack_shape: f32,
286        decay_ms: f32,
287        decay_shape: f32,
288    ) -> (f32, f32) {
289        if self.trig.check_trigger(trigger) {
290            self.state.trigger();
291        }
292
293        if self.state.is_running() {
294            env_target_stage_lin_time_adj!(
295                self.state,
296                0,
297                attack_ms,
298                0.0,
299                1.0,
300                |x: f32| sqrt4_to_pow4(x.clamp(0.0, 1.0), attack_shape),
301                {
302                    env_target_stage!(
303                        self.state,
304                        2,
305                        decay_ms,
306                        0.0,
307                        |x: f32| sqrt4_to_pow4(x.clamp(0.0, 1.0), decay_shape),
308                        {
309                            self.trig_sig.trigger();
310                            self.state.stop_immediately();
311                        }
312                    );
313                }
314            );
315        }
316
317        (self.state.current, self.trig_sig.next())
318    }
319}
320
321#[derive(Debug, Clone, Copy)]
322pub struct EnvADSRParams {
323    pub attack_ms: f32,
324    pub attack_shape: f32,
325    pub decay_ms: f32,
326    pub decay_shape: f32,
327    pub sustain: f32,
328    pub release_ms: f32,
329    pub release_shape: f32,
330}
331
332impl Default for EnvADSRParams {
333    fn default() -> Self {
334        Self {
335            attack_ms: 0.0,
336            attack_shape: 0.0,
337            decay_ms: 0.0,
338            decay_shape: 0.0,
339            sustain: 0.0,
340            release_ms: 0.0,
341            release_shape: 0.0,
342        }
343    }
344}
345
346#[derive(Debug, Clone)]
347pub struct EnvRetrigADSR {
348    state: EnvState,
349    trig: Trigger,
350    trig_sig: TrigSignal,
351}
352
353impl EnvRetrigADSR {
354    /// Creates a new instance of the envelope.
355    pub fn new() -> Self {
356        Self { state: EnvState::new(), trig: Trigger::new(), trig_sig: TrigSignal::new() }
357    }
358
359    /// Set the sample rate of the envelope. Unit in samples per second.
360    pub fn set_sample_rate(&mut self, srate: f32) {
361        self.state.set_sample_rate(srate);
362        self.trig_sig.set_sample_rate(srate);
363    }
364
365    /// Reset the internal state of the envelope.
366    pub fn reset(&mut self) {
367        self.state.reset();
368        self.trig_sig.reset();
369        self.trig.reset();
370    }
371
372    #[inline]
373    pub fn tick(
374        &mut self,
375        gate: f32,
376        params: &mut EnvADSRParams,
377    ) -> (f32, f32) {
378        if self.trig.check_trigger(gate) {
379            self.state.trigger();
380        }
381
382        if self.state.is_running() {
383            // Attack
384            env_target_stage_lin_time_adj!(
385                self.state,
386                0,
387                params.attack_ms,
388                0.0,
389                1.0,
390                |x: f32| sqrt4_to_pow4(x.clamp(0.0, 1.0), params.attack_shape),
391                {
392                    // Decay
393                    env_target_stage!(
394                        self.state,
395                        2,
396                        params.decay_ms,
397                        params.sustain,
398                        |x: f32| sqrt4_to_pow4(x.clamp(0.0, 1.0), params.decay_shape),
399                        {
400                            // Sustain
401                            env_sustain_stage!(
402                                self.state,
403                                4,
404                                params.sustain,
405                                gate,
406                                {
407                                    // Release
408                                    env_target_stage!(
409                                        self.state,
410                                        5,
411                                        params.release_ms,
412                                        0.0,
413                                        |x: f32| sqrt4_to_pow4(x.clamp(0.0, 1.0), params.release_shape),
414                                        {
415                                            self.trig_sig.trigger();
416                                            self.state.stop_immediately();
417                                        }
418                                    );
419                                }
420                            );
421                        }
422                    );
423                }
424            );
425        }
426
427        (self.state.current, self.trig_sig.next())
428    }
429}
430
431#[cfg(test)]
432mod test {
433    use super::*;
434    use crate::assert_decimated_slope_feq;
435    use crate::assert_vec_feq;
436
437    #[test]
438    fn check_hold_stage() {
439        let mut state = EnvState::new();
440        state.trigger();
441
442        state.current = 0.6;
443        for _ in 0..88 {
444            env_hold_stage!(state, 0, 2.0, {});
445            println!("V[{:6.4} / {}]: {:6.4}", state.phase, state.stage, state.current);
446            assert!(state.stage == 1);
447            assert!(state.current > 0.5);
448        }
449
450        env_hold_stage!(state, 0, 2.0, {});
451        assert!(state.stage == 2);
452        assert!(state.current > 0.5);
453    }
454
455    #[test]
456    fn check_target_stage() {
457        let mut state = EnvState::new();
458        state.trigger();
459
460        for _ in 0..88 {
461            env_target_stage!(state, 0, 2.0, 0.6, |x| x, {});
462            assert!(state.stage == 1);
463            println!("V[{:6.4} / {}]: {:6.4}", state.phase, state.stage, state.current);
464        }
465
466        env_target_stage!(state, 0, 2.0, 0.6, |x| x, {});
467        assert!(state.stage == 2);
468        println!("V[{:6.4} / {}]: {:6.4}", state.phase, state.stage, state.current);
469        assert!(state.current >= 0.5999);
470    }
471
472    #[test]
473    fn check_very_short_target_stage() {
474        let mut state = EnvState::new();
475        state.trigger();
476
477        env_target_stage!(state, 0, 0.01, 0.6, |x| x, {});
478        assert!(state.stage == 2);
479        assert!(state.current == 0.6);
480        println!("V[{:6.4} / {}]: {:6.4}", state.phase, state.stage, state.current);
481    }
482
483    #[test]
484    fn check_short_target_stage() {
485        let mut state = EnvState::new();
486        state.trigger();
487
488        env_target_stage!(state, 0, 0.03, 0.6, |x| x, {});
489        println!("V[{:6.4} / {}]: {:6.4}", state.phase, state.stage, state.current);
490        assert!(state.stage == 1);
491        assert!((state.current - 0.4535).abs() < 0.0001);
492
493        env_target_stage!(state, 0, 0.03, 0.6, |x| x, {});
494        println!("V[{:6.4} / {}]: {:6.4}", state.phase, state.stage, state.current);
495        assert!(state.stage == 2);
496        assert!(state.current == 0.6);
497    }
498
499    #[test]
500    fn check_target_adj_stage() {
501        let mut state = EnvState::new();
502        state.trigger();
503
504        state.current = 0.0;
505
506        for _ in 0..88 {
507            env_target_stage_lin_time_adj!(state, 0, 2.0, 0.0, 0.6, |x| x, {});
508            println!("V[{:6.4} / {}]: {:6.4}", state.phase, state.stage, state.current);
509            assert!(state.stage == 1);
510        }
511
512        env_target_stage_lin_time_adj!(state, 0, 2.0, 0.0, 0.6, |x| x, {});
513        assert!(state.stage == 2);
514        println!("V[{:6.4} / {}]: {:6.4}", state.phase, state.stage, state.current);
515        assert!(state.current >= 0.5999);
516    }
517
518    #[test]
519    fn check_target_adj_stage_shortened() {
520        let mut state = EnvState::new();
521        state.trigger();
522
523        state.current = 0.3;
524
525        for _ in 0..44 {
526            env_target_stage_lin_time_adj!(state, 0, 2.0, 0.0, 0.6, |x| x, {});
527            println!("V[{:6.4} / {}]: {:6.4}", state.phase, state.stage, state.current);
528            assert!(state.stage == 1);
529        }
530
531        env_target_stage_lin_time_adj!(state, 0, 2.0, 0.0, 0.6, |x| x, {});
532        assert!(state.stage == 2);
533        println!("V[{:6.4} / {}]: {:6.4}", state.phase, state.stage, state.current);
534        assert!(state.current >= 0.5999);
535    }
536
537    #[test]
538    fn check_target_adj_stage_none() {
539        let mut state = EnvState::new();
540        state.trigger();
541
542        state.current = 0.6;
543
544        env_target_stage_lin_time_adj!(state, 0, 2.0, 0.0, 0.6, |x| x, {});
545        assert!(state.stage == 2);
546        println!("V[{:6.4} / {}]: {:6.4}", state.phase, state.stage, state.current);
547        assert!(state.current >= 0.5999);
548    }
549
550    #[test]
551    fn check_sustain_stage() {
552        let mut state = EnvState::new();
553        state.trigger();
554
555        env_sustain_stage!(state, 0, 0.5, 1.0, {});
556        println!("V[{:6.4} / {}]: {:6.4}", state.phase, state.stage, state.current);
557        assert!(state.stage == 0);
558        assert!((state.current - 0.5).abs() < 0.0001);
559
560        env_sustain_stage!(state, 0, 0.5, 1.0, {});
561        println!("V[{:6.4} / {}]: {:6.4}", state.phase, state.stage, state.current);
562        assert!(state.stage == 0);
563        assert!((state.current - 0.5).abs() < 0.0001);
564
565        env_sustain_stage!(state, 0, 0.5, 0.0, {});
566        println!("V[{:6.4} / {}]: {:6.4}", state.phase, state.stage, state.current);
567        assert!(state.stage == 1);
568        assert!((state.current - 0.5).abs() < 0.0001);
569    }
570
571    #[test]
572    fn check_sustain_stage_short() {
573        let mut state = EnvState::new();
574        state.trigger();
575
576        env_sustain_stage!(state, 0, 0.5, 0.0, {});
577        println!("V[{:6.4} / {}]: {:6.4}", state.phase, state.stage, state.current);
578        assert!(state.stage == 1);
579        assert!((state.current - 0.5).abs() < 0.0001);
580    }
581
582    #[test]
583    fn check_ahd_env() {
584        let mut state = EnvState::new();
585        state.set_sample_rate(48000.0);
586        state.trigger();
587
588        let attack_ms = 1.0;
589        let hold_ms = 2.0;
590        let delay_ms = 2.0;
591
592        let mut env_samples = vec![];
593        for _ in 0..(((48000.0 * (attack_ms + hold_ms + delay_ms)) / 1000.0) as usize) {
594            env_target_stage!(state, 0, attack_ms, 1.0, |x| x, {
595                env_hold_stage!(state, 2, hold_ms, {
596                    env_target_stage!(state, 4, delay_ms, 0.0, |x| x, {});
597                });
598            });
599            env_samples.push(state.current);
600        }
601
602        assert_decimated_slope_feq!(env_samples[0..48], 4, vec![0.02083; 100]);
603        assert_decimated_slope_feq!(env_samples[48..146], 4, vec![0.0; 20]);
604        assert_decimated_slope_feq!(env_samples[146..240], 4, vec![-0.01041; 40]);
605    }
606
607    #[test]
608    fn check_env_ad() {
609        let mut env = EnvRetrigAD::new();
610
611        env.set_sample_rate(10.0);
612
613        let mut values = vec![];
614        let mut retrig_index = -1;
615        for i in 0..16 {
616            let (value, retrig) = env.tick(1.0, 1000.0, 0.5, 500.0, 0.5);
617            values.push(value);
618            if retrig > 0.0 {
619                retrig_index = i as i32;
620            }
621        }
622
623        assert_vec_feq!(
624            values,
625            vec![
626                0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.70000005, 0.8000001, 0.9000001, 1.0, 0.8, 0.6,
627                0.39999998, 0.19999999, 0.0, 0.0
628            ]
629        );
630
631        assert_eq!(retrig_index, 15);
632    }
633
634    #[test]
635    fn check_env_ad_shaped() {
636        let mut env = EnvRetrigAD::new();
637
638        env.set_sample_rate(10.0);
639
640        let mut values = vec![];
641        let mut retrig_index = -1;
642        for i in 0..16 {
643            let (value, retrig) = env.tick(1.0, 1000.0, 0.7, 500.0, 0.3);
644            values.push(value);
645            if retrig > 0.0 {
646                retrig_index = i as i32;
647            }
648        }
649
650        assert_vec_feq!(
651            values,
652            vec![
653                0.2729822, 0.39777088, 0.49817806, 0.58596444, 0.6656854, 0.7396773, 0.809328,
654                0.8755418, 0.93894666, 1.0, 0.928, 0.79199994, 0.592, 0.32799995, 0.0, 0.0
655            ]
656        );
657
658        assert_eq!(retrig_index, 15);
659    }
660}