wrote sampler one shot mode and tests for this
This commit is contained in:
parent
663e88efeb
commit
aff9984f08
3 changed files with 281 additions and 86 deletions
|
@ -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,
|
||||
|
|
|
@ -4,12 +4,16 @@
|
|||
|
||||
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,
|
||||
trig: Trigger,
|
||||
is_playing: bool,
|
||||
}
|
||||
|
||||
impl Sampl {
|
||||
|
@ -17,6 +21,8 @@ impl Sampl {
|
|||
Self {
|
||||
phase: 0.0,
|
||||
srate: 44100.0,
|
||||
trig: Trigger::new(),
|
||||
is_playing: false,
|
||||
}
|
||||
}
|
||||
pub const freq : &'static str =
|
||||
|
@ -49,35 +55,10 @@ impl Sampl {
|
|||
"Sampl sig\nSampler audio output\nRange: (-1..1)\n";
|
||||
}
|
||||
|
||||
impl DspNode for Sampl {
|
||||
fn outputs() -> usize { 1 }
|
||||
|
||||
fn set_sample_rate(&mut self, srate: f32) { self.srate = srate.into(); }
|
||||
fn reset(&mut self) { }
|
||||
|
||||
impl Sampl {
|
||||
#[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 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;
|
||||
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;
|
||||
|
||||
|
@ -99,12 +80,101 @@ impl DspNode for Sampl {
|
|||
let a = w + v + (x2 - x0) * 0.5;
|
||||
let b_neg = w + a;
|
||||
|
||||
let f = self.phase.fract() as f32;
|
||||
let f = self.phase.fract();
|
||||
|
||||
let out_sample = (((a * f) - b_neg) * f + c) * f + x0;
|
||||
out.write(frame, out_sample);
|
||||
self.phase = (i % sd_len) as f64 + f + sr_factor * speed;
|
||||
|
||||
self.phase += sr_factor * playback_speed as f64;
|
||||
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) {
|
||||
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)
|
||||
{
|
||||
let sample = at::Sampl::sample(atoms);
|
||||
let pmode = at::Sampl::pmode(atoms);
|
||||
let out = out::Sampl::sig(outputs);
|
||||
|
||||
if let SAtom::AudioSample((_, Some(sample_data))) = sample {
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
|
132
tests/basics.rs
132
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);
|
||||
}
|
||||
|
||||
|
||||
|
|
Loading…
Reference in a new issue