implemented offset and len for sampler, and unified the oneshot and loop implementation.
This commit is contained in:
parent
aff9984f08
commit
3530b6df2d
5 changed files with 677 additions and 469 deletions
|
@ -276,8 +276,8 @@ macro_rules! node_list {
|
||||||
sampl => Sampl UIType::Generic UICategory::Osc
|
sampl => Sampl UIType::Generic UICategory::Osc
|
||||||
(0 freq n_pit d_pit -1.0, 1.0, 440.0)
|
(0 freq n_pit d_pit -1.0, 1.0, 440.0)
|
||||||
(1 trig n_id n_id -1.0, 1.0, 0.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)
|
(2 offs n_id n_id 0.0, 1.0, 0.0)
|
||||||
(3 epos n_id n_id -1.0, 1.0, 0.0)
|
(3 len n_id n_id 0.0, 1.0, 1.0)
|
||||||
{4 0 sample audio_unloaded("") 0 0}
|
{4 0 sample audio_unloaded("") 0 0}
|
||||||
{5 1 pmode setting(0) 0 1}
|
{5 1 pmode setting(0) 0 1}
|
||||||
{6 2 dclick setting(1) 0 1}
|
{6 2 dclick setting(1) 0 1}
|
||||||
|
|
|
@ -32,10 +32,10 @@ impl Sampl {
|
||||||
pub const trig : &'static str =
|
pub const trig : &'static str =
|
||||||
"Sampl trig\nThe trigger input causes a resync of the playback phase \
|
"Sampl trig\nThe trigger input causes a resync of the playback phase \
|
||||||
and triggers the playback if the 'pmode' is 'OneShot'";
|
and triggers the playback if the 'pmode' is 'OneShot'";
|
||||||
pub const spos : &'static str =
|
pub const offs : &'static str =
|
||||||
"Sampl spos\nStart position offset.\nRange: (-1..1)\n";
|
"Sampl offs\nStart position offset.\nRange: (0..1)\n";
|
||||||
pub const epos : &'static str =
|
pub const len : &'static str =
|
||||||
"Sampl epos\nEnd position offset.\nRange: (-1..1)\n";
|
"Sampl len\nLength of the sample, after the offset has been applied.\nRange: (0..1)\n";
|
||||||
|
|
||||||
pub const sample : &'static str =
|
pub const sample : &'static str =
|
||||||
"Sampl sample\nThe audio sample that is played back.\nRange: (-1..1)\n";
|
"Sampl sample\nThe audio sample that is played back.\nRange: (-1..1)\n";
|
||||||
|
@ -59,6 +59,7 @@ impl Sampl {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn next_sample(&mut self, sr_factor: f64, speed: f64, sample_data: &[f32]) -> f32 {
|
fn next_sample(&mut self, sr_factor: f64, speed: f64, sample_data: &[f32]) -> f32 {
|
||||||
let sd_len = sample_data.len();
|
let sd_len = sample_data.len();
|
||||||
|
if sd_len < 1 { return 0.0; }
|
||||||
|
|
||||||
let i = self.phase.floor() as usize + sd_len;
|
let i = self.phase.floor() as usize + sd_len;
|
||||||
|
|
||||||
|
@ -89,30 +90,46 @@ impl Sampl {
|
||||||
(((a * f) - b_neg) * f + c) * f + x0
|
(((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]
|
#[inline]
|
||||||
fn play_loop(&mut self, inputs: &[ProcBuf], nframes: usize, sample_data: &[f32], out: &mut ProcBuf) {
|
fn play(&mut self, inputs: &[ProcBuf], nframes: usize,
|
||||||
let freq = inp::Sampl::freq(inputs);
|
sample_data: &[f32], out: &mut ProcBuf, do_loop: bool)
|
||||||
|
|
||||||
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 freq = inp::Sampl::freq(inputs);
|
||||||
let trig = inp::Sampl::trig(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_srate = sample_data[0] as f64;
|
||||||
let sample_data = &sample_data[1..];
|
let sample_data = &sample_data[1..];
|
||||||
|
@ -120,6 +137,16 @@ impl Sampl {
|
||||||
|
|
||||||
let mut is_playing = self.is_playing;
|
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 {
|
for frame in 0..nframes {
|
||||||
let trig_val = denorm::Sampl::trig(trig, frame);
|
let trig_val = denorm::Sampl::trig(trig, frame);
|
||||||
let triggered = self.trig.check_trigger(trig_val);
|
let triggered = self.trig.check_trigger(trig_val);
|
||||||
|
@ -135,12 +162,36 @@ impl Sampl {
|
||||||
|
|
||||||
let prev_phase = self.phase;
|
let prev_phase = self.phase;
|
||||||
|
|
||||||
let s = self.next_sample(
|
let cur_offs =
|
||||||
sr_factor, playback_speed as f64, sample_data);
|
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);
|
out.write(frame, s);
|
||||||
|
|
||||||
// played past end => stop playing.
|
if !do_loop && prev_phase > self.phase {
|
||||||
if prev_phase > self.phase {
|
// played past end => stop playing.
|
||||||
is_playing = false;
|
is_playing = false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -171,11 +222,20 @@ impl DspNode for Sampl {
|
||||||
let out = out::Sampl::sig(outputs);
|
let out = out::Sampl::sig(outputs);
|
||||||
|
|
||||||
if let SAtom::AudioSample((_, Some(sample_data))) = sample {
|
if let SAtom::AudioSample((_, Some(sample_data))) = sample {
|
||||||
if pmode.i() == 0 {
|
// 3 is for sample-sample-rate and at least 2 audio samples.
|
||||||
self.play_loop(inputs, ctx.nframes(), &sample_data[..], out);
|
if sample_data.len() < 3 {
|
||||||
} else {
|
for frame in 0..ctx.nframes() {
|
||||||
self.play_oneshot(inputs, ctx.nframes(), &sample_data[..], out);
|
out.write(frame, 0.0);
|
||||||
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.play(
|
||||||
|
inputs,
|
||||||
|
ctx.nframes(),
|
||||||
|
&sample_data[..],
|
||||||
|
out,
|
||||||
|
pmode.i() == 0);
|
||||||
} else {
|
} else {
|
||||||
for frame in 0..ctx.nframes() {
|
for frame in 0..ctx.nframes() {
|
||||||
out.write(frame, 0.0);
|
out.write(frame, 0.0);
|
||||||
|
|
437
tests/basics.rs
437
tests/basics.rs
|
@ -1,211 +1,5 @@
|
||||||
use hexodsp::matrix::*;
|
mod common;
|
||||||
use hexodsp::nodes::new_node_engine;
|
use common::*;
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn check_matrix_sine() {
|
fn check_matrix_sine() {
|
||||||
|
@ -1122,230 +916,3 @@ fn check_matrix_output_feedback() {
|
||||||
assert_float_eq!(fo_amp.1, 0.11627);
|
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
211
tests/common/mod.rs
Normal 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
370
tests/node_sampl.rs
Normal 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);
|
||||||
|
}
|
Loading…
Reference in a new issue