Compare commits

...

5 commits

Author SHA1 Message Date
Pascal Engélibert 0ab8cf1835 fix wasm build 2022-08-26 11:01:28 +02:00
Weird Constructor be4e9232cc Finished parameter access nodes 2022-08-20 17:27:08 +02:00
Weird Constructor 82d903edba Implemented all 24 input parameters 2022-08-20 14:01:54 +02:00
Weird Constructor 6bd070fcf3 Small refactor 2022-08-20 08:40:39 +02:00
Weird Constructor 87630cb49a Added ExtA external parameter node 2022-08-20 08:36:45 +02:00
8 changed files with 386 additions and 3 deletions

View file

@ -19,3 +19,4 @@ the scope handles for access to it's capture buffers.
* Feature: Added the `FormFM` node that was contributed by Dimas Leenman (aka Skythedragon).
* Feature: Added `MidiP` node for MIDI pitch/note input.
* Feature: Added `MidiCC` node for MIDI CC input.
* Feature: Added `ExtA` to `ExtF` nodes for plugin parameter access.

View file

@ -505,6 +505,8 @@ mod node_cqnt;
#[allow(non_upper_case_globals)]
mod node_delay;
#[allow(non_upper_case_globals)]
mod node_ext;
#[allow(non_upper_case_globals)]
mod node_fbwr_fbrd;
#[allow(non_upper_case_globals)]
mod node_formfm;
@ -600,6 +602,12 @@ use node_code::Code;
use node_comb::Comb;
use node_cqnt::CQnt;
use node_delay::Delay;
use node_ext::ExtA;
use node_ext::ExtB;
use node_ext::ExtC;
use node_ext::ExtD;
use node_ext::ExtE;
use node_ext::ExtF;
use node_fbwr_fbrd::FbRd;
use node_fbwr_fbrd::FbWr;
use node_formfm::FormFM;
@ -1449,6 +1457,54 @@ macro_rules! node_list {
[0 sig1]
[1 sig2]
[2 sig3],
exta => ExtA UIType::Generic UICategory::IOUtil
(0 slew n_timz d_timz r_tmz f_ms stp_m 0.0, 1.0, 0.0)
(1 atv1 n_id d_id r_id f_def stp_d -1.0, 1.0, 1.0)
(2 atv2 n_id d_id r_id f_def stp_d -1.0, 1.0, 1.0)
(3 atv3 n_id d_id r_id f_def stp_d -1.0, 1.0, 1.0)
[0 sig1]
[1 sig2]
[2 sig3],
extb => ExtB UIType::Generic UICategory::IOUtil
(0 slew n_timz d_timz r_tmz f_ms stp_m 0.0, 1.0, 0.0)
(1 atv1 n_id d_id r_id f_def stp_d -1.0, 1.0, 1.0)
(2 atv2 n_id d_id r_id f_def stp_d -1.0, 1.0, 1.0)
(3 atv3 n_id d_id r_id f_def stp_d -1.0, 1.0, 1.0)
[0 sig1]
[1 sig2]
[2 sig3],
extc => ExtC UIType::Generic UICategory::IOUtil
(0 slew n_timz d_timz r_tmz f_ms stp_m 0.0, 1.0, 0.0)
(1 atv1 n_id d_id r_id f_def stp_d -1.0, 1.0, 1.0)
(2 atv2 n_id d_id r_id f_def stp_d -1.0, 1.0, 1.0)
(3 atv3 n_id d_id r_id f_def stp_d -1.0, 1.0, 1.0)
[0 sig1]
[1 sig2]
[2 sig3],
extd => ExtD UIType::Generic UICategory::IOUtil
(0 slew n_timz d_timz r_tmz f_ms stp_m 0.0, 1.0, 0.0)
(1 atv1 n_id d_id r_id f_def stp_d -1.0, 1.0, 1.0)
(2 atv2 n_id d_id r_id f_def stp_d -1.0, 1.0, 1.0)
(3 atv3 n_id d_id r_id f_def stp_d -1.0, 1.0, 1.0)
[0 sig1]
[1 sig2]
[2 sig3],
exte => ExtE UIType::Generic UICategory::IOUtil
(0 slew n_timz d_timz r_tmz f_ms stp_m 0.0, 1.0, 0.0)
(1 atv1 n_id d_id r_id f_def stp_d -1.0, 1.0, 1.0)
(2 atv2 n_id d_id r_id f_def stp_d -1.0, 1.0, 1.0)
(3 atv3 n_id d_id r_id f_def stp_d -1.0, 1.0, 1.0)
[0 sig1]
[1 sig2]
[2 sig3],
extf => ExtF UIType::Generic UICategory::IOUtil
(0 slew n_timz d_timz r_tmz f_ms stp_m 0.0, 1.0, 0.0)
(1 atv1 n_id d_id r_id f_def stp_d -1.0, 1.0, 1.0)
(2 atv2 n_id d_id r_id f_def stp_d -1.0, 1.0, 1.0)
(3 atv3 n_id d_id r_id f_def stp_d -1.0, 1.0, 1.0)
[0 sig1]
[1 sig2]
[2 sig3],
out => Out UIType::Generic UICategory::IOUtil
(0 ch1 n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0)
(1 ch2 n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0)

123
src/dsp/node_ext.rs Normal file
View file

@ -0,0 +1,123 @@
// 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.
use crate::dsp::{
denorm, inp, out_idx, DspNode, LedPhaseVals, NodeContext, NodeId, ProcBuf, SAtom,
};
use crate::nodes::{NodeAudioContext, NodeExecContext};
use synfx_dsp::SlewValue;
macro_rules! define_ext {
($name: ident, $p1: ident, $p2: ident, $p3: ident) => {
#[derive(Debug, Clone)]
pub struct $name {
slew1: SlewValue<f32>,
slew2: SlewValue<f32>,
slew3: SlewValue<f32>,
}
impl $name {
pub fn new(_nid: &NodeId) -> Self {
Self { slew1: SlewValue::new(), slew2: SlewValue::new(), slew3: SlewValue::new() }
}
pub const slew: &'static str = "ExtA-F slew\nSlew limiter for the 3 parameters\nRange: (0..1)";
pub const atv1: &'static str = "ExtA-F atv1\nAttenuverter for the A1 parameter\nRange: (-1..1)";
pub const atv2: &'static str = "ExtA-F atv2\nAttenuverter for the A2 parameter\nRange: (-1..1)";
pub const atv3: &'static str = "ExtA-F atv3\nAttenuverter for the A3 parameter\nRange: (-1..1)";
pub const sig1: &'static str = "ExtA-F sig1\nA-F1 output channel\nRange: (0..1)";
pub const sig2: &'static str = "ExtA-F sig2\nA-F2 output channel\nRange: (0..1)";
pub const sig3: &'static str = "ExtA-F sig3\nA-F3 output channel\nRange: (0..1)";
pub const DESC: &'static str = "Ext. Parameter Set A-F Input\n\n\
This node gives access to the 24 input parameters of the HexoSynth VST3/CLAP plugin. \
A 'slew' limiter allows you to smooth out quick changes a bit if you need it. \
Attenuverters (attenuators that can also invert) allow to reduce the amplitude \
or invert the signal.";
pub const HELP: &'static str = r#"External Parameter Set A-F Input
This node gives access to the 24 input parameters of the
HexoSynth VST3/CLAP plugin. A 'slew' limiter allows you to smooth out quick
changes a bit if you need it. Attenuverters (attenuators that can also invert)
allow to reduce the amplitude or invert the signal.
All instances of the nodes 'ExtA', 'ExtB', ..., 'ExtF' have access to the same
3 input parameters (A1-A3, B1-B3, ..., F1-F3). That means there is no
difference whether you use the same instance of different ones.
Except that you can of course set the `atv` and `slew` parameters to different
values.
If you absolutely need more parameters to control the HexoSynth patch:
Keep in mind, that there is also the 'MidiCC' node, that allows HexoSynth to
react to MIDI CC messages.
"#;
}
impl DspNode for $name {
fn outputs() -> usize {
0
}
fn set_sample_rate(&mut self, _srate: f32) {}
fn reset(&mut self) {}
#[inline]
fn process<T: NodeAudioContext>(
&mut self,
ctx: &mut T,
ectx: &mut NodeExecContext,
_nctx: &NodeContext,
_atoms: &[SAtom],
inputs: &[ProcBuf],
outputs: &mut [ProcBuf],
ctx_vals: LedPhaseVals,
) {
let slew = inp::$name::slew(inputs);
let atv1 = inp::$name::atv1(inputs);
let atv2 = inp::$name::atv2(inputs);
let atv3 = inp::$name::atv3(inputs);
let sig2_i = out_idx::$name::sig2();
let (sig1, r) = outputs.split_at_mut(sig2_i);
let (sig2, sig3) = r.split_at_mut(1);
let sig1 = &mut sig1[0];
let sig2 = &mut sig2[0];
let sig3 = &mut sig3[0];
if let Some(params) = &ectx.ext_param {
let p1 = params.$p1();
let p2 = params.$p2();
let p3 = params.$p3();
for frame in 0..ctx.nframes() {
let slew_ms = denorm::$name::slew(slew, frame);
sig1.write(
frame,
denorm::$name::atv1(atv1, frame) * self.slew1.next(p1, slew_ms),
);
sig2.write(
frame,
denorm::$name::atv2(atv2, frame) * self.slew2.next(p2, slew_ms),
);
sig3.write(
frame,
denorm::$name::atv3(atv3, frame) * self.slew3.next(p3, slew_ms),
);
}
}
let last_frame = ctx.nframes() - 1;
ctx_vals[0].set(sig1.read(last_frame));
}
}
}
}
define_ext! {ExtA, a1, a2, a3}
define_ext! {ExtB, b1, b2, b3}
define_ext! {ExtC, c1, c2, c3}
define_ext! {ExtD, d1, d2, d3}
define_ext! {ExtE, e1, e2, e3}
define_ext! {ExtF, f1, f2, f3}

View file

@ -15,7 +15,6 @@ macro_rules! fa_midicc_cc {
}};
}
/// The (stereo) output port of the plugin
#[derive(Debug, Clone)]
pub struct MidiCC {
cur_cc1: f32,

View file

@ -64,6 +64,7 @@ And following DSP nodes:
| IO Util | Scope | Oscilloscope for up to 3 channels |
| IO Util | MidiP | MIDI Pitch/Note input from plugin host, DAW or hardware |
| IO Util | MidiCC | MIDI CC input from plugin host, DAW or hardware |
| IO Util | ExtA - ExtF | Access to plugin parameter sets A to F |
## API Examples

View file

@ -18,6 +18,7 @@ use std::io::Write;
use ringbuf::{Consumer, Producer};
use std::sync::Arc;
#[cfg(not(target_arch = "wasm32"))]
use core::arch::x86_64::{
_MM_FLUSH_ZERO_ON,
// _MM_FLUSH_ZERO_OFF,
@ -175,12 +176,66 @@ impl Default for FeedbackBuffer {
}
}
/// This trait needs to be implemented by the caller of the [NodeExecutor]
/// if it wants to provide the parameters for the "ExtA" to "ExtL" nodes.
pub trait ExternalParams: Send + Sync {
fn a1(&self) -> f32;
fn a2(&self) -> f32;
fn a3(&self) -> f32;
fn b1(&self) -> f32 {
self.a1()
}
fn b2(&self) -> f32 {
self.a2()
}
fn b3(&self) -> f32 {
self.a3()
}
fn c1(&self) -> f32 {
self.a1()
}
fn c2(&self) -> f32 {
self.a2()
}
fn c3(&self) -> f32 {
self.a3()
}
fn d1(&self) -> f32 {
self.a1()
}
fn d2(&self) -> f32 {
self.a2()
}
fn d3(&self) -> f32 {
self.a3()
}
fn e1(&self) -> f32 {
self.a1()
}
fn e2(&self) -> f32 {
self.a2()
}
fn e3(&self) -> f32 {
self.a3()
}
fn f1(&self) -> f32 {
self.a1()
}
fn f2(&self) -> f32 {
self.a2()
}
fn f3(&self) -> f32 {
self.a3()
}
}
/// Contains global state that all nodes can access.
/// This is used for instance to implement the feedbackd delay nodes.
pub struct NodeExecContext {
pub feedback_delay_buffers: Vec<FeedbackBuffer>,
pub midi_notes: Vec<HxTimedEvent>,
pub midi_ccs: Vec<HxTimedEvent>,
pub ext_param: Option<Arc<dyn ExternalParams>>,
}
impl NodeExecContext {
@ -189,7 +244,7 @@ impl NodeExecContext {
fbdb.resize_with(MAX_ALLOCATED_NODES, FeedbackBuffer::new);
let midi_notes = Vec::with_capacity(MAX_MIDI_NOTES_PER_BLOCK);
let midi_ccs = Vec::with_capacity(MAX_MIDI_CC_PER_BLOCK);
Self { feedback_delay_buffers: fbdb, midi_notes, midi_ccs }
Self { feedback_delay_buffers: fbdb, midi_notes, midi_ccs, ext_param: None }
}
fn set_sample_rate(&mut self, srate: f32) {
@ -271,6 +326,7 @@ impl NodeExecutor {
GraphMessage::NewProg { prog, copy_old_out } => {
let mut prev_prog = std::mem::replace(&mut self.prog, prog);
#[cfg(not(target_arch = "wasm32"))]
unsafe {
_MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON);
}
@ -361,6 +417,10 @@ impl NodeExecutor {
}
}
pub fn set_external_params(&mut self, ext_param: Arc<dyn ExternalParams>) {
self.exec_ctx.ext_param = Some(ext_param);
}
pub fn set_sample_rate(&mut self, sample_rate: f32) {
self.sample_rate = sample_rate;
self.exec_ctx.set_sample_rate(sample_rate);

View file

@ -59,7 +59,7 @@ fn check_node_code_state() {
put_v(&mut block_fun, 0, 0, 3, "value", "220.0");
put_n(&mut block_fun, 0, 1, 2, "phase");
block_fun.shift_port(0, 1, 2, 0, false); // move reset up
block_fun.shift_port(0, 1, 2, 0, true); // move output down
block_fun.shift_port(0, 1, 2, 0, true); // move output down
put_v(&mut block_fun, 0, 1, 4, "value", "2.0");
put_n(&mut block_fun, 0, 2, 3, "*");
put_n(&mut block_fun, 0, 3, 2, "-");

143
tests/node_exta.rs Normal file
View file

@ -0,0 +1,143 @@
// 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::*;
struct MyParams {}
impl hexodsp::nodes::ExternalParams for MyParams {
fn a1(&self) -> f32 {
0.23
}
fn a2(&self) -> f32 {
0.44
}
fn a3(&self) -> f32 {
-0.33
}
}
#[test]
fn check_node_exta() {
let (node_conf, mut node_exec) = new_node_engine();
let mut matrix = Matrix::new(node_conf, 3, 3);
let myparams = std::sync::Arc::new(MyParams {});
let mut chain = MatrixCellChain::new(CellDir::B);
chain.node_out("exta", "sig1").node_inp("out", "ch1").place(&mut matrix, 0, 0).unwrap();
let mut chain = MatrixCellChain::new(CellDir::B);
chain.node_out("exta", "sig3").node_inp("out", "ch2").place(&mut matrix, 1, 0).unwrap();
matrix.sync().unwrap();
node_exec.set_external_params(myparams);
let (ch1, ch2) = node_exec.test_run(0.1, false, &[]);
assert_decimated_feq!(ch1, 10, vec![0.23; 100]);
assert_decimated_feq!(ch2, 10, vec![-0.33; 100]);
node_pset_n(&mut matrix, "exta", 0, "atv1", -1.0);
node_pset_n(&mut matrix, "exta", 0, "atv3", 0.5);
let (ch1, ch2) = node_exec.test_run(0.1, false, &[]);
assert_decimated_feq!(
ch1,
80,
vec![
0.22895692,
0.14551038,
0.062063817,
-0.021382917,
-0.10482957,
-0.18827613,
-0.23,
-0.23,
-0.23,
-0.23,
-0.23
]
);
assert_decimated_feq!(
ch2,
80,
vec![
-0.32962584,
-0.29969355,
-0.26976123,
-0.23982893,
-0.20989662,
-0.17996432,
-0.165,
-0.165,
-0.165,
-0.165,
-0.165,
-0.165,
-0.165
]
);
}
#[test]
fn check_node_exta_slew() {
let (node_conf, mut node_exec) = new_node_engine();
let mut matrix = Matrix::new(node_conf, 3, 3);
let myparams = std::sync::Arc::new(MyParams {});
let mut chain = MatrixCellChain::new(CellDir::B);
chain
.node_out("exta", "sig1")
.set_denorm("slew", 40.0)
.node_inp("out", "ch1")
.place(&mut matrix, 0, 0)
.unwrap();
let mut chain = MatrixCellChain::new(CellDir::B);
chain
.node_out("exta", "sig3")
.set_denorm("slew", 40.0)
.node_inp("out", "ch2")
.place(&mut matrix, 1, 0)
.unwrap();
matrix.sync().unwrap();
node_exec.set_external_params(myparams);
let (ch1, ch2) = node_exec.test_run(0.1, false, &[]);
assert_decimated_feq!(
ch1,
80,
vec![
0.00056689343,
0.045918357,
0.09126975,
0.13662128,
0.18197326,
0.22732525,
0.23,
0.23,
0.23,
0.23,
0.23
]
);
assert_decimated_feq!(
ch2,
80,
vec![
-0.00056689343,
-0.045918357,
-0.09126975,
-0.13662128,
-0.18197326,
-0.22732525,
-0.2726772,
-0.3180292,
-0.33,
-0.33,
-0.33
]
);
}