#[derive(Debug, Clone)]
pub struct EnvelopeFollower {
envelope: f32,
attack_coeff: f32,
release_coeff: f32,
sample_rate: f32,
}
impl EnvelopeFollower {
#[must_use]
pub fn new(attack_ms: f32, release_ms: f32, sample_rate: f32) -> Self {
let attack_coeff = (-1000.0 / (attack_ms * sample_rate)).exp();
let release_coeff = (-1000.0 / (release_ms * sample_rate)).exp();
Self {
envelope: 0.0,
attack_coeff,
release_coeff,
sample_rate,
}
}
pub fn process(&mut self, input: f32) -> f32 {
let input_abs = input.abs();
if input_abs > self.envelope {
self.envelope = input_abs + self.attack_coeff * (self.envelope - input_abs);
} else {
self.envelope = input_abs + self.release_coeff * (self.envelope - input_abs);
}
self.envelope
}
#[must_use]
pub fn current(&self) -> f32 {
self.envelope
}
pub fn reset(&mut self) {
self.envelope = 0.0;
}
pub fn set_attack(&mut self, attack_ms: f32) {
self.attack_coeff = (-1000.0 / (attack_ms * self.sample_rate)).exp();
}
pub fn set_release(&mut self, release_ms: f32) {
self.release_coeff = (-1000.0 / (release_ms * self.sample_rate)).exp();
}
}
#[derive(Debug, Clone)]
pub struct RmsDetector {
buffer: Vec<f32>,
write_pos: usize,
sum_squares: f32,
window_size: usize,
}
impl RmsDetector {
#[must_use]
pub fn new(window_ms: f32, sample_rate: f32) -> Self {
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
let window_size = ((window_ms * sample_rate / 1000.0) as usize).max(1);
let buffer = vec![0.0; window_size];
Self {
buffer,
write_pos: 0,
sum_squares: 0.0,
window_size,
}
}
pub fn process(&mut self, input: f32) -> f32 {
let old_value = self.buffer[self.write_pos];
self.sum_squares -= old_value * old_value;
self.buffer[self.write_pos] = input;
self.sum_squares += input * input;
self.write_pos = (self.write_pos + 1) % self.window_size;
#[allow(clippy::cast_precision_loss)]
let rms_val = (self.sum_squares / self.window_size as f32).sqrt();
rms_val
}
#[must_use]
pub fn current(&self) -> f32 {
#[allow(clippy::cast_precision_loss)]
let rms_val = (self.sum_squares / self.window_size as f32).sqrt();
rms_val
}
pub fn reset(&mut self) {
self.buffer.fill(0.0);
self.write_pos = 0;
self.sum_squares = 0.0;
}
}
#[derive(Debug, Clone)]
pub struct PeakDetector {
peak: f32,
hold_counter: usize,
hold_samples: usize,
decay_coeff: f32,
}
impl PeakDetector {
#[must_use]
pub fn new(hold_ms: f32, decay_ms: f32, sample_rate: f32) -> Self {
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
let hold_samples = (hold_ms * sample_rate / 1000.0) as usize;
let decay_coeff = (-1000.0 / (decay_ms * sample_rate)).exp();
Self {
peak: 0.0,
hold_counter: 0,
hold_samples,
decay_coeff,
}
}
pub fn process(&mut self, input: f32) -> f32 {
let input_abs = input.abs();
if input_abs >= self.peak {
self.peak = input_abs;
self.hold_counter = self.hold_samples;
} else if self.hold_counter > 0 {
self.hold_counter -= 1;
} else {
self.peak *= self.decay_coeff;
}
self.peak
}
#[must_use]
pub fn current(&self) -> f32 {
self.peak
}
pub fn reset(&mut self) {
self.peak = 0.0;
self.hold_counter = 0;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_envelope_follower_attack() {
let mut env = EnvelopeFollower::new(1.0, 100.0, 48000.0);
let _ = env.process(0.0);
let v1 = env.process(1.0);
let v2 = env.process(1.0);
assert!(v1 > 0.0);
assert!(v2 > v1);
}
#[test]
fn test_envelope_follower_release() {
let mut env = EnvelopeFollower::new(1.0, 100.0, 48000.0);
env.process(1.0);
env.process(1.0);
let v1 = env.process(0.0);
let v2 = env.process(0.0);
let v3 = env.process(0.0);
assert!(v1 < 1.0);
assert!(v2 < v1);
assert!(v3 < v2);
assert!(v3 > 0.0); }
#[test]
fn test_rms_detector() {
let mut rms = RmsDetector::new(10.0, 48000.0);
assert_eq!(rms.process(0.0), 0.0);
for _ in 0..1000 {
rms.process(1.0);
}
let rms_value = rms.current();
assert!((rms_value - 1.0).abs() < 0.01);
}
#[test]
fn test_rms_sine_wave() {
let mut rms = RmsDetector::new(10.0, 48000.0);
use std::f32::consts::TAU;
for i in 0..1000 {
#[allow(clippy::cast_precision_loss)]
let sample = (i as f32 * TAU / 100.0).sin();
rms.process(sample);
}
let rms_value = rms.current();
assert!((rms_value - 0.707).abs() < 0.1);
}
#[test]
fn test_peak_detector() {
let mut peak = PeakDetector::new(100.0, 1000.0, 48000.0);
peak.process(1.0);
assert_eq!(peak.current(), 1.0);
for _ in 0..1000 {
peak.process(0.0);
}
assert_eq!(peak.current(), 1.0);
for _ in 0..10000 {
peak.process(0.0);
}
assert!(peak.current() < 1.0);
}
#[test]
fn test_envelope_reset() {
let mut env = EnvelopeFollower::new(1.0, 100.0, 48000.0);
env.process(1.0);
assert!(env.current() > 0.0);
env.reset();
assert_eq!(env.current(), 0.0);
}
}