/* SOURCE FILE: MPUISR.C
 * Last modified: Greg Hendershott, 9-13-1986.
 *
 *  ----------------------------------------------------------------------
 *  Copyright 1986 Greg Hendershott.  Permission is hereby granted to
 *  use and distribute this material for non-commercial purposes.  This
 *  code may not be sold to others, nor used to develop a program which
 *  will be sold to others.
 *  ----------------------------------------------------------------------
 *
 *  Note: I've used a tab size of 3 in my editor for this file.
 *
 *  This file contains a set of routines for communicating with the
 *  Roland MPU-401.  There are routines for initialization and
 *  de-initialization, for sending MPU commands and data, and for
 *  handling the interrupts the MPU generates when it wishes to
 *  send information or ACKnowledge a command you've sent it.
 *
 *	 The main interrupt handler, main_handler(), dispatches control to one
 *	 of several sub-handlers.	This scheme is necessary because MIDI
 *	 messages are sent over one byte at a time, but individual bytes have
 *	 different meanings that are dependent on the order they are sent in.
 *
 *  This set of functions is written so that some are simply declared
 *  here, since their content may vary from application to application.
 *  For instance, the routine data_handler() is called when there is a
 *  byte of incoming data that needs to be stored.  How and where you'll
 *  store this may vary depending on the program you're writing.  These
 *  functions, in other words, are designed in as general-purpose a way
 *  as possible.  You can compile this, put it in a library, and then
 *  link it to a variety of applications.
 *
 *  These are:
 *			data_handler()
 *			play_handler()
 *			clock_handler()
 *			measure_handler()
 *			event_handler()
 *
 *  Your source code must supply these routines for the linker.  The
 *  only two you may ever have to deal with are data_handler() and
 *  play_handler().  data_handler() is called by the ISR whenever it
 *  has a byte of recorded data for your program to do something with.
 *  Typically your program should just stuff it in a buffer someplace.
 *  play_handler() is called whenever the MPU needs another MIDI event
 *  to be played.  Here your routine must determine the MPU track
 *  number from the lower nibble of the argument and use put_data() to
 *  send out a complete MIDI event including leading timing byte.
 *
 *  Remember that all these routines are called by the interrupt handler
 *  and that you should avoid things that shouldn't be done inside an
 *  interrupt handler, like make most DOS calls.
 *
 *	 CURIOUS NOTE: As far as the interrupt vector table is concerned, the
 *	 MPU-401 generates interrupt 0x0A (10d).	Everything else -- manuals,
 *	 PIC 8259 chip, articles -- thinks it generates 0x02 (2d), which
 *	 corresponds to the IRQ2 non-maskable interrupt line.	 As for me,
 *	 I am clueless, but things seem to work as long as the above parties
 *	 are told what they want to believe.
 *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *	*/

 
/*#define DEBUG*/
/*#define LINT_ARGS*/

#include <mpuconst.h>
#include <midi.h>


/* Byte typedef
 */
typedef unsigned char byte;

/* Timeout value for attempts to communicate with MPU
 */
#define MAX_TRIES 5000

/* Constant values for the sub-handlers variable currentHandler
 */
#define MESSAGE 0
#define REC_FIRST 1
#define REC_SECOND 2
#define REC_THIRD 3
#define REC_SYSX 4

/* Interrupt set/restore constants
 */
#define IRQ2 0x0A
#define PIC_IMR 0x0021			/* PIC 8259 Interrupt Mask Register */
#define STACK_SIZE 256

/* Global variables a calling program may use.
 * Header file mpuisr.h contains the proper 'extern' declarations.
 */
int isr_dataInStop;				/* flag to just dump in buffer */
int isr_allEnd; 				/* set true when 0xFC received */

/* Some internal variables
 */
static int ackFlag;				/* set to true when ACK received */
static int currentHandler;		/* the current sub-handler */
#ifdef DEBUG
static unsigned services;		/* # of calls to main_handler() */
#endif


/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *										lookup routines 									*
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

/*											 lookup
 *
 *	Returns the number of data bytes that follow the voice message status
 * byte, or zero if argument is not a voice message status byte.
 */ 
lookup(status) {
	switch(status & 0xF0) {
		case 0x80: return 2;	/* note off */
		case 0x90: return 2;	/* note on */
		case 0xA0: return 2;	/* polyphonic key pressure */
		case 0xB0: return 2;	/* control change */
		case 0xC0: return 1;	/* program change */
		case 0xD0: return 1;	/* channel pressure */
		case 0xE0: return 2;	/* wheel change */
		default:   return 0;	/* not a voice message status byte */
	}
}


