use nih_plug::params::enums::Enum; use serde::{Deserialize, Serialize}; use std::f32::consts::PI; // Inspired by https://www.musicdsp.org/en/latest/Filters/267-simple-tilt-equalizer.html // Lowpass, Bandpass, Highpass based off tilt filter code // Ardura const SLOPE_NEG: f32 = -60.0; #[derive(Enum, PartialEq, Serialize, Deserialize, Clone)] pub enum ResponseType { Lowpass, Bandpass, Highpass, } #[derive(Serialize, Deserialize, Clone)] pub struct ArduraFilter { // Filter parameters sample_rate: f32, center_freq: f32, steepness: f32, shape: ResponseType, // Filter tracking/internal sample_rate_x3: f32, lgain: f32, hgain: f32, a0: f32, b1: f32, lp_out: f32, // Band pass separate vars band_a0_low: f32, band_b1_low: f32, band_out_low: f32, band_a0_high: f32, band_b1_high: f32, band_out_high: f32, } impl ArduraFilter { pub fn new(sample_rate: f32, center_freq: f32, steepness: f32, shape: ResponseType) -> Self { let amp = 6.0 / f32::ln(2.0); let sample_rate_x3 = 3.0 * sample_rate; let lgain; let hgain; match shape { // These are the gains for the slopes when math happens later ResponseType::Lowpass => { lgain = f32::exp(0.0 / amp) - 1.0; hgain = f32::exp(SLOPE_NEG / amp) - 1.0; } ResponseType::Bandpass => { lgain = f32::exp(0.0 / amp) - 1.0; hgain = f32::exp(SLOPE_NEG / amp) - 1.0; } ResponseType::Highpass => { lgain = f32::exp(SLOPE_NEG / amp) - 1.0; hgain = f32::exp(0.0 / amp) - 1.0; } } let omega = 2.0 * PI * center_freq; let n = 1.0 / (Self::scale_range(steepness, 0.98, 1.2) * (sample_rate_x3 + omega)); let a0 = 2.0 * omega * n; let b1 = (sample_rate_x3 - omega) * n; let lp_out = 0.0; // Initial value for lp_out ArduraFilter { center_freq, sample_rate_x3, lgain, hgain, a0, b1, lp_out, steepness, sample_rate: sample_rate, shape, band_a0_low: a0, band_b1_low: b1, band_out_low: lp_out, band_a0_high: a0, band_b1_high: b1, band_out_high: lp_out, } } pub fn update( &mut self, sample_rate: f32, center_freq: f32, steepness: f32, shape: ResponseType, ) { let mut recalculate = false; if self.sample_rate != sample_rate { self.sample_rate = sample_rate; self.sample_rate_x3 = self.sample_rate * 3.0; recalculate = true; } if self.center_freq != center_freq { self.center_freq = center_freq; recalculate = true; } if self.steepness != steepness { self.steepness = steepness; recalculate = true; } if self.shape != shape { self.shape = shape; recalculate = true; } if recalculate { let amp = 6.0 / f32::ln(2.0); match self.shape { ResponseType::Lowpass => { let omega = 2.0 * PI * center_freq; let n = 1.0 / (Self::scale_range(self.steepness, 0.98, 1.2) * (self.sample_rate_x3 + omega)); self.b1 = (self.sample_rate_x3 - omega) * n; self.lgain = f32::exp(0.0 / amp) - 1.0; self.hgain = f32::exp(SLOPE_NEG / amp) - 1.0; } ResponseType::Bandpass => { let width = self.steepness * self.steepness * 500.0; let l_omega = 2.0 * PI * (self.center_freq - width).clamp(20.0, 16000.0); let l_n = 1.0 / (Self::scale_range(self.steepness, 0.98, 1.2) * (self.sample_rate_x3 + l_omega)); self.band_a0_low = 2.0 * l_omega * l_n; self.band_b1_low = (self.sample_rate_x3 - l_omega) * l_n; let h_omega = 2.0 * PI * (self.center_freq + width).clamp(20.0, 16000.0); let h_n = 1.0 / (Self::scale_range(self.steepness, 0.98, 1.2) * (self.sample_rate_x3 + h_omega)); self.band_a0_high = 2.0 * h_omega * h_n; self.band_b1_high = (self.sample_rate_x3 - h_omega) * h_n; self.lgain = f32::exp(0.0 / amp) - 1.0; self.hgain = f32::exp(SLOPE_NEG / amp) - 1.0; } ResponseType::Highpass => { let omega = 2.0 * PI * center_freq; let n = 1.0 / (Self::scale_range(self.steepness, 0.98, 1.2) * (self.sample_rate_x3 + omega)); self.a0 = 2.0 * omega * n; self.b1 = (self.sample_rate_x3 - omega) * n; self.lgain = f32::exp(SLOPE_NEG / amp) - 1.0; self.hgain = f32::exp(0.0 / amp) - 1.0; } } } } pub fn process(&mut self, input: f32) -> f32 { let denorm = 1.0 / 4294967295.0; // Process the input using the tilt equalizer logic if self.shape == ResponseType::Bandpass { self.band_out_low = self.band_a0_low * input + self.band_b1_low * self.band_out_low; let temp = input + self.hgain * self.band_out_low + self.lgain * (input - self.band_out_low); self.band_out_high = self.band_a0_high * temp + self.band_b1_high * self.band_out_high; temp + self.lgain * self.band_out_high + self.hgain * (temp - self.band_out_high) + denorm } else { self.lp_out = self.a0 * input + self.b1 * self.lp_out; input + self.lgain * self.lp_out + self.hgain * (input - self.lp_out) + denorm } } // Super useful function to scale an input 0-1 into other ranges fn scale_range(input: f32, min_output: f32, max_output: f32) -> f32 { let scaled = input * (max_output - min_output) + min_output; scaled.clamp(min_output, max_output) } }