conformal_component/parameters/utils/ramped.rs
1use std::{
2 collections::{HashMap, HashSet},
3 hash::BuildHasher,
4 ops::{Range, RangeInclusive},
5};
6
7use crate::{
8 audio::approx_eq,
9 events::NoteID,
10 synth::{
11 NumericGlobalExpression, NumericPerNoteExpression, SwitchGlobalExpression,
12 SynthParamBufferStates, valid_range_for_per_note_expression,
13 },
14};
15
16use super::super::{
17 BufferState, BufferStates, EnumBufferState, IdHash, IdHashMap, InfoRef, InternalValue,
18 NumericBufferState, PiecewiseLinearCurve, PiecewiseLinearCurvePoint, SwitchBufferState,
19 TimedEnumValues, TimedSwitchValues, TimedValue, TypeSpecificInfoRef, hash_id,
20};
21
22#[derive(Clone, Debug)]
23struct RampedNumeric {
24 start: f32,
25 end: f32,
26 range: RangeInclusive<f32>,
27}
28
29#[derive(Clone, Debug)]
30struct RampedEnum {
31 start: u32,
32 end: u32,
33 range: Range<u32>,
34}
35
36#[derive(Clone, Debug)]
37struct RampedSwitch {
38 start: bool,
39 end: bool,
40}
41
42#[derive(Clone, Debug)]
43enum RampedState {
44 Constant(InternalValue),
45 Numeric(RampedNumeric),
46 Enum(RampedEnum),
47 Switch(RampedSwitch),
48}
49
50/// A simple implementation of a [`BufferStates`] that allows
51/// for parameters to change between the start and end of a buffer.
52///
53/// Each parameter can be either constant or ramped between two values.
54///
55/// For numeric parameters, the ramp is linear, for other parameter types
56/// the value changes half-way through the buffer.
57#[derive(Clone, Debug, Default)]
58pub struct RampedStatesMap {
59 buffer_size: usize,
60 map: IdHashMap<RampedState>,
61}
62
63fn ramped_numeric(start: f32, end: f32, range: RangeInclusive<f32>) -> RampedState {
64 if approx_eq(start, end, 1e-6) {
65 RampedState::Constant(InternalValue::Numeric(start))
66 } else {
67 RampedState::Numeric(RampedNumeric { start, end, range })
68 }
69}
70
71fn ramped_enum(start: u32, end: u32, num_values: usize) -> RampedState {
72 if start == end {
73 RampedState::Constant(InternalValue::Enum(start))
74 } else {
75 RampedState::Enum(RampedEnum {
76 start,
77 end,
78 range: 0..u32::try_from(num_values).unwrap(),
79 })
80 }
81}
82
83fn ramped_switch(start: bool, end: bool) -> RampedState {
84 if start == end {
85 RampedState::Constant(InternalValue::Switch(start))
86 } else {
87 RampedState::Switch(RampedSwitch { start, end })
88 }
89}
90
91fn ramp_for_numeric(
92 default: f32,
93 valid_range: RangeInclusive<f32>,
94 start_override: Option<InternalValue>,
95 end_override: Option<InternalValue>,
96) -> RampedState {
97 let start = match start_override {
98 Some(InternalValue::Numeric(v)) => v,
99 None => default,
100 _ => panic!(),
101 };
102 let end = match end_override {
103 Some(InternalValue::Numeric(v)) => v,
104 None => default,
105 _ => panic!(),
106 };
107 ramped_numeric(start, end, valid_range)
108}
109
110fn ramp_for_enum(
111 default: u32,
112 num_values: usize,
113 start_override: Option<InternalValue>,
114 end_override: Option<InternalValue>,
115) -> RampedState {
116 let start = match start_override {
117 Some(InternalValue::Enum(v)) => v,
118 None => default,
119 _ => panic!(),
120 };
121 let end = match end_override {
122 Some(InternalValue::Enum(v)) => v,
123 None => default,
124 _ => panic!(),
125 };
126 ramped_enum(start, end, num_values)
127}
128
129fn ramp_for_switch(
130 default: bool,
131 start_override: Option<InternalValue>,
132 end_override: Option<InternalValue>,
133) -> RampedState {
134 let start = match start_override {
135 Some(InternalValue::Switch(v)) => v,
136 None => default,
137 _ => panic!(),
138 };
139 let end = match end_override {
140 Some(InternalValue::Switch(v)) => v,
141 None => default,
142 _ => panic!(),
143 };
144 ramped_switch(start, end)
145}
146
147impl RampedStatesMap {
148 /// Constructor that creates a `RampedStatesMap`
149 /// from a list of `Info`s and `override`s.at the start and end of the buffer.
150 ///
151 /// These overrides work the same way as in [`override_defaults`].
152 ///
153 /// Note for a synth, you should use [`SynthRampedStatesMap::new`] instead.
154 ///
155 /// # Examples
156 /// ```
157 /// # use conformal_component::parameters::{StaticInfoRef, InternalValue, TypeSpecificInfoRef, RampedStatesMap, NumericBufferState, BufferStates};
158 /// # use std::collections::HashMap;
159 /// let infos = vec![
160 /// StaticInfoRef {
161 /// title: "Numeric",
162 /// short_title: "Numeric",
163 /// unique_id: "numeric",
164 /// flags: Default::default(),
165 /// type_specific: TypeSpecificInfoRef::Numeric {
166 /// default: 0.0,
167 /// valid_range: 0.0..=1.0,
168 /// units: None,
169 /// },
170 /// },
171 /// ];
172 ///
173 /// let start_overrides: HashMap<_, _> = vec![].into_iter().collect();
174 /// let end_overrides: HashMap<_, _> = vec![("numeric", InternalValue::Numeric(0.5))].into_iter().collect();
175 /// let states = RampedStatesMap::new(infos.iter().cloned(), &start_overrides, &end_overrides, 10);
176 ///
177 /// match states.get_numeric("numeric") {
178 /// Some(NumericBufferState::PiecewiseLinear(_)) => (),
179 /// _ => panic!("Expected a ramped value"),
180 /// };
181 /// ```
182 ///
183 /// # Panics
184 ///
185 /// Panics if `start_overrides` or `end_overrides` do not match the type of the parameter
186 /// specified in `infos`.
187 ///
188 /// Also panics if any of the enum parameters in `infos` has a number of values
189 /// that will not fit into a `u32`.
190 pub fn new<'a, S: AsRef<str> + 'a, H: BuildHasher, H_: BuildHasher>(
191 infos: impl IntoIterator<Item = InfoRef<'a, S>> + 'a,
192 start_overrides: &HashMap<&'_ str, InternalValue, H>,
193 end_overrides: &HashMap<&'_ str, InternalValue, H_>,
194 buffer_size: usize,
195 ) -> Self {
196 let map = infos
197 .into_iter()
198 .map(|info| {
199 let id = hash_id(info.unique_id);
200 let start_override = start_overrides.get(info.unique_id);
201 let end_override = end_overrides.get(info.unique_id);
202 let value = match info.type_specific {
203 TypeSpecificInfoRef::Numeric {
204 default,
205 valid_range,
206 ..
207 } => ramp_for_numeric(
208 default,
209 valid_range,
210 start_override.copied(),
211 end_override.copied(),
212 ),
213 TypeSpecificInfoRef::Enum {
214 default, values, ..
215 } => ramp_for_enum(
216 default,
217 values.len(),
218 start_override.copied(),
219 end_override.copied(),
220 ),
221 TypeSpecificInfoRef::Switch { default } => {
222 ramp_for_switch(default, start_override.copied(), end_override.copied())
223 }
224 };
225 (id, value)
226 })
227 .collect();
228 Self { buffer_size, map }
229 }
230
231 /// Helper to make a `RampedStatesMap` with all parameters constant.
232 ///
233 /// This is useful for _performance_ testing because while the parameters
234 /// are constant at run-time, the `RampedStatesMap` has the ability to
235 /// ramp between values, so consumers cannot be specialized to handle constant
236 /// values only
237 ///
238 /// Note that if you want to pass this into a synth, you should use [`Self::new_const_synth`]
239 /// instead.
240 ///
241 /// # Examples
242 ///
243 /// ```
244 /// # use conformal_component::parameters::{StaticInfoRef, InternalValue, TypeSpecificInfoRef, RampedStatesMap, NumericBufferState, BufferStates};
245 /// let infos = vec![
246 /// StaticInfoRef {
247 /// title: "Numeric",
248 /// short_title: "Numeric",
249 /// unique_id: "numeric",
250 /// flags: Default::default(),
251 /// type_specific: TypeSpecificInfoRef::Numeric {
252 /// default: 0.0,
253 /// valid_range: 0.0..=1.0,
254 /// units: None,
255 /// },
256 /// },
257 /// ];
258 ///
259 /// let overrides = vec![("numeric", InternalValue::Numeric(0.5))].into_iter().collect();
260 /// let states = RampedStatesMap::new_const(infos.iter().cloned(), &overrides);
261 /// match states.get_numeric("numeric") {
262 /// Some(NumericBufferState::Constant(0.5)) => (),
263 /// _ => panic!("Expected constant value of 0.5"),
264 /// };
265 /// ```
266 pub fn new_const<'a, S: AsRef<str> + 'a>(
267 infos: impl IntoIterator<Item = InfoRef<'a, S>> + 'a,
268 overrides: &HashMap<&'_ str, InternalValue>,
269 ) -> Self {
270 Self::new(infos, overrides, overrides, 0)
271 }
272}
273
274fn ramp_numeric(
275 start: f32,
276 end: f32,
277 buffer_size: usize,
278) -> impl Iterator<Item = PiecewiseLinearCurvePoint> + Clone {
279 [0, 1].iter().map(move |i| {
280 let value = if *i == 0 { start } else { end };
281 let sample_offset = if *i == 0 { 0 } else { buffer_size - 1 };
282 PiecewiseLinearCurvePoint {
283 sample_offset,
284 value,
285 }
286 })
287}
288
289fn ramp_enum(
290 start: u32,
291 end: u32,
292 buffer_size: usize,
293) -> impl Iterator<Item = TimedValue<u32>> + Clone {
294 [0, 1].iter().map(move |i| {
295 let value = if *i == 0 { start } else { end };
296 let sample_offset = if *i == 0 { 0 } else { buffer_size / 2 };
297 TimedValue {
298 sample_offset,
299 value,
300 }
301 })
302}
303
304fn ramp_switch(
305 start: bool,
306 end: bool,
307 buffer_size: usize,
308) -> impl Iterator<Item = TimedValue<bool>> + Clone {
309 [0, 1].iter().map(move |i| {
310 let value = if *i == 0 { start } else { end };
311 let sample_offset = if *i == 0 { 0 } else { buffer_size / 2 };
312 TimedValue {
313 sample_offset,
314 value,
315 }
316 })
317}
318
319impl BufferStates for RampedStatesMap {
320 fn get_by_hash(
321 &self,
322 id_hash: IdHash,
323 ) -> std::option::Option<
324 BufferState<
325 impl Iterator<Item = PiecewiseLinearCurvePoint> + Clone,
326 impl Iterator<Item = TimedValue<u32>> + Clone,
327 impl Iterator<Item = TimedValue<bool>> + Clone,
328 >,
329 > {
330 let param = self.map.get(&id_hash)?;
331 match param {
332 RampedState::Constant(value) => match value {
333 InternalValue::Numeric(n) => {
334 Some(BufferState::Numeric(NumericBufferState::Constant(*n)))
335 }
336 InternalValue::Enum(e) => Some(BufferState::Enum(EnumBufferState::Constant(*e))),
337 InternalValue::Switch(s) => {
338 Some(BufferState::Switch(SwitchBufferState::Constant(*s)))
339 }
340 },
341 RampedState::Numeric(RampedNumeric { start, end, range }) => {
342 Some(BufferState::Numeric(NumericBufferState::PiecewiseLinear(
343 PiecewiseLinearCurve::new(
344 ramp_numeric(*start, *end, self.buffer_size),
345 self.buffer_size,
346 range.clone(),
347 )?,
348 )))
349 }
350 RampedState::Enum(RampedEnum { start, end, range }) => Some(BufferState::Enum(
351 EnumBufferState::Varying(TimedEnumValues::new(
352 ramp_enum(*start, *end, self.buffer_size),
353 self.buffer_size,
354 range.clone(),
355 )?),
356 )),
357 RampedState::Switch(RampedSwitch { start, end }) => Some(BufferState::Switch(
358 SwitchBufferState::Varying(TimedSwitchValues::new(
359 ramp_switch(*start, *end, self.buffer_size),
360 self.buffer_size,
361 )?),
362 )),
363 }
364 }
365}
366
367fn valid_range_for_numeric_global_expression(
368 expression: NumericGlobalExpression,
369) -> RangeInclusive<f32> {
370 match expression {
371 NumericGlobalExpression::PitchBend => -1.0..=1.0,
372 NumericGlobalExpression::Timbre
373 | NumericGlobalExpression::Aftertouch
374 | NumericGlobalExpression::ExpressionPedal
375 | NumericGlobalExpression::ModWheel => 0.0..=1.0,
376 }
377}
378
379fn ramp_numeric_expressions(
380 start_overrides: &HashMap<NumericGlobalExpression, f32>,
381 end_overrides: &HashMap<NumericGlobalExpression, f32>,
382) -> HashMap<NumericGlobalExpression, RampedState> {
383 let all_expressions = start_overrides
384 .keys()
385 .chain(end_overrides.keys())
386 .collect::<HashSet<_>>();
387 all_expressions
388 .into_iter()
389 .map(|expression| {
390 (
391 *expression,
392 ramp_for_numeric(
393 Default::default(),
394 valid_range_for_numeric_global_expression(*expression),
395 start_overrides
396 .get(expression)
397 .copied()
398 .map(InternalValue::Numeric),
399 end_overrides
400 .get(expression)
401 .copied()
402 .map(InternalValue::Numeric),
403 ),
404 )
405 })
406 .collect()
407}
408
409fn ramp_switch_expressions(
410 start_overrides: &HashMap<SwitchGlobalExpression, bool>,
411 end_overrides: &HashMap<SwitchGlobalExpression, bool>,
412) -> HashMap<SwitchGlobalExpression, RampedState> {
413 let all_expressions = start_overrides
414 .keys()
415 .chain(end_overrides.keys())
416 .collect::<HashSet<_>>();
417 all_expressions
418 .into_iter()
419 .map(|expression| {
420 (
421 *expression,
422 ramp_for_switch(
423 Default::default(),
424 start_overrides
425 .get(expression)
426 .copied()
427 .map(InternalValue::Switch),
428 end_overrides
429 .get(expression)
430 .copied()
431 .map(InternalValue::Switch),
432 ),
433 )
434 })
435 .collect()
436}
437
438fn ramp_per_note_expressions(
439 start_overrides: &HashMap<(NumericPerNoteExpression, NoteID), f32>,
440 end_overrides: &HashMap<(NumericPerNoteExpression, NoteID), f32>,
441) -> HashMap<(NumericPerNoteExpression, NoteID), RampedState> {
442 let all_keys = start_overrides
443 .keys()
444 .chain(end_overrides.keys())
445 .collect::<HashSet<_>>();
446 all_keys
447 .into_iter()
448 .map(|key| {
449 let (expression, _) = *key;
450 (
451 *key,
452 ramp_for_numeric(
453 Default::default(),
454 valid_range_for_per_note_expression(expression),
455 start_overrides
456 .get(key)
457 .copied()
458 .map(InternalValue::Numeric),
459 end_overrides.get(key).copied().map(InternalValue::Numeric),
460 ),
461 )
462 })
463 .collect()
464}
465
466/// A simple implementation of a [`SynthParamBufferStates`] that allows
467/// for parameters to change between the start and end of a buffer.
468///
469/// This is similar to [`RampedStatesMap`], but it also includes the expression controller parameters
470/// needed for synths.
471///
472/// Each parameter can be either constant or ramped between two values.
473///
474/// For numeric parameters, the ramp is linear, for other parameter types
475/// the value changes half-way through the buffer.
476pub struct SynthRampedStatesMap {
477 states: RampedStatesMap,
478 numeric_expressions: HashMap<NumericGlobalExpression, RampedState>,
479 switch_expressions: HashMap<SwitchGlobalExpression, RampedState>,
480 per_note_expressions: HashMap<(NumericPerNoteExpression, NoteID), RampedState>,
481}
482
483/// Params for [`SynthRampedStatesMap::new`]
484pub struct SynthRampedOverrides<'a, 'b> {
485 /// Overrides for parameters at the start of the buffer
486 pub start_params: &'a HashMap<&'b str, InternalValue>,
487 /// Overrides for parameters at the end of the buffer
488 pub end_params: &'a HashMap<&'b str, InternalValue>,
489 /// Overrides for numeric global expression controllerss at the start of the buffer
490 pub start_numeric_expressions: &'a HashMap<NumericGlobalExpression, f32>,
491 /// Overrides for numeric global expression controllers at the end of the buffer
492 pub end_numeric_expressions: &'a HashMap<NumericGlobalExpression, f32>,
493 /// Overrides for switch global expression controllers at the start of the buffer
494 pub start_switch_expressions: &'a HashMap<SwitchGlobalExpression, bool>,
495 /// Overrides for switch global expression controllers at the end of the buffer
496 pub end_switch_expressions: &'a HashMap<SwitchGlobalExpression, bool>,
497}
498
499impl SynthRampedStatesMap {
500 /// Create a new [`SynthRampedStatesMap`] for synths from a list of `Info`s and `override`s.
501 ///
502 /// This is similar to [`RampedStatesMap::new`], but it also includes the expression controller parameters.
503 ///
504 /// # Examples
505 ///
506 /// ```
507 /// # use conformal_component::parameters::{StaticInfoRef, InternalValue, TypeSpecificInfoRef, SynthRampedStatesMap, NumericBufferState, BufferStates, SynthRampedOverrides};
508 /// # use conformal_component::synth::{SynthParamBufferStates, NumericGlobalExpression};
509 /// let infos = vec![
510 /// StaticInfoRef {
511 /// title: "Numeric",
512 /// short_title: "Numeric",
513 /// unique_id: "numeric",
514 /// flags: Default::default(),
515 /// type_specific: TypeSpecificInfoRef::Numeric {
516 /// default: 0.0,
517 /// valid_range: 0.0..=1.0,
518 /// units: None,
519 /// },
520 /// },
521 /// ];
522 ///
523 /// let start_expression_overrides = vec![(NumericGlobalExpression::ModWheel, 1.0)].into_iter().collect();
524 /// let end_param_overrides = vec![("numeric", InternalValue::Numeric(0.5))].into_iter().collect();
525 /// let states = SynthRampedStatesMap::new(
526 /// infos.iter().cloned(),
527 /// SynthRampedOverrides {
528 /// start_params: &Default::default(),
529 /// end_params: &end_param_overrides,
530 /// start_numeric_expressions: &start_expression_overrides,
531 /// end_numeric_expressions: &Default::default(),
532 /// start_switch_expressions: &Default::default(),
533 /// end_switch_expressions: &Default::default(),
534 /// },
535 /// 10
536 /// );
537 ///
538 /// // If we only overrode a value at the beginning or end
539 /// // it should be ramped
540 /// match states.get_numeric("numeric") {
541 /// Some(NumericBufferState::PiecewiseLinear(_)) => (),
542 /// _ => panic!("Expected a ramped value"),
543 /// };
544 /// match states.get_numeric_global_expression(NumericGlobalExpression::ModWheel) {
545 /// NumericBufferState::PiecewiseLinear(_) => (),
546 /// _ => panic!("Expected a ramped value"),
547 /// };
548 ///
549 /// // Params left at default should be constants
550 /// match states.get_numeric_global_expression(NumericGlobalExpression::PitchBend) {
551 /// NumericBufferState::Constant(0.0) => (),
552 /// _ => panic!("Expected a constant value"),
553 /// };
554 /// ```
555 ///
556 /// # Panics
557 ///
558 /// Panics if `start_overrides` or `end_overrides` do not match the type of the parameter
559 /// specified in `infos`.
560 pub fn new<'a, S: AsRef<str> + 'a>(
561 infos: impl IntoIterator<Item = InfoRef<'a, S>> + 'a,
562 SynthRampedOverrides {
563 start_params,
564 end_params,
565 start_numeric_expressions,
566 end_numeric_expressions,
567 start_switch_expressions,
568 end_switch_expressions,
569 }: SynthRampedOverrides<'_, '_>,
570 buffer_size: usize,
571 ) -> Self {
572 Self {
573 states: RampedStatesMap::new(infos, start_params, end_params, buffer_size),
574 numeric_expressions: ramp_numeric_expressions(
575 start_numeric_expressions,
576 end_numeric_expressions,
577 ),
578 switch_expressions: ramp_switch_expressions(
579 start_switch_expressions,
580 end_switch_expressions,
581 ),
582 per_note_expressions: Default::default(),
583 }
584 }
585
586 /// Create a new [`SynthRampedStatesMap`] with per-note expression overrides.
587 ///
588 /// This is similar to [`Self::new`], but also allows specifying per-note expression
589 /// values for specific notes at the start and end of the buffer.
590 ///
591 /// # Examples
592 ///
593 /// ```
594 /// # use conformal_component::parameters::{StaticInfoRef, InternalValue, TypeSpecificInfoRef, SynthRampedStatesMap, NumericBufferState, BufferStates, SynthRampedOverrides};
595 /// # use conformal_component::synth::{SynthParamBufferStates, NumericGlobalExpression, NumericPerNoteExpression};
596 /// # use conformal_component::events::{NoteID};
597 /// let infos = vec![
598 /// StaticInfoRef {
599 /// title: "Numeric",
600 /// short_title: "Numeric",
601 /// unique_id: "numeric",
602 /// flags: Default::default(),
603 /// type_specific: TypeSpecificInfoRef::Numeric {
604 /// default: 0.0,
605 /// valid_range: 0.0..=1.0,
606 /// units: None,
607 /// },
608 /// },
609 /// ];
610 ///
611 /// let note_id = NoteID::from_pitch(60);
612 /// let start_per_note = vec![
613 /// ((NumericPerNoteExpression::PitchBend, note_id), 0.0),
614 /// ].into_iter().collect();
615 /// let end_per_note = vec![
616 /// ((NumericPerNoteExpression::PitchBend, note_id), 2.0),
617 /// ].into_iter().collect();
618 ///
619 /// let states = SynthRampedStatesMap::new_with_per_note(
620 /// infos.iter().cloned(),
621 /// SynthRampedOverrides {
622 /// start_params: &Default::default(),
623 /// end_params: &Default::default(),
624 /// start_numeric_expressions: &Default::default(),
625 /// end_numeric_expressions: &Default::default(),
626 /// start_switch_expressions: &Default::default(),
627 /// end_switch_expressions: &Default::default(),
628 /// },
629 /// &start_per_note,
630 /// &end_per_note,
631 /// 10,
632 /// );
633 ///
634 /// match states.get_numeric_expression_for_note(NumericPerNoteExpression::PitchBend, note_id) {
635 /// NumericBufferState::PiecewiseLinear(_) => (),
636 /// _ => panic!("Expected a ramped value"),
637 /// };
638 /// ```
639 pub fn new_with_per_note<'a, S: AsRef<str> + 'a>(
640 infos: impl IntoIterator<Item = InfoRef<'a, S>> + 'a,
641 SynthRampedOverrides {
642 start_params,
643 end_params,
644 start_numeric_expressions,
645 end_numeric_expressions,
646 start_switch_expressions,
647 end_switch_expressions,
648 }: SynthRampedOverrides<'_, '_>,
649 start_per_note_expressions: &HashMap<(NumericPerNoteExpression, NoteID), f32>,
650 end_per_note_expressions: &HashMap<(NumericPerNoteExpression, NoteID), f32>,
651 buffer_size: usize,
652 ) -> Self {
653 Self {
654 states: RampedStatesMap::new(infos, start_params, end_params, buffer_size),
655 numeric_expressions: ramp_numeric_expressions(
656 start_numeric_expressions,
657 end_numeric_expressions,
658 ),
659 switch_expressions: ramp_switch_expressions(
660 start_switch_expressions,
661 end_switch_expressions,
662 ),
663 per_note_expressions: ramp_per_note_expressions(
664 start_per_note_expressions,
665 end_per_note_expressions,
666 ),
667 }
668 }
669
670 /// Create a new [`SynthRampedStatesMap`] for synths with all parameters constant.
671 ///
672 /// This is useful for _performance_ testing because while the parameters
673 /// are constant at run-time, the `SynthRampedStatesMap` has the ability to
674 /// ramp between values, so consumers cannot be specialized to handle constant
675 /// values only
676 ///
677 /// This is similar to [`RampedStatesMap::new_const`], but it also includes the expression controller parameters.
678 ///
679 /// # Examples
680 ///
681 /// ```
682 /// # use conformal_component::parameters::{StaticInfoRef, InternalValue, TypeSpecificInfoRef, SynthRampedStatesMap, NumericBufferState, BufferStates};
683 /// # use conformal_component::synth::{SynthParamBufferStates, NumericGlobalExpression};
684 ///
685 /// let infos = vec![
686 /// StaticInfoRef {
687 /// title: "Numeric",
688 /// short_title: "Numeric",
689 /// unique_id: "numeric",
690 /// flags: Default::default(),
691 /// type_specific: TypeSpecificInfoRef::Numeric {
692 /// default: 0.0,
693 /// valid_range: 0.0..=1.0,
694 /// units: None,
695 /// },
696 /// },
697 /// ];
698 /// let overrides = vec![("numeric", InternalValue::Numeric(0.5))].into_iter().collect();
699 /// let states = SynthRampedStatesMap::new_const(infos.iter().cloned(), &overrides, &Default::default(), &Default::default());
700 ///
701 /// // Overridden parameters get the values you passed in
702 /// match states.get_numeric("numeric") {
703 /// Some(NumericBufferState::Constant(0.5)) => (),
704 /// _ => panic!("Expected constant value of 0.5"),
705 /// };
706 ///
707 /// // Controller parameters will also be included
708 /// match states.get_numeric_global_expression(NumericGlobalExpression::ModWheel) {
709 /// NumericBufferState::Constant(0.0) => (),
710 /// _ => panic!("Expected constant value of 0.0"),
711 /// };
712 /// ```
713 pub fn new_const<'a, S: AsRef<str> + 'a>(
714 infos: impl IntoIterator<Item = InfoRef<'a, S>> + 'a,
715 overrides: &HashMap<&'_ str, InternalValue>,
716 numeric_expression_overrides: &HashMap<NumericGlobalExpression, f32>,
717 switch_expression_overrides: &HashMap<SwitchGlobalExpression, bool>,
718 ) -> Self {
719 Self::new_with_per_note(
720 infos,
721 SynthRampedOverrides {
722 start_params: overrides,
723 end_params: overrides,
724 start_numeric_expressions: numeric_expression_overrides,
725 end_numeric_expressions: numeric_expression_overrides,
726 start_switch_expressions: switch_expression_overrides,
727 end_switch_expressions: switch_expression_overrides,
728 },
729 &Default::default(),
730 &Default::default(),
731 0,
732 )
733 }
734
735 /// Create a new [`SynthRampedStatesMap`] for synths with all parameters constant,
736 /// including per-note expression overrides.
737 ///
738 /// This is similar to [`Self::new_const`], but also allows specifying per-note
739 /// expression values for specific notes.
740 ///
741 /// # Examples
742 ///
743 /// ```
744 /// # use conformal_component::parameters::{StaticInfoRef, InternalValue, TypeSpecificInfoRef, SynthRampedStatesMap, NumericBufferState, BufferStates};
745 /// # use conformal_component::synth::{SynthParamBufferStates, NumericGlobalExpression, NumericPerNoteExpression};
746 /// # use conformal_component::events::{NoteID};
747 ///
748 /// let infos = vec![
749 /// StaticInfoRef {
750 /// title: "Numeric",
751 /// short_title: "Numeric",
752 /// unique_id: "numeric",
753 /// flags: Default::default(),
754 /// type_specific: TypeSpecificInfoRef::Numeric {
755 /// default: 0.0,
756 /// valid_range: 0.0..=1.0,
757 /// units: None,
758 /// },
759 /// },
760 /// ];
761 ///
762 /// let note_id = NoteID::from_pitch(60);
763 /// let per_note_overrides = vec![
764 /// ((NumericPerNoteExpression::PitchBend, note_id), 1.5),
765 /// ].into_iter().collect();
766 ///
767 /// let states = SynthRampedStatesMap::new_const_with_per_note(
768 /// infos.iter().cloned(),
769 /// &Default::default(),
770 /// &Default::default(),
771 /// &Default::default(),
772 /// &per_note_overrides,
773 /// );
774 ///
775 /// match states.get_numeric_expression_for_note(NumericPerNoteExpression::PitchBend, note_id) {
776 /// NumericBufferState::Constant(v) if (v - 1.5).abs() < 1e-6 => (),
777 /// _ => panic!("Expected constant value of 1.5"),
778 /// };
779 /// ```
780 pub fn new_const_with_per_note<'a, S: AsRef<str> + 'a>(
781 infos: impl IntoIterator<Item = InfoRef<'a, S>> + 'a,
782 overrides: &HashMap<&'_ str, InternalValue>,
783 numeric_expression_overrides: &HashMap<NumericGlobalExpression, f32>,
784 switch_expression_overrides: &HashMap<SwitchGlobalExpression, bool>,
785 per_note_expression_overrides: &HashMap<(NumericPerNoteExpression, NoteID), f32>,
786 ) -> Self {
787 Self::new_with_per_note(
788 infos,
789 SynthRampedOverrides {
790 start_params: overrides,
791 end_params: overrides,
792 start_numeric_expressions: numeric_expression_overrides,
793 end_numeric_expressions: numeric_expression_overrides,
794 start_switch_expressions: switch_expression_overrides,
795 end_switch_expressions: switch_expression_overrides,
796 },
797 per_note_expression_overrides,
798 per_note_expression_overrides,
799 0,
800 )
801 }
802}
803
804impl BufferStates for SynthRampedStatesMap {
805 fn get_by_hash(
806 &self,
807 id_hash: IdHash,
808 ) -> std::option::Option<
809 BufferState<
810 impl Iterator<Item = PiecewiseLinearCurvePoint> + Clone,
811 impl Iterator<Item = TimedValue<u32>> + Clone,
812 impl Iterator<Item = TimedValue<bool>> + Clone,
813 >,
814 > {
815 self.states.get_by_hash(id_hash)
816 }
817}
818
819impl SynthParamBufferStates for SynthRampedStatesMap {
820 fn get_numeric_global_expression(
821 &self,
822 expression: NumericGlobalExpression,
823 ) -> NumericBufferState<impl Iterator<Item = PiecewiseLinearCurvePoint> + Clone> {
824 match self.numeric_expressions.get(&expression) {
825 Some(RampedState::Constant(InternalValue::Numeric(v))) => {
826 NumericBufferState::Constant(*v)
827 }
828 Some(RampedState::Numeric(RampedNumeric { start, end, range })) => {
829 let curve = PiecewiseLinearCurve::new(
830 ramp_numeric(*start, *end, self.states.buffer_size),
831 self.states.buffer_size,
832 range.clone(),
833 );
834 if let Some(curve) = curve {
835 NumericBufferState::PiecewiseLinear(curve)
836 } else {
837 panic!(
838 "{start} -> {end} is not a valid ramp for {expression:?} (range: {range:?})"
839 );
840 }
841 }
842
843 None => NumericBufferState::Constant(Default::default()),
844 _ => unreachable!(
845 "internal invariant violation: expected a numeric global expression to be either constant or ramped numeric"
846 ),
847 }
848 }
849
850 fn get_switch_global_expression(
851 &self,
852 expression: SwitchGlobalExpression,
853 ) -> SwitchBufferState<impl Iterator<Item = TimedValue<bool>> + Clone> {
854 match self.switch_expressions.get(&expression) {
855 Some(RampedState::Constant(InternalValue::Switch(v))) => {
856 SwitchBufferState::Constant(*v)
857 }
858 Some(RampedState::Switch(RampedSwitch { start, end })) => {
859 let values = TimedSwitchValues::new(
860 ramp_switch(*start, *end, self.states.buffer_size),
861 self.states.buffer_size,
862 );
863 if let Some(values) = values {
864 SwitchBufferState::Varying(values)
865 } else {
866 unreachable!(
867 "TimedSwitchValues invariant violated when ramping {expression:?} from {start} to {end}"
868 )
869 }
870 }
871 None => SwitchBufferState::Constant(Default::default()),
872 _ => unreachable!(
873 "internal invariant violation: expected a switch global expression to be either constant or ramped switch"
874 ),
875 }
876 }
877
878 fn get_numeric_expression_for_note(
879 &self,
880 expression: NumericPerNoteExpression,
881 note_id: NoteID,
882 ) -> NumericBufferState<impl Iterator<Item = PiecewiseLinearCurvePoint> + Clone> {
883 match self.per_note_expressions.get(&(expression, note_id)) {
884 Some(RampedState::Constant(InternalValue::Numeric(v))) => {
885 NumericBufferState::Constant(*v)
886 }
887 Some(RampedState::Numeric(RampedNumeric { start, end, range })) => {
888 let curve = PiecewiseLinearCurve::new(
889 ramp_numeric(*start, *end, self.states.buffer_size),
890 self.states.buffer_size,
891 range.clone(),
892 );
893 if let Some(curve) = curve {
894 NumericBufferState::PiecewiseLinear(curve)
895 } else {
896 panic!(
897 "{start} -> {end} is not a valid ramp for {expression:?} (range: {range:?})"
898 );
899 }
900 }
901 None => NumericBufferState::Constant(Default::default()),
902 _ => unreachable!(
903 "internal invariant violation: expected a per-note expression to be either constant or ramped numeric"
904 ),
905 }
906 }
907}