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    /// Set node ID
313    fn set_id(&mut self, id: NodeId);
314
315    /// Get input port by index
316    fn input_port(&self, index: usize) -> Option<&crate::traits::port::Port<T, BUF_SIZE>>;
317
318    /// Get mutable input port by index
319    fn input_port_mut(
320        &mut self,
321        index: usize,
322    ) -> Option<&mut crate::traits::port::Port<T, BUF_SIZE>>;
323
324    /// Get output port by index
325    fn output_port(&self, index: usize) -> Option<&crate::traits::port::Port<T, BUF_SIZE>>;
326
327    /// Get mutable output port by index
328    fn output_port_mut(
329        &mut self,
330        index: usize,
331    ) -> Option<&mut crate::traits::port::Port<T, BUF_SIZE>>;
332
333    /// Get control port by index
334    fn control_port(&self, index: usize) -> Option<&crate::traits::port::Port<T, BUF_SIZE>>;
335
336    /// Get mutable control port by index
337    fn control_port_mut(
338        &mut self,
339        index: usize,
340    ) -> Option<&mut crate::traits::port::Port<T, BUF_SIZE>>;
341
342    /// Get node state
343    fn state(&self) -> &NodeState<T, BUF_SIZE>;
344
345    /// Get mutable node state
346    fn state_mut(&mut self) -> &mut NodeState<T, BUF_SIZE>;
347
348    // ========================================================================
349    // Port Counting (with defaults)
350    // ========================================================================
351
352    /// Number of signal input ports
353    fn num_signal_inputs(&self) -> usize {
354        0
355    }
356
357    /// Number of signal output ports
358    fn num_signal_outputs(&self) -> usize {
359        0
360    }
361
362    /// Number of control input ports
363    fn num_control_inputs(&self) -> usize {
364        0
365    }
366
367    /// Number of control output ports
368    fn num_control_outputs(&self) -> usize {
369        0
370    }
371
372    /// Number of clock input ports
373    fn num_clock_inputs(&self) -> usize {
374        0
375    }
376
377    /// Number of clock output ports
378    fn num_clock_outputs(&self) -> usize {
379        0
380    }
381
382    /// Number of feedback ports
383    fn num_feedback_ports(&self) -> usize {
384        0
385    }
386
387    /// Total number of input ports
388    fn num_inputs(&self) -> usize {
389        self.num_signal_inputs()
390            + self.num_control_inputs()
391            + self.num_clock_inputs()
392            + self.num_feedback_ports()
393    }
394
395    /// Total number of output ports
396    fn num_outputs(&self) -> usize {
397        self.num_signal_outputs() + self.num_control_outputs() + self.num_clock_outputs()
398    }
399}
400
401// ============================================================================
402// Source Trait (Active generators)
403// ============================================================================
404
405/// Active source of signals
406///
407/// Sources generate signal from internal state. They have no signal inputs,
408/// but may have control and clock inputs for modulation.
409pub trait Source<T: crate::math::Transcendental, const BUF_SIZE: usize>: Node<T, BUF_SIZE> {
410    /// Generate the next block of signal
411    ///
412    /// # Arguments
413    /// * `clock` - Current clock tick
414    /// * `control_inputs` - Control signal values (one per control input)
415    /// * `clock_inputs` - Clock signal values (one per clock input)
416    ///
417    /// The source writes output samples into its own output port buffers,
418    /// accessible via `self.output_port_mut(index)`.
419    fn generate(
420        &mut self,
421        ctx: &crate::time::RenderContext,
422        control_inputs: &[T],
423        clock_inputs: &[crate::time::RenderContext],
424        tick: &crate::time::ClockTick,
425    ) -> ProcessResult<()>;
426
427    /// Number of signal outputs (default 1)
428    fn num_signal_outputs(&self) -> usize {
429        1
430    }
431
432    /// Number of control inputs (default 0)
433    fn num_control_inputs(&self) -> usize {
434        0
435    }
436
437    /// Number of clock inputs (default 0)
438    fn num_clock_inputs(&self) -> usize {
439        0
440    }
441
442    /// Attach a capture backend. No-op by default; I/O nodes override.
443    fn set_capture(&mut self, _capture: std::sync::Arc<dyn crate::io::IoCapture>) {}
444}
445
446// ============================================================================
447// Processor Trait (Passive processors)
448// ============================================================================
449
450/// Passive processor of signals
451///
452/// Processors transform input signals into output signals.
453/// They have signal inputs and outputs, and may have control and clock ports.
454pub trait Processor<T: crate::math::Transcendental, const BUF_SIZE: usize>:
455    Node<T, BUF_SIZE>
456{
457    /// Process a block of signal
458    ///
459    /// # Arguments
460    /// * `clock` - Current clock tick
461    /// * `signal_inputs` - Signal input buffers (one per signal input)
462    /// * `control_inputs` - Control signal values (one per control input)
463    /// * `clock_inputs` - Clock signal values (one per clock input)
464    /// * `feedback_inputs` - Feedback values from previous blocks (one per feedback port)
465    ///
466    /// The processor writes output samples into its own output port buffers,
467    /// accessible via `self.output_port_mut(index)`.
468    fn process(
469        &mut self,
470        ctx: &crate::time::RenderContext,
471        signal_inputs: &[&[T; BUF_SIZE]],
472        control_inputs: &[T],
473        clock_inputs: &[crate::time::RenderContext],
474        feedback_inputs: &[&[T; BUF_SIZE]],
475    ) -> ProcessResult<()>;
476
477    /// Latency in samples (for delay compensation)
478    fn latency(&self) -> usize {
479        0
480    }
481}
482
483// ============================================================================
484// Sink Trait (Active consumers)
485// ============================================================================
486
487/// Active sink of signals
488///
489/// Sinks consume signal and send it to external destinations.
490/// They have no signal outputs, but may have control and clock ports.
491pub trait Sink<T: crate::math::Transcendental, const BUF_SIZE: usize>: Node<T, BUF_SIZE> {
492    /// Consume a block of signal
493    ///
494    /// # Arguments
495    /// * `clock` - Current clock tick
496    /// * `signal_inputs` - Signal input buffers (one per signal input)
497    /// * `control_inputs` - Control signal values (one per control input)
498    /// * `clock_inputs` - Clock signal values (one per clock input)
499    /// * `feedback_inputs` - Feedback values from previous blocks
500    fn consume(
501        &mut self,
502        ctx: &crate::time::RenderContext,
503        signal_inputs: &[&[T; BUF_SIZE]],
504        control_inputs: &[T],
505        clock_inputs: &[crate::time::RenderContext],
506        feedback_inputs: &[&[T; BUF_SIZE]],
507        tick: &crate::time::ClockTick,
508    ) -> ProcessResult<()>;
509
510    /// Attach a playback backend. No-op by default; I/O nodes override.
511    fn set_playback(&mut self, _playback: std::sync::Arc<dyn crate::io::IoPlayback>) {}
512}
513
514// ============================================================================
515// Tests
516// ============================================================================
517
518#[cfg(test)]
519mod tests {
520    use super::*;
521
522    #[test]
523    fn test_node_id() {
524        let id = NodeId::new(42);
525        assert_eq!(id.inner(), 42);
526        assert_eq!(format!("{}", id), "Node(42)");
527    }
528
529    #[test]
530    fn test_node_category() {
531        assert_eq!(NodeCategory::Source.name(), "source");
532        assert_eq!(NodeCategory::Processor.name(), "processor");
533        assert_eq!(NodeCategory::Sink.name(), "sink");
534        assert_eq!(NodeCategory::Utility.name(), "utility");
535    }
536
537    #[test]
538    fn test_node_metadata_new() {
539        let metadata = NodeMetadata::new("Test", NodeCategory::Source);
540        assert_eq!(metadata.name, "Test");
541        assert_eq!(metadata.category, NodeCategory::Source);
542    }
543
544    #[test]
545    fn test_node_state() {
546        let mut state = NodeState::<f32, 64>::new(44100.0);
547        assert_eq!(state.sample_pos, 0);
548        assert_eq!(state.sample_rate, 44100.0);
549
550        state.advance();
551        assert_eq!(state.sample_pos, 64);
552        assert_eq!(state.blocks_processed, 1);
553
554        state.reset();
555        assert_eq!(state.sample_pos, 0);
556        assert_eq!(state.blocks_processed, 0);
557    }
558}