#include <stdlib.h>
#include <dos.h>
#include "std.h"
#include "mpuregs.h"
#include "mpuregs.h"

#define MPU_PRIVATES
#include "mpu.h"

// Manifest constants

#if 0
#define DEFAULT_BASE_ADDRESS 0x330		// Default MPU base address
#define DEFAULT_INTERRUPT 2				// Default MPU interrrupt
#define DEFAULT_READ_BUFFER_SIZE 1024	// Default read buffer size
#define SEND_RETRIES 0xFFF				// Number of times to try a send
#define EXTRA_READS_ON_OPEN 0x300		// Number of attempts to clear RECEIVE READY when opening
#define MAX_TIMING_BYTE_DELAY 0xFFF
#define RECEIVE_TRIES  3				// Number of attempts on READY_TO_RECEIVE
#define SEND_TRIES 3					// Number of attempts on READY_TO_SEND
#else
/*
	I had to increase these constants to get things to work
	on my 16mhz 286. I don't know yet if I have some kind of system
	specific problem, nor have I narrowed down exactly which
	constants needed to be increased. - lt
*/
#define DEFAULT_BASE_ADDRESS 0x330		// Default MPU base address
#define DEFAULT_INTERRUPT 2				// Default MPU interrrupt
#define DEFAULT_READ_BUFFER_SIZE 1024	// Default read buffer size
#define SEND_RETRIES 0x3FFF				// Number of times to try a send
#define EXTRA_READS_ON_OPEN 0xf00	// Number of attempts to clear RECEIVE READY when opening
#define MAX_TIMING_BYTE_DELAY 0x3FFF
#define RECEIVE_TRIES  12				// Number of attempts on READY_TO_RECEIVE
#define SEND_TRIES 12					// Number of attempts on READY_TO_SEND
#endif

#define TRACE_IN
#define TRACE_OUT

#if defined(TRACE_IN) || defined(TRACE_OUT)

#define LOGSIZE  4096

struct LogEntry {
	unsigned char tag;
	unsigned short byte;
};
struct MpuLog {
	struct LogEntry d[LOGSIZE];
	int tail;
};
struct MpuLog mpulog;

#endif


#ifdef DOC

DESCRIPTION

Returns a string error message corresponding to the supplied mpu error
number.
#endif

char *MpuErrorString(int mpuErrno) {
	return mpuErrorStrings[mpuErrno];
}

#ifdef DOC
DESCRIPTION

Returns most recent error code, or 0 if successful.
#endif

int GetMpuStatus(MpuPortT *mpu) {
	return mpu->mpuErrno;
}

#ifdef PDOC

DESCRIPTION

Is MPU data ready to send? This routine delays some to make sure.

RETURNS

NO = No data
YES = Data ready.
#endif

PRIVATE BOOL isReadyToSend(MpuPortT *mpu) {
	int retries;
	int TheStatus;
	volatile unsigned int j = 0;
	do
	{
		TheStatus = inportb(mpu->statusPort);
		j++;
	}
	while ( ((TheStatus & MPU_NOT_READY_TO_SEND) != 0) && (j < SEND_TRIES));
	return (j != SEND_TRIES);
}
#ifdef PDOC

DESCRIPTION

Is MPU data ready to receive? This routine delays some to make sure.

RETURNS

NO = No data
YES = Data ready.
#endif

PRIVATE BOOL isReadyToReceive(MpuPortT *mpu) {
	int retries;
	int TheStatus;
	volatile unsigned int j = 0;
	do
	{
		TheStatus = inportb(mpu->statusPort);
		j++;
	}
	while ( ((TheStatus & MPU_NOT_READY_TO_RECEIVE) != 0) && (j < RECEIVE_TRIES));
	return (j != RECEIVE_TRIES);
}

#ifdef PDOC

DESCRIPTION

Check to see if data is waiting.

RETURNS
	NO -> No data waiting
	YES -> Data waiting
#endif

BOOL DataReady(MpuPortT *mpu) {
	return (mpu->dataHead != mpu->dataTail);
}


