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)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct TriggerClock {
|
pub struct TriggerClock {
|
||||||
clock_phase: f64,
|
clock_phase: f64,
|
||||||
|
|
|
@ -4,12 +4,16 @@
|
||||||
|
|
||||||
use crate::nodes::NodeAudioContext;
|
use crate::nodes::NodeAudioContext;
|
||||||
use crate::dsp::{SAtom, ProcBuf, DspNode, LedPhaseVals};
|
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
|
/// A simple amplifier
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Sampl {
|
pub struct Sampl {
|
||||||
phase: f64,
|
phase: f64,
|
||||||
srate: f64,
|
srate: f64,
|
||||||
|
trig: Trigger,
|
||||||
|
is_playing: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Sampl {
|
impl Sampl {
|
||||||
|
@ -17,6 +21,8 @@ impl Sampl {
|
||||||
Self {
|
Self {
|
||||||
phase: 0.0,
|
phase: 0.0,
|
||||||
srate: 44100.0,
|
srate: 44100.0,
|
||||||
|
trig: Trigger::new(),
|
||||||
|
is_playing: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub const freq : &'static str =
|
pub const freq : &'static str =
|
||||||
|
@ -49,35 +55,10 @@ impl Sampl {
|
||||||
"Sampl sig\nSampler audio output\nRange: (-1..1)\n";
|
"Sampl sig\nSampler audio output\nRange: (-1..1)\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DspNode for Sampl {
|
impl Sampl {
|
||||||
fn outputs() -> usize { 1 }
|
|
||||||
|
|
||||||
fn set_sample_rate(&mut self, srate: f32) { self.srate = srate.into(); }
|
|
||||||
fn reset(&mut self) { }
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn process<T: NodeAudioContext>(
|
fn next_sample(&mut self, sr_factor: f64, speed: f64, sample_data: &[f32]) -> f32 {
|
||||||
&mut self, ctx: &mut T, atoms: &[SAtom], _params: &[ProcBuf],
|
let sd_len = sample_data.len();
|
||||||
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;
|
|
||||||
|
|
||||||
let i = self.phase.floor() as usize + sd_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 a = w + v + (x2 - x0) * 0.5;
|
||||||
let b_neg = w + a;
|
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;
|
self.phase = (i % sd_len) as f64 + f + sr_factor * speed;
|
||||||
out.write(frame, out_sample);
|
|
||||||
|
|
||||||
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 {
|
} else {
|
||||||
for frame in 0..ctx.nframes() {
|
for frame in 0..ctx.nframes() {
|
||||||
|
@ -112,41 +182,7 @@ impl DspNode for Sampl {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx_vals[0].set(1.0);
|
let last_frame = ctx.nframes() - 1;
|
||||||
// let neg = at::Amp::neg_att(atoms);
|
ctx_vals[0].set(out.read(last_frame));
|
||||||
//
|
|
||||||
// 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)
|
|
||||||
// };
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
132
tests/basics.rs
132
tests/basics.rs
|
@ -90,6 +90,23 @@ fn run_and_undersample(
|
||||||
out_samples
|
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(
|
fn run_and_get_l_rms_mimax(
|
||||||
node_exec: &mut hexodsp::nodes::NodeExecutor,
|
node_exec: &mut hexodsp::nodes::NodeExecutor,
|
||||||
len_ms: f32) -> (f32, f32, f32)
|
len_ms: f32) -> (f32, f32, f32)
|
||||||
|
@ -1163,7 +1180,7 @@ fn check_node_sampl_1() {
|
||||||
#[test]
|
#[test]
|
||||||
fn check_node_sampl_reload() {
|
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 mut matrix = Matrix::new(node_conf, 3, 3);
|
||||||
|
|
||||||
let smpl = NodeId::Sampl(0);
|
let smpl = NodeId::Sampl(0);
|
||||||
|
@ -1175,7 +1192,6 @@ fn check_node_sampl_reload() {
|
||||||
matrix.sync().unwrap();
|
matrix.sync().unwrap();
|
||||||
|
|
||||||
let sample_p = smpl.inp_param("sample").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"));
|
matrix.set_param(sample_p, SAtom::audio_unloaded("tests/sample_sin.wav"));
|
||||||
|
|
||||||
hexodsp::save_patch_to_file(&mut matrix, "check_matrix_serialize.hxy")
|
hexodsp::save_patch_to_file(&mut matrix, "check_matrix_serialize.hxy")
|
||||||
|
@ -1214,7 +1230,6 @@ fn check_node_sampl_load_err() {
|
||||||
matrix.sync().unwrap();
|
matrix.sync().unwrap();
|
||||||
|
|
||||||
let sample_p = smpl.inp_param("sample").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"));
|
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);
|
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);
|
assert_float_eq!(max, 0.0);
|
||||||
|
|
||||||
let err = matrix.pop_error();
|
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