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}