lib.rs (12217B)
1 extern crate thin_vec; 2 3 use midir::{ 4 InitError, MidiInput, MidiInputConnection, MidiInputPort, MidiOutput, MidiOutputConnection, 5 MidiOutputPort, 6 }; 7 use nsstring::{nsAString, nsString}; 8 use std::ptr; 9 use thin_vec::ThinVec; 10 use uuid::Uuid; 11 12 /* This Source Code Form is subject to the terms of the Mozilla Public 13 * License, v. 2.0. If a copy of the MPL was not distributed with this file, 14 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 15 extern crate midir; 16 17 #[repr(C)] 18 #[derive(Clone, Copy)] 19 pub struct GeckoTimeStamp { 20 value: u64, 21 } 22 23 enum MidiConnection { 24 Input(MidiInputConnection<CallbackData>), 25 Output(MidiOutputConnection), 26 } 27 28 struct MidiConnectionWrapper { 29 id: String, 30 connection: MidiConnection, 31 } 32 33 enum MidiPort { 34 Input(MidiInputPort), 35 Output(MidiOutputPort), 36 } 37 38 struct MidiPortWrapper { 39 id: String, 40 name: String, 41 port: MidiPort, 42 open_count: u32, 43 } 44 45 impl MidiPortWrapper { 46 fn input(self: &MidiPortWrapper) -> bool { 47 match self.port { 48 MidiPort::Input(_) => true, 49 MidiPort::Output(_) => false, 50 } 51 } 52 } 53 54 pub struct MidirWrapper { 55 ports: Vec<MidiPortWrapper>, 56 connections: Vec<MidiConnectionWrapper>, 57 } 58 59 struct CallbackData { 60 nsid: nsString, 61 open_timestamp: GeckoTimeStamp, 62 } 63 64 type AddCallback = unsafe extern "C" fn(id: &nsString, name: &nsString, input: bool); 65 type RemoveCallback = AddCallback; 66 67 impl MidirWrapper { 68 fn refresh( 69 self: &mut MidirWrapper, 70 add_callback: AddCallback, 71 remove_callback: Option<RemoveCallback>, 72 ) { 73 if let Ok(ports) = collect_ports() { 74 if let Some(remove_callback) = remove_callback { 75 self.remove_missing_ports(&ports, remove_callback); 76 } 77 78 self.add_new_ports(ports, add_callback); 79 } 80 } 81 82 fn remove_missing_ports( 83 self: &mut MidirWrapper, 84 ports: &Vec<MidiPortWrapper>, 85 remove_callback: RemoveCallback, 86 ) { 87 let old_ports = &mut self.ports; 88 let mut i = 0; 89 while i < old_ports.len() { 90 if !ports 91 .iter() 92 .any(|p| p.name == old_ports[i].name && p.input() == old_ports[i].input()) 93 { 94 let port = old_ports.remove(i); 95 let id = nsString::from(&port.id); 96 let name = nsString::from(&port.name); 97 unsafe { remove_callback(&id, &name, port.input()) }; 98 } else { 99 i += 1; 100 } 101 } 102 } 103 104 fn add_new_ports( 105 self: &mut MidirWrapper, 106 ports: Vec<MidiPortWrapper>, 107 add_callback: AddCallback, 108 ) { 109 for port in ports { 110 if !self.is_port_present(&port) && !Self::is_microsoft_synth_output(&port) { 111 let id = nsString::from(&port.id); 112 let name = nsString::from(&port.name); 113 unsafe { add_callback(&id, &name, port.input()) }; 114 self.ports.push(port); 115 } 116 } 117 } 118 119 fn is_port_present(self: &MidirWrapper, port: &MidiPortWrapper) -> bool { 120 self.ports 121 .iter() 122 .any(|p| p.name == port.name && p.input() == port.input()) 123 } 124 125 // We explicitly disable Microsoft's soft synthesizer, see bug 1798097 126 fn is_microsoft_synth_output(port: &MidiPortWrapper) -> bool { 127 !port.input() && (port.name == "Microsoft GS Wavetable Synth") 128 } 129 130 fn open_port( 131 self: &mut MidirWrapper, 132 nsid: &nsString, 133 timestamp: GeckoTimeStamp, 134 callback: unsafe extern "C" fn( 135 id: &nsString, 136 data: *const u8, 137 length: usize, 138 timestamp: &GeckoTimeStamp, 139 micros: u64, 140 ), 141 ) -> Result<(), ()> { 142 let id = nsid.to_string(); 143 let connections = &mut self.connections; 144 let port = self.ports.iter_mut().find(|e| e.id.eq(&id)); 145 if let Some(port) = port { 146 if port.open_count == 0 { 147 let connection = match &port.port { 148 MidiPort::Input(port) => { 149 let input = MidiInput::new("WebMIDI input").map_err(|_err| ())?; 150 let data = CallbackData { 151 nsid: nsid.clone(), 152 open_timestamp: timestamp, 153 }; 154 let connection = input 155 .connect( 156 port, 157 "Input connection", 158 move |stamp, message, data| unsafe { 159 callback( 160 &data.nsid, 161 message.as_ptr(), 162 message.len(), 163 &data.open_timestamp, 164 stamp, 165 ); 166 }, 167 data, 168 ) 169 .map_err(|_err| ())?; 170 MidiConnectionWrapper { 171 id: id.clone(), 172 connection: MidiConnection::Input(connection), 173 } 174 } 175 MidiPort::Output(port) => { 176 let output = MidiOutput::new("WebMIDI output").map_err(|_err| ())?; 177 let connection = output 178 .connect(port, "Output connection") 179 .map_err(|_err| ())?; 180 MidiConnectionWrapper { 181 connection: MidiConnection::Output(connection), 182 id: id.clone(), 183 } 184 } 185 }; 186 187 connections.push(connection); 188 } 189 190 port.open_count += 1; 191 return Ok(()); 192 } 193 194 Err(()) 195 } 196 197 fn close_port(self: &mut MidirWrapper, id: &str) { 198 let port = self.ports.iter_mut().find(|e| e.id.eq(&id)).unwrap(); 199 port.open_count -= 1; 200 201 if port.open_count > 0 { 202 return; 203 } 204 205 let connections = &mut self.connections; 206 let index = connections.iter().position(|e| e.id.eq(id)).unwrap(); 207 let connection_wrapper = connections.remove(index); 208 209 match connection_wrapper.connection { 210 MidiConnection::Input(connection) => { 211 connection.close(); 212 } 213 MidiConnection::Output(connection) => { 214 connection.close(); 215 } 216 } 217 } 218 219 fn send(self: &mut MidirWrapper, id: &str, data: &[u8]) -> Result<(), ()> { 220 let connections = &mut self.connections; 221 let index = connections.iter().position(|e| e.id.eq(id)).ok_or(())?; 222 let connection_wrapper = connections.get_mut(index).unwrap(); 223 224 match &mut connection_wrapper.connection { 225 MidiConnection::Output(connection) => { 226 connection.send(data).map_err(|_err| ())?; 227 } 228 _ => { 229 panic!("Sending on an input port!"); 230 } 231 } 232 233 Ok(()) 234 } 235 } 236 237 fn collect_ports() -> Result<Vec<MidiPortWrapper>, InitError> { 238 let input = MidiInput::new("WebMIDI input")?; 239 let output = MidiOutput::new("WebMIDI output")?; 240 let mut ports = Vec::<MidiPortWrapper>::new(); 241 collect_input_ports(&input, &mut ports); 242 collect_output_ports(&output, &mut ports); 243 Ok(ports) 244 } 245 246 impl MidirWrapper { 247 fn new() -> Result<MidirWrapper, InitError> { 248 let ports = Vec::new(); 249 let connections: Vec<MidiConnectionWrapper> = Vec::new(); 250 Ok(MidirWrapper { ports, connections }) 251 } 252 } 253 254 /// Create the C++ wrapper that will be used to talk with midir. 255 /// 256 /// This function will be exposed to C++ 257 /// 258 /// # Safety 259 /// 260 /// This function deliberately leaks the wrapper because ownership is 261 /// transfered to the C++ code. Use [midir_impl_shutdown()] to free it. 262 #[no_mangle] 263 pub unsafe extern "C" fn midir_impl_init(callback: AddCallback) -> *mut MidirWrapper { 264 if let Ok(mut midir_impl) = MidirWrapper::new() { 265 midir_impl.refresh(callback, None); 266 267 // Gecko invokes this initialization on a separate thread from all the 268 // other operations, so make it clear to Rust this needs to be Send. 269 fn assert_send<T: Send>(_: &T) {} 270 assert_send(&midir_impl); 271 272 let midir_box = Box::new(midir_impl); 273 // Leak the object as it will be owned by the C++ code from now on 274 Box::leak(midir_box) as *mut _ 275 } else { 276 ptr::null_mut() 277 } 278 } 279 280 /// Refresh the list of ports. 281 /// 282 /// This function will be exposed to C++ 283 /// 284 /// # Safety 285 /// 286 /// `wrapper` must be the pointer returned by [midir_impl_init()]. 287 #[no_mangle] 288 pub unsafe extern "C" fn midir_impl_refresh( 289 wrapper: *mut MidirWrapper, 290 add_callback: AddCallback, 291 remove_callback: RemoveCallback, 292 ) { 293 (*wrapper).refresh(add_callback, Some(remove_callback)) 294 } 295 296 /// Shutdown midir and free the C++ wrapper. 297 /// 298 /// This function will be exposed to C++ 299 /// 300 /// # Safety 301 /// 302 /// `wrapper` must be the pointer returned by [midir_impl_init()]. After this 303 /// has been called the wrapper object will be destoyed and cannot be accessed 304 /// anymore. 305 #[no_mangle] 306 pub unsafe extern "C" fn midir_impl_shutdown(wrapper: *mut MidirWrapper) { 307 // The MidirImpl object will be automatically destroyed when the contents 308 // of this box are automatically dropped at the end of the function 309 let _midir_box = Box::from_raw(wrapper); 310 } 311 312 /// Open a MIDI port. 313 /// 314 /// This function will be exposed to C++ 315 /// 316 /// # Safety 317 /// 318 /// `wrapper` must be the pointer returned by [midir_impl_init()]. 319 #[no_mangle] 320 pub unsafe extern "C" fn midir_impl_open_port( 321 wrapper: *mut MidirWrapper, 322 nsid: *mut nsString, 323 timestamp: *mut GeckoTimeStamp, 324 callback: unsafe extern "C" fn( 325 id: &nsString, 326 data: *const u8, 327 length: usize, 328 timestamp: &GeckoTimeStamp, 329 micros: u64, 330 ), 331 ) -> bool { 332 (*wrapper) 333 .open_port(nsid.as_ref().unwrap(), *timestamp, callback) 334 .is_ok() 335 } 336 337 /// Close a MIDI port. 338 /// 339 /// This function will be exposed to C++ 340 /// 341 /// # Safety 342 /// 343 /// `wrapper` must be the pointer returned by [midir_impl_init()]. 344 #[no_mangle] 345 pub unsafe extern "C" fn midir_impl_close_port(wrapper: *mut MidirWrapper, id: *mut nsString) { 346 (*wrapper).close_port(&(*id).to_string()); 347 } 348 349 /// Send a message over a MIDI output port. 350 /// 351 /// This function will be exposed to C++ 352 /// 353 /// # Safety 354 /// 355 /// `wrapper` must be the pointer returned by [midir_impl_init()]. 356 #[no_mangle] 357 pub unsafe extern "C" fn midir_impl_send( 358 wrapper: *mut MidirWrapper, 359 id: *const nsAString, 360 data: *const ThinVec<u8>, 361 ) -> bool { 362 (*wrapper) 363 .send(&(*id).to_string(), (*data).as_slice()) 364 .is_ok() 365 } 366 367 fn collect_input_ports(input: &MidiInput, wrappers: &mut Vec<MidiPortWrapper>) { 368 let ports = input.ports(); 369 for port in ports { 370 let id = Uuid::new_v4() 371 .as_hyphenated() 372 .encode_lower(&mut Uuid::encode_buffer()) 373 .to_owned(); 374 let name = input 375 .port_name(&port) 376 .unwrap_or_else(|_| "unknown input port".to_string()); 377 let port = MidiPortWrapper { 378 id, 379 name, 380 port: MidiPort::Input(port), 381 open_count: 0, 382 }; 383 wrappers.push(port); 384 } 385 } 386 387 fn collect_output_ports(output: &MidiOutput, wrappers: &mut Vec<MidiPortWrapper>) { 388 let ports = output.ports(); 389 for port in ports { 390 let id = Uuid::new_v4() 391 .as_hyphenated() 392 .encode_lower(&mut Uuid::encode_buffer()) 393 .to_owned(); 394 let name = output 395 .port_name(&port) 396 .unwrap_or_else(|_| "unknown input port".to_string()); 397 let port = MidiPortWrapper { 398 id, 399 name, 400 port: MidiPort::Output(port), 401 open_count: 0, 402 }; 403 wrappers.push(port); 404 } 405 }