1use std::collections::{HashMap, VecDeque};
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
15pub struct NodeId(pub u32);
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum NodeKind {
20 Deck,
21 Effect,
22 Group,
23 Master,
24 Cue,
25}
26
27pub trait AudioNode: Send + Sync {
36 fn process(&mut self, inputs: &[&[f64]], output: &mut [f64], frames: usize);
40
41 fn node_id(&self) -> NodeId;
43
44 fn kind(&self) -> NodeKind;
46
47 fn channels(&self) -> usize { 2 }
49}
50
51#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
57pub struct Edge {
58 pub from: NodeId,
59 pub to: NodeId,
60}
61
62pub struct AudioGraph {
68 nodes: HashMap<NodeId, Box<dyn AudioNode>>,
70 edges: Vec<Edge>,
72 eval_order: Vec<NodeId>,
74 buffers: HashMap<NodeId, Vec<f64>>,
76 max_frames: usize,
78}
79
80impl AudioGraph {
81 pub fn build(
85 nodes: Vec<Box<dyn AudioNode>>,
86 edges: Vec<Edge>,
87 max_frames: usize,
88 ) -> Result<Self, GraphError> {
89 let node_map: HashMap<NodeId, Box<dyn AudioNode>> = nodes
90 .into_iter()
91 .map(|n| (n.node_id(), n))
92 .collect();
93
94 let eval_order = topological_sort(&node_map, &edges)?;
95
96 let mut buffers = HashMap::new();
98 for (id, node) in &node_map {
99 buffers.insert(*id, vec![0.0f64; max_frames * node.channels()]);
100 }
101
102 Ok(Self { nodes: node_map, edges, eval_order, buffers, max_frames })
103 }
104
105 pub fn process(&mut self, frames: usize) {
110 debug_assert!(frames <= self.max_frames);
111
112 for buf in self.buffers.values_mut() {
114 for s in buf.iter_mut() { *s = 0.0; }
115 }
116
117 for &node_id in &self.eval_order {
119 let input_ids: Vec<NodeId> = self.edges.iter()
121 .filter(|e| e.to == node_id)
122 .map(|e| e.from)
123 .collect();
124
125 let input_ptrs: Vec<*const [f64]> = input_ids.iter()
129 .filter_map(|id| self.buffers.get(id).map(|b| b.as_slice() as *const [f64]))
130 .collect();
131
132 let inputs: Vec<&[f64]> = unsafe {
133 input_ptrs.iter().map(|p| &**p).collect()
134 };
135
136 let output_buf = self.buffers.get_mut(&node_id).unwrap();
137 let node = self.nodes.get_mut(&node_id).unwrap();
138 node.process(&inputs, output_buf, frames);
139 }
140 }
141
142 pub fn output(&self, node_id: NodeId) -> Option<&[f64]> {
144 self.buffers.get(&node_id).map(|b| b.as_slice())
145 }
146
147 pub fn eval_order(&self) -> &[NodeId] {
149 &self.eval_order
150 }
151
152 pub fn node_count(&self) -> usize {
154 self.nodes.len()
155 }
156
157 pub fn edge_count(&self) -> usize {
159 self.edges.len()
160 }
161
162 pub fn node(&self, id: NodeId) -> Option<&dyn AudioNode> {
164 self.nodes.get(&id).map(|n| n.as_ref())
165 }
166}
167
168#[derive(Debug, Clone)]
174pub enum GraphError {
175 Cycle(Vec<NodeId>),
177 MissingNode(NodeId),
179}
180
181impl std::fmt::Display for GraphError {
182 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
183 match self {
184 GraphError::Cycle(ids) => write!(f, "Graph cycle detected involving {:?}", ids),
185 GraphError::MissingNode(id) => write!(f, "Edge references missing node {:?}", id),
186 }
187 }
188}
189
190impl std::error::Error for GraphError {}
191
192fn topological_sort(
197 nodes: &HashMap<NodeId, Box<dyn AudioNode>>,
198 edges: &[Edge],
199) -> Result<Vec<NodeId>, GraphError> {
200 for edge in edges {
202 if !nodes.contains_key(&edge.from) {
203 return Err(GraphError::MissingNode(edge.from));
204 }
205 if !nodes.contains_key(&edge.to) {
206 return Err(GraphError::MissingNode(edge.to));
207 }
208 }
209
210 let mut in_degree: HashMap<NodeId, usize> = nodes.keys().map(|&id| (id, 0)).collect();
212 let mut adjacency: HashMap<NodeId, Vec<NodeId>> = nodes.keys().map(|&id| (id, Vec::new())).collect();
213
214 for edge in edges {
215 *in_degree.entry(edge.to).or_insert(0) += 1;
216 adjacency.entry(edge.from).or_default().push(edge.to);
217 }
218
219 let mut queue: VecDeque<NodeId> = in_degree.iter()
221 .filter(|(_, °)| deg == 0)
222 .map(|(&id, _)| id)
223 .collect();
224
225 let mut sorted = Vec::with_capacity(nodes.len());
226
227 while let Some(node_id) = queue.pop_front() {
228 sorted.push(node_id);
229 if let Some(neighbors) = adjacency.get(&node_id) {
230 for &neighbor in neighbors {
231 if let Some(deg) = in_degree.get_mut(&neighbor) {
232 *deg -= 1;
233 if *deg == 0 {
234 queue.push_back(neighbor);
235 }
236 }
237 }
238 }
239 }
240
241 if sorted.len() != nodes.len() {
242 let remaining: Vec<NodeId> = nodes.keys()
244 .filter(|id| !sorted.contains(id))
245 .copied()
246 .collect();
247 return Err(GraphError::Cycle(remaining));
248 }
249
250 Ok(sorted)
251}
252
253#[cfg(test)]
258mod tests {
259 use super::*;
260
261 struct PassthroughNode {
263 id: NodeId,
264 kind: NodeKind,
265 }
266
267 impl AudioNode for PassthroughNode {
268 fn process(&mut self, inputs: &[&[f64]], output: &mut [f64], frames: usize) {
269 let len = frames * self.channels();
271 for input in inputs {
272 for i in 0..len.min(input.len()).min(output.len()) {
273 output[i] += input[i];
274 }
275 }
276 }
277 fn node_id(&self) -> NodeId { self.id }
278 fn kind(&self) -> NodeKind { self.kind }
279 }
280
281 struct ConstNode {
283 id: NodeId,
284 value: f64,
285 }
286
287 impl AudioNode for ConstNode {
288 fn process(&mut self, _inputs: &[&[f64]], output: &mut [f64], frames: usize) {
289 let len = frames * self.channels();
290 for i in 0..len.min(output.len()) {
291 output[i] = self.value;
292 }
293 }
294 fn node_id(&self) -> NodeId { self.id }
295 fn kind(&self) -> NodeKind { NodeKind::Deck }
296 }
297
298 fn make_node(id: u32, kind: NodeKind) -> Box<dyn AudioNode> {
299 Box::new(PassthroughNode { id: NodeId(id), kind })
300 }
301
302 fn make_const(id: u32, value: f64) -> Box<dyn AudioNode> {
303 Box::new(ConstNode { id: NodeId(id), value })
304 }
305
306 #[test]
307 fn empty_graph() {
308 let g = AudioGraph::build(vec![], vec![], 64).unwrap();
309 assert_eq!(g.node_count(), 0);
310 assert_eq!(g.edge_count(), 0);
311 }
312
313 #[test]
314 fn single_node() {
315 let nodes: Vec<Box<dyn AudioNode>> = vec![make_const(1, 0.5)];
316 let mut g = AudioGraph::build(nodes, vec![], 64).unwrap();
317 g.process(64);
318 let out = g.output(NodeId(1)).unwrap();
319 assert!((out[0] - 0.5).abs() < 1e-10);
320 }
321
322 #[test]
323 fn linear_chain() {
324 let nodes: Vec<Box<dyn AudioNode>> = vec![
326 make_const(1, 1.0),
327 make_node(2, NodeKind::Effect),
328 make_node(3, NodeKind::Master),
329 ];
330 let edges = vec![
331 Edge { from: NodeId(1), to: NodeId(2) },
332 Edge { from: NodeId(2), to: NodeId(3) },
333 ];
334 let mut g = AudioGraph::build(nodes, edges, 64).unwrap();
335 assert_eq!(g.eval_order().len(), 3);
336
337 let order = g.eval_order();
339 let pos = |id: u32| order.iter().position(|n| n.0 == id).unwrap();
340 assert!(pos(1) < pos(2));
341 assert!(pos(2) < pos(3));
342
343 g.process(64);
344 let out = g.output(NodeId(3)).unwrap();
345 assert!((out[0] - 1.0).abs() < 1e-10);
347 }
348
349 #[test]
350 fn diamond_graph() {
351 let nodes: Vec<Box<dyn AudioNode>> = vec![
354 make_const(1, 0.5),
355 make_node(2, NodeKind::Effect),
356 make_node(3, NodeKind::Effect),
357 make_node(4, NodeKind::Master),
358 ];
359 let edges = vec![
360 Edge { from: NodeId(1), to: NodeId(2) },
361 Edge { from: NodeId(1), to: NodeId(3) },
362 Edge { from: NodeId(2), to: NodeId(4) },
363 Edge { from: NodeId(3), to: NodeId(4) },
364 ];
365 let mut g = AudioGraph::build(nodes, edges, 64).unwrap();
366 g.process(64);
367 let out = g.output(NodeId(4)).unwrap();
368 assert!((out[0] - 1.0).abs() < 1e-10);
370 }
371
372 #[test]
373 fn four_decks_to_master() {
374 let nodes: Vec<Box<dyn AudioNode>> = vec![
376 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), ];
384 let edges = vec![
385 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) }, ];
392 let mut g = AudioGraph::build(nodes, edges, 64).unwrap();
393 g.process(64);
394 let out = g.output(NodeId(7)).unwrap();
395 assert!((out[0] - 1.0).abs() < 1e-10);
397 }
398
399 #[test]
400 fn cycle_detected() {
401 let nodes: Vec<Box<dyn AudioNode>> = vec![
403 make_node(1, NodeKind::Effect),
404 make_node(2, NodeKind::Effect),
405 ];
406 let edges = vec![
407 Edge { from: NodeId(1), to: NodeId(2) },
408 Edge { from: NodeId(2), to: NodeId(1) },
409 ];
410 let result = AudioGraph::build(nodes, edges, 64);
411 assert!(result.is_err());
412 if let Err(GraphError::Cycle(ids)) = result {
413 assert_eq!(ids.len(), 2);
414 }
415 }
416
417 #[test]
418 fn missing_node_in_edge() {
419 let nodes: Vec<Box<dyn AudioNode>> = vec![make_node(1, NodeKind::Deck)];
420 let edges = vec![Edge { from: NodeId(1), to: NodeId(99) }];
421 let result = AudioGraph::build(nodes, edges, 64);
422 assert!(matches!(result, Err(GraphError::MissingNode(NodeId(99)))));
423 }
424
425 #[test]
426 fn eval_order_respects_topology() {
427 let nodes: Vec<Box<dyn AudioNode>> = vec![
429 make_node(1, NodeKind::Deck),
430 make_node(2, NodeKind::Deck),
431 make_node(3, NodeKind::Group),
432 make_node(4, NodeKind::Master),
433 ];
434 let edges = vec![
435 Edge { from: NodeId(1), to: NodeId(3) },
436 Edge { from: NodeId(2), to: NodeId(3) },
437 Edge { from: NodeId(3), to: NodeId(4) },
438 ];
439 let g = AudioGraph::build(nodes, edges, 64).unwrap();
440 let order = g.eval_order();
441 let pos = |id: u32| order.iter().position(|n| n.0 == id).unwrap();
442 assert!(pos(1) < pos(3));
443 assert!(pos(2) < pos(3));
444 assert!(pos(3) < pos(4));
445 }
446}