diff --git a/src/dsp/biquad.rs b/src/dsp/biquad.rs index 4d42551..45a4113 100644 --- a/src/dsp/biquad.rs +++ b/src/dsp/biquad.rs @@ -21,6 +21,9 @@ pub struct BiquadCoefs { pub b2: f32, } +// TODO: +// https://github.com/VCVRack/Befaco/blob/v1/src/ChowDSP.hpp#L339 +// more coeffs from there ^^^^^^^^^^^^^ ? impl BiquadCoefs { /// Returns settings for a Butterworth lowpass filter. /// Cutoff is the -3 dB point of the filter in Hz. @@ -60,7 +63,7 @@ impl BiquadCoefs { } /// 2nd order IIR filter implemented in normalized Direct Form I. -#[derive(Copy, Clone, Default)] +#[derive(Debug, Copy, Clone, Default)] pub struct Biquad { coefs: BiquadCoefs, x1: f32, @@ -84,7 +87,7 @@ impl Biquad { self.coefs = coefs; } - fn reset(&mut self) { + pub fn reset(&mut self) { self.x1 = 0.0; self.x2 = 0.0; self.y1 = 0.0; @@ -92,7 +95,7 @@ impl Biquad { } #[inline] - fn tick(&mut self, input: f32) -> f32 { + pub fn tick(&mut self, input: f32) -> f32 { let x0 = input; let y0 = self.coefs.b0 * x0 diff --git a/src/dsp/mod.rs b/src/dsp/mod.rs index 9fe01f8..658a189 100644 --- a/src/dsp/mod.rs +++ b/src/dsp/mod.rs @@ -36,6 +36,8 @@ mod node_mix3; mod node_bosc; #[allow(non_upper_case_globals)] mod node_vosc; +#[allow(non_upper_case_globals)] +mod node_biqfilt; pub mod biquad; pub mod tracker; @@ -67,6 +69,8 @@ use crate::fa_smap_clip; use crate::fa_smap_mode; use crate::fa_sfilter_type; use crate::fa_bosc_wtype; +use crate::fa_biqfilt_type; +use crate::fa_biqfilt_ord; use node_amp::Amp; use node_sin::Sin; @@ -86,6 +90,7 @@ use node_sfilter::SFilter; use node_mix3::Mix3; use node_bosc::BOsc; use node_vosc::VOsc; +use node_biqfilt::BiqFilt; pub const MIDI_MAX_FREQ : f32 = 13289.75; @@ -635,6 +640,14 @@ macro_rules! node_list { (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 16} [0 sig], + biqfilt => BiqFilt 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 q n_id d_id r_id f_def stp_d 0.0, 1.0, 0.5) + (3 gain n_ogin d_ogin r_id f_def stp_d 0.0, 1.0, 1.0) + {4 0 ftype setting(0) fa_biqfilt_type 0 0} + {5 1 order setting(0) fa_biqfilt_ord 0 3} + [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) {1 0 p param(0.0) fa_test_s 0 10} diff --git a/src/dsp/node_biqfilt.rs b/src/dsp/node_biqfilt.rs new file mode 100644 index 0000000..eb95bd5 --- /dev/null +++ b/src/dsp/node_biqfilt.rs @@ -0,0 +1,165 @@ +// Copyright (c) 2021 Weird Constructor +// This file is a part of HexoDSP. Released under GPL-3.0-or-later. +// See README.md and COPYING for details. + +use crate::nodes::{NodeAudioContext, NodeExecContext}; +use crate::dsp::{NodeId, SAtom, ProcBuf, DspNode, LedPhaseVals, NodeContext}; +use crate::dsp::biquad::*; + +#[macro_export] +macro_rules! fa_biqfilt_type { ($formatter: expr, $v: expr, $denorm_v: expr) => { { + let s = + match ($v.round() as usize) { + 0 => "BtW LP", + _ => "?", + }; + write!($formatter, "{}", s) +} } } + +#[macro_export] +macro_rules! fa_biqfilt_ord { ($formatter: expr, $v: expr, $denorm_v: expr) => { { + let s = + match ($v.round() as usize) { + 0 => "1", + 1 => "2", + 2 => "3", + 3 => "4", + _ => "?", + }; + write!($formatter, "{}", s) +} } } + +/// A simple amplifier +#[derive(Debug, Clone)] +pub struct BiqFilt { + cascade: Vec, + srate: f32, + ofreq: f32, + oq: f32, + ogain: f32, + otype: u8, +} + +impl BiqFilt { + pub fn new(_nid: &NodeId) -> Self { + Self { + cascade: vec![Biquad::new(); 4], + srate: 1.0 / 44100.0, + otype: 99, // value that can't be set by the user + ofreq: -2.0, // value that can't be set by the user + oq: -2.0, // value that can't be set by the user + ogain: -2.0, // value that can't be set by the user + } + } + pub const inp : &'static str = + "BiqFilt inp\nSignal input\nRange: (-1..1)\n"; + pub const freq : &'static str = + "BiqFilt freq\nFilter cutoff frequency.\nRange: (-1..1)\n"; + pub const q : &'static str = + "BiqFilt q\nFilter Q factor.\nRange: (0..1)\n"; + pub const gain : &'static str = + "BiqFilt gain\nFilter gain.\nRange: (0..1)\n"; + pub const ftype : &'static str = + "BiqFilt ftype\n"; + pub const order : &'static str = + "BiqFilt order\n"; + pub const sig : &'static str = + "BiqFilt sig\nFiltered signal output.\nRange: (-1..1)\n"; + pub const DESC : &'static str = +r#"Biquad Filter + +This is the implementation of a biquad filter cascade. +It is not meant for fast automation. Please use other nodes +like eg. SFilter for that. +"#; + pub const HELP : &'static str = +r#"BiqFilt - Biquad Filter (Cascade) + +This is the implementation of a biquad filter cascade. +It is not meant for fast automation and might blow up if you +treat it too rough. Please use other nodes like eg. SFilter for that. +"#; +} + +impl DspNode for BiqFilt { + fn outputs() -> usize { 1 } + + fn set_sample_rate(&mut self, srate: f32) { + self.srate = srate; + self.otype = 99; // cause recalculation of the filter + + for b in &mut self.cascade { + b.reset(); + } + } + + fn reset(&mut self) { + for b in &mut self.cascade { + b.reset(); + } + } + + #[inline] + fn process( + &mut self, ctx: &mut T, _ectx: &mut NodeExecContext, + _nctx: &NodeContext, + atoms: &[SAtom], inputs: &[ProcBuf], + outputs: &mut [ProcBuf], ctx_vals: LedPhaseVals) + { + use crate::dsp::{out, inp, denorm, at}; + + let inp = inp::BiqFilt::inp(inputs); + let freq = inp::BiqFilt::freq(inputs); + let q = inp::BiqFilt::q(inputs); + let gain = inp::BiqFilt::gain(inputs); + let ftype = at::BiqFilt::ftype(atoms); + let order = at::BiqFilt::order(atoms); + let out = out::BiqFilt::sig(outputs); + + let ftype = ftype.i() as u8; + let cfreq = denorm::BiqFilt::freq(freq, 0); + let cfreq = cfreq.clamp(0.0, 22000.0); + let cq = denorm::BiqFilt::q(q, 0); + let cgain = denorm::BiqFilt::gain(gain, 0); + + if ftype != self.otype + || (cfreq - self.ofreq).abs() > 0.0001 + || (cq - self.oq).abs() > 0.0001 + || (cgain - self.ogain).abs() > 0.0001 + { + // recalculate coeffs of all in the cascade + let coefs = + match ftype { + _ => BiquadCoefs::butter_lowpass(self.srate, cfreq), + }; + + for o in &mut self.cascade { + o.set_coefs(coefs); + o.reset(); + } + + self.otype = ftype; + self.ofreq = cfreq; + self.oq = cq; + self.ogain = cgain; + } + + let order = order.i() as u8; + + for frame in 0..ctx.nframes() { + let freq = denorm::BiqFilt::freq(freq, frame); +// let freq = freq.clamp($minfreq, $maxfreq); + let q = denorm::BiqFilt::q(q, frame); + let gain = denorm::BiqFilt::gain(gain, frame); + + let mut s = inp.read(frame); + for i in 0..order { + s = self.cascade[i as usize].tick(s); + } + + out.write(frame, s); + } + + ctx_vals[0].set(out.read(ctx.nframes() - 1)); + } +}