#[derive(Debug, Clone, Copy)]
pub struct EnvelopeFollower {
envelope: f32,
attack_coeff: f32,
release_coeff: f32,
}
impl EnvelopeFollower {
pub fn new(attack_ms: f32, release_ms: f32, sample_rate: u32) -> Self {
Self {
envelope: 0.0,
attack_coeff: Self::ms_to_coeff(attack_ms, sample_rate),
release_coeff: Self::ms_to_coeff(release_ms, sample_rate),
}
}
fn ms_to_coeff(time_ms: f32, sample_rate: u32) -> f32 {
if time_ms <= 0.0 {
return 0.0;
}
(-1.0 / (time_ms * 0.001 * sample_rate as f32)).exp()
}
#[inline]
pub fn process(&mut self, input_abs: f32) -> f32 {
let input_abs = if input_abs.is_finite() {
input_abs
} else {
0.0
};
let coeff = if input_abs > self.envelope {
self.attack_coeff
} else {
self.release_coeff
};
self.envelope = input_abs + coeff * (self.envelope - input_abs);
self.envelope
}
#[inline]
pub fn process_block(&mut self, samples: &[f32]) -> f32 {
for &sample in samples {
self.process(sample.abs());
}
self.envelope
}
pub fn set_times(&mut self, attack_ms: f32, release_ms: f32, sample_rate: u32) {
self.attack_coeff = Self::ms_to_coeff(attack_ms, sample_rate);
self.release_coeff = Self::ms_to_coeff(release_ms, sample_rate);
}
pub fn reset(&mut self) {
self.envelope = 0.0;
}
#[inline]
pub fn current(&self) -> f32 {
self.envelope
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_attack_from_silence() {
let mut env = EnvelopeFollower::new(10.0, 100.0, 48000);
let mut prev = 0.0;
for _ in 0..480 {
let val = env.process(1.0);
assert!(val >= prev);
prev = val;
}
assert!(prev > 0.5, "Envelope too slow: {prev}");
assert!(prev < 0.8, "Envelope too fast: {prev}");
}
#[test]
fn test_release_from_peak() {
let mut env = EnvelopeFollower::new(0.1, 50.0, 48000);
for _ in 0..48 {
env.process(1.0);
}
let peak = env.current();
assert!(peak > 0.9);
for _ in 0..2400 {
env.process(0.0);
}
let after_release = env.current();
assert!(after_release < 0.4, "Release too slow: {after_release}");
}
#[test]
fn test_process_block() {
let mut env = EnvelopeFollower::new(1.0, 100.0, 48000);
let block: Vec<f32> = (0..480).map(|_| 0.5).collect();
let result = env.process_block(&block);
assert!(result > 0.3);
}
#[test]
fn test_reset() {
let mut env = EnvelopeFollower::new(1.0, 100.0, 48000);
env.process(1.0);
assert!(env.current() > 0.0);
env.reset();
assert_eq!(env.current(), 0.0);
}
#[test]
fn test_zero_attack_time() {
let mut env = EnvelopeFollower::new(0.0, 100.0, 48000);
let val = env.process(0.75);
assert!((val - 0.75).abs() < 1e-6);
}
}