2 * Copyright (C) 2015 Tim Mayberry <mojofunk@gmail.com>
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19 #include "winmmemidi_input_device.h"
24 #include "pbd/compose.h"
25 #include "pbd/windows_timer_utils.h"
26 #include "pbd/windows_mmcss.h"
28 #include "midi_util.h"
32 static const uint32_t MIDI_BUFFER_SIZE = 32768;
33 static const uint32_t SYSEX_BUFFER_SIZE = 32768;
37 WinMMEMidiInputDevice::WinMMEMidiInputDevice (int index)
40 , m_midi_buffer(new RingBuffer<uint8_t>(MIDI_BUFFER_SIZE))
41 , m_sysex_buffer(new uint8_t[SYSEX_BUFFER_SIZE])
43 DEBUG_MIDI (string_compose ("Creating midi input device index: %1\n", index));
45 std::string error_msg;
47 if (!open (index, error_msg)) {
48 DEBUG_MIDI (error_msg);
49 throw std::runtime_error (error_msg);
52 // perhaps this should be called in open
53 if (!add_sysex_buffer (error_msg)) {
54 DEBUG_MIDI (error_msg);
55 std::string close_error;
56 if (!close (close_error)) {
57 DEBUG_MIDI (close_error);
59 throw std::runtime_error (error_msg);
62 set_device_name (index);
65 WinMMEMidiInputDevice::~WinMMEMidiInputDevice ()
67 std::string error_msg;
68 if (!close (error_msg)) {
69 DEBUG_MIDI (error_msg);
74 WinMMEMidiInputDevice::open (UINT index, std::string& error_msg)
76 MMRESULT result = midiInOpen (&m_handle,
78 (DWORD_PTR) winmm_input_callback,
80 CALLBACK_FUNCTION | MIDI_IO_STATUS);
81 if (result != MMSYSERR_NOERROR) {
82 error_msg = get_error_string (result);
85 DEBUG_MIDI (string_compose ("Opened MIDI device index %1\n", index));
90 WinMMEMidiInputDevice::close (std::string& error_msg)
92 // return error message for first error encountered?
95 MMRESULT result = midiInReset (m_handle);
96 if (result != MMSYSERR_NOERROR) {
97 error_msg = get_error_string (result);
98 DEBUG_MIDI (error_msg);
101 result = midiInUnprepareHeader (m_handle, &m_sysex_header, sizeof(MIDIHDR));
102 if (result != MMSYSERR_NOERROR) {
103 error_msg = get_error_string (result);
104 DEBUG_MIDI (error_msg);
107 result = midiInClose (m_handle);
108 if (result != MMSYSERR_NOERROR) {
109 error_msg = get_error_string (result);
110 DEBUG_MIDI (error_msg);
115 DEBUG_MIDI (string_compose ("Closed MIDI device: %1\n", name ()));
117 DEBUG_MIDI (string_compose ("Unable to Close MIDI device: %1\n", name ()));
123 WinMMEMidiInputDevice::add_sysex_buffer (std::string& error_msg)
125 m_sysex_header.dwBufferLength = SYSEX_BUFFER_SIZE;
126 m_sysex_header.dwFlags = 0;
127 m_sysex_header.dwBytesRecorded = 0;
128 m_sysex_header.lpData = (LPSTR)m_sysex_buffer.get ();
130 MMRESULT result = midiInPrepareHeader (m_handle, &m_sysex_header, sizeof(MIDIHDR));
132 if (result != MMSYSERR_NOERROR) {
133 error_msg = get_error_string (result);
134 DEBUG_MIDI (error_msg);
137 result = midiInAddBuffer (m_handle, &m_sysex_header, sizeof(MIDIHDR));
138 if (result != MMSYSERR_NOERROR) {
139 error_msg = get_error_string (result);
140 DEBUG_MIDI (error_msg);
143 DEBUG_MIDI ("Added Initial WinMME sysex buffer\n");
149 WinMMEMidiInputDevice::set_device_name (UINT index)
151 MIDIINCAPS capabilities;
152 MMRESULT result = midiInGetDevCaps (index, &capabilities, sizeof(capabilities));
153 if (result != MMSYSERR_NOERROR) {
154 DEBUG_MIDI (get_error_string (result));
155 m_name = "Unknown Midi Input Device";
158 m_name = capabilities.szPname;
164 WinMMEMidiInputDevice::get_error_string (MMRESULT error_code)
166 char error_msg[MAXERRORLENGTH];
167 MMRESULT result = midiInGetErrorText (error_code, error_msg, MAXERRORLENGTH);
168 if (result != MMSYSERR_NOERROR) {
171 return "WinMMEMidiInput: Unknown Error code";
175 WinMMEMidiInputDevice::winmm_input_callback(HMIDIIN handle,
181 WinMMEMidiInputDevice* midi_input = (WinMMEMidiInputDevice*)instance;
183 #ifdef USE_MMCSS_THREAD_PRIORITIES
185 static HANDLE input_thread = GetCurrentThread ();
186 static bool priority_boosted = false;
188 #if 0 // GetThreadId() is Vista or later only.
189 if (input_thread != GetCurrentThread ()) {
190 DWORD otid = GetThreadId (input_thread);
191 DWORD ntid = GetThreadId (GetCurrentThread ());
192 // There was a reference on the internet somewhere that it is possible
193 // for the callback to come from different threads(thread pool) this
194 // could be problematic but I haven't seen this behaviour yet
195 DEBUG_THREADS (string_compose (
196 "WinMME input Thread ID Changed: was %1, now %2\n", otid, ntid));
202 if (!priority_boosted) {
203 PBD::MMCSS::set_thread_characteristics ("Pro Audio", &task_handle);
204 PBD::MMCSS::set_thread_priority (task_handle, PBD::MMCSS::AVRT_PRIORITY_HIGH);
205 priority_boosted = true;
212 DEBUG_MIDI("WinMME: devices changed callback\n");
213 // devices_changed_callback
216 DEBUG_MIDI("WinMME: more data ..\n");
217 // passing MIDI_IO_STATUS to midiInOpen means that MIM_MOREDATA
218 // will be sent when the callback isn't processing MIM_DATA messages
219 // fast enough to keep up with messages arriving at input device
220 // driver. I'm not sure what could be done differently if that occurs
221 // so just handle MIM_DATA as per normal
223 DEBUG_MIDI(string_compose ("WinMME: short msg @ %1\n", (uint32_t) timestamp));
224 midi_input->handle_short_msg ((const uint8_t*)&midi_msg, (uint32_t)timestamp);
227 DEBUG_MIDI(string_compose ("WinMME: long msg @ %1\n", (uint32_t) timestamp));
228 midi_input->handle_sysex_msg ((MIDIHDR*)midi_msg, (uint32_t)timestamp);
231 DEBUG_MIDI ("WinMME: Driver sent an invalid MIDI message\n");
234 DEBUG_MIDI ("WinMME: Driver sent an invalid or incomplete SYSEX message\n");
237 DEBUG_MIDI ("WinMME: Driver sent an unknown message\n");
243 WinMMEMidiInputDevice::handle_short_msg (const uint8_t* midi_data,
246 int length = get_midi_msg_length (midi_data[0]);
248 if (length == 0 || length == -1) {
249 DEBUG_MIDI ("ERROR: midi input driver sent an invalid midi message\n");
253 enqueue_midi_msg (midi_data, length, timestamp);
257 WinMMEMidiInputDevice::handle_sysex_msg (MIDIHDR* const midi_header,
260 size_t byte_count = midi_header->dwBytesRecorded;
262 if (byte_count == 0) {
263 if ((midi_header->dwFlags & WHDR_DONE) != 0) {
264 DEBUG_MIDI("WinMME: In midi reset\n");
265 // unprepare handled by close
268 "ERROR: WinMME driver has returned sysex header to us with no bytes\n");
273 uint8_t* data = (uint8_t*)midi_header->lpData;
275 DEBUG_MIDI(string_compose("WinMME sysex flags: %1\n", midi_header->dwFlags));
277 if ((data[0] != 0xf0) || (data[byte_count - 1] != 0xf7)) {
278 DEBUG_MIDI(string_compose("Discarding %1 byte sysex chunk\n", byte_count));
280 enqueue_midi_msg (data, byte_count, timestamp);
283 DEBUG_MIDI("Adding sysex buffer back to WinMME buffer pool\n");
285 midi_header->dwFlags = 0;
286 midi_header->dwBytesRecorded = 0;
288 MMRESULT result = midiInPrepareHeader(m_handle, midi_header, sizeof(MIDIHDR));
290 if (result != MMSYSERR_NOERROR) {
291 DEBUG_MIDI(string_compose("Unable to prepare header: %1\n",
292 get_error_string(result)));
296 result = midiInAddBuffer(m_handle, midi_header, sizeof(MIDIHDR));
297 if (result != MMSYSERR_NOERROR) {
298 DEBUG_MIDI(string_compose("Unable to add sysex buffer to buffer pool : %1\n",
299 get_error_string(result)));
305 WinMMEMidiInputDevice::dequeue_midi_event (uint64_t timestamp_start,
306 uint64_t timestamp_end,
311 const uint32_t read_space = m_midi_buffer->read_space();
312 struct MidiEventHeader h(0,0);
314 if (read_space <= sizeof(MidiEventHeader)) {
318 RingBuffer<uint8_t>::rw_vector vector;
319 m_midi_buffer->get_read_vector (&vector);
320 if (vector.len[0] >= sizeof(MidiEventHeader)) {
321 memcpy ((uint8_t*)&h, vector.buf[0], sizeof(MidiEventHeader));
323 if (vector.len[0] > 0) {
324 memcpy ((uint8_t*)&h, vector.buf[0], vector.len[0]);
326 assert (vector.buf[1] || vector.len[0] == sizeof(MidiEventHeader));
327 memcpy (((uint8_t*)&h) + vector.len[0],
329 sizeof(MidiEventHeader) - vector.len[0]);
332 if (h.time >= timestamp_end) {
333 DEBUG_TIMING (string_compose ("WinMMEMidiInput EVENT %1(ms) early\n",
334 (h.time - timestamp_end) * 1e-3));
336 } else if (h.time < timestamp_start) {
337 DEBUG_TIMING (string_compose ("WinMMEMidiInput EVENT %1(ms) late\n",
338 (timestamp_start - h.time) * 1e-3));
341 m_midi_buffer->increment_read_idx (sizeof(MidiEventHeader));
344 if (h.size > data_size) {
345 DEBUG_MIDI ("WinMMEMidiInput::dequeue_event MIDI event too large!\n");
346 m_midi_buffer->increment_read_idx (h.size);
349 if (m_midi_buffer->read (&midi_data[0], h.size) != h.size) {
350 DEBUG_MIDI ("WinMMEMidiInput::dequeue_event Garbled MIDI EVENT DATA!!\n");
359 WinMMEMidiInputDevice::enqueue_midi_msg (const uint8_t* midi_data,
363 const uint32_t total_size = sizeof(MidiEventHeader) + data_size;
365 if (data_size == 0) {
366 DEBUG_MIDI ("ERROR: zero length midi data\n");
370 if (m_midi_buffer->write_space () < total_size) {
371 DEBUG_MIDI ("WinMMEMidiInput: ring buffer overflow\n");
375 // don't use winmme timestamps for now
376 uint64_t ts = PBD::get_microseconds ();
378 DEBUG_TIMING (string_compose (
379 "Enqueing MIDI data device: %1 with timestamp: %2 and size %3\n",
384 struct MidiEventHeader h (ts, data_size);
385 m_midi_buffer->write ((uint8_t*)&h, sizeof(MidiEventHeader));
386 m_midi_buffer->write (midi_data, data_size);
391 WinMMEMidiInputDevice::start ()
394 MMRESULT result = midiInStart (m_handle);
395 m_started = (result == MMSYSERR_NOERROR);
397 DEBUG_MIDI (get_error_string (result));
400 string_compose ("WinMMEMidiInput: device %1 started\n", name ()));
407 WinMMEMidiInputDevice::stop ()
410 MMRESULT result = midiInStop (m_handle);
411 m_started = (result != MMSYSERR_NOERROR);
413 DEBUG_MIDI (get_error_string (result));
416 string_compose ("WinMMEMidiInput: device %1 stopped\n", name ()));
422 } // namespace ARDOUR