Implemented the Hal Chamberlin SVF filter, and wrote first test.

This commit is contained in:
Weird Constructor 2021-07-12 19:54:01 +02:00
parent 88874c75b5
commit 47ad5610f3
4 changed files with 224 additions and 19 deletions

View file

@ -763,6 +763,63 @@ pub fn process_1pole_tpt_highpass(input: f64, freq: f64, israte: f64, z: &mut f6
input - v2 input - v2
} }
const FILTER_OVERSAMPLE_HAL_CHAMBERLIN : usize = 2;
// Hal Chamberlin's State Variable (12dB/oct) filter
// 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.
/// The filter does internal oversampling with very simple decimation to
/// rise the stability for cutoff frequency up to 16kHz.
///
/// * `input` - Input sample.
/// * `freq` - Frequency in Hz. Please keep it inside 0.0 to 16000.0 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.
/// * `band` - First state variable, containing the band pass result
/// after processing.
/// * `low` - Second state variable, containing the low pass result
/// after processing.
///
/// Returned are the results of the high and notch filter.
///
///```
/// let mut band = 0.0;
/// let mut low = 0.0;
/// let mut freq = 1000.0;
///
/// for s in samples.iter() {
/// let (high, notch) =
/// process_hal_chamberlin_svf(
/// s, freq, 0.5, &mut band, &mut low);
/// // ... do something with the result here.
/// }
///```
#[inline]
pub fn process_hal_chamberlin_svf(
input: f64, freq: f64, res: f64, israte: f64, band: &mut f64, low: &mut f64)
-> (f64, f64)
{
let q = 1.0 - res;
let cutoff = 2.0 * (std::f64::consts::PI * freq * 0.5 * israte).sin();
let mut high = 0.0;
let mut notch = 0.0;
for _ in 0..FILTER_OVERSAMPLE_HAL_CHAMBERLIN {
*low += cutoff * *band;
high = input - *low - q * *band;
*band += cutoff * high;
notch = high + *low;
}
//d// println!("q={:4.2} cut={:8.3} freq={:8.1} LP={:8.3} HP={:8.3} BP={:8.3} N={:8.3}",
//d// q, cutoff, freq, *low, high, *band, notch);
(high, notch)
}
// translated from Odin 2 Synthesizer Plugin // translated from Odin 2 Synthesizer Plugin
// Copyright (C) 2020 TheWaveWarden // Copyright (C) 2020 TheWaveWarden
// under GPLv3 or any later // under GPLv3 or any later

View file

