use std::f32::consts::PI;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum FilterType {
LowPass,
HighPass,
BandPass,
Notch, AllPass, Moog, None, }
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum FilterSlope {
Pole12dB, Pole24dB, }
#[derive(Clone, Copy)]
pub struct Filter {
pub filter_type: FilterType,
pub cutoff: f32, pub resonance: f32, pub slope: FilterSlope,
low: f32,
high: f32,
band: f32,
notch: f32,
low2: f32,
high2: f32,
band2: f32,
notch2: f32,
smooth_cutoff: f32,
smooth_resonance: f32,
moog_stage: [f32; 4],
moog_stage_tanh: [f32; 4],
moog_delay: [f32; 4],
cached_f: f32, cached_q: f32, cached_moog_f: f32, cached_moog_k: f32, last_smooth_cutoff: f32,
last_smooth_resonance: f32,
last_cutoff: f32,
last_resonance: f32,
process_fn: fn(&mut Filter, f32, f32) -> f32,
}
impl std::fmt::Debug for Filter {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Filter")
.field("filter_type", &self.filter_type)
.field("cutoff", &self.cutoff)
.field("resonance", &self.resonance)
.field("slope", &self.slope)
.finish()
}
}
impl Filter {
pub fn new(filter_type: FilterType, cutoff: f32, resonance: f32) -> Self {
Self::with_slope(filter_type, cutoff, resonance, FilterSlope::Pole12dB)
}
pub fn with_slope(filter_type: FilterType, cutoff: f32, resonance: f32, slope: FilterSlope) -> Self {
let cutoff = cutoff.clamp(20.0, 20000.0);
let resonance = resonance.clamp(0.0, 0.99);
let process_fn: fn(&mut Filter, f32, f32) -> f32 = match filter_type {
FilterType::None => Self::process_none,
FilterType::Moog => Self::process_moog,
_ => Self::process_svf, };
Self {
filter_type,
cutoff,
resonance,
slope,
low: 0.0,
high: 0.0,
band: 0.0,
notch: 0.0,
low2: 0.0,
high2: 0.0,
band2: 0.0,
notch2: 0.0,
smooth_cutoff: cutoff,
smooth_resonance: resonance,
moog_stage: [0.0; 4],
moog_stage_tanh: [0.0; 4],
moog_delay: [0.0; 4],
cached_f: 0.0,
cached_q: 0.0,
cached_moog_f: 0.0,
cached_moog_k: 0.0,
last_smooth_cutoff: -1.0, last_smooth_resonance: -1.0,
last_cutoff: cutoff,
last_resonance: resonance,
process_fn,
}
}
pub fn low_pass(cutoff: f32, resonance: f32) -> Self {
Self::new(FilterType::LowPass, cutoff, resonance)
}
pub fn high_pass(cutoff: f32, resonance: f32) -> Self {
Self::new(FilterType::HighPass, cutoff, resonance)
}
pub fn band_pass(cutoff: f32, resonance: f32) -> Self {
Self::new(FilterType::BandPass, cutoff, resonance)
}
pub fn notch(cutoff: f32, resonance: f32) -> Self {
Self::new(FilterType::Notch, cutoff, resonance)
}
pub fn all_pass(cutoff: f32, resonance: f32) -> Self {
Self::new(FilterType::AllPass, cutoff, resonance)
}
pub fn moog(cutoff: f32, resonance: f32) -> Self {
Self::new(FilterType::Moog, cutoff, resonance)
}
pub fn none() -> Self {
Self::new(FilterType::None, 20000.0, 0.0)
}
#[inline]
pub fn process(&mut self, input: f32, sample_rate: f32) -> f32 {
(self.process_fn)(self, input, sample_rate)
}
#[inline]
pub fn process_buffer(&mut self, buffer: &mut [f32], sample_rate: f32) {
match self.filter_type {
FilterType::None => {
return;
}
FilterType::Moog => {
self.process_buffer_moog(buffer, sample_rate);
}
_ => {
self.process_buffer_svf(buffer, sample_rate);
}
}
}
#[inline]
fn process_none(&mut self, input: f32, _sample_rate: f32) -> f32 {
input
}
#[inline]
fn process_svf(&mut self, input: f32, sample_rate: f32) -> f32 {
const SMOOTHING: f32 = 0.95;
const INV_SMOOTHING: f32 = 1.0 - SMOOTHING;
self.smooth_cutoff = self.smooth_cutoff.mul_add(SMOOTHING, self.cutoff * INV_SMOOTHING);
self.smooth_resonance = self.smooth_resonance.mul_add(SMOOTHING, self.resonance * INV_SMOOTHING);
let f = 2.0 * (PI * self.smooth_cutoff / sample_rate).sin();
let q = 1.0 - self.smooth_resonance;
self.low = self.low.mul_add(1.0, f * self.band);
self.high = input - self.low - q * self.band;
self.band = self.band.mul_add(1.0, f * self.high);
self.notch = self.high + self.low;
self.low = if self.low.abs() < 1e-15 { 0.0 } else { self.low };
self.band = if self.band.abs() < 1e-15 { 0.0 } else { self.band };
if self.low.abs() > 100.0 {
self.low = self.low.clamp(-100.0, 100.0);
self.band = self.band.clamp(-100.0, 100.0);
self.high = self.high.clamp(-100.0, 100.0);
}
let stage1_output = match self.filter_type {
FilterType::LowPass => self.low,
FilterType::HighPass => self.high,
FilterType::BandPass => self.band,
FilterType::Notch => self.notch,
FilterType::AllPass => self.notch - self.band,
FilterType::Moog => unreachable!(), FilterType::None => input,
};
let output = match self.slope {
FilterSlope::Pole12dB => stage1_output,
FilterSlope::Pole24dB => {
self.low2 = self.low2.mul_add(1.0, f * self.band2);
self.high2 = stage1_output - self.low2 - q * self.band2;
self.band2 = self.band2.mul_add(1.0, f * self.high2);
self.notch2 = self.high2 + self.low2;
self.low2 = if self.low2.abs() < 1e-15 { 0.0 } else { self.low2 };
self.band2 = if self.band2.abs() < 1e-15 { 0.0 } else { self.band2 };
if self.low2.abs() > 100.0 {
self.low2 = self.low2.clamp(-100.0, 100.0);
self.band2 = self.band2.clamp(-100.0, 100.0);
self.high2 = self.high2.clamp(-100.0, 100.0);
}
match self.filter_type {
FilterType::LowPass => self.low2,
FilterType::HighPass => self.high2,
FilterType::BandPass => self.band2,
FilterType::Notch => self.notch2,
FilterType::AllPass => self.notch2 - self.band2,
FilterType::Moog => unreachable!(), FilterType::None => stage1_output,
}
}
};
output.clamp(-2.0, 2.0)
}
#[inline]
fn process_moog(&mut self, input: f32, sample_rate: f32) -> f32 {
const SMOOTHING: f32 = 0.95;
const INV_SMOOTHING: f32 = 1.0 - SMOOTHING;
self.smooth_cutoff = self.smooth_cutoff.mul_add(SMOOTHING, self.cutoff * INV_SMOOTHING);
self.smooth_resonance = self.smooth_resonance.mul_add(SMOOTHING, self.resonance * INV_SMOOTHING);
let fc = self.smooth_cutoff / sample_rate;
let f = fc.mul_add(1.16, 0.0);
let k = self.smooth_resonance.mul_add(3.96, 0.0);
let input_compensated = input - k * self.moog_delay[3];
let input_tanh = input_compensated.tanh();
for i in 0..4 {
let stage_input = if i == 0 {
input_tanh
} else {
self.moog_stage_tanh[i - 1]
};
self.moog_stage[i] = self.moog_delay[i].mul_add(1.0 - f, stage_input * f);
self.moog_stage_tanh[i] = self.moog_stage[i].tanh();
self.moog_delay[i] = self.moog_stage[i];
}
let output = self.moog_stage_tanh[3];
output.clamp(-2.0, 2.0)
}
pub fn reset(&mut self) {
self.low = 0.0;
self.high = 0.0;
self.band = 0.0;
self.notch = 0.0;
self.low2 = 0.0;
self.high2 = 0.0;
self.band2 = 0.0;
self.notch2 = 0.0;
self.smooth_cutoff = self.cutoff;
self.smooth_resonance = self.resonance;
self.moog_stage = [0.0; 4];
self.moog_stage_tanh = [0.0; 4];
self.moog_delay = [0.0; 4];
self.last_smooth_cutoff = -1.0; self.last_smooth_resonance = -1.0;
self.last_cutoff = self.cutoff;
self.last_resonance = self.resonance;
}
#[inline]
fn process_buffer_svf(&mut self, buffer: &mut [f32], sample_rate: f32) {
use crate::synthesis::simd::SimdLanes;
const SMOOTHING: f32 = 0.95;
const INV_SMOOTHING: f32 = 1.0 - SMOOTHING;
const COEFF_UPDATE_THRESHOLD: f32 = 0.0001; const CONVERGENCE_THRESHOLD: f32 = 0.0001;
let params_changed = self.cutoff != self.last_cutoff || self.resonance != self.last_resonance;
let cutoff_converged = (self.smooth_cutoff - self.cutoff).abs() < CONVERGENCE_THRESHOLD;
let resonance_converged = (self.smooth_resonance - self.resonance).abs() < CONVERGENCE_THRESHOLD;
let needs_smoothing = params_changed || !cutoff_converged || !resonance_converged;
if params_changed {
self.last_cutoff = self.cutoff;
self.last_resonance = self.resonance;
}
for sample in buffer.iter_mut() {
let input = *sample;
if needs_smoothing {
self.smooth_cutoff = self.smooth_cutoff.mul_add(SMOOTHING, self.cutoff * INV_SMOOTHING);
self.smooth_resonance = self.smooth_resonance.mul_add(SMOOTHING, self.resonance * INV_SMOOTHING);
}
let cutoff_changed = (self.smooth_cutoff - self.last_smooth_cutoff).abs()
> self.last_smooth_cutoff.abs() * COEFF_UPDATE_THRESHOLD;
let resonance_changed = (self.smooth_resonance - self.last_smooth_resonance).abs()
> COEFF_UPDATE_THRESHOLD;
if cutoff_changed || resonance_changed {
self.cached_f = 2.0 * f32::fast_sin(PI * self.smooth_cutoff / sample_rate);
self.cached_q = 1.0 - self.smooth_resonance;
self.last_smooth_cutoff = self.smooth_cutoff;
self.last_smooth_resonance = self.smooth_resonance;
}
let f = self.cached_f;
let q = self.cached_q;
self.low = self.low.mul_add(1.0, f * self.band);
self.high = input - self.low - q * self.band;
self.band = self.band.mul_add(1.0, f * self.high);
self.notch = self.high + self.low;
self.low = if self.low.abs() < 1e-15 { 0.0 } else { self.low };
self.band = if self.band.abs() < 1e-15 { 0.0 } else { self.band };
self.low = self.low.clamp(-100.0, 100.0);
self.band = self.band.clamp(-100.0, 100.0);
self.high = self.high.clamp(-100.0, 100.0);
let stage1_output = match self.filter_type {
FilterType::LowPass => self.low,
FilterType::HighPass => self.high,
FilterType::BandPass => self.band,
FilterType::Notch => self.notch,
FilterType::AllPass => self.notch - self.band,
_ => input,
};
let output = if matches!(self.slope, FilterSlope::Pole24dB) {
self.low2 = self.low2.mul_add(1.0, f * self.band2);
self.high2 = stage1_output - self.low2 - q * self.band2;
self.band2 = self.band2.mul_add(1.0, f * self.high2);
self.notch2 = self.high2 + self.low2;
self.low2 = if self.low2.abs() < 1e-15 { 0.0 } else { self.low2 };
self.band2 = if self.band2.abs() < 1e-15 { 0.0 } else { self.band2 };
self.low2 = self.low2.clamp(-100.0, 100.0);
self.band2 = self.band2.clamp(-100.0, 100.0);
self.high2 = self.high2.clamp(-100.0, 100.0);
match self.filter_type {
FilterType::LowPass => self.low2,
FilterType::HighPass => self.high2,
FilterType::BandPass => self.band2,
FilterType::Notch => self.notch2,
FilterType::AllPass => self.notch2 - self.band2,
_ => stage1_output,
}
} else {
stage1_output
};
*sample = output.clamp(-2.0, 2.0);
}
}
#[inline]
fn process_buffer_moog(&mut self, buffer: &mut [f32], sample_rate: f32) {
use crate::synthesis::simd::SimdLanes;
const SMOOTHING: f32 = 0.95;
const INV_SMOOTHING: f32 = 1.0 - SMOOTHING;
const COEFF_UPDATE_THRESHOLD: f32 = 0.0001;
const CONVERGENCE_THRESHOLD: f32 = 0.0001;
let params_changed = self.cutoff != self.last_cutoff || self.resonance != self.last_resonance;
let cutoff_converged = (self.smooth_cutoff - self.cutoff).abs() < CONVERGENCE_THRESHOLD;
let resonance_converged = (self.smooth_resonance - self.resonance).abs() < CONVERGENCE_THRESHOLD;
let needs_smoothing = params_changed || !cutoff_converged || !resonance_converged;
if params_changed {
self.last_cutoff = self.cutoff;
self.last_resonance = self.resonance;
}
for sample in buffer.iter_mut() {
let input = *sample;
if needs_smoothing {
self.smooth_cutoff = self.smooth_cutoff.mul_add(SMOOTHING, self.cutoff * INV_SMOOTHING);
self.smooth_resonance = self.smooth_resonance.mul_add(SMOOTHING, self.resonance * INV_SMOOTHING);
}
let cutoff_changed = (self.smooth_cutoff - self.last_smooth_cutoff).abs()
> self.last_smooth_cutoff.abs() * COEFF_UPDATE_THRESHOLD;
let resonance_changed = (self.smooth_resonance - self.last_smooth_resonance).abs()
> COEFF_UPDATE_THRESHOLD;
if cutoff_changed || resonance_changed {
let fc = self.smooth_cutoff / sample_rate;
self.cached_moog_f = fc.mul_add(1.16, 0.0);
self.cached_moog_k = self.smooth_resonance.mul_add(3.96, 0.0);
self.last_smooth_cutoff = self.smooth_cutoff;
self.last_smooth_resonance = self.smooth_resonance;
}
let f = self.cached_moog_f;
let k = self.cached_moog_k;
let input_compensated = input - k * self.moog_delay[3];
let input_tanh = f32::fast_tanh(input_compensated);
for i in 0..4 {
let stage_input = if i == 0 {
input_tanh
} else {
self.moog_stage_tanh[i - 1]
};
self.moog_stage[i] = self.moog_delay[i].mul_add(1.0 - f, stage_input * f);
self.moog_stage_tanh[i] = f32::fast_tanh(self.moog_stage[i]);
self.moog_delay[i] = self.moog_stage[i];
}
let output = self.moog_stage_tanh[3];
*sample = output.clamp(-2.0, 2.0);
}
}
}
impl Default for Filter {
fn default() -> Self {
Self::none()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_filter_creation() {
let lpf = Filter::low_pass(1000.0, 0.5);
assert_eq!(lpf.filter_type, FilterType::LowPass);
assert_eq!(lpf.cutoff, 1000.0);
let hpf = Filter::high_pass(500.0, 0.3);
assert_eq!(hpf.filter_type, FilterType::HighPass);
let bpf = Filter::band_pass(2000.0, 0.7);
assert_eq!(bpf.filter_type, FilterType::BandPass);
}
#[test]
fn test_filter_bypass() {
let mut filter = Filter::none();
let input = 0.5;
let output = filter.process(input, 44100.0);
assert_eq!(input, output);
}
#[test]
fn test_notch_filter_creation() {
let notch = Filter::notch(1000.0, 0.7);
assert_eq!(notch.filter_type, FilterType::Notch);
assert_eq!(notch.cutoff, 1000.0);
assert_eq!(notch.resonance, 0.7);
}
#[test]
fn test_allpass_filter_creation() {
let allpass = Filter::all_pass(2000.0, 0.5);
assert_eq!(allpass.filter_type, FilterType::AllPass);
assert_eq!(allpass.cutoff, 2000.0);
}
#[test]
fn test_filter_slopes() {
let filter_12db = Filter::with_slope(FilterType::LowPass, 1000.0, 0.5, FilterSlope::Pole12dB);
assert_eq!(filter_12db.slope, FilterSlope::Pole12dB);
let filter_24db = Filter::with_slope(FilterType::LowPass, 1000.0, 0.5, FilterSlope::Pole24dB);
assert_eq!(filter_24db.slope, FilterSlope::Pole24dB);
}
#[test]
fn test_24db_filter_processes() {
let mut filter = Filter::with_slope(FilterType::LowPass, 1000.0, 0.5, FilterSlope::Pole24dB);
let input = 0.5;
let output = filter.process(input, 44100.0);
assert!(output.is_finite());
assert!(output.abs() <= 2.0);
}
#[test]
fn test_notch_filter_processes() {
let mut filter = Filter::notch(1000.0, 0.7);
let input = 0.5;
let output = filter.process(input, 44100.0);
assert!(output.is_finite());
}
#[test]
fn test_allpass_filter_processes() {
let mut filter = Filter::all_pass(1000.0, 0.5);
let input = 0.5;
let output = filter.process(input, 44100.0);
assert!(output.is_finite());
}
#[test]
fn test_moog_filter_creation() {
let moog = Filter::moog(1000.0, 0.7);
assert_eq!(moog.filter_type, FilterType::Moog);
assert_eq!(moog.cutoff, 1000.0);
assert_eq!(moog.resonance, 0.7);
}
#[test]
fn test_moog_filter_processes() {
let mut filter = Filter::moog(1000.0, 0.5);
let input = 0.5;
let output = filter.process(input, 44100.0);
assert!(output.is_finite());
assert!(output.abs() <= 2.0);
}
#[test]
fn test_moog_high_resonance() {
let mut filter = Filter::moog(440.0, 0.95);
for _ in 0..100 {
let output = filter.process(0.0, 44100.0);
assert!(output.is_finite());
assert!(output.abs() <= 2.0);
}
}
#[test]
fn test_moog_sweep() {
let mut filter = Filter::moog(100.0, 0.7);
for i in 0..100 {
filter.cutoff = 100.0 + i as f32 * 100.0;
let output = filter.process(0.5, 44100.0);
assert!(output.is_finite());
}
}
}