1use crate::registry::{NodeRegistry, RegistryError};
2use rill_core::buffer::Buffer;
3use rill_core::math::Transcendental;
4use rill_core::time::{ClockSource, ClockTick, SystemClock};
5use rill_core::traits::{SignalNode, NodeId, NodeParams, NodeVariant, PortId};
6use std::collections::VecDeque;
7
8#[derive(Debug, Clone)]
14#[allow(dead_code)]
15pub struct InternalRoute {
16 pub from: PortId,
17 pub to: PortId,
18}
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub enum ConnectionKind {
26 Direct,
27 FanOut,
28 FanIn,
29}
30
31#[derive(Debug, Clone)]
36pub enum BuildError {
37 CycleDetected,
38}
39
40#[derive(Debug, Clone, Copy, Default)]
45pub struct GraphStats {
46 pub blocks_processed: u64,
47 pub max_process_time_ns: u64,
48 pub avg_process_time_ns: f64,
49}
50
51pub(crate) struct NodeEntry<T: Transcendental, const BUF_SIZE: usize> {
56 pub(crate) node: NodeVariant<T, BUF_SIZE>,
57}
58
59pub struct GraphBuilder<T: Transcendental, const BUF_SIZE: usize> {
65 nodes: Vec<NodeEntry<T, BUF_SIZE>>,
66 audio_edges: Vec<(usize, usize, usize, usize)>,
67 control_edges: Vec<(usize, usize, usize, usize)>,
68 clock_edges: Vec<(usize, usize, usize, usize)>,
69 feedback_edges: Vec<(usize, usize, usize, usize)>,
70}
71
72impl<T: Transcendental, const BUF_SIZE: usize> Default for GraphBuilder<T, BUF_SIZE> {
73 fn default() -> Self {
74 Self::new()
75 }
76}
77
78impl<T: Transcendental, const BUF_SIZE: usize> GraphBuilder<T, BUF_SIZE> {
79 pub fn new() -> Self {
80 Self {
81 nodes: Vec::new(),
82 audio_edges: Vec::new(),
83 control_edges: Vec::new(),
84 clock_edges: Vec::new(),
85 feedback_edges: Vec::new(),
86 }
87 }
88
89 pub fn add_source(&mut self, source: Box<dyn rill_core::traits::Source<T, BUF_SIZE>>) -> usize {
90 let idx = self.nodes.len();
91 self.nodes.push(NodeEntry {
92 node: NodeVariant::Source(source),
93 });
94 idx
95 }
96
97 pub fn add_processor(
98 &mut self,
99 processor: Box<dyn rill_core::traits::Processor<T, BUF_SIZE>>,
100 ) -> usize {
101 let idx = self.nodes.len();
102 self.nodes.push(NodeEntry {
103 node: NodeVariant::Processor(processor),
104 });
105 idx
106 }
107
108 pub fn add_sink(&mut self, sink: Box<dyn rill_core::traits::Sink<T, BUF_SIZE>>) -> usize {
109 let idx = self.nodes.len();
110 self.nodes.push(NodeEntry {
111 node: NodeVariant::Sink(sink),
112 });
113 idx
114 }
115
116 pub fn add_node(
125 &mut self,
126 registry: &NodeRegistry<T, BUF_SIZE>,
127 type_name: &str,
128 params: &NodeParams,
129 ) -> Result<usize, RegistryError> {
130 let id = NodeId(self.nodes.len() as u32);
131 self.add_node_with_id(registry, type_name, params, id)
132 }
133
134 pub fn add_node_with_id(
147 &mut self,
148 registry: &NodeRegistry<T, BUF_SIZE>,
149 type_name: &str,
150 params: &NodeParams,
151 id: NodeId,
152 ) -> Result<usize, RegistryError> {
153 let node = registry.construct(type_name, id, params)?;
154 let idx = self.nodes.len();
155 self.nodes.push(NodeEntry { node });
156 Ok(idx)
157 }
158
159 pub fn node_count(&self) -> usize {
161 self.nodes.len()
162 }
163
164 pub fn connect_signal(
167 &mut self,
168 from_node: usize,
169 from_port: usize,
170 to_node: usize,
171 to_port: usize,
172 ) {
173 self.audio_edges
174 .push((from_node, from_port, to_node, to_port));
175 }
176
177 pub fn connect_control(
179 &mut self,
180 from_node: usize,
181 from_port: usize,
182 to_node: usize,
183 to_port: usize,
184 ) {
185 self.control_edges
186 .push((from_node, from_port, to_node, to_port));
187 }
188
189 pub fn connect_clock(
191 &mut self,
192 from_node: usize,
193 from_port: usize,
194 to_node: usize,
195 to_port: usize,
196 ) {
197 self.clock_edges
198 .push((from_node, from_port, to_node, to_port));
199 }
200
201 pub fn connect_feedback(
204 &mut self,
205 from_node: usize,
206 from_port: usize,
207 to_node: usize,
208 to_port: usize,
209 ) {
210 self.feedback_edges
211 .push((from_node, from_port, to_node, to_port));
212 }
213
214 pub fn build(
216 mut self,
217 clock_source: Box<dyn ClockSource>,
218 ) -> Result<SignalGraph<T, BUF_SIZE>, BuildError> {
219 let num_nodes = self.nodes.len();
220
221 let mut in_degree = vec![0usize; num_nodes];
223 let mut out_edges: Vec<Vec<(usize, usize, usize)>> = vec![Vec::new(); num_nodes];
224
225 for &(from_n, from_p, to_n, to_p) in &self.audio_edges {
226 in_degree[to_n] += 1;
227 out_edges[from_n].push((from_p, to_n, to_p));
228 }
229
230 let mut queue: VecDeque<usize> = in_degree
232 .iter()
233 .enumerate()
234 .filter(|(_, &d)| d == 0)
235 .map(|(i, _)| i)
236 .collect();
237
238 let mut topo = Vec::with_capacity(num_nodes);
239 let mut indeg = in_degree;
240 while let Some(idx) = queue.pop_front() {
241 topo.push(idx);
242 for &(_, to_n, _) in &out_edges[idx] {
243 indeg[to_n] -= 1;
244 if indeg[to_n] == 0 {
245 queue.push_back(to_n);
246 }
247 }
248 }
249
250 if topo.len() != num_nodes {
251 return Err(BuildError::CycleDetected);
252 }
253
254 for &(from_n, from_p, to_n, to_p) in &self.audio_edges {
256 if let Some(port) = self.nodes[from_n].node.output_port_mut(from_p) {
257 port.downstream.push((to_n, to_p));
258 }
259 }
260 for &(from_n, from_p, to_n, to_p) in &self.audio_edges {
263 let upstream = self.nodes[from_n]
264 .node
265 .output_port(from_p)
266 .map(|p| &p.buffer as *const Buffer<T, BUF_SIZE>);
267 if let Some(port) = self.nodes[to_n].node.input_port_mut(to_p) {
268 if port.upstream_buffer.is_none() {
269 port.upstream_buffer = upstream;
270 } else {
271 port.upstream_buffer = None;
272 }
273 }
274 }
275
276 for &(from_n, from_p, to_n, to_p) in &self.feedback_edges {
278 if let Some(port) = self.nodes[from_n].node.output_port_mut(from_p) {
280 port.feedback_buffer = Some(Buffer::new());
281 port.feedback_downstream.push((to_n, to_p));
282 }
283 }
284
285 let sample_rate = clock_source.sample_rate();
286
287 Ok(SignalGraph {
288 nodes: self.nodes,
289 topo_order: topo,
290 clock_source,
291 current_tick: ClockTick::new(0, BUF_SIZE as u32, sample_rate),
292 })
293 }
294}
295
296pub struct SignalGraph<T: Transcendental, const BUF_SIZE: usize> {
308 nodes: Vec<NodeEntry<T, BUF_SIZE>>,
309 topo_order: Vec<usize>,
310 #[allow(dead_code)]
311 clock_source: Box<dyn ClockSource>,
312 current_tick: ClockTick,
313}
314
315impl<T: Transcendental, const BUF_SIZE: usize> SignalGraph<T, BUF_SIZE> {
316 pub fn new(clock_source: Box<dyn ClockSource>) -> Self {
318 let sample_rate = clock_source.sample_rate();
319 Self {
320 nodes: Vec::new(),
321 topo_order: Vec::new(),
322 clock_source,
323 current_tick: ClockTick::new(0, BUF_SIZE as u32, sample_rate),
324 }
325 }
326
327 pub fn with_sample_rate(sample_rate: f32) -> Self {
329 Self::new(Box::new(SystemClock::with_sample_rate(sample_rate)))
330 }
331
332 pub fn output_buffer(&self, node_idx: usize, port_idx: usize) -> Option<&[T; BUF_SIZE]> {
334 self.nodes
335 .get(node_idx)?
336 .node
337 .output_port(port_idx)
338 .map(|p| p.buffer.as_array())
339 }
340
341 pub fn current_tick(&self) -> ClockTick {
346 self.current_tick
347 }
348
349 pub fn node_count(&self) -> usize {
350 self.nodes.len()
351 }
352
353 pub fn topo_order(&self) -> &[usize] {
354 &self.topo_order
355 }
356
357 pub(crate) fn node_entries(&self) -> &[NodeEntry<T, BUF_SIZE>] {
360 &self.nodes
361 }
362
363 pub(crate) fn sample_rate(&self) -> f32 {
364 self.current_tick.sample_rate
365 }
366
367 pub fn dispatch_set_parameters(
374 &mut self,
375 commands: &[rill_core::queues::signal::SetParameter],
376 ) {
377 for cmd in commands {
378 let target = cmd.port.node_id();
379 for entry in self.nodes.iter_mut() {
380 if entry.node.id() == target {
381 let _ = entry.node.apply_set_parameter(cmd);
382 break;
383 }
384 }
385 }
386 }
387
388 pub fn into_parts(
390 self,
391 ) -> (Vec<NodeVariant<T, BUF_SIZE>>, Vec<usize>, ClockTick) {
392 let nodes = self.nodes.into_iter().map(|e| e.node).collect();
393 (nodes, self.topo_order, self.current_tick)
394 }
395}
396
397#[cfg(test)]
402mod tests {
403 use super::*;
404 use rill_core::math::Transcendental;
405 use rill_core::time::ClockTick;
406 use rill_core::traits::{
407 SignalNode, NodeCategory, NodeId, NodeMetadata, NodeState, ParamValue, ParameterId, Port,
408 PortDirection, PortId, ProcessResult, Processor, Sink, Source,
409 };
410
411 struct ConstantSource<T: Transcendental, const BUF_SIZE: usize> {
415 value: T,
416 state: NodeState<T, BUF_SIZE>,
417 outputs: Vec<Port<T, BUF_SIZE>>,
418 }
419
420 impl<T: Transcendental, const BUF_SIZE: usize> ConstantSource<T, BUF_SIZE> {
421 fn new(value: T, sample_rate: f32) -> Self {
422 let mut outputs = Vec::with_capacity(1);
423 outputs.push(Port {
424 id: PortId::audio_out(NodeId(0), 0),
425 name: "output".into(),
426 direction: PortDirection::Output,
427 action: None,
428 pending_command: None,
429 buffer: Default::default(),
430 feedback_buffer: None,
431 downstream: Vec::new(),
432 feedback_downstream: Vec::new(),
433 upstream_buffer: None,
434 });
435 Self {
436 value,
437 state: NodeState::new(sample_rate),
438 outputs,
439 }
440 }
441 }
442
443 impl<T: Transcendental, const BUF_SIZE: usize> SignalNode<T, BUF_SIZE> for ConstantSource<T, BUF_SIZE> {
444 fn metadata(&self) -> NodeMetadata {
445 NodeMetadata {
446 type_name: None,
447 name: "ConstantSource".into(),
448 category: NodeCategory::Source,
449 description: String::new(),
450 author: String::new(),
451 version: "1.0".into(),
452 signal_inputs: 0,
453 signal_outputs: 1,
454 control_inputs: 0,
455 control_outputs: 0,
456 clock_inputs: 0,
457 clock_outputs: 0,
458 feedback_ports: 0,
459 parameters: vec![],
460 }
461 }
462 fn init(&mut self, _sample_rate: f32) {}
463 fn reset(&mut self) {}
464 fn get_parameter(&self, _id: &ParameterId) -> Option<ParamValue> {
465 None
466 }
467 fn set_parameter(&mut self, _id: &ParameterId, _value: ParamValue) -> ProcessResult<()> {
468 Ok(())
469 }
470 fn id(&self) -> NodeId {
471 NodeId(0)
472 }
473 fn set_id(&mut self, _id: NodeId) {}
474 fn input_port(&self, _index: usize) -> Option<&Port<T, BUF_SIZE>> {
475 None
476 }
477 fn input_port_mut(&mut self, _index: usize) -> Option<&mut Port<T, BUF_SIZE>> {
478 None
479 }
480 fn output_port(&self, index: usize) -> Option<&Port<T, BUF_SIZE>> {
481 self.outputs.get(index)
482 }
483 fn output_port_mut(&mut self, index: usize) -> Option<&mut Port<T, BUF_SIZE>> {
484 self.outputs.get_mut(index)
485 }
486 fn control_port(&self, _index: usize) -> Option<&Port<T, BUF_SIZE>> {
487 None
488 }
489 fn control_port_mut(&mut self, _index: usize) -> Option<&mut Port<T, BUF_SIZE>> {
490 None
491 }
492 fn state(&self) -> &NodeState<T, BUF_SIZE> {
493 &self.state
494 }
495 fn state_mut(&mut self) -> &mut NodeState<T, BUF_SIZE> {
496 &mut self.state
497 }
498 }
499
500 impl<T: Transcendental, const BUF_SIZE: usize> Source<T, BUF_SIZE> for ConstantSource<T, BUF_SIZE> {
501 fn generate(
502 &mut self,
503 _clock: &ClockTick,
504 _control_inputs: &[T],
505 _clock_inputs: &[ClockTick],
506 ) -> ProcessResult<()> {
507 let out = self.outputs[0].buffer.as_mut_array();
508 for sample in out.iter_mut() {
509 *sample = self.value;
510 }
511 Ok(())
512 }
513 fn num_signal_outputs(&self) -> usize {
514 1
515 }
516 }
517
518 struct NoopProcessor<T: Transcendental, const BUF_SIZE: usize> {
522 state: NodeState<T, BUF_SIZE>,
523 }
524
525 impl<T: Transcendental, const BUF_SIZE: usize> NoopProcessor<T, BUF_SIZE> {
526 fn new(sample_rate: f32) -> Self {
527 Self {
528 state: NodeState::new(sample_rate),
529 }
530 }
531 }
532
533 impl<T: Transcendental, const BUF_SIZE: usize> SignalNode<T, BUF_SIZE> for NoopProcessor<T, BUF_SIZE> {
534 fn metadata(&self) -> NodeMetadata {
535 NodeMetadata {
536 type_name: None,
537 name: "NoopProcessor".into(),
538 category: NodeCategory::Processor,
539 description: String::new(),
540 author: String::new(),
541 version: "1.0".into(),
542 signal_inputs: 0,
543 signal_outputs: 0,
544 control_inputs: 0,
545 control_outputs: 0,
546 clock_inputs: 0,
547 clock_outputs: 0,
548 feedback_ports: 0,
549 parameters: vec![],
550 }
551 }
552 fn init(&mut self, _sample_rate: f32) {}
553 fn reset(&mut self) {}
554 fn get_parameter(&self, _id: &ParameterId) -> Option<ParamValue> {
555 None
556 }
557 fn set_parameter(&mut self, _id: &ParameterId, _value: ParamValue) -> ProcessResult<()> {
558 Ok(())
559 }
560 fn id(&self) -> NodeId {
561 NodeId(1)
562 }
563 fn set_id(&mut self, _id: NodeId) {}
564 fn input_port(&self, _index: usize) -> Option<&Port<T, BUF_SIZE>> {
565 None
566 }
567 fn input_port_mut(&mut self, _index: usize) -> Option<&mut Port<T, BUF_SIZE>> {
568 None
569 }
570 fn output_port(&self, _index: usize) -> Option<&Port<T, BUF_SIZE>> {
571 None
572 }
573 fn output_port_mut(&mut self, _index: usize) -> Option<&mut Port<T, BUF_SIZE>> {
574 None
575 }
576 fn control_port(&self, _index: usize) -> Option<&Port<T, BUF_SIZE>> {
577 None
578 }
579 fn control_port_mut(&mut self, _index: usize) -> Option<&mut Port<T, BUF_SIZE>> {
580 None
581 }
582 fn state(&self) -> &NodeState<T, BUF_SIZE> {
583 &self.state
584 }
585 fn state_mut(&mut self) -> &mut NodeState<T, BUF_SIZE> {
586 &mut self.state
587 }
588 }
589
590 impl<T: Transcendental, const BUF_SIZE: usize> Processor<T, BUF_SIZE> for NoopProcessor<T, BUF_SIZE> {
591 fn process(
592 &mut self,
593 _clock: &ClockTick,
594 _signal_inputs: &[&[T; BUF_SIZE]],
595 _control_inputs: &[T],
596 _clock_inputs: &[ClockTick],
597 _feedback_inputs: &[&[T; BUF_SIZE]],
598 ) -> ProcessResult<()> {
599 Ok(())
600 }
601 }
602
603 struct NoopSink<T: Transcendental, const BUF_SIZE: usize> {
607 state: NodeState<T, BUF_SIZE>,
608 }
609
610 impl<T: Transcendental, const BUF_SIZE: usize> NoopSink<T, BUF_SIZE> {
611 fn new(sample_rate: f32) -> Self {
612 Self {
613 state: NodeState::new(sample_rate),
614 }
615 }
616 }
617
618 impl<T: Transcendental, const BUF_SIZE: usize> SignalNode<T, BUF_SIZE> for NoopSink<T, BUF_SIZE> {
619 fn metadata(&self) -> NodeMetadata {
620 NodeMetadata {
621 type_name: None,
622 name: "NoopSink".into(),
623 category: NodeCategory::Sink,
624 description: String::new(),
625 author: String::new(),
626 version: "1.0".into(),
627 signal_inputs: 0,
628 signal_outputs: 0,
629 control_inputs: 0,
630 control_outputs: 0,
631 clock_inputs: 0,
632 clock_outputs: 0,
633 feedback_ports: 0,
634 parameters: vec![],
635 }
636 }
637 fn init(&mut self, _sample_rate: f32) {}
638 fn reset(&mut self) {}
639 fn get_parameter(&self, _id: &ParameterId) -> Option<ParamValue> {
640 None
641 }
642 fn set_parameter(&mut self, _id: &ParameterId, _value: ParamValue) -> ProcessResult<()> {
643 Ok(())
644 }
645 fn id(&self) -> NodeId {
646 NodeId(2)
647 }
648 fn set_id(&mut self, _id: NodeId) {}
649 fn input_port(&self, _index: usize) -> Option<&Port<T, BUF_SIZE>> {
650 None
651 }
652 fn input_port_mut(&mut self, _index: usize) -> Option<&mut Port<T, BUF_SIZE>> {
653 None
654 }
655 fn output_port(&self, _index: usize) -> Option<&Port<T, BUF_SIZE>> {
656 None
657 }
658 fn output_port_mut(&mut self, _index: usize) -> Option<&mut Port<T, BUF_SIZE>> {
659 None
660 }
661 fn control_port(&self, _index: usize) -> Option<&Port<T, BUF_SIZE>> {
662 None
663 }
664 fn control_port_mut(&mut self, _index: usize) -> Option<&mut Port<T, BUF_SIZE>> {
665 None
666 }
667 fn state(&self) -> &NodeState<T, BUF_SIZE> {
668 &self.state
669 }
670 fn state_mut(&mut self) -> &mut NodeState<T, BUF_SIZE> {
671 &mut self.state
672 }
673 }
674
675 impl<T: Transcendental, const BUF_SIZE: usize> Sink<T, BUF_SIZE> for NoopSink<T, BUF_SIZE> {
676 fn consume(
677 &mut self,
678 _clock: &ClockTick,
679 _signal_inputs: &[&[T; BUF_SIZE]],
680 _control_inputs: &[T],
681 _clock_inputs: &[ClockTick],
682 _feedback_inputs: &[&[T; BUF_SIZE]],
683 ) -> ProcessResult<()> {
684 Ok(())
685 }
686 }
687
688 #[test]
693 fn test_graph_creation() {
694 let graph = SignalGraph::<f32, 64>::with_sample_rate(44100.0);
695 assert_eq!(graph.node_count(), 0);
696 }
697
698 #[test]
699 fn test_topo_order_correct() {
700 const BUF: usize = 64;
701 let mut builder = GraphBuilder::<f32, BUF>::new();
702
703 let src = builder.add_source(Box::new(ConstantSource::new(1.0, 44100.0)));
704 let proc = builder.add_processor(Box::new(NoopProcessor::new(44100.0)));
705 let sink = builder.add_sink(Box::new(NoopSink::new(44100.0)));
706
707 builder.connect_signal(src, 0, proc, 0);
708 builder.connect_signal(proc, 0, sink, 0);
709
710 let graph = builder
711 .build(Box::new(SystemClock::with_sample_rate(44100.0)))
712 .expect("build failed");
713
714 let order = graph.topo_order();
715 let src_pos = order.iter().position(|&i| i == src).unwrap();
716 let proc_pos = order.iter().position(|&i| i == proc).unwrap();
717 let sink_pos = order.iter().position(|&i| i == sink).unwrap();
718 assert!(src_pos < proc_pos);
719 assert!(proc_pos < sink_pos);
720 }
721
722 #[test]
723 fn test_cycle_detection() {
724 const BUF: usize = 64;
725 let mut builder = GraphBuilder::<f32, BUF>::new();
726
727 let a = builder.add_processor(Box::new(NoopProcessor::new(44100.0)));
728 let b = builder.add_processor(Box::new(NoopProcessor::new(44100.0)));
729
730 builder.connect_signal(a, 0, b, 0);
731 builder.connect_signal(b, 0, a, 0);
732
733 let result = builder.build(Box::new(SystemClock::with_sample_rate(44100.0)));
734 assert!(matches!(result, Err(BuildError::CycleDetected)));
735 }
736
737 #[test]
738 fn test_source_node_create() {
739 const BUF: usize = 64;
740 let mut builder = GraphBuilder::<f32, BUF>::new();
741 let idx = builder.add_source(Box::new(ConstantSource::new(0.5, 44100.0)));
742 let graph = builder
743 .build(Box::new(SystemClock::with_sample_rate(44100.0)))
744 .expect("build failed");
745 assert_eq!(graph.node_count(), 1);
746 assert_eq!(graph.topo_order(), &[idx]);
747 }
748}