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