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_tseq_cmode;
use crate::fa_sampl_dclick; use crate::fa_sampl_dclick;
use crate::fa_sampl_pmode; use crate::fa_sampl_pmode;
use crate::fa_sampl_dir;
use node_amp::Amp; use node_amp::Amp;
use node_sin::Sin; use node_sin::Sin;
@ -377,6 +378,7 @@ macro_rules! node_list {
{6 0 sample audio_unloaded("") f_def 0 0} {6 0 sample audio_unloaded("") f_def 0 0}
{7 1 pmode setting(0) fa_sampl_pmode 0 1} {7 1 pmode setting(0) fa_sampl_pmode 0 1}
{8 2 dclick setting(0) fa_sampl_dclick 0 1} {8 2 dclick setting(0) fa_sampl_dclick 0 1}
{9 3 dir setting(0) fa_sampl_dir 0 1}
[0 sig], [0 sig],
// node_param_idx // node_param_idx
// name denorm round format steps norm norm denorm // 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 crate::dsp::{out, at, inp, denorm, denorm_offs}; //, inp, denorm, denorm_v, inp_dir, at};
use super::helpers::Trigger; 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_export]
macro_rules! fa_sampl_dclick { ($formatter: expr, $v: expr, $denorm_v: expr) => { { macro_rules! fa_sampl_dclick { ($formatter: expr, $v: expr, $denorm_v: expr) => { {
let s = let s =
@ -85,6 +96,9 @@ impl Sampl {
"Sampl dclick\nIf this is enabled it will enable short fade in and out ramps.\n\ "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 \ 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."; 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 = pub const sig : &'static str =
"Sampl sig\nSampler audio output\nRange: (-1..1)\n"; "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 { 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)] #[allow(clippy::many_single_char_names)]
#[inline] #[inline]
fn next_sample(&mut self, sr_factor: f64, speed: f64, sample_data: &[f32]) -> f32 { 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; } if sd_len < 1 { return 0.0; }
let i = self.phase.floor() as usize + sd_len; 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 // Hermite interpolation, take from
// https://github.com/eric-wood/delay/blob/main/src/delay.rs#L52 // 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 a = w + v + (x2 - x0) * 0.5;
let b_neg = w + a; 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; let f = f as f32;
(((a * f) - b_neg) * f + c) * f + x0 (((a * f) - b_neg) * f + c) * f + x0
} }
@ -168,7 +213,7 @@ impl Sampl {
#[inline] #[inline]
fn play(&mut self, inputs: &[ProcBuf], nframes: usize, fn play(&mut self, inputs: &[ProcBuf], nframes: usize,
sample_data: &[f32], out: &mut ProcBuf, do_loop: bool, sample_data: &[f32], out: &mut ProcBuf, do_loop: bool,
declick: bool) declick: bool, reverse: bool)
{ {
let freq = inp::Sampl::freq(inputs); let freq = inp::Sampl::freq(inputs);
let trig = inp::Sampl::trig(inputs); let trig = inp::Sampl::trig(inputs);
@ -256,10 +301,17 @@ impl Sampl {
let sample_idx = self.phase.floor() as usize; let sample_idx = self.phase.floor() as usize;
let mut s = let mut s =
if reverse {
self.next_sample_rev(
sr_factor,
playback_speed as f64,
sample_slice)
} else {
self.next_sample( self.next_sample(
sr_factor, sr_factor,
playback_speed as f64, playback_speed as f64,
sample_slice); sample_slice)
};
if declick { if declick {
let samples_to_end = sample_slice.len() - sample_idx; let samples_to_end = sample_slice.len() - sample_idx;
@ -324,6 +376,7 @@ impl DspNode for Sampl {
let sample = at::Sampl::sample(atoms); let sample = at::Sampl::sample(atoms);
let pmode = at::Sampl::pmode(atoms); let pmode = at::Sampl::pmode(atoms);
let dclick = at::Sampl::dclick(atoms); let dclick = at::Sampl::dclick(atoms);
let dir = at::Sampl::dir(atoms);
let out = out::Sampl::sig(outputs); let out = out::Sampl::sig(outputs);
if let SAtom::AudioSample((_, Some(sample_data))) = sample { if let SAtom::AudioSample((_, Some(sample_data))) = sample {
@ -342,7 +395,8 @@ impl DspNode for Sampl {
&sample_data[..], &sample_data[..],
out, out,
pmode.i() == 0, pmode.i() == 0,
dclick.i() == 1); dclick.i() == 1,
dir.i() == 1);
} else { } else {
for frame in 0..ctx.nframes() { for frame in 0..ctx.nframes() {
out.write(frame, 0.0); 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); //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] #[test]
fn check_node_sampl_reload() { 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 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
]);
}