pub struct Matrix {
config: NodeConfigurator,
matrix: Vec<Cell>,
w: usize,
h: usize,
graph_ordering: NodeGraphOrdering,
saved_matrix: Option<Vec<Cell>>,
edges: Vec<Edge>,
properties: HashMap<String, SAtom>,
assigned_inputs: HashSet<ParamId>,
monitored_cell: Cell,
gen_counter: usize,
observer: Option<Arc<dyn MatrixObserver>>,
}
Fields
config: NodeConfigurator
The node configurator to control the backend.
matrix: Vec<Cell>
Holds the actual 2 dimensional matrix cells in one big vector.
w: usize
Width of the matrix.
h: usize
Height of the matrix.
graph_ordering: NodeGraphOrdering
The retained data structure of the graph topology.
This is used by sync()
and check()
to determine the
order and cycle freeness of the graph.
We store it in this field, so we don’t have to reallocate it
all the time.
saved_matrix: Option<Vec<Cell>>
Holds a saved version of the matrix
field
to roll back changes that might introduce cycles or
other invalid topology.
edges: Vec<Edge>
Stores the edges which are extracted from the matrix
field
by Matrix::update_graph_ordering_and_edges, which is used
by Matrix::sync and Matrix::check.
properties: HashMap<String, SAtom>
Holds custom user defined properties. They are saved with the MatrixRepr and you can set and retrieve these properties using Matrix::set_prop and Matrix::get_prop.
assigned_inputs: HashSet<ParamId>
Stores the crate::dsp::ParamId of the inputs that have an output assigned to them. It’s updates when Matrix::edges is updated and used by Matrix::param_input_is_used to return whether a parameter is controlled by some output port.
monitored_cell: Cell
Holds the currently monitored cell.
gen_counter: usize
A counter that increases for each sync(), it can be used by other components of the application to detect changes in the matrix to resync their own data.
observer: Option<Arc<dyn MatrixObserver>>
A trait object that tracks changed on the Matrix.
Implementations
sourceimpl Matrix
impl Matrix
pub fn new(config: NodeConfigurator, w: usize, h: usize) -> Self
sourcepub fn set_observer(&mut self, obs: Arc<dyn MatrixObserver>)
pub fn set_observer(&mut self, obs: Arc<dyn MatrixObserver>)
Assigns the MatrixObserver to observe changes on the Matrix.
pub fn size(&self) -> (usize, usize)
pub fn unique_index_for(&self, node_id: &NodeId) -> Option<usize>
pub fn info_for(&self, node_id: &NodeId) -> Option<NodeInfo>
pub fn phase_value_for(&self, node_id: &NodeId) -> f32
pub fn led_value_for(&self, node_id: &NodeId) -> f32
pub fn update_filters(&mut self)
pub fn filtered_led_for(&mut self, ni: &NodeId) -> (f32, f32)
pub fn filtered_out_fb_for(&mut self, ni: &NodeId, out: u8) -> (f32, f32)
sourcepub fn get_pattern_data(
&self,
tracker_id: usize
) -> Option<Arc<Mutex<PatternData>>>
pub fn get_pattern_data(
&self,
tracker_id: usize
) -> Option<Arc<Mutex<PatternData>>>
Returns the PatternData handle for the tracker tracker_id
.
Implicitly allocates the [Tracker] instance.
sourcepub fn has_tracker(&self, tracker_id: usize) -> bool
pub fn has_tracker(&self, tracker_id: usize) -> bool
Returns true if the tracker or pattern data for tracker_id
has been allocated/used yet.
sourcepub fn get_scope_handle(&self, scope: usize) -> Option<Arc<ScopeHandle>>
pub fn get_scope_handle(&self, scope: usize) -> Option<Arc<ScopeHandle>>
Retrieve the oscilloscope handle for the scope index scope
.
sourcepub fn check_pattern_data(&mut self, tracker_id: usize)
pub fn check_pattern_data(&mut self, tracker_id: usize)
Checks if there are any updates to send for the pattern data that belongs to the
tracker tracker_id
. Call this repeatedly, eg. once per frame in a GUI, in case the user
modified the pattern data. It will make sure that the modifications are sent to the
audio thread.
sourcepub fn check_block_function(
&mut self,
id: usize
) -> Result<(), BlkJITCompileError>
pub fn check_block_function(
&mut self,
id: usize
) -> Result<(), BlkJITCompileError>
Checks the block function for the id id
. If the block function did change,
updates are then sent to the audio thread.
See also Matrix::get_block_function.
sourcepub fn has_code_engine(&self, id: usize) -> bool
pub fn has_code_engine(&self, id: usize) -> bool
Returns true if the code engine or block function for tracker_id
has been allocated/used yet.
sourcepub fn get_block_function(&self, id: usize) -> Option<Arc<Mutex<BlockFun>>>
pub fn get_block_function(&self, id: usize) -> Option<Arc<Mutex<BlockFun>>>
Retrieve a handle to the block function id
. In case you modify the block function,
make sure to call Matrix::check_block_function.
sourcepub fn save_matrix(&mut self)
pub fn save_matrix(&mut self)
Saves the state of the hexagonal grid layout. This is usually used together with Matrix::check and Matrix::restore_matrix to try if changes on the matrix using Matrix::place (or other grid changing functions).
It is advised to use convenience functions such as Matrix::change_matrix.
See also Matrix::change_matrix, Matrix::check and Matrix::sync.
sourcepub fn restore_matrix(&mut self)
pub fn restore_matrix(&mut self)
Restores the previously via Matrix::save_matrix saved matrix.
It is advised to use convenience functions such as Matrix::change_matrix.
See also Matrix::change_matrix, Matrix::check.
sourcepub fn change_matrix<F>(&mut self, f: F) -> Result<(), MatrixError>where
F: FnMut(&mut Self),
pub fn change_matrix<F>(&mut self, f: F) -> Result<(), MatrixError>where
F: FnMut(&mut Self),
Helps encapsulating changes of the matrix and wraps them into a Matrix::save_matrix, Matrix::check and Matrix::restore_matrix.
use hexodsp::*;
let (node_conf, mut node_exec) = new_node_engine();
let mut matrix = Matrix::new(node_conf, 3, 3);
let res = matrix.change_matrix(|matrix| {
matrix.place(0, 1,
Cell::empty(NodeId::Sin(1))
.input(Some(0), None, None));
matrix.place(0, 0,
Cell::empty(NodeId::Sin(1))
.out(None, None, Some(0)));
});
// In this examples case there is an error, as we created
// a cycle:
assert!(res.is_err());
sourcepub fn change_matrix_err<F>(&mut self, f: F) -> Result<(), MatrixError>where
F: FnMut(&mut Self) -> Result<(), MatrixError>,
pub fn change_matrix_err<F>(&mut self, f: F) -> Result<(), MatrixError>where
F: FnMut(&mut Self) -> Result<(), MatrixError>,
Like Matrix::change_matrix but the function passed to this
needs to return a Result<(), MatrixError>
.
sourcepub fn place_multiple(&mut self, cells: &[Cell]) -> Result<(), MatrixError>
pub fn place_multiple(&mut self, cells: &[Cell]) -> Result<(), MatrixError>
Tries to place all cells
at once, if they are placed in empty
cells only! Returns an error of the destination cell is not empty
or out of range, or if the placement of the cluster results in any
other inconsistencies.
This action must be wrapped with Matrix::change_matrix_err!
Restores the matrix to the previous state if placing fails.
sourcepub fn place(&mut self, x: usize, y: usize, cell: Cell)
pub fn place(&mut self, x: usize, y: usize, cell: Cell)
Inserts a cell into the hexagonal grid of the matrix. You have to make sure that the resulting DSP graph topology does not have cycles, otherwise an upload to the DSP thread via Matrix::sync will fail.
If you try to place a cell outside the grid, it will not be placed and just silently ignored.
You can safely check the DSP topology of changes using the convenience function Matrix::change_matrix or alternatively: Matrix::save_matrix, Matrix::restore_matrix and Matrix::check.
See also the example in Matrix::change_matrix and Matrix::check.
sourcepub fn place_cell(&mut self, cell: Cell)
pub fn place_cell(&mut self, cell: Cell)
Set the cell at it’s assigned position. This is basically a shorthand
for Matrix::place. As if you would call:
m.place(cell.pos().0, cell.pos().1, cell)
.
sourcepub fn for_each_atom<F: FnMut(usize, ParamId, &SAtom, Option<f32>)>(&self, f: F)
pub fn for_each_atom<F: FnMut(usize, ParamId, &SAtom, Option<f32>)>(&self, f: F)
Iterates through all atoms. This is useful for reading all the atoms after a MatrixRepr has been loaded with Matrix::from_repr.
sourcepub fn get_generation(&self) -> usize
pub fn get_generation(&self) -> usize
Returns the DSP graph generation, which is increased after each call to Matrix::sync.
This can be used by external components to track if they should update their knowledge of the nodes in the DSP graph. Such as parameter values.
HexoSynth for instance updates the UI by tracking this value.
sourcepub fn to_repr(&self) -> MatrixRepr
pub fn to_repr(&self) -> MatrixRepr
Returns a serializable representation of the matrix. This representation contains all parameters, created nodes, connections and the tracker’s pattern data.
use hexodsp::*;
let (node_conf, mut _node_exec) = new_node_engine();
let mut matrix = Matrix::new(node_conf, 3, 3);
let sin = NodeId::Sin(2);
matrix.place(0, 0,
Cell::empty(sin)
.out(None, Some(0), None));
let freq_param = sin.inp_param("freq").unwrap();
matrix.set_param(freq_param, SAtom::param(-0.1));
let mut serialized = matrix.to_repr().serialize().to_string();
assert!(serialized.find("\"sin\",2,0,0,[-1,-1,-1],[-1,\"sig\",-1]").is_some());
assert!(serialized.find("\"freq\",220.0").is_some());
See also MatrixRepr::serialize.
sourcepub fn from_repr(&mut self, repr: &MatrixRepr) -> Result<(), MatrixError>
pub fn from_repr(&mut self, repr: &MatrixRepr) -> Result<(), MatrixError>
Loads the matrix from a previously my Matrix::to_repr generated matrix representation.
This function will call Matrix::sync after loading and overwriting the current matrix contents.
sourcepub fn set_prop(&mut self, key: &str, val: SAtom)
pub fn set_prop(&mut self, key: &str, val: SAtom)
Saves a property in the matrix, these can be retrieved using Matrix::get_prop and are saved/loaded along with the MatrixRepr. See also Matrix::to_repr and Matrix::from_repr.
use hexodsp::*;
let repr = {
let (node_conf, mut _node_exec) = new_node_engine();
let mut matrix = Matrix::new(node_conf, 3, 3);
matrix.set_prop("test", SAtom::setting(31337));
matrix.to_repr()
};
let (node_conf, mut _node_exec) = new_node_engine();
let mut matrix2 = Matrix::new(node_conf, 3, 3);
matrix2.from_repr(&repr).unwrap();
assert_eq!(matrix2.get_prop("test").unwrap().i(), 31337);
sourcepub fn get_prop(&mut self, key: &str) -> Option<&SAtom>
pub fn get_prop(&mut self, key: &str) -> Option<&SAtom>
Retrieves a matrix property. See also Matrix::set_prop for an example and more information.
sourcepub fn get_minmax_monitor_samples(&mut self, idx: usize) -> &MinMaxMonitorSamples
pub fn get_minmax_monitor_samples(&mut self, idx: usize) -> &MinMaxMonitorSamples
Receives the most recent data for the monitored signal at index idx
.
Might introduce a short wait, because internally a mutex is still locked.
If this leads to stuttering in the UI, we need to change the internal
handling to a triple buffer.
sourcepub fn monitored_cell(&self) -> &Cell
pub fn monitored_cell(&self) -> &Cell
Returns the currently monitored cell.
sourcepub fn monitor_cell(&mut self, cell: Cell)
pub fn monitor_cell(&mut self, cell: Cell)
Sets the cell to monitor next. Please bear in mind, that you need to
call sync
before retrieving the cell from the matrix, otherwise
the node instance might not have been created in the backend yet and
we can not start monitoring the cell.
sourcefn remonitor_cell(&mut self)
fn remonitor_cell(&mut self)
Is called by Matrix::sync to refresh the monitored cell. In case the matrix has changed (inputs/outputs of a cell) we show the current state.
Note, that if the UI actually moved a cell, it needs to monitor the newly moved cell anyways.
pub fn pop_error(&mut self) -> Option<String>
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.
sourcepub fn get_param_modamt(&self, param: &ParamId) -> Option<f32>
pub fn get_param_modamt(&self, param: &ParamId) -> Option<f32>
Retrieve the modulation amount of the input parameter.
sourcepub fn set_param_modamt(
&mut self,
param: ParamId,
modamt: Option<f32>
) -> Result<(), MatrixError>
pub fn set_param_modamt(
&mut self,
param: ParamId,
modamt: Option<f32>
) -> Result<(), MatrixError>
Assign or remove modulation of an input parameter.
pub fn get_adjacent_output(
&self,
x: usize,
y: usize,
dir: CellDir
) -> Option<(NodeId, u8)>
pub fn get_adjacent(&self, x: usize, y: usize, dir: CellDir) -> Option<&Cell>
pub fn adjacent_edge_has_input(&self, x: usize, y: usize, edge: CellDir) -> bool
sourcepub fn get_connections(
&self,
x: usize,
y: usize
) -> Option<Vec<((Cell, CellDir, u8), (Cell, CellDir, u8, (usize, usize)))>>
pub fn get_connections(
&self,
x: usize,
y: usize
) -> Option<Vec<((Cell, CellDir, u8), (Cell, CellDir, u8, (usize, usize)))>>
Retrieves the immediate connections to adjacent cells and returns a list. Returns none if there is no cell at the given position.
Returns a vector with pairs of this content:
( (center_cell, center_connection_dir, center_node_io_index), ( other_cell, other_connection_dir, other__node_io_index, (other_cell_x, other_cell_y) ) )
pub fn for_each<F: FnMut(usize, usize, &Cell)>(&self, f: F)
pub fn edge_label<'a>(
&self,
cell: &Cell,
edge: CellDir,
buf: &'a mut [u8]
) -> Option<(&'a str, bool)>
pub fn get_copy(&self, x: usize, y: usize) -> Option<Cell>
pub fn get(&self, x: usize, y: usize) -> Option<&Cell>
pub fn param_input_is_used(&self, p: ParamId) -> bool
pub fn get_unused_instance_node_id(&self, id: NodeId) -> NodeId
fn create_intermediate_nodes(&mut self)
fn update_graph_ordering_and_edges(&mut self)
sourcefn build_prog(&mut self) -> Result<NodeProg, MatrixError>
fn build_prog(&mut self) -> Result<NodeProg, MatrixError>
Compiles a NodeProg from the data collected by the previous call to Matrix::update_graph_ordering_and_edges.
May return an error if the graph topology is invalid (cycles) or something else happened.
sourcepub fn check(&mut self) -> Result<(), MatrixError>
pub fn check(&mut self) -> Result<(), MatrixError>
Checks the topology of the DSP graph represented by the hexagonal matrix.
Use Matrix::save_matrix and Matrix::restore_matrix for trying out changes before committing them to the DSP thread using Matrix::sync.
Note that there is a convenience function with Matrix::change_matrix to make it easier to test and rollback changes if they are faulty.
use hexodsp::*;
let (node_conf, mut node_exec) = new_node_engine();
let mut matrix = Matrix::new(node_conf, 3, 3);
matrix.save_matrix();
// ...
matrix.place(0, 1,
Cell::empty(NodeId::Sin(1))
.input(Some(0), None, None));
matrix.place(0, 0,
Cell::empty(NodeId::Sin(1))
.out(None, None, Some(0)));
// ...
let error =
if let Err(_) = matrix.check() {
matrix.restore_matrix();
true
} else {
matrix.sync().unwrap();
false
};
// In this examples case there is an error, as we created
// a cycle:
assert!(error);
sourcepub fn sync(&mut self) -> Result<(), MatrixError>
pub fn sync(&mut self) -> Result<(), MatrixError>
Synchronizes the matrix with the DSP thread. Call this everytime you changed any of the matrix Cells eg. with Matrix::place and want to publish the changes to the DSP thread.
This method might return an error, for instance if the DSP graph topology contains cycles or has other errors.
You can check any changes and roll them back using the method Matrix::change_matrix.
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 output port feedback for a specific output of the given NodeId.
See also NodeConfigurator::out_fb_for.
sourcepub fn update_output_feedback(&mut self)
pub fn update_output_feedback(&mut self)
Updates the output port feedback. Call this every UI frame or whenever you want to get the most recent values from Matrix::out_fb_for.
See also NodeConfigurator::update_output_feedback.
sourcepub fn set_dynamic_node1x1(&self, index: usize, node: Box<dyn DynamicNode1x1>)
pub fn set_dynamic_node1x1(&self, index: usize, node: Box<dyn DynamicNode1x1>)
Updates the dynamic node for the Rust1x1 nodes. The index
refers to the
instance NodeId::Rust1x1(index)
.
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 handle_graph_events(&mut self)
pub fn handle_graph_events(&mut self)
Handles events from the DSP graph. Such as MIDI events for MIDI learn functionality! Call this regularily (every UI frame) if you want to have MIDI learn to work and receive events such as MIDI events via the MatrixObserver.