/*
 * 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 <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
#include <time.h>
#include <limits.h>
#include <ctype.h>
#include <alsa/asoundlib.h>
#include <jack/thread.h>
#include <jack/ringbuffer.h>
#include <jack/midiport.h>
#include "midi_pack.h"
#include "midi_unpack.h"


#ifdef STANDALONE
#define MESSAGE(...) fprintf (stderr, __VA_ARGS__)
#else
#include "messagebuffer.h"
#endif

#define info_log(...)  MESSAGE (__VA_ARGS__)
#define error_log(...) MESSAGE (__VA_ARGS__)

#ifdef JACK_MIDI_DEBUG
#define debug_log(...) MESSAGE (__VA_ARGS__)
#else
#define debug_log(...)
#endif


enum {
	NANOSLEEP_RESOLUTION = 7000
};

#define NFRAMES_INF INT_MAX

enum {
#ifndef JACK_MIDI_DEBUG
	MAX_PFDS = 64,
	MAX_PORTS = MAX_PFDS - 1,
	MAX_EVENTS = 4096,
	MAX_DATA = 64 * 1024,
	MIDI_THREAD_PRIO = 80
#else
	MAX_PFDS = 6,
	MAX_PORTS = MAX_PFDS - 1,
	MAX_EVENTS = 16,
	MAX_DATA = 64,
	MIDI_THREAD_PRIO = 80
#endif
};

enum PortState {
	PORT_DESTROYED,
	PORT_CREATED,
	PORT_ADDED_TO_JACK,
	PORT_ADDED_TO_MIDI,
	PORT_REMOVED_FROM_MIDI,
	PORT_REMOVED_FROM_JACK,
	PORT_ZOMBIFIED,
};

typedef struct {
	int id[4]; //card, dev, dir, sub;
} alsa_id_t;


typedef struct {
	jack_time_t time;
	int size;
	int overruns;
} event_head_t;


typedef struct midi_port_t midi_port_t;
struct midi_port_t {
	midi_port_t *next;

	enum PortState state;

	alsa_id_t id;
	char dev[16];
	char name[64];

	jack_port_t *jack;
	snd_rawmidi_t *rawmidi;
	int npfds;
	int is_ready;

	jack_ringbuffer_t *event_ring;
	jack_ringbuffer_t *data_ring;

};

typedef struct input_port_t {
	midi_port_t base;

	// jack
	midi_unpack_t unpack;

	// midi
	int overruns;
} input_port_t;

typedef struct output_port_t {
	midi_port_t base;

	// jack
	midi_pack_t packer;

	// midi
	event_head_t next_event;
	int todo;
} output_port_t;

typedef struct alsa_rawmidi_t alsa_rawmidi_t;

typedef struct {
	alsa_rawmidi_t *midi;
	midi_port_t *port;
	void *buffer;
	jack_time_t frame_time;
	jack_nframes_t nframes;
} process_jack_t;

typedef struct {
	alsa_rawmidi_t *midi;
	int mode;
	midi_port_t *port;
	struct pollfd *rpfds;
	struct pollfd *wpfds;
	int max_pfds;
	jack_nframes_t cur_frames;
	jack_time_t cur_time;
	jack_time_t next_time;
} process_midi_t;

typedef struct midi_stream_t {
	alsa_rawmidi_t *owner;
	int mode;
	const char *name;
	pthread_t thread;
	int wake_pipe[2];

	struct {
		jack_ringbuffer_t *new_ports;
		int nports;
		midi_port_t *ports[MAX_PORTS];
	} jack, midi;

	size_t port_size;
	int (*port_init)(alsa_rawmidi_t *midi, midi_port_t *port);
	void (*port_close)(alsa_rawmidi_t *midi, midi_port_t *port);
	void (*process_jack)(process_jack_t *j);
	int (*process_midi)(process_midi_t *m);
} midi_stream_t;


struct alsa_rawmidi_t {
	alsa_midi_t ops;

	jack_client_t *client;
	int keep_walking;

	struct {
		pthread_t thread;
		midi_port_t *ports;
		int wake_pipe[2];
	} scan;

	midi_stream_t in;
	midi_stream_t out;
};

static int input_port_init(alsa_rawmidi_t *midi, midi_port_t *port);
static void input_port_close(alsa_rawmidi_t *midi, midi_port_t *port);

static void do_jack_input(process_jack_t *j);
static int do_midi_input(process_midi_t *m);

static int output_port_init(alsa_rawmidi_t *midi, midi_port_t *port);
static void output_port_close(alsa_rawmidi_t *midi, midi_port_t *port);

static void do_jack_output(process_jack_t *j);
static int do_midi_output(process_midi_t *m);



static
int stream_init (midi_stream_t *s, alsa_rawmidi_t *midi, const char *name)
{
	s->owner = midi;
	s->name = name;
	if (pipe (s->wake_pipe) == -1) {
		s->wake_pipe[0] = -1;
		error_log ("pipe() in stream_init(%s) failed: %s", name, strerror (errno));
		return -errno;
	}
	s->jack.new_ports = jack_ringbuffer_create (sizeof(midi_port_t*) * MAX_PORTS);
	s->midi.new_ports = jack_ringbuffer_create (sizeof(midi_port_t*) * MAX_PORTS);
	if (!s->jack.new_ports || !s->midi.new_ports) {
		return -ENOMEM;
	}
	return 0;
}

static
void stream_close (midi_stream_t *s)
{
	if (s->wake_pipe[0] != -1) {
		close (s->wake_pipe[0]);
		close (s->wake_pipe[1]);
	}
	if (s->jack.new_ports) {
		jack_ringbuffer_free (s->jack.new_ports);
	}
	if (s->midi.new_ports) {
		jack_ringbuffer_free (s->midi.new_ports);
	}
}

static void alsa_rawmidi_delete(alsa_midi_t *m);
static int alsa_rawmidi_attach(alsa_midi_t *m);
static int alsa_rawmidi_detach(alsa_midi_t *m);
static int alsa_rawmidi_start(alsa_midi_t *m);
static int alsa_rawmidi_stop(alsa_midi_t *m);
static void alsa_rawmidi_read(alsa_midi_t *m, jack_nframes_t nframes);
static void alsa_rawmidi_write(alsa_midi_t *m, jack_nframes_t nframes);

alsa_midi_t* alsa_rawmidi_new (jack_client_t *jack)
{
	alsa_rawmidi_t *midi = calloc (1, sizeof(alsa_rawmidi_t));

	if (!midi) {
		goto fail_0;
	}
	midi->client = jack;
	if (pipe (midi->scan.wake_pipe) == -1) {
		error_log ("pipe() in alsa_midi_new failed: %s", strerror (errno));
		goto fail_1;
	}

	if (stream_init (&midi->in, midi, "in")) {
		goto fail_2;
	}
	midi->in.mode = POLLIN;
	midi->in.port_size = sizeof(input_port_t);
	midi->in.port_init = input_port_init;
	midi->in.port_close = input_port_close;
	midi->in.process_jack = do_jack_input;
	midi->in.process_midi = do_midi_input;

	if (stream_init (&midi->out, midi, "out")) {
		goto fail_3;
	}
	midi->out.mode = POLLOUT;
	midi->out.port_size = sizeof(output_port_t);
	midi->out.port_init = output_port_init;
	midi->out.port_close = output_port_close;
	midi->out.process_jack = do_jack_output;
	midi->out.process_midi = do_midi_output;

	midi->ops.destroy = alsa_rawmidi_delete;
	midi->ops.attach = alsa_rawmidi_attach;
	midi->ops.detach = alsa_rawmidi_detach;
	midi->ops.start = alsa_rawmidi_start;
	midi->ops.stop = alsa_rawmidi_stop;
	midi->ops.read = alsa_rawmidi_read;
	midi->ops.write = alsa_rawmidi_write;

	return &midi->ops;
fail_3:
	stream_close (&midi->out);
fail_2:
	stream_close (&midi->in);
	close (midi->scan.wake_pipe[1]);
	close (midi->scan.wake_pipe[0]);
fail_1:
	free (midi);
fail_0:
	return NULL;
}

static
midi_port_t** scan_port_del(alsa_rawmidi_t *midi, midi_port_t **list);

static
void alsa_rawmidi_delete (alsa_midi_t *m)
{
	alsa_rawmidi_t *midi = (alsa_rawmidi_t*)m;

	alsa_rawmidi_detach (m);

	stream_close (&midi->out);
	stream_close (&midi->in);
	close (midi->scan.wake_pipe[0]);
	close (midi->scan.wake_pipe[1]);

	free (midi);
}

static void* scan_thread(void *);
static void *midi_thread(void *arg);

static
int alsa_rawmidi_attach (alsa_midi_t *m)
{
	return 0;
}

static
int alsa_rawmidi_detach (alsa_midi_t *m)
{
	alsa_rawmidi_t *midi = (alsa_rawmidi_t*)m;
	midi_port_t **list;

	alsa_rawmidi_stop (m);

	list = &midi->scan.ports;
	while (*list) {
		(*list)->state = PORT_REMOVED_FROM_JACK;
		list = scan_port_del (midi, list);
	}
	return 0;
}

static
int alsa_rawmidi_start (alsa_midi_t *m)
{
	alsa_rawmidi_t *midi = (alsa_rawmidi_t*)m;
	int err;
	char c = 'q';

	if (midi->keep_walking == 1) {
		return -EALREADY;
	}

	midi->keep_walking = 1;
	if ((err = jack_client_create_thread (midi->client, &midi->in.thread, MIDI_THREAD_PRIO, jack_is_realtime (midi->client), midi_thread, &midi->in))) {
		midi->keep_walking = 0;
		return err;
	}
	if ((err = jack_client_create_thread (midi->client, &midi->out.thread, MIDI_THREAD_PRIO, jack_is_realtime (midi->client), midi_thread, &midi->out))) {
		midi->keep_walking = 0;
		write (midi->in.wake_pipe[1], &c, 1);
		pthread_join (midi->in.thread, NULL);
		return err;
	}
	if ((err = jack_client_create_thread (midi->client, &midi->scan.thread, 0, 0, scan_thread, midi))) {
		midi->keep_walking = 0;
		write (midi->in.wake_pipe[1], &c, 1);
		write (midi->out.wake_pipe[1], &c, 1);
		pthread_join (midi->in.thread, NULL);
		pthread_join (midi->out.thread, NULL);
		return err;
	}
	return 0;
}

static
int alsa_rawmidi_stop (alsa_midi_t *m)
{
	alsa_rawmidi_t *midi = (alsa_rawmidi_t*)m;
	char c = 'q';

	if (midi->keep_walking == 0) {
		return -EALREADY;
	}
	midi->keep_walking = 0;
	write (midi->in.wake_pipe[1], &c, 1);
	write (midi->out.wake_pipe[1], &c, 1);
	write (midi->scan.wake_pipe[1], &c, 1);
	pthread_join (midi->in.thread, NULL);
	pthread_join (midi->out.thread, NULL);
	pthread_join (midi->scan.thread, NULL);
	// ports are freed in alsa_midi_detach()
	return 0;
}

static void jack_process(midi_stream_t *str, jack_nframes_t nframes);

static
void alsa_rawmidi_read (alsa_midi_t *m, jack_nframes_t nframes)
{
	alsa_rawmidi_t *midi = (alsa_rawmidi_t*)m;

	jack_process (&midi->in, nframes);
}

static
void alsa_rawmidi_write (alsa_midi_t *m, jack_nframes_t nframes)
{
	alsa_rawmidi_t *midi = (alsa_rawmidi_t*)m;

	jack_process (&midi->out, nframes);
}

/*
 * -----------------------------------------------------------------------------
 */
