From aff9984f08a041b19599d0dfcd8a27768e17ef55 Mon Sep 17 00:00:00 2001 From: Weird Constructor Date: Fri, 28 May 2021 21:37:16 +0200 Subject: [PATCH] wrote sampler one shot mode and tests for this --- src/dsp/helpers.rs | 35 ++++++++ src/dsp/node_sampl.rs | 200 +++++++++++++++++++++++++----------------- tests/basics.rs | 132 +++++++++++++++++++++++++++- 3 files changed, 281 insertions(+), 86 deletions(-) diff --git a/src/dsp/helpers.rs b/src/dsp/helpers.rs index 6b42393..e587d29 100644 --- a/src/dsp/helpers.rs +++ b/src/dsp/helpers.rs @@ -346,6 +346,41 @@ pub fn sqrt4_to_pow4(x: f32, v: f32) -> f32 { } } +#[derive(Debug, Clone, Copy)] +pub struct Trigger { + triggered: bool, +} + +impl Trigger { + pub fn new() -> Self { + Self { + triggered: false, + } + } + + #[inline] + pub fn reset(&mut self) { + self.triggered = false; + } + + #[inline] + pub fn check_trigger(&mut self, input: f32) -> bool { + if self.triggered { + if input <= 0.25 { + self.triggered = false; + } + false + } else { + if input > 0.75 { + self.triggered = true; + true + } else { + false + } + } + } +} + #[derive(Debug, Clone, Copy)] pub struct TriggerClock { clock_phase: f64, diff --git a/src/dsp/node_sampl.rs b/src/dsp/node_sampl.rs index 2401d16..6963028 100644 --- a/src/dsp/node_sampl.rs +++ b/src/dsp/node_sampl.rs @@ -4,19 +4,25 @@ use crate::nodes::NodeAudioContext; use crate::dsp::{SAtom, ProcBuf, DspNode, LedPhaseVals}; +use crate::dsp::{out, at, inp, denorm}; //, inp, denorm, denorm_v, inp_dir, at}; +use super::helpers::Trigger; /// A simple amplifier #[derive(Debug, Clone)] pub struct Sampl { - phase: f64, - srate: f64, + phase: f64, + srate: f64, + trig: Trigger, + is_playing: bool, } impl Sampl { pub fn new() -> Self { Self { - phase: 0.0, - srate: 44100.0, + phase: 0.0, + srate: 44100.0, + trig: Trigger::new(), + is_playing: false, } } pub const freq : &'static str = @@ -49,62 +55,126 @@ impl Sampl { "Sampl sig\nSampler audio output\nRange: (-1..1)\n"; } +impl Sampl { + #[inline] + fn next_sample(&mut self, sr_factor: f64, speed: f64, sample_data: &[f32]) -> f32 { + let sd_len = sample_data.len(); + + let i = self.phase.floor() as usize + sd_len; + + // 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 = 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 + } + + #[inline] + fn play_loop(&mut self, inputs: &[ProcBuf], nframes: usize, sample_data: &[f32], out: &mut ProcBuf) { + let freq = inp::Sampl::freq(inputs); + + let sample_srate = sample_data[0] as f64; + let sample_data = &sample_data[1..]; + let sr_factor = sample_srate / self.srate; + + for frame in 0..nframes { + let playback_speed = + denorm::Sampl::freq(freq, frame) / 440.0; + + out.write(frame, + self.next_sample( + sr_factor, playback_speed as f64, sample_data)); + } + } + + #[inline] + fn play_oneshot(&mut self, inputs: &[ProcBuf], nframes: usize, + sample_data: &[f32], out: &mut ProcBuf) + { + let freq = inp::Sampl::freq(inputs); + let trig = inp::Sampl::trig(inputs); + + let sample_srate = sample_data[0] as f64; + let sample_data = &sample_data[1..]; + let sr_factor = sample_srate / self.srate; + + let mut is_playing = self.is_playing; + + for frame in 0..nframes { + let trig_val = denorm::Sampl::trig(trig, frame); + let triggered = self.trig.check_trigger(trig_val); + + if triggered { + self.phase = 0.0; + is_playing = true; + } + + if is_playing { + let playback_speed = + denorm::Sampl::freq(freq, frame) / 440.0; + + let prev_phase = self.phase; + + let s = self.next_sample( + sr_factor, playback_speed as f64, sample_data); + out.write(frame, s); + + // played past end => stop playing. + if prev_phase > self.phase { + is_playing = false; + } + } else { + out.write(frame, 0.0); + } + } + + self.is_playing = is_playing; + } + +} + impl DspNode for Sampl { fn outputs() -> usize { 1 } fn set_sample_rate(&mut self, srate: f32) { self.srate = srate.into(); } - fn reset(&mut self) { } + fn reset(&mut self) { + self.trig.reset(); + } #[inline] fn process( &mut self, ctx: &mut T, atoms: &[SAtom], _params: &[ProcBuf], inputs: &[ProcBuf], outputs: &mut [ProcBuf], ctx_vals: LedPhaseVals) { - use crate::dsp::{out, at, inp, denorm}; //, inp, denorm, denorm_v, inp_dir, at}; - let sample = at::Sampl::sample(atoms); - let freq = inp::Sampl::freq(inputs); + let pmode = at::Sampl::pmode(atoms); let out = out::Sampl::sig(outputs); if let SAtom::AudioSample((_, Some(sample_data))) = sample { - let sd_len = sample_data.len() - 1; - let sd_len_f = sd_len as f64; - - let sample_srate = sample_data[0] as f64; - let sample_data = &sample_data[1..]; - - let sr_factor = sample_srate / self.srate; - - for frame in 0..ctx.nframes() { - let playback_speed = - denorm::Sampl::freq(freq, frame) / 440.0; - - let i = self.phase.floor() as usize + sd_len; - - // 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 = self.phase.fract() as f32; - - let out_sample = (((a * f) - b_neg) * f + c) * f + x0; - out.write(frame, out_sample); - - self.phase += sr_factor * playback_speed as f64; + if pmode.i() == 0 { + self.play_loop(inputs, ctx.nframes(), &sample_data[..], out); + } else { + self.play_oneshot(inputs, ctx.nframes(), &sample_data[..], out); } } else { for frame in 0..ctx.nframes() { @@ -112,41 +182,7 @@ impl DspNode for Sampl { } } - ctx_vals[0].set(1.0); -// let neg = at::Amp::neg_att(atoms); -// -// let last_frame = ctx.nframes() - 1; -// -// let last_val = -// if neg.i() > 0 { -// for frame in 0..ctx.nframes() { -// out.write(frame, -// inp.read(frame) -// * denorm_v::Amp::att( -// inp_dir::Amp::att(att, frame) -// .max(0.0)) -// * denorm::Amp::gain(gain, frame)); -// } -// -// inp.read(last_frame) -// * denorm_v::Amp::att( -// inp_dir::Amp::att(att, last_frame) -// .max(0.0)) -// * denorm::Amp::gain(gain, last_frame) -// -// } else { -// for frame in 0..ctx.nframes() { -// out.write(frame, -// inp.read(frame) -// * denorm_v::Amp::att( -// inp_dir::Amp::att(att, frame).abs()) -// * denorm::Amp::gain(gain, frame)); -// } -// -// inp.read(last_frame) -// * denorm_v::Amp::att( -// inp_dir::Amp::att(att, last_frame).abs()) -// * denorm::Amp::gain(gain, last_frame) -// }; + let last_frame = ctx.nframes() - 1; + ctx_vals[0].set(out.read(last_frame)); } } diff --git a/tests/basics.rs b/tests/basics.rs index 0f88315..ba6fca4 100644 --- a/tests/basics.rs +++ b/tests/basics.rs @@ -90,6 +90,23 @@ fn run_and_undersample( out_samples } +fn run_and_get_each_rms_mimax( + node_exec: &mut hexodsp::nodes::NodeExecutor, + len_ms: f32) -> Vec<(f32, f32, f32)> +{ + let (out_l, _out_r) = run_no_input(node_exec, (len_ms * 3.0) / 1000.0); + calc_rms_mimax_each_ms(&out_l[..], len_ms) +} + +fn run_and_get_first_rms_mimax( + node_exec: &mut hexodsp::nodes::NodeExecutor, + len_ms: f32) -> (f32, f32, f32) +{ + let (out_l, _out_r) = run_no_input(node_exec, (len_ms * 3.0) / 1000.0); + let rms_mimax = calc_rms_mimax_each_ms(&out_l[..], len_ms); + rms_mimax[0] +} + fn run_and_get_l_rms_mimax( node_exec: &mut hexodsp::nodes::NodeExecutor, len_ms: f32) -> (f32, f32, f32) @@ -1163,7 +1180,7 @@ fn check_node_sampl_1() { #[test] fn check_node_sampl_reload() { { - let (node_conf, mut node_exec) = new_node_engine(); + let (node_conf, _node_exec) = new_node_engine(); let mut matrix = Matrix::new(node_conf, 3, 3); let smpl = NodeId::Sampl(0); @@ -1175,7 +1192,6 @@ fn check_node_sampl_reload() { matrix.sync().unwrap(); let sample_p = smpl.inp_param("sample").unwrap(); - let freq_p = smpl.inp_param("freq").unwrap(); matrix.set_param(sample_p, SAtom::audio_unloaded("tests/sample_sin.wav")); hexodsp::save_patch_to_file(&mut matrix, "check_matrix_serialize.hxy") @@ -1214,7 +1230,6 @@ fn check_node_sampl_load_err() { matrix.sync().unwrap(); let sample_p = smpl.inp_param("sample").unwrap(); - let freq_p = smpl.inp_param("freq").unwrap(); matrix.set_param(sample_p, SAtom::audio_unloaded("tests/sample_NOSIN.wav")); let (rms, min, max) = run_and_get_l_rms_mimax(&mut node_exec, 50.0); @@ -1223,5 +1238,114 @@ fn check_node_sampl_load_err() { assert_float_eq!(max, 0.0); let err = matrix.pop_error(); - assert_eq!(err.unwrap(), "Couldn't load sample 'tests/sample_NOSIN.wav': LoadError(IoError(Os { code: 2, kind: NotFound, message: \"No such file or directory\" }))"); + assert_eq!(err.unwrap(), "Sample Loading Error\nCouldn't load sample 'tests/sample_NOSIN.wav':\nLoadError(IoError(Os { code: 2, kind: NotFound, message: \"No such file or directory\" }))"); } + +#[test] +fn check_node_sampl_trigger() { + 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 pmode_p = smpl.inp_param("pmode").unwrap(); + let trig_p = smpl.inp_param("trig").unwrap(); + matrix.set_param(sample_p, SAtom::audio_unloaded("tests/sample_sin.wav")); + matrix.set_param(pmode_p, SAtom::setting(1)); + + let (rms, min, max) = run_and_get_l_rms_mimax(&mut node_exec, 10.0); + assert_float_eq!(rms, 0.0); + assert_float_eq!(min, 0.0); + assert_float_eq!(max, 0.0); + + matrix.set_param(trig_p, (1.0).into()); + let (rms, min, max) = run_and_get_first_rms_mimax(&mut node_exec, 10.0); + assert_float_eq!(rms, 0.1136); + assert_float_eq!(min, -0.9998); + assert_float_eq!(max, 1.0); + + let (rms, min, max) = run_and_get_l_rms_mimax(&mut node_exec, 20.0); + assert_float_eq!(rms, 0.0); + assert_float_eq!(min, -0.0); + assert_float_eq!(max, 0.0); +} + +#[test] +fn check_node_sampl_trigger_reset_phase() { + 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 pmode_p = smpl.inp_param("pmode").unwrap(); + let trig_p = smpl.inp_param("trig").unwrap(); + + let mut test_sample_ramp = vec![0.0; 44101]; + test_sample_ramp[0] = 44100.0; + for i in 0..(test_sample_ramp.len() - 1) { + test_sample_ramp[i + 1] = + (i as f32) / ((test_sample_ramp.len() - 2) as f32) + } + + matrix.set_param(sample_p, + SAtom::audio( + "1second_ramp.wav", + std::sync::Arc::new(test_sample_ramp))); + matrix.set_param(pmode_p, SAtom::setting(1)); + + let (rms, min, max) = run_and_get_l_rms_mimax(&mut node_exec, 10.0); + assert_float_eq!(rms, 0.0); + assert_float_eq!(min, 0.0); + assert_float_eq!(max, 0.0); + + matrix.set_param(trig_p, (1.0).into()); + let rmsvec = run_and_get_each_rms_mimax(&mut node_exec, 100.0); + let (_rms, min, max) = rmsvec[0]; + assert_float_eq!(min, 0.0); + assert_float_eq!(max, 0.092496); + let (_rms, min, max) = rmsvec[2]; + assert_float_eq!(min, 0.19252); + assert_float_eq!(max, 0.29250); + + // lower trigger level, for retrigger later + matrix.set_param(trig_p, (0.0).into()); + let rmsvec = run_and_get_each_rms_mimax(&mut node_exec, 10.0); + + let (_rms, min, max) = rmsvec[2]; + assert_float_eq!(min, 0.31252); + assert_float_eq!(max, 0.32250); + + // retrigger the phase sample + matrix.set_param(trig_p, (1.0).into()); + let rmsvec = run_and_get_each_rms_mimax(&mut node_exec, 100.0); + + let (_rms, min, max) = rmsvec[0]; + // this is the start of the phase + assert_float_eq!(min, 0.0); + // this is the last value of the previous triggering + assert_float_eq!(max, 0.32998); + + let (_rms, min, max) = rmsvec[1]; + assert_float_eq!(min, 0.09251); + assert_float_eq!(max, 0.19249); + + let (_rms, min, max) = rmsvec[2]; + assert_float_eq!(min, 0.19252); + assert_float_eq!(max, 0.29250); +} + +