Skip to content
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

ndk: Add AMidi interface #353

Open
wants to merge 56 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
2daf09c
ndk: Add AMidi interface
paxbun Oct 4, 2022
9456589
ndk: Remove needless explicit lifetimes in midi.rs
paxbun Oct 4, 2022
9cf8581
ndk: Fix mismatching doc comments in midi.rs
paxbun Oct 4, 2022
9f7c9ef
ndk: Remove get_ prefix from midi.rs
paxbun Oct 4, 2022
466bf4a
ndk: Fix doc comments of MidiOutputPort::receive
paxbun Oct 4, 2022
4013f68
ndk-sys: Add feature `midi`
paxbun Oct 5, 2022
ff6697c
ndk: Add feature `midi`
paxbun Oct 5, 2022
02630b0
ndk: Make Midi types implement Send
paxbun Oct 5, 2022
d3d616e
ndk: Update ndk/src/midi.rs
paxbun Oct 5, 2022
4f53506
ndk: Fix mismatching doc comments in midi.rs
paxbun Oct 5, 2022
15b917f
ndk: Fix link to ndk-samples with a permalink
paxbun Oct 5, 2022
5ae1cad
ndk: Replace try_from_primitives in midi.rs with try_from
paxbun Oct 5, 2022
06ebda5
ndk: Make MidiOutputPort::receive return timestamp only with MidiOpco…
paxbun Oct 5, 2022
2c92479
ndk: Add ptr exposing functions to wrappers in midi.rs
paxbun Oct 5, 2022
b2ec4ea
ndk: Raise error when 2 or more msgs received in MidiOutputPort
paxbun Oct 5, 2022
6f262cc
ndk: Remove as_ptr() from structs in midi.rs
paxbun Oct 5, 2022
01877bc
ndk: Add more strict opcode checks to MidiOutputPort
paxbun Oct 5, 2022
48b4416
ndk: Make msg for unexpected opcode in midi.rs more clear
paxbun Oct 5, 2022
aa26247
ndk: Fix vague doc comments in midi.rs
paxbun Oct 5, 2022
8235a5c
ndk: Add mod media_error
paxbun Oct 5, 2022
12df844
ndk: Remove pub use for MediaErrorResult from media/mod.rs
paxbun Oct 5, 2022
34357c7
ndk: Remove pub use from media/mod.rs
paxbun Oct 5, 2022
23b18b0
ndk: Reflect midi.rs changes to CHANGELOG.md
paxbun Oct 5, 2022
c79c8c5
fix: Make MidiDevice::from_java unsafe
paxbun Jun 15, 2023
895350b
Merge branch 'master' into feat/amidi
paxbun Jun 16, 2023
acce571
fix(ndk): Remove all occurrences of ffi::size_t from midi.rs
paxbun Jun 16, 2023
8900c1c
fix(ndk): Remove unnecessary casts from midi.rs
paxbun Jun 16, 2023
95220e7
ndk: Rework media error types
MarijnS95 Jun 15, 2023
85288eb
Merge branch 'ndk-rework-media-error' into feat/amidi
paxbun Jun 16, 2023
cc1c2e3
fix(ndk): Reflect changes in media_error to midi
paxbun Jun 16, 2023
200ab56
Merge branch 'master' into feat/amidi
paxbun Jun 23, 2023
408e460
Update comments in ndk/src/midi.rs
paxbun Aug 10, 2023
9a5744b
Update ndk/src/midi.rs
paxbun Aug 10, 2023
412b6ad
Update ndk/src/midi.rs
paxbun Aug 10, 2023
aa4467e
Update ndk/CHANGELOG.md
paxbun Aug 10, 2023
3cfaad4
fix: Remove impl Send for MidiDevice
paxbun Aug 10, 2023
2e036d2
Fix comments in ndk/src/media_error.rs
paxbun Aug 10, 2023
aa79a19
ndk: Make "midi" feature dependent on "media"
paxbun Aug 10, 2023
0341b4d
fix: Remove redundant Result mapping from midi.rs
paxbun Aug 10, 2023
c8b2b2e
ndk: Add MidiDeviceType::Unknown
paxbun Aug 10, 2023
6044fc2
ndk: Add doc-comments to MidiInputPort
paxbun Aug 16, 2023
f22054d
ndk: Add doc-comments to fields of MidiOpcode::Data
paxbun Aug 16, 2023
734b81b
ndk: Reference the Java Android MIDI docs in the doc-comments
paxbun Aug 16, 2023
7220a9a
ndk: Fix doc-comments of MidiOutputPort::receive
paxbun Aug 16, 2023
241b096
ndk: Drop Send for ndk::midi::Midi*Port
paxbun Aug 17, 2023
d9ef310
ndk: Describe about Java VM thread attachment in the safety section o…
paxbun Aug 17, 2023
31159dc
ndk: Add safe wrapper of midi
paxbun Aug 17, 2023
129edc2
ndk: Fix typos in ndk::midi::safe
paxbun Aug 17, 2023
1ea5b32
Merge branch 'master' into feat/amidi
paxbun Aug 17, 2023
83fe4b2
ndk: Fix clippy warnings in ndk::midi
paxbun Aug 17, 2023
8c64160
ndk: Make examples in ndk::midi compilable
paxbun Aug 17, 2023
5428ca7
ndk: Remove link to SafeMidiDeviceBox from the module-level doc-comme…
paxbun Aug 17, 2023
802ab4a
ndk: Add more detailed description about safety of AMidi functions to…
paxbun Aug 17, 2023
300118e
ndk: Add missing brackets to links to functions in doc-comments in nd…
paxbun Aug 17, 2023
8086a7d
ndk: Enable media_error when "midi" is enabled
paxbun Aug 17, 2023
5707555
Merge remote-tracking branch 'origin/master' into feat/amidi
paxbun Dec 14, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ndk-sys/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ test = []
audio = []
bitmap = []
media = []
midi = []

