/*
 * MIDI byte <-> sequencer event coder
 *
 * Copyright (C) 1998,99 Takashi Iwai <iwai@ww.uni-erlangen.de>
 *
 * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include "../../../include/driver.h"
#include "../../../include/seq_kernel.h"
#include "midi_coder.h"

/* queue type */
/* from 0 to 7 are normal commands (note off, on, etc.) */
#define ST_SPECIAL	8
#define ST_SYSEX	ST_SPECIAL
/* from 8 to 15 are events for 0xf0-0xf7 */


/* status event types */
typedef void (*event_encode_t)(snd_midi_coder_t *st, snd_seq_event_t *ev);
typedef void (*event_decode_t)(snd_seq_event_t *ev, unsigned char *buf);

/*
 * prototypes
 */
static int read_queue(snd_midi_coder_t *st, unsigned char c, snd_seq_event_t *ev);
static void set_header(snd_midi_coder_t *st, snd_seq_event_t *ev);
static void note_event(snd_midi_coder_t *st, snd_seq_event_t *ev);
static void one_param_event(snd_midi_coder_t *st, snd_seq_event_t *ev);
static void pitchbend_event(snd_midi_coder_t *st, snd_seq_event_t *ev);
static void two_param_event(snd_midi_coder_t *st, snd_seq_event_t *ev);
static void songpos_event(snd_midi_coder_t *st, snd_seq_event_t *ev);
static int send_sysex(snd_midi_coder_t *st, snd_seq_event_t *ev);
static void note_decode(snd_seq_event_t *ev, unsigned char *buf);
static void one_param_decode(snd_seq_event_t *ev, unsigned char *buf);
static void pitchbend_decode(snd_seq_event_t *ev, unsigned char *buf);
static void two_param_decode(snd_seq_event_t *ev, unsigned char *buf);
static void songpos_decode(snd_seq_event_t *ev, unsigned char *buf);

/*
 * event list
 */
static struct status_event_list_t {
	int event;
	int qlen;
	event_encode_t encode;
	event_decode_t decode;
} status_event[] = {
	/* 0x80 - 0xf0 */
	{SND_SEQ_EVENT_NOTEOFF, 2, note_event, note_decode},
	{SND_SEQ_EVENT_NOTEON, 2, note_event, note_decode},
	{SND_SEQ_EVENT_KEYPRESS, 2, note_event, note_decode},
	{SND_SEQ_EVENT_CONTROLLER, 2, two_param_event, two_param_decode},
	{SND_SEQ_EVENT_PGMCHANGE, 1, one_param_event, one_param_decode},
	{SND_SEQ_EVENT_CHANPRESS, 1, one_param_event, one_param_decode},
	{SND_SEQ_EVENT_PITCHBEND, 2, pitchbend_event, pitchbend_decode},
	{0, 0, NULL, NULL}, /* 0xf0 */
	/* 0xf0 - 0xff */
	{SND_SEQ_EVENT_SYSEX, 1, NULL, NULL}, /* sysex: 0xf0 */
	{SND_SEQ_EVENT_QFRAME, 1, one_param_event, one_param_decode}, /* 0xf1 */
	{SND_SEQ_EVENT_SONGPOS, 2, songpos_event, songpos_decode}, /* 0xf2 */
	{SND_SEQ_EVENT_SONGSEL, 1, one_param_event, one_param_decode}, /* 0xf3 */
	{0, 0, NULL, NULL}, /* 0xf4 */
	{0, 0, NULL, NULL}, /* 0xf5 */
	{0, 0, NULL, NULL}, /* 0xf6 */
	{0, 0, NULL, NULL}, /* 0xf7 */
	{SND_SEQ_EVENT_CLOCK, 0, NULL, NULL}, /* 0xf8 */
	{SND_SEQ_EVENT_START, 0, NULL, NULL}, /* 0xf9 */
	{SND_SEQ_EVENT_CONTINUE, 0, NULL, NULL}, /* 0xfa */
	{0, 0, NULL, NULL}, /* 0xfb */
	{SND_SEQ_EVENT_STOP, 0, NULL, NULL}, /* 0xfc */
	{0, 0, NULL, NULL}, /* 0xfd */
	{SND_SEQ_EVENT_HEARTBEAT, 0, NULL, NULL}, /* 0xfe */
	{0, 0, NULL, NULL}, /* 0xff */
};

