use crate::{utils::EnvelopeFollower, AudioEffect};
#[derive(Debug, Clone)]
pub struct GateConfig {
pub threshold_db: f32,
pub attack_ms: f32,
pub release_ms: f32,
pub hysteresis_db: f32,
}
impl Default for GateConfig {
fn default() -> Self {
Self {
threshold_db: -40.0,
attack_ms: 1.0,
release_ms: 100.0,
hysteresis_db: 6.0,
}
}
}
pub struct Gate {
envelope: EnvelopeFollower,
config: GateConfig,
is_open: bool,
current_gain: f32,
gain_smoother: f32,
}
impl Gate {
#[must_use]
pub fn new(config: GateConfig, sample_rate: f32) -> Self {
Self {
envelope: EnvelopeFollower::new(config.attack_ms, config.release_ms, sample_rate),
config,
is_open: false,
current_gain: 0.0,
gain_smoother: 0.0,
}
}
fn db_to_linear(db: f32) -> f32 {
10.0_f32.powf(db / 20.0)
}
}
impl AudioEffect for Gate {
fn process_sample(&mut self, input: f32) -> f32 {
let envelope = self.envelope.process(input);
let threshold = Self::db_to_linear(self.config.threshold_db);
let target_gain = if self.is_open {
let close_threshold =
Self::db_to_linear(self.config.threshold_db - self.config.hysteresis_db);
if envelope < close_threshold {
self.is_open = false;
0.0
} else {
1.0
}
} else {
if envelope > threshold {
self.is_open = true;
1.0
} else {
0.0
}
};
let smoothing = 0.9;
self.gain_smoother = target_gain + smoothing * (self.gain_smoother - target_gain);
input * self.gain_smoother
}
fn reset(&mut self) {
self.envelope.reset();
self.is_open = false;
self.current_gain = 0.0;
self.gain_smoother = 0.0;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_gate() {
let config = GateConfig::default();
let mut gate = Gate::new(config, 48000.0);
let loud_output = gate.process_sample(0.5);
assert!(loud_output.is_finite());
let quiet_output = gate.process_sample(0.001);
assert!(quiet_output.is_finite());
}
#[test]
fn test_db_to_linear() {
assert!((Gate::db_to_linear(0.0) - 1.0).abs() < 0.01);
assert!(Gate::db_to_linear(-40.0) < 0.1);
}
}