9fcee83efb4aa9dec47926bd1068f693f47f1b31
[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.lpData = (LPSTR)m_sysex_buffer.get ();
128
129         MMRESULT result = midiInPrepareHeader (m_handle, &m_sysex_header, sizeof(MIDIHDR));
130
131         if (result != MMSYSERR_NOERROR) {
132                 error_msg = get_error_string (result);
133                 DEBUG_MIDI (error_msg);
134                 return false;
135         }
136         result = midiInAddBuffer (m_handle, &m_sysex_header, sizeof(MIDIHDR));
137         if (result != MMSYSERR_NOERROR) {
138                 error_msg = get_error_string (result);
139                 DEBUG_MIDI (error_msg);
140                 return false;
141         }
142         return true;
143 }
144
145 bool
146 WinMMEMidiInputDevice::set_device_name (UINT index)
147 {
148         MIDIINCAPS capabilities;
149         MMRESULT result = midiInGetDevCaps (index, &capabilities, sizeof(capabilities));
150         if (result != MMSYSERR_NOERROR) {
151                 DEBUG_MIDI (get_error_string (result));
152                 m_name = "Unknown Midi Input Device";
153                 return false;
154         } else {
155                 m_name = capabilities.szPname;
156         }
157         return true;
158 }
159
160 std::string
161 WinMMEMidiInputDevice::get_error_string (MMRESULT error_code)
162 {
163         char error_msg[MAXERRORLENGTH];
164         MMRESULT result = midiInGetErrorText (error_code, error_msg, MAXERRORLENGTH);
165         if (result != MMSYSERR_NOERROR) {
166                 return error_msg;
167         }
168         return "WinMMEMidiInput: Unknown Error code";
169 }
170
171 void CALLBACK
172 WinMMEMidiInputDevice::winmm_input_callback(HMIDIIN handle,
173                                             UINT msg,
174                                             DWORD_PTR instance,
175                                             DWORD_PTR midi_msg,
176                                             DWORD timestamp)
177 {
178         WinMMEMidiInputDevice* midi_input = (WinMMEMidiInputDevice*)instance;
179
180 #ifdef USE_MMCSS_THREAD_PRIORITIES
181
182         static HANDLE input_thread = GetCurrentThread ();
183         static bool priority_boosted = false;
184
185 #if 0 // GetThreadId() is Vista or later only.
186         if (input_thread != GetCurrentThread ()) {
187                 DWORD otid = GetThreadId (input_thread);
188                 DWORD ntid = GetThreadId (GetCurrentThread ());
189                 // There was a reference on the internet somewhere that it is possible
190                 // for the callback to come from different threads(thread pool) this
191                 // could be problematic but I haven't seen this behaviour yet
192                 DEBUG_THREADS (string_compose (
193                     "WinMME input Thread ID Changed: was %1, now %2\n", otid, ntid));
194         }
195 #endif
196
197         HANDLE task_handle;
198
199         if (!priority_boosted) {
200                 PBD::MMCSS::set_thread_characteristics ("Pro Audio", &task_handle);
201                 PBD::MMCSS::set_thread_priority (task_handle, PBD::MMCSS::AVRT_PRIORITY_HIGH);
202                 priority_boosted = true;
203         }
204 #endif
205
206         switch (msg) {
207         case MIM_OPEN:
208         case MIM_CLOSE:
209                 // devices_changed_callback
210                 break;
211         case MIM_MOREDATA:
212                 // passing MIDI_IO_STATUS to midiInOpen means that MIM_MOREDATA
213                 // will be sent when the callback isn't processing MIM_DATA messages
214                 // fast enough to keep up with messages arriving at input device
215                 // driver. I'm not sure what could be done differently if that occurs
216                 // so just handle MIM_DATA as per normal
217         case MIM_DATA:
218                 midi_input->handle_short_msg ((const uint8_t*)&midi_msg, (uint32_t)timestamp);
219                 break;
220         case MIM_LONGDATA:
221                 midi_input->handle_sysex_msg ((MIDIHDR*)&midi_msg, (uint32_t)timestamp);
222                 break;
223         case MIM_ERROR:
224                 DEBUG_MIDI ("WinMME: Driver sent an invalid MIDI message\n");
225                 break;
226         case MIM_LONGERROR:
227                 DEBUG_MIDI ("WinMME: Driver sent an invalid or incomplete SYSEX message\n");
228                 break;
229         }
230 }
231
232 void
233 WinMMEMidiInputDevice::handle_short_msg (const uint8_t* midi_data,
234                                          uint32_t timestamp)
235 {
236         int length = get_midi_msg_length (midi_data[0]);
237
238         if (length == 0 || length == -1) {
239                 DEBUG_MIDI ("ERROR: midi input driver sent an invalid midi message\n");
240                 return;
241         }
242
243         enqueue_midi_msg (midi_data, length, timestamp);
244 }
245
246 void
247 WinMMEMidiInputDevice::handle_sysex_msg (MIDIHDR* const midi_header,
248                                          uint32_t timestamp)
249 {
250         LPMIDIHDR header = (LPMIDIHDR)midi_header;
251         size_t byte_count = header->dwBytesRecorded;
252
253         if (!byte_count) {
254                 DEBUG_MIDI (
255                     "ERROR: WinMME driver has returned sysex header to us with no bytes\n");
256                 return;
257         }
258
259         uint8_t* data = (uint8_t*)header->lpData;
260
261         if ((data[0] != 0xf0) || (data[byte_count - 1] != 0xf7)) {
262                 DEBUG_MIDI (string_compose ("Discarding %1 byte sysex chunk\n", byte_count));
263         } else {
264                 enqueue_midi_msg (data, byte_count, timestamp);
265         }
266
267         MMRESULT result = midiInAddBuffer (m_handle, &m_sysex_header, sizeof(MIDIHDR));
268         if (result != MMSYSERR_NOERROR) {
269                 DEBUG_MIDI (get_error_string (result));
270         }
271 }
272
273 // fix param order
274 bool
275 WinMMEMidiInputDevice::dequeue_midi_event (uint64_t timestamp_start,
276                                            uint64_t timestamp_end,
277                                            uint64_t& timestamp,
278                                            uint8_t* midi_data,
279                                            size_t& data_size)
280 {
281         const uint32_t read_space = m_midi_buffer->read_space();
282         struct MidiEventHeader h(0,0);
283
284         if (read_space <= sizeof(MidiEventHeader)) {
285                 return false;
286         }
287
288         RingBuffer<uint8_t>::rw_vector vector;
289         m_midi_buffer->get_read_vector (&vector);
290         if (vector.len[0] >= sizeof(MidiEventHeader)) {
291                 memcpy ((uint8_t*)&h, vector.buf[0], sizeof(MidiEventHeader));
292         } else {
293                 if (vector.len[0] > 0) {
294                         memcpy ((uint8_t*)&h, vector.buf[0], vector.len[0]);
295                 }
296                 assert (vector.buf[1] || vector.len[0] == sizeof(MidiEventHeader));
297                 memcpy (((uint8_t*)&h) + vector.len[0],
298                         vector.buf[1],
299                         sizeof(MidiEventHeader) - vector.len[0]);
300         }
301
302         if (h.time >= timestamp_end) {
303                 DEBUG_TIMING (string_compose ("WinMMEMidiInput EVENT %1(ms) early\n",
304                                               (h.time - timestamp_end) * 1e-3));
305                 return false;
306         } else if (h.time < timestamp_start) {
307                 DEBUG_TIMING (string_compose ("WinMMEMidiInput EVENT %1(ms) late\n",
308                                               (timestamp_start - h.time) * 1e-3));
309         }
310
311         m_midi_buffer->increment_read_idx (sizeof(MidiEventHeader));
312
313         assert (h.size > 0);
314         if (h.size > data_size) {
315                 DEBUG_MIDI ("WinMMEMidiInput::dequeue_event MIDI event too large!\n");
316                 m_midi_buffer->increment_read_idx (h.size);
317                 return false;
318         }
319         if (m_midi_buffer->read (&midi_data[0], h.size) != h.size) {
320                 DEBUG_MIDI ("WinMMEMidiInput::dequeue_event Garbled MIDI EVENT DATA!!\n");
321                 return false;
322         }
323         timestamp = h.time;
324         data_size = h.size;
325         return true;
326 }
327
328 bool
329 WinMMEMidiInputDevice::enqueue_midi_msg (const uint8_t* midi_data,
330                                          size_t data_size,
331                                          uint32_t timestamp)
332 {
333         const uint32_t total_size = sizeof(MidiEventHeader) + data_size;
334
335         if (data_size == 0) {
336                 DEBUG_MIDI ("ERROR: zero length midi data\n");
337                 return false;
338         }
339
340         if (m_midi_buffer->write_space () < total_size) {
341                 DEBUG_MIDI ("WinMMEMidiInput: ring buffer overflow\n");
342                 return false;
343         }
344
345         // don't use winmme timestamps for now
346         uint64_t ts = PBD::get_microseconds ();
347
348         DEBUG_TIMING (string_compose (
349             "Enqueing MIDI data device: %1 with timestamp: %2 and size %3\n",
350             name (),
351             ts,
352             data_size));
353
354         struct MidiEventHeader h (ts, data_size);
355         m_midi_buffer->write ((uint8_t*)&h, sizeof(MidiEventHeader));
356         m_midi_buffer->write (midi_data, data_size);
357         return true;
358 }
359
360 bool
361 WinMMEMidiInputDevice::start ()
362 {
363         if (!m_started) {
364                 MMRESULT result = midiInStart (m_handle);
365                 m_started = (result == MMSYSERR_NOERROR);
366                 if (!m_started) {
367                         DEBUG_MIDI (get_error_string (result));
368                 } else {
369                         DEBUG_MIDI (
370                             string_compose ("WinMMEMidiInput: device %1 started\n", name ()));
371                 }
372         }
373         return m_started;
374 }
375
376 bool
377 WinMMEMidiInputDevice::stop ()
378 {
379         if (m_started) {
380                 MMRESULT result = midiInStop (m_handle);
381                 m_started = (result != MMSYSERR_NOERROR);
382                 if (m_started) {
383                         DEBUG_MIDI (get_error_string (result));
384                 } else {
385                         DEBUG_MIDI (
386                             string_compose ("WinMMEMidiInput: device %1 stopped\n", name ()));
387                 }
388         }
389         return !m_started;
390 }
391
392 } // namespace ARDOUR