avoid compiler warning on at least some linux versions
[ardour.git] / libs / backends / alsa / alsa_sequencer.cc
1 /*
2  * Copyright (C) 2014 Robin Gareus <robin@gareus.org>
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 <unistd.h>
20 #include <glibmm.h>
21
22 #include "select_sleep.h"
23 #include "alsa_sequencer.h"
24
25 #include "pbd/error.h"
26 #include "pbd/i18n.h"
27
28 using namespace ARDOUR;
29
30 #ifndef NDEBUG
31 #define _DEBUGPRINT(STR) fprintf(stderr, STR);
32 #else
33 #define _DEBUGPRINT(STR) ;
34 #endif
35
36 AlsaSeqMidiIO::AlsaSeqMidiIO (const std::string &name, const char *device, const bool input)
37         : AlsaMidiIO()
38         , _seq (0)
39 {
40         _name = name;
41         init (device, input);
42 }
43
44 AlsaSeqMidiIO::~AlsaSeqMidiIO ()
45 {
46         if (_seq) {
47                 snd_seq_close (_seq);
48                 _seq = 0;
49         }
50 }
51
52 void
53 AlsaSeqMidiIO::init (const char *device_name, const bool input)
54 {
55         if (snd_seq_open (&_seq, "hw",
56                                 input ? SND_SEQ_OPEN_INPUT : SND_SEQ_OPEN_OUTPUT, 0) < 0)
57         {
58                 _seq = 0;
59                 return;
60         }
61
62         if (snd_seq_set_client_name (_seq, "Ardour")) {
63                 _DEBUGPRINT("AlsaSeqMidiIO: cannot set client name.\n");
64                 goto initerr;
65         }
66
67         _port = snd_seq_create_simple_port (_seq, "port", SND_SEQ_PORT_CAP_NO_EXPORT |
68                         (input ? SND_SEQ_PORT_CAP_WRITE : SND_SEQ_PORT_CAP_READ),
69                         SND_SEQ_PORT_TYPE_APPLICATION);
70
71         if (_port < 0) {
72                 _DEBUGPRINT("AlsaSeqMidiIO: cannot create port.\n");
73                 goto initerr;
74         }
75
76         _npfds = snd_seq_poll_descriptors_count (_seq, input ? POLLIN : POLLOUT);
77         if (_npfds < 1) {
78                 _DEBUGPRINT("AlsaSeqMidiIO: no poll descriptor(s).\n");
79                 goto initerr;
80         }
81         _pfds = (struct pollfd*) malloc (_npfds * sizeof(struct pollfd));
82         snd_seq_poll_descriptors (_seq, _pfds, _npfds, input ? POLLIN : POLLOUT);
83
84
85         snd_seq_addr_t port;
86         if (snd_seq_parse_address (_seq, &port, device_name) < 0) {
87                 _DEBUGPRINT("AlsaSeqMidiIO: cannot resolve hardware port.\n");
88                 goto initerr;
89         }
90
91         if (input) {
92                 if (snd_seq_connect_from (_seq, _port, port.client, port.port) < 0) {
93                         _DEBUGPRINT("AlsaSeqMidiIO: cannot connect input port.\n");
94                         goto initerr;
95                 }
96         } else {
97                 if (snd_seq_connect_to (_seq, _port, port.client, port.port) < 0) {
98                         _DEBUGPRINT("AlsaSeqMidiIO: cannot connect output port.\n");
99                         goto initerr;
100                 }
101         }
102
103         snd_seq_nonblock(_seq, 1);
104
105         _state = 0;
106         return;
107
108 initerr:
109         PBD::error << _("AlsaSeqMidiIO: Device initialization failed.") << endmsg;
110         snd_seq_close (_seq);
111         _seq = 0;
112         return;
113 }
114
115 ///////////////////////////////////////////////////////////////////////////////
116
117 AlsaSeqMidiOut::AlsaSeqMidiOut (const std::string &name, const char *device)
118                 : AlsaSeqMidiIO (name, device, false)
119                 , AlsaMidiOut ()
120 {
121 }
122
123 void *
124 AlsaSeqMidiOut::main_process_thread ()
125 {
126         _running = true;
127         bool need_drain = false;
128         snd_midi_event_t *alsa_codec = NULL;
129         snd_midi_event_new (MaxAlsaMidiEventSize, &alsa_codec);
130         pthread_mutex_lock (&_notify_mutex);
131         while (_running) {
132                 bool have_data = false;
133                 struct MidiEventHeader h(0,0);
134                 uint8_t data[MaxAlsaMidiEventSize];
135
136                 const uint32_t read_space = _rb->read_space();
137
138                 if (read_space > sizeof(MidiEventHeader)) {
139                         if (_rb->read ((uint8_t*)&h, sizeof(MidiEventHeader)) != sizeof(MidiEventHeader)) {
140                                 _DEBUGPRINT("AlsaSeqMidiOut: Garbled MIDI EVENT HEADER!!\n");
141                                 break;
142                         }
143                         assert (read_space >= h.size);
144                         if (h.size > MaxAlsaMidiEventSize) {
145                                 _rb->increment_read_idx (h.size);
146                                 _DEBUGPRINT("AlsaSeqMidiOut: MIDI event too large!\n");
147                                 continue;
148                         }
149                         if (_rb->read (&data[0], h.size) != h.size) {
150                                 _DEBUGPRINT("AlsaSeqMidiOut: Garbled MIDI EVENT DATA!!\n");
151                                 break;
152                         }
153                         have_data = true;
154                 }
155
156                 if (!have_data) {
157                         if (need_drain) {
158                                 snd_seq_drain_output (_seq);
159                                 need_drain = false;
160                         }
161                         pthread_cond_wait (&_notify_ready, &_notify_mutex);
162                         continue;
163                 }
164
165                 snd_seq_event_t alsa_event;
166                 snd_seq_ev_clear (&alsa_event);
167                 snd_midi_event_reset_encode (alsa_codec);
168                 if (!snd_midi_event_encode (alsa_codec, data, h.size, &alsa_event)) {
169                         PBD::error << _("AlsaSeqMidiOut: Invalid Midi Event.") << endmsg;
170                         continue;
171                 }
172
173                 snd_seq_ev_set_source (&alsa_event, _port);
174                 snd_seq_ev_set_subs (&alsa_event);
175                 snd_seq_ev_set_direct (&alsa_event);
176
177                 uint64_t now = g_get_monotonic_time();
178                 while (h.time > now + 500) {
179                         if (need_drain) {
180                                 snd_seq_drain_output (_seq);
181                                 need_drain = false;
182                         } else {
183                                 select_sleep(h.time - now);
184                         }
185                         now = g_get_monotonic_time();
186                 }
187
188 retry:
189                 int perr = poll (_pfds, _npfds, 10 /* ms */);
190                 if (perr < 0) {
191                         PBD::error << _("AlsaSeqMidiOut: Error polling device. Terminating Midi Thread.") << endmsg;
192                         break;
193                 }
194                 if (perr == 0) {
195                         _DEBUGPRINT("AlsaSeqMidiOut: poll() timed out.\n");
196                         goto retry;
197                 }
198
199                 ssize_t err = snd_seq_event_output(_seq, &alsa_event);
200
201                 if ((err == -EAGAIN)) {
202                         snd_seq_drain_output (_seq);
203                         goto retry;
204                 }
205                 if (err == -EWOULDBLOCK) {
206                         select_sleep (1000);
207                         goto retry;
208                 }
209                 if (err < 0) {
210                         PBD::error << _("AlsaSeqMidiOut: write failed. Terminating Midi Thread.") << endmsg;
211                         break;
212                 }
213                 need_drain = true;
214         }
215
216         pthread_mutex_unlock (&_notify_mutex);
217
218         if (alsa_codec) {
219                 snd_midi_event_free(alsa_codec);
220         }
221         _DEBUGPRINT("AlsaSeqMidiOut: MIDI OUT THREAD STOPPED\n");
222         return 0;
223 }
224
225 ///////////////////////////////////////////////////////////////////////////////
226
227 AlsaSeqMidiIn::AlsaSeqMidiIn (const std::string &name, const char *device)
228                 : AlsaSeqMidiIO (name, device, true)
229                 , AlsaMidiIn ()
230 {
231 }
232
233 void *
234 AlsaSeqMidiIn::main_process_thread ()
235 {
236         _running = true;
237         bool do_poll = true;
238         snd_midi_event_t *alsa_codec = NULL;
239         snd_midi_event_new (MaxAlsaMidiEventSize, &alsa_codec);
240
241         while (_running) {
242
243                 if (do_poll) {
244                         snd_seq_poll_descriptors (_seq, _pfds, _npfds, POLLIN);
245                         int perr = poll (_pfds, _npfds, 100 /* ms */);
246
247                         if (perr < 0) {
248                                 PBD::error << _("AlsaSeqMidiIn: Error polling device. Terminating Midi Thread.") << endmsg;
249                                 break;
250                         }
251                         if (perr == 0) {
252                                 continue;
253                         }
254                 }
255
256                 snd_seq_event_t *event;
257                 uint64_t time = g_get_monotonic_time();
258                 ssize_t err = snd_seq_event_input (_seq, &event);
259
260 #if EAGAIN == EWOULDBLOCK
261                 if (err == -EAGAIN) {
262 #else
263                 if ((err == -EAGAIN) || (err == -EWOULDBLOCK)) {
264 #endif
265                         do_poll = true;
266                         continue;
267                 }
268                 if (err == -ENOSPC) {
269                         PBD::error << _("AlsaSeqMidiIn: FIFO overrun.") << endmsg;
270                         do_poll = true;
271                         continue;
272                 }
273                 if (err < 0) {
274                         PBD::error << _("AlsaSeqMidiIn: read error. Terminating Midi") << endmsg;
275                         break;
276                 }
277
278                 uint8_t data[MaxAlsaMidiEventSize];
279                 snd_midi_event_reset_decode (alsa_codec);
280                 ssize_t size = snd_midi_event_decode (alsa_codec, data, sizeof(data), event);
281
282                 if (size > 0) {
283                         queue_event (time, data, size);
284                 }
285                 do_poll = (0 == err);
286         }
287
288         if (alsa_codec) {
289                 snd_midi_event_free(alsa_codec);
290         }
291         _DEBUGPRINT("AlsaSeqMidiIn: MIDI IN THREAD STOPPED\n");
292         return 0;
293 }