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_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;
|
||||||
|
@ -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)
|
(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)
|
(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)
|
(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}
|
{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
|
||||||
|
|
|
@ -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 =
|
||||||
self.next_sample(
|
if reverse {
|
||||||
sr_factor,
|
self.next_sample_rev(
|
||||||
playback_speed as f64,
|
sr_factor,
|
||||||
sample_slice);
|
playback_speed as f64,
|
||||||
|
sample_slice)
|
||||||
|
} else {
|
||||||
|
self.next_sample(
|
||||||
|
sr_factor,
|
||||||
|
playback_speed as f64,
|
||||||
|
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);
|
||||||
|
|
|
@ -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
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue