Source code
Revision control
Copy as Markdown
Other Tools
use std::sync::{Arc, Mutex};
use crate::errors::*;
use crate::{Ignore, MidiMessage};
use coremidi::*;
mod external {
#[link(name = "CoreAudio", kind = "framework")]
extern "C" {
pub fn AudioConvertHostTimeToNanos(inHostTime: u64) -> u64;
pub fn AudioGetCurrentHostTime() -> u64;
}
}
pub struct MidiInput {
client: Client,
ignore_flags: Ignore,
}
#[derive(Clone)]
pub struct MidiInputPort {
source: Arc<Source>,
}
impl MidiInputPort {
pub fn id(&self) -> String {
self.source
.unique_id()
// According to macos docs "The system assigns unique IDs to all objects.", so I think we can ignore this case
.unwrap_or(0)
.to_string()
}
}
impl PartialEq for MidiInputPort {
fn eq(&self, other: &Self) -> bool {
if let (Some(id1), Some(id2)) = (self.source.unique_id(), other.source.unique_id()) {
id1 == id2
} else {
// According to macos docs "The system assigns unique IDs to all objects.", so I think we can ignore this case
false
}
}
}
impl MidiInput {
pub fn new(client_name: &str) -> Result<Self, InitError> {
match Client::new(client_name) {
Ok(cl) => Ok(MidiInput {
client: cl,
ignore_flags: Ignore::None,
}),
Err(_) => Err(InitError),
}
}
pub(crate) fn ports_internal(&self) -> Vec<crate::common::MidiInputPort> {
Sources
.into_iter()
.map(|s| crate::common::MidiInputPort {
imp: MidiInputPort {
source: Arc::new(s),
},
})
.collect()
}
pub fn ignore(&mut self, flags: Ignore) {
self.ignore_flags = flags;
}
pub fn port_count(&self) -> usize {
Sources::count()
}
pub fn port_name(&self, port: &MidiInputPort) -> Result<String, PortInfoError> {
match port.source.display_name() {
Some(name) => Ok(name),
None => Err(PortInfoError::CannotRetrievePortName),
}
}
fn handle_input<T>(packets: &PacketList, handler_data: &mut HandlerData<T>) {
let continue_sysex = &mut handler_data.continue_sysex;
let ignore = handler_data.ignore_flags;
let message = &mut handler_data.message;
let data = &mut handler_data.user_data.as_mut().unwrap();
for p in packets.iter() {
let pdata = p.data();
if pdata.len() == 0 {
continue;
}
let mut timestamp = p.timestamp();
if timestamp == 0 {
// this might happen for asnychronous sysex messages (?)
timestamp = unsafe { external::AudioGetCurrentHostTime() };
}
if !*continue_sysex {
message.timestamp =
unsafe { external::AudioConvertHostTimeToNanos(timestamp) } as u64 / 1000;
}
let mut cur_byte = 0;
if *continue_sysex {
// We have a continuing, segmented sysex message.
if !ignore.contains(Ignore::Sysex) {
// If we're not ignoring sysex messages, copy the entire packet.
message.bytes.extend_from_slice(pdata);
}
*continue_sysex = pdata[pdata.len() - 1] != 0xF7;
if !ignore.contains(Ignore::Sysex) && !*continue_sysex {
// If we reached the end of the sysex, invoke the user callback
(handler_data.callback)(message.timestamp, &message.bytes, data);
message.bytes.clear();
}
} else {
while cur_byte < pdata.len() {
// We are expecting that the next byte in the packet is a status byte.
let status = pdata[cur_byte];
if status & 0x80 == 0 {
break;
}
// Determine the number of bytes in the MIDI message.
let size;
if status < 0xC0 {
size = 3;
} else if status < 0xE0 {
size = 2;
} else if status < 0xF0 {
size = 3;
} else if status == 0xF0 {
// A MIDI sysex
if ignore.contains(Ignore::Sysex) {
size = 0;
cur_byte = pdata.len();
} else {
size = pdata.len() - cur_byte;
}
*continue_sysex = pdata[pdata.len() - 1] != 0xF7;
} else if status == 0xF1 {
// A MIDI time code message
if ignore.contains(Ignore::Time) {
size = 0;
cur_byte += 2;
} else {
size = 2;
}
} else if status == 0xF2 {
size = 3;
} else if status == 0xF3 {
size = 2;
} else if status == 0xF8 && ignore.contains(Ignore::Time) {
// A MIDI timing tick message and we're ignoring it.
size = 0;
cur_byte += 1;
} else if status == 0xFE && ignore.contains(Ignore::ActiveSense) {
// A MIDI active sensing message and we're ignoring it.
size = 0;
cur_byte += 1;
} else {
size = 1;
}
// Copy the MIDI data to our vector.
if size > 0 {
let message_bytes = &pdata[cur_byte..(cur_byte + size)];
if !*continue_sysex {
// This is either a non-sysex message or a non-segmented sysex message
(handler_data.callback)(message.timestamp, message_bytes, data);
message.bytes.clear();
} else {
// This is the beginning of a segmented sysex message
message.bytes.extend_from_slice(message_bytes);
}
cur_byte += size;
}
}
}
}
}
pub fn connect<F, T: Send + 'static>(
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 handler_data = Arc::new(Mutex::new(HandlerData {
message: MidiMessage::new(),
ignore_flags: self.ignore_flags,
continue_sysex: false,
callback: Box::new(callback),
user_data: Some(data),
}));
let handler_data2 = handler_data.clone();
let iport = match self.client.input_port(port_name, move |packets| {
MidiInput::handle_input(packets, &mut *handler_data2.lock().unwrap())
}) {
Ok(p) => p,
Err(_) => return Err(ConnectError::other("error creating MIDI input port", self)),
};
if let Err(_) = iport.connect_source(&port.source) {
return Err(ConnectError::other(
"error connecting MIDI input port",
self,
));
}
Ok(MidiInputConnection {
client: self.client,
details: InputConnectionDetails::Explicit(iport),
handler_data: handler_data,
})
}
pub fn create_virtual<F, T: Send + 'static>(
self,
port_name: &str,
callback: F,
data: T,
) -> Result<MidiInputConnection<T>, ConnectError<MidiInput>>
where
F: FnMut(u64, &[u8], &mut T) + Send + 'static,
{
let handler_data = Arc::new(Mutex::new(HandlerData {
message: MidiMessage::new(),
ignore_flags: self.ignore_flags,
continue_sysex: false,
callback: Box::new(callback),
user_data: Some(data),
}));
let handler_data2 = handler_data.clone();
let vrt = match self.client.virtual_destination(port_name, move |packets| {
MidiInput::handle_input(packets, &mut *handler_data2.lock().unwrap())
}) {
Ok(p) => p,
Err(_) => return Err(ConnectError::other("error creating MIDI input port", self)),
};
Ok(MidiInputConnection {
client: self.client,
details: InputConnectionDetails::Virtual(vrt),
handler_data: handler_data,
})
}
}
enum InputConnectionDetails {
Explicit(InputPort),
Virtual(VirtualDestination),
}
pub struct MidiInputConnection<T> {
client: Client,
#[allow(dead_code)]
details: InputConnectionDetails,
// TODO: get rid of Arc & Mutex?
// synchronization is required because the borrow checker does not
// know that the callback we're in here is never called concurrently
// (always in sequence)
handler_data: Arc<Mutex<HandlerData<T>>>,
}
impl<T> MidiInputConnection<T> {
pub fn close(self) -> (MidiInput, T) {
let mut handler_data_locked = self.handler_data.lock().unwrap();
(
MidiInput {
client: self.client,
ignore_flags: handler_data_locked.ignore_flags,
},
handler_data_locked.user_data.take().unwrap(),
)
}
}
/// This is all the data that is stored on the heap as long as a connection
/// is opened and passed to the callback handler.
///
/// It is important that `user_data` is the last field to not influence
/// offsets after monomorphization.
struct HandlerData<T> {
message: MidiMessage,
ignore_flags: Ignore,
continue_sysex: bool,
callback: Box<dyn FnMut(u64, &[u8], &mut T) + Send>,
user_data: Option<T>,
}
pub struct MidiOutput {
client: Client,
}
#[derive(Clone)]
pub struct MidiOutputPort {
dest: Arc<Destination>,
}
impl MidiOutputPort {
pub fn id(&self) -> String {
self.dest
.unique_id()
// According to macos docs "The system assigns unique IDs to all objects.", so I think we can ignore this case
.unwrap_or(0)
.to_string()
}
}
impl PartialEq for MidiOutputPort {
fn eq(&self, other: &Self) -> bool {
if let (Some(id1), Some(id2)) = (self.dest.unique_id(), other.dest.unique_id()) {
id1 == id2
} else {
// Acording to macos docs "The system assigns unique IDs to all objects.", so I think we can ignore this case
false
}
}
}
impl MidiOutput {
pub fn new(client_name: &str) -> Result<Self, InitError> {
match Client::new(client_name) {
Ok(cl) => Ok(MidiOutput { client: cl }),
Err(_) => Err(InitError),
}
}
pub(crate) fn ports_internal(&self) -> Vec<crate::common::MidiOutputPort> {
Destinations
.into_iter()
.map(|d| crate::common::MidiOutputPort {
imp: MidiOutputPort { dest: Arc::new(d) },
})
.collect()
}
pub fn port_count(&self) -> usize {
Destinations::count()
}
pub fn port_name(&self, port: &MidiOutputPort) -> Result<String, PortInfoError> {
match port.dest.display_name() {
Some(name) => Ok(name),
None => Err(PortInfoError::CannotRetrievePortName),
}
}
pub fn connect(
self,
port: &MidiOutputPort,
port_name: &str,
) -> Result<MidiOutputConnection, ConnectError<MidiOutput>> {
let oport = match self.client.output_port(port_name) {
Ok(p) => p,
Err(_) => return Err(ConnectError::other("error creating MIDI output port", self)),
};
Ok(MidiOutputConnection {
client: self.client,
details: OutputConnectionDetails::Explicit(oport, port.dest.clone()),
})
}
pub fn create_virtual(
self,
port_name: &str,
) -> Result<MidiOutputConnection, ConnectError<MidiOutput>> {
let vrt = match self.client.virtual_source(port_name) {
Ok(p) => p,
Err(_) => {
return Err(ConnectError::other(
"error creating virtual MIDI source",
self,
))
}
};
Ok(MidiOutputConnection {
client: self.client,
details: OutputConnectionDetails::Virtual(vrt),
})
}
}
enum OutputConnectionDetails {
Explicit(OutputPort, Arc<Destination>),
Virtual(VirtualSource),
}
pub struct MidiOutputConnection {
client: Client,
details: OutputConnectionDetails,
}
impl MidiOutputConnection {
pub fn close(self) -> MidiOutput {
MidiOutput {
client: self.client,
}
}
pub fn send(&mut self, message: &[u8]) -> Result<(), SendError> {
let send_time = if cfg!(feature = "coremidi_send_timestamped") {
unsafe { external::AudioGetCurrentHostTime() }
} else {
0
};
let packets = PacketBuffer::new(send_time, message);
match self.details {
OutputConnectionDetails::Explicit(ref port, ref dest) => port
.send(&dest, &packets)
.map_err(|_| SendError::Other("error sending MIDI message to port")),
OutputConnectionDetails::Virtual(ref vrt) => vrt
.received(&packets)
.map_err(|_| SendError::Other("error sending MIDI to virtual destinations")),
}
}
}