@ -582,7 +582,8 @@ macro_rules! node_list {
sfilter => SFilter UIType::Generic UICategory::Signal sfilter => SFilter UIType::Generic UICategory::Signal
(0 inp n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0) (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) (1 freq n_pit d_pit r_fq f_freq stp_d -1.0, 0.5647131, 1000.0)
{2 0 ftype setting(0) fa_sfilter_type 0 3} (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}
[0 sig], [0 sig],
test => Test UIType::Generic UICategory::IOUtil test => Test UIType::Generic UICategory::IOUtil
(0 f n_id d_id r_id f_def stp_d 0.0, 1.0, 0.5) (0 f n_id d_id r_id f_def stp_d 0.0, 1.0, 0.5)

View file

@ -9,16 +9,21 @@ use crate::dsp::helpers::{
process_1pole_highpass, process_1pole_highpass,
process_1pole_tpt_lowpass, process_1pole_tpt_lowpass,
process_1pole_tpt_highpass, process_1pole_tpt_highpass,
process_hal_chamberlin_svf,
}; };
#[macro_export] #[macro_export]
macro_rules! fa_sfilter_type { ($formatter: expr, $v: expr, $denorm_v: expr) => { { macro_rules! fa_sfilter_type { ($formatter: expr, $v: expr, $denorm_v: expr) => { {
let s = let s =
match ($v.round() as usize) { match ($v.round() as usize) {
0 => "LP(1p)", 0 => "LP 1p",
1 => "LP(1pt)", 1 => "LP 1pt",
2 => "HP(1p)", 2 => "HP 1p",
3 => "HP(1pt)", 3 => "HP 1pt",
4 => "LP 12s",
5 => "HP 12s",
6 => "BP 12s",
7 => "NO 12s",
_ => "?", _ => "?",
}; };
write!($formatter, "{}", s) write!($formatter, "{}", s)
@ -30,6 +35,7 @@ pub struct SFilter {
israte: f64, israte: f64,
z: f64, z: f64,
y: f64, y: f64,
otype: i8,
} }
impl SFilter { impl SFilter {
@ -38,12 +44,15 @@ impl SFilter {
israte: 1.0 / 44100.0, israte: 1.0 / 44100.0,
z: 0.0, z: 0.0,
y: 0.0, y: 0.0,
otype: -1,
} }
} }
pub const inp : &'static str = pub const inp : &'static str =
"SFilter inp\nSignal input\nRange: (-1..1)\n"; "SFilter inp\nSignal input\nRange: (-1..1)\n";
pub const freq : &'static str = pub const freq : &'static str =
"SFilter freq\nFilter cutoff frequency.\nRange: (-1..1)\n"; "SFilter freq\nFilter cutoff frequency.\nRange: (-1..1)\n";
pub const res : &'static str =
"SFilter res\nFilter resonance.\nRange: (0..1)\n";
pub const ftype : &'static str = pub const ftype : &'static str =
"SFilter ftype\nThe filter type, there are varying types of \ "SFilter ftype\nThe filter type, there are varying types of \
filters available. Please consult the node documentation for \ filters available. Please consult the node documentation for \
@ -64,10 +73,14 @@ of varying types. There are only few parameters for you to change: 'freq'
and 'res'onance. You can switch between the types with the 'ftype'. and 'res'onance. You can switch between the types with the 'ftype'.
There are currently following filters available: There are currently following filters available:
HP(1p) - One pole low-pass filter (6db) HP 1p - One pole low-pass filter (6db)
HP(1pt) - One pole low-pass filter (6db) (TPT form) HP 1pt - One pole low-pass filter (6db) (TPT form)
LP(1p) - One pole high-pass filter (6db) LP 1p - One pole high-pass filter (6db)
LP(1pt) - One pole high-pass filter (6db) (TPT form) LP 1pt - One pole high-pass filter (6db) (TPT form)
LP 12s - Low-pass Hal Chamberlin state variable filter (12dB)
HP 12s - High-pass Hal Chamberlin state variable filter (12dB)
BP 12s - Band-pass Hal Chamberlin state variable filter (12dB)
NO 12s - Notch Hal Chamberlin state variable filter (12dB)
"#; "#;
} }
@ -78,8 +91,9 @@ impl DspNode for SFilter {
self.israte = 1.0 / (srate as f64); self.israte = 1.0 / (srate as f64);
} }
fn reset(&mut self) { fn reset(&mut self) {
self.z = 0.0; self.z = 0.0;
self.y = 0.0; self.y = 0.0;
self.otype = -1;
} }
#[inline] #[inline]
@ -93,11 +107,20 @@ impl DspNode for SFilter {
let inp = inp::SFilter::inp(inputs); let inp = inp::SFilter::inp(inputs);
let freq = inp::SFilter::freq(inputs); let freq = inp::SFilter::freq(inputs);
let res = inp::SFilter::res(inputs);
let ftype = at::SFilter::ftype(atoms); let ftype = at::SFilter::ftype(atoms);
let out = out::SFilter::sig(outputs); let out = out::SFilter::sig(outputs);
match ftype.i() { let ftype = ftype.i() as i8;
0 => {
if ftype != self.otype {
self.y = 0.0;
self.z = 0.0;
self.otype = ftype;
}
match ftype {
0 => { // Lowpass
for frame in 0..ctx.nframes() { for frame in 0..ctx.nframes() {
let input = inp.read(frame) as f64; let input = inp.read(frame) as f64;
let freq = denorm::SFilter::freq(freq, frame) as f64; let freq = denorm::SFilter::freq(freq, frame) as f64;
@ -108,7 +131,7 @@ impl DspNode for SFilter {
as f32); as f32);
} }
}, },
1 => { 1 => { // Lowpass TPT
for frame in 0..ctx.nframes() { for frame in 0..ctx.nframes() {
let input = inp.read(frame) as f64; let input = inp.read(frame) as f64;
let freq = denorm::SFilter::freq(freq, frame) as f64; let freq = denorm::SFilter::freq(freq, frame) as f64;
@ -119,7 +142,7 @@ impl DspNode for SFilter {
as f32); as f32);
} }
}, },
2 => { 2 => { // Highpass
for frame in 0..ctx.nframes() { for frame in 0..ctx.nframes() {
let input = inp.read(frame) as f64; let input = inp.read(frame) as f64;
let freq = denorm::SFilter::freq(freq, frame) as f64; let freq = denorm::SFilter::freq(freq, frame) as f64;
@ -130,7 +153,7 @@ impl DspNode for SFilter {
as f32); as f32);
} }
}, },
3 => { 3 => { // Highpass TPT
for frame in 0..ctx.nframes() { for frame in 0..ctx.nframes() {
let input = inp.read(frame) as f64; let input = inp.read(frame) as f64;
let freq = denorm::SFilter::freq(freq, frame) as f64; let freq = denorm::SFilter::freq(freq, frame) as f64;
@ -141,6 +164,70 @@ impl DspNode for SFilter {
as f32); 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(1.0, 22000.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);
}
},
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, 22000.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);
}
},
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, 22000.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);
}
},
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, 22000.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);
}
},
_ => {}, _ => {},
} }

