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}