Skip to main content

rill_core/traits/
param.rs

1//! Parameter handling for signal 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    /// Raw byte array (for IoControl writes)
114    Bytes,
115}
116
117impl ParamType {
118    /// Get the name of the parameter type
119    pub fn name(&self) -> &'static str {
120        match self {
121            Self::Float => "float",
122            Self::Int => "int",
123            Self::Bool => "bool",
124            Self::String => "string",
125            Self::Choice => "choice",
126            Self::Bytes => "bytes",
127        }
128    }
129}
130
131impl fmt::Display for ParamType {
132    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
133        write!(f, "{}", self.name())
134    }
135}
136
137// ============================================================================
138// Parameter Value
139// ============================================================================
140
141/// Parameter value (can be of different types)
142#[derive(Debug, Clone, PartialEq)]
143#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
144pub enum ParamValue {
145    /// Floating point value
146    Float(f32),
147
148    /// Integer value
149    Int(i32),
150
151    /// Boolean value
152    Bool(bool),
153
154    /// String value
155    String(String),
156
157    /// Choice from a list of options
158    Choice(String),
159
160    /// Raw byte array for backend IoControl writes
161    Bytes(Vec<u8>),
162}
163
164impl ParamValue {
165    /// Get the type of this value
166    pub fn param_type(&self) -> ParamType {
167        match self {
168            Self::Float(_) => ParamType::Float,
169            Self::Int(_) => ParamType::Int,
170            Self::Bool(_) => ParamType::Bool,
171            Self::String(_) => ParamType::String,
172            Self::Choice(_) => ParamType::Choice,
173            Self::Bytes(_) => ParamType::Bytes,
174        }
175    }
176
177    /// Try to convert to f32
178    pub fn as_f32(&self) -> Option<f32> {
179        match self {
180            Self::Float(f) => Some(*f),
181            Self::Int(i) => Some(*i as f32),
182            Self::Bool(b) => Some(if *b { 1.0 } else { 0.0 }),
183            _ => None,
184        }
185    }
186
187    /// Try to convert to i32
188    pub fn as_i32(&self) -> Option<i32> {
189        match self {
190            Self::Float(f) => Some(*f as i32),
191            Self::Int(i) => Some(*i),
192            Self::Bool(b) => Some(if *b { 1 } else { 0 }),
193            _ => None,
194        }
195    }
196
197    /// Try to convert to bool
198    pub fn as_bool(&self) -> Option<bool> {
199        match self {
200            Self::Bool(b) => Some(*b),
201            Self::Float(f) => Some(*f > 0.5),
202            Self::Int(i) => Some(*i > 0),
203            _ => None,
204        }
205    }
206
207    /// Return the string value if this is a `String` or `Choice` variant.
208    pub fn as_str(&self) -> Option<&str> {
209        match self {
210            Self::String(s) | Self::Choice(s) => Some(s.as_str()),
211            _ => None,
212        }
213    }
214
215    /// Return the byte slice if this is a `Bytes` variant.
216    pub fn as_bytes(&self) -> Option<&[u8]> {
217        match self {
218            Self::Bytes(v) => Some(v.as_slice()),
219            _ => None,
220        }
221    }
222}
223
224// ============================================================================
225// Parameter Range
226// ============================================================================
227
228/// Range constraints for a parameter
229#[derive(Debug, Clone, PartialEq)]
230pub struct ParamRange {
231    /// Minimum value (if applicable)
232    pub min: Option<f32>,
233
234    /// Maximum value (if applicable)
235    pub max: Option<f32>,
236
237    /// Step size (if applicable)
238    pub step: Option<f32>,
239}
240
241impl ParamRange {
242    /// Create a new empty range
243    pub fn new() -> Self {
244        Self {
245            min: None,
246            max: None,
247            step: None,
248        }
249    }
250
251    /// Set minimum value
252    pub fn with_min(mut self, min: f32) -> Self {
253        self.min = Some(min);
254        self
255    }
256
257    /// Set maximum value
258    pub fn with_max(mut self, max: f32) -> Self {
259        self.max = Some(max);
260        self
261    }
262
263    /// Set step size
264    pub fn with_step(mut self, step: f32) -> Self {
265        self.step = Some(step);
266        self
267    }
268
269    /// Check if value is within range
270    pub fn contains(&self, value: f32) -> bool {
271        if let Some(min) = self.min {
272            if value < min {
273                return false;
274            }
275        }
276        if let Some(max) = self.max {
277            if value > max {
278                return false;
279            }
280        }
281        true
282    }
283
284    /// Clamp value to range
285    pub fn clamp(&self, value: f32) -> f32 {
286        let mut value = value;
287        if let Some(min) = self.min {
288            value = value.max(min);
289        }
290        if let Some(max) = self.max {
291            value = value.min(max);
292        }
293        value
294    }
295}
296
297impl Default for ParamRange {
298    fn default() -> Self {
299        Self::new()
300    }
301}
302
303// ============================================================================
304// Parameter Metadata
305// ============================================================================
306
307/// Metadata about a parameter
308#[derive(Debug, Clone, PartialEq)]
309pub struct ParamMetadata {
310    /// Parameter name (must be a valid ParameterId)
311    pub name: String,
312
313    /// Human-readable description
314    pub description: String,
315
316    /// Parameter type
317    pub typ: ParamType,
318
319    /// Default value
320    pub default: ParamValue,
321
322    /// Value range (if applicable)
323    pub range: ParamRange,
324
325    /// Unit of measurement (e.g., "Hz", "dB", "ms")
326    pub unit: Option<String>,
327
328    /// Possible choices (for Choice parameters)
329    pub choices: Option<Vec<(String, f32)>>,
330}
331
332impl ParamMetadata {
333    /// Create new parameter metadata
334    pub fn new(name: impl Into<String>, typ: ParamType, default: ParamValue) -> Self {
335        Self {
336            name: name.into(),
337            description: String::new(),
338            typ,
339            default,
340            range: ParamRange::default(),
341            unit: None,
342            choices: None,
343        }
344    }
345
346    /// Set description
347    pub fn with_description(mut self, description: impl Into<String>) -> Self {
348        self.description = description.into();
349        self
350    }
351
352    /// Set range
353    pub fn with_range(mut self, min: f32, max: f32, step: f32) -> Self {
354        self.range = ParamRange::new()
355            .with_min(min)
356            .with_max(max)
357            .with_step(step);
358        self
359    }
360
361    /// Set unit
362    pub fn with_unit(mut self, unit: impl Into<String>) -> Self {
363        self.unit = Some(unit.into());
364        self
365    }
366
367    /// Set choices
368    pub fn with_choices(mut self, choices: Vec<(String, f32)>) -> Self {
369        self.choices = Some(choices);
370        self
371    }
372}
373
374// ============================================================================
375// Params — bag of parameters for factory-based node construction
376// ============================================================================
377
378/// A flexible set of parameters passed to a node constructor.
379///
380/// Uses `HashMap<String, ParamValue>` so any node type can extract
381/// whatever named parameters it supports. This is intentionally
382/// open-ended — no fixed schema, no required fields.
383///
384/// See `NodeConstructor` (in `rill-graph`) for how builder uses this.
385#[derive(Debug, Clone, Default)]
386#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
387pub struct Params {
388    /// Sample rate the node will be initialized with.
389    pub sample_rate: f32,
390
391    /// Arbitrary named parameters.
392    pub parameters: HashMap<String, ParamValue>,
393}
394
395impl Params {
396    /// Create params with a given sample rate.
397    pub fn new(sample_rate: f32) -> Self {
398        Self {
399            sample_rate,
400            parameters: HashMap::new(),
401        }
402    }
403
404    /// Builder-style: insert a parameter and return self.
405    pub fn with(mut self, key: impl Into<String>, value: ParamValue) -> Self {
406        self.parameters.insert(key.into(), value);
407        self
408    }
409
410    /// Get a parameter by name.
411    pub fn get(&self, key: &str) -> Option<&ParamValue> {
412        self.parameters.get(key)
413    }
414
415    /// Insert or overwrite a parameter.
416    pub fn insert(&mut self, key: impl Into<String>, value: ParamValue) -> Option<ParamValue> {
417        self.parameters.insert(key.into(), value)
418    }
419
420    /// Remove a parameter, returning its value if present.
421    pub fn remove(&mut self, key: &str) -> Option<ParamValue> {
422        self.parameters.remove(key)
423    }
424
425    /// Check whether a parameter exists.
426    pub fn contains(&self, key: &str) -> bool {
427        self.parameters.contains_key(key)
428    }
429
430    /// Number of stored parameters.
431    pub fn len(&self) -> usize {
432        self.parameters.len()
433    }
434
435    /// True when no parameters have been stored.
436    pub fn is_empty(&self) -> bool {
437        self.parameters.is_empty()
438    }
439
440    /// Get a float parameter by name, falling back to `default`.
441    pub fn get_f32(&self, key: &str, default: f32) -> f32 {
442        self.parameters
443            .get(key)
444            .and_then(|v| v.as_f32())
445            .unwrap_or(default)
446    }
447
448    /// Get an integer parameter by name.
449    pub fn get_i32(&self, key: &str, default: i32) -> i32 {
450        self.parameters
451            .get(key)
452            .and_then(|v| v.as_i32())
453            .unwrap_or(default)
454    }
455
456    /// Get a bool parameter by name.
457    pub fn get_bool(&self, key: &str, default: bool) -> bool {
458        self.parameters
459            .get(key)
460            .and_then(|v| v.as_bool())
461            .unwrap_or(default)
462    }
463}
464
465// ============================================================================
466// Tests
467// ============================================================================
468
469#[cfg(test)]
470mod tests {
471    use super::*;
472
473    #[test]
474    fn test_parameter_id_valid() {
475        assert!(ParameterId::new("gain").is_ok());
476        assert!(ParameterId::new("cutoff_freq").is_ok());
477        assert!(ParameterId::new("delay_time_2").is_ok());
478    }
479
480    #[test]
481    fn test_parameter_id_invalid() {
482        assert!(ParameterId::new("").is_err());
483        assert!(ParameterId::new("1gain").is_err());
484        assert!(ParameterId::new("_gain").is_err());
485
486        let long_name = "a".repeat(ParameterId::MAX_LEN + 1);
487        assert!(ParameterId::new(long_name).is_err());
488    }
489
490    #[test]
491    fn test_param_value_conversion() {
492        let f = ParamValue::Float(42.0);
493        assert_eq!(f.as_f32(), Some(42.0));
494        assert_eq!(f.as_i32(), Some(42));
495        assert_eq!(f.as_bool(), Some(true));
496
497        let i = ParamValue::Int(0);
498        assert_eq!(i.as_f32(), Some(0.0));
499        assert_eq!(i.as_i32(), Some(0));
500        assert_eq!(i.as_bool(), Some(false));
501    }
502}