WinMME based midi input/output for portaudio backend
[ardour.git] / libs / backends / portaudio / winmmemidi_input_device.cc
1 /*
2  * Copyright (C) 2015 Tim Mayberry <mojofunk@gmail.com>
3  *
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.
8  *
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.
13  *
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.
17  */
18
19 #include "winmmemidi_input_device.h"
20
21 #include <stdexcept>
22 #include <cmath>
23
24 #include "pbd/compose.h"
25
26 #include "win_utils.h"
27 #include "midi_util.h"
28
29 #include "debug.h"
30
31 static const uint32_t MIDI_BUFFER_SIZE = 32768;
32 static const uint32_t SYSEX_BUFFER_SIZE = 32768;
33
34 namespace ARDOUR {
35
36 WinMMEMidiInputDevice::WinMMEMidiInputDevice (int index)
37         : m_handle(0)
38         , m_midi_buffer(new RingBuffer<uint8_t>(MIDI_BUFFER_SIZE))
39         , m_sysex_buffer(new uint8_t[SYSEX_BUFFER_SIZE])
40 {
41         DEBUG_MIDI (string_compose ("Creating midi input device index: %1\n", index));
42
43         std::string error_msg;
44
45         if (!open (index, error_msg)) {
46                 DEBUG_MIDI (error_msg);
47                 throw std::runtime_error (error_msg);
48         }
49
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);
56                 }
57                 throw std::runtime_error (error_msg);
58         }
59
60         set_device_name (index);
61 }
62
63 WinMMEMidiInputDevice::~WinMMEMidiInputDevice ()
64 {
65         std::string error_msg;
66         if (!close (error_msg)) {
67                 DEBUG_MIDI (error_msg);
68         }
69 }
70
71 bool
72 WinMMEMidiInputDevice::open (UINT index, std::string& error_msg)
73 {
74         MMRESULT result = midiInOpen (&m_handle,
75                                       index,
76                                       (DWORD_PTR) winmm_input_callback,
77                                       (DWORD_PTR) this,
78                                       CALLBACK_FUNCTION | MIDI_IO_STATUS);
79         if (result != MMSYSERR_NOERROR) {
80                 error_msg = get_error_string (result);
81                 return false;
82         }
83         DEBUG_MIDI (string_compose ("Opened MIDI device index %1\n", index));
84         return true;
85 }
86
87 bool
88 WinMMEMidiInputDevice::close (std::string& error_msg)
89 {
90         // return error message for first error encountered?
91         bool success = true;
92
93         MMRESULT result = midiInReset (m_handle);
94         if (result != MMSYSERR_NOERROR) {
95                 error_msg = get_error_string (result);
96                 DEBUG_MIDI (error_msg);
97                 success = false;
98         }
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);
103                 success = false;
104         }
105         result = midiInClose (m_handle);
106         if (result != MMSYSERR_NOERROR) {
107                 error_msg = get_error_string (result);
108                 DEBUG_MIDI (error_msg);
109                 success = false;
110         }
111         m_handle = 0;
112         if (success) {
113                 DEBUG_MIDI (string_compose ("Closed MIDI device: %1\n", name ()));
114         } else {
115                 DEBUG_MIDI (string_compose ("Unable to Close MIDI device: %1\n", name ()));
116         }
117         return success;
118 }
119
120 bool
121 WinMMEMidiInputDevice::add_sysex_buffer (std::string& error_msg)
122 {
123         m_sysex_header.dwBufferLength = SYSEX_BUFFER_SIZE;
124         m_sysex_header.dwFlags = 0;
125         m_sysex_header.lpData = (LPSTR)m_sysex_buffer.get ();
126
127         MMRESULT result = midiInPrepareHeader (m_handle, &m_sysex_header, sizeof(MIDIHDR));
128
129         if (result != MMSYSERR_NOERROR) {
130                 error_msg = get_error_string (result);
131                 DEBUG_MIDI (error_msg);
132                 return false;
133         }
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);
138                 return false;
139         }
140         return true;
141 }
142
143 bool
144 WinMMEMidiInputDevice::set_device_name (UINT index)
145 {
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";
151                 return false;
152         } else {
153                 m_name = capabilities.szPname;
154         }
155         return true;
156 }
157
158 std::string
159 WinMMEMidiInputDevice::get_error_string (MMRESULT error_code)
160 {
161         char error_msg[MAXERRORLENGTH];
162         MMRESULT result = midiInGetErrorText (error_code, error_msg, MAXERRORLENGTH);
163         if (result != MMSYSERR_NOERROR) {
164                 return error_msg;
165         }
166         return "WinMMEMidiInput: Unknown Error code";
167 }
168
169 void CALLBACK
170 WinMMEMidiInputDevice::winmm_input_callback(HMIDIIN handle,
171                                             UINT msg,
172                                             DWORD_PTR instance,
173                                             DWORD_PTR midi_msg,
174                                             DWORD timestamp)
175 {
176         WinMMEMidiInputDevice* midi_input = (WinMMEMidiInputDevice*)instance;
177
178         switch (msg) {
179         case MIM_OPEN:
180         case MIM_CLOSE:
181                 // devices_changed_callback
182                 break;
183         case MIM_MOREDATA:
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
189         case MIM_DATA:
190                 midi_input->handle_short_msg ((const uint8_t*)&midi_msg, (uint32_t)timestamp);
191                 break;
192         case MIM_LONGDATA:
193                 midi_input->handle_sysex_msg ((MIDIHDR*)&midi_msg, (uint32_t)timestamp);
194                 break;
195         case MIM_ERROR:
196                 DEBUG_MIDI ("WinMME: Driver sent an invalid MIDI message\n");
197                 break;
198         case MIM_LONGERROR:
199                 DEBUG_MIDI ("WinMME: Driver sent an invalid or incomplete SYSEX message\n");
200                 break;
201         }
202 }
203
204 void
205 WinMMEMidiInputDevice::handle_short_msg (const uint8_t* midi_data,
206                                          uint32_t timestamp)
207 {
208         int length = get_midi_msg_length (midi_data[0]);
209
210         if (length == 0 || length == -1) {
211                 DEBUG_MIDI ("ERROR: midi input driver sent an invalid midi message\n");
212                 return;
213         }
214
215         enqueue_midi_msg (midi_data, length, timestamp);
216 }
217
218 void
219 WinMMEMidiInputDevice::handle_sysex_msg (MIDIHDR* const midi_header,
220                                          uint32_t timestamp)
221 {
222 #ifdef ENABLE_SYSEX
223         LPMIDIHDR header = (LPMIDIHDR)midi_header;
224         size_t byte_count = header->dwBytesRecorded;
225
226         if (!byte_count) {
227                 DEBUG_MIDI (
228                     "ERROR: WinMME driver has returned sysex header to us with no bytes\n");
229                 return;
230         }
231
232         uint8_t* data = (uint8_t*)header->lpData;
233
234         if ((data[0] != 0xf0) || (data[byte_count - 1] != 0xf7)) {
235                 DEBUG_MIDI (string_compose ("Discarding %1 byte sysex chunk\n", byte_count));
236         } else {
237                 enqueue_midi_msg (data, byte_count, timestamp);
238         }
239
240         MMRESULT result = midiInAddBuffer (m_handle, &m_sysex_header, sizeof(MIDIHDR));
241         if (result != MMSYSERR_NOERROR) {
242                 DEBUG_MIDI (get_error_string (result));
243         }
244 #endif
245 }
246
247 // fix param order
248 bool
249 WinMMEMidiInputDevice::dequeue_midi_event (uint64_t timestamp_start,
250                                            uint64_t timestamp_end,
251                                            uint64_t& timestamp,
252                                            uint8_t* midi_data,
253                                            size_t& data_size)
254 {
255         const uint32_t read_space = m_midi_buffer->read_space();
256         struct MidiEventHeader h(0,0);
257
258         if (read_space <= sizeof(MidiEventHeader)) {
259                 return false;
260         }
261
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));
266         } else {
267                 if (vector.len[0] > 0) {
268                         memcpy ((uint8_t*)&h, vector.buf[0], vector.len[0]);
269                 }
270                 assert (vector.buf[1] || vector.len[0] == sizeof(MidiEventHeader));
271                 memcpy (((uint8_t*)&h) + vector.len[0],
272                         vector.buf[1],
273                         sizeof(MidiEventHeader) - vector.len[0]);
274         }
275
276         if (h.time >= timestamp_end) {
277                 DEBUG_TIMING (string_compose ("WinMMEMidiInput EVENT %1(ms) early\n",
278                                               (h.time - timestamp_end) * 1e-3));
279                 return false;
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));
283         }
284
285         m_midi_buffer->increment_read_idx (sizeof(MidiEventHeader));
286
287         assert (h.size > 0);
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);
291                 return false;
292         }
293         if (m_midi_buffer->read (&midi_data[0], h.size) != h.size) {
294                 DEBUG_MIDI ("WinMMEMidiInput::dequeue_event Garbled MIDI EVENT DATA!!\n");
295                 return false;
296         }
297         timestamp = h.time;
298         data_size = h.size;
299         return true;
300 }
301
302 bool
303 WinMMEMidiInputDevice::enqueue_midi_msg (const uint8_t* midi_data,
304                                          size_t data_size,
305                                          uint32_t timestamp)
306 {
307         const uint32_t total_size = sizeof(MidiEventHeader) + data_size;
308
309         if (data_size == 0) {
310                 DEBUG_MIDI ("ERROR: zero length midi data\n");
311                 return false;
312         }
313
314         if (m_midi_buffer->write_space () < total_size) {
315                 DEBUG_MIDI ("WinMMEMidiInput: ring buffer overflow\n");
316                 return false;
317         }
318
319         // don't use winmme timestamps for now
320         uint64_t ts = utils::get_microseconds ();
321
322         DEBUG_TIMING (string_compose (
323             "Enqueing MIDI data device: %1 with timestamp: %2 and size %3\n",
324             name (),
325             ts,
326             data_size));
327
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);
331         return true;
332 }
333
334 bool
335 WinMMEMidiInputDevice::start ()
336 {
337         if (!m_started) {
338                 MMRESULT result = midiInStart (m_handle);
339                 m_started = (result == MMSYSERR_NOERROR);
340                 if (!m_started) {
341                         DEBUG_MIDI (get_error_string (result));
342                 } else {
343                         DEBUG_MIDI (
344                             string_compose ("WinMMEMidiInput: device %1 started\n", name ()));
345                 }
346         }
347         return m_started;
348 }
349
350 bool
351 WinMMEMidiInputDevice::stop ()
352 {
353         if (m_started) {
354                 MMRESULT result = midiInStop (m_handle);
355                 m_started = (result != MMSYSERR_NOERROR);
356                 if (m_started) {
357                         DEBUG_MIDI (get_error_string (result));
358                 } else {
359                         DEBUG_MIDI (
360                             string_compose ("WinMMEMidiInput: device %1 stopped\n", name ()));
361                 }
362         }
363         return !m_started;
364 }
365
366 } // namespace ARDOUR