int SendData(MpuPortT *mpu,UCHAR data) {

	int TheStatus;
	// Wait for ready
#ifdef TRACE_OUT
	mpulog.d[mpulog.tail].tag = 'O';
	mpulog.d[mpulog.tail].byte = data;
	if( ++mpulog.tail >= LOGSIZE ) mpulog.tail = 0;
#endif
	do {
		TheStatus = inportb(mpu->statusPort);
	} while ( ((TheStatus & MPU_NOT_READY_TO_SEND) != 0));
#ifdef JUNK
	if (!isReadyToSend(mpu)) {
		mpu->mpuErrno = MPU_EHARDWARE;
		return NO;
	}
#endif

	outp(mpu->dataPort,data);
	return YES;
}



#ifdef PDOC

DESCRIPTION

Interrupt handlers for MPU-401. Up to 4 MPU's supported.
Four separate entry points allow an MpuPortT control block
to be associated with the interrupt.

#endif

PRIVATE MpuPortT *mpuForInterrupt[4];


PRIVATE void handleInterrupt(int portNum)
{
	MpuPortT *mpu = mpuForInterrupt[portNum];
	int tail;
	int data;
	int track;

	enable();
	if (mpu != NULL) {  /* Guard against spurious interrupts */
		while (( inp(mpu->statusPort) & MPU_NOT_READY_TO_RECEIVE) == 0) {
			tail = mpu->dataTail;
			mpu->readBuffer[tail] = data = inp(mpu->dataPort);
#ifdef TRACE_IN
			mpulog.d[mpulog.tail].tag = 'I';
			mpulog.d[mpulog.tail].byte = data;
			if( ++mpulog.tail >= LOGSIZE ) mpulog.tail = 0;
#endif

			if (data >= 0xF0) {
				if (data == 0xFF)
					mpu->_inSysex = YES;
				else if (data == 0xF7)
					mpu->_inSysex = NO;
				else if (data == 0xF9 ) {
						SendData(mpu,0xF8);
				}
				else if (data < 0xF7 && !mpu->_inSysex) {
					// callback track request
					track = data-0xF0;
					if (mpu->trackRequestHandler != NULL) {
						(*mpu->trackRequestHandler)(mpu->requestHandlerData,track);
					} else {
						SendData(mpu,0xF8);
					}
				}
			}

			++tail;
			if (tail >= mpu->bufferSize)
				tail = 0;
			if (tail == mpu->dataHead) {
				mpu->overrunError = YES;
			} else {
				mpu->dataTail = tail;
			}
		}
	}
	disable();
	outportb(0x20, 0x20);
}

PRIVATE void interrupt mpuInterrupt0(void) {
	handleInterrupt(0);
}
PRIVATE void interrupt mpuInterrupt1(void) {
	handleInterrupt(1);
}
PRIVATE void interrupt mpuInterrupt2(void) {
	handleInterrupt(2);
}

PRIVATE void interrupt mpuInterrupt3(void) {
	handleInterrupt(3);
}

typedef void interrupt interruptHandlerT(void);

PRIVATE interruptHandlerT far * mpuInterrupt[4] = {
	mpuInterrupt0,
	mpuInterrupt1,
	mpuInterrupt2,
	mpuInterrupt3
};

#ifdef PDOC

DESCRIPTION

Install an interrupt handler. Four separate interrupt handlers are
available. The first unused interrupt handler is assigned to the
supplied mpu port.

RETURNS

YES-> success
NO -> failure.
Call GetMpuStatus for error number.

#endif

int installInterruptHandler(MpuPortT *mpu) {
	int i;
	for (i = 0; i < 4 && mpuForInterrupt[i] != NULL; ++i)
		NOTHING;
	if (i == 4) {
		mpu->mpuErrno = MPU_NOINTERRUPTHANDLERS;
		return NO;
	}
	mpuForInterrupt[i] = mpu;

	mpu->oldInterrupt = getvect(mpu->mpuInterrupt+8);
	setvect(mpu->mpuInterrupt+8,mpuInterrupt[i]);

	/* Enable IRQ */
	outportb(0x21,inportb(0x21) & ((~0x80) >> mpu->mpuInterrupt));
	return YES;
}

#ifdef PDOC

DESCRIPTION

Removes the interrupt handler associated with the mpu port.
#endif