static inline
int can_pass (size_t sz, jack_ringbuffer_t *in, jack_ringbuffer_t *out)
{
	return jack_ringbuffer_read_space (in) >= sz && jack_ringbuffer_write_space (out) >= sz;
}

static
void midi_port_init (const alsa_rawmidi_t *midi, midi_port_t *port, snd_rawmidi_info_t *info, const alsa_id_t *id)
{
	const char *name;
	char *c;

	port->id = *id;
	snprintf (port->dev, sizeof(port->dev), "hw:%d,%d,%d", id->id[0], id->id[1], id->id[3]);
	name = snd_rawmidi_info_get_subdevice_name (info);
	if (!strlen (name)) {
		name = snd_rawmidi_info_get_name (info);
	}
	snprintf (port->name, sizeof(port->name), "%s %s %s", port->id.id[2] ? "out" : "in", port->dev, name);

	// replace all offending characters with '-'
	for (c = port->name; *c; ++c)
		if (!isalnum (*c)) {
			*c = '-';
		}

	port->state = PORT_CREATED;
}

static
inline int midi_port_open_jack (const alsa_rawmidi_t *midi, midi_port_t *port, int type, const char *name)
{
	port->jack = jack_port_register (midi->client, name, JACK_DEFAULT_MIDI_TYPE,
					 type | JackPortIsPhysical | JackPortIsTerminal, 0);
	return port->jack == NULL;
}

