1use crate::audio::io::AudioIO;
2use crate::midi::io::MIDIIO;
3use serde::{Deserialize, Serialize};
4use std::sync::Arc;
5
6pub trait AudioPorts {
8 fn audio_inputs(&self) -> Vec<Arc<AudioIO>>;
9 fn audio_outputs(&self) -> Vec<Arc<AudioIO>>;
10}
11
12pub trait MidiPorts {
14 fn midi_inputs(&self) -> Vec<Arc<MIDIIO>>;
15 fn midi_outputs(&self) -> Vec<Arc<MIDIIO>>;
16}
17
18pub trait Connectable: AudioPorts + MidiPorts {}
21impl<T: AudioPorts + MidiPorts> Connectable for T {}
22
23#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
25#[serde(tag = "type", content = "value")]
26pub enum ConnectableRef {
27 TrackInput,
28 TrackOutput,
29 ChildTrack(String),
30 ClapPlugin(usize),
31 Vst3Plugin(usize),
32 #[cfg(all(unix, not(target_os = "macos")))]
33 Lv2Plugin(usize),
34}
35
36#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
38pub struct ConnectableConnection {
39 pub from: ConnectableRef,
40 pub from_port: usize,
41 pub to: ConnectableRef,
42 pub to_port: usize,
43 pub kind: crate::kind::Kind,
44}
45
46fn audio_output_port(connectable: &dyn AudioPorts, port: usize) -> Result<Arc<AudioIO>, String> {
47 connectable
48 .audio_outputs()
49 .get(port)
50 .cloned()
51 .ok_or_else(|| format!("Audio output port {port} not found"))
52}
53
54fn audio_input_port(connectable: &dyn AudioPorts, port: usize) -> Result<Arc<AudioIO>, String> {
55 connectable
56 .audio_inputs()
57 .get(port)
58 .cloned()
59 .ok_or_else(|| format!("Audio input port {port} not found"))
60}
61
62fn midi_output_port(connectable: &dyn MidiPorts, port: usize) -> Result<Arc<MIDIIO>, String> {
63 connectable
64 .midi_outputs()
65 .get(port)
66 .cloned()
67 .ok_or_else(|| format!("MIDI output port {port} not found"))
68}
69
70fn midi_input_port(connectable: &dyn MidiPorts, port: usize) -> Result<Arc<MIDIIO>, String> {
71 connectable
72 .midi_inputs()
73 .get(port)
74 .cloned()
75 .ok_or_else(|| format!("MIDI input port {port} not found"))
76}
77
78pub fn connect_audio(
80 source: &dyn AudioPorts,
81 source_port: usize,
82 target: &dyn AudioPorts,
83 target_port: usize,
84) -> Result<(), String> {
85 let from = audio_output_port(source, source_port)?;
86 let to = audio_input_port(target, target_port)?;
87 AudioIO::connect(&from, &to);
88 Ok(())
89}
90
91pub fn disconnect_audio(
93 source: &dyn AudioPorts,
94 source_port: usize,
95 target: &dyn AudioPorts,
96 target_port: usize,
97) -> Result<(), String> {
98 let from = audio_output_port(source, source_port)?;
99 let to = audio_input_port(target, target_port)?;
100 AudioIO::disconnect(&from, &to)
101}
102
103pub fn connect_midi(
105 source: &dyn MidiPorts,
106 source_port: usize,
107 target: &dyn MidiPorts,
108 target_port: usize,
109) -> Result<(), String> {
110 let from = midi_output_port(source, source_port)?;
111 let to = midi_input_port(target, target_port)?;
112 MIDIIO::connect(&from, &to);
113 Ok(())
114}
115
116pub fn disconnect_midi(
118 source: &dyn MidiPorts,
119 source_port: usize,
120 target: &dyn MidiPorts,
121 target_port: usize,
122) -> Result<(), String> {
123 let from = midi_output_port(source, source_port)?;
124 let to = midi_input_port(target, target_port)?;
125 MIDIIO::disconnect(&from, &to)
126}
127
128#[cfg(test)]
129mod tests {
130 use super::{
131 AudioPorts, ConnectableConnection, ConnectableRef, MidiPorts, connect_audio, connect_midi,
132 disconnect_audio, disconnect_midi,
133 };
134 use crate::audio::io::AudioIO;
135 use crate::midi::io::MIDIIO;
136 use std::sync::Arc;
137
138 struct TestNode {
139 audio_ins: Vec<Arc<AudioIO>>,
140 audio_outs: Vec<Arc<AudioIO>>,
141 midi_ins: Vec<Arc<MIDIIO>>,
142 midi_outs: Vec<Arc<MIDIIO>>,
143 }
144
145 impl TestNode {
146 fn new(
147 audio_in_count: usize,
148 audio_out_count: usize,
149 midi_in_count: usize,
150 midi_out_count: usize,
151 buffer_size: usize,
152 ) -> Self {
153 Self {
154 audio_ins: (0..audio_in_count)
155 .map(|_| Arc::new(AudioIO::new(buffer_size)))
156 .collect(),
157 audio_outs: (0..audio_out_count)
158 .map(|_| Arc::new(AudioIO::new(buffer_size)))
159 .collect(),
160 midi_ins: (0..midi_in_count)
161 .map(|_| Arc::new(MIDIIO::new()))
162 .collect(),
163 midi_outs: (0..midi_out_count)
164 .map(|_| Arc::new(MIDIIO::new()))
165 .collect(),
166 }
167 }
168 }
169
170 impl AudioPorts for TestNode {
171 fn audio_inputs(&self) -> Vec<Arc<AudioIO>> {
172 self.audio_ins.clone()
173 }
174 fn audio_outputs(&self) -> Vec<Arc<AudioIO>> {
175 self.audio_outs.clone()
176 }
177 }
178
179 impl MidiPorts for TestNode {
180 fn midi_inputs(&self) -> Vec<Arc<MIDIIO>> {
181 self.midi_ins.clone()
182 }
183 fn midi_outputs(&self) -> Vec<Arc<MIDIIO>> {
184 self.midi_outs.clone()
185 }
186 }
187
188 #[test]
189 fn connect_audio_links_output_to_input() {
190 let source = TestNode::new(0, 1, 0, 0, 4);
191 let target = TestNode::new(1, 0, 0, 0, 4);
192
193 connect_audio(&source, 0, &target, 0).unwrap();
194
195 assert!(
196 target.audio_ins[0]
197 .connections()
198 .iter()
199 .any(|c| Arc::ptr_eq(c, &source.audio_outs[0]))
200 );
201 }
202
203 #[test]
204 fn disconnect_audio_removes_link() {
205 let source = TestNode::new(0, 1, 0, 0, 4);
206 let target = TestNode::new(1, 0, 0, 0, 4);
207 connect_audio(&source, 0, &target, 0).unwrap();
208
209 disconnect_audio(&source, 0, &target, 0).unwrap();
210
211 assert!(target.audio_ins[0].connections().is_empty());
212 assert!(source.audio_outs[0].connections().is_empty());
213 }
214
215 #[test]
216 fn disconnect_audio_errors_when_missing() {
217 let source = TestNode::new(0, 1, 0, 0, 4);
218 let target = TestNode::new(1, 0, 0, 0, 4);
219
220 let err = disconnect_audio(&source, 0, &target, 0).unwrap_err();
221 assert_eq!(err, "Connection not found");
222 }
223
224 #[test]
225 fn connect_midi_links_output_to_input() {
226 let source = TestNode::new(0, 0, 0, 1, 4);
227 let target = TestNode::new(0, 0, 1, 0, 4);
228
229 connect_midi(&source, 0, &target, 0).unwrap();
230
231 assert!(
232 target.midi_ins[0]
233 .sources()
234 .iter()
235 .any(|s| Arc::ptr_eq(s, &source.midi_outs[0]))
236 );
237 }
238
239 #[test]
240 fn disconnect_midi_removes_link() {
241 let source = TestNode::new(0, 0, 0, 1, 4);
242 let target = TestNode::new(0, 0, 1, 0, 4);
243 connect_midi(&source, 0, &target, 0).unwrap();
244
245 disconnect_midi(&source, 0, &target, 0).unwrap();
246
247 assert!(target.midi_ins[0].sources().is_empty());
248 assert!(source.midi_outs[0].connections().is_empty());
249 }
250
251 #[test]
252 fn connect_invalid_audio_port_errors() {
253 let source = TestNode::new(0, 0, 0, 0, 4);
254 let target = TestNode::new(1, 0, 0, 0, 4);
255
256 assert!(connect_audio(&source, 0, &target, 0).is_err());
257 assert!(connect_audio(&target, 0, &source, 0).is_err());
258 }
259
260 #[test]
261 fn connectable_ref_serde_round_trip() {
262 let refs = vec![
263 ConnectableRef::TrackInput,
264 ConnectableRef::TrackOutput,
265 ConnectableRef::ChildTrack("Child".to_string()),
266 ConnectableRef::ClapPlugin(3),
267 ConnectableRef::Vst3Plugin(7),
268 ];
269 for r in refs {
270 let serialized = serde_json::to_string(&r).unwrap();
271 let deserialized: ConnectableRef = serde_json::from_str(&serialized).unwrap();
272 assert_eq!(r, deserialized);
273 }
274 }
275
276 #[test]
277 fn connectable_connection_serde_round_trip() {
278 let conn = ConnectableConnection {
279 from: ConnectableRef::ChildTrack("Child".to_string()),
280 from_port: 0,
281 to: ConnectableRef::ClapPlugin(2),
282 to_port: 1,
283 kind: crate::kind::Kind::Audio,
284 };
285 let serialized = serde_json::to_string(&conn).unwrap();
286 let deserialized: ConnectableConnection = serde_json::from_str(&serialized).unwrap();
287 assert_eq!(conn, deserialized);
288 }
289}