View file

@ -27,11 +27,11 @@ fn setup_sfilter_matrix() -> (Matrix, NodeExecutor) {
fn fft_with_freq_res_type( fn fft_with_freq_res_type(
matrix: &mut Matrix, matrix: &mut Matrix,
node_exec: &mut NodeExecutor, node_exec: &mut NodeExecutor,
ftype: i64, freq: f32, _res: f32) -> Vec<(u16, u32)> ftype: i64, freq: f32, res: f32) -> Vec<(u16, u32)>
{ {
let sf = NodeId::SFilter(0); let sf = NodeId::SFilter(0);
pset_d_wait(matrix, node_exec, sf, "freq", freq); pset_d(matrix, sf, "freq", freq);
// pset_d_wait(&mut matrix, &mut node_exec, sf, "freq", freq); pset_d_wait(matrix, node_exec, sf, "res", res);
pset_s(matrix, sf, "ftype", ftype); pset_s(matrix, sf, "ftype", ftype);
run_and_get_fft4096(node_exec, 0, 1000.0) run_and_get_fft4096(node_exec, 0, 1000.0)
} }
@ -201,3 +201,63 @@ fn check_node_sfilter_highpass_tpt() {
(0, 24), (100, 16), (1000, 16), (4000, 16), (12000, 16), (0, 24), (100, 16), (1000, 16), (4000, 16), (12000, 16),
]); ]);
} }
#[test]
fn check_node_sfilter_halsvf_lowpass() {
let (mut matrix, mut node_exec) = setup_sfilter_matrix();
// Low Pass Hal Chamberlin SVF @ 1000Hz RES=1.0
let fft = fft_with_freq_res_type(&mut matrix, &mut node_exec, 4, 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, 240), (1000, 60),
(1500, 10), (2000, 0), (3000, 0), (4000, 0)
]);
// Low Pass Hal Chamberlin SVF @ 1000Hz RES=0.5
let fft = fft_with_freq_res_type(&mut matrix, &mut node_exec, 4, 1000.0, 0.5);
assert_eq!(
avg_fft_freqs(10.0, &[
500, 700, 900, 1000, 1500, 2000, 3000, 4000, 12000
], &fft[..]), vec![
(0, 20), (500, 20), (700, 30), (900, 40), (1000, 20),
(1500, 0), (2000, 0), (3000, 0), (4000, 0)
]);
// Low Pass Hal Chamberlin SVF @ 1000Hz RES=0.0
let fft = fft_with_freq_res_type(&mut matrix, &mut node_exec, 4, 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, 20), (700, 20), (900, 10), (1000, 10),
(1500, 0), (2000, 0), (3000, 0), (4000, 0)
]);
// // High Pass TPT @ 4000Hz
// let fft = fft_with_freq_res_type(&mut matrix, &mut node_exec, 3, 4000.0, 0.0);
// assert_eq!(
// avg_fft_freqs(4.0, &[
// 100, 250, 500, 750, 1000, 1500, 2000, 3000, 8000, 12000
// ], &fft[..]), vec![
// (0, 0), (100, 0), (250, 0), (500, 0), (750, 4), (1000, 4),
// (1500, 4), (2000, 8), (3000, 12), (8000, 16),
// ]);
//
// // High Pass TPT @ 22050Hz
// let fft = fft_with_freq_res_type(&mut matrix, &mut node_exec, 3, 22050.0, 0.0);
// assert_eq!(
// avg_fft_freqs(4.0, &[100, 1000, 4000, 12000, 22050], &fft[..]), vec![
// (0, 0), (100, 0), (1000, 0), (4000, 0), (12000, 0),
// ]);
//
// // High Pass TPT @ 0Hz
// let fft = fft_with_freq_res_type(&mut matrix, &mut node_exec, 3, 0.0, 0.0);
// assert_eq!(
// avg_fft_freqs(4.0, &[100, 1000, 4000, 12000, 22050], &fft[..]), vec![
// (0, 24), (100, 16), (1000, 16), (4000, 16), (12000, 16),
// ]);
}