diff --git a/README.md b/README.md index 3fd46d0..cad32fc 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ the following is the current list of plugins - multidim: randomized midi note generator - robotuna: automatic pitch correction - hysteria: hysteresis nonlinear effect -- threebandeq: [WIP] 3 band eq +- threebandeq: 3 band eq ### basic_gain @@ -134,7 +134,16 @@ turn the values up for loud and distorted. don't set them all to the max at once ### threebandeq [WIP] -wip, there's nothing yet +simple three band equalizer + +parameters: +- `low band`: frequency at which the low band ends +- `high band`: frequency at which the high band starts +- `low gain`: gain for the low band +- `mid gain`: gain for the mid band +- `high gain`: gain for the high band + +low band goes from 0hz to `low band`, mid band goes from `low band` to `high band`, high band goes from `high band` to half the sample rate ## contributing diff --git a/crates/threebandeq/src/lib.rs b/crates/threebandeq/src/lib.rs index 85fa9ea..bb06c05 100644 --- a/crates/threebandeq/src/lib.rs +++ b/crates/threebandeq/src/lib.rs @@ -1,31 +1,122 @@ #![allow(incomplete_features)] #![feature(generic_associated_types)] +// https://www.musicdsp.org/en/latest/Filters/236-3-band-equaliser.html + use baseplug::{Plugin, ProcessContext}; use serde::{Deserialize, Serialize}; baseplug::model! { #[derive(Debug, Serialize, Deserialize)] struct ThreeBandEqModel { - #[model(min = -90.0, max = 3.0)] - #[parameter(name = "gain", unit = "Decibels", - gradient = "Power(0.15)")] - gain: f32 + #[model(min = 0.0, max = 48000.0)] + #[parameter(name = "low band")] + low_band: f32, + #[model(min = 0.0, max = 48000.0)] + #[parameter(name = "mid band")] + high_band: f32, + + #[model(min = 0.0, max = 3.0)] + #[parameter(name = "low gain")] + low_gain: f32, + #[model(min = 0.0, max = 3.0)] + #[parameter(name = "mid gain")] + mid_gain: f32, + #[model(min = 0.0, max = 3.0)] + #[parameter(name = "high gain")] + high_gain: f32, } } impl Default for ThreeBandEqModel { fn default() -> Self { Self { - // "gain" is converted from dB to coefficient in the parameter handling code, - // so in the model here it's a coeff. - // -0dB == 1.0 - gain: 1.0, + low_band: 800.0, + high_band: 5000.0, + + low_gain: 1.0, + mid_gain: 1.0, + high_gain: 1.0, } } } -struct ThreeBandEq; +#[derive(Default)] +struct Eq { + // Filter #1 (Low band) + f1p0: f32, // Poles ... + f1p1: f32, + f1p2: f32, + f1p3: f32, + + // Filter #2 (High band) + f2p0: f32, // Poles ... + f2p1: f32, + f2p2: f32, + f2p3: f32, + + // Sample history buffer + sdm1: f32, // Sample data minus 1 + sdm2: f32, // 2 + sdm3: f32, // 3 +} +impl Eq { + #[allow(clippy::too_many_arguments)] + fn process( + &mut self, + input: f32, + lf: f32, + hf: f32, + lg: f32, + mg: f32, + hg: f32, + sample_rate: f32, + ) -> f32 { + let lf = 2.0 * (std::f32::consts::PI * (lf / sample_rate)).sin(); + let hf = 2.0 * (std::f32::consts::PI * (hf / sample_rate)).sin(); + + // Filter #1 (lowpass) + self.f1p0 += lf * (input - self.f1p0) + VSA; + self.f1p1 += lf * (self.f1p0 - self.f1p1); + self.f1p2 += lf * (self.f1p1 - self.f1p2); + self.f1p3 += lf * (self.f1p2 - self.f1p3); + + let l = self.f1p3; + + // Filter #2 (highpass) + self.f2p0 += hf * (input - self.f2p0) + VSA; + self.f2p1 += hf * (self.f2p0 - self.f2p1); + self.f2p2 += hf * (self.f2p1 - self.f2p2); + self.f2p3 += hf * (self.f2p2 - self.f2p3); + + // we subtract from sdm3 because the filter has a three sample delay + let h = self.sdm3 - self.f2p3; + + // Calculate midrange (signal - (low + high)) + let m = self.sdm3 - (h + l); + + // Scale + let l = l * lg; + let m = m * mg; + let h = h * hg; + + // Shuffle history buffer + self.sdm3 = self.sdm2; + self.sdm2 = self.sdm1; + self.sdm1 = input; + + l + m + h + } +} + +#[derive(Default)] +struct ThreeBandEq { + l_eq: Eq, + r_eq: Eq, +} + +/// Very small amount +const VSA: f32 = 1.0 / 4294967295.0; impl Plugin for ThreeBandEq { const NAME: &'static str = "basic gain"; @@ -39,7 +130,8 @@ impl Plugin for ThreeBandEq { #[inline] fn new(_sample_rate: f32, _model: &ThreeBandEqModel) -> Self { - Self + // Default cause we want to start with 0s in everything + Self::default() } #[inline] @@ -47,9 +139,20 @@ impl Plugin for ThreeBandEq { let input = &ctx.inputs[0].buffers; let output = &mut ctx.outputs[0].buffers; + let sr = ctx.sample_rate; + for i in 0..ctx.nframes { - output[0][i] = input[0][i] * model.gain[i]; - output[1][i] = input[1][i] * model.gain[i]; + // frequencies + let lf = model.low_band[i]; + let hf = model.high_band[i]; + + // gains + let lg = model.low_gain[i]; + let mg = model.mid_gain[i]; + let hg = model.high_gain[i]; + + output[0][i] = self.l_eq.process(input[0][i], lf, hf, lg, mg, hg, sr); + output[1][i] = self.r_eq.process(input[1][i], lf, hf, lg, mg, hg, sr); } } }