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