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//! - `SignalNode`: 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
200// ============================================================================
201// Parameter Range
202// ============================================================================
203
204/// Range constraints for a parameter
205#[derive(Debug, Clone, PartialEq)]
206pub struct ParamRange {
207    /// Minimum value (if applicable)
208    pub min: Option<f32>,
209
210    /// Maximum value (if applicable)
211    pub max: Option<f32>,
212
213    /// Step size (if applicable)
214    pub step: Option<f32>,
215}
216
217impl ParamRange {
218    /// Create a new empty range
219    pub fn new() -> Self {
220        Self {
221            min: None,
222            max: None,
223            step: None,
224        }
225    }
226
227    /// Set minimum value
228    pub fn with_min(mut self, min: f32) -> Self {
229        self.min = Some(min);
230        self
231    }
232
233    /// Set maximum value
234    pub fn with_max(mut self, max: f32) -> Self {
235        self.max = Some(max);
236        self
237    }
238
239    /// Set step size
240    pub fn with_step(mut self, step: f32) -> Self {
241        self.step = Some(step);
242        self
243    }
244
245    /// Check if value is within range
246    pub fn contains(&self, value: f32) -> bool {
247        if let Some(min) = self.min {
248            if value < min {
249                return false;
250            }
251        }
252        if let Some(max) = self.max {
253            if value > max {
254                return false;
255            }
256        }
257        true
258    }
259
260    /// Clamp value to range
261    pub fn clamp(&self, value: f32) -> f32 {
262        let mut value = value;
263        if let Some(min) = self.min {
264            value = value.max(min);
265        }
266        if let Some(max) = self.max {
267            value = value.min(max);
268        }
269        value
270    }
271}
272
273impl Default for ParamRange {
274    fn default() -> Self {
275        Self::new()
276    }
277}
278
279// ============================================================================
280// Parameter Metadata
281// ============================================================================
282
283/// Metadata about a parameter
284#[derive(Debug, Clone, PartialEq)]
285pub struct ParamMetadata {
286    /// Parameter name (must be a valid ParameterId)
287    pub name: String,
288
289    /// Human-readable description
290    pub description: String,
291
292    /// Parameter type
293    pub typ: ParamType,
294
295    /// Default value
296    pub default: ParamValue,
297
298    /// Value range (if applicable)
299    pub range: ParamRange,
300
301    /// Unit of measurement (e.g., "Hz", "dB", "ms")
302    pub unit: Option<String>,
303
304    /// Possible choices (for Choice parameters)
305    pub choices: Option<Vec<(String, f32)>>,
306}
307
308impl ParamMetadata {
309    /// Create new parameter metadata
310    pub fn new(name: impl Into<String>, typ: ParamType, default: ParamValue) -> Self {
311        Self {
312            name: name.into(),
313            description: String::new(),
314            typ,
315            default,
316            range: ParamRange::default(),
317            unit: None,
318            choices: None,
319        }
320    }
321
322    /// Set description
323    pub fn with_description(mut self, description: impl Into<String>) -> Self {
324        self.description = description.into();
325        self
326    }
327
328    /// Set range
329    pub fn with_range(mut self, min: f32, max: f32, step: f32) -> Self {
330        self.range = ParamRange::new()
331            .with_min(min)
332            .with_max(max)
333            .with_step(step);
334        self
335    }
336
337    /// Set unit
338    pub fn with_unit(mut self, unit: impl Into<String>) -> Self {
339        self.unit = Some(unit.into());
340        self
341    }
342
343    /// Set choices
344    pub fn with_choices(mut self, choices: Vec<(String, f32)>) -> Self {
345        self.choices = Some(choices);
346        self
347    }
348}
349
350// ============================================================================
351// NodeParams — bag of parameters for factory-based node construction
352// ============================================================================
353
354/// A flexible set of parameters passed to a node constructor.
355///
356/// Uses `HashMap<String, ParamValue>` so any node type can extract
357/// whatever named parameters it supports. This is intentionally
358/// open-ended — no fixed schema, no required fields.
359///
360/// See [`NodeConstructor`] (in `rill-graph`) for how builder uses this.
361#[derive(Debug, Clone, Default)]
362#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
363pub struct NodeParams {
364    /// Sample rate the node will be initialized with.
365    pub sample_rate: f32,
366
367    /// Arbitrary named parameters.
368    pub parameters: HashMap<String, ParamValue>,
369}
370
371impl NodeParams {
372    /// Create params with a given sample rate.
373    pub fn new(sample_rate: f32) -> Self {
374        Self {
375            sample_rate,
376            parameters: HashMap::new(),
377        }
378    }
379
380    /// Builder-style: insert a parameter and return self.
381    pub fn with(mut self, key: impl Into<String>, value: ParamValue) -> Self {
382        self.parameters.insert(key.into(), value);
383        self
384    }
385
386    /// Get a parameter by name.
387    pub fn get(&self, key: &str) -> Option<&ParamValue> {
388        self.parameters.get(key)
389    }
390
391    /// Insert or overwrite a parameter.
392    pub fn insert(&mut self, key: impl Into<String>, value: ParamValue) -> Option<ParamValue> {
393        self.parameters.insert(key.into(), value)
394    }
395
396    /// Remove a parameter, returning its value if present.
397    pub fn remove(&mut self, key: &str) -> Option<ParamValue> {
398        self.parameters.remove(key)
399    }
400
401    /// Check whether a parameter exists.
402    pub fn contains(&self, key: &str) -> bool {
403        self.parameters.contains_key(key)
404    }
405
406    /// Number of stored parameters.
407    pub fn len(&self) -> usize {
408        self.parameters.len()
409    }
410
411    /// True when no parameters have been stored.
412    pub fn is_empty(&self) -> bool {
413        self.parameters.is_empty()
414    }
415
416    /// Get a float parameter by name, falling back to `default`.
417    pub fn get_f32(&self, key: &str, default: f32) -> f32 {
418        self.parameters
419            .get(key)
420            .and_then(|v| v.as_f32())
421            .unwrap_or(default)
422    }
423
424    /// Get an integer parameter by name.
425    pub fn get_i32(&self, key: &str, default: i32) -> i32 {
426        self.parameters
427            .get(key)
428            .and_then(|v| v.as_i32())
429            .unwrap_or(default)
430    }
431
432    /// Get a bool parameter by name.
433    pub fn get_bool(&self, key: &str, default: bool) -> bool {
434        self.parameters
435            .get(key)
436            .and_then(|v| v.as_bool())
437            .unwrap_or(default)
438    }
439}
440
441// ============================================================================
442// Tests
443// ============================================================================
444
445#[cfg(test)]
446mod tests {
447    use super::*;
448
449    #[test]
450    fn test_parameter_id_valid() {
451        assert!(ParameterId::new("gain").is_ok());
452        assert!(ParameterId::new("cutoff_freq").is_ok());
453        assert!(ParameterId::new("delay_time_2").is_ok());
454    }
455
456    #[test]
457    fn test_parameter_id_invalid() {
458        assert!(ParameterId::new("").is_err());
459        assert!(ParameterId::new("1gain").is_err());
460        assert!(ParameterId::new("_gain").is_err());
461
462        let long_name = "a".repeat(ParameterId::MAX_LEN + 1);
463        assert!(ParameterId::new(long_name).is_err());
464    }
465
466    #[test]
467    fn test_param_value_conversion() {
468        let f = ParamValue::Float(42.0);
469        assert_eq!(f.as_f32(), Some(42.0));
470        assert_eq!(f.as_i32(), Some(42));
471        assert_eq!(f.as_bool(), Some(true));
472
473        let i = ParamValue::Int(0);
474        assert_eq!(i.as_f32(), Some(0.0));
475        assert_eq!(i.as_i32(), Some(0));
476        assert_eq!(i.as_bool(), Some(false));
477    }
478}