1use crate::factory::NodeFactory;
2use std::sync::Arc;
3
4use rill_core::buffer::{FixedBuffer, ResourceRegistry, TapeLoop};
5use rill_core::io::{IoCapture, IoDriver, IoPlayback};
6use rill_core::math::Transcendental;
7use rill_core::queues::CommandEnum;
8use rill_core::queues::SetParameter;
9use rill_core::time::{ClockTick, RenderContext, SystemClock};
10use rill_core::traits::port::Port;
11use rill_core::traits::processable::Processable;
12use rill_core::traits::{Node, NodeId, NodeVariant, Params, ProcessResult};
13use rill_core_actor::{Actor, ActorRef, ActorSystem};
14use std::cell::{RefCell, UnsafeCell};
15use std::collections::{HashSet, VecDeque};
16use std::rc::Rc;
17use std::sync::atomic::AtomicBool;
18
19#[derive(Debug, Clone)]
29pub enum BuildError {
30 CycleDetected,
32 Backend(String),
34 Registry(String),
36}
37
38impl std::fmt::Display for BuildError {
39 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40 match self {
41 Self::CycleDetected => write!(f, "graph cycle detected"),
42 Self::Backend(msg) => write!(f, "backend error: {msg}"),
43 Self::Registry(msg) => write!(f, "registry error: {msg}"),
44 }
45 }
46}
47
48struct NodeRecipe<T: Transcendental, const BUF_SIZE: usize> {
58 type_name: String,
59 id: NodeId,
60 params: Params,
61 routing_entries: Vec<(usize, usize, f32)>,
62 _phantom: std::marker::PhantomData<(T, [(); BUF_SIZE])>,
63}
64
65struct NodeEntry<T: Transcendental, const BUF_SIZE: usize> {
67 node: NodeVariant<T, BUF_SIZE>,
68}
69
70#[derive(Clone)]
76pub struct GraphResource {
77 pub name: String,
79 pub kind: String,
81 pub capacity: usize,
83}
84
85pub struct GraphBuilder<T: Transcendental, const BUF_SIZE: usize> {
98 recipes: Vec<NodeRecipe<T, BUF_SIZE>>,
99 signal_edges: Vec<(usize, usize, usize, usize)>,
100 control_edges: Vec<(usize, usize, usize, usize)>,
101 clock_edges: Vec<(usize, usize, usize, usize)>,
102 feedback_edges: Vec<(usize, usize, usize, usize)>,
103 resources: Vec<GraphResource>,
104 factory: Arc<NodeFactory<T, BUF_SIZE>>,
106 sample_rate: Option<f32>,
109 parent_ref: Option<ActorRef<CommandEnum>>,
111}
112
113impl<T: Transcendental, const BUF_SIZE: usize> GraphBuilder<T, BUF_SIZE> {
114 pub fn new(factory: Arc<NodeFactory<T, BUF_SIZE>>) -> Self {
116 Self {
117 recipes: Vec::new(),
118 signal_edges: Vec::new(),
119 control_edges: Vec::new(),
120 clock_edges: Vec::new(),
121 feedback_edges: Vec::new(),
122 resources: Vec::new(),
123 factory,
124 sample_rate: None,
125 parent_ref: None,
126 }
127 }
128
129 pub fn add_node(&mut self, type_name: &str, params: &Params) -> usize {
136 let id = NodeId(self.recipes.len() as u32);
137 self.add_node_with_id(type_name, params, id)
138 }
139
140 pub fn add_node_with_id(&mut self, type_name: &str, params: &Params, id: NodeId) -> usize {
146 let idx = self.recipes.len();
147 self.recipes.push(NodeRecipe {
148 type_name: type_name.to_string(),
149 id,
150 params: params.clone(),
151 routing_entries: Vec::new(),
152 _phantom: std::marker::PhantomData,
153 });
154 idx
155 }
156
157 pub fn add_routing_entry(&mut self, idx: usize, from: usize, to: usize, gain: f32) {
159 if let Some(recipe) = self.recipes.get_mut(idx) {
160 recipe.routing_entries.push((from, to, gain));
161 }
162 }
163
164 pub fn add_resource(&mut self, resource: GraphResource) {
166 self.resources.push(resource);
167 }
168
169 pub fn node_count(&self) -> usize {
171 self.recipes.len()
172 }
173
174 pub fn set_sample_rate(&mut self, sr: f32) {
176 self.sample_rate = Some(sr);
177 }
178
179 pub fn set_parent_ref(&mut self, parent: ActorRef<CommandEnum>) {
181 self.parent_ref = Some(parent);
182 }
183
184 pub fn connect_signal(
186 &mut self,
187 from_node: usize,
188 from_port: usize,
189 to_node: usize,
190 to_port: usize,
191 ) {
192 self.signal_edges
193 .push((from_node, from_port, to_node, to_port));
194 }
195
196 pub fn connect_control(
198 &mut self,
199 from_node: usize,
200 from_port: usize,
201 to_node: usize,
202 to_port: usize,
203 ) {
204 self.control_edges
205 .push((from_node, from_port, to_node, to_port));
206 }
207
208 pub fn connect_clock(
210 &mut self,
211 from_node: usize,
212 from_port: usize,
213 to_node: usize,
214 to_port: usize,
215 ) {
216 self.clock_edges
217 .push((from_node, from_port, to_node, to_port));
218 }
219
220 pub fn connect_feedback(
222 &mut self,
223 from_node: usize,
224 from_port: usize,
225 to_node: usize,
226 to_port: usize,
227 ) {
228 self.feedback_edges
229 .push((from_node, from_port, to_node, to_port));
230 }
231
232 pub fn build(self, system: &ActorSystem) -> Result<Graph<T, BUF_SIZE>, BuildError> {
238 let mut node_entries: Vec<NodeEntry<T, BUF_SIZE>> = Vec::with_capacity(self.recipes.len());
240 for recipe in &self.recipes {
241 let node = self
242 .factory
243 .construct(&recipe.type_name, recipe.id, &recipe.params)
244 .map_err(|e| BuildError::Registry(format!("{e}")))?;
245 node_entries.push(NodeEntry { node });
246 }
247
248 for (idx, node) in node_entries.iter_mut().enumerate() {
250 for &(from, to, gain) in &self.recipes[idx].routing_entries {
251 if let NodeVariant::Router(ref mut router) = node.node {
252 router.set_connection(from, to, T::from_f32(gain)).ok();
253 }
254 }
255 }
256
257 let num_nodes = node_entries.len();
258
259 let mut in_degree = vec![0usize; num_nodes];
261 let mut out_edges: Vec<Vec<(usize, usize, usize)>> = vec![Vec::new(); num_nodes];
262
263 for &(from_n, from_p, to_n, to_p) in &self.signal_edges {
264 in_degree[to_n] += 1;
265 out_edges[from_n].push((from_p, to_n, to_p));
266 }
267
268 let recording_roots: Vec<usize> = in_degree
270 .iter()
271 .enumerate()
272 .filter(|(i, &d)| d == 0 && self.recipes[*i].type_name == "rill/input")
273 .map(|(i, _)| i)
274 .collect();
275 let playback_roots: Vec<usize> = in_degree
276 .iter()
277 .enumerate()
278 .filter(|(i, &d)| d == 0 && self.recipes[*i].type_name != "rill/input")
279 .map(|(i, _)| i)
280 .collect();
281
282 let mut queue: VecDeque<usize> = in_degree
284 .iter()
285 .enumerate()
286 .filter(|(_, &d)| d == 0)
287 .map(|(i, _)| i)
288 .collect();
289
290 let mut topo = Vec::with_capacity(num_nodes);
291 let mut indeg = in_degree;
292 while let Some(idx) = queue.pop_front() {
293 topo.push(idx);
294 for &(_, to_n, _) in &out_edges[idx] {
295 indeg[to_n] -= 1;
296 if indeg[to_n] == 0 {
297 queue.push_back(to_n);
298 }
299 }
300 }
301
302 if topo.len() != num_nodes {
303 return Err(BuildError::CycleDetected);
304 }
305
306 let mut nodes: Vec<NodeVariant<T, BUF_SIZE>> =
308 node_entries.into_iter().map(|e| e.node).collect();
309
310 for &(from_n, from_p, to_n, to_p) in &self.signal_edges {
312 if let Some(port) = nodes[from_n].output_port_mut(from_p) {
313 port.add_downstream(to_n, to_p);
314 }
315 let in_ptr: *mut Port<T, BUF_SIZE> = nodes[to_n]
316 .input_port_mut(to_p)
317 .map(|p| p as *mut Port<T, BUF_SIZE>)
318 .unwrap_or(std::ptr::null_mut());
319 let out_ptr: *mut Port<T, BUF_SIZE> = nodes[from_n]
320 .output_port_mut(from_p)
321 .map(|p| p as *mut Port<T, BUF_SIZE>)
322 .unwrap_or(std::ptr::null_mut());
323 if !in_ptr.is_null() && !out_ptr.is_null() {
324 #[allow(unsafe_code)]
325 unsafe {
326 (*out_ptr).add_downstream_input_ptr(in_ptr);
327 }
328 }
329 }
330
331 let has_split_chain = !recording_roots.is_empty() && !playback_roots.is_empty();
335
336 let mut recording_set: HashSet<usize> = HashSet::new();
337 let mut playback_set: HashSet<usize> = HashSet::new();
338
339 if has_split_chain {
340 recording_set = recording_roots.iter().copied().collect();
341 let mut queue: VecDeque<usize> = recording_roots.iter().copied().collect();
342 while let Some(node) = queue.pop_front() {
343 for &(_, to_n, _) in &out_edges[node] {
344 if recording_set.insert(to_n) {
345 queue.push_back(to_n);
346 }
347 }
348 }
349
350 playback_set = playback_roots.iter().copied().collect();
351 let mut queue: VecDeque<usize> = playback_roots.iter().copied().collect();
352 while let Some(node) = queue.pop_front() {
353 for &(_, to_n, _) in &out_edges[node] {
354 if playback_set.insert(to_n) {
355 queue.push_back(to_n);
356 }
357 }
358 }
359
360 for node in recording_set.clone() {
362 if playback_set.contains(&node) && !recording_roots.contains(&node) {
363 recording_set.remove(&node);
364 }
365 }
366 }
367
368 for &(from_n, from_p, to_n, _) in &self.signal_edges {
370 let parent: *mut NodeVariant<T, BUF_SIZE> = &mut nodes[to_n];
371 if let Some(port) = nodes[from_n].output_port_mut(from_p) {
372 if has_split_chain {
373 let same_chain = (recording_set.contains(&from_n)
374 && recording_set.contains(&to_n))
375 || (playback_set.contains(&from_n) && playback_set.contains(&to_n));
376 if !same_chain {
377 continue;
378 }
379 }
380 port.add_downstream_node(parent);
381 }
382 }
383
384 for &(from_n, _from_p, to_n, to_p) in &self.signal_edges {
386 let same_chain = if has_split_chain {
387 (recording_set.contains(&from_n) && recording_set.contains(&to_n))
388 || (playback_set.contains(&from_n) && playback_set.contains(&to_n))
389 } else {
390 true
391 };
392 if same_chain {
393 let src: *mut NodeVariant<T, BUF_SIZE> = &mut nodes[from_n];
394 if let Some(port) = nodes[to_n].input_port_mut(to_p) {
395 port.set_upstream_node(src);
396 }
397 }
398 }
399
400 let mut out_degree: std::collections::HashMap<(usize, usize), usize> =
409 std::collections::HashMap::new();
410 let mut in_degree_port: std::collections::HashMap<(usize, usize), usize> =
411 std::collections::HashMap::new();
412 for &(from_n, from_p, to_n, to_p) in &self.signal_edges {
413 *out_degree.entry((from_n, from_p)).or_insert(0) += 1;
414 *in_degree_port.entry((to_n, to_p)).or_insert(0) += 1;
415 }
416 for &(from_n, from_p, to_n, to_p) in &self.signal_edges {
417 let exclusive = out_degree.get(&(from_n, from_p)) == Some(&1)
418 && in_degree_port.get(&(to_n, to_p)) == Some(&1);
419 let upstream = if exclusive {
420 nodes[from_n]
421 .output_port(from_p)
422 .map(|p| p.buffer() as *const FixedBuffer<T, BUF_SIZE>)
423 } else {
424 None
425 };
426 if let Some(port) = nodes[to_n].input_port_mut(to_p) {
427 port.set_upstream_buffer(upstream);
428 }
429 }
430
431 for &(from_n, from_p, to_n, to_p) in &self.feedback_edges {
433 if let Some(port) = nodes[from_n].output_port_mut(from_p) {
434 port.init_feedback_buffer();
435 port.add_feedback_downstream(to_n, to_p);
436 }
437 if let Some(port) = nodes[to_n].input_port_mut(to_p) {
438 port.init_feedback_buffer();
439 }
440 }
441 for &(from_n, from_p, to_n, to_p) in &self.feedback_edges {
442 let ptr = nodes[to_n]
443 .input_port(to_p)
444 .map(|p| p.feedback_buffer_ptr());
445 if let Some(port) = nodes[from_n].output_port_mut(from_p) {
446 if let Some(p) = ptr {
447 port.add_feedback_ptr(p);
448 }
449 }
450 }
451
452 let mut registry = ResourceRegistry::new();
456 for r in &self.resources {
457 if r.kind == "tape" {
458 if let Some(tape) = TapeLoop::<T>::new(r.capacity) {
459 registry.register_tape(&r.name, tape);
460 }
461 }
462 }
463 for entry in nodes.iter_mut() {
464 entry.resolve_resources(&mut registry);
465 }
466
467 let allocated = self.resources.clone();
468
469 let rec_ptrs: Vec<_> = recording_roots
471 .iter()
472 .map(|&i| &mut nodes[i] as *mut _)
473 .collect();
474
475 let sink_idx = if has_split_chain {
481 topo.iter()
482 .rev()
483 .copied()
484 .find(|&i| matches!(nodes[i], NodeVariant::Sink(_)))
485 .or_else(|| topo.last().copied())
486 } else {
487 None
488 };
489 let sink_ptr = match sink_idx {
490 Some(i) => &mut nodes[i] as *mut _,
491 None => std::ptr::null_mut(),
492 };
493
494 let feedback_ptrs: Vec<*mut NodeVariant<T, BUF_SIZE>> =
502 if has_split_chain && !self.feedback_edges.is_empty() {
503 let mut rev: Vec<Vec<usize>> = vec![Vec::new(); num_nodes];
504 for &(f, _, t, _) in &self.signal_edges {
505 rev[t].push(f);
506 }
507 let mut sink_reachable = vec![false; num_nodes];
509 if let Some(si) = sink_idx {
510 let mut q = VecDeque::new();
511 q.push_back(si);
512 sink_reachable[si] = true;
513 while let Some(n) = q.pop_front() {
514 for &u in &rev[n] {
515 if !sink_reachable[u] {
516 sink_reachable[u] = true;
517 q.push_back(u);
518 }
519 }
520 }
521 }
522 let mut in_branch = vec![false; num_nodes];
524 let mut q = VecDeque::new();
525 for &(from_n, _, _, _) in &self.feedback_edges {
526 if !in_branch[from_n] {
527 in_branch[from_n] = true;
528 q.push_back(from_n);
529 }
530 }
531 while let Some(n) = q.pop_front() {
532 for &u in &rev[n] {
533 if !in_branch[u] {
534 in_branch[u] = true;
535 q.push_back(u);
536 }
537 }
538 }
539 topo.iter()
540 .copied()
541 .filter(|&i| in_branch[i] && !sink_reachable[i] && !recording_set.contains(&i))
542 .map(|i| &mut nodes[i] as *mut _)
543 .collect()
544 } else {
545 Vec::new()
546 };
547
548 let nodes: Rc<UnsafeCell<Vec<NodeVariant<T, BUF_SIZE>>>> = Rc::new(UnsafeCell::new(nodes));
550
551 let pending_params: PendingParams = Rc::new(RefCell::new(Vec::new()));
552
553 let actor = system.spawn("graph", {
554 let n = nodes.clone();
555 let pending = pending_params.clone();
556 #[allow(unsafe_code)]
557 move |msg: CommandEnum| {
558 if let CommandEnum::SetParameter(param) = msg {
559 if param.sample_pos.is_some() {
560 pending.borrow_mut().push(param);
562 return;
563 }
564 let idx = param.port.node_id().inner() as usize;
565 unsafe {
566 let nv = &mut *n.get();
567 if idx < nv.len() {
568 let _ = nv[idx].set_parameter(¶m.parameter, param.value);
569 }
570 }
571 }
572 }
573 });
574
575 let actor_ref = actor.actor_ref();
576
577 Ok(Graph {
578 nodes,
579 topo_order: topo,
580 resources: allocated,
581 current_tick: ClockTick::new(
582 0,
583 BUF_SIZE as u32,
584 self.sample_rate.unwrap_or(44100.0),
585 String::new(),
586 ),
587 recording_roots: recording_roots.clone(),
588 playback_roots: playback_roots.clone(),
589 recording_ptrs: rec_ptrs,
590 sink_ptr,
591 feedback_ptrs,
592 actor: Some(actor),
593 actor_ref,
594 parent_ref: self.parent_ref.clone(),
595 system_clock: None,
596 pending_params,
597 })
598 }
599}
600
601type PendingParams = Rc<RefCell<Vec<SetParameter>>>;
610
611#[allow(unsafe_code)]
619fn apply_due_params<T: Transcendental, const BUF_SIZE: usize>(
620 nodes: &UnsafeCell<Vec<NodeVariant<T, BUF_SIZE>>>,
621 pending: &RefCell<Vec<SetParameter>>,
622 chunk_end: u64,
623) {
624 let mut pend = pending.borrow_mut();
625 if pend.is_empty() {
626 return;
627 }
628 pend.sort_by_key(|p| p.sample_pos.unwrap_or(0));
629 let split = pend.partition_point(|p| p.sample_pos.is_none_or(|sp| sp < chunk_end));
630 if split == 0 {
631 return;
632 }
633 unsafe {
634 let nv = &mut *nodes.get();
635 for p in pend.drain(0..split) {
636 let idx = p.port.node_id().inner() as usize;
637 if idx < nv.len() {
638 let _ = nv[idx].set_parameter(&p.parameter, p.value);
639 }
640 }
641 }
642}
643
644#[cfg(test)]
646type GraphParts<T, const BUF_SIZE: usize> = (Vec<NodeVariant<T, BUF_SIZE>>, Vec<usize>, ClockTick);
647
648pub struct Graph<T: Transcendental, const BUF_SIZE: usize> {
656 nodes: Rc<UnsafeCell<Vec<NodeVariant<T, BUF_SIZE>>>>,
657 topo_order: Vec<usize>,
658 recording_roots: Vec<usize>,
659 playback_roots: Vec<usize>,
660 recording_ptrs: Vec<*mut NodeVariant<T, BUF_SIZE>>,
661 sink_ptr: *mut NodeVariant<T, BUF_SIZE>,
662 feedback_ptrs: Vec<*mut NodeVariant<T, BUF_SIZE>>,
663 current_tick: ClockTick,
664 pub(crate) resources: Vec<GraphResource>,
665 actor: Option<Actor<CommandEnum>>,
666 actor_ref: ActorRef<CommandEnum>,
667 parent_ref: Option<ActorRef<CommandEnum>>,
668 pub system_clock: Option<Arc<SystemClock>>,
671 pending_params: PendingParams,
673}
674
675pub struct ProcessingState<T: Transcendental, const BUF_SIZE: usize> {
683 actor: Actor<CommandEnum>,
684 nodes: Rc<UnsafeCell<Vec<NodeVariant<T, BUF_SIZE>>>>,
685 recording_roots: Vec<usize>,
686 playback_roots: Vec<usize>,
687 recording_ptrs: Vec<*mut NodeVariant<T, BUF_SIZE>>,
688 sink_ptr: *mut NodeVariant<T, BUF_SIZE>,
689 feedback_ptrs: Vec<*mut NodeVariant<T, BUF_SIZE>>,
690 parent_ref: Option<ActorRef<CommandEnum>>,
691 system_clock: Option<Arc<SystemClock>>,
692 sample_rate: f32,
701 pending_params: PendingParams,
703}
704
705impl<T: Transcendental, const BUF_SIZE: usize> ProcessingState<T, BUF_SIZE> {
706 #[allow(unsafe_code)]
712 fn reinit_sample_rate(&mut self, sample_rate: f32) {
713 unsafe {
714 let nv = &mut *self.nodes.get();
715 for node in nv.iter_mut() {
716 node.init(sample_rate);
717 }
718 }
719 self.sample_rate = sample_rate;
720 }
721
722 #[allow(unsafe_code)]
727 pub fn process_block(&mut self, tick: &ClockTick) -> ProcessResult<()> {
728 if tick.sample_rate > 0.0 && (tick.sample_rate - self.sample_rate).abs() > 0.5 {
729 self.reinit_sample_rate(tick.sample_rate);
730 }
731 self.actor.drain();
732 apply_due_params(
733 &self.nodes,
734 &self.pending_params,
735 tick.sample_pos + tick.samples_since_last as u64,
736 );
737 let mut ctx = if let Some(ref clock) = self.system_clock {
738 RenderContext::with_tempo(
739 tick.sample_pos,
740 tick.samples_since_last,
741 tick.sample_rate,
742 clock.bpm() as f32,
743 )
744 } else {
745 RenderContext::new(tick.sample_pos, tick.samples_since_last, tick.sample_rate)
746 };
747 ctx.speed_ratio = tick.speed_ratio;
748 unsafe {
749 let nv = &mut *self.nodes.get();
750 for &root in self
751 .recording_roots
752 .iter()
753 .chain(self.playback_roots.iter())
754 {
755 let _ = nv[root].process_block(&ctx, tick);
756 for po in 0..nv[root].num_signal_outputs() {
757 if let Some(port) = nv[root].output_port(po) {
758 let _ = port.propagate(&ctx, tick);
759 }
760 }
761 }
762 }
763 Ok(())
764 }
765
766 pub fn send_clock_tick(&self, tick: &ClockTick) {
772 if tick.is_final {
773 if let Some(ref parent) = self.parent_ref {
774 parent.send(CommandEnum::ClockTick(tick.clone()));
775 }
776 }
777 }
778
779 #[allow(unsafe_code)]
785 pub fn wire_backends(
786 &mut self,
787 capture: Option<Arc<dyn IoCapture>>,
788 playback: Option<Arc<dyn IoPlayback>>,
789 ) {
790 unsafe {
791 let nv = &mut *self.nodes.get();
792 for node in nv.iter_mut() {
793 if let Some(ref c) = capture {
794 if let NodeVariant::Source(src) = node {
795 src.set_capture(c.clone())
796 }
797 }
798 if let Some(ref p) = playback {
799 if let NodeVariant::Sink(sink) = node {
800 sink.set_playback(p.clone())
801 }
802 }
803 }
804 }
805 }
806
807 pub fn run_with_driver(
812 mut self,
813 driver: Arc<dyn IoDriver>,
814 running: Arc<AtomicBool>,
815 ) -> Result<(), String> {
816 self.actor.drain();
817 let use_split = !self.recording_roots.is_empty() && !self.playback_roots.is_empty();
818 if use_split {
819 let mut actor = self.actor;
820 let rec_ptrs = self.recording_ptrs;
821 let sink = self.sink_ptr;
822 let fb_ptrs = self.feedback_ptrs;
823 let clock = self.system_clock;
824 let parent = self.parent_ref;
825
826 let clock_rec = clock.clone();
827 driver.set_input_process_callback(Box::new(move |tick: &ClockTick| {
828 actor.drain();
829 if let Some(ref c) = clock_rec {
830 let ctx = RenderContext::with_tempo(
831 tick.sample_pos,
832 tick.samples_since_last,
833 tick.sample_rate,
834 c.bpm() as f32,
835 );
836 p_forward(&rec_ptrs, &ctx, tick);
837 } else {
838 let ctx = RenderContext::new(
839 tick.sample_pos,
840 tick.samples_since_last,
841 tick.sample_rate,
842 );
843 p_forward(&rec_ptrs, &ctx, tick);
844 }
845 }));
846 driver.set_process_callback(Box::new(move |tick: &ClockTick| {
847 if let Some(ref c) = clock {
848 let mut ctx = RenderContext::with_tempo(
849 tick.sample_pos,
850 tick.samples_since_last,
851 tick.sample_rate,
852 c.bpm() as f32,
853 );
854 ctx.speed_ratio = tick.speed_ratio;
855 p_pull(sink, &ctx, tick);
856 p_process_branch(&fb_ptrs, &ctx, tick);
857 } else {
858 let mut ctx = RenderContext::new(
859 tick.sample_pos,
860 tick.samples_since_last,
861 tick.sample_rate,
862 );
863 ctx.speed_ratio = tick.speed_ratio;
864 p_pull(sink, &ctx, tick);
865 p_process_branch(&fb_ptrs, &ctx, tick);
866 }
867 if tick.is_final {
868 if let Some(ref p) = parent {
869 p.send(CommandEnum::ClockTick(tick.clone()));
870 }
871 }
872 }));
873 driver.run(running.clone())?;
874 } else {
875 driver.set_process_callback(Box::new(move |tick: &ClockTick| {
876 let _ = self.process_block(tick);
877 self.send_clock_tick(tick);
878 }));
879 driver.run(running.clone())?;
880 }
881 while running.load(std::sync::atomic::Ordering::Acquire) {
882 std::thread::park();
883 }
884 let _ = driver.stop();
885 Ok(())
886 }
887}
888
889#[allow(unsafe_code)]
895fn p_forward<T: Transcendental, const BUF_SIZE: usize>(
896 roots: &[*mut NodeVariant<T, BUF_SIZE>],
897 ctx: &RenderContext,
898 tick: &ClockTick,
899) {
900 for &root in roots {
901 unsafe {
902 let nv = &mut *root;
903 let _ = nv.process_block(ctx, tick);
904 for po in 0..nv.num_signal_outputs() {
905 if let Some(port) = nv.output_port(po) {
906 let _ = port.propagate(ctx, tick);
907 }
908 }
909 }
910 }
911}
912
913#[allow(unsafe_code)]
915fn p_pull<T: Transcendental, const BUF_SIZE: usize>(
916 sink: *mut NodeVariant<T, BUF_SIZE>,
917 ctx: &RenderContext,
918 tick: &ClockTick,
919) {
920 if sink.is_null() {
921 return;
922 }
923 unsafe {
924 p_pull_recurse(&mut *sink, ctx, tick);
925 }
926}
927
928#[allow(unsafe_code)]
929fn p_pull_recurse<T: Transcendental, const BUF_SIZE: usize>(
930 node: &mut NodeVariant<T, BUF_SIZE>,
931 ctx: &RenderContext,
932 tick: &ClockTick,
933) {
934 for pi in 0..node.num_signal_inputs() {
935 if let Some(p) = node.input_port_mut(pi) {
936 p.pre_process();
937 }
938 }
939 for pi in 0..node.num_signal_inputs() {
940 if let Some(p) = node.input_port(pi) {
941 let src = p.upstream_node();
942 if !src.is_null() {
943 unsafe {
944 p_pull_recurse(&mut *src, ctx, tick);
945 }
946 }
947 }
948 }
949 let _ = node.process_block(ctx, tick);
950 for po in 0..node.num_signal_outputs() {
951 if let Some(p) = node.output_port_mut(po) {
952 p.snapshot_feedback();
953 }
954 }
955 for po in 0..node.num_signal_outputs() {
956 if let Some(port) = node.output_port(po) {
957 let buf = port.buffer();
958 for &in_ptr in port.downstream_input_ptrs() {
959 unsafe {
960 let ip = &mut *in_ptr;
961 if !ip.is_zero_copy() {
962 let _ = ip.run_action(Some(buf.as_array()));
963 }
964 ip.set_data_received(true);
965 }
966 }
967 }
968 }
969}
970
971#[allow(unsafe_code)]
980fn p_process_branch<T: Transcendental, const BUF_SIZE: usize>(
981 branch: &[*mut NodeVariant<T, BUF_SIZE>],
982 ctx: &RenderContext,
983 tick: &ClockTick,
984) {
985 for &np in branch {
986 unsafe {
987 let node = &mut *np;
988 for pi in 0..node.num_signal_inputs() {
989 if let Some(p) = node.input_port_mut(pi) {
990 p.pre_process();
991 }
992 }
993 let _ = node.process_block(ctx, tick);
994 for po in 0..node.num_signal_outputs() {
995 if let Some(p) = node.output_port_mut(po) {
996 p.snapshot_feedback();
997 }
998 }
999 for po in 0..node.num_signal_outputs() {
1000 if let Some(port) = node.output_port(po) {
1001 let buf = port.buffer();
1002 for &in_ptr in port.downstream_input_ptrs() {
1003 let ip = &mut *in_ptr;
1004 if !ip.is_zero_copy() {
1005 let _ = ip.run_action(Some(buf.as_array()));
1006 }
1007 ip.set_data_received(true);
1008 }
1009 }
1010 }
1011 }
1012 }
1013}
1014
1015impl<T: Transcendental, const BUF_SIZE: usize> Graph<T, BUF_SIZE> {
1016 #[allow(unsafe_code)]
1022 pub fn nodes(&self) -> &[NodeVariant<T, BUF_SIZE>] {
1023 unsafe { &*self.nodes.get() }
1024 }
1025
1026 pub fn current_tick(&self) -> ClockTick {
1028 self.current_tick.clone()
1029 }
1030
1031 #[allow(unsafe_code)]
1033 pub fn node_count(&self) -> usize {
1034 unsafe { (*self.nodes.get()).len() }
1035 }
1036
1037 pub fn topo_order(&self) -> &[usize] {
1039 &self.topo_order
1040 }
1041
1042 #[allow(dead_code)]
1043 pub(crate) fn sample_rate(&self) -> f32 {
1044 self.current_tick.sample_rate
1045 }
1046
1047 #[allow(dead_code)]
1049 pub fn resources(&self) -> &[GraphResource] {
1050 &self.resources
1051 }
1052
1053 #[allow(unsafe_code)]
1064 pub fn process_block(&mut self, tick: &ClockTick) -> ProcessResult<()> {
1065 if let Some(ref mut actor) = self.actor {
1066 actor.drain();
1067 }
1068 apply_due_params(
1069 &self.nodes,
1070 &self.pending_params,
1071 tick.sample_pos + tick.samples_since_last as u64,
1072 );
1073 let ctx = if let Some(ref clock) = self.system_clock {
1074 RenderContext::with_tempo(
1075 tick.sample_pos,
1076 tick.samples_since_last,
1077 tick.sample_rate,
1078 clock.bpm() as f32,
1079 )
1080 } else {
1081 RenderContext::new(tick.sample_pos, tick.samples_since_last, tick.sample_rate)
1082 };
1083 self.current_tick = tick.clone();
1084 unsafe {
1085 let nv = &mut *self.nodes.get();
1086 for &root in self
1087 .recording_roots
1088 .iter()
1089 .chain(self.playback_roots.iter())
1090 {
1091 let _ = nv[root].process_block(&ctx, tick);
1092 for po in 0..nv[root].num_signal_outputs() {
1093 if let Some(port) = nv[root].output_port(po) {
1094 let _ = port.propagate(&ctx, tick);
1095 }
1096 }
1097 }
1098 }
1099 Ok(())
1100 }
1101
1102 pub fn into_processing_state(mut self) -> ProcessingState<T, BUF_SIZE> {
1108 let actor = self.actor.take().expect("graph actor missing");
1109 ProcessingState {
1110 actor,
1111 nodes: self.nodes,
1112 recording_roots: self.recording_roots,
1113 playback_roots: self.playback_roots,
1114 recording_ptrs: self.recording_ptrs,
1115 sink_ptr: self.sink_ptr,
1116 feedback_ptrs: self.feedback_ptrs,
1117 parent_ref: self.parent_ref,
1118 system_clock: self.system_clock,
1119 sample_rate: self.current_tick.sample_rate,
1120 pending_params: self.pending_params,
1121 }
1122 }
1123
1124 pub fn handle(&self) -> ActorRef<CommandEnum> {
1126 self.actor_ref.clone()
1127 }
1128
1129 #[cfg(test)]
1131 pub fn into_parts(self) -> GraphParts<T, BUF_SIZE> {
1132 let Self {
1133 nodes,
1134 topo_order,
1135 current_tick,
1136 resources: _,
1137 recording_roots: _,
1138 playback_roots: _,
1139 recording_ptrs: _,
1140 sink_ptr: _,
1141 feedback_ptrs: _,
1142 actor,
1143 actor_ref: _,
1144 parent_ref: _,
1145 system_clock: _,
1146 pending_params: _,
1147 } = self;
1148 drop(actor);
1149 let nodes = Rc::try_unwrap(nodes).unwrap().into_inner();
1150 (nodes, topo_order, current_tick)
1151 }
1152}
1153
1154#[cfg(test)]
1155mod tests {
1156 use super::*;
1157 use rill_core::math::Transcendental;
1158 use rill_core::time::RenderContext;
1159
1160 use rill_core::traits::{
1161 Node, NodeCategory, NodeId, NodeMetadata, NodeState, ParamValue, ParameterId, Port,
1162 ProcessResult, Processor, Sink, Source,
1163 };
1164 use rill_core_actor::ActorSystem;
1165 use std::sync::Arc;
1166
1167 fn test_system() -> ActorSystem {
1168 ActorSystem::new()
1169 }
1170
1171 fn test_factory<const B: usize>() -> Arc<NodeFactory<f32, B>> {
1172 let mut f = NodeFactory::<f32, B>::new();
1173
1174 f.register_fn("test/const", |id, params| {
1175 let value = params.get_f32("value", 1.0);
1176 let mut node = ConstantSource::<f32, B>::new(id, value, params.sample_rate);
1177 node.init(params.sample_rate);
1178 NodeVariant::Source(Box::new(node))
1179 });
1180
1181 f.register_fn("test/gain", |id, params| {
1182 let gain = params.get_f32("gain", 1.0);
1183 let mut node = GainProcessor::<f32, B>::new(id, params.sample_rate, gain);
1184 node.init(params.sample_rate);
1185 NodeVariant::Processor(Box::new(node))
1186 });
1187
1188 f.register_fn("test/capture", |id, params| {
1189 let mut node = CaptureSink::<f32, B>::new(id, params.sample_rate);
1190 node.init(params.sample_rate);
1191 NodeVariant::Sink(Box::new(node))
1192 });
1193
1194 Arc::new(f)
1195 }
1196
1197 fn test_builder<const B: usize>(factory: &Arc<NodeFactory<f32, B>>) -> GraphBuilder<f32, B> {
1198 GraphBuilder::new(factory.clone())
1199 }
1200
1201 fn test_params(sample_rate: f32) -> Params {
1202 let mut p = Params::new(sample_rate);
1203 p.insert("value".to_string(), ParamValue::Float(sample_rate));
1204 p
1205 }
1206
1207 pub(crate) struct ConstantSource<T: Transcendental, const B: usize> {
1212 id: NodeId,
1213 value: T,
1214 state: NodeState<T, B>,
1215 output: Port<T, B>,
1216 }
1217
1218 impl<T: Transcendental, const B: usize> ConstantSource<T, B> {
1219 pub fn new(id: NodeId, value: T, sample_rate: f32) -> Self {
1220 let state = NodeState::new(sample_rate);
1221 let output = Port::output(id, 0, "out");
1222 Self {
1223 id,
1224 value,
1225 state,
1226 output,
1227 }
1228 }
1229 }
1230
1231 impl<T: Transcendental, const B: usize> Node<T, B> for ConstantSource<T, B> {
1232 fn id(&self) -> NodeId {
1233 self.id
1234 }
1235 fn set_id(&mut self, id: NodeId) {
1236 self.id = id;
1237 }
1238 fn metadata(&self) -> NodeMetadata {
1239 NodeMetadata {
1240 name: "ConstantSource".into(),
1241 type_name: Some("test/const".into()),
1242 category: NodeCategory::Source,
1243 description: String::new(),
1244 author: String::new(),
1245 version: String::new(),
1246 parameters: vec![],
1247 signal_inputs: 0,
1248 signal_outputs: 1,
1249 control_inputs: 0,
1250 control_outputs: 0,
1251 clock_inputs: 0,
1252 clock_outputs: 0,
1253 feedback_ports: 0,
1254 }
1255 }
1256 fn init(&mut self, _: f32) {}
1257 fn reset(&mut self) {}
1258 fn get_parameter(&self, _: &ParameterId) -> Option<ParamValue> {
1259 None
1260 }
1261 fn set_parameter(&mut self, _: &ParameterId, _: ParamValue) -> ProcessResult<()> {
1262 Ok(())
1263 }
1264 fn control_port(&self, _: usize) -> Option<&Port<T, B>> {
1265 None
1266 }
1267 fn control_port_mut(&mut self, _: usize) -> Option<&mut Port<T, B>> {
1268 None
1269 }
1270 fn output_port(&self, i: usize) -> Option<&Port<T, B>> {
1271 if i == 0 {
1272 Some(&self.output)
1273 } else {
1274 None
1275 }
1276 }
1277 fn output_port_mut(&mut self, i: usize) -> Option<&mut Port<T, B>> {
1278 if i == 0 {
1279 Some(&mut self.output)
1280 } else {
1281 None
1282 }
1283 }
1284 fn num_signal_outputs(&self) -> usize {
1285 1
1286 }
1287 fn input_port(&self, _: usize) -> Option<&Port<T, B>> {
1288 None
1289 }
1290 fn input_port_mut(&mut self, _: usize) -> Option<&mut Port<T, B>> {
1291 None
1292 }
1293 fn state(&self) -> &NodeState<T, B> {
1294 &self.state
1295 }
1296 fn state_mut(&mut self) -> &mut NodeState<T, B> {
1297 &mut self.state
1298 }
1299 }
1300
1301 impl<T: Transcendental, const B: usize> Source<T, B> for ConstantSource<T, B> {
1302 fn generate(
1303 &mut self,
1304 _: &RenderContext,
1305 _: &[T],
1306 _: &[RenderContext],
1307 _: &ClockTick,
1308 ) -> ProcessResult<()> {
1309 self.output.write().fill(self.value);
1310 Ok(())
1311 }
1312 }
1313
1314 pub(crate) struct GainProcessor<T: Transcendental, const B: usize> {
1319 id: NodeId,
1320 gain: T,
1321 state: NodeState<T, B>,
1322 input: Port<T, B>,
1323 output: Port<T, B>,
1324 }
1325
1326 impl<T: Transcendental, const B: usize> GainProcessor<T, B> {
1327 pub fn new(id: NodeId, sample_rate: f32, gain: T) -> Self {
1328 let state = NodeState::new(sample_rate);
1329 let input = Port::input(id, 0, "in");
1330 let output = Port::output(id, 0, "out");
1331 Self {
1332 id,
1333 gain,
1334 state,
1335 input,
1336 output,
1337 }
1338 }
1339 }
1340
1341 impl<T: Transcendental, const B: usize> Node<T, B> for GainProcessor<T, B> {
1342 fn id(&self) -> NodeId {
1343 self.id
1344 }
1345 fn set_id(&mut self, id: NodeId) {
1346 self.id = id;
1347 }
1348 fn metadata(&self) -> NodeMetadata {
1349 NodeMetadata {
1350 name: "GainProcessor".into(),
1351 type_name: Some("test/gain".into()),
1352 category: NodeCategory::Processor,
1353 description: String::new(),
1354 author: String::new(),
1355 version: String::new(),
1356 parameters: vec![],
1357 signal_inputs: 1,
1358 signal_outputs: 1,
1359 control_inputs: 0,
1360 control_outputs: 0,
1361 clock_inputs: 0,
1362 clock_outputs: 0,
1363 feedback_ports: 0,
1364 }
1365 }
1366 fn init(&mut self, _: f32) {}
1367 fn reset(&mut self) {}
1368 fn get_parameter(&self, _: &ParameterId) -> Option<ParamValue> {
1369 None
1370 }
1371 fn set_parameter(&mut self, _: &ParameterId, _: ParamValue) -> ProcessResult<()> {
1372 Ok(())
1373 }
1374 fn control_port(&self, _: usize) -> Option<&Port<T, B>> {
1375 None
1376 }
1377 fn control_port_mut(&mut self, _: usize) -> Option<&mut Port<T, B>> {
1378 None
1379 }
1380 fn input_port(&self, i: usize) -> Option<&Port<T, B>> {
1381 if i == 0 {
1382 Some(&self.input)
1383 } else {
1384 None
1385 }
1386 }
1387 fn input_port_mut(&mut self, i: usize) -> Option<&mut Port<T, B>> {
1388 if i == 0 {
1389 Some(&mut self.input)
1390 } else {
1391 None
1392 }
1393 }
1394 fn num_signal_outputs(&self) -> usize {
1395 1
1396 }
1397 fn num_signal_inputs(&self) -> usize {
1398 1
1399 }
1400 fn output_port(&self, i: usize) -> Option<&Port<T, B>> {
1401 if i == 0 {
1402 Some(&self.output)
1403 } else {
1404 None
1405 }
1406 }
1407 fn output_port_mut(&mut self, i: usize) -> Option<&mut Port<T, B>> {
1408 if i == 0 {
1409 Some(&mut self.output)
1410 } else {
1411 None
1412 }
1413 }
1414 fn state(&self) -> &NodeState<T, B> {
1415 &self.state
1416 }
1417 fn state_mut(&mut self) -> &mut NodeState<T, B> {
1418 &mut self.state
1419 }
1420 }
1421
1422 impl<T: Transcendental, const B: usize> Processor<T, B> for GainProcessor<T, B> {
1423 fn process(
1424 &mut self,
1425 _: &RenderContext,
1426 _: &[&[T; B]],
1427 _: &[T],
1428 _: &[RenderContext],
1429 _: &[&[T; B]],
1430 ) -> ProcessResult<()> {
1431 let src = self.input.read();
1432 let buf = self.output.write();
1433 for i in 0..B {
1434 buf[i] = src[i] * self.gain;
1435 }
1436 Ok(())
1437 }
1438 }
1439
1440 pub(crate) struct CaptureSink<T: Transcendental, const B: usize> {
1445 id: NodeId,
1446 state: NodeState<T, B>,
1447 input: Port<T, B>,
1448 }
1449
1450 impl<T: Transcendental, const B: usize> CaptureSink<T, B> {
1451 pub fn new(id: NodeId, sample_rate: f32) -> Self {
1452 let state = NodeState::new(sample_rate);
1453 let input = Port::input(id, 0, "in");
1454 Self { id, state, input }
1455 }
1456 }
1457
1458 impl<T: Transcendental, const B: usize> Node<T, B> for CaptureSink<T, B> {
1459 fn id(&self) -> NodeId {
1460 self.id
1461 }
1462 fn set_id(&mut self, id: NodeId) {
1463 self.id = id;
1464 }
1465 fn metadata(&self) -> NodeMetadata {
1466 NodeMetadata {
1467 name: "CaptureSink".into(),
1468 type_name: Some("test/capture".into()),
1469 category: NodeCategory::Sink,
1470 description: String::new(),
1471 author: String::new(),
1472 version: String::new(),
1473 parameters: vec![],
1474 signal_inputs: 1,
1475 signal_outputs: 0,
1476 control_inputs: 0,
1477 control_outputs: 0,
1478 clock_inputs: 0,
1479 clock_outputs: 0,
1480 feedback_ports: 0,
1481 }
1482 }
1483 fn init(&mut self, _: f32) {}
1484 fn reset(&mut self) {}
1485 fn get_parameter(&self, _: &ParameterId) -> Option<ParamValue> {
1486 None
1487 }
1488 fn set_parameter(&mut self, _: &ParameterId, _: ParamValue) -> ProcessResult<()> {
1489 Ok(())
1490 }
1491 fn control_port(&self, _: usize) -> Option<&Port<T, B>> {
1492 None
1493 }
1494 fn control_port_mut(&mut self, _: usize) -> Option<&mut Port<T, B>> {
1495 None
1496 }
1497 fn output_port(&self, _: usize) -> Option<&Port<T, B>> {
1498 None
1499 }
1500 fn output_port_mut(&mut self, _: usize) -> Option<&mut Port<T, B>> {
1501 None
1502 }
1503 fn input_port(&self, i: usize) -> Option<&Port<T, B>> {
1504 if i == 0 {
1505 Some(&self.input)
1506 } else {
1507 None
1508 }
1509 }
1510 fn input_port_mut(&mut self, i: usize) -> Option<&mut Port<T, B>> {
1511 if i == 0 {
1512 Some(&mut self.input)
1513 } else {
1514 None
1515 }
1516 }
1517 fn num_signal_inputs(&self) -> usize {
1518 1
1519 }
1520 fn state(&self) -> &NodeState<T, B> {
1521 &self.state
1522 }
1523 fn state_mut(&mut self) -> &mut NodeState<T, B> {
1524 &mut self.state
1525 }
1526 }
1527
1528 impl<T: Transcendental, const B: usize> Sink<T, B> for CaptureSink<T, B> {
1529 fn consume(
1530 &mut self,
1531 _: &RenderContext,
1532 _: &[&[T; B]],
1533 _: &[T],
1534 _: &[RenderContext],
1535 _: &[&[T; B]],
1536 _: &ClockTick,
1537 ) -> ProcessResult<()> {
1538 Ok(())
1539 }
1540 }
1541
1542 const BUF: usize = 64;
1547
1548 #[test]
1549 fn test_fanout_branches_are_independent_not_zero_copy() {
1550 let factory = test_factory::<BUF>();
1553 let mut builder = test_builder::<BUF>(&factory);
1554 let system = test_system();
1555
1556 let src = builder.add_node("test/const", &test_params(44100.0));
1557 let a = builder.add_node("test/gain", &test_params(44100.0));
1558 let b = builder.add_node("test/gain", &test_params(44100.0));
1559 builder.connect_signal(src, 0, a, 0);
1560 builder.connect_signal(src, 0, b, 0);
1561
1562 let graph = builder.build(&system).unwrap();
1563 let nodes = graph.nodes();
1564 assert!(
1565 !nodes[a].input_port(0).unwrap().is_zero_copy(),
1566 "fan-out branch A must not alias the shared source buffer"
1567 );
1568 assert!(
1569 !nodes[b].input_port(0).unwrap().is_zero_copy(),
1570 "fan-out branch B must not alias the shared source buffer"
1571 );
1572 assert!(!nodes[a].input_port(0).unwrap().has_upstream_buffer());
1573 assert!(!nodes[b].input_port(0).unwrap().has_upstream_buffer());
1574 }
1575
1576 #[test]
1577 fn test_linear_chain_edge_is_zero_copy() {
1578 let factory = test_factory::<BUF>();
1580 let mut builder = test_builder::<BUF>(&factory);
1581 let system = test_system();
1582
1583 let src = builder.add_node("test/const", &test_params(44100.0));
1584 let g = builder.add_node("test/gain", &test_params(44100.0));
1585 builder.connect_signal(src, 0, g, 0);
1586
1587 let graph = builder.build(&system).unwrap();
1588 let nodes = graph.nodes();
1589 assert!(
1590 nodes[g].input_port(0).unwrap().is_zero_copy(),
1591 "exclusive 1:1 edge should be zero-copy"
1592 );
1593 }
1594
1595 #[test]
1596 #[allow(unsafe_code)]
1597 fn test_graph_source_to_sink() {
1598 let factory = test_factory::<BUF>();
1599 let mut builder = test_builder::<BUF>(&factory);
1600 let system = test_system();
1601
1602 let src_idx = builder.add_node("test/const", &test_params(44100.0));
1603 let snk_idx = builder.add_node("test/capture", &test_params(44100.0));
1604 builder.connect_signal(src_idx, 0, snk_idx, 0);
1605
1606 let graph = builder.build(&system).unwrap();
1607 let source_idx = graph
1608 .recording_roots
1609 .first()
1610 .or(graph.playback_roots.first())
1611 .copied()
1612 .unwrap_or(0);
1613
1614 let ctx = RenderContext::new(0, BUF as u32, 44100.0);
1615 let tick = ClockTick::new(0, BUF as u32, 44100.0, String::new());
1616 let nodes = graph.nodes.clone();
1617 unsafe {
1618 let nv = &mut *nodes.get();
1619 nv[source_idx].process_block(&ctx, &tick).unwrap();
1620 if let Some(port) = nv[source_idx].output_port(0) {
1621 port.propagate(&ctx, &tick).unwrap();
1622 }
1623 }
1624 unsafe {
1625 let nv = &*nodes.get();
1626 let val = nv[snk_idx]
1627 .input_port(0)
1628 .unwrap()
1629 .signal_buffer()
1630 .as_array()[0];
1631 assert!(val != 0.0, "signal should have propagated, got {}", val);
1632 }
1633 }
1634
1635 #[test]
1636 #[allow(unsafe_code)]
1637 fn test_graph_source_proc_sink() {
1638 let factory = test_factory::<BUF>();
1639 let mut builder = test_builder::<BUF>(&factory);
1640 let system = test_system();
1641
1642 let mut params = test_params(44100.0);
1643 params.insert("value".to_string(), ParamValue::Float(5.0));
1644 let src_idx = builder.add_node("test/const", ¶ms);
1645
1646 let mut gain_params = test_params(44100.0);
1647 gain_params.insert("gain".to_string(), ParamValue::Float(3.0));
1648 let proc_idx = builder.add_node("test/gain", &gain_params);
1649
1650 let snk_idx = builder.add_node("test/capture", &test_params(44100.0));
1651
1652 builder.connect_signal(src_idx, 0, proc_idx, 0);
1653 builder.connect_signal(proc_idx, 0, snk_idx, 0);
1654
1655 let graph = builder.build(&system).unwrap();
1656 let source_idx = graph
1657 .recording_roots
1658 .first()
1659 .or(graph.playback_roots.first())
1660 .copied()
1661 .unwrap_or(0);
1662
1663 eprintln!("topo: {:?}", graph.topo_order);
1664 eprintln!("source_idx: {source_idx}, src_idx: {src_idx}, proc_idx: {proc_idx}, snk_idx: {snk_idx}");
1665
1666 let ctx = RenderContext::new(0, BUF as u32, 44100.0);
1667 let tick = ClockTick::new(0, BUF as u32, 44100.0, String::new());
1668 let nodes = graph.nodes.clone();
1669 unsafe {
1670 let nv = &mut *nodes.get();
1671 eprintln!(
1672 "node types: src={:?}, proc={:?}, snk={:?}",
1673 std::mem::discriminant(&nv[0]),
1674 std::mem::discriminant(&nv[1]),
1675 std::mem::discriminant(&nv[2]),
1676 );
1677
1678 let _ = nv[source_idx].process_block(&ctx, &tick);
1679 let src_val = nv[source_idx].output_port(0).unwrap().read()[0];
1680 eprintln!("source output: {src_val}");
1681
1682 let out_port = nv[source_idx].output_port(0).unwrap();
1683 eprintln!(
1684 "source output port downstream_nodes: {}",
1685 out_port.downstream_nodes().len()
1686 );
1687 eprintln!(
1688 "source output port downstream_input_ptrs: {}",
1689 out_port.downstream_input_ptrs().len()
1690 );
1691
1692 {
1694 let proc_port = nv[proc_idx].output_port(0).unwrap();
1695 eprintln!(
1696 "PROC OUT port downstream_nodes: {}",
1697 proc_port.downstream_nodes().len()
1698 );
1699 eprintln!(
1700 "PROC OUT port downstream_input_ptrs: {}",
1701 proc_port.downstream_input_ptrs().len()
1702 );
1703 for (i, &dn) in proc_port.downstream().iter().enumerate() {
1704 eprintln!(" downstream[{}]: (node={}, port={})", i, dn.0, dn.1);
1705 }
1706 }
1707
1708 let src_out = nv[source_idx].output_port(0).unwrap();
1710 let proc_in = nv[proc_idx].input_port(0).unwrap();
1711 let proc_out = nv[proc_idx].output_port(0).unwrap();
1712 let snk_in = nv[snk_idx].input_port(0).unwrap();
1713 eprintln!("BUFFER ADDRESSES:");
1714 eprintln!(" src output buf: {:p}", src_out.read().as_ptr());
1715 eprintln!(" proc input buf: {:p}", proc_in.read().as_ptr());
1716 eprintln!(" proc output buf: {:p}", proc_out.read().as_ptr());
1717 eprintln!(" snk input buf: {:p}", snk_in.read().as_ptr());
1718 eprintln!(
1719 " proc_in.has_upstream_buffer(): {}",
1720 proc_in.has_upstream_buffer()
1721 );
1722 eprintln!(
1723 " snk_in.has_upstream_buffer(): {}",
1724 snk_in.has_upstream_buffer()
1725 );
1726 out_port.propagate(&ctx, &tick).unwrap();
1729
1730 {
1732 let nv = &*nodes.get();
1733 let snk_in = nv[snk_idx].input_port(0).unwrap();
1734 eprintln!("AFTER propagate - snk input buf[0]: {}", snk_in.read()[0]);
1735 }
1736
1737 let sink_buf = nv[snk_idx]
1738 .input_port(0)
1739 .unwrap()
1740 .signal_buffer()
1741 .as_array();
1742 eprintln!("SINK input port buffer first sample: {}", sink_buf[0]);
1743
1744 let proc_out_port = nv[proc_idx].output_port(0).unwrap();
1746 eprintln!(
1747 "proc output port downstream_nodes: {}",
1748 proc_out_port.downstream_nodes().len()
1749 );
1750 eprintln!(
1751 "proc output port downstream_input_ptrs: {}",
1752 proc_out_port.downstream_input_ptrs().len()
1753 );
1754
1755 let sink_val = nv[snk_idx]
1757 .input_port(0)
1758 .unwrap()
1759 .signal_buffer()
1760 .as_array()[0];
1761 eprintln!("sink input AFTER propagate: {sink_val}");
1762
1763 assert!(
1764 (sink_val - 15.0).abs() < 1e-4,
1765 "expected 15.0, got {}",
1766 sink_val
1767 );
1768 }
1769 }
1770
1771 #[test]
1776 #[allow(unsafe_code)]
1777 fn test_split_processes_feedback_branch() {
1778 let mut f = NodeFactory::<f32, BUF>::new();
1779 f.register_fn("rill/input", |id, params| {
1780 let mut n = ConstantSource::<f32, BUF>::new(id, 0.0, params.sample_rate);
1781 n.init(params.sample_rate);
1782 NodeVariant::Source(Box::new(n))
1783 });
1784 f.register_fn("test/const", |id, params| {
1785 let v = params.get_f32("value", 1.0);
1786 let mut n = ConstantSource::<f32, BUF>::new(id, v, params.sample_rate);
1787 n.init(params.sample_rate);
1788 NodeVariant::Source(Box::new(n))
1789 });
1790 f.register_fn("test/gain", |id, params| {
1791 let g = params.get_f32("gain", 1.0);
1792 let mut n = GainProcessor::<f32, BUF>::new(id, params.sample_rate, g);
1793 n.init(params.sample_rate);
1794 NodeVariant::Processor(Box::new(n))
1795 });
1796 f.register_fn("test/capture", |id, params| {
1797 let mut n = CaptureSink::<f32, BUF>::new(id, params.sample_rate);
1798 n.init(params.sample_rate);
1799 NodeVariant::Sink(Box::new(n))
1800 });
1801 let factory = Arc::new(f);
1802 let mut builder = test_builder::<BUF>(&factory);
1803 let system = test_system();
1804
1805 let rec_in = builder.add_node("rill/input", &test_params(44100.0)); let mut cparams = test_params(44100.0);
1807 cparams.insert("value", ParamValue::Float(2.0));
1808 let play = builder.add_node("test/const", &cparams); let sink = builder.add_node("test/capture", &test_params(44100.0)); let branch = builder.add_node("test/gain", &test_params(44100.0)); let rec_proc = builder.add_node("test/gain", &test_params(44100.0)); builder.connect_signal(play, 0, sink, 0); builder.connect_signal(play, 0, branch, 0); builder.connect_signal(rec_in, 0, rec_proc, 0); builder.connect_feedback(branch, 0, rec_proc, 0); let graph = builder.build(&system).unwrap();
1819 assert_eq!(
1820 graph.feedback_ptrs.len(),
1821 1,
1822 "the feedback-branch node must be detected at build time"
1823 );
1824
1825 let ctx = RenderContext::new(0, BUF as u32, 44100.0);
1826 for i in 0..3u64 {
1827 let tick = ClockTick::new(i * BUF as u64, BUF as u32, 44100.0, String::new());
1828 p_forward(&graph.recording_ptrs, &ctx, &tick);
1829 p_pull(graph.sink_ptr, &ctx, &tick);
1830 p_process_branch(&graph.feedback_ptrs, &ctx, &tick);
1831 }
1832
1833 unsafe {
1834 let nv = &*graph.nodes.get();
1835 let branch_out = nv[branch].output_port(0).unwrap().read()[0];
1836 assert!(
1837 (branch_out - 2.0).abs() < 1e-4,
1838 "feedback-branch node was not processed (out={branch_out}, expected 2.0)"
1839 );
1840 }
1841 }
1842}