PRIVATE void removeInterrupt(MpuPortT *mpu) {
	int i;

	// Mask interrupt
	outportb(0x21,inportb(0x21) | ((0x80) >> mpu->mpuInterrupt));
	outportb(0x20,0x20); // Just in case interrupt fired while we were disabling it!!!

	// Remove interrupt handler
	setvect(mpu->mpuInterrupt+8,mpu->oldInterrupt);

	for (i = 0; i < 4 & mpuForInterrupt[i] != mpu; ++i)
		NOTHING;
	if (i != 4) {
		mpuForInterrupt[i] = NULL;
	}
}
#ifdef DOC

DESCRIPTION
	Send one command byte

RETURNS
	YES = success.
	NO = failure.
	mpuErrorCode set on failure.
#endif


int SendCommand(MpuPortT *mpu,UCHAR cmd) {
#ifdef TRACE_CMDS
		printf("Cmd(%02X) ",cmd);
#endif
	FOREVER { // Until byte sent

		// Wait for ready
		if (!isReadyToSend(mpu)) {
			mpu->mpuErrno = MPU_EHARDWARE;
			return NO;
		}

		disable();
		outp(mpu->commandPort,cmd);
		while ((inp(mpu->statusPort) & MPU_NOT_READY_TO_RECEIVE) != 0)
			NOTHING;
		if (inp(mpu->dataPort) == ACK_MSG) {
			enable();
			return YES;
		}
		geninterrupt(mpu->mpuInterrupt);	// Regenerate interrupt for some reason
		enable();
	}
}


#ifdef PDOC

DESCRIPTION

Send one command byte. For use during initialization ONLY.
This routine reads and discards any extraneous data bytes that
may occur during an attempt to write a command byte.

RETURNS
	YES = success.
	NO = failure.
	mpuErrorCode set on failure.
#endif

int sendInitCommand(MpuPortT *mpu,UCHAR cmd) {
	int j;

#ifdef TRACE_CMDS
		printf("Cmd(%02X) ",cmd);
#endif

	FOREVER { // Until byte sent

		// Wait for ready
		while (!isReadyToSend(mpu)) {
			if (((inp(mpu->statusPort)) & MPU_NOT_READY_TO_RECEIVE) == 0) {
				j = 0;
				while ((( inp(mpu->statusPort)) & MPU_NOT_READY_TO_RECEIVE) == 0) {
					inp(mpu->dataPort);	/* Discard a received message */
					if (j++ > EXTRA_READS_ON_OPEN) {
						mpu->mpuErrno = MPU_EHARDWARE;
						return NO;
					}
				}
			} else {
				mpu->mpuErrno = MPU_EHARDWARE;
				return NO;
			}
		}

		disable();
		outp(mpu->commandPort,cmd);
		while (((inp(mpu->statusPort)) & MPU_NOT_READY_TO_RECEIVE) != 0)
			NOTHING;
		if (inp(mpu->dataPort) == ACK_MSG) {
			enable();
			return YES;
		}
		enable();
	}
}

#ifdef DOC

DESCRIPTION

Read a data byte from an MPU port.

RETURNS
	-1 => error condition
	else a data byte
#endif

static int _traceReads = NO;

int ReadData(MpuPortT *mpu) {
	int val;

	if (mpu->overrunError) {
		mpu->mpuErrno = MPU_OVERRUN;
		return -1;
	}
	if (mpu->ungetChar != -1) {
		val = mpu->ungetChar;
		mpu->ungetChar = -1;
		return val;
	}
	while (!DataReady(mpu))
		NOTHING;
	val = mpu->readBuffer[mpu->dataHead];
	++mpu->dataHead;
	if (mpu->dataHead == mpu->bufferSize) {
		mpu->dataHead = 0;
	}
	if (_traceReads) {
		printf("(%02X)",val);
	}
	return val;
}

#ifdef DOC

DESCRIPTION

Return the last character to the read buffer. Only one character may be
ungotten.
#endif

void UngetData(MpuPortT *mpu,UCHAR data) {
	mpu->ungetChar = data;
}
#ifdef DOC

DESCRIPTION

Create an MPU port. If any of the parameters are zero, default values
are used.

RETURNS

If NULL, then error. Call GetMpuError() to get error code.
else, a pointer to a newly opened MpuPortT.
#endif

