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}