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], pair[1]
367 ));
368 }
369 }
370 }
371 Ok(())
372 }
373
374 fn reachable(&self, from: NodeId, to: NodeId) -> bool {
376 if from == to {
377 return true;
378 }
379 let mut seen = HashSet::new();
380 let mut queue = VecDeque::from([from]);
381 seen.insert(from);
382 while let Some(n) = queue.pop_front() {
383 for &d in &self.dependents[n as usize] {
384 if d == to {
385 return true;
386 }
387 if seen.insert(d) {
388 queue.push_back(d);
389 }
390 }
391 }
392 false
393 }
394}
395
396struct Builder {
398 buffer_size: usize,
399 buffers: Vec<UnsafeCell<Vec<f32>>>,
400 port_map: HashMap<usize, BufferId>,
401 nodes: Vec<Op>,
402 edges: HashSet<(NodeId, NodeId)>,
403 consumer_ports: Vec<Arc<AudioIO>>,
405 port_readers: HashMap<BufferId, Vec<NodeId>>,
407 port_inplace_writers: HashMap<BufferId, Vec<NodeId>>,
409 producer: HashMap<BufferId, NodeId>,
411 hw_in_map: Vec<(usize, BufferId)>,
412 hw_out_map: Vec<(BufferId, usize)>,
413 midi_writers: HashMap<usize, NodeId>,
416 midi_readers: HashMap<usize, NodeId>,
418 midi_ports: Vec<Arc<crate::midi::io::MIDIIO>>,
420 midi_edges: Vec<(NodeId, NodeId)>,
421}
422
423impl Builder {
424 fn new(buffer_size: usize) -> Self {
425 Self {
426 buffer_size,
427 buffers: Vec::new(),
428 port_map: HashMap::new(),
429 nodes: Vec::new(),
430 edges: HashSet::new(),
431 consumer_ports: Vec::new(),
432 port_readers: HashMap::new(),
433 port_inplace_writers: HashMap::new(),
434 producer: HashMap::new(),
435 hw_in_map: Vec::new(),
436 hw_out_map: Vec::new(),
437 midi_writers: HashMap::new(),
438 midi_readers: HashMap::new(),
439 midi_ports: Vec::new(),
440 midi_edges: Vec::new(),
441 }
442 }
443
444 fn register_midi_track_ports(&mut self, t: &TrackData, first: NodeId, last: NodeId) {
448 for p in &t.midi.ins {
449 let key = Arc::as_ptr(p) as usize;
450 self.midi_writers.insert(key, first);
451 self.midi_readers.insert(key, first);
452 self.midi_ports.push(p.clone());
453 }
454 for p in &t.midi.outs {
455 let key = Arc::as_ptr(p) as usize;
456 self.midi_writers.insert(key, last);
457 self.midi_readers.insert(key, last);
458 self.midi_ports.push(p.clone());
459 }
460 }
461
462 fn register_plugin_midi_ports(
465 &mut self,
466 t: &TrackData,
467 kind: PluginKind,
468 index: usize,
469 node: NodeId,
470 ) {
471 let (midi_ins, midi_outs): (
472 Vec<Arc<crate::midi::io::MIDIIO>>,
473 Vec<Arc<crate::midi::io::MIDIIO>>,
474 ) = match kind {
475 PluginKind::Clap => {
476 let proc = t.clap_plugins[index].processor.clone();
477 (
478 proc.midi_input_ports().to_vec(),
479 proc.midi_output_ports().to_vec(),
480 )
481 }
482 PluginKind::Vst3 => {
483 let proc = t.vst3_plugins[index].processor.clone();
484 (
485 proc.midi_input_ports().to_vec(),
486 proc.midi_output_ports().to_vec(),
487 )
488 }
489 #[cfg(all(unix, not(target_os = "macos")))]
490 PluginKind::Lv2 => {
491 let proc = t.lv2_plugins[index].processor.clone();
492 (
493 proc.midi_input_ports().to_vec(),
494 proc.midi_output_ports().to_vec(),
495 )
496 }
497 };
498 for p in midi_ins {
499 let key = Arc::as_ptr(&p) as usize;
500 self.midi_writers.insert(key, node);
501 self.midi_readers.insert(key, node);
502 self.midi_ports.push(p);
503 }
504 for p in midi_outs {
505 let key = Arc::as_ptr(&p) as usize;
506 self.midi_writers.insert(key, node);
507 self.midi_ports.push(p);
508 }
509 }
510
511 fn buffer_for(&mut self, port: &Arc<AudioIO>) -> BufferId {
513 let key = Arc::as_ptr(port) as usize;
514 if let Some(&id) = self.port_map.get(&key) {
515 return id;
516 }
517 let id = self.buffers.len() as BufferId;
518 self.buffers
519 .push(UnsafeCell::new(vec![0.0; self.buffer_size]));
520 self.port_map.insert(key, id);
521 id
522 }
523
524 fn push_node(&mut self, op: Op) -> NodeId {
525 self.nodes.push(op);
526 (self.nodes.len() - 1) as NodeId
527 }
528
529 fn add_hw(&mut self, hw_inputs: &[Arc<AudioIO>], hw_outputs: &[Arc<AudioIO>]) {
530 for (channel, port) in hw_inputs.iter().enumerate() {
531 let output = self.buffer_for(port);
532 let node = self.push_node(Op::HwInput { channel, output });
533 self.producer.insert(output, node);
534 self.hw_in_map.push((channel, output));
535 }
536 for (channel, port) in hw_outputs.iter().enumerate() {
537 let buffer = self.buffer_for(port);
538 self.consumer_ports.push(port.clone());
539 self.hw_out_map.push((buffer, channel));
540 }
541 }
542
543 fn append_track(
547 &mut self,
548 track: TrackHandle,
549 predecessor: Option<NodeId>,
550 ) -> (NodeId, NodeId) {
551 let t = track.lock();
552 let ins: Vec<BufferId> = t.audio.ins.iter().map(|p| self.buffer_for(p)).collect();
553 let outs: Vec<BufferId> = t.audio.outs.iter().map(|p| self.buffer_for(p)).collect();
554 let metronome_source = t.metronome_source();
555 let metronome_out = metronome_source.as_ref().map(|p| self.buffer_for(p));
556 for p in &t.audio.ins {
557 self.consumer_ports.push(p.clone());
558 }
559
560 if t.is_folder {
561 let mut folder_input_outs = Vec::new();
562 if let Some(out) = metronome_out {
563 folder_input_outs.push(out);
564 }
565 let folder_input = self.push_node(Op::Task {
566 task: ProcessTask::FolderInput(track.clone()),
567 ins: ins.clone(),
568 outs: folder_input_outs,
569 });
570 if let Some(pred) = predecessor {
571 self.edges.insert((pred, folder_input));
572 }
573 self.register_task_ports(folder_input, &ins, true);
574 if let Some(out) = metronome_out {
575 self.producer.insert(out, folder_input);
576 }
577
578 let mut source_keys: HashMap<ConnectableRef, NodeId> = HashMap::new();
579 let mut target_keys: HashMap<ConnectableRef, NodeId> = HashMap::new();
580 source_keys.insert(ConnectableRef::TrackInput, folder_input);
581 target_keys.insert(ConnectableRef::TrackInput, folder_input);
582
583 let mut plugin_nodes: Vec<NodeId> = Vec::new();
584 for idx in 0..t.clap_plugins.len() {
585 let node = self.push_plugin(&track, &t, PluginKind::Clap, idx, folder_input);
586 let id = t.clap_plugins[idx].id;
587 source_keys.insert(ConnectableRef::ClapPlugin(id), node);
588 target_keys.insert(ConnectableRef::ClapPlugin(id), node);
589 plugin_nodes.push(node);
590 }
591 for idx in 0..t.vst3_plugins.len() {
592 let node = self.push_plugin(&track, &t, PluginKind::Vst3, idx, folder_input);
593 let id = t.vst3_plugins[idx].id;
594 source_keys.insert(ConnectableRef::Vst3Plugin(id), node);
595 target_keys.insert(ConnectableRef::Vst3Plugin(id), node);
596 plugin_nodes.push(node);
597 }
598 #[cfg(all(unix, not(target_os = "macos")))]
599 for idx in 0..t.lv2_plugins.len() {
600 let node = self.push_plugin(&track, &t, PluginKind::Lv2, idx, folder_input);
601 let id = t.lv2_plugins[idx].id;
602 source_keys.insert(ConnectableRef::Lv2Plugin(id), node);
603 target_keys.insert(ConnectableRef::Lv2Plugin(id), node);
604 plugin_nodes.push(node);
605 }
606
607 let mut child_lasts: Vec<NodeId> = Vec::new();
608 for child_track in &t.child_tracks {
609 let (child_first, child_last) =
610 self.append_track(child_track.clone(), Some(folder_input));
611 let child_name = child_track.lock().name.clone();
612 source_keys.insert(ConnectableRef::ChildTrack(child_name.clone()), child_last);
613 target_keys.insert(ConnectableRef::ChildTrack(child_name), child_first);
614 child_lasts.push(child_last);
615 }
616
617 let folder_output = self.push_node(Op::Task {
618 task: ProcessTask::FolderOutput(track.clone()),
619 ins: Vec::new(),
620 outs: outs.clone(),
621 });
622 self.edges.insert((folder_input, folder_output));
623 for &p in &plugin_nodes {
624 self.edges.insert((p, folder_output));
625 }
626 for &c in &child_lasts {
627 self.edges.insert((c, folder_output));
628 }
629 for &out in &outs {
630 self.producer.insert(out, folder_output);
631 }
632 self.register_midi_track_ports(&t, folder_input, folder_output);
633
634 for conn in t.connectable_connections() {
637 let ConnectableConnection { from, to, .. } = conn;
638 let (Some(&source), Some(&target)) = (source_keys.get(&from), target_keys.get(&to))
639 else {
640 continue;
641 };
642 if source != target {
643 self.edges.insert((source, target));
644 }
645 }
646
647 (folder_input, folder_output)
648 } else {
649 let mut task_outs = outs.clone();
650 if let Some(out) = metronome_out {
651 task_outs.push(out);
652 }
653 let task = self.push_node(Op::Task {
654 task: ProcessTask::Track(track.clone()),
655 ins: ins.clone(),
656 outs: task_outs,
657 });
658 if let Some(pred) = predecessor {
659 self.edges.insert((pred, task));
660 }
661 self.register_task_ports(task, &ins, true);
662 for &out in &outs {
663 self.producer.insert(out, task);
664 }
665 if let Some(out) = metronome_out {
666 self.producer.insert(out, task);
667 }
668 self.register_midi_track_ports(&t, task, task);
669 for idx in 0..t.clap_plugins.len() {
671 self.register_plugin_midi_ports(&t, PluginKind::Clap, idx, task);
672 }
673 for idx in 0..t.vst3_plugins.len() {
674 self.register_plugin_midi_ports(&t, PluginKind::Vst3, idx, task);
675 }
676 #[cfg(all(unix, not(target_os = "macos")))]
677 for idx in 0..t.lv2_plugins.len() {
678 self.register_plugin_midi_ports(&t, PluginKind::Lv2, idx, task);
679 }
680 (task, task)
681 }
682 }
683
684 fn push_plugin(
685 &mut self,
686 track: &TrackHandle,
687 t: &TrackData,
688 kind: PluginKind,
689 index: usize,
690 folder_input: NodeId,
691 ) -> NodeId {
692 let (input_ports, output_ports): (Vec<Arc<AudioIO>>, Vec<Arc<AudioIO>>) = match kind {
693 PluginKind::Clap => {
694 let proc = t.clap_plugins[index].processor.clone();
695 (proc.audio_inputs().to_vec(), proc.audio_outputs().to_vec())
696 }
697 PluginKind::Vst3 => {
698 let proc = t.vst3_plugins[index].processor.clone();
699 (proc.audio_inputs().to_vec(), proc.audio_outputs().to_vec())
700 }
701 #[cfg(all(unix, not(target_os = "macos")))]
702 PluginKind::Lv2 => {
703 let proc = t.lv2_plugins[index].processor.clone();
704 (proc.audio_inputs().to_vec(), proc.audio_outputs().to_vec())
705 }
706 };
707 for p in &input_ports {
708 self.consumer_ports.push(p.clone());
709 }
710 let pins: Vec<BufferId> = input_ports.iter().map(|p| self.buffer_for(p)).collect();
711 let pouts: Vec<BufferId> = output_ports.iter().map(|p| self.buffer_for(p)).collect();
712 let node = self.push_node(Op::Task {
713 task: ProcessTask::Plugin {
714 track: track.clone(),
715 kind,
716 index,
717 },
718 ins: pins.clone(),
719 outs: pouts.clone(),
720 });
721 self.edges.insert((folder_input, node));
722 self.register_task_ports(node, &pins, false);
723 for &out in &pouts {
724 self.producer.insert(out, node);
725 }
726 self.register_plugin_midi_ports(t, kind, index, node);
727 node
728 }
729
730 fn register_task_ports(&mut self, node: NodeId, ins: &[BufferId], in_place: bool) {
732 for &b in ins {
733 self.port_readers.entry(b).or_default().push(node);
734 if in_place {
735 self.port_inplace_writers.entry(b).or_default().push(node);
736 self.producer.insert(b, node);
737 }
738 }
739 }
740
741 fn finish(mut self) -> RenderPlan {
744 for port in self.consumer_ports.clone() {
745 let output = self.buffer_for(&port);
746 let sources: Vec<BufferId> = {
747 let conns = port.connections();
748 conns.iter().map(|p| self.buffer_for(p)).collect()
749 };
750 let node = if sources.is_empty() {
751 self.push_node(Op::Zero { output })
752 } else {
753 let node = self.push_node(Op::Sum {
754 inputs: sources.clone(),
755 delays: sources
756 .iter()
757 .map(|_| UnsafeCell::new(DelayLine::new()))
758 .collect(),
759 output,
760 });
761 for src in sources {
762 match self.producer.get(&src) {
763 Some(&prod) => {
764 self.edges.insert((prod, node));
765 }
766 None => {
767 tracing::warn!(
768 "render plan: connection source for buffer {src} has no producer; \
769 treating as silent"
770 );
771 }
772 }
773 }
774 node
775 };
776 if let Some(readers) = self.port_readers.get(&output).cloned() {
778 for reader in readers {
779 self.edges.insert((node, reader));
780 }
781 }
782 }
783
784 for port in self.midi_ports.clone() {
789 let key = Arc::as_ptr(&port) as usize;
790 let Some(&reader) = self.midi_readers.get(&key) else {
791 continue;
792 };
793 for source in port.sources() {
794 let src_key = Arc::as_ptr(&source) as usize;
795 let Some(&writer) = self.midi_writers.get(&src_key) else {
796 continue;
797 };
798 if writer != reader && self.edges.insert((writer, reader)) {
799 self.midi_edges.push((writer, reader));
800 }
801 }
802 }
803
804 let n = self.nodes.len();
805 let (order, forced) = topo_sort(n, &self.edges);
806 let mut remap = vec![0u32; n];
807 for (new_idx, &old_idx) in order.iter().enumerate() {
808 remap[old_idx as usize] = new_idx as NodeId;
809 }
810
811 let mut nodes = Vec::with_capacity(n);
812 for &old_idx in &order {
813 nodes.push(std::mem::replace(
814 &mut self.nodes[old_idx as usize],
815 Op::Zero { output: 0 },
816 ));
817 }
818
819 let mut indegree = vec![0u32; n];
820 let mut dependents: Vec<Vec<NodeId>> = vec![Vec::new(); n];
821 for &(from, to) in &self.edges {
822 let (from, to) = (remap[from as usize], remap[to as usize]);
823 indegree[to as usize] += 1;
824 dependents[from as usize].push(to);
825 }
826 let sources: Vec<NodeId> = (0..n as NodeId)
827 .filter(|&i| indegree[i as usize] == 0)
828 .collect();
829 let forced: Vec<NodeId> = forced.iter().map(|&f| remap[f as usize]).collect();
830 let midi_edges: Vec<(NodeId, NodeId)> = self
831 .midi_edges
832 .iter()
833 .map(|&(from, to)| (remap[from as usize], remap[to as usize]))
834 .collect();
835
836 let buffer_count = self.buffers.len();
837 let plan = RenderPlan {
838 buffer_size: self.buffer_size,
839 buffers: self.buffers,
840 buffer_latencies: (0..buffer_count).map(|_| AtomicUsize::new(0)).collect(),
841 nodes,
842 indegree,
843 dependents,
844 sources,
845 hw_in_map: self.hw_in_map,
846 hw_out_map: self.hw_out_map,
847 port_map: self.port_map,
848 midi_edges,
849 forced,
850 };
851 if let Err(e) = plan.verify() {
852 tracing::error!("render plan invariant violation: {e}");
853 }
854 plan
855 }
856}
857
858fn topo_sort(n: usize, edges: &HashSet<(NodeId, NodeId)>) -> (Vec<NodeId>, Vec<NodeId>) {
862 let mut indegree = vec![0u32; n];
863 let mut dependents: Vec<Vec<NodeId>> = vec![Vec::new(); n];
864 for &(from, to) in edges {
865 indegree[to as usize] += 1;
866 dependents[from as usize].push(to);
867 }
868 let mut queue: VecDeque<NodeId> = (0..n as NodeId)
869 .filter(|&i| indegree[i as usize] == 0)
870 .collect();
871 let mut order = Vec::with_capacity(n);
872 while let Some(node) = queue.pop_front() {
873 order.push(node);
874 for &d in &dependents[node as usize] {
875 indegree[d as usize] -= 1;
876 if indegree[d as usize] == 0 {
877 queue.push_back(d);
878 }
879 }
880 }
881 let placed: HashSet<NodeId> = order.iter().copied().collect();
882 let forced: Vec<NodeId> = (0..n as NodeId).filter(|i| !placed.contains(i)).collect();
883 order.extend(forced.iter().copied());
884 (order, forced)
885}
886
887#[cfg(test)]
888mod tests {
889 use super::*;
890 use crate::connectable::connect_audio;
891 use crate::state::State;
892
893 fn make_track(name: &str, ins: usize, outs: usize) -> TrackHandle {
894 Arc::new(Track::new(name.to_string(), ins, outs, 0, 0, 64, 48_000.0))
895 }
896
897 fn state_with(tracks: Vec<TrackHandle>) -> StateSnapshot {
898 let mut state = State::default();
899 for t in tracks {
900 state.tracks.insert(t.lock().name.clone(), t);
901 }
902 state.snapshot()
903 }
904
905 fn connect(a: &TrackHandle, a_port: usize, b: &TrackHandle, b_port: usize) {
907 let src = a.lock();
908 let dst = b.lock();
909 connect_audio(&*src, a_port, &*dst, b_port).expect("connect");
910 }
911
912 fn task_nodes(plan: &RenderPlan, name: &str) -> Vec<usize> {
913 plan.nodes
914 .iter()
915 .enumerate()
916 .filter_map(|(i, op)| match op {
917 Op::Task { task, .. } => {
918 let track = match task {
919 ProcessTask::Track(t)
920 | ProcessTask::FolderInput(t)
921 | ProcessTask::FolderOutput(t) => t,
922 ProcessTask::Plugin { track, .. } => track,
923 };
924 if track.lock().name == name {
925 Some(i)
926 } else {
927 None
928 }
929 }
930 _ => None,
931 })
932 .collect()
933 }
934
935 fn task_node(plan: &RenderPlan, name: &str, want: fn(&ProcessTask) -> bool) -> usize {
936 plan.nodes
937 .iter()
938 .enumerate()
939 .find_map(|(i, op)| match op {
940 Op::Task { task, .. } => {
941 let track = match task {
942 ProcessTask::Track(t)
943 | ProcessTask::FolderInput(t)
944 | ProcessTask::FolderOutput(t) => t,
945 ProcessTask::Plugin { track, .. } => track,
946 };
947 if track.lock().name == name && want(task) {
948 Some(i)
949 } else {
950 None
951 }
952 }
953 _ => None,
954 })
955 .expect("task node not found")
956 }
957
958 fn sum_nodes(plan: &RenderPlan) -> Vec<(usize, Vec<BufferId>, BufferId)> {
959 plan.nodes
960 .iter()
961 .enumerate()
962 .filter_map(|(i, op)| match op {
963 Op::Sum { inputs, output, .. } => Some((i, inputs.clone(), *output)),
964 _ => None,
965 })
966 .collect()
967 }
968
969 fn zero_count(plan: &RenderPlan) -> usize {
970 plan.nodes
971 .iter()
972 .filter(|op| matches!(op, Op::Zero { .. }))
973 .count()
974 }
975
976 fn is_track(t: &ProcessTask) -> bool {
977 matches!(t, ProcessTask::Track(_))
978 }
979 fn is_folder_input(t: &ProcessTask) -> bool {
980 matches!(t, ProcessTask::FolderInput(_))
981 }
982 fn is_folder_output(t: &ProcessTask) -> bool {
983 matches!(t, ProcessTask::FolderOutput(_))
984 }
985
986 #[test]
987 fn producer_chain_orders_zero_track_sum_track() {
988 let a = make_track("a", 1, 1);
989 let b = make_track("b", 1, 1);
990 connect(&a, 0, &b, 0);
991 let plan = RenderPlan::compile(&state_with(vec![a, b]), &[], &[], 64);
992 plan.verify().expect("invariants");
993
994 let sums = sum_nodes(&plan);
995 assert_eq!(sums.len(), 1, "one connected input -> one Sum");
996 assert_eq!(sums[0].1.len(), 1);
997
998 let task_a = task_node(&plan, "a", is_track);
999 let task_b = task_node(&plan, "b", is_track);
1000 let sum = sums[0].0;
1001 assert_eq!(zero_count(&plan), 1, "a's unconnected input -> Zero");
1002 let zero = plan
1003 .nodes
1004 .iter()
1005 .position(|op| matches!(op, Op::Zero { .. }))
1006 .expect("zero node");
1007
1008 assert!(zero < task_a, "Zero before the task that reads it");
1009 assert!(task_a < sum, "producer before the Sum of its consumer");
1010 assert!(sum < task_b, "Sum before the consuming track");
1011 assert_eq!(plan.sources, vec![zero as NodeId]);
1012 assert_eq!(plan.indegree[task_b], 1);
1013 assert!(plan.forced.is_empty());
1014 }
1015
1016 #[test]
1017 fn two_sources_insert_sum_with_two_inputs() {
1018 let a = make_track("a", 0, 1);
1019 let b = make_track("b", 0, 1);
1020 let c = make_track("c", 1, 1);
1021 connect(&a, 0, &c, 0);
1022 connect(&b, 0, &c, 0);
1023 let plan = RenderPlan::compile(&state_with(vec![a, b, c]), &[], &[], 64);
1024 plan.verify().expect("invariants");
1025
1026 let sums = sum_nodes(&plan);
1027 assert_eq!(sums.len(), 1);
1028 assert_eq!(sums[0].1.len(), 2, "both sources summed");
1029 assert_eq!(plan.indegree[sums[0].0], 2);
1030
1031 let task_a = task_node(&plan, "a", is_track);
1032 let task_b = task_node(&plan, "b", is_track);
1033 assert!(task_a < sums[0].0 && task_b < sums[0].0);
1034 assert_eq!(zero_count(&plan), 0);
1036 assert_eq!(plan.sources.len(), 2, "two root tracks are sources");
1037 }
1038
1039 #[test]
1040 fn metronome_source_is_produced_by_track_task() {
1041 let metronome = make_track("metronome", 0, 1);
1042 let source = {
1043 let mut track = metronome.lock();
1044 let (source, changed) = track.ensure_metronome_source(64);
1045 assert!(changed);
1046 source.expect("metronome source")
1047 };
1048 let plan = RenderPlan::compile(&state_with(vec![metronome]), &[], &[], 64);
1049 plan.verify().expect("invariants");
1050
1051 let source_key = Arc::as_ptr(&source) as usize;
1052 let source_buffer = *plan.port_map.get(&source_key).expect("source buffer");
1053 let task = task_node(&plan, "metronome", is_track);
1054
1055 match &plan.nodes[task] {
1056 Op::Task { outs, .. } => assert!(outs.contains(&source_buffer)),
1057 _ => unreachable!(),
1058 }
1059 }
1060
1061 #[test]
1062 fn folder_track_emits_input_child_output_chain() {
1063 let folder = make_track("folder", 1, 1);
1064 let child = make_track("child", 1, 1);
1065 folder.lock().is_folder = true;
1066 child.lock().parent_track = Some("folder".to_string());
1067 folder.lock().child_tracks.push(child.clone());
1068 let plan = RenderPlan::compile(&state_with(vec![folder, child]), &[], &[], 64);
1069 plan.verify().expect("invariants");
1070
1071 let fi = task_node(&plan, "folder", is_folder_input);
1072 let fo = task_node(&plan, "folder", is_folder_output);
1073 let child_task = task_node(&plan, "child", is_track);
1074 assert!(fi < child_task, "folder input before child");
1075 assert!(child_task < fo, "child before folder output");
1076 assert!(plan.dependents[fi].contains(&(child_task as NodeId)));
1077 assert!(plan.dependents[child_task].contains(&(fo as NodeId)));
1078 assert_eq!(task_nodes(&plan, "child").len(), 1);
1080 }
1081
1082 #[test]
1083 fn midi_only_connection_inserts_ordering_edge() {
1084 let a = Arc::new(Track::new("a".to_string(), 0, 0, 0, 1, 64, 48_000.0));
1086 let b = Arc::new(Track::new("b".to_string(), 0, 0, 1, 0, 64, 48_000.0));
1087 let a_out = a.lock().midi.outs[0].clone();
1088 let b_in = b.lock().midi.ins[0].clone();
1089 crate::midi::io::MIDIIO::connect(&a_out, &b_in);
1090
1091 let plan = RenderPlan::compile(&state_with(vec![a, b]), &[], &[], 64);
1092 plan.verify().expect("invariants");
1093
1094 let task_a = task_node(&plan, "a", is_track);
1095 let task_b = task_node(&plan, "b", is_track);
1096 assert_eq!(plan.midi_edges, vec![(task_a as NodeId, task_b as NodeId)]);
1097 assert!(task_a < task_b, "producer task ordered before consumer");
1098 assert!(plan.dependents[task_a].contains(&(task_b as NodeId)));
1099 assert!(plan.forced.is_empty());
1100 }
1101
1102 #[test]
1103 fn feedback_cycle_is_broken_and_marked_forced() {
1104 let a = make_track("a", 1, 1);
1105 let b = make_track("b", 1, 1);
1106 connect(&a, 0, &b, 0);
1107 connect(&b, 0, &a, 0);
1108 let plan = RenderPlan::compile(&state_with(vec![a, b]), &[], &[], 64);
1109
1110 assert_eq!(plan.nodes.len(), 4);
1112 assert!(plan.sources.is_empty());
1113 assert_eq!(plan.forced.len(), 4, "whole cycle marked forced");
1114 plan.verify().expect("invariants tolerate forced cycle");
1116 }
1117
1118 #[test]
1119 fn hw_bridges_become_source_and_sink_nodes() {
1120 let t = make_track("t", 1, 1);
1121 let hw_in = Arc::new(AudioIO::new(64));
1122 let hw_out = Arc::new(AudioIO::new(64));
1123 {
1125 let track = t.lock();
1126 AudioIO::connect(&hw_in, &track.audio.ins[0]);
1127 AudioIO::connect(&track.audio.outs[0], &hw_out);
1128 }
1129 let plan = RenderPlan::compile(
1130 &state_with(vec![t]),
1131 std::slice::from_ref(&hw_in),
1132 std::slice::from_ref(&hw_out),
1133 64,
1134 );
1135 plan.verify().expect("invariants");
1136
1137 let hw_node = plan
1138 .nodes
1139 .iter()
1140 .position(|op| matches!(op, Op::HwInput { .. }))
1141 .expect("HwInput node");
1142 assert_eq!(plan.hw_in_map.len(), 1);
1143 assert_eq!(plan.hw_out_map.len(), 1);
1144 let (chan, buf) = plan.hw_in_map[0];
1145 assert_eq!(chan, 0);
1146 match &plan.nodes[hw_node] {
1147 Op::HwInput { output, .. } => assert_eq!(*output, buf),
1148 _ => unreachable!(),
1149 }
1150 assert!(plan.sources.contains(&(hw_node as NodeId)));
1152 let sums = sum_nodes(&plan);
1153 assert_eq!(sums.len(), 2, "track input sum + hw_out bridge sum");
1154 let (out_buf, out_chan) = plan.hw_out_map[0];
1156 assert_eq!(out_chan, 0);
1157 assert!(sums.iter().any(|(_, _, output)| *output == out_buf));
1158 }
1159
1160 fn hand_plan(
1162 buffers: usize,
1163 nodes: Vec<Op>,
1164 indegree: Vec<u32>,
1165 dependents: Vec<Vec<NodeId>>,
1166 sources: Vec<NodeId>,
1167 ) -> RenderPlan {
1168 RenderPlan {
1169 buffer_size: 64,
1170 buffers: (0..buffers)
1171 .map(|_| UnsafeCell::new(vec![0.0; 64]))
1172 .collect(),
1173 buffer_latencies: (0..buffers).map(|_| AtomicUsize::new(0)).collect(),
1174 nodes,
1175 indegree,
1176 dependents,
1177 sources,
1178 hw_in_map: vec![],
1179 hw_out_map: vec![],
1180 port_map: HashMap::new(),
1181 midi_edges: vec![],
1182 forced: vec![],
1183 }
1184 }
1185
1186 #[test]
1187 fn verify_rejects_backward_edge() {
1188 let plan = hand_plan(
1189 2,
1190 vec![
1191 Op::Sum {
1192 inputs: vec![1],
1193 delays: vec![UnsafeCell::new(DelayLine::new())],
1194 output: 0,
1195 },
1196 Op::HwInput {
1197 channel: 0,
1198 output: 1,
1199 },
1200 ],
1201 vec![1, 0],
1202 vec![vec![], vec![0]],
1203 vec![1],
1204 );
1205 assert!(plan.verify().is_err());
1208 }
1209
1210 #[test]
1211 fn verify_rejects_racing_writers() {
1212 let plan = hand_plan(
1214 3,
1215 vec![
1216 Op::HwInput {
1217 channel: 0,
1218 output: 1,
1219 },
1220 Op::HwInput {
1221 channel: 1,
1222 output: 2,
1223 },
1224 Op::Sum {
1225 inputs: vec![1],
1226 delays: vec![UnsafeCell::new(DelayLine::new())],
1227 output: 0,
1228 },
1229 Op::Sum {
1230 inputs: vec![2],
1231 delays: vec![UnsafeCell::new(DelayLine::new())],
1232 output: 0,
1233 },
1234 ],
1235 vec![2, 0, 0, 0],
1236 vec![vec![], vec![2], vec![], vec![]],
1237 vec![0, 1],
1238 );
1239 let err = plan.verify().expect_err("racing writers must fail");
1240 assert!(err.contains("unordered nodes"));
1241 }
1242
1243 #[test]
1244 fn buffers_are_sized_and_silent() {
1245 let t = make_track("t", 2, 1);
1246 let plan = RenderPlan::compile(&state_with(vec![t]), &[], &[], 256);
1247 assert_eq!(plan.buffer_size, 256);
1248 assert_eq!(plan.buffer_count(), 3);
1250 for i in 0..plan.buffer_count() as BufferId {
1251 let buf = unsafe { plan.buffer(i) };
1253 assert_eq!(buf.len(), 256);
1254 assert!(buf.iter().all(|&s| s == 0.0));
1255 }
1256 assert_eq!(zero_count(&plan), 2);
1258 assert_eq!(plan.port_map.len(), 3);
1259 }
1260}