diff --git a/src/dsp/dattorro.rs b/src/dsp/dattorro.rs index 257c25b..c790cb4 100644 --- a/src/dsp/dattorro.rs +++ b/src/dsp/dattorro.rs @@ -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) } } diff --git a/src/dsp/helpers.rs b/src/dsp/helpers.rs index a5db8e4..625337c 100644 --- a/src/dsp/helpers.rs +++ b/src/dsp/helpers.rs @@ -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); diff --git a/src/dsp/mod.rs b/src/dsp/mod.rs index ac25490..9c0413f 100644 --- a/src/dsp/mod.rs +++ b/src/dsp/mod.rs @@ -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} diff --git a/src/dsp/node_pverb.rs b/src/dsp/node_pverb.rs new file mode 100644 index 0000000..bc90288 --- /dev/null +++ b/src/dsp/node_pverb.rs @@ -0,0 +1,209 @@ +// 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, + 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, +} + +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( + &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)); + } +}