/***************************************************************************
*   NAME:  MIDIFIX.C $Revision: 1.24 $
**  COPYRIGHT:
**  "Copyright (c) 1994,1995 by e-Tek Labs"
**
**       "This software is furnished under a license and may be used,
**       copied, or disclosed only in accordance with the terms of such
**       license and with the inclusion of the above copyright notice.
**       This software or any other copies thereof may not be provided or
**       otherwise made available to any other person. No title to and
**       ownership of the software is hereby transfered."
****************************************************************************
* $Log: midifix.c $
* Revision 1.24  1995/11/29 10:01:23  mleibow
* Added FX init macros.
* Revision 1.23  1995/11/22 04:17:58  mleibow
* Added GM, and GS reset
* Added GS sysex messages for chorus and reverb macros
* Revision 1.22  1995/10/26 14:48:37  teckert
* Fixed ACT failures.
* Revision 1.21  1995/10/24 11:26:32  sdsmith
* Fixes for Windows 95 compatability tests
* Revision 1.20  1995/10/24 11:25:40  unknown
* Fixes for Windows 95 compatability tests
* Revision 1.19  1995/10/24 11:25:03  unknown
* Fixes for Windows 95 compatability tests
* Revision 1.18  1995/10/13 17:33:08  mleibow
* Changed patch loader to use iwllist.c and iwllist.h instead of list.c and list.h
* Revision 1.16  1995/09/29 13:45:35  teckert
* Added iwReloadPatchMap and fixed editable patch loading (i.e. now makes sure
* that any non-editable patch with the same patch and bank # is unloaded).
* Revision 1.15  1995/09/05 15:55:29  teckert
* Added PnP style device capabilities structure.
* Revision 1.14  1995/07/11 10:27:18  teckert
* Added function to clear user flags when synth client closes
* Revision 1.13  1995/06/28 11:36:56  teckert
* Rewrote patch caching strategy to handle low memory conditions.
* Revision 1.12  1995/05/03 14:18:13  teckert
* Revision 1.11  1995/04/28 16:02:35  teckert
* Bug fixes including MCI pause-unpause anomaly and sysex message processing
* defered with callback
* Revision 1.10  1995/04/19 18:37:12  teckert
* Updated with new patch.c helper routines
* Revision 1.9  1995/04/19 17:18:45  mleibow
* Rewrote patch loading code
* Revision 1.8  1995/04/18 22:55:36  mleibow
* sysex messages were being interpreted even if they were for another synth.
* Revision 1.7  1995/04/10 13:47:52  teckert
* added allocation bug fixes
* Revision 1.6  1995/04/04 15:35:41  teckert
* Fixed hardware acquisition code
* Revision 1.5  1995/03/30 16:38:59  teckert
* Updated arbitration support
* Revision 1.4  1995/03/17 17:42:22  teckert
* Added hardware allocate and free calls to midi out devices
* Revision 1.3  1995/03/01 16:56:54  unknown
* Added file header
* Revision 1.1  1995/02/23 15:14:59  sdsmith
* Initial revision
***************************************************************************/
#include <windows.h>
#include <mmsystem.h>
#include <mmddk.h>
#include <iw.h>
#include <os.h>
#include "midi.h"
#include "interwav.h"
#include <viwd.h>
					  
#define DisableInterrupts OS_PUSH_DISABLE
#define RestoreInterrupts OS_POPF
#define TILED_SEL_INC 0x0008 
#define TRANSMIT_BUFFER_EMPTY 0x02
#define MODM_QUERYMEM   1000
			
// MIDI status codes                         
#define MIDI_NOTEOFF            0x80
#define MIDI_NOTEON             0x90
#define MIDI_POLYKEYPRESSURE    0xA0
#define MIDI_CONTROL            0xB0
#define MIDI_PROGRAMCHANGE      0xC0
#define MIDI_CHANNELPRESSURE    0xD0
#define MIDI_PITCHBEND          0xE0
#define MIDI_SYSEX              0xF0
#define MIDI_EOX                0xF7
#define MIDI_TIMINGCLOCK        0xF8

// Codes for midi parser status (MPS_WFxxx means "midi parser status == waiting for xxx")
#define MPS_WFSTATUS        0
#define MPS_WFBYTEONE       1
#define MPS_WFBYTETWO       2
#define MPS_INSYSEX         3

// Codes for sysex parser status (SPS_WFxxx means "sysex parser status == waiting for xxx")
#define SPS_INACTIVE        0
#define SPS_WFMID           1
#define SPS_WFSUBIDONE      2
#define SPS_WFSUBIDTWO      3
#define SPS_WFIWMESSAGE     4
#define SPS_WFPATCHEDMSG    5
#define SPS_WFPROGRAMMSB    6
#define SPS_WFPROGRAMLSB    7
#define SPS_WFBANK          8
#define SPS_WFCOMMAND       9
#define SPS_WFSUBCMND       10
#define SPS_WFDATA          11
#define SPS_WFENDOFSYSEX    12
#define SPS_DO_ROLAND	    13
#define SPS_DO_UNIVERSAL    14

MIDICLIENT MidiClients[MAXMIDICLIENTS+2] = {0};
DWORD dwSynthVolume = 0xFFFFFFFFul;
extern int nEnabled;
extern HTASK hTask;
int nGS = 1;

#define MIDI_Q_LENGTH 32

typedef struct MidiQEntry_tag {           
	int nClient;
	int nIsBuffer;                  // 0 for message, 1 for buffer
	union {
		unsigned char msg[3];
		LPMIDIHDR lpHdr;
		} u;
} MidiQEntry;
MidiQEntry MidiQueue[MIDI_Q_LENGTH];
int nMQIn = 0;
int nMQOut = 0;

/* Size of MIDI messages:  8  9  A  B  C  D  E  F */
int MidiMessageSize[8] = { 3, 3, 3, 3, 2, 2, 3, 0 };
#define MessageSize(x) (MidiMessageSize[((x)&0x70)>>4])

#define MAKEMESSAGE(a,b,c)  ((DWORD)((a)&0xff)|(((b)&0xff)<<8)|(((DWORD)(c)&0x000000ff)<<16))

int ParseBuffer( int nClient, LPMIDIHDR lpHdr );
void FAR PASCAL midiCallback( int nClient, WORD msg, DWORD dw1, DWORD dw2);
void FAR PASCAL modGetDevCaps( WORD wID, LPBYTE lpCaps, WORD wSize);


/****************************************************************************
The MIDI input buffer
*/
#define MIB_SIZE 32
int MIB_In = 0;
int MIB_Out = 0;
BYTE MidiInBuffer[MIB_SIZE];


