implemented offset and len for sampler, and unified the oneshot and loop implementation.

This commit is contained in:
Weird Constructor 2021-05-29 11:41:46 +02:00
parent aff9984f08
commit 3530b6df2d
5 changed files with 677 additions and 469 deletions

View file

@ -276,8 +276,8 @@ macro_rules! node_list {
sampl => Sampl UIType::Generic UICategory::Osc
(0 freq n_pit d_pit -1.0, 1.0, 440.0)
(1 trig n_id n_id -1.0, 1.0, 0.0)
(2 spos n_id n_id -1.0, 1.0, 0.0)
(3 epos n_id n_id -1.0, 1.0, 0.0)
(2 offs n_id n_id 0.0, 1.0, 0.0)
(3 len n_id n_id 0.0, 1.0, 1.0)
{4 0 sample audio_unloaded("") 0 0}
{5 1 pmode setting(0) 0 1}
{6 2 dclick setting(1) 0 1}

View file

@ -32,10 +32,10 @@ impl Sampl {
pub const trig : &'static str =
"Sampl trig\nThe trigger input causes a resync of the playback phase \
and triggers the playback if the 'pmode' is 'OneShot'";
pub const spos : &'static str =
"Sampl spos\nStart position offset.\nRange: (-1..1)\n";
pub const epos : &'static str =
"Sampl epos\nEnd position offset.\nRange: (-1..1)\n";
pub const offs : &'static str =
"Sampl offs\nStart position offset.\nRange: (0..1)\n";
pub const len : &'static str =
"Sampl len\nLength of the sample, after the offset has been applied.\nRange: (0..1)\n";
pub const sample : &'static str =
"Sampl sample\nThe audio sample that is played back.\nRange: (-1..1)\n";
@ -59,6 +59,7 @@ impl Sampl {
#[inline]
fn next_sample(&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 i = self.phase.floor() as usize + sd_len;
@ -89,30 +90,46 @@ impl Sampl {
(((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 offs = inp::Sampl::offs(inputs);
// let len = inp::Sampl::len(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;
//
// let start_idx =
// (sample_data.len() as f32
// * denorm::Sampl::offs(offs, frame).abs().min(0.999999))
// .floor() as usize;
//
// let end_idx_plus1 =
// ((sample_data.len() - start_idx) as f32
// * denorm::Sampl::len(offs, frame).abs().min(0.999999))
// .ceil() as usize;
//
// out.write(frame,
// self.next_sample(
// sr_factor,
// playback_speed as f64,
// sample_data[start_idx..end_idx_plus1]));
// }
// }
//
#[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)
fn play(&mut self, inputs: &[ProcBuf], nframes: usize,
sample_data: &[f32], out: &mut ProcBuf, do_loop: bool)
{
let freq = inp::Sampl::freq(inputs);
let trig = inp::Sampl::trig(inputs);
let offs = inp::Sampl::offs(inputs);
let len = inp::Sampl::len(inputs);
let sample_srate = sample_data[0] as f64;
let sample_data = &sample_data[1..];
@ -120,6 +137,16 @@ impl Sampl {
let mut is_playing = self.is_playing;
if do_loop {
is_playing = true;
}
let mut prev_offs = -10.0;
let mut prev_len = -10.0;
let mut start_idx = 0;
let mut end_idx_plus1 = sample_data.len();
for frame in 0..nframes {
let trig_val = denorm::Sampl::trig(trig, frame);
let triggered = self.trig.check_trigger(trig_val);
@ -135,12 +162,36 @@ impl Sampl {
let prev_phase = self.phase;
let s = self.next_sample(
sr_factor, playback_speed as f64, sample_data);
let cur_offs =
denorm::Sampl::offs(offs, frame)
.abs().min(0.999999);
if prev_offs != cur_offs {
start_idx =
(sample_data.len() as f32 * cur_offs)
.floor() as usize;
prev_offs = cur_offs;
}
let cur_len =
denorm::Sampl::len(len, frame)
.abs().min(0.999999);
if prev_len != cur_len {
end_idx_plus1 =
((sample_data.len() - start_idx) as f32
* denorm::Sampl::len(len, frame).abs().min(0.999999))
.ceil() as usize;
prev_len = cur_len;
}
let s = self.next_sample(
sr_factor,
playback_speed as f64,
&sample_data[start_idx..(start_idx + end_idx_plus1)]);
out.write(frame, s);
// played past end => stop playing.
if prev_phase > self.phase {
if !do_loop && prev_phase > self.phase {
// played past end => stop playing.
is_playing = false;
}
} else {
@ -171,11 +222,20 @@ impl DspNode for Sampl {
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);
// 3 is for sample-sample-rate and at least 2 audio samples.
if sample_data.len() < 3 {
for frame in 0..ctx.nframes() {
out.write(frame, 0.0);
}
return;
}
self.play(
inputs,
ctx.nframes(),
&sample_data[..],
out,
pmode.i() == 0);
} else {
for frame in 0..ctx.nframes() {
out.write(frame, 0.0);

View file

@ -1,211 +1,5 @@
use hexodsp::matrix::*;
use hexodsp::nodes::new_node_engine;
use hexodsp::dsp::*;
use hound;
//use num_complex::Complex;
use microfft;
macro_rules! assert_float_eq {
($a:expr, $b:expr) => {
if ($a - $b).abs() > 0.0001 {
panic!(r#"assertion failed: `(left == right)`
left: `{:?}`,
right: `{:?}`"#, $a, $b)
}
}
}
const SAMPLE_RATE : f32 = 44100.0;
fn save_wav(name: &str, buf: &[f32]) {
let spec = hound::WavSpec {
channels: 1,
sample_rate: SAMPLE_RATE as u32,
bits_per_sample: 16,
sample_format: hound::SampleFormat::Int,
};
let mut writer = hound::WavWriter::create(name, spec).unwrap();
for s in buf.iter() {
let amp = i16::MAX as f32;
writer.write_sample((amp * s) as i16).unwrap();
}
}
fn run_no_input(node_exec: &mut hexodsp::nodes::NodeExecutor, seconds: f32) -> (Vec<f32>, Vec<f32>) {
run_realtime_no_input(node_exec, seconds, false)
}
fn run_realtime_no_input(node_exec: &mut hexodsp::nodes::NodeExecutor, seconds: f32, sleep_a_bit: bool) -> (Vec<f32>, Vec<f32>) {
node_exec.test_run(seconds, sleep_a_bit)
}
fn calc_rms_mimax_each_ms(buf: &[f32], ms: f32) -> Vec<(f32, f32, f32)> {
let ms_samples = ms * SAMPLE_RATE / 1000.0;
let len_ms = ms_samples as usize;
let mut idx = 0;
let mut res = vec![];
loop {
if (idx + len_ms) > buf.len() {
break;
}
let mut max = -1000.0;
let mut min = 1000.0;
for s in buf[idx..(idx + len_ms)].iter() {
max = s.max(max);
min = s.min(min);
}
let rms : f32 =
buf[idx..(idx + len_ms)]
.iter()
.map(|s: &f32| s * s).sum::<f32>()
/ ms_samples;
res.push((rms, min, max));
idx += len_ms;
}
res
}
fn run_and_undersample(
node_exec: &mut hexodsp::nodes::NodeExecutor,
run_len_ms: f32, samples: usize) -> Vec<f32>
{
let (out_l, _out_r) = run_no_input(node_exec, run_len_ms / 1000.0);
let sample_interval = out_l.len() / samples;
let mut out_samples = vec![];
for i in 0..samples {
let idx = i * sample_interval;
out_samples.push(out_l[idx]);
}
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)
{
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[1]
}
fn run_and_get_fft4096(
node_exec: &mut hexodsp::nodes::NodeExecutor,
thres: u32,
offs_ms: f32) -> Vec<(u16, u32)>
{
let min_samples_for_fft = 4096.0;
let offs_samples = (offs_ms * (SAMPLE_RATE / 1000.0)).ceil();
let min_len_samples =
offs_samples
// 2.0 * for safety margin
+ 2.0 * min_samples_for_fft;
let run_len_s = min_len_samples / SAMPLE_RATE;
let (mut out_l, _out_r) = run_no_input(node_exec, run_len_s);
fft_thres_at_ms(&mut out_l[..], FFT::F4096, thres, offs_ms)
}
#[allow(unused)]
enum FFT {
F16,
F32,
F64,
F128,
F512,
F1024,
F2048,
F4096,
}
fn fft_thres_at_ms(buf: &mut [f32], size: FFT, amp_thres: u32, ms_idx: f32) -> Vec<(u16, u32)> {
let ms_sample_offs = ms_idx * (SAMPLE_RATE / 1000.0);
let fft_nbins = match size {
FFT::F16 => 16,
FFT::F32 => 32,
FFT::F64 => 64,
FFT::F128 => 128,
FFT::F512 => 512,
FFT::F1024 => 1024,
FFT::F2048 => 2048,
FFT::F4096 => 4096,
};
let len = fft_nbins;
let idx = ms_sample_offs as usize;
let mut res = vec![];
if (idx + len) > buf.len() {
return res;
}
// Hann window:
for (i, s) in buf[idx..(idx + len)].iter_mut().enumerate() {
let w =
0.5
* (1.0
- ((2.0 * std::f32::consts::PI * i as f32)
/ (fft_nbins as f32 - 1.0))
.cos());
*s *= w;
}
let spec =
match size {
FFT::F16 =>
microfft::real::rfft_16(&mut buf[idx..(idx + len)]),
FFT::F32 =>
microfft::real::rfft_32(&mut buf[idx..(idx + len)]),
FFT::F64 =>
microfft::real::rfft_64(&mut buf[idx..(idx + len)]),
FFT::F128 =>
microfft::real::rfft_128(&mut buf[idx..(idx + len)]),
FFT::F512 =>
microfft::real::rfft_512(&mut buf[idx..(idx + len)]),
FFT::F1024 =>
microfft::real::rfft_1024(&mut buf[idx..(idx + len)]),
FFT::F2048 =>
microfft::real::rfft_2048(&mut buf[idx..(idx + len)]),
FFT::F4096 =>
microfft::real::rfft_4096(&mut buf[idx..(idx + len)]),
};
let amplitudes: Vec<_> = spec.iter().map(|c| c.norm() as u32).collect();
for (i, amp) in amplitudes.iter().enumerate() {
if *amp >= amp_thres {
let freq = (i as f32 * SAMPLE_RATE) / fft_nbins as f32;
res.push((freq.round() as u16, *amp));
}
}
res
}
mod common;
use common::*;
#[test]
fn check_matrix_sine() {
@ -1122,230 +916,3 @@ fn check_matrix_output_feedback() {
assert_float_eq!(fo_amp.1, 0.11627);
}
#[test]
fn check_node_sampl_1() {
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();
matrix.set_param(sample_p, SAtom::audio_unloaded("tests/sample_sin.wav"));
let (rms, min, max) = run_and_get_l_rms_mimax(&mut node_exec, 50.0);
assert_float_eq!(rms, 0.505);
assert_float_eq!(min, -0.9998);
assert_float_eq!(max, 1.0);
let fft = run_and_get_fft4096(&mut node_exec, 800, 20.0);
assert_eq!(fft[0], (441, 940));
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], (894, 988));
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], (226, 966));
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, 953));
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, 818));
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, 964));
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], (1776, 877));
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], (7127, 1029));
}
#[test]
fn check_node_sampl_reload() {
{
let (node_conf, _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();
matrix.set_param(sample_p, SAtom::audio_unloaded("tests/sample_sin.wav"));
hexodsp::save_patch_to_file(&mut matrix, "check_matrix_serialize.hxy")
.unwrap();
}
{
let (node_conf, mut node_exec) = new_node_engine();
let mut matrix = Matrix::new(node_conf, 3, 3);
hexodsp::load_patch_from_file(
&mut matrix, "check_matrix_serialize.hxy").unwrap();
let (rms, min, max) = run_and_get_l_rms_mimax(&mut node_exec, 50.0);
assert_float_eq!(rms, 0.505);
assert_float_eq!(min, -0.9998);
assert_float_eq!(max, 1.0);
let fft = run_and_get_fft4096(&mut node_exec, 800, 20.0);
assert_eq!(fft[0], (441, 940));
}
}
#[test]
fn check_node_sampl_load_err() {
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();
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);
assert_float_eq!(rms, 0.0);
assert_float_eq!(min, 0.0);
assert_float_eq!(max, 0.0);
let err = matrix.pop_error();
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);
}

211
tests/common/mod.rs Normal file
View file

@ -0,0 +1,211 @@
pub use hexodsp::matrix::*;
pub use hexodsp::nodes::new_node_engine;
pub use hexodsp::dsp::*;
use hound;
//use num_complex::Complex;
use microfft;
pub const SAMPLE_RATE : f32 = 44100.0;
pub const SAMPLE_RATE_US : usize = 44100;
#[macro_export]
macro_rules! assert_float_eq {
($a:expr, $b:expr) => {
if ($a - $b).abs() > 0.0001 {
panic!(r#"assertion failed: `(left == right)`
left: `{:?}`,
right: `{:?}`"#, $a, $b)
}
}
}
pub fn save_wav(name: &str, buf: &[f32]) {
let spec = hound::WavSpec {
channels: 1,
sample_rate: SAMPLE_RATE as u32,
bits_per_sample: 16,
sample_format: hound::SampleFormat::Int,
};
let mut writer = hound::WavWriter::create(name, spec).unwrap();
for s in buf.iter() {
let amp = i16::MAX as f32;
writer.write_sample((amp * s) as i16).unwrap();
}
}
pub fn run_no_input(node_exec: &mut hexodsp::nodes::NodeExecutor, seconds: f32) -> (Vec<f32>, Vec<f32>) {
run_realtime_no_input(node_exec, seconds, false)
}
pub fn run_realtime_no_input(node_exec: &mut hexodsp::nodes::NodeExecutor, seconds: f32, sleep_a_bit: bool) -> (Vec<f32>, Vec<f32>) {
node_exec.test_run(seconds, sleep_a_bit)
}
pub fn calc_rms_mimax_each_ms(buf: &[f32], ms: f32) -> Vec<(f32, f32, f32)> {
let ms_samples = ms * SAMPLE_RATE / 1000.0;
let len_ms = ms_samples as usize;
let mut idx = 0;
let mut res = vec![];
loop {
if (idx + len_ms) > buf.len() {
break;
}
let mut max = -1000.0;
let mut min = 1000.0;
for s in buf[idx..(idx + len_ms)].iter() {
max = s.max(max);
min = s.min(min);
}
let rms : f32 =
buf[idx..(idx + len_ms)]
.iter()
.map(|s: &f32| s * s).sum::<f32>()
/ ms_samples;
res.push((rms, min, max));
idx += len_ms;
}
res
}
pub fn run_and_undersample(
node_exec: &mut hexodsp::nodes::NodeExecutor,
run_len_ms: f32, samples: usize) -> Vec<f32>
{
let (out_l, _out_r) = run_no_input(node_exec, run_len_ms / 1000.0);
let sample_interval = out_l.len() / samples;
let mut out_samples = vec![];
for i in 0..samples {
let idx = i * sample_interval;
out_samples.push(out_l[idx]);
}
out_samples
}
pub 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)
}
pub 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]
}
pub fn run_and_get_l_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[1]
}
pub fn run_and_get_fft4096(
node_exec: &mut hexodsp::nodes::NodeExecutor,
thres: u32,
offs_ms: f32) -> Vec<(u16, u32)>
{
let min_samples_for_fft = 4096.0;
let offs_samples = (offs_ms * (SAMPLE_RATE / 1000.0)).ceil();
let min_len_samples =
offs_samples
// 2.0 * for safety margin
+ 2.0 * min_samples_for_fft;
let run_len_s = min_len_samples / SAMPLE_RATE;
let (mut out_l, _out_r) = run_no_input(node_exec, run_len_s);
fft_thres_at_ms(&mut out_l[..], FFT::F4096, thres, offs_ms)
}
#[allow(unused)]
pub enum FFT {
F16,
F32,
F64,
F128,
F512,
F1024,
F2048,
F4096,
}
pub fn fft_thres_at_ms(buf: &mut [f32], size: FFT, amp_thres: u32, ms_idx: f32) -> Vec<(u16, u32)> {
let ms_sample_offs = ms_idx * (SAMPLE_RATE / 1000.0);
let fft_nbins = match size {
FFT::F16 => 16,
FFT::F32 => 32,
FFT::F64 => 64,
FFT::F128 => 128,
FFT::F512 => 512,
FFT::F1024 => 1024,
FFT::F2048 => 2048,
FFT::F4096 => 4096,
};
let len = fft_nbins;
let idx = ms_sample_offs as usize;
let mut res = vec![];
if (idx + len) > buf.len() {
return res;
}
// Hann window:
for (i, s) in buf[idx..(idx + len)].iter_mut().enumerate() {
let w =
0.5
* (1.0
- ((2.0 * std::f32::consts::PI * i as f32)
/ (fft_nbins as f32 - 1.0))
.cos());
*s *= w;
}
let spec =
match size {
FFT::F16 =>
microfft::real::rfft_16(&mut buf[idx..(idx + len)]),
FFT::F32 =>
microfft::real::rfft_32(&mut buf[idx..(idx + len)]),
FFT::F64 =>
microfft::real::rfft_64(&mut buf[idx..(idx + len)]),
FFT::F128 =>
microfft::real::rfft_128(&mut buf[idx..(idx + len)]),
FFT::F512 =>
microfft::real::rfft_512(&mut buf[idx..(idx + len)]),
FFT::F1024 =>
microfft::real::rfft_1024(&mut buf[idx..(idx + len)]),
FFT::F2048 =>
microfft::real::rfft_2048(&mut buf[idx..(idx + len)]),
FFT::F4096 =>
microfft::real::rfft_4096(&mut buf[idx..(idx + len)]),
};
let amplitudes: Vec<_> = spec.iter().map(|c| c.norm() as u32).collect();
for (i, amp) in amplitudes.iter().enumerate() {
if *amp >= amp_thres {
let freq = (i as f32 * SAMPLE_RATE) / fft_nbins as f32;
res.push((freq.round() as u16, *amp));
}
}
res
}

