Skip to main content

rill_core/traits/
node.rs

1//! Core node traits for the Rill ecosystem
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 crate::queues::signal::SetParameter;
10use crate::time::ClockTick;
11use crate::traits::param::{ParamMetadata, ParamValue, ParameterId};
12use crate::traits::ProcessResult;
13use std::any::TypeId;
14use std::fmt;
15
16// ============================================================================
17// Node Identification
18// ============================================================================
19
20/// Unique identifier for a node in the graph
21#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
22#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
23pub struct NodeId(pub u32);
24
25impl NodeId {
26    /// Create a new node ID
27    pub const fn new(id: u32) -> Self {
28        Self(id)
29    }
30
31    /// Get the inner value
32    pub const fn inner(&self) -> u32 {
33        self.0
34    }
35}
36
37impl fmt::Display for NodeId {
38    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39        write!(f, "Node({})", self.0)
40    }
41}
42
43impl From<u32> for NodeId {
44    fn from(id: u32) -> Self {
45        Self(id)
46    }
47}
48
49// ============================================================================
50// Node Category
51// ============================================================================
52
53/// Category of a node (for UI/organization)
54#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
55pub enum NodeCategory {
56    /// Source nodes (generators)
57    Source,
58
59    /// Processor nodes (effects, filters)
60    Processor,
61
62    /// Sink nodes (outputs)
63    Sink,
64
65    /// Utility nodes (routing, mixing)
66    Utility,
67
68    /// Analyzer nodes (meters, scopes)
69    Analyzer,
70
71    /// Sequencer nodes (pattern generators)
72    Sequencer,
73}
74
75impl NodeCategory {
76    /// Get the name of the category
77    pub const fn name(&self) -> &'static str {
78        match self {
79            Self::Source => "source",
80            Self::Processor => "processor",
81            Self::Sink => "sink",
82            Self::Utility => "utility",
83            Self::Analyzer => "analyzer",
84            Self::Sequencer => "sequencer",
85        }
86    }
87}
88
89impl fmt::Display for NodeCategory {
90    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91        write!(f, "{}", self.name())
92    }
93}
94
95// ============================================================================
96// Node Type ID
97// ============================================================================
98
99/// Type identifier for a node (for downcasting)
100#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
101pub struct NodeTypeId(TypeId);
102
103impl NodeTypeId {
104    /// Create a new node type ID from a type
105    pub fn of<T: 'static + ?Sized>() -> Self {
106        Self(TypeId::of::<T>())
107    }
108
109    /// Get the inner TypeId
110    pub fn as_type_id(&self) -> TypeId {
111        self.0
112    }
113}
114
115// ============================================================================
116// Node Metadata
117// ============================================================================
118
119/// Metadata about a node
120#[derive(Debug, Clone)]
121pub struct NodeMetadata {
122    /// Name of the node
123    pub name: String,
124
125    /// Canonical type name used for serialization / factory lookup
126    /// (e.g. `Some("rill/sine_osc")`). When `None`, [`NodeMetadata::name`] is used instead.
127    pub type_name: Option<String>,
128
129    /// Category of the node
130    pub category: NodeCategory,
131
132    /// Description of what the node does
133    pub description: String,
134
135    /// Author of the node
136    pub author: String,
137
138    /// Version of the node
139    pub version: String,
140
141    /// Number of signal input ports
142    pub signal_inputs: usize,
143
144    /// Number of signal output ports
145    pub signal_outputs: usize,
146
147    /// Number of control input ports
148    pub control_inputs: usize,
149
150    /// Number of control output ports
151    pub control_outputs: usize,
152
153    /// Number of clock input ports
154    pub clock_inputs: usize,
155
156    /// Number of clock output ports
157    pub clock_outputs: usize,
158
159    /// Number of feedback ports
160    pub feedback_ports: usize,
161
162    /// Parameters exposed by the node
163    pub parameters: Vec<ParamMetadata>,
164}
165
166impl NodeMetadata {
167    /// Create new node metadata with minimal info
168    pub fn new(name: &str, category: NodeCategory) -> Self {
169        Self {
170            type_name: None,
171            name: name.to_string(),
172            category,
173            description: String::new(),
174            author: String::new(),
175            version: String::new(),
176            signal_inputs: 0,
177            signal_outputs: 0,
178            control_inputs: 0,
179            control_outputs: 0,
180            clock_inputs: 0,
181            clock_outputs: 0,
182            feedback_ports: 0,
183            parameters: Vec::new(),
184        }
185    }
186}
187
188// ============================================================================
189// Node State
190// ============================================================================
191
192/// State of a node during processing
193/// State of a node during processing
194#[derive(Debug, Clone)]
195pub struct NodeState<T: crate::math::Transcendental, const BUF_SIZE: usize> {
196    /// Current sample position
197    pub sample_pos: u64,
198
199    /// Number of processed blocks
200    pub blocks_processed: u64,
201
202    /// Sample rate
203    pub sample_rate: f32,
204
205    /// Whether the node is active
206    pub active: bool,
207
208    /// Internal phase (for generators)
209    pub phase: T,
210}
211
212impl<T: crate::math::Transcendental, const BUF_SIZE: usize> NodeState<T, BUF_SIZE> {
213    /// Create new node state
214    pub fn new(sample_rate: f32) -> Self {
215        Self {
216            sample_pos: 0,
217            blocks_processed: 0,
218            sample_rate,
219            active: true,
220            phase: T::ZERO,
221        }
222    }
223
224    /// Advance state by one block
225    pub fn advance(&mut self) {
226        self.sample_pos += BUF_SIZE as u64;
227        self.blocks_processed += 1;
228    }
229
230    /// Get current time in seconds
231    pub fn current_time_seconds(&self) -> f64 {
232        self.sample_pos as f64 / self.sample_rate as f64
233    }
234
235    /// Reset state
236    pub fn reset(&mut self) {
237        self.sample_pos = 0;
238        self.blocks_processed = 0;
239        self.phase = T::ZERO;
240    }
241}
242
243// ============================================================================
244// SignalNode Trait (Base for all nodes)
245// ============================================================================
246
247/// Base trait for all audio nodes
248///
249/// This trait provides the fundamental operations that every node must implement:
250/// - Port counting
251/// - Parameter access
252/// - Initialization and reset
253/// - Optional telemetry sender
254///
255/// The actual processing is split into specialized traits:
256/// - `Source` for generators
257/// - `Processor` for processors with inputs/outputs
258/// - `Sink` for consumers
259pub trait SignalNode<T: crate::math::Transcendental, const BUF_SIZE: usize>: Send + Sync {
260    /// Get node metadata
261    fn metadata(&self) -> NodeMetadata;
262
263    /// Get the node's type ID
264    fn node_type_id(&self) -> NodeTypeId
265    where
266        Self: 'static + Sized,
267    {
268        NodeTypeId::of::<Self>()
269    }
270
271    /// Initialize the node with a sample rate
272    fn init(&mut self, sample_rate: f32);
273
274    /// Reset the node to its initial state
275    fn reset(&mut self);
276
277    /// Get the value of a parameter
278    fn get_parameter(&self, id: &ParameterId) -> Option<ParamValue>;
279
280    /// Set the value of a parameter
281    fn set_parameter(&mut self, id: &ParameterId, value: ParamValue) -> ProcessResult<()>;
282
283    /// Apply a `SetParameter` command to this node.
284    ///
285    /// Routes the command to the appropriate port based on `cmd.port`.
286    /// Falls back to `set_parameter()` when the port is not found.
287    fn apply_set_parameter(&mut self, cmd: &SetParameter) -> ProcessResult<()> {
288        use crate::traits::port::{PortDirection, PortType};
289        let value = T::from_f32(cmd.value);
290        let port = match cmd.port.port_type() {
291            PortType::Control => self.control_port_mut(cmd.port.index() as usize),
292            PortType::Signal => match cmd.port.direction() {
293                PortDirection::Input => self.input_port_mut(cmd.port.index() as usize),
294                PortDirection::Output => self.output_port_mut(cmd.port.index() as usize),
295            },
296            PortType::Param => self.input_port_mut(cmd.port.index() as usize),
297            PortType::Clock | PortType::Feedback => None,
298        };
299        match port {
300            Some(p) => {
301                p.set_value(value);
302                Ok(())
303            }
304            None => self.set_parameter(&cmd.parameter, ParamValue::Float(cmd.value)),
305        }
306    }
307
308    /// Get node ID
309    fn id(&self) -> NodeId;
310
311    /// Set node ID
312    fn set_id(&mut self, id: NodeId);
313
314    /// Get input port by index
315    fn input_port(&self, index: usize) -> Option<&crate::traits::port::Port<T, BUF_SIZE>>;
316
317    /// Get mutable input port by index
318    fn input_port_mut(
319        &mut self,
320        index: usize,
321    ) -> Option<&mut crate::traits::port::Port<T, BUF_SIZE>>;
322
323    /// Get output port by index
324    fn output_port(&self, index: usize) -> Option<&crate::traits::port::Port<T, BUF_SIZE>>;
325
326    /// Get mutable output port by index
327    fn output_port_mut(
328        &mut self,
329        index: usize,
330    ) -> Option<&mut crate::traits::port::Port<T, BUF_SIZE>>;
331
332    /// Get control port by index
333    fn control_port(&self, index: usize) -> Option<&crate::traits::port::Port<T, BUF_SIZE>>;
334
335    /// Get mutable control port by index
336    fn control_port_mut(
337        &mut self,
338        index: usize,
339    ) -> Option<&mut crate::traits::port::Port<T, BUF_SIZE>>;
340
341    /// Get node state
342    fn state(&self) -> &NodeState<T, BUF_SIZE>;
343
344    /// Get mutable node state
345    fn state_mut(&mut self) -> &mut NodeState<T, BUF_SIZE>;
346
347    // ========================================================================
348    // Port Counting (with defaults)
349    // ========================================================================
350
351    /// Number of signal input ports
352    fn num_signal_inputs(&self) -> usize {
353        0
354    }
355
356    /// Number of signal output ports
357    fn num_signal_outputs(&self) -> usize {
358        0
359    }
360
361    /// Number of control input ports
362    fn num_control_inputs(&self) -> usize {
363        0
364    }
365
366    /// Number of control output ports
367    fn num_control_outputs(&self) -> usize {
368        0
369    }
370
371    /// Number of clock input ports
372    fn num_clock_inputs(&self) -> usize {
373        0
374    }
375
376    /// Number of clock output ports
377    fn num_clock_outputs(&self) -> usize {
378        0
379    }
380
381    /// Number of feedback ports
382    fn num_feedback_ports(&self) -> usize {
383        0
384    }
385
386    /// Total number of input ports
387    fn num_inputs(&self) -> usize {
388        self.num_signal_inputs()
389            + self.num_control_inputs()
390            + self.num_clock_inputs()
391            + self.num_feedback_ports()
392    }
393
394    /// Total number of output ports
395    fn num_outputs(&self) -> usize {
396        self.num_signal_outputs() + self.num_control_outputs() + self.num_clock_outputs()
397    }
398
399    /// Attach a telemetry sender to this node.
400    ///
401    /// Nodes that push telemetry (e.g. clock tick from a hardware source)
402    /// should store this sender and use it from their `generate()` /
403    /// `process()` / `consume()` methods via `TelemetryTx::try_send`.
404    /// Default is no-op — override only in nodes that produce telemetry.
405    fn set_telemetry_tx(&mut self, _tx: crate::queues::telemetry::TelemetryTx) {}
406}
407
408// ============================================================================
409// Source Trait (Active generators)
410// ============================================================================
411
412/// Active source of signals
413///
414/// Sources generate audio from internal state. They have no audio inputs,
415/// but may have control and clock inputs for modulation.
416pub trait Source<T: crate::math::Transcendental, const BUF_SIZE: usize>:
417    SignalNode<T, BUF_SIZE>
418{
419    /// Generate the next block of audio
420    ///
421    /// # Arguments
422    /// * `clock` - Current clock tick
423    /// * `control_inputs` - Control signal values (one per control input)
424    /// * `clock_inputs` - Clock signal values (one per clock input)
425    ///
426    /// The source writes output samples into its own output port buffers,
427    /// accessible via `self.output_port_mut(index)`.
428    fn generate(
429        &mut self,
430        clock: &ClockTick,
431        control_inputs: &[T],
432        clock_inputs: &[ClockTick],
433    ) -> ProcessResult<()>;
434
435    /// Number of audio outputs (default 1)
436    fn num_signal_outputs(&self) -> usize {
437        1
438    }
439
440    /// Number of control inputs (default 0)
441    fn num_control_inputs(&self) -> usize {
442        0
443    }
444
445    /// Number of clock inputs (default 0)
446    fn num_clock_inputs(&self) -> usize {
447        0
448    }
449}
450
451// ============================================================================
452// Processor Trait (Passive processors)
453// ============================================================================
454
455/// Passive processor of signals
456///
457/// Processors transform input signals into output signals.
458/// They have audio inputs and outputs, and may have control and clock ports.
459pub trait Processor<T: crate::math::Transcendental, const BUF_SIZE: usize>:
460    SignalNode<T, BUF_SIZE>
461{
462    /// Process a block of audio
463    ///
464    /// # Arguments
465    /// * `clock` - Current clock tick
466    /// * `signal_inputs` - Audio input buffers (one per audio input)
467    /// * `control_inputs` - Control signal values (one per control input)
468    /// * `clock_inputs` - Clock signal values (one per clock input)
469    /// * `feedback_inputs` - Feedback values from previous blocks (one per feedback port)
470    ///
471    /// The processor writes output samples into its own output port buffers,
472    /// accessible via `self.output_port_mut(index)`.
473    fn process(
474        &mut self,
475        clock: &ClockTick,
476        signal_inputs: &[&[T; BUF_SIZE]],
477        control_inputs: &[T],
478        clock_inputs: &[ClockTick],
479        feedback_inputs: &[&[T; BUF_SIZE]],
480    ) -> ProcessResult<()>;
481
482    /// Latency in samples (for delay compensation)
483    fn latency(&self) -> usize {
484        0
485    }
486}
487
488// ============================================================================
489// Sink Trait (Active consumers)
490// ============================================================================
491
492/// Active sink of signals
493///
494/// Sinks consume audio and send it to external destinations.
495/// They have no audio outputs, but may have control and clock ports.
496pub trait Sink<T: crate::math::Transcendental, const BUF_SIZE: usize>:
497    SignalNode<T, BUF_SIZE>
498{
499    /// Consume a block of audio
500    ///
501    /// # Arguments
502    /// * `clock` - Current clock tick
503    /// * `signal_inputs` - Audio input buffers (one per audio input)
504    /// * `control_inputs` - Control signal values (one per control input)
505    /// * `clock_inputs` - Clock signal values (one per clock input)
506    /// * `feedback_inputs` - Feedback values from previous blocks
507    fn consume(
508        &mut self,
509        clock: &ClockTick,
510        signal_inputs: &[&[T; BUF_SIZE]],
511        control_inputs: &[T],
512        clock_inputs: &[ClockTick],
513        feedback_inputs: &[&[T; BUF_SIZE]],
514    ) -> ProcessResult<()>;
515}
516
517// ============================================================================
518// Tests
519// ============================================================================
520
521#[cfg(test)]
522mod tests {
523    use super::*;
524
525    #[test]
526    fn test_node_id() {
527        let id = NodeId::new(42);
528        assert_eq!(id.inner(), 42);
529        assert_eq!(format!("{}", id), "Node(42)");
530    }
531
532    #[test]
533    fn test_node_category() {
534        assert_eq!(NodeCategory::Source.name(), "source");
535        assert_eq!(NodeCategory::Processor.name(), "processor");
536        assert_eq!(NodeCategory::Sink.name(), "sink");
537        assert_eq!(NodeCategory::Utility.name(), "utility");
538    }
539
540    #[test]
541    fn test_node_metadata_new() {
542        let metadata = NodeMetadata::new("Test", NodeCategory::Source);
543        assert_eq!(metadata.name, "Test");
544        assert_eq!(metadata.category, NodeCategory::Source);
545    }
546
547    #[test]
548    fn test_node_state() {
549        let mut state = NodeState::<f32, 64>::new(44100.0);
550        assert_eq!(state.sample_pos, 0);
551        assert_eq!(state.sample_rate, 44100.0);
552
553        state.advance();
554        assert_eq!(state.sample_pos, 64);
555        assert_eq!(state.blocks_processed, 1);
556
557        state.reset();
558        assert_eq!(state.sample_pos, 0);
559        assert_eq!(state.blocks_processed, 0);
560    }
561}