static
int midi_port_open (const alsa_rawmidi_t *midi, midi_port_t *port)
{
	int err;
	int type;
	char name[64];
	snd_rawmidi_t **in = NULL;
	snd_rawmidi_t **out = NULL;

	if (port->id.id[2] == 0) {
		in = &port->rawmidi;
		type = JackPortIsOutput;
	} else {
		out = &port->rawmidi;
		type = JackPortIsInput;
	}

	if ((err = snd_rawmidi_open (in, out, port->dev, SND_RAWMIDI_NONBLOCK)) < 0) {
		return err;
	}

	/* Some devices (emu10k1) have subdevs with the same name,
	 * and we need to generate unique port name for jack */
	snprintf (name, sizeof(name), "%s", port->name);
	if (midi_port_open_jack (midi, port, type, name)) {
		int num;
		num = port->id.id[3] ? port->id.id[3] : port->id.id[1];
		snprintf (name, sizeof(name), "%s %d", port->name, num);
		if (midi_port_open_jack (midi, port, type, name)) {
			return 2;
		}
	}
	if ((port->event_ring = jack_ringbuffer_create (MAX_EVENTS * sizeof(event_head_t))) == NULL) {
		return 3;
	}
	if ((port->data_ring = jack_ringbuffer_create (MAX_DATA)) == NULL) {
		return 4;
	}

	return 0;
}