/****************************************************************************

FUNCTION DEFINITION:
PostCallbackMsg - posts a message to the callback

DESCRIPTION:
This function uses the PostAppMessage() function to add a message to the queue
of the IWLOADER program.  The IWLOADER program calls the MessageCallback()
function in the driver in the foreground where file access is possible.

RETURNS: void
*/
void PostCallbackMsg(
	WORD wParam,            // contains MC_LOADPATCH or MC_CACHEPATCH code
	LONG lParam)            // contains PatchListEntry pointer for MC_LOADPATCH message
						// for MC_CACHEPATCH message contains:
						// 
{
	if(hTask)
		PostAppMessage(hTask,WM_USER,wParam,lParam);
}


/****************************************************************************

FUNCTION DEFINITION:
AddMessage - adds a midi message to the queue

DESCRIPTION:                                 
In response to a MODM_DATA message this function is called to put the message into the
queue.

RETURNS: void
*/
int AddMessage(
	int nClient,                // index of the client that sent the message
	DWORD dwMessage)            // the midi message
{
	DisableInterrupts();

	if(((nMQIn + 1)%MIDI_Q_LENGTH)==nMQOut){        // Q is full
		RestoreInterrupts();
		return 0;
	}
	
	MidiQueue[nMQIn].nIsBuffer = 0;
	MidiQueue[nMQIn].nClient = nClient;
	MidiQueue[nMQIn].u.msg[0] = LOBYTE(LOWORD(dwMessage));
	MidiQueue[nMQIn].u.msg[1] = HIBYTE(LOWORD(dwMessage));
	MidiQueue[nMQIn].u.msg[2] = LOBYTE(HIWORD(dwMessage));
	
	if ( ++nMQIn == MIDI_Q_LENGTH )
		nMQIn = 0;                   
		
	RestoreInterrupts();
	return 1;
}

	
/****************************************************************************

FUNCTION DEFINITION:
AddBuffer - adds a midi buffer to the queue

DESCRIPTION:
In response to a MODM_LONGDATA message this function is called to put the midi data
buffer into the queue.

RETURNS: 1 if the buffer wav added to the Q, 0 if the Q was full
*/
int AddBuffer(
	int nClient,                // index of the client that sent the buffer
	LPMIDIHDR lpHdr)            // a pointer to the midi buffer
{                  
	DisableInterrupts();
	
	if(((nMQIn + 1)%MIDI_Q_LENGTH)==nMQOut){   // Q is full
		RestoreInterrupts();
		return 0;
	}

	MidiQueue[nMQIn].nIsBuffer = 1;
	MidiQueue[nMQIn].nClient = nClient;
	MidiQueue[nMQIn].u.lpHdr = lpHdr;
	
	MidiQueue[nMQIn].u.lpHdr->dwFlags |= MHDR_INQUEUE;
	MidiQueue[nMQIn].u.lpHdr->dwFlags &= ~MHDR_DONE;

	if ( ++nMQIn == MIDI_Q_LENGTH )
		nMQIn = 0;                   
		
	RestoreInterrupts();
	return 1;
}


/****************************************************************************

FUNCTION DEFINITION:
DoMidiMessage - executes a single midi message

DESCRIPTION:
The messages that are handled at this level are bank switching and drum program
changes, the rest are passed through to the kernel.

RETURNS: void
*/
void DoMidiMessage(
	int nClient,                // index of the client that sent the message
	unsigned char status,       // status byte of the midi message
	unsigned char second,       // second byte of the midi message
	unsigned char third)        // third byte of the midi message
{
	unsigned char nCh;
	struct patch far *pPatch;
	
	nCh = nClient * 16 + (status & 0x0f);
	
	switch(status & 0xf0){
		case MIDI_NOTEOFF:      
			NoteOff(nClient,status&0x0f,second);
			break;
		case MIDI_NOTEON:
			NoteOn(nClient,status&0x0f,second,third);
			break;
		case MIDI_POLYKEYPRESSURE:
			break;                              // not implemented
		case MIDI_CONTROL:
			if(second == 0x00){             // bank select
				if(nGS)                                         // GS this is a seven bit bank
					MidiClients[nClient].CurBank[status&0x0f] = third;
				else{                                           // MIDI this is the upper 7 bits of a 14 bit bank
					MidiClients[nClient].CurBank[status&0x0f] &= ~0x3F80;
					MidiClients[nClient].CurBank[status&0x0f] |= third<<7;
				}
			}else if(second == 0x20){        // MIDI this is the lower 7 bits of a 14 bit bank
				if(nGS==0){
					MidiClients[nClient].CurBank[status&0x0f] &= ~0x007F;
					MidiClients[nClient].CurBank[status&0x0f] |= third;
				}                    
			}else{
				iw_midi_parameter(nCh,second,third);
			}
			break;
		case MIDI_PROGRAMCHANGE:
			if((status&0x0f)==0x09){
				if(nGS)
					MidiClients[nClient].CurBank[0x09] = second;
			}else{
				ProgramAssign(nClient, status&0x0f, second);
			}
			break;
		case MIDI_CHANNELPRESSURE:
			break;                          // not implemented
		case MIDI_PITCHBEND:
			iw_midi_pitch_bend(nCh,second,third);
			break;
	}
}

				  
/****************************************************************************

FUNCTION DEFINITION:
DoQueue - handles any messages or buffers in the queue

DESCRIPTION:
After a message or buffer is added to the queue this function is called to handle
the message(s).  It is re-entrant and will continue until the queue is empty.

RETURNS: void
*/
void DoQueue(void)
{
	static int nInQHandler = 0;    
	
	DisableInterrupts();
	if(nInQHandler++ || (wIWDriverFlags & IWDF_SYSEXPENDING)){
		nInQHandler--;
		RestoreInterrupts();
		return;
	}
	
	while(nMQIn != nMQOut) {
		RestoreInterrupts();              
		
		if ( MidiQueue[nMQOut].nIsBuffer ){
			if(ParseBuffer( MidiQueue[nMQOut].nClient, MidiQueue[nMQOut].u.lpHdr )){
				// Suspend midi Q processing
				DisableInterrupts();
				break;
			}
			MidiQueue[nMQOut].u.lpHdr->dwFlags &= ~MHDR_INQUEUE;
			MidiQueue[nMQOut].u.lpHdr->dwFlags |= MHDR_DONE;
			midiCallback( MidiQueue[nMQOut].nClient, MOM_DONE, (DWORD)MidiQueue[nMQOut].u.lpHdr, 0 );
		}
		else{
			DoMidiMessage( MidiQueue[nMQOut].nClient, MidiQueue[nMQOut].u.msg[0], 
						   MidiQueue[nMQOut].u.msg[1], MidiQueue[nMQOut].u.msg[2] );
		}
		
		if ( ++nMQOut == MIDI_Q_LENGTH )
			nMQOut = 0;
		DisableInterrupts();
	}
	nInQHandler--;
	RestoreInterrupts();
}

