From 5dc702426c9778d1b00d7d3a4a5f092e44981924 Mon Sep 17 00:00:00 2001 From: Weird Constructor Date: Sat, 10 Jul 2021 22:16:55 +0200 Subject: [PATCH] Started simple filter implementation --- src/dsp/helpers.rs | 4 +- src/dsp/mod.rs | 9 ++++ src/dsp/node_noise.rs | 2 +- src/dsp/node_sfilter.rs | 115 ++++++++++++++++++++++++++++++++++++++++ tests/node_noise.rs | 25 +++++++++ 5 files changed, 152 insertions(+), 3 deletions(-) create mode 100644 src/dsp/node_sfilter.rs diff --git a/src/dsp/helpers.rs b/src/dsp/helpers.rs index 2fafe51..0e9b332 100644 --- a/src/dsp/helpers.rs +++ b/src/dsp/helpers.rs @@ -10,7 +10,7 @@ pub fn init_cos_tab() { for i in 0..(FAST_COS_TAB_SIZE+1) { let phase : f32 = (i as f32) - * ((std::f32::consts::PI * 2.0) + * ((std::f32::consts::TAU) / (FAST_COS_TAB_SIZE as f32)); unsafe { // XXX: note: mutable statics can be mutated by multiple @@ -21,7 +21,7 @@ pub fn init_cos_tab() { } } -const PHASE_SCALE : f32 = 1.0_f32 / (std::f32::consts::PI * 2.0_f32); +const PHASE_SCALE : f32 = 1.0_f32 / (std::f32::consts::TAU); pub fn fast_cos(mut x: f32) -> f32 { x = x.abs(); // cosine is symmetrical around 0, let's get rid of negative values diff --git a/src/dsp/mod.rs b/src/dsp/mod.rs index 3d0dde5..f8d094d 100644 --- a/src/dsp/mod.rs +++ b/src/dsp/mod.rs @@ -28,6 +28,8 @@ mod node_noise; mod node_map; #[allow(non_upper_case_globals)] mod node_smap; +#[allow(non_upper_case_globals)] +mod node_sfilter; pub mod tracker; mod satom; @@ -56,6 +58,7 @@ use crate::fa_noise_mode; use crate::fa_map_clip; use crate::fa_smap_clip; use crate::fa_smap_mode; +use crate::fa_sfilter_type; use node_amp::Amp; use node_sin::Sin; @@ -71,6 +74,7 @@ use node_allp::AllP; use node_noise::Noise; use node_map::Map; use node_smap::SMap; +use node_sfilter::SFilter; pub const MIDI_MAX_FREQ : f32 = 13289.75; @@ -575,6 +579,11 @@ macro_rules! node_list { (1 offs n_id d_id r_s f_def stp_d -1.0, 1.0, 0.0) {2 0 mode setting(0) fa_noise_mode 0 1} [0 sig], + 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 1} + [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_noise.rs b/src/dsp/node_noise.rs index 758fde6..f7abc29 100644 --- a/src/dsp/node_noise.rs +++ b/src/dsp/node_noise.rs @@ -38,7 +38,7 @@ impl Noise { } pub const atv : &'static str = - "Noise atv\n.Attenuverter input, to attenuate or inverter \ + "Noise atv\n.Attenuverter input, to attenuate or invert \ the noise.\nRange: (-1..1)"; pub const offs : &'static str = "Noise offs\n.Offset input, that is added to the output \ diff --git a/src/dsp/node_sfilter.rs b/src/dsp/node_sfilter.rs new file mode 100644 index 0000000..fdc75b7 --- /dev/null +++ b/src/dsp/node_sfilter.rs @@ -0,0 +1,115 @@ +// Copyright (c) 2021 Weird Constructor +// This is a part of HexoDSP. Released under (A)GPLv3 or any later. +// See README.md and COPYING for details. + +use crate::nodes::{NodeAudioContext, NodeExecContext}; +use crate::dsp::{NodeId, SAtom, ProcBuf, DspNode, LedPhaseVals, NodeContext}; + +#[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 => "HP(1p)", + _ => "?", + }; + write!($formatter, "{}", s) +} } } + +/// A simple amplifier +#[derive(Debug, Clone)] +pub struct SFilter { + israte: f64, + z: f64, +} + +impl SFilter { + pub fn new(_nid: &NodeId) -> Self { + Self { + israte: 1.0 / 44100.0, + z: 0.0, + } + } + 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 ftype : &'static str = + "SFilter ftype\nFilter type."; + pub const sig : &'static str = + "SFilter sig\nFiltered signal output.\nRange: (-1..1)\n"; + pub const DESC : &'static str = +r#"Simple Audio Filter + +This is a very simple collection of filters. +"#; + pub const HELP : &'static str = +r#"SFilter - Simple Audio Filter + +"#; +} + +impl DspNode for SFilter { + fn outputs() -> usize { 1 } + + fn set_sample_rate(&mut self, srate: f32) { + self.israte = 1.0 / (srate as f64); + } + fn reset(&mut self) { + self.z = 0.0; + } + + #[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::SFilter::inp(inputs); + let freq = inp::SFilter::freq(inputs); + let ftype = at::SFilter::ftype(atoms); + let out = out::SFilter::sig(outputs); + + match ftype.i() { + // one pole lp from valley rack free: + // https://github.com/ValleyAudio/ValleyRackFree/blob/v1.0/src/Common/DSP/OnePoleFilters.cpp + 0 => { + for frame in 0..ctx.nframes() { + let input = inp.read(frame) as f64; + let b = + (-std::f64::consts::TAU + * (denorm::SFilter::freq(freq, frame) as f64) + * self.israte).exp(); + let a = 1.0 - b; + + self.z = a * input + self.z * b; + out.write(frame, self.z as f32); + } + }, + // one pole from: + // http://www.willpirkle.com/Downloads/AN-4VirtualAnalogFilters.pdf + // (page 5) + 1 => { + for frame in 0..ctx.nframes() { + let input = inp.read(frame) as f64; + let g = + (std::f64::consts::PI + * (denorm::SFilter::freq(freq, frame) as f64) + * self.israte).tan(); + let a1 = g / (1.0 + g); + + let v1 = a1 * (input - self.z); + let v2 = v1 + self.z; + self.z = v2 + v1; + + let (m0, m1) = (0.0, 1.0); + out.write(frame, (m0 * input + m1 * v2) as f32); + } + }, + _ => {}, + } + } +} diff --git a/tests/node_noise.rs b/tests/node_noise.rs index d611f04..81b4bb5 100644 --- a/tests/node_noise.rs +++ b/tests/node_noise.rs @@ -143,3 +143,28 @@ fn check_node_noise_atv_offs_bipolar() { println!("mima {:?}", rms_mimax); assert_rmsmima!(rms_mimax[0], (0.2407, -0.0998, 0.8996)); } + +#[test] +fn check_node_noise_fft() { + let (node_conf, mut node_exec) = new_node_engine(); + let mut matrix = Matrix::new(node_conf, 3, 3); + + let noise = NodeId::Noise(0); + let out = NodeId::Out(0); + matrix.place(0, 0, Cell::empty(noise) + .out(None, None, noise.out("sig"))); + matrix.place(0, 1, Cell::empty(out) + .input(out.inp("ch1"), None, None)); + pset_s(&mut matrix, noise, "mode", 0); + pset_n(&mut matrix, noise, "atv", 1.0); + pset_n(&mut matrix, noise, "offs", 0.0); + matrix.sync().unwrap(); + + let fft = run_and_get_fft4096(&mut node_exec, 50, 1000.0); + assert!(fft.len() > 15); + for (_freq, lvl) in fft { + assert_float_eq!( + (((lvl as i64 - 57) as f32).abs() / 10.0).floor(), + 0.0); + } +}