midir 0.10.4

A cross-platform, realtime MIDI processing library, inspired by RtMidi.
Documentation
use std::io::{stderr, Write};
use std::{mem, slice};

use windows::Win32::Media::Audio::{midiInAddBuffer, HMIDIIN, MIDIHDR};
use windows::Win32::Media::{MMSYSERR_NOERROR, MM_MIM_DATA, MM_MIM_LONGDATA, MM_MIM_LONGERROR};

use crate::Ignore;

use super::{DWORD, DWORD_PTR, UINT};

use super::HandlerData;

pub extern "system" fn handle_input<T>(
    _: HMIDIIN,
    input_status: UINT,
    instance_ptr: DWORD_PTR,
    midi_message: DWORD_PTR,
    timestamp: DWORD,
) {
    if input_status != MM_MIM_DATA
        && input_status != MM_MIM_LONGDATA
        && input_status != MM_MIM_LONGERROR
    {
        return;
    }

    let data: &mut HandlerData<T> = unsafe { &mut *(instance_ptr as *mut HandlerData<T>) };

    // Calculate time stamp.
    data.message.timestamp = timestamp as u64 * 1000; // milliseconds -> microseconds

    if input_status == MM_MIM_DATA {
        // Channel or system message
        // Make sure the first byte is a status byte.
        let status: u8 = (midi_message & 0x000000FF) as u8;
        if status & 0x80 == 0 {
            return;
        }

        // Determine the number of bytes in the MIDI message.
        let nbytes: u16 = if status < 0xC0 {
            3
        } else if status < 0xE0 {
            2
        } else if status < 0xF0 {
            3
        } else if status == 0xF1 {
            if data.ignore_flags.contains(Ignore::Time) {
                return;
            } else {
                2
            }
        } else if status == 0xF2 {
            3
        } else if status == 0xF3 {
            2
        } else if status == 0xF8 && (data.ignore_flags.contains(Ignore::Time)) {
            // A MIDI timing tick message and we're ignoring it.
            return;
        } else if status == 0xFE && (data.ignore_flags.contains(Ignore::ActiveSense)) {
            // A MIDI active sensing message and we're ignoring it.
            return;
        } else {
            1
        };

        // Copy bytes to our MIDI message.
        let ptr = (&midi_message) as *const DWORD_PTR as *const u8;
        let bytes: &[u8] = unsafe { slice::from_raw_parts(ptr, nbytes as usize) };
        data.message.bytes.extend_from_slice(bytes);
    } else {
        // Sysex message (MIM_LONGDATA or MIM_LONGERROR)
        let sysex = unsafe { &*(midi_message as *const MIDIHDR) };
        if !data.ignore_flags.contains(Ignore::Sysex) && input_status != MM_MIM_LONGERROR {
            // Sysex message and we're not ignoring it
            let bytes: &[u8] =
                unsafe { slice::from_raw_parts(sysex.lpData.0, sysex.dwBytesRecorded as usize) };
            data.message.bytes.extend_from_slice(bytes);
            // TODO: If sysex messages are longer than MIDIR_SYSEX_BUFFER_SIZE, they
            //       are split in chunks. We could reassemble a single message.
        }

        // The WinMM API requires that the sysex buffer be requeued after
        // input of each sysex message.  Even if we are ignoring sysex
        // messages, we still need to requeue the buffer in case the user
        // decides to not ignore sysex messages in the future.  However,
        // it seems that WinMM calls this function with an empty sysex
        // buffer when an application closes and in this case, we should
        // avoid requeueing it, else the computer suddenly reboots after
        // one or two minutes.
        if (unsafe { *data.sysex_buffer.0[sysex.dwUser] }).dwBytesRecorded > 0 {
            //if ( sysex->dwBytesRecorded > 0 ) {
            let in_handle = data.in_handle.as_ref().unwrap().0.lock();
            let result = unsafe {
                midiInAddBuffer(
                    *in_handle,
                    data.sysex_buffer.0[sysex.dwUser],
                    mem::size_of::<MIDIHDR>() as u32,
                )
            };
            drop(in_handle);
            if result != MMSYSERR_NOERROR {
                let _ = writeln!(
                    stderr(),
                    "\nError in handle_input: Requeuing WinMM input sysex buffer failed.\n"
                );
            }

            if data.ignore_flags.contains(Ignore::Sysex) {
                return;
            }
        } else {
            return;
        }
    }

    (data.callback)(
        data.message.timestamp,
        &data.message.bytes,
        data.user_data.as_mut().unwrap(),
    );

    // Clear the vector for the next input message.
    data.message.bytes.clear();
}