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 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 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    /// Attach a telemetry sender to this node.
412    ///
413    /// Nodes that push telemetry (e.g. clock tick from a hardware source)
414    /// should store this sender and use it from their `generate()` /
415    /// `process()` / `consume()` methods via `TelemetryTx::try_send`.
416    /// Default is no-op — override only in nodes that produce telemetry.
417    fn set_telemetry_tx(&mut self, _tx: crate::queues::telemetry::TelemetryTx) {}
418}
419
420// ============================================================================
421// IoNode Trait
422// ============================================================================
423
424/// A node that owns an audio I/O backend.
425///
426/// Implemented by nodes that read from or write to an audio device
427/// (`Input`, `Output`, `LofiInput`). The backend is injected during graph
428/// assembly via [`resolve_backend`](IoNode::resolve_backend).
429pub trait IoNode<T: crate::math::Transcendental, const BUF_SIZE: usize>: Node<T, BUF_SIZE> {
430    /// Take ownership of an audio I/O backend.
431    fn resolve_backend(&mut self, backend: Box<dyn crate::io::IoBackend<T>>);
432}
433
434// ============================================================================
435// ActiveNode Trait
436// ============================================================================
437
438/// A node that drives graph processing through its audio backend.
439///
440/// The active node is the single node in a graph that hosts the audio
441/// callback loop. It receives a tick closure from [`Graph`] and registers
442/// it as the process callback on its backend.
443///
444/// Only one node per graph implements this trait — it must also implement
445/// [`IoNode`].
446pub trait ActiveNode<T: crate::math::Transcendental, const BUF_SIZE: usize>:
447    IoNode<T, BUF_SIZE>
448{
449    /// Run graph processing through this node's audio backend.
450    ///
451    /// The `tick` closure is called once per audio block with
452    /// `(sample_pos, sample_rate)`. The implementation must register it
453    /// as a process callback on the backend and block until `running`
454    /// becomes `false`.
455    fn run(
456        &mut self,
457        tick: Box<dyn FnMut(u64, f32)>,
458        running: std::sync::Arc<std::sync::atomic::AtomicBool>,
459    ) -> crate::io::IoResult<()>;
460}
461
462// ============================================================================
463// Source Trait (Active generators)
464// ============================================================================
465
466/// Active source of signals
467///
468/// Sources generate audio from internal state. They have no audio inputs,
469/// but may have control and clock inputs for modulation.
470pub trait Source<T: crate::math::Transcendental, const BUF_SIZE: usize>: Node<T, BUF_SIZE> {
471    /// Generate the next block of audio
472    ///
473    /// # Arguments
474    /// * `clock` - Current clock tick
475    /// * `control_inputs` - Control signal values (one per control input)
476    /// * `clock_inputs` - Clock signal values (one per clock input)
477    ///
478    /// The source writes output samples into its own output port buffers,
479    /// accessible via `self.output_port_mut(index)`.
480    fn generate(
481        &mut self,
482        clock: &ClockTick,
483        control_inputs: &[T],
484        clock_inputs: &[ClockTick],
485    ) -> ProcessResult<()>;
486
487    /// Number of audio outputs (default 1)
488    fn num_signal_outputs(&self) -> usize {
489        1
490    }
491
492    /// Number of control inputs (default 0)
493    fn num_control_inputs(&self) -> usize {
494        0
495    }
496
497    /// Number of clock inputs (default 0)
498    fn num_clock_inputs(&self) -> usize {
499        0
500    }
501}
502
503// ============================================================================
504// Processor Trait (Passive processors)
505// ============================================================================
506
507/// Passive processor of signals
508///
509/// Processors transform input signals into output signals.
510/// They have audio inputs and outputs, and may have control and clock ports.
511pub trait Processor<T: crate::math::Transcendental, const BUF_SIZE: usize>:
512    Node<T, BUF_SIZE>
513{
514    /// Process a block of audio
515    ///
516    /// # Arguments
517    /// * `clock` - Current clock tick
518    /// * `signal_inputs` - Audio input buffers (one per audio input)
519    /// * `control_inputs` - Control signal values (one per control input)
520    /// * `clock_inputs` - Clock signal values (one per clock input)
521    /// * `feedback_inputs` - Feedback values from previous blocks (one per feedback port)
522    ///
523    /// The processor writes output samples into its own output port buffers,
524    /// accessible via `self.output_port_mut(index)`.
525    fn process(
526        &mut self,
527        clock: &ClockTick,
528        signal_inputs: &[&[T; BUF_SIZE]],
529        control_inputs: &[T],
530        clock_inputs: &[ClockTick],
531        feedback_inputs: &[&[T; BUF_SIZE]],
532    ) -> ProcessResult<()>;
533
534    /// Latency in samples (for delay compensation)
535    fn latency(&self) -> usize {
536        0
537    }
538}
539
540// ============================================================================
541// Sink Trait (Active consumers)
542// ============================================================================
543
544/// Active sink of signals
545///
546/// Sinks consume audio and send it to external destinations.
547/// They have no audio outputs, but may have control and clock ports.
548pub trait Sink<T: crate::math::Transcendental, const BUF_SIZE: usize>: Node<T, BUF_SIZE> {
549    /// Consume a block of audio
550    ///
551    /// # Arguments
552    /// * `clock` - Current clock tick
553    /// * `signal_inputs` - Audio input buffers (one per audio input)
554    /// * `control_inputs` - Control signal values (one per control input)
555    /// * `clock_inputs` - Clock signal values (one per clock input)
556    /// * `feedback_inputs` - Feedback values from previous blocks
557    fn consume(
558        &mut self,
559        clock: &ClockTick,
560        signal_inputs: &[&[T; BUF_SIZE]],
561        control_inputs: &[T],
562        clock_inputs: &[ClockTick],
563        feedback_inputs: &[&[T; BUF_SIZE]],
564    ) -> ProcessResult<()>;
565}
566
567// ============================================================================
568// Tests
569// ============================================================================
570
571#[cfg(test)]
572mod tests {
573    use super::*;
574
575    #[test]
576    fn test_node_id() {
577        let id = NodeId::new(42);
578        assert_eq!(id.inner(), 42);
579        assert_eq!(format!("{}", id), "Node(42)");
580    }
581
582    #[test]
583    fn test_node_category() {
584        assert_eq!(NodeCategory::Source.name(), "source");
585        assert_eq!(NodeCategory::Processor.name(), "processor");
586        assert_eq!(NodeCategory::Sink.name(), "sink");
587        assert_eq!(NodeCategory::Utility.name(), "utility");
588    }
589
590    #[test]
591    fn test_node_metadata_new() {
592        let metadata = NodeMetadata::new("Test", NodeCategory::Source);
593        assert_eq!(metadata.name, "Test");
594        assert_eq!(metadata.category, NodeCategory::Source);
595    }
596
597    #[test]
598    fn test_node_state() {
599        let mut state = NodeState::<f32, 64>::new(44100.0);
600        assert_eq!(state.sample_pos, 0);
601        assert_eq!(state.sample_rate, 44100.0);
602
603        state.advance();
604        assert_eq!(state.sample_pos, 64);
605        assert_eq!(state.blocks_processed, 1);
606
607        state.reset();
608        assert_eq!(state.sample_pos, 0);
609        assert_eq!(state.blocks_processed, 0);
610    }
611}