diff --git a/src/matrix.rs b/src/matrix.rs index f152407..ad2a84e 100644 --- a/src/matrix.rs +++ b/src/matrix.rs @@ -470,7 +470,7 @@ impl Matrix { let _ = self.sync(); } - pub fn for_each_atom(&self, f: F) { + pub fn for_each_atom)>(&self, f: F) { self.config.for_each_param(f); } @@ -623,6 +623,17 @@ impl Matrix { self.config.set_param(param, at); } + /// Assign or remove modulation of an input parameter. + pub fn set_param_modamt(&mut self, param: ParamId, modamt: Option) + -> Result<(), MatrixError> + { + if self.config.set_param_modamt(param, modamt) { + self.sync() + } else { + Ok(()) + } + } + pub fn get_adjacent_output(&self, x: usize, y: usize, dir: CellDir) -> Option<(NodeId, u8)> { diff --git a/src/matrix_repr.rs b/src/matrix_repr.rs index 067c88a..1ba7053 100644 --- a/src/matrix_repr.rs +++ b/src/matrix_repr.rs @@ -142,7 +142,7 @@ impl PatternRepr { #[derive(Debug, Clone)] pub struct MatrixRepr { pub cells: Vec, - pub params: Vec<(ParamId, f32)>, + pub params: Vec<(ParamId, f32, Option)>, pub atoms: Vec<(ParamId, SAtom)>, pub patterns: Vec>, } @@ -312,7 +312,9 @@ impl MatrixRepr { if let Some(param_id) = param_id { m.params.push( - (param_id, v[3].as_f64().unwrap_or(0.0) as f32)); + (param_id, + v[3].as_f64().unwrap_or(0.0) as f32, + v[4].as_f64().map(|v| v as f32))); } else { return Err( MatrixDeserError::UnknownParamId(v.to_string())); @@ -358,14 +360,21 @@ impl MatrixRepr { let mut params = json!([]); if let Value::Array(params) = &mut params { - for (p, v) in self.params.iter() { - params.push( - json!([ - p.node_id().name(), - p.node_id().instance(), - p.name(), - v - ])); + for (p, v, ma) in self.params.iter() { + let mut param_v = json!([ + p.node_id().name(), + p.node_id().instance(), + p.name(), + v, + ]); + + if let Value::Array(param_v) = &mut param_v { + if let Some(ma) = ma { + param_v.push(json!(ma)); + } + } + + params.push(param_v); } } diff --git a/src/nodes/mod.rs b/src/nodes/mod.rs index 6aac65b..54cf001 100644 --- a/src/nodes/mod.rs +++ b/src/nodes/mod.rs @@ -53,10 +53,11 @@ pub enum GraphMessage { /// and the NodeConfigurator. #[derive(Debug)] pub enum QuickMessage { - AtomUpdate { at_idx: usize, value: SAtom }, - ParamUpdate { input_idx: usize, value: f32 }, + AtomUpdate { at_idx: usize, value: SAtom }, + ParamUpdate { input_idx: usize, value: f32 }, + ModamtUpdate { input_idx: usize, modamt: f32 }, /// Sets the buffer indices to monitor with the FeedbackProcessor. - SetMonitor { bufs: [usize; MON_SIG_CNT], }, + SetMonitor { bufs: [usize; MON_SIG_CNT], }, } pub const UNUSED_MONITOR_IDX : usize = 99999; diff --git a/src/nodes/node_conf.rs b/src/nodes/node_conf.rs index 6965a48..7d77a43 100644 --- a/src/nodes/node_conf.rs +++ b/src/nodes/node_conf.rs @@ -154,6 +154,8 @@ pub struct NodeConfigurator { params: std::collections::HashMap, /// Stores the most recently set parameter values param_values: std::collections::HashMap, + /// Stores the modulation amount of a parameter + param_modamt: std::collections::HashMap>, /// Contains non automateable atom data for the nodes atoms: std::collections::HashMap, /// Stores the most recently set atoms @@ -241,6 +243,7 @@ impl NodeConfigurator { output_fb_cons: None, params: std::collections::HashMap::new(), param_values: std::collections::HashMap::new(), + param_modamt: std::collections::HashMap::new(), atoms: std::collections::HashMap::new(), atom_values: std::collections::HashMap::new(), node2idx: HashMap::new(), @@ -299,6 +302,47 @@ impl NodeConfigurator { self.nodes.get_mut(idx) } + /// 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. + pub fn set_param_modamt(&mut self, param: ParamId, v: Option) -> bool { + if param.is_atom() { + return false; + } + + // Check if the modulation amount was already set, if not, we need + // to reconstruct the graph and upload an updated NodeProg. + if let Some(_old_modamt) = + self.param_modamt.get(¶m).copied().flatten() + { + if v.is_none() { + self.param_modamt.insert(param, v); + true + + } else { + let modamt = v.unwrap(); + + self.param_modamt.insert(param, v); + + if let Some(nparam) = self.params.get_mut(¶m) { + let input_idx = nparam.input_idx; + let _ = + self.shared.quick_update_prod.push( + QuickMessage::ModamtUpdate { + input_idx, modamt + }); + } + + false + } + + } else { + self.param_modamt.insert(param, v); + true + } + } + /// Assign [SAtom] values to input parameters and atoms. /// /// Only updates the DSP backend if [NodeConfigurator::rebuild_node_ports] was called @@ -356,12 +400,18 @@ impl NodeConfigurator { /// Most useful for serialization and saving patches. #[allow(clippy::type_complexity)] pub fn dump_param_values(&self) - -> (Vec<(ParamId, f32)>, Vec<(ParamId, SAtom)>) + -> (Vec<(ParamId, f32, Option)>, Vec<(ParamId, SAtom)>) { - let params : Vec<(ParamId, f32)> = + let params : Vec<(ParamId, f32, Option)> = self.param_values .iter() - .map(|(param_id, value)| (*param_id, *value)) + .map(|(param_id, value)| + (*param_id, + *value, + self.param_modamt + .get(param_id) + .copied() + .flatten())) .collect(); let atoms : Vec<(ParamId, SAtom)> = @@ -376,10 +426,13 @@ impl NodeConfigurator { /// Loads parameter values from a dump. You will still need to upload /// a new [NodeProg] which contains these values. pub fn load_dumped_param_values( - &mut self, params: &[(ParamId, f32)], atoms: &[(ParamId, SAtom)]) + &mut self, + params: &[(ParamId, f32, Option)], + atoms: &[(ParamId, SAtom)]) { - for (param_id, val) in params.iter() { + for (param_id, val, modamt) in params.iter() { self.set_param(*param_id, (*val).into()); + self.set_param_modamt(*param_id, *modamt); } for (param_id, val) in atoms.iter() { @@ -389,12 +442,12 @@ impl NodeConfigurator { /// Iterates over every parameter and calls the given function with /// it's current value. - pub fn for_each_param(&self, mut f: F) { + pub fn for_each_param)>(&self, mut f: F) { for (_, node_input) in self.atoms.iter() { if let Some(unique_idx) = self.unique_index_for(&node_input.param_id.node_id()) { - f(unique_idx, node_input.param_id, &node_input.value); + f(unique_idx, node_input.param_id, &node_input.value, None); } } @@ -402,8 +455,15 @@ impl NodeConfigurator { if let Some(unique_idx) = self.unique_index_for(&node_input.param_id.node_id()) { + let modamt = + self.param_modamt + .get(&node_input.param_id) + .copied() + .flatten(); + f(unique_idx, node_input.param_id, - &SAtom::param(node_input.value)); + &SAtom::param(node_input.value), + modamt); } } } @@ -513,11 +573,15 @@ impl NodeConfigurator { /// The monitor data can be retrieved using /// [NodeConfigurator::get_minmax_monitor_samples]. pub fn monitor(&mut self, - node_id: &NodeId, inputs: &[Option], outputs: &[Option]) + node_id: &NodeId, + inputs: &[Option], + outputs: &[Option]) { let mut bufs = [UNUSED_MONITOR_IDX; MON_SIG_CNT]; - if let Some((_node_info, Some(node_instance))) = self.node_by_id(node_id) { + if let Some((_node_info, Some(node_instance))) = + self.node_by_id(node_id) + { let mut i = 0; for inp_idx in inputs.iter().take(MON_SIG_CNT / 2) { if let Some(inp_idx) = inp_idx { @@ -572,6 +636,7 @@ impl NodeConfigurator { self.nodes.fill_with(|| (NodeInfo::Nop, None)); self.params .clear(); self.param_values.clear(); + self.param_modamt.clear(); self.atoms .clear(); self.atom_values .clear(); diff --git a/src/nodes/node_exec.rs b/src/nodes/node_exec.rs index 7b30b19..646f111 100644 --- a/src/nodes/node_exec.rs +++ b/src/nodes/node_exec.rs @@ -398,6 +398,11 @@ impl NodeExecutor { QuickMessage::ParamUpdate { input_idx, value } => { self.set_param(input_idx, value); }, + // TODO: MODAMT + // QuickMessage::ModamtUpdate { input_idx, modamt } => { + QuickMessage::ModamtUpdate { .. } => { + // assign to NodeProg + }, QuickMessage::SetMonitor { bufs } => { self.monitor_signal_cur_inp_indices = bufs; },