1 /*
2 * Copyright (C) 2010 Devin Anderson <surfacepatterns@gmail.com>
3 * Copyright (C) 2014-2017 Robin Gareus <robin@gareus.org>
4 * Copyright (C) 2016-2017 Paul Davis <paul@linuxaudiosystems.com>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 */
20
21 #include <unistd.h>
22 #include <glibmm.h>
23
24 #include "select_sleep.h"
25 #include "alsa_rawmidi.h"
26
27 #include "pbd/error.h"
28 #include "pbd/i18n.h"
29
30 using namespace ARDOUR;
31
32 #ifndef NDEBUG
33 #define _DEBUGPRINT(STR) fprintf(stderr, STR);
34 #else
35 #define _DEBUGPRINT(STR) ;
36 #endif
37
AlsaRawMidiIO(const std::string & name,const char * device,const bool input)38 AlsaRawMidiIO::AlsaRawMidiIO (const std::string &name, const char *device, const bool input)
39 : AlsaMidiIO()
40 , _device (0)
41 {
42 _name = name;
43 init (device, input);
44 }
45
~AlsaRawMidiIO()46 AlsaRawMidiIO::~AlsaRawMidiIO ()
47 {
48 if (_device) {
49 snd_rawmidi_drain (_device);
50 snd_rawmidi_close (_device);
51 _device = 0;
52 }
53 }
54
55 void
init(const char * device_name,const bool input)56 AlsaRawMidiIO::init (const char *device_name, const bool input)
57 {
58 if (snd_rawmidi_open (
59 input ? &_device : NULL,
60 input ? NULL : &_device,
61 device_name, SND_RAWMIDI_NONBLOCK) < 0) {
62 return;
63 }
64
65 _npfds = snd_rawmidi_poll_descriptors_count (_device);
66 if (_npfds < 1) {
67 _DEBUGPRINT("AlsaRawMidiIO: no poll descriptor(s).\n");
68 snd_rawmidi_close (_device);
69 _device = 0;
70 return;
71 }
72 _pfds = (struct pollfd*) malloc (_npfds * sizeof(struct pollfd));
73 snd_rawmidi_poll_descriptors (_device, _pfds, _npfds);
74
75 #if 0
76 _state = 0;
77 #else
78 snd_rawmidi_params_t *params;
79 if (snd_rawmidi_params_malloc (¶ms)) {
80 goto initerr;
81 }
82 if (snd_rawmidi_params_current (_device, params)) {
83 goto initerr;
84 }
85 if (snd_rawmidi_params_set_avail_min (_device, params, 1)) {
86 goto initerr;
87 }
88 if (snd_rawmidi_params_set_buffer_size (_device, params, 64)) {
89 goto initerr;
90 }
91 if (snd_rawmidi_params_set_no_active_sensing (_device, params, 1)) {
92 goto initerr;
93 }
94
95 _state = 0;
96 return;
97
98 initerr:
99 _DEBUGPRINT("AlsaRawMidiIO: parameter setup error\n");
100 snd_rawmidi_close (_device);
101 _device = 0;
102 #endif
103 return;
104 }
105
106 ///////////////////////////////////////////////////////////////////////////////
107
AlsaRawMidiOut(const std::string & name,const char * device)108 AlsaRawMidiOut::AlsaRawMidiOut (const std::string &name, const char *device)
109 : AlsaRawMidiIO (name, device, false)
110 , AlsaMidiOut ()
111 {
112 }
113
114 void *
main_process_thread()115 AlsaRawMidiOut::main_process_thread ()
116 {
117 _running = true;
118 pthread_mutex_lock (&_notify_mutex);
119 unsigned int need_drain = 0;
120 while (_running) {
121 bool have_data = false;
122 struct MidiEventHeader h(0,0);
123 uint8_t data[MaxAlsaMidiEventSize];
124
125 const uint32_t read_space = _rb->read_space();
126
127 if (read_space > sizeof(MidiEventHeader)) {
128 if (_rb->read ((uint8_t*)&h, sizeof(MidiEventHeader)) != sizeof(MidiEventHeader)) {
129 _DEBUGPRINT("AlsaRawMidiOut: Garbled MIDI EVENT HEADER!!\n");
130 break;
131 }
132 assert (read_space >= h.size);
133 if (h.size > MaxAlsaMidiEventSize) {
134 _rb->increment_read_idx (h.size);
135 _DEBUGPRINT("AlsaRawMidiOut: MIDI event too large!\n");
136 continue;
137 }
138 if (_rb->read (&data[0], h.size) != h.size) {
139 _DEBUGPRINT("AlsaRawMidiOut: Garbled MIDI EVENT DATA!!\n");
140 break;
141 }
142 have_data = true;
143 }
144
145 if (!have_data) {
146 if (need_drain > 0) {
147 snd_rawmidi_drain (_device);
148 need_drain = 0;
149 }
150 pthread_cond_wait (&_notify_ready, &_notify_mutex);
151 continue;
152 }
153
154 uint64_t now = g_get_monotonic_time();
155 while (h.time > now + 500) {
156 if (need_drain > 0) {
157 snd_rawmidi_drain (_device);
158 need_drain = 0;
159 } else {
160 select_sleep(h.time - now);
161 }
162 now = g_get_monotonic_time();
163 }
164
165 retry:
166 int perr = poll (_pfds, _npfds, 10 /* ms */);
167 if (perr < 0) {
168 PBD::error << _("AlsaRawMidiOut: Error polling device. Terminating Midi Thread.") << endmsg;
169 break;
170 }
171 if (perr == 0) {
172 _DEBUGPRINT("AlsaRawMidiOut: poll() timed out.\n");
173 goto retry;
174 }
175
176 unsigned short revents = 0;
177 if (snd_rawmidi_poll_descriptors_revents (_device, _pfds, _npfds, &revents)) {
178 PBD::error << _("AlsaRawMidiOut: Failed to poll device. Terminating Midi Thread.") << endmsg;
179 break;
180 }
181
182 if (revents & (POLLERR | POLLHUP | POLLNVAL)) {
183 PBD::error << _("AlsaRawMidiOut: poll error. Terminating Midi Thread.") << endmsg;
184 break;
185 }
186
187 if (!(revents & POLLOUT)) {
188 _DEBUGPRINT("AlsaRawMidiOut: POLLOUT not ready.\n");
189 select_sleep (1000);
190 goto retry;
191 }
192
193 ssize_t err = snd_rawmidi_write (_device, data, h.size);
194
195 #if 0 // DEBUG -- not rt-safe
196 printf("TX [%ld | %ld]", h.size, err);
197 for (size_t i = 0; i < h.size; ++i) {
198 printf (" %02x", data[i]);
199 }
200 printf ("\n");
201 #endif
202
203 if (err == -EAGAIN) {
204 snd_rawmidi_drain (_device);
205 goto retry;
206 }
207 if (err == -EWOULDBLOCK) {
208 select_sleep (1000);
209 goto retry;
210 }
211 if (err < 0) {
212 PBD::error << _("AlsaRawMidiOut: write failed. Terminating Midi Thread.") << endmsg;
213 break;
214 }
215 if ((size_t) err < h.size) {
216 _DEBUGPRINT("AlsaRawMidiOut: short write\n");
217 memmove(&data[0], &data[err], err);
218 h.size -= err;
219 goto retry;
220 }
221
222 if ((need_drain += h.size) >= 64) {
223 snd_rawmidi_drain (_device);
224 need_drain = 0;
225 }
226 }
227
228 pthread_mutex_unlock (&_notify_mutex);
229 _DEBUGPRINT("AlsaRawMidiOut: MIDI OUT THREAD STOPPED\n");
230 return 0;
231 }
232
233
234 ///////////////////////////////////////////////////////////////////////////////
235
AlsaRawMidiIn(const std::string & name,const char * device)236 AlsaRawMidiIn::AlsaRawMidiIn (const std::string &name, const char *device)
237 : AlsaRawMidiIO (name, device, true)
238 , AlsaMidiIn ()
239 , _event(0,0)
240 , _first_time(true)
241 , _unbuffered_bytes(0)
242 , _total_bytes(0)
243 , _expected_bytes(0)
244 , _status_byte(0)
245 {
246 }
247
248 void *
main_process_thread()249 AlsaRawMidiIn::main_process_thread ()
250 {
251 _running = true;
252 while (_running) {
253 unsigned short revents = 0;
254
255 int perr = poll (_pfds, _npfds, 100 /* ms */);
256 if (perr < 0) {
257 PBD::error << _("AlsaRawMidiIn: Error polling device. Terminating Midi Thread.") << endmsg;
258 break;
259 }
260 if (perr == 0) {
261 continue;
262 }
263
264 if (snd_rawmidi_poll_descriptors_revents (_device, _pfds, _npfds, &revents)) {
265 PBD::error << _("AlsaRawMidiIn: Failed to poll device. Terminating Midi Thread.") << endmsg;
266 break;
267 }
268
269 if (revents & (POLLERR | POLLHUP | POLLNVAL)) {
270 PBD::error << _("AlsaRawMidiIn: poll error. Terminating Midi Thread.") << endmsg;
271 break;
272 }
273
274 if (!(revents & POLLIN)) {
275 _DEBUGPRINT("AlsaRawMidiOut: POLLIN not ready.\n");
276 select_sleep (1000);
277 continue;
278 }
279
280 uint8_t data[MaxAlsaMidiEventSize];
281 uint64_t time = g_get_monotonic_time();
282 ssize_t err = snd_rawmidi_read (_device, data, sizeof(data));
283
284 #if EAGAIN != EWOULDBLOCK
285 if ((err == -EAGAIN) || (err == -EWOULDBLOCK)) {
286 #else
287 if (err == -EAGAIN) {
288 #endif
289 continue;
290 }
291 if (err < 0) {
292 PBD::error << _("AlsaRawMidiIn: read error. Terminating Midi") << endmsg;
293 break;
294 }
295 if (err == 0) {
296 _DEBUGPRINT("AlsaRawMidiIn: zero read\n");
297 continue;
298 }
299
300 #if 0
301 queue_event (time, data, err);
302 #else
303 parse_events (time, data, err);
304 #endif
305 }
306
307 _DEBUGPRINT("AlsaRawMidiIn: MIDI IN THREAD STOPPED\n");
308 return 0;
309 }
310
311 int
312 AlsaRawMidiIn::queue_event (const uint64_t time, const uint8_t *data, const size_t size) {
313 _event._pending = false;
314 return AlsaMidiIn::queue_event(time, data, size);
315 }
316
317 void
318 AlsaRawMidiIn::parse_events (const uint64_t time, const uint8_t *data, const size_t size) {
319 if (_event._pending) {
320 _DEBUGPRINT("AlsaRawMidiIn: queue pending event\n");
321 if (queue_event (_event._time, _parser_buffer, _event._size)) {
322 return;
323 }
324 }
325 for (size_t i = 0; i < size; ++i) {
326 if (_first_time && !(data[i] & 0x80)) {
327 continue;
328 }
329 _first_time = false; /// TODO optimize e.g. use fn pointer to different parse_events()
330 if (process_byte(time, data[i])) {
331 if (queue_event (_event._time, _parser_buffer, _event._size)) {
332 return;
333 }
334 }
335 }
336 }
337
338 // based on JackMidiRawInputWriteQueue by Devin Anderson //
339 bool
340 AlsaRawMidiIn::process_byte(const uint64_t time, const uint8_t byte)
341 {
342 if (byte >= 0xf8) {
343 // Realtime
344 if (byte == 0xfd) {
345 return false;
346 }
347 _parser_buffer[0] = byte;
348 prepare_byte_event(time, byte);
349 return true;
350 }
351 if (byte == 0xf7) {
352 // Sysex end
353 if (_status_byte == 0xf0) {
354 record_byte(byte);
355 return prepare_buffered_event(time);
356 }
357 _total_bytes = 0;
358 _unbuffered_bytes = 0;
359 _expected_bytes = 0;
360 _status_byte = 0;
361 return false;
362 }
363 if (byte >= 0x80) {
364 // Non-realtime status byte
365 if (_total_bytes) {
366 _DEBUGPRINT("AlsaRawMidiIn: discarded bogus midi message\n");
367 #if 0
368 for (size_t i=0; i < _total_bytes; ++i) {
369 printf("%02x ", _parser_buffer[i]);
370 }
371 printf("\n");
372 #endif
373 _total_bytes = 0;
374 _unbuffered_bytes = 0;
375 }
376 _status_byte = byte;
377 switch (byte & 0xf0) {
378 case 0x80:
379 case 0x90:
380 case 0xa0:
381 case 0xb0:
382 case 0xe0:
383 // Note On, Note Off, Aftertouch, Control Change, Pitch Wheel
384 _expected_bytes = 3;
385 break;
386 case 0xc0:
387 case 0xd0:
388 // Program Change, Channel Pressure
389 _expected_bytes = 2;
390 break;
391 case 0xf0:
392 switch (byte) {
393 case 0xf0:
394 // Sysex
395 _expected_bytes = 0;
396 break;
397 case 0xf1:
398 case 0xf3:
399 // MTC Quarter Frame, Song Select
400 _expected_bytes = 2;
401 break;
402 case 0xf2:
403 // Song Position
404 _expected_bytes = 3;
405 break;
406 case 0xf4:
407 case 0xf5:
408 // Undefined
409 _expected_bytes = 0;
410 _status_byte = 0;
411 return false;
412 case 0xf6:
413 // Tune Request
414 prepare_byte_event(time, byte);
415 _expected_bytes = 0;
416 _status_byte = 0;
417 return true;
418 }
419 }
420 record_byte(byte);
421 return false;
422 }
423 // Data byte
424 if (! _status_byte) {
425 // Data bytes without a status will be discarded.
426 _total_bytes++;
427 _unbuffered_bytes++;
428 return false;
429 }
430 if (! _total_bytes) {
431 _DEBUGPRINT("AlsaRawMidiIn: apply running status\n");
432 record_byte(_status_byte);
433 }
434 record_byte(byte);
435 return (_total_bytes == _expected_bytes) ? prepare_buffered_event(time) : false;
436 }
437