math_audio_dsp/
envelope_follower.rs1#[derive(Debug, Clone, Copy)]
19pub struct EnvelopeFollower {
20 envelope: f32,
21 attack_coeff: f32,
22 release_coeff: f32,
23}
24
25impl EnvelopeFollower {
26 pub fn new(attack_ms: f32, release_ms: f32, sample_rate: u32) -> Self {
31 Self {
32 envelope: 0.0,
33 attack_coeff: Self::ms_to_coeff(attack_ms, sample_rate),
34 release_coeff: Self::ms_to_coeff(release_ms, sample_rate),
35 }
36 }
37
38 fn ms_to_coeff(time_ms: f32, sample_rate: u32) -> f32 {
39 if time_ms <= 0.0 {
40 return 0.0;
41 }
42 (-1.0 / (time_ms * 0.001 * sample_rate as f32)).exp()
43 }
44
45 #[inline]
50 pub fn process(&mut self, input_abs: f32) -> f32 {
51 let input_abs = if input_abs.is_finite() {
52 input_abs
53 } else {
54 0.0
55 };
56 let coeff = if input_abs > self.envelope {
57 self.attack_coeff
58 } else {
59 self.release_coeff
60 };
61 self.envelope = input_abs + coeff * (self.envelope - input_abs);
62 self.envelope
63 }
64
65 #[inline]
69 pub fn process_block(&mut self, samples: &[f32]) -> f32 {
70 for &sample in samples {
71 self.process(sample.abs());
72 }
73 self.envelope
74 }
75
76 pub fn set_times(&mut self, attack_ms: f32, release_ms: f32, sample_rate: u32) {
78 self.attack_coeff = Self::ms_to_coeff(attack_ms, sample_rate);
79 self.release_coeff = Self::ms_to_coeff(release_ms, sample_rate);
80 }
81
82 pub fn reset(&mut self) {
84 self.envelope = 0.0;
85 }
86
87 #[inline]
89 pub fn current(&self) -> f32 {
90 self.envelope
91 }
92}
93
94#[cfg(test)]
95mod tests {
96 use super::*;
97
98 #[test]
99 fn test_attack_from_silence() {
100 let mut env = EnvelopeFollower::new(10.0, 100.0, 48000);
101 let mut prev = 0.0;
103 for _ in 0..480 {
104 let val = env.process(1.0);
106 assert!(val >= prev);
107 prev = val;
108 }
109 assert!(prev > 0.5, "Envelope too slow: {prev}");
111 assert!(prev < 0.8, "Envelope too fast: {prev}");
112 }
113
114 #[test]
115 fn test_release_from_peak() {
116 let mut env = EnvelopeFollower::new(0.1, 50.0, 48000);
117 for _ in 0..48 {
119 env.process(1.0);
120 }
121 let peak = env.current();
122 assert!(peak > 0.9);
123
124 for _ in 0..2400 {
126 env.process(0.0);
128 }
129 let after_release = env.current();
130 assert!(after_release < 0.4, "Release too slow: {after_release}");
131 }
132
133 #[test]
134 fn test_process_block() {
135 let mut env = EnvelopeFollower::new(1.0, 100.0, 48000);
136 let block: Vec<f32> = (0..480).map(|_| 0.5).collect();
137 let result = env.process_block(&block);
138 assert!(result > 0.3);
139 }
140
141 #[test]
142 fn test_reset() {
143 let mut env = EnvelopeFollower::new(1.0, 100.0, 48000);
144 env.process(1.0);
145 assert!(env.current() > 0.0);
146 env.reset();
147 assert_eq!(env.current(), 0.0);
148 }
149
150 #[test]
151 fn test_zero_attack_time() {
152 let mut env = EnvelopeFollower::new(0.0, 100.0, 48000);
153 let val = env.process(0.75);
155 assert!((val - 0.75).abs() < 1e-6);
156 }
157}