/*
 *	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 PF80 electronic piano.
 *	Copyright Martin Guy, University of Kent, November 1986
 */

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

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

/* forward declaration of static routines */
static int do2cmd(), do3cmd();
static initmidi();

#ifdef OPTOUT
int lastocmd = 0;	/* for output optimiser. 0 is illegal cmd byte */
#endif

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

	cmd = midiin();
	/* in this loop, continue if you have already loaded the next
	 * command byte int cmd. Otherwise the endloop will do it.
	 * This switch statement also copes with non-cmd bytes when cmd
	 * expected.
	 */
	while (!kbddatap()) {
		switch (cmd) {
		case ACTIVE:	/* I'm still here! */
			active();
			break;
		case KEYON:
			cmd = do3cmd(cmd, keyon);
			continue;
		case CONTROL:
			cmd = do3cmd(cmd, control);
			continue;
		case PROGRAM:
			cmd = do2cmd(cmd, program);
			continue;
		default:
			fprintf(stderr, "ILLCMD", cmd);
			/* display the bogus cmd and any following params */
			do {
				fprintf(stderr, " %02x", cmd);
				cmd = midiin();
			} while (!iscmd(cmd));
			putc('\n', stderr);
			continue;
		}
		cmd = midiin();
	}
}

static int
do2cmd(cmd, function)
int (*function)();
{
	register int par;

	par = midiin();
	if (iscmd(par)) {
		fprintf(stderr, "Bad data %02x %02x ??\n", cmd, par);
		return(par);	/* go and execute the poor command */
	}
	do {
		(* function)(par);
		par = midiin();
	} while (!iscmd(par));
	return(par);
}

/*
 *	Do 3-byte command, calling the specified function to do the
 *	necessary. Return the next command byte (which terminated this
 *	one).
 *	Cope with Yamaha giving multiple parameter pairs to each command.
 *	Cmd byte is passed for error reporting.
 */
static int
do3cmd(cmd, function)
int (* function)();
{
	register int par1, par2;

	par1 = midiin();
	if (iscmd(par1)) {
		fprintf(stderr, "Bad data %02x %02x ??\n", cmd, par1);
		return(par1);
	}
	
	do {
		par2 = midiin();
		if (iscmd(par2)) {
			fprintf(stderr, "Bad data %02x %02x %02x ??\n",
				cmd, par1, par2);
			return(par2);
		}
		(* function)(par1, par2);
		par1 = midiin();
	} while (!iscmd(par1));
	return(par1);
}

static
initmidi()
{
	/* turn sustain and keyhold off */
	midi3out(CONTROL, SUSTAIN, 0);
	midi3out(CONTROL, KEYHOLD, 0);
	/* turn all voices off */
	midi3out(CONTROL, 127, 127);
	/* 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);
}

