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