1use crate::audio::io::AudioIO;
24use crate::connectable::{ConnectableConnection, ConnectableRef};
25use crate::message::{PluginKind, ProcessTask};
26use crate::state::{StateSnapshot, TrackHandle};
27#[cfg(test)]
28use crate::track::Track;
29use crate::track::TrackData;
30use std::cell::UnsafeCell;
31use std::collections::{HashMap, HashSet, VecDeque};
32use std::sync::Arc;
33use std::sync::atomic::{AtomicUsize, Ordering};
34
35pub type SharedPlan = Arc<basedrop::Owned<RenderPlan>>;
42pub type PlanSlot = arc_swap::ArcSwap<basedrop::Owned<RenderPlan>>;
45
46pub type BufferId = u32;
48pub type NodeId = u32;
50
51#[derive(Debug)]
53pub enum Op {
54 Zero { output: BufferId },
56 Sum {
58 inputs: Vec<BufferId>,
59 delays: Vec<UnsafeCell<DelayLine>>,
60 output: BufferId,
61 },
62 Task {
67 task: ProcessTask,
68 ins: Vec<BufferId>,
69 outs: Vec<BufferId>,
70 },
71 HwInput { channel: usize, output: BufferId },
75}
76
77#[derive(Debug)]
79pub struct RenderPlan {
80 pub buffer_size: usize,
82 pub buffers: Vec<UnsafeCell<Vec<f32>>>,
92 pub buffer_latencies: Vec<AtomicUsize>,
93 pub nodes: Vec<Op>,
95 pub indegree: Vec<u32>,
97 pub dependents: Vec<Vec<NodeId>>,
99 pub sources: Vec<NodeId>,
101 pub hw_in_map: Vec<(usize, BufferId)>,
103 pub hw_out_map: Vec<(BufferId, usize)>,
105 pub port_map: HashMap<usize, BufferId>,
108 pub midi_edges: Vec<(NodeId, NodeId)>,
113 pub forced: Vec<NodeId>,
117}
118
119unsafe impl Sync for RenderPlan {}
127
128#[derive(Clone, Debug)]
129pub struct DelayLine {
130 buffer: Vec<f32>,
131 pos: usize,
132 delay: usize,
133}
134
135impl DelayLine {
136 pub fn new() -> Self {
137 Self {
138 buffer: Vec::new(),
139 pos: 0,
140 delay: 0,
141 }
142 }
143
144 pub fn process(&mut self, input: &[f32], delay: usize, output: &mut [f32], add: bool) {
145 if self.delay != delay || self.buffer.len() != delay {
146 self.process_with_transition(input, delay, output, add);
147 return;
148 }
149 self.process_direct(input, delay, output, add);
150 }
151
152 fn process_with_transition(
153 &mut self,
154 input: &[f32],
155 delay: usize,
156 output: &mut [f32],
157 add: bool,
158 ) {
159 let mut old_line = self.clone();
160 let mut old_output = vec![0.0; output.len()];
161 old_line.process_direct(input, self.delay, &mut old_output, false);
162
163 self.reset(delay);
164 let mut new_output = vec![0.0; output.len()];
165 self.process_direct(input, delay, &mut new_output, false);
166
167 let frames = output.len().min(input.len());
168 let fade_frames = frames.clamp(1, 128);
169 for frame in 0..frames {
170 let t = ((frame + 1) as f32 / fade_frames as f32).clamp(0.0, 1.0);
171 let sample = old_output[frame] * (1.0 - t) + new_output[frame] * t;
172 if add {
173 output[frame] += sample;
174 } else {
175 output[frame] = sample;
176 }
177 }
178 if !add && frames < output.len() {
179 output[frames..].fill(0.0);
180 }
181 }
182
183 fn reset(&mut self, delay: usize) {
184 self.delay = delay;
185 self.pos = 0;
186 self.buffer.resize(delay, 0.0);
187 self.buffer.fill(0.0);
188 }
189
190 fn process_direct(&mut self, input: &[f32], delay: usize, output: &mut [f32], add: bool) {
191 if delay == 0 {
192 if add {
193 crate::simd::add_sanitized_inplace(output, input);
194 } else {
195 crate::simd::copy_sanitized_inplace(output, input);
196 if input.len() < output.len() {
197 output[input.len()..].fill(0.0);
198 }
199 }
200 self.delay = 0;
201 self.buffer.clear();
202 self.pos = 0;
203 return;
204 }
205
206 let frames = output.len().min(input.len());
207 for frame in 0..frames {
208 let delayed = self.buffer[self.pos];
209 self.buffer[self.pos] = input[frame];
210 self.pos += 1;
211 if self.pos == self.buffer.len() {
212 self.pos = 0;
213 }
214 let delayed = if delayed.is_finite() { delayed } else { 0.0 };
215 if add {
216 output[frame] += delayed;
217 } else {
218 output[frame] = delayed;
219 }
220 }
221 if !add && frames < output.len() {
222 output[frames..].fill(0.0);
223 }
224 }
225}
226
227impl Default for DelayLine {
228 fn default() -> Self {
229 Self::new()
230 }
231}
232
233impl RenderPlan {
234 pub unsafe fn buffer_ptr(&self, id: BufferId) -> *mut Vec<f32> {
247 self.buffers[id as usize].get()
248 }
249
250 pub unsafe fn buffer(&self, id: BufferId) -> &[f32] {
256 unsafe { &*self.buffers[id as usize].get() }
257 }
258
259 pub fn buffer_latency(&self, id: BufferId) -> usize {
260 self.buffer_latencies[id as usize].load(Ordering::Acquire)
261 }
262
263 pub fn set_buffer_latency(&self, id: BufferId, latency: usize) {
264 self.buffer_latencies[id as usize].store(latency, Ordering::Release);
265 }
266
267 pub fn buffer_count(&self) -> usize {
269 self.buffers.len()
270 }
271 pub fn compile(
278 state: &StateSnapshot,
279 hw_inputs: &[Arc<AudioIO>],
280 hw_outputs: &[Arc<AudioIO>],
281 buffer_size: usize,
282 ) -> Self {
283 let mut b = Builder::new(buffer_size);
284 b.add_hw(hw_inputs, hw_outputs);
285
286 let mut ordered: Vec<(String, TrackHandle)> = state
287 .tracks
288 .iter()
289 .map(|(name, track)| (name.clone(), track.clone()))
290 .collect();
291 ordered.sort_by(|a, b| a.0.cmp(&b.0));
292
293 for (_name, track) in &ordered {
294 if track.lock().parent_track.is_some() {
295 continue;
296 }
297 b.append_track(track.clone(), None);
298 }
299
300 b.finish()
301 }
302
303 pub fn verify(&self) -> Result<(), String> {
306 let forced: HashSet<NodeId> = self.forced.iter().copied().collect();
307
308 for (from, dependents) in self.dependents.iter().enumerate() {
310 for &to in dependents {
311 if from as NodeId >= to
312 && !(forced.contains(&(from as NodeId)) && forced.contains(&to))
313 {
314 return Err(format!("edge {from} -> {to} violates topological order"));
315 }
316 }
317 }
318
319 for &(from, to) in &self.midi_edges {
321 if from >= to && !(forced.contains(&from) && forced.contains(&to)) {
322 return Err(format!(
323 "midi edge {from} -> {to} violates topological order"
324 ));
325 }
326 }
327
328 let mut writers: HashMap<BufferId, Vec<NodeId>> = HashMap::new();
332 for (idx, op) in self.nodes.iter().enumerate() {
333 let idx = idx as NodeId;
334 match op {
335 Op::Zero { output } | Op::Sum { output, .. } | Op::HwInput { output, .. } => {
336 writers.entry(*output).or_default().push(idx);
337 }
338 Op::Task { task, ins, outs } => {
339 let writes_ins =
340 matches!(task, ProcessTask::Track(_) | ProcessTask::FolderInput(_));
341 for b in outs {
342 writers.entry(*b).or_default().push(idx);
343 }
344 if writes_ins {
345 for b in ins {
346 writers.entry(*b).or_default().push(idx);
347 }
348 }
349 }
350 }
351 }
352
353 for buffer in 0..self.buffers.len() as BufferId {
354 let ws = writers.get(&buffer).cloned().unwrap_or_default();
355 if ws.is_empty() {
356 return Err(format!("buffer {buffer} has no writer"));
357 }
358 let mut sorted = ws;
361 sorted.sort_unstable();
362 for pair in sorted.windows(2) {
363 if !self.reachable(pair[0], pair[1]) {
364 return Err(format!(
365 "buffer {buffer} written by unordered nodes {} ({:?}) and {} ({:?})",
366 pair[0],
367 self.nodes[pair[0] as usize],
368 pair[1],
369 self.nodes[pair[1] as usize]
370 ));
371 }
372 }
373 }
374 Ok(())
375 }
376
377 fn reachable(&self, from: NodeId, to: NodeId) -> bool {
379 if from == to {
380 return true;
381 }
382 let mut seen = HashSet::new();
383 let mut queue = VecDeque::from([from]);
384 seen.insert(from);
385 while let Some(n) = queue.pop_front() {
386 for &d in &self.dependents[n as usize] {
387 if d == to {
388 return true;
389 }
390 if seen.insert(d) {
391 queue.push_back(d);
392 }
393 }
394 }
395 false
396 }
397}
398
399struct Builder {
401 buffer_size: usize,
402 buffers: Vec<UnsafeCell<Vec<f32>>>,
403 port_map: HashMap<usize, BufferId>,
404 nodes: Vec<Op>,
405 edges: HashSet<(NodeId, NodeId)>,
406 consumer_ports: Vec<Arc<AudioIO>>,
408 port_readers: HashMap<BufferId, Vec<NodeId>>,
410 port_inplace_writers: HashMap<BufferId, Vec<NodeId>>,
412 producer: HashMap<BufferId, NodeId>,
414 hw_in_map: Vec<(usize, BufferId)>,
415 hw_out_map: Vec<(BufferId, usize)>,
416 midi_writers: HashMap<usize, NodeId>,
419 midi_readers: HashMap<usize, NodeId>,
421 midi_ports: Vec<Arc<crate::midi::io::MIDIIO>>,
423 midi_edges: Vec<(NodeId, NodeId)>,
424}
425
426impl Builder {
427 fn new(buffer_size: usize) -> Self {
428 Self {
429 buffer_size,
430 buffers: Vec::new(),
431 port_map: HashMap::new(),
432 nodes: Vec::new(),
433 edges: HashSet::new(),
434 consumer_ports: Vec::new(),
435 port_readers: HashMap::new(),
436 port_inplace_writers: HashMap::new(),
437 producer: HashMap::new(),
438 hw_in_map: Vec::new(),
439 hw_out_map: Vec::new(),
440 midi_writers: HashMap::new(),
441 midi_readers: HashMap::new(),
442 midi_ports: Vec::new(),
443 midi_edges: Vec::new(),
444 }
445 }
446
447 fn register_midi_track_ports(&mut self, t: &TrackData, first: NodeId, last: NodeId) {
451 for p in &t.midi.ins {
452 let key = Arc::as_ptr(p) as usize;
453 self.midi_writers.insert(key, first);
454 self.midi_readers.insert(key, first);
455 self.midi_ports.push(p.clone());
456 }
457 for p in &t.midi.outs {
458 let key = Arc::as_ptr(p) as usize;
459 self.midi_writers.insert(key, last);
460 self.midi_readers.insert(key, last);
461 self.midi_ports.push(p.clone());
462 }
463 }
464
465 fn register_plugin_midi_ports(
468 &mut self,
469 t: &TrackData,
470 kind: PluginKind,
471 index: usize,
472 node: NodeId,
473 ) {
474 let (midi_ins, midi_outs): (
475 Vec<Arc<crate::midi::io::MIDIIO>>,
476 Vec<Arc<crate::midi::io::MIDIIO>>,
477 ) = match kind {
478 PluginKind::Clap => {
479 let proc = t.clap_plugins[index].processor.clone();
480 (
481 proc.midi_input_ports().to_vec(),
482 proc.midi_output_ports().to_vec(),
483 )
484 }
485 PluginKind::Vst3 => {
486 let proc = t.vst3_plugins[index].processor.clone();
487 (
488 proc.midi_input_ports().to_vec(),
489 proc.midi_output_ports().to_vec(),
490 )
491 }
492 #[cfg(unix)]
493 PluginKind::Lv2 => {
494 let proc = t.lv2_plugins[index].processor.clone();
495 (
496 proc.midi_input_ports().to_vec(),
497 proc.midi_output_ports().to_vec(),
498 )
499 }
500 };
501 for p in midi_ins {
502 let key = Arc::as_ptr(&p) as usize;
503 self.midi_writers.insert(key, node);
504 self.midi_readers.insert(key, node);
505 self.midi_ports.push(p);
506 }
507 for p in midi_outs {
508 let key = Arc::as_ptr(&p) as usize;
509 self.midi_writers.insert(key, node);
510 self.midi_ports.push(p);
511 }
512 }
513
514 fn buffer_for(&mut self, port: &Arc<AudioIO>) -> BufferId {
516 let key = Arc::as_ptr(port) as usize;
517 if let Some(&id) = self.port_map.get(&key) {
518 return id;
519 }
520 let id = self.buffers.len() as BufferId;
521 self.buffers
522 .push(UnsafeCell::new(vec![0.0; self.buffer_size]));
523 self.port_map.insert(key, id);
524 id
525 }
526
527 fn push_node(&mut self, op: Op) -> NodeId {
528 self.nodes.push(op);
529 (self.nodes.len() - 1) as NodeId
530 }
531
532 fn add_hw(&mut self, hw_inputs: &[Arc<AudioIO>], hw_outputs: &[Arc<AudioIO>]) {
533 for (channel, port) in hw_inputs.iter().enumerate() {
534 let output = self.buffer_for(port);
535 let node = self.push_node(Op::HwInput { channel, output });
536 self.producer.insert(output, node);
537 self.hw_in_map.push((channel, output));
538 }
539 for (channel, port) in hw_outputs.iter().enumerate() {
540 let buffer = self.buffer_for(port);
541 self.consumer_ports.push(port.clone());
542 self.hw_out_map.push((buffer, channel));
543 }
544 }
545
546 fn append_track(
550 &mut self,
551 track: TrackHandle,
552 predecessor: Option<NodeId>,
553 ) -> (NodeId, NodeId) {
554 let t = track.lock();
555 let ins: Vec<BufferId> = t.audio.ins.iter().map(|p| self.buffer_for(p)).collect();
556 let outs: Vec<BufferId> = t.audio.outs.iter().map(|p| self.buffer_for(p)).collect();
557 let metronome_source = t.metronome_source();
558 let metronome_out = metronome_source.as_ref().map(|p| self.buffer_for(p));
559 for p in &t.audio.ins {
560 self.consumer_ports.push(p.clone());
561 }
562
563 if t.is_folder {
564 let mut folder_input_outs = Vec::new();
565 if let Some(out) = metronome_out {
566 folder_input_outs.push(out);
567 }
568 let folder_input = self.push_node(Op::Task {
569 task: ProcessTask::FolderInput(track.clone()),
570 ins: ins.clone(),
571 outs: folder_input_outs,
572 });
573 if let Some(pred) = predecessor {
574 self.edges.insert((pred, folder_input));
575 }
576 self.register_task_ports(folder_input, &ins, true);
577 if let Some(out) = metronome_out {
578 self.producer.insert(out, folder_input);
579 }
580
581 let mut source_keys: HashMap<ConnectableRef, NodeId> = HashMap::new();
582 let mut target_keys: HashMap<ConnectableRef, NodeId> = HashMap::new();
583 source_keys.insert(ConnectableRef::TrackInput, folder_input);
584 target_keys.insert(ConnectableRef::TrackInput, folder_input);
585
586 let mut plugin_nodes: Vec<NodeId> = Vec::new();
587 for idx in 0..t.clap_plugins.len() {
588 let node = self.push_plugin(&track, &t, PluginKind::Clap, idx, folder_input);
589 let id = t.clap_plugins[idx].id;
590 source_keys.insert(ConnectableRef::ClapPlugin(id), node);
591 target_keys.insert(ConnectableRef::ClapPlugin(id), node);
592 plugin_nodes.push(node);
593 }
594 for idx in 0..t.vst3_plugins.len() {
595 let node = self.push_plugin(&track, &t, PluginKind::Vst3, idx, folder_input);
596 let id = t.vst3_plugins[idx].id;
597 source_keys.insert(ConnectableRef::Vst3Plugin(id), node);
598 target_keys.insert(ConnectableRef::Vst3Plugin(id), node);
599 plugin_nodes.push(node);
600 }
601 #[cfg(unix)]
602 for idx in 0..t.lv2_plugins.len() {
603 let node = self.push_plugin(&track, &t, PluginKind::Lv2, idx, folder_input);
604 let id = t.lv2_plugins[idx].id;
605 source_keys.insert(ConnectableRef::Lv2Plugin(id), node);
606 target_keys.insert(ConnectableRef::Lv2Plugin(id), node);
607 plugin_nodes.push(node);
608 }
609
610 let mut child_lasts: Vec<NodeId> = Vec::new();
611 for child_track in &t.child_tracks {
612 let (child_first, child_last) =
613 self.append_track(child_track.clone(), Some(folder_input));
614 let child_name = child_track.lock().name.clone();
615 source_keys.insert(ConnectableRef::ChildTrack(child_name.clone()), child_last);
616 target_keys.insert(ConnectableRef::ChildTrack(child_name), child_first);
617 child_lasts.push(child_last);
618 }
619
620 let folder_output = self.push_node(Op::Task {
621 task: ProcessTask::FolderOutput(track.clone()),
622 ins: Vec::new(),
623 outs: outs.clone(),
624 });
625 self.edges.insert((folder_input, folder_output));
626 for &p in &plugin_nodes {
627 self.edges.insert((p, folder_output));
628 }
629 for &c in &child_lasts {
630 self.edges.insert((c, folder_output));
631 }
632 for &out in &outs {
633 self.producer.insert(out, folder_output);
634 }
635 self.register_midi_track_ports(&t, folder_input, folder_output);
636
637 for conn in t.connectable_connections() {
640 let ConnectableConnection { from, to, .. } = conn;
641 let (Some(&source), Some(&target)) = (source_keys.get(&from), target_keys.get(&to))
642 else {
643 continue;
644 };
645 if source != target {
646 self.edges.insert((source, target));
647 }
648 }
649
650 (folder_input, folder_output)
651 } else {
652 let mut task_outs = outs.clone();
653 if let Some(out) = metronome_out {
654 task_outs.push(out);
655 }
656 let task = self.push_node(Op::Task {
657 task: ProcessTask::Track(track.clone()),
658 ins: ins.clone(),
659 outs: task_outs,
660 });
661 if let Some(pred) = predecessor {
662 self.edges.insert((pred, task));
663 }
664 self.register_task_ports(task, &ins, true);
665 for &out in &outs {
666 self.producer.insert(out, task);
667 }
668 if let Some(out) = metronome_out {
669 self.producer.insert(out, task);
670 }
671 self.register_midi_track_ports(&t, task, task);
672 for idx in 0..t.clap_plugins.len() {
674 self.register_plugin_midi_ports(&t, PluginKind::Clap, idx, task);
675 }
676 for idx in 0..t.vst3_plugins.len() {
677 self.register_plugin_midi_ports(&t, PluginKind::Vst3, idx, task);
678 }
679 #[cfg(unix)]
680 for idx in 0..t.lv2_plugins.len() {
681 self.register_plugin_midi_ports(&t, PluginKind::Lv2, idx, task);
682 }
683 (task, task)
684 }
685 }
686
687 fn push_plugin(
688 &mut self,
689 track: &TrackHandle,
690 t: &TrackData,
691 kind: PluginKind,
692 index: usize,
693 folder_input: NodeId,
694 ) -> NodeId {
695 let (input_ports, output_ports): (Vec<Arc<AudioIO>>, Vec<Arc<AudioIO>>) = match kind {
696 PluginKind::Clap => {
697 let proc = t.clap_plugins[index].processor.clone();
698 (proc.audio_inputs().to_vec(), proc.audio_outputs().to_vec())
699 }
700 PluginKind::Vst3 => {
701 let proc = t.vst3_plugins[index].processor.clone();
702 (proc.audio_inputs().to_vec(), proc.audio_outputs().to_vec())
703 }
704 #[cfg(unix)]
705 PluginKind::Lv2 => {
706 let proc = t.lv2_plugins[index].processor.clone();
707 (proc.audio_inputs().to_vec(), proc.audio_outputs().to_vec())
708 }
709 };
710 for p in &input_ports {
711 self.consumer_ports.push(p.clone());
712 }
713 let pins: Vec<BufferId> = input_ports.iter().map(|p| self.buffer_for(p)).collect();
714 let pouts: Vec<BufferId> = output_ports.iter().map(|p| self.buffer_for(p)).collect();
715 let node = self.push_node(Op::Task {
716 task: ProcessTask::Plugin {
717 track: track.clone(),
718 kind,
719 index,
720 },
721 ins: pins.clone(),
722 outs: pouts.clone(),
723 });
724 self.edges.insert((folder_input, node));
725 self.register_task_ports(node, &pins, false);
726 for &out in &pouts {
727 self.producer.insert(out, node);
728 }
729 self.register_plugin_midi_ports(t, kind, index, node);
730 node
731 }
732
733 fn register_task_ports(&mut self, node: NodeId, ins: &[BufferId], in_place: bool) {
735 for &b in ins {
736 self.port_readers.entry(b).or_default().push(node);
737 if in_place {
738 self.port_inplace_writers.entry(b).or_default().push(node);
739 self.producer.insert(b, node);
740 }
741 }
742 }
743
744 fn finish(mut self) -> RenderPlan {
747 let mut seen = HashSet::new();
752 self.consumer_ports
753 .retain(|p| seen.insert(Arc::as_ptr(p) as usize));
754
755 for port in self.consumer_ports.clone() {
756 let output = self.buffer_for(&port);
757 let readers = self.port_readers.get(&output).cloned().unwrap_or_default();
758 let sources: Vec<BufferId> = {
759 let conns = port.connections();
760 conns
761 .iter()
762 .filter_map(|p| {
763 let src = self.buffer_for(p);
764 match self.producer.get(&src) {
765 Some(producer) if readers.contains(producer) => None,
766 _ => Some(src),
767 }
768 })
769 .collect()
770 };
771 let node = if sources.is_empty() {
772 self.push_node(Op::Zero { output })
773 } else {
774 let node = self.push_node(Op::Sum {
775 inputs: sources.clone(),
776 delays: sources
777 .iter()
778 .map(|_| UnsafeCell::new(DelayLine::new()))
779 .collect(),
780 output,
781 });
782 for src in sources {
783 match self.producer.get(&src) {
784 Some(&prod) => {
785 self.edges.insert((prod, node));
786 }
787 None => {
788 tracing::warn!(
789 "render plan: connection source for buffer {src} has no producer; \
790 treating as silent"
791 );
792 }
793 }
794 }
795 node
796 };
797 for reader in readers {
799 self.edges.insert((node, reader));
800 }
801 }
802
803 for port in self.midi_ports.clone() {
808 let key = Arc::as_ptr(&port) as usize;
809 let Some(&reader) = self.midi_readers.get(&key) else {
810 continue;
811 };
812 for source in port.sources() {
813 let src_key = Arc::as_ptr(&source) as usize;
814 let Some(&writer) = self.midi_writers.get(&src_key) else {
815 continue;
816 };
817 if writer != reader && self.edges.insert((writer, reader)) {
818 self.midi_edges.push((writer, reader));
819 }
820 }
821 }
822
823 let n = self.nodes.len();
824 let (order, forced) = topo_sort(n, &self.edges);
825 let mut remap = vec![0u32; n];
826 for (new_idx, &old_idx) in order.iter().enumerate() {
827 remap[old_idx as usize] = new_idx as NodeId;
828 }
829
830 let mut nodes = Vec::with_capacity(n);
831 for &old_idx in &order {
832 nodes.push(std::mem::replace(
833 &mut self.nodes[old_idx as usize],
834 Op::Zero { output: 0 },
835 ));
836 }
837
838 let mut indegree = vec![0u32; n];
839 let mut dependents: Vec<Vec<NodeId>> = vec![Vec::new(); n];
840 for &(from, to) in &self.edges {
841 let (from, to) = (remap[from as usize], remap[to as usize]);
842 indegree[to as usize] += 1;
843 dependents[from as usize].push(to);
844 }
845 let sources: Vec<NodeId> = (0..n as NodeId)
846 .filter(|&i| indegree[i as usize] == 0)
847 .collect();
848 let forced: Vec<NodeId> = forced.iter().map(|&f| remap[f as usize]).collect();
849 let midi_edges: Vec<(NodeId, NodeId)> = self
850 .midi_edges
851 .iter()
852 .map(|&(from, to)| (remap[from as usize], remap[to as usize]))
853 .collect();
854
855 let buffer_count = self.buffers.len();
856 let plan = RenderPlan {
857 buffer_size: self.buffer_size,
858 buffers: self.buffers,
859 buffer_latencies: (0..buffer_count).map(|_| AtomicUsize::new(0)).collect(),
860 nodes,
861 indegree,
862 dependents,
863 sources,
864 hw_in_map: self.hw_in_map,
865 hw_out_map: self.hw_out_map,
866 port_map: self.port_map,
867 midi_edges,
868 forced,
869 };
870 if let Err(e) = plan.verify() {
871 tracing::error!("render plan invariant violation: {e}");
872 }
873 plan
874 }
875}
876
877fn topo_sort(n: usize, edges: &HashSet<(NodeId, NodeId)>) -> (Vec<NodeId>, Vec<NodeId>) {
881 let mut indegree = vec![0u32; n];
882 let mut dependents: Vec<Vec<NodeId>> = vec![Vec::new(); n];
883 for &(from, to) in edges {
884 indegree[to as usize] += 1;
885 dependents[from as usize].push(to);
886 }
887 let mut queue: VecDeque<NodeId> = (0..n as NodeId)
888 .filter(|&i| indegree[i as usize] == 0)
889 .collect();
890 let mut order = Vec::with_capacity(n);
891 while let Some(node) = queue.pop_front() {
892 order.push(node);
893 for &d in &dependents[node as usize] {
894 indegree[d as usize] -= 1;
895 if indegree[d as usize] == 0 {
896 queue.push_back(d);
897 }
898 }
899 }
900 let placed: HashSet<NodeId> = order.iter().copied().collect();
901 let forced: Vec<NodeId> = (0..n as NodeId).filter(|i| !placed.contains(i)).collect();
902 order.extend(forced.iter().copied());
903 (order, forced)
904}
905
906#[cfg(test)]
907mod tests {
908 use super::*;
909 use crate::connectable::connect_audio;
910 use crate::state::State;
911
912 fn make_track(name: &str, ins: usize, outs: usize) -> TrackHandle {
913 Arc::new(Track::new(name.to_string(), ins, outs, 0, 0, 64, 48_000.0))
914 }
915
916 fn state_with(tracks: Vec<TrackHandle>) -> StateSnapshot {
917 let mut state = State::default();
918 for t in tracks {
919 state.tracks.insert(t.lock().name.clone(), t);
920 }
921 state.snapshot()
922 }
923
924 fn connect(a: &TrackHandle, a_port: usize, b: &TrackHandle, b_port: usize) {
926 let src = a.lock();
927 let dst = b.lock();
928 connect_audio(&*src, a_port, &*dst, b_port).expect("connect");
929 }
930
931 fn task_nodes(plan: &RenderPlan, name: &str) -> Vec<usize> {
932 plan.nodes
933 .iter()
934 .enumerate()
935 .filter_map(|(i, op)| match op {
936 Op::Task { task, .. } => {
937 let track = match task {
938 ProcessTask::Track(t)
939 | ProcessTask::FolderInput(t)
940 | ProcessTask::FolderOutput(t) => t,
941 ProcessTask::Plugin { track, .. } => track,
942 };
943 if track.lock().name == name {
944 Some(i)
945 } else {
946 None
947 }
948 }
949 _ => None,
950 })
951 .collect()
952 }
953
954 fn task_node(plan: &RenderPlan, name: &str, want: fn(&ProcessTask) -> bool) -> usize {
955 plan.nodes
956 .iter()
957 .enumerate()
958 .find_map(|(i, op)| match op {
959 Op::Task { task, .. } => {
960 let track = match task {
961 ProcessTask::Track(t)
962 | ProcessTask::FolderInput(t)
963 | ProcessTask::FolderOutput(t) => t,
964 ProcessTask::Plugin { track, .. } => track,
965 };
966 if track.lock().name == name && want(task) {
967 Some(i)
968 } else {
969 None
970 }
971 }
972 _ => None,
973 })
974 .expect("task node not found")
975 }
976
977 fn sum_nodes(plan: &RenderPlan) -> Vec<(usize, Vec<BufferId>, BufferId)> {
978 plan.nodes
979 .iter()
980 .enumerate()
981 .filter_map(|(i, op)| match op {
982 Op::Sum { inputs, output, .. } => Some((i, inputs.clone(), *output)),
983 _ => None,
984 })
985 .collect()
986 }
987
988 fn zero_count(plan: &RenderPlan) -> usize {
989 plan.nodes
990 .iter()
991 .filter(|op| matches!(op, Op::Zero { .. }))
992 .count()
993 }
994
995 fn is_track(t: &ProcessTask) -> bool {
996 matches!(t, ProcessTask::Track(_))
997 }
998 fn is_folder_input(t: &ProcessTask) -> bool {
999 matches!(t, ProcessTask::FolderInput(_))
1000 }
1001 fn is_folder_output(t: &ProcessTask) -> bool {
1002 matches!(t, ProcessTask::FolderOutput(_))
1003 }
1004
1005 #[test]
1006 fn producer_chain_orders_zero_track_sum_track() {
1007 let a = make_track("a", 1, 1);
1008 let b = make_track("b", 1, 1);
1009 connect(&a, 0, &b, 0);
1010 let plan = RenderPlan::compile(&state_with(vec![a, b]), &[], &[], 64);
1011 plan.verify().expect("invariants");
1012
1013 let sums = sum_nodes(&plan);
1014 assert_eq!(sums.len(), 1, "one connected input -> one Sum");
1015 assert_eq!(sums[0].1.len(), 1);
1016
1017 let task_a = task_node(&plan, "a", is_track);
1018 let task_b = task_node(&plan, "b", is_track);
1019 let sum = sums[0].0;
1020 assert_eq!(zero_count(&plan), 1, "a's unconnected input -> Zero");
1021 let zero = plan
1022 .nodes
1023 .iter()
1024 .position(|op| matches!(op, Op::Zero { .. }))
1025 .expect("zero node");
1026
1027 assert!(zero < task_a, "Zero before the task that reads it");
1028 assert!(task_a < sum, "producer before the Sum of its consumer");
1029 assert!(sum < task_b, "Sum before the consuming track");
1030 assert_eq!(plan.sources, vec![zero as NodeId]);
1031 assert_eq!(plan.indegree[task_b], 1);
1032 assert!(plan.forced.is_empty());
1033 }
1034
1035 #[test]
1036 fn default_passthrough_stays_inside_track_task() {
1037 let track = make_track("t", 1, 1);
1038 let plan = RenderPlan::compile(&state_with(vec![track]), &[], &[], 64);
1039 plan.verify().expect("invariants");
1040
1041 let task = task_node(&plan, "t", is_track);
1042 let zeros = plan
1043 .nodes
1044 .iter()
1045 .enumerate()
1046 .filter_map(|(idx, op)| match op {
1047 Op::Zero { .. } => Some(idx),
1048 _ => None,
1049 })
1050 .collect::<Vec<_>>();
1051
1052 assert_eq!(zeros.len(), 1, "track input is not fed by its own output");
1053 assert!(zeros[0] < task, "input zero before the track task");
1054 assert!(plan.forced.is_empty());
1055 }
1056
1057 #[test]
1058 fn two_sources_insert_sum_with_two_inputs() {
1059 let a = make_track("a", 0, 1);
1060 let b = make_track("b", 0, 1);
1061 let c = make_track("c", 1, 1);
1062 connect(&a, 0, &c, 0);
1063 connect(&b, 0, &c, 0);
1064 let plan = RenderPlan::compile(&state_with(vec![a, b, c]), &[], &[], 64);
1065 plan.verify().expect("invariants");
1066
1067 let sums = sum_nodes(&plan);
1068 assert_eq!(sums.len(), 1);
1069 assert_eq!(sums[0].1.len(), 2, "both sources summed");
1070 assert_eq!(plan.indegree[sums[0].0], 2);
1071
1072 let task_a = task_node(&plan, "a", is_track);
1073 let task_b = task_node(&plan, "b", is_track);
1074 assert!(task_a < sums[0].0 && task_b < sums[0].0);
1075 assert_eq!(zero_count(&plan), 0);
1077 assert_eq!(plan.sources.len(), 2, "two root tracks are sources");
1078 }
1079
1080 #[test]
1081 fn metronome_source_is_produced_by_track_task() {
1082 let metronome = make_track("metronome", 0, 1);
1083 let source = {
1084 let mut track = metronome.lock();
1085 let (source, changed) = track.ensure_metronome_source(64);
1086 assert!(changed);
1087 source.expect("metronome source")
1088 };
1089 let plan = RenderPlan::compile(&state_with(vec![metronome]), &[], &[], 64);
1090 plan.verify().expect("invariants");
1091
1092 let source_key = Arc::as_ptr(&source) as usize;
1093 let source_buffer = *plan.port_map.get(&source_key).expect("source buffer");
1094 let task = task_node(&plan, "metronome", is_track);
1095
1096 match &plan.nodes[task] {
1097 Op::Task { outs, .. } => assert!(outs.contains(&source_buffer)),
1098 _ => unreachable!(),
1099 }
1100 }
1101
1102 #[test]
1103 fn folder_track_emits_input_child_output_chain() {
1104 let folder = make_track("folder", 1, 1);
1105 let child = make_track("child", 1, 1);
1106 folder.lock().is_folder = true;
1107 child.lock().parent_track = Some("folder".to_string());
1108 folder.lock().child_tracks.push(child.clone());
1109 let plan = RenderPlan::compile(&state_with(vec![folder, child]), &[], &[], 64);
1110 plan.verify().expect("invariants");
1111
1112 let fi = task_node(&plan, "folder", is_folder_input);
1113 let fo = task_node(&plan, "folder", is_folder_output);
1114 let child_task = task_node(&plan, "child", is_track);
1115 assert!(fi < child_task, "folder input before child");
1116 assert!(child_task < fo, "child before folder output");
1117 assert!(plan.dependents[fi].contains(&(child_task as NodeId)));
1118 assert!(plan.dependents[child_task].contains(&(fo as NodeId)));
1119 assert_eq!(task_nodes(&plan, "child").len(), 1);
1121 }
1122
1123 #[test]
1124 fn midi_only_connection_inserts_ordering_edge() {
1125 let a = Arc::new(Track::new("a".to_string(), 0, 0, 0, 1, 64, 48_000.0));
1127 let b = Arc::new(Track::new("b".to_string(), 0, 0, 1, 0, 64, 48_000.0));
1128 let a_out = a.lock().midi.outs[0].clone();
1129 let b_in = b.lock().midi.ins[0].clone();
1130 crate::midi::io::MIDIIO::connect(&a_out, &b_in);
1131
1132 let plan = RenderPlan::compile(&state_with(vec![a, b]), &[], &[], 64);
1133 plan.verify().expect("invariants");
1134
1135 let task_a = task_node(&plan, "a", is_track);
1136 let task_b = task_node(&plan, "b", is_track);
1137 assert_eq!(plan.midi_edges, vec![(task_a as NodeId, task_b as NodeId)]);
1138 assert!(task_a < task_b, "producer task ordered before consumer");
1139 assert!(plan.dependents[task_a].contains(&(task_b as NodeId)));
1140 assert!(plan.forced.is_empty());
1141 }
1142
1143 #[test]
1144 fn feedback_cycle_is_broken_and_marked_forced() {
1145 let a = make_track("a", 1, 1);
1146 let b = make_track("b", 1, 1);
1147 connect(&a, 0, &b, 0);
1148 connect(&b, 0, &a, 0);
1149 let plan = RenderPlan::compile(&state_with(vec![a, b]), &[], &[], 64);
1150
1151 assert_eq!(plan.nodes.len(), 4);
1153 assert!(plan.sources.is_empty());
1154 assert_eq!(plan.forced.len(), 4, "whole cycle marked forced");
1155 plan.verify().expect("invariants tolerate forced cycle");
1157 }
1158
1159 #[test]
1160 fn hw_bridges_become_source_and_sink_nodes() {
1161 let t = make_track("t", 1, 1);
1162 let hw_in = Arc::new(AudioIO::new(64));
1163 let hw_out = Arc::new(AudioIO::new(64));
1164 {
1166 let track = t.lock();
1167 AudioIO::connect(&hw_in, &track.audio.ins[0]);
1168 AudioIO::connect(&track.audio.outs[0], &hw_out);
1169 }
1170 let plan = RenderPlan::compile(
1171 &state_with(vec![t]),
1172 std::slice::from_ref(&hw_in),
1173 std::slice::from_ref(&hw_out),
1174 64,
1175 );
1176 plan.verify().expect("invariants");
1177
1178 let hw_node = plan
1179 .nodes
1180 .iter()
1181 .position(|op| matches!(op, Op::HwInput { .. }))
1182 .expect("HwInput node");
1183 assert_eq!(plan.hw_in_map.len(), 1);
1184 assert_eq!(plan.hw_out_map.len(), 1);
1185 let (chan, buf) = plan.hw_in_map[0];
1186 assert_eq!(chan, 0);
1187 match &plan.nodes[hw_node] {
1188 Op::HwInput { output, .. } => assert_eq!(*output, buf),
1189 _ => unreachable!(),
1190 }
1191 assert!(plan.sources.contains(&(hw_node as NodeId)));
1193 let sums = sum_nodes(&plan);
1194 assert_eq!(sums.len(), 2, "track input sum + hw_out bridge sum");
1195 let (out_buf, out_chan) = plan.hw_out_map[0];
1197 assert_eq!(out_chan, 0);
1198 assert!(sums.iter().any(|(_, _, output)| *output == out_buf));
1199 assert!(plan.forced.is_empty());
1200 }
1201
1202 fn hand_plan(
1204 buffers: usize,
1205 nodes: Vec<Op>,
1206 indegree: Vec<u32>,
1207 dependents: Vec<Vec<NodeId>>,
1208 sources: Vec<NodeId>,
1209 ) -> RenderPlan {
1210 RenderPlan {
1211 buffer_size: 64,
1212 buffers: (0..buffers)
1213 .map(|_| UnsafeCell::new(vec![0.0; 64]))
1214 .collect(),
1215 buffer_latencies: (0..buffers).map(|_| AtomicUsize::new(0)).collect(),
1216 nodes,
1217 indegree,
1218 dependents,
1219 sources,
1220 hw_in_map: vec![],
1221 hw_out_map: vec![],
1222 port_map: HashMap::new(),
1223 midi_edges: vec![],
1224 forced: vec![],
1225 }
1226 }
1227
1228 #[test]
1229 fn verify_rejects_backward_edge() {
1230 let plan = hand_plan(
1231 2,
1232 vec![
1233 Op::Sum {
1234 inputs: vec![1],
1235 delays: vec![UnsafeCell::new(DelayLine::new())],
1236 output: 0,
1237 },
1238 Op::HwInput {
1239 channel: 0,
1240 output: 1,
1241 },
1242 ],
1243 vec![1, 0],
1244 vec![vec![], vec![0]],
1245 vec![1],
1246 );
1247 assert!(plan.verify().is_err());
1250 }
1251
1252 #[test]
1253 fn verify_rejects_racing_writers() {
1254 let plan = hand_plan(
1256 3,
1257 vec![
1258 Op::HwInput {
1259 channel: 0,
1260 output: 1,
1261 },
1262 Op::HwInput {
1263 channel: 1,
1264 output: 2,
1265 },
1266 Op::Sum {
1267 inputs: vec![1],
1268 delays: vec![UnsafeCell::new(DelayLine::new())],
1269 output: 0,
1270 },
1271 Op::Sum {
1272 inputs: vec![2],
1273 delays: vec![UnsafeCell::new(DelayLine::new())],
1274 output: 0,
1275 },
1276 ],
1277 vec![2, 0, 0, 0],
1278 vec![vec![], vec![2], vec![], vec![]],
1279 vec![0, 1],
1280 );
1281 let err = plan.verify().expect_err("racing writers must fail");
1282 assert!(err.contains("unordered nodes"));
1283 }
1284
1285 #[test]
1286 fn buffers_are_sized_and_silent() {
1287 let t = make_track("t", 2, 1);
1288 let plan = RenderPlan::compile(&state_with(vec![t]), &[], &[], 256);
1289 assert_eq!(plan.buffer_size, 256);
1290 assert_eq!(plan.buffer_count(), 3);
1292 for i in 0..plan.buffer_count() as BufferId {
1293 let buf = unsafe { plan.buffer(i) };
1295 assert_eq!(buf.len(), 256);
1296 assert!(buf.iter().all(|&s| s == 0.0));
1297 }
1298 assert_eq!(zero_count(&plan), 2);
1300 assert_eq!(plan.port_map.len(), 3);
1301 }
1302}