atagen

headroom

public
Star0
Watch0
c454b10854660e0f55220e43b21a60913b505716
1
ssh://brewery@pls.lobotomise.me:33/atagen/headroom.git
crates/headroom-dsp/src/envelope.rsRust90 lines2.5 KB
1
//! exponential attack/release envelope follower.
2
3
use crate::util::time_to_alpha;
4
5
pub struct AttackRelease {
6
    attack_alpha: f32,
7
    release_alpha: f32,
8
    state: f32,
9
}
10
11
impl AttackRelease {
12
    #[must_use]
13
    pub fn new(attack_ms: f32, release_ms: f32, sample_rate: f32) -> Self {
14
        Self {
15
            attack_alpha: time_to_alpha(attack_ms, sample_rate),
16
            release_alpha: time_to_alpha(release_ms, sample_rate),
17
            state: 0.0,
18
        }
19
    }
20
21
    pub fn set_times(&mut self, attack_ms: f32, release_ms: f32, sample_rate: f32) {
22
        self.attack_alpha = time_to_alpha(attack_ms, sample_rate);
23
        self.release_alpha = time_to_alpha(release_ms, sample_rate);
24
    }
25
26
    /// attack on rising input, release on falling.
27
    pub fn process_peak(&mut self, target: f32) -> f32 {
28
        if target > self.state {
29
            self.state += self.attack_alpha * (target - self.state);
30
        } else {
31
            self.state += self.release_alpha * (target - self.state);
32
        }
33
        self.state
34
    }
35
36
    /// inverse of process_peak: attack on falling gain, release on rising (recover to unity).
37
    pub fn process_gain(&mut self, target: f32) -> f32 {
38
        if target < self.state {
39
            self.state += self.attack_alpha * (target - self.state);
40
        } else {
41
            self.state += self.release_alpha * (target - self.state);
42
        }
43
        self.state
44
    }
45
46
    #[must_use]
47
    pub fn state(&self) -> f32 {
48
        self.state
49
    }
50
51
    pub fn reset(&mut self, value: f32) {
52
        self.state = value;
53
    }
54
}
55
56
#[cfg(test)]
57
mod tests {
58
    use super::*;
59
60
    #[test]
61
    fn peak_mode_attacks_fast_releases_slow() {
62
        let fs = 48_000.0;
63
        let mut env = AttackRelease::new(0.1, 100.0, fs);
64
        // Drive to 1.0 and let it settle.
65
        for _ in 0..100 {
66
            env.process_peak(1.0);
67
        }
68
        assert!(env.state() > 0.99);
69
        // Drop input to 0.0 and verify slow decay.
70
        env.process_peak(0.0);
71
        assert!(env.state() > 0.999);
72
        for _ in 0..10 {
73
            env.process_peak(0.0);
74
        }
75
        // Still well above zero on the release time scale.
76
        assert!(env.state() > 0.8);
77
    }
78
79
    #[test]
80
    fn gain_mode_attacks_on_drop() {
81
        let fs = 48_000.0;
82
        let mut env = AttackRelease::new(0.1, 100.0, fs);
83
        env.reset(1.0);
84
        // Demand a gain drop. Should snap down quickly.
85
        for _ in 0..100 {
86
            env.process_gain(0.5);
87
        }
88
        assert!(env.state() < 0.51);
89
    }
90
}