Implement MIDI device enumeration and latency offset/calibration in portaudio backend
[ardour.git] / libs / backends / portaudio / winmmemidi_io.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 <windows.h>
20 #include <mmsystem.h>
21
22 #include <sstream>
23
24 #include "pbd/error.h"
25 #include "pbd/compose.h"
26 #include "pbd/windows_timer_utils.h"
27
28 #include "winmmemidi_io.h"
29 #include "debug.h"
30
31 #include "i18n.h"
32
33 using namespace ARDOUR;
34
35 WinMMEMidiIO::WinMMEMidiIO()
36         : m_active (false)
37         , m_enabled (true)
38         , m_run (false)
39         , m_changed_callback (0)
40         , m_changed_arg (0)
41 {
42         pthread_mutex_init (&m_device_lock, 0);
43 }
44
45 WinMMEMidiIO::~WinMMEMidiIO()
46 {
47         pthread_mutex_lock (&m_device_lock);
48         cleanup();
49         pthread_mutex_unlock (&m_device_lock);
50         pthread_mutex_destroy (&m_device_lock);
51 }
52
53 void
54 WinMMEMidiIO::cleanup()
55 {
56         DEBUG_MIDI ("MIDI cleanup\n");
57         m_active = false;
58
59         destroy_input_devices ();
60         destroy_output_devices ();
61 }
62
63 bool
64 WinMMEMidiIO::dequeue_input_event (uint32_t port,
65                                    uint64_t timestamp_start,
66                                    uint64_t timestamp_end,
67                                    uint64_t &timestamp,
68                                    uint8_t *d,
69                                    size_t &s)
70 {
71         if (!m_active) {
72                 return false;
73         }
74         assert(port < m_inputs.size());
75
76         // m_inputs access should be protected by trylock
77         return m_inputs[port]->dequeue_midi_event (
78             timestamp_start, timestamp_end, timestamp, d, s);
79 }
80
81 bool
82 WinMMEMidiIO::enqueue_output_event (uint32_t port,
83                                     uint64_t timestamp,
84                                     const uint8_t *d,
85                                     const size_t s)
86 {
87         if (!m_active) {
88                 return false;
89         }
90         assert(port < m_outputs.size());
91
92         // m_outputs access should be protected by trylock
93         return m_outputs[port]->enqueue_midi_event (timestamp, d, s);
94 }
95
96
97 std::string
98 WinMMEMidiIO::port_id (uint32_t port, bool input)
99 {
100         std::stringstream ss;
101
102         if (input) {
103                 ss << "system:midi_capture_";
104                 ss << port;
105         } else {
106                 ss << "system:midi_playback_";
107                 ss << port;
108         }
109         return ss.str();
110 }
111
112 std::string WinMMEMidiIO::port_name(uint32_t port, bool input)
113 {
114         if (input) {
115                 if (port < m_inputs.size ()) {
116                         return m_inputs[port]->name ();
117                 }
118         } else {
119                 if (port < m_outputs.size ()) {
120                         return m_outputs[port]->name ();
121                 }
122         }
123         return "";
124 }
125
126 void
127 WinMMEMidiIO::start ()
128 {
129         if (m_run) {
130                 DEBUG_MIDI ("MIDI driver already started\n");
131                 return;
132         }
133
134         m_run = true;
135         DEBUG_MIDI ("Starting MIDI driver\n");
136
137         PBD::MMTIMERS::set_min_resolution();
138         discover();
139         start_devices ();
140 }
141
142
143 void
144 WinMMEMidiIO::stop ()
145 {
146         if (!m_run) {
147                 DEBUG_MIDI ("MIDI driver already stopped\n");
148                 return;
149         }
150         DEBUG_MIDI ("Stopping MIDI driver\n");
151         m_run = false;
152         stop_devices ();
153         pthread_mutex_lock (&m_device_lock);
154         cleanup ();
155         pthread_mutex_unlock (&m_device_lock);
156
157         PBD::MMTIMERS::reset_resolution();
158 }
159
160 void
161 WinMMEMidiIO::start_devices ()
162 {
163         for (std::vector<WinMMEMidiInputDevice*>::iterator i = m_inputs.begin ();
164              i < m_inputs.end();
165              ++i) {
166                 if (!(*i)->start ()) {
167                         PBD::error << string_compose (_("Unable to start MIDI input device %1\n"),
168                                                       (*i)->name ()) << endmsg;
169                 }
170         }
171         for (std::vector<WinMMEMidiOutputDevice*>::iterator i = m_outputs.begin ();
172              i < m_outputs.end();
173              ++i) {
174                 if (!(*i)->start ()) {
175                         PBD::error << string_compose (_ ("Unable to start MIDI output device %1\n"),
176                                                       (*i)->name ()) << endmsg;
177                 }
178         }
179 }
180
181 void
182 WinMMEMidiIO::stop_devices ()
183 {
184         for (std::vector<WinMMEMidiInputDevice*>::iterator i = m_inputs.begin ();
185              i < m_inputs.end();
186              ++i) {
187                 if (!(*i)->stop ()) {
188                         PBD::error << string_compose (_ ("Unable to stop MIDI input device %1\n"),
189                                                       (*i)->name ()) << endmsg;
190                 }
191         }
192         for (std::vector<WinMMEMidiOutputDevice*>::iterator i = m_outputs.begin ();
193              i < m_outputs.end();
194              ++i) {
195                 if (!(*i)->stop ()) {
196                         PBD::error << string_compose (_ ("Unable to stop MIDI output device %1\n"),
197                                                       (*i)->name ()) << endmsg;
198                 }
199         }
200 }
201
202 void
203 WinMMEMidiIO::clear_device_info ()
204 {
205         for (std::vector<MidiDeviceInfo*>::iterator i = m_device_info.begin();
206              i != m_device_info.end();
207              ++i) {
208           delete *i;
209         }
210         m_device_info.clear();
211 }
212
213 bool
214 WinMMEMidiIO::get_input_name_from_index (int index, std::string& name)
215 {
216         MIDIINCAPS capabilities;
217         MMRESULT result = midiInGetDevCaps(index, &capabilities, sizeof(capabilities));
218         if (result == MMSYSERR_NOERROR) {
219                 name = capabilities.szPname;
220                 return true;
221         }
222         return false;
223 }
224
225 bool
226 WinMMEMidiIO::get_output_name_from_index (int index, std::string& name)
227 {
228         MIDIOUTCAPS capabilities;
229         MMRESULT result = midiOutGetDevCaps(index, &capabilities, sizeof(capabilities));
230         if (result == MMSYSERR_NOERROR) {
231                 name = capabilities.szPname;
232                 return true;
233         }
234         return false;
235 }
236
237 void
238 WinMMEMidiIO::update_device_info ()
239 {
240         std::set<std::string> device_names;
241
242         int in_count = midiInGetNumDevs ();
243
244         for (int i = 0; i < in_count; ++i) {
245                 std::string input_name;
246                 if (get_input_name_from_index(i, input_name)) {
247                         device_names.insert(input_name);
248                 }
249         }
250
251         int out_count = midiOutGetNumDevs ();
252
253         for (int i = 0; i < out_count; ++i) {
254                 std::string output_name;
255                 if (get_output_name_from_index(i, output_name)) {
256                         device_names.insert(output_name);
257                 }
258         }
259
260         clear_device_info ();
261
262         for (std::set<std::string>::const_iterator i = device_names.begin();
263              i != device_names.end();
264              ++i) {
265           m_device_info.push_back(new MidiDeviceInfo(*i));
266         }
267 }
268
269 MidiDeviceInfo*
270 WinMMEMidiIO::get_device_info (const std::string& name)
271 {
272         for (std::vector<MidiDeviceInfo*>::const_iterator i = m_device_info.begin();
273              i != m_device_info.end();
274              ++i) {
275                 if ((*i)->device_name == name) {
276                         return *i;
277                 }
278         }
279         return 0;
280 }
281
282 void
283 WinMMEMidiIO::create_input_devices ()
284 {
285         int srcCount = midiInGetNumDevs ();
286
287         DEBUG_MIDI (string_compose ("MidiIn count: %1\n", srcCount));
288
289         for (int i = 0; i < srcCount; ++i) {
290                 std::string input_name;
291                 if (!get_input_name_from_index (i, input_name)) {
292                         DEBUG_MIDI ("Unable to get MIDI input name from index\n");
293                         continue;
294                 }
295
296                 MidiDeviceInfo* info = get_device_info (input_name);
297
298                 if (!info) {
299                         DEBUG_MIDI ("Unable to MIDI device info from name\n");
300                         continue;
301                 }
302
303                 if (!info->enable) {
304                         DEBUG_MIDI(string_compose(
305                             "MIDI input device %1 not enabled, not opening device\n", input_name));
306                         continue;
307                 }
308
309                 try {
310                         WinMMEMidiInputDevice* midi_input = new WinMMEMidiInputDevice (i);
311                         if (midi_input) {
312                                 m_inputs.push_back (midi_input);
313                         }
314                 }
315                 catch (...) {
316                         DEBUG_MIDI ("Unable to create MIDI input\n");
317                         continue;
318                 }
319         }
320 }
321 void
322 WinMMEMidiIO::create_output_devices ()
323 {
324         int dstCount = midiOutGetNumDevs ();
325
326         DEBUG_MIDI (string_compose ("MidiOut count: %1\n", dstCount));
327
328         for (int i = 0; i < dstCount; ++i) {
329                 std::string output_name;
330                 if (!get_output_name_from_index (i, output_name)) {
331                         DEBUG_MIDI ("Unable to get MIDI output name from index\n");
332                         continue;
333                 }
334
335                 MidiDeviceInfo* info = get_device_info (output_name);
336
337                 if (!info) {
338                         DEBUG_MIDI ("Unable to MIDI device info from name\n");
339                         continue;
340                 }
341
342                 if (!info->enable) {
343                         DEBUG_MIDI(string_compose(
344                             "MIDI output device %1 not enabled, not opening device\n", output_name));
345                         continue;
346                 }
347
348                 try {
349                         WinMMEMidiOutputDevice* midi_output = new WinMMEMidiOutputDevice(i);
350                         if (midi_output) {
351                                 m_outputs.push_back(midi_output);
352                         }
353                 } catch(...) {
354                         DEBUG_MIDI ("Unable to create MIDI output\n");
355                         continue;
356                 }
357         }
358 }
359
360 void
361 WinMMEMidiIO::destroy_input_devices ()
362 {
363         while (!m_inputs.empty ()) {
364                 WinMMEMidiInputDevice* midi_input = m_inputs.back ();
365                 // assert(midi_input->stopped ());
366                 m_inputs.pop_back ();
367                 delete midi_input;
368         }
369 }
370
371 void
372 WinMMEMidiIO::destroy_output_devices ()
373 {
374         while (!m_outputs.empty ()) {
375                 WinMMEMidiOutputDevice* midi_output = m_outputs.back ();
376                 // assert(midi_output->stopped ());
377                 m_outputs.pop_back ();
378                 delete midi_output;
379         }
380 }
381
382 void
383 WinMMEMidiIO::discover()
384 {
385         if (!m_run) {
386                 return;
387         }
388
389         if (pthread_mutex_trylock (&m_device_lock)) {
390                 return;
391         }
392
393         cleanup ();
394
395         create_input_devices ();
396         create_output_devices ();
397
398         if (!(m_inputs.size () || m_outputs.size ())) {
399                 DEBUG_MIDI ("No midi inputs or outputs\n");
400                 pthread_mutex_unlock (&m_device_lock);
401                 return;
402         }
403
404         DEBUG_MIDI (string_compose ("Discovered %1 inputs and %2 outputs\n",
405                                     m_inputs.size (),
406                                     m_outputs.size ()));
407
408         if (m_changed_callback) {
409                 m_changed_callback(m_changed_arg);
410         }
411
412         m_active = true;
413         pthread_mutex_unlock (&m_device_lock);
414 }