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 audio graph:
4//! - `AudioNode`: 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#[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    /// Category of the node
125    pub category: NodeCategory,
126
127    /// Description of what the node does
128    pub description: String,
129
130    /// Author of the node
131    pub author: String,
132
133    /// Version of the node
134    pub version: String,
135
136    /// Number of audio input ports
137    pub audio_inputs: usize,
138
139    /// Number of audio output ports
140    pub audio_outputs: usize,
141
142    /// Number of control input ports
143    pub control_inputs: usize,
144
145    /// Number of control output ports
146    pub control_outputs: usize,
147
148    /// Number of clock input ports
149    pub clock_inputs: usize,
150
151    /// Number of clock output ports
152    pub clock_outputs: usize,
153
154    /// Number of feedback ports
155    pub feedback_ports: usize,
156
157    /// Parameters exposed by the node
158    pub parameters: Vec<ParamMetadata>,
159}
160
161impl NodeMetadata {
162    /// Create new node metadata with minimal info
163    pub fn new(name: &str, category: NodeCategory) -> Self {
164        Self {
165            name: name.to_string(),
166            category,
167            description: String::new(),
168            author: String::new(),
169            version: String::new(),
170            audio_inputs: 0,
171            audio_outputs: 0,
172            control_inputs: 0,
173            control_outputs: 0,
174            clock_inputs: 0,
175            clock_outputs: 0,
176            feedback_ports: 0,
177            parameters: Vec::new(),
178        }
179    }
180}
181
182// ============================================================================
183// Node State
184// ============================================================================
185
186/// State of a node during processing
187/// State of a node during processing
188#[derive(Debug, Clone)]
189pub struct NodeState<T: crate::math::Transcendental, const BUF_SIZE: usize> {
190    /// Current sample position
191    pub sample_pos: u64,
192
193    /// Number of processed blocks
194    pub blocks_processed: u64,
195
196    /// Sample rate
197    pub sample_rate: f32,
198
199    /// Whether the node is active
200    pub active: bool,
201
202    /// Internal phase (for generators)
203    pub phase: T,
204}
205
206impl<T: crate::math::Transcendental, const BUF_SIZE: usize> NodeState<T, BUF_SIZE> {
207    /// Create new node state
208    pub fn new(sample_rate: f32) -> Self {
209        Self {
210            sample_pos: 0,
211            blocks_processed: 0,
212            sample_rate,
213            active: true,
214            phase: T::ZERO,
215        }
216    }
217
218    /// Advance state by one block
219    pub fn advance(&mut self) {
220        self.sample_pos += BUF_SIZE as u64;
221        self.blocks_processed += 1;
222    }
223
224    /// Get current time in seconds
225    pub fn current_time_seconds(&self) -> f64 {
226        self.sample_pos as f64 / self.sample_rate as f64
227    }
228
229    /// Reset state
230    pub fn reset(&mut self) {
231        self.sample_pos = 0;
232        self.blocks_processed = 0;
233        self.phase = T::ZERO;
234    }
235}
236
237// ============================================================================
238// AudioNode Trait (Base for all nodes)
239// ============================================================================
240
241/// Base trait for all audio nodes
242///
243/// This trait provides the fundamental operations that every node must implement:
244/// - Port counting
245/// - Parameter access
246/// - Initialization and reset
247///
248/// The actual processing is split into specialized traits:
249/// - `Source` for generators
250/// - `Processor` for processors with inputs/outputs
251/// - `Sink` for consumers
252pub trait AudioNode<T: crate::math::Transcendental, const BUF_SIZE: usize>: Send + Sync {
253    /// Get node metadata
254    fn metadata(&self) -> NodeMetadata;
255
256    /// Get the node's type ID
257    fn node_type_id(&self) -> NodeTypeId
258    where
259        Self: 'static + Sized,
260    {
261        NodeTypeId::of::<Self>()
262    }
263
264    /// Initialize the node with a sample rate
265    fn init(&mut self, sample_rate: f32);
266
267    /// Reset the node to its initial state
268    fn reset(&mut self);
269
270    /// Get the value of a parameter
271    fn get_parameter(&self, id: &ParameterId) -> Option<ParamValue>;
272
273    /// Set the value of a parameter
274    fn set_parameter(&mut self, id: &ParameterId, value: ParamValue) -> ProcessResult<()>;
275
276    /// Apply a `SetParameter` command to this node.
277    ///
278    /// Routes the command to the appropriate port based on `cmd.port`.
279    /// Falls back to `set_parameter()` when the port is not found.
280    fn apply_set_parameter(&mut self, cmd: &SetParameter) -> ProcessResult<()> {
281        use crate::traits::port::{PortDirection, PortType};
282        let value = T::from_f32(cmd.value);
283        let port = match cmd.port.port_type() {
284            PortType::Control => self.control_port_mut(cmd.port.index() as usize),
285            PortType::Audio => match cmd.port.direction() {
286                PortDirection::Input => self.input_port_mut(cmd.port.index() as usize),
287                PortDirection::Output => self.output_port_mut(cmd.port.index() as usize),
288            },
289            PortType::Param => self.input_port_mut(cmd.port.index() as usize),
290            PortType::Clock | PortType::Feedback => None,
291        };
292        match port {
293            Some(p) => {
294                p.set_value(value);
295                Ok(())
296            }
297            None => self.set_parameter(&cmd.parameter, ParamValue::Float(cmd.value)),
298        }
299    }
300
301    /// Get node ID
302    fn id(&self) -> NodeId;
303
304    /// Set node ID
305    fn set_id(&mut self, id: NodeId);
306
307    /// Get input port by index
308    fn input_port(&self, index: usize) -> Option<&crate::traits::port::Port<T, BUF_SIZE>>;
309
310    /// Get mutable input port by index
311    fn input_port_mut(
312        &mut self,
313        index: usize,
314    ) -> Option<&mut crate::traits::port::Port<T, BUF_SIZE>>;
315
316    /// Get output port by index
317    fn output_port(&self, index: usize) -> Option<&crate::traits::port::Port<T, BUF_SIZE>>;
318
319    /// Get mutable output port by index
320    fn output_port_mut(
321        &mut self,
322        index: usize,
323    ) -> Option<&mut crate::traits::port::Port<T, BUF_SIZE>>;
324
325    /// Get control port by index
326    fn control_port(&self, index: usize) -> Option<&crate::traits::port::Port<T, BUF_SIZE>>;
327
328    /// Get mutable control port by index
329    fn control_port_mut(
330        &mut self,
331        index: usize,
332    ) -> Option<&mut crate::traits::port::Port<T, BUF_SIZE>>;
333
334    /// Get node state
335    fn state(&self) -> &NodeState<T, BUF_SIZE>;
336
337    /// Get mutable node state
338    fn state_mut(&mut self) -> &mut NodeState<T, BUF_SIZE>;
339
340    // ========================================================================
341    // Port Counting (with defaults)
342    // ========================================================================
343
344    /// Number of audio input ports
345    fn num_audio_inputs(&self) -> usize {
346        0
347    }
348
349    /// Number of audio output ports
350    fn num_audio_outputs(&self) -> usize {
351        0
352    }
353
354    /// Number of control input ports
355    fn num_control_inputs(&self) -> usize {
356        0
357    }
358
359    /// Number of control output ports
360    fn num_control_outputs(&self) -> usize {
361        0
362    }
363
364    /// Number of clock input ports
365    fn num_clock_inputs(&self) -> usize {
366        0
367    }
368
369    /// Number of clock output ports
370    fn num_clock_outputs(&self) -> usize {
371        0
372    }
373
374    /// Number of feedback ports
375    fn num_feedback_ports(&self) -> usize {
376        0
377    }
378
379    /// Total number of input ports
380    fn num_inputs(&self) -> usize {
381        self.num_audio_inputs()
382            + self.num_control_inputs()
383            + self.num_clock_inputs()
384            + self.num_feedback_ports()
385    }
386
387    /// Total number of output ports
388    fn num_outputs(&self) -> usize {
389        self.num_audio_outputs() + self.num_control_outputs() + self.num_clock_outputs()
390    }
391}
392
393// ============================================================================
394// Source Trait (Active generators)
395// ============================================================================
396
397/// Active source of audio signals
398///
399/// Sources generate audio from internal state. They have no audio inputs,
400/// but may have control and clock inputs for modulation.
401pub trait Source<T: crate::math::Transcendental, const BUF_SIZE: usize>: AudioNode<T, BUF_SIZE> {
402    /// Generate the next block of audio
403    ///
404    /// # Arguments
405    /// * `clock` - Current clock tick
406    /// * `control_inputs` - Control signal values (one per control input)
407    /// * `clock_inputs` - Clock signal values (one per clock input)
408    ///
409    /// The source writes output samples into its own output port buffers,
410    /// accessible via `self.output_port_mut(index)`.
411    fn generate(
412        &mut self,
413        clock: &ClockTick,
414        control_inputs: &[T],
415        clock_inputs: &[ClockTick],
416    ) -> ProcessResult<()>;
417
418    /// Number of audio outputs (default 1)
419    fn num_audio_outputs(&self) -> usize {
420        1
421    }
422
423    /// Number of control inputs (default 0)
424    fn num_control_inputs(&self) -> usize {
425        0
426    }
427
428    /// Number of clock inputs (default 0)
429    fn num_clock_inputs(&self) -> usize {
430        0
431    }
432}
433
434// ============================================================================
435// Processor Trait (Passive processors)
436// ============================================================================
437
438/// Passive processor of audio signals
439///
440/// Processors transform input signals into output signals.
441/// They have audio inputs and outputs, and may have control and clock ports.
442pub trait Processor<T: crate::math::Transcendental, const BUF_SIZE: usize>:
443    AudioNode<T, BUF_SIZE>
444{
445    /// Process a block of audio
446    ///
447    /// # Arguments
448    /// * `clock` - Current clock tick
449    /// * `audio_inputs` - Audio input buffers (one per audio input)
450    /// * `control_inputs` - Control signal values (one per control input)
451    /// * `clock_inputs` - Clock signal values (one per clock input)
452    /// * `feedback_inputs` - Feedback values from previous blocks (one per feedback port)
453    ///
454    /// The processor writes output samples into its own output port buffers,
455    /// accessible via `self.output_port_mut(index)`.
456    fn process(
457        &mut self,
458        clock: &ClockTick,
459        audio_inputs: &[&[T; BUF_SIZE]],
460        control_inputs: &[T],
461        clock_inputs: &[ClockTick],
462        feedback_inputs: &[&[T; BUF_SIZE]],
463    ) -> ProcessResult<()>;
464
465    /// Latency in samples (for delay compensation)
466    fn latency(&self) -> usize {
467        0
468    }
469}
470
471// ============================================================================
472// Sink Trait (Active consumers)
473// ============================================================================
474
475/// Active sink of audio signals
476///
477/// Sinks consume audio and send it to external destinations.
478/// They have no audio outputs, but may have control and clock ports.
479pub trait Sink<T: crate::math::Transcendental, const BUF_SIZE: usize>: AudioNode<T, BUF_SIZE> {
480    /// Consume a block of audio
481    ///
482    /// # Arguments
483    /// * `clock` - Current clock tick
484    /// * `audio_inputs` - Audio input buffers (one per audio input)
485    /// * `control_inputs` - Control signal values (one per control input)
486    /// * `clock_inputs` - Clock signal values (one per clock input)
487    /// * `feedback_inputs` - Feedback values from previous blocks
488    fn consume(
489        &mut self,
490        clock: &ClockTick,
491        audio_inputs: &[&[T; BUF_SIZE]],
492        control_inputs: &[T],
493        clock_inputs: &[ClockTick],
494        feedback_inputs: &[&[T; BUF_SIZE]],
495    ) -> ProcessResult<()>;
496}
497
498// ============================================================================
499// Tests
500// ============================================================================
501
502#[cfg(test)]
503mod tests {
504    use super::*;
505    use crate::math::Transcendental;
506
507    struct TestNode;
508
509    impl<T: Transcendental, const BUF_SIZE: usize> AudioNode<T, BUF_SIZE> for TestNode {
510        fn metadata(&self) -> NodeMetadata {
511            NodeMetadata {
512                name: "Test".to_string(),
513                category: NodeCategory::Utility,
514                description: "Test node".to_string(),
515                author: "Rill".to_string(),
516                version: "1.0".to_string(),
517                audio_inputs: 0,
518                audio_outputs: 0,
519                control_inputs: 0,
520                control_outputs: 0,
521                clock_inputs: 0,
522                clock_outputs: 0,
523                feedback_ports: 0,
524                parameters: vec![],
525            }
526        }
527
528        fn init(&mut self, _sample_rate: f32) {}
529        fn reset(&mut self) {}
530        fn get_parameter(&self, _id: &ParameterId) -> Option<ParamValue> {
531            None
532        }
533        fn set_parameter(&mut self, _id: &ParameterId, _value: ParamValue) -> ProcessResult<()> {
534            Ok(())
535        }
536
537        fn id(&self) -> NodeId {
538            NodeId(0)
539        }
540        fn set_id(&mut self, _id: NodeId) {}
541
542        fn input_port(&self, _index: usize) -> Option<&crate::traits::port::Port<T, BUF_SIZE>> {
543            None
544        }
545        fn input_port_mut(
546            &mut self,
547            _index: usize,
548        ) -> Option<&mut crate::traits::port::Port<T, BUF_SIZE>> {
549            None
550        }
551        fn output_port(&self, _index: usize) -> Option<&crate::traits::port::Port<T, BUF_SIZE>> {
552            None
553        }
554        fn output_port_mut(
555            &mut self,
556            _index: usize,
557        ) -> Option<&mut crate::traits::port::Port<T, BUF_SIZE>> {
558            None
559        }
560        fn control_port(&self, _index: usize) -> Option<&crate::traits::port::Port<T, BUF_SIZE>> {
561            None
562        }
563        fn control_port_mut(
564            &mut self,
565            _index: usize,
566        ) -> Option<&mut crate::traits::port::Port<T, BUF_SIZE>> {
567            None
568        }
569
570        fn state(&self) -> &NodeState<T, BUF_SIZE> {
571            unimplemented!()
572        }
573
574        fn state_mut(&mut self) -> &mut NodeState<T, BUF_SIZE> {
575            unimplemented!()
576        }
577    }
578
579    #[test]
580    fn test_node_id() {
581        let id = NodeId::new(42);
582        assert_eq!(id.inner(), 42);
583        assert_eq!(format!("{}", id), "Node(42)");
584    }
585
586    #[test]
587    fn test_node_category() {
588        assert_eq!(NodeCategory::Source.name(), "source");
589        assert_eq!(NodeCategory::Processor.name(), "processor");
590        assert_eq!(NodeCategory::Sink.name(), "sink");
591        assert_eq!(NodeCategory::Utility.name(), "utility");
592    }
593
594    #[test]
595    fn test_node_metadata_new() {
596        let metadata = NodeMetadata::new("Test", NodeCategory::Source);
597        assert_eq!(metadata.name, "Test");
598        assert_eq!(metadata.category, NodeCategory::Source);
599    }
600
601    #[test]
602    fn test_node_state() {
603        let mut state = NodeState::<f32, 64>::new(44100.0);
604        assert_eq!(state.sample_pos, 0);
605        assert_eq!(state.sample_rate, 44100.0);
606
607        state.advance();
608        assert_eq!(state.sample_pos, 64);
609        assert_eq!(state.blocks_processed, 1);
610
611        state.reset();
612        assert_eq!(state.sample_pos, 0);
613        assert_eq!(state.blocks_processed, 0);
614    }
615}