conformal_component/parameters/utils/states_map.rs
1use std::{collections::HashMap, hash::BuildHasher};
2
3use crate::{
4 events::NoteID,
5 synth::{
6 NumericGlobalExpression, NumericPerNoteExpression, SwitchGlobalExpression, SynthParamStates,
7 },
8};
9
10use super::super::{
11 IdHash, IdHashMap, InfoRef, InternalValue, States, TypeSpecificInfoRef, hash_id,
12};
13
14/// Helper function to get a map of param values based on the default values from a list of `Info`s.
15///
16/// Note that if you are passing these parameters to a synth, likely
17/// you want to use [`override_synth_defaults`] instead.
18///
19/// # Examples
20///
21/// ```
22/// # use conformal_component::parameters::{StaticInfoRef, InternalValue, TypeSpecificInfoRef, override_defaults};
23/// # use std::collections::HashMap;
24/// let infos = vec![
25/// StaticInfoRef {
26/// title: "Numeric",
27/// short_title: "Numeric",
28/// unique_id: "numeric",
29/// flags: Default::default(),
30/// type_specific: TypeSpecificInfoRef::Numeric {
31/// default: 0.0,
32/// valid_range: 0.0..=1.0,
33/// units: None,
34/// },
35/// },
36/// ];
37///
38/// // Without overriding, we'll just get a map containing
39/// // the default values.
40/// assert_eq!(
41/// override_defaults(infos.iter().cloned(), &HashMap::new()).get("numeric"),
42/// Some(&InternalValue::Numeric(0.0))
43/// );
44///
45/// // If we override the default value, we'll get that instead.
46/// assert_eq!(
47/// override_defaults(
48/// infos.iter().cloned(),
49/// &vec![("numeric", InternalValue::Numeric(0.5))].into_iter().collect::<HashMap<_, _>>()
50/// ).get("numeric"),
51/// Some(&InternalValue::Numeric(0.5))
52/// );
53/// ```
54pub fn override_defaults<'a, S: AsRef<str> + 'a, H: BuildHasher>(
55 infos: impl IntoIterator<Item = InfoRef<'a, S>> + 'a,
56 overrides: &HashMap<&'_ str, InternalValue, H>,
57) -> HashMap<String, InternalValue> {
58 infos
59 .into_iter()
60 .map(|info| {
61 let id = info.unique_id;
62 let value = overrides
63 .get(id)
64 .copied()
65 .unwrap_or(match info.type_specific {
66 TypeSpecificInfoRef::Enum { default, .. } => InternalValue::Enum(default),
67 TypeSpecificInfoRef::Numeric { default, .. } => InternalValue::Numeric(default),
68 TypeSpecificInfoRef::Switch { default, .. } => InternalValue::Switch(default),
69 });
70 (id.to_string(), value)
71 })
72 .collect()
73}
74
75/// A simple implementation of [`States`] that is backed by a [`HashMap`].
76///
77/// This is useful for testing or other places when you want to pass a [`States`]
78/// to a component outside of a Conformal wrapper.
79#[derive(Clone, Debug, Default)]
80pub struct StatesMap {
81 map: IdHashMap<InternalValue>,
82}
83
84impl<S: AsRef<str>> From<HashMap<S, InternalValue>> for StatesMap {
85 fn from(map: HashMap<S, InternalValue>) -> Self {
86 Self {
87 map: map
88 .into_iter()
89 .map(|(k, v)| (hash_id(k.as_ref()), v))
90 .collect(),
91 }
92 }
93}
94
95impl StatesMap {
96 /// Create a new [`StatesMap`] from a list of `Info`s and `override`s.
97 ///
98 /// This creates a `StatesMap` with all parameters set to default values,
99 /// except for the ones that are overridden by the `override`s.
100 ///
101 /// Note that if you want to pass this into a synth, you should use
102 /// [`SynthStatesMap::new_override_defaults`] instead.
103 ///
104 /// `overrides` work exactly as in [`override_defaults`].
105 ///
106 /// # Examples
107 ///
108 /// ```
109 /// # use conformal_component::parameters::{StaticInfoRef, InternalValue, TypeSpecificInfoRef, StatesMap, States};
110 /// let infos = vec![
111 /// StaticInfoRef {
112 /// title: "Numeric",
113 /// short_title: "Numeric",
114 /// unique_id: "numeric",
115 /// flags: Default::default(),
116 /// type_specific: TypeSpecificInfoRef::Numeric {
117 /// default: 0.0,
118 /// valid_range: 0.0..=1.0,
119 /// units: None,
120 /// },
121 /// },
122 /// ];
123 ///
124 /// let overrides = vec![("numeric", InternalValue::Numeric(0.5))].into_iter().collect();
125 ///
126 /// let states = StatesMap::new_override_defaults(infos.iter().cloned(), &overrides);
127 /// assert_eq!(states.get_numeric("numeric"), Some(0.5));
128 /// ```
129 pub fn new_override_defaults<'a, S: AsRef<str> + 'a>(
130 infos: impl IntoIterator<Item = InfoRef<'a, S>> + 'a,
131 overrides: &HashMap<&'_ str, InternalValue>,
132 ) -> Self {
133 Self {
134 map: override_defaults(infos, overrides)
135 .into_iter()
136 .map(|(k, v)| (hash_id(&k), v))
137 .collect(),
138 }
139 }
140
141 /// Create a new [`StatesMap`] from a list of `Info`s.
142 ///
143 /// Each parameter in `Info`s will be set to its default value.
144 ///
145 /// Note that if you want to pass this into a synth, you should use
146 /// [`Self::new_synth_defaults`] instead.
147 ///
148 /// # Examples
149 ///
150 /// ```
151 /// # use conformal_component::parameters::{StaticInfoRef, InternalValue, TypeSpecificInfoRef, StatesMap, States};
152 /// let infos = vec![
153 /// StaticInfoRef {
154 /// title: "Numeric",
155 /// short_title: "Numeric",
156 /// unique_id: "numeric",
157 /// flags: Default::default(),
158 /// type_specific: TypeSpecificInfoRef::Numeric {
159 /// default: 0.0,
160 /// valid_range: 0.0..=1.0,
161 /// units: None,
162 /// },
163 /// },
164 /// ];
165 ///
166 /// let states = StatesMap::new_defaults(infos.iter().cloned());
167 /// assert_eq!(states.get_numeric("numeric"), Some(0.0));
168 /// ```
169 pub fn new_defaults<'a, S: AsRef<str> + 'a>(
170 infos: impl IntoIterator<Item = InfoRef<'a, S>> + 'a,
171 ) -> Self {
172 Self::new_override_defaults(infos, &Default::default())
173 }
174}
175
176impl States for StatesMap {
177 fn get_by_hash(&self, id_hash: IdHash) -> Option<InternalValue> {
178 self.map.get(&id_hash).copied()
179 }
180}
181
182/// A simple implementation of [`SynthParamStates`] that is backed by a [`HashMap`].
183///
184/// This is useful for testing or other places when you want to pass a [`SynthParamStates`]
185/// to a component outside of a Conformal wrapper.
186#[derive(Clone, Debug, Default)]
187pub struct SynthStatesMap {
188 states: StatesMap,
189 numeric_expressions: HashMap<NumericGlobalExpression, f32>,
190 switch_expressions: HashMap<SwitchGlobalExpression, bool>,
191 per_note_expressions: HashMap<(NumericPerNoteExpression, NoteID), f32>,
192}
193
194impl SynthStatesMap {
195 /// Create a new [`SynthStatesMap`] to pass to a synth from a list of `Info`s and `override`s.
196 ///
197 /// This is similar to [`StatesMap::new_override_defaults`], but it also includes expression controllers.
198 ///
199 /// # Examples
200 ///
201 /// ```
202 /// # use conformal_component::parameters::{StaticInfoRef, InternalValue, TypeSpecificInfoRef, SynthStatesMap, States};
203 /// # use conformal_component::synth::{SynthParamStates, NumericGlobalExpression};
204 /// let infos = vec![
205 /// StaticInfoRef {
206 /// title: "Numeric",
207 /// short_title: "Numeric",
208 /// unique_id: "numeric",
209 /// flags: Default::default(),
210 /// type_specific: TypeSpecificInfoRef::Numeric {
211 /// default: 0.0,
212 /// valid_range: 0.0..=1.0,
213 /// units: None,
214 /// },
215 /// },
216 /// ];
217 ///
218 /// let overrides = vec![
219 /// // You can override declared parameters
220 /// ("numeric", InternalValue::Numeric(0.5)),
221 /// ].into_iter().collect();
222 /// let numeric_expression_overrides = vec![
223 /// // Or you can override control parameters
224 /// (NumericGlobalExpression::ModWheel, 0.2),
225 /// ].into_iter().collect();
226 /// let states = SynthStatesMap::new_override_defaults(infos.iter().cloned(), &overrides, &numeric_expression_overrides, &Default::default());
227 ///
228 /// // Overridden parameters get the values you passed in
229 /// assert_eq!(states.get_numeric("numeric"), Some(0.5));
230 /// assert_eq!(states.get_numeric_global_expression(NumericGlobalExpression::ModWheel), 0.2);
231 ///
232 /// // Other parameters get their default values
233 /// assert_eq!(states.get_numeric_global_expression(NumericGlobalExpression::PitchBend), 0.0);
234 /// ```
235 pub fn new_override_defaults<'a, S: AsRef<str> + 'a>(
236 infos: impl IntoIterator<Item = InfoRef<'a, S>> + 'a,
237 overrides: &HashMap<&'_ str, InternalValue>,
238 numeric_expression_overrides: &HashMap<NumericGlobalExpression, f32>,
239 switch_expression_overrides: &HashMap<SwitchGlobalExpression, bool>,
240 ) -> Self {
241 Self {
242 states: StatesMap::from(override_defaults(infos, overrides)),
243 numeric_expressions: numeric_expression_overrides.clone(),
244 switch_expressions: switch_expression_overrides.clone(),
245 per_note_expressions: Default::default(),
246 }
247 }
248
249 /// Create a new [`SynthStatesMap`] with per-note expression overrides.
250 ///
251 /// This is similar to [`Self::new_override_defaults`], but also allows specifying
252 /// per-note expression values for specific notes.
253 ///
254 /// # Examples
255 ///
256 /// ```
257 /// # use conformal_component::parameters::{StaticInfoRef, InternalValue, TypeSpecificInfoRef, SynthStatesMap, States};
258 /// # use conformal_component::synth::{SynthParamStates, NumericGlobalExpression, NumericPerNoteExpression};
259 /// # use conformal_component::events::{NoteID};
260 /// let infos = vec![
261 /// StaticInfoRef {
262 /// title: "Numeric",
263 /// short_title: "Numeric",
264 /// unique_id: "numeric",
265 /// flags: Default::default(),
266 /// type_specific: TypeSpecificInfoRef::Numeric {
267 /// default: 0.0,
268 /// valid_range: 0.0..=1.0,
269 /// units: None,
270 /// },
271 /// },
272 /// ];
273 ///
274 /// let note_id = NoteID::from_pitch(60);
275 /// let per_note_overrides = vec![
276 /// ((NumericPerNoteExpression::PitchBend, note_id), 1.5),
277 /// ].into_iter().collect();
278 ///
279 /// let states = SynthStatesMap::new_with_per_note(
280 /// infos.iter().cloned(),
281 /// &Default::default(),
282 /// &Default::default(),
283 /// &Default::default(),
284 /// &per_note_overrides,
285 /// );
286 ///
287 /// assert_eq!(states.get_numeric_expression_for_note(NumericPerNoteExpression::PitchBend, note_id), 1.5);
288 /// ```
289 pub fn new_with_per_note<'a, S: AsRef<str> + 'a>(
290 infos: impl IntoIterator<Item = InfoRef<'a, S>> + 'a,
291 overrides: &HashMap<&'_ str, InternalValue>,
292 numeric_expression_overrides: &HashMap<NumericGlobalExpression, f32>,
293 switch_expression_overrides: &HashMap<SwitchGlobalExpression, bool>,
294 per_note_expression_overrides: &HashMap<(NumericPerNoteExpression, NoteID), f32>,
295 ) -> Self {
296 Self {
297 states: StatesMap::from(override_defaults(infos, overrides)),
298 numeric_expressions: numeric_expression_overrides.clone(),
299 switch_expressions: switch_expression_overrides.clone(),
300 per_note_expressions: per_note_expression_overrides.clone(),
301 }
302 }
303
304 /// Create a new [`SynthStatesMap`] to pass to a synth from a list of `Info`s.
305 ///
306 /// Each parameter in `Info`s will be set to its default value.
307 ///
308 /// This is similar to [`StatesMap::new_defaults`], but it also includes expression controllers.
309 ///
310 /// # Examples
311 ///
312 /// ```
313 /// # use conformal_component::parameters::{StaticInfoRef, InternalValue, TypeSpecificInfoRef, SynthStatesMap, States};
314 /// # use conformal_component::synth::{SynthParamStates, NumericGlobalExpression};
315 /// let infos = vec![
316 /// StaticInfoRef {
317 /// title: "Numeric",
318 /// short_title: "Numeric",
319 /// unique_id: "numeric",
320 /// flags: Default::default(),
321 /// type_specific: TypeSpecificInfoRef::Numeric {
322 /// default: 0.0,
323 /// valid_range: 0.0..=1.0,
324 /// units: None,
325 /// },
326 /// },
327 /// ];
328 ///
329 /// let states = SynthStatesMap::new_defaults(infos.iter().cloned());
330 /// assert_eq!(states.get_numeric("numeric"), Some(0.0));
331 ///
332 /// // Controller parameters will also be included
333 /// assert_eq!(states.get_numeric_global_expression(NumericGlobalExpression::ModWheel), 0.0);
334 /// ```
335 pub fn new_defaults<'a, S: AsRef<str> + 'a>(
336 infos: impl IntoIterator<Item = InfoRef<'a, S>> + 'a,
337 ) -> Self {
338 Self::new_override_defaults(
339 infos,
340 &Default::default(),
341 &Default::default(),
342 &Default::default(),
343 )
344 }
345}
346
347impl States for SynthStatesMap {
348 fn get_by_hash(&self, id_hash: IdHash) -> Option<InternalValue> {
349 self.states.get_by_hash(id_hash)
350 }
351}
352
353impl SynthParamStates for SynthStatesMap {
354 fn get_numeric_global_expression(&self, expression: NumericGlobalExpression) -> f32 {
355 self.numeric_expressions
356 .get(&expression)
357 .copied()
358 .unwrap_or_default()
359 }
360 fn get_switch_global_expression(&self, expression: SwitchGlobalExpression) -> bool {
361 self.switch_expressions
362 .get(&expression)
363 .copied()
364 .unwrap_or_default()
365 }
366
367 fn get_numeric_expression_for_note(
368 &self,
369 expression: NumericPerNoteExpression,
370 note_id: NoteID,
371 ) -> f32 {
372 self.per_note_expressions
373 .get(&(expression, note_id))
374 .copied()
375 .unwrap_or_default()
376 }
377}