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// ============================================================================
410// Source Trait (Active generators)
411// ============================================================================
412
413/// Active source of signals
414///
415/// Sources generate audio from internal state. They have no audio inputs,
416/// but may have control and clock inputs for modulation.
417pub trait Source<T: crate::math::Transcendental, const BUF_SIZE: usize>: SignalNode<T, BUF_SIZE> {
418    /// Generate the next block of audio
419    ///
420    /// # Arguments
421    /// * `clock` - Current clock tick
422    /// * `control_inputs` - Control signal values (one per control input)
423    /// * `clock_inputs` - Clock signal values (one per clock input)
424    ///
425    /// The source writes output samples into its own output port buffers,
426    /// accessible via `self.output_port_mut(index)`.
427    fn generate(
428        &mut self,
429        clock: &ClockTick,
430        control_inputs: &[T],
431        clock_inputs: &[ClockTick],
432    ) -> ProcessResult<()>;
433
434    /// Number of audio outputs (default 1)
435    fn num_signal_outputs(&self) -> usize {
436        1
437    }
438
439    /// Number of control inputs (default 0)
440    fn num_control_inputs(&self) -> usize {
441        0
442    }
443
444    /// Number of clock inputs (default 0)
445    fn num_clock_inputs(&self) -> usize {
446        0
447    }
448}
449
450// ============================================================================
451// Processor Trait (Passive processors)
452// ============================================================================
453
454/// Passive processor of signals
455///
456/// Processors transform input signals into output signals.
457/// They have audio inputs and outputs, and may have control and clock ports.
458pub trait Processor<T: crate::math::Transcendental, const BUF_SIZE: usize>:
459    SignalNode<T, BUF_SIZE>
460{
461    /// Process a block of audio
462    ///
463    /// # Arguments
464    /// * `clock` - Current clock tick
465    /// * `signal_inputs` - Audio input buffers (one per audio input)
466    /// * `control_inputs` - Control signal values (one per control input)
467    /// * `clock_inputs` - Clock signal values (one per clock input)
468    /// * `feedback_inputs` - Feedback values from previous blocks (one per feedback port)
469    ///
470    /// The processor writes output samples into its own output port buffers,
471    /// accessible via `self.output_port_mut(index)`.
472    fn process(
473        &mut self,
474        clock: &ClockTick,
475        signal_inputs: &[&[T; BUF_SIZE]],
476        control_inputs: &[T],
477        clock_inputs: &[ClockTick],
478        feedback_inputs: &[&[T; BUF_SIZE]],
479    ) -> ProcessResult<()>;
480
481    /// Latency in samples (for delay compensation)
482    fn latency(&self) -> usize {
483        0
484    }
485}
486
487// ============================================================================
488// Sink Trait (Active consumers)
489// ============================================================================
490
491/// Active sink of signals
492///
493/// Sinks consume audio and send it to external destinations.
494/// They have no audio outputs, but may have control and clock ports.
495pub trait Sink<T: crate::math::Transcendental, const BUF_SIZE: usize>: SignalNode<T, BUF_SIZE> {
496    /// Consume a block of audio
497    ///
498    /// # Arguments
499    /// * `clock` - Current clock tick
500    /// * `signal_inputs` - Audio input buffers (one per audio input)
501    /// * `control_inputs` - Control signal values (one per control input)
502    /// * `clock_inputs` - Clock signal values (one per clock input)
503    /// * `feedback_inputs` - Feedback values from previous blocks
504    fn consume(
505        &mut self,
506        clock: &ClockTick,
507        signal_inputs: &[&[T; BUF_SIZE]],
508        control_inputs: &[T],
509        clock_inputs: &[ClockTick],
510        feedback_inputs: &[&[T; BUF_SIZE]],
511    ) -> ProcessResult<()>;
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}