/*
 *	Parser for MIDI data and output subroutine library.
 *
 *	The midi data spec is quite robust in the face of transmission
 *	errors, particularly lost bytes, as most command are only 3 bytes
 *	long, and the first byte of a command has the top bit set and all
 *	subsequent bytes of the command are from 0-127. This means that it
 *	is worthwhile coding carefully to recover from errors gracefully.
 *
/*	Supplement to the MIDI data spec:
 *	Observation reveals that a Key on/off message may contain several
 *	note/velocity pairs after an initial 9n byte. EG instead of
 *	90 48 60 90 48 00  it might say  90 48 60 48 00 . This applies to
 *	other 3-byte commands (at least C0) as well, and to 2-byte commands.
 *	This is probably why Yamaha and Roland are incompatible.
 *	
 *	Assumes you are transmitting and receiving on channel 1 (0000)
 *
 *	Compile-time paramaters:
 *	OPTOUT: if defined, will use the observed supplement on output.
 *
 *	All midi output should be done through midiout() and midi3out() only
 *	so that the output optimiser can do its magic stuff.
 *
 *	Developed with the Megamax C compiler on an Atari 520ST with a
 *	Yamaha DX7 and a Yamaha PF80 electronic piano.
 *	Copyright Martin Guy, November 1986, March 1988
 */

#include <stdio.h>
#include <osbind.h>
#include "midi.h"

/* declarations of routines to handle midi commands */
extern active();
extern keyon();
extern control();
extern program();
extern aftertouch();
extern pitchbend();
extern sysexcl();

/* forward declaration of static routines */
static do2cmd(), do3cmd(), doncmd();
static proc_general(), proc_optim(), proc_pessim();
static keyoff();	/* used to map 0x80 commands to keyon(note, 0) */

static int lasticmd;	/* last input command, needed to grok optimised input */
#ifdef OPTOUT
int lastocmd = 0;	/* for output optimiser. 0 is an impossible cmd byte */
#endif

/* States for midi parsing automaton */
#define	WAITCMD	0	/* next byte should be a command byte */
#define WAITARG 1	/* next may be a cmd byte, or another set of arguments
			 * for the previous command */
#define GOBBLING 2	/* error-handling state throws away data until next
			 * command byte arrives. */
static int state = WAITCMD;

/*
 *	midiparse() is the entry point for this module. It reads data from
 *	the MIDI port, decodes the MIDI command format and calls various
 *	functions to handle the different sorts of commands.
 *	Validation of which bytes are commands and which are arguments is
 *	done by the code in this file.
 *
 *	You need to provide:
 *	active()
 *	keyon(cmd, note, velocity)	velocity==0 means key off
 *	control(cmd, number, value)
 *	program(cmd, number)
 *	pitchbend(cmd, LSB, MSB)	7 bits are significant of each
 *	sysexcl(buf, nbytes)	undecoded buffer, including initial status byte
 *				not including the terminating EOX.
 *
 *	the command byte is passed so that the functions can decode the
 *	channel number from it if they like.
 *
 *	So that the system flows freely, midiparse should never block waiting
 *	for a byte from the midi.  This makes parsing the optimised format
 *	a pig because the parser has to be able to return and restart in the
 *	middle of an optimised sequence. We cannot read the next byte to see
 *	whether it is part of this command or not, because a potential
 *	continuation argument may turn out to be the next comamnd byte
 *	afer a delay.
 *	
 *	That said, we do assume that each
 *	command and its (first set of) arguments come in consecutively with
 *	no delay; the ST can not keep up with MIDI if you test for
 *	availability of each byte before you read it, which results is a
 *	highly impure parser, half restartable state machine, half
 *	inperative parser. Ech!
 *
 *	midiparse() should only be called when mididatap() returns TRUE
 *	if you don't want it to block.
 *
 *	midiparse() itself just switches on the parser's state and leaps
 *	into the automaton at the appropriate point.
 */

static int ngobbled;	/* how many bytes did we throw away while gobbling? */

/* enter the gobbling state */
static
gobble()
{
	state = GOBBLING;
	ngobbled = 0;
}

midiparse()
{
	register int cmd;	/* current command under inspection */

	cmd = midiin();

	switch (state) {
	case WAITCMD:		/* next byte should be a command */
		if (!iscmd(cmd)) {
			error("MIDI data error %02x", cmd);
			gobble();
			return;
		}
		proc_general(cmd);
		break;

	case WAITARG:
		/*
		 * Next byte might be a command byte, or the first byte
		 * of an extra set of arguments for the previous command.
		 */
		if (iscmd(cmd)) {
			proc_general(cmd); /* subject it to full processing */
		} else {
			proc_optim(cmd);   /* cmd is actually the 1st arg */
		}
		break;

	case GOBBLING:	/* throw away argument byte */
		if (!iscmd(cmd)) {
			ngobbled++;
			/* Gobble on! */
			break;
		} else {
			if (ngobbled>0) {
				m_printf("Threw away %d data bytes", ngobbled);
			}
			proc_general(cmd);
		}
		break;
	}
}