/****************************************************************************

FUNCTION DEFINITION:
ResetMidi - resets the midi device

DESCRIPTION:
Resets the midi device by stopping any playing notes and restoring controllers
to default values

RETURNS:
*/
void ResetMidi( 
	int nClient )                   // index of the client to be reset
{
	int i;

	iw_start_effect(0, 4); /* default - reverb macro 4 */
	iw_start_effect(1, 2); /* default - chorus macro 2 */
	for (i=nClient*16; i < nClient*16+16 ; i++) {
		iw_midi_all_notes_off(i);
		iw_midi_parameter(i, 0x78, 0); /* all sounds off */
		iw_midi_parameter(i, 0x79, 0); /* reset all controllers */
		iw_midi_parameter(i, 100, 0); /* RPN MSB 0 */
		iw_midi_parameter(i, 101, 0); /* RPN LSB 0 */
		iw_midi_parameter(i, 6, 2); /* Pitch Bend Sensitivity MSB */
		iw_midi_parameter(i, 38, 0); /* Pitch Bend Sensitivity LSB */
	}
}


static void process_roland_sysex(char *buf, int n, int client)
{
    int i;
    unsigned short csum;

    if (n < 3) return;
    if (buf[0] == 0x10 && (buf[1] == 0x42 || buf[1] == 0x45)) {
		/* roland Sound Canvas GS, SCC-1, SC-55, SC-55mkII */
		if (buf[2] == 0x12) { /* data set 1 */
			csum = 0;
			for (i=3; i < n-1; i++) {
				csum += buf[i];
			}
			csum = (128 - (csum & 0x7f)) & 0x7f;
			if (csum != (unsigned short)buf[n-1]) return; /* invalid csum */
			/* make sure we have a 3-byte address, and 1 byte data */
			if (n < 8) return;
			if (buf[3] == 0x40) {
				if (buf[4] == 00 && buf[5] == 0x7f && buf[6] == 0) { /* GS RESET */
				    ResetMidi(client);
				} else if (buf[4] == 01) {
					if (buf[5] == 0x30 || buf[5] == 0x31) { /* reverb macro */
						if ((int)buf[6] >= 0 && (int)buf[6] <= 7) {
							iw_start_effect(0, buf[6]);
						}
					} else if (buf[5] == 0x38 || buf[5] == 0x39) { /* chorus macro */
						if ((int)buf[6] >= 0 && (int)buf[6] <= 7) {
							iw_start_effect(1, buf[6]);
						}
					}
				}
			}
		}
    }
    return;
}

static void process_universal_sysex(char *buf, int n, int client)
{
    int i;
    unsigned short csum;

    if (n < 3) return;
    if (   buf[0] == 0x7f /* Broadcast */
	    && buf[1] == 0x09 /* General Midi Message */
	    && buf[2] == 0x01) { /* General Midi ON */
		ResetMidi(client);
	}
    return;
}

