use proc_macro2::TokenStream;
use quote::quote;
use crate::ir::{
FieldIR, ParamDefault, ParamFieldIR, ParamKind, ParamsIR, SmoothingStyle,
};
pub fn generate(ir: &ParamsIR) -> TokenStream {
let const_ids = generate_const_ids(ir);
let unit_consts = generate_unit_consts(ir);
let collision_check = generate_collision_check(ir);
let units_impl = generate_units_impl(ir);
let params_impl = generate_params_impl(ir);
let parameters_impl = generate_parameters_impl(ir);
let set_unit_ids_impl = generate_set_unit_ids(ir);
let default_impl = generate_default_impl(ir);
quote! {
#const_ids
#unit_consts
#collision_check
#units_impl
#params_impl
#parameters_impl
#set_unit_ids_impl
#default_impl
}
}
fn generate_const_ids(ir: &ParamsIR) -> TokenStream {
let struct_name = &ir.struct_name;
let const_defs: Vec<TokenStream> = ir
.param_fields()
.map(|param| {
let const_name = param.const_name();
let hash = param.hash_id;
quote! {
const #const_name: u32 = #hash;
}
})
.collect();
if const_defs.is_empty() {
quote! {}
} else {
quote! {
impl #struct_name {
#(#const_defs)*
}
}
}
}
fn generate_unit_consts(ir: &ParamsIR) -> TokenStream {
let struct_name = &ir.struct_name;
let flat_groups = ir.flat_group_names();
let flat_group_consts: Vec<TokenStream> = flat_groups
.iter()
.enumerate()
.map(|(idx, group_name)| {
let const_name = syn::Ident::new(
&format!("UNIT_GROUP_{}", group_name.to_uppercase().replace(' ', "_")),
proc_macro2::Span::call_site(),
);
let unit_id = (idx + 1) as i32; quote! {
pub const #const_name: ::beamer::core::params::UnitId = #unit_id;
}
})
.collect();
let flat_group_count = flat_groups.len() as i32;
let nested_consts: Vec<TokenStream> = ir
.nested_fields()
.enumerate()
.map(|(idx, nested)| {
let const_name = syn::Ident::new(
&format!("UNIT_{}", nested.field_name.to_string().to_uppercase()),
nested.span,
);
let unit_id = flat_group_count + (idx as i32) + 1;
quote! {
pub const #const_name: ::beamer::core::params::UnitId = #unit_id;
}
})
.collect();
if flat_group_consts.is_empty() && nested_consts.is_empty() {
quote! {}
} else {
quote! {
impl #struct_name {
#(#flat_group_consts)*
#(#nested_consts)*
}
}
}
}
fn generate_units_impl(ir: &ParamsIR) -> TokenStream {
let struct_name = &ir.struct_name;
let (impl_generics, ty_generics, where_clause) = ir.generics.split_for_impl();
let flat_groups = ir.flat_group_names();
let has_flat_groups = !flat_groups.is_empty();
let has_nested = ir.has_nested();
if !has_flat_groups && !has_nested {
return quote! {
impl #impl_generics ::beamer::core::params::Units for #struct_name #ty_generics #where_clause {}
};
}
let flat_group_count = flat_groups.len();
let flat_unit_infos: Vec<TokenStream> = flat_groups
.iter()
.enumerate()
.map(|(idx, group_name)| {
let unit_id = (idx + 1) as i32;
quote! {
#idx => Some(::beamer::core::params::UnitInfo {
id: #unit_id,
name: #group_name,
parent_id: 0, }),
}
})
.collect();
if has_nested {
quote! {
impl #impl_generics ::beamer::core::params::Units for #struct_name #ty_generics #where_clause {
fn unit_count(&self) -> usize {
use ::beamer::core::param_types::Params;
let flat_count = #flat_group_count;
let mut units = Vec::new();
self.collect_units(&mut units, (flat_count + 1) as i32, 0);
1 + flat_count + units.len()
}
fn unit_info(&self, index: usize) -> Option<::beamer::core::params::UnitInfo> {
use ::beamer::core::param_types::Params;
if index == 0 {
return Some(::beamer::core::params::UnitInfo::root());
}
let flat_idx = index - 1;
if flat_idx < #flat_group_count {
return match flat_idx {
#(#flat_unit_infos)*
_ => None,
};
}
let mut units = Vec::new();
self.collect_units(&mut units, (#flat_group_count + 1) as i32, 0);
let nested_idx = index - 1 - #flat_group_count;
units.get(nested_idx).cloned()
}
}
}
} else {
quote! {
impl #impl_generics ::beamer::core::params::Units for #struct_name #ty_generics #where_clause {
fn unit_count(&self) -> usize {
1 + #flat_group_count }
fn unit_info(&self, index: usize) -> Option<::beamer::core::params::UnitInfo> {
if index == 0 {
return Some(::beamer::core::params::UnitInfo::root());
}
let flat_idx = index - 1;
match flat_idx {
#(#flat_unit_infos)*
_ => None,
}
}
}
}
}
}
fn generate_set_unit_ids(ir: &ParamsIR) -> TokenStream {
let struct_name = &ir.struct_name;
let (impl_generics, ty_generics, where_clause) = ir.generics.split_for_impl();
let flat_groups = ir.flat_group_names();
let has_flat_groups = !flat_groups.is_empty();
let has_nested = ir.has_nested();
if !has_flat_groups && !has_nested {
return quote! {
impl #impl_generics #struct_name #ty_generics #where_clause {
pub fn set_unit_ids(&mut self) {}
}
};
}
let group_to_unit_id: std::collections::HashMap<&str, i32> = flat_groups
.iter()
.enumerate()
.map(|(idx, name)| (*name, (idx + 1) as i32))
.collect();
let flat_group_assignments: Vec<TokenStream> = ir
.param_fields()
.filter_map(|param| {
param.attrs.group.as_ref().map(|group_name| {
let field = ¶m.field_name;
let unit_id = group_to_unit_id.get(group_name.as_str()).copied().unwrap_or(0);
quote! {
self.#field.set_unit_id(#unit_id);
}
})
})
.collect();
let flat_group_count = flat_groups.len() as i32;
let nested_init = if has_nested {
quote! {
use ::beamer::core::param_types::Params;
self.assign_unit_ids(#flat_group_count + 1, 0);
}
} else {
quote! {}
};
quote! {
impl #impl_generics #struct_name #ty_generics #where_clause {
pub fn set_unit_ids(&mut self) {
#(#flat_group_assignments)*
#nested_init
}
}
}
}
fn generate_collision_check(ir: &ParamsIR) -> TokenStream {
let param_fields: Vec<_> = ir.param_fields().collect();
if param_fields.len() < 2 {
return quote! {};
}
let id_pairs: Vec<TokenStream> = param_fields
.iter()
.map(|param| {
let id_str = ¶m.string_id;
let hash = param.hash_id;
quote! { (#id_str, #hash) }
})
.collect();
let struct_name = &ir.struct_name;
quote! {
const _: () = {
const IDS: &[(&str, u32)] = &[#(#id_pairs),*];
let mut i = 0;
while i < IDS.len() {
let mut j = i + 1;
while j < IDS.len() {
if IDS[i].1 == IDS[j].1 {
panic!(concat!(
"Parameter ID hash collision in ",
stringify!(#struct_name),
": two IDs hash to the same value"
));
}
j += 1;
}
i += 1;
}
};
}
}
fn generate_params_impl(ir: &ParamsIR) -> TokenStream {
let struct_name = &ir.struct_name;
let (impl_generics, ty_generics, where_clause) = ir.generics.split_for_impl();
let count_impl = generate_count(ir);
let iter_impl = generate_iter(ir);
let by_id_impl = generate_by_id(ir);
let save_state_impl = generate_save_state(ir);
let load_state_impl = generate_load_state(ir);
let set_all_unit_ids_impl = generate_set_all_unit_ids(ir);
let nested_discovery_impl = generate_nested_discovery(ir);
let set_sample_rate_impl = generate_set_sample_rate(ir);
let reset_smoothing_impl = generate_reset_smoothing(ir);
quote! {
impl #impl_generics ::beamer::core::param_types::Params for #struct_name #ty_generics #where_clause {
fn count(&self) -> usize {
#count_impl
}
fn iter(&self) -> Box<dyn Iterator<Item = &dyn ::beamer::core::param_types::ParamRef> + '_> {
#iter_impl
}
fn by_id(&self, id: ::beamer::core::types::ParamId) -> Option<&dyn ::beamer::core::param_types::ParamRef> {
#by_id_impl
}
fn by_id_mut(&mut self, id: ::beamer::core::types::ParamId) -> Option<&dyn ::beamer::core::param_types::ParamRef> {
self.by_id(id)
}
#set_all_unit_ids_impl
#nested_discovery_impl
#save_state_impl
#load_state_impl
#set_sample_rate_impl
#reset_smoothing_impl
}
}
}
fn generate_set_all_unit_ids(ir: &ParamsIR) -> TokenStream {
if ir.param_count() == 0 {
return quote! {};
}
let assignments: Vec<TokenStream> = ir
.param_fields()
.map(|param| {
let field = ¶m.field_name;
quote! {
self.#field.set_unit_id(unit_id);
}
})
.collect();
quote! {
fn set_all_unit_ids(&mut self, unit_id: ::beamer::core::params::UnitId) {
#(#assignments)*
}
}
}
fn generate_nested_discovery(ir: &ParamsIR) -> TokenStream {
if !ir.has_nested() {
return quote! {};
}
let nested_count = ir.nested_fields().count();
let group_match_arms: Vec<TokenStream> = ir
.nested_fields()
.enumerate()
.map(|(idx, nested)| {
let field = &nested.field_name;
let name = &nested.group_name;
quote! {
#idx => Some((#name, &self.#field as &dyn ::beamer::core::param_types::Params)),
}
})
.collect();
let group_mut_match_arms: Vec<TokenStream> = ir
.nested_fields()
.enumerate()
.map(|(idx, nested)| {
let field = &nested.field_name;
let name = &nested.group_name;
quote! {
#idx => Some((#name, &mut self.#field as &mut dyn ::beamer::core::param_types::Params)),
}
})
.collect();
quote! {
fn nested_count(&self) -> usize {
#nested_count
}
fn nested_group(&self, index: usize) -> Option<(&'static str, &dyn ::beamer::core::param_types::Params)> {
match index {
#(#group_match_arms)*
_ => None,
}
}
fn nested_group_mut(&mut self, index: usize) -> Option<(&'static str, &mut dyn ::beamer::core::param_types::Params)> {
match index {
#(#group_mut_match_arms)*
_ => None,
}
}
}
}
fn generate_count(ir: &ParamsIR) -> TokenStream {
let param_count = ir.param_count();
if ir.has_nested() {
let nested_counts: Vec<TokenStream> = ir
.nested_fields()
.map(|nested| {
let field = &nested.field_name;
quote! { ::beamer::core::param_types::Params::count(&self.#field) }
})
.collect();
quote! {
#param_count #(+ #nested_counts)*
}
} else {
quote! { #param_count }
}
}
fn generate_iter(ir: &ParamsIR) -> TokenStream {
let param_iters: Vec<TokenStream> = ir
.param_fields()
.map(|param| {
let field = ¶m.field_name;
quote! { &self.#field as &dyn ::beamer::core::param_types::ParamRef }
})
.collect();
let nested_chains: Vec<TokenStream> = ir
.nested_fields()
.map(|nested| {
let field = &nested.field_name;
quote! { .chain(self.#field.iter()) }
})
.collect();
if param_iters.is_empty() && nested_chains.is_empty() {
quote! { Box::new(::std::iter::empty()) }
} else if param_iters.is_empty() {
let first_nested = &ir.nested_fields().next().unwrap().field_name;
let rest_nested: Vec<TokenStream> = ir
.nested_fields()
.skip(1)
.map(|n| {
let field = &n.field_name;
quote! { .chain(self.#field.iter()) }
})
.collect();
quote! {
Box::new(self.#first_nested.iter() #(#rest_nested)*)
}
} else {
quote! {
Box::new(
[#(#param_iters),*].into_iter()
#(#nested_chains)*
)
}
}
}
fn generate_by_id(ir: &ParamsIR) -> TokenStream {
let struct_name = &ir.struct_name;
let match_arms: Vec<TokenStream> = ir
.param_fields()
.map(|param| {
let field = ¶m.field_name;
let const_name = param.const_name();
quote! {
#struct_name::#const_name => Some(&self.#field),
}
})
.collect();
let nested_lookups: Vec<TokenStream> = ir
.nested_fields()
.map(|nested| {
let field = &nested.field_name;
quote! {
if let Some(param) = self.#field.by_id(id) {
return Some(param);
}
}
})
.collect();
if match_arms.is_empty() && nested_lookups.is_empty() {
quote! { None }
} else {
quote! {
match id {
#(#match_arms)*
_ => {
#(#nested_lookups)*
None
}
}
}
}
}
fn generate_save_state(ir: &ParamsIR) -> TokenStream {
let param_saves: Vec<TokenStream> = ir
.param_fields()
.map(|param| {
let field = ¶m.field_name;
let id_str = ¶m.string_id;
quote! {
let path = if prefix.is_empty() {
#id_str.to_string()
} else {
format!("{}/{}", prefix, #id_str)
};
let path_bytes = path.as_bytes();
data.push(path_bytes.len() as u8);
data.extend_from_slice(path_bytes);
data.extend_from_slice(&self.#field.get_normalized().to_le_bytes());
}
})
.collect();
let nested_saves: Vec<TokenStream> = ir
.nested_fields()
.map(|nested| {
let field = &nested.field_name;
let group_name = &nested.group_name;
quote! {
let nested_prefix = if prefix.is_empty() {
#group_name.to_string()
} else {
format!("{}/{}", prefix, #group_name)
};
self.#field.save_state_prefixed(data, &nested_prefix);
}
})
.collect();
let param_count = ir.param_count();
let estimated_capacity = param_count * 20;
quote! {
fn save_state_prefixed(&self, data: &mut Vec<u8>, prefix: &str) {
#(#param_saves)*
#(#nested_saves)*
}
fn save_state(&self) -> Vec<u8> {
let mut data = Vec::with_capacity(#estimated_capacity);
self.save_state_prefixed(&mut data, "");
data
}
}
}
fn generate_load_state(ir: &ParamsIR) -> TokenStream {
let direct_match_arms: Vec<TokenStream> = ir
.param_fields()
.map(|param| {
let field = ¶m.field_name;
let id_str = ¶m.string_id;
quote! {
#id_str => {
self.#field.set_normalized(value.clamp(0.0, 1.0));
true
}
}
})
.collect();
let nested_routes: Vec<TokenStream> = ir
.nested_fields()
.map(|nested| {
let field = &nested.field_name;
let group_name = &nested.group_name;
quote! {
#group_name => {
self.#field.load_state_path(rest, value);
true
}
}
})
.collect();
let nested_routing = if nested_routes.is_empty() {
quote! { false }
} else {
quote! {
match group {
#(#nested_routes)*
_ => false
}
}
};
let direct_matching = if direct_match_arms.is_empty() {
quote! { false }
} else {
quote! {
match path {
#(#direct_match_arms)*
_ => false
}
}
};
quote! {
fn load_state_path(&mut self, path: &str, value: f64) -> bool {
if let Some(slash_pos) = path.find('/') {
let group = &path[..slash_pos];
let rest = &path[slash_pos + 1..];
#nested_routing
} else {
#direct_matching
}
}
fn load_state(&mut self, data: &[u8]) -> Result<(), String> {
if data.is_empty() {
return Ok(());
}
let mut cursor = 0;
while cursor < data.len() {
let path_len = data[cursor] as usize;
cursor += 1;
if cursor + path_len + 8 > data.len() {
break; }
let path = match ::std::str::from_utf8(&data[cursor..cursor + path_len]) {
Ok(s) => s,
Err(_) => {
cursor += path_len + 8;
continue; }
};
cursor += path_len;
let value_bytes: [u8; 8] = data[cursor..cursor + 8]
.try_into()
.map_err(|_| "Invalid state data")?;
let value = f64::from_le_bytes(value_bytes);
cursor += 8;
self.load_state_path(path, value);
}
Ok(())
}
}
}
fn generate_parameters_impl(ir: &ParamsIR) -> TokenStream {
let struct_name = &ir.struct_name;
let (impl_generics, ty_generics, where_clause) = ir.generics.split_for_impl();
let count_impl = generate_count(ir);
let info_impl = generate_info(ir);
let get_match_arms: Vec<TokenStream> = ir
.param_fields()
.map(|param| {
let field = ¶m.field_name;
let const_name = param.const_name();
quote! {
#struct_name::#const_name => self.#field.get_normalized(),
}
})
.collect();
let set_match_arms: Vec<TokenStream> = ir
.param_fields()
.map(|param| {
let field = ¶m.field_name;
let const_name = param.const_name();
quote! {
#struct_name::#const_name => self.#field.set_normalized(value),
}
})
.collect();
quote! {
impl #impl_generics ::beamer::core::params::Parameters for #struct_name #ty_generics #where_clause {
fn count(&self) -> usize {
#count_impl
}
#info_impl
fn get_normalized(&self, id: ::beamer::core::types::ParamId) -> ::beamer::core::types::ParamValue {
match id {
#(#get_match_arms)*
_ => {
use ::beamer::core::param_types::Params;
self.by_id(id).map(|p| p.get_normalized()).unwrap_or(0.0)
}
}
}
fn set_normalized(&self, id: ::beamer::core::types::ParamId, value: ::beamer::core::types::ParamValue) {
match id {
#(#set_match_arms)*
_ => {
use ::beamer::core::param_types::Params;
if let Some(param) = self.by_id(id) {
param.set_normalized(value);
}
}
}
}
fn normalized_to_string(&self, id: ::beamer::core::types::ParamId, normalized: ::beamer::core::types::ParamValue) -> String {
use ::beamer::core::param_types::Params;
self.by_id(id).map(|p| p.display_normalized(normalized)).unwrap_or_default()
}
fn string_to_normalized(&self, id: ::beamer::core::types::ParamId, string: &str) -> Option<::beamer::core::types::ParamValue> {
use ::beamer::core::param_types::Params;
self.by_id(id).and_then(|p| p.parse(string))
}
fn normalized_to_plain(&self, id: ::beamer::core::types::ParamId, normalized: ::beamer::core::types::ParamValue) -> ::beamer::core::types::ParamValue {
use ::beamer::core::param_types::Params;
self.by_id(id).map(|p| p.normalized_to_plain(normalized)).unwrap_or(0.0)
}
fn plain_to_normalized(&self, id: ::beamer::core::types::ParamId, plain: ::beamer::core::types::ParamValue) -> ::beamer::core::types::ParamValue {
use ::beamer::core::param_types::Params;
self.by_id(id).map(|p| p.plain_to_normalized(plain)).unwrap_or(0.0)
}
}
}
}
fn generate_info(ir: &ParamsIR) -> TokenStream {
let param_match_arms: Vec<TokenStream> = ir
.param_fields()
.enumerate()
.map(|(idx, param)| {
let field = ¶m.field_name;
quote! {
#idx => Some(self.#field.info()),
}
})
.collect();
let param_count = ir.param_count();
if ir.has_nested() {
let nested_infos: Vec<TokenStream> = ir
.nested_fields()
.map(|nested| {
let field = &nested.field_name;
quote! {
let nested_count = ::beamer::core::param_types::Params::count(&self.#field);
if adjusted_index < nested_count {
return ::beamer::core::params::Parameters::info(&self.#field, adjusted_index);
}
adjusted_index -= nested_count;
}
})
.collect();
quote! {
fn info(&self, index: usize) -> Option<&::beamer::core::params::ParamInfo> {
match index {
#(#param_match_arms)*
_ => {
let mut adjusted_index = index - #param_count;
#(#nested_infos)*
None
}
}
}
}
} else {
quote! {
fn info(&self, index: usize) -> Option<&::beamer::core::params::ParamInfo> {
match index {
#(#param_match_arms)*
_ => None,
}
}
}
}
}
fn generate_set_sample_rate(ir: &ParamsIR) -> TokenStream {
let param_calls: Vec<TokenStream> = ir
.param_fields()
.map(|param| {
let field = ¶m.field_name;
quote! { self.#field.set_sample_rate(sample_rate); }
})
.collect();
let nested_calls: Vec<TokenStream> = ir
.nested_fields()
.map(|nested| {
let field = &nested.field_name;
quote! { self.#field.set_sample_rate(sample_rate); }
})
.collect();
if param_calls.is_empty() && nested_calls.is_empty() {
quote! {}
} else {
quote! {
fn set_sample_rate(&mut self, sample_rate: f64) {
#(#param_calls)*
#(#nested_calls)*
}
}
}
}
fn generate_reset_smoothing(ir: &ParamsIR) -> TokenStream {
let param_calls: Vec<TokenStream> = ir
.param_fields()
.map(|param| {
let field = ¶m.field_name;
quote! { self.#field.reset_smoothing(); }
})
.collect();
let nested_calls: Vec<TokenStream> = ir
.nested_fields()
.map(|nested| {
let field = &nested.field_name;
quote! { self.#field.reset_smoothing(); }
})
.collect();
if param_calls.is_empty() && nested_calls.is_empty() {
quote! {}
} else {
quote! {
fn reset_smoothing(&mut self) {
#(#param_calls)*
#(#nested_calls)*
}
}
}
}
fn generate_default_impl(ir: &ParamsIR) -> TokenStream {
if !ir.can_generate_default() {
return quote! {};
}
let struct_name = &ir.struct_name;
let (impl_generics, ty_generics, where_clause) = ir.generics.split_for_impl();
let field_inits: Vec<TokenStream> = ir
.fields
.iter()
.map(|field| match field {
FieldIR::Param(p) => generate_param_initializer(p, struct_name),
FieldIR::Nested(n) => {
let field = &n.field_name;
quote! { #field: Default::default() }
}
})
.collect();
let unit_id_init = if ir.has_nested() || ir.has_flat_groups() {
quote! {
params.set_unit_ids();
}
} else {
quote! {}
};
quote! {
impl #impl_generics Default for #struct_name #ty_generics #where_clause {
fn default() -> Self {
let mut params = Self {
#(#field_inits),*
};
#unit_id_init
params
}
}
}
}
fn generate_param_initializer(param: &ParamFieldIR, struct_name: &syn::Ident) -> TokenStream {
let field = ¶m.field_name;
let constructor = generate_constructor(param);
let builder_chain = generate_builder_chain(param, struct_name);
quote! {
#field: #constructor #builder_chain
}
}
fn generate_constructor(param: &ParamFieldIR) -> TokenStream {
match param.param_type {
crate::ir::ParamType::Float => generate_float_constructor(param),
crate::ir::ParamType::Int => generate_int_constructor(param),
crate::ir::ParamType::Bool => generate_bool_constructor(param),
crate::ir::ParamType::Enum => generate_enum_constructor(param),
}
}
fn generate_float_constructor(param: &ParamFieldIR) -> TokenStream {
let name = param.attrs.name.as_ref().expect("FloatParam requires name");
let default = match ¶m.attrs.default {
Some(ParamDefault::Float(v)) => *v,
Some(ParamDefault::Int(v)) => *v as f64,
_ => 0.0,
};
let kind = param.attrs.kind.unwrap_or(ParamKind::Linear);
match kind {
ParamKind::Percent => {
return quote! {
::beamer::core::param_types::FloatParam::percent(#name, #default)
};
}
ParamKind::Pan => {
return quote! {
::beamer::core::param_types::FloatParam::pan(#name, #default)
};
}
_ => {}
}
let (start, end) = param
.attrs
.range
.as_ref()
.map(|r| (r.start, r.end))
.or_else(|| kind.fixed_range())
.expect("FloatParam requires range");
match kind {
ParamKind::Db => quote! {
::beamer::core::param_types::FloatParam::db(#name, #default, #start..=#end)
},
ParamKind::Hz => quote! {
::beamer::core::param_types::FloatParam::hz(#name, #default, #start..=#end)
},
ParamKind::Ms => quote! {
::beamer::core::param_types::FloatParam::ms(#name, #default, #start..=#end)
},
ParamKind::Seconds => quote! {
::beamer::core::param_types::FloatParam::seconds(#name, #default, #start..=#end)
},
ParamKind::Ratio => quote! {
::beamer::core::param_types::FloatParam::ratio(#name, #default, #start..=#end)
},
ParamKind::Linear => quote! {
::beamer::core::param_types::FloatParam::new(#name, #default, #start..=#end)
},
ParamKind::Semitones => {
quote! {
::beamer::core::param_types::FloatParam::new(#name, #default, #start..=#end)
}
}
ParamKind::Percent | ParamKind::Pan => unreachable!("handled by early return"),
}
}
fn generate_int_constructor(param: &ParamFieldIR) -> TokenStream {
let name = param.attrs.name.as_ref().expect("IntParam requires name");
let default = match ¶m.attrs.default {
Some(ParamDefault::Int(v)) => *v,
Some(ParamDefault::Float(v)) => *v as i64,
_ => 0,
};
let range = param.attrs.range.as_ref().expect("IntParam requires range");
let start = range.start as i64;
let end = range.end as i64;
if param.attrs.kind == Some(ParamKind::Semitones) {
quote! {
::beamer::core::param_types::IntParam::semitones(#name, #default, #start..=#end)
}
} else {
quote! {
::beamer::core::param_types::IntParam::new(#name, #default, #start..=#end)
}
}
}
fn generate_bool_constructor(param: &ParamFieldIR) -> TokenStream {
if param.attrs.bypass {
return quote! {
::beamer::core::param_types::BoolParam::bypass()
};
}
let name = param.attrs.name.as_ref().expect("BoolParam requires name");
let default = match ¶m.attrs.default {
Some(ParamDefault::Bool(v)) => *v,
_ => false,
};
quote! {
::beamer::core::param_types::BoolParam::new(#name, #default)
}
}
fn generate_enum_constructor(param: &ParamFieldIR) -> TokenStream {
let name = param.attrs.name.as_ref().expect("EnumParam requires name");
quote! {
::beamer::core::param_types::EnumParam::new(#name)
}
}
fn generate_builder_chain(param: &ParamFieldIR, struct_name: &syn::Ident) -> TokenStream {
let const_name = param.const_name();
let with_id = quote! {
.with_id(#struct_name::#const_name)
};
let with_short_name = param.attrs.short_name.as_ref().map(|short| {
quote! { .with_short_name(#short) }
});
let with_smoother = if param.param_type == crate::ir::ParamType::Float {
param.attrs.smoothing.as_ref().map(|s| {
let time_ms = s.time_ms;
let style = match s.style {
SmoothingStyle::Exponential => {
quote! { ::beamer::core::smoothing::SmoothingStyle::Exponential(#time_ms) }
}
SmoothingStyle::Linear => {
quote! { ::beamer::core::smoothing::SmoothingStyle::Linear(#time_ms) }
}
};
quote! { .with_smoother(#style) }
})
} else {
None
};
quote! {
#with_id
#with_short_name
#with_smoother
}
}