Added MidiCC slew limiter and tests for MidiCC

This commit is contained in:
Weird Constructor 2022-08-18 06:28:59 +02:00
parent 6db2766695
commit 790e13ecb4
3 changed files with 183 additions and 20 deletions

View file

@ -1441,7 +1441,7 @@ macro_rules! node_list {
[1 gate] [1 gate]
[2 vel], [2 vel],
midicc => MidiCC UIType::Generic UICategory::IOUtil 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} {1 0 chan setting(0) mode fa_midip_chan 0 16}
{2 1 cc1 setting(0) mode fa_midicc_cc 0 127} {2 1 cc1 setting(0) mode fa_midicc_cc 0 127}
{3 2 cc2 setting(0) mode fa_midicc_cc 0 127} {3 2 cc2 setting(0) mode fa_midicc_cc 0 127}

View file

@ -2,8 +2,11 @@
// This file is a part of HexoDSP. Released under GPL-3.0-or-later. // This file is a part of HexoDSP. Released under GPL-3.0-or-later.
// See README.md and COPYING for details. // 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 crate::nodes::{HxMidiEvent, MidiEventPointer, NodeAudioContext, NodeExecContext};
use synfx_dsp::SlewValue;
#[macro_export] #[macro_export]
macro_rules! fa_midicc_cc { macro_rules! fa_midicc_cc {
@ -18,32 +21,50 @@ pub struct MidiCC {
cur_cc1: f32, cur_cc1: f32,
cur_cc2: f32, cur_cc2: f32,
cur_cc3: f32, cur_cc3: f32,
slew_cc1: SlewValue<f32>,
slew_cc2: SlewValue<f32>,
slew_cc3: SlewValue<f32>,
} }
impl MidiCC { impl MidiCC {
pub fn new(_nid: &NodeId) -> Self { 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 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 slew: &'static str = "MidiCC slew\nSlew limiter for the 3 CCs\nRange: (0..1)";
pub const cc1: &'static str = "MidiCC cc1\nMIDI selected CC"; pub const cc1: &'static str = "MidiCC cc1\nMIDI selected CC 1";
pub const cc2: &'static str = "MidiCC cc1\nMIDI selected CC"; pub const cc2: &'static str = "MidiCC cc2\nMIDI selected CC 2";
pub const cc3: &'static str = "MidiCC cc1\nMIDI selected CC"; 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 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 sig2: &'static str = "MidiCC sig2\nCC output channel 2\nRange: (0..1)";
pub const sig3: &'static str = "MidiCC sig1\nCC output channel 1\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\ pub const DESC: &'static str = "MIDI CC Input\n\n\
This output port node allows you to send audio signals \ This node is an input of MIDI CC events/values into the DSP graph. \
to audio devices or tracks in your DAW."; You get 3 CC value outputs: 'sig1', 'sig2' and 'sig3'. To set which CC \
pub const HELP: &'static str = r#"Audio Output Port 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 This node is an input of MIDI CC events/values into the DSP graph.
or tracks in your DAW. If you need a stereo output but only have a mono You get 3 CC value outputs: 'sig1', 'sig2' and 'sig3'. To set which CC
signal you can use the 'mono' setting to duplicate the signal on the 'ch1' gets which output you have to set the corresponding 'cc1', 'cc2' and
input to the second channel 'ch2'. '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], outputs: &mut [ProcBuf],
ctx_vals: LedPhaseVals, ctx_vals: LedPhaseVals,
) { ) {
let slew = inp::MidiCC::slew(inputs);
let chan = at::MidiCC::chan(atoms); let chan = at::MidiCC::chan(atoms);
let cc1 = at::MidiCC::cc1(atoms); let cc1 = at::MidiCC::cc1(atoms);
let cc2 = at::MidiCC::cc2(atoms); let cc2 = at::MidiCC::cc2(atoms);
@ -87,6 +109,8 @@ impl DspNode for MidiCC {
let mut change = false; let mut change = false;
for frame in 0..ctx.nframes() { for frame in 0..ctx.nframes() {
let slew_ms = denorm::MidiCC::slew(slew, frame);
while let Some(ev) = ptr.next_at(frame) { while let Some(ev) = ptr.next_at(frame) {
match ev { match ev {
HxMidiEvent::CC { channel, cc, value } => { HxMidiEvent::CC { channel, cc, value } => {
@ -109,9 +133,9 @@ impl DspNode for MidiCC {
} }
} }
sig1.write(frame, self.cur_cc1); sig1.write(frame, self.slew_cc1.next(self.cur_cc1, slew_ms));
sig2.write(frame, self.cur_cc2); sig2.write(frame, self.slew_cc2.next(self.cur_cc2, slew_ms));
sig3.write(frame, self.cur_cc3); sig3.write(frame, self.slew_cc3.next(self.cur_cc3, slew_ms));
} }
ctx_vals[0].set(if change { 1.0 } else { 0.0 }); ctx_vals[0].set(if change { 1.0 } else { 0.0 });

139
tests/node_midicc.rs Normal file
View file

@ -0,0 +1,139 @@
// Copyright (c) 2022 Weird Constructor <weirdconstructor@gmail.com>
// 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),]);
}