/****************************************************************************

FUNCTION DEFINITION:
HandleSysexByte - parses the next byte of a sysex message

DESCRIPTION:
Parses sysex message byte by byte by storing the byte and updating the parser
status.

RETURNS:1 if the message is complete, 0 otherwise
*/
int HandleSysexByte(
	int nClient,                    // the client that sent the sysex message
	unsigned char NextByte )        // the sysex message byte
{
	if( NextByte == MIDI_EOX ){
	    /* if sysex parser state ends at SPS_WFENDOFSYSEX, then error occurred */
	    if (MidiClients[nClient].nSysexParserStatus == SPS_WFENDOFSYSEX) {
			MidiClients[nClient].nSysexParserStatus = SPS_INACTIVE;
			return(1);
	    }
	    /* All valid sysex messages should end at state SPS_WFDATA or SPS_DOROLAND */
	    if (MidiClients[nClient].nSysexParserStatus == SPS_DO_ROLAND) {
			process_roland_sysex(MidiClients[nClient].SysMsg.data, MidiClients[nClient].SysMsg.nextdatabyte, nClient);
	    } else if (MidiClients[nClient].nSysexParserStatus == SPS_DO_UNIVERSAL) {
			process_universal_sysex(MidiClients[nClient].SysMsg.data, MidiClients[nClient].SysMsg.nextdatabyte, nClient);
	    } else if (MidiClients[nClient].nSysexParserStatus == SPS_WFDATA) {
			if (MidiClients[nClient].SysMsg.nextdatabyte) {
				MidiClients[nClient].SysMsg.data[MidiClients[nClient].SysMsg.nextdatabyte-1] = 0; /* NULL terminator */
			}
			if( MidiClients[nClient].SysMsg.checksum == 0 ){
				if(wIWDriverFlags & IWDF_INFOREGROUND){
					DoSysexMessage(nClient);
				}else{
					wIWDriverFlags |= IWDF_SYSEXPENDING;
					PostCallbackMsg(MC_DOSYSEX,nClient);
				}
			}
		}
	    MidiClients[nClient].nSysexParserStatus = SPS_INACTIVE;
	    return 1;                               // Signal end of sysex message
	}
	if( NextByte == MIDI_SYSEX ){               // Start of sysex message
		MidiClients[nClient].nSysexParserStatus = SPS_WFMID;
	}
	else if( NextByte > MIDI_SYSEX ){           // Some other sysex message
		MidiClients[nClient].nSysexParserStatus = SPS_WFENDOFSYSEX;
	}                                        
	else {
		switch(MidiClients[nClient].nSysexParserStatus){   // Data
			case SPS_INACTIVE:
				break;
			case SPS_WFMID: 
				if(NextByte == 0)               // Our MID
					MidiClients[nClient].nSysexParserStatus = SPS_WFSUBIDONE;
				else if (NextByte == 0x41) {	// Roland's MID
					MidiClients[nClient].SysMsg.nextdatabyte = 0;
					MidiClients[nClient].nSysexParserStatus = SPS_DO_ROLAND;
				} else if (NextByte == 0x7e) { // universal sysex
					MidiClients[nClient].SysMsg.nextdatabyte = 0;
					MidiClients[nClient].nSysexParserStatus = SPS_DO_UNIVERSAL;
				} else
					MidiClients[nClient].nSysexParserStatus = SPS_WFENDOFSYSEX;
				break;
			case SPS_WFSUBIDONE:
				if(NextByte == 0)               // Our MID
					MidiClients[nClient].nSysexParserStatus = SPS_WFSUBIDTWO;
				else
					MidiClients[nClient].nSysexParserStatus = SPS_WFENDOFSYSEX;
				break;
			case SPS_WFSUBIDTWO:
				if (NextByte == 0x6F)               // Our MID
					MidiClients[nClient].nSysexParserStatus = SPS_WFIWMESSAGE;
				else
					MidiClients[nClient].nSysexParserStatus = SPS_WFENDOFSYSEX;
				break;
			case SPS_WFIWMESSAGE:
				if (NextByte == 0)
					MidiClients[nClient].nSysexParserStatus = SPS_WFPATCHEDMSG;
				else
					MidiClients[nClient].nSysexParserStatus = SPS_WFENDOFSYSEX;
				break;
			case SPS_WFPATCHEDMSG:
				if (NextByte == 0)
					MidiClients[nClient].nSysexParserStatus = SPS_WFPROGRAMMSB;
				else
					MidiClients[nClient].nSysexParserStatus = SPS_WFENDOFSYSEX;
				break;
			case SPS_WFPROGRAMMSB:
				MidiClients[nClient].SysMsg.checksum = NextByte;
				MidiClients[nClient].SysMsg.program = NextByte==1?128:0;
				MidiClients[nClient].nSysexParserStatus = SPS_WFPROGRAMLSB;
				break;
			case SPS_WFPROGRAMLSB:
				MidiClients[nClient].SysMsg.checksum += NextByte;
				MidiClients[nClient].SysMsg.checksum &= 0x7F;
				MidiClients[nClient].SysMsg.program += NextByte;
				MidiClients[nClient].nSysexParserStatus = SPS_WFBANK;
				break;
			case SPS_WFBANK:
				MidiClients[nClient].SysMsg.checksum += NextByte;
				MidiClients[nClient].SysMsg.checksum &= 0x7F;
				MidiClients[nClient].SysMsg.bank = NextByte;
				MidiClients[nClient].nSysexParserStatus = SPS_WFCOMMAND;
				break;
			case SPS_WFCOMMAND:
				MidiClients[nClient].SysMsg.checksum += NextByte;
				MidiClients[nClient].SysMsg.checksum &= 0x7F;
				if(NextByte >= 0 && NextByte <= SXM_MAXCOMMAND){
					MidiClients[nClient].SysMsg.command = NextByte;
					MidiClients[nClient].nSysexParserStatus = SPS_WFSUBCMND;
				}
				else {
					MidiClients[nClient].nSysexParserStatus = SPS_WFENDOFSYSEX;
				}
				break;
			case SPS_WFSUBCMND:
				MidiClients[nClient].SysMsg.checksum += NextByte;
				MidiClients[nClient].SysMsg.checksum &= 0x7F;
				MidiClients[nClient].SysMsg.subcmnd = NextByte;
				MidiClients[nClient].nSysexParserStatus = SPS_WFDATA;
				MidiClients[nClient].SysMsg.nextdatabyte = 0;
				break;
			case SPS_WFDATA:
				MidiClients[nClient].SysMsg.checksum += NextByte;
				MidiClients[nClient].SysMsg.checksum &= 0x7F;
				MidiClients[nClient].SysMsg.data[MidiClients[nClient].SysMsg.nextdatabyte++] = NextByte;
				break;
			case SPS_DO_ROLAND:
				MidiClients[nClient].SysMsg.checksum += NextByte;
				MidiClients[nClient].SysMsg.checksum &= 0x7F;
				if (MidiClients[nClient].SysMsg.nextdatabyte < MAXSYSEXDATA)
				    MidiClients[nClient].SysMsg.data[MidiClients[nClient].SysMsg.nextdatabyte++] = NextByte;
			    break;
			case SPS_DO_UNIVERSAL:
				MidiClients[nClient].SysMsg.checksum += NextByte;
				MidiClients[nClient].SysMsg.checksum &= 0x7F;
				if (MidiClients[nClient].SysMsg.nextdatabyte < MAXSYSEXDATA)
				    MidiClients[nClient].SysMsg.data[MidiClients[nClient].SysMsg.nextdatabyte++] = NextByte;
			    break;
			case SPS_WFENDOFSYSEX:
				break;
		}
	}      
	return 0;
}                                                                             

			
/****************************************************************************

FUNCTION DEFINITION:
ParseBuffer - parses a buffer of midi data

DESCRIPTION:
Parses the buffer of midi data by sorting out midi messages, midi running-status
messages and sysex messages and calls the appropriate routine to execute the 
instruction.

RETURNS: 0 if MIDI queue parsing can continue, 1 if it should stop
*/
int ParseBuffer(
	int nClient,                    // index of the client that sent the buffer
	LPMIDIHDR lpHdr )               // pointer to the midi buffer
{
	DWORD nBytesLeft;
	LPSTR lpCurLoc;
	unsigned char NextByte;
	int nDeferFurtherParsing = 0;
	
	// Restore the pointers to the buffer being parsed or start parsing
	if(MidiClients[nClient].lpBufferQueue == lpHdr){
		nBytesLeft = MidiClients[nClient].dwBytesRecorded;
		lpCurLoc = MidiClients[nClient].lpCurPos;
		MidiClients[nClient].lpBufferQueue = NULL;
	}else{
		nBytesLeft = lpHdr->dwBufferLength;
		lpCurLoc = lpHdr->lpData;
	}
	
	while(nBytesLeft && !nDeferFurtherParsing){
		NextByte = *lpCurLoc;
		
		if( NextByte >= MIDI_TIMINGCLOCK ){;// If its a real-time sysex skip it
		}                                          
		else if( NextByte >= MIDI_NOTEOFF ){// If its a status byte set current status
			MidiClients[nClient].bStatus = NextByte;
			MidiClients[nClient].bByteOne = 0;
			MidiClients[nClient].bByteTwo = 0;
			if( NextByte >= MIDI_SYSEX ){  // If its a sysex send it to HandleSysexByte()
				if( HandleSysexByte(nClient, NextByte) )        // If the message is done reset the parser
					MidiClients[nClient].nMidiParserStatus = MPS_WFSTATUS;
				else
					MidiClients[nClient].nMidiParserStatus = MPS_INSYSEX;
				MidiClients[nClient].bRunningStatus = 0;        // Clear the running status byte
			}
			else{                               // If its any other status set current status
				MidiClients[nClient].bRunningStatus = NextByte; // and get the data bytes
				MidiClients[nClient].nMidiParserStatus = MPS_WFBYTEONE;
			}
		}                                                            
		else {                              // Its data
			switch(MidiClients[nClient].nMidiParserStatus){
				case MPS_WFSTATUS:          // If there is no running status byte ignore the data
					if( MidiClients[nClient].bRunningStatus == 0 )
						break;              // otherwise, use running status and fall through
					MidiClients[nClient].bStatus = MidiClients[nClient].bRunningStatus;

				case MPS_WFBYTEONE:
					MidiClients[nClient].bByteOne = NextByte;
					if( MessageSize(MidiClients[nClient].bStatus) == 2){    // If message is complete, call MidiMessage
						DoMidiMessage( nClient,
									   MidiClients[nClient].bStatus, 
									   MidiClients[nClient].bByteOne,
									   0 );
						MidiClients[nClient].nMidiParserStatus = MPS_WFSTATUS;
					}
					else {                          // Otherwise wait for byte two
						MidiClients[nClient].nMidiParserStatus = MPS_WFBYTETWO;
					}
					break;                               
					
				case MPS_WFBYTETWO:                 // The message is complete
					MidiClients[nClient].bByteTwo = NextByte;
					DoMidiMessage( nClient,
								   MidiClients[nClient].bStatus, 
								   MidiClients[nClient].bByteOne, 
								   MidiClients[nClient].bByteTwo );
					MidiClients[nClient].nMidiParserStatus = MPS_WFSTATUS;
					break;
					
				case MPS_INSYSEX:
					if(HandleSysexByte(nClient, NextByte)){   // If the sysex message is complete
						MidiClients[nClient].nMidiParserStatus = MPS_WFSTATUS;  // go back to normal status        
						if(!(wIWDriverFlags & IWDF_INFOREGROUND)){      // Suspend further buffer processing
							nDeferFurtherParsing=1;
						}
					}
					break;
			}
		}
		lpCurLoc++;
		if(!LOWORD(lpCurLoc)){                      // If we rolled over a 64K boundary
			lpCurLoc = MAKELP(HIWORD(lpCurLoc)+TILED_SEL_INC,0);
		}
		nBytesLeft--;
	}
	if(nDeferFurtherParsing){
		MidiClients[nClient].lpBufferQueue = lpHdr;
		MidiClients[nClient].lpCurPos = lpCurLoc;
		MidiClients[nClient].dwBytesRecorded = nBytesLeft;
		return 1;
	}
	return 0;
}



