From 8fbd62a39d1c0c776dbe90d2be1cb2364eb28747 Mon Sep 17 00:00:00 2001 From: Paul Davis Date: Fri, 11 Oct 2013 14:45:07 -0400 Subject: add ALSA MIDI code back as (slave) driver --- drivers/Makefile.am | 4 +- drivers/alsa-midi/Makefile.am | 16 - drivers/alsa-midi/alsa_midi.h | 48 -- drivers/alsa-midi/alsa_midi_driver.c | 122 ---- drivers/alsa-midi/alsa_rawmidi.c | 1204 ---------------------------------- drivers/alsa-midi/alsa_seqmidi.c | 925 -------------------------- drivers/alsa-midi/midi_pack.h | 49 -- drivers/alsa-midi/midi_unpack.h | 142 ---- drivers/alsa/Makefile.am | 5 +- drivers/alsa/alsa_driver.c | 581 ++++++++-------- drivers/alsa/alsa_driver.h | 3 - drivers/alsa/usx2y.c | 12 - drivers/alsa_midi/Makefile.am | 16 + drivers/alsa_midi/a2j.h | 139 ++++ drivers/alsa_midi/alsa_midi.c | 864 ++++++++++++++++++++++++ drivers/alsa_midi/alsa_midi_driver.c | 48 ++ drivers/alsa_midi/list.c | 147 +++++ drivers/alsa_midi/list.h | 903 +++++++++++++++++++++++++ drivers/alsa_midi/port.c | 217 ++++++ drivers/alsa_midi/port.h | 29 + drivers/alsa_midi/port_hash.c | 63 ++ drivers/alsa_midi/port_hash.h | 35 + drivers/alsa_midi/port_thread.c | 235 +++++++ drivers/alsa_midi/port_thread.h | 31 + drivers/am/Makefile.am | 16 + drivers/am/alsa_midi.h | 48 ++ drivers/am/alsa_midi_driver.c | 122 ++++ drivers/am/alsa_rawmidi.c | 1204 ++++++++++++++++++++++++++++++++++ drivers/am/alsa_seqmidi.c | 925 ++++++++++++++++++++++++++ drivers/am/midi_pack.h | 49 ++ drivers/am/midi_unpack.h | 142 ++++ 31 files changed, 5508 insertions(+), 2836 deletions(-) delete mode 100644 drivers/alsa-midi/Makefile.am delete mode 100644 drivers/alsa-midi/alsa_midi.h delete mode 100644 drivers/alsa-midi/alsa_midi_driver.c delete mode 100644 drivers/alsa-midi/alsa_rawmidi.c delete mode 100644 drivers/alsa-midi/alsa_seqmidi.c delete mode 100644 drivers/alsa-midi/midi_pack.h delete mode 100644 drivers/alsa-midi/midi_unpack.h create mode 100644 drivers/alsa_midi/Makefile.am create mode 100644 drivers/alsa_midi/a2j.h create mode 100644 drivers/alsa_midi/alsa_midi.c create mode 100644 drivers/alsa_midi/alsa_midi_driver.c create mode 100644 drivers/alsa_midi/list.c create mode 100644 drivers/alsa_midi/list.h create mode 100644 drivers/alsa_midi/port.c create mode 100644 drivers/alsa_midi/port.h create mode 100644 drivers/alsa_midi/port_hash.c create mode 100644 drivers/alsa_midi/port_hash.h create mode 100644 drivers/alsa_midi/port_thread.c create mode 100644 drivers/alsa_midi/port_thread.h create mode 100644 drivers/am/Makefile.am create mode 100644 drivers/am/alsa_midi.h create mode 100644 drivers/am/alsa_midi_driver.c create mode 100644 drivers/am/alsa_rawmidi.c create mode 100644 drivers/am/alsa_seqmidi.c create mode 100644 drivers/am/midi_pack.h create mode 100644 drivers/am/midi_unpack.h (limited to 'drivers') diff --git a/drivers/Makefile.am b/drivers/Makefile.am index 4d232c2..b159b19 100644 --- a/drivers/Makefile.am +++ b/drivers/Makefile.am @@ -7,7 +7,7 @@ ALSA_DIR = endif if HAVE_ALSA_MIDI -ALSA_MIDI_DIR = alsa-midi +ALSA_MIDI_DIR = alsa_midi else ALSA_MIDI_DIR = endif @@ -49,4 +49,4 @@ FIREWIRE_DIR = endif SUBDIRS = $(ALSA_MIDI_DIR) $(ALSA_DIR) dummy $(OSS_DIR) $(SUN_DIR) $(PA_DIR) $(CA_DIR) $(FREEBOB_DIR) $(FIREWIRE_DIR) netjack -DIST_SUBDIRS = alsa alsa-midi dummy oss sun portaudio coreaudio freebob firewire netjack +DIST_SUBDIRS = alsa alsa_midi dummy oss sun portaudio coreaudio freebob firewire netjack diff --git a/drivers/alsa-midi/Makefile.am b/drivers/alsa-midi/Makefile.am deleted file mode 100644 index 5c28128..0000000 --- a/drivers/alsa-midi/Makefile.am +++ /dev/null @@ -1,16 +0,0 @@ -MAINTAINERCLEANFILES=Makefile.in - -AM_CFLAGS = $(JACK_CFLAGS) - -plugindir = $(ADDON_DIR) - -plugin_LTLIBRARIES = jack_alsa_midi.la - -jack_alsa_midi_la_LDFLAGS = -module -avoid-version -jack_alsa_midi_la_SOURCES = alsa_rawmidi.c alsa_seqmidi.c alsa_midi_driver.c - -noinst_HEADERS = alsa_midi.h \ - midi_pack.h \ - midi_unpack.h - -jack_alsa_midi_la_LIBADD = $(ALSA_LIBS) diff --git a/drivers/alsa-midi/alsa_midi.h b/drivers/alsa-midi/alsa_midi.h deleted file mode 100644 index b49228a..0000000 --- a/drivers/alsa-midi/alsa_midi.h +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (c) 2006 Dmitry S. Baikov - * - * 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 - */ - -#ifndef __jack_alsa_midi_h__ -#define __jack_alsa_midi_h__ - -#include -#include "driver.h" - -typedef struct alsa_midi_t alsa_midi_t; -struct alsa_midi_t { - void (*destroy)(alsa_midi_t *amidi); - int (*attach)(alsa_midi_t *amidi); - int (*detach)(alsa_midi_t *amidi); - int (*start)(alsa_midi_t *amidi); - int (*stop)(alsa_midi_t *amidi); - void (*read)(alsa_midi_t *amidi, jack_nframes_t nframes); - void (*write)(alsa_midi_t *amidi, jack_nframes_t nframes); -}; - -alsa_midi_t* alsa_rawmidi_new(jack_client_t *jack); -alsa_midi_t* alsa_seqmidi_new(jack_client_t *jack, const char* alsa_name); - -typedef struct _alsa_midi_driver { - - JACK_DRIVER_DECL; - - alsa_midi_t *midi; - jack_client_t *client; - -} alsa_midi_driver_t; - -#endif /* __jack_alsa_midi_h__ */ diff --git a/drivers/alsa-midi/alsa_midi_driver.c b/drivers/alsa-midi/alsa_midi_driver.c deleted file mode 100644 index b27f03c..0000000 --- a/drivers/alsa-midi/alsa_midi_driver.c +++ /dev/null @@ -1,122 +0,0 @@ - -#include "alsa_midi.h" -#include - -static int -alsa_midi_driver_attach( alsa_midi_driver_t *driver, jack_engine_t *engine ) -{ - return driver->midi->attach(driver->midi); -} - -static int -alsa_midi_driver_detach( alsa_midi_driver_t *driver, jack_engine_t *engine ) -{ - return driver->midi->detach(driver->midi); -} - -static int -alsa_midi_driver_read( alsa_midi_driver_t *driver, jack_nframes_t nframes ) -{ - driver->midi->read(driver->midi, nframes); - return 0; -} - -static int -alsa_midi_driver_write( alsa_midi_driver_t *driver, jack_nframes_t nframes ) -{ - driver->midi->write(driver->midi, nframes); - return 0; -} - -static int -alsa_midi_driver_start( alsa_midi_driver_t *driver ) -{ - return driver->midi->start(driver->midi); -} - -static int -alsa_midi_driver_stop( alsa_midi_driver_t *driver ) -{ - return driver->midi->stop(driver->midi); -} - -static void -alsa_midi_driver_delete( alsa_midi_driver_t *driver ) -{ - if (driver->midi) - (driver->midi->destroy)(driver->midi); - - free (driver); -} - -static jack_driver_t * -alsa_midi_driver_new (jack_client_t *client, const char *name) -{ - alsa_midi_driver_t *driver; - - jack_info ("creating alsa_midi driver ..."); - - driver = (alsa_midi_driver_t *) calloc (1, sizeof (alsa_midi_driver_t)); - - jack_driver_init ((jack_driver_t *) driver); - - driver->attach = (JackDriverAttachFunction) alsa_midi_driver_attach; - driver->detach = (JackDriverDetachFunction) alsa_midi_driver_detach; - driver->read = (JackDriverReadFunction) alsa_midi_driver_read; - driver->write = (JackDriverWriteFunction) alsa_midi_driver_write; - driver->start = (JackDriverStartFunction) alsa_midi_driver_start; - driver->stop = (JackDriverStartFunction) alsa_midi_driver_stop; - - - driver->midi = alsa_seqmidi_new(client, NULL); - driver->client = client; - - return (jack_driver_t *) driver; -} - -/* DRIVER "PLUGIN" INTERFACE */ - -const char driver_client_name[] = "alsa_midi"; - -const jack_driver_desc_t * -driver_get_descriptor () -{ - jack_driver_desc_t * desc; - jack_driver_param_desc_t * params; - //unsigned int i; - - desc = calloc (1, sizeof (jack_driver_desc_t)); - - strcpy (desc->name,"alsa_midi"); - desc->nparams = 0; - - params = calloc (desc->nparams, sizeof (jack_driver_param_desc_t)); - - desc->params = params; - - return desc; -} - -jack_driver_t * -driver_initialize (jack_client_t *client, const JSList * params) -{ - const JSList * node; - const jack_driver_param_t * param; - - for (node = params; node; node = jack_slist_next (node)) { - param = (const jack_driver_param_t *) node->data; - - switch (param->character) { - default: - break; - } - } - - return alsa_midi_driver_new (client, NULL); -} - -void -driver_finish (jack_driver_t *driver) -{ - alsa_midi_driver_delete ((alsa_midi_driver_t *) driver); -} diff --git a/drivers/alsa-midi/alsa_rawmidi.c b/drivers/alsa-midi/alsa_rawmidi.c deleted file mode 100644 index 9db97fb..0000000 --- a/drivers/alsa-midi/alsa_rawmidi.c +++ /dev/null @@ -1,1204 +0,0 @@ -/* - * ALSA RAWMIDI < - > JACK MIDI bridge - * - * Copyright (c) 2006,2007 Dmitry S. Baikov - * - * 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 - */ - -/* Required for clock_nanosleep(). Thanks, Nedko */ -#define _GNU_SOURCE - -#include "alsa_midi.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "midi_pack.h" -#include "midi_unpack.h" - - -#ifdef STANDALONE -#define MESSAGE(...) fprintf(stderr, __VA_ARGS__) -#else -#include "messagebuffer.h" -#endif - -#define info_log(...) MESSAGE(__VA_ARGS__) -#define error_log(...) MESSAGE(__VA_ARGS__) - -#ifdef JACK_MIDI_DEBUG -#define debug_log(...) MESSAGE(__VA_ARGS__) -#else -#define debug_log(...) -#endif - - -enum { - NANOSLEEP_RESOLUTION = 7000 -}; - -#define NFRAMES_INF INT_MAX - -enum { -#ifndef JACK_MIDI_DEBUG - MAX_PFDS = 64, - MAX_PORTS = MAX_PFDS-1, - MAX_EVENTS = 4096, - MAX_DATA = 64*1024, - MIDI_THREAD_PRIO = 80 -#else - MAX_PFDS = 6, - MAX_PORTS = MAX_PFDS-1, - MAX_EVENTS = 16, - MAX_DATA = 64, - MIDI_THREAD_PRIO = 80 -#endif -}; - -enum PortState { - PORT_DESTROYED, - PORT_CREATED, - PORT_ADDED_TO_JACK, - PORT_ADDED_TO_MIDI, - PORT_REMOVED_FROM_MIDI, - PORT_REMOVED_FROM_JACK, - PORT_ZOMBIFIED, -}; - -typedef struct { - int id[4]; //card, dev, dir, sub; -} alsa_id_t; - - -typedef struct { - jack_time_t time; - int size; - int overruns; -} event_head_t; - - -typedef struct midi_port_t midi_port_t; -struct midi_port_t { - midi_port_t *next; - - enum PortState state; - - alsa_id_t id; - char dev[16]; - char name[64]; - - jack_port_t *jack; - snd_rawmidi_t *rawmidi; - int npfds; - int is_ready; - - jack_ringbuffer_t *event_ring; - jack_ringbuffer_t *data_ring; - -}; - -typedef struct input_port_t { - midi_port_t base; - - // jack - midi_unpack_t unpack; - - // midi - int overruns; -} input_port_t; - -typedef struct output_port_t { - midi_port_t base; - - // jack - midi_pack_t packer; - - // midi - event_head_t next_event; - int todo; -} output_port_t; - -typedef struct alsa_rawmidi_t alsa_rawmidi_t; - -typedef struct { - alsa_rawmidi_t *midi; - midi_port_t *port; - void *buffer; - jack_time_t frame_time; - jack_nframes_t nframes; -} process_jack_t; - -typedef struct { - alsa_rawmidi_t *midi; - int mode; - midi_port_t *port; - struct pollfd *rpfds; - struct pollfd *wpfds; - int max_pfds; - jack_nframes_t cur_frames; - jack_time_t cur_time; - jack_time_t next_time; -} process_midi_t; - -typedef struct midi_stream_t { - alsa_rawmidi_t *owner; - int mode; - const char *name; - pthread_t thread; - int wake_pipe[2]; - - struct { - jack_ringbuffer_t *new_ports; - int nports; - midi_port_t *ports[MAX_PORTS]; - } jack, midi; - - size_t port_size; - int (*port_init)(alsa_rawmidi_t *midi, midi_port_t *port); - void (*port_close)(alsa_rawmidi_t *midi, midi_port_t *port); - void (*process_jack)(process_jack_t *j); - int (*process_midi)(process_midi_t *m); -} midi_stream_t; - - -struct alsa_rawmidi_t { - alsa_midi_t ops; - - jack_client_t *client; - int keep_walking; - - struct { - pthread_t thread; - midi_port_t *ports; - int wake_pipe[2]; - } scan; - - midi_stream_t in; - midi_stream_t out; -}; - -static int input_port_init(alsa_rawmidi_t *midi, midi_port_t *port); -static void input_port_close(alsa_rawmidi_t *midi, midi_port_t *port); - -static void do_jack_input(process_jack_t *j); -static int do_midi_input(process_midi_t *m); - -static int output_port_init(alsa_rawmidi_t *midi, midi_port_t *port); -static void output_port_close(alsa_rawmidi_t *midi, midi_port_t *port); - -static void do_jack_output(process_jack_t *j); -static int do_midi_output(process_midi_t *m); - - - -static -int stream_init(midi_stream_t *s, alsa_rawmidi_t *midi, const char *name) -{ - s->owner = midi; - s->name = name; - if (pipe(s->wake_pipe)==-1) { - s->wake_pipe[0] = -1; - error_log("pipe() in stream_init(%s) failed: %s", name, strerror(errno)); - return -errno; - } - s->jack.new_ports = jack_ringbuffer_create(sizeof(midi_port_t*)*MAX_PORTS); - s->midi.new_ports = jack_ringbuffer_create(sizeof(midi_port_t*)*MAX_PORTS); - if (!s->jack.new_ports || !s->midi.new_ports) - return -ENOMEM; - return 0; -} - -static -void stream_close(midi_stream_t *s) -{ - if (s->wake_pipe[0] != -1) { - close(s->wake_pipe[0]); - close(s->wake_pipe[1]); - } - if (s->jack.new_ports) - jack_ringbuffer_free(s->jack.new_ports); - if (s->midi.new_ports) - jack_ringbuffer_free(s->midi.new_ports); -} - -static void alsa_rawmidi_delete(alsa_midi_t *m); -static int alsa_rawmidi_attach(alsa_midi_t *m); -static int alsa_rawmidi_detach(alsa_midi_t *m); -static int alsa_rawmidi_start(alsa_midi_t *m); -static int alsa_rawmidi_stop(alsa_midi_t *m); -static void alsa_rawmidi_read(alsa_midi_t *m, jack_nframes_t nframes); -static void alsa_rawmidi_write(alsa_midi_t *m, jack_nframes_t nframes); - -alsa_midi_t* alsa_rawmidi_new(jack_client_t *jack) -{ - alsa_rawmidi_t *midi = calloc(1, sizeof(alsa_rawmidi_t)); - if (!midi) - goto fail_0; - midi->client = jack; - if (pipe(midi->scan.wake_pipe)==-1) { - error_log("pipe() in alsa_midi_new failed: %s", strerror(errno)); - goto fail_1; - } - - if (stream_init(&midi->in, midi, "in")) - goto fail_2; - midi->in.mode = POLLIN; - midi->in.port_size = sizeof(input_port_t); - midi->in.port_init = input_port_init; - midi->in.port_close = input_port_close; - midi->in.process_jack = do_jack_input; - midi->in.process_midi = do_midi_input; - - if (stream_init(&midi->out, midi, "out")) - goto fail_3; - midi->out.mode = POLLOUT; - midi->out.port_size = sizeof(output_port_t); - midi->out.port_init = output_port_init; - midi->out.port_close = output_port_close; - midi->out.process_jack = do_jack_output; - midi->out.process_midi = do_midi_output; - - midi->ops.destroy = alsa_rawmidi_delete; - midi->ops.attach = alsa_rawmidi_attach; - midi->ops.detach = alsa_rawmidi_detach; - midi->ops.start = alsa_rawmidi_start; - midi->ops.stop = alsa_rawmidi_stop; - midi->ops.read = alsa_rawmidi_read; - midi->ops.write = alsa_rawmidi_write; - - return &midi->ops; - fail_3: - stream_close(&midi->out); - fail_2: - stream_close(&midi->in); - close(midi->scan.wake_pipe[1]); - close(midi->scan.wake_pipe[0]); - fail_1: - free(midi); - fail_0: - return NULL; -} - -static -midi_port_t** scan_port_del(alsa_rawmidi_t *midi, midi_port_t **list); - -static -void alsa_rawmidi_delete(alsa_midi_t *m) -{ - alsa_rawmidi_t *midi = (alsa_rawmidi_t*)m; - - alsa_rawmidi_detach(m); - - stream_close(&midi->out); - stream_close(&midi->in); - close(midi->scan.wake_pipe[0]); - close(midi->scan.wake_pipe[1]); - - free(midi); -} - -static void* scan_thread(void *); -static void *midi_thread(void *arg); - -static -int alsa_rawmidi_attach(alsa_midi_t *m) -{ - return 0; -} - -static -int alsa_rawmidi_detach(alsa_midi_t *m) -{ - alsa_rawmidi_t *midi = (alsa_rawmidi_t*)m; - midi_port_t **list; - - alsa_rawmidi_stop(m); - - list = &midi->scan.ports; - while (*list) { - (*list)->state = PORT_REMOVED_FROM_JACK; - list = scan_port_del(midi, list); - } - return 0; -} - -static -int alsa_rawmidi_start(alsa_midi_t *m) -{ - alsa_rawmidi_t *midi = (alsa_rawmidi_t*)m; - int err; - char c = 'q'; - if (midi->keep_walking == 1) - return -EALREADY; - - midi->keep_walking = 1; - if ((err = jack_client_create_thread(midi->client, &midi->in.thread, MIDI_THREAD_PRIO, jack_is_realtime(midi->client), midi_thread, &midi->in))) { - midi->keep_walking = 0; - return err; - } - if ((err = jack_client_create_thread(midi->client, &midi->out.thread, MIDI_THREAD_PRIO, jack_is_realtime(midi->client), midi_thread, &midi->out))) { - midi->keep_walking = 0; - write(midi->in.wake_pipe[1], &c, 1); - pthread_join(midi->in.thread, NULL); - return err; - } - if ((err = jack_client_create_thread(midi->client, &midi->scan.thread, 0, 0, scan_thread, midi))) { - midi->keep_walking = 0; - write(midi->in.wake_pipe[1], &c, 1); - write(midi->out.wake_pipe[1], &c, 1); - pthread_join(midi->in.thread, NULL); - pthread_join(midi->out.thread, NULL); - return err; - } - return 0; -} - -static -int alsa_rawmidi_stop(alsa_midi_t *m) -{ - alsa_rawmidi_t *midi = (alsa_rawmidi_t*)m; - char c = 'q'; - if (midi->keep_walking == 0) - return -EALREADY; - midi->keep_walking = 0; - write(midi->in.wake_pipe[1], &c, 1); - write(midi->out.wake_pipe[1], &c, 1); - write(midi->scan.wake_pipe[1], &c, 1); - pthread_join(midi->in.thread, NULL); - pthread_join(midi->out.thread, NULL); - pthread_join(midi->scan.thread, NULL); - // ports are freed in alsa_midi_detach() - return 0; -} - -static void jack_process(midi_stream_t *str, jack_nframes_t nframes); - -static -void alsa_rawmidi_read(alsa_midi_t *m, jack_nframes_t nframes) -{ - alsa_rawmidi_t *midi = (alsa_rawmidi_t*)m; - jack_process(&midi->in, nframes); -} - -static -void alsa_rawmidi_write(alsa_midi_t *m, jack_nframes_t nframes) -{ - alsa_rawmidi_t *midi = (alsa_rawmidi_t*)m; - jack_process(&midi->out, nframes); -} - -/* - * ----------------------------------------------------------------------------- - */ -static inline -int can_pass(size_t sz, jack_ringbuffer_t *in, jack_ringbuffer_t *out) -{ - return jack_ringbuffer_read_space(in) >= sz && jack_ringbuffer_write_space(out) >= sz; -} - -static -void midi_port_init(const alsa_rawmidi_t *midi, midi_port_t *port, snd_rawmidi_info_t *info, const alsa_id_t *id) -{ - const char *name; - char *c; - - port->id = *id; - snprintf(port->dev, sizeof(port->dev), "hw:%d,%d,%d", id->id[0], id->id[1], id->id[3]); - name = snd_rawmidi_info_get_subdevice_name(info); - if (!strlen(name)) - name = snd_rawmidi_info_get_name(info); - snprintf(port->name, sizeof(port->name), "%s %s %s", port->id.id[2] ? "out":"in", port->dev, name); - - // replace all offending characters with '-' - for (c=port->name; *c; ++c) - if (!isalnum(*c)) - *c = '-'; - - port->state = PORT_CREATED; -} - -static -inline int midi_port_open_jack(const alsa_rawmidi_t *midi, midi_port_t *port, int type, const char *name) -{ - port->jack = jack_port_register(midi->client, name, JACK_DEFAULT_MIDI_TYPE, - type | JackPortIsPhysical|JackPortIsTerminal, 0); - return port->jack == NULL; -} - -static -int midi_port_open(const alsa_rawmidi_t *midi, midi_port_t *port) -{ - int err; - int type; - char name[64]; - snd_rawmidi_t **in = NULL; - snd_rawmidi_t **out = NULL; - - if (port->id.id[2] == 0) { - in = &port->rawmidi; - type = JackPortIsOutput; - } else { - out = &port->rawmidi; - type = JackPortIsInput; - } - - if ((err = snd_rawmidi_open(in, out, port->dev, SND_RAWMIDI_NONBLOCK))<0) - return err; - - /* Some devices (emu10k1) have subdevs with the same name, - * and we need to generate unique port name for jack */ - snprintf(name, sizeof(name), "%s", port->name); - if (midi_port_open_jack(midi, port, type, name)) { - int num; - num = port->id.id[3] ? port->id.id[3] : port->id.id[1]; - snprintf(name, sizeof(name), "%s %d", port->name, num); - if (midi_port_open_jack(midi, port, type, name)) - return 2; - } - if ((port->event_ring = jack_ringbuffer_create(MAX_EVENTS*sizeof(event_head_t)))==NULL) - return 3; - if ((port->data_ring = jack_ringbuffer_create(MAX_DATA))==NULL) - return 4; - - return 0; -} - -static -void midi_port_close(const alsa_rawmidi_t *midi, midi_port_t *port) -{ - if (port->data_ring) { - jack_ringbuffer_free(port->data_ring); - port->data_ring = NULL; - } - if (port->event_ring) { - jack_ringbuffer_free(port->event_ring); - port->event_ring = NULL; - } - if (port->jack) { - jack_port_unregister(midi->client, port->jack); - port->jack = NULL; - } - if (port->rawmidi) { - snd_rawmidi_close(port->rawmidi); - port->rawmidi = NULL; - } -} - -/* - * ------------------------- Port scanning ------------------------------- - */ - -static -int alsa_id_before(const alsa_id_t *p1, const alsa_id_t *p2) -{ - int i; - for (i=0; i<4; ++i) { - if (p1->id[i] < p2->id[i]) - return 1; - else if (p1->id[i] > p2->id[i]) - return 0; - } - return 0; -} - -static -void alsa_get_id(alsa_id_t *id, snd_rawmidi_info_t *info) -{ - id->id[0] = snd_rawmidi_info_get_card(info); - id->id[1] = snd_rawmidi_info_get_device(info); - id->id[2] = snd_rawmidi_info_get_stream(info) == SND_RAWMIDI_STREAM_OUTPUT ? 1 : 0; - id->id[3] = snd_rawmidi_info_get_subdevice(info); -} - -static inline -void alsa_error(const char *func, int err) -{ - error_log("%s() failed", snd_strerror(err)); -} - -typedef struct { - alsa_rawmidi_t *midi; - midi_port_t **iterator; - snd_ctl_t *ctl; - snd_rawmidi_info_t *info; -} scan_t; - -static midi_port_t** scan_port_del(alsa_rawmidi_t *midi, midi_port_t **list); - -static -void scan_cleanup(alsa_rawmidi_t *midi) -{ - midi_port_t **list = &midi->scan.ports; - while (*list) - list = scan_port_del(midi, list); -} - -static void scan_card(scan_t *scan); -static midi_port_t** scan_port_open(alsa_rawmidi_t *midi, midi_port_t **list); - -void scan_cycle(alsa_rawmidi_t *midi) -{ - int card = -1, err; - scan_t scan; - midi_port_t **ports; - - //debug_log("scan: cleanup"); - scan_cleanup(midi); - - scan.midi = midi; - scan.iterator = &midi->scan.ports; - snd_rawmidi_info_alloca(&scan.info); - - //debug_log("scan: rescan"); - while ((err = snd_card_next(&card))>=0 && card>=0) { - char name[32]; - snprintf(name, sizeof(name), "hw:%d", card); - if ((err = snd_ctl_open(&scan.ctl, name, SND_CTL_NONBLOCK))>=0) { - scan_card(&scan); - snd_ctl_close(scan.ctl); - } else - alsa_error("scan: snd_ctl_open", err); - } - - // delayed open to workaround alsa<1.0.14 bug (can't open more than 1 subdevice if ctl is opened). - ports = &midi->scan.ports; - while (*ports) { - midi_port_t *port = *ports; - if (port->state == PORT_CREATED) - ports = scan_port_open(midi, ports); - else - ports = &port->next; - } -} - -static void scan_device(scan_t *scan); - -static -void scan_card(scan_t *scan) -{ - int device = -1; - int err; - - while ((err = snd_ctl_rawmidi_next_device(scan->ctl, &device))>=0 && device >=0) { - snd_rawmidi_info_set_device(scan->info, device); - - snd_rawmidi_info_set_stream(scan->info, SND_RAWMIDI_STREAM_INPUT); - snd_rawmidi_info_set_subdevice(scan->info, 0); - if ((err = snd_ctl_rawmidi_info(scan->ctl, scan->info))>=0) - scan_device(scan); - else if (err != -ENOENT) - alsa_error("scan: snd_ctl_rawmidi_info on device", err); - - snd_rawmidi_info_set_stream(scan->info, SND_RAWMIDI_STREAM_OUTPUT); - snd_rawmidi_info_set_subdevice(scan->info, 0); - if ((err = snd_ctl_rawmidi_info(scan->ctl, scan->info))>=0) - scan_device(scan); - else if (err != -ENOENT) - alsa_error("scan: snd_ctl_rawmidi_info on device", err); - } -} - -static void scan_port_update(scan_t *scan); - -static -void scan_device(scan_t *scan) -{ - int err; - int sub, nsubs = 0; - nsubs = snd_rawmidi_info_get_subdevices_count(scan->info); - - for (sub=0; subinfo, sub); - if ((err = snd_ctl_rawmidi_info(scan->ctl, scan->info)) < 0) { - alsa_error("scan: snd_ctl_rawmidi_info on subdevice", err); - continue; - } - scan_port_update(scan); - } -} - -static midi_port_t** scan_port_add(scan_t *scan, const alsa_id_t *id, midi_port_t **list); - -static -void scan_port_update(scan_t *scan) -{ - midi_port_t **list = scan->iterator; - alsa_id_t id; - alsa_get_id(&id, scan->info); - - while (*list && alsa_id_before(&(*list)->id, &id)) - list = scan_port_del(scan->midi, list); - - if (!*list || alsa_id_before(&id, &(*list)->id)) - list = scan_port_add(scan, &id, list); - else if (*list) - list = &(*list)->next; - - scan->iterator = list; -} - -static -midi_port_t** scan_port_add(scan_t *scan, const alsa_id_t *id, midi_port_t **list) -{ - midi_port_t *port; - midi_stream_t *str = id->id[2] ? &scan->midi->out : &scan->midi->in; - - port = calloc(1, str->port_size); - if (!port) - return list; - midi_port_init(scan->midi, port, scan->info, id); - - port->next = *list; - *list = port; - error_log("scan: added port %s %s", port->dev, port->name); - return &port->next; -} - -static -midi_port_t** scan_port_open(alsa_rawmidi_t *midi, midi_port_t **list) -{ - midi_stream_t *str; - midi_port_t *port; - - port = *list; - str = port->id.id[2] ? &midi->out : &midi->in; - - if (jack_ringbuffer_write_space(str->jack.new_ports) < sizeof(port)) - goto fail_0; - - if (midi_port_open(midi, port)) - goto fail_1; - if ((str->port_init)(midi, port)) - goto fail_2; - - port->state = PORT_ADDED_TO_JACK; - jack_ringbuffer_write(str->jack.new_ports, (char*) &port, sizeof(port)); - - error_log("scan: opened port %s %s", port->dev, port->name); - return &port->next; - - fail_2: - (str->port_close)(midi, port); - fail_1: - midi_port_close(midi, port); - port->state = PORT_ZOMBIFIED; - fail_0: - error_log("scan: can't open port %s %s", port->dev, port->name); - return &port->next; -} - -static -midi_port_t** scan_port_del(alsa_rawmidi_t *midi, midi_port_t **list) -{ - midi_port_t *port = *list; - if (port->state == PORT_REMOVED_FROM_JACK) { - error_log("scan: deleted port %s %s", port->dev, port->name); - *list = port->next; - if (port->id.id[2] ) - (midi->out.port_close)(midi, port); - else - (midi->in.port_close)(midi, port); - midi_port_close(midi, port); - free(port); - return list; - } else { - //debug_log("can't delete port %s, wrong state: %d", port->name, (int)port->state); - return &port->next; - } -} - -void* scan_thread(void *arg) -{ - alsa_rawmidi_t *midi = arg; - struct pollfd wakeup; - - wakeup.fd = midi->scan.wake_pipe[0]; - wakeup.events = POLLIN|POLLERR|POLLNVAL; - while (midi->keep_walking) { - int res; - //error_log("scanning...."); - scan_cycle(midi); - res = poll(&wakeup, 1, 2000); - if (res>0) { - char c; - read(wakeup.fd, &c, 1); - } else if (res<0 && errno != EINTR) - break; - } - return NULL; -} - - -/* - * ------------------------------- Input/Output ------------------------------ - */ - -static -void jack_add_ports(midi_stream_t *str) -{ - midi_port_t *port; - while (can_pass(sizeof(port), str->jack.new_ports, str->midi.new_ports) && str->jack.nports < MAX_PORTS) { - jack_ringbuffer_read(str->jack.new_ports, (char*)&port, sizeof(port)); - str->jack.ports[str->jack.nports++] = port; - port->state = PORT_ADDED_TO_MIDI; - jack_ringbuffer_write(str->midi.new_ports, (char*)&port, sizeof(port)); - } -} - -static -void jack_process(midi_stream_t *str, jack_nframes_t nframes) -{ - int r, w; - process_jack_t proc; - jack_nframes_t cur_frames; - - if (!str->owner->keep_walking) - return; - - proc.midi = str->owner; - proc.nframes = nframes; - proc.frame_time = jack_last_frame_time(proc.midi->client); - cur_frames = jack_frame_time(proc.midi->client); - if (proc.frame_time + proc.nframes < cur_frames) { - int periods_lost = (cur_frames - proc.frame_time) / proc.nframes; - proc.frame_time += periods_lost * proc.nframes; - debug_log("xrun detected: %d periods lost", periods_lost); - } - - // process existing ports - for (r=0, w=0; rjack.nports; ++r) { - midi_port_t *port = str->jack.ports[r]; - proc.port = port; - - assert (port->state > PORT_ADDED_TO_JACK && port->state < PORT_REMOVED_FROM_JACK); - - proc.buffer = jack_port_get_buffer(port->jack, nframes); - if (str->mode == POLLIN) - jack_midi_clear_buffer(proc.buffer); - - if (port->state == PORT_REMOVED_FROM_MIDI) { - port->state = PORT_REMOVED_FROM_JACK; // this signals to scan thread - continue; // this effectively removes port from the midi->in.jack.ports[] - } - - (str->process_jack)(&proc); - - if (r != w) - str->jack.ports[w] = port; - ++w; - } - if (str->jack.nports != w) - debug_log("jack_%s: nports %d -> %d", str->name, str->jack.nports, w); - str->jack.nports = w; - - jack_add_ports(str); // it makes no sense to add them earlier since they have no data yet - - // wake midi thread - write(str->wake_pipe[1], &r, 1); -} - -static -void *midi_thread(void *arg) -{ - midi_stream_t *str = arg; - alsa_rawmidi_t *midi = str->owner; - struct pollfd pfds[MAX_PFDS]; - int npfds; - jack_time_t wait_nsec = 1000*1000*1000; // 1 sec - process_midi_t proc; - - proc.midi = midi; - proc.mode = str->mode; - - pfds[0].fd = str->wake_pipe[0]; - pfds[0].events = POLLIN|POLLERR|POLLNVAL; - npfds = 1; - - //debug_log("midi_thread(%s): enter", str->name); - - while (midi->keep_walking) { - int poll_timeout; - int wait_nanosleep; - int r=1, w=1; // read,write pos in pfds - int rp=0, wp=0; // read, write pos in ports - - // sleep - //if (wait_nsec != 1000*1000*1000) { - // debug_log("midi_thread(%s): ", str->name); - // assert (wait_nsec == 1000*1000*1000); - //} - poll_timeout = wait_nsec / (1000*1000); - wait_nanosleep = wait_nsec % (1000*1000); - if (wait_nanosleep > NANOSLEEP_RESOLUTION) { - struct timespec ts; - ts.tv_sec = 0; - ts.tv_nsec = wait_nanosleep; - clock_nanosleep(CLOCK_MONOTONIC, 0, &ts, NULL); - } - int res = poll((struct pollfd*)&pfds, npfds, poll_timeout); - //debug_log("midi_thread(%s): poll exit: %d", str->name, res); - if (!midi->keep_walking) - break; - if (res < 0) { - if (errno == EINTR) - continue; - error_log("midi_thread(%s) poll failed: %s", str->name, strerror(errno)); - break; - } - - // check wakeup pipe - if (pfds[0].revents & ~POLLIN) - break; - if (pfds[0].revents & POLLIN) { - char c; - read(pfds[0].fd, &c, 1); - } - - // add new ports - while (jack_ringbuffer_read_space(str->midi.new_ports) >= sizeof(midi_port_t*) && str->midi.nports < MAX_PORTS) { - midi_port_t *port; - jack_ringbuffer_read(str->midi.new_ports, (char*)&port, sizeof(port)); - str->midi.ports[str->midi.nports++] = port; - debug_log("midi_thread(%s): added port %s", str->name, port->name); - } - -// if (res == 0) -// continue; - - // process ports - proc.cur_time = 0; //jack_frame_time(midi->client); - proc.next_time = NFRAMES_INF; - - for (rp = 0; rp < str->midi.nports; ++rp) { - midi_port_t *port = str->midi.ports[rp]; - proc.cur_time = jack_frame_time(midi->client); - proc.port = port; - proc.rpfds = &pfds[r]; - proc.wpfds = &pfds[w]; - proc.max_pfds = MAX_PFDS - w; - r += port->npfds; - if (!(str->process_midi)(&proc)) { - port->state = PORT_REMOVED_FROM_MIDI; // this signals to jack thread - continue; // this effectively removes port from array - } - w += port->npfds; - if (rp != wp) - str->midi.ports[wp] = port; - ++wp; - } - if (str->midi.nports != wp) - debug_log("midi_%s: nports %d -> %d", str->name, str->midi.nports, wp); - str->midi.nports = wp; - if (npfds != w) - debug_log("midi_%s: npfds %d -> %d", str->name, npfds, w); - npfds = w; - - /* - * Input : ports do not set proc.next_time. - * Output: port sets proc.next_time ONLY if it does not have queued data. - * So, zero timeout will not cause busy-looping. - */ - if (proc.next_time < proc.cur_time) { - debug_log("%s: late: next_time = %d, cur_time = %d", str->name, (int)proc.next_time, (int)proc.cur_time); - wait_nsec = 0; // we are late - } else if (proc.next_time != NFRAMES_INF) { - jack_time_t wait_frames = proc.next_time - proc.cur_time; - jack_nframes_t rate = jack_get_sample_rate(midi->client); - wait_nsec = (wait_frames * (1000*1000*1000)) / rate; - debug_log("midi_%s: timeout = %d", str->name, (int)wait_frames); - } else - wait_nsec = 1000*1000*1000; - //debug_log("midi_thread(%s): wait_nsec = %lld", str->name, wait_nsec); - } - return NULL; -} - -static -int midi_is_ready(process_midi_t *proc) -{ - midi_port_t *port = proc->port; - if (port->npfds) { - unsigned short revents = 0; - int res = snd_rawmidi_poll_descriptors_revents(port->rawmidi, proc->rpfds, port->npfds, &revents); - if (res) { - error_log("snd_rawmidi_poll_descriptors_revents failed on port %s with: %s", port->name, snd_strerror(res)); - return 0; - } - - if (revents & ~proc->mode) { - debug_log("midi: port %s failed", port->name); - return 0; - } - if (revents & proc->mode) { - port->is_ready = 1; - debug_log("midi: is_ready %s", port->name); - } - } - return 1; -} - -static -int midi_update_pfds(process_midi_t *proc) -{ - midi_port_t *port = proc->port; - if (port->npfds == 0) { - port->npfds = snd_rawmidi_poll_descriptors_count(port->rawmidi); - if (port->npfds > proc->max_pfds) { - debug_log("midi: not enough pfds for port %s", port->name); - return 0; - } - snd_rawmidi_poll_descriptors(port->rawmidi, proc->wpfds, port->npfds); - } else if (proc->rpfds != proc->wpfds) { - memmove(proc->wpfds, proc->rpfds, sizeof(struct pollfd) * port->npfds); - } - return 1; -} - -/* - * ------------------------------------ Input ------------------------------ - */ - -static -int input_port_init(alsa_rawmidi_t *midi, midi_port_t *port) -{ - input_port_t *in = (input_port_t*)port; - midi_unpack_init(&in->unpack); - return 0; -} - -static -void input_port_close(alsa_rawmidi_t *midi, midi_port_t *port) -{ -} - -/* - * Jack-level input. - */ - -static -void do_jack_input(process_jack_t *p) -{ - input_port_t *port = (input_port_t*) p->port; - event_head_t event; - while (jack_ringbuffer_read_space(port->base.event_ring) >= sizeof(event)) { - jack_ringbuffer_data_t vec[2]; - jack_nframes_t time; - int i, todo; - - jack_ringbuffer_read(port->base.event_ring, (char*)&event, sizeof(event)); - // TODO: take into account possible warping - if ((event.time + p->nframes) < p->frame_time) - time = 0; - else if (event.time >= p->frame_time) - time = p->nframes -1; - else - time = event.time + p->nframes - p->frame_time; - - jack_ringbuffer_get_read_vector(port->base.data_ring, vec); - assert ((vec[0].len + vec[1].len) >= event.size); - - if (event.overruns) - midi_unpack_reset(&port->unpack); - - todo = event.size; - for (i=0; i<2 && todo>0; ++i) { - int avail = todo < vec[i].len ? todo : vec[i].len; - int done = midi_unpack_buf(&port->unpack, (unsigned char*)vec[i].buf, avail, p->buffer, time); - if (done != avail) { - debug_log("jack_in: buffer overflow in port %s", port->base.name); - break; - } - todo -= done; - } - jack_ringbuffer_read_advance(port->base.data_ring, event.size); - } -} - -/* - * Low level input. - */ -static -int do_midi_input(process_midi_t *proc) -{ - input_port_t *port = (input_port_t*) proc->port; - if (!midi_is_ready(proc)) - return 0; - - if (port->base.is_ready) { - jack_ringbuffer_data_t vec[2]; - int res; - - jack_ringbuffer_get_write_vector(port->base.data_ring, vec); - if (jack_ringbuffer_write_space(port->base.event_ring) < sizeof(event_head_t) || vec[0].len < 1) { - port->overruns++; - if (port->base.npfds) - debug_log("midi_in: internal overflow on %s", port->base.name); - // remove from poll to prevent busy-looping - port->base.npfds = 0; - return 1; - } - res = snd_rawmidi_read(port->base.rawmidi, vec[0].buf, vec[0].len); - if (res < 0 && res != -EWOULDBLOCK) { - error_log("midi_in: reading from port %s failed: %s", port->base.name, snd_strerror(res)); - return 0; - } else if (res > 0) { - event_head_t event; - event.time = proc->cur_time; - event.size = res; - event.overruns = port->overruns; - port->overruns = 0; - debug_log("midi_in: read %d bytes at %d", (int)event.size, (int)event.time); - jack_ringbuffer_write_advance(port->base.data_ring, event.size); - jack_ringbuffer_write(port->base.event_ring, (char*)&event, sizeof(event)); - } - port->base.is_ready = 0; - } - - if (!midi_update_pfds(proc)) - return 0; - - return 1; -} - -/* - * ------------------------------------ Output ------------------------------ - */ - -static int output_port_init(alsa_rawmidi_t *midi, midi_port_t *port) -{ - output_port_t *out = (output_port_t*)port; - midi_pack_reset(&out->packer); - out->next_event.time = 0; - out->next_event.size = 0; - out->todo = 0; - return 0; -} - -static void output_port_close(alsa_rawmidi_t *midi, midi_port_t *port) -{ -} - -static -void do_jack_output(process_jack_t *proc) -{ - output_port_t *port = (output_port_t*) proc->port; - int nevents = jack_midi_get_event_count(proc->buffer); - int i; - if (nevents) - debug_log("jack_out: %d events in %s", nevents, port->base.name); - for (i=0; ibuffer, i); - - if (jack_ringbuffer_write_space(port->base.data_ring) < event.size || jack_ringbuffer_write_space(port->base.event_ring) < sizeof(hdr)) { - debug_log("jack_out: output buffer overflow on %s", port->base.name); - break; - } - - midi_pack_event(&port->packer, &event); - - jack_ringbuffer_write(port->base.data_ring, (char*)event.buffer, event.size); - - hdr.time = proc->frame_time + event.time + proc->nframes; - hdr.size = event.size; - jack_ringbuffer_write(port->base.event_ring, (char*)&hdr, sizeof(hdr)); - debug_log("jack_out: sent %d-byte event at %ld", (int)event.size, (long)event.time); - } -} - -static -int do_midi_output(process_midi_t *proc) -{ - int worked = 0; - output_port_t *port = (output_port_t*) proc->port; - - if (!midi_is_ready(proc)) - return 0; - - // eat events - while (port->next_event.time <= proc->cur_time) { - port->todo += port->next_event.size; - if (jack_ringbuffer_read(port->base.event_ring, (char*)&port->next_event, sizeof(port->next_event))!=sizeof(port->next_event)) { - port->next_event.time = 0; - port->next_event.size = 0; - break; - } else - debug_log("midi_out: at %ld got %d bytes for %ld", (long)proc->cur_time, (int)port->next_event.size, (long)port->next_event.time); - } - - if (port->todo) - debug_log("midi_out: todo = %d at %ld", (int)port->todo, (long)proc->cur_time); - - // calc next wakeup time - if (!port->todo && port->next_event.time && port->next_event.time < proc->next_time) { - proc->next_time = port->next_event.time; - debug_log("midi_out: next_time = %ld", (long)proc->next_time); - } - - if (port->todo && port->base.is_ready) { - // write data - int size = port->todo; - int res; - jack_ringbuffer_data_t vec[2]; - - jack_ringbuffer_get_read_vector(port->base.data_ring, vec); - if (size > vec[0].len) { - size = vec[0].len; - assert (size > 0); - } - res = snd_rawmidi_write(port->base.rawmidi, vec[0].buf, size); - if (res > 0) { - jack_ringbuffer_read_advance(port->base.data_ring, res); - debug_log("midi_out: written %d bytes to %s", res, port->base.name); - port->todo -= res; - worked = 1; - } else if (res == -EWOULDBLOCK) { - port->base.is_ready = 0; - debug_log("midi_out: -EWOULDBLOCK on %s", port->base.name); - return 1; - } else { - error_log("midi_out: writing to port %s failed: %s", port->base.name, snd_strerror(res)); - return 0; - } - snd_rawmidi_drain(port->base.rawmidi); - } - - // update pfds for this port - if (!midi_update_pfds(proc)) - return 0; - - if (!port->todo) { - int i; - if (worked) - debug_log("midi_out: relaxing on %s", port->base.name); - for (i=0; ibase.npfds; ++i) - proc->wpfds[i].events &= ~POLLOUT; - } else { - int i; - for (i=0; ibase.npfds; ++i) - proc->wpfds[i].events |= POLLOUT; - } - return 1; -} diff --git a/drivers/alsa-midi/alsa_seqmidi.c b/drivers/alsa-midi/alsa_seqmidi.c deleted file mode 100644 index 3351b36..0000000 --- a/drivers/alsa-midi/alsa_seqmidi.c +++ /dev/null @@ -1,925 +0,0 @@ -/* - * ALSA SEQ < - > JACK MIDI bridge - * - * Copyright (c) 2006,2007 Dmitry S. Baikov - * - * 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 - */ - -/* - * alsa_seqmidi_read: - * add new ports - * reads queued snd_seq_event's - * if PORT_EXIT: mark port as dead - * if PORT_ADD, PORT_CHANGE: send addr to port_thread (it also may mark port as dead) - * else process input event - * remove dead ports and send them to port_thread - * - * alsa_seqmidi_write: - * remove dead ports and send them to port_thread - * add new ports - * queue output events - * - * port_thread: - * wait for port_sem - * free deleted ports - * create new ports or mark existing as dead - */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "alsa_midi.h" - -#ifndef SND_SEQ_PORT_TYPE_PORT -#define SND_SEQ_PORT_TYPE_PORT (1<<19) /* Appears in version 1.0.12rc1 */ -#endif - -#ifndef SND_SEQ_PORT_TYPE_HARDWARE -#define SND_SEQ_PORT_TYPE_HARDWARE (1<<16) /* Appears in version 1.0.12rc1 */ -#endif - -#ifdef STANDALONE -#define MESSAGE(...) fprintf(stderr, __VA_ARGS__) -#else -#include "messagebuffer.h" -#endif - -#define info_log(...) MESSAGE(__VA_ARGS__) -#define error_log(...) MESSAGE(__VA_ARGS__) - -#ifdef JACK_MIDI_DEBUG -#define debug_log(...) MESSAGE(__VA_ARGS__) -#else -#define debug_log(...) -#endif - -#define NSEC_PER_SEC ((int64_t)1000*1000*1000) - -enum { - MAX_PORTS = 64, - MAX_EVENT_SIZE = 1024, -}; - -typedef struct port_t port_t; - -enum { - PORT_HASH_BITS = 4, - PORT_HASH_SIZE = 1 << PORT_HASH_BITS -}; - -typedef port_t* port_hash_t[PORT_HASH_SIZE]; - -struct port_t { - port_t *next; - int is_dead; - char name[64]; - snd_seq_addr_t remote; - jack_port_t *jack_port; - - jack_ringbuffer_t *early_events; // alsa_midi_event_t + data - int64_t last_out_time; - - void *jack_buf; -}; - -typedef struct { - snd_midi_event_t *codec; - - jack_ringbuffer_t *new_ports; - - port_t *ports[MAX_PORTS]; -} stream_t; - -typedef struct alsa_seqmidi { - alsa_midi_t ops; - jack_client_t *jack; - - snd_seq_t *seq; - int client_id; - int port_id; - int queue; - - int keep_walking; - - pthread_t port_thread; - sem_t port_sem; - jack_ringbuffer_t *port_add; // snd_seq_addr_t - jack_ringbuffer_t *port_del; // port_t* - - stream_t stream[2]; - - char alsa_name[32]; -} alsa_seqmidi_t; - -struct alsa_midi_event { - int64_t time; - int size; -}; -typedef struct alsa_midi_event alsa_midi_event_t; - -struct process_info { - int dir; - jack_nframes_t nframes; - jack_nframes_t period_start; - jack_nframes_t sample_rate; - jack_nframes_t cur_frames; - int64_t alsa_time; -}; - - -enum PortType { PORT_INPUT = 0, PORT_OUTPUT = 1 }; - -typedef void (*port_jack_func)(alsa_seqmidi_t *self, port_t *port,struct process_info* info); -static void do_jack_input(alsa_seqmidi_t *self, port_t *port, struct process_info* info); -static void do_jack_output(alsa_seqmidi_t *self, port_t *port, struct process_info* info); - -typedef struct { - int alsa_mask; - int jack_caps; - char name[9]; - port_jack_func jack_func; -} port_type_t; - -static port_type_t port_type[2] = { - { - SND_SEQ_PORT_CAP_SUBS_READ, - JackPortIsOutput, - "capture", - do_jack_input - }, - { - SND_SEQ_PORT_CAP_SUBS_WRITE, - JackPortIsInput, - "playback", - do_jack_output - } -}; - - -static void alsa_seqmidi_delete(alsa_midi_t *m); -static int alsa_seqmidi_attach(alsa_midi_t *m); -static int alsa_seqmidi_detach(alsa_midi_t *m); -static int alsa_seqmidi_start(alsa_midi_t *m); -static int alsa_seqmidi_stop(alsa_midi_t *m); -static void alsa_seqmidi_read(alsa_midi_t *m, jack_nframes_t nframes); -static void alsa_seqmidi_write(alsa_midi_t *m, jack_nframes_t nframes); - -static -void stream_init(alsa_seqmidi_t *self, int dir) -{ - stream_t *str = &self->stream[dir]; - - str->new_ports = jack_ringbuffer_create(MAX_PORTS*sizeof(port_t*)); - snd_midi_event_new(MAX_EVENT_SIZE, &str->codec); -} - -static void port_free(alsa_seqmidi_t *self, port_t *port); -static void free_ports(alsa_seqmidi_t *self, jack_ringbuffer_t *ports); - -static -void stream_attach(alsa_seqmidi_t *self, int dir) -{ -} - -static -void stream_detach(alsa_seqmidi_t *self, int dir) -{ - stream_t *str = &self->stream[dir]; - int i; - - free_ports(self, str->new_ports); - - // delete all ports from hash - for (i=0; iports[i]; - while (port) { - port_t *next = port->next; - port_free(self, port); - port = next; - } - str->ports[i] = NULL; - } -} - -static -void stream_close(alsa_seqmidi_t *self, int dir) -{ - stream_t *str = &self->stream[dir]; - - if (str->codec) - snd_midi_event_free(str->codec); - if (str->new_ports) - jack_ringbuffer_free(str->new_ports); -} - -alsa_midi_t* alsa_seqmidi_new(jack_client_t *client, const char* alsa_name) -{ - alsa_seqmidi_t *self = calloc(1, sizeof(alsa_seqmidi_t)); - debug_log("midi: new\n"); - if (!self) - return NULL; - self->jack = client; - if (!alsa_name) - alsa_name = "jack_midi"; - snprintf(self->alsa_name, sizeof(self->alsa_name), "%s", alsa_name); - - self->port_add = jack_ringbuffer_create(2*MAX_PORTS*sizeof(snd_seq_addr_t)); - self->port_del = jack_ringbuffer_create(2*MAX_PORTS*sizeof(port_t*)); - sem_init(&self->port_sem, 0, 0); - - stream_init(self, PORT_INPUT); - stream_init(self, PORT_OUTPUT); - - self->ops.destroy = alsa_seqmidi_delete; - self->ops.attach = alsa_seqmidi_attach; - self->ops.detach = alsa_seqmidi_detach; - self->ops.start = alsa_seqmidi_start; - self->ops.stop = alsa_seqmidi_stop; - self->ops.read = alsa_seqmidi_read; - self->ops.write = alsa_seqmidi_write; - return &self->ops; -} - -static -void alsa_seqmidi_delete(alsa_midi_t *m) -{ - alsa_seqmidi_t *self = (alsa_seqmidi_t*) m; - - debug_log("midi: delete\n"); - alsa_seqmidi_detach(m); - - stream_close(self, PORT_OUTPUT); - stream_close(self, PORT_INPUT); - - jack_ringbuffer_free(self->port_add); - jack_ringbuffer_free(self->port_del); - sem_close(&self->port_sem); - - free(self); -} - -static -int alsa_seqmidi_attach(alsa_midi_t *m) -{ - alsa_seqmidi_t *self = (alsa_seqmidi_t*) m; - int err; - - debug_log("midi: attach\n"); - - if (self->seq) - return -EALREADY; - - if ((err = snd_seq_open(&self->seq, "hw", SND_SEQ_OPEN_DUPLEX, 0)) < 0) { - error_log("failed to open alsa seq"); - return err; - } - snd_seq_set_client_name(self->seq, self->alsa_name); - self->port_id = snd_seq_create_simple_port(self->seq, "port", - SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_WRITE -#ifndef JACK_MIDI_DEBUG - |SND_SEQ_PORT_CAP_NO_EXPORT -#endif - ,SND_SEQ_PORT_TYPE_APPLICATION); - self->client_id = snd_seq_client_id(self->seq); - - self->queue = snd_seq_alloc_queue(self->seq); - snd_seq_start_queue(self->seq, self->queue, 0); - - stream_attach(self, PORT_INPUT); - stream_attach(self, PORT_OUTPUT); - - snd_seq_nonblock(self->seq, 1); - - return 0; -} - -static -int alsa_seqmidi_detach(alsa_midi_t *m) -{ - alsa_seqmidi_t *self = (alsa_seqmidi_t*) m; - - debug_log("midi: detach\n"); - - if (!self->seq) - return -EALREADY; - - alsa_seqmidi_stop(m); - - jack_ringbuffer_reset(self->port_add); - free_ports(self, self->port_del); - - stream_detach(self, PORT_INPUT); - stream_detach(self, PORT_OUTPUT); - - snd_seq_close(self->seq); - self->seq = NULL; - - return 0; -} - -static void* port_thread(void *); - -static void add_existing_ports(alsa_seqmidi_t *self); -static void update_ports(alsa_seqmidi_t *self); -static void add_ports(stream_t *str); - -static -int alsa_seqmidi_start(alsa_midi_t *m) -{ - alsa_seqmidi_t *self = (alsa_seqmidi_t*) m; - int err; - - debug_log("midi: start\n"); - - if (!self->seq) - return -EBADF; - - if (self->keep_walking) - return -EALREADY; - - snd_seq_connect_from(self->seq, self->port_id, SND_SEQ_CLIENT_SYSTEM, SND_SEQ_PORT_SYSTEM_ANNOUNCE); - snd_seq_drop_input(self->seq); - - add_existing_ports(self); - update_ports(self); - add_ports(&self->stream[PORT_INPUT]); - add_ports(&self->stream[PORT_OUTPUT]); - - self->keep_walking = 1; - - if ((err = pthread_create(&self->port_thread, NULL, port_thread, self))) { - self->keep_walking = 0; - return -errno; - } - - return 0; -} - -static -int alsa_seqmidi_stop(alsa_midi_t *m) -{ - alsa_seqmidi_t *self = (alsa_seqmidi_t*) m; - - debug_log("midi: stop\n"); - - if (!self->keep_walking) - return -EALREADY; - - snd_seq_disconnect_from(self->seq, self->port_id, SND_SEQ_CLIENT_SYSTEM, SND_SEQ_PORT_SYSTEM_ANNOUNCE); - - self->keep_walking = 0; - - sem_post(&self->port_sem); - pthread_join(self->port_thread, NULL); - self->port_thread = 0; - - return 0; -} - -static -int alsa_connect_from(alsa_seqmidi_t *self, int client, int port) -{ - snd_seq_port_subscribe_t* sub; - snd_seq_addr_t seq_addr; - int err; - - snd_seq_port_subscribe_alloca(&sub); - seq_addr.client = client; - seq_addr.port = port; - snd_seq_port_subscribe_set_sender(sub, &seq_addr); - seq_addr.client = self->client_id; - seq_addr.port = self->port_id; - snd_seq_port_subscribe_set_dest(sub, &seq_addr); - - snd_seq_port_subscribe_set_time_update(sub, 1); - snd_seq_port_subscribe_set_queue(sub, self->queue); - snd_seq_port_subscribe_set_time_real(sub, 1); - - if ((err=snd_seq_subscribe_port(self->seq, sub))) - error_log("can't subscribe to %d:%d - %s\n", client, port, snd_strerror(err)); - return err; -} - -/* - * ==================== Port routines ============================= - */ -static inline -int port_hash(snd_seq_addr_t addr) -{ - return (addr.client + addr.port) % PORT_HASH_SIZE; -} - -static -port_t* port_get(port_hash_t hash, snd_seq_addr_t addr) -{ - port_t **pport = &hash[port_hash(addr)]; - while (*pport) { - port_t *port = *pport; - if (port->remote.client == addr.client && port->remote.port == addr.port) - return port; - pport = &port->next; - } - return NULL; -} - -static -void port_insert(port_hash_t hash, port_t *port) -{ - port_t **pport = &hash[port_hash(port->remote)]; - port->next = *pport; - *pport = port; -} - -static -void port_setdead(port_hash_t hash, snd_seq_addr_t addr) -{ - port_t *port = port_get(hash, addr); - if (port) - port->is_dead = 1; // see jack_process - else - debug_log("port_setdead: not found (%d:%d)\n", addr.client, addr.port); -} - -static -void port_free(alsa_seqmidi_t *self, port_t *port) -{ - //snd_seq_disconnect_from(self->seq, self->port_id, port->remote.client, port->remote.port); - //snd_seq_disconnect_to(self->seq, self->port_id, port->remote.client, port->remote.port); - if (port->early_events) - jack_ringbuffer_free(port->early_events); - if (port->jack_port) - jack_port_unregister(self->jack, port->jack_port); - // info_log("port deleted: %s\n", port->name); - - free(port); -} - -static -port_t* port_create(alsa_seqmidi_t *self, int type, snd_seq_addr_t addr, const snd_seq_port_info_t *info) -{ - snd_seq_client_info_t* client_info; - port_t *port; - char *c; - int err; - int jack_caps; - - port = calloc(1, sizeof(port_t)); - if (!port) - return NULL; - - port->remote = addr; - - snd_seq_client_info_alloca (&client_info); - snd_seq_get_any_client_info (self->seq, addr.client, client_info); - - snprintf(port->name, sizeof(port->name), "%s/midi_%s_%d", - snd_seq_client_info_get_name(client_info), port_type[type].name, addr.port+1); - - // replace all offending characters by - - for (c = port->name; *c; ++c) - if (!isalnum(*c) && *c != '/' && *c != '_' && *c != ':' && *c != '(' && *c != ')') - *c = '-'; - - jack_caps = port_type[type].jack_caps; - - /* mark anything that looks like a hardware port as physical&terminal */ - - if (snd_seq_port_info_get_type (info) & (SND_SEQ_PORT_TYPE_HARDWARE|SND_SEQ_PORT_TYPE_PORT|SND_SEQ_PORT_TYPE_SPECIFIC)) { - jack_caps |= (JackPortIsPhysical|JackPortIsTerminal); - } - - port->jack_port = jack_port_register(self->jack, - port->name, JACK_DEFAULT_MIDI_TYPE, jack_caps, 0); - if (!port->jack_port) - goto failed; - - /* generate an alias */ - - snprintf(port->name, sizeof(port->name), "%s:midi/%s_%d", - snd_seq_client_info_get_name (client_info), port_type[type].name, addr.port+1); - - // replace all offending characters by - - for (c = port->name; *c; ++c) - if (!isalnum(*c) && *c != '/' && *c != '_' && *c != ':' && *c != '(' && *c != ')') - *c = '-'; - - jack_port_set_alias (port->jack_port, port->name); - - if (type == PORT_INPUT) - err = alsa_connect_from(self, port->remote.client, port->remote.port); - else - err = snd_seq_connect_to(self->seq, self->port_id, port->remote.client, port->remote.port); - if (err) - goto failed; - - port->early_events = jack_ringbuffer_create(MAX_EVENT_SIZE*16); - - // info_log("port created: %s\n", port->name); - return port; - - failed: - port_free(self, port); - return NULL; -} - -/* - * ==================== Port add/del handling thread ============================== - */ -static -void update_port_type(alsa_seqmidi_t *self, int type, snd_seq_addr_t addr, int caps, const snd_seq_port_info_t *info) -{ - stream_t *str = &self->stream[type]; - int alsa_mask = port_type[type].alsa_mask; - port_t *port = port_get(str->ports, addr); - - debug_log("update_port_type(%d:%d)\n", addr.client, addr.port); - - if (port && (caps & alsa_mask)!=alsa_mask) { - debug_log("setdead: %s\n", port->name); - port->is_dead = 1; - } - - if (!port && (caps & alsa_mask)==alsa_mask) { - assert (jack_ringbuffer_write_space(str->new_ports) >= sizeof(port)); - port = port_create(self, type, addr, info); - if (port) - jack_ringbuffer_write(str->new_ports, (char*)&port, sizeof(port)); - } -} - -static -void update_port(alsa_seqmidi_t *self, snd_seq_addr_t addr, const snd_seq_port_info_t *info) -{ - unsigned int port_caps = snd_seq_port_info_get_capability(info); - if (port_caps & SND_SEQ_PORT_CAP_NO_EXPORT) - return; - update_port_type(self, PORT_INPUT, addr, port_caps, info); - update_port_type(self, PORT_OUTPUT,addr, port_caps, info); -} - -static -void free_ports(alsa_seqmidi_t *self, jack_ringbuffer_t *ports) -{ - port_t *port; - int sz; - while ((sz = jack_ringbuffer_read(ports, (char*)&port, sizeof(port)))) { - assert (sz == sizeof(port)); - port_free(self, port); - } -} - -static -void update_ports(alsa_seqmidi_t *self) -{ - snd_seq_addr_t addr; - snd_seq_port_info_t *info; - int size; - - snd_seq_port_info_alloca(&info); - - while ((size = jack_ringbuffer_read(self->port_add, (char*)&addr, sizeof(addr)))) { - - int err; - - assert (size == sizeof(addr)); - assert (addr.client != self->client_id); - if ((err=snd_seq_get_any_port_info(self->seq, addr.client, addr.port, info))>=0) { - update_port(self, addr, info); - } else { - //port_setdead(self->stream[PORT_INPUT].ports, addr); - //port_setdead(self->stream[PORT_OUTPUT].ports, addr); - } - } -} - -static -void* port_thread(void *arg) -{ - alsa_seqmidi_t *self = arg; - - while (self->keep_walking) { - sem_wait(&self->port_sem); - free_ports(self, self->port_del); - update_ports(self); - } - debug_log("port_thread exited\n"); - return NULL; -} - -static -void add_existing_ports(alsa_seqmidi_t *self) -{ - snd_seq_addr_t addr; - snd_seq_client_info_t *client_info; - snd_seq_port_info_t *port_info; - - snd_seq_client_info_alloca(&client_info); - snd_seq_port_info_alloca(&port_info); - snd_seq_client_info_set_client(client_info, -1); - while (snd_seq_query_next_client(self->seq, client_info) >= 0) - { - addr.client = snd_seq_client_info_get_client(client_info); - if (addr.client == SND_SEQ_CLIENT_SYSTEM || addr.client == self->client_id) - continue; - snd_seq_port_info_set_client(port_info, addr.client); - snd_seq_port_info_set_port(port_info, -1); - while (snd_seq_query_next_port(self->seq, port_info) >= 0) - { - addr.port = snd_seq_port_info_get_port(port_info); - update_port(self, addr, port_info); - } - } -} - -/* - * =================== Input/output port handling ========================= - */ -static -void set_process_info(struct process_info *info, alsa_seqmidi_t *self, int dir, jack_nframes_t nframes) -{ - const snd_seq_real_time_t* alsa_time; - snd_seq_queue_status_t *status; - - snd_seq_queue_status_alloca(&status); - - info->dir = dir; - - info->period_start = jack_last_frame_time(self->jack); - info->nframes = nframes; - info->sample_rate = jack_get_sample_rate(self->jack); - - info->cur_frames = jack_frame_time(self->jack); - - // immediately get alsa'a real time (uhh, why everybody has their on 'real' time) - snd_seq_get_queue_status(self->seq, self->queue, status); - alsa_time = snd_seq_queue_status_get_real_time(status); - info->alsa_time = alsa_time->tv_sec * NSEC_PER_SEC + alsa_time->tv_nsec; - - if (info->period_start + info->nframes < info->cur_frames) { - int periods_lost = (info->cur_frames - info->period_start) / info->nframes; - info->period_start += periods_lost * info->nframes; - debug_log("xrun detected: %d periods lost\n", periods_lost); - } -} - -static -void add_ports(stream_t *str) -{ - port_t *port; - while (jack_ringbuffer_read(str->new_ports, (char*)&port, sizeof(port))) { - debug_log("jack: inserted port %s\n", port->name); - port_insert(str->ports, port); - } -} - -static -void jack_process(alsa_seqmidi_t *self, struct process_info *info) -{ - stream_t *str = &self->stream[info->dir]; - port_jack_func process = port_type[info->dir].jack_func; - int i, del=0; - - add_ports(str); - - // process ports - for (i=0; iports[i]; - while (*pport) { - port_t *port = *pport; - port->jack_buf = jack_port_get_buffer(port->jack_port, info->nframes); - if (info->dir == PORT_INPUT) - jack_midi_clear_buffer(port->jack_buf); - - if (!port->is_dead) - (*process)(self, port, info); - else if (jack_ringbuffer_write_space(self->port_del) >= sizeof(port)) { - debug_log("jack: removed port %s\n", port->name); - *pport = port->next; - jack_ringbuffer_write(self->port_del, (char*)&port, sizeof(port)); - del++; - continue; - } - - pport = &port->next; - } - } - - if (del) - sem_post(&self->port_sem); -} - -/* - * ============================ Input ============================== - */ -static -void do_jack_input(alsa_seqmidi_t *self, port_t *port, struct process_info *info) -{ - // process port->early_events - alsa_midi_event_t ev; - while (jack_ringbuffer_read(port->early_events, (char*)&ev, sizeof(ev))) { - jack_midi_data_t* buf; - jack_nframes_t time = ev.time - info->period_start; - if (time < 0) - time = 0; - else if (time >= info->nframes) - time = info->nframes - 1; - buf = jack_midi_event_reserve(port->jack_buf, time, ev.size); - if (buf) - jack_ringbuffer_read(port->early_events, (char*)buf, ev.size); - else - jack_ringbuffer_read_advance(port->early_events, ev.size); - debug_log("input: it's time for %d bytes at %d\n", ev.size, time); - } -} - -static -void port_event(alsa_seqmidi_t *self, snd_seq_event_t *ev) -{ - const snd_seq_addr_t addr = ev->data.addr; - - if (addr.client == self->client_id) - return; - - if (ev->type == SND_SEQ_EVENT_PORT_START || ev->type == SND_SEQ_EVENT_PORT_CHANGE) { - assert (jack_ringbuffer_write_space(self->port_add) >= sizeof(addr)); - - debug_log("port_event: add/change %d:%d\n", addr.client, addr.port); - jack_ringbuffer_write(self->port_add, (char*)&addr, sizeof(addr)); - sem_post(&self->port_sem); - } else if (ev->type == SND_SEQ_EVENT_PORT_EXIT) { - debug_log("port_event: del %d:%d\n", addr.client, addr.port); - port_setdead(self->stream[PORT_INPUT].ports, addr); - port_setdead(self->stream[PORT_OUTPUT].ports, addr); - } -} - -static -void input_event(alsa_seqmidi_t *self, snd_seq_event_t *alsa_event, struct process_info* info) -{ - jack_midi_data_t data[MAX_EVENT_SIZE]; - stream_t *str = &self->stream[PORT_INPUT]; - long size; - int64_t alsa_time, time_offset; - int64_t frame_offset, event_frame; - port_t *port; - - port = port_get(str->ports, alsa_event->source); - if (!port) - return; - - /* - * RPNs, NRPNs, Bank Change, etc. need special handling - * but seems, ALSA does it for us already. - */ - snd_midi_event_reset_decode(str->codec); - if ((size = snd_midi_event_decode(str->codec, data, sizeof(data), alsa_event))<0) - return; - - // fixup NoteOn with vel 0 - if ((data[0] & 0xF0) == 0x90 && data[2] == 0x00) { - data[0] = 0x80 + (data[0] & 0x0F); - data[2] = 0x40; - } - - alsa_time = alsa_event->time.time.tv_sec * NSEC_PER_SEC + alsa_event->time.time.tv_nsec; - time_offset = info->alsa_time - alsa_time; - frame_offset = (info->sample_rate * time_offset) / NSEC_PER_SEC; - event_frame = (int64_t)info->cur_frames - info->period_start - frame_offset + info->nframes; - - debug_log("input: %d bytes at event_frame=%d\n", (int)size, (int)event_frame); - - if (event_frame >= info->nframes && - jack_ringbuffer_write_space(port->early_events) >= (sizeof(alsa_midi_event_t) + size)) { - alsa_midi_event_t ev; - ev.time = event_frame + info->period_start; - ev.size = size; - jack_ringbuffer_write(port->early_events, (char*)&ev, sizeof(ev)); - jack_ringbuffer_write(port->early_events, (char*)data, size); - debug_log("postponed to next frame +%d\n", (int) (event_frame - info->nframes)); - return; - } - - if (event_frame < 0) - event_frame = 0; - else if (event_frame >= info->nframes) - event_frame = info->nframes - 1; - - jack_midi_event_write(port->jack_buf, event_frame, data, size); -} - -static -void alsa_seqmidi_read(alsa_midi_t *m, jack_nframes_t nframes) -{ - alsa_seqmidi_t *self = (alsa_seqmidi_t*) m; - int res; - snd_seq_event_t *event; - struct process_info info; - - if (!self->keep_walking) - return; - - set_process_info(&info, self, PORT_INPUT, nframes); - jack_process(self, &info); - - while ((res = snd_seq_event_input(self->seq, &event))>0) { - if (event->source.client == SND_SEQ_CLIENT_SYSTEM) - port_event(self, event); - else - input_event(self, event, &info); - } -} - -/* - * ============================ Output ============================== - */ - -static -void do_jack_output(alsa_seqmidi_t *self, port_t *port, struct process_info* info) -{ - stream_t *str = &self->stream[info->dir]; - int nevents = jack_midi_get_event_count(port->jack_buf); - int i; - for (i=0; ijack_buf, i); - - snd_seq_ev_clear(&alsa_event); - snd_midi_event_reset_encode(str->codec); - if (!snd_midi_event_encode(str->codec, jack_event.buffer, jack_event.size, &alsa_event)) - continue; // invalid event - - snd_seq_ev_set_source(&alsa_event, self->port_id); - snd_seq_ev_set_dest(&alsa_event, port->remote.client, port->remote.port); - - /* NOTE: in case of xrun it could become negative, so it is essential to use signed type! */ - frame_offset = (int64_t)jack_event.time + info->period_start + info->nframes - info->cur_frames; - if (frame_offset < 0) { - frame_offset = info->nframes + jack_event.time; - error_log("internal xrun detected: frame_offset = %"PRId64"\n", frame_offset); - } - /* Ken Ellinwood reported problems with this assert. - * Seems, magic 2 should be replaced with nperiods. */ - //FIXME: assert (frame_offset < info->nframes*2); - //if (frame_offset < info->nframes * info->nperiods) - // debug_log("alsa_out: BLAH-BLAH-BLAH"); - - out_time = info->alsa_time + (frame_offset * NSEC_PER_SEC) / info->sample_rate; - - // we should use absolute time to prevent reordering caused by rounding errors - if (out_time < port->last_out_time) { - debug_log("alsa_out: limiting out_time %lld at %lld\n", out_time, port->last_out_time); - out_time = port->last_out_time; - } else - port->last_out_time = out_time; - - out_rt.tv_nsec = out_time % NSEC_PER_SEC; - out_rt.tv_sec = out_time / NSEC_PER_SEC; - snd_seq_ev_schedule_real(&alsa_event, self->queue, 0, &out_rt); - - err = snd_seq_event_output(self->seq, &alsa_event); - - debug_log("alsa_out: written %d bytes to %s at %d (%lld): %d %s\n", - jack_event.size, port->name, (int)frame_offset, out_time - info->alsa_time, err, err < 0 ? snd_strerror(err) : "bytes queued"); - } -} - -static -void alsa_seqmidi_write(alsa_midi_t *m, jack_nframes_t nframes) -{ - alsa_seqmidi_t *self = (alsa_seqmidi_t*) m; - struct process_info info; - - if (!self->keep_walking) - return; - - set_process_info(&info, self, PORT_OUTPUT, nframes); - jack_process(self, &info); - snd_seq_drain_output(self->seq); -} diff --git a/drivers/alsa-midi/midi_pack.h b/drivers/alsa-midi/midi_pack.h deleted file mode 100644 index 6fb704b..0000000 --- a/drivers/alsa-midi/midi_pack.h +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (c) 2006,2007 Dmitry S. Baikov - * - * 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 - */ - -#ifndef __jack_midi_pack_h__ -#define __jack_midi_pack_h__ - -#include -#include "engine.h" - -typedef struct { - int running_status; -} midi_pack_t; - -static inline -void midi_pack_reset(midi_pack_t *p) -{ - p->running_status = 0; -} - -static -void midi_pack_event(midi_pack_t *p, jack_midi_event_t *e) -{ - if (e->buffer[0] >= 0x80 && e->buffer[0] < 0xF0) { // Voice Message - if (e->buffer[0] == p->running_status) { - e->buffer++; - e->size--; - } else - p->running_status = e->buffer[0]; - } else if (e->buffer[0] < 0xF8) { // not System Realtime - p->running_status = 0; - } -} - -#endif /* __jack_midi_pack_h__ */ diff --git a/drivers/alsa-midi/midi_unpack.h b/drivers/alsa-midi/midi_unpack.h deleted file mode 100644 index c917f4d..0000000 --- a/drivers/alsa-midi/midi_unpack.h +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright (c) 2006,2007 Dmitry S. Baikov - * - * 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 - */ - -#ifndef __jack_midi_unpack_h__ -#define __jack_midi_unpack_h__ - -#include -#include "engine.h" - -enum { - MIDI_UNPACK_MAX_MSG = 1024 -}; - -typedef struct { - int pos, need, size; - unsigned char data[MIDI_UNPACK_MAX_MSG]; -} midi_unpack_t; - -static inline -void midi_unpack_init(midi_unpack_t *u) -{ - u->pos = 0; - u->size = sizeof(u->data); - u->need = u->size; -} - -static inline -void midi_unpack_reset(midi_unpack_t *u) -{ - u->pos = 0; - u->need = u->size; -} - -static const unsigned char midi_voice_len[] = { - 3, /*0x80 Note Off*/ - 3, /*0x90 Note On*/ - 3, /*0xA0 Aftertouch*/ - 3, /*0xB0 Control Change*/ - 2, /*0xC0 Program Change*/ - 2, /*0xD0 Channel Pressure*/ - 3, /*0xE0 Pitch Wheel*/ - 1 /*0xF0 System*/ -}; - -static const unsigned char midi_system_len[] = { - 0, /*0xF0 System Exclusive Start*/ - 2, /*0xF1 MTC Quarter Frame*/ - 3, /*0xF2 Song Postion*/ - 2, /*0xF3 Song Select*/ - 0, /*0xF4 undefined*/ - 0, /*0xF5 undefined*/ - 1, /*0xF6 Tune Request*/ - 1 /*0xF7 System Exlusive End*/ -}; - -static -int midi_unpack_buf(midi_unpack_t *buf, const unsigned char *data, int len, void *jack_port_buf, jack_nframes_t time) -{ - int i; - for (i=0; i= 0xF8) // system realtime - { - jack_midi_event_write(jack_port_buf, time, &data[i], 1); - //jack_error("midi_unpack: written system relatime event\n"); - //midi_input_write(in, &data[i], 1); - } - else if (byte < 0x80) // data - { - assert (buf->pos < buf->size); - buf->data[buf->pos++] = byte; - } - else if (byte < 0xF0) // voice - { - assert (byte >= 0x80 && byte < 0xF0); - //buf->need = ((byte|0x0F) == 0xCF || (byte|0x0F)==0xDF) ? 2 : 3; - buf->need = midi_voice_len[(byte-0x80)>>4]; - buf->data[0] = byte; - buf->pos = 1; - } - else if (byte == 0xF7) // sysex end - { - assert (buf->pos < buf->size); - buf->data[buf->pos++] = byte; - buf->need = buf->pos; - } - else - { - assert (byte >= 0xF0 && byte < 0xF8); - buf->pos = 1; - buf->data[0] = byte; - buf->need = midi_system_len[byte - 0xF0]; - if (!buf->need) - buf->need = buf->size; - } - if (buf->pos == buf->need) - { - // TODO: deal with big sysex'es (they are silently dropped for now) - if (buf->data[0] >= 0x80 || (buf->data[0]==0xF0 && buf->data[buf->pos-1] == 0xF7)) { - /* convert Note On with velocity 0 to Note Off */ - if ((buf->data[0] & 0xF0) == 0x90 && buf->data[2] == 0) { - // we use temp array here to keep running status in sync - jack_midi_data_t temp[3] = { 0x80, 0, 0x40 }; - temp[0] |= buf->data[0] & 0x0F; - temp[1] = buf->data[1]; - jack_midi_event_write(jack_port_buf, time, temp, 3); - } else - jack_midi_event_write(jack_port_buf, time, &buf->data[0], buf->pos); - //jack_error("midi_unpack: written %d-byte event\n", buf->pos); - //midi_input_write(in, &buf->data[0], buf->pos); - } - /* keep running status */ - if (buf->data[0] >= 0x80 && buf->data[0] < 0xF0) - buf->pos = 1; - else - { - buf->pos = 0; - buf->need = buf->size; - } - } - } - assert (i==len); - return i; -} - -#endif /* __jack_midi_unpack_h__ */ diff --git a/drivers/alsa/Makefile.am b/drivers/alsa/Makefile.am index 0b9bf1e..04b6284 100644 --- a/drivers/alsa/Makefile.am +++ b/drivers/alsa/Makefile.am @@ -8,10 +8,7 @@ plugin_LTLIBRARIES = jack_alsa.la jack_alsa_la_LDFLAGS = -module -avoid-version jack_alsa_la_SOURCES = alsa_driver.c generic_hw.c memops.c \ - hammerfall.c hdsp.c ice1712.c usx2y.c \ - ../alsa-midi/alsa_rawmidi.c ../alsa-midi/alsa_seqmidi.c - -jack_alsa_la_CFLAGS = -I../alsa-midi + hammerfall.c hdsp.c ice1712.c usx2y.c noinst_HEADERS = alsa_driver.h \ generic.h \ diff --git a/drivers/alsa/alsa_driver.c b/drivers/alsa/alsa_driver.c index bd8fd85..93b1633 100644 --- a/drivers/alsa/alsa_driver.c +++ b/drivers/alsa/alsa_driver.c @@ -1017,9 +1017,6 @@ alsa_driver_start (alsa_driver_t *driver) malloc (sizeof (struct pollfd) * (driver->playback_nfds + driver->capture_nfds + 2)); - if (driver->midi && !driver->xrun_recovery) - (driver->midi->start)(driver->midi); - if (driver->playback_handle) { /* fill playback buffer with zeroes, and mark all fragments as having data. @@ -1124,9 +1121,6 @@ alsa_driver_stop (alsa_driver_t *driver) driver->hw->set_input_monitor_mask (driver->hw, 0); } - if (driver->midi && !driver->xrun_recovery) - (driver->midi->stop)(driver->midi); - return 0; } @@ -1140,9 +1134,6 @@ alsa_driver_restart (alsa_driver_t *driver) res = driver->nt_start((struct _jack_driver_nt *) driver); driver->xrun_recovery = 0; - if (res && driver->midi) - (driver->midi->stop)(driver->midi); - return res; } @@ -1565,9 +1556,6 @@ alsa_driver_read (alsa_driver_t *driver, jack_nframes_t nframes) return 0; } - if (driver->midi) - (driver->midi->read)(driver->midi, nframes); - if (!driver->capture_handle) { return 0; } @@ -1640,9 +1628,6 @@ alsa_driver_write (alsa_driver_t* driver, jack_nframes_t nframes) return -1; } - if (driver->midi) - (driver->midi->write)(driver->midi, nframes); - nwritten = 0; contiguous = 0; orig_nframes = nframes; @@ -1852,13 +1837,6 @@ alsa_driver_attach (alsa_driver_t *driver) } } - if (driver->midi) { - int err = (driver->midi->attach)(driver->midi); - if (err) - jack_error("ALSA: cannot attach midi: %d", err); - } - - return jack_activate (driver->client); } @@ -1871,278 +1849,276 @@ alsa_driver_detach (alsa_driver_t *driver) return 0; } - if (driver->midi) - (driver->midi->detach)(driver->midi); - - for (node = driver->capture_ports; node; - node = jack_slist_next (node)) { - jack_port_unregister (driver->client, - ((jack_port_t *) node->data)); - } - - jack_slist_free (driver->capture_ports); - driver->capture_ports = 0; - - for (node = driver->playback_ports; node; - node = jack_slist_next (node)) { - jack_port_unregister (driver->client, - ((jack_port_t *) node->data)); - } - - jack_slist_free (driver->playback_ports); - driver->playback_ports = 0; - - if (driver->monitor_ports) { - for (node = driver->monitor_ports; node; - node = jack_slist_next (node)) { - jack_port_unregister (driver->client, - ((jack_port_t *) node->data)); - } - - jack_slist_free (driver->monitor_ports); - driver->monitor_ports = 0; - } - - return 0; -} - -#if 0 -static int /* UNUSED */ -alsa_driver_change_sample_clock (alsa_driver_t *driver, SampleClockMode mode) - -{ - return driver->hw->change_sample_clock (driver->hw, mode); -} - -static void /* UNUSED */ -alsa_driver_request_all_monitor_input (alsa_driver_t *driver, int yn) - -{ - if (driver->hw_monitoring) { - if (yn) { - driver->hw->set_input_monitor_mask (driver->hw, ~0U); - } else { - driver->hw->set_input_monitor_mask ( - driver->hw, driver->input_monitor_mask); - } - } - - driver->all_monitor_in = yn; -} - -static void /* UNUSED */ -alsa_driver_set_hw_monitoring (alsa_driver_t *driver, int yn) -{ - if (yn) { - driver->hw_monitoring = TRUE; - - if (driver->all_monitor_in) { - driver->hw->set_input_monitor_mask (driver->hw, ~0U); - } else { - driver->hw->set_input_monitor_mask ( - driver->hw, driver->input_monitor_mask); - } - } else { - driver->hw_monitoring = FALSE; - driver->hw->set_input_monitor_mask (driver->hw, 0); - } -} - -static ClockSyncStatus /* UNUSED */ -alsa_driver_clock_sync_status (channel_t chn) -{ - return Lock; -} -#endif - -static void -alsa_driver_delete (alsa_driver_t *driver) -{ - JSList *node; - - if (driver->midi) - (driver->midi->destroy)(driver->midi); - - for (node = driver->clock_sync_listeners; node; - node = jack_slist_next (node)) { - free (node->data); - } - jack_slist_free (driver->clock_sync_listeners); - - if (driver->ctl_handle) { - snd_ctl_close (driver->ctl_handle); - driver->ctl_handle = 0; - } - - if (driver->capture_handle) { - snd_pcm_close (driver->capture_handle); - driver->capture_handle = 0; - } - - if (driver->playback_handle) { - snd_pcm_close (driver->playback_handle); - driver->capture_handle = 0; - } - - if (driver->capture_hw_params) { - snd_pcm_hw_params_free (driver->capture_hw_params); - driver->capture_hw_params = 0; - } - - if (driver->playback_hw_params) { - snd_pcm_hw_params_free (driver->playback_hw_params); - driver->playback_hw_params = 0; - } - - if (driver->capture_sw_params) { - snd_pcm_sw_params_free (driver->capture_sw_params); - driver->capture_sw_params = 0; - } - - if (driver->playback_sw_params) { - snd_pcm_sw_params_free (driver->playback_sw_params); - driver->playback_sw_params = 0; - } - - if (driver->pfd) { - free (driver->pfd); - } - - if (driver->hw) { - driver->hw->release (driver->hw); - driver->hw = 0; - } - free(driver->alsa_name_playback); - free(driver->alsa_name_capture); - free(driver->alsa_driver); - - alsa_driver_release_channel_dependent_memory (driver); - jack_driver_nt_finish ((jack_driver_nt_t *) driver); - free (driver); -} - -static char* -discover_alsa_using_apps () -{ - char found[2048]; - char command[5192]; - char* path = getenv ("PATH"); - char* dir; - size_t flen = 0; - int card; - int device; - size_t cmdlen = 0; - - if (!path) { - return NULL; - } - - /* look for lsof and give up if its not in PATH */ - - path = strdup (path); - dir = strtok (path, ":"); - while (dir) { - char maybe[PATH_MAX+1]; - snprintf (maybe, sizeof(maybe), "%s/lsof", dir); - if (access (maybe, X_OK) == 0) { - break; - } - dir = strtok (NULL, ":"); - } - free (path); - - if (!dir) { - return NULL; - } - - snprintf (command, sizeof (command), "lsof -Fc0 "); - cmdlen = strlen (command); - - for (card = 0; card < 8; ++card) { - for (device = 0; device < 8; ++device) { - char buf[32]; - - snprintf (buf, sizeof (buf), "/dev/snd/pcmC%dD%dp", card, device); - if (access (buf, F_OK) == 0) { - snprintf (command+cmdlen, sizeof(command)-cmdlen, "%s ", buf); - } - cmdlen = strlen (command); - - snprintf (buf, sizeof (buf), "/dev/snd/pcmC%dD%dc", card, device); - if (access (buf, F_OK) == 0) { - snprintf (command+cmdlen, sizeof(command)-cmdlen, "%s ", buf); - } - cmdlen = strlen (command); - } + for (node = driver->capture_ports; node; + node = jack_slist_next (node)) { + jack_port_unregister (driver->client, + ((jack_port_t *) node->data)); } - FILE* f = popen (command, "r"); - - if (!f) { - return NULL; - } - - while (!feof (f)) { - char buf[1024]; /* lsof doesn't output much */ - - if (!fgets (buf, sizeof (buf), f)) { - break; - } - - if (*buf != 'p') { - return NULL; - } - - /* buf contains NULL as a separator between the process field and the command field */ - char *pid = buf; - ++pid; /* skip leading 'p' */ - char *cmd = pid; - - /* skip to NULL */ - while (*cmd) { - ++cmd; - } - ++cmd; /* skip to 'c' */ - ++cmd; /* skip to first character of command */ - - snprintf (found+flen, sizeof (found)-flen, "%s (process ID %s)\n", cmd, pid); - flen = strlen (found); - - if (flen >= sizeof (found)) { - break; - } - } - - pclose (f); - - if (flen) { - return strdup (found); - } else { - return NULL; - } -} - - -static jack_driver_t * -alsa_driver_new (char *name, char *playback_alsa_device, - char *capture_alsa_device, - jack_client_t *client, - jack_nframes_t frames_per_cycle, - jack_nframes_t user_nperiods, - jack_nframes_t rate, - int hw_monitoring, - int hw_metering, - int capturing, - int playing, - DitherAlgorithm dither, - int soft_mode, - int monitor, - int user_capture_nchnls, - int user_playback_nchnls, - int shorts_first, - jack_nframes_t capture_latency, - jack_nframes_t playback_latency, - alsa_midi_t *midi_driver + jack_slist_free (driver->capture_ports); + driver->capture_ports = 0; + + for (node = driver->playback_ports; node; + node = jack_slist_next (node)) { + jack_port_unregister (driver->client, + ((jack_port_t *) node->data)); + } + + jack_slist_free (driver->playback_ports); + driver->playback_ports = 0; + + if (driver->monitor_ports) { + for (node = driver->monitor_ports; node; + node = jack_slist_next (node)) { + jack_port_unregister (driver->client, + ((jack_port_t *) node->data)); + } + + jack_slist_free (driver->monitor_ports); + driver->monitor_ports = 0; + } + + return 0; + } + + #if 0 + static int /* UNUSED */ + alsa_driver_change_sample_clock (alsa_driver_t *driver, SampleClockMode mode) + + { + return driver->hw->change_sample_clock (driver->hw, mode); + } + + static void /* UNUSED */ + alsa_driver_request_all_monitor_input (alsa_driver_t *driver, int yn) + + { + if (driver->hw_monitoring) { + if (yn) { + driver->hw->set_input_monitor_mask (driver->hw, ~0U); + } else { + driver->hw->set_input_monitor_mask ( + driver->hw, driver->input_monitor_mask); + } + } + + driver->all_monitor_in = yn; + } + + static void /* UNUSED */ + alsa_driver_set_hw_monitoring (alsa_driver_t *driver, int yn) + { + if (yn) { + driver->hw_monitoring = TRUE; + + if (driver->all_monitor_in) { + driver->hw->set_input_monitor_mask (driver->hw, ~0U); + } else { + driver->hw->set_input_monitor_mask ( + driver->hw, driver->input_monitor_mask); + } + } else { + driver->hw_monitoring = FALSE; + driver->hw->set_input_monitor_mask (driver->hw, 0); + } + } + + static ClockSyncStatus /* UNUSED */ + alsa_driver_clock_sync_status (channel_t chn) + { + return Lock; + } + #endif + + static void + alsa_driver_delete (alsa_driver_t *driver) + { + JSList *node; + + /* + if (driver->midi) + (driver->midi->destroy)((jack_driver_t*) driver->midi); + */ + + for (node = driver->clock_sync_listeners; node; + node = jack_slist_next (node)) { + free (node->data); + } + jack_slist_free (driver->clock_sync_listeners); + + if (driver->ctl_handle) { + snd_ctl_close (driver->ctl_handle); + driver->ctl_handle = 0; + } + + if (driver->capture_handle) { + snd_pcm_close (driver->capture_handle); + driver->capture_handle = 0; + } + + if (driver->playback_handle) { + snd_pcm_close (driver->playback_handle); + driver->capture_handle = 0; + } + + if (driver->capture_hw_params) { + snd_pcm_hw_params_free (driver->capture_hw_params); + driver->capture_hw_params = 0; + } + + if (driver->playback_hw_params) { + snd_pcm_hw_params_free (driver->playback_hw_params); + driver->playback_hw_params = 0; + } + + if (driver->capture_sw_params) { + snd_pcm_sw_params_free (driver->capture_sw_params); + driver->capture_sw_params = 0; + } + + if (driver->playback_sw_params) { + snd_pcm_sw_params_free (driver->playback_sw_params); + driver->playback_sw_params = 0; + } + + if (driver->pfd) { + free (driver->pfd); + } + + if (driver->hw) { + driver->hw->release (driver->hw); + driver->hw = 0; + } + free(driver->alsa_name_playback); + free(driver->alsa_name_capture); + free(driver->alsa_driver); + + alsa_driver_release_channel_dependent_memory (driver); + jack_driver_nt_finish ((jack_driver_nt_t *) driver); + free (driver); + } + + static char* + discover_alsa_using_apps () + { + char found[2048]; + char command[5192]; + char* path = getenv ("PATH"); + char* dir; + size_t flen = 0; + int card; + int device; + size_t cmdlen = 0; + + if (!path) { + return NULL; + } + + /* look for lsof and give up if its not in PATH */ + + path = strdup (path); + dir = strtok (path, ":"); + while (dir) { + char maybe[PATH_MAX+1]; + snprintf (maybe, sizeof(maybe), "%s/lsof", dir); + if (access (maybe, X_OK) == 0) { + break; + } + dir = strtok (NULL, ":"); + } + free (path); + + if (!dir) { + return NULL; + } + + snprintf (command, sizeof (command), "lsof -Fc0 "); + cmdlen = strlen (command); + + for (card = 0; card < 8; ++card) { + for (device = 0; device < 8; ++device) { + char buf[32]; + + snprintf (buf, sizeof (buf), "/dev/snd/pcmC%dD%dp", card, device); + if (access (buf, F_OK) == 0) { + snprintf (command+cmdlen, sizeof(command)-cmdlen, "%s ", buf); + } + cmdlen = strlen (command); + + snprintf (buf, sizeof (buf), "/dev/snd/pcmC%dD%dc", card, device); + if (access (buf, F_OK) == 0) { + snprintf (command+cmdlen, sizeof(command)-cmdlen, "%s ", buf); + } + cmdlen = strlen (command); + } + } + + FILE* f = popen (command, "r"); + + if (!f) { + return NULL; + } + + while (!feof (f)) { + char buf[1024]; /* lsof doesn't output much */ + + if (!fgets (buf, sizeof (buf), f)) { + break; + } + + if (*buf != 'p') { + return NULL; + } + + /* buf contains NULL as a separator between the process field and the command field */ + char *pid = buf; + ++pid; /* skip leading 'p' */ + char *cmd = pid; + + /* skip to NULL */ + while (*cmd) { + ++cmd; + } + ++cmd; /* skip to 'c' */ + ++cmd; /* skip to first character of command */ + + snprintf (found+flen, sizeof (found)-flen, "%s (process ID %s)\n", cmd, pid); + flen = strlen (found); + + if (flen >= sizeof (found)) { + break; + } + } + + pclose (f); + + if (flen) { + return strdup (found); + } else { + return NULL; + } + } + + + static jack_driver_t * + alsa_driver_new (char *name, char *playback_alsa_device, + char *capture_alsa_device, + jack_client_t *client, + jack_nframes_t frames_per_cycle, + jack_nframes_t user_nperiods, + jack_nframes_t rate, + int hw_monitoring, + int hw_metering, + int capturing, + int playing, + DitherAlgorithm dither, + int soft_mode, + int monitor, + int user_capture_nchnls, + int user_playback_nchnls, + int shorts_first, + jack_nframes_t capture_latency, + jack_nframes_t playback_latency ) { int err; @@ -2225,7 +2201,6 @@ alsa_driver_new (char *name, char *playback_alsa_device, driver->alsa_name_playback = strdup (playback_alsa_device); driver->alsa_name_capture = strdup (capture_alsa_device); - driver->midi = midi_driver; driver->xrun_recovery = 0; if (alsa_driver_check_card_type (driver)) { @@ -2684,12 +2659,8 @@ driver_get_descriptor () params[i].character = 'X'; params[i].type = JackDriverParamString; strcpy (params[i].value.str, "none"); - strcpy (params[i].short_desc, "ALSA MIDI driver (seq|raw)"); - strcpy (params[i].long_desc, - "ALSA MIDI driver:\n" - " none - no MIDI driver\n" - " seq - ALSA Sequencer driver\n" - " raw - ALSA RawMIDI driver\n"); + strcpy (params[i].short_desc, "legacy"); + strcpy (params[i].long_desc, "legacy option - do not use"); desc->params = params; @@ -2716,8 +2687,6 @@ driver_initialize (jack_client_t *client, const JSList * params) int shorts_first = FALSE; jack_nframes_t systemic_input_latency = 0; jack_nframes_t systemic_output_latency = 0; - char *midi_driver_name = "none"; - alsa_midi_t *midi = NULL; const JSList * node; const jack_driver_param_t * param; @@ -2807,7 +2776,7 @@ driver_initialize (jack_client_t *client, const JSList * params) break; case 'X': - midi_driver_name = strdup (param->value.str); + /* ignored, legacy option */ break; } @@ -2819,12 +2788,6 @@ driver_initialize (jack_client_t *client, const JSList * params) playback = TRUE; } - if (strcmp(midi_driver_name, "seq")==0) { - midi = alsa_seqmidi_new(client, NULL); - } else if (strcmp(midi_driver_name, "raw")==0) { - midi = alsa_rawmidi_new(client); - } - return alsa_driver_new ("alsa_pcm", playback_pcm_name, capture_pcm_name, client, frames_per_interrupt, @@ -2834,7 +2797,7 @@ driver_initialize (jack_client_t *client, const JSList * params) user_capture_nchnls, user_playback_nchnls, shorts_first, systemic_input_latency, - systemic_output_latency, midi); + systemic_output_latency); } void diff --git a/drivers/alsa/alsa_driver.h b/drivers/alsa/alsa_driver.h index 67bbc89..074ac69 100644 --- a/drivers/alsa/alsa_driver.h +++ b/drivers/alsa/alsa_driver.h @@ -38,8 +38,6 @@ #include "driver.h" #include "memops.h" -#include "../alsa-midi/alsa_midi.h" - typedef void (*ReadCopyFunction) (jack_default_audio_sample_t *dst, char *src, unsigned long src_bytes, unsigned long src_skip_bytes); @@ -136,7 +134,6 @@ typedef struct _alsa_driver { int xrun_count; int process_count; - alsa_midi_t *midi; int xrun_recovery; int previously_successfully_configured; diff --git a/drivers/alsa/usx2y.c b/drivers/alsa/usx2y.c index 41960ef..23efeb3 100644 --- a/drivers/alsa/usx2y.c +++ b/drivers/alsa/usx2y.c @@ -229,9 +229,6 @@ usx2y_driver_start (alsa_driver_t *driver) return -1; } - if (driver->midi && !driver->xrun_recovery) - (driver->midi->start)(driver->midi); - if (driver->playback_handle) { /* int i, j; */ /* char buffer[2000]; */ @@ -382,9 +379,6 @@ usx2y_driver_stop (alsa_driver_t *driver) munmap(h->hwdep_pcm_shm, sizeof(snd_usX2Y_hwdep_pcm_shm_t)); - if (driver->midi && !driver->xrun_recovery) - (driver->midi->stop)(driver->midi); - return 0; } @@ -487,9 +481,6 @@ usx2y_driver_read (alsa_driver_t *driver, jack_nframes_t nframes) return 0; } - if (driver->midi) - (driver->midi->read)(driver->midi, nframes); - nread = 0; if (snd_pcm_mmap_begin (driver->capture_handle, @@ -564,9 +555,6 @@ usx2y_driver_write (alsa_driver_t* driver, jack_nframes_t nframes) return 0; } - if (driver->midi) - (driver->midi->write)(driver->midi, nframes); - nwritten = 0; /* check current input monitor request status */ diff --git a/drivers/alsa_midi/Makefile.am b/drivers/alsa_midi/Makefile.am new file mode 100644 index 0000000..e7ebc86 --- /dev/null +++ b/drivers/alsa_midi/Makefile.am @@ -0,0 +1,16 @@ +MAINTAINERCLEANFILES = Makefile.in + +# +# slave driver to bridge to ALSA (sequencer) MIDI +# + +driverdir = $(ADDON_DIR) + +driver_LTLIBRARIES = jack_alsa_midi.la + +jack_alsa_midi_la_LDFLAGS = -module -avoid-version @OS_LDFLAGS@ +jack_alsa_midi_la_SOURCES = alsa_midi.c port.c port_hash.c port_thread.c list.c +jack_alsa_midi_la_LIBADD = $(ALSA_LIBS) + +noinst_HEADERS = a2j.h list.h port.h port_hash.h port_thread.h + diff --git a/drivers/alsa_midi/a2j.h b/drivers/alsa_midi/a2j.h new file mode 100644 index 0000000..c4428b3 --- /dev/null +++ b/drivers/alsa_midi/a2j.h @@ -0,0 +1,139 @@ +/* -*- Mode: C ; c-basic-offset: 2 -*- */ +/* + * ALSA SEQ < - > JACK MIDI bridge + * + * Copyright (c) 2006,2007 Dmitry S. Baikov + * Copyright (c) 2007,2008,2009 Nedko Arnaudov + * + * 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; version 2 of the License. + * + * 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 + */ + +#ifndef __jack_alsa_midi_h__ +#define __jack_alsa_midi_h__ + +#include +#include +#include +#include + +#include "driver.h" +#include "list.h" + +#define JACK_INVALID_PORT NULL + +#define MAX_PORTS 2048 +#define MAX_EVENT_SIZE 1024 + +#define PORT_HASH_BITS 4 +#define PORT_HASH_SIZE (1 << PORT_HASH_BITS) + +/* Beside enum use, these are indeces for (struct a2j).stream array */ +#define A2J_PORT_CAPTURE 0 // ALSA playback port -> JACK capture port +#define A2J_PORT_PLAYBACK 1 // JACK playback port -> ALSA capture port + +typedef struct a2j_port * a2j_port_hash_t[PORT_HASH_SIZE]; + +struct alsa_midi_driver; + +struct a2j_port +{ + struct a2j_port * next; /* hash - jack */ + struct list_head siblings; /* list - main loop */ + struct alsa_midi_driver * driver_ptr; + bool is_dead; + char name[64]; + snd_seq_addr_t remote; + jack_port_t * jack_port; + + jack_ringbuffer_t * inbound_events; // alsa_midi_event_t + data + int64_t last_out_time; + + void * jack_buf; +}; + +struct a2j_stream +{ + snd_midi_event_t *codec; + + jack_ringbuffer_t *new_ports; + + a2j_port_hash_t port_hash; + struct list_head list; +}; + +typedef struct alsa_midi_driver +{ + JACK_DRIVER_DECL; + + jack_client_t * jack_client; + + snd_seq_t *seq; + pthread_t alsa_input_thread; + pthread_t alsa_output_thread; + int client_id; + int port_id; + int queue; + bool freewheeling; + bool running; + bool finishing; + + jack_ringbuffer_t* port_add; // snd_seq_addr_t + jack_ringbuffer_t* port_del; // struct a2j_port* + jack_ringbuffer_t* outbound_events; // struct a2j_delivery_event + jack_nframes_t cycle_start; + + sem_t output_semaphore; + + struct a2j_stream stream[2]; + +} alsa_midi_driver_t; + +#define NSEC_PER_SEC ((int64_t)1000*1000*1000) + +struct a2j_alsa_midi_event +{ + int64_t time; + int size; +}; + +#define MAX_JACKMIDI_EV_SIZE 16 + +struct a2j_delivery_event +{ + struct list_head siblings; + + /* a jack MIDI event, plus the port its destined for: everything + the ALSA output thread needs to deliver the event. time is + part of the jack_event. + */ + jack_midi_event_t jack_event; + jack_nframes_t time; /* realtime, not offset time */ + struct a2j_port* port; + char midistring[MAX_JACKMIDI_EV_SIZE]; +}; + +void a2j_error (const char* fmt, ...); + +#define A2J_DEBUG +/*#undef A2J_DEBUG*/ + +#ifdef A2J_DEBUG +extern bool a2j_do_debug; +extern void _a2j_debug (const char* fmt, ...); +#define a2j_debug(fmt, ...) if (a2j_do_debug) { _a2j_debug ((fmt), ##__VA_ARGS__); } +#else +#define a2j_debug(fmt,...) +#endif + +#endif /* __jack_alsa_midi_h__ */ diff --git a/drivers/alsa_midi/alsa_midi.c b/drivers/alsa_midi/alsa_midi.c new file mode 100644 index 0000000..b0a8cc8 --- /dev/null +++ b/drivers/alsa_midi/alsa_midi.c @@ -0,0 +1,864 @@ +/* -*- Mode: C ; c-basic-offset: 2 -*- */ +/* + * ALSA SEQ < - > JACK MIDI bridge + * + * Copyright (c) 2006,2007 Dmitry S. Baikov + * Copyright (c) 2007,2008,2009 Nedko Arnaudov + * Copyright (c) 2009,2010,2013 Paul Davis + * + * 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; version 2 of the License. + * + * 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 +#include +#include +#include +#include +#include +#include +#include + +#include "list.h" +#include "a2j.h" +#include "port_hash.h" +#include "port.h" +#include "port_thread.h" + +#ifdef A2J_DEBUG +bool a2j_do_debug = false; + +void +_a2j_debug (const char* fmt, ...) +{ + va_list ap; + va_start (ap, fmt); + vfprintf (stderr, fmt, ap); + fputc ('\n', stdout); +} +#endif + +void +a2j_error (const char* fmt, ...) +{ + va_list ap; + va_start (ap, fmt); + vfprintf (stdout, fmt, ap); + fputc ('\n', stdout); +} + +static bool +a2j_stream_init(alsa_midi_driver_t* driver, int which) +{ + struct a2j_stream *str = &driver->stream[which]; + + str->new_ports = jack_ringbuffer_create (MAX_PORTS * sizeof(struct a2j_port *)); + if (str->new_ports == NULL) { + return false; + } + + snd_midi_event_new (MAX_EVENT_SIZE, &str->codec); + INIT_LIST_HEAD (&str->list); + + return true; +} + +static void +a2j_stream_detach (struct a2j_stream * stream_ptr) +{ + struct a2j_port * port_ptr; + struct list_head * node_ptr; + + if (!stream_ptr) { + return; + } + + while (!list_empty (&stream_ptr->list)) { + node_ptr = stream_ptr->list.next; + list_del (node_ptr); + port_ptr = list_entry (node_ptr, struct a2j_port, siblings); + a2j_debug ("port deleted: %s", port_ptr->name); + a2j_port_free (port_ptr); + } +} + +static +void +a2j_stream_close (alsa_midi_driver_t* driver, int which) +{ + struct a2j_stream *str = &driver->stream[which]; + + if (!str) { + return; + } + + if (str->codec) + snd_midi_event_free (str->codec); + if (str->new_ports) + jack_ringbuffer_free (str->new_ports); +} + +static void +stop_threads (alsa_midi_driver_t* driver) +{ + if (driver->running) { + void* thread_status; + + driver->running = false; /* tell alsa io thread to stop, whenever they wake up */ + /* do something that we need to do anyway and will wake the io thread, then join */ + snd_seq_disconnect_from (driver->seq, driver->port_id, SND_SEQ_CLIENT_SYSTEM, SND_SEQ_PORT_SYSTEM_ANNOUNCE); + a2j_debug ("wait for ALSA input thread\n"); + pthread_join (driver->alsa_input_thread, &thread_status); + a2j_debug ("input thread done\n"); + + /* wake output thread and join */ + sem_post (&driver->output_semaphore); + pthread_join (driver->alsa_output_thread, &thread_status); + a2j_debug ("output thread done\n"); + } +} + +/* + * =================== Input/output port handling ========================= + */ + +void a2j_add_ports (struct a2j_stream * str) +{ + struct a2j_port * port_ptr; + while (jack_ringbuffer_read (str->new_ports, (char *)&port_ptr, sizeof(port_ptr))) { + a2j_debug("jack: inserted port %s", port_ptr->name); + a2j_port_insert (str->port_hash, port_ptr); + } +} + +static +void +a2j_port_event (alsa_midi_driver_t* driver, snd_seq_event_t * ev) +{ + const snd_seq_addr_t addr = ev->data.addr; + + if (addr.client == driver->client_id) + return; + + if (ev->type == SND_SEQ_EVENT_PORT_START || ev->type == SND_SEQ_EVENT_PORT_CHANGE) { + if (jack_ringbuffer_write_space(driver->port_add) >= sizeof(addr)) { + a2j_debug("port_event: add/change %d:%d", addr.client, addr.port); + jack_ringbuffer_write(driver->port_add, (char*)&addr, sizeof(addr)); + } else { + a2j_error("dropping port_event: add/change %d:%d", addr.client, addr.port); + } + } else if (ev->type == SND_SEQ_EVENT_PORT_EXIT) { + a2j_debug("port_event: del %d:%d", addr.client, addr.port); + a2j_port_setdead(driver->stream[A2J_PORT_CAPTURE].port_hash, addr); + a2j_port_setdead(driver->stream[A2J_PORT_PLAYBACK].port_hash, addr); + } +} + +/* --- INBOUND FROM ALSA TO JACK ---- */ + +static void +a2j_input_event (alsa_midi_driver_t* driver, snd_seq_event_t * alsa_event) +{ + jack_midi_data_t data[MAX_EVENT_SIZE]; + struct a2j_stream *str = &driver->stream[A2J_PORT_CAPTURE]; + long size; + struct a2j_port *port; + jack_nframes_t now; + + now = jack_frame_time (driver->jack_client); + + if ((port = a2j_port_get(str->port_hash, alsa_event->source)) == NULL) { + return; + } + + /* + * RPNs, NRPNs, Bank Change, etc. need special handling + * but seems, ALSA does it for us already. + */ + snd_midi_event_reset_decode(str->codec); + if ((size = snd_midi_event_decode(str->codec, data, sizeof(data), alsa_event))<0) { + return; + } + + // fixup NoteOn with vel 0 + if ((data[0] & 0xF0) == 0x90 && data[2] == 0x00) { + data[0] = 0x80 + (data[0] & 0x0F); + data[2] = 0x40; + } + + a2j_debug("input: %d bytes at event_frame=%u", (int)size, now); + + if (jack_ringbuffer_write_space(port->inbound_events) >= (sizeof(struct a2j_alsa_midi_event) + size)) { + struct a2j_alsa_midi_event ev; + char *ev_charp = (char*) &ev; + size_t limit; + size_t to_write = sizeof(ev); + + jack_ringbuffer_data_t vec[2]; + jack_ringbuffer_get_write_vector( port->inbound_events, vec ); + ev.time = now; + ev.size = size; + + + limit = (to_write > vec[0].len ? vec[0].len : to_write); + if (limit) { + memcpy( vec[0].buf, ev_charp, limit ); + to_write -= limit; + ev_charp += limit; + vec[0].buf += limit; + vec[0].len -= limit; + } + if (to_write) { + memcpy( vec[1].buf, ev_charp, to_write ); + vec[1].buf += to_write; + vec[1].len -= to_write; + } + + to_write = size; + ev_charp = (char *)data; + limit = (to_write > vec[0].len ? vec[0].len : to_write); + if (limit) { + memcpy (vec[0].buf, ev_charp, limit); + } + to_write -= limit; + ev_charp += limit; + if (to_write) { + memcpy (vec[1].buf, ev_charp, to_write); + } + + jack_ringbuffer_write_advance( port->inbound_events, sizeof(ev) + size ); + } else { + a2j_error ("MIDI data lost (incoming event buffer full): %ld bytes lost", size); + } + +} + +static int +a2j_process_incoming (alsa_midi_driver_t* driver, struct a2j_port* port, jack_nframes_t nframes) +{ + jack_nframes_t one_period; + struct a2j_alsa_midi_event ev; + char *ev_buf; + + /* grab data queued by the ALSA input thread and write it into the JACK + port buffer. it will delivered during the JACK period that this + function is called from. + */ + + /* first clear the JACK port buffer in preparation for new data + */ + + a2j_debug ("PORT: %s process input", jack_port_name (port->jack_port)); + + jack_midi_clear_buffer (port->jack_buf); + + one_period = jack_get_buffer_size (driver->jack_client); + + while (jack_ringbuffer_peek (port->inbound_events, (char*)&ev, sizeof(ev) ) == sizeof(ev) ) { + + jack_midi_data_t* buf; + jack_nframes_t offset; + + a2j_debug ("Seen inbound event from read callback\n"); + + if (ev.time >= driver->cycle_start) { + a2j_debug ("event is too late\n"); + break; + } + + //jack_ringbuffer_read_advance (port->inbound_events, sizeof (ev)); + ev_buf = (char *) alloca( sizeof(ev) + ev.size ); + + if (jack_ringbuffer_peek (port->inbound_events, ev_buf, sizeof(ev) + ev.size ) != sizeof(ev) + ev.size) { + break; + } + + offset = driver->cycle_start - ev.time; + if (offset > one_period) { + /* from a previous cycle, somehow. cram it in at the front */ + offset = 0; + } else { + /* offset from start of the current cycle */ + offset = one_period - offset; + } + + a2j_debug ("event at %d offset %d", ev.time, offset); + + /* make sure there is space for it */ + + buf = jack_midi_event_reserve (port->jack_buf, offset, ev.size); + + if (buf) { + /* grab the event */ + memcpy( buf, ev_buf + sizeof(ev), ev.size ); + } else { + /* throw it away (no space) */ + a2j_error ("threw away MIDI event - not reserved at time %d", ev.time); + } + jack_ringbuffer_read_advance (port->inbound_events, sizeof(ev) + ev.size); + + a2j_debug("input on %s: sucked %d bytes from inbound at %d", jack_port_name (port->jack_port), ev.size, ev.time); + } + + return 0; +} + +void* +alsa_input_thread (void* arg) +{ + alsa_midi_driver_t* driver = arg; + int npfd; + struct pollfd * pfd; + snd_seq_addr_t addr; + snd_seq_client_info_t * client_info; + snd_seq_port_info_t * port_info; + bool initial; + snd_seq_event_t * event; + int ret; + + npfd = snd_seq_poll_descriptors_count(driver->seq, POLLIN); + pfd = (struct pollfd *)alloca(npfd * sizeof(struct pollfd)); + snd_seq_poll_descriptors(driver->seq, pfd, npfd, POLLIN); + + initial = true; + + while (driver->running) { + if ((ret = poll(pfd, npfd, 1000)) > 0) { + + while (snd_seq_event_input (driver->seq, &event) > 0) { + if (initial) { + snd_seq_client_info_alloca(&client_info); + snd_seq_port_info_alloca(&port_info); + snd_seq_client_info_set_client(client_info, -1); + while (snd_seq_query_next_client(driver->seq, client_info) >= 0) { + addr.client = snd_seq_client_info_get_client(client_info); + if (addr.client == SND_SEQ_CLIENT_SYSTEM || addr.client == driver->client_id) { + continue; + } + snd_seq_port_info_set_client(port_info, addr.client); + snd_seq_port_info_set_port(port_info, -1); + while (snd_seq_query_next_port(driver->seq, port_info) >= 0) { + addr.port = snd_seq_port_info_get_port(port_info); + a2j_update_port(driver, addr, port_info); + } + } + + initial = false; + } + + if (event->source.client == SND_SEQ_CLIENT_SYSTEM) { + a2j_port_event(driver, event); + } else { + a2j_input_event(driver, event); + } + + snd_seq_free_event (event); + } + } + } + + return (void*) 0; +} + +/* --- OUTBOUND FROM JACK TO ALSA ---- */ + +int +a2j_process_outgoing ( + alsa_midi_driver_t* driver, + struct a2j_port * port) +{ + /* collect data from JACK port buffer and queue it for delivery by ALSA output thread */ + + int nevents; + jack_ringbuffer_data_t vec[2]; + int i; + int written = 0; + size_t limit; + struct a2j_delivery_event* dev; + size_t gap = 0; + + jack_ringbuffer_get_write_vector (driver->outbound_events, vec); + + dev = (struct a2j_delivery_event*) vec[0].buf; + limit = vec[0].len / sizeof (struct a2j_delivery_event); + nevents = jack_midi_get_event_count (port->jack_buf); + + for (i = 0; (i < nevents) && (written < limit); ++i) { + + jack_midi_event_get (&dev->jack_event, port->jack_buf, i); + if (dev->jack_event.size <= MAX_JACKMIDI_EV_SIZE) + { + dev->time = dev->jack_event.time; + dev->port = port; + memcpy( dev->midistring, dev->jack_event.buffer, dev->jack_event.size ); + written++; + ++dev; + } + } + + /* anything left? use the second part of the vector, as much as possible */ + + if (i < nevents) + { + if (vec[0].len) + { + gap = vec[0].len - written * sizeof(struct a2j_delivery_event); + } + + dev = (struct a2j_delivery_event*) vec[1].buf; + + limit += (vec[1].len / sizeof (struct a2j_delivery_event)); + + while ((i < nevents) && (written < limit)) + { + jack_midi_event_get(&dev->jack_event, port->jack_buf, i); + if (dev->jack_event.size <= MAX_JACKMIDI_EV_SIZE) + { + dev->time = dev->jack_event.time; + dev->port = port; + memcpy(dev->midistring, dev->jack_event.buffer, dev->jack_event.size); + written++; + ++dev; + } + ++i; + } + } + + a2j_debug( "done pushing events: %d ... gap: %d ", (int)written, (int)gap ); + /* clear JACK port buffer; advance ring buffer ptr */ + + jack_ringbuffer_write_advance (driver->outbound_events, written * sizeof (struct a2j_delivery_event) + gap); + + return nevents; +} + +static int +time_sorter (struct a2j_delivery_event * a, struct a2j_delivery_event * b) +{ + if (a->time < b->time) { + return -1; + } else if (a->time > b->time) { + return 1; + } + return 0; +} + +static void* +alsa_output_thread(void * arg) +{ + alsa_midi_driver_t * driver = (alsa_midi_driver_t*) arg; + struct a2j_stream *str = &driver->stream[A2J_PORT_PLAYBACK]; + int i; + struct list_head evlist; + struct list_head * node_ptr; + jack_ringbuffer_data_t vec[2]; + snd_seq_event_t alsa_event; + struct a2j_delivery_event* ev; + float sr; + jack_nframes_t now; + int err; + int limit; + + while (driver->running) { + /* first, make a list of all events in the outbound_events FIFO */ + + INIT_LIST_HEAD(&evlist); + + jack_ringbuffer_get_read_vector (driver->outbound_events, vec); + + a2j_debug ("output thread: got %d+%d events", + (vec[0].len / sizeof (struct a2j_delivery_event)), + (vec[1].len / sizeof (struct a2j_delivery_event))); + + ev = (struct a2j_delivery_event*) vec[0].buf; + limit = vec[0].len / sizeof (struct a2j_delivery_event); + for (i = 0; i < limit; ++i) { + list_add_tail(&ev->siblings, &evlist); + ev++; + } + + ev = (struct a2j_delivery_event*) vec[1].buf; + limit = vec[1].len / sizeof (struct a2j_delivery_event); + for (i = 0; i < limit; ++i) { + list_add_tail(&ev->siblings, &evlist); + ev++; + } + + if (vec[0].len < sizeof(struct a2j_delivery_event) && (vec[1].len == 0)) { + /* no events: wait for some */ + a2j_debug ("output thread: wait for events"); + sem_wait (&driver->output_semaphore); + a2j_debug ("output thread: AWAKE ... loop back for events"); + continue; + } + + /* now sort this list by time */ + + list_sort(&evlist, struct a2j_delivery_event, siblings, time_sorter); + + /* now deliver */ + + sr = jack_get_sample_rate (driver->jack_client); + + list_for_each(node_ptr, &evlist) + { + ev = list_entry(node_ptr, struct a2j_delivery_event, siblings); + + snd_seq_ev_clear(&alsa_event); + snd_midi_event_reset_encode(str->codec); + if (!snd_midi_event_encode(str->codec, (const unsigned char *)ev->midistring, ev->jack_event.size, &alsa_event)) + { + continue; // invalid event + } + + snd_seq_ev_set_source(&alsa_event, driver->port_id); + snd_seq_ev_set_dest(&alsa_event, ev->port->remote.client, ev->port->remote.port); + snd_seq_ev_set_direct (&alsa_event); + + now = jack_frame_time (driver->jack_client); + + ev->time += driver->cycle_start; + + a2j_debug ("@ %d, next event @ %d", now, ev->time); + + /* do we need to wait a while before delivering? */ + + if (ev->time > now) { + struct timespec nanoseconds; + jack_nframes_t sleep_frames = ev->time - now; + float seconds = sleep_frames / sr; + + /* if the gap is long enough, sleep */ + + if (seconds > 0.001) { + nanoseconds.tv_sec = (time_t) seconds; + nanoseconds.tv_nsec = (long) NSEC_PER_SEC * (seconds - nanoseconds.tv_sec); + + a2j_debug ("output thread sleeps for %.2f msec", ((double) nanoseconds.tv_nsec / NSEC_PER_SEC) * 1000.0); + + if (nanosleep (&nanoseconds, NULL) < 0) { + fprintf (stderr, "BAD SLEEP\n"); + /* do something ? */ + } + } + } + + /* its time to deliver */ + err = snd_seq_event_output(driver->seq, &alsa_event); + snd_seq_drain_output (driver->seq); + now = jack_frame_time (driver->jack_client); + a2j_debug("alsa_out: written %d bytes to %s at %d, DELTA = %d", ev->jack_event.size, ev->port->name, now, + (int32_t) (now - ev->time)); + } + + /* free up space in the FIFO */ + + jack_ringbuffer_read_advance (driver->outbound_events, vec[0].len + vec[1].len); + + /* and head back for more */ + } + + return (void*) 0; +} + +/** CORE JACK PROCESSING */ + + +/* ALSA */ + +static void +a2j_jack_process_internal (alsa_midi_driver_t* driver, int dir, jack_nframes_t nframes) +{ + struct a2j_stream * stream_ptr; + int i; + struct a2j_port ** port_ptr_ptr; + struct a2j_port * port_ptr; + int nevents = 0; + + stream_ptr = &driver->stream[dir]; + a2j_add_ports(stream_ptr); + + // process ports + for (i = 0 ; i < PORT_HASH_SIZE ; i++) + { + port_ptr_ptr = &stream_ptr->port_hash[i]; + while (*port_ptr_ptr != NULL) + { + port_ptr = *port_ptr_ptr; + + if (!port_ptr->is_dead) { + port_ptr->jack_buf = jack_port_get_buffer(port_ptr->jack_port, nframes); + + if (dir == A2J_PORT_CAPTURE) { + a2j_process_incoming (driver, port_ptr, nframes); + } else { + nevents += a2j_process_outgoing (driver, port_ptr); + } + + } else if (jack_ringbuffer_write_space (driver->port_del) >= sizeof(port_ptr)) { + + a2j_debug("jack: removed port %s", port_ptr->name); + *port_ptr_ptr = port_ptr->next; + jack_ringbuffer_write(driver->port_del, (char*)&port_ptr, sizeof(port_ptr)); + continue; + + } + + port_ptr_ptr = &port_ptr->next; + } + } + + if (dir == A2J_PORT_PLAYBACK && nevents > 0) { + int sv; + + /* if we queued up anything for output, tell the output thread in + case its waiting for us. + */ + + sem_getvalue (&driver->output_semaphore, &sv); + sem_post (&driver->output_semaphore); + } +} + +/* JACK DRIVER FUNCTIONS */ + +static int +alsa_midi_read (alsa_midi_driver_t* driver, jack_nframes_t nframes) +{ + driver->cycle_start = jack_last_frame_time (driver->jack_client); + a2j_jack_process_internal (driver, A2J_PORT_CAPTURE, nframes); + return 0; +} + +static int +alsa_midi_write (alsa_midi_driver_t* driver, jack_nframes_t nframes) +{ + driver->cycle_start = jack_last_frame_time (driver->jack_client); + a2j_jack_process_internal (driver, A2J_PORT_PLAYBACK, nframes); + return 0; +} + + +static int +alsa_midi_start (alsa_midi_driver_t* driver) +{ + int error; + + snd_seq_start_queue (driver->seq, driver->queue, 0); + snd_seq_drop_input (driver->seq); + + a2j_add_ports(&driver->stream[A2J_PORT_CAPTURE]); + a2j_add_ports(&driver->stream[A2J_PORT_PLAYBACK]); + + driver->running = true; + + if (pthread_create(&driver->alsa_input_thread, NULL, alsa_input_thread, driver) < 0) { + a2j_error("cannot start ALSA input thread"); + return -1; + } + + /* wake the poll loop in the alsa input thread so initial ports are fetched */ + if ((error = snd_seq_connect_from (driver->seq, driver->port_id, SND_SEQ_CLIENT_SYSTEM, SND_SEQ_PORT_SYSTEM_ANNOUNCE)) < 0) { + a2j_error("snd_seq_connect_from() failed"); + return -1; + } + + if (pthread_create(&driver->alsa_output_thread, NULL, alsa_output_thread, driver) < 0) { + a2j_error("cannot start ALSA input thread"); + return -1; + } + + return 0; +} + +static int +alsa_midi_stop (alsa_midi_driver_t* driver) +{ + (void) snd_seq_stop_queue (driver->seq, driver->queue, 0); + return 0; +} + +static int +alsa_midi_attach (alsa_midi_driver_t* driver, jack_engine_t* engine) +{ + int error; + + driver->port_add = jack_ringbuffer_create (2 * MAX_PORTS * sizeof(snd_seq_addr_t)); + + if (driver->port_add == NULL) { + return -1; + + } + + driver->port_del = jack_ringbuffer_create(2 * MAX_PORTS * sizeof(struct a2j_port *)); + if (driver->port_del == NULL) { + return -1; + } + + driver->outbound_events = jack_ringbuffer_create (MAX_EVENT_SIZE * 16 * sizeof(struct a2j_delivery_event)); + if (driver->outbound_events == NULL) { + return -1; + } + + if (!a2j_stream_init (driver, A2J_PORT_CAPTURE)) { + return -1; + } + + if (!a2j_stream_init (driver, A2J_PORT_PLAYBACK)) { + return -1; + } + + if ((error = snd_seq_open(&driver->seq, "hw", SND_SEQ_OPEN_DUPLEX, 0)) < 0) { + a2j_error("failed to open alsa seq"); + return -1; + } + + if ((error = snd_seq_set_client_name(driver->seq, "jackmidi")) < 0) { + a2j_error("snd_seq_set_client_name() failed"); + return -1; + } + + if ((driver->port_id = snd_seq_create_simple_port( + driver->seq, + "port", + SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_WRITE +#ifndef DEBUG + |SND_SEQ_PORT_CAP_NO_EXPORT +#endif + ,SND_SEQ_PORT_TYPE_APPLICATION)) < 0) { + + a2j_error("snd_seq_create_simple_port() failed"); + return -1; + } + + if ((driver->client_id = snd_seq_client_id(driver->seq)) < 0) { + a2j_error("snd_seq_client_id() failed"); + return -1; + } + + if ((driver->queue = snd_seq_alloc_queue(driver->seq)) < 0) { + a2j_error("snd_seq_alloc_queue() failed"); + return -1; + } + + if ((error = snd_seq_nonblock(driver->seq, 1)) < 0) { + a2j_error("snd_seq_nonblock() failed"); + return -1; + } + + return jack_activate (driver->jack_client); +} + +static int +alsa_midi_detach (alsa_midi_driver_t* driver, jack_engine_t* engine) +{ + driver->finishing = true; + + stop_threads (driver); + snd_seq_close (driver->seq); + driver->seq = NULL; + return 0; +} + +static jack_driver_t * +alsa_midi_driver_new (jack_client_t *client, const char *name) +{ + alsa_midi_driver_t* driver = calloc(1, sizeof(alsa_midi_driver_t)); + + jack_info ("creating alsa_midi driver ..."); + + if (!driver) { + return NULL; + } + + jack_driver_init ((jack_driver_t *) driver); + + driver->attach = (JackDriverAttachFunction) alsa_midi_attach; + driver->detach = (JackDriverDetachFunction) alsa_midi_detach; + driver->read = (JackDriverReadFunction) alsa_midi_read; + driver->write = (JackDriverWriteFunction) alsa_midi_write; + driver->start = (JackDriverStartFunction) alsa_midi_start; + driver->stop = (JackDriverStartFunction) alsa_midi_stop; + + driver->jack_client = client; + + if (sem_init(&driver->output_semaphore, 0, 0) < 0) { + a2j_error ("can't create IO semaphore"); + free (driver); + return NULL; + } + + return (jack_driver_t *) driver; +} + +static void +alsa_midi_driver_delete (alsa_midi_driver_t* driver) +{ + a2j_stream_detach (&driver->stream[A2J_PORT_CAPTURE]); + a2j_stream_detach (&driver->stream[A2J_PORT_PLAYBACK]); + a2j_stream_close (driver, A2J_PORT_CAPTURE); + a2j_stream_close (driver, A2J_PORT_PLAYBACK); + + sem_destroy (&driver->output_semaphore); + + jack_ringbuffer_free (driver->outbound_events); + jack_ringbuffer_free (driver->port_add); + jack_ringbuffer_free (driver->port_del); +} + +/* DRIVER "PLUGIN" INTERFACE */ + +const char driver_client_name[] = "alsa_midi"; + +const jack_driver_desc_t * +driver_get_descriptor () +{ + jack_driver_desc_t * desc; + jack_driver_param_desc_t * params; + //unsigned int i; + + desc = calloc (1, sizeof (jack_driver_desc_t)); + + strcpy (desc->name,"alsa_midi"); + desc->nparams = 0; + + params = calloc (desc->nparams, sizeof (jack_driver_param_desc_t)); + + desc->params = params; + + return desc; +} + +jack_driver_t * +driver_initialize (jack_client_t *client, const JSList * params) +{ + const JSList * node; + const jack_driver_param_t * param; + + for (node = params; node; node = jack_slist_next (node)) { + param = (const jack_driver_param_t *) node->data; + + switch (param->character) { + default: + break; + } + } + + return alsa_midi_driver_new (client, NULL); +} + +void +driver_finish (jack_driver_t *driver) +{ + alsa_midi_driver_delete ((alsa_midi_driver_t *) driver); +} diff --git a/drivers/alsa_midi/alsa_midi_driver.c b/drivers/alsa_midi/alsa_midi_driver.c new file mode 100644 index 0000000..e25479f --- /dev/null +++ b/drivers/alsa_midi/alsa_midi_driver.c @@ -0,0 +1,48 @@ + +#include "alsa_midi.h" +#include + +static int +alsa_midi_driver_attach( alsa_midi_driver_t *driver, jack_engine_t *engine ) +{ + return driver->midi->attach(driver->midi); +} + +static int +alsa_midi_driver_detach( alsa_midi_driver_t *driver, jack_engine_t *engine ) +{ + return driver->midi->detach(driver->midi); +} + +static int +alsa_midi_driver_read( alsa_midi_driver_t *driver, jack_nframes_t nframes ) +{ + driver->midi->read(driver->midi, nframes); + return 0; +} + +static int +alsa_midi_driver_write( alsa_midi_driver_t *driver, jack_nframes_t nframes ) +{ + driver->midi->write(driver->midi, nframes); + return 0; +} + +static int +alsa_midi_driver_start( alsa_midi_driver_t *driver ) +{ + return driver->midi->start(driver->midi); +} + +static int +alsa_midi_driver_stop( alsa_midi_driver_t *driver ) +{ + return driver->midi->stop(driver->midi); +} + +static void +alsa_midi_driver_delete( alsa_midi_driver_t *driver ) +{ + +} + diff --git a/drivers/alsa_midi/list.c b/drivers/alsa_midi/list.c new file mode 100644 index 0000000..438066c --- /dev/null +++ b/drivers/alsa_midi/list.c @@ -0,0 +1,147 @@ +/* -*- Mode: C ; c-basic-offset: 2 -*- */ +/***************************************************************************** + * + * list_sort() adapted from linux kernel. + * + * 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; version 2 of the License + * + * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + *****************************************************************************/ + +#include + +#include "list.h" + +/* list sort from Mark J Roberts (mjr@znex.org) */ +void +__list_sort( + struct list_head *head, + int member_offset, + int (*cmp)(void * a, void * b)) +{ + struct list_head *p, *q, *e, *list, *tail, *oldhead; + int insize, nmerges, psize, qsize, i; + + list = head->next; + list_del(head); + insize = 1; + for (;;) { + p = oldhead = list; + list = tail = NULL; + nmerges = 0; + + while (p) { + nmerges++; + q = p; + psize = 0; + for (i = 0; i < insize; i++) { + psize++; + q = q->next == oldhead ? NULL : q->next; + if (!q) + break; + } + + qsize = insize; + while (psize > 0 || (qsize > 0 && q)) { + if (!psize) { + e = q; + q = q->next; + qsize--; + if (q == oldhead) + q = NULL; + } else if (!qsize || !q) { + e = p; + p = p->next; + psize--; + if (p == oldhead) + p = NULL; + } else if (cmp((void *)p - member_offset, (void *)q - member_offset) <= 0) { + e = p; + p = p->next; + psize--; + if (p == oldhead) + p = NULL; + } else { + e = q; + q = q->next; + qsize--; + if (q == oldhead) + q = NULL; + } + if (tail) + tail->next = e; + else + list = e; + e->prev = tail; + tail = e; + } + p = q; + } + + tail->next = list; + list->prev = tail; + + if (nmerges <= 1) + break; + + insize *= 2; + } + + head->next = list; + head->prev = list->prev; + list->prev->next = head; + list->prev = head; +} + +struct test_list_el { + int value; + struct list_head test_list_node; +}; + +int test_list_sort_comparator(struct test_list_el * e1, struct test_list_el * e2) +{ + return e1->value - e2->value; +} + +void test_list_sort(void) +{ + struct list_head test_list; + struct test_list_el *el, *next; + struct test_list_el te1 = {.value = 1}; + struct test_list_el te2 = {.value = 2}; + struct test_list_el te3 = {.value = 3}; + struct test_list_el te4 = {.value = 4}; + struct test_list_el te5 = {.value = 5}; + struct test_list_el te6 = {.value = 6}; + struct test_list_el te7 = {.value = 7}; + + const int expected[] = {1, 2, 3, 4, 5, 6, 7}; + int i; + + INIT_LIST_HEAD(&test_list); + list_add_tail(&te2.test_list_node, &test_list); + list_add_tail(&te6.test_list_node, &test_list); + list_add_tail(&te4.test_list_node, &test_list); + list_add_tail(&te5.test_list_node, &test_list); + list_add_tail(&te7.test_list_node, &test_list); + list_add_tail(&te1.test_list_node, &test_list); + list_add_tail(&te3.test_list_node, &test_list); + + list_sort(&test_list, struct test_list_el, test_list_node, test_list_sort_comparator); + + i = 0; + list_for_each_entry_safe(el, next, &test_list, test_list_node) { + assert(el->value == expected[i]); + i++; + } +} diff --git a/drivers/alsa_midi/list.h b/drivers/alsa_midi/list.h new file mode 100644 index 0000000..5b7f4d4 --- /dev/null +++ b/drivers/alsa_midi/list.h @@ -0,0 +1,903 @@ +/* -*- Mode: C ; c-basic-offset: 2 -*- */ +/***************************************************************************** + * + * Linux kernel header adapted for user-mode + * The 2.6.17-rt1 version was used. + * + * Original copyright holders of this code are unknown, they were not + * mentioned in the original file. + * + * 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; version 2 of the License + * + * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + *****************************************************************************/ + +#ifndef _LINUX_LIST_H +#define _LINUX_LIST_H + +#include + +#if !defined(offsetof) +#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) +#endif + +/** + * container_of - cast a member of a structure out to the containing structure + * @ptr: the pointer to the member. + * @type: the type of the container struct this is embedded in. + * @member: the name of the member within the struct. + * + */ +#define container_of(ptr, type, member) ({ \ + const typeof( ((type *)0)->member ) *__mptr = (ptr); \ + (type *)( (char *)__mptr - offsetof(type,member) );}) + +#define prefetch(x) (x = x) + +/* + * These are non-NULL pointers that will result in page faults + * under normal circumstances, used to verify that nobody uses + * non-initialized list entries. + */ +#define LIST_POISON1 ((void *) 0x00100100) +#define LIST_POISON2 ((void *) 0x00200200) + +/* + * Simple doubly linked list implementation. + * + * Some of the internal functions ("__xxx") are useful when + * manipulating whole lists rather than single entries, as + * sometimes we already know the next/prev entries and we can + * generate better code by using them directly rather than + * using the generic single-entry routines. + */ + +struct list_head { + struct list_head *next, *prev; +}; + +#define LIST_HEAD_INIT(name) { &(name), &(name) } + +#define LIST_HEAD(name) \ + struct list_head name = LIST_HEAD_INIT(name) + +static inline void INIT_LIST_HEAD(struct list_head *list) +{ + list->next = list; + list->prev = list; +} + +/* + * Insert a new entry between two known consecutive entries. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + */ +static inline void __list_add(struct list_head *new, + struct list_head *prev, + struct list_head *next) +{ + next->prev = new; + new->next = next; + new->prev = prev; + prev->next = new; +} + +/** + * list_add - add a new entry + * @new: new entry to be added + * @head: list head to add it after + * + * Insert a new entry after the specified head. + * This is good for implementing stacks. + */ +static inline void list_add(struct list_head *new, struct list_head *head) +{ + __list_add(new, head, head->next); +} + +/** + * list_add_tail - add a new entry + * @new: new entry to be added + * @head: list head to add it before + * + * Insert a new entry before the specified head. + * This is useful for implementing queues. + */ +static inline void list_add_tail(struct list_head *new, struct list_head *head) +{ + __list_add(new, head->prev, head); +} + +/* + * Insert a new entry between two known consecutive entries. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + */ +static inline void __list_add_rcu(struct list_head * new, + struct list_head * prev, struct list_head * next) +{ + new->next = next; + new->prev = prev; +// smp_wmb(); + next->prev = new; + prev->next = new; +} + +/** + * list_add_rcu - add a new entry to rcu-protected list + * @new: new entry to be added + * @head: list head to add it after + * + * Insert a new entry after the specified head. + * This is good for implementing stacks. + * + * The caller must take whatever precautions are necessary + * (such as holding appropriate locks) to avoid racing + * with another list-mutation primitive, such as list_add_rcu() + * or list_del_rcu(), running on this same list. + * However, it is perfectly legal to run concurrently with + * the _rcu list-traversal primitives, such as + * list_for_each_entry_rcu(). + */ +static inline void list_add_rcu(struct list_head *new, struct list_head *head) +{ + __list_add_rcu(new, head, head->next); +} + +/** + * list_add_tail_rcu - add a new entry to rcu-protected list + * @new: new entry to be added + * @head: list head to add it before + * + * Insert a new entry before the specified head. + * This is useful for implementing queues. + * + * The caller must take whatever precautions are necessary + * (such as holding appropriate locks) to avoid racing + * with another list-mutation primitive, such as list_add_tail_rcu() + * or list_del_rcu(), running on this same list. + * However, it is perfectly legal to run concurrently with + * the _rcu list-traversal primitives, such as + * list_for_each_entry_rcu(). + */ +static inline void list_add_tail_rcu(struct list_head *new, + struct list_head *head) +{ + __list_add_rcu(new, head->prev, head); +} + +/* + * Delete a list entry by making the prev/next entries + * point to each other. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + */ +static inline void __list_del(struct list_head * prev, struct list_head * next) +{ + next->prev = prev; + prev->next = next; +} + +/** + * list_del - deletes entry from list. + * @entry: the element to delete from the list. + * Note: list_empty on entry does not return true after this, the entry is + * in an undefined state. + */ +static inline void list_del(struct list_head *entry) +{ + __list_del(entry->prev, entry->next); + entry->next = LIST_POISON1; + entry->prev = LIST_POISON2; +} + +/** + * list_del_rcu - deletes entry from list without re-initialization + * @entry: the element to delete from the list. + * + * Note: list_empty on entry does not return true after this, + * the entry is in an undefined state. It is useful for RCU based + * lockfree traversal. + * + * In particular, it means that we can not poison the forward + * pointers that may still be used for walking the list. + * + * The caller must take whatever precautions are necessary + * (such as holding appropriate locks) to avoid racing + * with another list-mutation primitive, such as list_del_rcu() + * or list_add_rcu(), running on this same list. + * However, it is perfectly legal to run concurrently with + * the _rcu list-traversal primitives, such as + * list_for_each_entry_rcu(). + * + * Note that the caller is not permitted to immediately free + * the newly deleted entry. Instead, either synchronize_rcu() + * or call_rcu() must be used to defer freeing until an RCU + * grace period has elapsed. + */ +static inline void list_del_rcu(struct list_head *entry) +{ + __list_del(entry->prev, entry->next); + entry->prev = LIST_POISON2; +} + +/* + * list_replace_rcu - replace old entry by new one + * @old : the element to be replaced + * @new : the new element to insert + * + * The old entry will be replaced with the new entry atomically. + */ +static inline void list_replace_rcu(struct list_head *old, + struct list_head *new) +{ + new->next = old->next; + new->prev = old->prev; +// smp_wmb(); + new->next->prev = new; + new->prev->next = new; + old->prev = LIST_POISON2; +} + +/** + * list_del_init - deletes entry from list and reinitialize it. + * @entry: the element to delete from the list. + */ +static inline void list_del_init(struct list_head *entry) +{ + __list_del(entry->prev, entry->next); + INIT_LIST_HEAD(entry); +} + +/** + * list_move - delete from one list and add as another's head + * @list: the entry to move + * @head: the head that will precede our entry + */ +static inline void list_move(struct list_head *list, struct list_head *head) +{ + __list_del(list->prev, list->next); + list_add(list, head); +} + +/** + * list_move_tail - delete from one list and add as another's tail + * @list: the entry to move + * @head: the head that will follow our entry + */ +static inline void list_move_tail(struct list_head *list, + struct list_head *head) +{ + __list_del(list->prev, list->next); + list_add_tail(list, head); +} + +/** + * list_empty - tests whether a list is empty + * @head: the list to test. + */ +static inline int list_empty(const struct list_head *head) +{ + return head->next == head; +} + +/** + * list_empty_careful - tests whether a list is + * empty _and_ checks that no other CPU might be + * in the process of still modifying either member + * + * NOTE: using list_empty_careful() without synchronization + * can only be safe if the only activity that can happen + * to the list entry is list_del_init(). Eg. it cannot be used + * if another CPU could re-list_add() it. + * + * @head: the list to test. + */ +static inline int list_empty_careful(const struct list_head *head) +{ + struct list_head *next = head->next; + return (next == head) && (next == head->prev); +} + +static inline void __list_splice(struct list_head *list, + struct list_head *head) +{ + struct list_head *first = list->next; + struct list_head *last = list->prev; + struct list_head *at = head->next; + + first->prev = head; + head->next = first; + + last->next = at; + at->prev = last; +} + +/** + * list_splice - join two lists + * @list: the new list to add. + * @head: the place to add it in the first list. + */ +static inline void list_splice(struct list_head *list, struct list_head *head) +{ + if (!list_empty(list)) + __list_splice(list, head); +} + +/** + * list_splice_init - join two lists and reinitialise the emptied list. + * @list: the new list to add. + * @head: the place to add it in the first list. + * + * The list at @list is reinitialised + */ +static inline void list_splice_init(struct list_head *list, + struct list_head *head) +{ + if (!list_empty(list)) { + __list_splice(list, head); + INIT_LIST_HEAD(list); + } +} + +/** + * list_entry - get the struct for this entry + * @ptr: the &struct list_head pointer. + * @type: the type of the struct this is embedded in. + * @member: the name of the list_struct within the struct. + */ +#define list_entry(ptr, type, member) \ + container_of(ptr, type, member) + +/** + * list_for_each - iterate over a list + * @pos: the &struct list_head to use as a loop counter. + * @head: the head for your list. + */ +#define list_for_each(pos, head) \ + for (pos = (head)->next; prefetch(pos->next), pos != (head); \ + pos = pos->next) + +/** + * __list_for_each - iterate over a list + * @pos: the &struct list_head to use as a loop counter. + * @head: the head for your list. + * + * This variant differs from list_for_each() in that it's the + * simplest possible list iteration code, no prefetching is done. + * Use this for code that knows the list to be very short (empty + * or 1 entry) most of the time. + */ +#define __list_for_each(pos, head) \ + for (pos = (head)->next; pos != (head); pos = pos->next) + +/** + * list_for_each_prev - iterate over a list backwards + * @pos: the &struct list_head to use as a loop counter. + * @head: the head for your list. + */ +#define list_for_each_prev(pos, head) \ + for (pos = (head)->prev; prefetch(pos->prev), pos != (head); \ + pos = pos->prev) + +/** + * list_for_each_safe - iterate over a list safe against removal of list entry + * @pos: the &struct list_head to use as a loop counter. + * @n: another &struct list_head to use as temporary storage + * @head: the head for your list. + */ +#define list_for_each_safe(pos, n, head) \ + for (pos = (head)->next, n = pos->next; pos != (head); \ + pos = n, n = pos->next) + +/** + * list_for_each_entry - iterate over list of given type + * @pos: the type * to use as a loop counter. + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + */ +#define list_for_each_entry(pos, head, member) \ + for (pos = list_entry((head)->next, typeof(*pos), member); \ + prefetch(pos->member.next), &pos->member != (head); \ + pos = list_entry(pos->member.next, typeof(*pos), member)) + +/** + * list_for_each_entry_reverse - iterate backwards over list of given type. + * @pos: the type * to use as a loop counter. + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + */ +#define list_for_each_entry_reverse(pos, head, member) \ + for (pos = list_entry((head)->prev, typeof(*pos), member); \ + prefetch(pos->member.prev), &pos->member != (head); \ + pos = list_entry(pos->member.prev, typeof(*pos), member)) + +/** + * list_prepare_entry - prepare a pos entry for use as a start point in + * list_for_each_entry_continue + * @pos: the type * to use as a start point + * @head: the head of the list + * @member: the name of the list_struct within the struct. + */ +#define list_prepare_entry(pos, head, member) \ + ((pos) ? : list_entry(head, typeof(*pos), member)) + +/** + * list_for_each_entry_continue - iterate over list of given type + * continuing after existing point + * @pos: the type * to use as a loop counter. + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + */ +#define list_for_each_entry_continue(pos, head, member) \ + for (pos = list_entry(pos->member.next, typeof(*pos), member); \ + prefetch(pos->member.next), &pos->member != (head); \ + pos = list_entry(pos->member.next, typeof(*pos), member)) + +/** + * list_for_each_entry_from - iterate over list of given type + * continuing from existing point + * @pos: the type * to use as a loop counter. + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + */ +#define list_for_each_entry_from(pos, head, member) \ + for (; prefetch(pos->member.next), &pos->member != (head); \ + pos = list_entry(pos->member.next, typeof(*pos), member)) + +/** + * list_for_each_entry_safe - iterate over list of given type safe against removal of list entry + * @pos: the type * to use as a loop counter. + * @n: another type * to use as temporary storage + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + */ +#define list_for_each_entry_safe(pos, n, head, member) \ + for (pos = list_entry((head)->next, typeof(*pos), member), \ + n = list_entry(pos->member.next, typeof(*pos), member); \ + &pos->member != (head); \ + pos = n, n = list_entry(n->member.next, typeof(*n), member)) + +/** + * list_for_each_entry_safe_continue - iterate over list of given type + * continuing after existing point safe against removal of list entry + * @pos: the type * to use as a loop counter. + * @n: another type * to use as temporary storage + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + */ +#define list_for_each_entry_safe_continue(pos, n, head, member) \ + for (pos = list_entry(pos->member.next, typeof(*pos), member), \ + n = list_entry(pos->member.next, typeof(*pos), member); \ + &pos->member != (head); \ + pos = n, n = list_entry(n->member.next, typeof(*n), member)) + +/** + * list_for_each_entry_safe_from - iterate over list of given type + * from existing point safe against removal of list entry + * @pos: the type * to use as a loop counter. + * @n: another type * to use as temporary storage + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + */ +#define list_for_each_entry_safe_from(pos, n, head, member) \ + for (n = list_entry(pos->member.next, typeof(*pos), member); \ + &pos->member != (head); \ + pos = n, n = list_entry(n->member.next, typeof(*n), member)) + +/** + * list_for_each_entry_safe_reverse - iterate backwards over list of given type safe against + * removal of list entry + * @pos: the type * to use as a loop counter. + * @n: another type * to use as temporary storage + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + */ +#define list_for_each_entry_safe_reverse(pos, n, head, member) \ + for (pos = list_entry((head)->prev, typeof(*pos), member), \ + n = list_entry(pos->member.prev, typeof(*pos), member); \ + &pos->member != (head); \ + pos = n, n = list_entry(n->member.prev, typeof(*n), member)) + +/** + * list_for_each_rcu - iterate over an rcu-protected list + * @pos: the &struct list_head to use as a loop counter. + * @head: the head for your list. + * + * This list-traversal primitive may safely run concurrently with + * the _rcu list-mutation primitives such as list_add_rcu() + * as long as the traversal is guarded by rcu_read_lock(). + */ +#define list_for_each_rcu(pos, head) \ + for (pos = (head)->next; \ + prefetch(rcu_dereference(pos)->next), pos != (head); \ + pos = pos->next) + +#define __list_for_each_rcu(pos, head) \ + for (pos = (head)->next; \ + rcu_dereference(pos) != (head); \ + pos = pos->next) + +/** + * list_for_each_safe_rcu - iterate over an rcu-protected list safe + * against removal of list entry + * @pos: the &struct list_head to use as a loop counter. + * @n: another &struct list_head to use as temporary storage + * @head: the head for your list. + * + * This list-traversal primitive may safely run concurrently with + * the _rcu list-mutation primitives such as list_add_rcu() + * as long as the traversal is guarded by rcu_read_lock(). + */ +#define list_for_each_safe_rcu(pos, n, head) \ + for (pos = (head)->next; \ + n = rcu_dereference(pos)->next, pos != (head); \ + pos = n) + +/** + * list_for_each_entry_rcu - iterate over rcu list of given type + * @pos: the type * to use as a loop counter. + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + * + * This list-traversal primitive may safely run concurrently with + * the _rcu list-mutation primitives such as list_add_rcu() + * as long as the traversal is guarded by rcu_read_lock(). + */ +#define list_for_each_entry_rcu(pos, head, member) \ + for (pos = list_entry((head)->next, typeof(*pos), member); \ + prefetch(rcu_dereference(pos)->member.next), \ + &pos->member != (head); \ + pos = list_entry(pos->member.next, typeof(*pos), member)) + + +/** + * list_for_each_continue_rcu - iterate over an rcu-protected list + * continuing after existing point. + * @pos: the &struct list_head to use as a loop counter. + * @head: the head for your list. + * + * This list-traversal primitive may safely run concurrently with + * the _rcu list-mutation primitives such as list_add_rcu() + * as long as the traversal is guarded by rcu_read_lock(). + */ +#define list_for_each_continue_rcu(pos, head) \ + for ((pos) = (pos)->next; \ + prefetch(rcu_dereference((pos))->next), (pos) != (head); \ + (pos) = (pos)->next) + +/* + * Double linked lists with a single pointer list head. + * Mostly useful for hash tables where the two pointer list head is + * too wasteful. + * You lose the ability to access the tail in O(1). + */ + +struct hlist_head { + struct hlist_node *first; +}; + +struct hlist_node { + struct hlist_node *next, **pprev; +}; + +#define HLIST_HEAD_INIT { .first = NULL } +#define HLIST_HEAD(name) struct hlist_head name = { .first = NULL } +#define INIT_HLIST_HEAD(ptr) ((ptr)->first = NULL) +static inline void INIT_HLIST_NODE(struct hlist_node *h) +{ + h->next = NULL; + h->pprev = NULL; +} + +static inline int hlist_unhashed(const struct hlist_node *h) +{ + return !h->pprev; +} + +static inline int hlist_empty(const struct hlist_head *h) +{ + return !h->first; +} + +static inline void __hlist_del(struct hlist_node *n) +{ + struct hlist_node *next = n->next; + struct hlist_node **pprev = n->pprev; + *pprev = next; + if (next) + next->pprev = pprev; +} + +static inline void hlist_del(struct hlist_node *n) +{ + __hlist_del(n); + n->next = LIST_POISON1; + n->pprev = LIST_POISON2; +} + +/** + * hlist_del_rcu - deletes entry from hash list without re-initialization + * @n: the element to delete from the hash list. + * + * Note: list_unhashed() on entry does not return true after this, + * the entry is in an undefined state. It is useful for RCU based + * lockfree traversal. + * + * In particular, it means that we can not poison the forward + * pointers that may still be used for walking the hash list. + * + * The caller must take whatever precautions are necessary + * (such as holding appropriate locks) to avoid racing + * with another list-mutation primitive, such as hlist_add_head_rcu() + * or hlist_del_rcu(), running on this same list. + * However, it is perfectly legal to run concurrently with + * the _rcu list-traversal primitives, such as + * hlist_for_each_entry(). + */ +static inline void hlist_del_rcu(struct hlist_node *n) +{ + __hlist_del(n); + n->pprev = LIST_POISON2; +} + +static inline void hlist_del_init(struct hlist_node *n) +{ + if (!hlist_unhashed(n)) { + __hlist_del(n); + INIT_HLIST_NODE(n); + } +} + +/* + * hlist_replace_rcu - replace old entry by new one + * @old : the element to be replaced + * @new : the new element to insert + * + * The old entry will be replaced with the new entry atomically. + */ +static inline void hlist_replace_rcu(struct hlist_node *old, + struct hlist_node *new) +{ + struct hlist_node *next = old->next; + + new->next = next; + new->pprev = old->pprev; +// smp_wmb(); + if (next) + new->next->pprev = &new->next; + *new->pprev = new; + old->pprev = LIST_POISON2; +} + +static inline void hlist_add_head(struct hlist_node *n, struct hlist_head *h) +{ + struct hlist_node *first = h->first; + n->next = first; + if (first) + first->pprev = &n->next; + h->first = n; + n->pprev = &h->first; +} + + +/** + * hlist_add_head_rcu - adds the specified element to the specified hlist, + * while permitting racing traversals. + * @n: the element to add to the hash list. + * @h: the list to add to. + * + * The caller must take whatever precautions are necessary + * (such as holding appropriate locks) to avoid racing + * with another list-mutation primitive, such as hlist_add_head_rcu() + * or hlist_del_rcu(), running on this same list. + * However, it is perfectly legal to run concurrently with + * the _rcu list-traversal primitives, such as + * hlist_for_each_entry_rcu(), used to prevent memory-consistency + * problems on Alpha CPUs. Regardless of the type of CPU, the + * list-traversal primitive must be guarded by rcu_read_lock(). + */ +static inline void hlist_add_head_rcu(struct hlist_node *n, + struct hlist_head *h) +{ + struct hlist_node *first = h->first; + n->next = first; + n->pprev = &h->first; +// smp_wmb(); + if (first) + first->pprev = &n->next; + h->first = n; +} + +/* next must be != NULL */ +static inline void hlist_add_before(struct hlist_node *n, + struct hlist_node *next) +{ + n->pprev = next->pprev; + n->next = next; + next->pprev = &n->next; + *(n->pprev) = n; +} + +static inline void hlist_add_after(struct hlist_node *n, + struct hlist_node *next) +{ + next->next = n->next; + n->next = next; + next->pprev = &n->next; + + if(next->next) + next->next->pprev = &next->next; +} + +/** + * hlist_add_before_rcu - adds the specified element to the specified hlist + * before the specified node while permitting racing traversals. + * @n: the new element to add to the hash list. + * @next: the existing element to add the new element before. + * + * The caller must take whatever precautions are necessary + * (such as holding appropriate locks) to avoid racing + * with another list-mutation primitive, such as hlist_add_head_rcu() + * or hlist_del_rcu(), running on this same list. + * However, it is perfectly legal to run concurrently with + * the _rcu list-traversal primitives, such as + * hlist_for_each_entry_rcu(), used to prevent memory-consistency + * problems on Alpha CPUs. + */ +static inline void hlist_add_before_rcu(struct hlist_node *n, + struct hlist_node *next) +{ + n->pprev = next->pprev; + n->next = next; +// smp_wmb(); + next->pprev = &n->next; + *(n->pprev) = n; +} + +/** + * hlist_add_after_rcu - adds the specified element to the specified hlist + * after the specified node while permitting racing traversals. + * @prev: the existing element to add the new element after. + * @n: the new element to add to the hash list. + * + * The caller must take whatever precautions are necessary + * (such as holding appropriate locks) to avoid racing + * with another list-mutation primitive, such as hlist_add_head_rcu() + * or hlist_del_rcu(), running on this same list. + * However, it is perfectly legal to run concurrently with + * the _rcu list-traversal primitives, such as + * hlist_for_each_entry_rcu(), used to prevent memory-consistency + * problems on Alpha CPUs. + */ +static inline void hlist_add_after_rcu(struct hlist_node *prev, + struct hlist_node *n) +{ + n->next = prev->next; + n->pprev = &prev->next; +// smp_wmb(); + prev->next = n; + if (n->next) + n->next->pprev = &n->next; +} + +#define hlist_entry(ptr, type, member) container_of(ptr,type,member) + +#define hlist_for_each(pos, head) \ + for (pos = (head)->first; pos && ({ prefetch(pos->next); 1; }); \ + pos = pos->next) + +#define hlist_for_each_safe(pos, n, head) \ + for (pos = (head)->first; pos && ({ n = pos->next; 1; }); \ + pos = n) + +/** + * hlist_for_each_entry - iterate over list of given type + * @tpos: the type * to use as a loop counter. + * @pos: the &struct hlist_node to use as a loop counter. + * @head: the head for your list. + * @member: the name of the hlist_node within the struct. + */ +#define hlist_for_each_entry(tpos, pos, head, member) \ + for (pos = (head)->first; \ + pos && ({ prefetch(pos->next); 1;}) && \ + ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \ + pos = pos->next) + +/** + * hlist_for_each_entry_continue - iterate over a hlist continuing after existing point + * @tpos: the type * to use as a loop counter. + * @pos: the &struct hlist_node to use as a loop counter. + * @member: the name of the hlist_node within the struct. + */ +#define hlist_for_each_entry_continue(tpos, pos, member) \ + for (pos = (pos)->next; \ + pos && ({ prefetch(pos->next); 1;}) && \ + ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \ + pos = pos->next) + +/** + * hlist_for_each_entry_from - iterate over a hlist continuing from existing point + * @tpos: the type * to use as a loop counter. + * @pos: the &struct hlist_node to use as a loop counter. + * @member: the name of the hlist_node within the struct. + */ +#define hlist_for_each_entry_from(tpos, pos, member) \ + for (; pos && ({ prefetch(pos->next); 1;}) && \ + ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \ + pos = pos->next) + +/** + * hlist_for_each_entry_safe - iterate over list of given type safe against removal of list entry + * @tpos: the type * to use as a loop counter. + * @pos: the &struct hlist_node to use as a loop counter. + * @n: another &struct hlist_node to use as temporary storage + * @head: the head for your list. + * @member: the name of the hlist_node within the struct. + */ +#define hlist_for_each_entry_safe(tpos, pos, n, head, member) \ + for (pos = (head)->first; \ + pos && ({ n = pos->next; 1; }) && \ + ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \ + pos = n) + +/** + * hlist_for_each_entry_rcu - iterate over rcu list of given type + * @tpos: the type * to use as a loop counter. + * @pos: the &struct hlist_node to use as a loop counter. + * @head: the head for your list. + * @member: the name of the hlist_node within the struct. + * + * This list-traversal primitive may safely run concurrently with + * the _rcu list-mutation primitives such as hlist_add_head_rcu() + * as long as the traversal is guarded by rcu_read_lock(). + */ +#define hlist_for_each_entry_rcu(tpos, pos, head, member) \ + for (pos = (head)->first; \ + rcu_dereference(pos) && ({ prefetch(pos->next); 1;}) && \ + ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \ + pos = pos->next) + +#endif + +/** + * __list_sort - sort the list using given comparator with merge-sort algorithm + * @head: is a head of the list to be sorted + * @member_offset: is machine offset inside the list entry structure to the + * field of type struct list_head which links that entry with + * the list. + */ +extern void __list_sort(struct list_head * head, + int member_offset, + int (*comparator)(void*,void*)); + +/** + * list_sort - wrapper for __list_sort + * @head: is a head of the list to be sorted + * @type: is the type of list entry + * @member: is the name of the field inside entry that links that entry with + * other entries in the list. + * @comaprator: function comparing two entries, should return value lesser + * than 0 when the first argument is lesser than the second one. + */ +#define list_sort(head,type,member,comparator) \ + ({ \ + __list_sort(head, \ + offsetof(type, member), \ + (int (*)(void*, void*)) comparator); \ + }) + +void test_list_sort(void); diff --git a/drivers/alsa_midi/port.c b/drivers/alsa_midi/port.c new file mode 100644 index 0000000..fac5eee --- /dev/null +++ b/drivers/alsa_midi/port.c @@ -0,0 +1,217 @@ +/* + * ALSA SEQ < - > JACK MIDI bridge + * + * Copyright (c) 2006,2007 Dmitry S. Baikov + * Copyright (c) 2007,2008,2009 Nedko Arnaudov + * Copyright (c) 2009,2010 Paul Davis + * + * 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; version 2 of the License. + * + * 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 +#include +#include +#include +#include +#include + +#include "list.h" +#include "a2j.h" +#include "port_hash.h" +#include "port.h" + +/* This should be part of JACK API */ +#define JACK_IS_VALID_PORT_NAME_CHAR(c) \ + (isalnum(c) || \ + (c) == '/' || \ + (c) == '_' || \ + (c) == '(' || \ + (c) == ')' || \ + (c) == '-' || \ + (c) == '[' || \ + (c) == ']') + +static +int +a2j_alsa_connect_from (alsa_midi_driver_t * driver, int client, int port) +{ + snd_seq_port_subscribe_t* sub; + snd_seq_addr_t seq_addr; + int err; + + snd_seq_port_subscribe_alloca (&sub); + seq_addr.client = client; + seq_addr.port = port; + snd_seq_port_subscribe_set_sender (sub, &seq_addr); + seq_addr.client = driver->client_id; + seq_addr.port = driver->port_id; + snd_seq_port_subscribe_set_dest (sub, &seq_addr); + + snd_seq_port_subscribe_set_time_update (sub, 1); + snd_seq_port_subscribe_set_queue (sub, driver->queue); + snd_seq_port_subscribe_set_time_real (sub, 1); + + if ((err = snd_seq_subscribe_port (driver->seq, sub))) { + a2j_error ("can't subscribe to %d:%d - %s", client, port, snd_strerror(err)); + } + + return err; +} + +void +a2j_port_setdead (a2j_port_hash_t hash, snd_seq_addr_t addr) +{ + struct a2j_port *port = a2j_port_get(hash, addr); + + if (port) { + port->is_dead = true; // see jack_process_internal + } else { + a2j_debug("port_setdead: not found (%d:%d)", addr.client, addr.port); + } +} + +void +a2j_port_free (struct a2j_port * port) +{ + // snd_seq_disconnect_from (driver->seq, driver->port_id, port->remote.client, port->remote.port); + // snd_seq_disconnect_to (driver->seq, driver->port_id, port->remote.client, port->remote.port); + + if (port->inbound_events) { + jack_ringbuffer_free (port->inbound_events); + } + + if (port->jack_port != JACK_INVALID_PORT && !port->driver_ptr->finishing) { + jack_port_unregister (port->driver_ptr->jack_client, port->jack_port); + } + + free (port); +} + +void +a2j_port_fill_name (struct a2j_port * port_ptr, int dir, snd_seq_client_info_t * client_info_ptr, + const snd_seq_port_info_t * port_info_ptr, bool make_unique) +{ + char *c; + + if (make_unique) { + snprintf (port_ptr->name, + sizeof(port_ptr->name), + "%s [%d] %s %s", + snd_seq_client_info_get_name(client_info_ptr), + snd_seq_client_info_get_client(client_info_ptr), + snd_seq_port_info_get_name(port_info_ptr), + (dir == A2J_PORT_CAPTURE ? "in" : "out")); + } else { + snprintf (port_ptr->name, + sizeof(port_ptr->name), + "%s %s %s", + snd_seq_client_info_get_name(client_info_ptr), + snd_seq_port_info_get_name(port_info_ptr), + (dir == A2J_PORT_CAPTURE ? "in" : "out")); + } + + // replace all offending characters with ' ' + for (c = port_ptr->name; *c; ++c) { + if (!JACK_IS_VALID_PORT_NAME_CHAR(*c)) { + *c = ' '; + } + } +} + +struct a2j_port * +a2j_port_create (alsa_midi_driver_t * driver, int dir, snd_seq_addr_t addr, const snd_seq_port_info_t * info) +{ + struct a2j_port *port; + int err; + int client; + snd_seq_client_info_t * client_info_ptr; + int jack_caps; + struct a2j_stream * stream_ptr; + + stream_ptr = &driver->stream[dir]; + + if ((err = snd_seq_client_info_malloc (&client_info_ptr)) != 0) { + a2j_error("Failed to allocate client info"); + goto fail; + } + + client = snd_seq_port_info_get_client (info); + + err = snd_seq_get_any_client_info (driver->seq, client, client_info_ptr); + if (err != 0) { + a2j_error("Failed to get client info"); + goto fail_free_client_info; + } + + a2j_debug ("client name: '%s'", snd_seq_client_info_get_name(client_info_ptr)); + a2j_debug ("port name: '%s'", snd_seq_port_info_get_name(info)); + + port = calloc (1, sizeof(struct a2j_port)); + if (!port) { + goto fail_free_client_info; + } + + port->driver_ptr = driver; + port->jack_port = JACK_INVALID_PORT; + port->remote = addr; + + a2j_port_fill_name (port, dir, client_info_ptr, info, false); + + /* Add port to list early, before registering to JACK, so map functionality is guaranteed to work during port registration */ + list_add_tail (&port->siblings, &stream_ptr->list); + + if (dir == A2J_PORT_CAPTURE) { + jack_caps = JackPortIsOutput; + } else { + jack_caps = JackPortIsInput; + } + + /* mark anything that looks like a hardware port as physical&terminal */ + if (snd_seq_port_info_get_type (info) & (SND_SEQ_PORT_TYPE_HARDWARE|SND_SEQ_PORT_TYPE_PORT|SND_SEQ_PORT_TYPE_SPECIFIC)) { + jack_caps |= JackPortIsPhysical|JackPortIsTerminal; + } + + port->jack_port = jack_port_register (driver->jack_client, port->name, JACK_DEFAULT_MIDI_TYPE, jack_caps, 0); + if (port->jack_port == JACK_INVALID_PORT) { + a2j_error("jack_port_register() failed for '%s'", port->name); + goto fail_free_port; + } + + if (dir == A2J_PORT_CAPTURE) { + err = a2j_alsa_connect_from (driver, port->remote.client, port->remote.port); + } else { + err = snd_seq_connect_to (driver->seq, driver->port_id, port->remote.client, port->remote.port); + } + + if (err) { + a2j_debug("port skipped: %s", port->name); + goto fail_free_port; + } + + port->inbound_events = jack_ringbuffer_create(MAX_EVENT_SIZE*16); + + a2j_debug("port created: %s", port->name); + return port; + + fail_free_port: + list_del (&port->siblings); + + a2j_port_free (port); + + fail_free_client_info: + snd_seq_client_info_free (client_info_ptr); + + fail: + return NULL; +} diff --git a/drivers/alsa_midi/port.h b/drivers/alsa_midi/port.h new file mode 100644 index 0000000..2c8daf4 --- /dev/null +++ b/drivers/alsa_midi/port.h @@ -0,0 +1,29 @@ +/* -*- Mode: C ; c-basic-offset: 2 -*- */ +/* + * ALSA SEQ < - > JACK MIDI bridge + * + * Copyright (c) 2006,2007 Dmitry S. Baikov + * Copyright (c) 2007,2008,2009 Nedko Arnaudov + * + * 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; version 2 of the License. + * + * 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 + */ + +#ifndef PORT_H__757ADD0F_5E53_41F7_8B7F_8119C5E8A9F1__INCLUDED +#define PORT_H__757ADD0F_5E53_41F7_8B7F_8119C5E8A9F1__INCLUDED + +struct a2j_port* a2j_port_create (alsa_midi_driver_t* driver, int dir, snd_seq_addr_t addr, const snd_seq_port_info_t * info); +void a2j_port_setdead (a2j_port_hash_t hash, snd_seq_addr_t addr); +void a2j_port_free (struct a2j_port * port); + +#endif /* #ifndef PORT_H__757ADD0F_5E53_41F7_8B7F_8119C5E8A9F1__INCLUDED */ diff --git a/drivers/alsa_midi/port_hash.c b/drivers/alsa_midi/port_hash.c new file mode 100644 index 0000000..cc9c5c6 --- /dev/null +++ b/drivers/alsa_midi/port_hash.c @@ -0,0 +1,63 @@ +/* -*- Mode: C ; c-basic-offset: 2 -*- */ +/* + * ALSA SEQ < - > JACK MIDI bridge + * + * Copyright (c) 2006,2007 Dmitry S. Baikov + * Copyright (c) 2007,2008,2009 Nedko Arnaudov + * + * 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; version 2 of the License. + * + * 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 +#include +#include +#include +#include + +#include "list.h" +#include "a2j.h" +#include "port_hash.h" + +static inline +int +a2j_port_hash( + snd_seq_addr_t addr) +{ + return (addr.client + addr.port) % PORT_HASH_SIZE; +} + +struct a2j_port * +a2j_port_get( + a2j_port_hash_t hash, + snd_seq_addr_t addr) +{ + struct a2j_port **pport = &hash[a2j_port_hash(addr)]; + while (*pport) { + struct a2j_port *port = *pport; + if (port->remote.client == addr.client && port->remote.port == addr.port) + return port; + pport = &port->next; + } + return NULL; +} + +void +a2j_port_insert( + a2j_port_hash_t hash, + struct a2j_port * port) +{ + struct a2j_port **pport = &hash[a2j_port_hash(port->remote)]; + port->next = *pport; + *pport = port; +} diff --git a/drivers/alsa_midi/port_hash.h b/drivers/alsa_midi/port_hash.h new file mode 100644 index 0000000..ec21f11 --- /dev/null +++ b/drivers/alsa_midi/port_hash.h @@ -0,0 +1,35 @@ +/* -*- Mode: C ; c-basic-offset: 2 -*- */ +/* + * ALSA SEQ < - > JACK MIDI bridge + * + * Copyright (c) 2006,2007 Dmitry S. Baikov + * Copyright (c) 2007,2008,2009 Nedko Arnaudov + * + * 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; version 2 of the License. + * + * 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 + */ + +#ifndef PORT_HASH_H__A44CBCD6_E075_49CB_8F73_DF9772511D55__INCLUDED +#define PORT_HASH_H__A44CBCD6_E075_49CB_8F73_DF9772511D55__INCLUDED + +void +a2j_port_insert( + a2j_port_hash_t hash, + struct a2j_port * port); + +struct a2j_port * +a2j_port_get( + a2j_port_hash_t hash, + snd_seq_addr_t addr); + +#endif /* #ifndef PORT_HASH_H__A44CBCD6_E075_49CB_8F73_DF9772511D55__INCLUDED */ diff --git a/drivers/alsa_midi/port_thread.c b/drivers/alsa_midi/port_thread.c new file mode 100644 index 0000000..39b68d7 --- /dev/null +++ b/drivers/alsa_midi/port_thread.c @@ -0,0 +1,235 @@ +/* -*- Mode: C ; c-basic-offset: 2 -*- */ +/* + * ALSA SEQ < - > JACK MIDI bridge + * + * Copyright (c) 2006,2007 Dmitry S. Baikov + * Copyright (c) 2007,2008,2009 Nedko Arnaudov + * + * 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; version 2 of the License. + * + * 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 +#include +#include +#include +#include + +#include "list.h" +#include "a2j.h" +#include "port.h" +#include "port_hash.h" +#include "port_thread.h" + +struct a2j_port * +a2j_find_port_by_addr( + struct a2j_stream * stream_ptr, + snd_seq_addr_t addr) +{ + struct list_head * node_ptr; + struct a2j_port * port_ptr; + + list_for_each(node_ptr, &stream_ptr->list) + { + port_ptr = list_entry(node_ptr, struct a2j_port, siblings); + if (port_ptr->remote.client == addr.client && port_ptr->remote.port == addr.port) + { + return port_ptr; + } + } + + return NULL; +} + +struct a2j_port * +a2j_find_port_by_jack_port_name( + struct a2j_stream * stream_ptr, + const char * jack_port) +{ + struct list_head * node_ptr; + struct a2j_port * port_ptr; + + list_for_each(node_ptr, &stream_ptr->list) + { + port_ptr = list_entry(node_ptr, struct a2j_port, siblings); + if (strcmp(port_ptr->name, jack_port) == 0) + { + return port_ptr; + } + } + + return NULL; +} + +/* + * ==================== Port add/del handling thread ============================== + */ + +static +void +a2j_update_port_type (alsa_midi_driver_t * driver, int dir, snd_seq_addr_t addr, int caps, const snd_seq_port_info_t * info) +{ + struct a2j_stream * stream_ptr; + int alsa_mask; + struct a2j_port * port_ptr; + + a2j_debug("update_port_type(%d:%d)", addr.client, addr.port); + + stream_ptr = &driver->stream[dir]; + port_ptr = a2j_find_port_by_addr(stream_ptr, addr); + + if (dir == A2J_PORT_CAPTURE) { + alsa_mask = SND_SEQ_PORT_CAP_SUBS_READ; + } else { + alsa_mask = SND_SEQ_PORT_CAP_SUBS_WRITE; + } + + if (port_ptr != NULL && (caps & alsa_mask) != alsa_mask) { + a2j_debug("setdead: %s", port_ptr->name); + port_ptr->is_dead = true; + } + + if (port_ptr == NULL && (caps & alsa_mask) == alsa_mask) { + if(jack_ringbuffer_write_space(stream_ptr->new_ports) >= sizeof(port_ptr)) { + port_ptr = a2j_port_create (driver, dir, addr, info); + if (port_ptr != NULL) { + jack_ringbuffer_write(stream_ptr->new_ports, (char *)&port_ptr, sizeof(port_ptr)); + } + } else { + a2j_error( "dropping new port event... increase MAX_PORTS" ); + } + } +} + +void +a2j_update_port (alsa_midi_driver_t * driver, snd_seq_addr_t addr, const snd_seq_port_info_t * info) +{ + unsigned int port_caps = snd_seq_port_info_get_capability(info); + unsigned int port_type = snd_seq_port_info_get_type(info); + + a2j_debug("port %u:%u", addr.client, addr.port); + a2j_debug("port type: 0x%08X", port_type); + a2j_debug("port caps: 0x%08X", port_caps); + + if (port_type & SND_SEQ_PORT_TYPE_SPECIFIC) { + a2j_debug("SPECIFIC"); + } + + if (port_type & SND_SEQ_PORT_TYPE_MIDI_GENERIC) { + a2j_debug("MIDI_GENERIC"); + } + + if (port_type & SND_SEQ_PORT_TYPE_MIDI_GM) { + a2j_debug("MIDI_GM"); + } + + if (port_type & SND_SEQ_PORT_TYPE_MIDI_GS) { + a2j_debug("MIDI_GS"); + } + + if (port_type & SND_SEQ_PORT_TYPE_MIDI_XG) { + a2j_debug("MIDI_XG"); + } + + if (port_type & SND_SEQ_PORT_TYPE_MIDI_MT32) { + a2j_debug("MIDI_MT32"); + } + + if (port_type & SND_SEQ_PORT_TYPE_MIDI_GM2) { + a2j_debug("MIDI_GM2"); + } + + if (port_type & SND_SEQ_PORT_TYPE_SYNTH) { + a2j_debug("SYNTH"); + } + + if (port_type & SND_SEQ_PORT_TYPE_DIRECT_SAMPLE) { + a2j_debug("DIRECT_SAMPLE"); + } + + if (port_type & SND_SEQ_PORT_TYPE_SAMPLE) { + a2j_debug("SAMPLE"); + } + + if (port_type & SND_SEQ_PORT_TYPE_HARDWARE) { + a2j_debug("HARDWARE"); + } + + if (port_type & SND_SEQ_PORT_TYPE_SOFTWARE) { + a2j_debug("SOFTWARE"); + } + + if (port_type & SND_SEQ_PORT_TYPE_SYNTHESIZER) { + a2j_debug("SYNTHESIZER"); + } + + if (port_type & SND_SEQ_PORT_TYPE_PORT) { + a2j_debug("PORT"); + } + + if (port_type & SND_SEQ_PORT_TYPE_APPLICATION) { + a2j_debug("APPLICATION"); + } + + if (port_type == 0) { + a2j_debug("Ignoring port of type 0"); + return; + } + + if (port_caps & SND_SEQ_PORT_CAP_NO_EXPORT) { + a2j_debug("Ignoring no-export port"); + return; + } + + a2j_update_port_type (driver, A2J_PORT_CAPTURE, addr, port_caps, info); + a2j_update_port_type (driver, A2J_PORT_PLAYBACK, addr, port_caps, info); +} + +void +a2j_free_ports (jack_ringbuffer_t * ports) +{ + struct a2j_port *port; + int sz; + + while ((sz = jack_ringbuffer_read (ports, (char*)&port, sizeof(port)))) { + assert (sz == sizeof(port)); + a2j_debug("port deleted: %s", port->name); + list_del (&port->siblings); + a2j_port_free(port); + } +} + +void +a2j_update_ports (alsa_midi_driver_t * driver) +{ + snd_seq_addr_t addr; + int size; + + while ((size = jack_ringbuffer_read(driver->port_add, (char *)&addr, sizeof(addr))) != 0) { + + snd_seq_port_info_t * info; + int err; + + snd_seq_port_info_alloca(&info); + + assert (size == sizeof(addr)); + assert (addr.client != driver->client_id); + + if ((err = snd_seq_get_any_port_info(driver->seq, addr.client, addr.port, info)) >= 0) { + a2j_update_port(driver, addr, info); + } else { + a2j_port_setdead(driver->stream[A2J_PORT_CAPTURE].port_hash, addr); + a2j_port_setdead(driver->stream[A2J_PORT_PLAYBACK].port_hash, addr); + } + } +} diff --git a/drivers/alsa_midi/port_thread.h b/drivers/alsa_midi/port_thread.h new file mode 100644 index 0000000..28d68a9 --- /dev/null +++ b/drivers/alsa_midi/port_thread.h @@ -0,0 +1,31 @@ +/* -*- Mode: C ; c-basic-offset: 2 -*- */ +/* + * ALSA SEQ < - > JACK MIDI bridge + * + * Copyright (c) 2006,2007 Dmitry S. Baikov + * Copyright (c) 2007,2008,2009 Nedko Arnaudov + * + * 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; version 2 of the License. + * + * 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 + */ + +#ifndef PORT_THREAD_H__1C6B5065_5556_4AC6_AA9F_44C32A9648C6__INCLUDED +#define PORT_THREAD_H__1C6B5065_5556_4AC6_AA9F_44C32A9648C6__INCLUDED + +void a2j_update_port (alsa_midi_driver_t* driver, snd_seq_addr_t addr, const snd_seq_port_info_t* info); +void a2j_update_ports (alsa_midi_driver_t* driver); +void a2j_free_ports (jack_ringbuffer_t * ports); +struct a2j_port * a2j_find_port_by_addr (struct a2j_stream * stream_ptr, snd_seq_addr_t addr); +struct a2j_port * a2j_find_port_by_jack_port_name (struct a2j_stream * stream_ptr, const char * jack_port); + +#endif /* #ifndef PORT_THREAD_H__1C6B5065_5556_4AC6_AA9F_44C32A9648C6__INCLUDED */ diff --git a/drivers/am/Makefile.am b/drivers/am/Makefile.am new file mode 100644 index 0000000..5c28128 --- /dev/null +++ b/drivers/am/Makefile.am @@ -0,0 +1,16 @@ +MAINTAINERCLEANFILES=Makefile.in + +AM_CFLAGS = $(JACK_CFLAGS) + +plugindir = $(ADDON_DIR) + +plugin_LTLIBRARIES = jack_alsa_midi.la + +jack_alsa_midi_la_LDFLAGS = -module -avoid-version +jack_alsa_midi_la_SOURCES = alsa_rawmidi.c alsa_seqmidi.c alsa_midi_driver.c + +noinst_HEADERS = alsa_midi.h \ + midi_pack.h \ + midi_unpack.h + +jack_alsa_midi_la_LIBADD = $(ALSA_LIBS) diff --git a/drivers/am/alsa_midi.h b/drivers/am/alsa_midi.h new file mode 100644 index 0000000..b49228a --- /dev/null +++ b/drivers/am/alsa_midi.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2006 Dmitry S. Baikov + * + * 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 + */ + +#ifndef __jack_alsa_midi_h__ +#define __jack_alsa_midi_h__ + +#include +#include "driver.h" + +typedef struct alsa_midi_t alsa_midi_t; +struct alsa_midi_t { + void (*destroy)(alsa_midi_t *amidi); + int (*attach)(alsa_midi_t *amidi); + int (*detach)(alsa_midi_t *amidi); + int (*start)(alsa_midi_t *amidi); + int (*stop)(alsa_midi_t *amidi); + void (*read)(alsa_midi_t *amidi, jack_nframes_t nframes); + void (*write)(alsa_midi_t *amidi, jack_nframes_t nframes); +}; + +alsa_midi_t* alsa_rawmidi_new(jack_client_t *jack); +alsa_midi_t* alsa_seqmidi_new(jack_client_t *jack, const char* alsa_name); + +typedef struct _alsa_midi_driver { + + JACK_DRIVER_DECL; + + alsa_midi_t *midi; + jack_client_t *client; + +} alsa_midi_driver_t; + +#endif /* __jack_alsa_midi_h__ */ diff --git a/drivers/am/alsa_midi_driver.c b/drivers/am/alsa_midi_driver.c new file mode 100644 index 0000000..b27f03c --- /dev/null +++ b/drivers/am/alsa_midi_driver.c @@ -0,0 +1,122 @@ + +#include "alsa_midi.h" +#include + +static int +alsa_midi_driver_attach( alsa_midi_driver_t *driver, jack_engine_t *engine ) +{ + return driver->midi->attach(driver->midi); +} + +static int +alsa_midi_driver_detach( alsa_midi_driver_t *driver, jack_engine_t *engine ) +{ + return driver->midi->detach(driver->midi); +} + +static int +alsa_midi_driver_read( alsa_midi_driver_t *driver, jack_nframes_t nframes ) +{ + driver->midi->read(driver->midi, nframes); + return 0; +} + +static int +alsa_midi_driver_write( alsa_midi_driver_t *driver, jack_nframes_t nframes ) +{ + driver->midi->write(driver->midi, nframes); + return 0; +} + +static int +alsa_midi_driver_start( alsa_midi_driver_t *driver ) +{ + return driver->midi->start(driver->midi); +} + +static int +alsa_midi_driver_stop( alsa_midi_driver_t *driver ) +{ + return driver->midi->stop(driver->midi); +} + +static void +alsa_midi_driver_delete( alsa_midi_driver_t *driver ) +{ + if (driver->midi) + (driver->midi->destroy)(driver->midi); + + free (driver); +} + +static jack_driver_t * +alsa_midi_driver_new (jack_client_t *client, const char *name) +{ + alsa_midi_driver_t *driver; + + jack_info ("creating alsa_midi driver ..."); + + driver = (alsa_midi_driver_t *) calloc (1, sizeof (alsa_midi_driver_t)); + + jack_driver_init ((jack_driver_t *) driver); + + driver->attach = (JackDriverAttachFunction) alsa_midi_driver_attach; + driver->detach = (JackDriverDetachFunction) alsa_midi_driver_detach; + driver->read = (JackDriverReadFunction) alsa_midi_driver_read; + driver->write = (JackDriverWriteFunction) alsa_midi_driver_write; + driver->start = (JackDriverStartFunction) alsa_midi_driver_start; + driver->stop = (JackDriverStartFunction) alsa_midi_driver_stop; + + + driver->midi = alsa_seqmidi_new(client, NULL); + driver->client = client; + + return (jack_driver_t *) driver; +} + +/* DRIVER "PLUGIN" INTERFACE */ + +const char driver_client_name[] = "alsa_midi"; + +const jack_driver_desc_t * +driver_get_descriptor () +{ + jack_driver_desc_t * desc; + jack_driver_param_desc_t * params; + //unsigned int i; + + desc = calloc (1, sizeof (jack_driver_desc_t)); + + strcpy (desc->name,"alsa_midi"); + desc->nparams = 0; + + params = calloc (desc->nparams, sizeof (jack_driver_param_desc_t)); + + desc->params = params; + + return desc; +} + +jack_driver_t * +driver_initialize (jack_client_t *client, const JSList * params) +{ + const JSList * node; + const jack_driver_param_t * param; + + for (node = params; node; node = jack_slist_next (node)) { + param = (const jack_driver_param_t *) node->data; + + switch (param->character) { + default: + break; + } + } + + return alsa_midi_driver_new (client, NULL); +} + +void +driver_finish (jack_driver_t *driver) +{ + alsa_midi_driver_delete ((alsa_midi_driver_t *) driver); +} diff --git a/drivers/am/alsa_rawmidi.c b/drivers/am/alsa_rawmidi.c new file mode 100644 index 0000000..9db97fb --- /dev/null +++ b/drivers/am/alsa_rawmidi.c @@ -0,0 +1,1204 @@ +/* + * ALSA RAWMIDI < - > JACK MIDI bridge + * + * Copyright (c) 2006,2007 Dmitry S. Baikov + * + * 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 + */ + +/* Required for clock_nanosleep(). Thanks, Nedko */ +#define _GNU_SOURCE + +#include "alsa_midi.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "midi_pack.h" +#include "midi_unpack.h" + + +#ifdef STANDALONE +#define MESSAGE(...) fprintf(stderr, __VA_ARGS__) +#else +#include "messagebuffer.h" +#endif + +#define info_log(...) MESSAGE(__VA_ARGS__) +#define error_log(...) MESSAGE(__VA_ARGS__) + +#ifdef JACK_MIDI_DEBUG +#define debug_log(...) MESSAGE(__VA_ARGS__) +#else +#define debug_log(...) +#endif + + +enum { + NANOSLEEP_RESOLUTION = 7000 +}; + +#define NFRAMES_INF INT_MAX + +enum { +#ifndef JACK_MIDI_DEBUG + MAX_PFDS = 64, + MAX_PORTS = MAX_PFDS-1, + MAX_EVENTS = 4096, + MAX_DATA = 64*1024, + MIDI_THREAD_PRIO = 80 +#else + MAX_PFDS = 6, + MAX_PORTS = MAX_PFDS-1, + MAX_EVENTS = 16, + MAX_DATA = 64, + MIDI_THREAD_PRIO = 80 +#endif +}; + +enum PortState { + PORT_DESTROYED, + PORT_CREATED, + PORT_ADDED_TO_JACK, + PORT_ADDED_TO_MIDI, + PORT_REMOVED_FROM_MIDI, + PORT_REMOVED_FROM_JACK, + PORT_ZOMBIFIED, +}; + +typedef struct { + int id[4]; //card, dev, dir, sub; +} alsa_id_t; + + +typedef struct { + jack_time_t time; + int size; + int overruns; +} event_head_t; + + +typedef struct midi_port_t midi_port_t; +struct midi_port_t { + midi_port_t *next; + + enum PortState state; + + alsa_id_t id; + char dev[16]; + char name[64]; + + jack_port_t *jack; + snd_rawmidi_t *rawmidi; + int npfds; + int is_ready; + + jack_ringbuffer_t *event_ring; + jack_ringbuffer_t *data_ring; + +}; + +typedef struct input_port_t { + midi_port_t base; + + // jack + midi_unpack_t unpack; + + // midi + int overruns; +} input_port_t; + +typedef struct output_port_t { + midi_port_t base; + + // jack + midi_pack_t packer; + + // midi + event_head_t next_event; + int todo; +} output_port_t; + +typedef struct alsa_rawmidi_t alsa_rawmidi_t; + +typedef struct { + alsa_rawmidi_t *midi; + midi_port_t *port; + void *buffer; + jack_time_t frame_time; + jack_nframes_t nframes; +} process_jack_t; + +typedef struct { + alsa_rawmidi_t *midi; + int mode; + midi_port_t *port; + struct pollfd *rpfds; + struct pollfd *wpfds; + int max_pfds; + jack_nframes_t cur_frames; + jack_time_t cur_time; + jack_time_t next_time; +} process_midi_t; + +typedef struct midi_stream_t { + alsa_rawmidi_t *owner; + int mode; + const char *name; + pthread_t thread; + int wake_pipe[2]; + + struct { + jack_ringbuffer_t *new_ports; + int nports; + midi_port_t *ports[MAX_PORTS]; + } jack, midi; + + size_t port_size; + int (*port_init)(alsa_rawmidi_t *midi, midi_port_t *port); + void (*port_close)(alsa_rawmidi_t *midi, midi_port_t *port); + void (*process_jack)(process_jack_t *j); + int (*process_midi)(process_midi_t *m); +} midi_stream_t; + + +struct alsa_rawmidi_t { + alsa_midi_t ops; + + jack_client_t *client; + int keep_walking; + + struct { + pthread_t thread; + midi_port_t *ports; + int wake_pipe[2]; + } scan; + + midi_stream_t in; + midi_stream_t out; +}; + +static int input_port_init(alsa_rawmidi_t *midi, midi_port_t *port); +static void input_port_close(alsa_rawmidi_t *midi, midi_port_t *port); + +static void do_jack_input(process_jack_t *j); +static int do_midi_input(process_midi_t *m); + +static int output_port_init(alsa_rawmidi_t *midi, midi_port_t *port); +static void output_port_close(alsa_rawmidi_t *midi, midi_port_t *port); + +static void do_jack_output(process_jack_t *j); +static int do_midi_output(process_midi_t *m); + + + +static +int stream_init(midi_stream_t *s, alsa_rawmidi_t *midi, const char *name) +{ + s->owner = midi; + s->name = name; + if (pipe(s->wake_pipe)==-1) { + s->wake_pipe[0] = -1; + error_log("pipe() in stream_init(%s) failed: %s", name, strerror(errno)); + return -errno; + } + s->jack.new_ports = jack_ringbuffer_create(sizeof(midi_port_t*)*MAX_PORTS); + s->midi.new_ports = jack_ringbuffer_create(sizeof(midi_port_t*)*MAX_PORTS); + if (!s->jack.new_ports || !s->midi.new_ports) + return -ENOMEM; + return 0; +} + +static +void stream_close(midi_stream_t *s) +{ + if (s->wake_pipe[0] != -1) { + close(s->wake_pipe[0]); + close(s->wake_pipe[1]); + } + if (s->jack.new_ports) + jack_ringbuffer_free(s->jack.new_ports); + if (s->midi.new_ports) + jack_ringbuffer_free(s->midi.new_ports); +} + +static void alsa_rawmidi_delete(alsa_midi_t *m); +static int alsa_rawmidi_attach(alsa_midi_t *m); +static int alsa_rawmidi_detach(alsa_midi_t *m); +static int alsa_rawmidi_start(alsa_midi_t *m); +static int alsa_rawmidi_stop(alsa_midi_t *m); +static void alsa_rawmidi_read(alsa_midi_t *m, jack_nframes_t nframes); +static void alsa_rawmidi_write(alsa_midi_t *m, jack_nframes_t nframes); + +alsa_midi_t* alsa_rawmidi_new(jack_client_t *jack) +{ + alsa_rawmidi_t *midi = calloc(1, sizeof(alsa_rawmidi_t)); + if (!midi) + goto fail_0; + midi->client = jack; + if (pipe(midi->scan.wake_pipe)==-1) { + error_log("pipe() in alsa_midi_new failed: %s", strerror(errno)); + goto fail_1; + } + + if (stream_init(&midi->in, midi, "in")) + goto fail_2; + midi->in.mode = POLLIN; + midi->in.port_size = sizeof(input_port_t); + midi->in.port_init = input_port_init; + midi->in.port_close = input_port_close; + midi->in.process_jack = do_jack_input; + midi->in.process_midi = do_midi_input; + + if (stream_init(&midi->out, midi, "out")) + goto fail_3; + midi->out.mode = POLLOUT; + midi->out.port_size = sizeof(output_port_t); + midi->out.port_init = output_port_init; + midi->out.port_close = output_port_close; + midi->out.process_jack = do_jack_output; + midi->out.process_midi = do_midi_output; + + midi->ops.destroy = alsa_rawmidi_delete; + midi->ops.attach = alsa_rawmidi_attach; + midi->ops.detach = alsa_rawmidi_detach; + midi->ops.start = alsa_rawmidi_start; + midi->ops.stop = alsa_rawmidi_stop; + midi->ops.read = alsa_rawmidi_read; + midi->ops.write = alsa_rawmidi_write; + + return &midi->ops; + fail_3: + stream_close(&midi->out); + fail_2: + stream_close(&midi->in); + close(midi->scan.wake_pipe[1]); + close(midi->scan.wake_pipe[0]); + fail_1: + free(midi); + fail_0: + return NULL; +} + +static +midi_port_t** scan_port_del(alsa_rawmidi_t *midi, midi_port_t **list); + +static +void alsa_rawmidi_delete(alsa_midi_t *m) +{ + alsa_rawmidi_t *midi = (alsa_rawmidi_t*)m; + + alsa_rawmidi_detach(m); + + stream_close(&midi->out); + stream_close(&midi->in); + close(midi->scan.wake_pipe[0]); + close(midi->scan.wake_pipe[1]); + + free(midi); +} + +static void* scan_thread(void *); +static void *midi_thread(void *arg); + +static +int alsa_rawmidi_attach(alsa_midi_t *m) +{ + return 0; +} + +static +int alsa_rawmidi_detach(alsa_midi_t *m) +{ + alsa_rawmidi_t *midi = (alsa_rawmidi_t*)m; + midi_port_t **list; + + alsa_rawmidi_stop(m); + + list = &midi->scan.ports; + while (*list) { + (*list)->state = PORT_REMOVED_FROM_JACK; + list = scan_port_del(midi, list); + } + return 0; +} + +static +int alsa_rawmidi_start(alsa_midi_t *m) +{ + alsa_rawmidi_t *midi = (alsa_rawmidi_t*)m; + int err; + char c = 'q'; + if (midi->keep_walking == 1) + return -EALREADY; + + midi->keep_walking = 1; + if ((err = jack_client_create_thread(midi->client, &midi->in.thread, MIDI_THREAD_PRIO, jack_is_realtime(midi->client), midi_thread, &midi->in))) { + midi->keep_walking = 0; + return err; + } + if ((err = jack_client_create_thread(midi->client, &midi->out.thread, MIDI_THREAD_PRIO, jack_is_realtime(midi->client), midi_thread, &midi->out))) { + midi->keep_walking = 0; + write(midi->in.wake_pipe[1], &c, 1); + pthread_join(midi->in.thread, NULL); + return err; + } + if ((err = jack_client_create_thread(midi->client, &midi->scan.thread, 0, 0, scan_thread, midi))) { + midi->keep_walking = 0; + write(midi->in.wake_pipe[1], &c, 1); + write(midi->out.wake_pipe[1], &c, 1); + pthread_join(midi->in.thread, NULL); + pthread_join(midi->out.thread, NULL); + return err; + } + return 0; +} + +static +int alsa_rawmidi_stop(alsa_midi_t *m) +{ + alsa_rawmidi_t *midi = (alsa_rawmidi_t*)m; + char c = 'q'; + if (midi->keep_walking == 0) + return -EALREADY; + midi->keep_walking = 0; + write(midi->in.wake_pipe[1], &c, 1); + write(midi->out.wake_pipe[1], &c, 1); + write(midi->scan.wake_pipe[1], &c, 1); + pthread_join(midi->in.thread, NULL); + pthread_join(midi->out.thread, NULL); + pthread_join(midi->scan.thread, NULL); + // ports are freed in alsa_midi_detach() + return 0; +} + +static void jack_process(midi_stream_t *str, jack_nframes_t nframes); + +static +void alsa_rawmidi_read(alsa_midi_t *m, jack_nframes_t nframes) +{ + alsa_rawmidi_t *midi = (alsa_rawmidi_t*)m; + jack_process(&midi->in, nframes); +} + +static +void alsa_rawmidi_write(alsa_midi_t *m, jack_nframes_t nframes) +{ + alsa_rawmidi_t *midi = (alsa_rawmidi_t*)m; + jack_process(&midi->out, nframes); +} + +/* + * ----------------------------------------------------------------------------- + */ +static inline +int can_pass(size_t sz, jack_ringbuffer_t *in, jack_ringbuffer_t *out) +{ + return jack_ringbuffer_read_space(in) >= sz && jack_ringbuffer_write_space(out) >= sz; +} + +static +void midi_port_init(const alsa_rawmidi_t *midi, midi_port_t *port, snd_rawmidi_info_t *info, const alsa_id_t *id) +{ + const char *name; + char *c; + + port->id = *id; + snprintf(port->dev, sizeof(port->dev), "hw:%d,%d,%d", id->id[0], id->id[1], id->id[3]); + name = snd_rawmidi_info_get_subdevice_name(info); + if (!strlen(name)) + name = snd_rawmidi_info_get_name(info); + snprintf(port->name, sizeof(port->name), "%s %s %s", port->id.id[2] ? "out":"in", port->dev, name); + + // replace all offending characters with '-' + for (c=port->name; *c; ++c) + if (!isalnum(*c)) + *c = '-'; + + port->state = PORT_CREATED; +} + +static +inline int midi_port_open_jack(const alsa_rawmidi_t *midi, midi_port_t *port, int type, const char *name) +{ + port->jack = jack_port_register(midi->client, name, JACK_DEFAULT_MIDI_TYPE, + type | JackPortIsPhysical|JackPortIsTerminal, 0); + return port->jack == NULL; +} + +static +int midi_port_open(const alsa_rawmidi_t *midi, midi_port_t *port) +{ + int err; + int type; + char name[64]; + snd_rawmidi_t **in = NULL; + snd_rawmidi_t **out = NULL; + + if (port->id.id[2] == 0) { + in = &port->rawmidi; + type = JackPortIsOutput; + } else { + out = &port->rawmidi; + type = JackPortIsInput; + } + + if ((err = snd_rawmidi_open(in, out, port->dev, SND_RAWMIDI_NONBLOCK))<0) + return err; + + /* Some devices (emu10k1) have subdevs with the same name, + * and we need to generate unique port name for jack */ + snprintf(name, sizeof(name), "%s", port->name); + if (midi_port_open_jack(midi, port, type, name)) { + int num; + num = port->id.id[3] ? port->id.id[3] : port->id.id[1]; + snprintf(name, sizeof(name), "%s %d", port->name, num); + if (midi_port_open_jack(midi, port, type, name)) + return 2; + } + if ((port->event_ring = jack_ringbuffer_create(MAX_EVENTS*sizeof(event_head_t)))==NULL) + return 3; + if ((port->data_ring = jack_ringbuffer_create(MAX_DATA))==NULL) + return 4; + + return 0; +} + +static +void midi_port_close(const alsa_rawmidi_t *midi, midi_port_t *port) +{ + if (port->data_ring) { + jack_ringbuffer_free(port->data_ring); + port->data_ring = NULL; + } + if (port->event_ring) { + jack_ringbuffer_free(port->event_ring); + port->event_ring = NULL; + } + if (port->jack) { + jack_port_unregister(midi->client, port->jack); + port->jack = NULL; + } + if (port->rawmidi) { + snd_rawmidi_close(port->rawmidi); + port->rawmidi = NULL; + } +} + +/* + * ------------------------- Port scanning ------------------------------- + */ + +static +int alsa_id_before(const alsa_id_t *p1, const alsa_id_t *p2) +{ + int i; + for (i=0; i<4; ++i) { + if (p1->id[i] < p2->id[i]) + return 1; + else if (p1->id[i] > p2->id[i]) + return 0; + } + return 0; +} + +static +void alsa_get_id(alsa_id_t *id, snd_rawmidi_info_t *info) +{ + id->id[0] = snd_rawmidi_info_get_card(info); + id->id[1] = snd_rawmidi_info_get_device(info); + id->id[2] = snd_rawmidi_info_get_stream(info) == SND_RAWMIDI_STREAM_OUTPUT ? 1 : 0; + id->id[3] = snd_rawmidi_info_get_subdevice(info); +} + +static inline +void alsa_error(const char *func, int err) +{ + error_log("%s() failed", snd_strerror(err)); +} + +typedef struct { + alsa_rawmidi_t *midi; + midi_port_t **iterator; + snd_ctl_t *ctl; + snd_rawmidi_info_t *info; +} scan_t; + +static midi_port_t** scan_port_del(alsa_rawmidi_t *midi, midi_port_t **list); + +static +void scan_cleanup(alsa_rawmidi_t *midi) +{ + midi_port_t **list = &midi->scan.ports; + while (*list) + list = scan_port_del(midi, list); +} + +static void scan_card(scan_t *scan); +static midi_port_t** scan_port_open(alsa_rawmidi_t *midi, midi_port_t **list); + +void scan_cycle(alsa_rawmidi_t *midi) +{ + int card = -1, err; + scan_t scan; + midi_port_t **ports; + + //debug_log("scan: cleanup"); + scan_cleanup(midi); + + scan.midi = midi; + scan.iterator = &midi->scan.ports; + snd_rawmidi_info_alloca(&scan.info); + + //debug_log("scan: rescan"); + while ((err = snd_card_next(&card))>=0 && card>=0) { + char name[32]; + snprintf(name, sizeof(name), "hw:%d", card); + if ((err = snd_ctl_open(&scan.ctl, name, SND_CTL_NONBLOCK))>=0) { + scan_card(&scan); + snd_ctl_close(scan.ctl); + } else + alsa_error("scan: snd_ctl_open", err); + } + + // delayed open to workaround alsa<1.0.14 bug (can't open more than 1 subdevice if ctl is opened). + ports = &midi->scan.ports; + while (*ports) { + midi_port_t *port = *ports; + if (port->state == PORT_CREATED) + ports = scan_port_open(midi, ports); + else + ports = &port->next; + } +} + +static void scan_device(scan_t *scan); + +static +void scan_card(scan_t *scan) +{ + int device = -1; + int err; + + while ((err = snd_ctl_rawmidi_next_device(scan->ctl, &device))>=0 && device >=0) { + snd_rawmidi_info_set_device(scan->info, device); + + snd_rawmidi_info_set_stream(scan->info, SND_RAWMIDI_STREAM_INPUT); + snd_rawmidi_info_set_subdevice(scan->info, 0); + if ((err = snd_ctl_rawmidi_info(scan->ctl, scan->info))>=0) + scan_device(scan); + else if (err != -ENOENT) + alsa_error("scan: snd_ctl_rawmidi_info on device", err); + + snd_rawmidi_info_set_stream(scan->info, SND_RAWMIDI_STREAM_OUTPUT); + snd_rawmidi_info_set_subdevice(scan->info, 0); + if ((err = snd_ctl_rawmidi_info(scan->ctl, scan->info))>=0) + scan_device(scan); + else if (err != -ENOENT) + alsa_error("scan: snd_ctl_rawmidi_info on device", err); + } +} + +static void scan_port_update(scan_t *scan); + +static +void scan_device(scan_t *scan) +{ + int err; + int sub, nsubs = 0; + nsubs = snd_rawmidi_info_get_subdevices_count(scan->info); + + for (sub=0; subinfo, sub); + if ((err = snd_ctl_rawmidi_info(scan->ctl, scan->info)) < 0) { + alsa_error("scan: snd_ctl_rawmidi_info on subdevice", err); + continue; + } + scan_port_update(scan); + } +} + +static midi_port_t** scan_port_add(scan_t *scan, const alsa_id_t *id, midi_port_t **list); + +static +void scan_port_update(scan_t *scan) +{ + midi_port_t **list = scan->iterator; + alsa_id_t id; + alsa_get_id(&id, scan->info); + + while (*list && alsa_id_before(&(*list)->id, &id)) + list = scan_port_del(scan->midi, list); + + if (!*list || alsa_id_before(&id, &(*list)->id)) + list = scan_port_add(scan, &id, list); + else if (*list) + list = &(*list)->next; + + scan->iterator = list; +} + +static +midi_port_t** scan_port_add(scan_t *scan, const alsa_id_t *id, midi_port_t **list) +{ + midi_port_t *port; + midi_stream_t *str = id->id[2] ? &scan->midi->out : &scan->midi->in; + + port = calloc(1, str->port_size); + if (!port) + return list; + midi_port_init(scan->midi, port, scan->info, id); + + port->next = *list; + *list = port; + error_log("scan: added port %s %s", port->dev, port->name); + return &port->next; +} + +static +midi_port_t** scan_port_open(alsa_rawmidi_t *midi, midi_port_t **list) +{ + midi_stream_t *str; + midi_port_t *port; + + port = *list; + str = port->id.id[2] ? &midi->out : &midi->in; + + if (jack_ringbuffer_write_space(str->jack.new_ports) < sizeof(port)) + goto fail_0; + + if (midi_port_open(midi, port)) + goto fail_1; + if ((str->port_init)(midi, port)) + goto fail_2; + + port->state = PORT_ADDED_TO_JACK; + jack_ringbuffer_write(str->jack.new_ports, (char*) &port, sizeof(port)); + + error_log("scan: opened port %s %s", port->dev, port->name); + return &port->next; + + fail_2: + (str->port_close)(midi, port); + fail_1: + midi_port_close(midi, port); + port->state = PORT_ZOMBIFIED; + fail_0: + error_log("scan: can't open port %s %s", port->dev, port->name); + return &port->next; +} + +static +midi_port_t** scan_port_del(alsa_rawmidi_t *midi, midi_port_t **list) +{ + midi_port_t *port = *list; + if (port->state == PORT_REMOVED_FROM_JACK) { + error_log("scan: deleted port %s %s", port->dev, port->name); + *list = port->next; + if (port->id.id[2] ) + (midi->out.port_close)(midi, port); + else + (midi->in.port_close)(midi, port); + midi_port_close(midi, port); + free(port); + return list; + } else { + //debug_log("can't delete port %s, wrong state: %d", port->name, (int)port->state); + return &port->next; + } +} + +void* scan_thread(void *arg) +{ + alsa_rawmidi_t *midi = arg; + struct pollfd wakeup; + + wakeup.fd = midi->scan.wake_pipe[0]; + wakeup.events = POLLIN|POLLERR|POLLNVAL; + while (midi->keep_walking) { + int res; + //error_log("scanning...."); + scan_cycle(midi); + res = poll(&wakeup, 1, 2000); + if (res>0) { + char c; + read(wakeup.fd, &c, 1); + } else if (res<0 && errno != EINTR) + break; + } + return NULL; +} + + +/* + * ------------------------------- Input/Output ------------------------------ + */ + +static +void jack_add_ports(midi_stream_t *str) +{ + midi_port_t *port; + while (can_pass(sizeof(port), str->jack.new_ports, str->midi.new_ports) && str->jack.nports < MAX_PORTS) { + jack_ringbuffer_read(str->jack.new_ports, (char*)&port, sizeof(port)); + str->jack.ports[str->jack.nports++] = port; + port->state = PORT_ADDED_TO_MIDI; + jack_ringbuffer_write(str->midi.new_ports, (char*)&port, sizeof(port)); + } +} + +static +void jack_process(midi_stream_t *str, jack_nframes_t nframes) +{ + int r, w; + process_jack_t proc; + jack_nframes_t cur_frames; + + if (!str->owner->keep_walking) + return; + + proc.midi = str->owner; + proc.nframes = nframes; + proc.frame_time = jack_last_frame_time(proc.midi->client); + cur_frames = jack_frame_time(proc.midi->client); + if (proc.frame_time + proc.nframes < cur_frames) { + int periods_lost = (cur_frames - proc.frame_time) / proc.nframes; + proc.frame_time += periods_lost * proc.nframes; + debug_log("xrun detected: %d periods lost", periods_lost); + } + + // process existing ports + for (r=0, w=0; rjack.nports; ++r) { + midi_port_t *port = str->jack.ports[r]; + proc.port = port; + + assert (port->state > PORT_ADDED_TO_JACK && port->state < PORT_REMOVED_FROM_JACK); + + proc.buffer = jack_port_get_buffer(port->jack, nframes); + if (str->mode == POLLIN) + jack_midi_clear_buffer(proc.buffer); + + if (port->state == PORT_REMOVED_FROM_MIDI) { + port->state = PORT_REMOVED_FROM_JACK; // this signals to scan thread + continue; // this effectively removes port from the midi->in.jack.ports[] + } + + (str->process_jack)(&proc); + + if (r != w) + str->jack.ports[w] = port; + ++w; + } + if (str->jack.nports != w) + debug_log("jack_%s: nports %d -> %d", str->name, str->jack.nports, w); + str->jack.nports = w; + + jack_add_ports(str); // it makes no sense to add them earlier since they have no data yet + + // wake midi thread + write(str->wake_pipe[1], &r, 1); +} + +static +void *midi_thread(void *arg) +{ + midi_stream_t *str = arg; + alsa_rawmidi_t *midi = str->owner; + struct pollfd pfds[MAX_PFDS]; + int npfds; + jack_time_t wait_nsec = 1000*1000*1000; // 1 sec + process_midi_t proc; + + proc.midi = midi; + proc.mode = str->mode; + + pfds[0].fd = str->wake_pipe[0]; + pfds[0].events = POLLIN|POLLERR|POLLNVAL; + npfds = 1; + + //debug_log("midi_thread(%s): enter", str->name); + + while (midi->keep_walking) { + int poll_timeout; + int wait_nanosleep; + int r=1, w=1; // read,write pos in pfds + int rp=0, wp=0; // read, write pos in ports + + // sleep + //if (wait_nsec != 1000*1000*1000) { + // debug_log("midi_thread(%s): ", str->name); + // assert (wait_nsec == 1000*1000*1000); + //} + poll_timeout = wait_nsec / (1000*1000); + wait_nanosleep = wait_nsec % (1000*1000); + if (wait_nanosleep > NANOSLEEP_RESOLUTION) { + struct timespec ts; + ts.tv_sec = 0; + ts.tv_nsec = wait_nanosleep; + clock_nanosleep(CLOCK_MONOTONIC, 0, &ts, NULL); + } + int res = poll((struct pollfd*)&pfds, npfds, poll_timeout); + //debug_log("midi_thread(%s): poll exit: %d", str->name, res); + if (!midi->keep_walking) + break; + if (res < 0) { + if (errno == EINTR) + continue; + error_log("midi_thread(%s) poll failed: %s", str->name, strerror(errno)); + break; + } + + // check wakeup pipe + if (pfds[0].revents & ~POLLIN) + break; + if (pfds[0].revents & POLLIN) { + char c; + read(pfds[0].fd, &c, 1); + } + + // add new ports + while (jack_ringbuffer_read_space(str->midi.new_ports) >= sizeof(midi_port_t*) && str->midi.nports < MAX_PORTS) { + midi_port_t *port; + jack_ringbuffer_read(str->midi.new_ports, (char*)&port, sizeof(port)); + str->midi.ports[str->midi.nports++] = port; + debug_log("midi_thread(%s): added port %s", str->name, port->name); + } + +// if (res == 0) +// continue; + + // process ports + proc.cur_time = 0; //jack_frame_time(midi->client); + proc.next_time = NFRAMES_INF; + + for (rp = 0; rp < str->midi.nports; ++rp) { + midi_port_t *port = str->midi.ports[rp]; + proc.cur_time = jack_frame_time(midi->client); + proc.port = port; + proc.rpfds = &pfds[r]; + proc.wpfds = &pfds[w]; + proc.max_pfds = MAX_PFDS - w; + r += port->npfds; + if (!(str->process_midi)(&proc)) { + port->state = PORT_REMOVED_FROM_MIDI; // this signals to jack thread + continue; // this effectively removes port from array + } + w += port->npfds; + if (rp != wp) + str->midi.ports[wp] = port; + ++wp; + } + if (str->midi.nports != wp) + debug_log("midi_%s: nports %d -> %d", str->name, str->midi.nports, wp); + str->midi.nports = wp; + if (npfds != w) + debug_log("midi_%s: npfds %d -> %d", str->name, npfds, w); + npfds = w; + + /* + * Input : ports do not set proc.next_time. + * Output: port sets proc.next_time ONLY if it does not have queued data. + * So, zero timeout will not cause busy-looping. + */ + if (proc.next_time < proc.cur_time) { + debug_log("%s: late: next_time = %d, cur_time = %d", str->name, (int)proc.next_time, (int)proc.cur_time); + wait_nsec = 0; // we are late + } else if (proc.next_time != NFRAMES_INF) { + jack_time_t wait_frames = proc.next_time - proc.cur_time; + jack_nframes_t rate = jack_get_sample_rate(midi->client); + wait_nsec = (wait_frames * (1000*1000*1000)) / rate; + debug_log("midi_%s: timeout = %d", str->name, (int)wait_frames); + } else + wait_nsec = 1000*1000*1000; + //debug_log("midi_thread(%s): wait_nsec = %lld", str->name, wait_nsec); + } + return NULL; +} + +static +int midi_is_ready(process_midi_t *proc) +{ + midi_port_t *port = proc->port; + if (port->npfds) { + unsigned short revents = 0; + int res = snd_rawmidi_poll_descriptors_revents(port->rawmidi, proc->rpfds, port->npfds, &revents); + if (res) { + error_log("snd_rawmidi_poll_descriptors_revents failed on port %s with: %s", port->name, snd_strerror(res)); + return 0; + } + + if (revents & ~proc->mode) { + debug_log("midi: port %s failed", port->name); + return 0; + } + if (revents & proc->mode) { + port->is_ready = 1; + debug_log("midi: is_ready %s", port->name); + } + } + return 1; +} + +static +int midi_update_pfds(process_midi_t *proc) +{ + midi_port_t *port = proc->port; + if (port->npfds == 0) { + port->npfds = snd_rawmidi_poll_descriptors_count(port->rawmidi); + if (port->npfds > proc->max_pfds) { + debug_log("midi: not enough pfds for port %s", port->name); + return 0; + } + snd_rawmidi_poll_descriptors(port->rawmidi, proc->wpfds, port->npfds); + } else if (proc->rpfds != proc->wpfds) { + memmove(proc->wpfds, proc->rpfds, sizeof(struct pollfd) * port->npfds); + } + return 1; +} + +/* + * ------------------------------------ Input ------------------------------ + */ + +static +int input_port_init(alsa_rawmidi_t *midi, midi_port_t *port) +{ + input_port_t *in = (input_port_t*)port; + midi_unpack_init(&in->unpack); + return 0; +} + +static +void input_port_close(alsa_rawmidi_t *midi, midi_port_t *port) +{ +} + +/* + * Jack-level input. + */ + +static +void do_jack_input(process_jack_t *p) +{ + input_port_t *port = (input_port_t*) p->port; + event_head_t event; + while (jack_ringbuffer_read_space(port->base.event_ring) >= sizeof(event)) { + jack_ringbuffer_data_t vec[2]; + jack_nframes_t time; + int i, todo; + + jack_ringbuffer_read(port->base.event_ring, (char*)&event, sizeof(event)); + // TODO: take into account possible warping + if ((event.time + p->nframes) < p->frame_time) + time = 0; + else if (event.time >= p->frame_time) + time = p->nframes -1; + else + time = event.time + p->nframes - p->frame_time; + + jack_ringbuffer_get_read_vector(port->base.data_ring, vec); + assert ((vec[0].len + vec[1].len) >= event.size); + + if (event.overruns) + midi_unpack_reset(&port->unpack); + + todo = event.size; + for (i=0; i<2 && todo>0; ++i) { + int avail = todo < vec[i].len ? todo : vec[i].len; + int done = midi_unpack_buf(&port->unpack, (unsigned char*)vec[i].buf, avail, p->buffer, time); + if (done != avail) { + debug_log("jack_in: buffer overflow in port %s", port->base.name); + break; + } + todo -= done; + } + jack_ringbuffer_read_advance(port->base.data_ring, event.size); + } +} + +/* + * Low level input. + */ +static +int do_midi_input(process_midi_t *proc) +{ + input_port_t *port = (input_port_t*) proc->port; + if (!midi_is_ready(proc)) + return 0; + + if (port->base.is_ready) { + jack_ringbuffer_data_t vec[2]; + int res; + + jack_ringbuffer_get_write_vector(port->base.data_ring, vec); + if (jack_ringbuffer_write_space(port->base.event_ring) < sizeof(event_head_t) || vec[0].len < 1) { + port->overruns++; + if (port->base.npfds) + debug_log("midi_in: internal overflow on %s", port->base.name); + // remove from poll to prevent busy-looping + port->base.npfds = 0; + return 1; + } + res = snd_rawmidi_read(port->base.rawmidi, vec[0].buf, vec[0].len); + if (res < 0 && res != -EWOULDBLOCK) { + error_log("midi_in: reading from port %s failed: %s", port->base.name, snd_strerror(res)); + return 0; + } else if (res > 0) { + event_head_t event; + event.time = proc->cur_time; + event.size = res; + event.overruns = port->overruns; + port->overruns = 0; + debug_log("midi_in: read %d bytes at %d", (int)event.size, (int)event.time); + jack_ringbuffer_write_advance(port->base.data_ring, event.size); + jack_ringbuffer_write(port->base.event_ring, (char*)&event, sizeof(event)); + } + port->base.is_ready = 0; + } + + if (!midi_update_pfds(proc)) + return 0; + + return 1; +} + +/* + * ------------------------------------ Output ------------------------------ + */ + +static int output_port_init(alsa_rawmidi_t *midi, midi_port_t *port) +{ + output_port_t *out = (output_port_t*)port; + midi_pack_reset(&out->packer); + out->next_event.time = 0; + out->next_event.size = 0; + out->todo = 0; + return 0; +} + +static void output_port_close(alsa_rawmidi_t *midi, midi_port_t *port) +{ +} + +static +void do_jack_output(process_jack_t *proc) +{ + output_port_t *port = (output_port_t*) proc->port; + int nevents = jack_midi_get_event_count(proc->buffer); + int i; + if (nevents) + debug_log("jack_out: %d events in %s", nevents, port->base.name); + for (i=0; ibuffer, i); + + if (jack_ringbuffer_write_space(port->base.data_ring) < event.size || jack_ringbuffer_write_space(port->base.event_ring) < sizeof(hdr)) { + debug_log("jack_out: output buffer overflow on %s", port->base.name); + break; + } + + midi_pack_event(&port->packer, &event); + + jack_ringbuffer_write(port->base.data_ring, (char*)event.buffer, event.size); + + hdr.time = proc->frame_time + event.time + proc->nframes; + hdr.size = event.size; + jack_ringbuffer_write(port->base.event_ring, (char*)&hdr, sizeof(hdr)); + debug_log("jack_out: sent %d-byte event at %ld", (int)event.size, (long)event.time); + } +} + +static +int do_midi_output(process_midi_t *proc) +{ + int worked = 0; + output_port_t *port = (output_port_t*) proc->port; + + if (!midi_is_ready(proc)) + return 0; + + // eat events + while (port->next_event.time <= proc->cur_time) { + port->todo += port->next_event.size; + if (jack_ringbuffer_read(port->base.event_ring, (char*)&port->next_event, sizeof(port->next_event))!=sizeof(port->next_event)) { + port->next_event.time = 0; + port->next_event.size = 0; + break; + } else + debug_log("midi_out: at %ld got %d bytes for %ld", (long)proc->cur_time, (int)port->next_event.size, (long)port->next_event.time); + } + + if (port->todo) + debug_log("midi_out: todo = %d at %ld", (int)port->todo, (long)proc->cur_time); + + // calc next wakeup time + if (!port->todo && port->next_event.time && port->next_event.time < proc->next_time) { + proc->next_time = port->next_event.time; + debug_log("midi_out: next_time = %ld", (long)proc->next_time); + } + + if (port->todo && port->base.is_ready) { + // write data + int size = port->todo; + int res; + jack_ringbuffer_data_t vec[2]; + + jack_ringbuffer_get_read_vector(port->base.data_ring, vec); + if (size > vec[0].len) { + size = vec[0].len; + assert (size > 0); + } + res = snd_rawmidi_write(port->base.rawmidi, vec[0].buf, size); + if (res > 0) { + jack_ringbuffer_read_advance(port->base.data_ring, res); + debug_log("midi_out: written %d bytes to %s", res, port->base.name); + port->todo -= res; + worked = 1; + } else if (res == -EWOULDBLOCK) { + port->base.is_ready = 0; + debug_log("midi_out: -EWOULDBLOCK on %s", port->base.name); + return 1; + } else { + error_log("midi_out: writing to port %s failed: %s", port->base.name, snd_strerror(res)); + return 0; + } + snd_rawmidi_drain(port->base.rawmidi); + } + + // update pfds for this port + if (!midi_update_pfds(proc)) + return 0; + + if (!port->todo) { + int i; + if (worked) + debug_log("midi_out: relaxing on %s", port->base.name); + for (i=0; ibase.npfds; ++i) + proc->wpfds[i].events &= ~POLLOUT; + } else { + int i; + for (i=0; ibase.npfds; ++i) + proc->wpfds[i].events |= POLLOUT; + } + return 1; +} diff --git a/drivers/am/alsa_seqmidi.c b/drivers/am/alsa_seqmidi.c new file mode 100644 index 0000000..3351b36 --- /dev/null +++ b/drivers/am/alsa_seqmidi.c @@ -0,0 +1,925 @@ +/* + * ALSA SEQ < - > JACK MIDI bridge + * + * Copyright (c) 2006,2007 Dmitry S. Baikov + * + * 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 + */ + +/* + * alsa_seqmidi_read: + * add new ports + * reads queued snd_seq_event's + * if PORT_EXIT: mark port as dead + * if PORT_ADD, PORT_CHANGE: send addr to port_thread (it also may mark port as dead) + * else process input event + * remove dead ports and send them to port_thread + * + * alsa_seqmidi_write: + * remove dead ports and send them to port_thread + * add new ports + * queue output events + * + * port_thread: + * wait for port_sem + * free deleted ports + * create new ports or mark existing as dead + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "alsa_midi.h" + +#ifndef SND_SEQ_PORT_TYPE_PORT +#define SND_SEQ_PORT_TYPE_PORT (1<<19) /* Appears in version 1.0.12rc1 */ +#endif + +#ifndef SND_SEQ_PORT_TYPE_HARDWARE +#define SND_SEQ_PORT_TYPE_HARDWARE (1<<16) /* Appears in version 1.0.12rc1 */ +#endif + +#ifdef STANDALONE +#define MESSAGE(...) fprintf(stderr, __VA_ARGS__) +#else +#include "messagebuffer.h" +#endif + +#define info_log(...) MESSAGE(__VA_ARGS__) +#define error_log(...) MESSAGE(__VA_ARGS__) + +#ifdef JACK_MIDI_DEBUG +#define debug_log(...) MESSAGE(__VA_ARGS__) +#else +#define debug_log(...) +#endif + +#define NSEC_PER_SEC ((int64_t)1000*1000*1000) + +enum { + MAX_PORTS = 64, + MAX_EVENT_SIZE = 1024, +}; + +typedef struct port_t port_t; + +enum { + PORT_HASH_BITS = 4, + PORT_HASH_SIZE = 1 << PORT_HASH_BITS +}; + +typedef port_t* port_hash_t[PORT_HASH_SIZE]; + +struct port_t { + port_t *next; + int is_dead; + char name[64]; + snd_seq_addr_t remote; + jack_port_t *jack_port; + + jack_ringbuffer_t *early_events; // alsa_midi_event_t + data + int64_t last_out_time; + + void *jack_buf; +}; + +typedef struct { + snd_midi_event_t *codec; + + jack_ringbuffer_t *new_ports; + + port_t *ports[MAX_PORTS]; +} stream_t; + +typedef struct alsa_seqmidi { + alsa_midi_t ops; + jack_client_t *jack; + + snd_seq_t *seq; + int client_id; + int port_id; + int queue; + + int keep_walking; + + pthread_t port_thread; + sem_t port_sem; + jack_ringbuffer_t *port_add; // snd_seq_addr_t + jack_ringbuffer_t *port_del; // port_t* + + stream_t stream[2]; + + char alsa_name[32]; +} alsa_seqmidi_t; + +struct alsa_midi_event { + int64_t time; + int size; +}; +typedef struct alsa_midi_event alsa_midi_event_t; + +struct process_info { + int dir; + jack_nframes_t nframes; + jack_nframes_t period_start; + jack_nframes_t sample_rate; + jack_nframes_t cur_frames; + int64_t alsa_time; +}; + + +enum PortType { PORT_INPUT = 0, PORT_OUTPUT = 1 }; + +typedef void (*port_jack_func)(alsa_seqmidi_t *self, port_t *port,struct process_info* info); +static void do_jack_input(alsa_seqmidi_t *self, port_t *port, struct process_info* info); +static void do_jack_output(alsa_seqmidi_t *self, port_t *port, struct process_info* info); + +typedef struct { + int alsa_mask; + int jack_caps; + char name[9]; + port_jack_func jack_func; +} port_type_t; + +static port_type_t port_type[2] = { + { + SND_SEQ_PORT_CAP_SUBS_READ, + JackPortIsOutput, + "capture", + do_jack_input + }, + { + SND_SEQ_PORT_CAP_SUBS_WRITE, + JackPortIsInput, + "playback", + do_jack_output + } +}; + + +static void alsa_seqmidi_delete(alsa_midi_t *m); +static int alsa_seqmidi_attach(alsa_midi_t *m); +static int alsa_seqmidi_detach(alsa_midi_t *m); +static int alsa_seqmidi_start(alsa_midi_t *m); +static int alsa_seqmidi_stop(alsa_midi_t *m); +static void alsa_seqmidi_read(alsa_midi_t *m, jack_nframes_t nframes); +static void alsa_seqmidi_write(alsa_midi_t *m, jack_nframes_t nframes); + +static +void stream_init(alsa_seqmidi_t *self, int dir) +{ + stream_t *str = &self->stream[dir]; + + str->new_ports = jack_ringbuffer_create(MAX_PORTS*sizeof(port_t*)); + snd_midi_event_new(MAX_EVENT_SIZE, &str->codec); +} + +static void port_free(alsa_seqmidi_t *self, port_t *port); +static void free_ports(alsa_seqmidi_t *self, jack_ringbuffer_t *ports); + +static +void stream_attach(alsa_seqmidi_t *self, int dir) +{ +} + +static +void stream_detach(alsa_seqmidi_t *self, int dir) +{ + stream_t *str = &self->stream[dir]; + int i; + + free_ports(self, str->new_ports); + + // delete all ports from hash + for (i=0; iports[i]; + while (port) { + port_t *next = port->next; + port_free(self, port); + port = next; + } + str->ports[i] = NULL; + } +} + +static +void stream_close(alsa_seqmidi_t *self, int dir) +{ + stream_t *str = &self->stream[dir]; + + if (str->codec) + snd_midi_event_free(str->codec); + if (str->new_ports) + jack_ringbuffer_free(str->new_ports); +} + +alsa_midi_t* alsa_seqmidi_new(jack_client_t *client, const char* alsa_name) +{ + alsa_seqmidi_t *self = calloc(1, sizeof(alsa_seqmidi_t)); + debug_log("midi: new\n"); + if (!self) + return NULL; + self->jack = client; + if (!alsa_name) + alsa_name = "jack_midi"; + snprintf(self->alsa_name, sizeof(self->alsa_name), "%s", alsa_name); + + self->port_add = jack_ringbuffer_create(2*MAX_PORTS*sizeof(snd_seq_addr_t)); + self->port_del = jack_ringbuffer_create(2*MAX_PORTS*sizeof(port_t*)); + sem_init(&self->port_sem, 0, 0); + + stream_init(self, PORT_INPUT); + stream_init(self, PORT_OUTPUT); + + self->ops.destroy = alsa_seqmidi_delete; + self->ops.attach = alsa_seqmidi_attach; + self->ops.detach = alsa_seqmidi_detach; + self->ops.start = alsa_seqmidi_start; + self->ops.stop = alsa_seqmidi_stop; + self->ops.read = alsa_seqmidi_read; + self->ops.write = alsa_seqmidi_write; + return &self->ops; +} + +static +void alsa_seqmidi_delete(alsa_midi_t *m) +{ + alsa_seqmidi_t *self = (alsa_seqmidi_t*) m; + + debug_log("midi: delete\n"); + alsa_seqmidi_detach(m); + + stream_close(self, PORT_OUTPUT); + stream_close(self, PORT_INPUT); + + jack_ringbuffer_free(self->port_add); + jack_ringbuffer_free(self->port_del); + sem_close(&self->port_sem); + + free(self); +} + +static +int alsa_seqmidi_attach(alsa_midi_t *m) +{ + alsa_seqmidi_t *self = (alsa_seqmidi_t*) m; + int err; + + debug_log("midi: attach\n"); + + if (self->seq) + return -EALREADY; + + if ((err = snd_seq_open(&self->seq, "hw", SND_SEQ_OPEN_DUPLEX, 0)) < 0) { + error_log("failed to open alsa seq"); + return err; + } + snd_seq_set_client_name(self->seq, self->alsa_name); + self->port_id = snd_seq_create_simple_port(self->seq, "port", + SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_WRITE +#ifndef JACK_MIDI_DEBUG + |SND_SEQ_PORT_CAP_NO_EXPORT +#endif + ,SND_SEQ_PORT_TYPE_APPLICATION); + self->client_id = snd_seq_client_id(self->seq); + + self->queue = snd_seq_alloc_queue(self->seq); + snd_seq_start_queue(self->seq, self->queue, 0); + + stream_attach(self, PORT_INPUT); + stream_attach(self, PORT_OUTPUT); + + snd_seq_nonblock(self->seq, 1); + + return 0; +} + +static +int alsa_seqmidi_detach(alsa_midi_t *m) +{ + alsa_seqmidi_t *self = (alsa_seqmidi_t*) m; + + debug_log("midi: detach\n"); + + if (!self->seq) + return -EALREADY; + + alsa_seqmidi_stop(m); + + jack_ringbuffer_reset(self->port_add); + free_ports(self, self->port_del); + + stream_detach(self, PORT_INPUT); + stream_detach(self, PORT_OUTPUT); + + snd_seq_close(self->seq); + self->seq = NULL; + + return 0; +} + +static void* port_thread(void *); + +static void add_existing_ports(alsa_seqmidi_t *self); +static void update_ports(alsa_seqmidi_t *self); +static void add_ports(stream_t *str); + +static +int alsa_seqmidi_start(alsa_midi_t *m) +{ + alsa_seqmidi_t *self = (alsa_seqmidi_t*) m; + int err; + + debug_log("midi: start\n"); + + if (!self->seq) + return -EBADF; + + if (self->keep_walking) + return -EALREADY; + + snd_seq_connect_from(self->seq, self->port_id, SND_SEQ_CLIENT_SYSTEM, SND_SEQ_PORT_SYSTEM_ANNOUNCE); + snd_seq_drop_input(self->seq); + + add_existing_ports(self); + update_ports(self); + add_ports(&self->stream[PORT_INPUT]); + add_ports(&self->stream[PORT_OUTPUT]); + + self->keep_walking = 1; + + if ((err = pthread_create(&self->port_thread, NULL, port_thread, self))) { + self->keep_walking = 0; + return -errno; + } + + return 0; +} + +static +int alsa_seqmidi_stop(alsa_midi_t *m) +{ + alsa_seqmidi_t *self = (alsa_seqmidi_t*) m; + + debug_log("midi: stop\n"); + + if (!self->keep_walking) + return -EALREADY; + + snd_seq_disconnect_from(self->seq, self->port_id, SND_SEQ_CLIENT_SYSTEM, SND_SEQ_PORT_SYSTEM_ANNOUNCE); + + self->keep_walking = 0; + + sem_post(&self->port_sem); + pthread_join(self->port_thread, NULL); + self->port_thread = 0; + + return 0; +} + +static +int alsa_connect_from(alsa_seqmidi_t *self, int client, int port) +{ + snd_seq_port_subscribe_t* sub; + snd_seq_addr_t seq_addr; + int err; + + snd_seq_port_subscribe_alloca(&sub); + seq_addr.client = client; + seq_addr.port = port; + snd_seq_port_subscribe_set_sender(sub, &seq_addr); + seq_addr.client = self->client_id; + seq_addr.port = self->port_id; + snd_seq_port_subscribe_set_dest(sub, &seq_addr); + + snd_seq_port_subscribe_set_time_update(sub, 1); + snd_seq_port_subscribe_set_queue(sub, self->queue); + snd_seq_port_subscribe_set_time_real(sub, 1); + + if ((err=snd_seq_subscribe_port(self->seq, sub))) + error_log("can't subscribe to %d:%d - %s\n", client, port, snd_strerror(err)); + return err; +} + +/* + * ==================== Port routines ============================= + */ +static inline +int port_hash(snd_seq_addr_t addr) +{ + return (addr.client + addr.port) % PORT_HASH_SIZE; +} + +static +port_t* port_get(port_hash_t hash, snd_seq_addr_t addr) +{ + port_t **pport = &hash[port_hash(addr)]; + while (*pport) { + port_t *port = *pport; + if (port->remote.client == addr.client && port->remote.port == addr.port) + return port; + pport = &port->next; + } + return NULL; +} + +static +void port_insert(port_hash_t hash, port_t *port) +{ + port_t **pport = &hash[port_hash(port->remote)]; + port->next = *pport; + *pport = port; +} + +static +void port_setdead(port_hash_t hash, snd_seq_addr_t addr) +{ + port_t *port = port_get(hash, addr); + if (port) + port->is_dead = 1; // see jack_process + else + debug_log("port_setdead: not found (%d:%d)\n", addr.client, addr.port); +} + +static +void port_free(alsa_seqmidi_t *self, port_t *port) +{ + //snd_seq_disconnect_from(self->seq, self->port_id, port->remote.client, port->remote.port); + //snd_seq_disconnect_to(self->seq, self->port_id, port->remote.client, port->remote.port); + if (port->early_events) + jack_ringbuffer_free(port->early_events); + if (port->jack_port) + jack_port_unregister(self->jack, port->jack_port); + // info_log("port deleted: %s\n", port->name); + + free(port); +} + +static +port_t* port_create(alsa_seqmidi_t *self, int type, snd_seq_addr_t addr, const snd_seq_port_info_t *info) +{ + snd_seq_client_info_t* client_info; + port_t *port; + char *c; + int err; + int jack_caps; + + port = calloc(1, sizeof(port_t)); + if (!port) + return NULL; + + port->remote = addr; + + snd_seq_client_info_alloca (&client_info); + snd_seq_get_any_client_info (self->seq, addr.client, client_info); + + snprintf(port->name, sizeof(port->name), "%s/midi_%s_%d", + snd_seq_client_info_get_name(client_info), port_type[type].name, addr.port+1); + + // replace all offending characters by - + for (c = port->name; *c; ++c) + if (!isalnum(*c) && *c != '/' && *c != '_' && *c != ':' && *c != '(' && *c != ')') + *c = '-'; + + jack_caps = port_type[type].jack_caps; + + /* mark anything that looks like a hardware port as physical&terminal */ + + if (snd_seq_port_info_get_type (info) & (SND_SEQ_PORT_TYPE_HARDWARE|SND_SEQ_PORT_TYPE_PORT|SND_SEQ_PORT_TYPE_SPECIFIC)) { + jack_caps |= (JackPortIsPhysical|JackPortIsTerminal); + } + + port->jack_port = jack_port_register(self->jack, + port->name, JACK_DEFAULT_MIDI_TYPE, jack_caps, 0); + if (!port->jack_port) + goto failed; + + /* generate an alias */ + + snprintf(port->name, sizeof(port->name), "%s:midi/%s_%d", + snd_seq_client_info_get_name (client_info), port_type[type].name, addr.port+1); + + // replace all offending characters by - + for (c = port->name; *c; ++c) + if (!isalnum(*c) && *c != '/' && *c != '_' && *c != ':' && *c != '(' && *c != ')') + *c = '-'; + + jack_port_set_alias (port->jack_port, port->name); + + if (type == PORT_INPUT) + err = alsa_connect_from(self, port->remote.client, port->remote.port); + else + err = snd_seq_connect_to(self->seq, self->port_id, port->remote.client, port->remote.port); + if (err) + goto failed; + + port->early_events = jack_ringbuffer_create(MAX_EVENT_SIZE*16); + + // info_log("port created: %s\n", port->name); + return port; + + failed: + port_free(self, port); + return NULL; +} + +/* + * ==================== Port add/del handling thread ============================== + */ +static +void update_port_type(alsa_seqmidi_t *self, int type, snd_seq_addr_t addr, int caps, const snd_seq_port_info_t *info) +{ + stream_t *str = &self->stream[type]; + int alsa_mask = port_type[type].alsa_mask; + port_t *port = port_get(str->ports, addr); + + debug_log("update_port_type(%d:%d)\n", addr.client, addr.port); + + if (port && (caps & alsa_mask)!=alsa_mask) { + debug_log("setdead: %s\n", port->name); + port->is_dead = 1; + } + + if (!port && (caps & alsa_mask)==alsa_mask) { + assert (jack_ringbuffer_write_space(str->new_ports) >= sizeof(port)); + port = port_create(self, type, addr, info); + if (port) + jack_ringbuffer_write(str->new_ports, (char*)&port, sizeof(port)); + } +} + +static +void update_port(alsa_seqmidi_t *self, snd_seq_addr_t addr, const snd_seq_port_info_t *info) +{ + unsigned int port_caps = snd_seq_port_info_get_capability(info); + if (port_caps & SND_SEQ_PORT_CAP_NO_EXPORT) + return; + update_port_type(self, PORT_INPUT, addr, port_caps, info); + update_port_type(self, PORT_OUTPUT,addr, port_caps, info); +} + +static +void free_ports(alsa_seqmidi_t *self, jack_ringbuffer_t *ports) +{ + port_t *port; + int sz; + while ((sz = jack_ringbuffer_read(ports, (char*)&port, sizeof(port)))) { + assert (sz == sizeof(port)); + port_free(self, port); + } +} + +static +void update_ports(alsa_seqmidi_t *self) +{ + snd_seq_addr_t addr; + snd_seq_port_info_t *info; + int size; + + snd_seq_port_info_alloca(&info); + + while ((size = jack_ringbuffer_read(self->port_add, (char*)&addr, sizeof(addr)))) { + + int err; + + assert (size == sizeof(addr)); + assert (addr.client != self->client_id); + if ((err=snd_seq_get_any_port_info(self->seq, addr.client, addr.port, info))>=0) { + update_port(self, addr, info); + } else { + //port_setdead(self->stream[PORT_INPUT].ports, addr); + //port_setdead(self->stream[PORT_OUTPUT].ports, addr); + } + } +} + +static +void* port_thread(void *arg) +{ + alsa_seqmidi_t *self = arg; + + while (self->keep_walking) { + sem_wait(&self->port_sem); + free_ports(self, self->port_del); + update_ports(self); + } + debug_log("port_thread exited\n"); + return NULL; +} + +static +void add_existing_ports(alsa_seqmidi_t *self) +{ + snd_seq_addr_t addr; + snd_seq_client_info_t *client_info; + snd_seq_port_info_t *port_info; + + snd_seq_client_info_alloca(&client_info); + snd_seq_port_info_alloca(&port_info); + snd_seq_client_info_set_client(client_info, -1); + while (snd_seq_query_next_client(self->seq, client_info) >= 0) + { + addr.client = snd_seq_client_info_get_client(client_info); + if (addr.client == SND_SEQ_CLIENT_SYSTEM || addr.client == self->client_id) + continue; + snd_seq_port_info_set_client(port_info, addr.client); + snd_seq_port_info_set_port(port_info, -1); + while (snd_seq_query_next_port(self->seq, port_info) >= 0) + { + addr.port = snd_seq_port_info_get_port(port_info); + update_port(self, addr, port_info); + } + } +} + +/* + * =================== Input/output port handling ========================= + */ +static +void set_process_info(struct process_info *info, alsa_seqmidi_t *self, int dir, jack_nframes_t nframes) +{ + const snd_seq_real_time_t* alsa_time; + snd_seq_queue_status_t *status; + + snd_seq_queue_status_alloca(&status); + + info->dir = dir; + + info->period_start = jack_last_frame_time(self->jack); + info->nframes = nframes; + info->sample_rate = jack_get_sample_rate(self->jack); + + info->cur_frames = jack_frame_time(self->jack); + + // immediately get alsa'a real time (uhh, why everybody has their on 'real' time) + snd_seq_get_queue_status(self->seq, self->queue, status); + alsa_time = snd_seq_queue_status_get_real_time(status); + info->alsa_time = alsa_time->tv_sec * NSEC_PER_SEC + alsa_time->tv_nsec; + + if (info->period_start + info->nframes < info->cur_frames) { + int periods_lost = (info->cur_frames - info->period_start) / info->nframes; + info->period_start += periods_lost * info->nframes; + debug_log("xrun detected: %d periods lost\n", periods_lost); + } +} + +static +void add_ports(stream_t *str) +{ + port_t *port; + while (jack_ringbuffer_read(str->new_ports, (char*)&port, sizeof(port))) { + debug_log("jack: inserted port %s\n", port->name); + port_insert(str->ports, port); + } +} + +static +void jack_process(alsa_seqmidi_t *self, struct process_info *info) +{ + stream_t *str = &self->stream[info->dir]; + port_jack_func process = port_type[info->dir].jack_func; + int i, del=0; + + add_ports(str); + + // process ports + for (i=0; iports[i]; + while (*pport) { + port_t *port = *pport; + port->jack_buf = jack_port_get_buffer(port->jack_port, info->nframes); + if (info->dir == PORT_INPUT) + jack_midi_clear_buffer(port->jack_buf); + + if (!port->is_dead) + (*process)(self, port, info); + else if (jack_ringbuffer_write_space(self->port_del) >= sizeof(port)) { + debug_log("jack: removed port %s\n", port->name); + *pport = port->next; + jack_ringbuffer_write(self->port_del, (char*)&port, sizeof(port)); + del++; + continue; + } + + pport = &port->next; + } + } + + if (del) + sem_post(&self->port_sem); +} + +/* + * ============================ Input ============================== + */ +static +void do_jack_input(alsa_seqmidi_t *self, port_t *port, struct process_info *info) +{ + // process port->early_events + alsa_midi_event_t ev; + while (jack_ringbuffer_read(port->early_events, (char*)&ev, sizeof(ev))) { + jack_midi_data_t* buf; + jack_nframes_t time = ev.time - info->period_start; + if (time < 0) + time = 0; + else if (time >= info->nframes) + time = info->nframes - 1; + buf = jack_midi_event_reserve(port->jack_buf, time, ev.size); + if (buf) + jack_ringbuffer_read(port->early_events, (char*)buf, ev.size); + else + jack_ringbuffer_read_advance(port->early_events, ev.size); + debug_log("input: it's time for %d bytes at %d\n", ev.size, time); + } +} + +static +void port_event(alsa_seqmidi_t *self, snd_seq_event_t *ev) +{ + const snd_seq_addr_t addr = ev->data.addr; + + if (addr.client == self->client_id) + return; + + if (ev->type == SND_SEQ_EVENT_PORT_START || ev->type == SND_SEQ_EVENT_PORT_CHANGE) { + assert (jack_ringbuffer_write_space(self->port_add) >= sizeof(addr)); + + debug_log("port_event: add/change %d:%d\n", addr.client, addr.port); + jack_ringbuffer_write(self->port_add, (char*)&addr, sizeof(addr)); + sem_post(&self->port_sem); + } else if (ev->type == SND_SEQ_EVENT_PORT_EXIT) { + debug_log("port_event: del %d:%d\n", addr.client, addr.port); + port_setdead(self->stream[PORT_INPUT].ports, addr); + port_setdead(self->stream[PORT_OUTPUT].ports, addr); + } +} + +static +void input_event(alsa_seqmidi_t *self, snd_seq_event_t *alsa_event, struct process_info* info) +{ + jack_midi_data_t data[MAX_EVENT_SIZE]; + stream_t *str = &self->stream[PORT_INPUT]; + long size; + int64_t alsa_time, time_offset; + int64_t frame_offset, event_frame; + port_t *port; + + port = port_get(str->ports, alsa_event->source); + if (!port) + return; + + /* + * RPNs, NRPNs, Bank Change, etc. need special handling + * but seems, ALSA does it for us already. + */ + snd_midi_event_reset_decode(str->codec); + if ((size = snd_midi_event_decode(str->codec, data, sizeof(data), alsa_event))<0) + return; + + // fixup NoteOn with vel 0 + if ((data[0] & 0xF0) == 0x90 && data[2] == 0x00) { + data[0] = 0x80 + (data[0] & 0x0F); + data[2] = 0x40; + } + + alsa_time = alsa_event->time.time.tv_sec * NSEC_PER_SEC + alsa_event->time.time.tv_nsec; + time_offset = info->alsa_time - alsa_time; + frame_offset = (info->sample_rate * time_offset) / NSEC_PER_SEC; + event_frame = (int64_t)info->cur_frames - info->period_start - frame_offset + info->nframes; + + debug_log("input: %d bytes at event_frame=%d\n", (int)size, (int)event_frame); + + if (event_frame >= info->nframes && + jack_ringbuffer_write_space(port->early_events) >= (sizeof(alsa_midi_event_t) + size)) { + alsa_midi_event_t ev; + ev.time = event_frame + info->period_start; + ev.size = size; + jack_ringbuffer_write(port->early_events, (char*)&ev, sizeof(ev)); + jack_ringbuffer_write(port->early_events, (char*)data, size); + debug_log("postponed to next frame +%d\n", (int) (event_frame - info->nframes)); + return; + } + + if (event_frame < 0) + event_frame = 0; + else if (event_frame >= info->nframes) + event_frame = info->nframes - 1; + + jack_midi_event_write(port->jack_buf, event_frame, data, size); +} + +static +void alsa_seqmidi_read(alsa_midi_t *m, jack_nframes_t nframes) +{ + alsa_seqmidi_t *self = (alsa_seqmidi_t*) m; + int res; + snd_seq_event_t *event; + struct process_info info; + + if (!self->keep_walking) + return; + + set_process_info(&info, self, PORT_INPUT, nframes); + jack_process(self, &info); + + while ((res = snd_seq_event_input(self->seq, &event))>0) { + if (event->source.client == SND_SEQ_CLIENT_SYSTEM) + port_event(self, event); + else + input_event(self, event, &info); + } +} + +/* + * ============================ Output ============================== + */ + +static +void do_jack_output(alsa_seqmidi_t *self, port_t *port, struct process_info* info) +{ + stream_t *str = &self->stream[info->dir]; + int nevents = jack_midi_get_event_count(port->jack_buf); + int i; + for (i=0; ijack_buf, i); + + snd_seq_ev_clear(&alsa_event); + snd_midi_event_reset_encode(str->codec); + if (!snd_midi_event_encode(str->codec, jack_event.buffer, jack_event.size, &alsa_event)) + continue; // invalid event + + snd_seq_ev_set_source(&alsa_event, self->port_id); + snd_seq_ev_set_dest(&alsa_event, port->remote.client, port->remote.port); + + /* NOTE: in case of xrun it could become negative, so it is essential to use signed type! */ + frame_offset = (int64_t)jack_event.time + info->period_start + info->nframes - info->cur_frames; + if (frame_offset < 0) { + frame_offset = info->nframes + jack_event.time; + error_log("internal xrun detected: frame_offset = %"PRId64"\n", frame_offset); + } + /* Ken Ellinwood reported problems with this assert. + * Seems, magic 2 should be replaced with nperiods. */ + //FIXME: assert (frame_offset < info->nframes*2); + //if (frame_offset < info->nframes * info->nperiods) + // debug_log("alsa_out: BLAH-BLAH-BLAH"); + + out_time = info->alsa_time + (frame_offset * NSEC_PER_SEC) / info->sample_rate; + + // we should use absolute time to prevent reordering caused by rounding errors + if (out_time < port->last_out_time) { + debug_log("alsa_out: limiting out_time %lld at %lld\n", out_time, port->last_out_time); + out_time = port->last_out_time; + } else + port->last_out_time = out_time; + + out_rt.tv_nsec = out_time % NSEC_PER_SEC; + out_rt.tv_sec = out_time / NSEC_PER_SEC; + snd_seq_ev_schedule_real(&alsa_event, self->queue, 0, &out_rt); + + err = snd_seq_event_output(self->seq, &alsa_event); + + debug_log("alsa_out: written %d bytes to %s at %d (%lld): %d %s\n", + jack_event.size, port->name, (int)frame_offset, out_time - info->alsa_time, err, err < 0 ? snd_strerror(err) : "bytes queued"); + } +} + +static +void alsa_seqmidi_write(alsa_midi_t *m, jack_nframes_t nframes) +{ + alsa_seqmidi_t *self = (alsa_seqmidi_t*) m; + struct process_info info; + + if (!self->keep_walking) + return; + + set_process_info(&info, self, PORT_OUTPUT, nframes); + jack_process(self, &info); + snd_seq_drain_output(self->seq); +} diff --git a/drivers/am/midi_pack.h b/drivers/am/midi_pack.h new file mode 100644 index 0000000..6fb704b --- /dev/null +++ b/drivers/am/midi_pack.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2006,2007 Dmitry S. Baikov + * + * 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 + */ + +#ifndef __jack_midi_pack_h__ +#define __jack_midi_pack_h__ + +#include +#include "engine.h" + +typedef struct { + int running_status; +} midi_pack_t; + +static inline +void midi_pack_reset(midi_pack_t *p) +{ + p->running_status = 0; +} + +static +void midi_pack_event(midi_pack_t *p, jack_midi_event_t *e) +{ + if (e->buffer[0] >= 0x80 && e->buffer[0] < 0xF0) { // Voice Message + if (e->buffer[0] == p->running_status) { + e->buffer++; + e->size--; + } else + p->running_status = e->buffer[0]; + } else if (e->buffer[0] < 0xF8) { // not System Realtime + p->running_status = 0; + } +} + +#endif /* __jack_midi_pack_h__ */ diff --git a/drivers/am/midi_unpack.h b/drivers/am/midi_unpack.h new file mode 100644 index 0000000..c917f4d --- /dev/null +++ b/drivers/am/midi_unpack.h @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2006,2007 Dmitry S. Baikov + * + * 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 + */ + +#ifndef __jack_midi_unpack_h__ +#define __jack_midi_unpack_h__ + +#include +#include "engine.h" + +enum { + MIDI_UNPACK_MAX_MSG = 1024 +}; + +typedef struct { + int pos, need, size; + unsigned char data[MIDI_UNPACK_MAX_MSG]; +} midi_unpack_t; + +static inline +void midi_unpack_init(midi_unpack_t *u) +{ + u->pos = 0; + u->size = sizeof(u->data); + u->need = u->size; +} + +static inline +void midi_unpack_reset(midi_unpack_t *u) +{ + u->pos = 0; + u->need = u->size; +} + +static const unsigned char midi_voice_len[] = { + 3, /*0x80 Note Off*/ + 3, /*0x90 Note On*/ + 3, /*0xA0 Aftertouch*/ + 3, /*0xB0 Control Change*/ + 2, /*0xC0 Program Change*/ + 2, /*0xD0 Channel Pressure*/ + 3, /*0xE0 Pitch Wheel*/ + 1 /*0xF0 System*/ +}; + +static const unsigned char midi_system_len[] = { + 0, /*0xF0 System Exclusive Start*/ + 2, /*0xF1 MTC Quarter Frame*/ + 3, /*0xF2 Song Postion*/ + 2, /*0xF3 Song Select*/ + 0, /*0xF4 undefined*/ + 0, /*0xF5 undefined*/ + 1, /*0xF6 Tune Request*/ + 1 /*0xF7 System Exlusive End*/ +}; + +static +int midi_unpack_buf(midi_unpack_t *buf, const unsigned char *data, int len, void *jack_port_buf, jack_nframes_t time) +{ + int i; + for (i=0; i= 0xF8) // system realtime + { + jack_midi_event_write(jack_port_buf, time, &data[i], 1); + //jack_error("midi_unpack: written system relatime event\n"); + //midi_input_write(in, &data[i], 1); + } + else if (byte < 0x80) // data + { + assert (buf->pos < buf->size); + buf->data[buf->pos++] = byte; + } + else if (byte < 0xF0) // voice + { + assert (byte >= 0x80 && byte < 0xF0); + //buf->need = ((byte|0x0F) == 0xCF || (byte|0x0F)==0xDF) ? 2 : 3; + buf->need = midi_voice_len[(byte-0x80)>>4]; + buf->data[0] = byte; + buf->pos = 1; + } + else if (byte == 0xF7) // sysex end + { + assert (buf->pos < buf->size); + buf->data[buf->pos++] = byte; + buf->need = buf->pos; + } + else + { + assert (byte >= 0xF0 && byte < 0xF8); + buf->pos = 1; + buf->data[0] = byte; + buf->need = midi_system_len[byte - 0xF0]; + if (!buf->need) + buf->need = buf->size; + } + if (buf->pos == buf->need) + { + // TODO: deal with big sysex'es (they are silently dropped for now) + if (buf->data[0] >= 0x80 || (buf->data[0]==0xF0 && buf->data[buf->pos-1] == 0xF7)) { + /* convert Note On with velocity 0 to Note Off */ + if ((buf->data[0] & 0xF0) == 0x90 && buf->data[2] == 0) { + // we use temp array here to keep running status in sync + jack_midi_data_t temp[3] = { 0x80, 0, 0x40 }; + temp[0] |= buf->data[0] & 0x0F; + temp[1] = buf->data[1]; + jack_midi_event_write(jack_port_buf, time, temp, 3); + } else + jack_midi_event_write(jack_port_buf, time, &buf->data[0], buf->pos); + //jack_error("midi_unpack: written %d-byte event\n", buf->pos); + //midi_input_write(in, &buf->data[0], buf->pos); + } + /* keep running status */ + if (buf->data[0] >= 0x80 && buf->data[0] < 0xF0) + buf->pos = 1; + else + { + buf->pos = 0; + buf->need = buf->size; + } + } + } + assert (i==len); + return i; +} + +#endif /* __jack_midi_unpack_h__ */ -- cgit v1.2.1