conformal_component/parameters/
utils.rs

1use std::{
2    collections::HashMap,
3    hash::BuildHasher,
4    ops::{Range, RangeInclusive},
5};
6
7use crate::{audio::approx_eq, synth::CONTROLLER_PARAMETERS};
8
9use super::{
10    BufferState, BufferStates, EnumBufferState, IdHash, InfoRef, InternalValue, NumericBufferState,
11    PiecewiseLinearCurve, PiecewiseLinearCurvePoint, States, SwitchBufferState, TimedEnumValues,
12    TimedSwitchValues, TimedValue, TypeSpecificInfoRef, hash_id,
13};
14
15#[derive(Clone)]
16enum ConstantOrIterating<V, I> {
17    Constant(V),
18    Iterating(I),
19}
20
21impl<V: Copy, I: Iterator<Item = V>> Iterator for ConstantOrIterating<V, I> {
22    type Item = V;
23
24    fn next(&mut self) -> Option<Self::Item> {
25        match self {
26            ConstantOrIterating::Constant(v) => Some(*v),
27            ConstantOrIterating::Iterating(i) => i.next(),
28        }
29    }
30}
31
32/// Convert a piecewise linear curve into a per-sample iterator for a buffer.
33fn piecewise_linear_curve_per_sample<
34    I: IntoIterator<Item = PiecewiseLinearCurvePoint, IntoIter: Clone>,
35>(
36    curve: PiecewiseLinearCurve<I>,
37) -> impl Iterator<Item = f32> + Clone {
38    let buffer_size = curve.buffer_size();
39    let mut i = curve.into_iter();
40    let mut next = i.next();
41    let mut last = None;
42    let mut last_sample_offset = 0;
43    (0..buffer_size).map(move |idx| {
44        if let Some(PiecewiseLinearCurvePoint {
45            sample_offset,
46            value,
47        }) = next
48        {
49            if sample_offset == idx {
50                last = Some(value);
51                last_sample_offset = sample_offset;
52                next = i.next();
53                value
54            } else {
55                // unwrap is safe here because we know there is a point at zero.
56                let delta = value - last.unwrap();
57
58                // Note that we will fix any rounding errors when we hit the next point,
59                // so we allow a lossy cast in the next block.
60                #[allow(clippy::cast_precision_loss)]
61                {
62                    let delta_per_sample = delta / ((sample_offset - last_sample_offset) as f32);
63
64                    last.unwrap() + delta_per_sample * ((idx - last_sample_offset) as f32)
65                }
66            }
67        } else {
68            // Unwrap is safe here because we know that there is at least one point in curve.
69            last.unwrap()
70        }
71    })
72}
73
74/// Converts a [`NumericBufferState`] into a per-sample iterator.
75///
76/// This provides the value of the parameter at each sample in the buffer.
77///
78/// # Example
79///
80/// ```
81/// # use conformal_component::parameters::{numeric_per_sample, NumericBufferState, PiecewiseLinearCurvePoint, PiecewiseLinearCurve };
82/// # use conformal_component::audio::all_approx_eq;
83/// let state = NumericBufferState::PiecewiseLinear(PiecewiseLinearCurve::new(
84///   vec![
85///     PiecewiseLinearCurvePoint { sample_offset: 0, value: 0.0 },
86///     PiecewiseLinearCurvePoint { sample_offset: 10, value: 1.0 },
87///   ],
88///   13,
89///   0.0..=1.0,
90/// ).unwrap());
91/// assert!(
92///   all_approx_eq(
93///     numeric_per_sample(state),
94///     [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.0, 1.0],
95///     1e-6
96///   )
97/// );
98/// ```
99pub fn numeric_per_sample<I: IntoIterator<Item = PiecewiseLinearCurvePoint, IntoIter: Clone>>(
100    state: NumericBufferState<I>,
101) -> impl Iterator<Item = f32> + Clone {
102    match state {
103        NumericBufferState::Constant(v) => ConstantOrIterating::Constant(v),
104        NumericBufferState::PiecewiseLinear(c) => {
105            ConstantOrIterating::Iterating(piecewise_linear_curve_per_sample(c))
106        }
107    }
108}
109
110#[allow(clippy::missing_panics_doc)] // We only panic when invariants are broken.
111fn timed_enum_per_sample<I: IntoIterator<Item = TimedValue<u32>, IntoIter: Clone>>(
112    values: TimedEnumValues<I>,
113) -> impl Iterator<Item = u32> + Clone {
114    let buffer_size = values.buffer_size();
115    let mut i = values.into_iter();
116    let mut next = i.next();
117    let mut last = None;
118    (0..buffer_size).map(move |idx| {
119        if let Some(TimedValue {
120            sample_offset,
121            value,
122        }) = next
123        {
124            if sample_offset == idx {
125                last = Some(value);
126                next = i.next();
127                value
128            } else {
129                // unwrap is safe here because we know there is a point at zero.
130                last.unwrap()
131            }
132        } else {
133            // Unwrap is safe here because we know that there is at least one point in curve.
134            last.unwrap()
135        }
136    })
137}
138
139/// Converts an [`EnumBufferState`] into a per-sample iterator.
140///
141/// This provides the value of the parameter at each sample in the buffer.
142///
143/// # Example
144///
145/// ```
146/// # use conformal_component::parameters::{enum_per_sample, EnumBufferState, TimedEnumValues, TimedValue };
147/// let state = EnumBufferState::Varying(TimedEnumValues::new(
148///   vec![
149///     TimedValue { sample_offset: 0, value: 0 },
150///     TimedValue { sample_offset: 3, value: 1 },
151///   ],
152///   5,
153///   0..2,
154/// ).unwrap());
155/// assert!(
156///   enum_per_sample(state).eq([0, 0, 0, 1, 1].iter().cloned())
157/// );
158/// ```
159pub fn enum_per_sample<I: IntoIterator<Item = TimedValue<u32>, IntoIter: Clone>>(
160    state: EnumBufferState<I>,
161) -> impl Iterator<Item = u32> + Clone {
162    match state {
163        EnumBufferState::Constant(v) => ConstantOrIterating::Constant(v),
164        EnumBufferState::Varying(c) => ConstantOrIterating::Iterating(timed_enum_per_sample(c)),
165    }
166}
167
168#[allow(clippy::missing_panics_doc)] // We only panic when invariants are broken.
169fn timed_switch_per_sample<I: IntoIterator<Item = TimedValue<bool>, IntoIter: Clone>>(
170    values: TimedSwitchValues<I>,
171) -> impl Iterator<Item = bool> + Clone {
172    let buffer_size = values.buffer_size();
173    let mut i = values.into_iter();
174    let mut next = i.next();
175    let mut last = None;
176    (0..buffer_size).map(move |idx| {
177        if let Some(TimedValue {
178            sample_offset,
179            value,
180        }) = next
181        {
182            if sample_offset == idx {
183                last = Some(value);
184                next = i.next();
185                value
186            } else {
187                // unwrap is safe here because we know there is a point at zero.
188                last.unwrap()
189            }
190        } else {
191            // Unwrap is safe here because we know that there is at least one point in curve.
192            last.unwrap()
193        }
194    })
195}
196
197/// Converts a [`SwitchBufferState`] into a per-sample iterator.
198///
199/// This provides the value of the parameter at each sample in the buffer.
200///
201/// # Example
202///
203/// ```
204/// # use conformal_component::parameters::{switch_per_sample, SwitchBufferState, TimedSwitchValues, TimedValue };
205/// let state = SwitchBufferState::Varying(TimedSwitchValues::new(
206///   vec![
207///     TimedValue { sample_offset: 0, value: false },
208///     TimedValue { sample_offset: 3, value: true },
209///   ],
210///   5,
211/// ).unwrap());
212/// assert!(
213///   switch_per_sample(state).eq([false, false, false, true, true].iter().cloned())
214/// );
215/// ```
216pub fn switch_per_sample<I: IntoIterator<Item = TimedValue<bool>, IntoIter: Clone>>(
217    state: SwitchBufferState<I>,
218) -> impl Iterator<Item = bool> + Clone {
219    match state {
220        SwitchBufferState::Constant(v) => ConstantOrIterating::Constant(v),
221        SwitchBufferState::Varying(c) => ConstantOrIterating::Iterating(timed_switch_per_sample(c)),
222    }
223}
224
225#[macro_export]
226#[doc(hidden)]
227macro_rules! pzip_part {
228    (numeric $path:literal $params:ident) => {{
229        use conformal_component::parameters::BufferStates;
230        conformal_component::parameters::numeric_per_sample($params.get_numeric($path).unwrap())
231    }};
232    (enum $path:literal $params:ident) => {{
233        use conformal_component::parameters::BufferStates;
234        conformal_component::parameters::enum_per_sample($params.get_enum($path).unwrap())
235    }};
236    (switch $path:literal $params:ident) => {{
237        use conformal_component::parameters::BufferStates;
238        conformal_component::parameters::switch_per_sample($params.get_switch($path).unwrap())
239    }};
240}
241
242// Optimization opportunity - add maps here that only apply to the control points
243// in the linear curves!
244
245/// Utility to get a per-sample iterator including the state of multiple parameters.
246///
247/// This is a convenient way to consume a [`BufferStates`] object if you intend
248/// to track the per-sample state of multiple parameters.
249///
250/// This macro indexes into a [`BufferStates`] object with a list of parameter
251/// ids and their types. See the examples below for usage.
252///
253/// # Examples
254///
255/// ```
256/// # use conformal_component::pzip;
257/// # use conformal_component::parameters::{ConstantBufferStates, StaticInfoRef, TypeSpecificInfoRef, InternalValue};
258/// let params = ConstantBufferStates::new_defaults(
259///   vec![
260///     StaticInfoRef {
261///       title: "Numeric",
262///       short_title: "Numeric",
263///       unique_id: "gain",
264///       flags: Default::default(),
265///       type_specific: TypeSpecificInfoRef::Numeric {
266///         default: 0.0,
267///         valid_range: 0.0..=1.0,
268///         units: None,
269///       },
270///     },
271///     StaticInfoRef {
272///       title: "Enum",
273///       short_title: "Enum",
274///       unique_id: "letter",
275///       flags: Default::default(),
276///       type_specific: TypeSpecificInfoRef::Enum {
277///         default: 1,
278///         values: &["A", "B", "C"],
279///       },
280///     },
281///     StaticInfoRef {
282///       title: "Switch",
283///       short_title: "Switch",
284///       unique_id: "my special switch",
285///       flags: Default::default(),
286///       type_specific: TypeSpecificInfoRef::Switch {
287///         default: false,
288///       },
289///     },
290///   ],
291/// );
292///
293/// let samples: Vec<_> = pzip!(params[
294///   numeric "gain",
295///   enum "letter",
296///   switch "my special switch"
297/// ]).take(2).collect();
298///
299/// assert_eq!(samples, vec![(0.0, 1, false), (0.0, 1, false)]);
300/// ```
301#[macro_export]
302macro_rules! pzip {
303    ($params:ident[$($kind:ident $path:literal),+]) => {
304        conformal_component::itertools::izip!(
305            $(
306                conformal_component::pzip_part!($kind $path $params),
307            )+
308        )
309    };
310}
311
312/// Helper function to get a map of param values based on the default values from a list of `Info`s.
313///
314/// Note that if you are passing these parameters to a synth, likely
315/// you want to use [`override_synth_defaults`] instead.
316///
317/// # Examples
318///
319/// ```
320/// # use conformal_component::parameters::{StaticInfoRef, InternalValue, TypeSpecificInfoRef, override_defaults};
321/// # use std::collections::HashMap;
322/// let infos = vec![
323///    StaticInfoRef {
324///      title: "Numeric",
325///      short_title: "Numeric",
326///      unique_id: "numeric",
327///      flags: Default::default(),
328///      type_specific: TypeSpecificInfoRef::Numeric {
329///        default: 0.0,
330///        valid_range: 0.0..=1.0,
331///        units: None,
332///      },
333///    },
334/// ];
335///
336/// // Without overriding, we'll just get a map containing
337/// // the default values.
338/// assert_eq!(
339///   override_defaults(infos.iter().cloned(), &HashMap::new()).get("numeric"),
340///   Some(&InternalValue::Numeric(0.0))
341/// );
342///
343/// // If we override the default value, we'll get that instead.
344/// assert_eq!(
345///   override_defaults(
346///     infos.iter().cloned(),
347///     &vec![("numeric", InternalValue::Numeric(0.5))].into_iter().collect::<HashMap<_, _>>()
348///   ).get("numeric"),
349///   Some(&InternalValue::Numeric(0.5))
350///  );
351/// ```
352pub fn override_defaults<'a, S: AsRef<str> + 'a, H: BuildHasher>(
353    infos: impl IntoIterator<Item = InfoRef<'a, S>> + 'a,
354    overrides: &HashMap<&'_ str, InternalValue, H>,
355) -> HashMap<String, InternalValue> {
356    infos
357        .into_iter()
358        .map(|info| {
359            let id = info.unique_id;
360            let value = overrides
361                .get(id)
362                .copied()
363                .unwrap_or(match info.type_specific {
364                    TypeSpecificInfoRef::Enum { default, .. } => InternalValue::Enum(default),
365                    TypeSpecificInfoRef::Numeric { default, .. } => InternalValue::Numeric(default),
366                    TypeSpecificInfoRef::Switch { default, .. } => InternalValue::Switch(default),
367                });
368            (id.to_string(), value)
369        })
370        .collect()
371}
372
373/// Helper function to get a map of synth param values based on the default values from a list of `Info`s.
374///
375/// This is similar to [`override_defaults`], but it also includes the controller parameters
376/// that are common to all synths. ([`crate::synth::CONTROLLER_PARAMETERS`]).
377///
378/// Thus, this is more appropriate to use if you plan to pass the parameters to a synth.
379///
380/// # Examples
381///
382/// ```
383/// # use conformal_component::parameters::{StaticInfoRef, InternalValue, TypeSpecificInfoRef, override_synth_defaults};
384/// # use conformal_component::synth::MOD_WHEEL_PARAMETER;
385/// # use std::collections::HashMap;
386/// let infos = vec![
387///   StaticInfoRef {
388///     title: "Numeric",
389///     short_title: "Numeric",
390///     unique_id: "numeric",
391///     flags: Default::default(),
392///     type_specific: TypeSpecificInfoRef::Numeric {
393///       default: 0.0,
394///       valid_range: 0.0..=1.0,
395///       units: None,
396///     },
397///   },
398/// ];
399///
400/// // Without overrides, we'll get the default value.
401/// assert_eq!(
402///   override_synth_defaults(infos.iter().cloned(), &HashMap::new()).get("numeric"),
403///   Some(&InternalValue::Numeric(0.0)),
404/// );
405///
406/// // Note that control parameters are included in the result.
407/// assert_eq!(
408///   override_synth_defaults(infos.iter().cloned(), &HashMap::new()).get(MOD_WHEEL_PARAMETER),
409///   Some(&InternalValue::Numeric(0.0)),
410/// );
411///
412/// // If we override the default value of a parameter, we'll get that instead.
413/// assert_eq!(
414///   override_synth_defaults(
415///     infos.iter().cloned(),
416///     &vec![("numeric", InternalValue::Numeric(0.5))].into_iter().collect::<HashMap<_, _>>()
417///   ).get("numeric"),
418///   Some(&InternalValue::Numeric(0.5)),
419/// );
420///
421/// // We can also override control parameters
422/// assert_eq!(
423///   override_synth_defaults(
424///     infos.iter().cloned(),
425///     &vec![(MOD_WHEEL_PARAMETER, InternalValue::Numeric(0.5))].into_iter().collect::<HashMap<_, _>>()
426///   ).get(MOD_WHEEL_PARAMETER),
427///   Some(&InternalValue::Numeric(0.5)),
428/// );
429/// ```
430pub fn override_synth_defaults<'a, 'b: 'a, H: BuildHasher>(
431    infos: impl IntoIterator<Item = InfoRef<'a, &'b str>> + 'a,
432    overrides: &HashMap<&'_ str, InternalValue, H>,
433) -> HashMap<String, InternalValue> {
434    override_defaults(infos.into_iter().chain(CONTROLLER_PARAMETERS), overrides)
435}
436
437/// A simple implementation of [`States`] that is backed by a [`HashMap`].
438///
439/// This is useful for testing or other places when you want to pass a [`States`]
440/// to a component outside of a Conformal wrapper.
441#[derive(Clone, Debug, Default)]
442pub struct StatesMap {
443    map: HashMap<IdHash, InternalValue>,
444}
445
446impl<S: AsRef<str>> From<HashMap<S, InternalValue>> for StatesMap {
447    fn from(map: HashMap<S, InternalValue>) -> Self {
448        Self {
449            map: map
450                .into_iter()
451                .map(|(k, v)| (hash_id(k.as_ref()), v))
452                .collect(),
453        }
454    }
455}
456
457impl StatesMap {
458    /// Create a new [`StatesMap`] from a list of `Info`s and `override`s.
459    ///
460    /// This creates a `StatesMap` with all parameters set to default values,
461    /// except for the ones that are overridden by the `override`s.
462    ///
463    /// Note that if you want to pass this into a synth, you should use
464    /// [`Self::new_override_synth_defaults`] instead.
465    ///
466    /// `overrides` work exactly as in [`override_defaults`].
467    ///
468    /// # Examples
469    ///
470    /// ```
471    /// # use conformal_component::parameters::{StaticInfoRef, InternalValue, TypeSpecificInfoRef, StatesMap, States};
472    /// let infos = vec![
473    ///   StaticInfoRef {
474    ///     title: "Numeric",
475    ///     short_title: "Numeric",
476    ///     unique_id: "numeric",
477    ///     flags: Default::default(),
478    ///     type_specific: TypeSpecificInfoRef::Numeric {
479    ///       default: 0.0,
480    ///       valid_range: 0.0..=1.0,
481    ///       units: None,
482    ///     },
483    ///   },
484    /// ];
485    ///
486    /// let overrides = vec![("numeric", InternalValue::Numeric(0.5))].into_iter().collect();
487    ///
488    /// let states = StatesMap::new_override_defaults(infos.iter().cloned(), &overrides);
489    /// assert_eq!(states.get_numeric("numeric"), Some(0.5));
490    /// ```
491    pub fn new_override_defaults<'a, S: AsRef<str> + 'a>(
492        infos: impl IntoIterator<Item = InfoRef<'a, S>> + 'a,
493        overrides: &HashMap<&'_ str, InternalValue>,
494    ) -> Self {
495        Self {
496            map: override_defaults(infos, overrides)
497                .into_iter()
498                .map(|(k, v)| (hash_id(&k), v))
499                .collect(),
500        }
501    }
502
503    /// Create a new [`StatesMap`] from a list of `Info`s.
504    ///
505    /// Each parameter in `Info`s will be set to its default value.
506    ///
507    /// Note that if you want to pass this into a synth, you should use
508    /// [`Self::new_synth_defaults`] instead.
509    ///
510    /// # Examples
511    ///
512    /// ```
513    /// # use conformal_component::parameters::{StaticInfoRef, InternalValue, TypeSpecificInfoRef, StatesMap, States};
514    /// let infos = vec![
515    ///   StaticInfoRef {
516    ///     title: "Numeric",
517    ///     short_title: "Numeric",
518    ///     unique_id: "numeric",
519    ///     flags: Default::default(),
520    ///     type_specific: TypeSpecificInfoRef::Numeric {
521    ///       default: 0.0,
522    ///       valid_range: 0.0..=1.0,
523    ///       units: None,
524    ///     },
525    ///   },
526    /// ];
527    ///
528    /// let states = StatesMap::new_defaults(infos.iter().cloned());
529    /// assert_eq!(states.get_numeric("numeric"), Some(0.0));
530    /// ```
531    pub fn new_defaults<'a, S: AsRef<str> + 'a>(
532        infos: impl IntoIterator<Item = InfoRef<'a, S>> + 'a,
533    ) -> Self {
534        Self::new_override_defaults(infos, &Default::default())
535    }
536
537    /// Create a new [`StatesMap`] to pass to a synth from a list of `Info`s and `override`s.
538    ///
539    /// This is similar to [`Self::new_override_defaults`], but it also includes the controller parameters
540    /// that are common to all synths. ([`crate::synth::CONTROLLER_PARAMETERS`]).
541    ///
542    /// Thus, this is more appropriate to use if you plan to pass the parameters to a synth.
543    ///
544    /// # Examples
545    ///
546    /// ```
547    /// # use conformal_component::parameters::{StaticInfoRef, InternalValue, TypeSpecificInfoRef, StatesMap, States};
548    /// # use conformal_component::synth::{MOD_WHEEL_PARAMETER, PITCH_BEND_PARAMETER};
549    /// let infos = vec![
550    ///   StaticInfoRef {
551    ///     title: "Numeric",
552    ///     short_title: "Numeric",
553    ///     unique_id: "numeric",
554    ///     flags: Default::default(),
555    ///     type_specific: TypeSpecificInfoRef::Numeric {
556    ///       default: 0.0,
557    ///       valid_range: 0.0..=1.0,
558    ///       units: None,
559    ///     },
560    ///   },
561    /// ];
562    ///
563    /// let overrides = vec![
564    ///   // You can override declared parameters
565    ///   ("numeric", InternalValue::Numeric(0.5)),
566    ///   // Or you can override control parameters
567    ///   (MOD_WHEEL_PARAMETER, InternalValue::Numeric(0.2)),
568    /// ].into_iter().collect();
569    /// let states = StatesMap::new_override_synth_defaults(infos.iter().cloned(), &overrides);
570    ///
571    /// // Overridden parameters get the values you passed in
572    /// assert_eq!(states.get_numeric("numeric"), Some(0.5));
573    /// assert_eq!(states.get_numeric(MOD_WHEEL_PARAMETER), Some(0.2));
574    ///
575    /// // Other parameters get their default values
576    /// assert_eq!(states.get_numeric(PITCH_BEND_PARAMETER), Some(0.0));
577    /// ```
578    pub fn new_override_synth_defaults<'a, 'b: 'a>(
579        infos: impl IntoIterator<Item = InfoRef<'a, &'b str>> + 'a,
580        overrides: &HashMap<&'_ str, InternalValue>,
581    ) -> Self {
582        Self {
583            map: override_synth_defaults(infos, overrides)
584                .into_iter()
585                .map(|(k, v)| (hash_id(&k), v))
586                .collect(),
587        }
588    }
589
590    /// Create a new [`StatesMap`] to pass to a synth from a list of `Info`s.
591    ///
592    /// Each parameter in `Info`s will be set to its default value.
593    ///
594    /// This is similar to [`Self::new_defaults`], but it also includes the controller parameters
595    /// that are common to all synths. ([`crate::synth::CONTROLLER_PARAMETERS`]).
596    ///
597    /// Thus, this is more appropriate to use if you plan to pass the parameters to a synth.
598    ///
599    /// # Examples
600    ///
601    /// ```
602    /// # use conformal_component::parameters::{StaticInfoRef, InternalValue, TypeSpecificInfoRef, StatesMap, States};
603    /// # use conformal_component::synth::{MOD_WHEEL_PARAMETER};
604    /// let infos = vec![
605    ///   StaticInfoRef {
606    ///     title: "Numeric",
607    ///     short_title: "Numeric",
608    ///     unique_id: "numeric",
609    ///     flags: Default::default(),
610    ///     type_specific: TypeSpecificInfoRef::Numeric {
611    ///       default: 0.0,
612    ///       valid_range: 0.0..=1.0,
613    ///       units: None,
614    ///     },
615    ///   },
616    /// ];
617    ///
618    /// let states = StatesMap::new_synth_defaults(infos.iter().cloned());
619    /// assert_eq!(states.get_numeric("numeric"), Some(0.0));
620    ///
621    /// // Controller parameters will also be included
622    /// assert_eq!(states.get_numeric(MOD_WHEEL_PARAMETER), Some(0.0));
623    /// ```
624    pub fn new_synth_defaults<'a, 'b: 'a>(
625        infos: impl IntoIterator<Item = InfoRef<'a, &'b str>> + 'a,
626    ) -> Self {
627        Self::new_override_synth_defaults(infos, &Default::default())
628    }
629}
630
631impl States for StatesMap {
632    fn get_by_hash(&self, id_hash: IdHash) -> Option<InternalValue> {
633        self.map.get(&id_hash).copied()
634    }
635}
636
637/// Simple implementation of [`BufferStates`] trait where every parameter is
638/// constant throughout the whole buffer.
639///
640/// This is in general useful for testing or other scenarios where you need
641/// to create a [`BufferStates`] object outside of a Conformal wrapper.
642#[derive(Clone, Debug, Default)]
643pub struct ConstantBufferStates<S> {
644    s: S,
645}
646
647impl<S: States> BufferStates for ConstantBufferStates<S> {
648    fn get_by_hash(
649        &self,
650        id_hash: IdHash,
651    ) -> std::option::Option<
652        BufferState<
653            impl Iterator<Item = PiecewiseLinearCurvePoint> + Clone,
654            impl Iterator<Item = TimedValue<u32>> + Clone,
655            impl Iterator<Item = TimedValue<bool>> + Clone,
656        >,
657    > {
658        match self.s.get_by_hash(id_hash) {
659            Some(InternalValue::Numeric(n)) => {
660                Some(BufferState::Numeric(NumericBufferState::<
661                    std::iter::Empty<PiecewiseLinearCurvePoint>,
662                >::Constant(n)))
663            }
664            Some(InternalValue::Enum(e)) => Some(BufferState::Enum(EnumBufferState::<
665                std::iter::Empty<TimedValue<u32>>,
666            >::Constant(e))),
667            Some(InternalValue::Switch(s)) => Some(BufferState::Switch(SwitchBufferState::<
668                std::iter::Empty<TimedValue<bool>>,
669            >::Constant(s))),
670            None => None,
671        }
672    }
673}
674
675impl<S: States> ConstantBufferStates<S> {
676    /// Create a new [`ConstantBufferStates`] object from a [`States`] object.
677    pub fn new(s: S) -> Self {
678        Self { s }
679    }
680}
681
682impl ConstantBufferStates<StatesMap> {
683    /// Create a new [`ConstantBufferStates`] object from a list of `Info`s and `override`s.
684    ///
685    /// This creates a `ConstantBufferStates` with all parameters set to default values
686    /// for the whole buffer.
687    ///
688    /// Note that if you want to pass this into a synth, you should use
689    /// [`Self::new_override_synth_defaults`] instead.
690    ///
691    /// `overrides` work exactly as in [`override_defaults`].
692    ///
693    /// # Examples
694    ///
695    /// ```
696    /// # use conformal_component::parameters::{StaticInfoRef, InternalValue, TypeSpecificInfoRef, ConstantBufferStates, BufferStates, NumericBufferState};
697    /// let infos = vec![
698    ///   StaticInfoRef {
699    ///     title: "Numeric",
700    ///     short_title: "Numeric",
701    ///     unique_id: "numeric",
702    ///     flags: Default::default(),
703    ///     type_specific: TypeSpecificInfoRef::Numeric {
704    ///       default: 0.0,
705    ///       valid_range: 0.0..=1.0,
706    ///       units: None,
707    ///     },
708    ///   },
709    /// ];
710    /// let overrides = vec![("numeric", InternalValue::Numeric(0.5))].into_iter().collect();
711    /// let buffer_states = ConstantBufferStates::new_override_defaults(infos, &overrides);
712    /// match buffer_states.get_numeric("numeric") {
713    ///   Some(NumericBufferState::Constant(0.5)) => (),
714    ///   _ => panic!("Expected constant value of 0.5"),
715    /// };
716    /// ```
717    pub fn new_override_defaults<'a, S: AsRef<str> + 'a>(
718        infos: impl IntoIterator<Item = InfoRef<'a, S>> + 'a,
719        overrides: &HashMap<&'_ str, InternalValue>,
720    ) -> Self {
721        Self::new(StatesMap::new_override_defaults(infos, overrides))
722    }
723
724    /// Create a new [`ConstantBufferStates`] object from a list of `Info`s.
725    ///
726    /// Each parameter in `Info`s will be set to its default value for the whole buffer.
727    ///
728    /// Note that if you want to pass this into a synth, you should use
729    /// [`Self::new_synth_defaults`] instead.
730    ///
731    /// # Examples
732    ///
733    /// ```
734    /// # use conformal_component::parameters::{StaticInfoRef, InternalValue, TypeSpecificInfoRef, ConstantBufferStates, BufferStates, NumericBufferState};
735    /// let infos = vec![
736    ///   StaticInfoRef {
737    ///     title: "Numeric",
738    ///     short_title: "Numeric",
739    ///     unique_id: "numeric",
740    ///     flags: Default::default(),
741    ///     type_specific: TypeSpecificInfoRef::Numeric {
742    ///       default: 0.0,
743    ///       valid_range: 0.0..=1.0,
744    ///       units: None,
745    ///     },
746    ///   },
747    /// ];
748    ///
749    /// let buffer_states = ConstantBufferStates::new_defaults(infos);
750    /// match buffer_states.get_numeric("numeric") {
751    ///   Some(NumericBufferState::Constant(0.0)) => (),
752    ///   _ => panic!("Expected constant value of 0.0"),
753    /// };
754    /// ```
755    pub fn new_defaults<'a, S: AsRef<str> + 'a>(
756        infos: impl IntoIterator<Item = InfoRef<'a, S>> + 'a,
757    ) -> Self {
758        Self::new_override_defaults(infos, &Default::default())
759    }
760
761    /// Create a new [`ConstantBufferStates`] object to pass to a synth from a list of `Info`s and `override`s.
762    ///
763    /// This is similar to [`Self::new_override_defaults`], but it also includes the controller parameters
764    /// that are common to all synths. ([`crate::synth::CONTROLLER_PARAMETERS`]).
765    ///
766    /// Thus, this is more appropriate to use if you plan to pass the parameters to a synth.
767    ///
768    /// # Examples
769    ///
770    /// ```
771    /// # use conformal_component::parameters::{StaticInfoRef, InternalValue, TypeSpecificInfoRef, ConstantBufferStates, BufferStates, NumericBufferState};
772    /// # use conformal_component::synth::{MOD_WHEEL_PARAMETER, PITCH_BEND_PARAMETER};
773    /// let infos = vec![
774    ///   StaticInfoRef {
775    ///     title: "Numeric",
776    ///     short_title: "Numeric",
777    ///     unique_id: "numeric",
778    ///     flags: Default::default(),
779    ///     type_specific: TypeSpecificInfoRef::Numeric {
780    ///       default: 0.0,
781    ///       valid_range: 0.0..=1.0,
782    ///       units: None,
783    ///     },
784    ///   },
785    /// ];
786    /// let overrides = vec![
787    ///   // You can override declared parameters
788    ///   ("numeric", InternalValue::Numeric(0.5)),
789    ///   // Or you can override control parameters
790    ///   (MOD_WHEEL_PARAMETER, InternalValue::Numeric(0.2)),
791    /// ].into_iter().collect();
792    ///
793    /// let buffer_states = ConstantBufferStates::new_override_synth_defaults(infos, &overrides);
794    ///
795    /// // Overridden parameters get the values you passed in
796    /// match buffer_states.get_numeric("numeric") {
797    ///   Some(NumericBufferState::Constant(0.5)) => (),
798    ///   _ => panic!("Expected constant value of 0.5"),
799    /// };
800    /// match buffer_states.get_numeric(MOD_WHEEL_PARAMETER) {
801    ///   Some(NumericBufferState::Constant(0.2)) => (),
802    ///   _ => panic!("Expected constant value of 0.2"),
803    /// };
804    ///
805    /// // Other parameters get their default values
806    /// match buffer_states.get_numeric(PITCH_BEND_PARAMETER) {
807    ///   Some(NumericBufferState::Constant(0.0)) => (),
808    ///   _ => panic!("Expected constant value of 0.0"),
809    /// };
810    /// ```
811    pub fn new_override_synth_defaults<'a, 'b: 'a>(
812        infos: impl IntoIterator<Item = InfoRef<'a, &'b str>> + 'a,
813        overrides: &HashMap<&'_ str, InternalValue>,
814    ) -> Self {
815        Self::new(StatesMap::new_override_synth_defaults(infos, overrides))
816    }
817
818    /// Create a new [`ConstantBufferStates`] object to pass to a synth from a list of `Info`s.
819    ///
820    /// Each parameter in `Info`s will be set to its default value for the whole buffer.
821    ///
822    /// This is similar to [`Self::new_defaults`], but it also includes the controller parameters
823    /// that are common to all synths. ([`crate::synth::CONTROLLER_PARAMETERS`]).
824    ///
825    /// Thus, this is more appropriate to use if you plan to pass the parameters to a synth.
826    ///
827    /// # Examples
828    ///
829    /// ```
830    /// # use conformal_component::parameters::{StaticInfoRef, InternalValue, TypeSpecificInfoRef, ConstantBufferStates, BufferStates, NumericBufferState};
831    /// # use conformal_component::synth::{MOD_WHEEL_PARAMETER};
832    /// let infos = vec![
833    ///   StaticInfoRef {
834    ///     title: "Numeric",
835    ///     short_title: "Numeric",
836    ///     unique_id: "numeric",
837    ///     flags: Default::default(),
838    ///     type_specific: TypeSpecificInfoRef::Numeric {
839    ///       default: 0.0,
840    ///       valid_range: 0.0..=1.0,
841    ///       units: None,
842    ///     },
843    ///   },
844    /// ];
845    ///
846    /// let buffer_states = ConstantBufferStates::new_synth_defaults(infos);
847    /// match buffer_states.get_numeric("numeric") {
848    ///   Some(NumericBufferState::Constant(0.0)) => (),
849    ///   _ => panic!("Expected constant value of 0.0"),
850    /// };
851    /// match buffer_states.get_numeric(MOD_WHEEL_PARAMETER) {
852    ///   Some(NumericBufferState::Constant(0.0)) => (),
853    ///   _ => panic!("Expected constant value of 0.0"),
854    /// };
855    /// ```
856    pub fn new_synth_defaults<'a, 'b: 'a>(
857        infos: impl IntoIterator<Item = InfoRef<'a, &'b str>> + 'a,
858    ) -> Self {
859        Self::new_override_synth_defaults(infos, &Default::default())
860    }
861}
862
863#[derive(Clone, Debug)]
864enum RampedState {
865    Constant(InternalValue),
866    RampedNumeric {
867        start: f32,
868        end: f32,
869        range: RangeInclusive<f32>,
870    },
871    RampedEnum {
872        start: u32,
873        end: u32,
874        range: Range<u32>,
875    },
876    RampedSwitch {
877        start: bool,
878        end: bool,
879    },
880}
881
882/// A simple implementation of a [`BufferStates`] that allows
883/// for parameters to change between the start and end of a buffer.
884///
885/// Each parameter can be either constant or ramped between two values.
886///
887/// For numeric parameters, the ramp is linear, for other parameter types
888/// the value changes half-way through the buffer.
889#[derive(Clone, Debug, Default)]
890pub struct RampedStatesMap {
891    buffer_size: usize,
892    map: HashMap<IdHash, RampedState>,
893}
894
895fn ramped_numeric(start: f32, end: f32, range: RangeInclusive<f32>) -> RampedState {
896    if approx_eq(start, end, 1e-6) {
897        RampedState::Constant(InternalValue::Numeric(start))
898    } else {
899        RampedState::RampedNumeric { start, end, range }
900    }
901}
902fn ramped_enum(start: u32, end: u32, num_vaules: usize) -> RampedState {
903    if start == end {
904        RampedState::Constant(InternalValue::Enum(start))
905    } else {
906        RampedState::RampedEnum {
907            start,
908            end,
909            range: 0..u32::try_from(num_vaules).unwrap(),
910        }
911    }
912}
913fn ramped_switch(start: bool, end: bool) -> RampedState {
914    if start == end {
915        RampedState::Constant(InternalValue::Switch(start))
916    } else {
917        RampedState::RampedSwitch { start, end }
918    }
919}
920
921impl RampedStatesMap {
922    /// Constructor that creates a `RampedStatesMap`
923    /// from a list of `Info`s and `override`s.at the start and end of the buffer.
924    ///
925    /// These overrides work the same way as in [`override_defaults`].
926    ///
927    /// Note that if you want to pass this into a synth, you should use [`Self::new_synth`] instead.
928    ///
929    /// # Examples
930    /// ```
931    /// # use conformal_component::parameters::{StaticInfoRef, InternalValue, TypeSpecificInfoRef, RampedStatesMap, NumericBufferState, BufferStates};
932    /// # use std::collections::HashMap;
933    /// let infos = vec![
934    ///   StaticInfoRef {
935    ///     title: "Numeric",
936    ///     short_title: "Numeric",
937    ///     unique_id: "numeric",
938    ///     flags: Default::default(),
939    ///     type_specific: TypeSpecificInfoRef::Numeric {
940    ///       default: 0.0,
941    ///       valid_range: 0.0..=1.0,
942    ///       units: None,
943    ///     },
944    ///   },
945    /// ];
946    ///
947    /// let start_overrides: HashMap<_, _> = vec![].into_iter().collect();
948    /// let end_overrides: HashMap<_, _> = vec![("numeric", InternalValue::Numeric(0.5))].into_iter().collect();
949    /// let states = RampedStatesMap::new(infos.iter().cloned(), &start_overrides, &end_overrides, 10);
950    ///
951    /// match states.get_numeric("numeric") {
952    ///   Some(NumericBufferState::PiecewiseLinear(_)) => (),
953    ///   _ => panic!("Expected a ramped value"),
954    /// };
955    /// ```
956    ///
957    /// # Panics
958    ///
959    /// Panics if `start_overrides` or `end_overrides` do not match the type of the parameter
960    /// specified in `infos`.
961    ///
962    /// Also panics if any of the enum parameters in `infos` has a number of values
963    /// that will not fit into a `u32`.
964    pub fn new<'a, S: AsRef<str> + 'a, H: BuildHasher, H_: BuildHasher>(
965        infos: impl IntoIterator<Item = InfoRef<'a, S>> + 'a,
966        start_overrides: &HashMap<&'_ str, InternalValue, H>,
967        end_overrides: &HashMap<&'_ str, InternalValue, H_>,
968        buffer_size: usize,
969    ) -> Self {
970        let map = infos
971            .into_iter()
972            .map(|info| {
973                let id = hash_id(info.unique_id);
974                let value = match (
975                    info.type_specific,
976                    start_overrides.get(info.unique_id),
977                    end_overrides.get(info.unique_id),
978                ) {
979                    (
980                        TypeSpecificInfoRef::Numeric { valid_range, .. },
981                        Some(InternalValue::Numeric(start)),
982                        Some(InternalValue::Numeric(end)),
983                    ) => ramped_numeric(*start, *end, valid_range),
984                    (
985                        TypeSpecificInfoRef::Numeric {
986                            default,
987                            valid_range,
988                            ..
989                        },
990                        None,
991                        Some(InternalValue::Numeric(end)),
992                    ) => ramped_numeric(default, *end, valid_range),
993                    (
994                        TypeSpecificInfoRef::Numeric {
995                            default,
996                            valid_range,
997                            ..
998                        },
999                        Some(InternalValue::Numeric(start)),
1000                        None,
1001                    ) => ramped_numeric(*start, default, valid_range),
1002                    (TypeSpecificInfoRef::Numeric { default, .. }, None, None) => {
1003                        RampedState::Constant(InternalValue::Numeric(default))
1004                    }
1005                    (
1006                        TypeSpecificInfoRef::Enum { values, .. },
1007                        Some(InternalValue::Enum(start)),
1008                        Some(InternalValue::Enum(end)),
1009                    ) => ramped_enum(*start, *end, values.len()),
1010                    (
1011                        TypeSpecificInfoRef::Enum {
1012                            default, values, ..
1013                        },
1014                        None,
1015                        Some(InternalValue::Enum(end)),
1016                    ) => ramped_enum(default, *end, values.len()),
1017                    (
1018                        TypeSpecificInfoRef::Enum {
1019                            default, values, ..
1020                        },
1021                        Some(InternalValue::Enum(start)),
1022                        None,
1023                    ) => ramped_enum(*start, default, values.len()),
1024                    (TypeSpecificInfoRef::Enum { default, .. }, None, None) => {
1025                        RampedState::Constant(InternalValue::Enum(default))
1026                    }
1027                    (
1028                        TypeSpecificInfoRef::Switch { .. },
1029                        Some(InternalValue::Switch(start)),
1030                        Some(InternalValue::Switch(end)),
1031                    ) => ramped_switch(*start, *end),
1032                    (
1033                        TypeSpecificInfoRef::Switch { default },
1034                        None,
1035                        Some(InternalValue::Switch(end)),
1036                    ) => ramped_switch(default, *end),
1037                    (
1038                        TypeSpecificInfoRef::Switch { default },
1039                        Some(InternalValue::Switch(start)),
1040                        None,
1041                    ) => ramped_switch(*start, default),
1042                    (TypeSpecificInfoRef::Switch { default }, None, None) => {
1043                        RampedState::Constant(InternalValue::Switch(default))
1044                    }
1045                    _ => panic!(),
1046                };
1047                (id, value)
1048            })
1049            .collect();
1050        Self { buffer_size, map }
1051    }
1052
1053    /// Create a new [`RampedStatesMap`] for synths from a list of `Info`s and `override`s.
1054    ///
1055    /// This is similar to [`Self::new`], but it also includes the controller parameters
1056    /// that are common to all synths. ([`crate::synth::CONTROLLER_PARAMETERS`]).
1057    ///
1058    /// Thus, this is more appropriate to use if you plan to pass the parameters to a synth.
1059    ///
1060    /// # Examples
1061    ///
1062    /// ```
1063    /// # use conformal_component::parameters::{StaticInfoRef, InternalValue, TypeSpecificInfoRef, RampedStatesMap, NumericBufferState, BufferStates};
1064    /// # use conformal_component::synth::{MOD_WHEEL_PARAMETER, PITCH_BEND_PARAMETER};
1065    /// let infos = vec![
1066    ///   StaticInfoRef {
1067    ///     title: "Numeric",
1068    ///     short_title: "Numeric",
1069    ///     unique_id: "numeric",
1070    ///     flags: Default::default(),
1071    ///     type_specific: TypeSpecificInfoRef::Numeric {
1072    ///       default: 0.0,
1073    ///       valid_range: 0.0..=1.0,
1074    ///       units: None,
1075    ///     },
1076    ///   },
1077    /// ];
1078    ///
1079    /// let start_overrides = vec![(MOD_WHEEL_PARAMETER, InternalValue::Numeric(1.0))].into_iter().collect();
1080    /// let end_overrides = vec![("numeric", InternalValue::Numeric(0.5))].into_iter().collect();
1081    /// let states = RampedStatesMap::new_synth(
1082    ///   infos.iter().cloned(),
1083    ///   &start_overrides,
1084    ///   &end_overrides,
1085    ///   10
1086    /// );
1087    ///
1088    /// // If we only overrode a value at the beginning or end
1089    /// // it should be ramped
1090    /// match states.get_numeric("numeric") {
1091    ///   Some(NumericBufferState::PiecewiseLinear(_)) => (),
1092    ///   _ => panic!("Expected a ramped value"),
1093    /// };
1094    /// match states.get_numeric(MOD_WHEEL_PARAMETER) {
1095    ///   Some(NumericBufferState::PiecewiseLinear(_)) => (),
1096    ///   _ => panic!("Expected a ramped value"),
1097    /// };
1098    ///
1099    /// // Params left at default should be constants
1100    /// match states.get_numeric(PITCH_BEND_PARAMETER) {
1101    ///   Some(NumericBufferState::Constant(0.0)) => (),
1102    ///   _ => panic!("Expected a constant value"),
1103    /// };
1104    /// ```
1105    ///
1106    /// # Panics
1107    ///
1108    /// Panics if `start_overrides` or `end_overrides` do not match the type of the parameter
1109    /// specified in `infos`.
1110    pub fn new_synth<'a, 'b: 'a>(
1111        infos: impl IntoIterator<Item = InfoRef<'a, &'b str>> + 'a,
1112        start_overrides: &HashMap<&'_ str, InternalValue>,
1113        end_overrides: &HashMap<&'_ str, InternalValue>,
1114        buffer_size: usize,
1115    ) -> Self {
1116        Self::new(
1117            infos.into_iter().chain(CONTROLLER_PARAMETERS),
1118            start_overrides,
1119            end_overrides,
1120            buffer_size,
1121        )
1122    }
1123
1124    /// Helper to make a `RampedStatesMap` with all parameters constant.
1125    ///
1126    /// This is useful for _performance_ testing because while the parameters
1127    /// are constant at run-time, the `RampedStatesMap` has the ability to
1128    /// ramp between values, so consumers cannot be specialized to handle constant
1129    /// values only
1130    ///
1131    /// Note that if you want to pass this into a synth, you should use [`Self::new_const_synth`]
1132    /// instead.
1133    ///
1134    /// # Examples
1135    ///
1136    /// ```
1137    /// # use conformal_component::parameters::{StaticInfoRef, InternalValue, TypeSpecificInfoRef, RampedStatesMap, NumericBufferState, BufferStates};
1138    /// let infos = vec![
1139    ///   StaticInfoRef {
1140    ///     title: "Numeric",
1141    ///     short_title: "Numeric",
1142    ///     unique_id: "numeric",
1143    ///     flags: Default::default(),
1144    ///     type_specific: TypeSpecificInfoRef::Numeric {
1145    ///       default: 0.0,
1146    ///       valid_range: 0.0..=1.0,
1147    ///       units: None,
1148    ///     },
1149    ///   },
1150    /// ];
1151    ///
1152    /// let overrides = vec![("numeric", InternalValue::Numeric(0.5))].into_iter().collect();
1153    /// let states = RampedStatesMap::new_const(infos.iter().cloned(), &overrides);
1154    /// match states.get_numeric("numeric") {
1155    ///   Some(NumericBufferState::Constant(0.5)) => (),
1156    ///   _ => panic!("Expected constant value of 0.5"),
1157    /// };
1158    /// ```
1159    pub fn new_const<'a, S: AsRef<str> + 'a>(
1160        infos: impl IntoIterator<Item = InfoRef<'a, S>> + 'a,
1161        overrides: &HashMap<&'_ str, InternalValue>,
1162    ) -> Self {
1163        Self::new(infos, overrides, overrides, 0)
1164    }
1165
1166    /// Create a new [`RampedStatesMap`] for synths with all parameters constant.
1167    ///
1168    /// This is useful for _performance_ testing because while the parameters
1169    /// are constant at run-time, the `RampedStatesMap` has the ability to
1170    /// ramp between values, so consumers cannot be specialized to handle constant
1171    /// values only
1172    ///
1173    /// This is similar to [`Self::new_const`], but it also includes the controller parameters
1174    /// that are common to all synths. ([`crate::synth::CONTROLLER_PARAMETERS`]).
1175    ///
1176    /// Thus, this is more appropriate to use if you plan to pass the parameters to a synth.
1177    ///
1178    /// # Examples
1179    ///
1180    /// ```
1181    /// # use conformal_component::parameters::{StaticInfoRef, InternalValue, TypeSpecificInfoRef, RampedStatesMap, NumericBufferState, BufferStates};
1182    /// # use conformal_component::synth::{MOD_WHEEL_PARAMETER};
1183    ///
1184    /// let infos = vec![
1185    ///   StaticInfoRef {
1186    ///     title: "Numeric",
1187    ///     short_title: "Numeric",
1188    ///     unique_id: "numeric",
1189    ///     flags: Default::default(),
1190    ///     type_specific: TypeSpecificInfoRef::Numeric {
1191    ///       default: 0.0,
1192    ///       valid_range: 0.0..=1.0,
1193    ///       units: None,
1194    ///     },
1195    ///   },
1196    /// ];
1197    /// let overrides = vec![("numeric", InternalValue::Numeric(0.5))].into_iter().collect();
1198    /// let states = RampedStatesMap::new_const_synth(infos.iter().cloned(), &overrides);
1199    ///
1200    /// // Overridden parameters get the values you passed in
1201    /// match states.get_numeric("numeric") {
1202    ///   Some(NumericBufferState::Constant(0.5)) => (),
1203    ///   _ => panic!("Expected constant value of 0.5"),
1204    /// };
1205    ///
1206    /// // Controller parameters will also be included
1207    /// match states.get_numeric(MOD_WHEEL_PARAMETER) {
1208    ///   Some(NumericBufferState::Constant(0.0)) => (),
1209    ///   _ => panic!("Expected constant value of 0.0"),
1210    /// };
1211    /// ```
1212    pub fn new_const_synth<'a, 'b: 'a>(
1213        infos: impl IntoIterator<Item = InfoRef<'a, &'b str>> + 'a,
1214        overrides: &HashMap<&'_ str, InternalValue>,
1215    ) -> Self {
1216        Self::new_synth(infos, overrides, overrides, 0)
1217    }
1218}
1219
1220fn ramp_numeric(
1221    start: f32,
1222    end: f32,
1223    buffer_size: usize,
1224) -> impl Iterator<Item = PiecewiseLinearCurvePoint> + Clone {
1225    [0, 1].iter().map(move |i| {
1226        let value = if *i == 0 { start } else { end };
1227        let sample_offset = if *i == 0 { 0 } else { buffer_size - 1 };
1228        PiecewiseLinearCurvePoint {
1229            sample_offset,
1230            value,
1231        }
1232    })
1233}
1234
1235fn ramp_enum(
1236    start: u32,
1237    end: u32,
1238    buffer_size: usize,
1239) -> impl Iterator<Item = TimedValue<u32>> + Clone {
1240    [0, 1].iter().map(move |i| {
1241        let value = if *i == 0 { start } else { end };
1242        let sample_offset = if *i == 0 { 0 } else { buffer_size / 2 };
1243        TimedValue {
1244            sample_offset,
1245            value,
1246        }
1247    })
1248}
1249
1250fn ramp_switch(
1251    start: bool,
1252    end: bool,
1253    buffer_size: usize,
1254) -> impl Iterator<Item = TimedValue<bool>> + Clone {
1255    [0, 1].iter().map(move |i| {
1256        let value = if *i == 0 { start } else { end };
1257        let sample_offset = if *i == 0 { 0 } else { buffer_size / 2 };
1258        TimedValue {
1259            sample_offset,
1260            value,
1261        }
1262    })
1263}
1264
1265impl BufferStates for RampedStatesMap {
1266    fn get_by_hash(
1267        &self,
1268        id_hash: IdHash,
1269    ) -> std::option::Option<
1270        BufferState<
1271            impl Iterator<Item = PiecewiseLinearCurvePoint> + Clone,
1272            impl Iterator<Item = TimedValue<u32>> + Clone,
1273            impl Iterator<Item = TimedValue<bool>> + Clone,
1274        >,
1275    > {
1276        let param = self.map.get(&id_hash)?;
1277        match param {
1278            RampedState::Constant(value) => match value {
1279                InternalValue::Numeric(n) => {
1280                    Some(BufferState::Numeric(NumericBufferState::Constant(*n)))
1281                }
1282                InternalValue::Enum(e) => Some(BufferState::Enum(EnumBufferState::Constant(*e))),
1283                InternalValue::Switch(s) => {
1284                    Some(BufferState::Switch(SwitchBufferState::Constant(*s)))
1285                }
1286            },
1287            RampedState::RampedNumeric { start, end, range } => Some(BufferState::Numeric(
1288                NumericBufferState::PiecewiseLinear(PiecewiseLinearCurve::new(
1289                    ramp_numeric(*start, *end, self.buffer_size),
1290                    self.buffer_size,
1291                    range.clone(),
1292                )?),
1293            )),
1294            RampedState::RampedEnum { start, end, range } => Some(BufferState::Enum(
1295                EnumBufferState::Varying(TimedEnumValues::new(
1296                    ramp_enum(*start, *end, self.buffer_size),
1297                    self.buffer_size,
1298                    range.clone(),
1299                )?),
1300            )),
1301            RampedState::RampedSwitch { start, end } => Some(BufferState::Switch(
1302                SwitchBufferState::Varying(TimedSwitchValues::new(
1303                    ramp_switch(*start, *end, self.buffer_size),
1304                    self.buffer_size,
1305                )?),
1306            )),
1307        }
1308    }
1309}
1310
1311#[cfg(test)]
1312mod tests {
1313    use crate::audio::all_approx_eq;
1314
1315    use super::super::{
1316        PiecewiseLinearCurve, PiecewiseLinearCurvePoint, TimedEnumValues, TimedSwitchValues,
1317        TimedValue,
1318    };
1319    use super::{
1320        piecewise_linear_curve_per_sample, timed_enum_per_sample, timed_switch_per_sample,
1321    };
1322
1323    const TEST_EPSILON: f32 = 1e-7;
1324
1325    #[test]
1326    fn piecewise_linear_curve_per_sample_basics() {
1327        let vals = piecewise_linear_curve_per_sample(
1328            PiecewiseLinearCurve::new(
1329                (&[
1330                    PiecewiseLinearCurvePoint {
1331                        sample_offset: 0,
1332                        value: 0.0,
1333                    },
1334                    PiecewiseLinearCurvePoint {
1335                        sample_offset: 5,
1336                        value: 5.0,
1337                    },
1338                    PiecewiseLinearCurvePoint {
1339                        sample_offset: 7,
1340                        value: 5.0,
1341                    },
1342                    PiecewiseLinearCurvePoint {
1343                        sample_offset: 8,
1344                        value: 10.0,
1345                    },
1346                ])
1347                    .iter()
1348                    .cloned(),
1349                10,
1350                0.0..=10.0,
1351            )
1352            .unwrap(),
1353        )
1354        .collect::<Vec<_>>();
1355        assert!(all_approx_eq(
1356            vals.iter().copied(),
1357            ([0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 5.0, 5.0, 10.0, 10.0])
1358                .iter()
1359                .copied(),
1360            TEST_EPSILON
1361        ));
1362    }
1363
1364    #[test]
1365    fn timed_enum_per_sample_basics() {
1366        let vals = timed_enum_per_sample(
1367            TimedEnumValues::new(
1368                (&[
1369                    TimedValue {
1370                        sample_offset: 0,
1371                        value: 0,
1372                    },
1373                    TimedValue {
1374                        sample_offset: 7,
1375                        value: 2,
1376                    },
1377                    TimedValue {
1378                        sample_offset: 8,
1379                        value: 3,
1380                    },
1381                ])
1382                    .iter()
1383                    .cloned(),
1384                10,
1385                0..4,
1386            )
1387            .unwrap(),
1388        )
1389        .collect::<Vec<_>>();
1390        assert!(
1391            vals.iter()
1392                .copied()
1393                .zip(([0, 0, 0, 0, 0, 0, 0, 2, 3, 3]).iter().copied())
1394                .all(|(a, b)| a == b)
1395        );
1396    }
1397
1398    #[test]
1399    fn timed_switch_per_sample_basics() {
1400        let vals = timed_switch_per_sample(
1401            TimedSwitchValues::new(
1402                (&[
1403                    TimedValue {
1404                        sample_offset: 0,
1405                        value: false,
1406                    },
1407                    TimedValue {
1408                        sample_offset: 7,
1409                        value: true,
1410                    },
1411                    TimedValue {
1412                        sample_offset: 8,
1413                        value: false,
1414                    },
1415                ])
1416                    .iter()
1417                    .cloned(),
1418                10,
1419            )
1420            .unwrap(),
1421        )
1422        .collect::<Vec<_>>();
1423        assert!(
1424            vals.iter()
1425                .copied()
1426                .zip(
1427                    ([
1428                        false, false, false, false, false, false, false, true, false, false
1429                    ])
1430                    .iter()
1431                    .copied()
1432                )
1433                .all(|(a, b)| a == b)
1434        );
1435    }
1436}