diff --git a/src/dsp/mod.rs b/src/dsp/mod.rs index 4da3ca6..6e8ac74 100644 --- a/src/dsp/mod.rs +++ b/src/dsp/mod.rs @@ -466,14 +466,16 @@ macro_rules! node_list { (0 inp n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0) (1 trig n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0) (2 time n_time d_time r_tms f_ms stp_m 0.0, 1.0, 250.0) - (3 fb n_id d_id r_id f_def stp_d 0.0, 1.0, 0.0) + (3 fb n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0) (4 mix n_id d_id r_id f_def stp_d 0.0, 1.0, 0.5) {5 0 mode setting(0) fa_delay_mode 0 1} [0 sig], test => Test UIType::Generic UICategory::IOUtil (0 f n_id d_id r_id f_def stp_d 0.0, 1.0, 0.5) - {1 0 p param(0.0) fa_test_s 0 10} - [0 sig], + {1 0 p param(0.0) fa_test_s 0 10} + {2 1 trig param(0.0) fa_test_s 0 0} + [0 sig] + [1 tsig], } } } @@ -1028,6 +1030,13 @@ macro_rules! make_node_info_enum { })+ } + #[allow(non_snake_case)] + pub mod out_idx { + $(pub mod $variant { + $(#[inline] pub fn $out() -> usize { $out_idx })* + })+ + } + mod ni { $( #[derive(Debug, Clone)] diff --git a/src/dsp/node_test.rs b/src/dsp/node_test.rs index 0c003c4..e8f21c9 100644 --- a/src/dsp/node_test.rs +++ b/src/dsp/node_test.rs @@ -4,6 +4,7 @@ use crate::nodes::{NodeAudioContext, NodeExecContext}; use crate::dsp::{NodeId, SAtom, ProcBuf, GraphFun, GraphAtomData, DspNode, LedPhaseVals}; +use crate::dsp::helpers::{TrigSignal}; #[macro_export] macro_rules! fa_test_s { ($formatter: expr, $v: expr, $denorm_v: expr) => { { @@ -28,26 +29,39 @@ macro_rules! fa_test_s { ($formatter: expr, $v: expr, $denorm_v: expr) => { { /// A simple amplifier #[derive(Debug, Clone)] pub struct Test { + trig_sig: TrigSignal, + trigger: bool, } impl Test { + pub fn new(_nid: &NodeId) -> Self { + Self { + trigger: false, + trig_sig: TrigSignal::new(), + } + } + + pub const f : &'static str = "F Test"; + pub const p : &'static str = "Test p\nAn unsmoothed parameter for automated tests."; + pub const trig: &'static str = "Test trig\nA trigger input, that will create a short pulse on the 'tsig' output.\nRange: (-1..1)"; + pub const sig : &'static str = "Test sig\nThe output of p as signal"; + pub const tsig : &'static str = "Test tsig\nA short trigger pulse will be generated when the 'trig' input is triggered."; + pub const DESC : &'static str = r#""#; pub const HELP : &'static str = r#""#; - pub fn new(_nid: &NodeId) -> Self { - Self { - } - } - pub const f : &'static str = "F Test"; - pub const p : &'static str = "Test p\nJust an unsmoothed parameter for tests."; - pub const sig : &'static str = "Test sig\nThe output of p as signal"; } impl DspNode for Test { - fn outputs() -> usize { 1 } + fn outputs() -> usize { 2 } - fn set_sample_rate(&mut self, _srate: f32) { } - fn reset(&mut self) { } + fn set_sample_rate(&mut self, srate: f32) { + self.trig_sig.set_sample_rate(srate); + } + + fn reset(&mut self) { + self.trig_sig.reset(); + } #[inline] fn process( @@ -55,12 +69,36 @@ impl DspNode for Test { atoms: &[SAtom], _params: &[ProcBuf], _inputs: &[ProcBuf], outputs: &mut [ProcBuf], _led: LedPhaseVals) { - use crate::dsp::{out, at}; + use crate::dsp::{out_idx, at}; let p = at::Test::p(atoms); - let out = out::Test::sig(outputs); + let trig = at::Test::trig(atoms); + let tsig = out_idx::Test::tsig(); + + let (out, tsig) = outputs.split_at_mut(tsig); + let out = &mut out[0]; + let tsig = &mut tsig[0]; + + let mut trigger = trig.i(); + if !self.trigger && trigger > 0 { + self.trigger = true; + + } else if !self.trigger && trigger == 0 { + self.trigger = false; + + } else if self.trigger { + trigger = 0; + } + for frame in 0..ctx.nframes() { + if trigger > 0 { + self.trig_sig.trigger(); + trigger = 0; + } + out.write(frame, p.f()); + let t = self.trig_sig.next(); + tsig.write(frame, t); } } diff --git a/tests/common/mod.rs b/tests/common/mod.rs index d191f3b..257ad8e 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -194,6 +194,27 @@ assertion failed: `(left[{}] == right[{}])` } } +#[allow(dead_code)] +pub fn collect_signal_changes(inp: &[f32], thres: i64) -> Vec<(usize, i64)> { + let mut idxs = vec![]; + let mut last_sig = 0.0; + for i in 0..inp.len() { + if (inp[i] - last_sig).abs() > 0.1 { + idxs.push((i, (inp[i] * 100.0).floor() as i64)); + last_sig = inp[i]; + } + } + + let mut idxs_big = vec![]; + for v in idxs.iter() { + if v.1.abs() > thres { + idxs_big.push(*v); + } + } + + return idxs_big; +} + #[macro_export] macro_rules! assert_rmsmima { ($rms:expr, $b:expr) => { diff --git a/tests/node_delay.rs b/tests/node_delay.rs new file mode 100644 index 0000000..d81b7aa --- /dev/null +++ b/tests/node_delay.rs @@ -0,0 +1,296 @@ +mod common; +use common::*; + +#[test] +fn check_node_delay_1() { + let (node_conf, mut node_exec) = new_node_engine(); + let mut matrix = Matrix::new(node_conf, 4, 4); + + let ad = NodeId::Ad(0); + let sin = NodeId::Sin(0); + let dly = NodeId::Delay(0); + let out = NodeId::Out(0); + matrix.place(0, 0, Cell::empty(sin) + .out(None, None, sin.out("sig"))); + matrix.place(0, 1, Cell::empty(ad) + .input(ad.inp("inp"), None, None) + .out(None, None, ad.out("sig"))); + matrix.place(0, 2, Cell::empty(dly) + .input(dly.inp("inp"), None, None) + .out(None, None, dly.out("sig"))); + matrix.place(0, 3, Cell::empty(out) + .input(out.inp("ch1"), None, None) + .out(None, None, None)); + matrix.sync().unwrap(); + + pset_d(&mut matrix, ad, "atk", 50.0); + pset_d(&mut matrix, ad, "dcy", 50.0); + pset_n(&mut matrix, ad, "trig", 1.0); + + let res = run_for_ms(&mut node_exec, 500.0); + // 441 decimation => 10ms resolution + assert_decimated_feq!(res.0, 441, vec![ + // 10ms smoothing time + 0.0, + // burst of sine for 100ms: + 0.018363932, -0.124816686, 0.21992423, -0.19471036, 0.00002711302, + 0.27546832, -0.35064548, 0.25555965, -0.0991776, 0.000008648983, + // 150ms silence: + 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, + // delayed burst of sine for 100ms: + 0.015279313, -0.119179465, 0.22757527, -0.22698581, 0.05398392, + 0.22569486, -0.3332433, 0.26348564, -0.11514694, 0.008539479, + // silence afterwards: + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 + ]); +} + +#[test] +fn check_node_delay_2() { + let (node_conf, mut node_exec) = new_node_engine(); + let mut matrix = Matrix::new(node_conf, 4, 4); + + let dly = NodeId::Delay(0); + let out = NodeId::Out(0); + matrix.place(0, 2, Cell::empty(dly) + .out(None, None, dly.out("sig"))); + matrix.place(0, 3, Cell::empty(out) + .input(out.inp("ch1"), None, None) + .out(None, None, None)); + matrix.sync().unwrap(); + + pset_d(&mut matrix, dly, "time", 31.0); + pset_d(&mut matrix, dly, "inp", 1.0); + + let res = run_for_ms(&mut node_exec, 150.0); + // 441 decimation => 10ms resolution + assert_decimated_feq!(res.0, 441, vec![ + // 10ms smoothing time for "inp" + 0.001133, + // 30ms delaytime just mixing the 0.5: + 0.5, 0.5, 0.5, + // the delayed smoothing ramp (10ms): + 0.9513, + // the delay + input signal: + 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0 + ]); +} + +#[test] +fn check_node_delay_time_mod() { + let (node_conf, mut node_exec) = new_node_engine(); + let mut matrix = Matrix::new(node_conf, 4, 4); + + let sin = NodeId::Sin(0); + let dly = NodeId::Delay(0); + let out = NodeId::Out(0); + matrix.place(1, 1, Cell::empty(sin) + .out(None, None, sin.out("sig"))); + matrix.place(1, 2, Cell::empty(dly) + .input(dly.inp("inp"), None, dly.inp("time")) + .out(None, None, dly.out("sig"))); + matrix.place(1, 3, Cell::empty(out) + .input(out.inp("ch1"), None, None) + .out(None, None, None)); + matrix.sync().unwrap(); + + pset_n(&mut matrix, dly, "mix", 1.0); + pset_d(&mut matrix, dly, "time", 100.0); + + // skip delay time: + run_for_ms(&mut node_exec, 100.0); + + let fft = run_and_get_fft4096_now(&mut node_exec, 600); + assert_eq!(fft[0], (431, 614)); + assert_eq!(fft[1], (441, 1012)); + + let sin2 = NodeId::Sin(1); + matrix.place(0, 3, Cell::empty(sin2) + .out(sin2.out("sig"), None, None)); + + matrix.sync().unwrap(); + pset_d(&mut matrix, sin2, "freq", 0.5); + + // let everything settle down and the delay buffer fill with stuff: + run_for_ms(&mut node_exec, 5000.0); + + // skip some time to let everything settle: + run_for_ms(&mut node_exec, 670.0); + + let fft = run_and_get_fft4096_now(&mut node_exec, 110); + // Expect a sine sweep over a + // range of low frequencies: + assert_eq!(fft[0], (108, 111)); + assert_eq!(fft[5], (312, 110)); + assert_eq!(fft[10], (700, 110)); + + // Sweep upwards: + run_for_ms(&mut node_exec, 300.0); + let fft = run_and_get_fft4096_now(&mut node_exec, 122); + assert_eq!(fft[0], (2509, 123)); + assert_eq!(fft[8], (2821, 123)); + + // Sweep at mostly highest point: + run_for_ms(&mut node_exec, 700.0); + let fft = run_and_get_fft4096_now(&mut node_exec, 300); + assert_eq!(fft[0], (6417, 309)); + assert_eq!(fft[4], (6471, 407)); +} + + +#[test] +fn check_node_delay_trig() { + let (node_conf, mut node_exec) = new_node_engine(); + let mut matrix = Matrix::new(node_conf, 4, 4); + + let test = NodeId::Test(0); + let dly = NodeId::Delay(0); + let out = NodeId::Out(0); + matrix.place(1, 1, Cell::empty(test) + .out(None, None, test.out("tsig"))); + matrix.place(0, 3, Cell::empty(test) + .out(test.out("sig"), None, None)); + matrix.place(1, 2, Cell::empty(dly) + .input(dly.inp("inp"), None, dly.inp("trig")) + .out(None, None, dly.out("sig"))); + matrix.place(1, 3, Cell::empty(out) + .input(out.inp("ch1"), None, None) + .out(None, None, None)); + matrix.sync().unwrap(); + + pset_n(&mut matrix, dly, "mix", 1.0); + pset_n(&mut matrix, dly, "mode", 1.0); + pset_d(&mut matrix, dly, "time", 5.0); + + // Trigger the delay 2 times, with an interval of 20ms: + pset_n(&mut matrix, test, "p", 1.0); + run_for_ms(&mut node_exec, 10.0); + pset_n(&mut matrix, test, "p", 0.0); + run_for_ms(&mut node_exec, 10.0); + pset_n(&mut matrix, test, "p", 1.0); + run_for_ms(&mut node_exec, 10.0); + pset_n(&mut matrix, test, "p", 0.0); + run_for_ms(&mut node_exec, 10.0); + + // Now the delay should have a 20ms delay time. + + // Emit the trigger signal: + pset_n(&mut matrix, test, "trig", 1.0); + + let res = run_for_ms(&mut node_exec, 30.0); + + let mut idx_first_non_zero = 99999; + for i in 0..res.0.len() { + if res.0[i] > 0.0 { + idx_first_non_zero = i; + break; + } + } + + // We expect the signal to be delayed by 20ms: + assert_eq!(idx_first_non_zero, (44100 * 20) / 1000); +} + + +#[test] +fn check_node_delay_fb() { + let (node_conf, mut node_exec) = new_node_engine(); + let mut matrix = Matrix::new(node_conf, 4, 4); + + let test = NodeId::Test(0); + let dly = NodeId::Delay(0); + let out = NodeId::Out(0); + matrix.place(1, 1, Cell::empty(test) + .out(None, None, test.out("tsig"))); + matrix.place(1, 2, Cell::empty(dly) + .input(dly.inp("inp"), None, None) + .out(None, None, dly.out("sig"))); + matrix.place(1, 3, Cell::empty(out) + .input(out.inp("ch1"), None, None) + .out(None, None, None)); + + pset_n(&mut matrix, dly, "mix", 1.0); + pset_d(&mut matrix, dly, "time", 5.0); + pset_n(&mut matrix, dly, "fb", 0.5); + + matrix.sync().unwrap(); + + // Emit the trigger signal: + pset_n(&mut matrix, test, "trig", 1.0); + + let res = run_for_ms(&mut node_exec, 100.0); + + let idxs_big = collect_signal_changes(&res.0[..], 50); + + // We expect the signal to be delayed by 20ms: + assert_eq!(idxs_big, vec![(220, 106), (441, 53)]); +} + +#[test] +fn check_node_delay_fb_neg() { + let (node_conf, mut node_exec) = new_node_engine(); + let mut matrix = Matrix::new(node_conf, 4, 4); + + let test = NodeId::Test(0); + let dly = NodeId::Delay(0); + let out = NodeId::Out(0); + matrix.place(1, 1, Cell::empty(test) + .out(None, None, test.out("tsig"))); + matrix.place(1, 2, Cell::empty(dly) + .input(dly.inp("inp"), None, None) + .out(None, None, dly.out("sig"))); + matrix.place(1, 3, Cell::empty(out) + .input(out.inp("ch1"), None, None) + .out(None, None, None)); + + pset_n(&mut matrix, dly, "mix", 1.0); + pset_d(&mut matrix, dly, "time", 10.0); + pset_n(&mut matrix, dly, "fb", -1.0); + + matrix.sync().unwrap(); + + // Emit the trigger signal: + pset_n(&mut matrix, test, "trig", 1.0); + + let res = run_for_ms(&mut node_exec, 40.0); + + let idxs_big = collect_signal_changes(&res.0[..], 70); + + assert_eq!(idxs_big, vec![(441, 100), (883, -100), (1325, 100)]); +} + + +#[test] +fn check_node_delay_fb_pos() { + let (node_conf, mut node_exec) = new_node_engine(); + let mut matrix = Matrix::new(node_conf, 4, 4); + + let test = NodeId::Test(0); + let dly = NodeId::Delay(0); + let out = NodeId::Out(0); + matrix.place(1, 1, Cell::empty(test) + .out(None, None, test.out("tsig"))); + matrix.place(1, 2, Cell::empty(dly) + .input(dly.inp("inp"), None, None) + .out(None, None, dly.out("sig"))); + matrix.place(1, 3, Cell::empty(out) + .input(out.inp("ch1"), None, None) + .out(None, None, None)); + + pset_n(&mut matrix, dly, "mix", 1.0); + pset_d(&mut matrix, dly, "time", 10.0); + pset_n(&mut matrix, dly, "fb", 1.0); + + matrix.sync().unwrap(); + + // Emit the trigger signal: + pset_n(&mut matrix, test, "trig", 1.0); + + let res = run_for_ms(&mut node_exec, 40.0); + + let idxs_big = collect_signal_changes(&res.0[..], 70); + + assert_eq!(idxs_big, vec![(441, 100), (883, 100), (1325, 100)]); +}