/****************************************************************************

FUNCTION DEFINITION:
MidiPortOutByte - Outputs one byte to the uart

DESCRIPTION:
Waits until the port is ready to receive the next byte and writes it to the
port.  The function will timeout after waiting 500 milli-seconds.

RETURNS: void
*/
void MidiPortOutByte(
	BYTE bByte)                         // The byte to be written to the port
{
	DWORD dwTime;
					
	// if its a status byte other than a realtime message then set the running status
	// to this value
	if( bByte >= MIDI_NOTEOFF && bByte < MIDI_SYSEX )
		MidiClients[OUTPORTCLIENT].bRunningStatus = bByte;
	else if( bByte >= MIDI_SYSEX && bByte < MIDI_TIMINGCLOCK )
		MidiClients[OUTPORTCLIENT].bRunningStatus = 0;
					
	dwTime = timeGetTime();
	while(!(iw_uart_status() & IW_UART_TD)){
		if(timeGetTime() > dwTime+500)          // 500 milli-second timeout
			break;
	}           
	if(iw_uart_status() & IW_UART_TD)
		iw_uart_send((int)bByte);
}


/****************************************************************************

FUNCTION DEFINITION:
RecordSysexByte - Records the sysex byte into the midi-in buffer

DESCRIPTION:
This function is called from the ISR to handle receipt of the next midi byte
while receiving a sysex message.  It records the byte into the buffer and
returns the buffer if it is full or if the sysex message is complete.

RETURNS: 1 if the message is complete, 0 otherwise
*/
int RecordSysexByte(
	BYTE nData)
{       
	LPMIDIHDR lpHdr;
	
	if(!MidiClients[INPORTCLIENT].lpBufferQueue)
		return 0;
	
	*MidiClients[INPORTCLIENT].lpCurPos = nData;
	MidiClients[INPORTCLIENT].dwBytesRecorded++;
	
	if(!(LOWORD(MidiClients[INPORTCLIENT].lpCurPos+1)))
		MidiClients[INPORTCLIENT].lpCurPos = (LPBYTE)MAKELONG(0,HIWORD(MidiClients[INPORTCLIENT].lpCurPos)+TILED_SEL_INC);
	else
		MidiClients[INPORTCLIENT].lpCurPos++;
		
	if( nData==MIDI_EOX || MidiClients[INPORTCLIENT].dwBytesRecorded == 
						   MidiClients[INPORTCLIENT].lpBufferQueue->dwBufferLength){
		lpHdr = MidiClients[INPORTCLIENT].lpBufferQueue;
		MidiClients[INPORTCLIENT].lpBufferQueue = lpHdr->lpNext;
		lpHdr->dwBytesRecorded = MidiClients[INPORTCLIENT].dwBytesRecorded;
		lpHdr->dwFlags &= ~MHDR_INQUEUE;
		lpHdr->dwFlags |= MHDR_DONE;
		midiCallback(INPORTCLIENT, MIM_LONGDATA, (DWORD)lpHdr, MidiClients[INPORTCLIENT].dwTime);
		MidiClients[INPORTCLIENT].dwBytesRecorded = 0;
		if(MidiClients[INPORTCLIENT].lpBufferQueue)
			MidiClients[INPORTCLIENT].lpCurPos = MidiClients[INPORTCLIENT].lpBufferQueue->lpData;
		if(nData==MIDI_EOX)
			return 1;
	}                                                           
	return 0;
}


/****************************************************************************

FUNCTION DEFINITION:
SendLongError - sends the MIM_LONGERROR message to the midi-in client

DESCRIPTION:
This function is called when a sysex message is prematurely ended by receipt 
of a non-realtime status byte other than the EOX byte (end of sysex).

RETURNS: void
*/
void SendLongError(void)
{                                                 
	if(!MidiClients[INPORTCLIENT].lpBufferQueue || MidiClients[INPORTCLIENT].nMidiParserStatus != MPS_INSYSEX)
		return;
	OS_PUSH_DISABLE();
	MidiClients[INPORTCLIENT].lpBufferQueue->dwBytesRecorded = MidiClients[INPORTCLIENT].dwBytesRecorded;
	MidiClients[INPORTCLIENT].lpBufferQueue->dwFlags &= ~MHDR_INQUEUE;
	MidiClients[INPORTCLIENT].lpBufferQueue->dwFlags |= MHDR_DONE;
	midiCallback(INPORTCLIENT,MIM_LONGERROR,(DWORD)MidiClients[INPORTCLIENT].lpBufferQueue,MidiClients[INPORTCLIENT].dwTime);
	MidiClients[INPORTCLIENT].lpBufferQueue = MidiClients[INPORTCLIENT].lpBufferQueue->lpNext;
	MidiClients[INPORTCLIENT].dwBytesRecorded = 0;
	OS_POPF();
}


