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