/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *									send/receive routines									*
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
	 
 
/*											waitforDRR
 *
 * Waits until statport shows ready to receive data -- is NOT ready if
 * bit 6 is set to 1 (is ready when cleared).
 *
 * Returns zero if MPU was not ready to receive it after MAX_TRIES
 * checks of the DRR flag; otherwise returns non-zero.
 *
 * Used by both put_cmd() and put_data().
 */
static waitforDRR() {
	register int tries;
 
	for (tries=0; (inportb(STATPORT) & DRR) && (tries < MAX_TRIES); tries++)
		;
	return( tries < MAX_TRIES );
}
 
 
/*											 put_cmd
 *
 * Sends a command to the MPU through the COMPORT.  Waits for ackFlag to
 * be set to 1 by the interrupt service routine, which will set this
 * on receipt of an ACK (0xFE) from the MPU.
 * Returns: zero if either waitforDRR() timed out or the wait for ackFlag
 * timed out, else non-zero.
 */
put_cmd(command) {
	register int tries;
 
	if (!waitforDRR()) 
		return(0);
	ackFlag = 0;
	outportb(COMPORT, command);
	if (command == UART) 
		return 1;		 /* MPU doesn't ACK UART commands */
	
	/* Wait till handler gets ACK and sets ackFlag to 1 -- but not forever. 
	 */
	for (tries=0; (!ackFlag) && (tries<MAX_TRIES); tries++)
		;
	return( tries < MAX_TRIES );
}
 
 
/*											  put_data
 *
 * Sends a data byte out through DATAPORT.
 * Returns: zero if waitforDRR() timed out, else non-zero.
 */
put_data(data) {
	if (waitforDRR())	{
		outportb(DATAPORT, data);
		return 1;
	}
	else 
		return 0;
}
 
 
/*											 get_data
 *
 * Gets a data byte from DATAPORT after waiting for DSR to be true.
 * Returns: the data byte.  No error return.
 */
get_data() {
	register int tries;
 
	/* First wait for DSR flag to be cleared */
	for (tries=0;((inportb(STATPORT) & DSR)==1) && (tries < MAX_TRIES);tries++)
		;
	/* Get data byte from DATAPORT */
	return( inportb(DATAPORT) );
}
 
 

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *								interrupt service routines									*
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
 

/* External routines.
 *
 * The following routines must be linked in by the calling program.  They
 * will be called under certain conditions by the interrupt handler.  As
 * interrupt service routines, it is vital that they be compiled without
 * stack-checking enabled, as our assembler shell sets up an alternate
 * stack.
 */
extern play_handler();			/* satisfies play requests */
extern data_handler();			/* does something with received data */
extern clock_handler(); 		/* called when MPU sends clock-to-host */
extern measure_handler();		/* called when MPU sends measure-end */
extern event_handler(); 		/* called when a complete MIDI event is in */

static int dataBytesLeft;		/*number of outstanding MIDI data bytes*/
static byte runningStatus;		/*the running MIDI status byte*/

/* Here is a buffer in which to build a complete MIDI event
 * during successive interrupt services.  When the event is complete,
 * a pointer to the buffer is passed to event_handler().  This is
 * handy for real-time event re-direction programs.  There is no
 * leading timing byte, and event_handler() is expected to maintain
 * the running status.
 * The size is big enough for all non-system exclusive events.
 */
#define L_EVTBUF 3
static byte evtbuf [L_EVTBUF];
static int i_evtbuf;

#define flush_evtbuf()			(i_evtbuf = 0)
#define append_evtbuf(data)	{evtbuf[i_evtbuf] = (data); \
	i_evtbuf = (i_evtbuf+1) % L_EVTBUF; }


/*									  message_handler()
 *
 * Handles an MPU message, which can be a variety of things.
 */
message_handler(data) {
	if	 ( data == ACK ) 
		ackFlag = 1;				/*command ACK*/
	
	else if ( (data <= 0xEF) || (data==OVERFLOW) )
		rec_1st_handler(data);	/*timing value*/
	
	else if ( (0xF0 <= data && data <= 0xF7) || (data==0xF9) )
		play_handler(data);		/*trk or condutor play request*/
	
	else if (data == CLOCK) 
		clock_handler();			/* clock-to-host */

	else if ( data == ALLEND ) 
		isr_allEnd = 1; 		/* all trks done -- set flag */
	
	/* else ignore it */
}
 

/*									rec_1st_handler()
 *
 * Begins processing incoming track data -- handles timing byte and
 *	decides if further data is coming.
 */
rec_1st_handler(data) {
	data_handler(data);		  /* record the timing byte */
	/* Are further data bytes are coming? if so, next handler next time.
    */
	if (data != OVERFLOW) 
		currentHandler = REC_SECOND;
	/* Else currentHandler remains MESSAGE */
}
 

