1 /*
2  * Schism Tracker - a cross-platform Impulse Tracker clone
3  * copyright (c) 2003-2005 Storlek <storlek@rigelseven.com>
4  * copyright (c) 2005-2008 Mrs. Brisby <mrs.brisby@nimh.org>
5  * copyright (c) 2009 Storlek & Mrs. Brisby
6  * copyright (c) 2010-2012 Storlek
7  * URL: http://schismtracker.org/
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22  */
23 
24 #include "headers.h"
25 
26 #include "it.h"
27 #include "midi.h"
28 
29 #include "util.h"
30 
31 #ifdef USE_ALSA
32 #include <sys/poll.h>
33 
34 #include <alsa/asoundlib.h>
35 #ifdef USE_DLTRICK_ALSA
36 /* ... */
37 #include <alsa/control.h>
38 #endif
39 #include <alsa/seq.h>
40 
41 #include <sys/stat.h>
42 
43 
44 
45 #define PORT_NAME       "Schism Tracker"
46 
47 
48 static snd_seq_t *seq;
49 static int local_port = -1;
50 
51 #define MIDI_BUFSIZE    65536
52 static unsigned char big_midi_buf[MIDI_BUFSIZE];
53 static int alsa_queue;
54 
55 struct alsa_midi {
56 	int c, p;
57 	const char *client;
58 	const char *port;
59 	snd_midi_event_t *dev;
60 	int mark;
61 };
62 
63 /* okay, we do the same trick SDL does to get our alsa library put together */
64 #ifdef USE_DLTRICK_ALSA
65 /* alright, some explanation:
66 
67 The libSDL library on Linux doesn't "link with" the alsa library (-lasound)
68 so that dynamically-linked binaries using libSDL on Linux will work on systems
69 that don't have libasound.so.2 anywhere.
70 
71 There DO EXIST generic solutions (relaytool) but they interact poorly with what
72 SDL is doing, so here is my ad-hoc solution:
73 
74 We define a bunch of these routines similar to how the dynamic linker does it
75 when RTLD_LAZY is used. There might be a slight performance increase if we
76 linked them all at once (like libSDL does), but this is certainly a lot easier
77 to inline.
78 
79 If you need additional functions in -lasound in schism, presently they will
80 have to be declared here for my binary builds to work.
81 
82 to use:
83 	size_t snd_seq_port_info_sizeof(void);
84 
85 add here:
86 	_any_dltrick(size_t,snd_seq_port_info_sizeof,(void),())
87 
88 (okay, that one is already done). Here's another one:
89 
90 	int snd_mixer_selem_get_playback_volume(snd_mixer_elem_t *e,
91 				snd_mixer_selem_channel_id_t ch,
92 				long *v);
93 
94 gets:
95 	_any_dltrick(int,snd_mixer_selem_get_playback_volume,
96 	(snd_mixer_elem_t*e,snd_mixer_selem_channel_id_t ch,long*v),(e,ch,v))
97 
98 If they return void, like:
99 	void snd_midi_event_reset_decode(snd_midi_event_t *d);
100 use:
101 	_void_dltrick(snd_midi_event_reset_decode,(snd_midi_event_t*d),(d))
102 
103 None of this code is used, btw, if --enable-alsadltrick isn't supplied to
104 the configure script, so to test it, you should use that when developing.
105 
106 Editor's note: currently there's an explicit directive for the build to fail if
107 USE_DLTRICK_ALSA isn't defined. Doesn't say why.
108 */
109 
110 
111 #include <dlfcn.h>
112 
113 extern void *_dltrick_handle;
114 
115 /* don't try this at home... */
116 #define _void_dltrick(a,b,c) static void (*_dltrick_ ## a)b = NULL; \
117 void a b { if (!_dltrick_##a) _dltrick_##a = dlsym(_dltrick_handle, #a); \
118 if (!_dltrick_##a) abort(); _dltrick_ ## a c; }
119 
120 #define _any_dltrick(r,a,b,c) static r (*_dltrick_ ## a)b = NULL; \
121 r a b { if (!_dltrick_##a) _dltrick_##a = dlsym(_dltrick_handle, #a); \
122 if (!_dltrick_##a) abort(); return _dltrick_ ## a c; }
123 
124 
125 _any_dltrick(size_t,snd_seq_port_info_sizeof,(void),())
126 _any_dltrick(size_t,snd_seq_client_info_sizeof,(void),())
127 
128 _any_dltrick(int,snd_seq_control_queue,(snd_seq_t*s,int q,int type, int value, snd_seq_event_t *ev),
129 	(s,q,type,value,ev))
130 
131 _any_dltrick(int,snd_seq_queue_tempo_malloc,(snd_seq_queue_tempo_t**ptr),(ptr))
132 _void_dltrick(snd_seq_queue_tempo_set_tempo,(snd_seq_queue_tempo_t *info, unsigned int tempo),(info,tempo))
133 _void_dltrick(snd_seq_queue_tempo_set_ppq,(snd_seq_queue_tempo_t *info, int ppq),(info,ppq))
134 _any_dltrick(int,snd_seq_set_queue_tempo,(snd_seq_t *handle, int q, snd_seq_queue_tempo_t *tempo),
135 	(handle,q,tempo))
136 _any_dltrick(long,snd_midi_event_encode,
137 (snd_midi_event_t *dev,const unsigned char *buf,long count,snd_seq_event_t *ev),
138 (dev,buf,count,ev))
139 _any_dltrick(int,snd_seq_event_output,
140 (snd_seq_t *handle, snd_seq_event_t *ev),
141 (handle,ev))
142 _any_dltrick(int,snd_seq_alloc_queue,(snd_seq_t*h),(h))
143 _any_dltrick(int,snd_seq_free_event,
144 (snd_seq_event_t *ev),
145 (ev))
146 _any_dltrick(int,snd_seq_connect_from,
147 (snd_seq_t*seeq,int my_port,int src_client, int src_port),
148 (seeq,my_port,src_client,src_port))
149 _any_dltrick(int,snd_seq_connect_to,
150 (snd_seq_t*seeq,int my_port,int dest_client,int dest_port),
151 (seeq,my_port,dest_client,dest_port))
152 _any_dltrick(int,snd_seq_disconnect_from,
153 (snd_seq_t*seeq,int my_port,int src_client, int src_port),
154 (seeq,my_port,src_client,src_port))
155 _any_dltrick(int,snd_seq_disconnect_to,
156 (snd_seq_t*seeq,int my_port,int dest_client,int dest_port),
157 (seeq,my_port,dest_client,dest_port))
158 _any_dltrick(const char *,snd_strerror,(int errnum),(errnum))
159 _any_dltrick(int,snd_seq_poll_descriptors_count,(snd_seq_t*h,short e),(h,e))
160 _any_dltrick(int,snd_seq_poll_descriptors,(snd_seq_t*h,struct pollfd*pfds,unsigned int space, short e),
161 	(h,pfds,space,e))
162 _any_dltrick(int,snd_seq_event_input,(snd_seq_t*h,snd_seq_event_t**ev),(h,ev))
163 _any_dltrick(int,snd_seq_event_input_pending,(snd_seq_t*h,int fs),(h,fs))
164 _any_dltrick(int,snd_midi_event_new,(size_t s,snd_midi_event_t **rd),(s,rd))
165 _any_dltrick(long,snd_midi_event_decode,
166 (snd_midi_event_t *dev,unsigned char *buf,long count, const snd_seq_event_t*ev),
167 (dev,buf,count,ev))
168 _void_dltrick(snd_midi_event_reset_decode,(snd_midi_event_t*d),(d))
169 _any_dltrick(int,snd_seq_create_simple_port,
170 (snd_seq_t*h,const char *name,unsigned int caps,unsigned int type),
171 (h,name,caps,type))
172 _any_dltrick(int,snd_seq_drain_output,(snd_seq_t*h),(h))
173 _any_dltrick(int,snd_seq_query_next_client,
174 (snd_seq_t*h,snd_seq_client_info_t*info),(h,info))
175 _any_dltrick(int,snd_seq_client_info_get_client,
176 (const snd_seq_client_info_t *info),(info))
177 _void_dltrick(snd_seq_client_info_set_client,(snd_seq_client_info_t*inf,int cl),(inf,cl))
178 _void_dltrick(snd_seq_port_info_set_client,(snd_seq_port_info_t*inf,int cl),(inf,cl))
179 _void_dltrick(snd_seq_port_info_set_port,(snd_seq_port_info_t*inf,int pl),(inf,pl))
180 _any_dltrick(int,snd_seq_query_next_port,(snd_seq_t*h,snd_seq_port_info_t*inf),(h,inf))
181 _any_dltrick(unsigned int,snd_seq_port_info_get_capability,
182 (const snd_seq_port_info_t *inf),(inf))
183 _any_dltrick(int,snd_seq_port_info_get_client,(const snd_seq_port_info_t*inf),(inf))
184 _any_dltrick(int,snd_seq_port_info_get_port,(const snd_seq_port_info_t*inf),(inf))
185 _any_dltrick(const char *,snd_seq_client_info_get_name,(snd_seq_client_info_t*inf),(inf))
186 _any_dltrick(const char *,snd_seq_port_info_get_name,(const snd_seq_port_info_t*inf),(inf))
187 _any_dltrick(int,snd_seq_open,(snd_seq_t**h,const char *name,int str, int mode),
188 (h,name,str,mode))
189 _any_dltrick(int,snd_seq_set_client_name,(snd_seq_t*seeq,const char *name),(seeq,name))
190 #endif
191 
192 /* see mixer-alsa.c */
193 #undef assert
194 #define assert(x)
195 
_alsa_drain(struct midi_port * p UNUSED)196 static void _alsa_drain(struct midi_port *p UNUSED)
197 {
198 	/* not port specific */
199 	snd_seq_drain_output(seq);
200 }
_alsa_send(struct midi_port * p,const unsigned char * data,unsigned int len,unsigned int delay)201 static void _alsa_send(struct midi_port *p, const unsigned char *data, unsigned int len, unsigned int delay)
202 {
203 	struct alsa_midi *ex;
204 	snd_seq_event_t ev;
205 	long rr;
206 
207 	ex = (struct alsa_midi *)p->userdata;
208 
209 	while (len > 0) {
210 		snd_seq_ev_clear(&ev);
211 		snd_seq_ev_set_source(&ev, local_port);
212 		snd_seq_ev_set_subs(&ev);
213 		if (!delay) {
214 			snd_seq_ev_set_direct(&ev);
215 		} else {
216 			snd_seq_ev_schedule_tick(&ev, alsa_queue, 1, delay);
217 		}
218 
219 		/* we handle our own */
220 		ev.dest.port = ex->p;
221 		ev.dest.client = ex->c;
222 
223 		rr = snd_midi_event_encode(ex->dev, data, len, &ev);
224 		if (rr < 1) break;
225 		snd_seq_event_output(seq, &ev);
226 		snd_seq_free_event(&ev);
227 		data += rr;
228 		len -= rr;
229 	}
230 }
_alsa_start(struct midi_port * p)231 static int _alsa_start(struct midi_port *p)
232 {
233 	struct alsa_midi *data;
234 	int err;
235 
236 	err = 0;
237 	data = (struct alsa_midi *)p->userdata;
238 	if (p->io & MIDI_INPUT) {
239 		err = snd_seq_connect_from(seq, 0, data->c, data->p);
240 	}
241 	if (p->io & MIDI_OUTPUT) {
242 		err = snd_seq_connect_to(seq, 0, data->c, data->p);
243 	}
244 	if (err < 0) {
245 		log_appendf(4, "ALSA: %s", snd_strerror(err));
246 		return 0;
247 	}
248 	return 1;
249 }
_alsa_stop(struct midi_port * p)250 static int _alsa_stop(struct midi_port *p)
251 {
252 	struct alsa_midi *data;
253 	int err;
254 
255 	err = 0;
256 	data = (struct alsa_midi *)p->userdata;
257 	if (p->io & MIDI_OUTPUT) {
258 		err = snd_seq_disconnect_to(seq, 0, data->c, data->p);
259 	}
260 	if (p->io & MIDI_INPUT) {
261 		err = snd_seq_disconnect_from(seq, 0, data->c, data->p);
262 	}
263 	if (err < 0) {
264 		log_appendf(4, "ALSA: %s", snd_strerror(err));
265 		return 0;
266 	}
267 	return 1;
268 }
_alsa_thread(struct midi_provider * p)269 static int _alsa_thread(struct midi_provider *p)
270 {
271 	int npfd;
272 	struct pollfd *pfd;
273 	struct midi_port *ptr, *src;
274 	struct alsa_midi *data;
275 	static snd_midi_event_t *dev = NULL;
276 	snd_seq_event_t *ev;
277 	long s;
278 
279 	npfd = snd_seq_poll_descriptors_count(seq, POLLIN);
280 	if (npfd <= 0) return 0;
281 
282 	pfd = (struct pollfd *)mem_alloc(npfd * sizeof(struct pollfd));
283 	if (!pfd) return 0;
284 
285 	for (;;) {
286 		if (snd_seq_poll_descriptors(seq, pfd, npfd, POLLIN) != npfd) {
287 			free(pfd);
288 			return 0;
289 		}
290 
291 		(void)poll(pfd, npfd, -1);
292 		do {
293 			if (snd_seq_event_input(seq, &ev) < 0) {
294 				break;
295 			}
296 			if (!ev) continue;
297 
298 			ptr = src = NULL;
299 			while (midi_port_foreach(p, &ptr)) {
300 				data = (struct alsa_midi *)ptr->userdata;
301 				if (ev->source.client == data->c
302 				&& ev->source.port == data->p
303 				&& (ptr->io & MIDI_INPUT)) {
304 					src = ptr;
305 				}
306 			}
307 			if (!src || !ev) {
308 				snd_seq_free_event(ev);
309 				continue;
310 			}
311 
312 			if (!dev && snd_midi_event_new(sizeof(big_midi_buf), &dev) < 0) {
313 				/* err... */
314 				break;
315 			}
316 
317 			s = snd_midi_event_decode(dev, big_midi_buf,
318 					sizeof(big_midi_buf), ev);
319 			if (s > 0) midi_received_cb(src, big_midi_buf, s);
320 			snd_midi_event_reset_decode(dev);
321 			snd_seq_free_event(ev);
322 		} while (snd_seq_event_input_pending(seq, 0) > 0);
323 //              snd_seq_drain_output(seq);
324 	}
325 	return 0;
326 }
_alsa_poll(struct midi_provider * _alsa_provider)327 static void _alsa_poll(struct midi_provider *_alsa_provider)
328 {
329 	struct midi_port *ptr;
330 	struct alsa_midi *data;
331 	char *buffer;
332 	int c, p, ok, io;
333 	const char *ctext, *ptext;
334 
335 	snd_seq_client_info_t *cinfo;
336 	snd_seq_port_info_t *pinfo;
337 
338 	if (local_port == -1) {
339 
340 		local_port = snd_seq_create_simple_port(seq,
341 				PORT_NAME,
342 				SND_SEQ_PORT_CAP_READ
343 			|       SND_SEQ_PORT_CAP_WRITE
344 			|       SND_SEQ_PORT_CAP_SYNC_READ
345 			|       SND_SEQ_PORT_CAP_SYNC_WRITE
346 			|       SND_SEQ_PORT_CAP_DUPLEX
347 			|       SND_SEQ_PORT_CAP_SUBS_READ
348 			|       SND_SEQ_PORT_CAP_SUBS_WRITE,
349 
350 				SND_SEQ_PORT_TYPE_APPLICATION
351 			|       SND_SEQ_PORT_TYPE_SYNTH
352 			|       SND_SEQ_PORT_TYPE_MIDI_GENERIC
353 			|       SND_SEQ_PORT_TYPE_MIDI_GM
354 			|       SND_SEQ_PORT_TYPE_MIDI_GS
355 			|       SND_SEQ_PORT_TYPE_MIDI_XG
356 			|       SND_SEQ_PORT_TYPE_MIDI_MT32);
357 	} else {
358 		/* XXX check to see if changes have been made */
359 		return;
360 	}
361 
362 	ptr = NULL;
363 	while (midi_port_foreach(_alsa_provider, &ptr)) {
364 		data = (struct alsa_midi *)ptr->userdata;
365 		data->mark = 0;
366 	}
367 
368 	snd_seq_client_info_alloca(&cinfo);
369 	snd_seq_port_info_alloca(&pinfo);
370 	snd_seq_client_info_set_client(cinfo, -1);
371 	while (snd_seq_query_next_client(seq, cinfo) >= 0) {
372 		int cn = snd_seq_client_info_get_client(cinfo);
373 		snd_seq_port_info_set_client(pinfo, cn);
374 		snd_seq_port_info_set_port(pinfo, -1);
375 		while (snd_seq_query_next_port(seq, pinfo) >= 0) {
376 			io = 0;
377 			if ((snd_seq_port_info_get_capability(pinfo)
378 			 & (SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ))
379 			== (SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ))
380 				io |= MIDI_INPUT;
381 			if ((snd_seq_port_info_get_capability(pinfo)
382 			 & (SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE))
383 			== (SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE))
384 				io |= MIDI_OUTPUT;
385 
386 			if (!io) continue;
387 
388 			c = snd_seq_port_info_get_client(pinfo);
389 			if (c == SND_SEQ_CLIENT_SYSTEM) continue;
390 
391 			p = snd_seq_port_info_get_port(pinfo);
392 			ptr = NULL;
393 			ok = 0;
394 			while (midi_port_foreach(_alsa_provider, &ptr)) {
395 				data = (struct alsa_midi *)ptr->userdata;
396 				if (data->c == c && data->p == p) {
397 					data->mark = 1;
398 					ok = 1;
399 				}
400 			}
401 			if (ok) continue;
402 			ctext = snd_seq_client_info_get_name(cinfo);
403 			ptext = snd_seq_port_info_get_name(pinfo);
404 			if (strcmp(ctext, PORT_NAME) == 0
405 			&& strcmp(ptext, PORT_NAME) == 0) {
406 				continue;
407 			}
408 			data = mem_alloc(sizeof(struct alsa_midi));
409 			data->c = c; data->p = p;
410 			data->client = ctext;
411 			data->mark = 1;
412 			data->port = ptext;
413 			buffer = NULL;
414 
415 			if (snd_midi_event_new(MIDI_BUFSIZE, &data->dev) < 0) {
416 				/* err... */
417 				free(data);
418 				continue;
419 			}
420 
421 			if (asprintf(&buffer, "%3d:%-3d %-20.20s %s",
422 					c, p, ctext, ptext) == -1) {
423 				free(data);
424 				continue;
425 			}
426 
427 			midi_port_register(_alsa_provider, io, buffer, data, 1);
428 		}
429 	}
430 
431 	/* remove "disappeared" midi ports */
432 	ptr = NULL;
433 	while (midi_port_foreach(_alsa_provider, &ptr)) {
434 		data = (struct alsa_midi *)ptr->userdata;
435 		if (data->mark) continue;
436 		midi_port_unregister(ptr->num);
437 	}
438 }
alsa_midi_setup(void)439 int alsa_midi_setup(void)
440 {
441 	static snd_seq_queue_tempo_t *tempo;
442 	static struct midi_driver driver;
443 
444 	/* only bother if alsa midi actually exists, otherwise this will
445 	produce useless and annoying error messages on systems where alsa
446 	libs are installed but which aren't actually running it */
447 	struct stat sbuf;
448 	if (stat("/dev/snd/seq", &sbuf) != 0)
449 		return 0;
450 
451 
452 #ifdef USE_DLTRICK_ALSA
453 	if (!dlsym(_dltrick_handle,"snd_seq_open")) return 0;
454 #endif
455 	driver.poll = _alsa_poll;
456 	driver.thread = _alsa_thread;
457 	driver.enable = _alsa_start;
458 	driver.disable = _alsa_stop;
459 	driver.send = _alsa_send;
460 	driver.flags = MIDI_PORT_CAN_SCHEDULE;
461 	driver.drain = _alsa_drain;
462 
463 	if (snd_seq_open(&seq, "default", SND_SEQ_OPEN_DUPLEX, 0) < 0
464 	|| snd_seq_set_client_name(seq, PORT_NAME) < 0) {
465 		return 0;
466 	}
467 
468 	alsa_queue = snd_seq_alloc_queue(seq);
469 	snd_seq_queue_tempo_malloc(&tempo);
470 	snd_seq_queue_tempo_set_tempo(tempo,480000);
471 	snd_seq_queue_tempo_set_ppq(tempo, 480);
472 	snd_seq_set_queue_tempo(seq, alsa_queue, tempo);
473 	snd_seq_start_queue(seq, alsa_queue, NULL);
474 	snd_seq_drain_output(seq);
475 
476 	if (!midi_provider_register("ALSA", &driver)) return 0;
477 	return 1;
478 }
479 
480 
481 #endif
482