Skip to main content

rill_core/traits/
param.rs

1//! Parameter handling for audio nodes
2//!
3//! Parameters are values that can be changed at runtime,
4//! either by automation or direct user control.
5
6use super::error::{ParameterError, ParameterResult};
7use std::fmt;
8use std::str::FromStr;
9
10// ============================================================================
11// Parameter ID
12// ============================================================================
13
14/// Type-safe parameter identifier with validation
15#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
16pub struct ParameterId {
17    name: String,
18}
19
20impl ParameterId {
21    /// Maximum length of a parameter name
22    pub const MAX_LEN: usize = 64;
23
24    /// Create a new ParameterId with validation
25    ///
26    /// # Rules
27    /// - Not empty
28    /// - Max length MAX_LEN
29    /// - Starts with a letter (a-z, A-Z)
30    /// - Contains only letters, digits, and underscores
31    pub fn new(name: impl Into<String>) -> ParameterResult<Self> {
32        let name = name.into();
33
34        if name.is_empty() {
35            return Err(ParameterError::Empty);
36        }
37
38        if name.len() > Self::MAX_LEN {
39            return Err(ParameterError::TooLong { max: Self::MAX_LEN });
40        }
41
42        let first = name.chars().next().unwrap();
43        if !first.is_ascii_alphabetic() {
44            return Err(ParameterError::MustStartWithLetter);
45        }
46
47        for c in name.chars() {
48            if !c.is_ascii_alphanumeric() && c != '_' {
49                return Err(ParameterError::InvalidCharacter(c));
50            }
51        }
52
53        Ok(Self { name })
54    }
55
56    /// Get the string representation
57    pub fn as_str(&self) -> &str {
58        &self.name
59    }
60
61    /// Convert into a String
62    pub fn into_string(self) -> String {
63        self.name
64    }
65}
66
67impl AsRef<str> for ParameterId {
68    fn as_ref(&self) -> &str {
69        &self.name
70    }
71}
72
73impl fmt::Display for ParameterId {
74    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75        write!(f, "{}", self.name)
76    }
77}
78
79impl FromStr for ParameterId {
80    type Err = ParameterError;
81
82    fn from_str(s: &str) -> Result<Self, Self::Err> {
83        ParameterId::new(s)
84    }
85}
86
87// ============================================================================
88// Parameter Type
89// ============================================================================
90
91/// Type of parameter value
92#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
93pub enum ParamType {
94    /// Floating point value
95    Float,
96
97    /// Integer value
98    Int,
99
100    /// Boolean value
101    Bool,
102
103    /// String value
104    String,
105
106    /// Choice from a list of options
107    Choice,
108}
109
110impl ParamType {
111    /// Get the name of the parameter type
112    pub fn name(&self) -> &'static str {
113        match self {
114            Self::Float => "float",
115            Self::Int => "int",
116            Self::Bool => "bool",
117            Self::String => "string",
118            Self::Choice => "choice",
119        }
120    }
121}
122
123impl fmt::Display for ParamType {
124    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
125        write!(f, "{}", self.name())
126    }
127}
128
129// ============================================================================
130// Parameter Value
131// ============================================================================
132
133/// Parameter value (can be of different types)
134#[derive(Debug, Clone, PartialEq)]
135pub enum ParamValue {
136    /// Floating point value
137    Float(f32),
138
139    /// Integer value
140    Int(i32),
141
142    /// Boolean value
143    Bool(bool),
144
145    /// String value
146    String(String),
147
148    /// Choice from a list of options
149    Choice(String),
150}
151
152impl ParamValue {
153    /// Get the type of this value
154    pub fn param_type(&self) -> ParamType {
155        match self {
156            Self::Float(_) => ParamType::Float,
157            Self::Int(_) => ParamType::Int,
158            Self::Bool(_) => ParamType::Bool,
159            Self::String(_) => ParamType::String,
160            Self::Choice(_) => ParamType::Choice,
161        }
162    }
163
164    /// Try to convert to f32
165    pub fn as_f32(&self) -> Option<f32> {
166        match self {
167            Self::Float(f) => Some(*f),
168            Self::Int(i) => Some(*i as f32),
169            Self::Bool(b) => Some(if *b { 1.0 } else { 0.0 }),
170            _ => None,
171        }
172    }
173
174    /// Try to convert to i32
175    pub fn as_i32(&self) -> Option<i32> {
176        match self {
177            Self::Float(f) => Some(*f as i32),
178            Self::Int(i) => Some(*i),
179            Self::Bool(b) => Some(if *b { 1 } else { 0 }),
180            _ => None,
181        }
182    }
183
184    /// Try to convert to bool
185    pub fn as_bool(&self) -> Option<bool> {
186        match self {
187            Self::Bool(b) => Some(*b),
188            Self::Float(f) => Some(*f > 0.5),
189            Self::Int(i) => Some(*i > 0),
190            _ => None,
191        }
192    }
193}
194
195// ============================================================================
196// Parameter Range
197// ============================================================================
198
199/// Range constraints for a parameter
200#[derive(Debug, Clone, PartialEq)]
201pub struct ParamRange {
202    /// Minimum value (if applicable)
203    pub min: Option<f32>,
204
205    /// Maximum value (if applicable)
206    pub max: Option<f32>,
207
208    /// Step size (if applicable)
209    pub step: Option<f32>,
210}
211
212impl ParamRange {
213    /// Create a new empty range
214    pub fn new() -> Self {
215        Self {
216            min: None,
217            max: None,
218            step: None,
219        }
220    }
221
222    /// Set minimum value
223    pub fn with_min(mut self, min: f32) -> Self {
224        self.min = Some(min);
225        self
226    }
227
228    /// Set maximum value
229    pub fn with_max(mut self, max: f32) -> Self {
230        self.max = Some(max);
231        self
232    }
233
234    /// Set step size
235    pub fn with_step(mut self, step: f32) -> Self {
236        self.step = Some(step);
237        self
238    }
239
240    /// Check if value is within range
241    pub fn contains(&self, value: f32) -> bool {
242        if let Some(min) = self.min {
243            if value < min {
244                return false;
245            }
246        }
247        if let Some(max) = self.max {
248            if value > max {
249                return false;
250            }
251        }
252        true
253    }
254
255    /// Clamp value to range
256    pub fn clamp(&self, value: f32) -> f32 {
257        let mut value = value;
258        if let Some(min) = self.min {
259            value = value.max(min);
260        }
261        if let Some(max) = self.max {
262            value = value.min(max);
263        }
264        value
265    }
266}
267
268impl Default for ParamRange {
269    fn default() -> Self {
270        Self::new()
271    }
272}
273
274// ============================================================================
275// Parameter Metadata
276// ============================================================================
277
278/// Metadata about a parameter
279#[derive(Debug, Clone, PartialEq)]
280pub struct ParamMetadata {
281    /// Parameter name (must be a valid ParameterId)
282    pub name: String,
283
284    /// Human-readable description
285    pub description: String,
286
287    /// Parameter type
288    pub typ: ParamType,
289
290    /// Default value
291    pub default: ParamValue,
292
293    /// Value range (if applicable)
294    pub range: ParamRange,
295
296    /// Unit of measurement (e.g., "Hz", "dB", "ms")
297    pub unit: Option<String>,
298
299    /// Possible choices (for Choice parameters)
300    pub choices: Option<Vec<(String, f32)>>,
301}
302
303impl ParamMetadata {
304    /// Create new parameter metadata
305    pub fn new(name: impl Into<String>, typ: ParamType, default: ParamValue) -> Self {
306        Self {
307            name: name.into(),
308            description: String::new(),
309            typ,
310            default,
311            range: ParamRange::default(),
312            unit: None,
313            choices: None,
314        }
315    }
316
317    /// Set description
318    pub fn with_description(mut self, description: impl Into<String>) -> Self {
319        self.description = description.into();
320        self
321    }
322
323    /// Set range
324    pub fn with_range(mut self, min: f32, max: f32, step: f32) -> Self {
325        self.range = ParamRange::new()
326            .with_min(min)
327            .with_max(max)
328            .with_step(step);
329        self
330    }
331
332    /// Set unit
333    pub fn with_unit(mut self, unit: impl Into<String>) -> Self {
334        self.unit = Some(unit.into());
335        self
336    }
337
338    /// Set choices
339    pub fn with_choices(mut self, choices: Vec<(String, f32)>) -> Self {
340        self.choices = Some(choices);
341        self
342    }
343}
344
345// ============================================================================
346// Tests
347// ============================================================================
348
349#[cfg(test)]
350mod tests {
351    use super::*;
352
353    #[test]
354    fn test_parameter_id_valid() {
355        assert!(ParameterId::new("gain").is_ok());
356        assert!(ParameterId::new("cutoff_freq").is_ok());
357        assert!(ParameterId::new("delay_time_2").is_ok());
358    }
359
360    #[test]
361    fn test_parameter_id_invalid() {
362        assert!(ParameterId::new("").is_err());
363        assert!(ParameterId::new("1gain").is_err());
364        assert!(ParameterId::new("_gain").is_err());
365
366        let long_name = "a".repeat(ParameterId::MAX_LEN + 1);
367        assert!(ParameterId::new(long_name).is_err());
368    }
369
370    #[test]
371    fn test_param_value_conversion() {
372        let f = ParamValue::Float(42.0);
373        assert_eq!(f.as_f32(), Some(42.0));
374        assert_eq!(f.as_i32(), Some(42));
375        assert_eq!(f.as_bool(), Some(true));
376
377        let i = ParamValue::Int(0);
378        assert_eq!(i.as_f32(), Some(0.0));
379        assert_eq!(i.as_i32(), Some(0));
380        assert_eq!(i.as_bool(), Some(false));
381    }
382}