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::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// Node Trait (Base for all nodes)
245// ============================================================================
246
247/// Base trait for all signal 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 Node<T: crate::math::Transcendental, const BUF_SIZE: usize> {
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 port = match cmd.port.port_type() {
290            PortType::Control => self.control_port_mut(cmd.port.index() as usize),
291            PortType::Signal => match cmd.port.direction() {
292                PortDirection::Input => self.input_port_mut(cmd.port.index() as usize),
293                PortDirection::Output => self.output_port_mut(cmd.port.index() as usize),
294            },
295            PortType::Param => self.input_port_mut(cmd.port.index() as usize),
296            PortType::Clock | PortType::Feedback => None,
297        };
298        match (port, &cmd.value) {
299            (Some(p), ParamValue::Float(v)) => {
300                p.set_value(T::from_f32(*v));
301                Ok(())
302            }
303            _ => self.set_parameter(&cmd.parameter, cmd.value.clone()),
304        }
305    }
306
307    /// Get node ID
308    fn id(&self) -> NodeId;
309
310    /// Resolve named resource buffers (tape loops, etc.) from the registry.
311    fn resolve_resources(&mut self, _buffers: &crate::buffer::BufferRegistry<T>) {}
312
313    /// Downcast to [`IoNode`] if this node implements it.
314    fn as_io_node_mut(&mut self) -> Option<&mut dyn IoNode<T, BUF_SIZE>> {
315        None
316    }
317
318    /// Downcast to [`ActiveNode`] if this node implements it.
319    fn as_active_node_mut(&mut self) -> Option<&mut dyn ActiveNode<T, BUF_SIZE>> {
320        None
321    }
322
323    /// Set node ID
324    fn set_id(&mut self, id: NodeId);
325
326    /// Get input port by index
327    fn input_port(&self, index: usize) -> Option<&crate::traits::port::Port<T, BUF_SIZE>>;
328
329    /// Get mutable input port by index
330    fn input_port_mut(
331        &mut self,
332        index: usize,
333    ) -> Option<&mut crate::traits::port::Port<T, BUF_SIZE>>;
334
335    /// Get output port by index
336    fn output_port(&self, index: usize) -> Option<&crate::traits::port::Port<T, BUF_SIZE>>;
337
338    /// Get mutable output port by index
339    fn output_port_mut(
340        &mut self,
341        index: usize,
342    ) -> Option<&mut crate::traits::port::Port<T, BUF_SIZE>>;
343
344    /// Get control port by index
345    fn control_port(&self, index: usize) -> Option<&crate::traits::port::Port<T, BUF_SIZE>>;
346
347    /// Get mutable control port by index
348    fn control_port_mut(
349        &mut self,
350        index: usize,
351    ) -> Option<&mut crate::traits::port::Port<T, BUF_SIZE>>;
352
353    /// Get node state
354    fn state(&self) -> &NodeState<T, BUF_SIZE>;
355
356    /// Get mutable node state
357    fn state_mut(&mut self) -> &mut NodeState<T, BUF_SIZE>;
358
359    // ========================================================================
360    // Port Counting (with defaults)
361    // ========================================================================
362
363    /// Number of signal input ports
364    fn num_signal_inputs(&self) -> usize {
365        0
366    }
367
368    /// Number of signal output ports
369    fn num_signal_outputs(&self) -> usize {
370        0
371    }
372
373    /// Number of control input ports
374    fn num_control_inputs(&self) -> usize {
375        0
376    }
377
378    /// Number of control output ports
379    fn num_control_outputs(&self) -> usize {
380        0
381    }
382
383    /// Number of clock input ports
384    fn num_clock_inputs(&self) -> usize {
385        0
386    }
387
388    /// Number of clock output ports
389    fn num_clock_outputs(&self) -> usize {
390        0
391    }
392
393    /// Number of feedback ports
394    fn num_feedback_ports(&self) -> usize {
395        0
396    }
397
398    /// Total number of input ports
399    fn num_inputs(&self) -> usize {
400        self.num_signal_inputs()
401            + self.num_control_inputs()
402            + self.num_clock_inputs()
403            + self.num_feedback_ports()
404    }
405
406    /// Total number of output ports
407    fn num_outputs(&self) -> usize {
408        self.num_signal_outputs() + self.num_control_outputs() + self.num_clock_outputs()
409    }
410}
411
412// ============================================================================
413// IoNode Trait
414// ============================================================================
415
416/// A node that owns an I/O backend.
417///
418/// Implemented by nodes that read from or write to a hardware device
419/// (`Input`, `Output`, `LofiInput`). The backend is injected during graph
420/// assembly via [`resolve_backend`](IoNode::resolve_backend).
421pub trait IoNode<T: crate::math::Transcendental, const BUF_SIZE: usize>: Node<T, BUF_SIZE> {
422    /// Take ownership of an I/O backend.
423    fn resolve_backend(&mut self, backend: Box<dyn crate::io::IoBackend<T>>);
424}
425
426// ============================================================================
427// ActiveNode Trait
428// ============================================================================
429
430/// A node that drives graph processing through its I/O backend.
431///
432/// The active node is the single node in a graph that hosts the
433/// callback loop. It receives a tick closure from [`Graph`] and registers
434/// it as the process callback on its backend.
435///
436/// Only one node per graph implements this trait — it must also implement
437/// [`IoNode`].
438pub trait ActiveNode<T: crate::math::Transcendental, const BUF_SIZE: usize>:
439    IoNode<T, BUF_SIZE>
440{
441    /// Run graph processing through this node's I/O backend.
442    ///
443    /// The `tick` closure is called once per signal block with
444    /// `(sample_pos, sample_rate)`. The implementation must register it
445    /// as a process callback on the backend and block until `running`
446    /// becomes `false`.
447    fn run(
448        &mut self,
449        tick: Box<dyn FnMut(u64, f32)>,
450        running: std::sync::Arc<std::sync::atomic::AtomicBool>,
451    ) -> crate::io::IoResult<()>;
452}
453
454// ============================================================================
455// Source Trait (Active generators)
456// ============================================================================
457
458/// Active source of signals
459///
460/// Sources generate signal from internal state. They have no signal inputs,
461/// but may have control and clock inputs for modulation.
462pub trait Source<T: crate::math::Transcendental, const BUF_SIZE: usize>: Node<T, BUF_SIZE> {
463    /// Generate the next block of signal
464    ///
465    /// # Arguments
466    /// * `clock` - Current clock tick
467    /// * `control_inputs` - Control signal values (one per control input)
468    /// * `clock_inputs` - Clock signal values (one per clock input)
469    ///
470    /// The source writes output samples into its own output port buffers,
471    /// accessible via `self.output_port_mut(index)`.
472    fn generate(
473        &mut self,
474        clock: &ClockTick,
475        control_inputs: &[T],
476        clock_inputs: &[ClockTick],
477    ) -> ProcessResult<()>;
478
479    /// Number of signal outputs (default 1)
480    fn num_signal_outputs(&self) -> usize {
481        1
482    }
483
484    /// Number of control inputs (default 0)
485    fn num_control_inputs(&self) -> usize {
486        0
487    }
488
489    /// Number of clock inputs (default 0)
490    fn num_clock_inputs(&self) -> usize {
491        0
492    }
493}
494
495// ============================================================================
496// Processor Trait (Passive processors)
497// ============================================================================
498
499/// Passive processor of signals
500///
501/// Processors transform input signals into output signals.
502/// They have signal inputs and outputs, and may have control and clock ports.
503pub trait Processor<T: crate::math::Transcendental, const BUF_SIZE: usize>:
504    Node<T, BUF_SIZE>
505{
506    /// Process a block of signal
507    ///
508    /// # Arguments
509    /// * `clock` - Current clock tick
510    /// * `signal_inputs` - Signal input buffers (one per signal input)
511    /// * `control_inputs` - Control signal values (one per control input)
512    /// * `clock_inputs` - Clock signal values (one per clock input)
513    /// * `feedback_inputs` - Feedback values from previous blocks (one per feedback port)
514    ///
515    /// The processor writes output samples into its own output port buffers,
516    /// accessible via `self.output_port_mut(index)`.
517    fn process(
518        &mut self,
519        clock: &ClockTick,
520        signal_inputs: &[&[T; BUF_SIZE]],
521        control_inputs: &[T],
522        clock_inputs: &[ClockTick],
523        feedback_inputs: &[&[T; BUF_SIZE]],
524    ) -> ProcessResult<()>;
525
526    /// Latency in samples (for delay compensation)
527    fn latency(&self) -> usize {
528        0
529    }
530}
531
532// ============================================================================
533// Sink Trait (Active consumers)
534// ============================================================================
535
536/// Active sink of signals
537///
538/// Sinks consume signal and send it to external destinations.
539/// They have no signal outputs, but may have control and clock ports.
540pub trait Sink<T: crate::math::Transcendental, const BUF_SIZE: usize>: Node<T, BUF_SIZE> {
541    /// Consume a block of signal
542    ///
543    /// # Arguments
544    /// * `clock` - Current clock tick
545    /// * `signal_inputs` - Signal input buffers (one per signal input)
546    /// * `control_inputs` - Control signal values (one per control input)
547    /// * `clock_inputs` - Clock signal values (one per clock input)
548    /// * `feedback_inputs` - Feedback values from previous blocks
549    fn consume(
550        &mut self,
551        clock: &ClockTick,
552        signal_inputs: &[&[T; BUF_SIZE]],
553        control_inputs: &[T],
554        clock_inputs: &[ClockTick],
555        feedback_inputs: &[&[T; BUF_SIZE]],
556    ) -> ProcessResult<()>;
557}
558
559// ============================================================================
560// Tests
561// ============================================================================
562
563#[cfg(test)]
564mod tests {
565    use super::*;
566
567    #[test]
568    fn test_node_id() {
569        let id = NodeId::new(42);
570        assert_eq!(id.inner(), 42);
571        assert_eq!(format!("{}", id), "Node(42)");
572    }
573
574    #[test]
575    fn test_node_category() {
576        assert_eq!(NodeCategory::Source.name(), "source");
577        assert_eq!(NodeCategory::Processor.name(), "processor");
578        assert_eq!(NodeCategory::Sink.name(), "sink");
579        assert_eq!(NodeCategory::Utility.name(), "utility");
580    }
581
582    #[test]
583    fn test_node_metadata_new() {
584        let metadata = NodeMetadata::new("Test", NodeCategory::Source);
585        assert_eq!(metadata.name, "Test");
586        assert_eq!(metadata.category, NodeCategory::Source);
587    }
588
589    #[test]
590    fn test_node_state() {
591        let mut state = NodeState::<f32, 64>::new(44100.0);
592        assert_eq!(state.sample_pos, 0);
593        assert_eq!(state.sample_rate, 44100.0);
594
595        state.advance();
596        assert_eq!(state.sample_pos, 64);
597        assert_eq!(state.blocks_processed, 1);
598
599        state.reset();
600        assert_eq!(state.sample_pos, 0);
601        assert_eq!(state.blocks_processed, 0);
602    }
603}