implemented reverse mode for sample player
This commit is contained in:
parent
30b67efe70
commit
4f29168a08
3 changed files with 163 additions and 14 deletions
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
]);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue