implemented declick, working on tests

This commit is contained in:
Weird Constructor 2021-05-29 13:45:26 +02:00
parent 3530b6df2d
commit a7a2a26d67
4 changed files with 243 additions and 120 deletions

View file

@ -280,7 +280,7 @@ macro_rules! node_list {
(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}
{6 2 dclick setting(0) 0 1}
[0 sig],
sin => Sin UIType::Generic UICategory::Osc
(0 freq n_pit d_pit -1.0, 1.0, 440.0)

View file

@ -7,6 +7,8 @@ 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;
const RAMP_TIME_MS : f64 = 3.14;
/// A simple amplifier
#[derive(Debug, Clone)]
pub struct Sampl {
@ -90,41 +92,10 @@ 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(&mut self, inputs: &[ProcBuf], nframes: usize,
sample_data: &[f32], out: &mut ProcBuf, do_loop: bool)
sample_data: &[f32], out: &mut ProcBuf, do_loop: bool,
declick: bool)
{
let freq = inp::Sampl::freq(inputs);
let trig = inp::Sampl::trig(inputs);
@ -135,6 +106,9 @@ impl Sampl {
let sample_data = &sample_data[1..];
let sr_factor = sample_srate / self.srate;
let ramp_sample_count = ((RAMP_TIME_MS * self.srate) / 1000.0).ceil() as usize;
let ramp_inc = 1000.0 / (RAMP_TIME_MS * self.srate);
let mut is_playing = self.is_playing;
if do_loop {
@ -163,8 +137,7 @@ impl Sampl {
let prev_phase = self.phase;
let cur_offs =
denorm::Sampl::offs(offs, frame)
.abs().min(0.999999);
denorm::Sampl::offs(offs, frame).abs().min(0.999999);
if prev_offs != cur_offs {
start_idx =
(sample_data.len() as f32 * cur_offs)
@ -173,20 +146,41 @@ impl Sampl {
}
let cur_len =
denorm::Sampl::len(len, frame)
.abs().min(0.999999);
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))
((sample_data.len() - start_idx) as f32 * cur_len)
.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)]);
let sample_slice =
&sample_data[start_idx..(start_idx + end_idx_plus1)];
// next_sample mutates self.phase, so we need the current phase
// that is used for looking up the sample from the audio data.
let sample_idx = self.phase.floor() as usize;
let mut s =
self.next_sample(
sr_factor,
playback_speed as f64,
sample_slice);
if declick {
let samples_to_end = sample_data.len() - sample_idx;
let ramp_atten_factor =
if sample_idx < ramp_sample_count {
sample_idx as f64 * ramp_inc
} else if samples_to_end < ramp_sample_count {
1.0 - (samples_to_end as f64 * ramp_inc)
} else {
1.0
};
s *= ramp_atten_factor as f32;
}
out.write(frame, s);
@ -219,6 +213,7 @@ impl DspNode for Sampl {
{
let sample = at::Sampl::sample(atoms);
let pmode = at::Sampl::pmode(atoms);
let dclick = at::Sampl::dclick(atoms);
let out = out::Sampl::sig(outputs);
if let SAtom::AudioSample((_, Some(sample_data))) = sample {
@ -235,7 +230,8 @@ impl DspNode for Sampl {
ctx.nframes(),
&sample_data[..],
out,
pmode.i() == 0);
pmode.i() == 0,
dclick.i() == 1);
} else {
for frame in 0..ctx.nframes() {
out.write(frame, 0.0);

View file

@ -7,6 +7,7 @@ use hound;
use microfft;
pub const SAMPLE_RATE : f32 = 44100.0;
#[allow(dead_code)]
pub const SAMPLE_RATE_US : usize = 44100;
#[macro_export]
@ -20,6 +21,59 @@ macro_rules! assert_float_eq {
}
}
#[macro_export]
macro_rules! assert_fpair_eq {
($a:expr, $b:expr) => {
if ($a.0 - $b.0).abs() > 0.0001 {
panic!(r#"assertion failed: `(left.0 == right.0)`
left: `{:?}`,
right: `{:?}`"#, $a.0, $b.0)
}
if ($a.1 - $b.1).abs() > 0.0001 {
panic!(r#"assertion failed: `(left.1 == right.1)`
left: `{:?}`,
right: `{:?}`"#, $a.1, $b.1)
}
}
}
#[macro_export]
macro_rules! assert_f3tupl_eq {
($a:expr, $b:expr) => {
if ($a.0 - $b.0).abs() > 0.0001 {
panic!(r#"assertion failed: `(left.0 == right.0)`
left: `{:?}`,
right: `{:?}`"#, $a.0, $b.0)
}
if ($a.1 - $b.1).abs() > 0.0001 {
panic!(r#"assertion failed: `(left.1 == right.1)`
left: `{:?}`,
right: `{:?}`"#, $a.1, $b.1)
}
if ($a.2 - $b.2).abs() > 0.0001 {
panic!(r#"assertion failed: `(left.2 == right.2)`
left: `{:?}`,
right: `{:?}`"#, $a.2, $b.2)
}
}
}
#[macro_export]
macro_rules! assert_rmsmima {
($rms:expr, $b:expr) => {
assert_f3tupl_eq!($rms, $b);
}
}
#[macro_export]
macro_rules! assert_minmax_of_rms {
($rms:expr, $b:expr) => {
let (_, min, max) = $rms;
assert_fpair_eq!((min, max), $b);
}
}
#[allow(dead_code)]
pub fn save_wav(name: &str, buf: &[f32]) {
let spec = hound::WavSpec {
channels: 1,
@ -39,6 +93,11 @@ pub fn run_no_input(node_exec: &mut hexodsp::nodes::NodeExecutor, seconds: f32)
run_realtime_no_input(node_exec, seconds, false)
}
#[allow(dead_code)]
pub fn run_for_ms(node_exec: &mut hexodsp::nodes::NodeExecutor, ms: f32) -> (Vec<f32>, Vec<f32>) {
run_realtime_no_input(node_exec, ms / 1000.0, 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)
}
@ -75,6 +134,7 @@ pub fn calc_rms_mimax_each_ms(buf: &[f32], ms: f32) -> Vec<(f32, f32, f32)> {
res
}
#[allow(dead_code)]
pub fn run_and_undersample(
node_exec: &mut hexodsp::nodes::NodeExecutor,
run_len_ms: f32, samples: usize) -> Vec<f32>
@ -92,6 +152,7 @@ pub fn run_and_undersample(
out_samples
}
#[allow(dead_code)]
pub fn run_and_get_each_rms_mimax(
node_exec: &mut hexodsp::nodes::NodeExecutor,
len_ms: f32) -> Vec<(f32, f32, f32)>
@ -100,6 +161,7 @@ pub fn run_and_get_each_rms_mimax(
calc_rms_mimax_each_ms(&out_l[..], len_ms)
}
#[allow(dead_code)]
pub fn run_and_get_first_rms_mimax(
node_exec: &mut hexodsp::nodes::NodeExecutor,
len_ms: f32) -> (f32, f32, f32)

View file

@ -19,9 +19,7 @@ fn check_node_sampl_1() {
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);
assert_rmsmima!((rms, min, max), (0.505, -0.9998, 1.0));
let fft = run_and_get_fft4096(&mut node_exec, 800, 20.0);
assert_eq!(fft[0], (441, 940));
@ -84,10 +82,8 @@ fn check_node_sampl_reload() {
&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 rmsmima = run_and_get_l_rms_mimax(&mut node_exec, 50.0);
assert_rmsmima!(rmsmima, (0.505, -0.9998, 1.0));
let fft = run_and_get_fft4096(&mut node_exec, 800, 20.0);
assert_eq!(fft[0], (441, 940));
@ -111,9 +107,9 @@ fn check_node_sampl_load_err() {
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);
assert_rmsmima!(
(rms, min, max),
(0.0, 0.0, 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\" }))");
@ -138,21 +134,24 @@ fn check_node_sampl_trigger() {
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);
let rmsmima = run_and_get_l_rms_mimax(&mut node_exec, 10.0);
assert_rmsmima!(rmsmima, (0.0, 0.0, 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 rmsmima = run_and_get_first_rms_mimax(&mut node_exec, 10.0);
assert_rmsmima!(rmsmima, (0.1136, -0.9998, 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);
let rmsmima = run_and_get_l_rms_mimax(&mut node_exec, 20.0);
assert_rmsmima!(rmsmima, (0.0, 0.0, 0.0));
}
fn create_1sec_const(s: f32) -> SAtom {
let mut test_sample_ramp = vec![s; (SAMPLE_RATE_US) + 1];
test_sample_ramp[0] = SAMPLE_RATE;
SAtom::audio(
"1second_const.wav",
std::sync::Arc::new(test_sample_ramp))
}
fn create_1sec_ramp() -> SAtom {
@ -196,20 +195,13 @@ fn check_node_sampl_trigger_reset_phase() {
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);
assert_minmax_of_rms!(rmsvec[0], (0.0, 0.092496));
assert_minmax_of_rms!(rmsvec[2], (0.19252, 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);
assert_minmax_of_rms!(rmsvec[2], (0.31252, 0.32250));
// retrigger the phase sample
matrix.set_param(trig_p, (1.0).into());
@ -221,13 +213,8 @@ fn check_node_sampl_trigger_reset_phase() {
// 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);
assert_minmax_of_rms!(rmsvec[1], (0.09251, 0.19249));
assert_minmax_of_rms!(rmsvec[2], (0.19252, 0.29250));
}
#[test]
@ -253,21 +240,13 @@ fn check_node_sampl_trigger_loop_reset_phase() {
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);
assert_minmax_of_rms!(rmsvec[0], (0.0, 0.0999));
assert_minmax_of_rms!(rmsvec[2], (0.2, 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);
assert_minmax_of_rms!(rmsvec[0], (0.0, 0.3074));
assert_minmax_of_rms!(rmsvec[2], (0.1925, 0.2925));
}
#[test]
@ -296,23 +275,13 @@ fn check_node_sampl_offs_len() {
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);
assert_minmax_of_rms!(rmsvec[0], (0.001113, 0.54999));
assert_minmax_of_rms!(rmsvec[2], (0.6, 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);
assert_minmax_of_rms!(rmsvec[0], (0.65, 0.6999));
assert_minmax_of_rms!(rmsvec[1], (0.70, 0.75));
assert_minmax_of_rms!(rmsvec[2], (0.5, 0.55));
}
@ -344,9 +313,7 @@ fn check_node_sampl_offs_len_zero_crash() {
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);
assert_minmax_of_rms!(rmsvec[0], (0.0, 1.0));
// Select part 0.5 to 0.75 of the sample:
matrix.set_param(offs_p, SAtom::param(0.9));
@ -354,9 +321,7 @@ fn check_node_sampl_offs_len_zero_crash() {
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);
assert_minmax_of_rms!(rmsvec[0], (0.0, 0.0));
// Select part 0.5 to 0.75 of the sample:
matrix.set_param(offs_p, SAtom::param(1.0));
@ -364,7 +329,107 @@ fn check_node_sampl_offs_len_zero_crash() {
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);
assert_minmax_of_rms!(rmsvec[0], (0.0, 0.0));
}
#[test]
fn check_node_sampl_declick() {
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 dclick_p = smpl.inp_param("dclick").unwrap();
let trig_p = smpl.inp_param("trig").unwrap();
matrix.set_param(sample_p, create_1sec_const(1.0));
// One Shot Mode
matrix.set_param(pmode_p, SAtom::setting(1));
matrix.set_param(dclick_p, SAtom::setting(0));
matrix.set_param(trig_p, (1.0).into());
let rmsvec = run_and_get_each_rms_mimax(&mut node_exec, 5.0);
assert_minmax_of_rms!(rmsvec[0], (0.0, 0.0));
assert_minmax_of_rms!(rmsvec[1], (0.0, 1.0));
assert_minmax_of_rms!(rmsvec[2], (1.0, 1.0));
// reset trigger:
matrix.set_param(trig_p, (0.0).into());
run_for_ms(&mut node_exec, 1000.0);
matrix.set_param(dclick_p, SAtom::setting(1));
matrix.set_param(trig_p, (1.0).into());
// let the trigger appear in the sampler:
run_for_ms(&mut node_exec, 7.5);
// now the de-click should run:
let rmsvec = run_and_get_each_rms_mimax(&mut node_exec, 1.0);
assert_minmax_of_rms!(rmsvec[0], (0.0, 0.3105));
assert_minmax_of_rms!(rmsvec[1], (0.3177, 0.6282));
assert_minmax_of_rms!(rmsvec[2], (0.6354, 0.9460));
}
#[test]
fn check_node_sampl_declick_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 dclick_p = smpl.inp_param("dclick").unwrap();
let trig_p = smpl.inp_param("trig").unwrap();
let offs_p = smpl.inp_param("offs").unwrap();
let len_p = smpl.inp_param("len").unwrap();
matrix.set_param(sample_p, create_1sec_const(1.0));
// One Shot Mode
matrix.set_param(pmode_p, SAtom::setting(1));
matrix.set_param(dclick_p, SAtom::setting(1));
matrix.set_param(trig_p, (1.0).into());
matrix.set_param(offs_p, SAtom::param(0.9));
matrix.set_param(len_p, SAtom::param(0.06));
// trigger:
run_for_ms(&mut node_exec, 7.5);
let res = run_for_ms(&mut node_exec, 12.0);
println!("RMS: {:#?}", res.0);
assert!(false);
// assert_minmax_of_rms!(rmsvec[0], (0.0, 2.0));
// assert_minmax_of_rms!(rmsvec[1], (0.0, 1.0));
// assert_minmax_of_rms!(rmsvec[2], (1.0, 1.0));
//
// // reset trigger:
// matrix.set_param(trig_p, (0.0).into());
// run_for_ms(&mut node_exec, 1000.0);
//
// matrix.set_param(dclick_p, SAtom::setting(1));
// matrix.set_param(trig_p, (1.0).into());
// // let the trigger appear in the sampler:
// run_for_ms(&mut node_exec, 7.5);
// // now the de-click should run:
// let rmsvec = run_and_get_each_rms_mimax(&mut node_exec, 1.0);
//
// assert_minmax_of_rms!(rmsvec[0], (0.0, 0.3105));
// assert_minmax_of_rms!(rmsvec[1], (0.3177, 0.6282));
// assert_minmax_of_rms!(rmsvec[2], (0.6354, 0.9460));
}