/****************************************************************************

FUNCTION DEFINITION:
GetTime - reports the time, relative to the MIDM_START message

DESCRIPTION:
Gets the current time using timeGetTime() to reduce overhead and returns the 
amount of time elapsed wince receipt of the MIDM_START message in milliseconds.

RETURNS: number of milliseconds since the MIDM_START message was received
*/
DWORD GetTime(void)
{
	DWORD dwTime;
	
	dwTime = timeGetTime();
	dwTime -= MidiClients[INPORTCLIENT].dwTimeZero;
	
	return dwTime;
}


/****************************************************************************

FUNCTION DEFINITION:
HandleMidiByteIn - Parses next input byte

DESCRIPTION:
This function parses the midi byte and sends the completed messages to the 
client with a callback.

RETURNS: void
*/
void HandleMidiByteIn(
	BYTE nData)                      // the data byte from the port
{                                        
	DWORD dwTime;               
	int nClient = INPORTCLIENT;
											
	if( nData == 0xff ){            // Midi Status Error or Reset
		if( MidiClients[nClient].nMidiParserStatus != MPS_INSYSEX){
			midiCallback(INPORTCLIENT,MIM_ERROR,
							 MAKEMESSAGE(MidiClients[nClient].bStatus,MidiClients[nClient].bByteOne,0),
							 MidiClients[nClient].dwTime);       
			MidiClients[nClient].nMidiParserStatus = MPS_WFSTATUS;
		}       
		return;
	}                                                   
	if(  nData >= MIDI_NOTEOFF){    // If its a status byte get the time
		dwTime = GetTime();
	}
	if( nData >= MIDI_TIMINGCLOCK ){    // If its a real-time sysex send it
		midiCallback(INPORTCLIENT,MIM_DATA,MAKEMESSAGE(nData,0,0),dwTime);
	}        
	else if( nData == MIDI_EOX ){
		if( MidiClients[nClient].nMidiParserStatus != MPS_INSYSEX){
			SendLongError();
		}else{
			RecordSysexByte(nData);
			MidiClients[nClient].nMidiParserStatus = MPS_WFSTATUS;
		}
	}
	else if( nData >= MIDI_NOTEOFF){    // If its a status byte check for a partial msg
		if( MidiClients[nClient].nMidiParserStatus != MPS_WFSTATUS ){
			MidiClients[nClient].nMidiParserStatus = MPS_WFSTATUS;
			MidiClients[nClient].bRunningStatus = 0;
			if( MidiClients[nClient].nMidiParserStatus == MPS_INSYSEX)
				SendLongError();
			else                
				midiCallback(INPORTCLIENT,MIM_ERROR,
							 MAKEMESSAGE(MidiClients[nClient].bStatus,MidiClients[nClient].bByteOne,0),
							 MidiClients[nClient].dwTime);
		}
		MidiClients[nClient].dwTime = dwTime;
		MidiClients[nClient].bStatus = nData;
		MidiClients[nClient].bByteOne = 0;
		MidiClients[nClient].bByteTwo = 0;
		if( nData >= MIDI_SYSEX ){      // If its a sysex send it to RecordSysexByte()
			if( RecordSysexByte(nData) )    // If the message is done reset the parser
				MidiClients[nClient].nMidiParserStatus = MPS_WFSTATUS;
			else
				MidiClients[nClient].nMidiParserStatus = MPS_INSYSEX;
			MidiClients[nClient].bRunningStatus = 0;// Clear the running status byte
		}                                                     
		else{                           // If its any other status set current status
			MidiClients[nClient].bRunningStatus = nData; // and get the data bytes
			MidiClients[nClient].nMidiParserStatus = MPS_WFBYTEONE;
		}                           
	}                                 
	else {                              // Its data
		switch(MidiClients[nClient].nMidiParserStatus){
			case MPS_WFSTATUS:          // If there is no running status byte ignore the data
				if( MidiClients[nClient].bRunningStatus == 0 ){
					midiCallback(INPORTCLIENT,MIM_ERROR,MAKEMESSAGE(nData,0,0),dwTime);
					break;
				}                       // otherwise, use running status and fall through
				MidiClients[nClient].bStatus = MidiClients[nClient].bRunningStatus;
				
			case MPS_WFBYTEONE:
				MidiClients[nClient].bByteOne = nData;
				if( MessageSize(MidiClients[nClient].bStatus) == 2){    // If message is complete, call MidiMessage
					midiCallback(INPORTCLIENT,MIM_DATA,
								 MAKEMESSAGE(MidiClients[nClient].bStatus,MidiClients[nClient].bByteOne,nData),
								 MidiClients[nClient].dwTime);
					MidiClients[nClient].nMidiParserStatus = MPS_WFSTATUS;
				}
				else {                          // Otherwise wait for byte two
					MidiClients[nClient].nMidiParserStatus = MPS_WFBYTETWO;
				}
				break;                               
					
			case MPS_WFBYTETWO:                 // The message is complete
				MidiClients[nClient].bByteTwo = nData;
				midiCallback(INPORTCLIENT,MIM_DATA,
							 MAKEMESSAGE(MidiClients[nClient].bStatus,MidiClients[nClient].bByteOne,nData),
							 MidiClients[nClient].dwTime);
				MidiClients[nClient].nMidiParserStatus = MPS_WFSTATUS;
				break;
					
			case MPS_INSYSEX:
				if( RecordSysexByte(nData) )    // If the sysex message is complete
					MidiClients[nClient].nMidiParserStatus = MPS_WFSTATUS;  // go back to normal status
				break;
		}
	}
}


