use std::slice;
use std::sync::{Arc, Condvar, Mutex};
use std::time::Duration;
use windows::Win32::Media::Audio::{
CALLBACK_FUNCTION, HMIDIIN, HMIDIOUT, MHDR_PREPARED, MIDI_IO_STATUS, MIDIHDR, MIDIINCAPSW,
MIDIOUTCAPSW, midiInAddBuffer, midiInClose, midiInGetDevCapsW, midiInGetNumDevs, midiInOpen,
midiInPrepareHeader, midiInReset, midiInStart, midiInUnprepareHeader, midiOutClose,
midiOutGetDevCapsW, midiOutGetNumDevs, midiOutLongMsg, midiOutOpen, midiOutPrepareHeader,
midiOutReset, midiOutShortMsg, midiOutUnprepareHeader,
};
use windows::Win32::Media::{
MM_MIM_DATA, MM_MIM_ERROR, MM_MIM_LONGDATA, MM_MIM_LONGERROR, MM_MIM_MOREDATA, MM_MOM_DONE,
MMSYSERR_NOERROR,
};
use windows::core::PSTR;
const MIM_DATA: u32 = MM_MIM_DATA;
const MIM_LONGDATA: u32 = MM_MIM_LONGDATA;
const MIM_ERROR: u32 = MM_MIM_ERROR;
const MIM_LONGERROR: u32 = MM_MIM_LONGERROR;
const MIM_MOREDATA: u32 = MM_MIM_MOREDATA;
const MOM_DONE: u32 = MM_MOM_DONE;
const SYSEX_BUFFER_COUNT: usize = 4;
const SYSEX_BUFFER_LEN: usize = 1024;
const LONG_MSG_TIMEOUT: Duration = Duration::from_secs(2);
#[derive(Clone, Copy)]
pub struct MidiInputPort(u32);
#[derive(Clone, Copy)]
pub struct MidiOutputPort(u32);
pub struct MidiInput;
pub struct MidiOutput;
struct InputCallbackState {
callback: MidiCallback,
}
type MidiCallback = Box<dyn FnMut(&[u8]) + Send>;
struct OutputCallbackState {
done: Mutex<bool>,
done_cv: Condvar,
}
impl MidiInput {
pub fn new(_name: &str) -> Result<Self, String> {
Ok(Self)
}
pub fn ports(&self) -> Vec<MidiInputPort> {
(0..unsafe { midiInGetNumDevs() })
.map(MidiInputPort)
.collect()
}
pub fn port_name(&self, port: &MidiInputPort) -> Result<String, String> {
let mut caps = MIDIINCAPSW::default();
let rc = unsafe {
midiInGetDevCapsW(port.0 as usize, &mut caps, size_of::<MIDIINCAPSW>() as u32)
};
if rc != MMSYSERR_NOERROR {
return Err(format!("midiInGetDevCapsW failed with code {rc}"));
}
let pname = unsafe { core::ptr::read_unaligned(core::ptr::addr_of!(caps.szPname)) };
Ok(wchar_to_string(&pname))
}
pub fn connect<F: FnMut(&[u8]) + Send + 'static>(
&self,
port: &MidiInputPort,
_name: &str,
callback: F,
) -> Result<MidiInputConnection, String> {
let state = Box::into_raw(Box::new(InputCallbackState {
callback: Box::new(callback),
}));
let mut handle = HMIDIIN::default();
let rc = unsafe {
midiInOpen(
&mut handle,
port.0,
Some(input_callback as *const () as usize),
Some(state as usize),
CALLBACK_FUNCTION | MIDI_IO_STATUS,
)
};
if rc != MMSYSERR_NOERROR {
unsafe {
drop(Box::from_raw(state));
}
return Err(format!("midiInOpen failed with code {rc}"));
}
let mut buffers = Vec::with_capacity(SYSEX_BUFFER_COUNT);
for _ in 0..SYSEX_BUFFER_COUNT {
let data = Box::into_raw(Box::new([0u8; SYSEX_BUFFER_LEN]));
buffers.push(MIDIHDR {
lpData: PSTR(data.cast::<u8>()),
dwBufferLength: SYSEX_BUFFER_LEN as u32,
..Default::default()
});
}
for buffer in &mut buffers {
let rc = unsafe { midiInPrepareHeader(handle, buffer, size_of::<MIDIHDR>() as u32) };
if rc != MMSYSERR_NOERROR {
let _ = unsafe { midiInReset(handle) };
for prepared in &mut buffers {
if prepared.dwFlags & MHDR_PREPARED != 0 {
let _ = unsafe {
midiInUnprepareHeader(handle, prepared, size_of::<MIDIHDR>() as u32)
};
}
}
let _ = unsafe { midiInClose(handle) };
for buffer in &buffers {
unsafe {
drop(Box::from_raw(
buffer.lpData.0.cast::<[u8; SYSEX_BUFFER_LEN]>(),
));
}
}
unsafe {
drop(Box::from_raw(state));
}
return Err(format!("midiInPrepareHeader failed with code {rc}"));
}
let rc = unsafe { midiInAddBuffer(handle, buffer, size_of::<MIDIHDR>() as u32) };
if rc != MMSYSERR_NOERROR {
let _ = unsafe { midiInReset(handle) };
for buffer in &mut buffers {
if buffer.dwFlags & MHDR_PREPARED != 0 {
let _ = unsafe {
midiInUnprepareHeader(handle, buffer, size_of::<MIDIHDR>() as u32)
};
}
}
let _ = unsafe { midiInClose(handle) };
for buffer in &buffers {
unsafe {
drop(Box::from_raw(
buffer.lpData.0.cast::<[u8; SYSEX_BUFFER_LEN]>(),
));
}
}
unsafe {
drop(Box::from_raw(state));
}
return Err(format!("midiInAddBuffer failed with code {rc}"));
}
}
let rc = unsafe { midiInStart(handle) };
if rc != MMSYSERR_NOERROR {
let _ = unsafe { midiInReset(handle) };
for buffer in &mut buffers {
if buffer.dwFlags & MHDR_PREPARED != 0 {
let _ = unsafe {
midiInUnprepareHeader(handle, buffer, size_of::<MIDIHDR>() as u32)
};
}
}
let _ = unsafe { midiInClose(handle) };
for buffer in &buffers {
unsafe {
drop(Box::from_raw(
buffer.lpData.0.cast::<[u8; SYSEX_BUFFER_LEN]>(),
));
}
}
unsafe {
drop(Box::from_raw(state));
}
return Err(format!("midiInStart failed with code {rc}"));
}
Ok(MidiInputConnection {
handle,
state,
buffers,
})
}
}
pub struct MidiInputConnection {
handle: HMIDIIN,
state: *mut InputCallbackState,
buffers: Vec<MIDIHDR>,
}
unsafe impl Send for MidiInputConnection {}
impl MidiInputConnection {
pub fn close(self) -> Result<(), String> {
let reset_rc = unsafe { midiInReset(self.handle) };
let mut last_err = None;
if reset_rc != MMSYSERR_NOERROR {
last_err = Some(format!("midiInReset failed with code {reset_rc}"));
}
for buffer in &self.buffers {
if buffer.dwFlags & MHDR_PREPARED != 0 {
let rc = unsafe {
midiInUnprepareHeader(
self.handle,
buffer as *const _ as *mut _,
size_of::<MIDIHDR>() as u32,
)
};
if rc != MMSYSERR_NOERROR {
last_err = Some(format!("midiInUnprepareHeader failed with code {rc}"));
}
}
}
let close_rc = unsafe { midiInClose(self.handle) };
if close_rc != MMSYSERR_NOERROR {
last_err = Some(format!("midiInClose failed with code {close_rc}"));
}
for buffer in &self.buffers {
unsafe {
drop(Box::from_raw(
buffer.lpData.0.cast::<[u8; SYSEX_BUFFER_LEN]>(),
));
}
}
unsafe {
drop(Box::from_raw(self.state));
}
match last_err {
Some(err) => Err(err),
None => Ok(()),
}
}
}
impl MidiOutput {
pub fn new(_name: &str) -> Result<Self, String> {
Ok(Self)
}
pub fn ports(&self) -> Vec<MidiOutputPort> {
(0..unsafe { midiOutGetNumDevs() })
.map(MidiOutputPort)
.collect()
}
pub fn port_name(&self, port: &MidiOutputPort) -> Result<String, String> {
let mut caps = MIDIOUTCAPSW::default();
let rc = unsafe {
midiOutGetDevCapsW(port.0 as usize, &mut caps, size_of::<MIDIOUTCAPSW>() as u32)
};
if rc != MMSYSERR_NOERROR {
return Err(format!("midiOutGetDevCapsW failed with code {rc}"));
}
let pname = unsafe { core::ptr::read_unaligned(core::ptr::addr_of!(caps.szPname)) };
Ok(wchar_to_string(&pname))
}
pub fn connect(
&self,
port: &MidiOutputPort,
_name: &str,
) -> Result<MidiOutputConnection, String> {
let state = Arc::new(OutputCallbackState {
done: Mutex::new(false),
done_cv: Condvar::new(),
});
let state_ptr = Arc::into_raw(state) as usize;
let mut handle = HMIDIOUT::default();
let rc = unsafe {
midiOutOpen(
&mut handle,
port.0,
Some(output_callback as *const () as usize),
Some(state_ptr),
CALLBACK_FUNCTION,
)
};
if rc != MMSYSERR_NOERROR {
unsafe {
drop(Arc::from_raw(state_ptr as *const OutputCallbackState));
}
return Err(format!("midiOutOpen failed with code {rc}"));
}
Ok(MidiOutputConnection {
handle,
state: state_ptr as *const OutputCallbackState,
})
}
}
pub struct MidiOutputConnection {
handle: HMIDIOUT,
state: *const OutputCallbackState,
}
unsafe impl Send for MidiOutputConnection {}
impl MidiOutputConnection {
pub fn send(&mut self, data: &[u8]) -> Result<(), String> {
if data.is_empty() {
return Ok(());
}
if data.len() <= 3 {
let mut msg = 0u32;
for (shift, byte) in data.iter().enumerate() {
msg |= u32::from(*byte) << (shift * 8);
}
let rc = unsafe { midiOutShortMsg(self.handle, msg) };
if rc != MMSYSERR_NOERROR {
return Err(format!("midiOutShortMsg failed with code {rc}"));
}
return Ok(());
}
let state = unsafe { &*self.state };
let mut data_buf = data.to_vec();
let mut header = MIDIHDR {
lpData: PSTR(data_buf.as_mut_ptr()),
dwBufferLength: data.len() as u32,
..Default::default()
};
*state
.done
.lock()
.map_err(|e| format!("MIDI output lock poisoned: {e}"))? = false;
let header_size = size_of::<MIDIHDR>() as u32;
let rc = unsafe { midiOutPrepareHeader(self.handle, &mut header, header_size) };
if rc != MMSYSERR_NOERROR {
return Err(format!("midiOutPrepareHeader failed with code {rc}"));
}
let rc = unsafe { midiOutLongMsg(self.handle, &header, header_size) };
if rc != MMSYSERR_NOERROR {
let _ = unsafe { midiOutUnprepareHeader(self.handle, &mut header, header_size) };
return Err(format!("midiOutLongMsg failed with code {rc}"));
}
let mut done = state
.done
.lock()
.map_err(|e| format!("MIDI output lock poisoned: {e}"))?;
let (guard, _timeout_result) = state
.done_cv
.wait_timeout(done, LONG_MSG_TIMEOUT)
.map_err(|e| format!("MIDI output lock poisoned: {e}"))?;
done = guard;
let completed = *done;
drop(done);
if !completed {
let _ = unsafe { midiOutUnprepareHeader(self.handle, &mut header, header_size) };
return Err(format!(
"Timed out after {LONG_MSG_TIMEOUT:?} waiting for midiOutLongMsg completion"
));
}
let rc = unsafe { midiOutUnprepareHeader(self.handle, &mut header, header_size) };
if rc != MMSYSERR_NOERROR {
return Err(format!("midiOutUnprepareHeader failed with code {rc}"));
}
Ok(())
}
pub fn close(self) -> Result<(), String> {
let reset_rc = unsafe { midiOutReset(self.handle) };
let close_rc = unsafe { midiOutClose(self.handle) };
unsafe {
drop(Arc::from_raw(self.state));
}
match (reset_rc, close_rc) {
(MMSYSERR_NOERROR, MMSYSERR_NOERROR) => Ok(()),
(rc, MMSYSERR_NOERROR) => Err(format!("midiOutReset failed with code {rc}")),
(_, rc) => Err(format!("midiOutClose failed with code {rc}")),
}
}
}
extern "system" fn input_callback(
hmidi: HMIDIIN,
msg: u32,
instance: usize,
param1: usize,
_param2: usize,
) {
let state = unsafe { &mut *(instance as *mut InputCallbackState) };
match msg {
MIM_DATA | MIM_MOREDATA => {
let packed = param1 as u32;
let bytes = [
(packed & 0xFF) as u8,
((packed >> 8) & 0xFF) as u8,
((packed >> 16) & 0xFF) as u8,
];
let len = short_message_len(bytes[0]);
(state.callback)(&bytes[..len]);
}
MIM_LONGDATA => {
let header = unsafe { &mut *(param1 as *mut MIDIHDR) };
if header.dwBytesRecorded > 0 {
let data = unsafe {
slice::from_raw_parts(header.lpData.0, header.dwBytesRecorded as usize)
};
(state.callback)(data);
}
let _ = unsafe { midiInAddBuffer(hmidi, header, size_of::<MIDIHDR>() as u32) };
}
MIM_ERROR => {
}
MIM_LONGERROR => {
let header = unsafe { &mut *(param1 as *mut MIDIHDR) };
let _ = unsafe { midiInAddBuffer(hmidi, header, size_of::<MIDIHDR>() as u32) };
}
_ => {}
}
}
extern "system" fn output_callback(
_hmidi: HMIDIOUT,
msg: u32,
instance: usize,
_param1: usize,
_param2: usize,
) {
if msg != MOM_DONE {
return;
}
let state = unsafe { &*(instance as *const OutputCallbackState) };
if let Ok(mut done) = state.done.lock() {
*done = true;
state.done_cv.notify_one();
}
}
fn short_message_len(status: u8) -> usize {
match status {
0xC0..=0xDF => 2,
0xF1 | 0xF3 => 2,
0xF2 => 3,
0xF0..=0xFF => 1,
_ => 3,
}
}
fn wchar_to_string(buf: &[u16]) -> String {
let end = buf.iter().position(|&c| c == 0).unwrap_or(buf.len());
String::from_utf16_lossy(&buf[..end])
}