diff --git a/src/dsp/helpers.rs b/src/dsp/helpers.rs index d1974b8..a6568e7 100644 --- a/src/dsp/helpers.rs +++ b/src/dsp/helpers.rs @@ -927,6 +927,7 @@ const FILTER_OVERSAMPLE_HAL_CHAMBERLIN : usize = 2; /// otherwise the filter becomes unstable. /// * `res` - Resonance from 0.0 to 0.99. Resonance of 1.0 is not recommended, /// as the filter will then oscillate itself out of control. +/// * `israte` - 1.0 divided by the sampling rate (eg. 1.0 / 44100.0). /// * `band` - First state variable, containing the band pass result /// after processing. /// * `low` - Second state variable, containing the low pass result @@ -973,6 +974,73 @@ pub fn process_hal_chamberlin_svf( (high, notch) } +/// This function processes a Simper SVF. It's a much newer algorithm +/// for filtering and provides easy to calculate multiple outputs. +/// +/// * `input` - Input sample. +/// * `freq` - Frequency in Hz. +/// otherwise the filter becomes unstable. +/// * `res` - Resonance from 0.0 to 0.99. Resonance of 1.0 is not recommended, +/// as the filter will then oscillate itself out of control. +/// * `israte` - 1.0 divided by the sampling rate (eg. 1.0 / 44100.0). +/// * `band` - First state variable, containing the band pass result +/// after processing. +/// * `low` - Second state variable, containing the low pass result +/// after processing. +/// +/// This function returns the low pass, band pass and high pass signal. +/// For a notch or peak filter signal, please consult the following example: +/// +///``` +/// use hexodsp::dsp::helpers::*; +/// +/// let samples = vec![0.0; 44100]; +/// let mut ic1eq = 0.0; +/// let mut ic2eq = 0.0; +/// let mut freq = 1000.0; +/// +/// for s in samples.iter() { +/// let (low, band, high) = +/// process_simper_svf( +/// *s, freq, 0.5, 1.0 / 44100.0, &mut ic1eq, &mut ic2eq); +/// +/// // You can easily calculate the notch and peak results too: +/// let notch = low + high; +/// let peak = low - high; +/// // ... do something with the result here. +/// } +///``` +// Simper SVF taken from baseplug (Rust crate) example svf_simper.rs: +// implemented from https://cytomic.com/files/dsp/SvfLinearTrapOptimised2.pdf +// thanks, andy! +#[inline] +pub fn process_simper_svf( + input: f64, freq: f64, res: f64, israte: f64, ic1eq: &mut f64, ic2eq: &mut f64 +) -> (f64, f64, f64) { + let g = (std::f64::consts::PI * freq * israte).tan(); + let k = 2f64 - (1.9f64 * res); + + let a1 = 1.0 / (1.0 + (g * (g + k))); + let a2 = g * a1; + let a3 = g * a2; + + let v3 = input - *ic2eq; + let v1 = (a1 * *ic1eq) + (a2 * v3); + let v2 = *ic2eq + (a2 * *ic1eq) + (a3 * v3); + + *ic1eq = (2.0 * v1) - *ic1eq; + *ic2eq = (2.0 * v2) - *ic2eq; + + // low = v2 + // band = v1 + // high = input - k * v1 - v2 + // notch = low + high = input - k * v1 + // peak = low - high = 2 * v2 - input + k * v1 + // all = low + high - k * band = input - 2 * k * v1 + + (v2, v1, input - k * v1 - v2) +} + // translated from Odin 2 Synthesizer Plugin // Copyright (C) 2020 TheWaveWarden // under GPLv3 or any later diff --git a/src/dsp/mod.rs b/src/dsp/mod.rs index 885d350..724682c 100644 --- a/src/dsp/mod.rs +++ b/src/dsp/mod.rs @@ -583,7 +583,7 @@ macro_rules! node_list { (0 inp n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0) (1 freq n_pit d_pit r_fq f_freq stp_d -1.0, 0.5647131, 1000.0) (2 res n_id d_id r_id f_def stp_d 0.0, 0.99, 0.5) - {3 0 ftype setting(0) fa_sfilter_type 0 7} + {3 0 ftype setting(8) fa_sfilter_type 0 12} [0 sig], test => Test UIType::Generic UICategory::IOUtil (0 f n_id d_id r_id f_def stp_d 0.0, 1.0, 0.5) diff --git a/src/dsp/node_sfilter.rs b/src/dsp/node_sfilter.rs index 449d744..029f586 100644 --- a/src/dsp/node_sfilter.rs +++ b/src/dsp/node_sfilter.rs @@ -10,6 +10,7 @@ use crate::dsp::helpers::{ process_1pole_tpt_lowpass, process_1pole_tpt_highpass, process_hal_chamberlin_svf, + process_simper_svf, }; #[macro_export] @@ -24,6 +25,11 @@ macro_rules! fa_sfilter_type { ($formatter: expr, $v: expr, $denorm_v: expr) => 5 => "HP 12c", 6 => "BP 12c", 7 => "NO 12c", + 8 => "LP 12s", + 9 => "HP 12s", + 10 => "BP 12s", + 11 => "NO 12s", + 12 => "PK 12s", _ => "?", }; write!($formatter, "{}", s) @@ -85,9 +91,54 @@ that is limited to max cutoff frequency of 16kHz. HP 12c - High-pass Hal Chamberlin state variable filter (12dB) BP 12c - Band-pass Hal Chamberlin state variable filter (12dB) NO 12c - Notch Hal Chamberlin state variable filter (12dB) + +The (Andrew) Simper state variable filter is a newer design. + + LP 12s - Low-pass Simper state variable filter (12dB) + HP 12s - High-pass Simper state variable filter (12dB) + BP 12s - Band-pass Simper state variable filter (12dB) + NO 12s - Notch Simper state variable filter (12dB) + PK 12s - Peak Simper state variable filter (12dB) "#; } +macro_rules! process_filter_fun { + ($nframes: expr, $inp: expr, $out: ident, $freq: ident, $res: ident, + $input: ident, $minfreq: expr, $maxfreq: expr, $block: block) => { { + for frame in 0..$nframes { + let $input = $inp.read(frame) as f64; + let $freq = denorm::SFilter::freq($freq, frame) as f64; + let $freq = $freq.clamp($minfreq, $maxfreq); + let $res = denorm::SFilter::res($res, frame) as f64; + let $res = $res.clamp(0.0, 0.99); + let s = $block; + $out.write(frame, s as f32); + } + } }; + ($nframes: expr, $inp: expr, $out: ident, $freq: ident, $res: ident, + $input: ident, $maxfreq: expr, $block: block) => { { + for frame in 0..$nframes { + let $input = $inp.read(frame) as f64; + let $freq = denorm::SFilter::freq($freq, frame) as f64; + let $freq = $freq.clamp(1.0, $maxfreq); + let $res = denorm::SFilter::res($res, frame) as f64; + let $res = $res.clamp(0.0, 0.99); + let s = $block; + $out.write(frame, s as f32); + } + } }; + ($nframes: expr, $inp: expr, $out: ident, $freq: ident, + $input: ident, $maxfreq: expr, $block: block) => { { + for frame in 0..$nframes { + let $input = $inp.read(frame) as f64; + let $freq = denorm::SFilter::freq($freq, frame) as f64; + let $freq = $freq.clamp(1.0, $maxfreq); + let s = $block; + $out.write(frame, s as f32); + } + } } +} + impl DspNode for SFilter { fn outputs() -> usize { 1 } @@ -125,112 +176,82 @@ impl DspNode for SFilter { match ftype { 0 => { // Lowpass - for frame in 0..ctx.nframes() { - let input = inp.read(frame) as f64; - let freq = denorm::SFilter::freq(freq, frame) as f64; - let freq = freq.clamp(1.0, 22000.0); - out.write(frame, + process_filter_fun!( + ctx.nframes(), inp, out, freq, input, 22000.0, { process_1pole_lowpass( input, freq, self.israte, &mut self.z) - as f32); - } + }) }, 1 => { // Lowpass TPT - for frame in 0..ctx.nframes() { - let input = inp.read(frame) as f64; - let freq = denorm::SFilter::freq(freq, frame) as f64; - let freq = freq.clamp(1.0, 22000.0); - out.write(frame, + process_filter_fun!( + ctx.nframes(), inp, out, freq, input, 22000.0, { process_1pole_tpt_lowpass( input, freq, self.israte, &mut self.z) - as f32); - } + }) }, 2 => { // Highpass - for frame in 0..ctx.nframes() { - let input = inp.read(frame) as f64; - let freq = denorm::SFilter::freq(freq, frame) as f64; - let freq = freq.clamp(1.0, 22000.0); - out.write(frame, + process_filter_fun!( + ctx.nframes(), inp, out, freq, input, 22000.0, { process_1pole_highpass( input, freq, self.israte, &mut self.z, &mut self.y) - as f32); - } + }) }, 3 => { // Highpass TPT - for frame in 0..ctx.nframes() { - let input = inp.read(frame) as f64; - let freq = denorm::SFilter::freq(freq, frame) as f64; - let freq = freq.clamp(1.0, 22000.0); - out.write(frame, + process_filter_fun!( + ctx.nframes(), inp, out, freq, input, 22000.0, { process_1pole_tpt_highpass( input, freq, self.israte, &mut self.z) - as f32); - } + }) }, 4 => { // Low Pass Hal Chamberlin SVF - for frame in 0..ctx.nframes() { - let input = inp.read(frame) as f64; - let freq = denorm::SFilter::freq(freq, frame) as f64; - let freq = freq.clamp(2.0, 16000.0); - let res = denorm::SFilter::res(res, frame) as f64; - let res = res.clamp(0.0, 0.99); - - let (_high, _notch) = - process_hal_chamberlin_svf( - input, freq, res, self.israte, - &mut self.z, &mut self.y); - - out.write(frame, self.y as f32); - } + process_filter_fun!( + ctx.nframes(), inp, out, freq, res, input, 2.0, 16000.0, { + let (_high, _notch) = + process_hal_chamberlin_svf( + input, freq, res, self.israte, + &mut self.z, &mut self.y); + self.y + }); }, 5 => { // High Pass Hal Chamberlin SVF - for frame in 0..ctx.nframes() { - let input = inp.read(frame) as f64; - let freq = denorm::SFilter::freq(freq, frame) as f64; - let freq = freq.clamp(1.0, 16000.0); - let res = denorm::SFilter::res(res, frame) as f64; - let res = res.clamp(0.0, 0.99); - - let (high, _notch) = - process_hal_chamberlin_svf( - input, freq, res, self.israte, - &mut self.z, &mut self.y); - - out.write(frame, high as f32); - } + process_filter_fun!( + ctx.nframes(), inp, out, freq, res, input, 16000.0, { + let (high, _notch) = + process_hal_chamberlin_svf( + input, freq, res, self.israte, + &mut self.z, &mut self.y); + high + }); }, 6 => { // Band Pass Hal Chamberlin SVF - for frame in 0..ctx.nframes() { - let input = inp.read(frame) as f64; - let freq = denorm::SFilter::freq(freq, frame) as f64; - let freq = freq.clamp(1.0, 16000.0); - let res = denorm::SFilter::res(res, frame) as f64; - let res = res.clamp(0.0, 0.99); - - let (_high, _notch) = - process_hal_chamberlin_svf( - input, freq, res, self.israte, - &mut self.z, &mut self.y); - - out.write(frame, self.z as f32); - } + process_filter_fun!( + ctx.nframes(), inp, out, freq, res, input, 16000.0, { + let (_high, _notch) = + process_hal_chamberlin_svf( + input, freq, res, self.israte, + &mut self.z, &mut self.y); + self.z + }); }, 7 => { // Notch Hal Chamberlin SVF - for frame in 0..ctx.nframes() { - let input = inp.read(frame) as f64; - let freq = denorm::SFilter::freq(freq, frame) as f64; - let freq = freq.clamp(1.0, 16000.0); - let res = denorm::SFilter::res(res, frame) as f64; - let res = res.clamp(0.0, 0.99); - - let (_high, notch) = - process_hal_chamberlin_svf( - input, freq, res, self.israte, - &mut self.z, &mut self.y); - - out.write(frame, notch as f32); - } + process_filter_fun!( + ctx.nframes(), inp, out, freq, res, input, 16000.0, { + let (_high, notch) = + process_hal_chamberlin_svf( + input, freq, res, self.israte, + &mut self.z, &mut self.y); + notch + }); + }, + 8 => { // Simper SVF Low Pass + process_filter_fun!( + ctx.nframes(), inp, out, freq, res, input, 22000.0, { + let (low, _band, _high) = + process_simper_svf( + input, freq, res, self.israte, + &mut self.z, &mut self.y); + low + }); }, _ => {}, } diff --git a/tests/node_sfilter.rs b/tests/node_sfilter.rs index d9a9667..5bb9a3f 100644 --- a/tests/node_sfilter.rs +++ b/tests/node_sfilter.rs @@ -552,3 +552,91 @@ fn check_node_sfilter_halsvf_notch() { (0, 20), (10, 32), (100, 16), (1000, 20), (4000, 16), (12000, 20) ]); } + +#[test] +fn check_node_sfilter_simpersvf_lowpass() { + let (mut matrix, mut node_exec) = setup_sfilter_matrix(); + + // Low Pass Simper SVF @ 1000Hz RES=1.0 + let fft = fft_with_freq_res_type(&mut matrix, &mut node_exec, 8, 1000.0, 1.0); + assert_eq!( + avg_fft_freqs(10.0, &[ + 500, 700, 900, 1000, 1500, 2000, 3000, 4000, 12000 + ], &fft[..]), vec![ + (0, 20), (500, 20), (700, 50), (900, 110), (1000, 40), + (1500, 10), (2000, 0), (3000, 0), (4000, 0) + ]); + + // Low Pass Simper SVF @ 1000Hz RES=0.5 + let fft = fft_with_freq_res_type(&mut matrix, &mut node_exec, 8, 1000.0, 0.5); + assert_eq!( + avg_fft_freqs(10.0, &[ + 500, 700, 900, 1000, 1500, 2000, 3000, 4000, 12000 + ], &fft[..]), vec![ + (0, 10), (500, 10), (700, 20), (900, 10), (1000, 10), + (1500, 0), (2000, 0), (3000, 0), (4000, 0) + ]); + + // Low Pass Simper SVF @ 1000Hz RES=0.0 + let fft = fft_with_freq_res_type(&mut matrix, &mut node_exec, 8, 1000.0, 0.0); + assert_eq!( + avg_fft_freqs(10.0, &[ + 500, 700, 900, 1000, 1500, 2000, 3000, 4000, 12000 + ], &fft[..]), vec![ + (0, 10), (500, 10), (700, 10), (900, 0), (1000, 0), + (1500, 0), (2000, 0), (3000, 0), (4000, 0) + ]); + + // Low Pass Simper SVF @ 4000Hz RES=1.0 + let fft = fft_with_freq_res_type(&mut matrix, &mut node_exec, 8, 4000.0, 1.0); + assert_eq!( + avg_fft_freqs(4.0, &[ + 100, 500, 1000, 2000, 3500, 4000, 5000, 6000, 8000, 12000 + ], &fft[..]), vec![ + (0, 24), (100, 16), (500, 20), (1000, 20), (2000, 36), (3500, 132), + (4000, 80), (5000, 20), (6000, 8), (8000, 0) + ]); + + // Low Pass Simper SVF @ 4000Hz RES=0.0 + let fft = fft_with_freq_res_type(&mut matrix, &mut node_exec, 8, 4000.0, 0.0); + assert_eq!( + avg_fft_freqs(4.0, &[ + 100, 500, 1000, 2000, 3500, 4000, 5000, 6000, 8000, 12000 + ], &fft[..]), vec![ + (0, 20), (100, 12), (500, 16), (1000, 16), (2000, 12), (3500, 8), + (4000, 8), (5000, 4), (6000, 4), (8000, 0) + ]); + + // Low Pass Simper SVF @ 22050Hz RES=0.0 + let fft = fft_with_freq_res_type(&mut matrix, &mut node_exec, 8, 22050.0, 0.0); + assert_eq!( + avg_fft_freqs(8.0, &[100, 1000, 4000, 12000, 16000, 20000, 22050, 22051], &fft[..]), vec![ + (0, 16), (100, 16), (1000, 16), (4000, 16), (12000, 16), + (16000, 16), (20000, 16), (22050, 0) + ]); + + // Low Pass Simper SVF @ 22050Hz RES=1.0 + let fft = fft_with_freq_res_type(&mut matrix, &mut node_exec, 8, 22050.0, 1.0); + assert_eq!( + avg_fft_freqs(8.0, &[100, 1000, 4000, 12000, 16000, 20000, 22050, 22051], &fft[..]), vec![ + (0, 8), (100, 16), (1000, 16), (4000, 16), (12000, 16), + (16000, 16), (20000, 16), (22050, 0) + ]); + + // Low Pass Simper SVF @ 0Hz RES=0.0 + let fft = fft_with_freq_res_type(&mut matrix, &mut node_exec, 8, 0.0, 0.0); + assert_eq!( + avg_fft_freqs(4.0, &[10, 100, 1000, 4000, 12000, 22050, 22051], &fft[..]), vec![ + (0, 0), (10, 0), (100, 0), (1000, 0), (4000, 0), (12000, 0), + (22050, 0) + ]); + + // Low Pass Simper SVF @ 0Hz RES=1.0 + let fft = fft_with_freq_res_type(&mut matrix, &mut node_exec, 8, 0.0, 1.0); + assert_eq!( + avg_fft_freqs(4.0, &[1, 5, 10, 100, 1000, 4000, 12000, 22050, 22051], &fft[..]), vec![ + (0, 56), (1, 0), (5, 0), (10, 0), (100, 0), (1000, 0), + (4000, 0), (12000, 0), (22050, 0) + ]); +} +