Finished reverb translation and added as a node, but there are still bugs to fix.

This commit is contained in:
Weird Constructor 2021-08-07 16:56:59 +02:00
parent 43f1f6d7b9
commit e45b8a5ebd
4 changed files with 336 additions and 9 deletions

View file

@ -39,7 +39,7 @@ const DAT_LEFT_DELAY2_TIME_MS : f32 = 3720.0 / DAT_SAMPLES_PER_MS;
const DAT_RIGHT_DELAY1_TIME_MS : f32 = 4217.0 / DAT_SAMPLES_PER_MS;
const DAT_RIGHT_DELAY2_TIME_MS : f32 = 3163.0 / DAT_SAMPLES_PER_MS;
const DAT_TAPS_TIME_MS : [f32; 7] = [
const DAT_LEFT_TAPS_TIME_MS : [f32; 7] = [
266.0 / DAT_SAMPLES_PER_MS,
2974.0 / DAT_SAMPLES_PER_MS,
1913.0 / DAT_SAMPLES_PER_MS,
@ -49,6 +49,16 @@ const DAT_TAPS_TIME_MS : [f32; 7] = [
1066.0 / DAT_SAMPLES_PER_MS,
];
const DAT_RIGHT_TAPS_TIME_MS : [f32; 7] = [
353.0 / DAT_SAMPLES_PER_MS,
3627.0 / DAT_SAMPLES_PER_MS,
1228.0 / DAT_SAMPLES_PER_MS,
2673.0 / DAT_SAMPLES_PER_MS,
2111.0 / DAT_SAMPLES_PER_MS,
335.0 / DAT_SAMPLES_PER_MS,
121.0 / DAT_SAMPLES_PER_MS,
];
const DAT_LFO_FREQS_HZ : [f32; 4] = [ 0.1, 0.15, 0.12, 0.18 ];
const DAT_INPUT_DIFFUSION1 : f32 = 0.75;
@ -68,6 +78,7 @@ use crate::dsp::helpers::{
DCBlockFilter
};
#[derive(Debug, Clone)]
pub struct DattorroReverb {
last_scale: f32,
@ -97,8 +108,8 @@ pub trait DattorroReverbParams {
/// Time for the pre-delay of the reverb. Any sensible `ms` that fits
/// into a delay buffer of 5 seconds.
fn pre_delay_time_ms(&self) -> f32;
/// The size of the reverb, values go from 0.0025 to 4.0
fn time_scale(&self) -> f32;
/// The size of the reverb, values go from 0.0 to 1.0.
fn time_scale(&self) -> f32;
/// High-pass input filter cutoff freq in Hz, range: 0.0 to 22000.0
fn input_high_cutoff_hz(&self) -> f32;
/// Low-pass input filter cutoff freq in Hz, range: 0.0 to 22000.0
@ -118,6 +129,8 @@ pub trait DattorroReverbParams {
fn input_diffusion_mix(&self) -> f32;
/// The amount of plate diffusion going on, range: 0.0 to 1.0
fn diffusion(&self) -> f32;
/// Internal tank decay time, range: 0.0 to 1.0
fn decay(&self) -> f32;
}
impl DattorroReverb {
@ -313,21 +326,26 @@ impl DattorroReverb {
) -> (f32, f32)
{
// Some parameter setup...
self.set_time_scale(params.time_scale());
let timescale = 0.0025 + (4.0 - 0.0025) * params.time_scale();
self.set_time_scale(timescale);
self.hpf[0].set_freq(params.reverb_high_cutoff_hz());
self.hpf[1].set_freq(params.reverb_high_cutoff_hz());
self.lpf[0].set_freq(params.reverb_low_cutoff_hz());
self.lpf[1].set_freq(params.reverb_low_cutoff_hz());
let mod_speed = params.mod_speed();
let mod_speed = mod_speed * mod_speed;
let mod_speed = mod_speed * 99.0 + 1.0;
self.lfos[0].set(
DAT_LFO_FREQS_HZ[0] * params.mod_speed(), params.mod_shape());
DAT_LFO_FREQS_HZ[0] * mod_speed, params.mod_shape());
self.lfos[1].set(
DAT_LFO_FREQS_HZ[1] * params.mod_speed(), params.mod_shape());
DAT_LFO_FREQS_HZ[1] * mod_speed, params.mod_shape());
self.lfos[2].set(
DAT_LFO_FREQS_HZ[2] * params.mod_speed(), params.mod_shape());
DAT_LFO_FREQS_HZ[2] * mod_speed, params.mod_shape());
self.lfos[3].set(
DAT_LFO_FREQS_HZ[3] * params.mod_speed(), params.mod_shape());
DAT_LFO_FREQS_HZ[3] * mod_speed, params.mod_shape());
self.apf1[0].2 = -DAT_PLATE_DIFFUSION1 * params.diffusion();
self.apf1[1].2 = -DAT_PLATE_DIFFUSION1 * params.diffusion();
@ -338,6 +356,8 @@ impl DattorroReverb {
left_apf2_delay_ms, right_apf2_delay_ms)
= self.calc_apf_delay_times(params);
// Parameter setup done!
// Input into their corresponding DC blockers
let input_r = self.inp_dc_block[0].next(input_r);
let input_l = self.inp_dc_block[1].next(input_l);
@ -367,6 +387,58 @@ impl DattorroReverb {
self.left_sum += tank_feed;
self.right_sum += tank_feed;
(0.0, 0.0)
// Calculate tank decay of the left/right signal channels.
let decay = 1.0 - params.decay().clamp(0.1, 0.9999);
let decay = 1.0 - (decay * decay);
// Left Sum => APF1 => Delay1 => LPF => HPF => APF2 => Delay2
// And then send this over to the right sum.
let left = self.left_sum;
let left = self.apf1[0].0.next(left_apf1_delay_ms, self.apf1[0].2, left);
let left_apf_tap = left;
let left = self.delay1[0].0.next_cubic(self.delay1[0].1, left);
let left = self.lpf[0].process(left);
let left = self.hpf[0].process(left);
let left = left * decay;
let left = self.apf2[0].0.next(left_apf2_delay_ms, self.apf2[0].2, left);
let left = self.delay2[0].0.next_cubic(self.delay2[0].1, left);
// Right Sum => APF1 => Delay1 => LPF => HPF => APF2 => Delay2
// And then send this over to the left sum.
let right = self.right_sum;
let right = self.apf1[1].0.next(right_apf1_delay_ms, self.apf1[1].2, right);
let right_apf_tap = right;
let right = self.delay1[1].0.next_cubic(self.delay1[1].1, right);
let right = self.lpf[1].process(right);
let right = self.hpf[1].process(right);
let right = right * decay;
let right = self.apf2[1].0.next(right_apf2_delay_ms, self.apf2[1].2, right);
let right = self.delay2[1].0.next_cubic(self.delay2[1].1, right);
self.right_sum = left * decay;
self.left_sum = right * decay;
let mut left_accum = left_apf_tap;
left_accum += self.delay1[0].0.tap( DAT_LEFT_TAPS_TIME_MS[0]);
left_accum += self.delay1[0].0.tap( DAT_LEFT_TAPS_TIME_MS[1]);
left_accum -= self.apf2[0].0.delay_tap(DAT_LEFT_TAPS_TIME_MS[2]);
left_accum += self.delay2[0].0.tap( DAT_LEFT_TAPS_TIME_MS[3]);
left_accum -= self.delay1[1].0.tap( DAT_LEFT_TAPS_TIME_MS[4]);
left_accum -= self.apf2[1].0.delay_tap(DAT_LEFT_TAPS_TIME_MS[5]);
left_accum -= self.delay2[1].0.tap( DAT_LEFT_TAPS_TIME_MS[6]);
let mut right_accum = right_apf_tap;
right_accum += self.delay1[1].0.tap( DAT_RIGHT_TAPS_TIME_MS[0]);
right_accum += self.delay1[1].0.tap( DAT_RIGHT_TAPS_TIME_MS[1]);
right_accum -= self.apf2[1].0.delay_tap(DAT_RIGHT_TAPS_TIME_MS[2]);
right_accum += self.delay2[1].0.tap( DAT_RIGHT_TAPS_TIME_MS[3]);
right_accum -= self.delay1[0].0.tap( DAT_RIGHT_TAPS_TIME_MS[4]);
right_accum -= self.apf2[0].0.delay_tap(DAT_RIGHT_TAPS_TIME_MS[5]);
right_accum -= self.delay2[0].0.tap( DAT_RIGHT_TAPS_TIME_MS[6]);
let left_out = self.out_dc_block[0].next(left_accum);
let right_out = self.out_dc_block[1].next(right_accum);
(left_out * 0.5, right_out * 0.5)
}
}

View file

@ -740,6 +740,21 @@ impl DelayBuffer {
self.wr = (self.wr + 1) % self.data.len();
}
/// Combines [DelayBuffer::cubic_interpolate_at] and [DelayBuffer::feed]
/// into one convenient function.
#[inline]
pub fn next_cubic(&mut self, delay_time_ms: f32, input: f32) -> f32 {
let res = self.cubic_interpolate_at(delay_time_ms);
self.feed(input);
res
}
/// Shorthand for [DelayBuffer::cubic_interpolate_at].
#[inline]
pub fn tap(&self, delay_time_ms: f32) -> f32 {
self.cubic_interpolate_at(delay_time_ms)
}
/// Fetch a sample from the delay buffer at the given time.
///
/// * `delay_time_ms` - Delay time in milliseconds.
@ -814,6 +829,11 @@ impl AllPass {
self.delay.reset();
}
#[inline]
pub fn delay_tap(&self, time: f32) -> f32 {
self.delay.cubic_interpolate_at(time)
}
#[inline]
pub fn next(&mut self, time: f32, g: f32, v: f32) -> f32 {
let s = self.delay.cubic_interpolate_at(time);
@ -842,6 +862,11 @@ impl Comb {
self.delay.reset();
}
#[inline]
pub fn delay_tap(&self, time: f32) -> f32 {
self.delay.cubic_interpolate_at(time)
}
#[inline]
pub fn next_feedback(&mut self, time: f32, g: f32, v: f32) -> f32 {
let s = self.delay.cubic_interpolate_at(time);

View file

@ -42,6 +42,8 @@ mod node_biqfilt;
mod node_comb;
#[allow(non_upper_case_globals)]
mod node_tslfo;
#[allow(non_upper_case_globals)]
mod node_pverb;
pub mod biquad;
pub mod tracker;
@ -101,6 +103,7 @@ use node_vosc::VOsc;
use node_biqfilt::BiqFilt;
use node_comb::Comb;
use node_tslfo::TsLfo;
use node_pverb::PVerb;
pub const MIDI_MAX_FREQ : f32 = 13289.75;
@ -770,6 +773,24 @@ macro_rules! node_list {
{4 0 ftype setting(0) fa_biqfilt_type 0 1}
{5 1 order setting(0) fa_biqfilt_ord 0 3}
[0 sig],
pverb => PVerb UIType::Generic UICategory::Signal
( 0 in_l n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0)
( 1 in_r n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0)
( 2 predly n_time d_time r_tms f_ms stp_m 0.0, 1.0, 0.0)
( 3 size n_id d_id r_id f_def stp_d 0.0, 1.0, 0.5)
( 4 dcy n_id d_id r_id f_def stp_d 0.0, 1.0, 0.25)
( 5 ilpf n_pit d_pit r_fq f_freq stp_d -1.0, 0.5647131, 22050.0)
( 6 ihpf n_pit d_pit r_fq f_freq stp_d -1.0, 0.5647131, 0.0)
( 7 idif n_id d_id r_id f_def stp_d 0.0, 1.0, 1.0)
( 8 dmix n_id d_id r_id f_def stp_d 0.0, 1.0, 1.0)
( 9 mspeed n_id d_id r_id f_def stp_d 0.0, 1.0, 0.0)
(10 mshp n_id d_id r_id f_def stp_d 0.0, 1.0, 1.0)
(11 mdepth n_id d_id r_id f_def stp_d 0.0, 1.0, 1.0)
(12 rlpf n_pit d_pit r_fq f_freq stp_d -1.0, 0.5647131, 22050.0)
(13 rhpf n_pit d_pit r_fq f_freq stp_d -1.0, 0.5647131, 0.0)
(14 mix n_id d_id r_id f_def stp_d 0.0, 1.0, 0.5)
[0 sig_l]
[1 sig_r],
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}

209
src/dsp/node_pverb.rs Normal file
View file

@ -0,0 +1,209 @@
// Copyright (c) 2021 Weird Constructor <weirdconstructor@gmail.com>
// 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,
GraphAtomData, GraphFun, NodeContext,
denorm
};
use super::helpers::crossfade;
use super::dattorro::{
DattorroReverb,
DattorroReverbParams
};
pub struct DatParams {
frame: usize,
predly: ProcBuf,
size: ProcBuf,
dcy: ProcBuf,
ilpf: ProcBuf,
ihpf: ProcBuf,
idif: ProcBuf,
dmix: ProcBuf,
mspeed: ProcBuf,
mshp: ProcBuf,
mdepth: ProcBuf,
rlpf: ProcBuf,
rhpf: ProcBuf,
}
impl DatParams {
#[inline]
pub fn set_frame(&mut self, frame: usize) { self.frame = frame; }
}
impl DattorroReverbParams for DatParams {
fn pre_delay_time_ms(&self) -> f32 {
denorm::PVerb::predly(&self.predly, self.frame)
}
fn time_scale(&self) -> f32 {
denorm::PVerb::size(&self.size, self.frame)
}
fn decay(&self) -> f32 {
denorm::PVerb::dcy(&self.dcy, self.frame)
}
fn input_low_cutoff_hz(&self) -> f32 {
denorm::PVerb::ilpf(&self.ilpf, self.frame)
}
fn input_high_cutoff_hz(&self) -> f32 {
denorm::PVerb::ihpf(&self.ihpf, self.frame)
}
fn diffusion(&self) -> f32 {
denorm::PVerb::idif(&self.idif, self.frame)
}
fn input_diffusion_mix(&self) -> f32 {
denorm::PVerb::dmix(&self.dmix, self.frame)
}
fn mod_speed(&self) -> f32 {
denorm::PVerb::mspeed(&self.mspeed, self.frame)
}
fn mod_depth(&self) -> f32 {
denorm::PVerb::mdepth(&self.mdepth, self.frame)
}
fn mod_shape(&self) -> f32 {
denorm::PVerb::mshp(&self.mshp, self.frame)
}
fn reverb_low_cutoff_hz(&self) -> f32 {
denorm::PVerb::rlpf(&self.rlpf, self.frame)
}
fn reverb_high_cutoff_hz(&self) -> f32 {
denorm::PVerb::rhpf(&self.rhpf, self.frame)
}
}
#[derive(Debug, Clone)]
pub struct PVerb {
verb: Box<DattorroReverb>,
}
impl PVerb {
pub fn new(_nid: &NodeId) -> Self {
Self {
verb: Box::new(DattorroReverb::new()),
}
}
pub const in_l : &'static str =
"PVerb in_l\n\nRange: (-1..1)\n";
pub const in_r : &'static str =
"PVerb in_r\n\nRange: (-1..1)\n";
pub const sig_l : &'static str =
"PVerb sig_l\n\nRange: (0..1)";
pub const sig_r : &'static str =
"PVerb sig_r\n\nRange: (0..1)";
pub const predly : &'static str =
"PVerb predly\n\nRange: (0..1)";
pub const size : &'static str =
"PVerb size\n\nRange: (0..1)";
pub const dcy : &'static str =
"PVerb dcy\n\nRange: (0..1)";
pub const ilpf : &'static str =
"PVerb ilpf\n\nRange: (0..1)";
pub const ihpf : &'static str =
"PVerb ihpf\n\nRange: (0..1)";
pub const idif : &'static str =
"PVerb idif\n\nRange: (0..1)";
pub const dmix : &'static str =
"PVerb dmix\n\nRange: (0..1)";
pub const mspeed : &'static str =
"PVerb mspeed\n\nRange: (0..1)";
pub const mshp : &'static str =
"PVerb mshp\n\nRange: (0..1)";
pub const mdepth : &'static str =
"PVerb mdepth\n\nRange: (0..1)";
pub const rlpf : &'static str =
"PVerb rlpf\n\nRange: (0..1)";
pub const rhpf : &'static str =
"PVerb rhpf\n\nRange: (0..1)";
pub const mix : &'static str =
"PVerb mix\n\nRange: (0..1)";
pub const DESC : &'static str =
r#"Plate Reverb
This is a simple but yet powerful small plate reverb based on the design by Jon Dattorro. It should suit your needs from small rooms up to large athmospheric sound scapes.
"#;
pub const HELP : &'static str =
r#"PVerb - Plate Reverb (by Jon Dattorro)
This is a simple but yet powerful small plate reverb based on the design
by Jon Dattorro. It should suit your needs from small rooms up to large
athmospheric sound scapes. It provides two inputs, and two outputs for
stereo signals. You can also feed a monophonic input, and you will get
a stereo output.
It provides simple low-pass and high-pass filters for the inputs
and another set of them for the internal reverberation tank to control
the bandwidth of the reverbs.
Internal modulation keeps the sound alive and spreads it even more.
"#;
}
impl DspNode for PVerb {
fn outputs() -> usize { 1 }
fn set_sample_rate(&mut self, srate: f32) {
self.verb.set_sample_rate(srate);
}
fn reset(&mut self) {
self.verb.reset();
}
#[inline]
fn process<T: NodeAudioContext>(
&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, out_idx};
let in_l = inp::PVerb::in_l(inputs);
let in_r = inp::PVerb::in_r(inputs);
let mut params = DatParams {
frame: 0,
predly: *inp::PVerb::predly(inputs),
size: *inp::PVerb::size(inputs),
dcy: *inp::PVerb::dcy(inputs),
ilpf: *inp::PVerb::ilpf(inputs),
ihpf: *inp::PVerb::ihpf(inputs),
idif: *inp::PVerb::idif(inputs),
dmix: *inp::PVerb::dmix(inputs),
mspeed: *inp::PVerb::mspeed(inputs),
mshp: *inp::PVerb::mshp(inputs),
mdepth: *inp::PVerb::mdepth(inputs),
rlpf: *inp::PVerb::rlpf(inputs),
rhpf: *inp::PVerb::rhpf(inputs),
};
let mix = inp::PVerb::mix(inputs);
// let out_l = out::PVerb::sig_l(outputs);
// let out_r = out::PVerb::sig_r(outputs);
let out_i = out_idx::PVerb::sig_r();
let (out_l, out_r) = outputs.split_at_mut(out_i);
let out_l = &mut out_l[0];
let out_r = &mut out_r[0];
let mut verb = &mut *self.verb;
for frame in 0..ctx.nframes() {
let (i_l, i_r) = (in_l.read(frame), in_r.read(frame));
params.set_frame(frame);
let (l, r) = verb.process(&mut params, i_l, i_r);
out_l.write(frame, crossfade(i_l, l, denorm::PVerb::mix(mix, frame)));
out_r.write(frame, crossfade(i_r, r, denorm::PVerb::mix(mix, frame)));
}
ctx_vals[0].set(
out_l.read(ctx.nframes() - 1)
+ out_r.read(ctx.nframes() - 1));
}
}