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}