static
void midi_port_close (const alsa_rawmidi_t *midi, midi_port_t *port)
{
	if (port->data_ring) {
		jack_ringbuffer_free (port->data_ring);
		port->data_ring = NULL;
	}
	if (port->event_ring) {
		jack_ringbuffer_free (port->event_ring);
		port->event_ring = NULL;
	}
	if (port->jack) {
		jack_port_unregister (midi->client, port->jack);
		port->jack = NULL;
	}
	if (port->rawmidi) {
		snd_rawmidi_close (port->rawmidi);
		port->rawmidi = NULL;
	}
}

/*
 * ------------------------- Port scanning -------------------------------
 */

static
int alsa_id_before (const alsa_id_t *p1, const alsa_id_t *p2)
{
	int i;

	for (i = 0; i < 4; ++i) {
		if (p1->id[i] < p2->id[i]) {
			return 1;
		} else if (p1->id[i] > p2->id[i]) {
			return 0;
		}
	}
	return 0;
}

static
void alsa_get_id (alsa_id_t *id, snd_rawmidi_info_t *info)
{
	id->id[0] = snd_rawmidi_info_get_card (info);
	id->id[1] = snd_rawmidi_info_get_device (info);
	id->id[2] = snd_rawmidi_info_get_stream (info) == SND_RAWMIDI_STREAM_OUTPUT ? 1 : 0;
	id->id[3] = snd_rawmidi_info_get_subdevice (info);
}

static inline
void alsa_error (const char *func, int err)
{
	error_log ("%s() failed", snd_strerror (err));
}

typedef struct {
	alsa_rawmidi_t *midi;
	midi_port_t **iterator;
	snd_ctl_t *ctl;
	snd_rawmidi_info_t *info;
} scan_t;

static midi_port_t** scan_port_del(alsa_rawmidi_t *midi, midi_port_t **list);

static
void scan_cleanup (alsa_rawmidi_t *midi)
{
	midi_port_t **list = &midi->scan.ports;

	while (*list)
		list = scan_port_del (midi, list);
}

static void scan_card(scan_t *scan);
static midi_port_t** scan_port_open(alsa_rawmidi_t *midi, midi_port_t **list);

void scan_cycle (alsa_rawmidi_t *midi)
{
	int card = -1, err;
	scan_t scan;
	midi_port_t **ports;

	//debug_log("scan: cleanup");
	scan_cleanup (midi);

	scan.midi = midi;
	scan.iterator = &midi->scan.ports;
	snd_rawmidi_info_alloca (&scan.info);

	//debug_log("scan: rescan");
	while ((err = snd_card_next (&card)) >= 0 && card >= 0) {
		char name[32];
		snprintf (name, sizeof(name), "hw:%d", card);
		if ((err = snd_ctl_open (&scan.ctl, name, SND_CTL_NONBLOCK)) >= 0) {
			scan_card (&scan);
			snd_ctl_close (scan.ctl);
		} else {
			alsa_error ("scan: snd_ctl_open", err);
		}
	}

	// delayed open to workaround alsa<1.0.14 bug (can't open more than 1 subdevice if ctl is opened).
	ports = &midi->scan.ports;
	while (*ports) {
		midi_port_t *port = *ports;
		if (port->state == PORT_CREATED) {
			ports = scan_port_open (midi, ports);
		} else {
			ports = &port->next;
		}
	}
}

static void scan_device(scan_t *scan);

static
void scan_card (scan_t *scan)
{
	int device = -1;
	int err;

	while ((err = snd_ctl_rawmidi_next_device (scan->ctl, &device)) >= 0 && device >= 0) {
		snd_rawmidi_info_set_device (scan->info, device);

		snd_rawmidi_info_set_stream (scan->info, SND_RAWMIDI_STREAM_INPUT);
		snd_rawmidi_info_set_subdevice (scan->info, 0);
		if ((err = snd_ctl_rawmidi_info (scan->ctl, scan->info)) >= 0) {
			scan_device (scan);
		} else if (err != -ENOENT) {
			alsa_error ("scan: snd_ctl_rawmidi_info on device", err);
		}

		snd_rawmidi_info_set_stream (scan->info, SND_RAWMIDI_STREAM_OUTPUT);
		snd_rawmidi_info_set_subdevice (scan->info, 0);
		if ((err = snd_ctl_rawmidi_info (scan->ctl, scan->info)) >= 0) {
			scan_device (scan);
		} else if (err != -ENOENT) {
			alsa_error ("scan: snd_ctl_rawmidi_info on device", err);
		}
	}
}

static void scan_port_update(scan_t *scan);

static
void scan_device (scan_t *scan)
{
	int err;
	int sub, nsubs = 0;

	nsubs = snd_rawmidi_info_get_subdevices_count (scan->info);

	for (sub = 0; sub < nsubs; ++sub) {
		snd_rawmidi_info_set_subdevice (scan->info, sub);
		if ((err = snd_ctl_rawmidi_info (scan->ctl, scan->info)) < 0) {
			alsa_error ("scan: snd_ctl_rawmidi_info on subdevice", err);
			continue;
		}
		scan_port_update (scan);
	}
}

