1use rill_core::buffer::Buffer;
2use rill_core::math::Transcendental;
3use rill_core::time::{ClockSource, ClockTick, SystemClock};
4use rill_core::traits::{AudioNode, NodeVariant, PortId};
5use std::collections::VecDeque;
6
7#[derive(Debug, Clone)]
13#[allow(dead_code)]
14pub struct InternalRoute {
15 pub from: PortId,
16 pub to: PortId,
17}
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub enum ConnectionKind {
25 Direct,
26 FanOut,
27 FanIn,
28}
29
30#[derive(Debug, Clone)]
35pub enum BuildError {
36 CycleDetected,
37}
38
39#[derive(Debug, Clone, Copy, Default)]
44pub struct GraphStats {
45 pub blocks_processed: u64,
46 pub max_process_time_ns: u64,
47 pub avg_process_time_ns: f64,
48}
49
50struct NodeEntry<T: Transcendental, const BUF_SIZE: usize> {
55 node: NodeVariant<T, BUF_SIZE>,
56}
57
58pub struct GraphBuilder<T: Transcendental, const BUF_SIZE: usize> {
64 nodes: Vec<NodeEntry<T, BUF_SIZE>>,
65 audio_edges: Vec<(usize, usize, usize, usize)>,
66 feedback_edges: Vec<(usize, usize, usize, usize)>,
67}
68
69impl<T: Transcendental, const BUF_SIZE: usize> Default for GraphBuilder<T, BUF_SIZE> {
70 fn default() -> Self {
71 Self::new()
72 }
73}
74
75impl<T: Transcendental, const BUF_SIZE: usize> GraphBuilder<T, BUF_SIZE> {
76 pub fn new() -> Self {
77 Self {
78 nodes: Vec::new(),
79 audio_edges: Vec::new(),
80 feedback_edges: Vec::new(),
81 }
82 }
83
84 pub fn add_source(&mut self, source: Box<dyn rill_core::traits::Source<T, BUF_SIZE>>) -> usize {
85 let idx = self.nodes.len();
86 self.nodes.push(NodeEntry {
87 node: NodeVariant::Source(source),
88 });
89 idx
90 }
91
92 pub fn add_processor(
93 &mut self,
94 processor: Box<dyn rill_core::traits::Processor<T, BUF_SIZE>>,
95 ) -> usize {
96 let idx = self.nodes.len();
97 self.nodes.push(NodeEntry {
98 node: NodeVariant::Processor(processor),
99 });
100 idx
101 }
102
103 pub fn add_sink(&mut self, sink: Box<dyn rill_core::traits::Sink<T, BUF_SIZE>>) -> usize {
104 let idx = self.nodes.len();
105 self.nodes.push(NodeEntry {
106 node: NodeVariant::Sink(sink),
107 });
108 idx
109 }
110
111 pub fn connect_audio(
114 &mut self,
115 from_node: usize,
116 from_port: usize,
117 to_node: usize,
118 to_port: usize,
119 ) {
120 self.audio_edges
121 .push((from_node, from_port, to_node, to_port));
122 }
123
124 pub fn connect_feedback(
127 &mut self,
128 from_node: usize,
129 from_port: usize,
130 to_node: usize,
131 to_port: usize,
132 ) {
133 self.feedback_edges
134 .push((from_node, from_port, to_node, to_port));
135 }
136
137 pub fn build(
139 mut self,
140 clock_source: Box<dyn ClockSource>,
141 ) -> Result<AudioGraph<T, BUF_SIZE>, BuildError> {
142 let num_nodes = self.nodes.len();
143
144 let mut in_degree = vec![0usize; num_nodes];
146 let mut out_edges: Vec<Vec<(usize, usize, usize)>> = vec![Vec::new(); num_nodes];
147
148 for &(from_n, from_p, to_n, to_p) in &self.audio_edges {
149 in_degree[to_n] += 1;
150 out_edges[from_n].push((from_p, to_n, to_p));
151 }
152
153 let mut queue: VecDeque<usize> = in_degree
155 .iter()
156 .enumerate()
157 .filter(|(_, &d)| d == 0)
158 .map(|(i, _)| i)
159 .collect();
160
161 let mut topo = Vec::with_capacity(num_nodes);
162 let mut indeg = in_degree;
163 while let Some(idx) = queue.pop_front() {
164 topo.push(idx);
165 for &(_, to_n, _) in &out_edges[idx] {
166 indeg[to_n] -= 1;
167 if indeg[to_n] == 0 {
168 queue.push_back(to_n);
169 }
170 }
171 }
172
173 if topo.len() != num_nodes {
174 return Err(BuildError::CycleDetected);
175 }
176
177 for &(from_n, from_p, to_n, to_p) in &self.audio_edges {
179 if let Some(port) = self.nodes[from_n].node.output_port_mut(from_p) {
180 port.downstream.push((to_n, to_p));
181 }
182 }
183
184 for &(from_n, from_p, to_n, to_p) in &self.feedback_edges {
186 if let Some(port) = self.nodes[from_n].node.output_port_mut(from_p) {
188 port.feedback_buffer = Some(Buffer::new());
189 port.feedback_downstream.push((to_n, to_p));
190 }
191 }
192
193 let sample_rate = clock_source.sample_rate();
194
195 Ok(AudioGraph {
196 nodes: self.nodes,
197 topo_order: topo,
198 clock_source,
199 current_tick: ClockTick::new(0, BUF_SIZE as u32, sample_rate),
200 })
201 }
202}
203
204pub struct AudioGraph<T: Transcendental, const BUF_SIZE: usize> {
216 nodes: Vec<NodeEntry<T, BUF_SIZE>>,
217 topo_order: Vec<usize>,
218 #[allow(dead_code)]
219 clock_source: Box<dyn ClockSource>,
220 current_tick: ClockTick,
221}
222
223impl<T: Transcendental, const BUF_SIZE: usize> AudioGraph<T, BUF_SIZE> {
224 pub fn new(clock_source: Box<dyn ClockSource>) -> Self {
226 let sample_rate = clock_source.sample_rate();
227 Self {
228 nodes: Vec::new(),
229 topo_order: Vec::new(),
230 clock_source,
231 current_tick: ClockTick::new(0, BUF_SIZE as u32, sample_rate),
232 }
233 }
234
235 pub fn with_sample_rate(sample_rate: f32) -> Self {
237 Self::new(Box::new(SystemClock::with_sample_rate(sample_rate)))
238 }
239
240 pub fn output_buffer(&self, node_idx: usize, port_idx: usize) -> Option<&[T; BUF_SIZE]> {
242 self.nodes
243 .get(node_idx)?
244 .node
245 .output_port(port_idx)
246 .map(|p| p.buffer.as_array())
247 }
248
249 pub fn current_tick(&self) -> ClockTick {
254 self.current_tick
255 }
256
257 pub fn node_count(&self) -> usize {
258 self.nodes.len()
259 }
260
261 pub fn topo_order(&self) -> &[usize] {
262 &self.topo_order
263 }
264
265 pub fn dispatch_set_parameters(
270 &mut self,
271 commands: &[rill_core::queues::signal::SetParameter],
272 ) {
273 for cmd in commands {
274 let target = cmd.port.node_id();
275 for entry in self.nodes.iter_mut() {
276 if entry.node.id() == target {
277 let _ = entry.node.apply_set_parameter(cmd);
278 break;
279 }
280 }
281 }
282 }
283}
284
285#[cfg(test)]
290mod tests {
291 use super::*;
292 use rill_core::math::Transcendental;
293 use rill_core::time::ClockTick;
294 use rill_core::traits::{
295 AudioNode, NodeCategory, NodeId, NodeMetadata, NodeState, ParamValue, ParameterId, Port,
296 PortDirection, PortId, ProcessResult, Processor, Sink, Source,
297 };
298
299 struct ConstantSource<T: Transcendental, const BUF_SIZE: usize> {
303 value: T,
304 state: NodeState<T, BUF_SIZE>,
305 outputs: Vec<Port<T, BUF_SIZE>>,
306 }
307
308 impl<T: Transcendental, const BUF_SIZE: usize> ConstantSource<T, BUF_SIZE> {
309 fn new(value: T, sample_rate: f32) -> Self {
310 let mut outputs = Vec::with_capacity(1);
311 outputs.push(Port {
312 id: PortId::audio_out(NodeId(0), 0),
313 name: "output".into(),
314 direction: PortDirection::Output,
315 action: None,
316 pending_command: None,
317 buffer: Default::default(),
318 feedback_buffer: None,
319 downstream: Vec::new(),
320 feedback_downstream: Vec::new(),
321 });
322 Self {
323 value,
324 state: NodeState::new(sample_rate),
325 outputs,
326 }
327 }
328 }
329
330 impl<T: Transcendental, const BUF_SIZE: usize> AudioNode<T, BUF_SIZE> for ConstantSource<T, BUF_SIZE> {
331 fn metadata(&self) -> NodeMetadata {
332 NodeMetadata {
333 name: "ConstantSource".into(),
334 category: NodeCategory::Source,
335 description: String::new(),
336 author: String::new(),
337 version: "1.0".into(),
338 audio_inputs: 0,
339 audio_outputs: 1,
340 control_inputs: 0,
341 control_outputs: 0,
342 clock_inputs: 0,
343 clock_outputs: 0,
344 feedback_ports: 0,
345 parameters: vec![],
346 }
347 }
348 fn init(&mut self, _sample_rate: f32) {}
349 fn reset(&mut self) {}
350 fn get_parameter(&self, _id: &ParameterId) -> Option<ParamValue> {
351 None
352 }
353 fn set_parameter(&mut self, _id: &ParameterId, _value: ParamValue) -> ProcessResult<()> {
354 Ok(())
355 }
356 fn id(&self) -> NodeId {
357 NodeId(0)
358 }
359 fn set_id(&mut self, _id: NodeId) {}
360 fn input_port(&self, _index: usize) -> Option<&Port<T, BUF_SIZE>> {
361 None
362 }
363 fn input_port_mut(&mut self, _index: usize) -> Option<&mut Port<T, BUF_SIZE>> {
364 None
365 }
366 fn output_port(&self, index: usize) -> Option<&Port<T, BUF_SIZE>> {
367 self.outputs.get(index)
368 }
369 fn output_port_mut(&mut self, index: usize) -> Option<&mut Port<T, BUF_SIZE>> {
370 self.outputs.get_mut(index)
371 }
372 fn control_port(&self, _index: usize) -> Option<&Port<T, BUF_SIZE>> {
373 None
374 }
375 fn control_port_mut(&mut self, _index: usize) -> Option<&mut Port<T, BUF_SIZE>> {
376 None
377 }
378 fn state(&self) -> &NodeState<T, BUF_SIZE> {
379 &self.state
380 }
381 fn state_mut(&mut self) -> &mut NodeState<T, BUF_SIZE> {
382 &mut self.state
383 }
384 }
385
386 impl<T: Transcendental, const BUF_SIZE: usize> Source<T, BUF_SIZE> for ConstantSource<T, BUF_SIZE> {
387 fn generate(
388 &mut self,
389 _clock: &ClockTick,
390 _control_inputs: &[T],
391 _clock_inputs: &[ClockTick],
392 ) -> ProcessResult<()> {
393 let out = self.outputs[0].buffer.as_mut_array();
394 for sample in out.iter_mut() {
395 *sample = self.value;
396 }
397 Ok(())
398 }
399 fn num_audio_outputs(&self) -> usize {
400 1
401 }
402 }
403
404 struct NoopProcessor<T: Transcendental, const BUF_SIZE: usize> {
408 state: NodeState<T, BUF_SIZE>,
409 }
410
411 impl<T: Transcendental, const BUF_SIZE: usize> NoopProcessor<T, BUF_SIZE> {
412 fn new(sample_rate: f32) -> Self {
413 Self {
414 state: NodeState::new(sample_rate),
415 }
416 }
417 }
418
419 impl<T: Transcendental, const BUF_SIZE: usize> AudioNode<T, BUF_SIZE> for NoopProcessor<T, BUF_SIZE> {
420 fn metadata(&self) -> NodeMetadata {
421 NodeMetadata {
422 name: "NoopProcessor".into(),
423 category: NodeCategory::Processor,
424 description: String::new(),
425 author: String::new(),
426 version: "1.0".into(),
427 audio_inputs: 0,
428 audio_outputs: 0,
429 control_inputs: 0,
430 control_outputs: 0,
431 clock_inputs: 0,
432 clock_outputs: 0,
433 feedback_ports: 0,
434 parameters: vec![],
435 }
436 }
437 fn init(&mut self, _sample_rate: f32) {}
438 fn reset(&mut self) {}
439 fn get_parameter(&self, _id: &ParameterId) -> Option<ParamValue> {
440 None
441 }
442 fn set_parameter(&mut self, _id: &ParameterId, _value: ParamValue) -> ProcessResult<()> {
443 Ok(())
444 }
445 fn id(&self) -> NodeId {
446 NodeId(1)
447 }
448 fn set_id(&mut self, _id: NodeId) {}
449 fn input_port(&self, _index: usize) -> Option<&Port<T, BUF_SIZE>> {
450 None
451 }
452 fn input_port_mut(&mut self, _index: usize) -> Option<&mut Port<T, BUF_SIZE>> {
453 None
454 }
455 fn output_port(&self, _index: usize) -> Option<&Port<T, BUF_SIZE>> {
456 None
457 }
458 fn output_port_mut(&mut self, _index: usize) -> Option<&mut Port<T, BUF_SIZE>> {
459 None
460 }
461 fn control_port(&self, _index: usize) -> Option<&Port<T, BUF_SIZE>> {
462 None
463 }
464 fn control_port_mut(&mut self, _index: usize) -> Option<&mut Port<T, BUF_SIZE>> {
465 None
466 }
467 fn state(&self) -> &NodeState<T, BUF_SIZE> {
468 &self.state
469 }
470 fn state_mut(&mut self) -> &mut NodeState<T, BUF_SIZE> {
471 &mut self.state
472 }
473 }
474
475 impl<T: Transcendental, const BUF_SIZE: usize> Processor<T, BUF_SIZE> for NoopProcessor<T, BUF_SIZE> {
476 fn process(
477 &mut self,
478 _clock: &ClockTick,
479 _audio_inputs: &[&[T; BUF_SIZE]],
480 _control_inputs: &[T],
481 _clock_inputs: &[ClockTick],
482 _feedback_inputs: &[&[T; BUF_SIZE]],
483 ) -> ProcessResult<()> {
484 Ok(())
485 }
486 }
487
488 struct NoopSink<T: Transcendental, const BUF_SIZE: usize> {
492 state: NodeState<T, BUF_SIZE>,
493 }
494
495 impl<T: Transcendental, const BUF_SIZE: usize> NoopSink<T, BUF_SIZE> {
496 fn new(sample_rate: f32) -> Self {
497 Self {
498 state: NodeState::new(sample_rate),
499 }
500 }
501 }
502
503 impl<T: Transcendental, const BUF_SIZE: usize> AudioNode<T, BUF_SIZE> for NoopSink<T, BUF_SIZE> {
504 fn metadata(&self) -> NodeMetadata {
505 NodeMetadata {
506 name: "NoopSink".into(),
507 category: NodeCategory::Sink,
508 description: String::new(),
509 author: String::new(),
510 version: "1.0".into(),
511 audio_inputs: 0,
512 audio_outputs: 0,
513 control_inputs: 0,
514 control_outputs: 0,
515 clock_inputs: 0,
516 clock_outputs: 0,
517 feedback_ports: 0,
518 parameters: vec![],
519 }
520 }
521 fn init(&mut self, _sample_rate: f32) {}
522 fn reset(&mut self) {}
523 fn get_parameter(&self, _id: &ParameterId) -> Option<ParamValue> {
524 None
525 }
526 fn set_parameter(&mut self, _id: &ParameterId, _value: ParamValue) -> ProcessResult<()> {
527 Ok(())
528 }
529 fn id(&self) -> NodeId {
530 NodeId(2)
531 }
532 fn set_id(&mut self, _id: NodeId) {}
533 fn input_port(&self, _index: usize) -> Option<&Port<T, BUF_SIZE>> {
534 None
535 }
536 fn input_port_mut(&mut self, _index: usize) -> Option<&mut Port<T, BUF_SIZE>> {
537 None
538 }
539 fn output_port(&self, _index: usize) -> Option<&Port<T, BUF_SIZE>> {
540 None
541 }
542 fn output_port_mut(&mut self, _index: usize) -> Option<&mut Port<T, BUF_SIZE>> {
543 None
544 }
545 fn control_port(&self, _index: usize) -> Option<&Port<T, BUF_SIZE>> {
546 None
547 }
548 fn control_port_mut(&mut self, _index: usize) -> Option<&mut Port<T, BUF_SIZE>> {
549 None
550 }
551 fn state(&self) -> &NodeState<T, BUF_SIZE> {
552 &self.state
553 }
554 fn state_mut(&mut self) -> &mut NodeState<T, BUF_SIZE> {
555 &mut self.state
556 }
557 }
558
559 impl<T: Transcendental, const BUF_SIZE: usize> Sink<T, BUF_SIZE> for NoopSink<T, BUF_SIZE> {
560 fn consume(
561 &mut self,
562 _clock: &ClockTick,
563 _audio_inputs: &[&[T; BUF_SIZE]],
564 _control_inputs: &[T],
565 _clock_inputs: &[ClockTick],
566 _feedback_inputs: &[&[T; BUF_SIZE]],
567 ) -> ProcessResult<()> {
568 Ok(())
569 }
570 }
571
572 #[test]
577 fn test_graph_creation() {
578 let graph = AudioGraph::<f32, 64>::with_sample_rate(44100.0);
579 assert_eq!(graph.node_count(), 0);
580 }
581
582 #[test]
583 fn test_topo_order_correct() {
584 const BUF: usize = 64;
585 let mut builder = GraphBuilder::<f32, BUF>::new();
586
587 let src = builder.add_source(Box::new(ConstantSource::new(1.0, 44100.0)));
588 let proc = builder.add_processor(Box::new(NoopProcessor::new(44100.0)));
589 let sink = builder.add_sink(Box::new(NoopSink::new(44100.0)));
590
591 builder.connect_audio(src, 0, proc, 0);
592 builder.connect_audio(proc, 0, sink, 0);
593
594 let graph = builder
595 .build(Box::new(SystemClock::with_sample_rate(44100.0)))
596 .expect("build failed");
597
598 let order = graph.topo_order();
599 let src_pos = order.iter().position(|&i| i == src).unwrap();
600 let proc_pos = order.iter().position(|&i| i == proc).unwrap();
601 let sink_pos = order.iter().position(|&i| i == sink).unwrap();
602 assert!(src_pos < proc_pos);
603 assert!(proc_pos < sink_pos);
604 }
605
606 #[test]
607 fn test_cycle_detection() {
608 const BUF: usize = 64;
609 let mut builder = GraphBuilder::<f32, BUF>::new();
610
611 let a = builder.add_processor(Box::new(NoopProcessor::new(44100.0)));
612 let b = builder.add_processor(Box::new(NoopProcessor::new(44100.0)));
613
614 builder.connect_audio(a, 0, b, 0);
615 builder.connect_audio(b, 0, a, 0);
616
617 let result = builder.build(Box::new(SystemClock::with_sample_rate(44100.0)));
618 assert!(matches!(result, Err(BuildError::CycleDetected)));
619 }
620
621 #[test]
622 fn test_source_node_create() {
623 const BUF: usize = 64;
624 let mut builder = GraphBuilder::<f32, BUF>::new();
625 let idx = builder.add_source(Box::new(ConstantSource::new(0.5, 44100.0)));
626 let graph = builder
627 .build(Box::new(SystemClock::with_sample_rate(44100.0)))
628 .expect("build failed");
629 assert_eq!(graph.node_count(), 1);
630 assert_eq!(graph.topo_order(), &[idx]);
631 }
632}