diff options
Diffstat (limited to 'drivers/am/alsa_seqmidi.c')
-rw-r--r-- | drivers/am/alsa_seqmidi.c | 925 |
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); +} |