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