#define numberof(ary)	(sizeof(ary)/sizeof(ary[0]))

/*
 * new/delete record
 */

snd_midi_coder_t *
snd_midi_coder_new(void)
{
	return (snd_midi_coder_t*)snd_kcalloc(sizeof(snd_midi_coder_t), GFP_KERNEL);
}

void
snd_midi_coder_delete(snd_midi_coder_t *dev)
{
	if (dev->private && dev->private_free)
		dev->private_free(dev->private);
	snd_kfree(dev);
}


/*
 * initialize record
 */
void
snd_midi_coder_init(snd_midi_coder_t *dev)
{
	dev->status = 0;
	dev->read = 0;
	dev->qlen = 0;
	dev->type = 0;
	dev->lastcmd = 0xff;
}


/*
 * read a byte and encode to sequencer event if finished
 */
int
snd_midi_coder_encode(snd_midi_coder_t *st, unsigned char c, snd_seq_event_t *ev)
{
	int rc;

	rc = 0;

	/* check active sensing */
	if (c == 0xfe) {
		st->type = ST_SPECIAL + 0x0e; /* 0xfe */
		set_header(st, ev);
		st->status = 0;
		st->qlen = 0;
		return 1;
	}

	if (st->status == 0xf0) {
		/* processing sysex message */
		st->buf[st->read++] = c;
		if (c == 0xf7) {
			rc = send_sysex(st, ev);
		}
	} else if (st->qlen) {
		/* processing command arguments */
		rc = read_queue(st, c, ev);
	} else {
		/* new command */
		st->read = 0;
		if ((c & 0xf0) == 0xf0) { /* special events */
			st->status = c;
			st->type = (c & 0x0f) + ST_SPECIAL;
			st->qlen = status_event[st->type].qlen;
			if (st->qlen == 0 && status_event[st->type].event) {
				/* one byte event */
				set_header(st, ev);
				rc = 1;
			}
		} else if (c & 0x80) { /* status change */
			st->status = c;
			st->type = (c >> 4) & 0x07;
			st->qlen = status_event[st->type].qlen;
		} else if (st->status) {
			/* process this byte as argument */
			st->qlen = status_event[st->type].qlen;
			rc = read_queue(st, c, ev);
		}
	}

	return rc;
}

/*
 */
static int
read_queue(snd_midi_coder_t *st, unsigned char c, snd_seq_event_t *ev)
{
	int rc = 0;
	st->buf[st->read++] = c;
	if (st->read >= st->qlen) {
		if (status_event[st->type].encode) {
			set_header(st, ev);
			status_event[st->type].encode(st, ev);
			rc = 1;
		}
		st->qlen = 0;
	}
	return rc;
}


/* fill standard header data */
static void
set_header(snd_midi_coder_t *st, snd_seq_event_t *ev)
{
	ev->type = status_event[st->type].event;
	ev->flags |= SND_SEQ_EVENT_LENGTH_FIXED;
	ev->source.channel = st->status & 0x0f;
	ev->dest.channel = ev->source.channel;
}

/* encode note event */
static void
note_event(snd_midi_coder_t *st, snd_seq_event_t *ev)
{
	ev->data.note.note = st->buf[0];
	ev->data.note.velocity = st->buf[1];
}

/* encode one parameter controls */
static void
one_param_event(snd_midi_coder_t *st, snd_seq_event_t *ev)
{
	ev->data.control.value = st->buf[0];
}

