diff options
author | pommicket <pommicket@gmail.com> | 2022-10-03 23:35:06 -0400 |
---|---|---|
committer | pommicket <pommicket@gmail.com> | 2022-10-03 23:35:06 -0400 |
commit | d109dc4ba5dcb85b16d35ff5235c6db346da47bc (patch) | |
tree | 4b0bf558f5199ce3f45a84b40a965da37069b02c /src | |
parent | 4d7d33298f6cebf54d9794a168bfc24d291dd08c (diff) |
midi input on windows
Diffstat (limited to 'src')
-rw-r--r-- | src/main.rs | 24 | ||||
-rw-r--r-- | src/midi_input.rs | 714 | ||||
-rw-r--r-- | src/soundfont.rs | 3 |
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[..]; |