370
tests/node_sampl.rs Normal file
View file

@ -0,0 +1,370 @@
mod common;
use common::*;
#[test]
fn check_node_sampl_1() {
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();
matrix.set_param(sample_p, SAtom::audio_unloaded("tests/sample_sin.wav"));
let (rms, min, max) = run_and_get_l_rms_mimax(&mut node_exec, 50.0);
assert_float_eq!(rms, 0.505);
assert_float_eq!(min, -0.9998);
assert_float_eq!(max, 1.0);
let fft = run_and_get_fft4096(&mut node_exec, 800, 20.0);
assert_eq!(fft[0], (441, 940));
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], (894, 988));
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], (226, 966));
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, 953));
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, 818));
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, 964));
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], (1776, 877));
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], (7127, 1029));
}
#[test]
fn check_node_sampl_reload() {
{
let (node_conf, _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();
matrix.set_param(sample_p, SAtom::audio_unloaded("tests/sample_sin.wav"));
hexodsp::save_patch_to_file(&mut matrix, "check_matrix_serialize.hxy")
.unwrap();
}
{
let (node_conf, mut node_exec) = new_node_engine();
let mut matrix = Matrix::new(node_conf, 3, 3);
hexodsp::load_patch_from_file(
&mut matrix, "check_matrix_serialize.hxy").unwrap();
let (rms, min, max) = run_and_get_l_rms_mimax(&mut node_exec, 50.0);
assert_float_eq!(rms, 0.505);
assert_float_eq!(min, -0.9998);
assert_float_eq!(max, 1.0);
let fft = run_and_get_fft4096(&mut node_exec, 800, 20.0);
assert_eq!(fft[0], (441, 940));
}
}
#[test]
fn check_node_sampl_load_err() {
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();
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);
assert_float_eq!(rms, 0.0);
assert_float_eq!(min, 0.0);
assert_float_eq!(max, 0.0);
let err = matrix.pop_error();
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);
}
fn create_1sec_ramp() -> SAtom {
let mut test_sample_ramp = vec![0.0; (SAMPLE_RATE_US) + 1];
test_sample_ramp[0] = SAMPLE_RATE;
for i in 0..(test_sample_ramp.len() - 1) {
test_sample_ramp[i + 1] =
(i as f32) / ((test_sample_ramp.len() - 2) as f32)
}
SAtom::audio(
"1second_ramp.wav",
std::sync::Arc::new(test_sample_ramp))
}
#[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();
matrix.set_param(sample_p, create_1sec_ramp());
// One Shot Mode
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);
}
#[test]
fn check_node_sampl_trigger_loop_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();
matrix.set_param(sample_p, create_1sec_ramp());
// Loop mode:
matrix.set_param(pmode_p, SAtom::setting(0));
matrix.set_param(trig_p, (0.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.09999);
let (_rms, min, max) = rmsvec[2];
assert_float_eq!(min, 0.2);
assert_float_eq!(max, 0.2999);
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.3074);
let (_rms, min, max) = rmsvec[2];
assert_float_eq!(min, 0.1925);
assert_float_eq!(max, 0.2925);
}
#[test]
fn check_node_sampl_offs_len() {
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 offs_p = smpl.inp_param("offs").unwrap();
let len_p = smpl.inp_param("len").unwrap();
matrix.set_param(sample_p, create_1sec_ramp());
matrix.set_param(pmode_p, SAtom::setting(0));
// Select part 0.5 to 0.75 of the sample:
matrix.set_param(offs_p, SAtom::param(0.5));
matrix.set_param(len_p, SAtom::param(0.5));
let rmsvec = run_and_get_each_rms_mimax(&mut node_exec, 50.0);
let (_rms, min, max) = rmsvec[0];
assert_float_eq!(min, 0.0011133);
assert_float_eq!(max, 0.54999);
let (_rms, min, max) = rmsvec[2];
assert_float_eq!(min, 0.6);
assert_float_eq!(max, 0.65);
let rmsvec = run_and_get_each_rms_mimax(&mut node_exec, 50.0);
let (_rms, min, max) = rmsvec[0];
assert_float_eq!(min, 0.65);
assert_float_eq!(max, 0.6999);
let (_rms, min, max) = rmsvec[1];
assert_float_eq!(min, 0.70);
assert_float_eq!(max, 0.75);
let (_rms, min, max) = rmsvec[2];
assert_float_eq!(min, 0.5);
assert_float_eq!(max, 0.55);
}
#[test]
fn check_node_sampl_offs_len_zero_crash() {
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 offs_p = smpl.inp_param("offs").unwrap();
let len_p = smpl.inp_param("len").unwrap();
let trig_p = smpl.inp_param("trig").unwrap();
matrix.set_param(sample_p, create_1sec_ramp());
matrix.set_param(pmode_p, SAtom::setting(0));
// Select part 0.5 to 0.75 of the sample:
matrix.set_param(offs_p, SAtom::param(1.0));
matrix.set_param(len_p, SAtom::param(0.0));
matrix.set_param(trig_p, (1.0).into());
let rmsvec = run_and_get_each_rms_mimax(&mut node_exec, 50.0);
let (_rms, min, max) = rmsvec[0];
assert_float_eq!(min, 0.0);
assert_float_eq!(max, 1.0);
// Select part 0.5 to 0.75 of the sample:
matrix.set_param(offs_p, SAtom::param(0.9));
matrix.set_param(len_p, SAtom::param(0.0));
matrix.set_param(trig_p, (1.0).into());
let rmsvec = run_and_get_each_rms_mimax(&mut node_exec, 50.0);
let (_rms, min, max) = rmsvec[0];
assert_float_eq!(min, 0.0);
assert_float_eq!(max, 0.0);
// Select part 0.5 to 0.75 of the sample:
matrix.set_param(offs_p, SAtom::param(1.0));
matrix.set_param(len_p, SAtom::param(0.0));
matrix.set_param(trig_p, (1.0).into());
let rmsvec = run_and_get_each_rms_mimax(&mut node_exec, 50.0);
let (_rms, min, max) = rmsvec[0];
assert_float_eq!(min, 0.0);
assert_float_eq!(max, 0.0);
}