Skip to content

Commit

Permalink
implement #[derive(Params)] macro
Browse files Browse the repository at this point in the history
  • Loading branch information
micahrj committed Jan 14, 2024
1 parent d643df3 commit 8e37852
Show file tree
Hide file tree
Showing 5 changed files with 318 additions and 35 deletions.
2 changes: 1 addition & 1 deletion coupler-derive/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
228 changes: 225 additions & 3 deletions coupler-derive/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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<LitStr>,
range: Option<Expr>,
}

fn parse_struct(input: &DeriveInput) -> Result<Vec<ParamInfo>, 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::<LitInt>()?);
} else if ident == "name" {
if name.is_some() {
return Err(Error::new_spanned(
&meta.path,
"duplicate param attribute `name`",
));
}

name = Some(meta.value()?.parse::<LitStr>()?);
} else if ident == "range" {
if range.is_some() {
return Err(Error::new_spanned(
&meta.path,
"duplicate param attribute `range`",
));
}

range = Some(meta.value()?.parse::<Expr>()?);
} 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) = &param.range {
range.to_token_stream()
} else {
let ty = &param.ty;
quote! { <#ty as ::coupler::param::DefaultRange>::default_range() }
}
})
.collect();

let params_expanded = params.iter().zip(&ranges).map(|(param, range)| {
let ident = &param.ident;
let ty = &param.ty;
let id = &param.id;

let name = if let Some(name) = &param.name {
name.clone()
} else {
LitStr::new(&param.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 = &param.ident;
let ty = &param.ty;
let id = &param.id;

quote! {
#id => {
self.#ident = ::coupler::param::Range::<#ty>::decode(&(#range), __value);
}
}
});

let get_cases = params.iter().zip(&ranges).map(|(param, range)| {
let ident = &param.ident;
let ty = &param.ty;
let id = &param.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()
}
2 changes: 1 addition & 1 deletion examples/gain/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ publish = false
formats = ["clap", "vst3"]

[dependencies]
coupler = { workspace = true }
coupler = { workspace = true, features = ["derive"] }
56 changes: 26 additions & 30 deletions examples/gain/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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::<f64>()];
let mut buf = [0; std::mem::size_of::<f32>()];
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(),
}
}

Expand All @@ -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) {}
Expand All @@ -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);
}
_ => {}
}
Expand All @@ -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;
}
}
}
Expand Down
Loading

0 comments on commit 8e37852

Please sign in to comment.