Skip to main content

rill_core/traits/
port.rs

1//! Port types and identifiers for the Rill ecosystem
2//!
3//! Ports are the connection points between nodes in the audio graph.
4//! Each output port owns a `Buffer<T, BUF_SIZE>` and an optional `Action`
5//! that defines how data is produced. Input ports are connection endpoints
6//! that receive data from upstream output ports.
7
8use crate::buffer::Buffer;
9use crate::math::Transcendental;
10use crate::time::ClockTick;
11use crate::traits::algorithm::Algorithm;
12use crate::traits::node::{AudioNode, NodeId};
13use crate::traits::processable::NodeVariant;
14use crate::traits::PortError;
15use std::fmt;
16
17// ============================================================================
18// Port Type
19// ============================================================================
20
21/// Type of a port - what kind of signal it carries
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
23pub enum PortType {
24    /// Audio signal port - carries sound (typically -1.0 to 1.0)
25    Audio,
26
27    /// Control signal port - carries modulation/automation
28    Control,
29
30    /// Clock signal port - carries timing information
31    Clock,
32
33    /// Feedback port - stores state between blocks
34    Feedback,
35
36    /// Parameter port - for node parameters (special)
37    Param,
38}
39
40impl PortType {
41    /// Get the name of the port type
42    pub const fn name(&self) -> &'static str {
43        match self {
44            Self::Audio => "audio",
45            Self::Control => "control",
46            Self::Clock => "clock",
47            Self::Feedback => "feedback",
48            Self::Param => "param",
49        }
50    }
51
52    /// Check if this port carries audio-rate signals
53    pub const fn is_audio_rate(&self) -> bool {
54        matches!(self, Self::Audio)
55    }
56
57    /// Check if this port carries control-rate signals
58    pub const fn is_control_rate(&self) -> bool {
59        matches!(self, Self::Control)
60    }
61
62    /// Check if this port carries clock signals
63    pub const fn is_clock(&self) -> bool {
64        matches!(self, Self::Clock)
65    }
66}
67
68impl fmt::Display for PortType {
69    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70        write!(f, "{}", self.name())
71    }
72}
73
74// ============================================================================
75// Port Direction
76// ============================================================================
77
78/// Direction of a port (input or output)
79#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
80pub enum PortDirection {
81    /// Input port (receives data into the node)
82    Input,
83
84    /// Output port (sends data out of the node)
85    Output,
86}
87
88impl PortDirection {
89    /// Get the name of the direction
90    pub const fn name(&self) -> &'static str {
91        match self {
92            Self::Input => "input",
93            Self::Output => "output",
94        }
95    }
96
97    /// Check if this is an input port
98    pub const fn is_input(&self) -> bool {
99        matches!(self, Self::Input)
100    }
101
102    /// Check if this is an output port
103    pub const fn is_output(&self) -> bool {
104        matches!(self, Self::Output)
105    }
106}
107
108impl fmt::Display for PortDirection {
109    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
110        write!(f, "{}", self.name())
111    }
112}
113
114// ============================================================================
115// Port ID
116// ============================================================================
117
118/// Unique identifier for a port within a graph
119#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
120pub struct PortId {
121    node: NodeId,
122    port_type: PortType,
123    direction: PortDirection,
124    index: u16,
125}
126
127impl PortId {
128    /// Create a new port ID
129    pub const fn new(
130        node: NodeId,
131        port_type: PortType,
132        direction: PortDirection,
133        index: u16,
134    ) -> Self {
135        Self {
136            node,
137            port_type,
138            direction,
139            index,
140        }
141    }
142
143    // ========================================================================
144    // Audio Port Constructors
145    // ========================================================================
146
147    /// Create a new audio input port
148    pub const fn audio_in(node: NodeId, index: u16) -> Self {
149        Self::new(node, PortType::Audio, PortDirection::Input, index)
150    }
151
152    /// Create a new audio output port
153    pub const fn audio_out(node: NodeId, index: u16) -> Self {
154        Self::new(node, PortType::Audio, PortDirection::Output, index)
155    }
156
157    // ========================================================================
158    // Control Port Constructors
159    // ========================================================================
160
161    /// Create a new control input port
162    pub const fn control_in(node: NodeId, index: u16) -> Self {
163        Self::new(node, PortType::Control, PortDirection::Input, index)
164    }
165
166    /// Create a new control output port
167    pub const fn control_out(node: NodeId, index: u16) -> Self {
168        Self::new(node, PortType::Control, PortDirection::Output, index)
169    }
170
171    // ========================================================================
172    // Clock Port Constructors
173    // ========================================================================
174
175    /// Create a new clock input port
176    pub const fn clock_in(node: NodeId, index: u16) -> Self {
177        Self::new(node, PortType::Clock, PortDirection::Input, index)
178    }
179
180    /// Create a new clock output port
181    pub const fn clock_out(node: NodeId, index: u16) -> Self {
182        Self::new(node, PortType::Clock, PortDirection::Output, index)
183    }
184
185    // ========================================================================
186    // Feedback Port Constructors
187    // ========================================================================
188
189    /// Create a new feedback input port
190    pub const fn feedback_in(node: NodeId, index: u16) -> Self {
191        Self::new(node, PortType::Feedback, PortDirection::Input, index)
192    }
193
194    /// Create a new feedback output port
195    pub const fn feedback_out(node: NodeId, index: u16) -> Self {
196        Self::new(node, PortType::Feedback, PortDirection::Output, index)
197    }
198
199    // ========================================================================
200    // Parameter Port Constructors
201    // ========================================================================
202
203    /// Create a new parameter port (always input)
204    pub const fn param(node: NodeId, index: u16) -> Self {
205        Self::new(node, PortType::Param, PortDirection::Input, index)
206    }
207
208    // ========================================================================
209    // Getters
210    // ========================================================================
211
212    /// Get the node ID
213    pub const fn node_id(&self) -> NodeId {
214        self.node
215    }
216
217    /// Get the port type
218    pub const fn port_type(&self) -> PortType {
219        self.port_type
220    }
221
222    /// Get the port direction
223    pub const fn direction(&self) -> PortDirection {
224        self.direction
225    }
226
227    /// Get the port index
228    pub const fn index(&self) -> u16 {
229        self.index
230    }
231
232    // ========================================================================
233    // Predicates
234    // ========================================================================
235
236    /// Check if this is an input port
237    pub const fn is_input(&self) -> bool {
238        self.direction.is_input()
239    }
240
241    /// Check if this is an output port
242    pub const fn is_output(&self) -> bool {
243        self.direction.is_output()
244    }
245
246    /// Check if this is an audio port
247    pub const fn is_audio(&self) -> bool {
248        matches!(self.port_type, PortType::Audio)
249    }
250
251    /// Check if this is a control port
252    pub const fn is_control(&self) -> bool {
253        matches!(self.port_type, PortType::Control)
254    }
255
256    /// Check if this is a clock port
257    pub const fn is_clock(&self) -> bool {
258        matches!(self.port_type, PortType::Clock)
259    }
260
261    /// Check if this is a feedback port
262    pub const fn is_feedback(&self) -> bool {
263        matches!(self.port_type, PortType::Feedback)
264    }
265
266    /// Check if this is a parameter port
267    pub const fn is_param(&self) -> bool {
268        matches!(self.port_type, PortType::Param)
269    }
270}
271
272impl fmt::Display for PortId {
273    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
274        write!(
275            f,
276            "Node({}).{}_{}[{}]",
277            self.node.inner(),
278            self.port_type.name(),
279            self.direction.name(),
280            self.index
281        )
282    }
283}
284
285// ============================================================================
286// Port Structure
287// ============================================================================
288
289/// A port on a node.
290///
291/// Each port has an owned `Buffer<T, BUF_SIZE>` for its data and an optional
292/// `Action` that defines per-port processing. Output ports typically have
293/// an action; input ports may have one for preprocessing.
294///
295/// Ports can optionally participate in feedback edges:
296/// - On an output port in a feedback edge, `feedback_buffer` stores the
297///   previous block's output, snapshotted after DSP via `snapshot_feedback()`.
298/// - On an input port in a feedback edge, `feedback_buffer` holds the delayed
299///   feedback value that gets mixed into `buffer` by `pre_process()`.
300/// - `downstream` lists audio connections from this output port to input ports
301///   of other nodes, populated at build time by the graph builder.
302pub struct Port<T: Transcendental, const BUF_SIZE: usize> {
303    /// Port identifier
304    pub id: PortId,
305    /// Port name
306    pub name: String,
307    /// Port direction (input/output)
308    pub direction: PortDirection,
309    /// Per-port processing algorithm (None for simple input ports)
310    pub action: Option<Box<dyn Algorithm<T>>>,
311    /// Pending command value from the control path, delivered to the
312    /// algorithm via `Algorithm::apply_command()` before the next
313    /// `process()` call. When there is no algorithm, this value is
314    /// written directly into the buffer.
315    pub pending_command: Option<T>,
316    /// Owned audio buffer
317    pub buffer: Buffer<T, BUF_SIZE>,
318    /// Delayed feedback state (None if not on a feedback edge).
319    /// On output ports: snapshotted from buffer after DSP for next block.
320    /// On input ports: receives the delayed feedback from the source output,
321    /// mixed into buffer by pre_process().
322    pub feedback_buffer: Option<Buffer<T, BUF_SIZE>>,
323    /// Downstream audio connections: (target_node_index, target_port_index).
324    /// Set at build time by GraphBuilder; immutable during processing.
325    pub downstream: Vec<(usize, usize)>,
326    /// Feedback edge targets from this output port: (target_node_index, target_port_index).
327    /// Populated at build time by GraphBuilder; the delayed feedback buffer is
328    /// written to these input ports by a processing driver.
329    pub feedback_downstream: Vec<(usize, usize)>,
330}
331
332impl<T: Transcendental, const BUF_SIZE: usize> fmt::Debug for Port<T, BUF_SIZE> {
333    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
334        f.debug_struct("Port")
335            .field("id", &self.id)
336            .field("name", &self.name)
337            .field("direction", &self.direction)
338            .field("has_action", &self.action.is_some())
339            .field("has_feedback", &self.feedback_buffer.is_some())
340            .field("downstream_len", &self.downstream.len())
341            .finish()
342    }
343}
344
345impl<T: Transcendental, const BUF_SIZE: usize> Port<T, BUF_SIZE> {
346    /// Create a new audio output port
347    pub fn output(node_id: NodeId, index: u16, name: &str) -> Self {
348        Self {
349            id: PortId::audio_out(node_id, index),
350            name: name.to_string(),
351            direction: PortDirection::Output,
352            action: None,
353            pending_command: None,
354            buffer: Buffer::new(),
355            feedback_buffer: None,
356            downstream: Vec::new(),
357            feedback_downstream: Vec::new(),
358        }
359    }
360
361    /// Create a new audio output port with an algorithm
362    pub fn output_with_action(
363        node_id: NodeId,
364        index: u16,
365        name: &str,
366        action: Box<dyn Algorithm<T>>,
367    ) -> Self {
368        Self {
369            id: PortId::audio_out(node_id, index),
370            name: name.to_string(),
371            direction: PortDirection::Output,
372            action: Some(action),
373            pending_command: None,
374            buffer: Buffer::new(),
375            feedback_buffer: None,
376            downstream: Vec::new(),
377            feedback_downstream: Vec::new(),
378        }
379    }
380
381    /// Create a new audio input port
382    pub fn input(node_id: NodeId, index: u16, name: &str) -> Self {
383        Self {
384            id: PortId::audio_in(node_id, index),
385            name: name.to_string(),
386            direction: PortDirection::Input,
387            action: None,
388            pending_command: None,
389            buffer: Buffer::new(),
390            feedback_buffer: None,
391            downstream: Vec::new(),
392            feedback_downstream: Vec::new(),
393        }
394    }
395
396    /// Create a new control output port
397    pub fn control_output(node_id: NodeId, index: u16, name: &str) -> Self {
398        Self {
399            id: PortId::control_out(node_id, index),
400            name: name.to_string(),
401            direction: PortDirection::Output,
402            action: None,
403            pending_command: None,
404            buffer: Buffer::new(),
405            feedback_buffer: None,
406            downstream: Vec::new(),
407            feedback_downstream: Vec::new(),
408        }
409    }
410
411    /// Create a new control output port with an algorithm
412    pub fn control_output_with_action(
413        node_id: NodeId,
414        index: u16,
415        name: &str,
416        action: Box<dyn Algorithm<T>>,
417    ) -> Self {
418        Self {
419            id: PortId::control_out(node_id, index),
420            name: name.to_string(),
421            direction: PortDirection::Output,
422            action: Some(action),
423            pending_command: None,
424            buffer: Buffer::new(),
425            feedback_buffer: None,
426            downstream: Vec::new(),
427            feedback_downstream: Vec::new(),
428        }
429    }
430
431    /// Create a new control input port
432    pub fn control_input(node_id: NodeId, index: u16, name: &str) -> Self {
433        Self {
434            id: PortId::control_in(node_id, index),
435            name: name.to_string(),
436            direction: PortDirection::Input,
437            action: None,
438            pending_command: None,
439            buffer: Buffer::new(),
440            feedback_buffer: None,
441            downstream: Vec::new(),
442            feedback_downstream: Vec::new(),
443        }
444    }
445
446    /// Get the port ID
447    pub fn id(&self) -> PortId {
448        self.id
449    }
450
451    /// Get the port name
452    pub fn name(&self) -> &str {
453        &self.name
454    }
455
456    /// Check if port is an input
457    pub fn is_input(&self) -> bool {
458        self.direction.is_input()
459    }
460
461    /// Check if port is an output
462    pub fn is_output(&self) -> bool {
463        self.direction.is_output()
464    }
465
466    /// Get a reference to the buffer
467    pub fn buffer(&self) -> &Buffer<T, BUF_SIZE> {
468        &self.buffer
469    }
470
471    /// Get a mutable reference to the buffer
472    pub fn buffer_mut(&mut self) -> &mut Buffer<T, BUF_SIZE> {
473        &mut self.buffer
474    }
475
476    /// Pre-process this port before node DSP.
477    ///
478    /// For input ports on a feedback edge, mixes the delayed feedback
479    /// (from `feedback_buffer`) into the current `buffer`.
480    /// No-op when `feedback_buffer` is `None`.
481    ///
482    /// `tick` is the current clock tick, available for future
483    /// sample-accurate or time-varying port-level processing.
484    pub fn pre_process(&mut self, _tick: &ClockTick) {
485        if let Some(ref fb) = self.feedback_buffer {
486            let arr = self.buffer.as_mut_array();
487            let fb_arr = fb.as_array();
488            for i in 0..BUF_SIZE {
489                arr[i] = arr[i] + fb_arr[i];
490            }
491        }
492    }
493
494    /// Snapshot the buffer into `feedback_buffer` after node DSP.
495    ///
496    /// For output ports on a feedback edge, saves the current buffer
497    /// so it can be used as delayed feedback in the next block.
498    /// No-op when `feedback_buffer` is `None`.
499    pub fn snapshot_feedback(&mut self) {
500        if let Some(ref mut fb) = self.feedback_buffer {
501            fb.copy_from(self.buffer.as_array());
502        }
503    }
504
505    /// Propagate this port's buffer to all downstream input ports.
506    ///
507    /// Iterates over `downstream` and copies `buffer` into each target
508    /// input port's buffer. The caller must ensure no aliasing between
509    /// this port's node and any target node (guaranteed by DAG topology).
510    ///
511    /// `tick` is the current clock tick, available for future
512    /// sample-accurate or time-varying port-level propagation.
513    pub fn propagate(&self, _tick: &ClockTick, nodes: &mut [NodeVariant<T, BUF_SIZE>]) {
514        for &(target_node, target_port) in &self.downstream {
515            if let Some(p) = nodes[target_node].input_port_mut(target_port) {
516                p.buffer.copy_from(self.buffer.as_array());
517            }
518        }
519    }
520
521    /// Run the port's algorithm.
522    ///
523    /// Delivers any pending command via `Algorithm::apply_command()`, then
524    /// calls `Algorithm::process()` with the input and output slices.
525    /// When no algorithm is attached, the pending command value (if any)
526    /// is written directly into the buffer; otherwise input is passed
527    /// through or zero-filled.
528    pub fn run_action(
529        &mut self,
530        input: Option<&[T; BUF_SIZE]>,
531        ctx: &crate::traits::algorithm::ActionContext,
532    ) -> crate::traits::ProcessResult<()> {
533        match &mut self.action {
534            Some(action) => {
535                // Deliver any pending command to the algorithm
536                if let Some(cmd) = self.pending_command.take() {
537                    action.apply_command(cmd);
538                }
539                let input_slice = input.map(|arr| arr.as_slice());
540                action.process(input_slice, self.buffer.as_mut_slice(), ctx)
541            }
542            None => {
543                // No algorithm — use pending command value if set,
544                // otherwise pass through input or zero-fill.
545                if let Some(cmd) = self.pending_command.take() {
546                    self.buffer.fill(cmd);
547                } else if let Some(input_data) = input {
548                    self.buffer.copy_from(input_data);
549                } else {
550                    self.buffer.fill(T::ZERO);
551                }
552                Ok(())
553            }
554        }
555    }
556
557    /// Set a command value for this port.
558    ///
559    /// The value is stored as a pending command and delivered to the
560    /// algorithm (or written directly to the buffer) on the next
561    /// `run_action()` call.
562    pub fn set_value(&mut self, value: T) {
563        self.pending_command = Some(value);
564    }
565}
566
567// ============================================================================
568// Active Port Trait
569// ============================================================================
570
571/// Trait for ports that can actively pull/push data.
572pub trait ActivePort<T: Transcendental, const BUF_SIZE: usize> {
573    /// Pull data from the port (for input ports).
574    fn pull(&mut self) -> Option<[T; BUF_SIZE]>;
575
576    /// Push data into the port (for output ports).
577    fn push(&mut self, data: [T; BUF_SIZE]) -> Result<(), PortError>;
578
579    /// Check if the port is connected.
580    fn is_connected(&self) -> bool;
581
582    /// Called on each clock tick (optional).
583    fn on_tick(&mut self, _tick: &ClockTick) {}
584}
585
586impl<T: Transcendental, const BUF_SIZE: usize> ActivePort<T, BUF_SIZE> for Port<T, BUF_SIZE> {
587    #[inline]
588    fn pull(&mut self) -> Option<[T; BUF_SIZE]> {
589        if self.is_input() {
590            Some(*self.buffer.as_array())
591        } else {
592            None
593        }
594    }
595
596    #[inline]
597    fn push(&mut self, data: [T; BUF_SIZE]) -> Result<(), PortError> {
598        if self.is_output() {
599            self.buffer = Buffer::from_array(data);
600            Ok(())
601        } else {
602            Err(PortError::NotFound(self.id.to_string()))
603        }
604    }
605
606    #[inline]
607    fn is_connected(&self) -> bool {
608        self.action.is_some()
609    }
610
611    #[inline]
612    fn on_tick(&mut self, _tick: &ClockTick) {}
613}
614
615// ============================================================================
616// Tests
617// ============================================================================
618
619#[cfg(test)]
620mod tests {
621    use super::*;
622
623    #[test]
624    fn test_port_id_creation() {
625        let node = NodeId(42);
626
627        let audio_in = PortId::audio_in(node, 0);
628        assert_eq!(audio_in.port_type(), PortType::Audio);
629        assert!(audio_in.is_input());
630
631        let clock_out = PortId::clock_out(node, 0);
632        assert_eq!(clock_out.port_type(), PortType::Clock);
633        assert!(clock_out.is_output());
634
635        let feedback_in = PortId::feedback_in(node, 0);
636        assert_eq!(feedback_in.port_type(), PortType::Feedback);
637        assert!(feedback_in.is_input());
638    }
639}