MpuPortT *CreateMpuPort(
	int base_address,    	// Mpu Base address (default 0x330)
	int interrupt_number,   // Mpu Interrupt (default 2)
	int buffer_size,		// Receive buffer size (default 1024)
	enum MpuOperatingModeT operating_mode, // Operating mode:
							 // STOP_MODE,
							 // RECORD_MODE,
							 // PLAY_MODE,
							 // RECORDPLAY_MODE
	enum MpuClockT clock_source, // 	MPU_INTERNAL_CLOCK
								 // or	MPU_MIDI_CLOCK
								 // or	MPU_FSK_CLOCK
	int tempo,					// Beats per minute
	enum MpuTimebaseT timebase, // Ticks per beat (see MpuTimebaseT in MPU.H)
	int metronome_measure_length, // beats per measure,0-> metronome off
	int mode,				// See DESCRIPTION
	int midi_channel_mask,  // bit n controls midi channel n+1
							// bit = 0 -> pass through without host intervention
							// bit = 1 -> record/filter this midi channel
	int *result				// retcode is placed here
) {
	static int timebaseTicks[] = {
	  48,72,96,120,144,168,192
	};


	MpuPortT *mpu;
	int t;

	if (result)
		*result = 0;

	if (base_address == 0)
		base_address = DEFAULT_BASE_ADDRESS;
	if (interrupt_number == 0)
		interrupt_number = DEFAULT_INTERRUPT;
	if (buffer_size == 0)
		buffer_size = DEFAULT_READ_BUFFER_SIZE;

	mpu = malloc(sizeof(MpuPortT));
	if (mpu == NULL) {
		if (result)
        	*result = MPU_NOMEM;
		return NULL;
	}

	mpu->dataPort = base_address;
	mpu->commandPort = base_address+1;
	mpu->statusPort = base_address+1;
	mpu->mpuInterrupt = interrupt_number;
	mpu->currentTime = 0;
	mpu->ungetChar = -1;
	mpu->_inSysex = 0;

	mpu->readBuffer = malloc(buffer_size);
	if (mpu->readBuffer == NULL) {
		free(mpu);
		mpu->mpuErrno = MPU_NOMEM;
		return NULL;
	}
	mpu->bufferSize = buffer_size;
	mpu->dataHead = 0;
	mpu->dataTail = 0;
	mpu->overrunError = NO;
	mpu->currentMode = STOP_MODE;
	mpu->lastCommand = 0;
	mpu->trackRequestHandler = NULL;
	mpu->requestHandlerData = NULL;
	mpu->mpuErrno = 0;

	// Synch up on a timing byte to start.
	for (t = 0; t < MAX_TIMING_BYTE_DELAY; ++t) {
		if ((( inp(mpu->statusPort)) & MPU_NOT_READY_TO_RECEIVE) == 0) {
			inp(mpu->dataPort);
		}
	}

	if (!sendInitCommand(mpu,MPU_RESET_CMD)) {
		*result = MPU_NOHARDWARE;
		free(mpu->readBuffer);
		free(mpu);
		return NULL;
	}

	switch (clock_source) {
		case MPU_INTERNAL_CLOCK:
			sendInitCommand(mpu,INT_CLOCK_CMD);
			break;
		case MPU_MIDI_CLOCK:
			sendInitCommand(mpu,MIDI_CLOCK_CMD);
			break;
		case MPU_FSK_CLOCK:
			sendInitCommand(mpu,FSK_CLOCK_CMD);
			break;
	}
	sendInitCommand(mpu,TIMING_BYTE_ALWAYS_CMD);
	if (mode & MPU_RX_MODE) {
		sendInitCommand(mpu,MODE_MESS_ON_CMD);
	}
	if (mode & MPU_EXCLUSIVE_THRU) {
		sendInitCommand(mpu,EXCLUSIVE_THRU_ON_CMD);
	}
	if (mode & MPU_RX_COMMON) {
		sendInitCommand(mpu,COMMON_TO_HOST_ON_CMD);
	}
	if (MPU_RX_REALTIME) {
		sendInitCommand(mpu,REAL_TIME_TO_HOST_ON_CMD);
	}
	if (metronome_measure_length) {
		sendInitCommand(mpu,MIDI_METRONOME_CMD);
		SendData(mpu,metronome_measure_length);
		sendInitCommand(mpu,METRONOME_W_ACCENTS_CMD);
	} else {
		sendInitCommand(mpu,METRONOME_OFF_CMD);
	}
	if (mode & MPU_RX_BENDER) {
		sendInitCommand(mpu,BENDER_ON_CMD);
	} else {
		sendInitCommand(mpu,BENDER_OFF_CMD);
	}
	if (mode & MPU_VOICES_THRU)
		sendInitCommand(mpu,MIDI_THRU_ON_CMD);
	else
		sendInitCommand(mpu,MIDI_THRU_OFF_CMD);
	if (mode & MPU_RX_EXCLUSIVE)
		sendInitCommand(mpu,EXCLUSIVE_TO_HOST_ON_CMD);
	else
		sendInitCommand(mpu,EXCLUSIVE_TO_HOST_OFF_CMD);
	sendInitCommand(mpu,TIMEBASE_48_CMD+timebase);
	sendInitCommand(mpu,SET_TEMPO_CMD);
	SendData(mpu,tempo);

	sendInitCommand(mpu,MIDI_CHANNEL_MASK_LO_CMD);
	SendData(mpu,midi_channel_mask);
	sendInitCommand(mpu,MIDI_CHANNEL_MASK_HI_CMD);
	SendData(mpu,midi_channel_mask >> 8);


	if (!installInterruptHandler(mpu)) { // Can use normal SendCommand after this!
		free(mpu->readBuffer);
		free(mpu);
		return NULL;
	}

	sendInitCommand(mpu,CLEAR_PLAY_COUNTERS_CMD);
	sendInitCommand(mpu,CLEAR_RECORD_COUNTER_CMD);

	mpu->currentMode = STOP_MODE;

	SetMpuOperatingMode(mpu,operating_mode);

	return mpu;
}

