diff --git a/configure.ac b/configure.ac index 54eb2a3..e2cda6f 100644 --- a/configure.ac +++ b/configure.ac @@ -16,7 +16,7 @@ dnl micro version = incremented when implementation-only dnl changes are made dnl --- JACK_MAJOR_VERSION=0 -JACK_MINOR_VERSION=104 +JACK_MINOR_VERSION=105 JACK_MICRO_VERSION=0 dnl --- @@ -44,7 +44,7 @@ dnl slacker than this, and closer to those for the JACK version dnl number. dnl --- JACK_API_CURRENT=0 -JACK_API_REVISION=26 +JACK_API_REVISION=27 JACK_API_AGE=0 AC_SUBST(JACK_MAJOR_VERSION) diff --git a/drivers/alsa/Makefile.am b/drivers/alsa/Makefile.am index afa2665..0d497e1 100644 --- a/drivers/alsa/Makefile.am +++ b/drivers/alsa/Makefile.am @@ -8,9 +8,13 @@ plugin_LTLIBRARIES = jack_alsa.la jack_alsa_la_LDFLAGS = -module -avoid-version jack_alsa_la_SOURCES = alsa_driver.c generic_hw.c memops.c \ + alsa_seqmidi.c alsa_rawmidi.c \ hammerfall.c hdsp.c ice1712.c usx2y.c noinst_HEADERS = alsa_driver.h \ + alsa_midi.h \ + midi_pack.h \ + midi_unpack.h \ generic.h \ hammerfall.h \ hdsp.h \ diff --git a/drivers/alsa/alsa_driver.c b/drivers/alsa/alsa_driver.c index 0affea8..749254d 100644 --- a/drivers/alsa/alsa_driver.c +++ b/drivers/alsa/alsa_driver.c @@ -1022,6 +1022,9 @@ alsa_driver_start (alsa_driver_t *driver) malloc (sizeof (struct pollfd) * (driver->playback_nfds + driver->capture_nfds + 2)); + if (driver->midi) + (driver->midi->start)(driver->midi); + if (driver->playback_handle) { /* fill playback buffer with zeroes, and mark all fragments as having data. @@ -1126,6 +1129,9 @@ alsa_driver_stop (alsa_driver_t *driver) driver->hw->set_input_monitor_mask (driver->hw, 0); } + if (driver->midi) + (driver->midi->stop)(driver->midi); + return 0; } @@ -1553,6 +1559,9 @@ alsa_driver_read (alsa_driver_t *driver, jack_nframes_t nframes) if (nframes > driver->frames_per_cycle) { return -1; } + + if (driver->midi) + (driver->midi->read)(driver->midi, nframes); nread = 0; contiguous = 0; @@ -1622,6 +1631,9 @@ 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; @@ -1805,6 +1817,12 @@ 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); } @@ -1818,6 +1836,9 @@ 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, @@ -1904,6 +1925,9 @@ 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); @@ -1975,7 +1999,8 @@ alsa_driver_new (char *name, char *playback_alsa_device, int user_playback_nchnls, int shorts_first, jack_nframes_t capture_latency, - jack_nframes_t playback_latency + jack_nframes_t playback_latency, + alsa_midi_t *midi_driver ) { int err; @@ -2058,6 +2083,8 @@ 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; + if (alsa_driver_check_card_type (driver)) { alsa_driver_delete (driver); return NULL; @@ -2332,7 +2359,7 @@ driver_get_descriptor () desc = calloc (1, sizeof (jack_driver_desc_t)); strcpy (desc->name,"alsa"); - desc->nparams = 17; + desc->nparams = 18; params = calloc (desc->nparams, sizeof (jack_driver_param_desc_t)); @@ -2483,6 +2510,18 @@ driver_get_descriptor () strcpy (params[i].short_desc, "Extra output latency (frames)"); strcpy (params[i].long_desc, params[i].short_desc); + i++; + strcpy (params[i].name, "midi"); + 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"); + desc->params = params; return desc; @@ -2508,6 +2547,8 @@ 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; @@ -2596,6 +2637,10 @@ driver_initialize (jack_client_t *client, const JSList * params) systemic_output_latency = param->value.ui; break; + case 'X': + midi_driver_name = strdup (param->value.str); + break; + } } @@ -2605,6 +2650,12 @@ 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, @@ -2614,7 +2665,7 @@ driver_initialize (jack_client_t *client, const JSList * params) user_capture_nchnls, user_playback_nchnls, shorts_first, systemic_input_latency, - systemic_output_latency); + systemic_output_latency, midi); } void diff --git a/drivers/alsa/alsa_driver.h b/drivers/alsa/alsa_driver.h index 520df37..6363df3 100644 --- a/drivers/alsa/alsa_driver.h +++ b/drivers/alsa/alsa_driver.h @@ -39,6 +39,8 @@ #include #include +#include "./alsa_midi.h" + typedef void (*ReadCopyFunction) (jack_default_audio_sample_t *dst, char *src, unsigned long src_bytes, unsigned long src_skip_bytes); @@ -141,6 +143,8 @@ typedef struct _alsa_driver { int xrun_count; int process_count; + alsa_midi_t *midi; + } alsa_driver_t; static inline void diff --git a/drivers/alsa/alsa_midi.h b/drivers/alsa/alsa_midi.h new file mode 100644 index 0000000..960c30b --- /dev/null +++ b/drivers/alsa/alsa_midi.h @@ -0,0 +1,38 @@ +/* + * 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 + +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); + +#endif /* __jack_alsa_midi_h__ */ diff --git a/drivers/alsa/alsa_rawmidi.c b/drivers/alsa/alsa_rawmidi.c new file mode 100644 index 0000000..0309912 --- /dev/null +++ b/drivers/alsa/alsa_rawmidi.c @@ -0,0 +1,1193 @@ +/* + * 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 +#endif + +#define info_log(...) MESSAGE(__VA_ARGS__) +#define error_log(...) MESSAGE(__VA_ARGS__) + +#ifdef DEBUG +#define debug_log(...) MESSAGE(__VA_ARGS__) +#else +#define debug_log(...) +#endif + + +enum { + NANOSLEEP_RESOLUTION = 7000 +}; + +#define NFRAMES_INF INT_MAX + +enum { +#ifndef 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\n", 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\n", 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); + if (port->event_ring) + jack_ringbuffer_free(port->event_ring); + if (port->jack) + jack_port_unregister(midi->client, port->jack); + if (port->rawmidi) + snd_rawmidi_close(port->rawmidi); + memset(port, 0, sizeof(*port)); +} + +/* + * ------------------------- 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); +} + +#include + +static inline +void alsa_error(const char *func, int err) +{ + error_log("%s() failed\n", 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\n"); + scan_cleanup(midi); + + scan.midi = midi; + scan.iterator = &midi->scan.ports; + snd_rawmidi_info_alloca(&scan.info); + + //debug_log("scan: rescan\n"); + 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\n", 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\n", port->dev, port->name); + return &port->next; + + fail_2: + (str->port_close)(midi, port); + fail_1: + midi_port_close(midi, port); + fail_0: + *list = port->next; + error_log("scan: can't open port %s %s\n", port->dev, port->name); + free(port); + return list; +} + +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\n", 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\n", 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....\n"); + 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; + + if (!str->owner->keep_walking) + return; + + proc.midi = str->owner; + proc.nframes = nframes; + proc.frame_time = jack_last_frame_time(proc.midi->client); + + // 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\n", 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\n", 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\n", str->name, res); + if (!midi->keep_walking) + break; + if (res < 0) { + if (errno == EINTR) + continue; + error_log("midi_thread(%s) poll failed: %s\n", 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\n", 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\n", str->name, str->midi.nports, wp); + str->midi.nports = wp; + if (npfds != w) + debug_log("midi_%s: npfds %d -> %d\n", 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\n", str->name, proc.next_time, 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\n", str->name, wait_frames); + } else + wait_nsec = 1000*1000*1000; + //debug_log("midi_thread(%s): wait_nsec = %lld\n", 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\n", port->name, snd_strerror(res)); + return 0; + } + + if (revents & ~proc->mode) { + debug_log("midi: port %s failed\n", port->name); + return 0; + } + if (revents & proc->mode) { + port->is_ready = 1; + debug_log("midi: is_ready %s\n", 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\n", 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\n", 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\n", 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\n", 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\n", (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\n", 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\n", 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\n", (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\n", (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\n", (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\n", (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\n", 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\n", port->base.name); + return 1; + } else { + error_log("midi_out: writing to port %s failed: %s\n", 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\n", 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/alsa_seqmidi.c b/drivers/alsa/alsa_seqmidi.c new file mode 100644 index 0000000..638bae5 --- /dev/null +++ b/drivers/alsa/alsa_seqmidi.c @@ -0,0 +1,870 @@ +/* + * 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" + +#ifdef STANDALONE +#define MESSAGE(...) fprintf(stderr, __VA_ARGS__) +#else +#include +#endif + +#define info_log(...) MESSAGE(__VA_ARGS__) +#define error_log(...) MESSAGE(__VA_ARGS__) + +#ifdef 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[4]; + port_jack_func jack_func; +} port_type_t; + +static port_type_t port_type[2] = { + { + SND_SEQ_PORT_CAP_SUBS_READ, + JackPortIsOutput|JackPortIsPhysical|JackPortIsTerminal, + "in", + do_jack_input + }, + { + SND_SEQ_PORT_CAP_SUBS_WRITE, + JackPortIsInput|JackPortIsPhysical|JackPortIsTerminal, + "out", + 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 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) +{ + port_t *port; + char *c; + int err; + + port = calloc(1, sizeof(port_t)); + if (!port) + return NULL; + + port->remote = addr; + + snprintf(port->name, sizeof(port->name), "%s-%d-%d-%s", + port_type[type].name, addr.client, addr.port, snd_seq_port_info_get_name(info)); + + // replace all offending characters by - + for (c = port->name; *c; ++c) + if (!isalnum(*c)) + *c = '-'; + + port->jack_port = jack_port_register(self->jack, + port->name, JACK_DEFAULT_MIDI_TYPE, port_type[type].jack_caps, 0); + if (!port->jack_port) + goto failed; + + 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; + int size; + + while ((size = jack_ringbuffer_read(self->port_add, (char*)&addr, sizeof(addr)))) { + snd_seq_port_info_t *info; + int err; + + snd_seq_port_info_alloca(&info); + 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; +} + +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] == 0x90 && data[2] == 0x00) { + data[0] = 0x80; + 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); + + frame_offset = jack_event.time + info->period_start + info->nframes - info->cur_frames; + 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) + 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: %d\n", jack_event.size, port->name, (int)frame_offset, err); + } +} + +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_pack.h b/drivers/alsa/midi_pack.h new file mode 100644 index 0000000..c449eb7 --- /dev/null +++ b/drivers/alsa/midi_pack.h @@ -0,0 +1,46 @@ +/* + * 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__ + +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_unpack.h b/drivers/alsa/midi_unpack.h new file mode 100644 index 0000000..d5d636d --- /dev/null +++ b/drivers/alsa/midi_unpack.h @@ -0,0 +1,139 @@ +/* + * 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__ + +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); + //printf("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); + //printf("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/example-clients/.cvsignore b/example-clients/.cvsignore index dba6104..6fa658f 100644 --- a/example-clients/.cvsignore +++ b/example-clients/.cvsignore @@ -21,3 +21,6 @@ inprocess.la inprocess.lo intime.la intime.lo +jack_alias +jack_evmon +jack_thread_wait diff --git a/example-clients/midiseq.c b/example-clients/midiseq.c index 44f2e9a..97459ae 100644 --- a/example-clients/midiseq.c +++ b/example-clients/midiseq.c @@ -45,7 +45,7 @@ int process(jack_nframes_t nframes, void *arg) int i,j; void* port_buf = jack_port_get_buffer(output_port, nframes); unsigned char* buffer; - jack_midi_clear_buffer(port_buf, nframes); + jack_midi_clear_buffer(port_buf); /*memset(buffer, 0, nframes*sizeof(jack_default_audio_sample_t));*/ for(i=0; i 1) { printf(" midisine: have %d events\n", event_count); for(i=0; i 1.0) ? ramp - 2.0 : ramp; diff --git a/jack/midiport.h b/jack/midiport.h index 3ff415e..da574f8 100644 --- a/jack/midiport.h +++ b/jack/midiport.h @@ -45,12 +45,10 @@ typedef struct _jack_midi_event /* Get number of events in a port buffer. * * @param port_buffer Port buffer from which to retrieve event. - * @param nframes Number of valid frames this cycle. * @return number of events inside @a port_buffer */ jack_nframes_t -jack_midi_get_event_count(void* port_buffer, - jack_nframes_t nframes); +jack_midi_get_event_count(void* port_buffer); /** Get a MIDI event from an event port buffer. @@ -62,14 +60,12 @@ jack_midi_get_event_count(void* port_buffer, * @param event Event structure to store retrieved event in. * @param port_buffer Port buffer from which to retrieve event. * @param event_index Index of event to retrieve. - * @param nframes Number of valid frames this cycle. * @return 0 on success, ENODATA if buffer is empty. */ int jack_midi_event_get(jack_midi_event_t *event, void *port_buffer, - jack_nframes_t event_index, - jack_nframes_t nframes); + jack_nframes_t event_index); /** Clear an event buffer. @@ -79,11 +75,9 @@ jack_midi_event_get(jack_midi_event_t *event, * function may not be called on an input port's buffer. * * @param port_buffer Port buffer to clear (must be an output port buffer). - * @param nframes Number of valid frames this cycle. */ void -jack_midi_clear_buffer(void *port_buffer, - jack_nframes_t nframes); +jack_midi_clear_buffer(void *port_buffer); /** Get the size of the largest event that can be stored by the port. @@ -92,10 +86,9 @@ jack_midi_clear_buffer(void *port_buffer, * events already stored in the port. * * @param port_buffer Port buffer to check size of. - * @param nframes Number of valid frames this cycle. */ size_t -jack_midi_max_event_size(void* port_buffer, jack_nframes_t nframes); +jack_midi_max_event_size(void* port_buffer); /** Allocate space for an event to be written to an event port buffer. @@ -110,15 +103,13 @@ jack_midi_max_event_size(void* port_buffer, jack_nframes_t nframes); * @param port_buffer Buffer to write event to. * @param time Sample offset of event. * @param data_size Length of event's raw data in bytes. - * @param nframes Number of valid frames this event. * @return Pointer to the beginning of the reserved event's data buffer, or * NULL on error (ie not enough space). */ jack_midi_data_t* jack_midi_event_reserve(void *port_buffer, jack_nframes_t time, - size_t data_size, - jack_nframes_t nframes); + size_t data_size); /** Write an event into an event port buffer. @@ -131,15 +122,13 @@ jack_midi_event_reserve(void *port_buffer, * @param time Sample offset of event. * @param data Message data to be written. * @param data_size Length of @a data in bytes. - * @param nframes Number of valid frames this event. * @return 0 on success, ENOBUFS if there's not enough space in buffer for event. */ int jack_midi_event_write(void *port_buffer, jack_nframes_t time, const jack_midi_data_t *data, - size_t data_size, - jack_nframes_t nframes); + size_t data_size); /** Get the number of events that could not be written to @a port_buffer. @@ -148,12 +137,10 @@ jack_midi_event_write(void *port_buffer, * Currently the only way this can happen is if events are lost on port mixdown. * * @param port_buffer Port to receive count for. - * @param nframes Number of valid frames this cycle. * @returns Number of events that could not be written to @a port_buffer. */ jack_nframes_t -jack_midi_get_lost_event_count(void *port_buffer, - jack_nframes_t nframes); +jack_midi_get_lost_event_count(void *port_buffer); #ifdef __cplusplus diff --git a/jack/port.h b/jack/port.h index e73d1c1..a8b5b44 100644 --- a/jack/port.h +++ b/jack/port.h @@ -120,7 +120,7 @@ typedef struct _jack_port_functions { * A better solution is to make jack_engine_place_buffers to be type-specific, * but this works. */ - void (*buffer_init)(void *buffer, size_t size); + void (*buffer_init)(void *buffer, size_t size, jack_nframes_t); /* Function to mixdown multiple inputs to a buffer. Can be NULL, * indicating that multiple input connections are not legal for diff --git a/jackd/engine.c b/jackd/engine.c index e1c47a4..e701c45 100644 --- a/jackd/engine.c +++ b/jackd/engine.c @@ -332,7 +332,8 @@ jack_engine_place_port_buffers (jack_engine_t* engine, jack_port_type_id_t ptid, jack_shmsize_t one_buffer, jack_shmsize_t size, - unsigned long nports) + unsigned long nports, + jack_nframes_t nframes) { jack_shmsize_t offset; /* shared memory offset */ jack_port_buffer_info_t *bi; @@ -408,7 +409,7 @@ jack_engine_place_port_buffers (jack_engine_t* engine, bi = pti->info; for (i=0; ibuffer_init(shm_segment + bi->offset, one_buffer); + pfuncs->buffer_init(shm_segment + bi->offset, one_buffer, nframes); } pthread_mutex_unlock (&pti->lock); @@ -466,7 +467,7 @@ jack_resize_port_segment (jack_engine_t *engine, } } - jack_engine_place_port_buffers (engine, ptid, one_buffer, size, nports); + jack_engine_place_port_buffers (engine, ptid, one_buffer, size, nports, engine->control->buffer_size); #ifdef USE_MLOCK if (engine->control->real_time) { @@ -3475,12 +3476,19 @@ jack_port_do_register (jack_engine_t *engine, jack_request_t *req) req->x.port_info.client_id)) == NULL) { jack_error ("unknown client id in port registration request"); + jack_unlock_graph (engine); + return -1; + } + + if ((port = jack_get_port_by_name(engine, req->x.port_info.name)) != NULL) { + jack_error ("duplicate port name in port registration request"); + jack_unlock_graph (engine); return -1; } - jack_unlock_graph (engine); if ((port_id = jack_get_free_port (engine)) == (jack_port_id_t) -1) { jack_error ("no ports available!"); + jack_unlock_graph (engine); return -1; } @@ -3501,10 +3509,11 @@ jack_port_do_register (jack_engine_t *engine, jack_request_t *req) if (jack_port_assign_buffer (engine, port)) { jack_error ("cannot assign buffer for port"); + jack_port_release (engine, &engine->internal_ports[port_id]); + jack_unlock_graph (engine); return -1; } - jack_lock_graph (engine); client->ports = jack_slist_prepend (client->ports, port); jack_port_registration_notify (engine, port_id, TRUE); jack_unlock_graph (engine); diff --git a/libjack/midiport.c b/libjack/midiport.c index 905bef1..dcc3c04 100644 --- a/libjack/midiport.c +++ b/libjack/midiport.c @@ -28,6 +28,7 @@ typedef struct _jack_midi_port_info_private { + jack_nframes_t nframes; /**< Number of frames in buffer */ size_t buffer_size; /**< Size of buffer in bytes */ jack_nframes_t event_count; /**< Number of events stored in this buffer */ jack_nframes_t last_write_loc; /**< Used for both writing and mixdown */ @@ -44,11 +45,13 @@ typedef struct _jack_midi_port_internal_event { /* jack_midi_port_functions.buffer_init */ static void jack_midi_buffer_init(void *port_buffer, - size_t buffer_size) + size_t buffer_size, + jack_nframes_t nframes) { jack_midi_port_info_private_t *info = (jack_midi_port_info_private_t *) port_buffer; /* We can also add some magic field to midi buffer to validate client calls */ + info->nframes = nframes; info->buffer_size = buffer_size; info->event_count = 0; info->last_write_loc = 0; @@ -57,8 +60,7 @@ jack_midi_buffer_init(void *port_buffer, jack_nframes_t -jack_midi_get_event_count(void *port_buffer, - jack_nframes_t nframes) +jack_midi_get_event_count(void *port_buffer) { jack_midi_port_info_private_t *info = (jack_midi_port_info_private_t *) port_buffer; @@ -69,8 +71,7 @@ jack_midi_get_event_count(void *port_buffer, int jack_midi_event_get(jack_midi_event_t *event, void *port_buffer, - jack_nframes_t event_idx, - jack_nframes_t nframes) + jack_nframes_t event_idx) { jack_midi_port_internal_event_t *port_event; jack_midi_port_info_private_t *info = @@ -91,8 +92,7 @@ jack_midi_event_get(jack_midi_event_t *event, size_t -jack_midi_max_event_size(void *port_buffer, - jack_nframes_t nframes) +jack_midi_max_event_size(void *port_buffer) { jack_midi_port_info_private_t *info = (jack_midi_port_info_private_t *) port_buffer; @@ -116,8 +116,7 @@ jack_midi_max_event_size(void *port_buffer, jack_midi_data_t* jack_midi_event_reserve(void *port_buffer, jack_nframes_t time, - size_t data_size, - jack_nframes_t nframes) + size_t data_size) { jack_midi_data_t *retbuf = (jack_midi_data_t *) port_buffer; @@ -128,11 +127,11 @@ jack_midi_event_reserve(void *port_buffer, size_t buffer_size = info->buffer_size; - if (time < 0 || time >= nframes) - return NULL; + if (time < 0 || time >= info->nframes) + goto failed; if (info->event_count > 0 && time < event_buffer[info->event_count-1].time) - return NULL; + goto failed; /* Check if data_size is >0 and there is enough space in the buffer for the event. */ if (data_size <=0 || @@ -140,7 +139,7 @@ jack_midi_event_reserve(void *port_buffer, + ((info->event_count + 1) * sizeof(jack_midi_port_internal_event_t)) + data_size > buffer_size) { - return NULL; + goto failed; } else { info->last_write_loc += data_size; retbuf = &retbuf[buffer_size - 1 - info->last_write_loc]; @@ -151,6 +150,9 @@ jack_midi_event_reserve(void *port_buffer, info->event_count += 1; return retbuf; } + failed: + info->events_lost++; + return NULL; } @@ -158,11 +160,10 @@ int jack_midi_event_write(void *port_buffer, jack_nframes_t time, const jack_midi_data_t *data, - size_t data_size, - jack_nframes_t nframes) + size_t data_size) { jack_midi_data_t *retbuf = - jack_midi_event_reserve(port_buffer, time, data_size, nframes); + jack_midi_event_reserve(port_buffer, time, data_size); if (retbuf) { memcpy(retbuf, data, data_size); @@ -179,8 +180,7 @@ jack_midi_event_write(void *port_buffer, * been reset. */ void -jack_midi_clear_buffer(void *port_buffer, - jack_nframes_t nframes) +jack_midi_clear_buffer(void *port_buffer) { jack_midi_port_info_private_t *info = (jack_midi_port_info_private_t *) port_buffer; @@ -193,14 +193,14 @@ jack_midi_clear_buffer(void *port_buffer, /* jack_midi_port_functions.mixdown */ static void -jack_midi_port_mixdown(jack_port_t *port, - jack_nframes_t nframes) +jack_midi_port_mixdown(jack_port_t *port, jack_nframes_t nframes) { JSList *node; jack_port_t *input; jack_nframes_t num_events = 0; jack_nframes_t i = 0; int err = 0; + jack_nframes_t lost_events = 0; /* The next (single) event to mix in to the buffer */ jack_midi_port_info_private_t *earliest_info; @@ -211,7 +211,7 @@ jack_midi_port_mixdown(jack_port_t *port, jack_midi_port_internal_event_t *in_events; /* Corresponds to in_info */ jack_midi_port_info_private_t *out_info; /* Output 'buffer' */ - jack_midi_clear_buffer(port->mix_buffer, nframes); + jack_midi_clear_buffer(port->mix_buffer); out_info = (jack_midi_port_info_private_t *) port->mix_buffer; @@ -228,6 +228,7 @@ jack_midi_port_mixdown(jack_port_t *port, in_info = (jack_midi_port_info_private_t *) jack_output_port_buffer(input); num_events += in_info->event_count; + lost_events += in_info->events_lost; in_info->last_write_loc = 0; } @@ -267,8 +268,7 @@ jack_midi_port_mixdown(jack_port_t *port, jack_port_buffer(port), earliest_event->time, &earliest_buffer[earliest_event->byte_offset], - earliest_event->size, - nframes); + earliest_event->size); earliest_info->last_write_loc++; @@ -279,12 +279,14 @@ jack_midi_port_mixdown(jack_port_t *port, } } assert(out_info->event_count == num_events - out_info->events_lost); + + // inherit total lost events count from all connected ports. + out_info->events_lost += lost_events; } jack_nframes_t -jack_midi_get_lost_event_count(void *port_buffer, - jack_nframes_t nframes) +jack_midi_get_lost_event_count(void *port_buffer) { return ((jack_midi_port_info_private_t *) port_buffer)->events_lost; } diff --git a/libjack/port.c b/libjack/port.c index db43f39..e87cdf1 100644 --- a/libjack/port.c +++ b/libjack/port.c @@ -37,7 +37,8 @@ #include "local.h" static void jack_generic_buffer_init(void *port_buffer, - size_t buffer_size); + size_t buffer_size, + jack_nframes_t nframes); static void jack_audio_port_mixdown (jack_port_t *port, jack_nframes_t nframes); @@ -359,8 +360,8 @@ jack_get_port_functions(jack_port_type_id_t ptid) * Fills buffer with zeroes. For audio ports, engine->silent_buffer relies on it. */ static void -jack_generic_buffer_init(void *buffer, size_t size) -{ +jack_generic_buffer_init(void *buffer, size_t size, jack_nframes_t nframes) +{ memset(buffer, 0, size); } @@ -749,7 +750,7 @@ jack_port_get_buffer (jack_port_t *port, jack_nframes_t nframes) * sizeof (jack_default_audio_sample_t) * nframes; port->mix_buffer = jack_pool_alloc (buffer_size); - port->fptr.buffer_init (port->mix_buffer, buffer_size); + port->fptr.buffer_init (port->mix_buffer, buffer_size, nframes); } port->fptr.mixdown (port, nframes); return (void *) port->mix_buffer;