/*								  rec_2nd_handler()
 */
rec_2nd_handler(data) {
	/* Is it an MPU 'mark'? */
	if (data==NOP || data==MEASUREEND || data==DATAEND) { 
		if (data==MEASUREEND)
			measure_handler();
		data_handler(data);
		currentHandler = MESSAGE;	/* No more data bytes coming */
	}
	else {
		flush_evtbuf();
		if (data >= 0x80)	{
			/* It's a new status byte
			 */
			runningStatus = data;				/* running status now this status */
			if ((data & 0xF0) == SYSX)
				currentHandler = REC_SYSX;		/* system exclusive? */
			else {
				dataBytesLeft = lookup(data); /* # of data bytes following */
				currentHandler = REC_THIRD;
			}
			append_evtbuf(data);					/* add to our event buffer */
			data_handler(data);					/* give to data_handler() */
		}
		else {
			/* More data under the running status byte
			 */
			dataBytesLeft = lookup(runningStatus);
			currentHandler = REC_THIRD;
			rec_3rd_handler(data);
		}
	}
}
 

/*									rec_3rd_handler
 */
rec_3rd_handler(data) {
	append_evtbuf(data);			/* add to our event buffer */
	data_handler(data);			/* give to data_handler() */
	if (--dataBytesLeft == 0) {
		event_handler(evtbuf);	/* got a complete event for event_handler() */
		currentHandler = MESSAGE;
	}
}

/*								rec_sysx_handler
 */
rec_sysx_handler(data) {
	append_evtbuf(data);			/* add to our event buffer */
	data_handler(data);			/* give to data_handler() */
	if (data == EOX) {
		event_handler(evtbuf);	/* got a complete event for event_handler() */
		currentHandler = MESSAGE;
	}
	/* Actually, the MIDI Spec 1.0 says that a non-real-time status byte
	 * can end a system exclusive message, but this better not happen,
	 * because any new status byte will be prefaced by the MPU with a
	 * timing byte.  Ergo, if we see a new status byte here, rather than
	 * at rec_1st_handler, we've already missed the boat.  No way out of
	 * this, as far as I can tell.	Just have to hope that all SYSX
	 * messages are ended by EOX bytes, nice and kosher.
	 */
}


/*									main_handler
 *
 * This is the main interrupt service routine, which does interrupt
 * service bookkeeping and dispatches control to one of the sub-handlers.
 * It is called by c_handler(), which gets a data byte from DATAPORT.
 * This allows debugging by calling main_handler with imaginary incoming
 * data in non-realtime.
 *
 */

main_handler(data) {
#ifdef DEBUG
	++services;						/* If debugging, record this call */
#endif

	/* Dispatch control to current sub-handler */
	switch (currentHandler) {
		case MESSAGE:
			message_handler(data); break;
		case REC_FIRST:
			rec_1st_handler(data); break;
		case REC_SECOND:
			rec_2nd_handler(data); break;
		case REC_THIRD:
			rec_3rd_handler(data); break;
		case REC_SYSX:
			rec_sysx_handler(data); break;
	}
}


/*								c_handler
 *
 * This is called by the assembler shell created by intrinit.
 * It gets the waiting data byte from DATAPORT and calls main_handler().
 */
/*far*/ c_handler() {
	static int data;
	
	data = inportb(DATAPORT);  /* Get waiting data byte from dataPort */
	main_handler(data);			/* Pass it to main_handler */

	outportb(0x20,0x20); /* send End Of Interrupt to interrupt controller*/
			     /* required for C86 v2.1	 BB  */
}


/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *										initialization	
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

init_handler() {
	byte imr;
 
	/* Set up main_handler with STACK_SIZE stack reservation to service IRQ2
	 */
	intrinit(c_handler, STACK_SIZE, IRQ2);

	/* Enable hardware interrupt IRQ2
	 */
	imr = inportb(PIC_IMR);
	imr = imr & 0xFB;	 /* 0xFB = 11111011 binary -- unmask 3rd bit for IRQ 2 */
	outportb(PIC_IMR, imr);

	/* Initialize some handler routine variables
	 */
	currentHandler = MESSAGE;
	isr_dataInStop = 0;
	return 1;		/* aok */
}

/* can't use this yet - C86 2.1 doesn't have intrrest().  BB  
void deinit_handler() {
	byte imr;

	/* Disable IRQ2 interrupt handling
	 */
	imr = inportb(PIC_IMR);
	imr = (imr | 0x04);		/* mask 3rd bit */
	outportb(PIC_IMR,imr);

	/* Restore vector table entry
	 */
	intrrest(IRQ2);
}

*/ 
imr | 0x04);		/* mask 3rd bit */
	outportb(PIC_IMR,imr);

	/* Restor