/* encode pitch wheel change */
static void
pitchbend_event(snd_midi_coder_t *st, snd_seq_event_t *ev)
{
	ev->data.control.value = (int)st->buf[1] * 128 + (int)st->buf[0] - 8192;
}

/* encode midi control change */
static void
two_param_event(snd_midi_coder_t *st, snd_seq_event_t *ev)
{
	ev->data.control.param = st->buf[0];
	ev->data.control.value = st->buf[1];
}

/* encode song position */
static void
songpos_event(snd_midi_coder_t *st, snd_seq_event_t *ev)
{
	ev->data.control.value = (int)st->buf[1] * 128 + (int)st->buf[0];
}

/* send sysex */
static int
send_sysex(snd_midi_coder_t *st, snd_seq_event_t *ev)
{
	int rc = 0;
	unsigned char *buf;

	set_header(st, ev);
	ev->flags &= ~SND_SEQ_EVENT_LENGTH_MASK;
	ev->flags |= SND_SEQ_EVENT_LENGTH_VARIABLE;
	ev->data.ext.len = st->read + 1;
	ev->data.ext.ptr = buf = snd_seq_ext_malloc(ev->data.ext.len, 1);
	if (buf) {
		buf[0] = 0xf0; /* sysex command */
		memcpy(buf + 1, st->buf, st->read);
		rc = 1;
	}
	st->status = 0;
	st->qlen = 0;
	return rc;
}


/*
 * decode from a sequencer event to midi bytes
 */

int
snd_midi_coder_decode(snd_midi_coder_t *dev, snd_seq_event_t *ev, void *opt)
{
	int cmd, type;

	if (! dev->input)
		return -ENODEV;

	for (type = 0; type < numberof(status_event); type++) {
		if (ev->type == status_event[type].event)
			break;
	}
	if (type >= numberof(status_event))
		return -EINVAL;

	if (type >= ST_SPECIAL)
		cmd = 0xf0 + (type - ST_SPECIAL);
	else
		cmd = (type << 4) | (ev->dest.channel & 0x0f);

	if (cmd == 0xf0) { /* sysex */
		if ((ev->flags & SND_SEQ_EVENT_LENGTH_MASK) != SND_SEQ_EVENT_LENGTH_VARIABLE)
			return -EINVAL;
		return dev->input(ev->data.ext.ptr, ev->data.ext.len, dev->private, opt);
	} else {
		int qlen;
		unsigned char buf[4];
		if ((cmd & 0xf0) == 0xf0 || dev->lastcmd != cmd) {
			buf[0] = cmd;
			status_event[type].decode(ev, buf + 1);
			qlen = status_event[type].qlen + 1;
			dev->lastcmd = cmd;
		} else {
			status_event[type].decode(ev, buf + 1);
			qlen = status_event[type].qlen + 1;
		}
		return dev->input(buf, qlen, dev->private, opt);
	}
}


/* decode note event */
static void
note_decode(snd_seq_event_t *ev, unsigned char *buf)
{
	buf[0] = ev->data.note.note & 0x7f;
	buf[1] = ev->data.note.velocity & 0x7f;
}

/* decode one parameter controls */
static void
one_param_decode(snd_seq_event_t *ev, unsigned char *buf)
{
	buf[0] = ev->data.control.value & 0x7f;
}

/* decode pitch wheel change */
static void
pitchbend_decode(snd_seq_event_t *ev, unsigned char *buf)
{
	int value = ev->data.control.value + 8192;
	buf[0] = value & 0x7f;
	buf[1] = (value >> 7) & 0x7f;
}

/* decode midi control change */
static void
two_param_decode(snd_seq_event_t *ev, unsigned char *buf)
{
	buf[0] = ev->data.control.param & 0x7f;
	buf[1] = ev->data.control.value & 0x7f;
}

/* decode song position */
static void
songpos_decode(snd_seq_event_t *ev, unsigned char *buf)
{
	buf[0] = ev->data.control.value & 0x7f;
	buf[1] = (ev->data.control.value >> 7) & 0x7f;
}
