1use crate::{Instruction, InstructionBatch, NodeId};
7
8#[derive(Debug, Clone)]
10pub enum MountError {
11 DuplicateKey(String),
13}
14
15impl std::fmt::Display for MountError {
16 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
17 match self {
18 Self::DuplicateKey(key) => write!(f, "duplicate mounted node key: {key}"),
19 }
20 }
21}
22
23impl std::error::Error for MountError {}
24
25#[derive(Debug, Clone, Default)]
27pub struct Graph {
28 roots: Vec<Node>,
29}
30
31#[derive(Debug, Clone)]
33pub enum GraphRoots {
34 Single(Node),
36 Many(Vec<Node>),
38}
39
40impl From<Node> for GraphRoots {
41 fn from(node: Node) -> Self {
42 Self::Single(node)
43 }
44}
45
46impl From<Vec<Node>> for GraphRoots {
47 fn from(roots: Vec<Node>) -> Self {
48 Self::Many(roots)
49 }
50}
51
52impl<const N: usize> From<[Node; N]> for GraphRoots {
53 fn from(roots: [Node; N]) -> Self {
54 Self::Many(roots.into_iter().collect())
55 }
56}
57
58#[derive(Debug, Clone)]
87pub struct MountedNode {
88 node_id: NodeId,
89 kind: String,
90 key: Option<String>,
91}
92
93impl MountedNode {
94 pub fn id(&self) -> NodeId {
96 self.node_id
97 }
98
99 pub fn kind(&self) -> &str {
101 &self.kind
102 }
103
104 pub fn key(&self) -> Option<&str> {
106 self.key.as_deref()
107 }
108
109 pub fn set_property(
111 &self,
112 property: impl Into<String>,
113 value: serde_json::Value,
114 ) -> InstructionBatch {
115 let mut batch = InstructionBatch::new();
116 batch.push(Instruction::SetProperty {
117 node_id: self.node_id,
118 property: property.into(),
119 value,
120 });
121 batch.push(Instruction::CommitUpdates);
122 batch
123 }
124
125 pub fn set_const_value(&self, value: f64) -> InstructionBatch {
127 self.set_property("value", serde_json::json!(value))
128 }
129}
130
131#[derive(Debug, Clone, Default)]
133pub struct MountedGraph {
134 batch: InstructionBatch,
135 roots: Vec<MountedNode>,
136 nodes: Vec<(Vec<usize>, MountedNode)>,
137 keyed_nodes: Vec<(String, MountedNode)>,
138}
139
140impl MountedGraph {
141 pub fn batch(&self) -> &InstructionBatch {
143 &self.batch
144 }
145
146 pub fn into_batch(self) -> InstructionBatch {
148 self.batch
149 }
150
151 pub fn roots(&self) -> &[MountedNode] {
153 &self.roots
154 }
155
156 pub fn node_at(&self, path: &[usize]) -> Option<MountedNode> {
158 self.nodes
159 .iter()
160 .find(|(node_path, _)| node_path.as_slice() == path)
161 .map(|(_, node)| node.clone())
162 }
163
164 pub fn node_with_key(&self, key: &str) -> Option<MountedNode> {
166 self.keyed_nodes
167 .iter()
168 .find(|(node_key, _)| node_key == key)
169 .map(|(_, node)| node.clone())
170 }
171
172 pub fn all_nodes(&self) -> impl Iterator<Item = (&[usize], &MountedNode)> {
174 self.nodes
175 .iter()
176 .map(|(path, node)| (path.as_slice(), node))
177 }
178
179 pub fn set_const_value(&self, key: &str, value: f64) -> Option<InstructionBatch> {
181 let node = self.node_with_key(key)?;
182
183 if node.kind() != "const" {
184 return None;
185 }
186
187 Some(node.set_const_value(value))
188 }
189}
190
191impl Graph {
192 pub fn new() -> Self {
194 Self::default()
195 }
196
197 pub fn render<R>(mut self, roots: R) -> Self
199 where
200 R: Into<GraphRoots>,
201 {
202 match roots.into() {
203 GraphRoots::Single(node) => self.roots.push(node),
204 GraphRoots::Many(roots) => self.roots.extend(roots),
205 }
206 self
207 }
208
209 pub fn root<R>(self, roots: R) -> Self
211 where
212 R: Into<GraphRoots>,
213 {
214 self.render(roots)
215 }
216
217 pub fn with_root<R>(self, roots: R) -> Self
219 where
220 R: Into<GraphRoots>,
221 {
222 self.render(roots)
223 }
224
225 pub fn mount(&self) -> std::result::Result<MountedGraph, MountError> {
230 let mut next_id: NodeId = 1;
231 self.mount_with_id_counter(&mut next_id)
232 }
233
234 pub fn mount_with_id_counter(
243 &self,
244 next_id: &mut NodeId,
245 ) -> std::result::Result<MountedGraph, MountError> {
246 let mut batch = InstructionBatch::new();
247 let mut mounted = MountedGraph::default();
248
249 let mut lowered_roots = Vec::with_capacity(self.roots.len());
250
251 for (channel, root) in self.roots.iter().enumerate() {
252 let root_id = *next_id;
253 *next_id += 1;
254
255 batch.push(Instruction::CreateNode {
256 node_id: root_id,
257 node_type: "root".to_string(),
258 });
259 batch.push(Instruction::SetProperty {
260 node_id: root_id,
261 property: "channel".to_string(),
262 value: serde_json::json!(channel),
263 });
264
265 let child_id = *next_id;
266 *next_id += 1;
267 let path = vec![channel];
268 let mounted_root = lower_node(
269 root,
270 child_id,
271 &path,
272 next_id,
273 &mut batch,
274 &mut mounted.nodes,
275 &mut mounted.keyed_nodes,
276 )?;
277
278 batch.push(Instruction::AppendChild {
279 parent_id: root_id,
280 child_id,
281 child_output_channel: root.output_channel as i32,
282 });
283 lowered_roots.push(root_id);
284 mounted.roots.push(mounted_root);
285 }
286
287 batch.push(Instruction::ActivateRoots {
288 roots: lowered_roots,
289 });
290 batch.push(Instruction::CommitUpdates);
291
292 mounted.batch = batch;
293 Ok(mounted)
294 }
295
296 pub fn push_root(&mut self, node: Node) {
298 self.roots.push(node);
299 }
300
301 pub fn roots(&self) -> &[Node] {
303 &self.roots
304 }
305
306 pub fn lower(&self) -> InstructionBatch {
311 self.mount()
312 .expect("graph contains duplicate keys")
313 .into_batch()
314 }
315}
316
317#[derive(Debug, Clone)]
319pub struct Node {
320 kind: String,
321 props: serde_json::Value,
322 children: Vec<Node>,
323 output_channel: usize,
324}
325
326impl Node {
327 pub(crate) fn new(
328 kind: impl Into<String>,
329 props: serde_json::Value,
330 children: Vec<Node>,
331 ) -> Self {
332 Self {
333 kind: kind.into(),
334 props,
335 children,
336 output_channel: 0,
337 }
338 }
339
340 pub(crate) fn with_output_channel(mut self, output_channel: usize) -> Self {
341 self.output_channel = output_channel;
342 self
343 }
344
345 pub fn kind(&self) -> &str {
346 &self.kind
347 }
348
349 pub fn props(&self) -> &serde_json::Value {
350 &self.props
351 }
352
353 pub fn children(&self) -> &[Node] {
354 &self.children
355 }
356
357 pub fn output_channel(&self) -> usize {
358 self.output_channel
359 }
360}
361
362pub type Value = serde_json::Value;
363
364fn lower_node(
365 node: &Node,
366 node_id: NodeId,
367 path: &[usize],
368 next_id: &mut NodeId,
369 batch: &mut InstructionBatch,
370 mounted_nodes: &mut Vec<(Vec<usize>, MountedNode)>,
371 keyed_nodes: &mut Vec<(String, MountedNode)>,
372) -> std::result::Result<MountedNode, MountError> {
373 batch.push(Instruction::CreateNode {
374 node_id,
375 node_type: node.kind.clone(),
376 });
377
378 if let Value::Object(props) = &node.props {
379 for (key, value) in props {
380 batch.push(Instruction::SetProperty {
381 node_id,
382 property: key.clone(),
383 value: value.clone(),
384 });
385 }
386 }
387
388 for (child_index, child) in node.children.iter().enumerate() {
389 let child_id = *next_id;
390 *next_id += 1;
391
392 let mut child_path = path.to_vec();
393 child_path.push(child_index);
394
395 lower_node(
396 child,
397 child_id,
398 &child_path,
399 next_id,
400 batch,
401 mounted_nodes,
402 keyed_nodes,
403 )?;
404 batch.push(Instruction::AppendChild {
405 parent_id: node_id,
406 child_id,
407 child_output_channel: child.output_channel as i32,
408 });
409 }
410
411 let mounted_node = MountedNode {
412 node_id,
413 kind: node.kind.clone(),
414 key: key_from_props(&node.props),
415 };
416 mounted_nodes.push((path.to_vec(), mounted_node.clone()));
417 if let Some(key) = mounted_node.key.clone() {
418 if keyed_nodes
419 .iter()
420 .any(|(existing_key, _)| existing_key == &key)
421 {
422 log::error!("duplicate mounted node key: {key}");
423 return Err(MountError::DuplicateKey(key));
424 }
425 keyed_nodes.push((key, mounted_node.clone()));
426 }
427 Ok(mounted_node)
428}
429
430fn key_from_props(props: &Value) -> Option<String> {
431 match props {
432 Value::Object(map) => map
433 .get("key")
434 .and_then(|value| value.as_str())
435 .map(|key| key.to_string()),
436 _ => None,
437 }
438}
439
440#[cfg(test)]
441mod tests {
442 use super::Graph;
443 use crate::authoring::{el, extra, mc};
444
445 #[test]
446 fn lowers_multichannel_output_channels_on_append_edges() {
447 let graph = Graph::new().root(mc::sample(
448 serde_json::json!({"path": "a.wav", "channels": 2}),
449 el::const_(1.0),
450 ));
451
452 let payload: serde_json::Value =
453 serde_json::from_str(&graph.lower().to_json_string()).expect("valid batch json");
454 let instructions = payload.as_array().expect("batch is an array");
455 let mut node_types = std::collections::HashMap::new();
456
457 for instruction in instructions {
458 let array = instruction.as_array().expect("instruction is an array");
459 if array.first().and_then(|value| value.as_i64()) == Some(0) {
460 node_types.insert(
461 array[1].as_i64().expect("node id") as i32,
462 array[2].as_str().expect("node type"),
463 );
464 }
465 }
466
467 let output_channels: Vec<i64> = instructions
468 .iter()
469 .filter_map(|instruction| {
470 let array = instruction.as_array()?;
471 if array.first()?.as_i64()? != 2 {
472 return None;
473 }
474
475 let parent_id = array.get(1)?.as_i64()? as i32;
476 if node_types.get(&parent_id).copied() != Some("root") {
477 return None;
478 }
479
480 array.get(3)?.as_i64()
481 })
482 .collect();
483
484 assert_eq!(output_channels, vec![0, 1]);
485 }
486
487 #[test]
488 fn stride_delay_mounts_with_signal_children() {
489 let delay_ms = el::const_with_key("delay", 250.0);
490 let fb = el::const_with_key("fb", 0.3);
491 let input = el::r#in(serde_json::json!({"channel": 0}), None);
492
493 let delayed = extra::stride_delay(
494 serde_json::json!({ "maxDelayMs": 1500, "transitionMs": 60 }),
495 delay_ms,
496 fb,
497 input,
498 );
499
500 let graph = Graph::new().render(vec![delayed]);
501 let mounted = graph.mount().expect("mount");
502 let batch = mounted.batch();
503
504 let json = batch.to_json_string();
506 assert!(json.len() > 2, "batch should contain instructions");
507
508 let sd_nodes: Vec<_> = mounted
510 .all_nodes()
511 .filter(|(_, n)| n.kind() == "stridedelay")
512 .collect();
513 assert_eq!(sd_nodes.len(), 1, "expected 1 stridedelay node");
514
515 let delay_const = mounted
517 .all_nodes()
518 .find(|(_, n)| n.key().as_deref() == Some("delay"));
519 assert!(delay_const.is_some(), "keyed 'delay' const should exist");
520
521 let fb_const = mounted
522 .all_nodes()
523 .find(|(_, n)| n.key().as_deref() == Some("fb"));
524 assert!(fb_const.is_some(), "keyed 'fb' const should exist");
525 }
526
527 #[test]
528 fn stride_delay_children_order_in_batch() {
529 let delay_ms = el::const_with_key("delay", 250.0);
532 let fb = el::const_with_key("fb", 0.0);
533 let input = el::r#in(serde_json::json!({"channel": 0}), None);
534
535 let delayed = extra::stride_delay(
536 serde_json::json!({ "maxDelayMs": 1500 }),
537 delay_ms,
538 fb,
539 input,
540 );
541
542 let graph = Graph::new().render(vec![delayed]);
543 let json_str = graph.lower().to_json_string();
544 let payload: serde_json::Value = serde_json::from_str(&json_str).expect("valid batch json");
545 let instructions = payload.as_array().expect("batch is an array");
546
547 let mut node_types: std::collections::HashMap<i64, String> =
549 std::collections::HashMap::new();
550 for inst in instructions {
551 let arr = inst.as_array().expect("instruction is array");
552 if arr[0].as_i64() == Some(0) {
553 node_types.insert(
555 arr[1].as_i64().unwrap(),
556 arr[2].as_str().unwrap().to_string(),
557 );
558 }
559 }
560
561 let sd_id = node_types
563 .iter()
564 .find(|(_, t)| t.as_str() == "stridedelay")
565 .map(|(id, _)| *id)
566 .expect("stridedelay node should exist");
567
568 let children_of_sd: Vec<(i64, String)> = instructions
571 .iter()
572 .filter_map(|inst| {
573 let arr = inst.as_array()?;
574 if arr[0].as_i64()? != 2 {
575 return None;
576 }
577 if arr[1].as_i64()? != sd_id {
578 return None;
579 }
580 let child_id = arr[2].as_i64()?;
581 let child_type = node_types.get(&child_id)?.clone();
582 Some((child_id, child_type))
583 })
584 .collect();
585
586 assert_eq!(
588 children_of_sd.len(),
589 3,
590 "stridedelay should have 3 children"
591 );
592 assert_eq!(
593 children_of_sd[0].1, "const",
594 "child 0 should be const (delayMs)"
595 );
596 assert_eq!(children_of_sd[1].1, "const", "child 1 should be const (fb)");
597 assert_eq!(children_of_sd[2].1, "in", "child 2 should be in (audio)");
598 }
599
600 #[test]
601 fn stride_delay_runtime_produces_output() {
602 use crate::Runtime;
603
604 let sr = 44100.0;
605 let block = 64;
606 let runtime = Runtime::new()
607 .sample_rate(sr)
608 .buffer_size(block)
609 .call()
610 .expect("runtime creation");
611
612 let delay_ms = el::const_(250.0);
615 let fb = el::const_(0.0);
616 let input = el::r#in(serde_json::json!({"channel": 0}), None);
617
618 let delayed = extra::stride_delay(
619 serde_json::json!({ "maxDelayMs": 500, "transitionMs": 10 }),
620 delay_ms,
621 fb,
622 input,
623 );
624
625 let graph = Graph::new().render(vec![delayed]);
626 let mounted = graph.mount().expect("mount");
627 runtime
628 .apply_instructions(mounted.batch())
629 .expect("apply instructions");
630
631 let mut input_buf = vec![0.0_f64; block];
633 input_buf[0] = 1.0;
634 let mut output_buf = vec![0.0_f64; block];
635
636 let inputs = [input_buf.as_slice()];
637 let mut outputs = [output_buf.as_mut_slice()];
638 runtime
639 .process(block, &inputs, &mut outputs)
640 .expect("process");
641
642 let silence = vec![0.0_f64; block];
650 for _ in 0..200 {
651 let inputs = [silence.as_slice()];
652 let mut out = vec![0.0_f64; block];
653 let mut outputs = [out.as_mut_slice()];
654 runtime
655 .process(block, &inputs, &mut outputs)
656 .expect("process");
657
658 if outputs[0].iter().any(|&s| s.abs() > 1e-10) {
660 return;
662 }
663 }
664
665 panic!("stride delay produced no output after 200 blocks — delay effect is not working");
666 }
667
668 #[test]
669 fn stride_delay_with_computed_children_produces_output() {
670 use crate::Runtime;
673
674 let sr = 44100.0;
675 let block = 64;
676 let runtime = Runtime::new()
677 .sample_rate(sr)
678 .buffer_size(block)
679 .call()
680 .expect("runtime creation");
681
682 let base_delay = el::const_(250.0);
683 let spread = el::const_(0.0);
684 let offset = el::const_(0.0); let ch_delay = el::mul((
688 base_delay,
689 el::add((1.0, el::mul((spread.clone(), offset.clone())))),
690 ));
691
692 let base_fb = el::const_(0.3);
694 let ch_fb = el::mul((
695 base_fb,
696 el::sub((1.0, el::mul((spread, el::mul((offset, 0.3)))))),
697 ));
698
699 let input = el::r#in(serde_json::json!({"channel": 0}), None);
700
701 let delayed = extra::stride_delay(
702 serde_json::json!({ "maxDelayMs": 500, "transitionMs": 10 }),
703 ch_delay,
704 ch_fb,
705 input,
706 );
707
708 let graph = Graph::new().render(vec![delayed]);
709 let mounted = graph.mount().expect("mount");
710 runtime
711 .apply_instructions(mounted.batch())
712 .expect("apply instructions");
713
714 let mut input_buf = vec![0.0_f64; block];
716 input_buf[0] = 1.0;
717 let mut output_buf = vec![0.0_f64; block];
718
719 let inputs = [input_buf.as_slice()];
720 let mut outputs = [output_buf.as_mut_slice()];
721 runtime
722 .process(block, &inputs, &mut outputs)
723 .expect("process");
724
725 let silence = vec![0.0_f64; block];
726 for _ in 0..200 {
727 let inputs = [silence.as_slice()];
728 let mut out = vec![0.0_f64; block];
729 let mut outputs = [out.as_mut_slice()];
730 runtime
731 .process(block, &inputs, &mut outputs)
732 .expect("process");
733
734 if outputs[0].iter().any(|&s| s.abs() > 1e-10) {
735 return; }
737 }
738
739 panic!("stride delay with computed children produced no output");
740 }
741
742 #[test]
743 fn stride_delay_with_mix_blend() {
744 use crate::Runtime;
746
747 let sr = 44100.0;
748 let block = 512;
749 let runtime = Runtime::new()
750 .sample_rate(sr)
751 .buffer_size(block)
752 .call()
753 .expect("runtime creation");
754
755 let delay_ms = el::const_(50.0); let fb = el::const_(0.0);
757 let mix_val = 0.5;
758 let mix_wet = el::const_(mix_val);
759 let mix_dry = el::const_(mix_val);
760 let input = el::r#in(serde_json::json!({"channel": 0}), None);
761
762 let delayed = extra::stride_delay(
763 serde_json::json!({ "maxDelayMs": 200, "transitionMs": 10 }),
764 delay_ms,
765 fb,
766 input.clone(),
767 );
768
769 let wet = el::mul((delayed, mix_wet));
771 let dry = el::mul((input, el::sub((1.0, mix_dry))));
772 let out = el::add((wet, dry));
773
774 let graph = Graph::new().render(vec![out]);
775 let mounted = graph.mount().expect("mount");
776 runtime
777 .apply_instructions(mounted.batch())
778 .expect("apply instructions");
779
780 let input_signal: Vec<f64> = (0..block)
782 .map(|i| if i < 100 { 1.0 } else { 0.0 })
783 .collect();
784 let mut output_buf = vec![0.0_f64; block];
785
786 let inputs = [input_signal.as_slice()];
787 let mut outputs = [output_buf.as_mut_slice()];
788 runtime
789 .process(block, &inputs, &mut outputs)
790 .expect("process");
791
792 let first_block_max = outputs[0].iter().copied().fold(0.0_f64, f64::max);
794 eprintln!("first block max output: {first_block_max}");
795
796 let mut output_buf2 = vec![0.0_f64; block];
798 let inputs2 = [input_signal.as_slice()];
799 let mut outputs2 = [output_buf2.as_mut_slice()];
800 runtime
801 .process(block, &inputs2, &mut outputs2)
802 .expect("process 2");
803 let second_block_max = outputs2[0].iter().copied().fold(0.0_f64, f64::max);
804 eprintln!("second block max output: {second_block_max}");
805
806 assert!(
807 first_block_max > 0.01 || second_block_max > 0.01,
808 "dry path should produce output within first two blocks, got max={first_block_max}, {second_block_max}"
809 );
810
811 let silence = vec![0.0_f64; block];
814 let mut found_delayed = false;
815
816 for block_num in 0..20 {
817 let inputs = [silence.as_slice()];
818 let mut out = vec![0.0_f64; block];
819 let mut outputs = [out.as_mut_slice()];
820 runtime
821 .process(block, &inputs, &mut outputs)
822 .expect("process");
823
824 if outputs[0].iter().any(|&s| s.abs() > 0.01) {
826 found_delayed = true;
827 eprintln!(
828 "delayed signal appeared in block {} (sample ~{})",
829 block_num + 1,
830 (block_num + 1) * block
831 );
832 break;
833 }
834 }
835
836 assert!(
837 found_delayed,
838 "delay effect should produce output after the dry signal stops"
839 );
840 }
841
842 #[test]
843 fn mount_returns_error_on_duplicate_key() {
844 use crate::graph::MountError;
845
846 let a = el::const_with_key("dup", 1.0);
848 let b = el::const_with_key("dup", 2.0);
849 let out = el::add((a, b));
850
851 let graph = Graph::new().render(vec![out]);
852 let result = graph.mount();
853
854 match result {
855 Err(MountError::DuplicateKey(key)) => {
856 assert_eq!(key, "dup");
857 }
858 Ok(_) => panic!("expected DuplicateKey error, got Ok"),
859 }
860 }
861}