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