From 790e13ecb48361d560eacbb5fcd042533d96b0b6 Mon Sep 17 00:00:00 2001 From: Weird Constructor Date: Thu, 18 Aug 2022 06:28:59 +0200 Subject: [PATCH] Added MidiCC slew limiter and tests for MidiCC --- src/dsp/mod.rs | 2 +- src/dsp/node_midicc.rs | 62 ++++++++++++------ tests/node_midicc.rs | 139 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 183 insertions(+), 20 deletions(-) create mode 100644 tests/node_midicc.rs diff --git a/src/dsp/mod.rs b/src/dsp/mod.rs index 0bac319..c41d73b 100644 --- a/src/dsp/mod.rs +++ b/src/dsp/mod.rs @@ -1441,7 +1441,7 @@ macro_rules! node_list { [1 gate] [2 vel], midicc => MidiCC UIType::Generic UICategory::IOUtil - (0 slew n_lfot d_lfot r_lfot f_lfoms stp_f 0.0, 1.0, 0.0) + (0 slew n_timz d_timz r_tmz f_ms stp_m 0.0, 1.0, 0.0) {1 0 chan setting(0) mode fa_midip_chan 0 16} {2 1 cc1 setting(0) mode fa_midicc_cc 0 127} {3 2 cc2 setting(0) mode fa_midicc_cc 0 127} diff --git a/src/dsp/node_midicc.rs b/src/dsp/node_midicc.rs index e23bf48..5953db5 100644 --- a/src/dsp/node_midicc.rs +++ b/src/dsp/node_midicc.rs @@ -2,8 +2,11 @@ // This file is a part of HexoDSP. Released under GPL-3.0-or-later. // See README.md and COPYING for details. -use crate::dsp::{at, out_idx, DspNode, LedPhaseVals, NodeContext, NodeId, ProcBuf, SAtom}; +use crate::dsp::{ + at, denorm, inp, out_idx, DspNode, LedPhaseVals, NodeContext, NodeId, ProcBuf, SAtom, +}; use crate::nodes::{HxMidiEvent, MidiEventPointer, NodeAudioContext, NodeExecContext}; +use synfx_dsp::SlewValue; #[macro_export] macro_rules! fa_midicc_cc { @@ -18,32 +21,50 @@ pub struct MidiCC { cur_cc1: f32, cur_cc2: f32, cur_cc3: f32, + slew_cc1: SlewValue, + slew_cc2: SlewValue, + slew_cc3: SlewValue, } impl MidiCC { pub fn new(_nid: &NodeId) -> Self { - Self { cur_cc1: 0.0, cur_cc2: 0.0, cur_cc3: 0.0 } + Self { + cur_cc1: 0.0, + cur_cc2: 0.0, + cur_cc3: 0.0, + slew_cc1: SlewValue::new(), + slew_cc2: SlewValue::new(), + slew_cc3: SlewValue::new(), + } } pub const chan: &'static str = "MidiCC chan\nMIDI Channel 0 to 15\n"; - pub const slew: &'static str = "MidiCC slew\nSlew limiter for the 3 CCs\n- 'MIDI' gate same as MIDI input\n- 'Trigger' output only triggers on 'gate' output\n- 'Gate Len' output gate with the length of the 'gatel' parameter\n"; - pub const cc1: &'static str = "MidiCC cc1\nMIDI selected CC"; - pub const cc2: &'static str = "MidiCC cc1\nMIDI selected CC"; - pub const cc3: &'static str = "MidiCC cc1\nMIDI selected CC"; + pub const slew: &'static str = "MidiCC slew\nSlew limiter for the 3 CCs\nRange: (0..1)"; + pub const cc1: &'static str = "MidiCC cc1\nMIDI selected CC 1"; + pub const cc2: &'static str = "MidiCC cc2\nMIDI selected CC 2"; + pub const cc3: &'static str = "MidiCC cc3\nMIDI selected CC 3"; pub const sig1: &'static str = "MidiCC sig1\nCC output channel 1\nRange: (0..1)"; - pub const sig2: &'static str = "MidiCC sig1\nCC output channel 1\nRange: (0..1)"; - pub const sig3: &'static str = "MidiCC sig1\nCC output channel 1\nRange: (0..1)"; + pub const sig2: &'static str = "MidiCC sig2\nCC output channel 2\nRange: (0..1)"; + pub const sig3: &'static str = "MidiCC sig3\nCC output channel 3\nRange: (0..1)"; - pub const DESC: &'static str = "Audio Output Port\n\n\ - This output port node allows you to send audio signals \ - to audio devices or tracks in your DAW."; - pub const HELP: &'static str = r#"Audio Output Port + pub const DESC: &'static str = "MIDI CC Input\n\n\ + This node is an input of MIDI CC events/values into the DSP graph. \ + You get 3 CC value outputs: 'sig1', 'sig2' and 'sig3'. To set which CC \ + gets which output you have to set the corresponding 'cc1', 'cc2' and \ + 'cc3' parameters."; + pub const HELP: &'static str = r#"MIDI CC Input -This output port node allows you to send audio signals to audio devices -or tracks in your DAW. If you need a stereo output but only have a mono -signal you can use the 'mono' setting to duplicate the signal on the 'ch1' -input to the second channel 'ch2'. +This node is an input of MIDI CC events/values into the DSP graph. +You get 3 CC value outputs: 'sig1', 'sig2' and 'sig3'. To set which CC +gets which output you have to set the corresponding 'cc1', 'cc2' and +'cc3' parameters."; + +If the CC values change to rapidly or you hear audible artifacts, you can +try to limit the speed of change with the 'slew' limiter. + +If you need different 'slew' values for the CCs, I recommend creating other +MidiCC instances with different 'slew' settings. "#; } @@ -66,6 +87,7 @@ impl DspNode for MidiCC { outputs: &mut [ProcBuf], ctx_vals: LedPhaseVals, ) { + let slew = inp::MidiCC::slew(inputs); let chan = at::MidiCC::chan(atoms); let cc1 = at::MidiCC::cc1(atoms); let cc2 = at::MidiCC::cc2(atoms); @@ -87,6 +109,8 @@ impl DspNode for MidiCC { let mut change = false; for frame in 0..ctx.nframes() { + let slew_ms = denorm::MidiCC::slew(slew, frame); + while let Some(ev) = ptr.next_at(frame) { match ev { HxMidiEvent::CC { channel, cc, value } => { @@ -109,9 +133,9 @@ impl DspNode for MidiCC { } } - sig1.write(frame, self.cur_cc1); - sig2.write(frame, self.cur_cc2); - sig3.write(frame, self.cur_cc3); + sig1.write(frame, self.slew_cc1.next(self.cur_cc1, slew_ms)); + sig2.write(frame, self.slew_cc2.next(self.cur_cc2, slew_ms)); + sig3.write(frame, self.slew_cc3.next(self.cur_cc3, slew_ms)); } ctx_vals[0].set(if change { 1.0 } else { 0.0 }); diff --git a/tests/node_midicc.rs b/tests/node_midicc.rs new file mode 100644 index 0000000..0facf1e --- /dev/null +++ b/tests/node_midicc.rs @@ -0,0 +1,139 @@ +// Copyright (c) 2022 Weird Constructor +// This file is a part of HexoDSP. Released under GPL-3.0-or-later. +// See README.md and COPYING for details. + +mod common; +use common::*; + +#[test] +fn check_node_midicc_test_receive() { + let (node_conf, mut node_exec) = new_node_engine(); + let mut matrix = Matrix::new(node_conf, 3, 3); + + let mut chain = MatrixCellChain::new(CellDir::B); + chain + .node_out("midicc", "sig1") + .set_atom("cc1", SAtom::setting(10)) + .node_inp("out", "ch1") + .place(&mut matrix, 0, 0) + .unwrap(); + let mut chain = MatrixCellChain::new(CellDir::B); + chain + .node_out("midicc", "sig2") + .set_atom("cc2", SAtom::setting(12)) + .node_inp("out", "ch2") + .place(&mut matrix, 1, 0) + .unwrap(); + matrix.sync().unwrap(); + + // Test run for 5ms with 3 Note On events at sample positions + // 5, 10 and 130 in this block of samples: + let (ch1, ch2) = node_exec.test_run( + 0.005, + false, + &[ + HxTimedEvent::cc(3, 0, 12, 1.55), + HxTimedEvent::cc(5, 0, 10, 0.55), + HxTimedEvent::cc(100, 0, 10, 0.35), + HxTimedEvent::cc(120, 0, 10, 0.15), + HxTimedEvent::cc(190, 0, 10, 0.05), + HxTimedEvent::cc(200, 0, 12, 1.15), + ], + ); + + let changes = collect_signal_changes(&ch1[..], 0); + assert_eq!(changes, &[(5, 55), (100, 35), (120, 15), (190, 5)]); + + let changes = collect_signal_changes(&ch2[..], 0); + assert_eq!(changes, &[(3, 155), (200, 115)]); +} + +#[test] +fn check_node_midicc_test_slew() { + let (node_conf, mut node_exec) = new_node_engine(); + let mut matrix = Matrix::new(node_conf, 3, 3); + + let mut chain = MatrixCellChain::new(CellDir::B); + chain + .node_out("midicc", "sig1") + .set_atom("cc1", SAtom::setting(10)) + .set_denorm("slew", 4.0) + .node_inp("out", "ch1") + .place(&mut matrix, 0, 0) + .unwrap(); + matrix.sync().unwrap(); + + // Test run for 5ms with 3 Note On events at sample positions + // 5, 10 and 130 in this block of samples: + let (ch1, ch2) = node_exec.test_run( + 0.005, + false, + &[ + HxTimedEvent::cc(3, 0, 12, 1.55), + HxTimedEvent::cc(5, 0, 10, 0.55), + HxTimedEvent::cc(100, 0, 10, 0.35), + HxTimedEvent::cc(120, 0, 10, 0.15), + HxTimedEvent::cc(190, 0, 10, 0.05), + HxTimedEvent::cc(200, 0, 12, 1.15), + ], + ); + + let changes: Vec<(usize, i32)> = collect_signal_changes_flt(&ch1[..], 0.0) + .iter() + .step_by(20) + .map(|(a, b)| (*a, (b * 1000.0).round() as i32)) + .collect(); + assert_eq!( + changes, + &[ + (5, 6), + (25, 119), + (45, 232), + (65, 346), + (85, 459), + (105, 505), + (125, 391), + (145, 278), + (165, 164), + (206, 54) + ] + ); +} + +#[test] +fn check_node_midicc_test_slew2() { + let (node_conf, mut node_exec) = new_node_engine(); + let mut matrix = Matrix::new(node_conf, 3, 3); + + let mut chain = MatrixCellChain::new(CellDir::B); + chain + .node_out("midicc", "sig1") + .set_atom("cc1", SAtom::setting(10)) + .set_denorm("slew", 2.0) + .node_inp("out", "ch1") + .place(&mut matrix, 0, 0) + .unwrap(); + matrix.sync().unwrap(); + + // Test run for 5ms with 3 Note On events at sample positions + // 5, 10 and 130 in this block of samples: + let (ch1, ch2) = node_exec.test_run( + 0.005, + false, + &[ + HxTimedEvent::cc(3, 0, 12, 1.55), + HxTimedEvent::cc(5, 0, 10, 0.55), + HxTimedEvent::cc(100, 0, 10, 0.35), + HxTimedEvent::cc(120, 0, 10, 0.15), + HxTimedEvent::cc(190, 0, 10, 0.05), + HxTimedEvent::cc(200, 0, 12, 1.15), + ], + ); + + let changes: Vec<(usize, i32)> = collect_signal_changes_flt(&ch1[..], 0.0) + .iter() + .step_by(20) + .map(|(a, b)| (*a, (b * 1000.0).round() as i32)) + .collect(); + assert_eq!(changes, &[(5, 11), (25, 238), (45, 465), (111, 414), (133, 191),]); +}