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