static midi_port_t** scan_port_add(scan_t *scan, const alsa_id_t *id, midi_port_t **list);

static
void scan_port_update (scan_t *scan)
{
	midi_port_t **list = scan->iterator;
	alsa_id_t id;

	alsa_get_id (&id, scan->info);

	while (*list && alsa_id_before (&(*list)->id, &id))
		list = scan_port_del (scan->midi, list);

	if (!*list || alsa_id_before (&id, &(*list)->id)) {
		list = scan_port_add (scan, &id, list);
	} else if (*list) {
		list = &(*list)->next;
	}

	scan->iterator = list;
}

static
midi_port_t** scan_port_add (scan_t *scan, const alsa_id_t *id, midi_port_t **list)
{
	midi_port_t *port;
	midi_stream_t *str = id->id[2] ? &scan->midi->out : &scan->midi->in;

	port = calloc (1, str->port_size);
	if (!port) {
		return list;
	}
	midi_port_init (scan->midi, port, scan->info, id);

	port->next = *list;
	*list = port;
	error_log ("scan: added port %s %s", port->dev, port->name);
	return &port->next;
}

static
midi_port_t** scan_port_open (alsa_rawmidi_t *midi, midi_port_t **list)
{
	midi_stream_t *str;
	midi_port_t *port;

	port = *list;
	str = port->id.id[2] ? &midi->out : &midi->in;

	if (jack_ringbuffer_write_space (str->jack.new_ports) < sizeof(port)) {
		goto fail_0;
	}

	if (midi_port_open (midi, port)) {
		goto fail_1;
	}
	if ((str->port_init)(midi, port)) {
		goto fail_2;
	}

	port->state = PORT_ADDED_TO_JACK;
	jack_ringbuffer_write (str->jack.new_ports, (char*)&port, sizeof(port));

	error_log ("scan: opened port %s %s", port->dev, port->name);
	return &port->next;

fail_2:
	(str->port_close)(midi, port);
fail_1:
	midi_port_close (midi, port);
	port->state = PORT_ZOMBIFIED;
fail_0:
	error_log ("scan: can't open port %s %s", port->dev, port->name);
	return &port->next;
}

static
midi_port_t** scan_port_del (alsa_rawmidi_t *midi, midi_port_t **list)
{
	midi_port_t *port = *list;

	if (port->state == PORT_REMOVED_FROM_JACK) {
		error_log ("scan: deleted port %s %s", port->dev, port->name);
		*list = port->next;
		if (port->id.id[2] ) {
			(midi->out.port_close)(midi, port);
		} else {
			(midi->in.port_close)(midi, port);
		}
		midi_port_close (midi, port);
		free (port);
		return list;
	} else {
		//debug_log("can't delete port %s, wrong state: %d", port->name, (int)port->state);
		return &port->next;
	}
}

void* scan_thread (void *arg)
{
	alsa_rawmidi_t *midi = arg;
	struct pollfd wakeup;

	wakeup.fd = midi->scan.wake_pipe[0];
	wakeup.events = POLLIN | POLLERR | POLLNVAL;
	while (midi->keep_walking) {
		int res;
		//error_log("scanning....");
		scan_cycle (midi);
		res = poll (&wakeup, 1, 2000);
		if (res > 0) {
			char c;
			read (wakeup.fd, &c, 1);
		} else if (res < 0 && errno != EINTR) {
			break;
		}
	}
	return NULL;
}


/*
 * ------------------------------- Input/Output  ------------------------------
 */

static
void jack_add_ports (midi_stream_t *str)
{
	midi_port_t *port;

	while (can_pass (sizeof(port), str->jack.new_ports, str->midi.new_ports) && str->jack.nports < MAX_PORTS) {
		jack_ringbuffer_read (str->jack.new_ports, (char*)&port, sizeof(port));
		str->jack.ports[str->jack.nports++] = port;
		port->state = PORT_ADDED_TO_MIDI;
		jack_ringbuffer_write (str->midi.new_ports, (char*)&port, sizeof(port));
	}
}