/****************************************************************************

FUNCTION DEFINITION:
MidiByteIn - midi in port receive buffer full ISR

DESCRIPTION:
This function is called from the ISR to handle receipt of the next midi byte.
It stores the byte in the buffer and if the re-entry variable is not zero
it processes the bytes in the buffer until it is empty.  If the re-entry variable
is greater than one the routine exits.

RETURNS: 0 for success
*/
int MidiByteIn(
	int nStatus,                    // the port status
	int nData)                      // the data byte from the port
{
	static int InMidiInHandler = 0;
		
	OS_PUSH_DISABLE();      
	if((nStatus & IW_UART_ERR_FRAMING)||(nStatus & IW_UART_ERR_OVERRUN))
		MidiInBuffer[MIB_In++] = (BYTE)0xFF;
	else
		MidiInBuffer[MIB_In++] = (BYTE)nData;
		
	if(MIB_In==MIB_SIZE)
		MIB_In = 0;   
		
	InMidiInHandler++;
	if(InMidiInHandler > 1){
		InMidiInHandler--;
		OS_POPF();
		return 0;
	}

	while(MIB_Out != MIB_In){
		OS_POPF();
		HandleMidiByteIn(MidiInBuffer[MIB_Out++]);
		if(MIB_Out==MIB_SIZE)
			MIB_Out = 0;   
		OS_PUSH_DISABLE();
	}

	InMidiInHandler--;
	OS_POPF();
}


/****************************************************************************

FUNCTION DEFINITION:
midiCallback - This calls DriverCallback for a midi device.

DESCRIPTION:
Uses the MMSYSTEM DLL function DriverCallback to send the indicated message
to the client.

RETURNS: void
*/
void FAR PASCAL midiCallback(
	int nClient,                    // The MIDI client
	WORD msg,                       // The message to send.
	DWORD dw1,                      // Message dependant parameter one
	DWORD dw2)                      // Message dependant parameter two
{

	// invoke the callback function, if it exists.  dwFlags contains driver-
	// specific flags in the LOWORD and generic driver flags in the HIWORD

	if (MidiClients[nClient].dwCallback)
		DriverCallback(MidiClients[nClient].dwCallback, // client's callback DWORD
			   HIWORD(MidiClients[nClient].dwFlags),    // callback flags
			   MidiClients[nClient].hMidi,              // handle to the wave device
			   msg,                                     // the message
			   MidiClients[nClient].dwInstance,         // client's instance data
			   dw1,                                     // first DWORD
			   dw2);                                    // second DWORD
}


