use crate::AudioEffect;
#[derive(Debug, Clone)]
pub struct MoogConfig {
pub frequency: f32,
pub resonance: f32,
}
impl Default for MoogConfig {
fn default() -> Self {
Self {
frequency: 1000.0,
resonance: 0.3,
}
}
}
pub struct MoogFilter {
stage: [f32; 4],
delay: [f32; 4],
cutoff: f32,
resonance: f32,
config: MoogConfig,
sample_rate: f32,
}
impl MoogFilter {
#[must_use]
pub fn new(config: MoogConfig, sample_rate: f32) -> Self {
let mut filter = Self {
stage: [0.0; 4],
delay: [0.0; 4],
cutoff: 0.0,
resonance: 0.0,
config,
sample_rate,
};
filter.update_coefficients();
filter
}
fn update_coefficients(&mut self) {
let fc = self.config.frequency / (self.sample_rate * 0.5);
self.cutoff = fc * 1.16;
self.resonance = self.config.resonance * 4.0;
}
pub fn set_frequency(&mut self, frequency: f32) {
self.config.frequency = frequency.clamp(20.0, self.sample_rate * 0.4);
self.update_coefficients();
}
pub fn set_resonance(&mut self, resonance: f32) {
self.config.resonance = resonance.clamp(0.0, 1.0);
self.update_coefficients();
}
fn tanh_approx(x: f32) -> f32 {
if x < -3.0 {
-1.0
} else if x > 3.0 {
1.0
} else {
x * (27.0 + x * x) / (27.0 + 9.0 * x * x)
}
}
}
impl AudioEffect for MoogFilter {
const EFFECT_ID: &'static str = "moog_filter";
fn process_sample(&mut self, input: f32) -> f32 {
let input_with_fb = input - self.resonance * self.stage[3];
for i in 0..4 {
let stage_input = if i == 0 {
input_with_fb
} else {
self.stage[i - 1]
};
self.stage[i] = self.delay[i]
+ self.cutoff * (Self::tanh_approx(stage_input) - Self::tanh_approx(self.delay[i]));
self.delay[i] = self.stage[i];
}
self.stage[3]
}
fn reset(&mut self) {
self.stage.fill(0.0);
self.delay.fill(0.0);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_moog_filter() {
let config = MoogConfig::default();
let mut filter = MoogFilter::new(config, 48000.0);
let output = filter.process_sample(1.0);
assert!(output.is_finite());
}
#[test]
fn test_moog_resonance() {
let config = MoogConfig {
frequency: 1000.0,
resonance: 0.8,
};
let mut filter = MoogFilter::new(config, 48000.0);
for _ in 0..1000 {
let output = filter.process_sample(0.5);
assert!(output.is_finite());
assert!(output.abs() < 10.0); }
}
}