diff --git a/src/dsp/helpers.rs b/src/dsp/helpers.rs index 0430320..f6b6fa9 100644 --- a/src/dsp/helpers.rs +++ b/src/dsp/helpers.rs @@ -918,7 +918,7 @@ const FILTER_OVERSAMPLE_HAL_CHAMBERLIN : usize = 2; // https://www.earlevel.com/main/2003/03/02/the-digital-state-variable-filter/ // Inspired by SynthV1 by Rui Nuno Capela, under the terms of // GPLv2 or any later: -/// Process a HAL Chamberlin filter with two delays/state variables. +/// Process a HAL Chamberlin filter with two delays/state variables that is 12dB. /// The filter does internal oversampling with very simple decimation to /// rise the stability for cutoff frequency up to 16kHz. /// @@ -974,7 +974,7 @@ pub fn process_hal_chamberlin_svf( (high, notch) } -/// This function processes a Simper SVF. It's a much newer algorithm +/// This function processes a Simper SVF with 12dB. It's a much newer algorithm /// for filtering and provides easy to calculate multiple outputs. /// /// * `input` - Input sample. @@ -1042,6 +1042,44 @@ pub fn process_simper_svf( (v2, v1, input - k * v1 - v2) } +/// This function implements a simple Stilson/Moog filter with 24dB. +/// It provides multiple outputs for low, high and band pass and a notch +/// output. +/// +// Stilson/Moog implementation partly translated from SynthV1 by rncbc +// https://github.com/rncbc/synthv1/blob/master/src/synthv1_filter.h#L103 +// under GPLv2 or any later. +#[inline] +pub fn process_stilson_moog( + input: f32, freq: f32, res: f32, israte: f32, + b0: &mut f32, b1: &mut f32, b2: &mut f32, b3: &mut f32, b4: &mut f32 +) -> (f32, f32, f32, f32) { +// let cutoff = freq * israte; +// let cutoff = (std::f32::consts::PI * freq * israte).tan(); + let cutoff = 2.0 * freq * israte; + + let c = 1.0 - cutoff; + let p = cutoff + 0.8 * cutoff * c; + let f = p + p - 1.0; +// let f = 2.0 * cutoff.sin() - 1.0; + let q = res * (1.0 + 0.5 * c * (1.0 - c + 5.6 * c * c)); + + let inp = input - q * *b4; + let t1 = *b1; *b1 = (inp + *b0) * p - *b1 * f; + let t2 = *b2; *b2 = (*b1 + t1) * p - *b2 * f; + let t1 = *b3; *b3 = (*b2 + t2) * p - *b3 * f; + *b4 = (*b3 + t1) * p - *b4 * f; + + *b4 = *b4 - *b4 * *b4 * *b4 * 0.166667; // clipping + + *b0 = inp; + + let band = 3.0 * (*b3 - *b4); + + // low, band, high, notch + (*b4, band, inp - *b4, band - inp) +} + // 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 3d8aca5..93d932d 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, 1.0, 0.5) - {3 0 ftype setting(8) fa_sfilter_type 0 12} + {3 0 ftype setting(8) fa_sfilter_type 0 16} [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 a3cfb5b..1abb6fc 100644 --- a/src/dsp/node_sfilter.rs +++ b/src/dsp/node_sfilter.rs @@ -11,6 +11,7 @@ use crate::dsp::helpers::{ process_1pole_tpt_highpass, process_hal_chamberlin_svf, process_simper_svf, + process_stilson_moog, }; #[macro_export] @@ -30,6 +31,10 @@ macro_rules! fa_sfilter_type { ($formatter: expr, $v: expr, $denorm_v: expr) => 10 => "BP 12s", 11 => "NO 12s", 12 => "PK 12s", + 13 => "LP 24m", + 14 => "HP 24m", + 15 => "BP 24m", + 16 => "NO 24m", _ => "?", }; write!($formatter, "{}", s) @@ -43,6 +48,7 @@ pub struct SFilter { y: f32, k: f32, h: f32, + m: f32, otype: i8, } @@ -54,6 +60,7 @@ impl SFilter { y: 0.0, k: 0.0, h: 0.0, + m: 0.0, otype: -1, } } @@ -219,6 +226,7 @@ impl DspNode for SFilter { self.y = 0.0; self.k = 0.0; self.h = 0.0; + self.m = 0.0; self.otype = -1; } @@ -244,6 +252,7 @@ impl DspNode for SFilter { self.z = 0.0; self.k = 0.0; self.h = 0.0; + self.m = 0.0; self.otype = ftype; } @@ -366,6 +375,18 @@ impl DspNode for SFilter { low - high }); }, + 13 => { // Stilson/Moog Low Pass + process_filter_fun32!( + ctx.nframes(), inp, out, freq, res, 1.0, input, 22000.0, { + let (low, _band, _high, _notch) = + process_stilson_moog( + input, freq, res, self.israte, + &mut self.z, &mut self.y, &mut self.k, + &mut self.h, &mut self.m); + + low + }); + }, _ => {}, } diff --git a/tests/node_sfilter.rs b/tests/node_sfilter.rs index 1981068..d1a60b4 100644 --- a/tests/node_sfilter.rs +++ b/tests/node_sfilter.rs @@ -995,3 +995,97 @@ fn check_node_sfilter_simpersvf_peak() { (0, 164), (10, 40), (100, 16), (1000, 20), (4000, 16), (12000, 20) ]); } + +#[test] +fn check_node_sfilter_moog_lowpass() { + let (mut matrix, mut node_exec) = setup_sfilter_matrix(); + + // Low Pass Stilson/Moog @ 1000Hz RES=1.0 + let fft = fft_with_freq_res_type(&mut matrix, &mut node_exec, 13, 1000.0, 1.0); + assert_eq!( + avg_fft_freqs(4.0, &[ + 100, 200, 400, 500, 700, 900, 1000, 1100, 1200, + 1500, 2000, 3000, 4000, 6000, 12000 + ], &fft[..]), vec![ + (0, 0), (100, 4), (200, 4), (400, 0), (500, 4), (700, 4), + (900, 12), (1000, 36), (1100, 80), (1200, 8), + (1500, 0), (2000, 0), (3000, 0), (4000, 0), (6000, 0) + ]); + + // Low Pass Stilson/Moog @ 1000Hz RES=0.5 + let fft = fft_with_freq_res_type(&mut matrix, &mut node_exec, 13, 1000.0, 0.5); + assert_eq!( + avg_fft_freqs(4.0, &[ + 100, 200, 400, 500, 700, 900, 1000, 1100, 1200, + 1500, 2000, 3000, 4000, 6000, 12000 + ], &fft[..]), vec![ + (0, 4), (100, 4), (200, 4), (400, 4), (500, 8), + (700, 12), (900, 16), (1000, 12), (1100, 8), (1200, 4), + (1500, 0), (2000, 0), (3000, 0), (4000, 0), (6000, 0) + ]); + + // Low Pass Stilson/Moog @ 1000Hz RES=0.0 + let fft = fft_with_freq_res_type(&mut matrix, &mut node_exec, 13, 1000.0, 0.0); + assert_eq!( + avg_fft_freqs(4.0, &[ + 100, 200, 400, 500, 700, 900, 1000, 1100, 1200, + 1500, 2000, 3000, 4000, 6000, 12000 + ], &fft[..]), vec![ + (0, 16), (100, 16), (200, 16), (400, 4), (500, 12), (700, 8), + (900, 4), (1000, 4), (1100, 0), (1200, 0), (1500, 0), (2000, 0), + (3000, 0), (4000, 0), (6000, 0) + ]); + + // Low Pass Stilson/Moog @ 4000Hz RES=1.0 + let fft = fft_with_freq_res_type(&mut matrix, &mut node_exec, 13, 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, 332), + (4000, 164), (5000, 20), (6000, 8), (8000, 0) + ]); + + // Low Pass Stilson/Moog @ 4000Hz RES=0.0 + let fft = fft_with_freq_res_type(&mut matrix, &mut node_exec, 13, 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 Stilson/Moog @ 22050Hz RES=0.0 + let fft = fft_with_freq_res_type(&mut matrix, &mut node_exec, 13, 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 Stilson/Moog @ 22050Hz RES=1.0 + let fft = fft_with_freq_res_type(&mut matrix, &mut node_exec, 13, 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 Stilson/Moog @ 0Hz RES=0.0 + let fft = fft_with_freq_res_type(&mut matrix, &mut node_exec, 13, 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 Stilson/Moog @ 0Hz RES=1.0 + let fft = fft_with_freq_res_type(&mut matrix, &mut node_exec, 13, 0.0, 1.0); + assert_eq!( + avg_fft_freqs(4.0, &[1, 5, 10, 100, 1000, 4000, 12000, 22050, 22051], &fft[..]), vec![ + (0, 68), (1, 0), (5, 0), (10, 4), (100, 0), (1000, 0), + (4000, 0), (12000, 0), (22050, 0) + ]); +} +