1use crate::modules::common::flush_denorm;
8use crate::port::{GraphModule, ParamId, PortId, PortSpec, PortValues, SignalKind};
9use crate::StdMap;
10use alloc::boxed::Box;
11use alloc::collections::VecDeque;
12use alloc::format;
13use alloc::string::{String, ToString};
14use alloc::vec::Vec;
15use serde::{Deserialize, Serialize};
16use slotmap::{DefaultKey, SlotMap};
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
20pub enum ValidationMode {
21 None,
23 #[default]
28 Warn,
29 Strict,
31}
32
33#[derive(Debug, Clone)]
35pub struct CompatibilityResult {
36 pub compatible: bool,
37 pub warning: Option<String>,
38}
39
40impl SignalKind {
41 pub fn is_compatible_with(&self, other: &SignalKind) -> CompatibilityResult {
44 use SignalKind::*;
45
46 if self == other {
48 return CompatibilityResult {
49 compatible: true,
50 warning: None,
51 };
52 }
53
54 match (self, other) {
56 (Audio, CvBipolar) | (CvBipolar, Audio) => CompatibilityResult {
58 compatible: true,
59 warning: Some("Audio/CV connection - ensure this is intentional".to_string()),
60 },
61
62 (CvBipolar, CvUnipolar) | (CvUnipolar, CvBipolar) => CompatibilityResult {
64 compatible: true,
65 warning: Some(
66 "Bipolar/Unipolar CV mismatch - signal may be clipped or offset".to_string(),
67 ),
68 },
69
70 (CvBipolar, VoltPerOctave) => CompatibilityResult {
72 compatible: true,
73 warning: None,
74 },
75
76 (VoltPerOctave, CvBipolar) => CompatibilityResult {
78 compatible: true,
79 warning: None,
80 },
81
82 (Gate, Trigger) | (Trigger, Gate) => CompatibilityResult {
84 compatible: true,
85 warning: Some("Gate/Trigger connection - timing behavior may differ".to_string()),
86 },
87
88 (Clock, Trigger) | (Trigger, Clock) => CompatibilityResult {
89 compatible: true,
90 warning: None,
91 },
92
93 (Clock, Gate) | (Gate, Clock) => CompatibilityResult {
94 compatible: true,
95 warning: Some("Clock/Gate connection - duty cycle may affect behavior".to_string()),
96 },
97
98 (Audio, VoltPerOctave) => CompatibilityResult {
100 compatible: true,
101 warning: Some(
102 "Audio-rate pitch modulation - ensure this is intentional".to_string(),
103 ),
104 },
105
106 (CvUnipolar, VoltPerOctave) => CompatibilityResult {
108 compatible: true,
109 warning: Some("Unipolar CV to V/Oct - may need offset adjustment".to_string()),
110 },
111
112 (VoltPerOctave, CvUnipolar) => CompatibilityResult {
114 compatible: true,
115 warning: Some("V/Oct to Unipolar - negative voltages will be clipped".to_string()),
116 },
117
118 (Audio, Gate) | (Audio, Trigger) => CompatibilityResult {
120 compatible: true,
121 warning: Some("Audio to Gate/Trigger - signal will be thresholded".to_string()),
122 },
123
124 _ => CompatibilityResult {
126 compatible: true,
127 warning: Some(format!("Unusual connection: {:?} -> {:?}", self, other)),
128 },
129 }
130 }
131}
132
133pub type NodeId = DefaultKey;
135
136pub type CableId = usize;
143
144#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
146pub struct PortRef {
147 pub node: NodeId,
148 pub port: PortId,
149}
150
151#[derive(Debug, Clone, Serialize, Deserialize)]
153pub struct Cable {
154 #[serde(default)]
156 pub id: CableId,
157 pub from: PortRef,
158 pub to: PortRef,
159 pub attenuation: Option<f64>,
162 pub offset: Option<f64>,
164}
165
166struct Node {
168 module: Box<dyn GraphModule>,
169 name: String,
170 position: Option<(f32, f32)>,
171 param_overrides: StdMap<String, f64>,
176}
177
178#[derive(Debug, Clone, Default)]
183pub struct PatchMeta {
184 pub name: Option<String>,
186 pub author: Option<String>,
188 pub description: Option<String>,
190 pub tags: Vec<String>,
192}
193
194#[derive(Debug, Clone)]
199#[non_exhaustive]
200pub enum PatchError {
201 InvalidNode {
203 node: NodeId,
204 },
205 InvalidPort {
211 node: NodeId,
212 name: Option<String>,
213 port: Option<PortId>,
214 available: Vec<String>,
215 },
216 InvalidCable,
217 CycleDetected {
223 nodes: Vec<NodeId>,
224 names: Vec<String>,
225 },
226 CompilationFailed(String),
227 SignalMismatch {
229 from_kind: SignalKind,
230 to_kind: SignalKind,
231 message: String,
232 },
233}
234
235impl core::fmt::Display for PatchError {
236 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
237 match self {
238 PatchError::InvalidNode { node } => write!(f, "Invalid node: {:?}", node),
239 PatchError::InvalidPort {
240 node,
241 name,
242 port,
243 available,
244 } => {
245 write!(f, "Invalid port")?;
246 match (name, port) {
247 (Some(n), _) => write!(f, " '{}'", n)?,
248 (None, Some(p)) => write!(f, " #{}", p)?,
249 (None, None) => {}
250 }
251 write!(f, " on node {:?}", node)?;
252 if available.is_empty() {
253 write!(f, " (module exposes no matching ports)")
254 } else {
255 write!(f, " (available ports: {})", available.join(", "))
256 }
257 }
258 PatchError::InvalidCable => write!(f, "Invalid cable"),
259 PatchError::CycleDetected { nodes, names } => {
260 if names.is_empty() {
261 write!(f, "Cycle detected involving {} nodes", nodes.len())
262 } else {
263 write!(f, "Cycle detected: {}", names.join(" -> "))
264 }
265 }
266 PatchError::CompilationFailed(msg) => write!(f, "Compilation failed: {}", msg),
267 PatchError::SignalMismatch {
268 from_kind,
269 to_kind,
270 message,
271 } => write!(
272 f,
273 "Signal mismatch: {:?} -> {:?}: {}",
274 from_kind, to_kind, message
275 ),
276 }
277 }
278}
279
280#[cfg(feature = "std")]
281impl std::error::Error for PatchError {}
282
283#[derive(Clone)]
285pub struct NodeHandle {
286 id: NodeId,
287 spec: PortSpec,
288}
289
290impl NodeHandle {
291 pub fn id(&self) -> NodeId {
292 self.id
293 }
294
295 pub fn from_module(id: NodeId, module: &dyn GraphModule) -> Self {
297 Self {
298 id,
299 spec: module.port_spec().clone(),
300 }
301 }
302
303 pub fn output(&self, name: &str) -> Result<PortRef, PatchError> {
309 match self.spec.output_by_name(name) {
310 Some(port) => Ok(PortRef {
311 node: self.id,
312 port: port.id,
313 }),
314 None => Err(PatchError::InvalidPort {
315 node: self.id,
316 name: Some(name.to_string()),
317 port: None,
318 available: self.output_names().iter().map(|s| s.to_string()).collect(),
319 }),
320 }
321 }
322
323 pub fn input(&self, name: &str) -> Result<PortRef, PatchError> {
327 match self.spec.input_by_name(name) {
328 Some(port) => Ok(PortRef {
329 node: self.id,
330 port: port.id,
331 }),
332 None => Err(PatchError::InvalidPort {
333 node: self.id,
334 name: Some(name.to_string()),
335 port: None,
336 available: self.input_names().iter().map(|s| s.to_string()).collect(),
337 }),
338 }
339 }
340
341 pub fn out(&self, name: &str) -> PortRef {
346 self.output(name).unwrap_or_else(|_| {
347 panic!(
348 "Unknown output port: '{}'. Valid output ports: [{}]",
349 name,
350 self.output_names().join(", ")
351 )
352 })
353 }
354
355 pub fn in_(&self, name: &str) -> PortRef {
360 self.input(name).unwrap_or_else(|_| {
361 panic!(
362 "Unknown input port: '{}'. Valid input ports: [{}]",
363 name,
364 self.input_names().join(", ")
365 )
366 })
367 }
368
369 pub fn input_names(&self) -> Vec<&str> {
371 self.spec.inputs.iter().map(|p| p.name.as_str()).collect()
372 }
373
374 pub fn output_names(&self) -> Vec<&str> {
376 self.spec.outputs.iter().map(|p| p.name.as_str()).collect()
377 }
378
379 pub fn spec(&self) -> &PortSpec {
381 &self.spec
382 }
383}
384
385struct InEdge {
391 src_slot: usize,
393 attenuation: Option<f64>,
395 offset: Option<f64>,
397}
398
399struct InputPlan {
401 port_id: PortId,
403 default: f64,
405 normalled_to: Option<PortId>,
407 has_connection: bool,
411 edges: Vec<InEdge>,
413}
414
415struct NodeExec {
417 node_id: NodeId,
419 out_base: usize,
422 out_ids: Vec<PortId>,
424 inputs: Vec<InputPlan>,
426}
427
428impl NodeExec {
429 fn gather(&self, out_buf: &[f64], dst: &mut PortValues) {
434 dst.clear();
435 for plan in &self.inputs {
437 if plan.has_connection {
438 let mut sum = 0.0;
439 for e in &plan.edges {
440 let value = out_buf[e.src_slot];
441 let attenuated = e.attenuation.map(|a| value * a).unwrap_or(value);
442 let with_offset = e.offset.map(|o| attenuated + o).unwrap_or(attenuated);
443 sum += with_offset;
444 }
445 dst.set(plan.port_id, sum);
446 } else if plan.normalled_to.is_none() {
447 dst.set(plan.port_id, plan.default);
448 }
449 }
451 for plan in &self.inputs {
457 if dst.has(plan.port_id) {
458 continue;
459 }
460 let value = match plan.normalled_to {
461 Some(norm) => dst.get(norm).unwrap_or(plan.default),
462 None => plan.default,
463 };
464 dst.set(plan.port_id, value);
465 }
466 }
467
468 fn scatter(&self, src: &PortValues, out_buf: &mut [f64]) {
473 for (k, &port_id) in self.out_ids.iter().enumerate() {
474 if let Some(value) = src.get(port_id) {
475 out_buf[self.out_base + k] = flush_denorm(value);
476 }
477 }
478 }
479}
480
481fn resolve_normalled_chains(inputs: &mut [InputPlan]) {
499 let n = inputs.len();
500 let original: Vec<Option<PortId>> = inputs.iter().map(|p| p.normalled_to).collect();
505 for i in 0..n {
506 if inputs[i].has_connection || original[i].is_none() {
508 continue;
509 }
510 let mut terminal = None;
511 let mut cursor = original[i];
512 for _ in 0..n {
514 let Some(pid) = cursor else { break };
515 match inputs.iter().position(|p| p.port_id == pid) {
516 None => break,
518 Some(idx) => {
519 if inputs[idx].has_connection || original[idx].is_none() {
520 terminal = Some(pid);
522 break;
523 }
524 cursor = original[idx];
526 }
527 }
528 }
529 inputs[i].normalled_to = terminal;
530 }
531}
532
533#[derive(Default)]
540struct Routing {
541 nodes: Vec<NodeExec>,
543 out_buf: Vec<f64>,
545 out_slot_index: StdMap<PortRef, usize>,
548 output_slots: Option<(usize, usize)>,
550 scratch_in: Vec<PortValues>,
552 scratch_out: Vec<PortValues>,
554}
555
556impl Routing {
557 fn read_output(&self) -> (f64, f64) {
560 match self.output_slots {
561 Some((left, right)) => (self.out_buf[left], self.out_buf[right]),
562 None => (0.0, 0.0),
563 }
564 }
565}
566
567pub struct Patch {
569 nodes: SlotMap<NodeId, Node>,
570 cables: Vec<Cable>,
571
572 next_cable_id: CableId,
574
575 execution_order: Vec<NodeId>,
577 routing: Routing,
579
580 dirty: bool,
583 last_compile_error: Option<PatchError>,
585
586 sample_rate: f64,
588
589 output_node: Option<NodeId>,
591
592 validation_mode: ValidationMode,
594 warnings: Vec<String>,
595
596 meta: PatchMeta,
598}
599
600impl Patch {
601 pub fn new(sample_rate: f64) -> Self {
603 Self {
604 nodes: SlotMap::new(),
605 cables: Vec::new(),
606 next_cable_id: 0,
607 execution_order: Vec::new(),
608 routing: Routing::default(),
609 dirty: true,
612 last_compile_error: None,
613 sample_rate,
614 output_node: None,
615 validation_mode: ValidationMode::Warn,
618 warnings: Vec::new(),
619 meta: PatchMeta::default(),
620 }
621 }
622
623 pub fn meta(&self) -> &PatchMeta {
625 &self.meta
626 }
627
628 pub fn meta_mut(&mut self) -> &mut PatchMeta {
630 &mut self.meta
631 }
632
633 pub fn set_meta(&mut self, meta: PatchMeta) {
635 self.meta = meta;
636 }
637
638 pub fn set_validation_mode(&mut self, mode: ValidationMode) {
640 self.validation_mode = mode;
641 }
642
643 pub fn validation_mode(&self) -> ValidationMode {
645 self.validation_mode
646 }
647
648 pub fn warnings(&self) -> &[String] {
650 &self.warnings
651 }
652
653 pub fn clear_warnings(&mut self) {
655 self.warnings.clear();
656 }
657
658 pub fn sample_rate(&self) -> f64 {
660 self.sample_rate
661 }
662
663 pub fn add<M: GraphModule + 'static>(
665 &mut self,
666 name: impl Into<String>,
667 mut module: M,
668 ) -> NodeHandle {
669 module.set_sample_rate(self.sample_rate);
670 let spec = module.port_spec().clone();
671 let id = self.nodes.insert(Node {
672 module: Box::new(module),
673 name: name.into(),
674 position: None,
675 param_overrides: StdMap::new(),
676 });
677 self.invalidate();
678 NodeHandle { id, spec }
679 }
680
681 pub fn add_boxed(
683 &mut self,
684 name: impl Into<String>,
685 mut module: Box<dyn GraphModule>,
686 ) -> NodeHandle {
687 module.set_sample_rate(self.sample_rate);
688 let spec = module.port_spec().clone();
689 let id = self.nodes.insert(Node {
690 module,
691 name: name.into(),
692 position: None,
693 param_overrides: StdMap::new(),
694 });
695 self.invalidate();
696 NodeHandle { id, spec }
697 }
698
699 pub fn remove(&mut self, node: NodeId) -> Result<(), PatchError> {
701 if self.nodes.remove(node).is_none() {
702 return Err(PatchError::InvalidNode { node });
703 }
704
705 self.cables
707 .retain(|cable| cable.from.node != node && cable.to.node != node);
708
709 if self.output_node == Some(node) {
710 self.output_node = None;
711 }
712
713 self.invalidate();
714 Ok(())
715 }
716
717 fn alloc_cable_id(&mut self) -> CableId {
719 let id = self.next_cable_id;
720 self.next_cable_id += 1;
721 id
722 }
723
724 pub fn connect(&mut self, from: PortRef, to: PortRef) -> Result<CableId, PatchError> {
729 self.validate_output_port(from)?;
730 self.validate_input_port(to)?;
731 self.validate_signal_compatibility(from, to)?;
732
733 let id = self.alloc_cable_id();
734 self.cables.push(Cable {
735 id,
736 from,
737 to,
738 attenuation: None,
739 offset: None,
740 });
741 self.invalidate();
742 Ok(id)
743 }
744
745 pub fn connect_attenuated(
747 &mut self,
748 from: PortRef,
749 to: PortRef,
750 attenuation: f64,
751 ) -> Result<CableId, PatchError> {
752 self.validate_output_port(from)?;
753 self.validate_input_port(to)?;
754 self.validate_signal_compatibility(from, to)?;
755
756 let id = self.alloc_cable_id();
757 self.cables.push(Cable {
758 id,
759 from,
760 to,
761 attenuation: Some(attenuation.clamp(0.0, 1.0)),
762 offset: None,
763 });
764 self.invalidate();
765 Ok(id)
766 }
767
768 pub fn connect_modulated(
772 &mut self,
773 from: PortRef,
774 to: PortRef,
775 attenuation: f64,
776 offset: f64,
777 ) -> Result<CableId, PatchError> {
778 self.validate_output_port(from)?;
779 self.validate_input_port(to)?;
780 self.validate_signal_compatibility(from, to)?;
781
782 let id = self.alloc_cable_id();
783 self.cables.push(Cable {
784 id,
785 from,
786 to,
787 attenuation: Some(attenuation.clamp(-2.0, 2.0)),
788 offset: Some(offset.clamp(-10.0, 10.0)),
789 });
790 self.invalidate();
791 Ok(id)
792 }
793
794 fn validate_signal_compatibility(
796 &mut self,
797 from: PortRef,
798 to: PortRef,
799 ) -> Result<(), PatchError> {
800 if self.validation_mode == ValidationMode::None {
801 return Ok(());
802 }
803
804 let from_kind = self.get_output_port_kind(from);
806 let to_kind = self.get_input_port_kind(to);
807
808 if let (Some(from_kind), Some(to_kind)) = (from_kind, to_kind) {
809 let result = from_kind.is_compatible_with(&to_kind);
810
811 if let Some(warning) = result.warning {
812 let from_name = self.get_name(from.node).unwrap_or("unknown");
813 let to_name = self.get_name(to.node).unwrap_or("unknown");
814 let full_warning = format!(
815 "{}.{} -> {}.{}: {}",
816 from_name, from.port, to_name, to.port, warning
817 );
818
819 match self.validation_mode {
820 ValidationMode::Warn => {
821 self.warnings.push(full_warning);
822 }
823 ValidationMode::Strict => {
824 return Err(PatchError::SignalMismatch {
825 from_kind,
826 to_kind,
827 message: warning,
828 });
829 }
830 ValidationMode::None => {}
831 }
832 }
833 }
834
835 Ok(())
836 }
837
838 fn get_output_port_kind(&self, port_ref: PortRef) -> Option<SignalKind> {
840 let node = self.nodes.get(port_ref.node)?;
841 node.module
842 .port_spec()
843 .outputs
844 .iter()
845 .find(|p| p.id == port_ref.port)
846 .map(|p| p.kind)
847 }
848
849 fn get_input_port_kind(&self, port_ref: PortRef) -> Option<SignalKind> {
851 let node = self.nodes.get(port_ref.node)?;
852 node.module
853 .port_spec()
854 .inputs
855 .iter()
856 .find(|p| p.id == port_ref.port)
857 .map(|p| p.kind)
858 }
859
860 pub fn mult(&mut self, from: PortRef, to: &[PortRef]) -> Result<Vec<CableId>, PatchError> {
862 to.iter().map(|&dest| self.connect(from, dest)).collect()
863 }
864
865 pub fn disconnect(&mut self, cable_id: CableId) -> Result<(), PatchError> {
870 let idx = self
871 .cables
872 .iter()
873 .position(|c| c.id == cable_id)
874 .ok_or(PatchError::InvalidCable)?;
875 self.cables.remove(idx);
876 self.invalidate();
877 Ok(())
878 }
879
880 pub fn set_output(&mut self, node: NodeId) {
887 self.output_node = Some(node);
888 self.dirty = true;
889 }
890
891 pub fn output_node(&self) -> Option<NodeId> {
893 self.output_node
894 }
895
896 pub fn try_set_output(&mut self, node: NodeId) -> Result<(), PatchError> {
901 let n = self
902 .nodes
903 .get(node)
904 .ok_or(PatchError::InvalidNode { node })?;
905 if n.module.port_spec().outputs.is_empty() {
906 return Err(PatchError::InvalidPort {
907 node,
908 name: None,
909 port: None,
910 available: Vec::new(),
911 });
912 }
913 self.output_node = Some(node);
914 self.dirty = true;
915 Ok(())
916 }
917
918 pub fn set_param(&mut self, node: NodeId, param: ParamId, value: f64) {
920 if let Some(n) = self.nodes.get_mut(node) {
921 n.module.set_param(param, value);
922 }
923 }
924
925 pub fn get_param(&self, node: NodeId, param: ParamId) -> Option<f64> {
927 self.nodes.get(node).and_then(|n| n.module.get_param(param))
928 }
929
930 pub fn set_position(&mut self, node: NodeId, position: (f32, f32)) {
932 if let Some(n) = self.nodes.get_mut(node) {
933 n.position = Some(position);
934 }
935 }
936
937 pub fn get_position(&self, node: NodeId) -> Option<(f32, f32)> {
939 self.nodes.get(node).and_then(|n| n.position)
940 }
941
942 pub fn get_name(&self, node: NodeId) -> Option<&str> {
944 self.nodes.get(node).map(|n| n.name.as_str())
945 }
946
947 pub fn node_count(&self) -> usize {
949 self.nodes.len()
950 }
951
952 pub fn cable_count(&self) -> usize {
954 self.cables.len()
955 }
956
957 pub fn cables(&self) -> &[Cable] {
959 &self.cables
960 }
961
962 pub fn execution_order(&self) -> &[NodeId] {
964 &self.execution_order
965 }
966
967 fn invalidate(&mut self) {
973 self.execution_order.clear();
974 self.routing = Routing::default();
975 self.dirty = true;
976 }
977
978 fn validate_output_port(&self, port_ref: PortRef) -> Result<(), PatchError> {
979 let node = self
980 .nodes
981 .get(port_ref.node)
982 .ok_or(PatchError::InvalidNode {
983 node: port_ref.node,
984 })?;
985 let spec = node.module.port_spec();
986 if spec.outputs.iter().any(|p| p.id == port_ref.port) {
987 Ok(())
988 } else {
989 Err(PatchError::InvalidPort {
990 node: port_ref.node,
991 name: None,
992 port: Some(port_ref.port),
993 available: spec.outputs.iter().map(|p| p.name.clone()).collect(),
994 })
995 }
996 }
997
998 fn validate_input_port(&self, port_ref: PortRef) -> Result<(), PatchError> {
999 let node = self
1000 .nodes
1001 .get(port_ref.node)
1002 .ok_or(PatchError::InvalidNode {
1003 node: port_ref.node,
1004 })?;
1005 let spec = node.module.port_spec();
1006 if spec.inputs.iter().any(|p| p.id == port_ref.port) {
1007 Ok(())
1008 } else {
1009 Err(PatchError::InvalidPort {
1010 node: port_ref.node,
1011 name: None,
1012 port: Some(port_ref.port),
1013 available: spec.inputs.iter().map(|p| p.name.clone()).collect(),
1014 })
1015 }
1016 }
1017
1018 pub fn compile(&mut self) -> Result<(), PatchError> {
1026 let order = match self.topological_sort() {
1027 Ok(order) => order,
1028 Err(e) => {
1029 self.execution_order.clear();
1030 self.routing = Routing::default();
1031 self.dirty = false;
1034 self.last_compile_error = Some(e.clone());
1035 return Err(e);
1036 }
1037 };
1038 self.execution_order = order;
1039
1040 self.build_routing();
1042
1043 self.dirty = false;
1044 self.last_compile_error = None;
1045 Ok(())
1046 }
1047
1048 fn build_routing(&mut self) {
1052 let mut routing = Routing::default();
1053
1054 let mut slot: usize = 0;
1058 for &node_id in &self.execution_order {
1059 let node = self
1060 .nodes
1061 .get(node_id)
1062 .expect("execution_order only holds live nodes");
1063 let spec = node.module.port_spec();
1064 let out_base = slot;
1065 let mut out_ids = Vec::with_capacity(spec.outputs.len());
1066 for output in &spec.outputs {
1067 routing.out_slot_index.insert(
1068 PortRef {
1069 node: node_id,
1070 port: output.id,
1071 },
1072 slot,
1073 );
1074 out_ids.push(output.id);
1075 slot += 1;
1076 }
1077 routing.nodes.push(NodeExec {
1078 node_id,
1079 out_base,
1080 out_ids,
1081 inputs: Vec::new(),
1082 });
1083 }
1084 routing.out_buf.resize(slot, 0.0);
1085
1086 for (exec_idx, &node_id) in self.execution_order.iter().enumerate() {
1089 let node = self
1090 .nodes
1091 .get(node_id)
1092 .expect("execution_order only holds live nodes");
1093 let spec = node.module.port_spec();
1094
1095 let mut scratch_in = PortValues::new();
1096 let mut scratch_out = PortValues::new();
1097 let mut inputs = Vec::with_capacity(spec.inputs.len());
1098
1099 for input in &spec.inputs {
1100 let port_ref = PortRef {
1101 node: node_id,
1102 port: input.id,
1103 };
1104 let mut edges = Vec::new();
1105 let mut has_connection = false;
1106 for cable in &self.cables {
1107 if cable.to == port_ref {
1108 has_connection = true;
1109 if let Some(&src_slot) = routing.out_slot_index.get(&cable.from) {
1111 edges.push(InEdge {
1112 src_slot,
1113 attenuation: cable.attenuation,
1114 offset: cable.offset,
1115 });
1116 }
1117 }
1118 }
1119 let default = node
1123 .param_overrides
1124 .get(&input.name)
1125 .copied()
1126 .unwrap_or(input.default);
1127 inputs.push(InputPlan {
1128 port_id: input.id,
1129 default,
1130 normalled_to: input.normalled_to,
1131 has_connection,
1132 edges,
1133 });
1134 scratch_in.set(input.id, 0.0);
1135 }
1136 resolve_normalled_chains(&mut inputs);
1139 for output in &spec.outputs {
1140 scratch_out.set(output.id, 0.0);
1141 }
1142
1143 routing.nodes[exec_idx].inputs = inputs;
1144 routing.scratch_in.push(scratch_in);
1145 routing.scratch_out.push(scratch_out);
1146 }
1147
1148 routing.output_slots = self.output_node.and_then(|out_node| {
1150 let node = self.nodes.get(out_node)?;
1151 let outputs = &node.module.port_spec().outputs;
1152 let left_id = outputs.first()?.id;
1153 let left = *routing.out_slot_index.get(&PortRef {
1154 node: out_node,
1155 port: left_id,
1156 })?;
1157 let right = outputs
1158 .get(1)
1159 .and_then(|p| {
1160 routing
1161 .out_slot_index
1162 .get(&PortRef {
1163 node: out_node,
1164 port: p.id,
1165 })
1166 .copied()
1167 })
1168 .unwrap_or(left);
1169 Some((left, right))
1170 });
1171
1172 self.routing = routing;
1173 }
1174
1175 fn node_breaks_feedback(&self, node: NodeId) -> bool {
1177 self.nodes
1178 .get(node)
1179 .map(|n| n.module.breaks_feedback_cycle())
1180 .unwrap_or(false)
1181 }
1182
1183 fn topological_sort(&self) -> Result<Vec<NodeId>, PatchError> {
1184 let mut in_degree: StdMap<NodeId, usize> = self.nodes.keys().map(|k| (k, 0)).collect();
1185 let mut successors: StdMap<NodeId, Vec<NodeId>> =
1186 self.nodes.keys().map(|k| (k, Vec::new())).collect();
1187
1188 for cable in &self.cables {
1189 if self.node_breaks_feedback(cable.to.node) {
1195 continue;
1196 }
1197 if let Some(deg) = in_degree.get_mut(&cable.to.node) {
1198 *deg += 1;
1199 }
1200 if let Some(succ) = successors.get_mut(&cable.from.node) {
1201 succ.push(cable.to.node);
1202 }
1203 }
1204
1205 let mut queue: VecDeque<NodeId> = VecDeque::new();
1209 for id in self.nodes.keys() {
1210 if in_degree.get(&id).copied().unwrap_or(0) == 0 {
1211 queue.push_back(id);
1212 }
1213 }
1214
1215 let mut result = Vec::with_capacity(self.nodes.len());
1216
1217 while let Some(node) = queue.pop_front() {
1218 result.push(node);
1219 if let Some(succ) = successors.get(&node) {
1221 for &s in succ {
1222 if let Some(deg) = in_degree.get_mut(&s) {
1223 *deg -= 1;
1224 if *deg == 0 {
1225 queue.push_back(s);
1226 }
1227 }
1228 }
1229 }
1230 }
1231
1232 if result.len() != self.nodes.len() {
1233 let nodes: Vec<NodeId> = self
1235 .nodes
1236 .keys()
1237 .filter(|k| in_degree.get(k).copied().unwrap_or(0) > 0)
1238 .collect();
1239 let names = nodes
1240 .iter()
1241 .map(|&id| self.get_name(id).unwrap_or("<unknown>").to_string())
1242 .collect();
1243 return Err(PatchError::CycleDetected { nodes, names });
1244 }
1245
1246 Ok(result)
1247 }
1248
1249 pub fn last_compile_error(&self) -> Option<&PatchError> {
1255 self.last_compile_error.as_ref()
1256 }
1257
1258 pub fn tick(&mut self) -> (f64, f64) {
1274 if self.dirty {
1278 let _ = self.compile();
1279 }
1280 self.tick_step()
1281 }
1282
1283 pub fn tick_block(&mut self, out_left: &mut [f64], out_right: &mut [f64]) {
1293 if self.dirty {
1294 let _ = self.compile();
1295 }
1296 let frames = out_left.len().min(out_right.len());
1297 for frame in 0..frames {
1298 let (left, right) = self.tick_step();
1299 out_left[frame] = left;
1300 out_right[frame] = right;
1301 }
1302 }
1303
1304 fn tick_step(&mut self) -> (f64, f64) {
1311 let routing = &mut self.routing;
1312 let nodes = &mut self.nodes;
1313
1314 for i in 0..routing.nodes.len() {
1315 routing.nodes[i].gather(&routing.out_buf, &mut routing.scratch_in[i]);
1317
1318 let node_id = routing.nodes[i].node_id;
1321 routing.scratch_out[i].clear();
1322 if let Some(node) = nodes.get_mut(node_id) {
1323 node.module
1324 .tick(&routing.scratch_in[i], &mut routing.scratch_out[i]);
1325 }
1326
1327 routing.nodes[i].scatter(&routing.scratch_out[i], &mut routing.out_buf);
1329 }
1330
1331 routing.read_output()
1332 }
1333
1334 pub fn reset(&mut self) {
1336 for (_, node) in &mut self.nodes {
1337 node.module.reset();
1338 }
1339 for value in self.routing.out_buf.iter_mut() {
1340 *value = 0.0;
1341 }
1342 }
1343
1344 pub fn nodes(&self) -> impl Iterator<Item = (NodeId, &str, &dyn GraphModule)> {
1346 self.nodes
1347 .iter()
1348 .map(|(id, node)| (id, node.name.as_str(), node.module.as_ref()))
1349 }
1350
1351 pub fn get_node_id_by_name(&self, name: &str) -> Option<NodeId> {
1353 self.nodes
1354 .iter()
1355 .find(|(_, node)| node.name == name)
1356 .map(|(id, _)| id)
1357 }
1358
1359 pub fn get_handle_by_name(&self, name: &str) -> Option<NodeHandle> {
1361 self.nodes
1362 .iter()
1363 .find(|(_, node)| node.name == name)
1364 .map(|(id, node)| NodeHandle::from_module(id, node.module.as_ref()))
1365 }
1366
1367 pub fn disconnect_ports(&mut self, from: PortRef, to: PortRef) -> Result<(), PatchError> {
1369 let idx = self
1370 .cables
1371 .iter()
1372 .position(|c| c.from == from && c.to == to)
1373 .ok_or(PatchError::InvalidCable)?;
1374
1375 self.cables.remove(idx);
1376 self.invalidate();
1377 Ok(())
1378 }
1379
1380 pub fn module_names(&self) -> Vec<&str> {
1382 self.nodes
1383 .iter()
1384 .map(|(_, node)| node.name.as_str())
1385 .collect()
1386 }
1387
1388 pub fn get_output_value(&self, node: NodeId, port: PortId) -> Option<f64> {
1393 self.routing
1394 .out_slot_index
1395 .get(&PortRef { node, port })
1396 .map(|&slot| self.routing.out_buf[slot])
1397 }
1398
1399 pub fn get_output_signal_kind(&self, node: NodeId, port: PortId) -> Option<SignalKind> {
1401 let node_data = self.nodes.get(node)?;
1402 node_data
1403 .module
1404 .port_spec()
1405 .outputs
1406 .iter()
1407 .find(|p| p.id == port)
1408 .map(|p| p.kind)
1409 }
1410}
1411
1412#[cfg(feature = "alloc")]
1415fn is_control_input(kind: SignalKind) -> bool {
1416 kind != SignalKind::Audio
1417}
1418
1419#[cfg(feature = "alloc")]
1422fn port_param_info(port: &crate::port::PortDef, value: f64) -> crate::introspection::ParamInfo {
1423 let (min, max) = port.kind.voltage_range();
1424 crate::introspection::ParamInfo::new(port.name.clone(), port.name.clone())
1425 .with_range(min, max)
1426 .with_default(port.default)
1427 .with_value(value)
1428}
1429
1430#[cfg(feature = "alloc")]
1442impl Patch {
1443 pub fn param_infos(&self, node: NodeId) -> Vec<crate::introspection::ParamInfo> {
1449 let Some(n) = self.nodes.get(node) else {
1450 return Vec::new();
1451 };
1452 let spec = n.module.port_spec();
1453 let mut infos: Vec<crate::introspection::ParamInfo> = n
1454 .module
1455 .introspect()
1456 .map(|i| i.param_infos())
1457 .unwrap_or_default()
1458 .into_iter()
1459 .filter(|p| spec.input_by_name(&p.id).is_none())
1461 .collect();
1462
1463 for input in &spec.inputs {
1464 if !is_control_input(input.kind) {
1465 continue;
1466 }
1467 let value = n
1468 .param_overrides
1469 .get(&input.name)
1470 .copied()
1471 .unwrap_or(input.default);
1472 infos.push(port_param_info(input, value));
1473 }
1474 infos
1475 }
1476
1477 pub fn get_param_by_id(&self, node: NodeId, id: &str) -> Option<f64> {
1479 let n = self.nodes.get(node)?;
1480 if let Some(port) = n.module.port_spec().input_by_name(id) {
1482 if is_control_input(port.kind) {
1483 return Some(n.param_overrides.get(id).copied().unwrap_or(port.default));
1484 }
1485 }
1486 n.module
1487 .introspect()
1488 .and_then(|i| i.get_param_info(id))
1489 .map(|p| p.value)
1490 }
1491
1492 pub fn set_param_by_id(&mut self, node: NodeId, id: &str, value: f64) -> bool {
1498 let is_port = self
1500 .nodes
1501 .get(node)
1502 .map(|n| {
1503 n.module
1504 .port_spec()
1505 .input_by_name(id)
1506 .map(|p| is_control_input(p.kind))
1507 .unwrap_or(false)
1508 })
1509 .unwrap_or(false);
1510
1511 if is_port {
1512 if let Some(n) = self.nodes.get_mut(node) {
1513 n.param_overrides.insert(id.to_string(), value);
1514 }
1515 self.invalidate();
1517 return true;
1518 }
1519
1520 if let Some(n) = self.nodes.get_mut(node) {
1521 if let Some(intro) = n.module.introspect_mut() {
1522 return intro.set_param_by_id(id, value);
1523 }
1524 }
1525 false
1526 }
1527
1528 pub fn deserialize_module_state(
1537 &mut self,
1538 node: NodeId,
1539 state: &serde_json::Value,
1540 ) -> Result<(), String> {
1541 match self.nodes.get_mut(node) {
1542 Some(n) => n.module.deserialize_state(state),
1543 None => Ok(()),
1544 }
1545 }
1546}
1547
1548impl core::fmt::Debug for Patch {
1552 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1553 struct NodeDebug<'a> {
1555 id: NodeId,
1556 name: &'a str,
1557 type_id: &'a str,
1558 }
1559 impl core::fmt::Debug for NodeDebug<'_> {
1560 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1561 write!(f, "{:?}: {} ({})", self.id, self.name, self.type_id)
1562 }
1563 }
1564
1565 let nodes: Vec<NodeDebug> = self
1566 .nodes
1567 .iter()
1568 .map(|(id, n)| NodeDebug {
1569 id,
1570 name: n.name.as_str(),
1571 type_id: n.module.type_id(),
1572 })
1573 .collect();
1574
1575 f.debug_struct("Patch")
1576 .field("sample_rate", &self.sample_rate)
1577 .field("nodes", &nodes)
1578 .field("cables", &self.cables)
1579 .field("output_node", &self.output_node)
1580 .field("validation_mode", &self.validation_mode)
1581 .field("dirty", &self.dirty)
1582 .field("warnings", &self.warnings.len())
1583 .finish()
1584 }
1585}
1586
1587#[cfg(test)]
1588mod tests {
1589 use super::*;
1590 use crate::port::{PortDef, SignalKind};
1591 use alloc::vec;
1592
1593 struct Passthrough {
1595 spec: PortSpec,
1596 }
1597
1598 impl Passthrough {
1599 fn new() -> Self {
1600 Self {
1601 spec: PortSpec {
1602 inputs: vec![PortDef::new(0, "in", SignalKind::Audio)],
1603 outputs: vec![PortDef::new(10, "out", SignalKind::Audio)],
1604 },
1605 }
1606 }
1607 }
1608
1609 impl GraphModule for Passthrough {
1610 fn port_spec(&self) -> &PortSpec {
1611 &self.spec
1612 }
1613
1614 fn tick(&mut self, inputs: &PortValues, outputs: &mut PortValues) {
1615 let input = inputs.get_or(0, 0.0);
1616 outputs.set(10, input);
1617 }
1618
1619 fn reset(&mut self) {}
1620
1621 fn set_sample_rate(&mut self, _: f64) {}
1622 }
1623
1624 #[test]
1625 fn test_add_module() {
1626 let mut patch = Patch::new(44100.0);
1627 let handle = patch.add("test", Passthrough::new());
1628 assert_eq!(patch.node_count(), 1);
1629 assert!(patch.get_name(handle.id()).is_some());
1630 }
1631
1632 #[test]
1633 fn test_connect() {
1634 let mut patch = Patch::new(44100.0);
1635 let a = patch.add("a", Passthrough::new());
1636 let b = patch.add("b", Passthrough::new());
1637
1638 let result = patch.connect(a.out("out"), b.in_("in"));
1639 assert!(result.is_ok());
1640 assert_eq!(patch.cable_count(), 1);
1641 }
1642
1643 #[test]
1644 fn test_topological_sort() {
1645 let mut patch = Patch::new(44100.0);
1646 let a = patch.add("a", Passthrough::new());
1647 let b = patch.add("b", Passthrough::new());
1648 let c = patch.add("c", Passthrough::new());
1649
1650 patch.connect(a.out("out"), b.in_("in")).unwrap();
1652 patch.connect(b.out("out"), c.in_("in")).unwrap();
1653
1654 patch.compile().unwrap();
1655
1656 let order = patch.execution_order();
1657 let a_pos = order.iter().position(|&x| x == a.id()).unwrap();
1658 let b_pos = order.iter().position(|&x| x == b.id()).unwrap();
1659 let c_pos = order.iter().position(|&x| x == c.id()).unwrap();
1660
1661 assert!(a_pos < b_pos, "A should come before B");
1662 assert!(b_pos < c_pos, "B should come before C");
1663 }
1664
1665 #[test]
1666 fn test_cycle_detection() {
1667 let mut patch = Patch::new(44100.0);
1668 let a = patch.add("a", Passthrough::new());
1669 let b = patch.add("b", Passthrough::new());
1670
1671 patch.connect(a.out("out"), b.in_("in")).unwrap();
1673 patch.connect(b.out("out"), a.in_("in")).unwrap();
1674
1675 let result = patch.compile();
1676 assert!(matches!(result, Err(PatchError::CycleDetected { .. })));
1677 }
1678
1679 #[test]
1680 fn test_mult() {
1681 let mut patch = Patch::new(44100.0);
1682 let a = patch.add("a", Passthrough::new());
1683 let b = patch.add("b", Passthrough::new());
1684 let c = patch.add("c", Passthrough::new());
1685
1686 let result = patch.mult(a.out("out"), &[b.in_("in"), c.in_("in")]);
1687 assert!(result.is_ok());
1688 assert_eq!(patch.cable_count(), 2);
1689 }
1690
1691 #[test]
1692 fn test_disconnect() {
1693 let mut patch = Patch::new(44100.0);
1694 let a = patch.add("a", Passthrough::new());
1695 let b = patch.add("b", Passthrough::new());
1696
1697 let cable_id = patch.connect(a.out("out"), b.in_("in")).unwrap();
1698 assert_eq!(patch.cable_count(), 1);
1699
1700 patch.disconnect(cable_id).unwrap();
1701 assert_eq!(patch.cable_count(), 0);
1702 }
1703
1704 #[test]
1705 fn test_remove_module() {
1706 let mut patch = Patch::new(44100.0);
1707 let a = patch.add("a", Passthrough::new());
1708 let b = patch.add("b", Passthrough::new());
1709
1710 patch.connect(a.out("out"), b.in_("in")).unwrap();
1711 assert_eq!(patch.node_count(), 2);
1712 assert_eq!(patch.cable_count(), 1);
1713
1714 patch.remove(a.id()).unwrap();
1715 assert_eq!(patch.node_count(), 1);
1716 assert_eq!(patch.cable_count(), 0); }
1718
1719 struct GateModule {
1725 spec: PortSpec,
1726 }
1727
1728 impl GateModule {
1729 fn new() -> Self {
1730 Self {
1731 spec: PortSpec {
1732 inputs: vec![PortDef::new(0, "in", SignalKind::Gate)],
1733 outputs: vec![PortDef::new(10, "out", SignalKind::Gate)],
1734 },
1735 }
1736 }
1737 }
1738
1739 impl GraphModule for GateModule {
1740 fn port_spec(&self) -> &PortSpec {
1741 &self.spec
1742 }
1743 fn tick(&mut self, inputs: &PortValues, outputs: &mut PortValues) {
1744 outputs.set(10, inputs.get_or(0, 0.0));
1745 }
1746 fn reset(&mut self) {}
1747 fn set_sample_rate(&mut self, _: f64) {}
1748 }
1749
1750 #[test]
1751 fn test_validation_mode_none() {
1752 let mut patch = Patch::new(44100.0);
1753 patch.set_validation_mode(ValidationMode::None);
1754
1755 let audio = patch.add("audio", Passthrough::new());
1756 let gate = patch.add("gate", GateModule::new());
1757
1758 let result = patch.connect(audio.out("out"), gate.in_("in"));
1760 assert!(result.is_ok());
1761 assert!(patch.warnings().is_empty());
1762 }
1763
1764 #[test]
1765 fn test_validation_mode_warn() {
1766 let mut patch = Patch::new(44100.0);
1767 patch.set_validation_mode(ValidationMode::Warn);
1768
1769 let audio = patch.add("audio", Passthrough::new());
1770 let gate = patch.add("gate", GateModule::new());
1771
1772 let result = patch.connect(audio.out("out"), gate.in_("in"));
1774 assert!(result.is_ok());
1775 assert!(!patch.warnings().is_empty());
1776 }
1777
1778 #[test]
1779 fn test_validation_mode_strict() {
1780 let mut patch = Patch::new(44100.0);
1781 patch.set_validation_mode(ValidationMode::Strict);
1782
1783 let audio = patch.add("audio", Passthrough::new());
1784 let gate = patch.add("gate", GateModule::new());
1785
1786 let result = patch.connect(audio.out("out"), gate.in_("in"));
1788 assert!(matches!(result, Err(PatchError::SignalMismatch { .. })));
1789 }
1790
1791 #[test]
1792 fn test_same_signal_type_no_warning() {
1793 let mut patch = Patch::new(44100.0);
1794 patch.set_validation_mode(ValidationMode::Warn);
1795
1796 let a = patch.add("a", Passthrough::new());
1797 let b = patch.add("b", Passthrough::new());
1798
1799 let result = patch.connect(a.out("out"), b.in_("in"));
1801 assert!(result.is_ok());
1802 assert!(patch.warnings().is_empty());
1803 }
1804
1805 #[test]
1806 fn test_connect_modulated() {
1807 let mut patch = Patch::new(44100.0);
1808 let a = patch.add("a", Passthrough::new());
1809 let b = patch.add("b", Passthrough::new());
1810
1811 let result = patch.connect_modulated(a.out("out"), b.in_("in"), 0.5, 1.0);
1813 assert!(result.is_ok());
1814
1815 let cables = patch.cables();
1816 assert_eq!(cables.len(), 1);
1817 assert_eq!(cables[0].attenuation, Some(0.5));
1818 assert_eq!(cables[0].offset, Some(1.0));
1819 }
1820
1821 #[test]
1822 fn test_modulated_signal_processing() {
1823 let mut patch = Patch::new(44100.0);
1824
1825 struct ConstModule {
1827 spec: PortSpec,
1828 value: f64,
1829 }
1830
1831 impl ConstModule {
1832 fn new(value: f64) -> Self {
1833 Self {
1834 value,
1835 spec: PortSpec {
1836 inputs: vec![],
1837 outputs: vec![PortDef::new(10, "out", SignalKind::Audio)],
1838 },
1839 }
1840 }
1841 }
1842
1843 impl GraphModule for ConstModule {
1844 fn port_spec(&self) -> &PortSpec {
1845 &self.spec
1846 }
1847 fn tick(&mut self, _: &PortValues, outputs: &mut PortValues) {
1848 outputs.set(10, self.value);
1849 }
1850 fn reset(&mut self) {}
1851 fn set_sample_rate(&mut self, _: f64) {}
1852 }
1853
1854 struct RecordModule {
1855 spec: PortSpec,
1856 last_value: f64,
1857 }
1858
1859 impl RecordModule {
1860 fn new() -> Self {
1861 Self {
1862 spec: PortSpec {
1863 inputs: vec![PortDef::new(0, "in", SignalKind::Audio)],
1864 outputs: vec![],
1865 },
1866 last_value: 0.0,
1867 }
1868 }
1869 }
1870
1871 impl GraphModule for RecordModule {
1872 fn port_spec(&self) -> &PortSpec {
1873 &self.spec
1874 }
1875 fn tick(&mut self, inputs: &PortValues, _: &mut PortValues) {
1876 self.last_value = inputs.get_or(0, 0.0);
1877 }
1878 fn reset(&mut self) {}
1879 fn set_sample_rate(&mut self, _: f64) {}
1880 }
1881
1882 let source = patch.add("source", ConstModule::new(4.0));
1883 let sink = patch.add("sink", RecordModule::new());
1884
1885 patch
1887 .connect_modulated(source.out("out"), sink.in_("in"), 0.5, 2.0)
1888 .unwrap();
1889 patch.set_output(sink.id());
1890 patch.compile().unwrap();
1891 patch.tick();
1892
1893 }
1896
1897 #[test]
1898 fn test_signal_compatibility() {
1899 assert!(SignalKind::Audio
1901 .is_compatible_with(&SignalKind::Audio)
1902 .warning
1903 .is_none());
1904 assert!(SignalKind::Audio
1905 .is_compatible_with(&SignalKind::CvBipolar)
1906 .warning
1907 .is_some());
1908 assert!(SignalKind::Gate
1909 .is_compatible_with(&SignalKind::Trigger)
1910 .warning
1911 .is_some());
1912 assert!(SignalKind::Clock
1913 .is_compatible_with(&SignalKind::Trigger)
1914 .warning
1915 .is_none());
1916 }
1917
1918 #[test]
1919 fn test_patch_get_name() {
1920 let mut patch = Patch::new(44100.0);
1921 let a = patch.add("my_module", Passthrough::new());
1922
1923 let name = patch.get_name(a.id());
1924 assert_eq!(name, Some("my_module"));
1925
1926 use slotmap::DefaultKey;
1928 let fake_id: NodeId = DefaultKey::default();
1929 assert!(patch.get_name(fake_id).is_none());
1930 }
1931
1932 #[test]
1933 fn test_patch_set_position() {
1934 let mut patch = Patch::new(44100.0);
1935 let a = patch.add("a", Passthrough::new());
1936
1937 patch.set_position(a.id(), (100.0, 200.0));
1938 }
1940
1941 #[test]
1942 fn test_patch_clear_warnings() {
1943 let mut patch = Patch::new(44100.0);
1944 patch.set_validation_mode(ValidationMode::Warn);
1945
1946 let audio = patch.add("audio", Passthrough::new());
1947 let gate = patch.add("gate", GateModule::new());
1948
1949 patch.connect(audio.out("out"), gate.in_("in")).unwrap();
1950 assert!(!patch.warnings().is_empty());
1951
1952 patch.clear_warnings();
1953 assert!(patch.warnings().is_empty());
1954 }
1955
1956 #[test]
1957 fn test_patch_validation_mode_getter() {
1958 let mut patch = Patch::new(44100.0);
1959 patch.set_validation_mode(ValidationMode::Strict);
1960 assert_eq!(patch.validation_mode(), ValidationMode::Strict);
1961 }
1962
1963 #[test]
1964 fn test_patch_sample_rate() {
1965 let patch = Patch::new(48000.0);
1966 assert_eq!(patch.sample_rate(), 48000.0);
1967 }
1968
1969 #[test]
1970 fn test_patch_execution_order() {
1971 let mut patch = Patch::new(44100.0);
1972 let a = patch.add("a", Passthrough::new());
1973 let b = patch.add("b", Passthrough::new());
1974 patch.connect(a.out("out"), b.in_("in")).unwrap();
1975 patch.compile().unwrap();
1976
1977 let order = patch.execution_order();
1978 assert_eq!(order.len(), 2);
1979 }
1980
1981 #[test]
1982 fn test_patch_mult() {
1983 let mut patch = Patch::new(44100.0);
1984 let a = patch.add("a", Passthrough::new());
1985 let b = patch.add("b", Passthrough::new());
1986 let c = patch.add("c", Passthrough::new());
1987
1988 let result = patch.mult(a.out("out"), &[b.in_("in"), c.in_("in")]);
1990 assert!(result.is_ok());
1991 assert_eq!(patch.cable_count(), 2);
1992 }
1993
1994 #[test]
1995 fn test_patch_reset() {
1996 let mut patch = Patch::new(44100.0);
1997 let a = patch.add("a", Passthrough::new());
1998 patch.set_output(a.id());
1999 patch.compile().unwrap();
2000
2001 for _ in 0..100 {
2002 patch.tick();
2003 }
2004
2005 patch.reset();
2006 }
2008
2009 #[test]
2010 fn test_patch_set_param_get_param() {
2011 use crate::modules::Vco;
2012 let mut patch = Patch::new(44100.0);
2013 let vco = patch.add("vco", Vco::new(44100.0));
2014
2015 patch.set_param(vco.id(), 0, 0.5);
2017 let _ = patch.get_param(vco.id(), 0);
2018 }
2019
2020 #[test]
2021 fn test_node_handle_spec() {
2022 let mut patch = Patch::new(44100.0);
2023 let a = patch.add("a", Passthrough::new());
2024
2025 let spec = a.spec();
2026 assert!(!spec.inputs.is_empty());
2027 assert!(!spec.outputs.is_empty());
2028 }
2029
2030 #[test]
2031 fn test_patch_validation_mode() {
2032 let mut patch = Patch::new(44100.0);
2033
2034 patch.set_validation_mode(ValidationMode::Strict);
2035 assert_eq!(patch.validation_mode(), ValidationMode::Strict);
2036
2037 patch.set_validation_mode(ValidationMode::Warn);
2038 assert_eq!(patch.validation_mode(), ValidationMode::Warn);
2039 }
2040
2041 struct SumModule {
2047 spec: PortSpec,
2048 }
2049 impl SumModule {
2050 fn new() -> Self {
2051 Self {
2052 spec: PortSpec {
2053 inputs: vec![
2054 PortDef::new(0, "a", SignalKind::Audio),
2055 PortDef::new(1, "b", SignalKind::Audio),
2056 ],
2057 outputs: vec![PortDef::new(10, "out", SignalKind::Audio)],
2058 },
2059 }
2060 }
2061 }
2062 impl GraphModule for SumModule {
2063 fn port_spec(&self) -> &PortSpec {
2064 &self.spec
2065 }
2066 fn tick(&mut self, inputs: &PortValues, outputs: &mut PortValues) {
2067 outputs.set(10, inputs.get_or(0, 0.0) + inputs.get_or(1, 0.0));
2068 }
2069 fn reset(&mut self) {}
2070 fn set_sample_rate(&mut self, _: f64) {}
2071 }
2072
2073 struct FeedbackDelay {
2075 spec: PortSpec,
2076 buffer: f64,
2077 }
2078 impl FeedbackDelay {
2079 fn new() -> Self {
2080 Self {
2081 spec: PortSpec {
2082 inputs: vec![PortDef::new(0, "in", SignalKind::Audio)],
2083 outputs: vec![PortDef::new(10, "out", SignalKind::Audio)],
2084 },
2085 buffer: 0.0,
2086 }
2087 }
2088 }
2089 impl GraphModule for FeedbackDelay {
2090 fn port_spec(&self) -> &PortSpec {
2091 &self.spec
2092 }
2093 fn tick(&mut self, inputs: &PortValues, outputs: &mut PortValues) {
2094 outputs.set(10, self.buffer);
2095 self.buffer = inputs.get_or(0, 0.0);
2096 }
2097 fn reset(&mut self) {
2098 self.buffer = 0.0;
2099 }
2100 fn set_sample_rate(&mut self, _: f64) {}
2101 fn breaks_feedback_cycle(&self) -> bool {
2102 true
2103 }
2104 }
2105
2106 struct ConstSource {
2108 spec: PortSpec,
2109 value: f64,
2110 }
2111 impl ConstSource {
2112 fn new(value: f64) -> Self {
2113 Self {
2114 spec: PortSpec {
2115 inputs: vec![],
2116 outputs: vec![PortDef::new(10, "out", SignalKind::Audio)],
2117 },
2118 value,
2119 }
2120 }
2121 }
2122 impl GraphModule for ConstSource {
2123 fn port_spec(&self) -> &PortSpec {
2124 &self.spec
2125 }
2126 fn tick(&mut self, _: &PortValues, outputs: &mut PortValues) {
2127 outputs.set(10, self.value);
2128 }
2129 fn reset(&mut self) {}
2130 fn set_sample_rate(&mut self, _: f64) {}
2131 }
2132
2133 #[test]
2135 fn test_cable_ids_are_stable_across_disconnect() {
2136 let mut patch = Patch::new(44100.0);
2137 let a = patch.add("a", Passthrough::new());
2138 let b = patch.add("b", SumModule::new());
2139 let c = patch.add("c", SumModule::new());
2140
2141 let c1 = patch.connect(a.out("out"), b.in_("a")).unwrap();
2142 let c2 = patch.connect(a.out("out"), b.in_("b")).unwrap();
2143 let c3 = patch.connect(a.out("out"), c.in_("a")).unwrap();
2144 assert_eq!(patch.cable_count(), 3);
2145
2146 patch.disconnect(c1).unwrap();
2148 assert_eq!(patch.cable_count(), 2);
2149
2150 patch.disconnect(c3).unwrap();
2153 assert_eq!(patch.cable_count(), 1);
2154 let remaining = &patch.cables()[0];
2155 assert_eq!(remaining.id, c2);
2156 assert_eq!(remaining.to, b.in_("b"));
2157
2158 assert!(matches!(
2160 patch.disconnect(c1),
2161 Err(PatchError::InvalidCable)
2162 ));
2163 }
2164
2165 #[test]
2167 fn test_mutation_after_compile_is_reflected_on_tick() {
2168 let mut patch = Patch::new(44100.0);
2169 let src = patch.add("src", ConstSource::new(1.0));
2170 let out = patch.add("out", Passthrough::new());
2171 patch.connect(src.out("out"), out.in_("in")).unwrap();
2172 patch.set_output(out.id());
2173 patch.compile().unwrap();
2174
2175 let (l0, _) = patch.tick();
2177 assert!((l0 - 1.0).abs() < 1e-9);
2178
2179 let src2 = patch.add("src2", ConstSource::new(2.0));
2181 let sum = patch.add("sum", SumModule::new());
2182 patch
2184 .disconnect_ports(src.out("out"), out.in_("in"))
2185 .unwrap();
2186 patch.connect(src.out("out"), sum.in_("a")).unwrap();
2187 patch.connect(src2.out("out"), sum.in_("b")).unwrap();
2188 patch.connect(sum.out("out"), out.in_("in")).unwrap();
2189
2190 let (l1, _) = patch.tick();
2193 assert!((l1 - 3.0).abs() < 1e-9, "expected 3.0, got {}", l1);
2194 }
2195
2196 #[test]
2198 fn test_cycle_mutation_surfaces_via_last_compile_error() {
2199 let mut patch = Patch::new(44100.0);
2200 let a = patch.add("a", Passthrough::new());
2201 let b = patch.add("b", Passthrough::new());
2202 patch.connect(a.out("out"), b.in_("in")).unwrap();
2203 patch.set_output(b.id());
2204 patch.compile().unwrap();
2205 assert!(patch.last_compile_error().is_none());
2206
2207 patch.connect(b.out("out"), a.in_("in")).unwrap();
2209
2210 let (l, r) = patch.tick();
2212 assert_eq!((l, r), (0.0, 0.0));
2213 match patch.last_compile_error() {
2214 Some(PatchError::CycleDetected { names, .. }) => {
2215 assert_eq!(names.len(), 2);
2216 }
2217 other => panic!("expected CycleDetected, got {:?}", other),
2218 }
2219 }
2220
2221 #[test]
2223 fn test_feedback_loop_with_delay_compiles_and_decays() {
2224 struct Impulse {
2227 spec: PortSpec,
2228 fired: bool,
2229 }
2230 impl GraphModule for Impulse {
2231 fn port_spec(&self) -> &PortSpec {
2232 &self.spec
2233 }
2234 fn tick(&mut self, _: &PortValues, outputs: &mut PortValues) {
2235 outputs.set(10, if self.fired { 0.0 } else { 1.0 });
2236 self.fired = true;
2237 }
2238 fn reset(&mut self) {
2239 self.fired = false;
2240 }
2241 fn set_sample_rate(&mut self, _: f64) {}
2242 }
2243
2244 let mut patch = Patch::new(44100.0);
2245 let impulse = patch.add(
2246 "impulse",
2247 Impulse {
2248 spec: PortSpec {
2249 inputs: vec![],
2250 outputs: vec![PortDef::new(10, "out", SignalKind::Audio)],
2251 },
2252 fired: false,
2253 },
2254 );
2255 let sum = patch.add("sum", SumModule::new());
2256 let delay = patch.add("delay", FeedbackDelay::new());
2257
2258 patch.connect(impulse.out("out"), sum.in_("a")).unwrap();
2261 patch
2262 .connect_attenuated(delay.out("out"), sum.in_("b"), 0.5)
2263 .unwrap();
2264 patch.connect(sum.out("out"), delay.in_("in")).unwrap();
2265 patch.set_output(delay.id());
2266
2267 patch.compile().expect("feedback loop should compile");
2269 assert!(patch.last_compile_error().is_none());
2270
2271 let mut outs = Vec::new();
2272 for _ in 0..14 {
2273 outs.push(patch.tick().0);
2274 }
2275
2276 let nonzero: Vec<f64> = outs.iter().copied().filter(|v| v.abs() > 1e-9).collect();
2278 assert!(
2279 nonzero.len() >= 3,
2280 "expected multiple decaying echoes, got {:?}",
2281 outs
2282 );
2283 let peak_early = outs.iter().cloned().fold(0.0_f64, f64::max);
2286 let peak_late = outs[outs.len() - 3..]
2287 .iter()
2288 .cloned()
2289 .fold(0.0_f64, f64::max);
2290 assert!(
2291 peak_late < peak_early,
2292 "echo should decay: early peak {}, late peak {}",
2293 peak_early,
2294 peak_late
2295 );
2296 }
2297
2298 #[test]
2300 fn test_breakerless_cycle_still_errors() {
2301 let mut patch = Patch::new(44100.0);
2302 let a = patch.add("a", Passthrough::new());
2303 let b = patch.add("b", Passthrough::new());
2304 patch.connect(a.out("out"), b.in_("in")).unwrap();
2305 patch.connect(b.out("out"), a.in_("in")).unwrap();
2306 assert!(matches!(
2307 patch.compile(),
2308 Err(PatchError::CycleDetected { .. })
2309 ));
2310 }
2311
2312 #[test]
2315 fn test_normalled_input_uses_current_sibling_value() {
2316 use crate::modules::StereoOutput;
2317 let mut patch = Patch::new(44100.0);
2318 struct Ramp {
2320 spec: PortSpec,
2321 n: f64,
2322 }
2323 impl GraphModule for Ramp {
2324 fn port_spec(&self) -> &PortSpec {
2325 &self.spec
2326 }
2327 fn tick(&mut self, _: &PortValues, outputs: &mut PortValues) {
2328 self.n += 1.0;
2329 outputs.set(10, self.n);
2330 }
2331 fn reset(&mut self) {
2332 self.n = 0.0;
2333 }
2334 fn set_sample_rate(&mut self, _: f64) {}
2335 }
2336 let ramp = patch.add(
2337 "ramp",
2338 Ramp {
2339 spec: PortSpec {
2340 inputs: vec![],
2341 outputs: vec![PortDef::new(10, "out", SignalKind::Audio)],
2342 },
2343 n: 0.0,
2344 },
2345 );
2346 let out = patch.add("out", StereoOutput::new());
2347 patch.connect(ramp.out("out"), out.in_("left")).unwrap();
2349 patch.set_output(out.id());
2350 patch.compile().unwrap();
2351
2352 for _ in 0..5 {
2353 let (l, r) = patch.tick();
2354 assert!(l > 0.0);
2355 assert_eq!(l, r, "mono fallback must be current-sample, not delayed");
2356 }
2357 }
2358
2359 struct NormalChain {
2365 spec: PortSpec,
2366 }
2367 impl NormalChain {
2368 fn new(cycle: bool) -> Self {
2369 let (n0, n1) = if cycle { (1, 0) } else { (1, 2) };
2370 Self {
2371 spec: PortSpec {
2372 inputs: vec![
2373 PortDef::new(0, "a", SignalKind::Audio)
2374 .with_default(0.1)
2375 .normalled_to(n0),
2376 PortDef::new(1, "b", SignalKind::Audio)
2377 .with_default(0.2)
2378 .normalled_to(n1),
2379 PortDef::new(2, "c", SignalKind::Audio).with_default(0.3),
2380 ],
2381 outputs: vec![
2382 PortDef::new(10, "oa", SignalKind::Audio),
2383 PortDef::new(11, "ob", SignalKind::Audio),
2384 PortDef::new(12, "oc", SignalKind::Audio),
2385 ],
2386 },
2387 }
2388 }
2389 }
2390 impl GraphModule for NormalChain {
2391 fn port_spec(&self) -> &PortSpec {
2392 &self.spec
2393 }
2394 fn tick(&mut self, inputs: &PortValues, outputs: &mut PortValues) {
2395 outputs.set(10, inputs.get_or(0, f64::NAN));
2396 outputs.set(11, inputs.get_or(1, f64::NAN));
2397 outputs.set(12, inputs.get_or(2, f64::NAN));
2398 }
2399 fn reset(&mut self) {}
2400 fn set_sample_rate(&mut self, _: f64) {}
2401 }
2402
2403 #[test]
2409 fn test_normalled_chain_resolves_transitively() {
2410 let mut patch = Patch::new(44100.0);
2411 let src = patch.add("src", ConstSource::new(0.75));
2412 let node = patch.add("chain", NormalChain::new(false));
2413 patch.connect(src.out("out"), node.in_("c")).unwrap();
2415 patch.set_output(node.id());
2416 patch.compile().unwrap();
2417
2418 let (oa, ob) = patch.tick();
2421 assert!(
2422 (oa - 0.75).abs() < 1e-9,
2423 "forward-normalled input 0 must resolve to patched source, got {oa}"
2424 );
2425 assert!(
2426 (ob - 0.75).abs() < 1e-9,
2427 "transitively-normalled input 1 must resolve to patched source, got {ob}"
2428 );
2429 }
2430
2431 #[test]
2434 fn test_normalled_cycle_falls_back_to_default() {
2435 let mut patch = Patch::new(44100.0);
2436 let node = patch.add("chain", NormalChain::new(true));
2437 patch.set_output(node.id());
2438 patch.compile().unwrap();
2439 let (oa, ob) = patch.tick();
2440 assert!(
2441 (oa - 0.1).abs() < 1e-9,
2442 "cycled input 0 -> own default, got {oa}"
2443 );
2444 assert!(
2445 (ob - 0.2).abs() < 1e-9,
2446 "cycled input 1 -> own default, got {ob}"
2447 );
2448 }
2449
2450 #[test]
2452 fn test_execution_order_is_deterministic() {
2453 fn build_order() -> Vec<usize> {
2454 let mut patch = Patch::new(44100.0);
2455 let s1 = patch.add("s1", ConstSource::new(1.0));
2457 let s2 = patch.add("s2", ConstSource::new(2.0));
2458 let s3 = patch.add("s3", ConstSource::new(3.0));
2459 let sum = patch.add("sum", SumModule::new());
2460 patch.connect(s1.out("out"), sum.in_("a")).unwrap();
2461 patch.connect(s2.out("out"), sum.in_("b")).unwrap();
2462 patch.connect(s3.out("out"), sum.in_("a")).unwrap();
2463 patch.compile().unwrap();
2464 let ids = [s1.id(), s2.id(), s3.id(), sum.id()];
2466 patch
2467 .execution_order()
2468 .iter()
2469 .map(|nid| ids.iter().position(|x| x == nid).unwrap())
2470 .collect()
2471 }
2472 assert_eq!(build_order(), build_order());
2473 }
2474
2475 #[test]
2478 fn test_read_output_uses_first_two_outputs_mono_duplicated() {
2479 let mut patch = Patch::new(44100.0);
2480 let src = patch.add("src", ConstSource::new(0.7));
2481 patch.set_output(src.id());
2482 patch.compile().unwrap();
2483 let (l, r) = patch.tick();
2484 assert!((l - 0.7).abs() < 1e-9);
2485 assert_eq!(l, r, "mono node must duplicate to both channels");
2486 }
2487
2488 #[test]
2490 fn test_try_set_output_validates() {
2491 let mut patch = Patch::new(44100.0);
2492 let src = patch.add("src", ConstSource::new(1.0));
2493 assert!(patch.try_set_output(src.id()).is_ok());
2494
2495 struct SinkNoOut {
2497 spec: PortSpec,
2498 }
2499 impl GraphModule for SinkNoOut {
2500 fn port_spec(&self) -> &PortSpec {
2501 &self.spec
2502 }
2503 fn tick(&mut self, _: &PortValues, _: &mut PortValues) {}
2504 fn reset(&mut self) {}
2505 fn set_sample_rate(&mut self, _: f64) {}
2506 }
2507 let sink = patch.add(
2508 "sink",
2509 SinkNoOut {
2510 spec: PortSpec {
2511 inputs: vec![PortDef::new(0, "in", SignalKind::Audio)],
2512 outputs: vec![],
2513 },
2514 },
2515 );
2516 assert!(matches!(
2517 patch.try_set_output(sink.id()),
2518 Err(PatchError::InvalidPort { .. })
2519 ));
2520 }
2521
2522 #[test]
2524 fn test_node_handle_fallible_ports_and_names() {
2525 let mut patch = Patch::new(44100.0);
2526 let a = patch.add("a", Passthrough::new());
2527
2528 assert!(a.output("out").is_ok());
2529 assert!(a.input("in").is_ok());
2530
2531 match a.output("nope") {
2533 Err(PatchError::InvalidPort { available, .. }) => {
2534 assert!(available.iter().any(|n| n == "out"));
2535 }
2536 other => panic!("expected InvalidPort, got {:?}", other),
2537 }
2538 assert!(a.input("nope").is_err());
2539
2540 assert_eq!(a.input_names(), vec!["in"]);
2541 assert_eq!(a.output_names(), vec!["out"]);
2542 }
2543
2544 #[test]
2546 fn test_invalid_port_display_lists_available() {
2547 let mut patch = Patch::new(44100.0);
2548 let a = patch.add("a", Passthrough::new());
2549 let b = patch.add("b", Passthrough::new());
2550 let bad = PortRef {
2552 node: b.id(),
2553 port: 999,
2554 };
2555 let err = patch.connect(a.out("out"), bad).unwrap_err();
2556 let msg = alloc::format!("{}", err);
2557 assert!(msg.contains("Invalid port"), "got: {}", msg);
2558 assert!(
2559 msg.contains("in"),
2560 "should list available port 'in': {}",
2561 msg
2562 );
2563 }
2564
2565 #[test]
2567 fn test_cycle_detected_display_names() {
2568 let mut patch = Patch::new(44100.0);
2569 let a = patch.add("osc", Passthrough::new());
2570 let b = patch.add("filt", Passthrough::new());
2571 patch.connect(a.out("out"), b.in_("in")).unwrap();
2572 patch.connect(b.out("out"), a.in_("in")).unwrap();
2573 let err = patch.compile().unwrap_err();
2574 let msg = alloc::format!("{}", err);
2575 assert!(msg.contains("Cycle detected"), "got: {}", msg);
2576 assert!(
2577 msg.contains("osc") && msg.contains("filt"),
2578 "cycle message should name modules: {}",
2579 msg
2580 );
2581 }
2582
2583 #[test]
2585 fn test_default_validation_mode_is_warn() {
2586 let patch = Patch::new(44100.0);
2587 assert_eq!(patch.validation_mode(), ValidationMode::Warn);
2588 assert_eq!(ValidationMode::default(), ValidationMode::Warn);
2589 }
2590
2591 #[test]
2593 fn test_patch_debug_impl() {
2594 let mut patch = Patch::new(44100.0);
2595 let a = patch.add("my_osc", Passthrough::new());
2596 let b = patch.add("my_out", Passthrough::new());
2597 patch.connect(a.out("out"), b.in_("in")).unwrap();
2598 patch.set_output(b.id());
2599 let s = alloc::format!("{:?}", patch);
2600 assert!(s.contains("Patch"));
2601 assert!(s.contains("my_osc"));
2602 assert!(s.contains("my_out"));
2603 assert!(s.contains("validation_mode"));
2604 }
2605
2606 #[test]
2613 fn test_tick_block_matches_per_sample_tick() {
2614 fn ramp_svf_patch() -> (Patch, NodeHandle) {
2615 let mut patch = Patch::new(44100.0);
2616 let src = patch.add("src", ConstSource::new(0.9));
2617 let pass = patch.add("pass", Passthrough::new());
2618 patch.connect(src.out("out"), pass.in_("in")).unwrap();
2619 patch.set_output(pass.id());
2620 patch.compile().unwrap();
2621 (patch, pass)
2622 }
2623
2624 let (mut a, _) = ramp_svf_patch();
2626 let mut reference = Vec::new();
2627 for _ in 0..8 {
2628 reference.push(a.tick());
2629 }
2630
2631 let (mut b, _) = ramp_svf_patch();
2633 let mut left = [0.0_f64; 8];
2634 let mut right = [0.0_f64; 8];
2635 b.tick_block(&mut left, &mut right);
2636
2637 for (i, &(l, r)) in reference.iter().enumerate() {
2638 assert!((left[i] - l).abs() < 1e-12, "left[{}] mismatch", i);
2639 assert!((right[i] - r).abs() < 1e-12, "right[{}] mismatch", i);
2640 }
2641 }
2642
2643 #[test]
2645 fn test_tick_block_uses_min_length() {
2646 let mut patch = Patch::new(44100.0);
2647 let src = patch.add("src", ConstSource::new(1.0));
2648 patch.set_output(src.id());
2649 patch.compile().unwrap();
2650
2651 let mut left = [0.0_f64; 4];
2652 let mut right = [0.0_f64; 2]; patch.tick_block(&mut left, &mut right);
2654
2655 assert_eq!(left[0], 1.0);
2656 assert_eq!(left[1], 1.0);
2657 assert_eq!(left[2], 0.0, "frame beyond min length must be untouched");
2658 assert_eq!(left[3], 0.0);
2659 assert_eq!(right[0], 1.0);
2660 assert_eq!(right[1], 1.0);
2661 }
2662
2663 #[test]
2666 fn test_denormal_is_flushed_at_scatter() {
2667 let mut patch = Patch::new(44100.0);
2668 let src = patch.add("src", ConstSource::new(1e-30));
2670 let pass = patch.add("pass", Passthrough::new());
2671 patch.connect(src.out("out"), pass.in_("in")).unwrap();
2672 patch.set_output(pass.id());
2673 patch.compile().unwrap();
2674
2675 let (l, r) = patch.tick();
2676 assert_eq!(l, 0.0, "subnormal must be flushed to zero");
2677 assert_eq!(r, 0.0);
2678 assert_eq!(patch.get_output_value(src.id(), 10), Some(0.0));
2680 }
2681
2682 #[test]
2685 fn test_precompiled_adjacency_sums_and_exposes_outputs() {
2686 let mut patch = Patch::new(44100.0);
2687 let s1 = patch.add("s1", ConstSource::new(2.0));
2688 let s2 = patch.add("s2", ConstSource::new(3.0));
2689 let sum = patch.add("sum", SumModule::new());
2690 patch.connect(s1.out("out"), sum.in_("a")).unwrap();
2692 patch
2693 .connect_modulated(s2.out("out"), sum.in_("a"), 0.5, 1.0)
2694 .unwrap();
2695 patch.set_output(sum.id());
2696 patch.compile().unwrap();
2697
2698 let (l, _) = patch.tick();
2699 assert!((l - 4.5).abs() < 1e-12, "expected 4.5, got {}", l);
2700 assert_eq!(patch.get_output_value(sum.id(), 10), Some(4.5));
2701 assert_eq!(patch.get_output_value(sum.id(), 999), None);
2703 }
2704}