use proc_macro2::Span;
#[derive(Debug, Clone, Default)]
pub struct ParamAttrs {
pub name: Option<String>,
pub default: Option<ParamDefault>,
pub range: Option<RangeSpec>,
pub kind: Option<ParamKind>,
pub short_name: Option<String>,
pub smoothing: Option<SmoothingSpec>,
pub bypass: bool,
pub group: Option<String>,
}
impl ParamAttrs {
pub fn has_required_for(&self, param_type: ParamType) -> bool {
match param_type {
ParamType::Float => {
self.name.is_some()
&& self.default.is_some()
&& (self.range.is_some() || self.kind.as_ref().is_some_and(|k| k.has_fixed_range()))
}
ParamType::Int => {
self.name.is_some() && self.default.is_some() && self.range.is_some()
}
ParamType::Bool => {
self.bypass || (self.name.is_some() && self.default.is_some())
}
ParamType::Enum => self.name.is_some(),
}
}
}
#[derive(Debug, Clone)]
pub enum ParamDefault {
Float(f64),
Int(i64),
Bool(bool),
}
#[derive(Debug, Clone)]
pub struct RangeSpec {
pub start: f64,
pub end: f64,
pub span: Span,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ParamKind {
Db,
Hz,
Ms,
Seconds,
Percent,
Pan,
Ratio,
Linear,
Semitones,
}
impl ParamKind {
pub fn from_str(s: &str) -> Option<Self> {
match s {
"db" => Some(ParamKind::Db),
"hz" => Some(ParamKind::Hz),
"ms" => Some(ParamKind::Ms),
"seconds" => Some(ParamKind::Seconds),
"percent" => Some(ParamKind::Percent),
"pan" => Some(ParamKind::Pan),
"ratio" => Some(ParamKind::Ratio),
"linear" => Some(ParamKind::Linear),
"semitones" => Some(ParamKind::Semitones),
_ => None,
}
}
pub fn has_fixed_range(&self) -> bool {
matches!(self, ParamKind::Percent | ParamKind::Pan)
}
pub fn fixed_range(&self) -> Option<(f64, f64)> {
match self {
ParamKind::Percent => Some((0.0, 1.0)),
ParamKind::Pan => Some((-1.0, 1.0)),
_ => None,
}
}
}
#[derive(Debug, Clone)]
pub struct SmoothingSpec {
pub style: SmoothingStyle,
pub time_ms: f64,
pub span: Span,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SmoothingStyle {
Exponential,
Linear,
}
impl SmoothingStyle {
pub fn from_str(s: &str) -> Option<Self> {
match s {
"exp" => Some(SmoothingStyle::Exponential),
"linear" => Some(SmoothingStyle::Linear),
_ => None,
}
}
}
#[allow(dead_code)]
pub struct ParamsIR {
pub struct_name: syn::Ident,
pub generics: syn::Generics,
pub fields: Vec<FieldIR>,
pub span: Span,
}
pub enum FieldIR {
Param(ParamFieldIR),
Nested(Box<NestedFieldIR>),
}
#[allow(dead_code)]
pub struct ParamFieldIR {
pub field_name: syn::Ident,
pub param_type: ParamType,
pub string_id: String,
pub hash_id: u32,
pub span: Span,
pub attrs: ParamAttrs,
}
impl ParamFieldIR {
pub fn has_declarative_attrs(&self) -> bool {
self.attrs.has_required_for(self.param_type)
}
pub fn const_name(&self) -> syn::Ident {
let name = self.field_name.to_string().to_uppercase();
syn::Ident::new(&format!("PARAM_{}_VST3_ID", name), self.span)
}
}
#[allow(dead_code)]
pub struct NestedFieldIR {
pub field_name: syn::Ident,
pub field_type: syn::Type,
pub group_name: String,
pub unit_id: i32,
pub parent_unit_id: i32,
pub span: Span,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ParamType {
Float,
Int,
Bool,
Enum,
}
impl ParamsIR {
pub fn param_fields(&self) -> impl Iterator<Item = &ParamFieldIR> {
self.fields.iter().filter_map(|f| match f {
FieldIR::Param(p) => Some(p),
FieldIR::Nested(_) => None,
})
}
pub fn nested_fields(&self) -> impl Iterator<Item = &NestedFieldIR> {
self.fields.iter().filter_map(|f| match f {
FieldIR::Param(_) => None,
FieldIR::Nested(n) => Some(n.as_ref()),
})
}
pub fn param_count(&self) -> usize {
self.param_fields().count()
}
pub fn has_nested(&self) -> bool {
self.nested_fields().next().is_some()
}
pub fn can_generate_default(&self) -> bool {
self.param_fields().all(|p| p.has_declarative_attrs())
}
pub fn has_flat_groups(&self) -> bool {
self.param_fields().any(|p| p.attrs.group.is_some())
}
pub fn flat_group_names(&self) -> Vec<&str> {
let mut seen = std::collections::HashSet::new();
let mut groups = Vec::new();
for param in self.param_fields() {
if let Some(ref group) = param.attrs.group {
if seen.insert(group.as_str()) {
groups.push(group.as_str());
}
}
}
groups
}
}