[package.metadata.docs.rs]
rustdoc-args = ["--cfg", "docsrs"]
Expand Down
4 changes: 4 additions & 0 deletions ndk-sys/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,7 @@ extern "C" {}
#[cfg(all(feature = "audio", target_os = "android"))]
#[link(name = "aaudio")]
extern "C" {}

#[cfg(all(feature = "midi", target_os = "android"))]
#[link(name = "amidi")]
paxbun marked this conversation as resolved.
Show resolved Hide resolved
extern "C" {}
paxbun marked this conversation as resolved.
Show resolved Hide resolved
1 change: 1 addition & 0 deletions ndk/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ all = ["audio", "bitmap","media", "api-level-30"]
audio = ["ffi/audio", "api-level-26"]
bitmap = ["ffi/bitmap"]
media = ["ffi/media"]
midi = ["ffi/midi", "api-level-29"]
MarijnS95 marked this conversation as resolved.
Show resolved Hide resolved

api-level-23 = []
api-level-24 = ["api-level-23"]
Expand Down
1 change: 1 addition & 0 deletions ndk/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ pub mod hardware_buffer_format;
pub mod input_queue;
pub mod looper;
pub mod media;
pub mod midi;
pub mod native_activity;
pub mod native_window;
pub mod surface_texture;
Expand Down
14 changes: 11 additions & 3 deletions ndk/src/media/mod.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,30 @@
//! Bindings for the NDK media classes.
//!
//! See also [the NDK docs](https://developer.android.com/ndk/reference/group/media)
#![cfg(feature = "media")]
#![cfg(any(feature = "media", feature = "midi"))]

mod error;
MarijnS95 marked this conversation as resolved.
Show resolved Hide resolved

#[cfg(feature = "media")]
pub mod image_reader;
#[cfg(feature = "media")]
pub mod media_codec;

pub use error::NdkMediaError;
use std::{mem::MaybeUninit, ptr::NonNull};
use std::mem::MaybeUninit;

#[cfg(feature = "media")]
use std::ptr::NonNull;

pub type Result<T, E = NdkMediaError> = std::result::Result<T, E>;

