pub struct NodeConfigurator {Show 14 fields
pub(crate) nodes: Vec<(NodeInfo, Option<NodeInstance>, Node)>,
pub(crate) node2idx: HashMap<NodeId, usize>,
pub(crate) shared: SharedNodeConf,
pub(crate) node_global: NodeGlobalRef,
feedback_filter: FeedbackFilter,
sample_lib: SampleLibrary,
errors: Vec<String>,
params: HashMap<ParamId, NodeInputParam>,
param_values: HashMap<ParamId, f32>,
param_modamt: HashMap<ParamId, Option<f32>>,
atoms: HashMap<ParamId, NodeInputAtom>,
atom_values: HashMap<ParamId, SAtom>,
output_fb_values: Vec<f32>,
output_fb_cons: Option<Output<Vec<f32>>>,
}
Expand description
This struct holds the frontend node configuration.
It stores which nodes are allocated and where. Allocation of new nodes is done here, and parameter management and synchronization is also done by this. It generally acts as facade for the executed node graph in the backend.
This API is the most low level API provided by HexoDSP.
It shows how to create nodes and connect them.
The execution of the nodes in the audio thread is
controlled by a NodeProg
, which defines the order
the nodes are executed in.
This only showcases the non-realtime generation of audio samples. For a real time application of this library please refer to the examples that come with this library.
use hexodsp::*;
let (mut node_conf, mut node_exec) = new_node_engine();
node_conf.create_node(NodeId::Sin(0));
node_conf.create_node(NodeId::Amp(0));
let mut prog = node_conf.rebuild_node_ports();
node_conf.add_prog_node(&mut prog, &NodeId::Sin(0));
node_conf.add_prog_node(&mut prog, &NodeId::Amp(0));
node_conf.set_prog_node_exec_connection(
&mut prog,
(NodeId::Amp(0), NodeId::Amp(0).inp("inp").unwrap()),
(NodeId::Sin(0), NodeId::Sin(0).out("sig").unwrap()));
node_conf.upload_prog(prog, true);
let (out_l, out_r) = node_exec.test_run(0.1, false, &[]);
Fields
nodes: Vec<(NodeInfo, Option<NodeInstance>, Node)>
Holds all the nodes, their parameters and type.
node2idx: HashMap<NodeId, usize>
An index of all nodes ever instanciated. Be aware, that currently there is no cleanup implemented. That means, any instanciated NodeId will persist throughout the whole runtime. A garbage collector might be implemented when saving presets.
The shared parts of the NodeConfigurator and the crate::nodes::NodeExecutor.
node_global: NodeGlobalRef
Reference to the crate::NodeGlobalData that is used to initialize the crate::dsp::DspNode instances creates by this NodeConfigurator.
feedback_filter: FeedbackFilter
sample_lib: SampleLibrary
Loads and Caches audio samples that are set as parameters for nodes.
errors: Vec<String>
Error messages:
params: HashMap<ParamId, NodeInputParam>
Contains (automateable) parameters
param_values: HashMap<ParamId, f32>
Stores the most recently set parameter values
param_modamt: HashMap<ParamId, Option<f32>>
Stores the modulation amount of a parameter
atoms: HashMap<ParamId, NodeInputAtom>
Contains non automateable atom data for the nodes
atom_values: HashMap<ParamId, SAtom>
Stores the most recently set atoms
output_fb_values: Vec<f32>
Holds a copy of the most recently updated output port feedback values. Update this by calling NodeConfigurator::update_output_feedback.
output_fb_cons: Option<Output<Vec<f32>>>
Holds the channel to the backend that sends output port feedback. This is queried by NodeConfigurator::update_output_feedback.
Implementations
sourceimpl NodeConfigurator
impl NodeConfigurator
pub(crate) fn new() -> (Self, SharedNodeExec)
pub fn for_each<F: FnMut(&NodeInfo, NodeId, usize)>(&self, f: F)
pub fn pop_error(&mut self) -> Option<String>
pub fn unique_index_for(&self, ni: &NodeId) -> Option<usize>
pub(crate) fn node_by_id(
&self,
ni: &NodeId
) -> Option<&(NodeInfo, Option<NodeInstance>, Node)>
pub(crate) fn node_by_id_mut(
&mut self,
ni: &NodeId
) -> Option<&mut (NodeInfo, Option<NodeInstance>, Node)>
sourcepub fn get_param_modamt(&self, param: &ParamId) -> Option<f32>
pub fn get_param_modamt(&self, param: &ParamId) -> Option<f32>
Returns the current modulation amount of the given parameter.
Returns None
if no modulation amount if set and thus no
implicit attenuverter is set.
sourcepub fn set_param_modamt(&mut self, param: ParamId, v: Option<f32>) -> bool
pub fn set_param_modamt(&mut self, param: ParamId, v: Option<f32>) -> bool
Set the modulation amount of a parameter. Returns true if a new NodeProg needs to be created, which can be necessary if there was no modulation amount assigned to this parameter yet.
sourcepub fn get_param(&self, param: &ParamId) -> Option<SAtom>
pub fn get_param(&self, param: &ParamId) -> Option<SAtom>
Retrieve SAtom values for input parameters and atoms.
sourcepub fn set_param(&mut self, param: ParamId, at: SAtom)
pub fn set_param(&mut self, param: ParamId, at: SAtom)
Assign SAtom values to input parameters and atoms.
Only updates the DSP backend if NodeConfigurator::rebuild_node_ports was called before calling this. If no graph or the corresponding parameter is not active yet, then the value will be remembered until NodeConfigurator::rebuild_node_ports is called.
sourcepub fn dump_param_values(
&self
) -> (Vec<(ParamId, f32, Option<f32>)>, Vec<(ParamId, SAtom)>)
pub fn dump_param_values(
&self
) -> (Vec<(ParamId, f32, Option<f32>)>, Vec<(ParamId, SAtom)>)
Dumps all set parameters (inputs and atoms). Most useful for serialization and saving patches.
sourcepub fn load_dumped_param_values(
&mut self,
params: &[(ParamId, f32, Option<f32>)],
atoms: &[(ParamId, SAtom)],
normalize_params: bool
)
pub fn load_dumped_param_values(
&mut self,
params: &[(ParamId, f32, Option<f32>)],
atoms: &[(ParamId, SAtom)],
normalize_params: bool
)
Loads parameter values from a dump. You will still need to upload a new NodeProg which contains these values.
sourcepub fn for_each_param<F: FnMut(usize, ParamId, &SAtom, Option<f32>)>(&self, f: F)
pub fn for_each_param<F: FnMut(usize, ParamId, &SAtom, Option<f32>)>(&self, f: F)
Iterates over every parameter and calls the given function with it’s current value.
sourcepub fn phase_value_for(&self, ni: &NodeId) -> f32
pub fn phase_value_for(&self, ni: &NodeId) -> f32
Returns the current phase value of the given node.
It usually returns something like the position of a sequencer or the phase of an oscillator.
sourcepub fn led_value_for(&self, ni: &NodeId) -> f32
pub fn led_value_for(&self, ni: &NodeId) -> f32
Returns the current status LED value of the given node.
A status LED might be anything a specific node deems the most important value. Often it might be just the current value of the primary signal output.
sourcepub fn update_filters(&mut self)
pub fn update_filters(&mut self)
Triggers recalculation of the filtered values from the current LED values and output feedback.
This function internally calls NodeConfigurator::update_output_feedback for you, so you don’t need to call it yourself.
See also NodeConfigurator::filtered_led_for and NodeConfigurator::filtered_out_fb_for.
sourcepub fn filtered_led_for(&mut self, ni: &NodeId) -> (f32, f32)
pub fn filtered_led_for(&mut self, ni: &NodeId) -> (f32, f32)
Returns a filtered LED value that is smoothed a bit and provides a min and max value.
Make sure to call NodeConfigurator::update_filters before calling this function, or the values won’t be up to date.
use hexodsp::*;
let (mut node_conf, mut node_exec) = new_node_engine();
node_conf.create_node(NodeId::Sin(0));
node_conf.create_node(NodeId::Amp(0));
let mut prog = node_conf.rebuild_node_ports();
node_conf.add_prog_node(&mut prog, &NodeId::Sin(0));
node_conf.add_prog_node(&mut prog, &NodeId::Amp(0));
node_conf.set_prog_node_exec_connection(
&mut prog,
(NodeId::Amp(0), NodeId::Amp(0).inp("inp").unwrap()),
(NodeId::Sin(0), NodeId::Sin(0).out("sig").unwrap()));
node_conf.upload_prog(prog, true);
node_exec.test_run(0.1, false, &[]);
assert!((node_conf.led_value_for(&NodeId::Sin(0)) - (-0.062522)).abs() < 0.001);
assert!((node_conf.led_value_for(&NodeId::Amp(0)) - (-0.062522)).abs() < 0.001);
for _ in 0..10 {
node_exec.test_run(0.1, false, &[]);
node_conf.update_filters();
node_conf.filtered_led_for(&NodeId::Sin(0));
node_conf.filtered_led_for(&NodeId::Amp(0));
}
assert_eq!((node_conf.filtered_led_for(&NodeId::Sin(0)).0 * 1000.0).floor() as i64, 62);
assert_eq!((node_conf.filtered_led_for(&NodeId::Amp(0)).0 * 1000.0).floor() as i64, 62);
sourcepub fn filtered_out_fb_for(&mut self, node_id: &NodeId, out: u8) -> (f32, f32)
pub fn filtered_out_fb_for(&mut self, node_id: &NodeId, out: u8) -> (f32, f32)
Returns a filtered output port value that is smoothed a bit and provides a min and max value.
Make sure to call NodeConfigurator::update_filters before calling this function, or the values won’t be up to date. That function also calls NodeConfigurator::update_output_feedback for you conveniently.
For an example on how to use see NodeConfigurator::filtered_led_for which has the same semantics as this function.
sourcepub fn monitor(
&mut self,
node_id: &NodeId,
inputs: &[Option<u8>],
outputs: &[Option<u8>]
)
pub fn monitor(
&mut self,
node_id: &NodeId,
inputs: &[Option<u8>],
outputs: &[Option<u8>]
)
Monitor the given inputs and outputs of a specific node.
The monitor data can be retrieved using NodeConfigurator::get_minmax_monitor_samples.
pub fn get_node_global(&self) -> NodeGlobalRef
pub fn delete_nodes(&mut self)
pub fn create_node(&mut self, ni: NodeId) -> Option<(&NodeInfo, u8)>
sourcepub fn unused_instance_node_id(&self, id: NodeId) -> NodeId
pub fn unused_instance_node_id(&self, id: NodeId) -> NodeId
Returns the first instance of the given NodeId (starting with the instance of the NodeId) that has not been used.
Primarily used by the (G)UI when creating new nodes to be added to the graph.
Should be called after the NodeProg has been created (and after NodeConfigurator::rebuild_node_ports was called).
If new nodes were created/deleted/reordered in between this function might not work properly and assign already used instances.
sourcepub fn rebuild_node_ports(&mut self) -> NodeProg
pub fn rebuild_node_ports(&mut self) -> NodeProg
Rebuilds Input/Output/Atom indices for the nodes, which is necessary if nodes were created/deleted or reordered. It also assigns input parameter and atom values for new nodes.
Returns a new NodeProg with space for all allocated nodes inputs, outputs and atoms.
Execute this after a NodeConfigurator::create_node.
sourcepub fn add_prog_node(&mut self, prog: &mut NodeProg, node_id: &NodeId)
pub fn add_prog_node(&mut self, prog: &mut NodeProg, node_id: &NodeId)
Creates a new NodeOp and add it to the NodeProg.
It will fail silently if the nodes have not been created yet or NodeConfigurator::rebuild_node_ports was not called before. So make sure this is the case or don’t expect the node and input to be executed.
sourcepub fn set_prog_node_exec_connection(
&mut self,
prog: &mut NodeProg,
node_input: (NodeId, u8),
adjacent_output: (NodeId, u8)
)
pub fn set_prog_node_exec_connection(
&mut self,
prog: &mut NodeProg,
node_input: (NodeId, u8),
adjacent_output: (NodeId, u8)
)
Adds an adjacent output connection to the given node input. Will either create a new NodeOp in the NodeProg or append to an existing one. This means the order you set the to be executed node connections, is the order the NodeProg is going to be executed by the DSP thread later.
It will fail silently if the nodes have not been created yet or NodeConfigurator::rebuild_node_ports was not called before. So make sure this is the case or don’t expect the node and input to be executed.
sourcepub fn upload_prog(&mut self, prog: NodeProg, copy_old_out: bool)
pub fn upload_prog(&mut self, prog: NodeProg, copy_old_out: bool)
Uploads a new NodeProg instance.
Create a new NodeProg instance with NodeConfigurator::rebuild_node_ports for each call to this function. Otherwise things like the NodeConfigurator::out_fb_for might not work properly!
The copy_old_out
parameter should be set if there are only
new nodes appended to the end of the node instances.
It helps to prevent clicks when there is a feedback path somewhere.
It must not be set when a completely new set of node instances was created, for instance when a completely new patch was loaded.
Here is an example on how to use the NodeConfigurator directly to setup and upload a NodeProg:
use hexodsp::*;
let (mut node_conf, mut node_exec) = new_node_engine();
node_conf.create_node(NodeId::Sin(0));
node_conf.create_node(NodeId::Amp(0));
let mut prog = node_conf.rebuild_node_ports();
node_conf.add_prog_node(&mut prog, &NodeId::Sin(0));
node_conf.add_prog_node(&mut prog, &NodeId::Amp(0));
node_conf.set_prog_node_exec_connection(
&mut prog,
(NodeId::Amp(0), NodeId::Amp(0).inp("inp").unwrap()),
(NodeId::Sin(0), NodeId::Sin(0).out("sig").unwrap()));
node_conf.upload_prog(prog, true);
sourcepub fn out_fb_for(&self, node_id: &NodeId, out: u8) -> Option<f32>
pub fn out_fb_for(&self, node_id: &NodeId, out: u8) -> Option<f32>
Retrieves the feedback value for a specific output port of the given NodeId. You need to call NodeConfigurator::update_output_feedback before this, or otherwise your output values might be outdated or not available at all.
See also NodeConfigurator::filtered_out_fb_for for a filtered variant suitable for UI usage.
sourcepub fn update_output_feedback(&mut self)
pub fn update_output_feedback(&mut self)
Checks if the backend has new output feedback values. Call this function for each frame of the UI to get the most up to date output feedback values that are available.
Retrieve the output value by calling NodeConfigurator::out_fb_for.
pub fn get_minmax_monitor_samples(&mut self, idx: usize) -> &MinMaxMonitorSamples
sourcepub fn inject_midi_event(&mut self, midi_ev: HxMidiEvent)
pub fn inject_midi_event(&mut self, midi_ev: HxMidiEvent)
Injects a HxMidiEvent directly into audio thread, so that it can trickle back to the GUI thread the standard way. This is mostly used for automated testing. And maybe some day for some kind of remote control script from WLambda?
sourcepub fn next_event(&mut self) -> Option<GraphEvent>
pub fn next_event(&mut self) -> Option<GraphEvent>
Returns the next GraphEvent from the DSP/audio/backend thread.