Skip to main content

rill_core/traits/
param.rs

1//! Parameter handling for audio nodes
2//!
3//! Defines the fundamental building blocks of the signal graph:
4//! - `Node`: Base trait for all nodes
5//! - `Source`: Active generator (has no inputs)
6//! - `Processor`: Passive processor (has inputs and outputs)
7//! - `Sink`: Active consumer (has no outputs)
8
9use super::error::{ParameterError, ParameterResult};
10use std::collections::HashMap;
11use std::fmt;
12use std::str::FromStr;
13
14// ============================================================================
15// Parameter ID
16// ============================================================================
17
18/// Type-safe parameter identifier with validation
19#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
20pub struct ParameterId {
21    name: String,
22}
23
24impl ParameterId {
25    /// Maximum length of a parameter name
26    pub const MAX_LEN: usize = 64;
27
28    /// Create a new ParameterId with validation
29    ///
30    /// # Rules
31    /// - Not empty
32    /// - Max length MAX_LEN
33    /// - Starts with a letter (a-z, A-Z)
34    /// - Contains only letters, digits, and underscores
35    pub fn new(name: impl Into<String>) -> ParameterResult<Self> {
36        let name = name.into();
37
38        if name.is_empty() {
39            return Err(ParameterError::Empty);
40        }
41
42        if name.len() > Self::MAX_LEN {
43            return Err(ParameterError::TooLong { max: Self::MAX_LEN });
44        }
45
46        let first = name.chars().next().unwrap();
47        if !first.is_ascii_alphabetic() {
48            return Err(ParameterError::MustStartWithLetter);
49        }
50
51        for c in name.chars() {
52            if !c.is_ascii_alphanumeric() && c != '_' {
53                return Err(ParameterError::InvalidCharacter(c));
54            }
55        }
56
57        Ok(Self { name })
58    }
59
60    /// Get the string representation
61    pub fn as_str(&self) -> &str {
62        &self.name
63    }
64
65    /// Convert into a String
66    pub fn into_string(self) -> String {
67        self.name
68    }
69}
70
71impl AsRef<str> for ParameterId {
72    fn as_ref(&self) -> &str {
73        &self.name
74    }
75}
76
77impl fmt::Display for ParameterId {
78    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79        write!(f, "{}", self.name)
80    }
81}
82
83impl FromStr for ParameterId {
84    type Err = ParameterError;
85
86    fn from_str(s: &str) -> Result<Self, Self::Err> {
87        ParameterId::new(s)
88    }
89}
90
91// ============================================================================
92// Parameter Type
93// ============================================================================
94
95/// Type of parameter value
96#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
97pub enum ParamType {
98    /// Floating point value
99    Float,
100
101    /// Integer value
102    Int,
103
104    /// Boolean value
105    Bool,
106
107    /// String value
108    String,
109
110    /// Choice from a list of options
111    Choice,
112}
113
114impl ParamType {
115    /// Get the name of the parameter type
116    pub fn name(&self) -> &'static str {
117        match self {
118            Self::Float => "float",
119            Self::Int => "int",
120            Self::Bool => "bool",
121            Self::String => "string",
122            Self::Choice => "choice",
123        }
124    }
125}
126
127impl fmt::Display for ParamType {
128    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
129        write!(f, "{}", self.name())
130    }
131}
132
133// ============================================================================
134// Parameter Value
135// ============================================================================
136
137/// Parameter value (can be of different types)
138#[derive(Debug, Clone, PartialEq)]
139#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
140pub enum ParamValue {
141    /// Floating point value
142    Float(f32),
143
144    /// Integer value
145    Int(i32),
146
147    /// Boolean value
148    Bool(bool),
149
150    /// String value
151    String(String),
152
153    /// Choice from a list of options
154    Choice(String),
155}
156
157impl ParamValue {
158    /// Get the type of this value
159    pub fn param_type(&self) -> ParamType {
160        match self {
161            Self::Float(_) => ParamType::Float,
162            Self::Int(_) => ParamType::Int,
163            Self::Bool(_) => ParamType::Bool,
164            Self::String(_) => ParamType::String,
165            Self::Choice(_) => ParamType::Choice,
166        }
167    }
168
169    /// Try to convert to f32
170    pub fn as_f32(&self) -> Option<f32> {
171        match self {
172            Self::Float(f) => Some(*f),
173            Self::Int(i) => Some(*i as f32),
174            Self::Bool(b) => Some(if *b { 1.0 } else { 0.0 }),
175            _ => None,
176        }
177    }
178
179    /// Try to convert to i32
180    pub fn as_i32(&self) -> Option<i32> {
181        match self {
182            Self::Float(f) => Some(*f as i32),
183            Self::Int(i) => Some(*i),
184            Self::Bool(b) => Some(if *b { 1 } else { 0 }),
185            _ => None,
186        }
187    }
188
189    /// Try to convert to bool
190    pub fn as_bool(&self) -> Option<bool> {
191        match self {
192            Self::Bool(b) => Some(*b),
193            Self::Float(f) => Some(*f > 0.5),
194            Self::Int(i) => Some(*i > 0),
195            _ => None,
196        }
197    }
198
199    /// Return the string value if this is a `String` or `Choice` variant.
200    pub fn as_str(&self) -> Option<&str> {
201        match self {
202            Self::String(s) | Self::Choice(s) => Some(s.as_str()),
203            _ => None,
204        }
205    }
206}
207
208// ============================================================================
209// Parameter Range
210// ============================================================================
211
212/// Range constraints for a parameter
213#[derive(Debug, Clone, PartialEq)]
214pub struct ParamRange {
215    /// Minimum value (if applicable)
216    pub min: Option<f32>,
217
218    /// Maximum value (if applicable)
219    pub max: Option<f32>,
220
221    /// Step size (if applicable)
222    pub step: Option<f32>,
223}
224
225impl ParamRange {
226    /// Create a new empty range
227    pub fn new() -> Self {
228        Self {
229            min: None,
230            max: None,
231            step: None,
232        }
233    }
234
235    /// Set minimum value
236    pub fn with_min(mut self, min: f32) -> Self {
237        self.min = Some(min);
238        self
239    }
240
241    /// Set maximum value
242    pub fn with_max(mut self, max: f32) -> Self {
243        self.max = Some(max);
244        self
245    }
246
247    /// Set step size
248    pub fn with_step(mut self, step: f32) -> Self {
249        self.step = Some(step);
250        self
251    }
252
253    /// Check if value is within range
254    pub fn contains(&self, value: f32) -> bool {
255        if let Some(min) = self.min {
256            if value < min {
257                return false;
258            }
259        }
260        if let Some(max) = self.max {
261            if value > max {
262                return false;
263            }
264        }
265        true
266    }
267
268    /// Clamp value to range
269    pub fn clamp(&self, value: f32) -> f32 {
270        let mut value = value;
271        if let Some(min) = self.min {
272            value = value.max(min);
273        }
274        if let Some(max) = self.max {
275            value = value.min(max);
276        }
277        value
278    }
279}
280
281impl Default for ParamRange {
282    fn default() -> Self {
283        Self::new()
284    }
285}
286
287// ============================================================================
288// Parameter Metadata
289// ============================================================================
290
291/// Metadata about a parameter
292#[derive(Debug, Clone, PartialEq)]
293pub struct ParamMetadata {
294    /// Parameter name (must be a valid ParameterId)
295    pub name: String,
296
297    /// Human-readable description
298    pub description: String,
299
300    /// Parameter type
301    pub typ: ParamType,
302
303    /// Default value
304    pub default: ParamValue,
305
306    /// Value range (if applicable)
307    pub range: ParamRange,
308
309    /// Unit of measurement (e.g., "Hz", "dB", "ms")
310    pub unit: Option<String>,
311
312    /// Possible choices (for Choice parameters)
313    pub choices: Option<Vec<(String, f32)>>,
314}
315
316impl ParamMetadata {
317    /// Create new parameter metadata
318    pub fn new(name: impl Into<String>, typ: ParamType, default: ParamValue) -> Self {
319        Self {
320            name: name.into(),
321            description: String::new(),
322            typ,
323            default,
324            range: ParamRange::default(),
325            unit: None,
326            choices: None,
327        }
328    }
329
330    /// Set description
331    pub fn with_description(mut self, description: impl Into<String>) -> Self {
332        self.description = description.into();
333        self
334    }
335
336    /// Set range
337    pub fn with_range(mut self, min: f32, max: f32, step: f32) -> Self {
338        self.range = ParamRange::new()
339            .with_min(min)
340            .with_max(max)
341            .with_step(step);
342        self
343    }
344
345    /// Set unit
346    pub fn with_unit(mut self, unit: impl Into<String>) -> Self {
347        self.unit = Some(unit.into());
348        self
349    }
350
351    /// Set choices
352    pub fn with_choices(mut self, choices: Vec<(String, f32)>) -> Self {
353        self.choices = Some(choices);
354        self
355    }
356}
357
358// ============================================================================
359// Params — bag of parameters for factory-based node construction
360// ============================================================================
361
362/// A flexible set of parameters passed to a node constructor.
363///
364/// Uses `HashMap<String, ParamValue>` so any node type can extract
365/// whatever named parameters it supports. This is intentionally
366/// open-ended — no fixed schema, no required fields.
367///
368/// See `NodeConstructor` (in `rill-graph`) for how builder uses this.
369#[derive(Debug, Clone, Default)]
370#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
371pub struct Params {
372    /// Sample rate the node will be initialized with.
373    pub sample_rate: f32,
374
375    /// Arbitrary named parameters.
376    pub parameters: HashMap<String, ParamValue>,
377}
378
379impl Params {
380    /// Create params with a given sample rate.
381    pub fn new(sample_rate: f32) -> Self {
382        Self {
383            sample_rate,
384            parameters: HashMap::new(),
385        }
386    }
387
388    /// Builder-style: insert a parameter and return self.
389    pub fn with(mut self, key: impl Into<String>, value: ParamValue) -> Self {
390        self.parameters.insert(key.into(), value);
391        self
392    }
393
394    /// Get a parameter by name.
395    pub fn get(&self, key: &str) -> Option<&ParamValue> {
396        self.parameters.get(key)
397    }
398
399    /// Insert or overwrite a parameter.
400    pub fn insert(&mut self, key: impl Into<String>, value: ParamValue) -> Option<ParamValue> {
401        self.parameters.insert(key.into(), value)
402    }
403
404    /// Remove a parameter, returning its value if present.
405    pub fn remove(&mut self, key: &str) -> Option<ParamValue> {
406        self.parameters.remove(key)
407    }
408
409    /// Check whether a parameter exists.
410    pub fn contains(&self, key: &str) -> bool {
411        self.parameters.contains_key(key)
412    }
413
414    /// Number of stored parameters.
415    pub fn len(&self) -> usize {
416        self.parameters.len()
417    }
418
419    /// True when no parameters have been stored.
420    pub fn is_empty(&self) -> bool {
421        self.parameters.is_empty()
422    }
423
424    /// Get a float parameter by name, falling back to `default`.
425    pub fn get_f32(&self, key: &str, default: f32) -> f32 {
426        self.parameters
427            .get(key)
428            .and_then(|v| v.as_f32())
429            .unwrap_or(default)
430    }
431
432    /// Get an integer parameter by name.
433    pub fn get_i32(&self, key: &str, default: i32) -> i32 {
434        self.parameters
435            .get(key)
436            .and_then(|v| v.as_i32())
437            .unwrap_or(default)
438    }
439
440    /// Get a bool parameter by name.
441    pub fn get_bool(&self, key: &str, default: bool) -> bool {
442        self.parameters
443            .get(key)
444            .and_then(|v| v.as_bool())
445            .unwrap_or(default)
446    }
447}
448
449// ============================================================================
450// Tests
451// ============================================================================
452
453#[cfg(test)]
454mod tests {
455    use super::*;
456
457    #[test]
458    fn test_parameter_id_valid() {
459        assert!(ParameterId::new("gain").is_ok());
460        assert!(ParameterId::new("cutoff_freq").is_ok());
461        assert!(ParameterId::new("delay_time_2").is_ok());
462    }
463
464    #[test]
465    fn test_parameter_id_invalid() {
466        assert!(ParameterId::new("").is_err());
467        assert!(ParameterId::new("1gain").is_err());
468        assert!(ParameterId::new("_gain").is_err());
469
470        let long_name = "a".repeat(ParameterId::MAX_LEN + 1);
471        assert!(ParameterId::new(long_name).is_err());
472    }
473
474    #[test]
475    fn test_param_value_conversion() {
476        let f = ParamValue::Float(42.0);
477        assert_eq!(f.as_f32(), Some(42.0));
478        assert_eq!(f.as_i32(), Some(42));
479        assert_eq!(f.as_bool(), Some(true));
480
481        let i = ParamValue::Int(0);
482        assert_eq!(i.as_f32(), Some(0.0));
483        assert_eq!(i.as_i32(), Some(0));
484        assert_eq!(i.as_bool(), Some(false));
485    }
486}