use std::time::Duration;
pub struct EnvelopeFollower {
level: f32,
sample_rate: f64,
attack_time: Duration,
release_time: Duration,
attack_coefficient: f64,
release_coefficient: f64,
}
fn calculate_coefficient(sample_rate: f64, time: Duration) -> f64 {
(-1.0 / (sample_rate * time.as_secs_f64())).exp()
}
impl EnvelopeFollower {
pub fn new(sample_rate: f64, attack_time: Duration, release_time: Duration) -> Self {
Self {
level: 0.0,
sample_rate,
attack_time,
release_time,
attack_coefficient: calculate_coefficient(sample_rate, attack_time),
release_coefficient: calculate_coefficient(sample_rate, release_time),
}
}
pub fn set_attack_time(&mut self, attack_time: Duration) {
if self.attack_time != attack_time {
self.attack_time = attack_time;
self.attack_coefficient = calculate_coefficient(self.sample_rate, attack_time);
}
}
pub fn set_release_time(&mut self, release_time: Duration) {
if self.release_time != release_time {
self.release_time = release_time;
self.release_coefficient = calculate_coefficient(self.sample_rate, release_time);
}
}
pub fn process(&mut self, sample: f32) -> f32 {
let input = sample.abs();
let coefficient = if self.level < input {
self.attack_coefficient
} else {
self.release_coefficient
};
self.level = (self.level * coefficient as f32) + input * (1.0_f32 - coefficient as f32);
self.level
}
}
#[cfg(test)]
mod tests {
use approx::assert_relative_eq;
use itertools::Itertools;
use super::*;
#[test]
fn test_0_attack_time() {
let sample_rate = 48_000.0;
let attack_time = Duration::from_millis(0);
let release_time = Duration::from_millis(100);
let mut envelope = EnvelopeFollower::new(sample_rate, attack_time, release_time);
let frame_count = sample_rate as usize;
let envelope: Vec<f32> = (0..frame_count)
.map(|_| envelope.process(1.0_f32))
.collect();
for value in envelope {
assert_relative_eq!(value, 1.0_f32);
}
}
#[test]
fn test_0_release_time() {
let sample_rate = 48_000.0;
let attack_time = Duration::from_millis(0);
let release_time = Duration::from_millis(0);
let mut envelope = EnvelopeFollower::new(sample_rate, attack_time, release_time);
let frame_count = sample_rate as usize;
envelope.process(1.0_f32);
let envelope: Vec<f32> = (0..frame_count)
.map(|_| envelope.process(0.0_f32))
.collect();
for value in envelope {
assert_relative_eq!(value, 0.0_f32);
}
}
#[test]
fn test_attack_time() {
let sample_rate = 48_000.0;
let attack_time = Duration::from_millis(100);
let release_time = Duration::from_millis(0);
let mut envelope = EnvelopeFollower::new(sample_rate, attack_time, release_time);
let frame_count = (sample_rate * attack_time.as_secs_f64()).ceil() as usize;
let envelope: Vec<f32> = (0..frame_count - 1)
.map(|_| envelope.process(1.0_f32))
.collect();
let max_value = 1.0_f32 - (-1.0_f32).exp();
assert!(envelope.iter().tuple_windows().all(|(a, b)| a <= b));
assert!(envelope
.iter()
.all(|value| 0.0 <= *value && *value <= max_value));
}
#[test]
fn test_release_time() {
let sample_rate = 48_000.0;
let attack_time = Duration::from_millis(0);
let release_time = Duration::from_millis(100);
let mut envelope = EnvelopeFollower::new(sample_rate, attack_time, release_time);
let frame_count = (sample_rate * release_time.as_secs_f64()).ceil() as usize;
envelope.process(1.0_f32);
let envelope: Vec<f32> = (0..frame_count - 1)
.map(|_| envelope.process(0.0_f32))
.collect();
let min_value = (-1.0_f32).exp();
assert!(envelope.iter().tuple_windows().all(|(a, b)| a >= b));
assert!(envelope
.iter()
.all(|value| min_value <= *value && *value <= 1.0_f32));
}
}