From 85ad5e79559f29cf68392de535f8e2859b1e39d8 Mon Sep 17 00:00:00 2001 From: Weird Constructor Date: Mon, 28 Jun 2021 05:10:46 +0200 Subject: [PATCH] helpers for comb and allpass, and added allpass node --- src/dsp/helpers.rs | 67 ++++++++++++++++++++++++++ src/dsp/mod.rs | 25 +++++++++- src/dsp/node_allp.rs | 110 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 201 insertions(+), 1 deletion(-) create mode 100644 src/dsp/node_allp.rs diff --git a/src/dsp/helpers.rs b/src/dsp/helpers.rs index 560bc52..8afa829 100644 --- a/src/dsp/helpers.rs +++ b/src/dsp/helpers.rs @@ -619,6 +619,73 @@ impl DelayBuffer { } } +/// Default size of the delay buffer: 1 seconds at 8 times 48kHz +const DEFAULT_ALLPASS_COMB_SAMPLES : usize = 8 * 48000; + +#[derive(Debug, Clone)] +pub struct AllPass { + delay: DelayBuffer, +} + +impl AllPass { + pub fn new() -> Self { + Self { + delay: DelayBuffer::new_with_size(DEFAULT_ALLPASS_COMB_SAMPLES), + } + } + + pub fn set_sample_rate(&mut self, srate: f32) { + self.delay.set_sample_rate(srate); + } + + pub fn reset(&mut self) { + self.delay.reset(); + } + + #[inline] + pub fn next(&mut self, time: f32, g: f32, v: f32) -> f32 { + let s = self.delay.cubic_interpolate_at(time); + self.delay.feed(v + s * g); + s + -1.0 * g * v + } +} + +#[derive(Debug, Clone)] +pub struct Comb { + delay: DelayBuffer, +} + +impl Comb { + pub fn new() -> Self { + Self { + delay: DelayBuffer::new_with_size(DEFAULT_ALLPASS_COMB_SAMPLES), + } + } + + pub fn set_sample_rate(&mut self, srate: f32) { + self.delay.set_sample_rate(srate); + } + + pub fn reset(&mut self) { + self.delay.reset(); + } + + #[inline] + pub fn next_feedback(&mut self, time: f32, g: f32, v: f32) -> f32 { + let s = self.delay.cubic_interpolate_at(time); + self.delay.feed(v + s * g); + v + } + + #[inline] + pub fn next_feedforward(&mut self, time: f32, g: f32, v: f32) -> f32 { + let s = self.delay.cubic_interpolate_at(time); + self.delay.feed(v); + v + s * g + } +} + + // translated from Odin 2 Synthesizer Plugin // Copyright (C) 2020 TheWaveWarden // under GPLv3 or any later diff --git a/src/dsp/mod.rs b/src/dsp/mod.rs index 256e410..be0669f 100644 --- a/src/dsp/mod.rs +++ b/src/dsp/mod.rs @@ -20,6 +20,8 @@ mod node_fbwr_fbrd; mod node_ad; #[allow(non_upper_case_globals)] mod node_delay; +#[allow(non_upper_case_globals)] +mod node_allp; pub mod tracker; mod satom; @@ -55,6 +57,7 @@ use node_fbwr_fbrd::FbWr; use node_fbwr_fbrd::FbRd; use node_ad::Ad; use node_delay::Delay; +use node_allp::AllP; pub const MIDI_MAX_FREQ : f32 = 13289.75; @@ -311,6 +314,20 @@ macro_rules! r_tms { ($x: expr, $coarse: expr) => { } } } +/// The rounding function for milliseconds knobs +macro_rules! r_fms { ($x: expr, $coarse: expr) => { + if $coarse { + if d_ftme!($x) > 1000.0 { + n_ftme!((d_ftme!($x) / 100.0).round() * 100.0) + } else if d_ftme!($x) > 100.0 { + n_ftme!((d_ftme!($x) / 10.0).round() * 10.0) + } else { + n_ftme!((d_ftme!($x)).round()) + } + } else { + n_ftme!((d_ftme!($x) * 10.0).round() / 10.0) + } +} } /// The default steps function: macro_rules! stp_d { () => { (20.0, 100.0) } } @@ -370,7 +387,8 @@ define_exp!{n_declick d_declick 0.0, 50.0} define_exp!{n_env d_env 0.0, 1000.0} -define_exp!{n_time d_time 0.5, 5000.0} +define_exp!{n_time d_time 0.5, 5000.0} +define_exp!{n_ftme d_ftme 0.25, 1000.0} // Special linear gain factor for the Out node, to be able // to reach more exact "1.0". @@ -471,6 +489,11 @@ macro_rules! node_list { (4 mix n_id d_id r_id f_def stp_d 0.0, 1.0, 0.5) {5 0 mode setting(0) fa_delay_mode 0 1} [0 sig], + allp => AllP UIType::Generic UICategory::Signal + (0 inp n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0) + (1 time n_ftme d_ftme r_fms f_ms stp_m 0.0, 1.0, 25.0) + (2 g n_id d_id r_id f_def stp_d -1.0, 1.0, 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) {1 0 p param(0.0) fa_test_s 0 10} diff --git a/src/dsp/node_allp.rs b/src/dsp/node_allp.rs new file mode 100644 index 0000000..b6ca616 --- /dev/null +++ b/src/dsp/node_allp.rs @@ -0,0 +1,110 @@ +// 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}; +use crate::dsp::helpers::AllPass; + +/// A simple amplifier +#[derive(Debug, Clone)] +pub struct AllP { + allpass: Box, +} + +impl AllP { + pub fn new(_nid: &NodeId) -> Self { + Self { + allpass: Box::new(AllPass::new()), + } + } + + pub const inp : &'static str = + "AllP inp\nThe signal input for the allpass filter.\nRange: (-1..1)"; + pub const g : &'static str = + "AllP g\nThe internal factor for the allpass filter.\nRange: (-1..1)"; + pub const time : &'static str = + "AllP time\nThe allpass delay time.\nRange: (0..1)"; + pub const sig : &'static str = + "AllP sig\nThe output of allpass filter.\nRange: (-1..1)"; + + pub const DESC : &'static str = +r#"Simple Single Allpass Filter + +This is an allpass filter that can be used to build reverbs +or anything you might find it useful for. +"#; +pub const HELP : &'static str = +r#"AllP - A Simple Single Allpass Filter + +This is an allpass filter that can be used to build reverbs +or anything you might find it useful for. + +Typical arrangements are (Schroeder Reverb): + + t=4.5ms + g=0.7 -> Comb + AllP -> AllP -> AllP -> -> Comb + t=42ms t=13.5ms -> Comb + g=0.7 g=0.7 -> Comb + +Or: + + Comb -> t=0.48ms + Comb -> g=0.7 + Comb -> AllP -> AllP -> AllP + Comb -> t=5ms t=1.68ms + g=0.7 g=0.7 + +Typical values for the comb filters are in the range g=0.6 to 0.9 +and time in the range of 30ms to 250ms. + +Feel free to deviate from this and experiment around. + +Building your own reverbs is fun! + +(And don't forget that you can create feedback +using the FbWr and FbRd nodes!) +"#; +} + +impl DspNode for AllP { + fn outputs() -> usize { 1 } + + fn set_sample_rate(&mut self, srate: f32) { + self.allpass.set_sample_rate(srate); + } + + fn reset(&mut self) { + self.allpass.reset(); + } + + #[inline] + fn process( + &mut self, ctx: &mut T, _ectx: &mut NodeExecContext, + atoms: &[SAtom], _params: &[ProcBuf], inputs: &[ProcBuf], + outputs: &mut [ProcBuf], ctx_vals: LedPhaseVals) + { + use crate::dsp::{at, out, inp, denorm}; + + let inp = inp::AllP::inp(inputs); + let time = inp::AllP::time(inputs); + let g = inp::AllP::g(inputs); + let out = out::AllP::sig(outputs); + + let ap = &mut *self.allpass; + + for frame in 0..ctx.nframes() { + let v = inp.read(frame); + + out.write(frame, + ap.next( + denorm::AllP::time(time, frame), + denorm::AllP::g(g, frame), + v)); + } + + let last_frame = ctx.nframes() - 1; + ctx_vals[0].set(out.read(last_frame)); + } +}