atagen

headroom

public
Star0
Watch0
da174e47c3dfe446a07b3dd77fdfcd414277a44e
1
ssh://brewery@pls.lobotomise.me:33/atagen/headroom.git

Blame: crates/headroom-dsp/src/envelope.rs

614b4e43f9f3atagen2026-05-27 15:49 UTC1
//! exponential attack/release envelope follower.
614b4e43f9f3atagen2026-05-27 15:49 UTC2
614b4e43f9f3atagen2026-05-27 15:49 UTC3
use crate::util::time_to_alpha;
614b4e43f9f3atagen2026-05-27 15:49 UTC4
614b4e43f9f3atagen2026-05-27 15:49 UTC5
pub struct AttackRelease {
614b4e43f9f3atagen2026-05-27 15:49 UTC6
    attack_alpha: f32,
614b4e43f9f3atagen2026-05-27 15:49 UTC7
    release_alpha: f32,
614b4e43f9f3atagen2026-05-27 15:49 UTC8
    state: f32,
614b4e43f9f3atagen2026-05-27 15:49 UTC9
}
614b4e43f9f3atagen2026-05-27 15:49 UTC10
614b4e43f9f3atagen2026-05-27 15:49 UTC11
impl AttackRelease {
614b4e43f9f3atagen2026-05-27 15:49 UTC12
    #[must_use]
614b4e43f9f3atagen2026-05-27 15:49 UTC13
    pub fn new(attack_ms: f32, release_ms: f32, sample_rate: f32) -> Self {
614b4e43f9f3atagen2026-05-27 15:49 UTC14
        Self {
614b4e43f9f3atagen2026-05-27 15:49 UTC15
            attack_alpha: time_to_alpha(attack_ms, sample_rate),
614b4e43f9f3atagen2026-05-27 15:49 UTC16
            release_alpha: time_to_alpha(release_ms, sample_rate),
614b4e43f9f3atagen2026-05-27 15:49 UTC17
            state: 0.0,
614b4e43f9f3atagen2026-05-27 15:49 UTC18
        }
614b4e43f9f3atagen2026-05-27 15:49 UTC19
    }
614b4e43f9f3atagen2026-05-27 15:49 UTC20
614b4e43f9f3atagen2026-05-27 15:49 UTC21
    pub fn set_times(&mut self, attack_ms: f32, release_ms: f32, sample_rate: f32) {
614b4e43f9f3atagen2026-05-27 15:49 UTC22
        self.attack_alpha = time_to_alpha(attack_ms, sample_rate);
614b4e43f9f3atagen2026-05-27 15:49 UTC23
        self.release_alpha = time_to_alpha(release_ms, sample_rate);
614b4e43f9f3atagen2026-05-27 15:49 UTC24
    }
614b4e43f9f3atagen2026-05-27 15:49 UTC25
614b4e43f9f3atagen2026-05-27 15:49 UTC26
    /// attack on rising input, release on falling.
614b4e43f9f3atagen2026-05-27 15:49 UTC27
    pub fn process_peak(&mut self, target: f32) -> f32 {
614b4e43f9f3atagen2026-05-27 15:49 UTC28
        if target > self.state {
614b4e43f9f3atagen2026-05-27 15:49 UTC29
            self.state += self.attack_alpha * (target - self.state);
614b4e43f9f3atagen2026-05-27 15:49 UTC30
        } else {
614b4e43f9f3atagen2026-05-27 15:49 UTC31
            self.state += self.release_alpha * (target - self.state);
614b4e43f9f3atagen2026-05-27 15:49 UTC32
        }
614b4e43f9f3atagen2026-05-27 15:49 UTC33
        self.state
614b4e43f9f3atagen2026-05-27 15:49 UTC34
    }
614b4e43f9f3atagen2026-05-27 15:49 UTC35
614b4e43f9f3atagen2026-05-27 15:49 UTC36
    /// inverse of process_peak: attack on falling gain, release on rising (recover to unity).
614b4e43f9f3atagen2026-05-27 15:49 UTC37
    pub fn process_gain(&mut self, target: f32) -> f32 {
614b4e43f9f3atagen2026-05-27 15:49 UTC38
        if target < self.state {
614b4e43f9f3atagen2026-05-27 15:49 UTC39
            self.state += self.attack_alpha * (target - self.state);
614b4e43f9f3atagen2026-05-27 15:49 UTC40
        } else {
614b4e43f9f3atagen2026-05-27 15:49 UTC41
            self.state += self.release_alpha * (target - self.state);
614b4e43f9f3atagen2026-05-27 15:49 UTC42
        }
614b4e43f9f3atagen2026-05-27 15:49 UTC43
        self.state
614b4e43f9f3atagen2026-05-27 15:49 UTC44
    }
614b4e43f9f3atagen2026-05-27 15:49 UTC45
614b4e43f9f3atagen2026-05-27 15:49 UTC46
    #[must_use]
614b4e43f9f3atagen2026-05-27 15:49 UTC47
    pub fn state(&self) -> f32 {
614b4e43f9f3atagen2026-05-27 15:49 UTC48
        self.state
614b4e43f9f3atagen2026-05-27 15:49 UTC49
    }
614b4e43f9f3atagen2026-05-27 15:49 UTC50
614b4e43f9f3atagen2026-05-27 15:49 UTC51
    pub fn reset(&mut self, value: f32) {
614b4e43f9f3atagen2026-05-27 15:49 UTC52
        self.state = value;
614b4e43f9f3atagen2026-05-27 15:49 UTC53
    }
614b4e43f9f3atagen2026-05-27 15:49 UTC54
}
614b4e43f9f3atagen2026-05-27 15:49 UTC55
614b4e43f9f3atagen2026-05-27 15:49 UTC56
#[cfg(test)]
614b4e43f9f3atagen2026-05-27 15:49 UTC57
mod tests {
614b4e43f9f3atagen2026-05-27 15:49 UTC58
    use super::*;
614b4e43f9f3atagen2026-05-27 15:49 UTC59
614b4e43f9f3atagen2026-05-27 15:49 UTC60
    #[test]
614b4e43f9f3atagen2026-05-27 15:49 UTC61
    fn peak_mode_attacks_fast_releases_slow() {
614b4e43f9f3atagen2026-05-27 15:49 UTC62
        let fs = 48_000.0;
614b4e43f9f3atagen2026-05-27 15:49 UTC63
        let mut env = AttackRelease::new(0.1, 100.0, fs);
614b4e43f9f3atagen2026-05-27 15:49 UTC64
        // Drive to 1.0 and let it settle.
614b4e43f9f3atagen2026-05-27 15:49 UTC65
        for _ in 0..100 {
614b4e43f9f3atagen2026-05-27 15:49 UTC66
            env.process_peak(1.0);
614b4e43f9f3atagen2026-05-27 15:49 UTC67
        }
614b4e43f9f3atagen2026-05-27 15:49 UTC68
        assert!(env.state() > 0.99);
614b4e43f9f3atagen2026-05-27 15:49 UTC69
        // Drop input to 0.0 and verify slow decay.
614b4e43f9f3atagen2026-05-27 15:49 UTC70
        env.process_peak(0.0);
614b4e43f9f3atagen2026-05-27 15:49 UTC71
        assert!(env.state() > 0.999);
614b4e43f9f3atagen2026-05-27 15:49 UTC72
        for _ in 0..10 {
614b4e43f9f3atagen2026-05-27 15:49 UTC73
            env.process_peak(0.0);
614b4e43f9f3atagen2026-05-27 15:49 UTC74
        }
614b4e43f9f3atagen2026-05-27 15:49 UTC75
        // Still well above zero on the release time scale.
614b4e43f9f3atagen2026-05-27 15:49 UTC76
        assert!(env.state() > 0.8);
614b4e43f9f3atagen2026-05-27 15:49 UTC77
    }
614b4e43f9f3atagen2026-05-27 15:49 UTC78
614b4e43f9f3atagen2026-05-27 15:49 UTC79
    #[test]
614b4e43f9f3atagen2026-05-27 15:49 UTC80
    fn gain_mode_attacks_on_drop() {
614b4e43f9f3atagen2026-05-27 15:49 UTC81
        let fs = 48_000.0;
614b4e43f9f3atagen2026-05-27 15:49 UTC82
        let mut env = AttackRelease::new(0.1, 100.0, fs);
614b4e43f9f3atagen2026-05-27 15:49 UTC83
        env.reset(1.0);
614b4e43f9f3atagen2026-05-27 15:49 UTC84
        // Demand a gain drop. Should snap down quickly.
614b4e43f9f3atagen2026-05-27 15:49 UTC85
        for _ in 0..100 {
614b4e43f9f3atagen2026-05-27 15:49 UTC86
            env.process_gain(0.5);
614b4e43f9f3atagen2026-05-27 15:49 UTC87
        }
614b4e43f9f3atagen2026-05-27 15:49 UTC88
        assert!(env.state() < 0.51);
614b4e43f9f3atagen2026-05-27 15:49 UTC89
    }
614b4e43f9f3atagen2026-05-27 15:49 UTC90
}