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