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