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"
26 #include "win_utils.h"
27 #include "midi_util.h"
31 static const uint32_t MIDI_BUFFER_SIZE = 32768;
32 static const uint32_t SYSEX_BUFFER_SIZE = 32768;
36 WinMMEMidiInputDevice::WinMMEMidiInputDevice (int index)
38 , m_midi_buffer(new RingBuffer<uint8_t>(MIDI_BUFFER_SIZE))
39 , m_sysex_buffer(new uint8_t[SYSEX_BUFFER_SIZE])
41 DEBUG_MIDI (string_compose ("Creating midi input device index: %1\n", index));
43 std::string error_msg;
45 if (!open (index, error_msg)) {
46 DEBUG_MIDI (error_msg);
47 throw std::runtime_error (error_msg);
50 // perhaps this should be called in open
51 if (!add_sysex_buffer (error_msg)) {
52 DEBUG_MIDI (error_msg);
53 std::string close_error;
54 if (!close (close_error)) {
55 DEBUG_MIDI (close_error);
57 throw std::runtime_error (error_msg);
60 set_device_name (index);
63 WinMMEMidiInputDevice::~WinMMEMidiInputDevice ()
65 std::string error_msg;
66 if (!close (error_msg)) {
67 DEBUG_MIDI (error_msg);
72 WinMMEMidiInputDevice::open (UINT index, std::string& error_msg)
74 MMRESULT result = midiInOpen (&m_handle,
76 (DWORD_PTR) winmm_input_callback,
78 CALLBACK_FUNCTION | MIDI_IO_STATUS);
79 if (result != MMSYSERR_NOERROR) {
80 error_msg = get_error_string (result);
83 DEBUG_MIDI (string_compose ("Opened MIDI device index %1\n", index));
88 WinMMEMidiInputDevice::close (std::string& error_msg)
90 // return error message for first error encountered?
93 MMRESULT result = midiInReset (m_handle);
94 if (result != MMSYSERR_NOERROR) {
95 error_msg = get_error_string (result);
96 DEBUG_MIDI (error_msg);
99 result = midiInUnprepareHeader (m_handle, &m_sysex_header, sizeof(MIDIHDR));
100 if (result != MMSYSERR_NOERROR) {
101 error_msg = get_error_string (result);
102 DEBUG_MIDI (error_msg);
105 result = midiInClose (m_handle);
106 if (result != MMSYSERR_NOERROR) {
107 error_msg = get_error_string (result);
108 DEBUG_MIDI (error_msg);
113 DEBUG_MIDI (string_compose ("Closed MIDI device: %1\n", name ()));
115 DEBUG_MIDI (string_compose ("Unable to Close MIDI device: %1\n", name ()));
121 WinMMEMidiInputDevice::add_sysex_buffer (std::string& error_msg)
123 m_sysex_header.dwBufferLength = SYSEX_BUFFER_SIZE;
124 m_sysex_header.dwFlags = 0;
125 m_sysex_header.lpData = (LPSTR)m_sysex_buffer.get ();
127 MMRESULT result = midiInPrepareHeader (m_handle, &m_sysex_header, sizeof(MIDIHDR));
129 if (result != MMSYSERR_NOERROR) {
130 error_msg = get_error_string (result);
131 DEBUG_MIDI (error_msg);
134 result = midiInAddBuffer (m_handle, &m_sysex_header, sizeof(MIDIHDR));
135 if (result != MMSYSERR_NOERROR) {
136 error_msg = get_error_string (result);
137 DEBUG_MIDI (error_msg);
144 WinMMEMidiInputDevice::set_device_name (UINT index)
146 MIDIINCAPS capabilities;
147 MMRESULT result = midiInGetDevCaps (index, &capabilities, sizeof(capabilities));
148 if (result != MMSYSERR_NOERROR) {
149 DEBUG_MIDI (get_error_string (result));
150 m_name = "Unknown Midi Input Device";
153 m_name = capabilities.szPname;
159 WinMMEMidiInputDevice::get_error_string (MMRESULT error_code)
161 char error_msg[MAXERRORLENGTH];
162 MMRESULT result = midiInGetErrorText (error_code, error_msg, MAXERRORLENGTH);
163 if (result != MMSYSERR_NOERROR) {
166 return "WinMMEMidiInput: Unknown Error code";
170 WinMMEMidiInputDevice::winmm_input_callback(HMIDIIN handle,
176 WinMMEMidiInputDevice* midi_input = (WinMMEMidiInputDevice*)instance;
181 // devices_changed_callback
184 // passing MIDI_IO_STATUS to midiInOpen means that MIM_MOREDATA
185 // will be sent when the callback isn't processing MIM_DATA messages
186 // fast enough to keep up with messages arriving at input device
187 // driver. I'm not sure what could be done differently if that occurs
188 // so just handle MIM_DATA as per normal
190 midi_input->handle_short_msg ((const uint8_t*)&midi_msg, (uint32_t)timestamp);
193 midi_input->handle_sysex_msg ((MIDIHDR*)&midi_msg, (uint32_t)timestamp);
196 DEBUG_MIDI ("WinMME: Driver sent an invalid MIDI message\n");
199 DEBUG_MIDI ("WinMME: Driver sent an invalid or incomplete SYSEX message\n");
205 WinMMEMidiInputDevice::handle_short_msg (const uint8_t* midi_data,
208 int length = get_midi_msg_length (midi_data[0]);
210 if (length == 0 || length == -1) {
211 DEBUG_MIDI ("ERROR: midi input driver sent an invalid midi message\n");
215 enqueue_midi_msg (midi_data, length, timestamp);
219 WinMMEMidiInputDevice::handle_sysex_msg (MIDIHDR* const midi_header,
223 LPMIDIHDR header = (LPMIDIHDR)midi_header;
224 size_t byte_count = header->dwBytesRecorded;
228 "ERROR: WinMME driver has returned sysex header to us with no bytes\n");
232 uint8_t* data = (uint8_t*)header->lpData;
234 if ((data[0] != 0xf0) || (data[byte_count - 1] != 0xf7)) {
235 DEBUG_MIDI (string_compose ("Discarding %1 byte sysex chunk\n", byte_count));
237 enqueue_midi_msg (data, byte_count, timestamp);
240 MMRESULT result = midiInAddBuffer (m_handle, &m_sysex_header, sizeof(MIDIHDR));
241 if (result != MMSYSERR_NOERROR) {
242 DEBUG_MIDI (get_error_string (result));
249 WinMMEMidiInputDevice::dequeue_midi_event (uint64_t timestamp_start,
250 uint64_t timestamp_end,
255 const uint32_t read_space = m_midi_buffer->read_space();
256 struct MidiEventHeader h(0,0);
258 if (read_space <= sizeof(MidiEventHeader)) {
262 RingBuffer<uint8_t>::rw_vector vector;
263 m_midi_buffer->get_read_vector (&vector);
264 if (vector.len[0] >= sizeof(MidiEventHeader)) {
265 memcpy ((uint8_t*)&h, vector.buf[0], sizeof(MidiEventHeader));
267 if (vector.len[0] > 0) {
268 memcpy ((uint8_t*)&h, vector.buf[0], vector.len[0]);
270 assert (vector.buf[1] || vector.len[0] == sizeof(MidiEventHeader));
271 memcpy (((uint8_t*)&h) + vector.len[0],
273 sizeof(MidiEventHeader) - vector.len[0]);
276 if (h.time >= timestamp_end) {
277 DEBUG_TIMING (string_compose ("WinMMEMidiInput EVENT %1(ms) early\n",
278 (h.time - timestamp_end) * 1e-3));
280 } else if (h.time < timestamp_start) {
281 DEBUG_TIMING (string_compose ("WinMMEMidiInput EVENT %1(ms) late\n",
282 (timestamp_start - h.time) * 1e-3));
285 m_midi_buffer->increment_read_idx (sizeof(MidiEventHeader));
288 if (h.size > data_size) {
289 DEBUG_MIDI ("WinMMEMidiInput::dequeue_event MIDI event too large!\n");
290 m_midi_buffer->increment_read_idx (h.size);
293 if (m_midi_buffer->read (&midi_data[0], h.size) != h.size) {
294 DEBUG_MIDI ("WinMMEMidiInput::dequeue_event Garbled MIDI EVENT DATA!!\n");
303 WinMMEMidiInputDevice::enqueue_midi_msg (const uint8_t* midi_data,
307 const uint32_t total_size = sizeof(MidiEventHeader) + data_size;
309 if (data_size == 0) {
310 DEBUG_MIDI ("ERROR: zero length midi data\n");
314 if (m_midi_buffer->write_space () < total_size) {
315 DEBUG_MIDI ("WinMMEMidiInput: ring buffer overflow\n");
319 // don't use winmme timestamps for now
320 uint64_t ts = utils::get_microseconds ();
322 DEBUG_TIMING (string_compose (
323 "Enqueing MIDI data device: %1 with timestamp: %2 and size %3\n",
328 struct MidiEventHeader h (ts, data_size);
329 m_midi_buffer->write ((uint8_t*)&h, sizeof(MidiEventHeader));
330 m_midi_buffer->write (midi_data, data_size);
335 WinMMEMidiInputDevice::start ()
338 MMRESULT result = midiInStart (m_handle);
339 m_started = (result == MMSYSERR_NOERROR);
341 DEBUG_MIDI (get_error_string (result));
344 string_compose ("WinMMEMidiInput: device %1 started\n", name ()));
351 WinMMEMidiInputDevice::stop ()
354 MMRESULT result = midiInStop (m_handle);
355 m_started = (result != MMSYSERR_NOERROR);
357 DEBUG_MIDI (get_error_string (result));
360 string_compose ("WinMMEMidiInput: device %1 stopped\n", name ()));
366 } // namespace ARDOUR