use crate::buffer::Buffer;
use crate::math::Transcendental;
use crate::time::ClockTick;
use crate::traits::algorithm::Algorithm;
use crate::traits::node::{AudioNode, NodeId};
use crate::traits::processable::NodeVariant;
use crate::traits::PortError;
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum PortType {
Audio,
Control,
Clock,
Feedback,
Param,
}
impl PortType {
pub const fn name(&self) -> &'static str {
match self {
Self::Audio => "audio",
Self::Control => "control",
Self::Clock => "clock",
Self::Feedback => "feedback",
Self::Param => "param",
}
}
pub const fn is_audio_rate(&self) -> bool {
matches!(self, Self::Audio)
}
pub const fn is_control_rate(&self) -> bool {
matches!(self, Self::Control)
}
pub const fn is_clock(&self) -> bool {
matches!(self, Self::Clock)
}
}
impl fmt::Display for PortType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.name())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum PortDirection {
Input,
Output,
}
impl PortDirection {
pub const fn name(&self) -> &'static str {
match self {
Self::Input => "input",
Self::Output => "output",
}
}
pub const fn is_input(&self) -> bool {
matches!(self, Self::Input)
}
pub const fn is_output(&self) -> bool {
matches!(self, Self::Output)
}
}
impl fmt::Display for PortDirection {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.name())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct PortId {
node: NodeId,
port_type: PortType,
direction: PortDirection,
index: u16,
}
impl PortId {
pub const fn new(
node: NodeId,
port_type: PortType,
direction: PortDirection,
index: u16,
) -> Self {
Self {
node,
port_type,
direction,
index,
}
}
pub const fn audio_in(node: NodeId, index: u16) -> Self {
Self::new(node, PortType::Audio, PortDirection::Input, index)
}
pub const fn audio_out(node: NodeId, index: u16) -> Self {
Self::new(node, PortType::Audio, PortDirection::Output, index)
}
pub const fn control_in(node: NodeId, index: u16) -> Self {
Self::new(node, PortType::Control, PortDirection::Input, index)
}
pub const fn control_out(node: NodeId, index: u16) -> Self {
Self::new(node, PortType::Control, PortDirection::Output, index)
}
pub const fn clock_in(node: NodeId, index: u16) -> Self {
Self::new(node, PortType::Clock, PortDirection::Input, index)
}
pub const fn clock_out(node: NodeId, index: u16) -> Self {
Self::new(node, PortType::Clock, PortDirection::Output, index)
}
pub const fn feedback_in(node: NodeId, index: u16) -> Self {
Self::new(node, PortType::Feedback, PortDirection::Input, index)
}
pub const fn feedback_out(node: NodeId, index: u16) -> Self {
Self::new(node, PortType::Feedback, PortDirection::Output, index)
}
pub const fn param(node: NodeId, index: u16) -> Self {
Self::new(node, PortType::Param, PortDirection::Input, index)
}
pub const fn node_id(&self) -> NodeId {
self.node
}
pub const fn port_type(&self) -> PortType {
self.port_type
}
pub const fn direction(&self) -> PortDirection {
self.direction
}
pub const fn index(&self) -> u16 {
self.index
}
pub const fn is_input(&self) -> bool {
self.direction.is_input()
}
pub const fn is_output(&self) -> bool {
self.direction.is_output()
}
pub const fn is_audio(&self) -> bool {
matches!(self.port_type, PortType::Audio)
}
pub const fn is_control(&self) -> bool {
matches!(self.port_type, PortType::Control)
}
pub const fn is_clock(&self) -> bool {
matches!(self.port_type, PortType::Clock)
}
pub const fn is_feedback(&self) -> bool {
matches!(self.port_type, PortType::Feedback)
}
pub const fn is_param(&self) -> bool {
matches!(self.port_type, PortType::Param)
}
}
impl fmt::Display for PortId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Node({}).{}_{}[{}]",
self.node.inner(),
self.port_type.name(),
self.direction.name(),
self.index
)
}
}
pub struct Port<T: Transcendental, const BUF_SIZE: usize> {
pub id: PortId,
pub name: String,
pub direction: PortDirection,
pub action: Option<Box<dyn Algorithm<T>>>,
pub pending_command: Option<T>,
pub buffer: Buffer<T, BUF_SIZE>,
pub feedback_buffer: Option<Buffer<T, BUF_SIZE>>,
pub downstream: Vec<(usize, usize)>,
pub upstream_buffer: Option<*const Buffer<T, BUF_SIZE>>,
pub feedback_downstream: Vec<(usize, usize)>,
}
impl<T: Transcendental, const BUF_SIZE: usize> fmt::Debug for Port<T, BUF_SIZE> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Port")
.field("id", &self.id)
.field("name", &self.name)
.field("direction", &self.direction)
.field("has_action", &self.action.is_some())
.field("has_feedback", &self.feedback_buffer.is_some())
.field("downstream_len", &self.downstream.len())
.finish()
}
}
impl<T: Transcendental, const BUF_SIZE: usize> Port<T, BUF_SIZE> {
pub fn output(node_id: NodeId, index: u16, name: &str) -> Self {
Self {
id: PortId::audio_out(node_id, index),
name: name.to_string(),
direction: PortDirection::Output,
action: None,
pending_command: None,
buffer: Buffer::new(),
feedback_buffer: None,
downstream: Vec::new(),
feedback_downstream: Vec::new(),
upstream_buffer: None,
}
}
pub fn output_with_action(
node_id: NodeId,
index: u16,
name: &str,
action: Box<dyn Algorithm<T>>,
) -> Self {
Self {
id: PortId::audio_out(node_id, index),
name: name.to_string(),
direction: PortDirection::Output,
action: Some(action),
pending_command: None,
buffer: Buffer::new(),
feedback_buffer: None,
downstream: Vec::new(),
feedback_downstream: Vec::new(),
upstream_buffer: None,
}
}
pub fn input(node_id: NodeId, index: u16, name: &str) -> Self {
Self {
id: PortId::audio_in(node_id, index),
name: name.to_string(),
direction: PortDirection::Input,
action: None,
pending_command: None,
buffer: Buffer::new(),
feedback_buffer: None,
downstream: Vec::new(),
feedback_downstream: Vec::new(),
upstream_buffer: None,
}
}
pub fn control_output(node_id: NodeId, index: u16, name: &str) -> Self {
Self {
id: PortId::control_out(node_id, index),
name: name.to_string(),
direction: PortDirection::Output,
action: None,
pending_command: None,
buffer: Buffer::new(),
feedback_buffer: None,
downstream: Vec::new(),
feedback_downstream: Vec::new(),
upstream_buffer: None,
}
}
pub fn control_output_with_action(
node_id: NodeId,
index: u16,
name: &str,
action: Box<dyn Algorithm<T>>,
) -> Self {
Self {
id: PortId::control_out(node_id, index),
name: name.to_string(),
direction: PortDirection::Output,
action: Some(action),
pending_command: None,
buffer: Buffer::new(),
feedback_buffer: None,
downstream: Vec::new(),
feedback_downstream: Vec::new(),
upstream_buffer: None,
}
}
pub fn control_input(node_id: NodeId, index: u16, name: &str) -> Self {
Self {
id: PortId::control_in(node_id, index),
name: name.to_string(),
direction: PortDirection::Input,
action: None,
pending_command: None,
buffer: Buffer::new(),
feedback_buffer: None,
downstream: Vec::new(),
feedback_downstream: Vec::new(),
upstream_buffer: None,
}
}
pub fn id(&self) -> PortId {
self.id
}
pub fn name(&self) -> &str {
&self.name
}
pub fn is_input(&self) -> bool {
self.direction.is_input()
}
pub fn is_output(&self) -> bool {
self.direction.is_output()
}
pub fn buffer(&self) -> &Buffer<T, BUF_SIZE> {
&self.buffer
}
pub fn buffer_mut(&mut self) -> &mut Buffer<T, BUF_SIZE> {
&mut self.buffer
}
pub fn audio_buffer(&self) -> &Buffer<T, BUF_SIZE> {
match self.upstream_buffer {
Some(ptr) => {
#[allow(unsafe_code)]
unsafe {
&*ptr
}
}
None => &self.buffer,
}
}
#[allow(unsafe_code)]
pub unsafe fn upstream_ref(ptr: *const Buffer<T, BUF_SIZE>) -> &'static Buffer<T, BUF_SIZE> {
&*ptr
}
pub fn pre_process(&mut self, _tick: &ClockTick) {
if let Some(ref fb) = self.feedback_buffer {
let arr = self.buffer.as_mut_array();
let fb_arr = fb.as_array();
for i in 0..BUF_SIZE {
arr[i] = arr[i] + fb_arr[i];
}
}
}
pub fn snapshot_feedback(&mut self) {
if let Some(ref mut fb) = self.feedback_buffer {
fb.copy_from(self.buffer.as_array());
}
}
pub fn propagate(&self, _tick: &ClockTick, nodes: &mut [NodeVariant<T, BUF_SIZE>]) {
for &(target_node, target_port) in &self.downstream {
if let Some(p) = nodes[target_node].input_port_mut(target_port) {
p.buffer.copy_from(self.buffer.as_array());
}
}
}
pub fn run_action(
&mut self,
input: Option<&[T; BUF_SIZE]>,
ctx: &crate::traits::algorithm::ActionContext,
) -> crate::traits::ProcessResult<()> {
match &mut self.action {
Some(action) => {
if let Some(cmd) = self.pending_command.take() {
action.apply_command(cmd);
}
let input_slice = input.map(|arr| arr.as_slice());
action.process(input_slice, self.buffer.as_mut_slice(), ctx)
}
None => {
if let Some(cmd) = self.pending_command.take() {
self.buffer.fill(cmd);
} else if let Some(input_data) = input {
self.buffer.copy_from(input_data);
} else {
self.buffer.fill(T::ZERO);
}
Ok(())
}
}
}
pub fn set_value(&mut self, value: T) {
self.pending_command = Some(value);
}
}
pub trait ActivePort<T: Transcendental, const BUF_SIZE: usize> {
fn pull(&mut self) -> Option<[T; BUF_SIZE]>;
fn push(&mut self, data: [T; BUF_SIZE]) -> Result<(), PortError>;
fn is_connected(&self) -> bool;
fn on_tick(&mut self, _tick: &ClockTick) {}
}
impl<T: Transcendental, const BUF_SIZE: usize> ActivePort<T, BUF_SIZE> for Port<T, BUF_SIZE> {
#[inline]
fn pull(&mut self) -> Option<[T; BUF_SIZE]> {
if self.is_input() {
Some(*self.buffer.as_array())
} else {
None
}
}
#[inline]
fn push(&mut self, data: [T; BUF_SIZE]) -> Result<(), PortError> {
if self.is_output() {
self.buffer = Buffer::from_array(data);
Ok(())
} else {
Err(PortError::NotFound(self.id.to_string()))
}
}
#[inline]
fn is_connected(&self) -> bool {
self.action.is_some()
}
#[inline]
fn on_tick(&mut self, _tick: &ClockTick) {}
}
#[allow(unsafe_code)]
unsafe impl<T: Transcendental + Send, const BUF_SIZE: usize> Send for Port<T, BUF_SIZE> {}
#[allow(unsafe_code)]
unsafe impl<T: Transcendental + Sync, const BUF_SIZE: usize> Sync for Port<T, BUF_SIZE> {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_port_id_creation() {
let node = NodeId(42);
let audio_in = PortId::audio_in(node, 0);
assert_eq!(audio_in.port_type(), PortType::Audio);
assert!(audio_in.is_input());
let clock_out = PortId::clock_out(node, 0);
assert_eq!(clock_out.port_type(), PortType::Clock);
assert!(clock_out.is_output());
let feedback_in = PortId::feedback_in(node, 0);
assert_eq!(feedback_in.port_type(), PortType::Feedback);
assert!(feedback_in.is_input());
}
}