sim_lib_audio_graph_core/
graph.rs1use std::collections::{BTreeMap, VecDeque};
2
3use sim_kernel::{Error, Result};
4
5use crate::{
6 BlockArena, NullEventSink, Patch, PatchNode, PortDecl, PortDir, PortUri, PrepareConfig,
7 ProcessBlock, Processor, ProcessorDescriptor, RateContract, Transport,
8};
9
10#[derive(Clone, Debug, PartialEq, Eq)]
12pub struct Cable {
13 pub from: PortUri,
15 pub to: PortUri,
17}
18
19impl Cable {
20 pub fn new(from: PortUri, to: PortUri) -> Self {
22 Self { from, to }
23 }
24}
25
26pub struct Graph {
33 nodes: BTreeMap<String, GraphNode>,
34 cables: Vec<Cable>,
35 order: Vec<String>,
36 prepared: Option<PreparedGraph>,
37 arena: BlockArena,
38}
39
40struct GraphNode {
41 processor: Box<dyn Processor>,
42 in_channels: u16,
43 out_channels: u16,
44 descriptor: ProcessorDescriptor,
45}
46
47#[derive(Clone, Copy, Debug, PartialEq, Eq)]
48struct PreparedGraph {
49 max_block_frames: u32,
50}
51
52impl Default for Graph {
53 fn default() -> Self {
54 Self::new()
55 }
56}
57
58impl Graph {
59 pub fn new() -> Self {
61 Self {
62 nodes: BTreeMap::new(),
63 cables: Vec::new(),
64 order: Vec::new(),
65 prepared: None,
66 arena: BlockArena::empty(),
67 }
68 }
69
70 pub fn add_node(
75 &mut self,
76 id: impl Into<String>,
77 processor: Box<dyn Processor>,
78 in_channels: u16,
79 out_channels: u16,
80 ) -> Result<()> {
81 let id = id.into();
82 if id.is_empty() {
83 return Err(Error::Eval(
84 "audio graph node id cannot be empty".to_owned(),
85 ));
86 }
87 if self.nodes.contains_key(&id) {
88 return Err(Error::Eval(format!("duplicate audio graph node: {id}")));
89 }
90 let descriptor = processor.descriptor(in_channels, out_channels);
91 self.nodes.insert(
92 id,
93 GraphNode {
94 processor,
95 in_channels,
96 out_channels,
97 descriptor,
98 },
99 );
100 self.order.clear();
101 self.prepared = None;
102 Ok(())
103 }
104
105 pub fn connect(&mut self, from: PortUri, to: PortUri) -> Result<()> {
110 let source_rate = self.validate_endpoint(&from, PortDirection::Out)?;
111 let target_rate = self.validate_endpoint(&to, PortDirection::In)?;
112 source_rate.ensure_compatible(target_rate)?;
113 self.cables.push(Cable::new(from, to));
114 match self.topological_order() {
115 Ok(order) => {
116 self.order = order;
117 self.prepared = None;
118 Ok(())
119 }
120 Err(error) => {
121 self.cables.pop();
122 Err(error)
123 }
124 }
125 }
126
127 pub fn prepare(&mut self, sample_rate_hz: u32, max_block_frames: u32) -> Result<()> {
130 if sample_rate_hz == 0 {
131 return Err(Error::Eval("sample rate must be nonzero".to_owned()));
132 }
133 if max_block_frames == 0 {
134 return Err(Error::Eval("max block frames must be nonzero".to_owned()));
135 }
136 let order = self.topological_order()?;
137 let mut max_channels = 1usize;
138 for id in &order {
139 let node = self
140 .nodes
141 .get_mut(id)
142 .ok_or_else(|| Error::Eval(format!("missing audio graph node: {id}")))?;
143 max_channels = max_channels.max(usize::from(node.in_channels));
144 max_channels = max_channels.max(usize::from(node.out_channels));
145 node.processor.prepare(PrepareConfig::new(
146 sample_rate_hz,
147 max_block_frames,
148 node.in_channels,
149 node.out_channels,
150 ));
151 }
152 self.order = order;
153 self.prepared = Some(PreparedGraph { max_block_frames });
154 self.arena = BlockArena::with_f32_capacity(max_block_frames as usize * max_channels);
155 Ok(())
156 }
157
158 pub fn process_offline(&mut self, input: &[Vec<f32>], frames: u32) -> Result<Vec<Vec<f32>>> {
164 let prepared = self.prepared.ok_or_else(|| {
165 Error::Eval("audio graph must be prepared before processing".to_owned())
166 })?;
167 if frames > prepared.max_block_frames {
168 return Err(Error::Eval(format!(
169 "process block has {frames} frames, max prepared block is {}",
170 prepared.max_block_frames
171 )));
172 }
173 if self.order.is_empty() {
174 return Err(Error::Eval("audio graph has no nodes".to_owned()));
175 }
176 let frames_len = frames as usize;
177 for (index, lane) in input.iter().enumerate() {
178 if lane.len() < frames_len {
179 return Err(Error::Eval(format!(
180 "input audio lane {index} has {} frames, expected at least {frames_len}",
181 lane.len()
182 )));
183 }
184 }
185
186 let mut node_outputs = BTreeMap::<String, Vec<Vec<f32>>>::new();
187 let order = self.order.clone();
188 for id in &order {
189 let (in_channels, out_channels) = self.node_channel_counts(id)?;
190 let incoming = self.incoming_edges(id)?;
191 let mut in_buffers = vec![vec![0.0; frames_len]; usize::from(in_channels)];
192 if incoming.is_empty() {
193 for (channel, buffer) in in_buffers.iter_mut().enumerate() {
194 if let Some(source) = input.get(channel) {
195 buffer.copy_from_slice(&source[..frames_len]);
196 }
197 }
198 } else {
199 for edge in incoming {
200 let source_outputs = node_outputs.get(&edge.source_node).ok_or_else(|| {
201 Error::Eval(format!(
202 "missing processed output for node {}",
203 edge.source_node
204 ))
205 })?;
206 let source = source_outputs.get(edge.source_index).ok_or_else(|| {
207 Error::Eval(format!(
208 "source output channel {} is out of range for node {}",
209 edge.source_index, edge.source_node
210 ))
211 })?;
212 let target = in_buffers.get_mut(edge.target_index).ok_or_else(|| {
213 Error::Eval(format!(
214 "target input channel {} is out of range for node {id}",
215 edge.target_index
216 ))
217 })?;
218 target.copy_from_slice(&source[..frames_len]);
219 }
220 }
221
222 let mut out_buffers = vec![vec![0.0; frames_len]; usize::from(out_channels)];
223 {
224 let in_audio = in_buffers.iter().map(Vec::as_slice).collect::<Vec<_>>();
225 let mut out_audio = out_buffers
226 .iter_mut()
227 .map(Vec::as_mut_slice)
228 .collect::<Vec<_>>();
229 let mut out_events = NullEventSink;
230 let in_events = [];
231 self.arena.reset();
232 let mut block = ProcessBlock {
233 frames,
234 in_audio: in_audio.as_slice(),
235 out_audio: out_audio.as_mut_slice(),
236 in_events: &in_events,
237 out_events: &mut out_events,
238 transport: Transport::default(),
239 scratch: &mut self.arena,
240 };
241 block.validate_audio_lanes()?;
242 let processor = self
243 .nodes
244 .get_mut(id)
245 .ok_or_else(|| Error::Eval(format!("missing audio graph node: {id}")))?;
246 processor.processor.process(&mut block);
247 }
248 node_outputs.insert(id.clone(), out_buffers);
249 }
250
251 let output_node = order
252 .last()
253 .ok_or_else(|| Error::Eval("audio graph has no output node".to_owned()))?;
254 node_outputs
255 .remove(output_node)
256 .ok_or_else(|| Error::Eval(format!("missing graph output for node {output_node}")))
257 }
258
259 pub fn to_patch(&self) -> Patch {
261 Patch {
262 nodes: self.patch_nodes(),
263 cables: self.cables.clone(),
264 }
265 }
266
267 pub fn cables(&self) -> &[Cable] {
269 &self.cables
270 }
271
272 pub fn patch_nodes(&self) -> Vec<PatchNode> {
274 self.nodes
275 .iter()
276 .map(|(id, node)| PatchNode {
277 id: id.clone(),
278 in_channels: node.in_channels,
279 out_channels: node.out_channels,
280 })
281 .collect()
282 }
283
284 pub fn topological_node_order(&self) -> Result<Vec<String>> {
286 self.topological_order()
287 }
288
289 pub fn node_descriptor(&self, id: &str) -> Result<&ProcessorDescriptor> {
291 self.nodes
292 .get(id)
293 .map(|node| &node.descriptor)
294 .ok_or_else(|| Error::Eval(format!("unknown audio graph node: {id}")))
295 }
296
297 fn validate_endpoint(&self, uri: &PortUri, direction: PortDirection) -> Result<RateContract> {
298 let node_id = uri.node_id().ok_or_else(|| {
299 Error::Eval(format!("port URI does not reference a graph node: {uri}"))
300 })?;
301 let node = self
302 .nodes
303 .get(node_id)
304 .ok_or_else(|| Error::Eval(format!("unknown audio graph node: {node_id}")))?;
305 let port = descriptor_port(&node.descriptor, uri, direction)?;
306 if uri.index >= u32::from(port.channels) {
307 return Err(Error::Eval(format!(
308 "port index {} is out of range for node {node_id}",
309 uri.index
310 )));
311 }
312 Ok(port.rate_contract)
313 }
314
315 fn node_channel_counts(&self, id: &str) -> Result<(u16, u16)> {
316 self.nodes
317 .get(id)
318 .map(|node| (node.in_channels, node.out_channels))
319 .ok_or_else(|| Error::Eval(format!("missing audio graph node: {id}")))
320 }
321
322 fn incoming_edges(&self, node_id: &str) -> Result<Vec<Edge>> {
323 self.cables
324 .iter()
325 .filter(|cable| cable.to.node_id() == Some(node_id))
326 .map(|cable| {
327 Ok(Edge {
328 source_node: cable_source_node(cable)?,
329 source_index: cable.from.index as usize,
330 target_index: cable.to.index as usize,
331 })
332 })
333 .collect()
334 }
335
336 fn topological_order(&self) -> Result<Vec<String>> {
337 let mut indegree = self
338 .nodes
339 .keys()
340 .map(|id| (id.clone(), 0usize))
341 .collect::<BTreeMap<_, _>>();
342 let mut outgoing = self
343 .nodes
344 .keys()
345 .map(|id| (id.clone(), Vec::<String>::new()))
346 .collect::<BTreeMap<_, _>>();
347
348 for cable in &self.cables {
349 let source = cable_source_node(cable)?;
350 let target = cable_target_node(cable)?;
351 if !self.nodes.contains_key(&source) {
352 return Err(Error::Eval(format!("unknown audio graph node: {source}")));
353 }
354 if !self.nodes.contains_key(&target) {
355 return Err(Error::Eval(format!("unknown audio graph node: {target}")));
356 }
357 *indegree
358 .get_mut(&target)
359 .ok_or_else(|| Error::Eval(format!("missing node indegree: {target}")))? += 1;
360 outgoing
361 .get_mut(&source)
362 .ok_or_else(|| Error::Eval(format!("missing node outputs: {source}")))?
363 .push(target);
364 }
365 for targets in outgoing.values_mut() {
366 targets.sort();
367 }
368
369 let mut ready = indegree
370 .iter()
371 .filter_map(|(id, count)| (*count == 0).then_some(id.clone()))
372 .collect::<VecDeque<_>>();
373 let mut order = Vec::with_capacity(self.nodes.len());
374 while let Some(id) = ready.pop_front() {
375 order.push(id.clone());
376 let targets = outgoing
377 .get(&id)
378 .ok_or_else(|| Error::Eval(format!("missing outgoing list for node {id}")))?;
379 for target in targets {
380 let count = indegree
381 .get_mut(target)
382 .ok_or_else(|| Error::Eval(format!("missing indegree for node {target}")))?;
383 *count = count.saturating_sub(1);
384 if *count == 0 {
385 ready.push_back(target.clone());
386 }
387 }
388 }
389 if order.len() != self.nodes.len() {
390 return Err(Error::Eval("audio graph contains a cycle".to_owned()));
391 }
392 Ok(order)
393 }
394}
395
396#[derive(Clone, Copy, Debug, PartialEq, Eq)]
397enum PortDirection {
398 In,
399 Out,
400}
401
402impl PortDirection {
403 fn port_dir(self) -> PortDir {
404 match self {
405 Self::In => PortDir::In,
406 Self::Out => PortDir::Out,
407 }
408 }
409}
410
411fn descriptor_port<'a>(
412 descriptor: &'a ProcessorDescriptor,
413 uri: &PortUri,
414 direction: PortDirection,
415) -> Result<&'a PortDecl> {
416 let port_name = uri
417 .node_port_name()
418 .ok_or_else(|| Error::Eval(format!("port URI does not name a graph node port: {uri}")))?;
419 let direction_ports = descriptor
420 .ports()
421 .iter()
422 .filter(|port| port.dir == direction.port_dir())
423 .collect::<Vec<_>>();
424 if let Some(port) = direction_ports
425 .iter()
426 .copied()
427 .find(|port| port.name == port_name)
428 {
429 return Ok(port);
430 }
431 if direction_ports.len() == 1 {
432 return Ok(direction_ports[0]);
433 }
434 Err(Error::Eval(format!(
435 "unknown audio graph node port {port_name}"
436 )))
437}
438
439#[derive(Clone, Debug, PartialEq, Eq)]
440struct Edge {
441 source_node: String,
442 source_index: usize,
443 target_index: usize,
444}
445
446fn cable_source_node(cable: &Cable) -> Result<String> {
447 cable
448 .from
449 .node_id()
450 .map(str::to_owned)
451 .ok_or_else(|| Error::Eval(format!("invalid source port URI: {}", cable.from)))
452}
453
454fn cable_target_node(cable: &Cable) -> Result<String> {
455 cable
456 .to
457 .node_id()
458 .map(str::to_owned)
459 .ok_or_else(|| Error::Eval(format!("invalid target port URI: {}", cable.to)))
460}