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