wrote sampler one shot mode and tests for this

This commit is contained in:
Weird Constructor 2021-05-28 21:37:16 +02:00
parent 663e88efeb
commit aff9984f08
3 changed files with 281 additions and 86 deletions

View file

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

View file

@ -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<T: NodeAudioContext>(
&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));
}
}

View file

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