/*
 * midi2osc.c - MIDI to OSC forwarder
 *
 * Written 2011 by Werner Almesberger
 * Copyright 2011 Werner Almesberger
 *
 * 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.
 */


#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <alsa/asoundlib.h>
#include "lo/lo.h"


#define	NAME "midi2osc"


static int debug = 0;


static struct map {
	int chan_in, ctrl_in;
	int chan_out, ctrl_out;
	int value;
	struct map *next;
} *mappings = NULL;


static void add(int chan_in, int ctrl_in, int chan_out, int ctrl_out,
    int value)
{
	struct map *new;

	new = malloc(sizeof(struct map));
	new->chan_in = chan_in;
	new->ctrl_in = ctrl_in;
	new->chan_out = chan_out;
	new->ctrl_out = ctrl_out;
	new->value = value;
	new->next = mappings;
	mappings = new;
}


static void add_mapping(const char *s)
{
	unsigned chan_in, ctrl_in, chan_out, ctrl_out, value;

	if (sscanf(s, "c%u.%u=c%u.%u=%u",
	    &chan_in, &ctrl_in, &chan_out, &ctrl_out, &value) == 5)
		add(chan_in, ctrl_in, chan_out, ctrl_out, value);
	else if (sscanf(s, "c%u.%u=c%u.%u",
	    &chan_in, &ctrl_in, &chan_out, &ctrl_out) == 4)
		add(chan_in, ctrl_in, chan_out, ctrl_out, -1);
	else if (sscanf(s, "c%u.%u=c%u", &chan_in, &ctrl_in, &chan_out) == 3)
		add(chan_in, ctrl_in, chan_out, -1, -1);
	else if (sscanf(s, "c%u=c%u.%u", &chan_in, &chan_out, &ctrl_out) == 3)
		add(chan_in, -1, chan_out, ctrl_out, -1);
	else if (sscanf(s, "c.%u=c%u.%u", &ctrl_in, &chan_out, &ctrl_out) == 3)
		add(-1, ctrl_in, chan_out, ctrl_out, -1);
	else if (sscanf(s, "c%u=c%u", &chan_in, &chan_out) == 2)
		add(chan_in, -1, chan_out, -1, -1);
	else {
		fprintf(stderr, "unrecognized mapping syntax\n");
		exit(1);
	}
}


static void map(uint8_t *chan, uint8_t *ctrl, uint8_t *value)
{
	const struct map *m;

	for (m = mappings; m; m = m->next)
		if ((m->chan_in == -1 || m->chan_in == *chan) &&
		    (m->ctrl_in == -1 || m->ctrl_in == *ctrl)) {
			if (m->chan_out != -1)
				*chan = m->chan_out;
			if (m->ctrl_out != -1)
				*ctrl = m->ctrl_out;
			if (m->value != -1)
				*value = m->value;
			return;
		}
}


static void forward(snd_seq_t *midi, lo_address osc)
{
	snd_seq_event_t *ev;
	uint8_t msg[4] = { 0, };
	uint8_t chan, ctrl, value;

	while (snd_seq_event_input(midi, &ev)) {
		switch (ev->type) {
		case SND_SEQ_EVENT_NOTEON:
			msg[1] = 0x90 | ev->data.control.channel;
			msg[2] = ev->data.note.note;
			msg[3] = ev->data.note.velocity;
			break;
		case SND_SEQ_EVENT_CONTROLLER:
			chan = ev->data.control.channel;
			ctrl = ev->data.control.param;
			value = ev->data.control.value;
			map(&chan, &ctrl, &value);
			if (debug)
				fprintf(stderr, "c%u.%u=%u -> c%u.%u=%u\n",
				    ev->data.control.channel,
				    ev->data.control.param,
				    ev->data.control.value,
				    chan, ctrl, value);
			msg[1] = 0xb0 | chan;
			msg[2] = ctrl;
			msg[3] = value;
			break;
		case SND_SEQ_EVENT_PITCHBEND:
			msg[1] = 0xe0 | ev->data.control.channel;
			msg[2] = ev->data.control.value;
			msg[3] = 0;
			break;
		default:
			/* Flickernoise currently doesn't support any others */
			if (debug)
				fprintf(stderr, "unrecognized MIDI event\n");
			snd_seq_free_event(ev);
			continue;
		}
		if (lo_send(osc, "/midi", "m", msg) < 0)
			fprintf(stderr, "%s\n", lo_address_errstr(osc));
		snd_seq_free_event(ev);
	}
}


static void usage(const char *name)
{
	fprintf(stderr,
"usage: %s hostname [-d] [mapping ...] [port]\n\n"
"  mappings are of the form\n"
"  c[<chan>][.<control>]=c<chan>[.<control>[=<value>]]\n\n"
"  -d   debug mode: print all MIDI messages\n",
    name);
	exit(1);
}


int main(int argc, char **argv)
{
	const char *port = "4444";	/* Milkymist One OSC port */
	lo_address osc;
	snd_seq_t *midi;
	int c, arg;

	while ((c = getopt(argc, argv, "d")) != EOF)
		switch (c) {
		case 'd':
			debug = 1;
			break;
		default:
			usage(*argv);
		}

	for (arg = optind; arg != argc; arg++) {
		if (!strchr(argv[arg], '='))
			break;
		add_mapping(argv[arg]);
	}
	switch (argc-arg) {
	case 1:
		break;
	case 2:
		port = argv[arg+1];
		break;
	default:
		usage(*argv);
	}

	osc = lo_address_new(argv[arg], port);
	if (!osc) {
		fprintf(stderr, "invalid address %s %s\n", argv[arg], port);
		exit(1);
	}

	if (snd_seq_open(&midi, "hw", SND_SEQ_OPEN_INPUT,0) < 0) {
		fprintf(stderr, "can't open ALSA sequencer\n");
		exit(1);
	}
	snd_seq_set_client_name(midi, NAME);
	if (snd_seq_create_simple_port(midi, NAME,
              SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE,
              SND_SEQ_PORT_TYPE_APPLICATION) < 0) {
		fprintf(stderr, "can't create sequencer port\n");
		exit(1);
	}

	forward(midi, osc);

	return 0;
}
