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