static
void jack_process (midi_stream_t *str, jack_nframes_t nframes)
{
	int r, w;
	process_jack_t proc;
	jack_nframes_t cur_frames;

	if (!str->owner->keep_walking) {
		return;
	}

	proc.midi = str->owner;
	proc.nframes = nframes;
	proc.frame_time = jack_last_frame_time (proc.midi->client);
	cur_frames = jack_frame_time (proc.midi->client);
	if (proc.frame_time + proc.nframes < cur_frames) {
		int periods_lost = (cur_frames - proc.frame_time) / proc.nframes;
		proc.frame_time += periods_lost * proc.nframes;
		debug_log ("xrun detected: %d periods lost", periods_lost);
	}

	// process existing ports
	for (r = 0, w = 0; r < str->jack.nports; ++r) {
		midi_port_t *port = str->jack.ports[r];
		proc.port = port;

		assert (port->state > PORT_ADDED_TO_JACK && port->state < PORT_REMOVED_FROM_JACK);

		proc.buffer = jack_port_get_buffer (port->jack, nframes);
		if (str->mode == POLLIN) {
			jack_midi_clear_buffer (proc.buffer);
		}

		if (port->state == PORT_REMOVED_FROM_MIDI) {
			port->state = PORT_REMOVED_FROM_JACK;   // this signals to scan thread
			continue;                               // this effectively removes port from the midi->in.jack.ports[]
		}

		(str->process_jack)(&proc);

		if (r != w) {
			str->jack.ports[w] = port;
		}
		++w;
	}
	if (str->jack.nports != w) {
		debug_log ("jack_%s: nports %d -> %d", str->name, str->jack.nports, w);
	}
	str->jack.nports = w;

	jack_add_ports (str); // it makes no sense to add them earlier since they have no data yet

	// wake midi thread
	write (str->wake_pipe[1], &r, 1);
}

static
void *midi_thread (void *arg)
{
	midi_stream_t *str = arg;
	alsa_rawmidi_t *midi = str->owner;
	struct pollfd pfds[MAX_PFDS];
	int npfds;
	jack_time_t wait_nsec = 1000 * 1000 * 1000; // 1 sec
	process_midi_t proc;

	proc.midi = midi;
	proc.mode = str->mode;

	pfds[0].fd = str->wake_pipe[0];
	pfds[0].events = POLLIN | POLLERR | POLLNVAL;
	npfds = 1;

	//debug_log("midi_thread(%s): enter", str->name);

	while (midi->keep_walking) {
		int poll_timeout;
		int wait_nanosleep;
		int r = 1, w = 1;       // read,write pos in pfds
		int rp = 0, wp = 0;     // read, write pos in ports

		// sleep
		//if (wait_nsec != 1000*1000*1000) {
		//	debug_log("midi_thread(%s): ", str->name);
		//	assert (wait_nsec == 1000*1000*1000);
		//}
		poll_timeout = wait_nsec / (1000 * 1000);
		wait_nanosleep = wait_nsec % (1000 * 1000);
		if (wait_nanosleep > NANOSLEEP_RESOLUTION) {
			struct timespec ts;
			ts.tv_sec = 0;
			ts.tv_nsec = wait_nanosleep;
			clock_nanosleep (CLOCK_MONOTONIC, 0, &ts, NULL);
		}
		int res = poll ((struct pollfd*)&pfds, npfds, poll_timeout);
		//debug_log("midi_thread(%s): poll exit: %d", str->name, res);
		if (!midi->keep_walking) {
			break;
		}
		if (res < 0) {
			if (errno == EINTR) {
				continue;
			}
			error_log ("midi_thread(%s) poll failed: %s", str->name, strerror (errno));
			break;
		}

		// check wakeup pipe
		if (pfds[0].revents & ~POLLIN) {
			break;
		}
		if (pfds[0].revents & POLLIN) {
			char c;
			read (pfds[0].fd, &c, 1);
		}

		// add new ports
		while (jack_ringbuffer_read_space (str->midi.new_ports) >= sizeof(midi_port_t*) && str->midi.nports < MAX_PORTS) {
			midi_port_t *port;
			jack_ringbuffer_read (str->midi.new_ports, (char*)&port, sizeof(port));
			str->midi.ports[str->midi.nports++] = port;
			debug_log ("midi_thread(%s): added port %s", str->name, port->name);
		}

//		if (res == 0)
//			continue;

		// process ports
		proc.cur_time = 0; //jack_frame_time(midi->client);
		proc.next_time = NFRAMES_INF;

		for (rp = 0; rp < str->midi.nports; ++rp) {
			midi_port_t *port = str->midi.ports[rp];
			proc.cur_time = jack_frame_time (midi->client);
			proc.port = port;
			proc.rpfds = &pfds[r];
			proc.wpfds = &pfds[w];
			proc.max_pfds = MAX_PFDS - w;
			r += port->npfds;
			if (!(str->process_midi)(&proc)) {
				port->state = PORT_REMOVED_FROM_MIDI;   // this signals to jack thread
				continue;                               // this effectively removes port from array
			}
			w += port->npfds;
			if (rp != wp) {
				str->midi.ports[wp] = port;
			}
			++wp;
		}
		if (str->midi.nports != wp) {
			debug_log ("midi_%s: nports %d -> %d", str->name, str->midi.nports, wp);
		}
		str->midi.nports = wp;
		if (npfds != w) {
			debug_log ("midi_%s: npfds %d -> %d", str->name, npfds, w);
		}
		npfds = w;

		/*
		 * Input : ports do not set proc.next_time.
		 * Output: port sets proc.next_time ONLY if it does not have queued data.
		 * So, zero timeout will not cause busy-looping.
		 */
		if (proc.next_time < proc.cur_time) {
			debug_log ("%s: late: next_time = %d, cur_time = %d", str->name, (int)proc.next_time, (int)proc.cur_time);
			wait_nsec = 0; // we are late
		} else if (proc.next_time != NFRAMES_INF) {
			jack_time_t wait_frames = proc.next_time - proc.cur_time;
			jack_nframes_t rate = jack_get_sample_rate (midi->client);
			wait_nsec = (wait_frames * (1000 * 1000 * 1000)) / rate;
			debug_log ("midi_%s: timeout = %d", str->name, (int)wait_frames);
		} else {
			wait_nsec = 1000 * 1000 * 1000;
		}
		//debug_log("midi_thread(%s): wait_nsec = %lld", str->name, wait_nsec);
	}
	return NULL;
}

