diff --git a/src/matrix.rs b/src/matrix.rs index d038084..db2ab32 100644 --- a/src/matrix.rs +++ b/src/matrix.rs @@ -15,7 +15,7 @@ pub use crate::monitor::MON_SIG_CNT; use crate::matrix_repr::*; use crate::dsp::tracker::PatternData; -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; /// This is a cell/tile of the hexagonal [Matrix]. /// @@ -297,6 +297,11 @@ pub struct Matrix { /// by [Matrix::sync] and [Matrix::check]. edges: Vec, + /// 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]. + properties: HashMap, + /// Stores the [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 @@ -326,6 +331,7 @@ impl Matrix { graph_ordering: NodeGraphOrdering::new(), edges: Vec::with_capacity(MAX_ALLOCATED_NODES * 2), assigned_inputs: HashSet::new(), + properties: HashMap::new(), config, w, h, @@ -455,6 +461,7 @@ impl Matrix { self.matrix[x * self.h + y] = cell; } + /// Clears the contents of the matrix. It's completely empty after this. pub fn clear(&mut self) { for cell in self.matrix.iter_mut() { *cell = Cell::empty(NodeId::Nop); @@ -464,12 +471,15 @@ impl Matrix { self.edges.clear(); self.assigned_inputs.clear(); self.saved_matrix = None; + self.properties.clear(); self.config.delete_nodes(); self.monitor_cell(Cell::empty(NodeId::Nop)); let _ = self.sync(); } + /// Iterates through all atoms. This is useful for reading + /// the atoms after a [MatrixRepr] has been loaded with [Matrix::from_repr]. pub fn for_each_atom)>(&self, f: F) { self.config.for_each_param(f); } @@ -533,11 +543,18 @@ impl Matrix { tracker_id += 1; } + let properties = + self.properties + .iter() + .map(|(k, v)| (k.to_string(), v.clone())) + .collect(); + MatrixRepr { cells, params, atoms, patterns, + properties, } } @@ -553,6 +570,10 @@ impl Matrix { &repr.params[..], &repr.atoms[..]); + for (key, val) in repr.properties.iter() { + self.set_prop(key, val.clone()); + } + for cell_repr in repr.cells.iter() { let cell = Cell::from_repr(cell_repr); self.place(cell.x as usize, cell.y as usize, cell); @@ -569,6 +590,38 @@ impl Matrix { self.sync() } + /// 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); + ///``` + pub fn set_prop(&mut self, key: &str, val: SAtom) { + self.properties.insert(key.to_string(), val); + } + + /// Retrieves a matrix property. See also [Matrix::set_prop] for an + /// example and more information. + pub fn get_prop(&mut self, key: &str) -> Option<&SAtom> { + self.properties.get(key) + } + /// 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 diff --git a/src/matrix_repr.rs b/src/matrix_repr.rs index 849f454..7459125 100644 --- a/src/matrix_repr.rs +++ b/src/matrix_repr.rs @@ -145,6 +145,7 @@ pub struct MatrixRepr { pub params: Vec<(ParamId, f32, Option)>, pub atoms: Vec<(ParamId, SAtom)>, pub patterns: Vec>, + pub properties: Vec<(String, SAtom)>, } #[derive(Debug, Clone)] @@ -231,16 +232,18 @@ fn serialize_atom(atom: &SAtom) -> Value { impl MatrixRepr { pub fn empty() -> Self { - let cells = vec![]; - let params = vec![]; - let atoms = vec![]; - let patterns = vec![]; + let cells = vec![]; + let params = vec![]; + let atoms = vec![]; + let patterns = vec![]; + let properties = vec![]; Self { cells, params, atoms, patterns, + properties, } } @@ -337,6 +340,15 @@ impl MatrixRepr { } } + let props = &v["props"]; + if let Value::Array(props) = props { + for v in props.iter() { + let key = v[0].as_str().unwrap_or(""); + m.properties.push( + (key.to_string(), deserialize_atom(&v[1])?)); + } + } + let patterns = &v["patterns"]; if let Value::Array(patterns) = patterns { for p in patterns.iter() { @@ -355,6 +367,7 @@ impl MatrixRepr { "VERSION": 1, }); + self.properties.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap()); self.params.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap()); self.atoms.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap()); @@ -395,6 +408,15 @@ impl MatrixRepr { v["atoms"] = atoms; + let mut props = json!([]); + if let Value::Array(props) = &mut props { + for (k, v) in self.properties.iter() { + props.push(json!([k, serialize_atom(v)])); + } + } + + v["props"] = props; + let mut cells = json!([]); if let Value::Array(cells) = &mut cells { for cell in self.cells.iter() { @@ -447,7 +469,7 @@ mod tests { let s = matrix_repr.serialize(); assert_eq!(s, - "{\"VERSION\":1,\"atoms\":[],\"cells\":[],\"params\":[],\"patterns\":[]}"); + "{\"VERSION\":1,\"atoms\":[],\"cells\":[],\"params\":[],\"patterns\":[],\"props\":[]}"); assert!(MatrixRepr::deserialize(&s).is_ok()); } @@ -477,7 +499,7 @@ mod tests { let s = mr.serialize(); assert_eq!(s, - "{\"VERSION\":1,\"atoms\":[[\"out\",0,\"mono\",[\"i\",0]]],\"cells\":[[\"sin\",2,0,0,[-1,-1,-1],[-1,0,-1]],[\"out\",0,1,0,[-1,0,-1],[-1,-1,0]]],\"params\":[[\"out\",0,\"ch1\",0.0],[\"out\",0,\"ch2\",0.0],[\"sin\",0,\"det\",0.0],[\"sin\",1,\"det\",0.0],[\"sin\",2,\"det\",0.0],[\"sin\",0,\"freq\",0.0],[\"sin\",1,\"freq\",0.0],[\"sin\",2,\"freq\",-0.10000000149011612],[\"out\",0,\"gain\",0.5]],\"patterns\":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null]}"); + "{\"VERSION\":1,\"atoms\":[[\"out\",0,\"mono\",[\"i\",0]]],\"cells\":[[\"sin\",2,0,0,[-1,-1,-1],[-1,0,-1]],[\"out\",0,1,0,[-1,0,-1],[-1,-1,0]]],\"params\":[[\"out\",0,\"ch1\",0.0],[\"out\",0,\"ch2\",0.0],[\"sin\",0,\"det\",0.0],[\"sin\",1,\"det\",0.0],[\"sin\",2,\"det\",0.0],[\"sin\",0,\"freq\",0.0],[\"sin\",1,\"freq\",0.0],[\"sin\",2,\"freq\",-0.10000000149011612],[\"out\",0,\"gain\",0.5]],\"patterns\":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],\"props\":[]}"); let mut mr2 = MatrixRepr::deserialize(&s).unwrap(); @@ -660,7 +682,7 @@ mod tests { fn check_matrix_repr_mod_amt_1() { use crate::nodes::new_node_engine; - let mr = { + let s = { let (node_conf, mut _node_exec) = new_node_engine(); let mut matrix = Matrix::new(node_conf, 3, 3); @@ -680,13 +702,15 @@ mod tests { matrix.set_param_modamt( sin.inp_param("freq").unwrap(), Some(0.6)).unwrap(); - matrix.to_repr() + let mut mr = matrix.to_repr(); + mr.serialize().to_string() }; { let (node_conf, mut _node_exec) = new_node_engine(); let mut matrix = Matrix::new(node_conf, 3, 3); + let mr = MatrixRepr::deserialize(&s).unwrap(); matrix.from_repr(&mr).unwrap(); let mut v = std::collections::HashMap::new(); @@ -698,4 +722,29 @@ mod tests { assert_eq!(*v.get("det").unwrap(), Some(-0.6)); } } + + #[test] + fn check_matrix_repr_properties() { + use crate::nodes::new_node_engine; + + let s = { + 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)); + + let mut mr = matrix.to_repr(); + mr.serialize().to_string() + }; + + { + let (node_conf, mut _node_exec) = new_node_engine(); + let mut matrix = Matrix::new(node_conf, 3, 3); + + let mr = MatrixRepr::deserialize(&s).unwrap(); + matrix.from_repr(&mr).unwrap(); + + assert_eq!(matrix.get_prop("test").unwrap().i(), 31337); + } + } } diff --git a/tests/hr.wav b/tests/hr.wav new file mode 100644 index 0000000..ef33420 Binary files /dev/null and b/tests/hr.wav differ diff --git a/tests/node_sampl.rs b/tests/node_sampl.rs index 4393164..6a6a62c 100644 --- a/tests/node_sampl.rs +++ b/tests/node_sampl.rs @@ -627,3 +627,29 @@ fn check_node_sampl_rev_2() { 0.6802875, 0.79366875, 0.90705 ]); } + +#[test] +fn check_node_sampl_4() { + let (node_conf, mut node_exec) = new_node_engine(); + let mut matrix = Matrix::new(node_conf, 3, 3); + + let smpl = NodeId::Sampl(0); + let out = NodeId::Out(0); + matrix.place(0, 0, Cell::empty(smpl) + .out(None, None, smpl.out("sig"))); + matrix.place(0, 1, Cell::empty(out) + .input(out.inp("ch1"), None, None)); + matrix.sync().unwrap(); + + let sample_p = smpl.inp_param("sample").unwrap(); + matrix.set_param(sample_p, SAtom::audio_unloaded("hr.wav")); + + let fft = run_and_get_fft4096(&mut node_exec, 0, 1000.0); + assert_eq!( + avg_fft_freqs(4.0, + &[10, 100, 200, 300, 440, 800, 1000, 2000, 3000, 4000, 12000, 22050], + &fft[..]), vec![ + (0, 32), (10, 204), (100, 104), (200, 16), (300, 32), (440, 8), + (800, 4), (1000, 0), (2000, 0), (3000, 0), (4000, 0), (12000, 0) + ]); +}