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