diff --git a/coupler-derive/Cargo.toml b/coupler-derive/Cargo.toml index 81636be..c57dcd1 100644 --- a/coupler-derive/Cargo.toml +++ b/coupler-derive/Cargo.toml @@ -8,6 +8,6 @@ edition = "2021" proc-macro = true [dependencies] -syn = "1.0" +syn = { version = "2.0", features = ["full"] } quote = "1.0" proc-macro2 = "1.0" diff --git a/coupler-derive/src/lib.rs b/coupler-derive/src/lib.rs index 2090541..3f8dd83 100644 --- a/coupler-derive/src/lib.rs +++ b/coupler-derive/src/lib.rs @@ -1,6 +1,228 @@ use proc_macro::TokenStream; +use quote::{quote, ToTokens}; +use syn::{parse_macro_input, Data, DeriveInput, Error, Expr, Fields, Ident, LitInt, LitStr, Type}; -#[proc_macro_derive(Params)] -pub fn derive_params(_input: TokenStream) -> TokenStream { - TokenStream::new() +struct ParamInfo { + ident: Ident, + ty: Type, + id: LitInt, + name: Option, + range: Option, +} + +fn parse_struct(input: &DeriveInput) -> Result, Error> { + let body = match &input.data { + Data::Struct(body) => body, + _ => { + return Err(Error::new_spanned( + &input, + "#[derive(Params)] can only be used on structs", + )); + } + }; + + let fields = match &body.fields { + Fields::Named(fields) => fields, + _ => { + return Err(Error::new_spanned( + &input, + "#[derive(Params)] can only be used on structs with named fields", + )); + } + }; + + let mut params = Vec::new(); + + for field in &fields.named { + let mut param_info = None; + + for attr in &field.attrs { + if !attr.path().is_ident("param") { + continue; + } + + if param_info.is_some() { + return Err(Error::new_spanned(&attr, "duplicate `param` attribute")); + } + + let mut id = None; + let mut name = None; + let mut range = None; + + attr.parse_nested_meta(|meta| { + let ident = meta.path.get_ident().ok_or_else(|| { + Error::new_spanned(&meta.path, "expected this path to be an identifier") + })?; + if ident == "id" { + if id.is_some() { + return Err(Error::new_spanned( + &meta.path, + "duplicate param attribute `id`", + )); + } + + id = Some(meta.value()?.parse::()?); + } else if ident == "name" { + if name.is_some() { + return Err(Error::new_spanned( + &meta.path, + "duplicate param attribute `name`", + )); + } + + name = Some(meta.value()?.parse::()?); + } else if ident == "range" { + if range.is_some() { + return Err(Error::new_spanned( + &meta.path, + "duplicate param attribute `range`", + )); + } + + range = Some(meta.value()?.parse::()?); + } else { + return Err(Error::new_spanned( + &meta.path, + format!("unknown param attribute `{}`", ident), + )); + } + + Ok(()) + })?; + + let id = if let Some(id) = id { + id + } else { + return Err(Error::new_spanned(&attr, "missing `id` attribute")); + }; + + param_info = Some(ParamInfo { + ident: field.ident.clone().unwrap(), + ty: field.ty.clone(), + id, + name, + range, + }); + } + + if let Some(param_info) = param_info { + params.push(param_info); + } + } + + Ok(params) +} + +#[proc_macro_derive(Params, attributes(param))] +pub fn derive_params(input: TokenStream) -> TokenStream { + let input: DeriveInput = parse_macro_input!(input as DeriveInput); + + let params = match parse_struct(&input) { + Ok(params) => params, + Err(err) => { + return err.into_compile_error().into(); + } + }; + + let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); + let ident = &input.ident; + + let ranges: Vec<_> = params + .iter() + .map(|param| { + if let Some(range) = ¶m.range { + range.to_token_stream() + } else { + let ty = ¶m.ty; + quote! { <#ty as ::coupler::param::DefaultRange>::default_range() } + } + }) + .collect(); + + let params_expanded = params.iter().zip(&ranges).map(|(param, range)| { + let ident = ¶m.ident; + let ty = ¶m.ty; + let id = ¶m.id; + + let name = if let Some(name) = ¶m.name { + name.clone() + } else { + LitStr::new(¶m.ident.to_string(), param.ident.span()) + }; + + quote! { + ::coupler::param::ParamInfo { + id: #id, + name: ::std::string::ToString::to_string(#name), + default: ::coupler::param::Range::<#ty>::encode(&(#range), __default.#ident), + steps: ::coupler::param::Range::<#ty>::steps(&(#range)), + parse: ::std::boxed::Box::new(|__str| { + match <#ty as ::std::str::FromStr>::from_str(__str) { + ::std::result::Result::Ok(__value) => { + Some(::coupler::param::Range::<#ty>::encode(&(#range), __value)) + } + ::std::result::Result::Err(_) => None, + } + }), + display: ::std::boxed::Box::new(|__value, __formatter| { + ::std::fmt::Display::fmt( + &::coupler::param::Range::<#ty>::decode(&(#range), __value), + __formatter + ) + }), + } + } + }); + + let set_cases = params.iter().zip(&ranges).map(|(param, range)| { + let ident = ¶m.ident; + let ty = ¶m.ty; + let id = ¶m.id; + + quote! { + #id => { + self.#ident = ::coupler::param::Range::<#ty>::decode(&(#range), __value); + } + } + }); + + let get_cases = params.iter().zip(&ranges).map(|(param, range)| { + let ident = ¶m.ident; + let ty = ¶m.ty; + let id = ¶m.id; + + quote! { + #id => { + ::coupler::param::Range::<#ty>::encode(&(#range), self.#ident) + } + } + }); + + let expanded = quote! { + impl #impl_generics ::coupler::param::Params for #ident #ty_generics #where_clause { + fn params() -> ::std::vec::Vec<::coupler::param::ParamInfo> { + let __default: #ident #ty_generics = ::std::default::Default::default(); + + ::std::vec![ + #(#params_expanded,)* + ] + } + + fn set_param(&mut self, __id: ::coupler::ParamId, __value: ::coupler::ParamValue) { + match __id { + #(#set_cases)* + _ => {} + } + } + + fn get_param(&self, __id: ::coupler::ParamId) -> ::coupler::ParamValue { + match __id { + #(#get_cases)* + _ => 0.0, + } + } + } + }; + + expanded.into() } diff --git a/examples/gain/Cargo.toml b/examples/gain/Cargo.toml index 67f82d5..7e1bcc9 100644 --- a/examples/gain/Cargo.toml +++ b/examples/gain/Cargo.toml @@ -9,4 +9,4 @@ publish = false formats = ["clap", "vst3"] [dependencies] -coupler = { workspace = true } +coupler = { workspace = true, features = ["derive"] } diff --git a/examples/gain/src/lib.rs b/examples/gain/src/lib.rs index c48a340..88e4c49 100644 --- a/examples/gain/src/lib.rs +++ b/examples/gain/src/lib.rs @@ -4,10 +4,20 @@ use coupler::format::clap::*; use coupler::format::vst3::*; use coupler::{buffers::*, bus::*, events::*, param::*, parent::*, *}; -const GAIN: ParamId = 0; +#[derive(Params, Clone)] +struct GainParams { + #[param(id = 0, name = "Gain", range = 0.0..1.0)] + gain: f32, +} + +impl Default for GainParams { + fn default() -> GainParams { + GainParams { gain: 1.0 } + } +} pub struct Gain { - gain: f64, + params: GainParams, } impl Plugin for Gain { @@ -33,52 +43,41 @@ impl Plugin for Gain { formats: vec![Format::Mono], }, ], - params: vec![ParamInfo { - id: GAIN, - name: "Gain".to_string(), - default: 1.0, - steps: None, - parse: Box::new(|s| s.parse().ok()), - display: Box::new(|v, f| write!(f, "{:.2}", v)), - }], + params: GainParams::params(), } } fn new(_host: Host) -> Self { - Gain { gain: 1.0 } + Gain { + params: GainParams::default(), + } } fn set_param(&mut self, id: ParamId, value: ParamValue) { - match id { - GAIN => self.gain = value, - _ => {} - } + self.params.set_param(id, value); } fn get_param(&self, id: ParamId) -> ParamValue { - match id { - GAIN => self.gain, - _ => 0.0, - } + self.params.get_param(id) } fn save(&self, output: &mut impl Write) -> io::Result<()> { - output.write(&self.gain.to_le_bytes())?; + output.write(&self.params.gain.to_le_bytes())?; Ok(()) } fn load(&mut self, input: &mut impl Read) -> io::Result<()> { - let mut buf = [0; std::mem::size_of::()]; + let mut buf = [0; std::mem::size_of::()]; input.read_exact(&mut buf)?; - self.gain = f64::from_le_bytes(buf); + self.params.gain = f32::from_le_bytes(buf); Ok(()) } fn processor(&self, _config: Config) -> Self::Processor { GainProcessor { - gain: self.gain as f32, + params: self.params.clone(), } } @@ -104,15 +103,12 @@ impl ClapPlugin for Gain { } pub struct GainProcessor { - gain: f32, + params: GainParams, } impl Processor for GainProcessor { fn set_param(&mut self, id: ParamId, value: ParamValue) { - match id { - GAIN => self.gain = value as f32, - _ => {} - } + self.params.set_param(id, value); } fn reset(&mut self) {} @@ -121,7 +117,7 @@ impl Processor for GainProcessor { for event in events { match event.data { Data::ParamChange { id, value } => { - self.set_param(id, value); + self.params.set_param(id, value); } _ => {} } @@ -133,7 +129,7 @@ impl Processor for GainProcessor { for i in 0..main.channel_count() { for sample in &mut main[i] { - *sample *= self.gain; + *sample *= self.params.gain; } } } diff --git a/src/param.rs b/src/param.rs index 22b4d8e..37b157f 100644 --- a/src/param.rs +++ b/src/param.rs @@ -1,5 +1,8 @@ use std::fmt::{self, Formatter}; +#[cfg(feature = "derive")] +pub use coupler_derive::Params; + use crate::{ParamId, ParamValue}; pub type ParseFn = dyn Fn(&str) -> Option + Send + Sync; @@ -13,3 +16,65 @@ pub struct ParamInfo { pub parse: Box, pub display: Box, } + +pub trait Params { + fn params() -> Vec; + fn set_param(&mut self, id: ParamId, value: ParamValue); + fn get_param(&self, id: ParamId) -> ParamValue; +} + +pub trait Range { + fn steps(&self) -> Option; + fn encode(&self, value: T) -> ParamValue; + fn decode(&self, value: ParamValue) -> T; +} + +pub trait DefaultRange: Sized { + type Range: Range; + + fn default_range() -> Self::Range; +} + +impl Range for std::ops::Range { + fn steps(&self) -> Option { + None + } + + fn encode(&self, value: f32) -> ParamValue { + ((value - self.start) / (self.end - self.start)) as f64 + } + + fn decode(&self, value: ParamValue) -> f32 { + (1.0 - value as f32) * self.start + value as f32 * self.end + } +} + +impl Range for std::ops::Range { + fn steps(&self) -> Option { + None + } + + fn encode(&self, value: f64) -> ParamValue { + ((value - self.start) / (self.end - self.start)) as f64 + } + + fn decode(&self, value: ParamValue) -> f64 { + (1.0 - value as f64) * self.start + value as f64 * self.end + } +} + +impl DefaultRange for f32 { + type Range = std::ops::Range; + + fn default_range() -> Self::Range { + 0.0..1.0 + } +} + +impl DefaultRange for f64 { + type Range = std::ops::Range; + + fn default_range() -> Self::Range { + 0.0..1.0 + } +}