diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /third_party/rust/midir/src/backend/jack | |
parent | Initial commit. (diff) | |
download | firefox-esr-upstream.tar.xz firefox-esr-upstream.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/midir/src/backend/jack')
-rw-r--r-- | third_party/rust/midir/src/backend/jack/mod.rs | 364 | ||||
-rw-r--r-- | third_party/rust/midir/src/backend/jack/wrappers.rs | 250 |
2 files changed, 614 insertions, 0 deletions
diff --git a/third_party/rust/midir/src/backend/jack/mod.rs b/third_party/rust/midir/src/backend/jack/mod.rs new file mode 100644 index 0000000000..92cc81a295 --- /dev/null +++ b/third_party/rust/midir/src/backend/jack/mod.rs @@ -0,0 +1,364 @@ +extern crate jack_sys; +extern crate libc; + +use self::jack_sys::jack_nframes_t; +use self::libc::c_void; + +use std::{mem, slice}; +use std::ffi::CString; + +mod wrappers; +use self::wrappers::*; + +use ::{Ignore, MidiMessage}; +use ::errors::*; + +const OUTPUT_RINGBUFFER_SIZE: usize = 16384; + +struct InputHandlerData<T> { + port: Option<MidiPort>, + ignore_flags: Ignore, + callback: Box<dyn FnMut(u64, &[u8], &mut T) + Send>, + user_data: Option<T> +} + +pub struct MidiInput { + ignore_flags: Ignore, + client: Option<Client>, +} + +#[derive(Clone, PartialEq)] +pub struct MidiInputPort { + name: CString +} + +pub struct MidiInputConnection<T> { + handler_data: Box<InputHandlerData<T>>, + client: Option<Client> +} + +impl MidiInput { + pub fn new(client_name: &str) -> Result<Self, InitError> { + let client = match Client::open(client_name, JackOpenOptions::NoStartServer) { + Ok(c) => c, + Err(_) => { return Err(InitError); } // TODO: maybe add message that Jack server might not be running + }; + + Ok(MidiInput { + ignore_flags: Ignore::None, + client: Some(client), + }) + } + + pub fn ignore(&mut self, flags: Ignore) { + self.ignore_flags = flags; + } + + pub(crate) fn ports_internal(&self) -> Vec<::common::MidiInputPort> { + let ports = self.client.as_ref().unwrap().get_midi_ports(PortFlags::PortIsOutput); + let mut result = Vec::with_capacity(ports.count()); + for i in 0..ports.count() { + result.push(::common::MidiInputPort { + imp: MidiInputPort { + name: ports.get_c_name(i).into() + } + }) + } + result + } + + pub fn port_count(&self) -> usize { + self.client.as_ref().unwrap().get_midi_ports(PortFlags::PortIsOutput).count() + } + + pub fn port_name(&self, port: &MidiInputPort) -> Result<String, PortInfoError> { + Ok(port.name.to_string_lossy().into()) + } + + fn activate_callback<F, T: Send>(&mut self, callback: F, data: T) + -> Box<InputHandlerData<T>> + where F: FnMut(u64, &[u8], &mut T) + Send + 'static + { + let handler_data = Box::new(InputHandlerData { + port: None, + ignore_flags: self.ignore_flags, + callback: Box::new(callback), + user_data: Some(data) + }); + + let data_ptr = unsafe { mem::transmute_copy::<_, *mut InputHandlerData<T>>(&handler_data) }; + + self.client.as_mut().unwrap().set_process_callback(handle_input::<T>, data_ptr as *mut c_void); + self.client.as_mut().unwrap().activate(); + handler_data + } + + pub fn connect<F, T: Send>( + mut self, port: &MidiInputPort, port_name: &str, callback: F, data: T + ) -> Result<MidiInputConnection<T>, ConnectError<MidiInput>> + where F: FnMut(u64, &[u8], &mut T) + Send + 'static { + + let mut handler_data = self.activate_callback(callback, data); + + // Create port ... + let dest_port = match self.client.as_mut().unwrap().register_midi_port(port_name, PortFlags::PortIsInput) { + Ok(p) => p, + Err(()) => { return Err(ConnectError::other("could not register JACK port", self)); } + }; + + // ... and connect it to the output + if let Err(_) = self.client.as_mut().unwrap().connect(&port.name, dest_port.get_name()) { + return Err(ConnectError::new(ConnectErrorKind::InvalidPort, self)); + } + + handler_data.port = Some(dest_port); + + Ok(MidiInputConnection { + handler_data: handler_data, + client: self.client.take() + }) + } + + pub fn create_virtual<F, T: Send>( + mut self, port_name: &str, callback: F, data: T + ) -> Result<MidiInputConnection<T>, ConnectError<Self>> + where F: FnMut(u64, &[u8], &mut T) + Send + 'static { + + let mut handler_data = self.activate_callback(callback, data); + + // Create port + let port = match self.client.as_mut().unwrap().register_midi_port(port_name, PortFlags::PortIsInput) { + Ok(p) => p, + Err(()) => { return Err(ConnectError::other("could not register JACK port", self)); } + }; + + handler_data.port = Some(port); + + Ok(MidiInputConnection { + handler_data: handler_data, + client: self.client.take() + }) + } +} + +impl<T> MidiInputConnection<T> { + pub fn close(mut self) -> (MidiInput, T) { + self.close_internal(); + + (MidiInput { + client: self.client.take(), + ignore_flags: self.handler_data.ignore_flags, + }, self.handler_data.user_data.take().unwrap()) + } + + fn close_internal(&mut self) { + let port = self.handler_data.port.take().unwrap(); + self.client.as_mut().unwrap().unregister_midi_port(port); + self.client.as_mut().unwrap().deactivate(); + } +} + +impl<T> Drop for MidiInputConnection<T> { + fn drop(&mut self) { + if self.client.is_some() { + self.close_internal(); + } + } +} + +extern "C" fn handle_input<T>(nframes: jack_nframes_t, arg: *mut c_void) -> i32 { + let data: &mut InputHandlerData<T> = unsafe { &mut *(arg as *mut InputHandlerData<T>) }; + + // Is port created? + if let Some(ref port) = data.port { + let buff = port.get_midi_buffer(nframes); + + let mut message = MidiMessage::new(); // TODO: create MidiMessage once and reuse its buffer for every handle_input call + + // We have midi events in buffer + let evcount = buff.get_event_count(); + let mut event = mem::MaybeUninit::uninit(); + + for j in 0..evcount { + message.bytes.clear(); + unsafe { buff.get_event(event.as_mut_ptr(), j) }; + let event = unsafe { event.assume_init() }; + + for i in 0..event.size { + message.bytes.push(unsafe { *event.buffer.offset(i as isize) }); + } + + message.timestamp = Client::get_time(); // this is in microseconds + (data.callback)(message.timestamp, &message.bytes, data.user_data.as_mut().unwrap()); + } + } + + return 0; +} + +struct OutputHandlerData { + port: Option<MidiPort>, + buff_size: Ringbuffer, + buff_message: Ringbuffer, +} + +pub struct MidiOutput { + client: Option<Client>, +} + +#[derive(Clone, PartialEq)] +pub struct MidiOutputPort { + name: CString +} + +pub struct MidiOutputConnection { + handler_data: Box<OutputHandlerData>, + client: Option<Client> +} + +impl MidiOutput { + pub fn new(client_name: &str) -> Result<Self, InitError> { + let client = match Client::open(client_name, JackOpenOptions::NoStartServer) { + Ok(c) => c, + Err(_) => { return Err(InitError); } // TODO: maybe add message that Jack server might not be running + }; + + Ok(MidiOutput { + client: Some(client), + }) + } + + pub(crate) fn ports_internal(&self) -> Vec<::common::MidiOutputPort> { + let ports = self.client.as_ref().unwrap().get_midi_ports(PortFlags::PortIsInput); + let mut result = Vec::with_capacity(ports.count()); + for i in 0..ports.count() { + result.push(::common::MidiOutputPort { + imp: MidiOutputPort { + name: ports.get_c_name(i).into() + } + }) + } + result + } + + pub fn port_count(&self) -> usize { + self.client.as_ref().unwrap().get_midi_ports(PortFlags::PortIsInput).count() + } + + pub fn port_name(&self, port: &MidiOutputPort) -> Result<String, PortInfoError> { + Ok(port.name.to_string_lossy().into()) + } + + fn activate_callback(&mut self) -> Box<OutputHandlerData> { + let handler_data = Box::new(OutputHandlerData { + port: None, + buff_size: Ringbuffer::new(OUTPUT_RINGBUFFER_SIZE), + buff_message: Ringbuffer::new(OUTPUT_RINGBUFFER_SIZE) + }); + + let data_ptr = unsafe { mem::transmute_copy::<_, *mut OutputHandlerData>(&handler_data) }; + + self.client.as_mut().unwrap().set_process_callback(handle_output, data_ptr as *mut c_void); + self.client.as_mut().unwrap().activate(); + handler_data + } + + pub fn connect(mut self, port: &MidiOutputPort, port_name: &str) -> Result<MidiOutputConnection, ConnectError<MidiOutput>> { + let mut handler_data = self.activate_callback(); + + // Create port ... + let source_port = match self.client.as_mut().unwrap().register_midi_port(port_name, PortFlags::PortIsOutput) { + Ok(p) => p, + Err(()) => { return Err(ConnectError::other("could not register JACK port", self)); } + }; + + // ... and connect it to the input + if let Err(_) = self.client.as_mut().unwrap().connect(source_port.get_name(), &port.name) { + return Err(ConnectError::new(ConnectErrorKind::InvalidPort, self)); + } + + handler_data.port = Some(source_port); + + Ok(MidiOutputConnection { + handler_data: handler_data, + client: self.client.take() + }) + } + + pub fn create_virtual( + mut self, port_name: &str + ) -> Result<MidiOutputConnection, ConnectError<Self>> { + let mut handler_data = self.activate_callback(); + + // Create port + let port = match self.client.as_mut().unwrap().register_midi_port(port_name, PortFlags::PortIsOutput) { + Ok(p) => p, + Err(()) => { return Err(ConnectError::other("could not register JACK port", self)); } + }; + + handler_data.port = Some(port); + + Ok(MidiOutputConnection { + handler_data: handler_data, + client: self.client.take() + }) + } +} + +impl MidiOutputConnection { + pub fn send(&mut self, message: &[u8]) -> Result<(), SendError> { + let nbytes = message.len(); + + // Write full message to buffer + let written = self.handler_data.buff_message.write(message); + debug_assert!(written == nbytes, "not enough bytes written to ALSA ringbuffer `message`"); + let nbytes_slice = unsafe { slice::from_raw_parts(&nbytes as *const usize as *const u8, mem::size_of_val(&nbytes)) }; + let written = self.handler_data.buff_size.write(nbytes_slice); + debug_assert!(written == mem::size_of_val(&nbytes), "not enough bytes written to ALSA ringbuffer `size`"); + Ok(()) + } + + pub fn close(mut self) -> MidiOutput { + self.close_internal(); + + MidiOutput { + client: self.client.take(), + } + } + + fn close_internal(&mut self) { + let port = self.handler_data.port.take().unwrap(); + self.client.as_mut().unwrap().unregister_midi_port(port); + self.client.as_mut().unwrap().deactivate(); + } +} + +impl Drop for MidiOutputConnection { + fn drop(&mut self) { + if self.client.is_some() { + self.close_internal(); + } + } +} + +extern "C" fn handle_output(nframes: jack_nframes_t, arg: *mut c_void) -> i32 { + let data: &mut OutputHandlerData = unsafe { mem::transmute(arg) }; + + // Is port created? + if let Some(ref port) = data.port { + let mut space: usize = 0; + + let mut buff = port.get_midi_buffer(nframes); + buff.clear(); + + while data.buff_size.get_read_space() > 0 { + let read = data.buff_size.read(&mut space as *mut usize as *mut u8, mem::size_of::<usize>()); + debug_assert!(read == mem::size_of::<usize>(), "not enough bytes read from `size` ringbuffer"); + let midi_data = buff.event_reserve(0, space); + let read = data.buff_message.read(midi_data, space); + debug_assert!(read == space, "not enough bytes read from `message` ringbuffer"); + } + } + + return 0; +} diff --git a/third_party/rust/midir/src/backend/jack/wrappers.rs b/third_party/rust/midir/src/backend/jack/wrappers.rs new file mode 100644 index 0000000000..ef00e2d73f --- /dev/null +++ b/third_party/rust/midir/src/backend/jack/wrappers.rs @@ -0,0 +1,250 @@ +#![allow(non_upper_case_globals, dead_code)] + +use std::{ptr, slice, str}; +use std::ffi::{CStr, CString}; +use std::ops::Index; + +use super::libc::{c_void, size_t}; + +use super::jack_sys::{ + jack_get_time, + jack_client_t, + jack_client_open, + jack_client_close, + jack_get_ports, + jack_port_t, + jack_port_register, + jack_port_unregister, + jack_port_name, + jack_free, + jack_activate, + jack_deactivate, + jack_nframes_t, + jack_set_process_callback, + jack_connect, + jack_port_get_buffer, + jack_midi_data_t, + jack_midi_get_event_count, + jack_midi_event_get, + jack_midi_event_t, + jack_midi_clear_buffer, + jack_midi_event_reserve, + jack_ringbuffer_t, + jack_ringbuffer_create, + jack_ringbuffer_read_space, + jack_ringbuffer_read, + jack_ringbuffer_free, + jack_ringbuffer_write, +}; + +pub const JACK_DEFAULT_MIDI_TYPE: &[u8] = b"8 bit raw midi\0"; + +bitflags! { + pub struct JackOpenOptions: u32 { + const NoStartServer = 1; + const UseExactName = 2; + const ServerName = 4; + const SessionID = 32; + } +} + +bitflags! { + pub struct PortFlags: u32 { + const PortIsInput = 1; + const PortIsOutput = 2; + const PortIsPhysical = 4; + const PortCanMonitor = 8; + const PortIsTerminal = 16; + } +} + +// TODO: hide this type +pub type ProcessCallback = extern "C" fn(nframes: jack_nframes_t, arg: *mut c_void) -> i32; + +pub struct Client { + p: *mut jack_client_t +} + +unsafe impl Send for Client {} + +impl Client { + pub fn get_time() -> u64 { + unsafe { jack_get_time() } + } + + pub fn open(name: &str, options: JackOpenOptions) -> Result<Client, ()> { + let c_name = CString::new(name).ok().expect("client name must not contain null bytes"); + let result = unsafe { jack_client_open(c_name.as_ptr(), options.bits(), ptr::null_mut()) }; + if result.is_null() { + Err(()) + } else { + Ok(Client { p: result }) + } + } + + pub fn get_midi_ports(&self, flags: PortFlags) -> PortInfos { + let ports_ptr = unsafe { jack_get_ports(self.p, ptr::null_mut(), JACK_DEFAULT_MIDI_TYPE.as_ptr() as *const i8, flags.bits() as u64) }; + let slice = if ports_ptr.is_null() { + &[] + } else { + unsafe { + let count = (0isize..).find(|i| (*ports_ptr.offset(*i)).is_null()).unwrap() as usize; + slice::from_raw_parts(ports_ptr, count) + } + }; + PortInfos { p: slice } + } + + pub fn register_midi_port(&mut self, name: &str, flags: PortFlags) -> Result<MidiPort, ()> { + let c_name = CString::new(name).ok().expect("port name must not contain null bytes"); + let result = unsafe { jack_port_register(self.p, c_name.as_ptr(), JACK_DEFAULT_MIDI_TYPE.as_ptr() as *const i8, flags.bits() as u64, 0) }; + if result.is_null() { + Err(()) + } else { + Ok(MidiPort { p: result }) + } + } + + /// This can not be implemented in Drop, because it needs a reference + /// to the client. But it consumes the MidiPort. + pub fn unregister_midi_port(&mut self, client: MidiPort) { + unsafe { jack_port_unregister(self.p, client.p) }; + } + + pub fn activate(&mut self) { + unsafe { jack_activate(self.p) }; + } + + pub fn deactivate(&mut self) { + unsafe { jack_deactivate(self.p) }; + } + + /// The code in the supplied function must be suitable for real-time + /// execution. That means that it cannot call functions that might block + /// for a long time. This includes all I/O functions (disk, TTY, network), + /// malloc, free, printf, pthread_mutex_lock, sleep, wait, poll, select, + /// pthread_join, pthread_cond_wait, etc, etc. + pub fn set_process_callback(&mut self, callback: ProcessCallback, data: *mut c_void) { + unsafe { jack_set_process_callback(self.p, Some(callback), data) }; + } + + pub fn connect(&mut self, source_port: &CStr, destination_port: &CStr) -> Result<(), ()> { + let rc = unsafe { jack_connect(self.p, source_port.as_ptr(), destination_port.as_ptr()) }; + if rc == 0 { + Ok(()) + } else { + Err(()) // TODO: maybe handle EEXIST explicitly + } + } +} + +impl Drop for Client { + fn drop(&mut self) { + unsafe { jack_client_close(self.p) }; + } +} + +pub struct PortInfos<'a> { + p: &'a[*const i8], +} + +unsafe impl<'a> Send for PortInfos<'a> {} + +impl<'a> PortInfos<'a> { + pub fn count(&self) -> usize { + self.p.len() + } + + pub fn get_c_name(&self, index: usize) -> &CStr { + let ptr = self.p[index]; + unsafe { CStr::from_ptr(ptr) } + } +} + +impl<'a> Index<usize> for PortInfos<'a> { + type Output = str; + + fn index(&self, index: usize) -> &Self::Output { + let slice = self.get_c_name(index).to_bytes(); + str::from_utf8(slice).ok().expect("Error converting port name to UTF8") + } +} + +impl<'a> Drop for PortInfos<'a> { + fn drop(&mut self) { + if self.p.len() > 0 { + unsafe { jack_free(self.p.as_ptr() as *mut _) } + } + } +} + +pub struct MidiPort { + p: *mut jack_port_t +} + +unsafe impl Send for MidiPort {} + +impl MidiPort { + pub fn get_name(&self) -> &CStr { + unsafe { CStr::from_ptr(jack_port_name(self.p)) } + } + + pub fn get_midi_buffer(&self, nframes: jack_nframes_t) -> MidiBuffer { + let buf = unsafe { jack_port_get_buffer(self.p, nframes) }; + MidiBuffer { p: buf } + } +} + +pub struct MidiBuffer { + p: *mut c_void +} + +impl MidiBuffer { + pub fn get_event_count(&self) -> u32 { + unsafe { jack_midi_get_event_count(self.p) } + } + + pub unsafe fn get_event(&self, ev: *mut jack_midi_event_t, index: u32) { + jack_midi_event_get(ev, self.p, index); + } + + pub fn clear(&mut self) { + unsafe { jack_midi_clear_buffer(self.p) } + } + + pub fn event_reserve(&mut self, time: jack_nframes_t, data_size: usize) -> *mut jack_midi_data_t { + unsafe { jack_midi_event_reserve(self.p, time, data_size as size_t) } + } +} + +pub struct Ringbuffer { + p: *mut jack_ringbuffer_t +} + +unsafe impl Send for Ringbuffer {} + +impl Ringbuffer { + pub fn new(size: usize) -> Ringbuffer { + let result = unsafe { jack_ringbuffer_create(size as size_t) }; + Ringbuffer { p: result } + } + + pub fn get_read_space(&self) -> usize { + unsafe { jack_ringbuffer_read_space(self.p) as usize } + } + + pub fn read(&mut self, destination: *mut u8, count: usize) -> usize { + let bytes_read = unsafe { jack_ringbuffer_read(self.p, destination as *mut i8, count as size_t) }; + bytes_read as usize + } + + pub fn write(&mut self, source: &[u8]) -> usize { + unsafe { jack_ringbuffer_write(self.p, source.as_ptr() as *const i8, source.len() as size_t) as usize } + } +} + +impl Drop for Ringbuffer{ + fn drop(&mut self) { + unsafe { jack_ringbuffer_free(self.p) } + } +}
\ No newline at end of file |