implemented reverse mode for sample player

This commit is contained in:
Weird Constructor 2021-06-12 09:12:25 +02:00
parent 30b67efe70
commit 4f29168a08
3 changed files with 163 additions and 14 deletions

View file

@ -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

View file

@ -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);

View file

@ -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
]);
}