Fix build on El Capitan
[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 #include "pbd/windows_timer_utils.h"
26 #include "pbd/windows_mmcss.h"
27
28 #include "midi_util.h"
29
30 #include "debug.h"
31
32 static const uint32_t MIDI_BUFFER_SIZE = 32768;
33 static const uint32_t SYSEX_BUFFER_SIZE = 32768;
34
35 namespace ARDOUR {
36
37 WinMMEMidiInputDevice::WinMMEMidiInputDevice (int index)
38         : m_handle(0)
39         , m_started(false)
40         , m_midi_buffer(new RingBuffer<uint8_t>(MIDI_BUFFER_SIZE))
41         , m_sysex_buffer(new uint8_t[SYSEX_BUFFER_SIZE])
42 {
43         DEBUG_MIDI (string_compose ("Creating midi input device index: %1\n", index));
44
45         std::string error_msg;
46
47         if (!open (index, error_msg)) {
48                 DEBUG_MIDI (error_msg);
49                 throw std::runtime_error (error_msg);
50         }
51
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);
58                 }
59                 throw std::runtime_error (error_msg);
60         }
61
62         set_device_name (index);
63 }
64
65 WinMMEMidiInputDevice::~WinMMEMidiInputDevice ()
66 {
67         std::string error_msg;
68         if (!close (error_msg)) {
69                 DEBUG_MIDI (error_msg);
70         }
71 }
72
73 bool
74 WinMMEMidiInputDevice::open (UINT index, std::string& error_msg)
75 {
76         MMRESULT result = midiInOpen (&m_handle,
77                                       index,
78                                       (DWORD_PTR) winmm_input_callback,
79                                       (DWORD_PTR) this,
80                                       CALLBACK_FUNCTION | MIDI_IO_STATUS);
81         if (result != MMSYSERR_NOERROR) {
82                 error_msg = get_error_string (result);
83                 return false;
84         }
85         DEBUG_MIDI (string_compose ("Opened MIDI device index %1\n", index));
86         return true;
87 }
88
89 bool
90 WinMMEMidiInputDevice::close (std::string& error_msg)
91 {
92         // return error message for first error encountered?
93         bool success = true;
94
95         MMRESULT result = midiInReset (m_handle);
96         if (result != MMSYSERR_NOERROR) {
97                 error_msg = get_error_string (result);
98                 DEBUG_MIDI (error_msg);
99                 success = false;
100         }
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);
105                 success = false;
106         }
107         result = midiInClose (m_handle);
108         if (result != MMSYSERR_NOERROR) {
109                 error_msg = get_error_string (result);
110                 DEBUG_MIDI (error_msg);
111                 success = false;
112         }
113         m_handle = 0;
114         if (success) {
115                 DEBUG_MIDI (string_compose ("Closed MIDI device: %1\n", name ()));
116         } else {
117                 DEBUG_MIDI (string_compose ("Unable to Close MIDI device: %1\n", name ()));
118         }
119         return success;
120 }
121
122 bool
123 WinMMEMidiInputDevice::add_sysex_buffer (std::string& error_msg)
124 {
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 ();
129
130         MMRESULT result = midiInPrepareHeader (m_handle, &m_sysex_header, sizeof(MIDIHDR));
131
132         if (result != MMSYSERR_NOERROR) {
133                 error_msg = get_error_string (result);
134                 DEBUG_MIDI (error_msg);
135                 return false;
136         }
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);
141                 return false;
142         } else {
143                 DEBUG_MIDI ("Added Initial WinMME sysex buffer\n");
144         }
145         return true;
146 }
147
148 bool
149 WinMMEMidiInputDevice::set_device_name (UINT index)
150 {
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";
156                 return false;
157         } else {
158                 m_name = capabilities.szPname;
159         }
160         return true;
161 }
162
163 std::string
164 WinMMEMidiInputDevice::get_error_string (MMRESULT error_code)
165 {
166         char error_msg[MAXERRORLENGTH];
167         MMRESULT result = midiInGetErrorText (error_code, error_msg, MAXERRORLENGTH);
168         if (result != MMSYSERR_NOERROR) {
169                 return error_msg;
170         }
171         return "WinMMEMidiInput: Unknown Error code";
172 }
173
174 void CALLBACK
175 WinMMEMidiInputDevice::winmm_input_callback(HMIDIIN handle,
176                                             UINT msg,
177                                             DWORD_PTR instance,
178                                             DWORD_PTR midi_msg,
179                                             DWORD timestamp)
180 {
181         WinMMEMidiInputDevice* midi_input = (WinMMEMidiInputDevice*)instance;
182
183 #ifdef USE_MMCSS_THREAD_PRIORITIES
184
185         static HANDLE input_thread = GetCurrentThread ();
186         static bool priority_boosted = false;
187
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));
197         }
198 #endif
199
200         HANDLE task_handle;
201
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;
206         }
207 #endif
208
209         switch (msg) {
210         case MIM_OPEN:
211         case MIM_CLOSE:
212                 DEBUG_MIDI("WinMME: devices changed callback\n");
213                 // devices_changed_callback
214                 break;
215         case MIM_MOREDATA:
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
222         case MIM_DATA:
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);
225                 break;
226         case MIM_LONGDATA:
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);
229                 break;
230         case MIM_ERROR:
231                 DEBUG_MIDI ("WinMME: Driver sent an invalid MIDI message\n");
232                 break;
233         case MIM_LONGERROR:
234                 DEBUG_MIDI ("WinMME: Driver sent an invalid or incomplete SYSEX message\n");
235                 break;
236         default:
237                 DEBUG_MIDI ("WinMME: Driver sent an unknown message\n");
238                 break;
239         }
240 }
241
242 void
243 WinMMEMidiInputDevice::handle_short_msg (const uint8_t* midi_data,
244                                          uint32_t timestamp)
245 {
246         int length = get_midi_msg_length (midi_data[0]);
247
248         if (length == 0 || length == -1) {
249                 DEBUG_MIDI ("ERROR: midi input driver sent an invalid midi message\n");
250                 return;
251         }
252
253         enqueue_midi_msg (midi_data, length, timestamp);
254 }
255
256 void
257 WinMMEMidiInputDevice::handle_sysex_msg (MIDIHDR* const midi_header,
258                                          uint32_t timestamp)
259 {
260         size_t byte_count = midi_header->dwBytesRecorded;
261
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
266                 } else {
267                         DEBUG_MIDI(
268                             "ERROR: WinMME driver has returned sysex header to us with no bytes\n");
269                 }
270                 return;
271         }
272
273         uint8_t* data = (uint8_t*)midi_header->lpData;
274
275         DEBUG_MIDI(string_compose("WinMME sysex flags: %1\n", midi_header->dwFlags));
276
277         if ((data[0] != 0xf0) || (data[byte_count - 1] != 0xf7)) {
278                 DEBUG_MIDI(string_compose("Discarding %1 byte sysex chunk\n", byte_count));
279         } else {
280                 enqueue_midi_msg (data, byte_count, timestamp);
281         }
282
283         DEBUG_MIDI("Adding sysex buffer back to WinMME buffer pool\n");
284
285         midi_header->dwFlags = 0;
286         midi_header->dwBytesRecorded = 0;
287
288         MMRESULT result = midiInPrepareHeader(m_handle, midi_header, sizeof(MIDIHDR));
289
290         if (result != MMSYSERR_NOERROR) {
291                 DEBUG_MIDI(string_compose("Unable to prepare header: %1\n",
292                                           get_error_string(result)));
293                 return;
294         }
295
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)));
300         }
301 }
302
303 // fix param order
304 bool
305 WinMMEMidiInputDevice::dequeue_midi_event (uint64_t timestamp_start,
306                                            uint64_t timestamp_end,
307                                            uint64_t& timestamp,
308                                            uint8_t* midi_data,
309                                            size_t& data_size)
310 {
311         const uint32_t read_space = m_midi_buffer->read_space();
312         struct MidiEventHeader h(0,0);
313
314         if (read_space <= sizeof(MidiEventHeader)) {
315                 return false;
316         }
317
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));
322         } else {
323                 if (vector.len[0] > 0) {
324                         memcpy ((uint8_t*)&h, vector.buf[0], vector.len[0]);
325                 }
326                 assert (vector.buf[1] || vector.len[0] == sizeof(MidiEventHeader));
327                 memcpy (((uint8_t*)&h) + vector.len[0],
328                         vector.buf[1],
329                         sizeof(MidiEventHeader) - vector.len[0]);
330         }
331
332         if (h.time >= timestamp_end) {
333                 DEBUG_TIMING (string_compose ("WinMMEMidiInput EVENT %1(ms) early\n",
334                                               (h.time - timestamp_end) * 1e-3));
335                 return false;
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));
339         }
340
341         m_midi_buffer->increment_read_idx (sizeof(MidiEventHeader));
342
343         assert (h.size > 0);
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);
347                 return false;
348         }
349         if (m_midi_buffer->read (&midi_data[0], h.size) != h.size) {
350                 DEBUG_MIDI ("WinMMEMidiInput::dequeue_event Garbled MIDI EVENT DATA!!\n");
351                 return false;
352         }
353         timestamp = h.time;
354         data_size = h.size;
355         return true;
356 }
357
358 bool
359 WinMMEMidiInputDevice::enqueue_midi_msg (const uint8_t* midi_data,
360                                          size_t data_size,
361                                          uint32_t timestamp)
362 {
363         const uint32_t total_size = sizeof(MidiEventHeader) + data_size;
364
365         if (data_size == 0) {
366                 DEBUG_MIDI ("ERROR: zero length midi data\n");
367                 return false;
368         }
369
370         if (m_midi_buffer->write_space () < total_size) {
371                 DEBUG_MIDI ("WinMMEMidiInput: ring buffer overflow\n");
372                 return false;
373         }
374
375         // don't use winmme timestamps for now
376         uint64_t ts = PBD::get_microseconds ();
377
378         DEBUG_TIMING (string_compose (
379             "Enqueing MIDI data device: %1 with timestamp: %2 and size %3\n",
380             name (),
381             ts,
382             data_size));
383
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);
387         return true;
388 }
389
390 bool
391 WinMMEMidiInputDevice::start ()
392 {
393         if (!m_started) {
394                 MMRESULT result = midiInStart (m_handle);
395                 m_started = (result == MMSYSERR_NOERROR);
396                 if (!m_started) {
397                         DEBUG_MIDI (get_error_string (result));
398                 } else {
399                         DEBUG_MIDI (
400                             string_compose ("WinMMEMidiInput: device %1 started\n", name ()));
401                 }
402         }
403         return m_started;
404 }
405
406 bool
407 WinMMEMidiInputDevice::stop ()
408 {
409         if (m_started) {
410                 MMRESULT result = midiInStop (m_handle);
411                 m_started = (result != MMSYSERR_NOERROR);
412                 if (m_started) {
413                         DEBUG_MIDI (get_error_string (result));
414                 } else {
415                         DEBUG_MIDI (
416                             string_compose ("WinMMEMidiInput: device %1 stopped\n", name ()));
417                 }
418         }
419         return !m_started;
420 }
421
422 } // namespace ARDOUR