#ifdef DOC

DESCRIPTION

Stops the MPU. Sets PLAY, RECORD and MIDI modes to STOP.
Waits for MPU to complete pending PLAY operations.
#endif

void StopMpu(MpuPortT *mpu) {
	int data;

	switch (mpu->currentMode) {
	case PLAY_MODE:
		SendCommand(mpu,PLAY_STOP | MIDI_STOP);
		break;
	case RECORD_MODE:
		SendCommand(mpu,RECORD_STOP | MIDI_STOP);
		break;
	case RECORDPLAY_MODE:
		SendCommand(mpu,RECORD_STOP | PLAY_STOP | MIDI_STOP);
		break;
	}
	if (mpu->currentMode == RECORD_MODE
		|| mpu->currentMode == RECORDPLAY_MODE) {
		// Wait for End of recording
		do {
			data = ReadData(mpu);
		} while (data != ALL_END_MSG && data != -1);
	}
	mpu->currentMode = STOP_MODE;
}


#ifdef DOC
DESCRIPTION

Set Mpu Operating mode:

STOP_MODE: Don't send or receive messages.
RECORD_MODE: Receive messages
PLAY_MODE: Allow sending of scheduled messages, receive no messages.
RECORDPLAY_MODE: Allow sending of scheduled messages, receive messages.

BUGS

Doesn't currently support CONTINUE operation.

#endif

void SetMpuOperatingMode(MpuPortT *mpu,MpuOperatingModeT operating_mode) {
	switch (operating_mode) {
	case STOP_MODE:
		StopMpu(mpu);
		break;
	case RECORD_MODE:
		SendCommand(mpu,RECORD_START | MIDI_START);
		break;
	case PLAY_MODE:
		SendCommand(mpu,PLAY_START | MIDI_START);
		break;
	case RECORDPLAY_MODE:
		SendCommand(mpu,PLAY_START | RECORD_START | MIDI_START);
		break;
	}
}

#ifdef DOC

DESCRIPTION

Destroy an MPU port. Deallocates memory, and removes the interrupt.

RETURNS

YES -> success.
NO -> failure
Call GetMpuStatus for error code.
#endif

BOOL DestroyMpu(MpuPortT *mpu) {
	if (mpu->currentMode != STOP_MODE) {
		StopMpu(mpu);
	}
	if (mpu == NULL)
		return YES;
	removeInterrupt(mpu);
	free(mpu->readBuffer);
	free(mpu);
	return YES;
}




