Implemented the Hal Chamberlin SVF filter, and wrote first test.
This commit is contained in:
parent
88874c75b5
commit
47ad5610f3
4 changed files with 224 additions and 19 deletions
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
|
||||
|
|
|
@ -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),
|
||||
// ]);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue