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