use block::RcBlock;
use core_foundation::{
base::{OSStatus, TCFType},
string::CFString,
};
use std::cell::RefCell;
use std::{mem::MaybeUninit, ops::Deref, os::raw::c_void, ptr};
use coremidi_sys::{
MIDIClientCreate, MIDIClientCreateWithBlock, MIDIDestinationCreateWithBlock,
MIDIDestinationCreateWithProtocol, MIDIEventList, MIDIInputPortCreateWithBlock,
MIDIInputPortCreateWithProtocol, MIDINotification, MIDINotifyBlock, MIDIOutputPortCreate,
MIDIPacketList, MIDIReadBlock, MIDIReceiveBlock, MIDISourceCreate,
};
use crate::ports::InputPortWithContext;
use crate::{
endpoints::{destinations::VirtualDestination, sources::VirtualSource},
notifications::Notification,
object::Object,
packets::PacketList,
ports::{InputPort, OutputPort},
result_from_status, EventList, Protocol,
};
type CallbackByRef = Box<dyn FnMut(&Notification) + Send + 'static>;
type CallbackByOwn = Box<dyn FnMut(Notification) + Send + 'static>;
pub enum NotifyCallback {
ByReference(RefCell<CallbackByRef>),
ByOwnership(RefCell<CallbackByOwn>),
}
impl NotifyCallback {
pub fn by_reference<F>(callback: F) -> Self
where
F: FnMut(&Notification) + Send + 'static,
{
Self::ByReference(RefCell::new(Box::new(callback)))
}
pub fn by_ownership<F>(callback: F) -> Self
where
F: FnMut(Notification) + Send + 'static,
{
Self::ByOwnership(RefCell::new(Box::new(callback)))
}
}
impl<F> From<F> for NotifyCallback
where
F: FnMut(&Notification) + Send + 'static,
{
fn from(callback: F) -> Self {
Self::by_reference(callback)
}
}
#[derive(Debug)]
pub struct Client {
object: Object,
}
impl Client {
pub fn new_with_notifications<F>(name: &str, callback: F) -> Result<Client, OSStatus>
where
F: Into<NotifyCallback>,
{
let client_name = CFString::new(name);
let mut client_ref = MaybeUninit::uninit();
let notify_block = Self::notify_block(callback.into());
let status = unsafe {
MIDIClientCreateWithBlock(
client_name.as_concrete_TypeRef(),
client_ref.as_mut_ptr(),
notify_block.deref() as *const _ as MIDINotifyBlock,
)
};
result_from_status(status, || {
let client_ref = unsafe { client_ref.assume_init() };
Client {
object: Object(client_ref),
}
})
}
pub fn new(name: &str) -> Result<Client, OSStatus> {
let client_name = CFString::new(name);
let mut client_ref = MaybeUninit::uninit();
let status = unsafe {
MIDIClientCreate(
client_name.as_concrete_TypeRef(),
None,
ptr::null_mut(),
client_ref.as_mut_ptr(),
)
};
result_from_status(status, || {
let client_ref = unsafe { client_ref.assume_init() };
Client {
object: Object(client_ref),
}
})
}
pub fn output_port(&self, name: &str) -> Result<OutputPort, OSStatus> {
let port_name = CFString::new(name);
let mut port_ref = MaybeUninit::uninit();
let status = unsafe {
MIDIOutputPortCreate(
self.object.0,
port_name.as_concrete_TypeRef(),
port_ref.as_mut_ptr(),
)
};
result_from_status(status, || {
let port_ref = unsafe { port_ref.assume_init() };
OutputPort::new(port_ref)
})
}
pub fn input_port<F>(&self, name: &str, callback: F) -> Result<InputPort, OSStatus>
where
F: FnMut(&PacketList) + Send + 'static,
{
let port_name = CFString::new(name);
let mut port_ref = MaybeUninit::uninit();
let read_block = Self::read_block(callback);
let status = unsafe {
MIDIInputPortCreateWithBlock(
self.object.0,
port_name.as_concrete_TypeRef(),
port_ref.as_mut_ptr(),
read_block.deref() as *const _ as MIDIReadBlock,
)
};
result_from_status(status, || {
let port_ref = unsafe { port_ref.assume_init() };
InputPort::new(port_ref)
})
}
pub fn input_port_with_protocol<T, F>(
&self,
name: &str,
protocol: Protocol,
callback: F,
) -> Result<InputPortWithContext<T>, OSStatus>
where
F: FnMut(&EventList, &mut T) + Send + 'static,
{
let port_name = CFString::new(name);
let mut port_ref = MaybeUninit::uninit();
let receive_block = Self::receive_block::<T, _>(callback);
let status = unsafe {
MIDIInputPortCreateWithProtocol(
self.object.0,
port_name.as_concrete_TypeRef(),
protocol.into(),
port_ref.as_mut_ptr(),
receive_block.deref() as *const _ as MIDIReceiveBlock,
)
};
result_from_status(status, || {
let port_ref = unsafe { port_ref.assume_init() };
InputPortWithContext::<T>::new(port_ref)
})
}
pub fn virtual_source(&self, name: &str) -> Result<VirtualSource, OSStatus> {
let virtual_source_name = CFString::new(name);
let mut virtual_source = MaybeUninit::uninit();
let status = unsafe {
MIDISourceCreate(
self.object.0,
virtual_source_name.as_concrete_TypeRef(),
virtual_source.as_mut_ptr(),
)
};
result_from_status(status, || {
let endpoint_ref = unsafe { virtual_source.assume_init() };
VirtualSource::new(endpoint_ref)
})
}
pub fn virtual_destination<F>(
&self,
name: &str,
callback: F,
) -> Result<VirtualDestination, OSStatus>
where
F: FnMut(&PacketList) + Send + 'static,
{
let virtual_destination_name = CFString::new(name);
let mut virtual_destination = MaybeUninit::uninit();
let read_block = Self::read_block(callback);
let status = unsafe {
MIDIDestinationCreateWithBlock(
self.object.0,
virtual_destination_name.as_concrete_TypeRef(),
virtual_destination.as_mut_ptr(),
read_block.deref() as *const _ as MIDIReadBlock,
)
};
result_from_status(status, || {
let endpoint_ref = unsafe { virtual_destination.assume_init() };
VirtualDestination::new(endpoint_ref)
})
}
pub fn virtual_destination_with_protocol<F>(
&self,
name: &str,
protocol: Protocol,
mut callback: F,
) -> Result<VirtualDestination, OSStatus>
where
F: FnMut(&EventList) + Send + 'static,
{
let virtual_destination_name = CFString::new(name);
let mut virtual_destination = MaybeUninit::uninit();
let receive_block =
Self::receive_block::<(), _>(move |event_list, _| (callback)(event_list));
let status = unsafe {
MIDIDestinationCreateWithProtocol(
self.object.0,
virtual_destination_name.as_concrete_TypeRef(),
protocol.into(),
virtual_destination.as_mut_ptr(),
receive_block.deref() as *const _ as MIDIReceiveBlock,
)
};
result_from_status(status, || {
let endpoint_ref = unsafe { virtual_destination.assume_init() };
VirtualDestination::new(endpoint_ref)
})
}
fn notify_block(callback: NotifyCallback) -> RcBlock<(*const MIDINotification,), ()> {
let notify_block = block::ConcreteBlock::new(move |message: *const MIDINotification| {
let message = unsafe { &*message };
if let Ok(notification) = Notification::try_from(message) {
match &callback {
NotifyCallback::ByReference(f) => (f.borrow_mut())(¬ification),
NotifyCallback::ByOwnership(f) => (f.borrow_mut())(notification),
}
}
});
notify_block.copy()
}
fn read_block<F>(callback: F) -> RcBlock<(*const MIDIPacketList, *mut c_void), ()>
where
F: FnMut(&PacketList) + Send + 'static,
{
let callback = RefCell::new(callback);
let read_block = block::ConcreteBlock::new(
move |pktlist: *const MIDIPacketList, _src_conn_ref_con: *mut c_void| {
let packet_list = unsafe { &*(pktlist as *const PacketList) };
(callback.borrow_mut())(packet_list);
},
);
read_block.copy()
}
fn receive_block<T, F>(callback: F) -> RcBlock<(*const MIDIEventList, *mut c_void), ()>
where
F: FnMut(&EventList, &mut T) + Send + 'static,
{
let callback = RefCell::new(callback);
let receive_block = block::ConcreteBlock::new(
move |evtlist: *const MIDIEventList, src_conn_ref_con: *mut c_void| {
let event_list = unsafe { &*(evtlist as *const EventList) };
let context = unsafe { &mut *(src_conn_ref_con as *mut T) };
(callback.borrow_mut())(event_list, context);
},
);
receive_block.copy()
}
}
impl Deref for Client {
type Target = Object;
fn deref(&self) -> &Object {
&self.object
}
}