summaryrefslogtreecommitdiff
path: root/drivers/am/alsa_seqmidi.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/am/alsa_seqmidi.c')
-rw-r--r--drivers/am/alsa_seqmidi.c925
1 files changed, 925 insertions, 0 deletions
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 <c0ff@konstruktiv.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+/*
+ * 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 <alsa/asoundlib.h>
+#include <jack/jack.h>
+#include <jack/midiport.h>
+#include <jack/ringbuffer.h>
+#include <jack/thread.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <signal.h>
+#include <semaphore.h>
+#include <time.h>
+#include <ctype.h>
+
+#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; i<PORT_HASH_SIZE; ++i) {
+ port_t *port = str->ports[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; i<PORT_HASH_SIZE; ++i) {
+ port_t **pport = &str->ports[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; i<nevents; ++i) {
+ jack_midi_event_t jack_event;
+ snd_seq_event_t alsa_event;
+ int64_t frame_offset;
+ int64_t out_time;
+ snd_seq_real_time_t out_rt;
+ int err;
+
+ jack_midi_event_get(&jack_event, port->jack_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);
+}