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}