conformal_component/parameters/utils/
pzip.rs

1#[macro_export]
2#[doc(hidden)]
3macro_rules! pzip_part {
4    (numeric $path:literal $params:ident) => {{
5        use $crate::parameters::BufferStates;
6        $crate::parameters::decompose_numeric(
7            $params
8                .numeric_by_hash(const { $crate::parameters::hash_id($path) })
9                .unwrap(),
10        )
11    }};
12    (enum $path:literal $params:ident) => {{
13        use $crate::parameters::BufferStates;
14        $crate::parameters::decompose_enum(
15            $params
16                .enum_by_hash(const { $crate::parameters::hash_id($path) })
17                .unwrap(),
18        )
19    }};
20    (switch $path:literal $params:ident) => {{
21        use $crate::parameters::BufferStates;
22        $crate::parameters::decompose_switch(
23            $params
24                .switch_by_hash(const { $crate::parameters::hash_id($path) })
25                .unwrap(),
26        )
27    }};
28    (global_expression_numeric $variant:ident $params:ident) => {{
29        use $crate::synth::{NumericGlobalExpression, SynthParamBufferStates};
30        $crate::parameters::decompose_numeric(
31            $params.get_numeric_global_expression(NumericGlobalExpression::$variant),
32        )
33    }};
34    (global_expression_switch $variant:ident $params:ident) => {{
35        use $crate::synth::{SwitchGlobalExpression, SynthParamBufferStates};
36        $crate::parameters::decompose_switch(
37            $params.get_switch_global_expression(SwitchGlobalExpression::$variant),
38        )
39    }};
40    (external_numeric $expr:tt $params:ident) => {{ $crate::parameters::decompose_numeric($expr) }};
41}
42
43#[macro_export]
44#[doc(hidden)]
45macro_rules! pzip_value_type {
46    (numeric) => {
47        f32
48    };
49    (enum) => {
50        u32
51    };
52    (switch) => {
53        bool
54    };
55    (global_expression_numeric) => {
56        f32
57    };
58    (global_expression_switch) => {
59        bool
60    };
61    (external_numeric) => {
62        f32
63    };
64}
65
66#[macro_export]
67#[doc(hidden)]
68macro_rules! pzip_collect {
69    // Base case: Generate the struct and function
70    (
71        $params:ident,
72        [], // No more inputs
73        [ $($names:ident,)* ], // Remaining names
74        [ $($acc_name:ident $acc_kind:ident $acc_path:tt)* ] // Accumulated
75    ) => {
76        {
77            #[allow(unused_parens, non_snake_case, clippy::too_many_arguments)]
78            fn pzip_impl<
79                $($acc_name: Iterator<Item = $crate::pzip_value_type!($acc_kind)> + Clone),*
80            >(
81                $($acc_name: ($crate::pzip_value_type!($acc_kind), Option<$acc_name>)),*
82            ) -> impl Iterator<Item = ($($crate::pzip_value_type!($acc_kind)),*)> + Clone {
83                #[derive(Clone, Copy)]
84                #[allow(non_snake_case)]
85                struct Values<$($acc_name: Copy),*> {
86                    $($acc_name: $acc_name),*
87                }
88
89                #[derive(Clone)]
90                #[allow(non_snake_case)]
91                struct Iters<$($acc_name),*> {
92                    $($acc_name: Option<$acc_name>),*
93                }
94
95                struct PZipIter<$($acc_name),*> {
96                    values: Values<$($crate::pzip_value_type!($acc_kind)),*>,
97                    iters: Iters<$($acc_name),*>,
98                    mask: u64,
99                }
100
101                impl<$($acc_name: Clone),*> Clone for PZipIter<$($acc_name),*> {
102                    fn clone(&self) -> Self {
103                        PZipIter {
104                            values: self.values,
105                            iters: Iters { $($acc_name: self.iters.$acc_name.clone()),* },
106                            mask: self.mask,
107                        }
108                    }
109                }
110
111                impl<$($acc_name: Iterator<Item = $crate::pzip_value_type!($acc_kind)> + Clone),*> Iterator for PZipIter<$($acc_name),*> {
112                    #[allow(unused_parens)]
113                    type Item = ($($crate::pzip_value_type!($acc_kind)),*);
114
115                    #[inline(always)]
116                    fn next(&mut self) -> Option<Self::Item> {
117                        {
118                            let mut _bit = 1u64;
119                            $(
120                                if self.mask & _bit != 0 {
121                                    self.values.$acc_name = self.iters.$acc_name.as_mut().unwrap().next()?;
122                                }
123                                _bit <<= 1;
124                            )*
125                        }
126                        Some(($(self.values.$acc_name),*))
127                    }
128                }
129
130                let mut mask = 0u64;
131                {
132                    let mut _bit = 1u64;
133                    $(
134                        if $acc_name.1.is_some() {
135                            mask |= _bit;
136                        }
137                        _bit <<= 1;
138                    )*
139                }
140
141                PZipIter {
142                    values: Values { $($acc_name: $acc_name.0),* },
143                    iters: Iters { $($acc_name: $acc_name.1),* },
144                    mask,
145                }
146            }
147
148            pzip_impl(
149                $( $crate::pzip_part!($acc_kind $acc_path $params) ),*
150            )
151        }
152    };
153
154    // Recursive step
155    (
156        $params:ident,
157        [ $k:ident $p:tt $(, $rest_k:ident $rest_p:tt)* ],
158        [ $next_name:ident, $($rest_names:ident,)* ],
159        [ $($acc:tt)* ]
160    ) => {
161        $crate::pzip_collect!(
162            $params,
163            [ $($rest_k $rest_p),* ],
164            [ $($rest_names,)* ],
165            [ $($acc)* $next_name $k $p ]
166        )
167    };
168}
169
170/// Utility to get a per-sample iterator including the state of multiple parameters.
171///
172/// This is a convenient way to consume a [`BufferStates`] object if you intend
173/// to track the per-sample state of multiple parameters.
174///
175/// This macro indexes into a [`BufferStates`] object with a list of parameter
176/// ids and their types. See the examples below for usage.
177///
178/// # Examples
179///
180/// ```
181/// # use conformal_component::pzip;
182/// # use conformal_component::parameters::{ConstantBufferStates, StaticInfoRef, TypeSpecificInfoRef, InternalValue};
183/// let params = ConstantBufferStates::new_defaults(
184///   vec![
185///     StaticInfoRef {
186///       title: "Numeric",
187///       short_title: "Numeric",
188///       unique_id: "gain",
189///       flags: Default::default(),
190///       type_specific: TypeSpecificInfoRef::Numeric {
191///         default: 0.0,
192///         valid_range: 0.0..=1.0,
193///         units: None,
194///       },
195///     },
196///     StaticInfoRef {
197///       title: "Enum",
198///       short_title: "Enum",
199///       unique_id: "letter",
200///       flags: Default::default(),
201///       type_specific: TypeSpecificInfoRef::Enum {
202///         default: 1,
203///         values: &["A", "B", "C"],
204///       },
205///     },
206///     StaticInfoRef {
207///       title: "Switch",
208///       short_title: "Switch",
209///       unique_id: "my special switch",
210///       flags: Default::default(),
211///       type_specific: TypeSpecificInfoRef::Switch {
212///         default: false,
213///       },
214///     },
215///   ],
216/// );
217///
218/// let samples: Vec<_> = pzip!(params[
219///   numeric "gain",
220///   enum "letter",
221///   switch "my special switch"
222/// ]).take(2).collect();
223///
224/// assert_eq!(samples, vec![(0.0, 1, false), (0.0, 1, false)]);
225/// ```
226///
227/// # Global Expression Parameters
228///
229/// For synths, you can also access global expression controllers
230/// using `global_expression_numeric` and `global_expression_switch`.
231/// Note that this requires the parameter source to implement
232/// [`SynthParamBufferStates`](crate::synth::SynthParamBufferStates).
233///
234/// ```
235/// # use conformal_component::pzip;
236/// # use conformal_component::parameters::{ConstantBufferStates, StaticInfoRef, TypeSpecificInfoRef, InternalValue};
237/// # use conformal_component::synth::SynthParamBufferStates;
238/// let params = ConstantBufferStates::new_synth_defaults(
239///   vec![
240///     StaticInfoRef {
241///       title: "Gain",
242///       short_title: "Gain",
243///       unique_id: "gain",
244///       flags: Default::default(),
245///       type_specific: TypeSpecificInfoRef::Numeric {
246///         default: 0.5,
247///         valid_range: 0.0..=1.0,
248///         units: None,
249///       },
250///     },
251///   ],
252/// );
253///
254/// let samples: Vec<_> = pzip!(params[
255///   numeric "gain",
256///   global_expression_numeric ModWheel,
257///   global_expression_switch SustainPedal
258/// ]).take(1).collect();
259///
260/// assert_eq!(samples, vec![(0.5, 0.0, false)]);
261/// ```
262///
263/// # External Numeric Parameters
264///
265/// You can also inject a [`NumericBufferState`](crate::parameters::NumericBufferState)
266/// from outside the params object using `external_numeric`. The expression must
267/// be wrapped in parentheses.
268///
269/// ```
270/// # use conformal_component::pzip;
271/// # use conformal_component::parameters::{ConstantBufferStates, StaticInfoRef, TypeSpecificInfoRef, InternalValue, NumericBufferState};
272/// let params = ConstantBufferStates::new_defaults(
273///   vec![
274///     StaticInfoRef {
275///       title: "Gain",
276///       short_title: "Gain",
277///       unique_id: "gain",
278///       flags: Default::default(),
279///       type_specific: TypeSpecificInfoRef::Numeric {
280///         default: 0.5,
281///         valid_range: 0.0..=1.0,
282///         units: None,
283///       },
284///     },
285///   ],
286/// );
287///
288/// let external: NumericBufferState<std::iter::Empty<_>> = NumericBufferState::Constant(0.75);
289/// let samples: Vec<_> = pzip!(params[
290///   numeric "gain",
291///   external_numeric (external)
292/// ]).take(2).collect();
293///
294/// assert_eq!(samples, vec![(0.5, 0.75), (0.5, 0.75)]);
295/// ```
296#[macro_export]
297macro_rules! pzip {
298    ($params:ident[$($kind:ident $path:tt),+]) => {
299        $crate::pzip_collect!(
300            $params,
301            [ $($kind $path),+ ],
302            [ P0, P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, P20, P21, P22, P23, P24, P25, P26, P27, P28, P29, P30, P31, P32, P33, P34, P35, P36, P37, P38, P39, P40, P41, P42, P43, P44, P45, P46, P47, P48, P49, P50, P51, P52, P53, P54, P55, P56, P57, P58, P59, P60, P61, P62, P63, P64, P65, P66, P67, P68, P69, P70, P71, P72, P73, P74, P75, P76, P77, P78, P79, P80, P81, P82, P83, P84, P85, P86, P87, P88, P89, P90, P91, P92, P93, P94, P95, P96, P97, P98, P99, P100, P101, P102, P103, P104, P105, P106, P107, P108, P109, P110, P111, P112, P113, P114, P115, P116, P117, P118, P119, P120, P121, P122, P123, P124, P125, P126, P127, P128, P129, P130, P131, P132, P133, P134, P135, P136, P137, P138, P139, P140, P141, P142, P143, P144, P145, P146, P147, P148, P149, P150, P151, P152, P153, P154, P155, P156, P157, P158, P159, P160, P161, P162, P163, P164, P165, P166, P167, P168, P169, P170, P171, P172, P173, P174, P175, P176, P177, P178, P179, P180, P181, P182, P183, P184, P185, P186, P187, P188, P189, P190, P191, P192, P193, P194, P195, P196, P197, P198, P199, P200, P201, P202, P203, P204, P205, P206, P207, P208, P209, P210, P211, P212, P213, P214, P215, P216, P217, P218, P219, P220, P221, P222, P223, P224, P225, P226, P227, P228, P229, P230, P231, P232, P233, P234, P235, P236, P237, P238, P239, P240, P241, P242, P243, P244, P245, P246, P247, P248, P249, P250, P251, P252, P253, P254, P255, ],
303            []
304        )
305    };
306    ($expr_head:ident $(. $expr_part:ident $( ( $($args:tt)* ) )? )+ [$($kind:ident $path:tt),+]) => {
307        {
308            let __pzip_params = $expr_head $(. $expr_part $( ( $($args)* ) )? )+;
309            $crate::pzip!(__pzip_params[$($kind $path),+])
310        }
311    };
312}
313
314/// Grab an instantaneous snapshot of parameter values at the start of the buffer.
315///
316/// This has the same syntax as [`pzip!`] but instead of returning a per-sample
317/// iterator, it returns the values from the first sample as a tuple.
318///
319/// This is useful for parameters that don't need to be modulated every
320/// sample.
321///
322/// # Examples
323///
324/// ```
325/// # use conformal_component::pgrab;
326/// # use conformal_component::parameters::{ConstantBufferStates, StaticInfoRef, TypeSpecificInfoRef, InternalValue};
327/// let params = ConstantBufferStates::new_defaults(
328///   vec![
329///     StaticInfoRef {
330///       title: "Gain",
331///       short_title: "Gain",
332///       unique_id: "gain",
333///       flags: Default::default(),
334///       type_specific: TypeSpecificInfoRef::Numeric {
335///         default: 0.5,
336///         valid_range: 0.0..=1.0,
337///         units: None,
338///       },
339///     },
340///     StaticInfoRef {
341///       title: "Switch",
342///       short_title: "Switch",
343///       unique_id: "enabled",
344///       flags: Default::default(),
345///       type_specific: TypeSpecificInfoRef::Switch {
346///         default: true,
347///       },
348///     },
349///   ],
350/// );
351///
352/// let (gain, enabled) = pgrab!(params[numeric "gain", switch "enabled"]);
353/// assert_eq!(gain, 0.5);
354/// assert_eq!(enabled, true);
355/// ```
356///
357/// It also works with a single parameter:
358///
359/// ```
360/// # use conformal_component::pgrab;
361/// # use conformal_component::parameters::{ConstantBufferStates, StaticInfoRef, TypeSpecificInfoRef, InternalValue};
362/// let params = ConstantBufferStates::new_defaults(
363///   vec![
364///     StaticInfoRef {
365///       title: "Gain",
366///       short_title: "Gain",
367///       unique_id: "gain",
368///       flags: Default::default(),
369///       type_specific: TypeSpecificInfoRef::Numeric {
370///         default: 0.75,
371///         valid_range: 0.0..=1.0,
372///         units: None,
373///       },
374///     },
375///   ],
376/// );
377///
378/// let gain = pgrab!(params[numeric "gain"]);
379/// assert_eq!(gain, 0.75);
380/// ```
381#[macro_export]
382macro_rules! pgrab {
383    ($params:ident[$($kind:ident $path:tt),+]) => {
384        $crate::pzip!($params[$($kind $path),+]).next().unwrap()
385    };
386    ($expr_head:ident $(. $expr_part:ident $( ( $($args:tt)* ) )? )+ [$($kind:ident $path:tt),+]) => {
387        {
388            let __pgrab_params = $expr_head $(. $expr_part $( ( $($args)* ) )? )+;
389            $crate::pgrab!(__pgrab_params[$($kind $path),+])
390        }
391    };
392}