From 4f29168a088a09d275f37cd5cbe894f85f8a5669 Mon Sep 17 00:00:00 2001 From: Weird Constructor Date: Sat, 12 Jun 2021 09:12:25 +0200 Subject: [PATCH] implemented reverse mode for sample player --- src/dsp/mod.rs | 8 ++-- src/dsp/node_sampl.rs | 76 ++++++++++++++++++++++++++++++----- tests/node_sampl.rs | 93 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 163 insertions(+), 14 deletions(-) diff --git a/src/dsp/mod.rs b/src/dsp/mod.rs index 83c28ce..241934a 100644 --- a/src/dsp/mod.rs +++ b/src/dsp/mod.rs @@ -37,6 +37,7 @@ use crate::fa_amp_neg_att; use crate::fa_tseq_cmode; use crate::fa_sampl_dclick; use crate::fa_sampl_pmode; +use crate::fa_sampl_dir; use node_amp::Amp; use node_sin::Sin; @@ -374,9 +375,10 @@ macro_rules! node_list { (3 len n_id n_id r_id f_def stp_d 0.0, 1.0, 1.0) (4 dcms n_declick d_declick r_ms f_ms stp_m 0.0, 1.0, 3.0) (5 det n_det d_det r_det f_det stp_f -0.2, 0.2, 0.0) - {6 0 sample audio_unloaded("") f_def 0 0} - {7 1 pmode setting(0) fa_sampl_pmode 0 1} - {8 2 dclick setting(0) fa_sampl_dclick 0 1} + {6 0 sample audio_unloaded("") f_def 0 0} + {7 1 pmode setting(0) fa_sampl_pmode 0 1} + {8 2 dclick setting(0) fa_sampl_dclick 0 1} + {9 3 dir setting(0) fa_sampl_dir 0 1} [0 sig], // node_param_idx // name denorm round format steps norm norm denorm diff --git a/src/dsp/node_sampl.rs b/src/dsp/node_sampl.rs index d17a37a..fa91423 100644 --- a/src/dsp/node_sampl.rs +++ b/src/dsp/node_sampl.rs @@ -7,6 +7,17 @@ use crate::dsp::{NodeId, SAtom, ProcBuf, DspNode, LedPhaseVals}; use crate::dsp::{out, at, inp, denorm, denorm_offs}; //, inp, denorm, denorm_v, inp_dir, at}; use super::helpers::Trigger; +#[macro_export] +macro_rules! fa_sampl_dir { ($formatter: expr, $v: expr, $denorm_v: expr) => { { + let s = + match ($v.round() as usize) { + 0 => "Forward", + 1 => "Reverse", + _ => "?", + }; + write!($formatter, "{}", s) +} } } + #[macro_export] macro_rules! fa_sampl_dclick { ($formatter: expr, $v: expr, $denorm_v: expr) => { { let s = @@ -85,6 +96,9 @@ impl Sampl { "Sampl dclick\nIf this is enabled it will enable short fade in and out ramps.\n\ This if useful if you don't want to add an envelope just for \ getting rid of the clicks if spos and epos are modulated."; + pub const dir : &'static str = + "Sampl dir\nWith this you can reverse the direction of the playhead.\n\ + Or put simple: Play your sample forward or backward."; pub const sig : &'static str = "Sampl sig\nSampler audio output\nRange: (-1..1)\n"; @@ -129,6 +143,40 @@ be provided on the 'trig' input port. The 'trig' input also works in } impl Sampl { + #[allow(clippy::many_single_char_names)] + #[inline] + fn next_sample_rev(&mut self, sr_factor: f64, speed: f64, sample_data: &[f32]) -> f32 { + let sd_len = sample_data.len(); + if sd_len < 1 { return 0.0; } + + let j = self.phase.floor() as usize % sd_len; + let i = ((sd_len - 1) - j) + sd_len; + + let f = self.phase.fract(); + self.phase = j as f64 + f + sr_factor * speed; + + // Hermite interpolation, take from + // https://github.com/eric-wood/delay/blob/main/src/delay.rs#L52 + // + // Thanks go to Eric Wood! + // + // For the interpolation code: + // MIT License, Copyright (c) 2021 Eric Wood + let xm1 = sample_data[(i + 1) % sd_len]; + let x0 = sample_data[i % sd_len]; + let x1 = sample_data[(i - 1) % sd_len]; + let x2 = sample_data[(i - 2) % sd_len]; + + let c = (x1 - xm1) * 0.5; + let v = x0 - x1; + let w = c + v; + let a = w + v + (x2 - x0) * 0.5; + let b_neg = w + a; + + let f = (1.0 - f) as f32; + (((a * f) - b_neg) * f + c) * f + x0 + } + #[allow(clippy::many_single_char_names)] #[inline] fn next_sample(&mut self, sr_factor: f64, speed: f64, sample_data: &[f32]) -> f32 { @@ -136,6 +184,8 @@ impl Sampl { if sd_len < 1 { return 0.0; } let i = self.phase.floor() as usize + sd_len; + let f = self.phase.fract(); + self.phase = (i % sd_len) as f64 + f + sr_factor * speed; // Hermite interpolation, take from // https://github.com/eric-wood/delay/blob/main/src/delay.rs#L52 @@ -155,12 +205,7 @@ impl Sampl { let a = w + v + (x2 - x0) * 0.5; let b_neg = w + a; - let f = self.phase.fract(); - - self.phase = (i % sd_len) as f64 + f + sr_factor * speed; - let f = f as f32; - (((a * f) - b_neg) * f + c) * f + x0 } @@ -168,7 +213,7 @@ impl Sampl { #[inline] fn play(&mut self, inputs: &[ProcBuf], nframes: usize, sample_data: &[f32], out: &mut ProcBuf, do_loop: bool, - declick: bool) + declick: bool, reverse: bool) { let freq = inp::Sampl::freq(inputs); let trig = inp::Sampl::trig(inputs); @@ -256,10 +301,17 @@ impl Sampl { let sample_idx = self.phase.floor() as usize; let mut s = - self.next_sample( - sr_factor, - playback_speed as f64, - sample_slice); + if reverse { + self.next_sample_rev( + sr_factor, + playback_speed as f64, + sample_slice) + } else { + self.next_sample( + sr_factor, + playback_speed as f64, + sample_slice) + }; if declick { let samples_to_end = sample_slice.len() - sample_idx; @@ -324,6 +376,7 @@ impl DspNode for Sampl { let sample = at::Sampl::sample(atoms); let pmode = at::Sampl::pmode(atoms); let dclick = at::Sampl::dclick(atoms); + let dir = at::Sampl::dir(atoms); let out = out::Sampl::sig(outputs); if let SAtom::AudioSample((_, Some(sample_data))) = sample { @@ -342,7 +395,8 @@ impl DspNode for Sampl { &sample_data[..], out, pmode.i() == 0, - dclick.i() == 1); + dclick.i() == 1, + dir.i() == 1); } else { for frame in 0..ctx.nframes() { out.write(frame, 0.0); diff --git a/tests/node_sampl.rs b/tests/node_sampl.rs index 1834e4c..c8126a9 100644 --- a/tests/node_sampl.rs +++ b/tests/node_sampl.rs @@ -137,6 +137,60 @@ fn check_node_sampl_detune() { //d// save_wav("check_node_sampl_detune.wav", &out_l); } +#[test] +fn check_node_sampl_reverse() { + let (node_conf, mut node_exec) = new_node_engine(); + let mut matrix = Matrix::new(node_conf, 3, 3); + + let smpl = NodeId::Sampl(0); + let out = NodeId::Out(0); + matrix.place(0, 0, Cell::empty(smpl) + .out(None, None, smpl.out("sig"))); + matrix.place(0, 1, Cell::empty(out) + .input(out.inp("ch1"), None, None)); + matrix.sync().unwrap(); + + let sample_p = smpl.inp_param("sample").unwrap(); + let freq_p = smpl.inp_param("freq").unwrap(); + let dir_p = smpl.inp_param("dir").unwrap(); + matrix.set_param(sample_p, SAtom::audio_unloaded("tests/sample_sin.wav")); + matrix.set_param(dir_p, SAtom::setting(1)); + + let (rms, min, max) = run_and_get_l_rms_mimax(&mut node_exec, 50.0); + assert_rmsmima!((rms, min, max), (0.50059, -0.9997, 0.9997)); + + let fft = run_and_get_fft4096(&mut node_exec, 800, 20.0); + assert_eq!(fft[0], (441, 1023)); + + matrix.set_param(freq_p, SAtom::param(0.1)); + let fft = run_and_get_fft4096(&mut node_exec, 800, 20.0); + assert_eq!(fft[0], (883, 1020)); + + matrix.set_param(freq_p, SAtom::param(-0.1)); + let fft = run_and_get_fft4096(&mut node_exec, 800, 20.0); + assert_eq!(fft[0], (215, 880)); + + matrix.set_param(freq_p, SAtom::param(-0.2)); + let fft = run_and_get_fft4096(&mut node_exec, 800, 20.0); + assert_eq!(fft[0], (108, 986)); + + matrix.set_param(freq_p, SAtom::param(-0.4)); + let fft = run_and_get_fft4096(&mut node_exec, 800, 20.0); + assert_eq!(fft[0], (22, 831)); + + matrix.set_param(freq_p, SAtom::param(-0.5)); + let fft = run_and_get_fft4096(&mut node_exec, 800, 20.0); + assert_eq!(fft[0], (11, 999)); + + matrix.set_param(freq_p, SAtom::param(0.2)); + let fft = run_and_get_fft4096(&mut node_exec, 800, 20.0); + assert_eq!(fft[0], (1766, 1008)); + + matrix.set_param(freq_p, SAtom::param(0.4)); + let fft = run_and_get_fft4096(&mut node_exec, 800, 20.0); + assert_eq!(fft[0], (7052, 942)); +} + #[test] fn check_node_sampl_reload() { { @@ -534,3 +588,42 @@ fn check_node_sampl_declick_offs_len() { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 ]); } + +#[test] +fn check_node_sampl_rev_2() { + let (node_conf, mut node_exec) = new_node_engine(); + let mut matrix = Matrix::new(node_conf, 3, 3); + + let smpl = NodeId::Sampl(0); + let out = NodeId::Out(0); + matrix.place(0, 0, Cell::empty(smpl) + .out(None, None, smpl.out("sig"))); + matrix.place(0, 1, Cell::empty(out) + .input(out.inp("ch1"), None, None)); + matrix.sync().unwrap(); + + let sample_p = smpl.inp_param("sample").unwrap(); + let dir_p = smpl.inp_param("dir").unwrap(); + let pmode_p = smpl.inp_param("pmode").unwrap(); + + matrix.set_param(sample_p, create_1sec_ramp()); + // Reverse: + matrix.set_param(dir_p, SAtom::setting(1)); + // Loop mode: + matrix.set_param(pmode_p, SAtom::setting(0)); + + let res = run_for_ms(&mut node_exec, 1000.0); + assert_decimated_feq!(res.0, 5000, vec![ + 0.9999773, 0.886596, 0.77321476, 0.6598335, 0.5464522, + 0.43307102, 0.31968975, 0.20630851, 0.09292727 + ]); + + // Forward: + matrix.set_param(dir_p, SAtom::setting(0)); + + let res = run_for_ms(&mut node_exec, 1000.0); + assert_decimated_feq!(res.0, 5000, vec![ + 0.0, 0.11338125, 0.2267625, 0.34014374, 0.453525, 0.5669062, + 0.6802875, 0.79366875, 0.90705 + ]); +}