static
int midi_is_ready (process_midi_t *proc)
{
	midi_port_t *port = proc->port;

	if (port->npfds) {
		unsigned short revents = 0;
		int res = snd_rawmidi_poll_descriptors_revents (port->rawmidi, proc->rpfds, port->npfds, &revents);
		if (res) {
			error_log ("snd_rawmidi_poll_descriptors_revents failed on port %s with: %s", port->name, snd_strerror (res));
			return 0;
		}

		if (revents & ~proc->mode) {
			debug_log ("midi: port %s failed", port->name);
			return 0;
		}
		if (revents & proc->mode) {
			port->is_ready = 1;
			debug_log ("midi: is_ready %s", port->name);
		}
	}
	return 1;
}

static
int midi_update_pfds (process_midi_t *proc)
{
	midi_port_t *port = proc->port;

	if (port->npfds == 0) {
		port->npfds = snd_rawmidi_poll_descriptors_count (port->rawmidi);
		if (port->npfds > proc->max_pfds) {
			debug_log ("midi: not enough pfds for port %s", port->name);
			return 0;
		}
		snd_rawmidi_poll_descriptors (port->rawmidi, proc->wpfds, port->npfds);
	} else if (proc->rpfds != proc->wpfds) {
		memmove (proc->wpfds, proc->rpfds, sizeof(struct pollfd) * port->npfds);
	}
	return 1;
}

/*
 * ------------------------------------ Input ------------------------------
 */

static
int input_port_init (alsa_rawmidi_t *midi, midi_port_t *port)
{
	input_port_t *in = (input_port_t*)port;

	midi_unpack_init (&in->unpack);
	return 0;
}

static
void input_port_close (alsa_rawmidi_t *midi, midi_port_t *port)
{
}

/*
 * Jack-level input.
 */

static
void do_jack_input (process_jack_t *p)
{
	input_port_t *port = (input_port_t*)p->port;
	event_head_t event;

	while (jack_ringbuffer_read_space (port->base.event_ring) >= sizeof(event)) {
		jack_ringbuffer_data_t vec[2];
		jack_nframes_t time;
		int i, todo;

		jack_ringbuffer_read (port->base.event_ring, (char*)&event, sizeof(event));
		// TODO: take into account possible warping
		if ((event.time + p->nframes) < p->frame_time) {
			time = 0;
		} else if (event.time >= p->frame_time) {
			time = p->nframes - 1;
		} else {
			time = event.time + p->nframes - p->frame_time;
		}

		jack_ringbuffer_get_read_vector (port->base.data_ring, vec);
		assert ((vec[0].len + vec[1].len) >= event.size);

		if (event.overruns) {
			midi_unpack_reset (&port->unpack);
		}

		todo = event.size;
		for (i = 0; i < 2 && todo > 0; ++i) {
			int avail = todo < vec[i].len ? todo : vec[i].len;
			int done = midi_unpack_buf (&port->unpack, (unsigned char*)vec[i].buf, avail, p->buffer, time);
			if (done != avail) {
				debug_log ("jack_in: buffer overflow in port %s", port->base.name);
				break;
			}
			todo -= done;
		}
		jack_ringbuffer_read_advance (port->base.data_ring, event.size);
	}
}

/*
 * Low level input.
 */
static
int do_midi_input (process_midi_t *proc)
{
	input_port_t *port = (input_port_t*)proc->port;

	if (!midi_is_ready (proc)) {
		return 0;
	}

	if (port->base.is_ready) {
		jack_ringbuffer_data_t vec[2];
		int res;

		jack_ringbuffer_get_write_vector (port->base.data_ring, vec);
		if (jack_ringbuffer_write_space (port->base.event_ring) < sizeof(event_head_t) || vec[0].len < 1) {
			port->overruns++;
			if (port->base.npfds) {
				debug_log ("midi_in: internal overflow on %s", port->base.name);
			}
			// remove from poll to prevent busy-looping
			port->base.npfds = 0;
			return 1;
		}
		res = snd_rawmidi_read (port->base.rawmidi, vec[0].buf, vec[0].len);
		if (res < 0 && res != -EWOULDBLOCK) {
			error_log ("midi_in: reading from port %s failed: %s", port->base.name, snd_strerror (res));
			return 0;
		} else if (res > 0) {
			event_head_t event;
			event.time = proc->cur_time;
			event.size = res;
			event.overruns = port->overruns;
			port->overruns = 0;
			debug_log ("midi_in: read %d bytes at %d", (int)event.size, (int)event.time);
			jack_ringbuffer_write_advance (port->base.data_ring, event.size);
			jack_ringbuffer_write (port->base.event_ring, (char*)&event, sizeof(event));
		}
		port->base.is_ready = 0;
	}

	if (!midi_update_pfds (proc)) {
		return 0;
	}

	return 1;
}

