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
}
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
// Copyright (C) 2020 TheWaveWarden
// under GPLv3 or any later

View file

@ -582,7 +582,8 @@ macro_rules! node_list {
sfilter => SFilter UIType::Generic UICategory::Signal
(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 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],
test => Test UIType::Generic UICategory::IOUtil
(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_tpt_lowpass,
process_1pole_tpt_highpass,
process_hal_chamberlin_svf,
};
#[macro_export]
macro_rules! fa_sfilter_type { ($formatter: expr, $v: expr, $denorm_v: expr) => { {
let s =
match ($v.round() as usize) {
0 => "LP(1p)",
1 => "LP(1pt)",
2 => "HP(1p)",
3 => "HP(1pt)",
0 => "LP 1p",
1 => "LP 1pt",
2 => "HP 1p",
3 => "HP 1pt",
4 => "LP 12s",
5 => "HP 12s",
6 => "BP 12s",
7 => "NO 12s",
_ => "?",
};
write!($formatter, "{}", s)
@ -30,6 +35,7 @@ pub struct SFilter {
israte: f64,
z: f64,
y: f64,
otype: i8,
}
impl SFilter {
@ -38,12 +44,15 @@ impl SFilter {
israte: 1.0 / 44100.0,
z: 0.0,
y: 0.0,
otype: -1,
}
}
pub const inp : &'static str =
"SFilter inp\nSignal input\nRange: (-1..1)\n";
pub const freq : &'static str =
"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 =
"SFilter ftype\nThe filter type, there are varying types of \
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'.
There are currently following filters available:
HP(1p) - One pole low-pass filter (6db)
HP(1pt) - One pole low-pass filter (6db) (TPT form)
LP(1p) - One pole high-pass filter (6db)
LP(1pt) - One pole high-pass filter (6db) (TPT form)
HP 1p - One pole low-pass filter (6db)
HP 1pt - One pole low-pass filter (6db) (TPT form)
LP 1p - One pole high-pass filter (6db)
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);
}
fn reset(&mut self) {
self.z = 0.0;
self.y = 0.0;
self.z = 0.0;
self.y = 0.0;
self.otype = -1;
}
#[inline]
@ -93,11 +107,20 @@ impl DspNode for SFilter {
let inp = inp::SFilter::inp(inputs);
let freq = inp::SFilter::freq(inputs);
let res = inp::SFilter::res(inputs);
let ftype = at::SFilter::ftype(atoms);
let out = out::SFilter::sig(outputs);
match ftype.i() {
0 => {
let ftype = ftype.i() as i8;
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() {
let input = inp.read(frame) as f64;
let freq = denorm::SFilter::freq(freq, frame) as f64;
@ -108,7 +131,7 @@ impl DspNode for SFilter {
as f32);
}
},
1 => {
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;
@ -119,7 +142,7 @@ impl DspNode for SFilter {
as f32);
}
},
2 => {
2 => { // Highpass
for frame in 0..ctx.nframes() {
let input = inp.read(frame) as f64;
let freq = denorm::SFilter::freq(freq, frame) as f64;
@ -130,7 +153,7 @@ impl DspNode for SFilter {
as f32);
}
},
3 => {
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;
@ -141,6 +164,70 @@ impl DspNode for SFilter {
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(
matrix: &mut Matrix,
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);
pset_d_wait(matrix, node_exec, sf, "freq", freq);
// pset_d_wait(&mut matrix, &mut node_exec, sf, "freq", freq);
pset_d(matrix, sf, "freq", freq);
pset_d_wait(matrix, node_exec, sf, "res", res);
pset_s(matrix, sf, "ftype", ftype);
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),
]);
}
#[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),
// ]);
}