Move Windows timer utility functions from PA backend into libpbd
[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
27 #include "midi_util.h"
28
29 #include "mmcss.h"
30
31 #include "debug.h"
32
33 static const uint32_t MIDI_BUFFER_SIZE = 32768;
34 static const uint32_t SYSEX_BUFFER_SIZE = 32768;
35
36 namespace ARDOUR {
37
38 WinMMEMidiInputDevice::WinMMEMidiInputDevice (int index)
39         : m_handle(0)
40         , m_started(false)
41         , m_midi_buffer(new RingBuffer<uint8_t>(MIDI_BUFFER_SIZE))
42         , m_sysex_buffer(new uint8_t[SYSEX_BUFFER_SIZE])
43 {
44         DEBUG_MIDI (string_compose ("Creating midi input device index: %1\n", index));
45
46         std::string error_msg;
47
48         if (!open (index, error_msg)) {
49                 DEBUG_MIDI (error_msg);
50                 throw std::runtime_error (error_msg);
51         }
52
53         // perhaps this should be called in open
54         if (!add_sysex_buffer (error_msg)) {
55                 DEBUG_MIDI (error_msg);
56                 std::string close_error;
57                 if (!close (close_error)) {
58                         DEBUG_MIDI (close_error);
59                 }
60                 throw std::runtime_error (error_msg);
61         }
62
63         set_device_name (index);
64 }
65
66 WinMMEMidiInputDevice::~WinMMEMidiInputDevice ()
67 {
68         std::string error_msg;
69         if (!close (error_msg)) {
70                 DEBUG_MIDI (error_msg);
71         }
72 }
73
74 bool
75 WinMMEMidiInputDevice::open (UINT index, std::string& error_msg)
76 {
77         MMRESULT result = midiInOpen (&m_handle,
78                                       index,
79                                       (DWORD_PTR) winmm_input_callback,
80                                       (DWORD_PTR) this,
81                                       CALLBACK_FUNCTION | MIDI_IO_STATUS);
82         if (result != MMSYSERR_NOERROR) {
83                 error_msg = get_error_string (result);
84                 return false;
85         }
86         DEBUG_MIDI (string_compose ("Opened MIDI device index %1\n", index));
87         return true;
88 }
89
90 bool
91 WinMMEMidiInputDevice::close (std::string& error_msg)
92 {
93         // return error message for first error encountered?
94         bool success = true;
95
96         MMRESULT result = midiInReset (m_handle);
97         if (result != MMSYSERR_NOERROR) {
98                 error_msg = get_error_string (result);
99                 DEBUG_MIDI (error_msg);
100                 success = false;
101         }
102         result = midiInUnprepareHeader (m_handle, &m_sysex_header, sizeof(MIDIHDR));
103         if (result != MMSYSERR_NOERROR) {
104                 error_msg = get_error_string (result);
105                 DEBUG_MIDI (error_msg);
106                 success = false;
107         }
108         result = midiInClose (m_handle);
109         if (result != MMSYSERR_NOERROR) {
110                 error_msg = get_error_string (result);
111                 DEBUG_MIDI (error_msg);
112                 success = false;
113         }
114         m_handle = 0;
115         if (success) {
116                 DEBUG_MIDI (string_compose ("Closed MIDI device: %1\n", name ()));
117         } else {
118                 DEBUG_MIDI (string_compose ("Unable to Close MIDI device: %1\n", name ()));
119         }
120         return success;
121 }
122
123 bool
124 WinMMEMidiInputDevice::add_sysex_buffer (std::string& error_msg)
125 {
126         m_sysex_header.dwBufferLength = SYSEX_BUFFER_SIZE;
127         m_sysex_header.dwFlags = 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         }
143         return true;
144 }
145
146 bool
147 WinMMEMidiInputDevice::set_device_name (UINT index)
148 {
149         MIDIINCAPS capabilities;
150         MMRESULT result = midiInGetDevCaps (index, &capabilities, sizeof(capabilities));
151         if (result != MMSYSERR_NOERROR) {
152                 DEBUG_MIDI (get_error_string (result));
153                 m_name = "Unknown Midi Input Device";
154                 return false;
155         } else {
156                 m_name = capabilities.szPname;
157         }
158         return true;
159 }
160
161 std::string
162 WinMMEMidiInputDevice::get_error_string (MMRESULT error_code)
163 {
164         char error_msg[MAXERRORLENGTH];
165         MMRESULT result = midiInGetErrorText (error_code, error_msg, MAXERRORLENGTH);
166         if (result != MMSYSERR_NOERROR) {
167                 return error_msg;
168         }
169         return "WinMMEMidiInput: Unknown Error code";
170 }
171
172 void CALLBACK
173 WinMMEMidiInputDevice::winmm_input_callback(HMIDIIN handle,
174                                             UINT msg,
175                                             DWORD_PTR instance,
176                                             DWORD_PTR midi_msg,
177                                             DWORD timestamp)
178 {
179         WinMMEMidiInputDevice* midi_input = (WinMMEMidiInputDevice*)instance;
180
181 #ifdef USE_MMCSS_THREAD_PRIORITIES
182
183         static HANDLE input_thread = GetCurrentThread ();
184         static bool priority_boosted = false;
185
186 #if 0 // GetThreadId() is Vista or later only.
187         if (input_thread != GetCurrentThread ()) {
188                 DWORD otid = GetThreadId (input_thread);
189                 DWORD ntid = GetThreadId (GetCurrentThread ());
190                 // There was a reference on the internet somewhere that it is possible
191                 // for the callback to come from different threads(thread pool) this
192                 // could be problematic but I haven't seen this behaviour yet
193                 DEBUG_THREADS (string_compose (
194                     "WinMME input Thread ID Changed: was %1, now %2\n", otid, ntid));
195         }
196 #endif
197
198         HANDLE task_handle;
199
200         if (!priority_boosted) {
201                 mmcss::set_thread_characteristics ("Pro Audio", &task_handle);
202                 mmcss::set_thread_priority (task_handle, mmcss::AVRT_PRIORITY_HIGH);
203                 priority_boosted = true;
204         }
205 #endif
206
207         switch (msg) {
208         case MIM_OPEN:
209         case MIM_CLOSE:
210                 // devices_changed_callback
211                 break;
212         case MIM_MOREDATA:
213                 // passing MIDI_IO_STATUS to midiInOpen means that MIM_MOREDATA
214                 // will be sent when the callback isn't processing MIM_DATA messages
215                 // fast enough to keep up with messages arriving at input device
216                 // driver. I'm not sure what could be done differently if that occurs
217                 // so just handle MIM_DATA as per normal
218         case MIM_DATA:
219                 midi_input->handle_short_msg ((const uint8_t*)&midi_msg, (uint32_t)timestamp);
220                 break;
221         case MIM_LONGDATA:
222                 midi_input->handle_sysex_msg ((MIDIHDR*)&midi_msg, (uint32_t)timestamp);
223                 break;
224         case MIM_ERROR:
225                 DEBUG_MIDI ("WinMME: Driver sent an invalid MIDI message\n");
226                 break;
227         case MIM_LONGERROR:
228                 DEBUG_MIDI ("WinMME: Driver sent an invalid or incomplete SYSEX message\n");
229                 break;
230         }
231 }
232
233 void
234 WinMMEMidiInputDevice::handle_short_msg (const uint8_t* midi_data,
235                                          uint32_t timestamp)
236 {
237         int length = get_midi_msg_length (midi_data[0]);
238
239         if (length == 0 || length == -1) {
240                 DEBUG_MIDI ("ERROR: midi input driver sent an invalid midi message\n");
241                 return;
242         }
243
244         enqueue_midi_msg (midi_data, length, timestamp);
245 }
246
247 void
248 WinMMEMidiInputDevice::handle_sysex_msg (MIDIHDR* const midi_header,
249                                          uint32_t timestamp)
250 {
251 #ifdef ENABLE_SYSEX
252         LPMIDIHDR header = (LPMIDIHDR)midi_header;
253         size_t byte_count = header->dwBytesRecorded;
254
255         if (!byte_count) {
256                 DEBUG_MIDI (
257                     "ERROR: WinMME driver has returned sysex header to us with no bytes\n");
258                 return;
259         }
260
261         uint8_t* data = (uint8_t*)header->lpData;
262
263         if ((data[0] != 0xf0) || (data[byte_count - 1] != 0xf7)) {
264                 DEBUG_MIDI (string_compose ("Discarding %1 byte sysex chunk\n", byte_count));
265         } else {
266                 enqueue_midi_msg (data, byte_count, timestamp);
267         }
268
269         MMRESULT result = midiInAddBuffer (m_handle, &m_sysex_header, sizeof(MIDIHDR));
270         if (result != MMSYSERR_NOERROR) {
271                 DEBUG_MIDI (get_error_string (result));
272         }
273 #endif
274 }
275
276 // fix param order
277 bool
278 WinMMEMidiInputDevice::dequeue_midi_event (uint64_t timestamp_start,
279                                            uint64_t timestamp_end,
280                                            uint64_t& timestamp,
281                                            uint8_t* midi_data,
282                                            size_t& data_size)
283 {
284         const uint32_t read_space = m_midi_buffer->read_space();
285         struct MidiEventHeader h(0,0);
286
287         if (read_space <= sizeof(MidiEventHeader)) {
288                 return false;
289         }
290
291         RingBuffer<uint8_t>::rw_vector vector;
292         m_midi_buffer->get_read_vector (&vector);
293         if (vector.len[0] >= sizeof(MidiEventHeader)) {
294                 memcpy ((uint8_t*)&h, vector.buf[0], sizeof(MidiEventHeader));
295         } else {
296                 if (vector.len[0] > 0) {
297                         memcpy ((uint8_t*)&h, vector.buf[0], vector.len[0]);
298                 }
299                 assert (vector.buf[1] || vector.len[0] == sizeof(MidiEventHeader));
300                 memcpy (((uint8_t*)&h) + vector.len[0],
301                         vector.buf[1],
302                         sizeof(MidiEventHeader) - vector.len[0]);
303         }
304
305         if (h.time >= timestamp_end) {
306                 DEBUG_TIMING (string_compose ("WinMMEMidiInput EVENT %1(ms) early\n",
307                                               (h.time - timestamp_end) * 1e-3));
308                 return false;
309         } else if (h.time < timestamp_start) {
310                 DEBUG_TIMING (string_compose ("WinMMEMidiInput EVENT %1(ms) late\n",
311                                               (timestamp_start - h.time) * 1e-3));
312         }
313
314         m_midi_buffer->increment_read_idx (sizeof(MidiEventHeader));
315
316         assert (h.size > 0);
317         if (h.size > data_size) {
318                 DEBUG_MIDI ("WinMMEMidiInput::dequeue_event MIDI event too large!\n");
319                 m_midi_buffer->increment_read_idx (h.size);
320                 return false;
321         }
322         if (m_midi_buffer->read (&midi_data[0], h.size) != h.size) {
323                 DEBUG_MIDI ("WinMMEMidiInput::dequeue_event Garbled MIDI EVENT DATA!!\n");
324                 return false;
325         }
326         timestamp = h.time;
327         data_size = h.size;
328         return true;
329 }
330
331 bool
332 WinMMEMidiInputDevice::enqueue_midi_msg (const uint8_t* midi_data,
333                                          size_t data_size,
334                                          uint32_t timestamp)
335 {
336         const uint32_t total_size = sizeof(MidiEventHeader) + data_size;
337
338         if (data_size == 0) {
339                 DEBUG_MIDI ("ERROR: zero length midi data\n");
340                 return false;
341         }
342
343         if (m_midi_buffer->write_space () < total_size) {
344                 DEBUG_MIDI ("WinMMEMidiInput: ring buffer overflow\n");
345                 return false;
346         }
347
348         // don't use winmme timestamps for now
349         uint64_t ts = PBD::get_microseconds ();
350
351         DEBUG_TIMING (string_compose (
352             "Enqueing MIDI data device: %1 with timestamp: %2 and size %3\n",
353             name (),
354             ts,
355             data_size));
356
357         struct MidiEventHeader h (ts, data_size);
358         m_midi_buffer->write ((uint8_t*)&h, sizeof(MidiEventHeader));
359         m_midi_buffer->write (midi_data, data_size);
360         return true;
361 }
362
363 bool
364 WinMMEMidiInputDevice::start ()
365 {
366         if (!m_started) {
367                 MMRESULT result = midiInStart (m_handle);
368                 m_started = (result == MMSYSERR_NOERROR);
369                 if (!m_started) {
370                         DEBUG_MIDI (get_error_string (result));
371                 } else {
372                         DEBUG_MIDI (
373                             string_compose ("WinMMEMidiInput: device %1 started\n", name ()));
374                 }
375         }
376         return m_started;
377 }
378
379 bool
380 WinMMEMidiInputDevice::stop ()
381 {
382         if (m_started) {
383                 MMRESULT result = midiInStop (m_handle);
384                 m_started = (result != MMSYSERR_NOERROR);
385                 if (m_started) {
386                         DEBUG_MIDI (get_error_string (result));
387                 } else {
388                         DEBUG_MIDI (
389                             string_compose ("WinMMEMidiInput: device %1 stopped\n", name ()));
390                 }
391         }
392         return !m_started;
393 }
394
395 } // namespace ARDOUR