| 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 | } |