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}