Skip to main content

wavecraft_protocol/
macros.rs

1//! Macros for defining parameters with minimal boilerplate.
2//!
3//! The `vstkit_params!` macro generates parameter enums and `ParamSet` implementations
4//! from a declarative syntax.
5
6/// Define a parameter set with minimal boilerplate.
7///
8/// This macro generates:
9/// - A parameter ID enum (with `#[repr(u32)]`)
10/// - A `ParamSet` implementation
11/// - Conversion from the ID enum to `ParamId`
12///
13/// # Syntax
14///
15/// ```text
16/// vstkit_params! {
17///     ParamSetName;
18///
19///     ParameterName {
20///         id: 0,
21///         name: "Display Name",
22///         short_name: "Short",
23///         unit: "unit",
24///         default: 0.0,
25///         min: -10.0,
26///         max: 10.0,
27///         step: 0.1,
28///     },
29///
30///     // ... more parameters
31/// }
32/// ```
33///
34/// # Example
35///
36/// ```rust
37/// use wavecraft_protocol::vstkit_params;
38///
39/// vstkit_params! {
40///     MyParams;
41///     
42///     Volume {
43///         id: 0,
44///         name: "Volume",
45///         short_name: "Vol",
46///         unit: "dB",
47///         default: 0.0,
48///         min: -60.0,
49///         max: 12.0,
50///         step: 0.1,
51///     },
52///     
53///     Pan {
54///         id: 1,
55///         name: "Pan",
56///         short_name: "Pan",
57///         unit: "",
58///         default: 0.0,
59///         min: -1.0,
60///         max: 1.0,
61///         step: 0.01,
62///     },
63/// }
64///
65/// // The macro generates:
66/// // - enum MyParamsId { Volume = 0, Pan = 1 }
67/// // - struct MyParams
68/// // - impl ParamSet for MyParams
69/// // - impl From<MyParamsId> for ParamId
70/// ```
71#[macro_export]
72macro_rules! vstkit_params {
73    // Main pattern: ParamSetName; ParameterName { fields }, ...
74    (
75        $set_name:ident;
76        $(
77            $param_name:ident {
78                id: $id:expr,
79                name: $name:expr,
80                short_name: $short_name:expr,
81                unit: $unit:expr,
82                default: $default:expr,
83                min: $min:expr,
84                max: $max:expr,
85                step: $step:expr $(,)?
86            }
87        ),* $(,)?
88    ) => {
89        // Generate the parameter ID enum
90        paste::paste! {
91            #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
92            #[repr(u32)]
93            pub enum [<$set_name Id>] {
94                $(
95                    $param_name = $id,
96                )*
97            }
98
99            // Generate conversion to ParamId
100            impl ::std::convert::From<[<$set_name Id>]> for $crate::ParamId {
101                fn from(id: [<$set_name Id>]) -> Self {
102                    $crate::ParamId(id as u32)
103                }
104            }
105        }
106
107        // Generate the parameter set struct
108        pub struct $set_name;
109
110        // Generate ParamSet implementation
111        paste::paste! {
112            impl $crate::ParamSet for $set_name {
113                type Id = [<$set_name Id>];
114
115                const SPECS: &'static [$crate::ParamSpec] = &[
116                    $(
117                        $crate::ParamSpec {
118                            id: $crate::ParamId($id),
119                            name: $name,
120                            short_name: $short_name,
121                            unit: $unit,
122                            default: $default,
123                            min: $min,
124                            max: $max,
125                            step: $step,
126                        },
127                    )*
128                ];
129
130                fn spec(id: Self::Id) -> ::std::option::Option<&'static $crate::ParamSpec> {
131                    Self::SPECS.iter().find(|s| s.id.0 == id as u32)
132                }
133
134                fn iter() -> impl ::std::iter::Iterator<Item = &'static $crate::ParamSpec> {
135                    Self::SPECS.iter()
136                }
137            }
138        }
139    };
140}
141
142#[cfg(test)]
143mod tests {
144    use crate::{ParamId, ParamSet};
145
146    // Test the macro with a simple parameter set
147    vstkit_params! {
148        TestParams;
149
150        Gain {
151            id: 0,
152            name: "Gain",
153            short_name: "Gain",
154            unit: "dB",
155            default: 0.0,
156            min: -24.0,
157            max: 24.0,
158            step: 0.1,
159        },
160
161        Frequency {
162            id: 1,
163            name: "Frequency",
164            short_name: "Freq",
165            unit: "Hz",
166            default: 1000.0,
167            min: 20.0,
168            max: 20000.0,
169            step: 1.0,
170        },
171    }
172
173    #[test]
174    fn test_generated_enum() {
175        let gain_id = TestParamsId::Gain;
176        let freq_id = TestParamsId::Frequency;
177
178        assert_eq!(gain_id as u32, 0);
179        assert_eq!(freq_id as u32, 1);
180    }
181
182    #[test]
183    fn test_param_id_conversion() {
184        let gain_id: ParamId = TestParamsId::Gain.into();
185        assert_eq!(gain_id.0, 0);
186    }
187
188    #[test]
189    fn test_param_set_specs() {
190        assert_eq!(TestParams::SPECS.len(), 2);
191        assert_eq!(TestParams::count(), 2);
192
193        let gain_spec = &TestParams::SPECS[0];
194        assert_eq!(gain_spec.name, "Gain");
195        assert_eq!(gain_spec.unit, "dB");
196        assert_eq!(gain_spec.default, 0.0);
197    }
198
199    #[test]
200    fn test_param_set_spec_lookup() {
201        let gain_spec = TestParams::spec(TestParamsId::Gain);
202        assert!(gain_spec.is_some());
203        assert_eq!(gain_spec.unwrap().name, "Gain");
204
205        let freq_spec = TestParams::spec(TestParamsId::Frequency);
206        assert!(freq_spec.is_some());
207        assert_eq!(freq_spec.unwrap().name, "Frequency");
208    }
209
210    #[test]
211    fn test_param_set_iter() {
212        let names: Vec<&str> = TestParams::iter().map(|s| s.name).collect();
213        assert_eq!(names, vec!["Gain", "Frequency"]);
214    }
215}