/*
 *	General command processor.
 *
 *	Switch between optimisable and unoptimisable commands and call
 *	proc_optim with the first argument, or proc_pessim if it is not
 *	an optimisable command (eg because it has no arguments).
 *	If in doubt, make it an optimisable command, and it will just never
 *	happen.
 */
static
proc_general(cmd)
{
	register int arg;

again:
	switch (cmd) {
	case ACTIVE:
	case SYSEXCL:
		proc_pessim(cmd);
		break;
	default:
		arg = midiin();		/* get 1st arg for proc_optim */
		if (iscmd(arg)) {
			error("MIDI data error %02x %02x", cmd, arg);
			/* sigh. go and process the new command instead */
			cmd = arg;
			goto again;
		}
		lasticmd = cmd;
		proc_optim(arg);
		break;
	}
}

/*
 *	Parser for optimised commands.  The command is in the global
 *	variable "lasticmd" and arg1 is the first argument to the
 *	midi command.
 */
static
proc_optim(arg1)
{
	register int cmd = lasticmd;

	/* All these commands encode the channel number in the bottom 4 bits */
	switch (cmd & 0xF0) {
	case KEYOFF:
		do3cmd(cmd, arg1, keyoff); /* keyoff is internal to this file */
		break;
	case KEYON:
		do3cmd(cmd, arg1, keyon);
		break;
	case CONTROL:
		do3cmd(cmd, arg1, control);
		break;
	case PROGRAM:
		do2cmd(cmd, arg1, program);
		break;
	case AFTERTOUCH:
		do2cmd(cmd, arg1, aftertouch);
		break;
	case PITCHBEND:
		do3cmd(cmd, arg1, pitchbend);
		break;
	default:
		error("MIDI data error %02x", cmd);
		/* ignore parameters */
		gobble();
		break;
	}
	state = WAITARG;
	/* returns from here all the way back to whoever called midiparse() */
}

static
keyoff(cmd, note, vel)
{
	keyon(cmd, note, 0);
}

static
proc_pessim(cmd)
{
	switch (cmd) {
	case SYSEXCL:
		doncmd(cmd, sysexcl);
		break;
	case ACTIVE:	/* I'm still here! */
		active();
		break;
	default:
		error("MIDI data error %02x", cmd);
		gobble();
		return;
	}
	state = WAITCMD;
	/* return from this routine effects a return from midiparse() */
}

static
do2cmd(cmd, arg, function)
int (*function)();
{
	(* function)(cmd, arg);
}

/*
 *	Do 3-byte command, calling the specified function to do the
 *	necessary.
 */
static
do3cmd(cmd, arg1, function)
int (* function)();
{
	register int arg2;

	arg2 = midiin();
	if (iscmd(arg2)) {
		error("MIDI data error %02x %02x %02x", cmd, arg1, arg2);
		/* go and execute the command instead */
		/* It is *just* possible to cause stack overflow if you get
		 * exactly the wrong set of data repeatedly */
		proc_general(arg2);
		return;
	}
	
	(* function)(cmd, arg1, arg2);
}

static
doncmd(cmd, function)
int (*function)();
{
	auto char buf[6+4096+1];	/* length of a 32-voice dump */
	register int i;			/* where to put the next byte in buf */
	register int arg;
	
	buf[0] = cmd; i = 1;
	while (!iscmd(arg = midiin())) {
		/* check for buffer overflow */
		if (i >= sizeof(buf)) {
			error("MIDI data error: overlong system exclusive message (>%d bytes)",
				sizeof(buf));
			gobble();
			return;
		}
		buf[i++] = arg;
	}

	if (arg == EOX) {
		/* Good. That's what we expected. */
		(*function)(buf, i);
		state = WAITCMD;
	} else {
		error("MIDI data error %02x...%02x", cmd, arg);
		/* forget the sysex and go and do the cmd */
		proc_general(arg);
	}
}

midiinit()
{
	/* throw away any pending input */
	while (mididatap()) midiin();
}

/*
 *	Output a midi triple remembering the last command so that we
 *	can output in the COM PAR1 PAR2 PAR1 PAR2 ... format if
 *	OPTOUT is defined.
 */
midi3out(cmd, par1, par2)
{
#ifdef OPTOUT
	if (cmd != lastocmd) {
		Bconout(MIDI, cmd);
		lastocmd = cmd;
	}
#else 
	Bconout(MIDI, cmd);
#endif
	Bconout(MIDI, par1);
	Bconout(MIDI, par2);
}

/*
 *	Output a midi duple remembering the last command so that we
 *	can output in the COM PAR PAR ... format if OPTOUT is defined.
 */
midi2out(cmd, par)
{
#ifdef OPTOUT
	if (cmd != lastocmd) {
		Bconout(MIDI, cmd);
		lastocmd = cmd;
	}
#else 
	Bconout(MIDI, cmd);
#endif
	Bconout(MIDI, par);
}

/* 
 *	Send a variable-length block of bytes out of the MIDI port
 */
midinout(buf, nbytes)
char *buf;
int nbytes;
{
	register int i;

	for (i=0; i<nbytes; i++) {
		Bconout(MIDI, buf[i]);
	}
#ifdef OPTOUT
	lastocmd = 0;
#endif
}