/****************************************************************************

FUNCTION DEFINITION:
modMessage - the synthesizer and MIDI port out devices' entry point

DESCRIPTION:
This function conforms to the standard MIDI output driver message proc modMessage,
which is documented in mmddk.h.

RETURNS: MMSYSERR_XXX value or message-specific DWORD value.
*/
DWORD FAR PASCAL _loadds modMessage(
	WORD id,                // device id
	WORD msg,               // specifies the message
	DWORD dwUser,           // user's id (for MODM_OPEN this is a pointer to a DWORD that
							// should be set to the client's ID)
	DWORD dwParam1,         // message dependant parameter
	DWORD dwParam2)         // message dependant parameter
{
	DWORD           dwResult;
	WORD            wClientIndex;
	WORD            wChan;
	BYTE            bStatus;       
	int             MsgLen;
	DWORD           dwByte;
	LPBYTE          lpByte;
//  MIXERCONTROLDETAILS mxcd;
//  MIXERCONTROLDETAILS_UNSIGNED mxcd_u[2];


	if (!nEnabled) {
//      D1("modMessage called while disabled");
		if (msg == MODM_GETNUMDEVS)
			return 0L;
		else
			return MMSYSERR_NOTENABLED;
	}

	// this driver supports two devices, the synth and MIDI output port
	if (id > 1) {               
//      D1("invalid midi device id");
		return MMSYSERR_BADDEVICEID;
	}

	switch (msg) {

		case MODM_GETNUMDEVS:
//          D1("MODM_GETNUMDEVS");
			return 2L;              // Device 0 is the synth, Device 1 is the port

		case MODM_GETDEVCAPS:
//          D1("MODM_GETDEVCAPS");                          
			if(wIWDriverFlags  & IWDF_WINDOWS95){
			    modGetDevCaps( id, ((MDEVICECAPSEX FAR *)dwParam1)->pCaps, (WORD)((MDEVICECAPSEX FAR *)dwParam1)->cbSize);
			}else{
			    modGetDevCaps( id, (LPBYTE)dwParam1, (WORD)dwParam2);
			}				
			return MMSYSERR_NOERROR;

		case MODM_QUERYMEM:
//          D1("MODM_QUERYMEM");        
			*((LPDWORD)dwParam1) = iw_mem_avail();
			return MMSYSERR_NOERROR;

		case MODM_CACHEPATCHES:
//          D1("MODM_CACHEPATCHES");
//                        if(!(MidiClients[(WORD)dwUser].bClientStatus & MCS_FIRSTCACHE)){
//                                MidiClients[(WORD)dwUser].bClientStatus |= MCS_FIRSTCACHE;
//                                for( wClientIndex=0 ; wClientIndex<MAXMIDICLIENTS ; wClientIndex++ ){
//                                        if(wClientIndex!=(WORD)dwUser && MidiClients[wClientIndex].bClientStatus!=MCS_CLOSED)
//                                                break;
//                                }                     
//                                if(wClientIndex==MAXMIDICLIENTS)
//                                        ResetPatchMemory();
//                        }
			return id?MMSYSERR_NOERROR:CachePatches((WORD)dwUser,(LPPATCHARRAY)dwParam1, HIWORD(dwParam2), LOWORD(dwParam2),1);

		case MODM_CACHEDRUMPATCHES:
//          D1("MODM_CACHEDRUMPATCHES");
			return id?MMSYSERR_NOERROR:CachePatches((WORD)dwUser,(LPPATCHARRAY)dwParam1, HIWORD(dwParam2), LOWORD(dwParam2),0);

		case MODM_OPEN:
//          D1("MODM_OPEN");
							 
			if(id==0){
			    if (nBlockMidiSynth)
				return MMSYSERR_ALLOCATED;

			    if (wIWDriverFlags & IWDF_PATCH_CONFIG_FAIL) return(MMSYSERR_ERROR);

				for( wClientIndex=0 ; wClientIndex<MAXMIDICLIENTS ; wClientIndex++ )
					if( MidiClients[wClientIndex].bClientStatus == MCS_CLOSED )
						break;
			
				if( wClientIndex == MAXMIDICLIENTS )
					return MMSYSERR_ALLOCATED;
			}else{
				wClientIndex = OUTPORTCLIENT;
				if( MidiClients[wClientIndex].bClientStatus != MCS_CLOSED )
					return MMSYSERR_ALLOCATED;
			}
			if((MidiClients[wClientIndex].wHWAH=HWAllocate(id?(IWAR_PORTOUT):(IWAR_SOMEVOICES|IWAR_SOMEMEMORY)))==-1){
				return MMSYSERR_ALLOCATED;
			}
			MidiClients[wClientIndex].bClientStatus = MCS_OPEN;
			
			// save client information
			MidiClients[wClientIndex].dwCallback = ((LPMIDIOPENDESC)dwParam1)->dwCallback;
			MidiClients[wClientIndex].dwInstance = ((LPMIDIOPENDESC)dwParam1)->dwInstance;
			MidiClients[wClientIndex].hMidi = ((LPMIDIOPENDESC)dwParam1)->hMidi;
			MidiClients[wClientIndex].dwFlags = dwParam2;
			if(id==0){
				MidiClients[wClientIndex].wVolume = LOWORD(dwSynthVolume)>>9;
//                                iw_midi_synth_volume(wClientIndex, MidiClients[wClientIndex].wVolume);
				for(wChan = 0;wChan<16;wChan++){
					MidiClients[wClientIndex].CurBank[wChan] = 0;         
//                                        if(wChan==9)continue;
//                                        if(MidiClients[wClientIndex].CurPatch[wChan]){
//                                           if(!(MidiClients[wClientIndex].CurPatch[wChan]->nStatus & PS_LOADED))
//                                                LoadPatch(MidiClients[wClientIndex].CurPatch[wChan]);
//                                           if(MidiClients[wClientIndex].CurPatch[wChan]->nStatus & PS_LOADED)
//                                                iw_midi_change_program(wClientIndex*16+wChan,
//                                                        MidiClients[wClientIndex].CurPatch[wChan]->pPatch);
//                                        }
				}
				nLoadStatus = LS_LOADONCACHE;
				mxdLineChange(1, MM_INTERWAVE_MIDIOUT);
			}
			MidiClients[wClientIndex].nMidiParserStatus = MPS_WFSTATUS;
			MidiClients[wClientIndex].nSysexParserStatus = SPS_INACTIVE;
			MidiClients[wClientIndex].bRunningStatus = 0;                

			*((DWORD FAR *)dwUser) = wClientIndex;
		
			// notify client
			midiCallback(wClientIndex, MOM_OPEN, 0L, 0L);
			return MMSYSERR_NOERROR;

	case MODM_CLOSE:
//          D1("MODM_CLOSE");    
			if(id==0){
				ResetMidi((WORD)dwUser);
				iwu_clear_patch_uds();
				mxdLineChange(0, MM_INTERWAVE_MIDIOUT);
			}
			HWFree(MidiClients[(WORD)dwUser].wHWAH);
			// notify client
			midiCallback((WORD)dwUser, MOM_CLOSE, 0L, 0L);
			MidiClients[(WORD)dwUser].bClientStatus = MCS_CLOSED;
			return MMSYSERR_NOERROR;

	case MODM_RESET:
//          D1("MODM_RESET");
			if(id==0){
				ResetMidi((WORD)dwUser);
			}
  
			MidiClients[(WORD)dwUser].nMidiParserStatus = MPS_WFSTATUS;
			MidiClients[(WORD)dwUser].nSysexParserStatus = SPS_INACTIVE;
			MidiClients[(WORD)dwUser].bRunningStatus = 0;                
			return MMSYSERR_NOERROR;

	case MODM_DATA:                    // message is in dwParam1
//          D4("MODM_DATA");
			if(id==0){
				if(!AddMessage((WORD)dwUser,dwParam1))
					return MIDIERR_NOTREADY;
				DoQueue();
			}else{                              
				bStatus = LOBYTE(LOWORD(dwParam1));
				if(bStatus >= MIDI_TIMINGCLOCK)
					MsgLen = 1;
				else if(bStatus >= MIDI_NOTEOFF && bStatus < MIDI_SYSEX ){
					MsgLen = MessageSize(bStatus);
					MidiClients[OUTPORTCLIENT].bRunningStatus = bStatus;
				}
				else if(bStatus < MIDI_NOTEOFF && MidiClients[OUTPORTCLIENT].bRunningStatus)
					MsgLen = MessageSize(MidiClients[OUTPORTCLIENT].bRunningStatus)-1;
				else
					MsgLen = 0;
					
				if(MsgLen > 0){
					MidiPortOutByte(LOBYTE(LOWORD(dwParam1)));
					if(MsgLen > 1){
						MidiPortOutByte(HIBYTE(LOWORD(dwParam1)));
						if(MsgLen > 2){
							MidiPortOutByte(LOBYTE(HIWORD(dwParam1)));
						}
					}
				}
			}
			return MMSYSERR_NOERROR;

	case MODM_LONGDATA:      // far pointer to header in dwParam1
//          D1("MODM_LONGDATA");           
			
			if( !( ((LPMIDIHDR)dwParam1)->dwFlags & MHDR_PREPARED ) )
				return MIDIERR_UNPREPARED;
						 
			if(id==0){                         
				if(!AddBuffer((WORD)dwUser,(LPMIDIHDR)dwParam1))
					return MIDIERR_NOTREADY;
				DoQueue();
			}else{
				lpByte = ((LPMIDIHDR)dwParam1)->lpData;
				if(!lpByte){
					return MMSYSERR_NOERROR;
				}
				for(dwByte=0;dwByte<((LPMIDIHDR)dwParam1)->dwBufferLength;dwByte++){
					MidiPortOutByte(*lpByte);
                                        // Increment the byte pointer, huge style
					if(LOWORD(lpByte+1)){
						lpByte++;
					}else{       
						lpByte = (LPBYTE)MAKELONG(0,HIWORD(lpByte)+TILED_SEL_INC);
					}
				}                          
				midiCallback((WORD)dwUser, MOM_DONE, dwParam1, 0L);
			}
			return MMSYSERR_NOERROR;

	case MODM_SETVOLUME:
//          D1("MODM_SETVOLUME");    
			if(id==1)
				break;

			dwSynthVolume = dwParam1;
			for( wClientIndex=0 ; wClientIndex<MAXMIDICLIENTS ; wClientIndex++ ){
				MidiClients[wClientIndex].wVolume =  LOWORD(dwSynthVolume)>>9;
				iw_midi_synth_volume(wClientIndex, MidiClients[wClientIndex].wVolume);
			}

//          mxcd_u[0].dwValue = (DWORD)LOWORD(dwParam1);
//          mxcd_u[1].dwValue = (DWORD)HIWORD(dwParam1);
//          MxdSetControlDetails((PMXDINST)NULL, (LPMIXERCONTROLDETAILS)&mxcd, (DWORD)NULL);  
			return MMSYSERR_NOERROR;

	case MODM_GETVOLUME:
//          D1("MODM_GETVOLUME");
			if(id==1)
				break;
			*((LPDWORD)dwParam1) = MAKELONG(dwSynthVolume,0);
			return MMSYSERR_NOERROR;

	default:
			return MMSYSERR_NOTSUPPORTED;
	}

	// should never get here...
	return MMSYSERR_NOTSUPPORTED;
}           
