Fix handling of Sysex messages with WinMME midi driver
[ardour.git] / libs / backends / portaudio / winmmemidi_input_device.cc
index 1067b4674393fcb1f4e9459bd75f4cf01067b790..4e0e84f468d5855367aaf77bf97dc4487148f178 100644 (file)
@@ -22,8 +22,9 @@
 #include <cmath>
 
 #include "pbd/compose.h"
+#include "pbd/windows_timer_utils.h"
+#include "pbd/windows_mmcss.h"
 
-#include "win_utils.h"
 #include "midi_util.h"
 
 #include "debug.h"
@@ -35,6 +36,7 @@ namespace ARDOUR {
 
 WinMMEMidiInputDevice::WinMMEMidiInputDevice (int index)
        : m_handle(0)
+       , m_started(false)
        , m_midi_buffer(new RingBuffer<uint8_t>(MIDI_BUFFER_SIZE))
        , m_sysex_buffer(new uint8_t[SYSEX_BUFFER_SIZE])
 {
@@ -122,6 +124,7 @@ WinMMEMidiInputDevice::add_sysex_buffer (std::string& error_msg)
 {
        m_sysex_header.dwBufferLength = SYSEX_BUFFER_SIZE;
        m_sysex_header.dwFlags = 0;
+       m_sysex_header.dwBytesRecorded = 0;
        m_sysex_header.lpData = (LPSTR)m_sysex_buffer.get ();
 
        MMRESULT result = midiInPrepareHeader (m_handle, &m_sysex_header, sizeof(MIDIHDR));
@@ -136,6 +139,8 @@ WinMMEMidiInputDevice::add_sysex_buffer (std::string& error_msg)
                error_msg = get_error_string (result);
                DEBUG_MIDI (error_msg);
                return false;
+       } else {
+               DEBUG_MIDI ("Added Initial WinMME sysex buffer\n");
        }
        return true;
 }
@@ -175,22 +180,52 @@ WinMMEMidiInputDevice::winmm_input_callback(HMIDIIN handle,
 {
        WinMMEMidiInputDevice* midi_input = (WinMMEMidiInputDevice*)instance;
 
+#ifdef USE_MMCSS_THREAD_PRIORITIES
+
+       static HANDLE input_thread = GetCurrentThread ();
+       static bool priority_boosted = false;
+
+#if 0 // GetThreadId() is Vista or later only.
+       if (input_thread != GetCurrentThread ()) {
+               DWORD otid = GetThreadId (input_thread);
+               DWORD ntid = GetThreadId (GetCurrentThread ());
+               // There was a reference on the internet somewhere that it is possible
+               // for the callback to come from different threads(thread pool) this
+               // could be problematic but I haven't seen this behaviour yet
+               DEBUG_THREADS (string_compose (
+                   "WinMME input Thread ID Changed: was %1, now %2\n", otid, ntid));
+       }
+#endif
+
+       HANDLE task_handle;
+
+       if (!priority_boosted) {
+               PBD::MMCSS::set_thread_characteristics ("Pro Audio", &task_handle);
+               PBD::MMCSS::set_thread_priority (task_handle, PBD::MMCSS::AVRT_PRIORITY_HIGH);
+               priority_boosted = true;
+       }
+#endif
+
        switch (msg) {
        case MIM_OPEN:
        case MIM_CLOSE:
+               DEBUG_MIDI("WinMME: devices changed callback\n");
                // devices_changed_callback
                break;
        case MIM_MOREDATA:
+               DEBUG_MIDI("WinMME: more data ..\n");
                // passing MIDI_IO_STATUS to midiInOpen means that MIM_MOREDATA
                // will be sent when the callback isn't processing MIM_DATA messages
                // fast enough to keep up with messages arriving at input device
                // driver. I'm not sure what could be done differently if that occurs
                // so just handle MIM_DATA as per normal
        case MIM_DATA:
+               DEBUG_MIDI(string_compose ("WinMME: short msg @ %1\n", (uint32_t) timestamp));
                midi_input->handle_short_msg ((const uint8_t*)&midi_msg, (uint32_t)timestamp);
                break;
        case MIM_LONGDATA:
-               midi_input->handle_sysex_msg ((MIDIHDR*)&midi_msg, (uint32_t)timestamp);
+               DEBUG_MIDI(string_compose ("WinMME: long msg @ %1\n", (uint32_t) timestamp));
+               midi_input->handle_sysex_msg ((MIDIHDR*)midi_msg, (uint32_t)timestamp);
                break;
        case MIM_ERROR:
                DEBUG_MIDI ("WinMME: Driver sent an invalid MIDI message\n");
@@ -198,6 +233,9 @@ WinMMEMidiInputDevice::winmm_input_callback(HMIDIIN handle,
        case MIM_LONGERROR:
                DEBUG_MIDI ("WinMME: Driver sent an invalid or incomplete SYSEX message\n");
                break;
+       default:
+               DEBUG_MIDI ("WinMME: Driver sent an unknown message\n");
+               break;
        }
 }
 
@@ -219,29 +257,47 @@ void
 WinMMEMidiInputDevice::handle_sysex_msg (MIDIHDR* const midi_header,
                                          uint32_t timestamp)
 {
-#ifdef ENABLE_SYSEX
-       LPMIDIHDR header = (LPMIDIHDR)midi_header;
-       size_t byte_count = header->dwBytesRecorded;
+       size_t byte_count = midi_header->dwBytesRecorded;
 
-       if (!byte_count) {
-               DEBUG_MIDI (
-                   "ERROR: WinMME driver has returned sysex header to us with no bytes\n");
+       if (byte_count == 0) {
+               if ((midi_header->dwFlags & WHDR_DONE) != 0) {
+                       DEBUG_MIDI("WinMME: In midi reset\n");
+                       // unprepare handled by close
+               } else {
+                       DEBUG_MIDI(
+                           "ERROR: WinMME driver has returned sysex header to us with no bytes\n");
+               }
                return;
        }
 
-       uint8_t* data = (uint8_t*)header->lpData;
+       uint8_t* data = (uint8_t*)midi_header->lpData;
+
+       DEBUG_MIDI(string_compose("WinMME sysex flags: %1\n", midi_header->dwFlags));
 
        if ((data[0] != 0xf0) || (data[byte_count - 1] != 0xf7)) {
-               DEBUG_MIDI (string_compose ("Discarding %1 byte sysex chunk\n", byte_count));
+               DEBUG_MIDI(string_compose("Discarding %1 byte sysex chunk\n", byte_count));
        } else {
                enqueue_midi_msg (data, byte_count, timestamp);
        }
 
-       MMRESULT result = midiInAddBuffer (m_handle, &m_sysex_header, sizeof(MIDIHDR));
+       DEBUG_MIDI("Adding sysex buffer back to WinMME buffer pool\n");
+
+       midi_header->dwFlags = 0;
+       midi_header->dwBytesRecorded = 0;
+
+       MMRESULT result = midiInPrepareHeader(m_handle, midi_header, sizeof(MIDIHDR));
+
        if (result != MMSYSERR_NOERROR) {
-               DEBUG_MIDI (get_error_string (result));
+               DEBUG_MIDI(string_compose("Unable to prepare header: %1\n",
+                                         get_error_string(result)));
+               return;
+       }
+
+       result = midiInAddBuffer(m_handle, midi_header, sizeof(MIDIHDR));
+       if (result != MMSYSERR_NOERROR) {
+               DEBUG_MIDI(string_compose("Unable to add sysex buffer to buffer pool : %1\n",
+                                         get_error_string(result)));
        }
-#endif
 }
 
 // fix param order
@@ -317,7 +373,7 @@ WinMMEMidiInputDevice::enqueue_midi_msg (const uint8_t* midi_data,
        }
 
        // don't use winmme timestamps for now
-       uint64_t ts = utils::get_microseconds ();
+       uint64_t ts = PBD::get_microseconds ();
 
        DEBUG_TIMING (string_compose (
            "Enqueing MIDI data device: %1 with timestamp: %2 and size %3\n",