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 signal 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::{SignalNode, 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    /// Signal port - carries signal blocks (audio, sensor, etc.)
25    Signal,
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::Signal => "signal",
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::Signal)
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 signal input port
148    pub const fn audio_in(node: NodeId, index: u16) -> Self {
149        Self::new(node, PortType::Signal, PortDirection::Input, index)
150    }
151
152    /// Create a new signal output port
153    pub const fn audio_out(node: NodeId, index: u16) -> Self {
154        Self::new(node, PortType::Signal, 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::Signal)
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.
302/// - `upstream_buffer` on input ports: direct pointer to the upstream output
303///   port's buffer for zero-copy routing. `None` for fan-in/feedback ports.
304///
305/// # Safety
306/// `upstream_buffer` is safe because the graph topology is immutable and
307/// processing is strictly single-threaded in topological order. The
308/// upstream output buffer is guaranteed to outlive the downstream input
309/// port that references it.
310pub struct Port<T: Transcendental, const BUF_SIZE: usize> {
311    /// Port identifier
312    pub id: PortId,
313    /// Port name
314    pub name: String,
315    /// Port direction (input/output)
316    pub direction: PortDirection,
317    /// Per-port processing algorithm (None for simple input ports)
318    pub action: Option<Box<dyn Algorithm<T>>>,
319    /// Pending command value from the control path
320    pub pending_command: Option<T>,
321    /// Owned audio buffer (for output ports and input ports without upstream)
322    pub buffer: Buffer<T, BUF_SIZE>,
323    /// Delayed feedback state (None if not on a feedback edge)
324    pub feedback_buffer: Option<Buffer<T, BUF_SIZE>>,
325    /// Downstream audio connections: (target_node_index, target_port_index)
326    pub downstream: Vec<(usize, usize)>,
327    /// Direct pointer to upstream output buffer for zero-copy routing.
328    /// `Some` for input ports with exactly one upstream (1:1 connection).
329    /// `None` for output ports, fan-in, feedback, or unconnected input ports.
330    ///
331    /// # Safety
332    /// Valid for the engine's lifetime: the graph topology is static and
333    /// processing is single-threaded in topological order.
334    pub upstream_buffer: Option<*const Buffer<T, BUF_SIZE>>,
335    /// Feedback edge targets from this output port
336    pub feedback_downstream: Vec<(usize, usize)>,
337}
338
339impl<T: Transcendental, const BUF_SIZE: usize> fmt::Debug for Port<T, BUF_SIZE> {
340    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
341        f.debug_struct("Port")
342            .field("id", &self.id)
343            .field("name", &self.name)
344            .field("direction", &self.direction)
345            .field("has_action", &self.action.is_some())
346            .field("has_feedback", &self.feedback_buffer.is_some())
347            .field("downstream_len", &self.downstream.len())
348            .finish()
349    }
350}
351
352impl<T: Transcendental, const BUF_SIZE: usize> Port<T, BUF_SIZE> {
353    /// Create a new signal output port
354    pub fn output(node_id: NodeId, index: u16, name: &str) -> Self {
355        Self {
356            id: PortId::audio_out(node_id, index),
357            name: name.to_string(),
358            direction: PortDirection::Output,
359            action: None,
360            pending_command: None,
361            buffer: Buffer::new(),
362            feedback_buffer: None,
363            downstream: Vec::new(),
364            feedback_downstream: Vec::new(),
365            upstream_buffer: None,
366        }
367    }
368
369    /// Create a new signal output port with an algorithm
370    pub fn output_with_action(
371        node_id: NodeId,
372        index: u16,
373        name: &str,
374        action: Box<dyn Algorithm<T>>,
375    ) -> Self {
376        Self {
377            id: PortId::audio_out(node_id, index),
378            name: name.to_string(),
379            direction: PortDirection::Output,
380            action: Some(action),
381            pending_command: None,
382            buffer: Buffer::new(),
383            feedback_buffer: None,
384            downstream: Vec::new(),
385            feedback_downstream: Vec::new(),
386            upstream_buffer: None,
387        }
388    }
389
390    /// Create a new signal input port
391    pub fn input(node_id: NodeId, index: u16, name: &str) -> Self {
392        Self {
393            id: PortId::audio_in(node_id, index),
394            name: name.to_string(),
395            direction: PortDirection::Input,
396            action: None,
397            pending_command: None,
398            buffer: Buffer::new(),
399            feedback_buffer: None,
400            downstream: Vec::new(),
401            feedback_downstream: Vec::new(),
402            upstream_buffer: None,
403        }
404    }
405
406    /// Create a new control output port
407    pub fn control_output(node_id: NodeId, index: u16, name: &str) -> Self {
408        Self {
409            id: PortId::control_out(node_id, index),
410            name: name.to_string(),
411            direction: PortDirection::Output,
412            action: None,
413            pending_command: None,
414            buffer: Buffer::new(),
415            feedback_buffer: None,
416            downstream: Vec::new(),
417            feedback_downstream: Vec::new(),
418            upstream_buffer: None,
419        }
420    }
421
422    /// Create a new control output port with an algorithm
423    pub fn control_output_with_action(
424        node_id: NodeId,
425        index: u16,
426        name: &str,
427        action: Box<dyn Algorithm<T>>,
428    ) -> Self {
429        Self {
430            id: PortId::control_out(node_id, index),
431            name: name.to_string(),
432            direction: PortDirection::Output,
433            action: Some(action),
434            pending_command: None,
435            buffer: Buffer::new(),
436            feedback_buffer: None,
437            downstream: Vec::new(),
438            feedback_downstream: Vec::new(),
439            upstream_buffer: None,
440        }
441    }
442
443    /// Create a new control input port
444    pub fn control_input(node_id: NodeId, index: u16, name: &str) -> Self {
445        Self {
446            id: PortId::control_in(node_id, index),
447            name: name.to_string(),
448            direction: PortDirection::Input,
449            action: None,
450            pending_command: None,
451            buffer: Buffer::new(),
452            feedback_buffer: None,
453            downstream: Vec::new(),
454            feedback_downstream: Vec::new(),
455            upstream_buffer: None,
456        }
457    }
458
459    /// Get the port ID
460    pub fn id(&self) -> PortId {
461        self.id
462    }
463
464    /// Get the port name
465    pub fn name(&self) -> &str {
466        &self.name
467    }
468
469    /// Check if port is an input
470    pub fn is_input(&self) -> bool {
471        self.direction.is_input()
472    }
473
474    /// Check if port is an output
475    pub fn is_output(&self) -> bool {
476        self.direction.is_output()
477    }
478
479    /// Get a reference to the buffer
480    pub fn buffer(&self) -> &Buffer<T, BUF_SIZE> {
481        &self.buffer
482    }
483
484    /// Get a mutable reference to the buffer
485    pub fn buffer_mut(&mut self) -> &mut Buffer<T, BUF_SIZE> {
486        &mut self.buffer
487    }
488
489    /// Get the effective audio buffer for an input port.
490    ///
491    /// Returns a reference to the upstream output buffer when this port has
492    /// a direct 1:1 connection (zero-copy), or the port's own buffer for
493    /// fan-in/feedback/unconnected ports (copy-based).
494    ///
495    /// # Safety
496    /// The upstream pointer is valid because the graph topology is static
497    /// and processing is single-threaded in topological order. The upstream
498    /// output buffer is owned by another port in the same graph and lives
499    /// for the entire processing session.
500    pub fn audio_buffer(&self) -> &Buffer<T, BUF_SIZE> {
501        match self.upstream_buffer {
502            Some(ptr) => {
503                #[allow(unsafe_code)]
504                unsafe {
505                    &*ptr
506                }
507            }
508            None => &self.buffer,
509        }
510    }
511
512    /// Directly dereference an upstream buffer pointer to a `&Buffer`.
513    /// Used by the engine to avoid borrowing `self` (and thus the node)
514    /// when building audio input references for zero-copy processing.
515    ///
516    /// # Safety
517    /// `ptr` must be a valid, aligned pointer to a `Buffer<T, BUF_SIZE>`
518    /// that lives for the entire processing session.
519    #[allow(unsafe_code)]
520    pub unsafe fn upstream_ref(ptr: *const Buffer<T, BUF_SIZE>) -> &'static Buffer<T, BUF_SIZE> {
521        &*ptr
522    }
523
524    /// Pre-process this port before node DSP.
525    ///
526    /// For input ports on a feedback edge, mixes the delayed feedback
527    /// (from `feedback_buffer`) into the current `buffer`.
528    /// No-op when `feedback_buffer` is `None`.
529    ///
530    /// `tick` is the current clock tick, available for future
531    /// sample-accurate or time-varying port-level processing.
532    pub fn pre_process(&mut self, _tick: &ClockTick) {
533        if let Some(ref fb) = self.feedback_buffer {
534            let arr = self.buffer.as_mut_array();
535            let fb_arr = fb.as_array();
536            for i in 0..BUF_SIZE {
537                arr[i] = arr[i] + fb_arr[i];
538            }
539        }
540    }
541
542    /// Snapshot the buffer into `feedback_buffer` after node DSP.
543    ///
544    /// For output ports on a feedback edge, saves the current buffer
545    /// so it can be used as delayed feedback in the next block.
546    /// No-op when `feedback_buffer` is `None`.
547    pub fn snapshot_feedback(&mut self) {
548        if let Some(ref mut fb) = self.feedback_buffer {
549            fb.copy_from(self.buffer.as_array());
550        }
551    }
552
553    /// Propagate this port's buffer to all downstream input ports.
554    ///
555    /// Iterates over `downstream` and copies `buffer` into each target
556    /// input port's buffer. The caller must ensure no aliasing between
557    /// this port's node and any target node (guaranteed by DAG topology).
558    ///
559    /// `tick` is the current clock tick, available for future
560    /// sample-accurate or time-varying port-level propagation.
561    pub fn propagate(&self, _tick: &ClockTick, nodes: &mut [NodeVariant<T, BUF_SIZE>]) {
562        for &(target_node, target_port) in &self.downstream {
563            if let Some(p) = nodes[target_node].input_port_mut(target_port) {
564                p.buffer.copy_from(self.buffer.as_array());
565            }
566        }
567    }
568
569    /// Run the port's algorithm.
570    ///
571    /// Delivers any pending command via `Algorithm::apply_command()`, then
572    /// calls `Algorithm::process()` with the input and output slices.
573    /// When no algorithm is attached, the pending command value (if any)
574    /// is written directly into the buffer; otherwise input is passed
575    /// through or zero-filled.
576    pub fn run_action(
577        &mut self,
578        input: Option<&[T; BUF_SIZE]>,
579        ctx: &crate::traits::algorithm::ActionContext,
580    ) -> crate::traits::ProcessResult<()> {
581        match &mut self.action {
582            Some(action) => {
583                // Deliver any pending command to the algorithm
584                if let Some(cmd) = self.pending_command.take() {
585                    action.apply_command(cmd);
586                }
587                let input_slice = input.map(|arr| arr.as_slice());
588                action.process(input_slice, self.buffer.as_mut_slice(), ctx)
589            }
590            None => {
591                // No algorithm — use pending command value if set,
592                // otherwise pass through input or zero-fill.
593                if let Some(cmd) = self.pending_command.take() {
594                    self.buffer.fill(cmd);
595                } else if let Some(input_data) = input {
596                    self.buffer.copy_from(input_data);
597                } else {
598                    self.buffer.fill(T::ZERO);
599                }
600                Ok(())
601            }
602        }
603    }
604
605    /// Set a command value for this port.
606    ///
607    /// The value is stored as a pending command and delivered to the
608    /// algorithm (or written directly to the buffer) on the next
609    /// `run_action()` call.
610    pub fn set_value(&mut self, value: T) {
611        self.pending_command = Some(value);
612    }
613}
614
615// ============================================================================
616// Active Port Trait
617// ============================================================================
618
619/// Trait for ports that can actively pull/push data.
620pub trait ActivePort<T: Transcendental, const BUF_SIZE: usize> {
621    /// Pull data from the port (for input ports).
622    fn pull(&mut self) -> Option<[T; BUF_SIZE]>;
623
624    /// Push data into the port (for output ports).
625    fn push(&mut self, data: [T; BUF_SIZE]) -> Result<(), PortError>;
626
627    /// Check if the port is connected.
628    fn is_connected(&self) -> bool;
629
630    /// Called on each clock tick (optional).
631    fn on_tick(&mut self, _tick: &ClockTick) {}
632}
633
634impl<T: Transcendental, const BUF_SIZE: usize> ActivePort<T, BUF_SIZE> for Port<T, BUF_SIZE> {
635    #[inline]
636    fn pull(&mut self) -> Option<[T; BUF_SIZE]> {
637        if self.is_input() {
638            Some(*self.buffer.as_array())
639        } else {
640            None
641        }
642    }
643
644    #[inline]
645    fn push(&mut self, data: [T; BUF_SIZE]) -> Result<(), PortError> {
646        if self.is_output() {
647            self.buffer = Buffer::from_array(data);
648            Ok(())
649        } else {
650            Err(PortError::NotFound(self.id.to_string()))
651        }
652    }
653
654    #[inline]
655    fn is_connected(&self) -> bool {
656        self.action.is_some()
657    }
658
659    #[inline]
660    fn on_tick(&mut self, _tick: &ClockTick) {}
661}
662
663// SAFETY: `upstream_buffer` is a raw pointer to a buffer owned by another
664// Port in the same static graph. The graph is immutable during processing
665// and runs single-threaded in topological order. The pointer target
666// outlives the pointer for the entire processing session.
667#[allow(unsafe_code)]
668unsafe impl<T: Transcendental + Send, const BUF_SIZE: usize> Send for Port<T, BUF_SIZE> {}
669#[allow(unsafe_code)]
670unsafe impl<T: Transcendental + Sync, const BUF_SIZE: usize> Sync for Port<T, BUF_SIZE> {}
671
672// ============================================================================
673// Tests
674// ============================================================================
675
676#[cfg(test)]
677mod tests {
678    use super::*;
679
680    #[test]
681    fn test_port_id_creation() {
682        let node = NodeId(42);
683
684        let audio_in = PortId::audio_in(node, 0);
685        assert_eq!(audio_in.port_type(), PortType::Signal);
686        assert!(audio_in.is_input());
687
688        let clock_out = PortId::clock_out(node, 0);
689        assert_eq!(clock_out.port_type(), PortType::Clock);
690        assert!(clock_out.is_output());
691
692        let feedback_in = PortId::feedback_in(node, 0);
693        assert_eq!(feedback_in.port_type(), PortType::Feedback);
694        assert!(feedback_in.is_input());
695    }
696}