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