1use std::sync::{Arc, Mutex};
18
19use anyhow::Result;
20use wasmtime::{Caller, Linker};
21
22use crate::capabilities::{read_guest_bytes, write_guest_bytes, HostState};
23
24#[cfg(target_os = "macos")]
27mod platform {
28 use std::collections::{HashMap, VecDeque};
29 use std::sync::{Arc, Mutex};
30
31 use coremidi::{Client, Destination, InputPort, OutputPort, PacketList, Source};
32
33 const MAX_QUEUED_MESSAGES: usize = 4096;
38
39 fn enqueue_messages(data: &[u8], out: &mut VecDeque<Vec<u8>>) {
48 let mut i = 0;
49 while i < data.len() {
50 let status = data[i];
51 if status & 0x80 == 0 {
52 i += 1;
55 continue;
56 }
57 let end = match status & 0xF0 {
58 0x80 | 0x90 | 0xA0 | 0xB0 | 0xE0 => i + 3,
59 0xC0 | 0xD0 => i + 2,
60 0xF0 => match status {
61 0xF0 => {
62 match data[i + 1..].iter().position(|&b| b == 0xF7) {
64 Some(p) => i + 1 + p + 1,
65 None => data.len(),
66 }
67 }
68 0xF1 | 0xF3 => i + 2,
69 0xF2 => i + 3,
70 _ => i + 1,
73 },
74 _ => i + 1,
75 };
76 let end = end.min(data.len());
77 let msg: Vec<u8> = data[i..end].to_vec();
78 if out.len() >= MAX_QUEUED_MESSAGES {
79 out.pop_front();
80 }
81 out.push_back(msg);
82 i = end;
83 }
84 }
85
86 pub struct InputConn {
89 _port: InputPort,
91 pub queue: Arc<Mutex<VecDeque<Vec<u8>>>>,
92 }
93
94 pub struct OutputConn {
95 port: OutputPort,
96 dest_idx: usize,
97 }
98
99 pub struct MidiState {
102 client: Client,
103 next_handle: u32,
104 inputs: HashMap<u32, InputConn>,
105 outputs: HashMap<u32, OutputConn>,
106 }
107
108 impl MidiState {
109 pub fn new() -> Option<Self> {
110 let client = Client::new("oxide-browser").ok()?;
111 Some(Self {
112 client,
113 next_handle: 1,
114 inputs: HashMap::new(),
115 outputs: HashMap::new(),
116 })
117 }
118
119 fn alloc_handle(&mut self) -> u32 {
120 let h = self.next_handle;
121 self.next_handle = self.next_handle.wrapping_add(1).max(1);
122 h
123 }
124
125 pub fn input_count() -> u32 {
126 coremidi::Sources::count() as u32
127 }
128
129 pub fn output_count() -> u32 {
130 coremidi::Destinations::count() as u32
131 }
132
133 pub fn input_name(index: u32) -> Option<String> {
134 let src = Source::from_index(index as usize)?;
135 src.display_name()
136 .or_else(|| Some(format!("Input {}", index)))
137 }
138
139 pub fn output_name(index: u32) -> Option<String> {
140 let dst = Destination::from_index(index as usize)?;
141 dst.display_name()
142 .or_else(|| Some(format!("Output {}", index)))
143 }
144
145 pub fn open_input(&mut self, index: u32) -> u32 {
146 let source = match Source::from_index(index as usize) {
147 Some(s) => s,
148 None => return 0,
149 };
150 let queue: Arc<Mutex<VecDeque<Vec<u8>>>> = Arc::new(Mutex::new(VecDeque::new()));
151 let q = queue.clone();
152 let port_name = format!("oxide-in-{}", index);
153 let port = match self
154 .client
155 .input_port(&port_name, move |pkt_list: &PacketList| {
156 let mut lock = q.lock().unwrap();
157 for pkt in pkt_list.iter() {
158 let bytes = pkt.data();
159 if !bytes.is_empty() {
160 enqueue_messages(bytes, &mut lock);
161 }
162 }
163 }) {
164 Ok(p) => p,
165 Err(_) => return 0,
166 };
167 if port.connect_source(&source).is_err() {
168 return 0;
169 }
170 let handle = self.alloc_handle();
171 self.inputs.insert(handle, InputConn { _port: port, queue });
172 handle
173 }
174
175 pub fn open_output(&mut self, index: u32) -> u32 {
176 let port_name = format!("oxide-out-{}", index);
177 let port = match self.client.output_port(&port_name) {
178 Ok(p) => p,
179 Err(_) => return 0,
180 };
181 let handle = self.alloc_handle();
182 self.outputs.insert(
183 handle,
184 OutputConn {
185 port,
186 dest_idx: index as usize,
187 },
188 );
189 handle
190 }
191
192 pub fn send(&mut self, handle: u32, data: &[u8]) -> bool {
193 let out = match self.outputs.get_mut(&handle) {
194 Some(o) => o,
195 None => return false,
196 };
197 let dest = match Destination::from_index(out.dest_idx) {
198 Some(d) => d,
199 None => return false,
200 };
201 let packets = coremidi::PacketBuffer::new(0, data);
203 out.port.send(&dest, &packets).is_ok()
204 }
205
206 pub fn peek_len(&self, handle: u32) -> Option<usize> {
210 self.inputs
211 .get(&handle)?
212 .queue
213 .lock()
214 .unwrap()
215 .front()
216 .map(|m| m.len())
217 }
218
219 pub fn recv(&self, handle: u32) -> Option<Vec<u8>> {
220 self.inputs.get(&handle)?.queue.lock().unwrap().pop_front()
221 }
222
223 pub fn close(&mut self, handle: u32) {
224 self.inputs.remove(&handle);
225 self.outputs.remove(&handle);
226 }
227 }
228}
229
230#[cfg(not(target_os = "macos"))]
233mod platform {
234 pub struct MidiState;
236
237 impl MidiState {
238 pub fn new() -> Option<Self> {
239 Some(Self)
240 }
241 pub fn input_count() -> u32 {
242 0
243 }
244 pub fn output_count() -> u32 {
245 0
246 }
247 pub fn input_name(_index: u32) -> Option<String> {
248 None
249 }
250 pub fn output_name(_index: u32) -> Option<String> {
251 None
252 }
253 pub fn open_input(&mut self, _index: u32) -> u32 {
254 0
255 }
256 pub fn open_output(&mut self, _index: u32) -> u32 {
257 0
258 }
259 pub fn send(&mut self, _handle: u32, _data: &[u8]) -> bool {
260 false
261 }
262 pub fn peek_len(&self, _handle: u32) -> Option<usize> {
263 None
264 }
265 pub fn recv(&self, _handle: u32) -> Option<Vec<u8>> {
266 None
267 }
268 pub fn close(&mut self, _handle: u32) {}
269 }
270}
271
272pub use platform::MidiState;
275
276fn ensure_midi(state: &Arc<Mutex<Option<MidiState>>>) {
279 let mut g = state.lock().unwrap();
280 if g.is_none() {
281 *g = MidiState::new();
282 }
283}
284
285pub fn register_midi_functions(linker: &mut Linker<HostState>) -> Result<()> {
289 linker.func_wrap(
292 "oxide",
293 "api_midi_input_count",
294 |_caller: Caller<'_, HostState>| -> u32 { MidiState::input_count() },
295 )?;
296
297 linker.func_wrap(
300 "oxide",
301 "api_midi_output_count",
302 |_caller: Caller<'_, HostState>| -> u32 { MidiState::output_count() },
303 )?;
304
305 linker.func_wrap(
310 "oxide",
311 "api_midi_input_name",
312 |mut caller: Caller<'_, HostState>, index: u32, out_ptr: u32, out_cap: u32| -> u32 {
313 let name = match MidiState::input_name(index) {
314 Some(n) => n,
315 None => return 0,
316 };
317 let mem = match caller.data().memory {
318 Some(m) => m,
319 None => return 0,
320 };
321 let bytes = name.as_bytes();
322 let len = bytes.len().min(out_cap as usize);
323 if write_guest_bytes(&mem, &mut caller, out_ptr, &bytes[..len]).is_err() {
324 return 0;
325 }
326 len as u32
327 },
328 )?;
329
330 linker.func_wrap(
333 "oxide",
334 "api_midi_output_name",
335 |mut caller: Caller<'_, HostState>, index: u32, out_ptr: u32, out_cap: u32| -> u32 {
336 let name = match MidiState::output_name(index) {
337 Some(n) => n,
338 None => return 0,
339 };
340 let mem = match caller.data().memory {
341 Some(m) => m,
342 None => return 0,
343 };
344 let bytes = name.as_bytes();
345 let len = bytes.len().min(out_cap as usize);
346 if write_guest_bytes(&mem, &mut caller, out_ptr, &bytes[..len]).is_err() {
347 return 0;
348 }
349 len as u32
350 },
351 )?;
352
353 linker.func_wrap(
357 "oxide",
358 "api_midi_open_input",
359 |caller: Caller<'_, HostState>, index: u32| -> u32 {
360 let midi = caller.data().midi.clone();
361 ensure_midi(&midi);
362 let mut g = midi.lock().unwrap();
363 g.as_mut().map(|s| s.open_input(index)).unwrap_or(0)
364 },
365 )?;
366
367 linker.func_wrap(
370 "oxide",
371 "api_midi_open_output",
372 |caller: Caller<'_, HostState>, index: u32| -> u32 {
373 let midi = caller.data().midi.clone();
374 ensure_midi(&midi);
375 let mut g = midi.lock().unwrap();
376 g.as_mut().map(|s| s.open_output(index)).unwrap_or(0)
377 },
378 )?;
379
380 linker.func_wrap(
384 "oxide",
385 "api_midi_send",
386 |caller: Caller<'_, HostState>, handle: u32, ptr: u32, len: u32| -> i32 {
387 let mem = match caller.data().memory {
388 Some(m) => m,
389 None => return -1,
390 };
391 let data = match read_guest_bytes(&mem, &caller, ptr, len) {
392 Ok(b) => b,
393 Err(_) => return -1,
394 };
395 let midi = caller.data().midi.clone();
396 let mut g = midi.lock().unwrap();
397 if g.as_mut().is_some_and(|s| s.send(handle, &data)) {
398 0
399 } else {
400 -1
401 }
402 },
403 )?;
404
405 linker.func_wrap(
412 "oxide",
413 "api_midi_recv",
414 |mut caller: Caller<'_, HostState>, handle: u32, out_ptr: u32, out_cap: u32| -> i32 {
415 let midi = caller.data().midi.clone();
416 let peek = {
417 let g = midi.lock().unwrap();
418 g.as_ref().and_then(|s| s.peek_len(handle))
419 };
420 let msg_len = match peek {
421 Some(n) => n,
422 None => return -1,
423 };
424 if msg_len > out_cap as usize {
425 return -2;
426 }
427 let msg = {
428 let g = midi.lock().unwrap();
429 match g.as_ref().and_then(|s| s.recv(handle)) {
430 Some(m) => m,
431 None => return -1,
432 }
433 };
434 let mem = match caller.data().memory {
435 Some(m) => m,
436 None => return -1,
437 };
438 if write_guest_bytes(&mem, &mut caller, out_ptr, &msg).is_err() {
439 return -1;
440 }
441 msg.len() as i32
442 },
443 )?;
444
445 linker.func_wrap(
449 "oxide",
450 "api_midi_close",
451 |caller: Caller<'_, HostState>, handle: u32| {
452 let midi = caller.data().midi.clone();
453 let mut g = midi.lock().unwrap();
454 if let Some(ref mut state) = *g {
455 state.close(handle);
456 }
457 },
458 )?;
459
460 Ok(())
461}