beamer_macros/lib.rs
1//! Derive macros for the Beamer audio plugin framework.
2//!
3//! This crate provides the `#[derive(Parameters)]` macro for generating parameter
4//! trait implementations automatically.
5//!
6//! # Declarative Parameter Descriptor
7//!
8//! Parameters can be defined entirely through attributes - the macro generates
9//! the `Default` impl automatically:
10//!
11//! ```ignore
12//! use beamer::prelude::*;
13//!
14//! #[derive(Parameters)]
15//! pub struct GainParameters {
16//! #[parameter(id = "gain", name = "Gain", default = 0.0, range = -60.0..=12.0, kind = "db")]
17//! pub gain: FloatParameter,
18//!
19//! #[parameter(id = "bypass", bypass = true)]
20//! pub bypass: BoolParameter,
21//! }
22//!
23//! // No manual Default impl needed - macro generates everything!
24//! ```
25//!
26//! The macro generates implementations for both `Parameters` (high-level) and
27//! `ParameterStore` (host integration) traits, plus `Default` when all required
28//! attributes are present.
29//!
30//! # Flat Visual Grouping
31//!
32//! Use `group = "..."` for visual grouping in the DAW without nested structs:
33//!
34//! ```ignore
35//! #[derive(Parameters)]
36//! pub struct SynthParameters {
37//! // Filter parameters - grouped visually in DAW
38//! #[parameter(id = "cutoff", name = "Cutoff", group = "Filter", ...)]
39//! pub cutoff: FloatParameter,
40//!
41//! #[parameter(id = "reso", name = "Resonance", group = "Filter", ...)]
42//! pub resonance: FloatParameter,
43//!
44//! // Output parameters - different visual group
45//! #[parameter(id = "gain", name = "Gain", group = "Output", ...)]
46//! pub gain: FloatParameter,
47//! }
48//!
49//! // Access is flat: parameters.cutoff, parameters.resonance, parameters.gain
50//! // But DAW shows them in collapsible "Filter" and "Output" groups
51//! ```
52
53use proc_macro::TokenStream;
54
55mod codegen;
56mod config_file;
57mod enum_parameter;
58mod has_parameters;
59mod init;
60mod ir;
61mod parse;
62mod range_eval;
63mod validate;
64
65/// Derive macro for implementing parameter traits.
66///
67/// This macro generates:
68/// - `Parameters` trait implementation (count, iter, by_id, save_state, load_state)
69/// - `ParameterStore` trait implementation (host integration)
70/// - `Default` implementation (when declarative attributes are complete)
71/// - Compile-time hash collision detection
72///
73/// # Attributes
74///
75/// ## Required
76/// - `id = "..."` - String ID that gets hashed to u32.
77///
78/// ## Declarative (enables auto-generated Default)
79/// - `name = "..."` - Display name
80/// - `default = <value>` - Default value (float, int, or bool)
81/// - `range = start..=end` - Value range (for FloatParameter/IntParameter)
82/// - `kind = "..."` - Unit type: db, db_log, db_log_offset, hz, ms, seconds, percent, pan, ratio, linear, semitones
83/// - `short_name = "..."` - Short name for constrained UIs
84/// - `smoothing = "exp:5.0"` - Parameter smoothing (exp or linear)
85/// - `bypass` - Mark as bypass parameter (BoolParameter only)
86/// - `group = "..."` - Visual grouping in DAW without nested struct
87///
88/// ## Nested Groups
89/// - `#[nested(group = "...")]` - For fields containing nested parameter structs
90///
91/// # Example
92///
93/// ```ignore
94/// #[derive(Parameters)]
95/// pub struct PluginParameters {
96/// #[parameter(id = "gain", name = "Gain", default = 0.0, range = -60.0..=12.0, kind = "db")]
97/// pub gain: FloatParameter,
98///
99/// #[parameter(id = "freq", name = "Frequency", default = 1000.0, range = 20.0..=20000.0, kind = "hz", group = "Filter")]
100/// pub frequency: FloatParameter,
101///
102/// #[nested(group = "Output")]
103/// pub output: OutputParameters,
104/// }
105/// ```
106#[proc_macro_derive(Parameters, attributes(parameter, nested))]
107pub fn derive_parameters(input: TokenStream) -> TokenStream {
108 let input = syn::parse_macro_input!(input as syn::DeriveInput);
109
110 match derive_parameters_impl(input) {
111 Ok(tokens) => tokens.into(),
112 Err(err) => err.to_compile_error().into(),
113 }
114}
115
116fn derive_parameters_impl(input: syn::DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
117 let ir = parse::parse(input)?;
118 validate::validate(&ir)?;
119 Ok(codegen::generate(&ir))
120}
121
122/// Derive macro for implementing `EnumParameterValue` trait on enums.
123///
124/// This macro generates the `EnumParameterValue` implementation that allows
125/// an enum to be used with `EnumParameter<E>` in parameter structs.
126///
127/// # Requirements
128///
129/// - The type must be an enum
130/// - All variants must be unit variants (no fields)
131/// - The enum must also derive `Copy`, `Clone`, and `PartialEq`
132///
133/// # Attributes
134///
135/// - `#[name = "..."]` - Optional display name for a variant. If not specified,
136/// the variant identifier is used as the display name.
137/// - `#[default]` - Mark a variant as the default. If not specified, the first
138/// variant is used. Only one variant can be marked as default.
139///
140/// # Example
141///
142/// ```ignore
143/// use beamer::EnumParameter;
144///
145/// #[derive(Copy, Clone, PartialEq, EnumParameter)]
146/// pub enum FilterType {
147/// #[name = "Low Pass"]
148/// LowPass,
149/// #[default]
150/// #[name = "High Pass"]
151/// HighPass,
152/// #[name = "Band Pass"]
153/// BandPass,
154/// Notch, // Uses "Notch" as display name
155/// }
156///
157/// // Now FilterType can be used with EnumParameter:
158/// #[derive(Parameters)]
159/// pub struct FilterParameters {
160/// #[parameter(id = "filter_type")]
161/// pub filter_type: EnumParameter<FilterType>,
162/// }
163/// ```
164#[proc_macro_derive(EnumParameter, attributes(name, default))]
165pub fn derive_enum_parameter(input: TokenStream) -> TokenStream {
166 let input = syn::parse_macro_input!(input as syn::DeriveInput);
167
168 match enum_parameter::derive_enum_parameter_impl(input) {
169 Ok(tokens) => tokens.into(),
170 Err(err) => err.to_compile_error().into(),
171 }
172}
173
174/// Derive macro for implementing the `HasParameters` trait.
175///
176/// This macro generates the `HasParameters` implementation for structs that hold
177/// parameter collections. It eliminates the boilerplate of implementing
178/// `parameters()` and `parameters_mut()` on both Descriptor and Processor types.
179///
180/// # Usage
181///
182/// Mark the field containing your parameters with `#[parameters]`:
183///
184/// ```ignore
185/// use beamer::prelude::*;
186///
187/// #[derive(Default, HasParameters)]
188/// pub struct GainPlugin {
189/// #[parameters]
190/// parameters: GainParameters,
191/// }
192///
193/// #[derive(HasParameters)]
194/// pub struct GainProcessor {
195/// #[parameters]
196/// parameters: GainParameters,
197/// // other processor-only fields...
198/// }
199/// ```
200///
201/// # Requirements
202///
203/// - The struct must have named fields
204/// - Exactly one field must be marked with `#[parameters]`
205/// - The marked field's type must implement `ParameterStore`, `ParameterGroups`, and `Parameters`
206///
207/// # What It Generates
208///
209/// ```ignore
210/// impl HasParameters for GainPlugin {
211/// type Parameters = GainParameters;
212///
213/// fn parameters(&self) -> &Self::Parameters {
214/// &self.parameters
215/// }
216///
217/// fn parameters_mut(&mut self) -> &mut Self::Parameters {
218/// &mut self.parameters
219/// }
220/// }
221/// ```
222#[proc_macro_derive(HasParameters, attributes(parameters))]
223pub fn derive_has_parameters(input: TokenStream) -> TokenStream {
224 let input = syn::parse_macro_input!(input as syn::DeriveInput);
225
226 match has_parameters::derive_has_parameters_impl(input) {
227 Ok(tokens) => tokens.into(),
228 Err(err) => err.to_compile_error().into(),
229 }
230}
231
232/// Attribute macro that generates plugin configuration and entry points.
233///
234/// Place `#[beamer::export]` on your Descriptor struct to automatically generate:
235/// - `pub static CONFIG: Config` from `Config.toml` in the crate root
236/// - Factory presets from `Presets.toml` (if present)
237/// - `export_plugin!` call with the correct arguments
238///
239/// # Requirements
240///
241/// - A `Config.toml` file must exist in the crate root (next to `Cargo.toml`)
242/// - The struct must derive `HasParameters` (with a `#[parameters]` field)
243/// - The struct must implement `Descriptor`
244///
245/// # Example
246///
247/// ```ignore
248/// use beamer::prelude::*;
249///
250/// #[beamer::export]
251/// #[derive(Default, HasParameters)]
252/// pub struct GainDescriptor {
253/// #[parameters]
254/// pub parameters: GainParameters,
255/// }
256///
257/// impl Descriptor for GainDescriptor { /* ... */ }
258/// // No CONFIG static or export_plugin! needed!
259/// ```
260#[proc_macro_attribute]
261pub fn export(_attr: TokenStream, item: TokenStream) -> TokenStream {
262 let input = syn::parse_macro_input!(item as syn::DeriveInput);
263 let struct_ident = input.ident.clone();
264
265 match init::export_impl(struct_ident) {
266 Ok(generated) => {
267 let original: proc_macro2::TokenStream = {
268 let ts: TokenStream = quote::quote! { #input }.into();
269 ts.into()
270 };
271 let combined = quote::quote! {
272 #original
273 #generated
274 };
275 combined.into()
276 }
277 Err(err) => {
278 let err = syn::Error::new(input.ident.span(), err);
279 let original: proc_macro2::TokenStream = {
280 let ts: TokenStream = quote::quote! { #input }.into();
281 ts.into()
282 };
283 let error_tokens = err.to_compile_error();
284 let combined = quote::quote! {
285 #original
286 #error_tokens
287 };
288 combined.into()
289 }
290 }
291}