fn construct<T>(with_ptr: impl FnOnce(*mut T) -> ffi::media_status_t) -> Result<T> {
pub(crate) fn construct<T>(with_ptr: impl FnOnce(*mut T) -> ffi::media_status_t) -> Result<T> {
let mut result = MaybeUninit::uninit();
let status = with_ptr(result.as_mut_ptr());
NdkMediaError::from_status(status).map(|()| unsafe { result.assume_init() })
}

#[cfg(feature = "media")]
fn construct_never_null<T>(
with_ptr: impl FnOnce(*mut *mut T) -> ffi::media_status_t,
) -> Result<NonNull<T>> {
Expand All @@ -35,6 +42,7 @@ fn construct_never_null<T>(
///
/// As such this function always asserts on `null` values,
/// even when `cfg!(debug_assertions)` is disabled.
#[cfg(feature = "media")]
fn get_unlikely_to_be_null<T>(get_ptr: impl FnOnce() -> *mut T) -> NonNull<T> {
let result = get_ptr();
NonNull::new(result).expect("result should never be null")
Expand Down
286 changes: 286 additions & 0 deletions ndk/src/midi.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,286 @@
//! 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::fmt;
use std::marker::PhantomData;
use std::os::raw::{c_int, c_uint};
use std::ptr::NonNull;

/// Result of [`MidiOutputPort::receive`].
paxbun marked this conversation as resolved.
Show resolved Hide resolved
#[derive(Copy, Clone, Debug)]
#[repr(u32)]
pub enum MidiOpcode {
/// No MIDI messages are available.
NoMessage,
/// Received a MIDI message with the given length.
Data(usize),
/// Instructed to discard all pending MIDI data.
Flush,
}

#[derive(Copy, Clone, Debug, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
#[repr(u32)]
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 {
inner: NonNull<ffi::AMidiDevice>,
}

impl MidiDevice {
/// Creates an `MidiDevice` from a pointer.
///
/// # Safety
/// By calling this function, you assert that the pointer is a valid pointer to a native
/// `AMidiDevice`.
pub unsafe fn from_ptr(inner: NonNull<ffi::AMidiDevice>) -> Self {
Self { inner }
}

fn as_ptr(&self) -> *mut ffi::AMidiDevice {
self.inner.as_ptr()
}

/// Connects a native Midi Device object to the associated Java MidiDevice object.
///
/// Use this AMidiDevice to access the rest of the native MIDI API.
MarijnS95 marked this conversation as resolved.
Show resolved Hide resolved
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_primitive(device_type as u32).map_err(|e| {
paxbun marked this conversation as resolved.
Show resolved Hide resolved
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> {
inner: 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.inner)
.finish()
}
}

impl<'a> MidiInputPort<'a> {
/// Creates an `MidiInputPort` from a pointer.
///
/// # Safety
/// By calling this function, you assert that the pointer is a valid pointer to a native
/// `AMidiInputPort`.
pub unsafe fn from_ptr(inner: NonNull<ffi::AMidiInputPort>) -> Self {
Self {
inner,
_marker: PhantomData,
}
}

fn as_ptr(&self) -> *mut ffi::AMidiInputPort {
self.inner.as_ptr()
}

/// Sends data to the specified input port.
MarijnS95 marked this conversation as resolved.
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> {
inner: 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.inner)
.finish()
}
}

impl<'a> MidiOutputPort<'a> {
/// Creates an `MidiOutputPort` from a pointer.
///
/// # Safety
/// By calling this function, you assert that the pointer is a valid pointer to a native
/// `AMidiOutputPort`.
pub unsafe fn from_ptr(inner: NonNull<ffi::AMidiOutputPort>) -> Self {
Self {
inner,
_marker: PhantomData,
}
}

fn as_ptr(&self) -> *mut ffi::AMidiOutputPort {
self.inner.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.
Show resolved Hide resolved
pub fn receive(&self, buffer: &mut [u8]) -> Result<(MidiOpcode, i64)> {
let mut opcode = 0i32;
let mut timestamp = 0i64;
let mut num_bytes_received: ffi::size_t = 0;
let result = 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.
Show resolved Hide resolved
&mut timestamp,
)
};

if result < 0 {
Err(NdkMediaError::from_status(ffi::media_status_t(result as c_int)).unwrap_err())
} else if result == 0 {
MarijnS95 marked this conversation as resolved.
Show resolved Hide resolved
Ok((MidiOpcode::NoMessage, timestamp))
} else if opcode as c_uint == ffi::AMIDI_OPCODE_DATA {
Ok((MidiOpcode::Data(num_bytes_received as usize), timestamp))
} else {
Ok((MidiOpcode::Flush, timestamp))
}
}
}

impl<'a> Drop for MidiOutputPort<'a> {
fn drop(&mut self) {
unsafe { ffi::AMidiOutputPort_close(self.as_ptr()) };
}
}