/*
 * ------------------------------------ Output ------------------------------
 */

static int output_port_init (alsa_rawmidi_t *midi, midi_port_t *port)
{
	output_port_t *out = (output_port_t*)port;

	midi_pack_reset (&out->packer);
	out->next_event.time = 0;
	out->next_event.size = 0;
	out->todo = 0;
	return 0;
}

static void output_port_close (alsa_rawmidi_t *midi, midi_port_t *port)
{
}

static
void do_jack_output (process_jack_t *proc)
{
	output_port_t *port = (output_port_t*)proc->port;
	int nevents = jack_midi_get_event_count (proc->buffer);
	int i;

	if (nevents) {
		debug_log ("jack_out: %d events in %s", nevents, port->base.name);
	}
	for (i = 0; i < nevents; ++i) {
		jack_midi_event_t event;
		event_head_t hdr;

		jack_midi_event_get (&event, proc->buffer, i);

		if (jack_ringbuffer_write_space (port->base.data_ring) < event.size || jack_ringbuffer_write_space (port->base.event_ring) < sizeof(hdr)) {
			debug_log ("jack_out: output buffer overflow on %s", port->base.name);
			break;
		}

		midi_pack_event (&port->packer, &event);

		jack_ringbuffer_write (port->base.data_ring, (char*)event.buffer, event.size);

		hdr.time = proc->frame_time + event.time + proc->nframes;
		hdr.size = event.size;
		jack_ringbuffer_write (port->base.event_ring, (char*)&hdr, sizeof(hdr));
		debug_log ("jack_out: sent %d-byte event at %ld", (int)event.size, (long)event.time);
	}
}

static
int do_midi_output (process_midi_t *proc)
{
	int worked = 0;
	output_port_t *port = (output_port_t*)proc->port;

	if (!midi_is_ready (proc)) {
		return 0;
	}

	// eat events
	while (port->next_event.time <= proc->cur_time) {
		port->todo += port->next_event.size;
		if (jack_ringbuffer_read (port->base.event_ring, (char*)&port->next_event, sizeof(port->next_event)) != sizeof(port->next_event)) {
			port->next_event.time = 0;
			port->next_event.size = 0;
			break;
		} else {
			debug_log ("midi_out: at %ld got %d bytes for %ld", (long)proc->cur_time, (int)port->next_event.size, (long)port->next_event.time);
		}
	}

	if (port->todo) {
		debug_log ("midi_out: todo = %d at %ld", (int)port->todo, (long)proc->cur_time);
	}

	// calc next wakeup time
	if (!port->todo && port->next_event.time && port->next_event.time < proc->next_time) {
		proc->next_time = port->next_event.time;
		debug_log ("midi_out: next_time = %ld", (long)proc->next_time);
	}

	if (port->todo && port->base.is_ready) {
		// write data
		int size = port->todo;
		int res;
		jack_ringbuffer_data_t vec[2];

		jack_ringbuffer_get_read_vector (port->base.data_ring, vec);
		if (size > vec[0].len) {
			size = vec[0].len;
			assert (size > 0);
		}
		res = snd_rawmidi_write (port->base.rawmidi, vec[0].buf, size);
		if (res > 0) {
			jack_ringbuffer_read_advance (port->base.data_ring, res);
			debug_log ("midi_out: written %d bytes to %s", res, port->base.name);
			port->todo -= res;
			worked = 1;
		} else if (res == -EWOULDBLOCK) {
			port->base.is_ready = 0;
			debug_log ("midi_out: -EWOULDBLOCK on %s", port->base.name);
			return 1;
		} else {
			error_log ("midi_out: writing to port %s failed: %s", port->base.name, snd_strerror (res));
			return 0;
		}
		snd_rawmidi_drain (port->base.rawmidi);
	}

	// update pfds for this port
	if (!midi_update_pfds (proc)) {
		return 0;
	}

	if (!port->todo) {
		int i;
		if (worked) {
			debug_log ("midi_out: relaxing on %s", port->base.name);
		}
		for (i = 0; i < port->base.npfds; ++i)
			proc->wpfds[i].events &= ~POLLOUT;
	} else {
		int i;
		for (i = 0; i < port->base.npfds; ++i)
			proc->wpfds[i].events |= POLLOUT;
	}
	return 1;
}
