-
Notifications
You must be signed in to change notification settings - Fork 117
ndk: Add AMidi interface #353
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 15 commits
2daf09c
9456589
9cf8581
9f7c9ef
466bf4a
4013f68
ff6697c
02630b0
d3d616e
4f53506
15b917f
5ae1cad
06ebda5
2c92479
b2ec4ea
6f262cc
01877bc
48b4416
aa26247
8235a5c
12df844
34357c7
23b18b0
c79c8c5
895350b
acce571
8900c1c
95220e7
85288eb
cc1c2e3
200ab56
408e460
9a5744b
412b6ad
aa4467e
3cfaad4
2e036d2
aa79a19
0341b4d
c8b2b2e
6044fc2
f22054d
734b81b
7220a9a
241b096
d9ef310
31159dc
129edc2
1ea5b32
83fe4b2
8c64160
5428ca7
802ab4a
300118e
8086a7d
5707555
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,305 @@ | ||
//! Bindings for [`AMidiDevice`], [`AMidiInputPort`], and [`AMidiOutputPort`] | ||
//! | ||
//! See [the NDK guide](https://developer.android.com/ndk/guides/audio/midi) for | ||
//! design and usage instructions, and [the NDK reference](https://developer.android.com/ndk/reference/group/midi) | ||
//! for an API overview. | ||
//! | ||
//! [`AMidiDevice`]: https://developer.android.com/ndk/reference/group/midi#amididevice | ||
//! [`AMidiInputPort`]: https://developer.android.com/ndk/reference/group/midi#amidiinputport | ||
//! [`AMidiOutputPort`]: https://developer.android.com/ndk/reference/group/midi#amidioutputport | ||
#![cfg(feature = "midi")] | ||
|
||
pub use super::media::Result; | ||
use super::media::{construct, NdkMediaError}; | ||
|
||
use num_enum::{IntoPrimitive, TryFromPrimitive}; | ||
use std::convert::TryFrom; | ||
use std::fmt; | ||
use std::marker::PhantomData; | ||
use std::os::raw::{c_int, c_uint}; | ||
use std::ptr::NonNull; | ||
|
||
// There is no mention about thread-safety in the NDK reference, but the official Android C++ MIDI | ||
// sample stores `AMidiDevice *` and `AMidi{Input,Output}Port *` in global variables and accesses the | ||
// ports from separate threads. | ||
// See https://github.com/android/ndk-samples/blob/7f6936ea044ee29c36b5c3ebd62bb3a64e1e6014/native-midi/app/src/main/cpp/AppMidiManager.cpp | ||
unsafe impl Send for MidiDevice {} | ||
paxbun marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
unsafe impl<'a> Send for MidiInputPort<'a> {} | ||
unsafe impl<'a> Send for MidiOutputPort<'a> {} | ||
|
||
|
||
/// Result of [`MidiOutputPort::receive`]. | ||
paxbun marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
#[derive(Copy, Clone, Debug)] | ||
pub enum MidiOpcode { | ||
/// No MIDI messages are available. | ||
NoMessage, | ||
/// Received a MIDI message with the given length and the timestamp. | ||
Data { length: usize, timestamp: i64 }, | ||
/// Instructed to discard all pending MIDI data. | ||
Flush, | ||
} | ||
|
||
#[repr(u32)] | ||
#[derive(Copy, Clone, Debug, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] | ||
pub enum MidiDeviceType { | ||
Bluetooth = ffi::AMIDI_DEVICE_TYPE_BLUETOOTH, | ||
USB = ffi::AMIDI_DEVICE_TYPE_USB, | ||
Virtual = ffi::AMIDI_DEVICE_TYPE_VIRTUAL, | ||
} | ||
|
||
#[derive(Debug)] | ||
pub struct MidiDevice { | ||
ptr: NonNull<ffi::AMidiDevice>, | ||
} | ||
|
||
impl MidiDevice { | ||
/// Assumes ownership of `ptr` | ||
/// | ||
/// # Safety | ||
/// `ptr` must be a valid pointer to an Android [`ffi::AMidiDevice`]. | ||
pub unsafe fn from_ptr(ptr: NonNull<ffi::AMidiDevice>) -> Self { | ||
Self { ptr } | ||
} | ||
|
||
pub fn ptr(&self) -> NonNull<ffi::AMidiDevice> { | ||
self.ptr | ||
} | ||
|
||
fn as_ptr(&self) -> *mut ffi::AMidiDevice { | ||
self.ptr.as_ptr() | ||
} | ||
|
||
/// Connects a native Midi Device object to the associated Java MidiDevice object. | ||
/// | ||
/// Use the returned [`MidiDevice`] to access the rest of the native MIDI API. | ||
pub fn from_java( | ||
env: *mut jni_sys::JNIEnv, | ||
midi_device_obj: jni_sys::jobject, | ||
) -> Result<MidiDevice> { | ||
unsafe { | ||
let ptr = construct(|res| ffi::AMidiDevice_fromJava(env, midi_device_obj, res))?; | ||
Ok(Self::from_ptr(NonNull::new_unchecked(ptr))) | ||
} | ||
} | ||
|
||
/// Gets the number of input (sending) ports available on the specified MIDI device. | ||
pub fn num_input_ports(&self) -> Result<usize> { | ||
let num_input_ports = unsafe { ffi::AMidiDevice_getNumInputPorts(self.as_ptr()) }; | ||
if num_input_ports >= 0 { | ||
Ok(num_input_ports as usize) | ||
} else { | ||
NdkMediaError::from_status(ffi::media_status_t(num_input_ports as c_int)).map(|_| 0) | ||
} | ||
} | ||
|
||
/// Gets the number of output (receiving) ports available on the specified MIDI device. | ||
pub fn num_output_ports(&self) -> Result<usize> { | ||
let num_output_ports = unsafe { ffi::AMidiDevice_getNumOutputPorts(self.as_ptr()) }; | ||
if num_output_ports >= 0 { | ||
Ok(num_output_ports as usize) | ||
} else { | ||
Err( | ||
NdkMediaError::from_status(ffi::media_status_t(num_output_ports as c_int)) | ||
.unwrap_err(), | ||
) | ||
} | ||
} | ||
|
||
/// Gets the MIDI device type. | ||
pub fn device_type(&self) -> Result<MidiDeviceType> { | ||
let device_type = unsafe { ffi::AMidiDevice_getType(self.as_ptr()) }; | ||
if device_type >= 0 { | ||
let device_type = MidiDeviceType::try_from(device_type as u32).map_err(|e| { | ||
NdkMediaError::UnknownResult(ffi::media_status_t(e.number as c_int)) | ||
})?; | ||
Ok(device_type) | ||
} else { | ||
Err(NdkMediaError::from_status(ffi::media_status_t(device_type)).unwrap_err()) | ||
} | ||
} | ||
|
||
/// Opens the input port so that the client can send data to it. | ||
pub fn open_input_port(&self, port_number: i32) -> Result<MidiInputPort> { | ||
unsafe { | ||
let input_port = | ||
construct(|res| ffi::AMidiInputPort_open(self.as_ptr(), port_number, res))?; | ||
Ok(MidiInputPort::from_ptr(NonNull::new_unchecked(input_port))) | ||
} | ||
} | ||
|
||
/// Opens the output port so that the client can receive data from it. | ||
pub fn open_output_port(&self, port_number: i32) -> Result<MidiOutputPort> { | ||
unsafe { | ||
let output_port = | ||
construct(|res| ffi::AMidiOutputPort_open(self.as_ptr(), port_number, res))?; | ||
Ok(MidiOutputPort::from_ptr(NonNull::new_unchecked( | ||
output_port, | ||
))) | ||
} | ||
} | ||
} | ||
|
||
impl Drop for MidiDevice { | ||
fn drop(&mut self) { | ||
let status = unsafe { ffi::AMidiDevice_release(self.as_ptr()) }; | ||
NdkMediaError::from_status(status).unwrap(); | ||
} | ||
} | ||
|
||
pub struct MidiInputPort<'a> { | ||
ptr: NonNull<ffi::AMidiInputPort>, | ||
_marker: PhantomData<&'a MidiDevice>, | ||
} | ||
|
||
impl<'a> fmt::Debug for MidiInputPort<'a> { | ||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
f.debug_struct("MidiInputPort") | ||
.field("inner", &self.ptr) | ||
.finish() | ||
} | ||
} | ||
|
||
impl<'a> MidiInputPort<'a> { | ||
/// Assumes ownership of `ptr` | ||
/// | ||
/// # Safety | ||
/// `ptr` must be a valid pointer to an Android [`ffi::AMidiInputPort`]. | ||
pub unsafe fn from_ptr(ptr: NonNull<ffi::AMidiInputPort>) -> Self { | ||
Self { | ||
ptr, | ||
_marker: PhantomData, | ||
} | ||
} | ||
|
||
pub fn ptr(&self) -> NonNull<ffi::AMidiInputPort> { | ||
self.ptr | ||
} | ||
|
||
fn as_ptr(&self) -> *mut ffi::AMidiInputPort { | ||
self.ptr.as_ptr() | ||
} | ||
|
||
/// Sends data to the specified input port. | ||
MarijnS95 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
pub fn send(&self, buffer: &[u8]) -> Result<usize> { | ||
let num_bytes_sent = unsafe { | ||
ffi::AMidiInputPort_send(self.as_ptr(), buffer.as_ptr(), buffer.len() as ffi::size_t) | ||
}; | ||
if num_bytes_sent >= 0 { | ||
Ok(num_bytes_sent as usize) | ||
} else { | ||
Err( | ||
NdkMediaError::from_status(ffi::media_status_t(num_bytes_sent as c_int)) | ||
.unwrap_err(), | ||
) | ||
} | ||
} | ||
|
||
/// Sends a message with a 'MIDI flush command code' to the specified port. | ||
/// | ||
/// This should cause a receiver to discard any pending MIDI data it may have accumulated and | ||
/// not processed. | ||
pub fn send_flush(&self) -> Result<()> { | ||
let result = unsafe { ffi::AMidiInputPort_sendFlush(self.as_ptr()) }; | ||
NdkMediaError::from_status(result) | ||
} | ||
|
||
pub fn send_with_timestamp(&self, buffer: &[u8], timestamp: i64) -> Result<usize> { | ||
MarijnS95 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
let num_bytes_sent = unsafe { | ||
ffi::AMidiInputPort_sendWithTimestamp( | ||
self.as_ptr(), | ||
buffer.as_ptr(), | ||
buffer.len() as ffi::size_t, | ||
timestamp, | ||
) | ||
}; | ||
if num_bytes_sent >= 0 { | ||
Ok(num_bytes_sent as usize) | ||
} else { | ||
Err( | ||
NdkMediaError::from_status(ffi::media_status_t(num_bytes_sent as c_int)) | ||
.unwrap_err(), | ||
) | ||
} | ||
} | ||
} | ||
|
||
impl<'a> Drop for MidiInputPort<'a> { | ||
fn drop(&mut self) { | ||
unsafe { ffi::AMidiInputPort_close(self.as_ptr()) }; | ||
} | ||
} | ||
|
||
pub struct MidiOutputPort<'a> { | ||
ptr: NonNull<ffi::AMidiOutputPort>, | ||
_marker: PhantomData<&'a MidiDevice>, | ||
} | ||
|
||
impl<'a> fmt::Debug for MidiOutputPort<'a> { | ||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
f.debug_struct("MidiOutputPort") | ||
.field("inner", &self.ptr) | ||
.finish() | ||
} | ||
} | ||
|
||
impl<'a> MidiOutputPort<'a> { | ||
/// Assumes ownership of `ptr` | ||
/// | ||
/// # Safety | ||
/// `ptr` must be a valid pointer to an Android [`ffi::AMidiOutputPort`]. | ||
pub unsafe fn from_ptr(ptr: NonNull<ffi::AMidiOutputPort>) -> Self { | ||
Self { | ||
ptr, | ||
_marker: PhantomData, | ||
} | ||
} | ||
|
||
pub fn ptr(&self) -> NonNull<ffi::AMidiOutputPort> { | ||
self.ptr | ||
} | ||
|
||
fn as_ptr(&self) -> *mut ffi::AMidiOutputPort { | ||
self.ptr.as_ptr() | ||
} | ||
|
||
/// Receives the next pending MIDI message. | ||
/// | ||
/// To retrieve all pending messages, the client should repeatedly call this method until it | ||
/// returns [`Ok(MidiOpcode::NoMessage)`]. | ||
/// | ||
/// Note that this is a non-blocking call. If there are no Midi messages are available, the | ||
/// function returns [`Ok(MidiOpcode::NoMessage)`] immediately (for 0 messages received). | ||
MarijnS95 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
pub fn receive(&self, buffer: &mut [u8]) -> Result<MidiOpcode> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe add an extra paragraph explaining that the Alternatively I've been toying with Unfortunately all slice helpers around I'll leave this choice up to you, depending on convenience. The user quite likely already has something along the form of (i.e. it is quite hard to ignore/overlook match receive(&mut buffer)? {
NoMessage => break,
Data { length, timestamp: _ } => {
let useful = &buffer[..length as usize];
// ...
}
Flush => ...,
} |
||
let mut opcode = 0i32; | ||
let mut timestamp = 0i64; | ||
let mut num_bytes_received: ffi::size_t = 0; | ||
let num_messages_received = unsafe { | ||
ffi::AMidiOutputPort_receive( | ||
self.as_ptr(), | ||
&mut opcode, | ||
buffer.as_mut_ptr(), | ||
buffer.len() as ffi::size_t, | ||
&mut num_bytes_received, | ||
MarijnS95 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
&mut timestamp, | ||
) | ||
}; | ||
|
||
match num_messages_received { | ||
r if r < 0 => { | ||
Err(NdkMediaError::from_status(ffi::media_status_t(r as c_int)).unwrap_err()) | ||
} | ||
0 => Ok(MidiOpcode::NoMessage), | ||
1 if opcode as c_uint == ffi::AMIDI_OPCODE_DATA => Ok(MidiOpcode::Data { | ||
length: num_bytes_received as usize, | ||
timestamp, | ||
}), | ||
1 => Ok(MidiOpcode::Flush), | ||
MarijnS95 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
r => unreachable!("Result is positive integer {}", r), | ||
} | ||
} | ||
} | ||
|
||
impl<'a> Drop for MidiOutputPort<'a> { | ||
fn drop(&mut self) { | ||
unsafe { ffi::AMidiOutputPort_close(self.as_ptr()) }; | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.