summaryrefslogtreecommitdiff
path: root/src/sys/alsa/midi-alsa.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/sys/alsa/midi-alsa.c')
-rw-r--r--src/sys/alsa/midi-alsa.c481
1 files changed, 481 insertions, 0 deletions
diff --git a/src/sys/alsa/midi-alsa.c b/src/sys/alsa/midi-alsa.c
new file mode 100644
index 0000000..9314939
--- /dev/null
+++ b/src/sys/alsa/midi-alsa.c
@@ -0,0 +1,481 @@
+/*
+ * Schism Tracker - a cross-platform Impulse Tracker clone
+ * copyright (c) 2003-2005 Storlek <storlek@rigelseven.com>
+ * copyright (c) 2005-2008 Mrs. Brisby <mrs.brisby@nimh.org>
+ * copyright (c) 2009 Storlek & Mrs. Brisby
+ * copyright (c) 2010-2012 Storlek
+ * URL: http://schismtracker.org/
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "headers.h"
+
+#include "it.h"
+#include "midi.h"
+
+#include "util.h"
+
+#ifdef USE_ALSA
+#include <sys/poll.h>
+
+#include <alsa/asoundlib.h>
+#ifdef USE_DLTRICK_ALSA
+/* ... */
+#include <alsa/control.h>
+#endif
+#include <alsa/seq.h>
+
+#include <sys/stat.h>
+
+
+
+#define PORT_NAME "Schism Tracker"
+
+
+static snd_seq_t *seq;
+static int local_port = -1;
+
+#define MIDI_BUFSIZE 65536
+static unsigned char big_midi_buf[MIDI_BUFSIZE];
+static int alsa_queue;
+
+struct alsa_midi {
+ int c, p;
+ const char *client;
+ const char *port;
+ snd_midi_event_t *dev;
+ int mark;
+};
+
+/* okay, we do the same trick SDL does to get our alsa library put together */
+#ifdef USE_DLTRICK_ALSA
+/* alright, some explanation:
+
+The libSDL library on Linux doesn't "link with" the alsa library (-lasound)
+so that dynamically-linked binaries using libSDL on Linux will work on systems
+that don't have libasound.so.2 anywhere.
+
+There DO EXIST generic solutions (relaytool) but they interact poorly with what
+SDL is doing, so here is my ad-hoc solution:
+
+We define a bunch of these routines similar to how the dynamic linker does it
+when RTLD_LAZY is used. There might be a slight performance increase if we
+linked them all at once (like libSDL does), but this is certainly a lot easier
+to inline.
+
+If you need additional functions in -lasound in schism, presently they will
+have to be declared here for my binary builds to work.
+
+to use:
+ size_t snd_seq_port_info_sizeof(void);
+
+add here:
+ _any_dltrick(size_t,snd_seq_port_info_sizeof,(void),())
+
+(okay, that one is already done). Here's another one:
+
+ int snd_mixer_selem_get_playback_volume(snd_mixer_elem_t *e,
+ snd_mixer_selem_channel_id_t ch,
+ long *v);
+
+gets:
+ _any_dltrick(int,snd_mixer_selem_get_playback_volume,
+ (snd_mixer_elem_t*e,snd_mixer_selem_channel_id_t ch,long*v),(e,ch,v))
+
+If they return void, like:
+ void snd_midi_event_reset_decode(snd_midi_event_t *d);
+use:
+ _void_dltrick(snd_midi_event_reset_decode,(snd_midi_event_t*d),(d))
+
+None of this code is used, btw, if --enable-alsadltrick isn't supplied to
+the configure script, so to test it, you should use that when developing.
+
+Editor's note: currently there's an explicit directive for the build to fail if
+USE_DLTRICK_ALSA isn't defined. Doesn't say why.
+*/
+
+
+#include <dlfcn.h>
+
+extern void *_dltrick_handle;
+
+/* don't try this at home... */
+#define _void_dltrick(a,b,c) static void (*_dltrick_ ## a)b = NULL; \
+void a b { if (!_dltrick_##a) _dltrick_##a = dlsym(_dltrick_handle, #a); \
+if (!_dltrick_##a) abort(); _dltrick_ ## a c; }
+
+#define _any_dltrick(r,a,b,c) static r (*_dltrick_ ## a)b = NULL; \
+r a b { if (!_dltrick_##a) _dltrick_##a = dlsym(_dltrick_handle, #a); \
+if (!_dltrick_##a) abort(); return _dltrick_ ## a c; }
+
+
+_any_dltrick(size_t,snd_seq_port_info_sizeof,(void),())
+_any_dltrick(size_t,snd_seq_client_info_sizeof,(void),())
+
+_any_dltrick(int,snd_seq_control_queue,(snd_seq_t*s,int q,int type, int value, snd_seq_event_t *ev),
+ (s,q,type,value,ev))
+
+_any_dltrick(int,snd_seq_queue_tempo_malloc,(snd_seq_queue_tempo_t**ptr),(ptr))
+_void_dltrick(snd_seq_queue_tempo_set_tempo,(snd_seq_queue_tempo_t *info, unsigned int tempo),(info,tempo))
+_void_dltrick(snd_seq_queue_tempo_set_ppq,(snd_seq_queue_tempo_t *info, int ppq),(info,ppq))
+_any_dltrick(int,snd_seq_set_queue_tempo,(snd_seq_t *handle, int q, snd_seq_queue_tempo_t *tempo),
+ (handle,q,tempo))
+_any_dltrick(long,snd_midi_event_encode,
+(snd_midi_event_t *dev,const unsigned char *buf,long count,snd_seq_event_t *ev),
+(dev,buf,count,ev))
+_any_dltrick(int,snd_seq_event_output,
+(snd_seq_t *handle, snd_seq_event_t *ev),
+(handle,ev))
+_any_dltrick(int,snd_seq_alloc_queue,(snd_seq_t*h),(h))
+_any_dltrick(int,snd_seq_free_event,
+(snd_seq_event_t *ev),
+(ev))
+_any_dltrick(int,snd_seq_connect_from,
+(snd_seq_t*seeq,int my_port,int src_client, int src_port),
+(seeq,my_port,src_client,src_port))
+_any_dltrick(int,snd_seq_connect_to,
+(snd_seq_t*seeq,int my_port,int dest_client,int dest_port),
+(seeq,my_port,dest_client,dest_port))
+_any_dltrick(int,snd_seq_disconnect_from,
+(snd_seq_t*seeq,int my_port,int src_client, int src_port),
+(seeq,my_port,src_client,src_port))
+_any_dltrick(int,snd_seq_disconnect_to,
+(snd_seq_t*seeq,int my_port,int dest_client,int dest_port),
+(seeq,my_port,dest_client,dest_port))
+_any_dltrick(const char *,snd_strerror,(int errnum),(errnum))
+_any_dltrick(int,snd_seq_poll_descriptors_count,(snd_seq_t*h,short e),(h,e))
+_any_dltrick(int,snd_seq_poll_descriptors,(snd_seq_t*h,struct pollfd*pfds,unsigned int space, short e),
+ (h,pfds,space,e))
+_any_dltrick(int,snd_seq_event_input,(snd_seq_t*h,snd_seq_event_t**ev),(h,ev))
+_any_dltrick(int,snd_seq_event_input_pending,(snd_seq_t*h,int fs),(h,fs))
+_any_dltrick(int,snd_midi_event_new,(size_t s,snd_midi_event_t **rd),(s,rd))
+_any_dltrick(long,snd_midi_event_decode,
+(snd_midi_event_t *dev,unsigned char *buf,long count, const snd_seq_event_t*ev),
+(dev,buf,count,ev))
+_void_dltrick(snd_midi_event_reset_decode,(snd_midi_event_t*d),(d))
+_any_dltrick(int,snd_seq_create_simple_port,
+(snd_seq_t*h,const char *name,unsigned int caps,unsigned int type),
+(h,name,caps,type))
+_any_dltrick(int,snd_seq_drain_output,(snd_seq_t*h),(h))
+_any_dltrick(int,snd_seq_query_next_client,
+(snd_seq_t*h,snd_seq_client_info_t*info),(h,info))
+_any_dltrick(int,snd_seq_client_info_get_client,
+(const snd_seq_client_info_t *info),(info))
+_void_dltrick(snd_seq_client_info_set_client,(snd_seq_client_info_t*inf,int cl),(inf,cl))
+_void_dltrick(snd_seq_port_info_set_client,(snd_seq_port_info_t*inf,int cl),(inf,cl))
+_void_dltrick(snd_seq_port_info_set_port,(snd_seq_port_info_t*inf,int pl),(inf,pl))
+_any_dltrick(int,snd_seq_query_next_port,(snd_seq_t*h,snd_seq_port_info_t*inf),(h,inf))
+_any_dltrick(unsigned int,snd_seq_port_info_get_capability,
+(const snd_seq_port_info_t *inf),(inf))
+_any_dltrick(int,snd_seq_port_info_get_client,(const snd_seq_port_info_t*inf),(inf))
+_any_dltrick(int,snd_seq_port_info_get_port,(const snd_seq_port_info_t*inf),(inf))
+_any_dltrick(const char *,snd_seq_client_info_get_name,(snd_seq_client_info_t*inf),(inf))
+_any_dltrick(const char *,snd_seq_port_info_get_name,(const snd_seq_port_info_t*inf),(inf))
+_any_dltrick(int,snd_seq_open,(snd_seq_t**h,const char *name,int str, int mode),
+(h,name,str,mode))
+_any_dltrick(int,snd_seq_set_client_name,(snd_seq_t*seeq,const char *name),(seeq,name))
+#endif
+
+/* see mixer-alsa.c */
+#undef assert
+#define assert(x)
+
+static void _alsa_drain(struct midi_port *p UNUSED)
+{
+ /* not port specific */
+ snd_seq_drain_output(seq);
+}
+static void _alsa_send(struct midi_port *p, const unsigned char *data, unsigned int len, unsigned int delay)
+{
+ struct alsa_midi *ex;
+ snd_seq_event_t ev;
+ long rr;
+
+ ex = (struct alsa_midi *)p->userdata;
+
+ while (len > 0) {
+ snd_seq_ev_clear(&ev);
+ snd_seq_ev_set_source(&ev, local_port);
+ snd_seq_ev_set_subs(&ev);
+ if (!delay) {
+ snd_seq_ev_set_direct(&ev);
+ } else {
+ snd_seq_ev_schedule_tick(&ev, alsa_queue, 1, delay);
+ }
+
+ /* we handle our own */
+ ev.dest.port = ex->p;
+ ev.dest.client = ex->c;
+
+ rr = snd_midi_event_encode(ex->dev, data, len, &ev);
+ if (rr < 1) break;
+ snd_seq_event_output(seq, &ev);
+ snd_seq_free_event(&ev);
+ data += rr;
+ len -= rr;
+ }
+}
+static int _alsa_start(struct midi_port *p)
+{
+ struct alsa_midi *data;
+ int err;
+
+ err = 0;
+ data = (struct alsa_midi *)p->userdata;
+ if (p->io & MIDI_INPUT) {
+ err = snd_seq_connect_from(seq, 0, data->c, data->p);
+ }
+ if (p->io & MIDI_OUTPUT) {
+ err = snd_seq_connect_to(seq, 0, data->c, data->p);
+ }
+ if (err < 0) {
+ log_appendf(4, "ALSA: %s", snd_strerror(err));
+ return 0;
+ }
+ return 1;
+}
+static int _alsa_stop(struct midi_port *p)
+{
+ struct alsa_midi *data;
+ int err;
+
+ err = 0;
+ data = (struct alsa_midi *)p->userdata;
+ if (p->io & MIDI_OUTPUT) {
+ err = snd_seq_disconnect_to(seq, 0, data->c, data->p);
+ }
+ if (p->io & MIDI_INPUT) {
+ err = snd_seq_disconnect_from(seq, 0, data->c, data->p);
+ }
+ if (err < 0) {
+ log_appendf(4, "ALSA: %s", snd_strerror(err));
+ return 0;
+ }
+ return 1;
+}
+static int _alsa_thread(struct midi_provider *p)
+{
+ int npfd;
+ struct pollfd *pfd;
+ struct midi_port *ptr, *src;
+ struct alsa_midi *data;
+ static snd_midi_event_t *dev = NULL;
+ snd_seq_event_t *ev;
+ long s;
+
+ npfd = snd_seq_poll_descriptors_count(seq, POLLIN);
+ if (npfd <= 0) return 0;
+
+ pfd = (struct pollfd *)mem_alloc(npfd * sizeof(struct pollfd));
+ if (!pfd) return 0;
+
+ for (;;) {
+ if (snd_seq_poll_descriptors(seq, pfd, npfd, POLLIN) != npfd) {
+ free(pfd);
+ return 0;
+ }
+
+ (void)poll(pfd, npfd, -1);
+ do {
+ if (snd_seq_event_input(seq, &ev) < 0) {
+ break;
+ }
+ if (!ev) continue;
+
+ ptr = src = NULL;
+ while (midi_port_foreach(p, &ptr)) {
+ data = (struct alsa_midi *)ptr->userdata;
+ if (ev->source.client == data->c
+ && ev->source.port == data->p
+ && (ptr->io & MIDI_INPUT)) {
+ src = ptr;
+ }
+ }
+ if (!src || !ev) {
+ snd_seq_free_event(ev);
+ continue;
+ }
+
+ if (!dev && snd_midi_event_new(sizeof(big_midi_buf), &dev) < 0) {
+ /* err... */
+ break;
+ }
+
+ s = snd_midi_event_decode(dev, big_midi_buf,
+ sizeof(big_midi_buf), ev);
+ if (s > 0) midi_received_cb(src, big_midi_buf, s);
+ snd_midi_event_reset_decode(dev);
+ snd_seq_free_event(ev);
+ } while (snd_seq_event_input_pending(seq, 0) > 0);
+// snd_seq_drain_output(seq);
+ }
+ return 0;
+}
+static void _alsa_poll(struct midi_provider *_alsa_provider)
+{
+ struct midi_port *ptr;
+ struct alsa_midi *data;
+ char *buffer;
+ int c, p, ok, io;
+ const char *ctext, *ptext;
+
+ snd_seq_client_info_t *cinfo;
+ snd_seq_port_info_t *pinfo;
+
+ if (local_port == -1) {
+
+ local_port = snd_seq_create_simple_port(seq,
+ PORT_NAME,
+ SND_SEQ_PORT_CAP_READ
+ | SND_SEQ_PORT_CAP_WRITE
+ | SND_SEQ_PORT_CAP_SYNC_READ
+ | SND_SEQ_PORT_CAP_SYNC_WRITE
+ | SND_SEQ_PORT_CAP_DUPLEX
+ | SND_SEQ_PORT_CAP_SUBS_READ
+ | SND_SEQ_PORT_CAP_SUBS_WRITE,
+
+ SND_SEQ_PORT_TYPE_APPLICATION
+ | SND_SEQ_PORT_TYPE_SYNTH
+ | SND_SEQ_PORT_TYPE_MIDI_GENERIC
+ | SND_SEQ_PORT_TYPE_MIDI_GM
+ | SND_SEQ_PORT_TYPE_MIDI_GS
+ | SND_SEQ_PORT_TYPE_MIDI_XG
+ | SND_SEQ_PORT_TYPE_MIDI_MT32);
+ } else {
+ /* XXX check to see if changes have been made */
+ return;
+ }
+
+ ptr = NULL;
+ while (midi_port_foreach(_alsa_provider, &ptr)) {
+ data = (struct alsa_midi *)ptr->userdata;
+ data->mark = 0;
+ }
+
+ snd_seq_client_info_alloca(&cinfo);
+ snd_seq_port_info_alloca(&pinfo);
+ snd_seq_client_info_set_client(cinfo, -1);
+ while (snd_seq_query_next_client(seq, cinfo) >= 0) {
+ int cn = snd_seq_client_info_get_client(cinfo);
+ snd_seq_port_info_set_client(pinfo, cn);
+ snd_seq_port_info_set_port(pinfo, -1);
+ while (snd_seq_query_next_port(seq, pinfo) >= 0) {
+ io = 0;
+ if ((snd_seq_port_info_get_capability(pinfo)
+ & (SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ))
+ == (SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ))
+ io |= MIDI_INPUT;
+ if ((snd_seq_port_info_get_capability(pinfo)
+ & (SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE))
+ == (SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE))
+ io |= MIDI_OUTPUT;
+
+ if (!io) continue;
+
+ c = snd_seq_port_info_get_client(pinfo);
+ if (c == SND_SEQ_CLIENT_SYSTEM) continue;
+
+ p = snd_seq_port_info_get_port(pinfo);
+ ptr = NULL;
+ ok = 0;
+ while (midi_port_foreach(_alsa_provider, &ptr)) {
+ data = (struct alsa_midi *)ptr->userdata;
+ if (data->c == c && data->p == p) {
+ data->mark = 1;
+ ok = 1;
+ }
+ }
+ if (ok) continue;
+ ctext = snd_seq_client_info_get_name(cinfo);
+ ptext = snd_seq_port_info_get_name(pinfo);
+ if (strcmp(ctext, PORT_NAME) == 0
+ && strcmp(ptext, PORT_NAME) == 0) {
+ continue;
+ }
+ data = mem_alloc(sizeof(struct alsa_midi));
+ data->c = c; data->p = p;
+ data->client = ctext;
+ data->mark = 1;
+ data->port = ptext;
+ buffer = NULL;
+
+ if (snd_midi_event_new(MIDI_BUFSIZE, &data->dev) < 0) {
+ /* err... */
+ free(data);
+ continue;
+ }
+
+ if (asprintf(&buffer, "%3d:%-3d %-20.20s %s",
+ c, p, ctext, ptext) == -1) {
+ free(data);
+ continue;
+ }
+
+ midi_port_register(_alsa_provider, io, buffer, data, 1);
+ }
+ }
+
+ /* remove "disappeared" midi ports */
+ ptr = NULL;
+ while (midi_port_foreach(_alsa_provider, &ptr)) {
+ data = (struct alsa_midi *)ptr->userdata;
+ if (data->mark) continue;
+ midi_port_unregister(ptr->num);
+ }
+}
+int alsa_midi_setup(void)
+{
+ static snd_seq_queue_tempo_t *tempo;
+ static struct midi_driver driver;
+
+ /* only bother if alsa midi actually exists, otherwise this will
+ produce useless and annoying error messages on systems where alsa
+ libs are installed but which aren't actually running it */
+ struct stat sbuf;
+ if (stat("/dev/snd/seq", &sbuf) != 0)
+ return 0;
+
+
+#ifdef USE_DLTRICK_ALSA
+ if (!dlsym(_dltrick_handle,"snd_seq_open")) return 0;
+#endif
+ driver.poll = _alsa_poll;
+ driver.thread = _alsa_thread;
+ driver.enable = _alsa_start;
+ driver.disable = _alsa_stop;
+ driver.send = _alsa_send;
+ driver.flags = MIDI_PORT_CAN_SCHEDULE;
+ driver.drain = _alsa_drain;
+
+ if (snd_seq_open(&seq, "default", SND_SEQ_OPEN_DUPLEX, 0) < 0
+ || snd_seq_set_client_name(seq, PORT_NAME) < 0) {
+ return 0;
+ }
+
+ alsa_queue = snd_seq_alloc_queue(seq);
+ snd_seq_queue_tempo_malloc(&tempo);
+ snd_seq_queue_tempo_set_tempo(tempo,480000);
+ snd_seq_queue_tempo_set_ppq(tempo, 480);
+ snd_seq_set_queue_tempo(seq, alsa_queue, tempo);
+ snd_seq_start_queue(seq, alsa_queue, NULL);
+ snd_seq_drain_output(seq);
+
+ if (!midi_provider_register("ALSA", &driver)) return 0;
+ return 1;
+}
+
+
+#endif
© All Rights Reserved