summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorpommicket <pommicket@gmail.com>2022-10-03 23:35:06 -0400
committerpommicket <pommicket@gmail.com>2022-10-03 23:35:06 -0400
commitd109dc4ba5dcb85b16d35ff5235c6db346da47bc (patch)
tree4b0bf558f5199ce3f45a84b40a965da37069b02c /src
parent4d7d33298f6cebf54d9794a168bfc24d291dd08c (diff)
midi input on windows
Diffstat (limited to 'src')
-rw-r--r--src/main.rs24
-rw-r--r--src/midi_input.rs714
-rw-r--r--src/soundfont.rs3
3 files changed, 522 insertions, 219 deletions
diff --git a/src/main.rs b/src/main.rs
index b14f9e9..6c1dd9a 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,4 +1,4 @@
-// @TODO: sort presets alphabetically
+#![allow(unused_imports)] // @TODO: delete me
extern crate cpal;
use std::io::Write;
@@ -9,7 +9,7 @@ mod midi_input;
mod soundfont;
#[allow(unused)]
-fn playmidi_main() -> Result<(), String> {
+fn midi_in_main() -> Result<(), String> {
let mut device_mgr = midi_input::DeviceManager::new()?;
device_mgr.set_quiet(true);
let devices = device_mgr.list()?;
@@ -55,12 +55,10 @@ fn playmidi_main() -> Result<(), String> {
.expect("error opening MIDI device");
while device.is_connected() {
- let maybe_event = device.read_event();
- if let Some(event) = maybe_event {
- println!("{:?}", event);
- } else {
- std::thread::sleep(std::time::Duration::from_millis(10));
+ while let Some(event) = device.read_event() {
+ println!("{:?}",event);
}
+ std::thread::sleep(std::time::Duration::from_millis(140));
if let Some(err) = device.get_error() {
eprintln!("Error: {}", err);
device.clear_error();
@@ -69,7 +67,8 @@ fn playmidi_main() -> Result<(), String> {
Ok(())
}
-fn main() {
+#[allow(unused)]
+fn soundfont_main() {
let mut sf = match soundfont::SoundFont::open("/usr/share/sounds/sf2/FluidR3_GM.sf2") {
Err(x) => {
eprintln!("Error: {}", String::from(x));
@@ -148,3 +147,12 @@ fn main() {
std::thread::sleep(std::time::Duration::from_millis(100));
}
}
+
+fn main() {
+ match midi_in_main() {
+ Err(e) => println!("{}", e),
+ _ => {}
+ }
+ /*
+ */
+}
diff --git a/src/midi_input.rs b/src/midi_input.rs
index 27b2c1c..219a8eb 100644
--- a/src/midi_input.rs
+++ b/src/midi_input.rs
@@ -1,7 +1,5 @@
use std::ffi::{c_char, c_int, c_void, CStr, CString};
/// Query and read from MIDI input devices.
-/// Don't assume these functions are thread-safe.
-/// You should only need to call them in the main thread anyways.
/// Basic usage:
/// ```
/// let mut manager = DeviceManager::new().unwrap();
@@ -20,13 +18,14 @@ use std::ffi::{c_char, c_int, c_void, CStr, CString};
/// }
/// ```
use std::sync::Mutex;
-// so much stuff depends on libc
-// it's not such a big deal to add it as a dependency
+// so much stuff depends on libc so it's not such
+// a big deal to add it as a dependency
// (we only really need it for size_t, ssize_t)
extern crate libc;
use libc::{free, size_t, ssize_t};
// (snd_rawmidi_t is an opaque struct)
+#[cfg(unix)]
type SndRawMidiT = c_void;
/// Opaque device manager type.
@@ -34,10 +33,17 @@ pub struct DeviceManager {}
/// Opaque type for device ID.
#[derive(Debug, Clone)]
+#[cfg(unix)]
pub struct DeviceID {
name: String,
}
+#[derive(Debug, Clone)]
+#[cfg(windows)]
+pub struct DeviceID {
+ id: u32,
+}
+
/// Information about a device.
pub struct DeviceInfo {
/// A human-readable name for the device.
@@ -51,11 +57,17 @@ pub struct DeviceInfo {
/// Opaque type for MIDI input device.
#[derive(Debug)]
pub struct Device {
+ #[cfg(unix)]
rawmidi: *mut SndRawMidiT,
+ #[cfg(windows)]
+ hmi: usize,
+ #[cfg(windows)]
+ name: String, // needed to detect if device is disconnected
error: i32,
// used to hold first data byte of two-data-byte MIDI events.
buffered_byte: Option<u8>,
last_status: u8,
+ #[cfg(unix)]
connected: bool,
}
@@ -90,8 +102,7 @@ pub enum DeviceListError {
/// A note of 60 +/- n indicates n semitones above/below middle C.
#[derive(Debug, Clone, Copy)]
pub enum Event {
- /// a note was pressed
- /// This may also indicate a note off, if `vel = 0`.
+ /// a note was pressed (if vel = 0, this is automatically converted to a note off event)
NoteOn { channel: u8, note: u8, vel: u8 },
/// a note was released
NoteOff { channel: u8, note: u8, vel: u8 },
@@ -120,6 +131,8 @@ pub enum Event {
/// Other { status: 0xf0, data: Some(0x1a) }
/// ```
/// corresponds to the raw MIDI data `f0 33 45 1a`.
+ /// sysex messages (& some other system common messages) won't work on windows.
+ /// (too much of a pain to implement)
Other { status: u8, data: Option<u8> },
}
@@ -155,6 +168,7 @@ impl std::ops::Index<usize> for DeviceList {
}
}
+#[cfg(unix)]
#[link(name = "asound", kind = "dylib")]
extern "C" {
fn snd_device_name_hint(
@@ -176,6 +190,132 @@ extern "C" {
fn snd_lib_error_set_handler(handler: *mut c_void) -> c_int;
}
+#[cfg(windows)]
+mod windows {
+ pub use std::collections::VecDeque;
+ use std::sync::Mutex;
+
+ #[repr(C)]
+ #[derive(Default)]
+ pub struct MidiInCapsW {
+ pub mid: u16,
+ pub pid: u16,
+ pub driver_version: u32,
+ pub pname: [u16; 32],
+ pub support: u32,
+ }
+
+ #[link(name = "winmm", kind = "dylib")]
+ extern "C" {
+ pub fn midiInGetNumDevs() -> u32;
+ pub fn midiInGetDevCapsW(device_id: u32, pmic: *mut MidiInCapsW, cbmic: u32) -> u32;
+ pub fn midiInOpen(
+ phmi: *mut HMidiIn,
+ device_id: u32,
+ callback: MidiInProc,
+ instance: usize,
+ flags: u32,
+ ) -> u32;
+ // According to Microsoft:
+ // "This function is supported for backward compatibility.
+ // New applications can cast a handle of the device rather than retrieving the device identifier."
+ // ^ what? this isn't true?? hmi is some random pointer and deviceID is a small integer...
+ pub fn midiInGetID(hmi: HMidiIn, pdevice_id: *mut u32) -> u32;
+ pub fn midiInClose(hmi: HMidiIn) -> u32;
+ pub fn midiInStart(hmi: HMidiIn) -> u32;
+ //pub fn midiInStop(hmi: HMidiIn) -> u32;
+ }
+
+ pub type MidiInProc =
+ extern "C" fn(midi_in: HMidiIn, msg: u32, instance: usize, param1: usize, param2: usize);
+ pub type HMidiIn = usize;
+
+ pub const CALLBACK_FUNCTION: u32 = 0x00030000;
+ pub const MMSYSERR_BADDEVICEID: u32 = 2;
+ pub const MMSYSERR_ALLOCATED: u32 = 4;
+ pub const MMSYSERR_NOMEM: u32 = 7;
+ pub const MMSYSERR_INVALFLAG: u32 = 10;
+ pub const MMSYSERR_INVALPARAM: u32 = 11;
+
+ pub const MM_MIM_DATA: u32 = 0x3C3;
+
+ pub fn midi_in_dev_get_caps(device_id: u32) -> Option<MidiInCapsW> {
+ let mut mic = MidiInCapsW {
+ ..Default::default()
+ };
+ let result = unsafe {
+ midiInGetDevCapsW(device_id, &mut mic as _, std::mem::size_of::<MidiInCapsW>() as u32)
+ };
+ if result == 0 {
+ Some(mic)
+ } else {
+ None
+ }
+ }
+
+ // returns u32::MAX on error.
+ pub fn midi_in_get_id(hmi: HMidiIn) -> u32 {
+ let mut device_id = u32::MAX;
+ unsafe { midiInGetID(hmi, (&mut device_id) as *mut u32) };
+ device_id
+ }
+
+ // returns empty string on error.
+ pub fn get_device_name(id: u32) -> String {
+ if let Some(mic) = midi_in_dev_get_caps(id) {
+
+ let mut name_len = 0;
+ while name_len < mic.pname.len() && mic.pname[name_len] != 0 {
+ name_len += 1;
+ }
+ String::from_utf16_lossy(&mic.pname[..name_len])
+ } else {
+ String::new()
+ }
+ }
+
+ pub type MidiQueue = VecDeque<u8>;
+ pub static MIDI_QUEUES: Mutex<Vec<(HMidiIn, MidiQueue)>> = Mutex::new(vec![]);
+ pub extern "C" fn midi_callback(
+ midi_in: HMidiIn,
+ msg: u32,
+ _instance: usize,
+ param1: usize,
+ _param2: usize,
+ ) {
+ if msg != MM_MIM_DATA {
+ // don't care
+ return;
+ }
+
+ if let Ok(mut queues) = MIDI_QUEUES.lock() {
+ for (dev, queue) in queues.iter_mut() {
+ if *dev == midi_in {
+ // windows has "helpfully" semi-parsed the MIDI for us.
+ // unparse it so we can use the same parsing code across platforms.
+ let status = param1 as u8;
+ // (clearing top bit of data1,data2 just in case)
+ let data1 = ((param1 >> 8) & 0x7f) as u8;
+ let data2 = ((param1 >> 16) & 0x7f) as u8;
+ queue.push_back(status);
+ match status >> 4 {
+ 0b1000 | 0b1001 | 0b1010 | 0b1011 | 0b1110 => {
+ // events with 2 data bytes
+ queue.push_back(data1);
+ queue.push_back(data2);
+ },
+ 0b1100 | 0b1101 => {
+ // events with 1 data byte
+ queue.push_back(data1);
+ },
+ _ => {},
+ }
+ }
+ }
+ }
+ }
+}
+
impl From<&DeviceOpenError> for String {
fn from(e: &DeviceOpenError) -> String {
use DeviceOpenError::*;
@@ -209,6 +349,7 @@ impl From<DeviceListError> for String {
}
// technically there should be varargs here but oh well
+#[cfg(unix)]
pub unsafe extern "C" fn snd_lib_error_handler_quiet(
_file: *const c_char,
_line: i32,
@@ -218,17 +359,14 @@ pub unsafe extern "C" fn snd_lib_error_handler_quiet(
}
// convert cstr to Option<String>. you'll get back None if
-// either cstr is null, or invalid UTF-8.
+// cstr is null.
+#[cfg(unix)]
unsafe fn cstr_to_option_string(cstr: *const c_char) -> Option<String> {
if cstr == 0 as _ {
return None;
}
- let result = CStr::from_ptr(cstr).to_str();
- match result {
- Ok(s) => Some(s.to_string()),
- Err(_) => None,
- }
+ Some(String::from_utf8_lossy(CStr::from_ptr(cstr).to_bytes()).to_string())
}
static DEVICE_MANAGER_EXISTS: Mutex<bool> = Mutex::new(false);
@@ -249,157 +387,282 @@ impl DeviceManager {
/// Pass `true` to stop ALSA from outputting errors to `stderr`
/// (no effect on Windows).
+ #[allow(unused_variables)] // quiet unused on windows
pub fn set_quiet(&mut self, quiet: bool) {
- let mut callback: *mut c_void = 0 as _;
-
- if quiet {
- callback = snd_lib_error_handler_quiet as _;
- }
- unsafe {
- snd_lib_error_set_handler(callback);
+ #[cfg(unix)]
+ {
+ let mut callback: *mut c_void = 0 as _;
+ if quiet {
+ callback = snd_lib_error_handler_quiet as _;
+ }
+ unsafe {
+ snd_lib_error_set_handler(callback);
+ }
}
}
/// Returns a `DeviceList` containing descriptions for all MIDI input devices.
- pub fn list(&self) -> Result<DeviceList, DeviceListError> {
- let mut hints: *mut *mut c_void = 0 as _;
- let err: c_int;
- unsafe {
- err =
- snd_device_name_hint(-1, "rawmidi\0".as_ptr() as *const c_char, (&mut hints) as _);
- }
-
- // in theory hints should never be null if err != 0.
- // but you can never be sure.
- if err != 0 || hints == 0 as _ {
- return Err(DeviceListError::Other(format!(
- "failed to get device hints (error code {})",
- err
- )));
- }
-
- let mut idx: usize = 0;
- let mut devices = vec![];
- let mut default = None;
-
- loop {
- let hint;
+ pub fn list(&mut self) -> Result<DeviceList, DeviceListError> {
+ #[cfg(unix)]
+ {
+ let mut hints: *mut *mut c_void = 0 as _;
+ let err: c_int;
unsafe {
- hint = *hints.add(idx);
+ err = snd_device_name_hint(
+ -1,
+ "rawmidi\0".as_ptr() as *const c_char,
+ (&mut hints) as _,
+ );
}
- if hint.is_null() {
- break;
+
+ // in theory hints should never be null if err != 0.
+ // but you can never be sure.
+ if err != 0 || hints == 0 as _ {
+ return Err(DeviceListError::Other(format!(
+ "failed to get device hints (error code {})",
+ err
+ )));
}
- let name;
- let desc;
+ let mut idx: usize = 0;
+ let mut devices = vec![];
+ let mut default = None;
- unsafe {
- let name_cstr = snd_device_name_get_hint(hint, "NAME\0".as_ptr() as _);
- let desc_cstr = snd_device_name_get_hint(hint, "DESC\0".as_ptr() as _);
- //let ioid_cstr = snd_device_name_get_hint(hint, "IOID\0".as_ptr() as _);
+ loop {
+ let hint;
+ unsafe {
+ hint = *hints.add(idx);
+ }
+ if hint.is_null() {
+ break;
+ }
- name = cstr_to_option_string(name_cstr);
- desc = cstr_to_option_string(desc_cstr);
+ let name;
+ let desc;
- free(name_cstr as _);
- free(desc_cstr as _);
- }
+ unsafe {
+ let name_cstr = snd_device_name_get_hint(hint, "NAME\0".as_ptr() as _);
+ let desc_cstr = snd_device_name_get_hint(hint, "DESC\0".as_ptr() as _);
+ //let ioid_cstr = snd_device_name_get_hint(hint, "IOID\0".as_ptr() as _);
- // we need the name to be able to do anything with the device
- if let Some(name_unwrapped) = name {
- let has_desc = desc.is_some();
- let desc_unwrapped = desc.unwrap_or_else(|| "(no description)".to_string());
- let desc_str = format!("{}\n{}", name_unwrapped, desc_unwrapped);
- let info = DeviceInfo {
- id: DeviceID {
- name: name_unwrapped,
- },
- name: desc_str,
- };
+ name = cstr_to_option_string(name_cstr);
+ desc = cstr_to_option_string(desc_cstr);
- if has_desc && default.is_none() {
- default = Some(idx);
+ free(name_cstr as _);
+ free(desc_cstr as _);
}
- devices.push(info);
+ // we need the name to be able to do anything with the device
+ if let Some(name_unwrapped) = name {
+ let has_desc = desc.is_some();
+ let desc_unwrapped = desc.unwrap_or_else(|| "(no description)".to_string());
+ let desc_str = format!("{}\n{}", name_unwrapped, desc_unwrapped);
+ let info = DeviceInfo {
+ id: DeviceID {
+ name: name_unwrapped,
+ },
+ name: desc_str,
+ };
+
+ if has_desc && default.is_none() {
+ default = Some(idx);
+ }
+
+ devices.push(info);
+ }
+
+ idx += 1;
}
- idx += 1;
- }
+ unsafe {
+ snd_device_name_free_hint(hints);
+ }
- unsafe {
- snd_device_name_free_hint(hints);
+ if devices.is_empty() {
+ return Err(DeviceListError::NoDevices);
+ }
+ Ok(DeviceList {
+ devices,
+ default: default.unwrap_or(0),
+ })
}
+ #[cfg(windows)]
+ {
+ use windows::*;
+ let num_devs = unsafe { midiInGetNumDevs() };
+ let mut default = None;
+ let mut devices = Vec::with_capacity(num_devs as usize);
+ for i in 0..num_devs {
+ let name = get_device_name(i);
+ if !name.is_empty() {
+ // idk what MIDIIN devices are.
+ if default.is_none() && !name.starts_with("MIDIIN") {
+ default = Some(i);
+ }
+ devices.push(DeviceInfo {
+ name,
+ // we'll just use the index for the ID
+ // this isn't perfect since someone might
+ // disconnect a device between list() and open(),
+ // and then this could (maybe?) refer to a different device.
+ // we could also use the name as the ID, but that isn't guaranteed
+ // to be unique & the user could still disconnect between midiInGetDevCaps and midiInOpen.
+ id: DeviceID { id: i },
+ });
+ }
+ }
- if devices.is_empty() {
- return Err(DeviceListError::NoDevices);
+ if devices.is_empty() {
+ return Err(DeviceListError::NoDevices);
+ }
+ Ok(DeviceList {
+ devices,
+ default: default.unwrap_or(0) as usize,
+ })
}
- Ok(DeviceList {
- devices,
- default: default.unwrap_or(0),
- })
}
/// Open a device.
- pub fn open(&self, id: &DeviceID) -> Result<Device, DeviceOpenError> {
- let name_cstr = CString::new(&id.name[..]).map_err(|_| {
- // (name has null bytes)
- // this should never happen since `name` should
- // have been constructed from a cstr.
- // but it could happen if someone uses unsafe or something.
- DeviceOpenError::Other("invalid device ID".to_string())
- })?;
- let mut input: *mut SndRawMidiT = 0 as _;
- let mut err;
- unsafe {
- err = snd_rawmidi_open((&mut input) as _, 0 as _, name_cstr.as_ptr(), 0);
- if err == 0 && !input.is_null() {
- err = snd_rawmidi_nonblock(input, 1);
+ pub fn open(&mut self, id: &DeviceID) -> Result<Device, DeviceOpenError> {
+ #[cfg(unix)]
+ {
+ let name_cstr = CString::new(&id.name[..]).map_err(|_| {
+ // (name has null bytes)
+ // this should never happen since `name` should
+ // have been constructed from a cstr.
+ // but it could happen if someone uses unsafe or something.
+ DeviceOpenError::Other("invalid device ID".to_string())
+ })?;
+ let mut input: *mut SndRawMidiT = 0 as _;
+ let mut err;
+ unsafe {
+ err = snd_rawmidi_open((&mut input) as _, 0 as _, name_cstr.as_ptr(), 0);
+ if err == 0 && !input.is_null() {
+ err = snd_rawmidi_nonblock(input, 1);
+ }
+ }
+ if err != 0 || input.is_null() {
+ if err == -2 {
+ return Err(DeviceOpenError::NotFound(id.name.clone()));
+ }
+ return Err(DeviceOpenError::Other(format!(
+ "other error (code {})",
+ err
+ )));
}
+ Ok(Device::new(input))
}
- if err != 0 || input.is_null() {
- if err == -2 {
- return Err(DeviceOpenError::NotFound(id.name.clone()));
+ #[cfg(windows)]
+ {
+ use windows::*;
+ let mut hmi = 0 as HMidiIn;
+ let result = unsafe {
+ midiInOpen(
+ (&mut hmi) as _,
+ id.id,
+ midi_callback,
+ 0,
+ CALLBACK_FUNCTION,
+ )
+ };
+
+ if result != 0 {
+ let result_str = match result {
+ MMSYSERR_BADDEVICEID => {
+ return Err(DeviceOpenError::NotFound(format!("{}", id.id)))
+ }
+ MMSYSERR_ALLOCATED => "MMSYSERR_ALLOCATED".to_string(),
+ MMSYSERR_INVALFLAG => "MMSYSERR_INVALFLAG".to_string(),
+ MMSYSERR_INVALPARAM => "MMSYSERR_INVALPARAM".to_string(),
+ MMSYSERR_NOMEM => "MMSYSERR_NOMEM".to_string(),
+ x => format!("{}", x),
+ };
+ return Err(DeviceOpenError::Other(format!(
+ "windows error {}",
+ result_str
+ )));
}
- return Err(DeviceOpenError::Other(format!(
- "other error (code {})",
- err
- )));
+
+ let mut queues = MIDI_QUEUES
+ .lock()
+ .map_err(|_| DeviceOpenError::Other("failed to lock mutex".to_string()))?;
+ queues.push((hmi, VecDeque::new()));
+ drop(queues);
+
+ unsafe { midiInStart(hmi); }
+
+ Ok(Device::new(hmi))
}
- Ok(Device {
- rawmidi: input,
+ }
+}
+
+impl Device {
+ #[cfg(unix)]
+ fn new(rawmidi: *mut SndRawMidiT) -> Self {
+ Self {
+ rawmidi,
buffered_byte: None,
error: 0,
last_status: 0,
connected: true,
- })
+ }
}
-}
-
-impl Device {
+
+ #[cfg(windows)]
+ fn new(hmi: windows::HMidiIn) -> Self {
+ let id = windows::midi_in_get_id(hmi);
+ Self {
+ hmi,
+ name: windows::get_device_name(id),
+ buffered_byte: None,
+ error: 0,
+ last_status: 0,
+ }
+ }
+
fn read_raw(&mut self, buffer: &mut [u8]) -> usize {
- let n;
- unsafe {
- let pbuffer = &mut buffer[0] as *mut u8 as *mut c_void;
- n = snd_rawmidi_read(self.rawmidi, pbuffer, buffer.len() as _);
+ #[cfg(unix)]
+ {
+ let n;
+ unsafe {
+ let pbuffer = &mut buffer[0] as *mut u8 as *mut c_void;
+ n = snd_rawmidi_read(self.rawmidi, pbuffer, buffer.len() as _);
+ }
+ if n == -11 {
+ // EAGAIN (indicates no bytes were read)
+ 0
+ } else if n == -19 {
+ // ENODEV (device was disconnected)
+ self.connected = false;
+ 0
+ } else if n < 0 {
+ self.error = n as _;
+ 0
+ } else if (n as usize) <= buffer.len() {
+ n as usize
+ } else {
+ // this shouldn't happen
+ // something messed up
+ self.error = -1000;
+ 0
+ }
}
- if n == -11 {
- // EAGAIN (indicates no bytes were read)
- 0
- } else if n == -19 {
- // ENODEV (device was disconnected)
- self.connected = false;
- 0
- } else if n < 0 {
- self.error = n as _;
- 0
- } else if (n as usize) <= buffer.len() {
- n as usize
- } else {
- // this shouldn't happen
- // something messed up
- self.error = -1000;
+
+ #[cfg(windows)]
+ {
+ if let Ok(mut queues) = windows::MIDI_QUEUES.lock() {
+ // in theory, this find() should always succeed
+ let result = queues.iter_mut().find(|(hmi, _)| *hmi == self.hmi);
+ if let Some((_, vec)) = result {
+ for i in 0..buffer.len() {
+ match vec.pop_front() {
+ Some(x) => buffer[i] = x,
+ None => return i,
+ }
+ }
+ return buffer.len();
+ }
+ }
0
}
}
@@ -419,9 +682,19 @@ impl Device {
/// returns false if the device was disconnected.
/// if a device is disconnected and reconnected, you need to reopen it.
- /// (its ID should remain the same)
+ /// (its ID may not remain the same.)
pub fn is_connected(&self) -> bool {
+ #[cfg(unix)]
+ {
self.connected
+ }
+ #[cfg(windows)]
+ {
+ // there's no way of telling when a device is connected, it seems.
+ // this is the best we can do.
+ let name_expected = windows::get_device_name(windows::midi_in_get_id(self.hmi));
+ !name_expected.is_empty() && name_expected == self.name
+ }
}
/// get the device error if there is one
@@ -439,92 +712,103 @@ impl Device {
/// read a MIDI event.
/// things may get screwed up if you aren't careful about
- /// mixing `.read_bytes()` with this.
- /// For simplicity, if there's a read error, this just returns None.
+ /// mixing `.read_bytes()` with this (i don't know why you would mix them).
+ /// For simplicity, if there's a read error, this just returns `None`.
/// Check `get_error()` to find out more.
pub fn read_event(&mut self) -> Option<Event> {
- let mut byte = self.read_byte()?;
- if (byte & 0x80) != 0 {
- let status = byte;
- // new status
- self.last_status = status;
- self.buffered_byte = None;
-
- if (status & 0b11110000) > 0b11000000 {
- return Some(Event::Other { status, data: None });
- }
-
- byte = self.read_byte()?;
+ loop {
+ let mut byte = self.read_byte()?;
if (byte & 0x80) != 0 {
- // no data provided for MIDI event which should have data.
- // what can we do...
- return None;
+ let status = byte;
+ // new status
+ self.last_status = status;
+ self.buffered_byte = None;
+
+ if (status >> 4) == 0b1111 {
+ return Some(Event::Other { status, data: None });
+ }
+
+ byte = self.read_byte()?;
+ if (byte & 0x80) != 0 {
+ // no data provided for MIDI event which should have data.
+ // what can we do...
+ // returning None is bad because it stops the stream.
+ return Some(Event::Other { status, data: Some(byte & 0x7f) });
+ }
}
- }
-
- let channel = self.last_status & 0xf;
-
- // at this point we have a data byte
- assert!((byte & 0x80) == 0);
-
- match self.last_status & 0xf0 {
- 0x80 | 0x90 | 0xA0 | 0xB0 | 0xE0 => {
- // event with two bytes of data.
- if let Some(data1) = self.buffered_byte {
- // we have both data bytes!
- self.buffered_byte = None;
- let data2 = byte;
- return Some(match self.last_status & 0xf0 {
- 0x80 => Event::NoteOff {
- channel,
- note: data1,
- vel: data2,
- },
- 0x90 => Event::NoteOn {
- channel,
- note: data1,
- vel: data2,
- },
- 0xA0 => Event::NoteAftertouch {
- channel,
- note: data1,
- pressure: data2,
- },
- 0xB0 => Event::ControlChange {
- channel,
- controller: data1,
- value: data2,
- },
- 0xE0 => {
- let amount = ((data1 as i16) << 7 | (data2 as i16)) - 0x2000;
- Event::PitchBend { channel, amount }
- }
- _ => panic!("what"),
+
+ let channel = self.last_status & 0xf;
+
+ // at this point we have a data byte
+ assert!((byte & 0x80) == 0);
+
+ match self.last_status >> 4 {
+ 0b1000 | 0b1001 | 0b1010 | 0b1011 | 0b1110 => {
+ // event with two bytes of data.
+ if let Some(data1) = self.buffered_byte {
+ // we have both data bytes!
+ self.buffered_byte = None;
+ let data2 = byte;
+ return Some(match self.last_status >> 4 {
+ 0b1000 => Event::NoteOff {
+ channel,
+ note: data1,
+ vel: data2,
+ },
+ 0b1001 => if data2 == 0 {
+ Event::NoteOff {
+ channel,
+ note: data1,
+ vel: data2,
+ }
+ } else {
+ Event::NoteOn {
+ channel,
+ note: data1,
+ vel: data2,
+ }
+ },
+ 0b1010 => Event::NoteAftertouch {
+ channel,
+ note: data1,
+ pressure: data2,
+ },
+ 0b1011 => Event::ControlChange {
+ channel,
+ controller: data1,
+ value: data2,
+ },
+ 0b1110 => {
+ let amount = (data1 as i16 | (data2 as i16) << 7) - 0x2000;
+ Event::PitchBend { channel, amount }
+ }
+ _ => panic!("what"),
+ });
+ } else {
+ self.buffered_byte = Some(byte);
+ continue;
+ }
+ }
+ 0b1100 => {
+ return Some(Event::ProgramChange {
+ channel,
+ program: byte,
+ });
+ }
+ 0b1101 => {
+ return Some(Event::ChannelAftertouch {
+ channel,
+ pressure: byte,
+ });
+ }
+ _ => {
+ return Some(Event::Other {
+ status: self.last_status,
+ data: Some(byte),
});
- } else {
- self.buffered_byte = Some(byte);
}
- }
- 0xC0 => {
- return Some(Event::ProgramChange {
- channel,
- program: byte,
- });
- }
- 0xD0 => {
- return Some(Event::ChannelAftertouch {
- channel,
- pressure: byte,
- });
- }
- _ => {
- return Some(Event::Other {
- status: self.last_status,
- data: Some(byte),
- });
}
}
- None
}
}
@@ -536,9 +820,21 @@ impl Drop for DeviceManager {
impl Drop for Device {
fn drop(&mut self) {
- unsafe {
- // there's not much we can do if this fails.
- snd_rawmidi_close(self.rawmidi);
+ #[cfg(unix)]
+ {
+ unsafe {
+ // there's not much we can do if this fails.
+ snd_rawmidi_close(self.rawmidi);
+ }
+ }
+ #[cfg(windows)]
+ {
+ use windows::*;
+ let mut queues = MIDI_QUEUES.lock().unwrap();
+ queues.retain(|(hmi, _)| *hmi != self.hmi);
+ unsafe {
+ midiInClose(self.hmi);
+ }
}
}
}
diff --git a/src/soundfont.rs b/src/soundfont.rs
index 5bede73..4cddb0b 100644
--- a/src/soundfont.rs
+++ b/src/soundfont.rs
@@ -12,7 +12,6 @@ IMPORTANT SOUNDFONT TERMINOLOGY:
an object zone refers to a sample
a SAMPLE is a block of audio data with some properties of how it should be played
*/
-
use std::fs::File;
use std::io::{Read, Seek, Write};
@@ -1008,7 +1007,7 @@ impl SoundFont {
// here's the key: audio samples measure voltage, not power
// P = V^2 / R
// so a 10x larger sample will have 100x the power (and will be 20dB, not 10dB louder).
- */
+ */
let mut t = hold_time;
let data = &sample.data[..];