use std::collections::{HashMap, VecDeque};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct NodeId(pub u32);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NodeKind {
Deck,
Effect,
Group,
Master,
Cue,
}
pub trait AudioNode: Send + Sync {
fn process(&mut self, inputs: &[&[f64]], output: &mut [f64], frames: usize);
fn node_id(&self) -> NodeId;
fn kind(&self) -> NodeKind;
fn channels(&self) -> usize { 2 }
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Edge {
pub from: NodeId,
pub to: NodeId,
}
pub struct AudioGraph {
nodes: HashMap<NodeId, Box<dyn AudioNode>>,
edges: Vec<Edge>,
eval_order: Vec<NodeId>,
buffers: HashMap<NodeId, Vec<f64>>,
max_frames: usize,
}
impl AudioGraph {
pub fn build(
nodes: Vec<Box<dyn AudioNode>>,
edges: Vec<Edge>,
max_frames: usize,
) -> Result<Self, GraphError> {
let node_map: HashMap<NodeId, Box<dyn AudioNode>> = nodes
.into_iter()
.map(|n| (n.node_id(), n))
.collect();
let eval_order = topological_sort(&node_map, &edges)?;
let mut buffers = HashMap::new();
for (id, node) in &node_map {
buffers.insert(*id, vec![0.0f64; max_frames * node.channels()]);
}
Ok(Self { nodes: node_map, edges, eval_order, buffers, max_frames })
}
pub fn process(&mut self, frames: usize) {
debug_assert!(frames <= self.max_frames);
for buf in self.buffers.values_mut() {
for s in buf.iter_mut() { *s = 0.0; }
}
for &node_id in &self.eval_order {
let input_ids: Vec<NodeId> = self.edges.iter()
.filter(|e| e.to == node_id)
.map(|e| e.from)
.collect();
let input_ptrs: Vec<*const [f64]> = input_ids.iter()
.filter_map(|id| self.buffers.get(id).map(|b| b.as_slice() as *const [f64]))
.collect();
let inputs: Vec<&[f64]> = unsafe {
input_ptrs.iter().map(|p| &**p).collect()
};
let output_buf = self.buffers.get_mut(&node_id).unwrap();
let node = self.nodes.get_mut(&node_id).unwrap();
node.process(&inputs, output_buf, frames);
}
}
pub fn output(&self, node_id: NodeId) -> Option<&[f64]> {
self.buffers.get(&node_id).map(|b| b.as_slice())
}
pub fn eval_order(&self) -> &[NodeId] {
&self.eval_order
}
pub fn node_count(&self) -> usize {
self.nodes.len()
}
pub fn edge_count(&self) -> usize {
self.edges.len()
}
pub fn node(&self, id: NodeId) -> Option<&dyn AudioNode> {
self.nodes.get(&id).map(|n| n.as_ref())
}
}
#[derive(Debug, Clone)]
pub enum GraphError {
Cycle(Vec<NodeId>),
MissingNode(NodeId),
}
impl std::fmt::Display for GraphError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
GraphError::Cycle(ids) => write!(f, "Graph cycle detected involving {:?}", ids),
GraphError::MissingNode(id) => write!(f, "Edge references missing node {:?}", id),
}
}
}
impl std::error::Error for GraphError {}
fn topological_sort(
nodes: &HashMap<NodeId, Box<dyn AudioNode>>,
edges: &[Edge],
) -> Result<Vec<NodeId>, GraphError> {
for edge in edges {
if !nodes.contains_key(&edge.from) {
return Err(GraphError::MissingNode(edge.from));
}
if !nodes.contains_key(&edge.to) {
return Err(GraphError::MissingNode(edge.to));
}
}
let mut in_degree: HashMap<NodeId, usize> = nodes.keys().map(|&id| (id, 0)).collect();
let mut adjacency: HashMap<NodeId, Vec<NodeId>> = nodes.keys().map(|&id| (id, Vec::new())).collect();
for edge in edges {
*in_degree.entry(edge.to).or_insert(0) += 1;
adjacency.entry(edge.from).or_default().push(edge.to);
}
let mut queue: VecDeque<NodeId> = in_degree.iter()
.filter(|(_, °)| deg == 0)
.map(|(&id, _)| id)
.collect();
let mut sorted = Vec::with_capacity(nodes.len());
while let Some(node_id) = queue.pop_front() {
sorted.push(node_id);
if let Some(neighbors) = adjacency.get(&node_id) {
for &neighbor in neighbors {
if let Some(deg) = in_degree.get_mut(&neighbor) {
*deg -= 1;
if *deg == 0 {
queue.push_back(neighbor);
}
}
}
}
}
if sorted.len() != nodes.len() {
let remaining: Vec<NodeId> = nodes.keys()
.filter(|id| !sorted.contains(id))
.copied()
.collect();
return Err(GraphError::Cycle(remaining));
}
Ok(sorted)
}
#[cfg(test)]
mod tests {
use super::*;
struct PassthroughNode {
id: NodeId,
kind: NodeKind,
}
impl AudioNode for PassthroughNode {
fn process(&mut self, inputs: &[&[f64]], output: &mut [f64], frames: usize) {
let len = frames * self.channels();
for input in inputs {
for i in 0..len.min(input.len()).min(output.len()) {
output[i] += input[i];
}
}
}
fn node_id(&self) -> NodeId { self.id }
fn kind(&self) -> NodeKind { self.kind }
}
struct ConstNode {
id: NodeId,
value: f64,
}
impl AudioNode for ConstNode {
fn process(&mut self, _inputs: &[&[f64]], output: &mut [f64], frames: usize) {
let len = frames * self.channels();
for i in 0..len.min(output.len()) {
output[i] = self.value;
}
}
fn node_id(&self) -> NodeId { self.id }
fn kind(&self) -> NodeKind { NodeKind::Deck }
}
fn make_node(id: u32, kind: NodeKind) -> Box<dyn AudioNode> {
Box::new(PassthroughNode { id: NodeId(id), kind })
}
fn make_const(id: u32, value: f64) -> Box<dyn AudioNode> {
Box::new(ConstNode { id: NodeId(id), value })
}
#[test]
fn empty_graph() {
let g = AudioGraph::build(vec![], vec![], 64).unwrap();
assert_eq!(g.node_count(), 0);
assert_eq!(g.edge_count(), 0);
}
#[test]
fn single_node() {
let nodes: Vec<Box<dyn AudioNode>> = vec![make_const(1, 0.5)];
let mut g = AudioGraph::build(nodes, vec![], 64).unwrap();
g.process(64);
let out = g.output(NodeId(1)).unwrap();
assert!((out[0] - 0.5).abs() < 1e-10);
}
#[test]
fn linear_chain() {
let nodes: Vec<Box<dyn AudioNode>> = vec![
make_const(1, 1.0),
make_node(2, NodeKind::Effect),
make_node(3, NodeKind::Master),
];
let edges = vec![
Edge { from: NodeId(1), to: NodeId(2) },
Edge { from: NodeId(2), to: NodeId(3) },
];
let mut g = AudioGraph::build(nodes, edges, 64).unwrap();
assert_eq!(g.eval_order().len(), 3);
let order = g.eval_order();
let pos = |id: u32| order.iter().position(|n| n.0 == id).unwrap();
assert!(pos(1) < pos(2));
assert!(pos(2) < pos(3));
g.process(64);
let out = g.output(NodeId(3)).unwrap();
assert!((out[0] - 1.0).abs() < 1e-10);
}
#[test]
fn diamond_graph() {
let nodes: Vec<Box<dyn AudioNode>> = vec![
make_const(1, 0.5),
make_node(2, NodeKind::Effect),
make_node(3, NodeKind::Effect),
make_node(4, NodeKind::Master),
];
let edges = vec![
Edge { from: NodeId(1), to: NodeId(2) },
Edge { from: NodeId(1), to: NodeId(3) },
Edge { from: NodeId(2), to: NodeId(4) },
Edge { from: NodeId(3), to: NodeId(4) },
];
let mut g = AudioGraph::build(nodes, edges, 64).unwrap();
g.process(64);
let out = g.output(NodeId(4)).unwrap();
assert!((out[0] - 1.0).abs() < 1e-10);
}
#[test]
fn four_decks_to_master() {
let nodes: Vec<Box<dyn AudioNode>> = vec![
make_const(1, 0.25), make_const(2, 0.25), make_const(3, 0.25), make_const(4, 0.25), make_node(5, NodeKind::Group), make_node(6, NodeKind::Group), make_node(7, NodeKind::Master), ];
let edges = vec![
Edge { from: NodeId(1), to: NodeId(5) }, Edge { from: NodeId(3), to: NodeId(5) }, Edge { from: NodeId(2), to: NodeId(6) }, Edge { from: NodeId(4), to: NodeId(6) }, Edge { from: NodeId(5), to: NodeId(7) }, Edge { from: NodeId(6), to: NodeId(7) }, ];
let mut g = AudioGraph::build(nodes, edges, 64).unwrap();
g.process(64);
let out = g.output(NodeId(7)).unwrap();
assert!((out[0] - 1.0).abs() < 1e-10);
}
#[test]
fn cycle_detected() {
let nodes: Vec<Box<dyn AudioNode>> = vec![
make_node(1, NodeKind::Effect),
make_node(2, NodeKind::Effect),
];
let edges = vec![
Edge { from: NodeId(1), to: NodeId(2) },
Edge { from: NodeId(2), to: NodeId(1) },
];
let result = AudioGraph::build(nodes, edges, 64);
assert!(result.is_err());
if let Err(GraphError::Cycle(ids)) = result {
assert_eq!(ids.len(), 2);
}
}
#[test]
fn missing_node_in_edge() {
let nodes: Vec<Box<dyn AudioNode>> = vec![make_node(1, NodeKind::Deck)];
let edges = vec![Edge { from: NodeId(1), to: NodeId(99) }];
let result = AudioGraph::build(nodes, edges, 64);
assert!(matches!(result, Err(GraphError::MissingNode(NodeId(99)))));
}
#[test]
fn eval_order_respects_topology() {
let nodes: Vec<Box<dyn AudioNode>> = vec![
make_node(1, NodeKind::Deck),
make_node(2, NodeKind::Deck),
make_node(3, NodeKind::Group),
make_node(4, NodeKind::Master),
];
let edges = vec![
Edge { from: NodeId(1), to: NodeId(3) },
Edge { from: NodeId(2), to: NodeId(3) },
Edge { from: NodeId(3), to: NodeId(4) },
];
let g = AudioGraph::build(nodes, edges, 64).unwrap();
let order = g.eval_order();
let pos = |id: u32| order.iter().position(|n| n.0 == id).unwrap();
assert!(pos(1) < pos(3));
assert!(pos(2) < pos(3));
assert!(pos(3) < pos(4));
}
}