helpers for comb and allpass, and added allpass node
This commit is contained in:
parent
a92ad0de35
commit
85ad5e7955
3 changed files with 201 additions and 1 deletions
|
@ -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
|
||||
|
|
|
@ -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}
|
||||
|
|
110
src/dsp/node_allp.rs
Normal file
110
src/dsp/node_allp.rs
Normal file
|
@ -0,0 +1,110 @@
|
|||
// Copyright (c) 2021 Weird Constructor <weirdconstructor@gmail.com>
|
||||
// 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<AllPass>,
|
||||
}
|
||||
|
||||
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<T: NodeAudioContext>(
|
||||
&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));
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue