/***************************************************************************
*   NAME:  MIDIIN.C $Revision: 1.11 $
**  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: midiin.c $
* Revision 1.11  1995/10/26 14:48:11  teckert
* Fixed ACT failures.
* Revision 1.10  1995/10/24 11:26:33  sdsmith
* Fixes for Windows 95 compatability tests
* Revision 1.9  1995/09/05 15:54:57  teckert
* Added PnP style device capabilities structure.
* Revision 1.8  1995/05/05 13:54:17  teckert
* Fixed some midi-in test failures
* Revision 1.7  1995/05/03 14:19:05  teckert
* Revision 1.6  1995/04/10 13:47:56  teckert
* fixed midi input code
* Revision 1.5  1995/03/30 16:39:30  teckert
* Updated arbitration support
* Revision 1.4  1995/03/17 17:42:59  teckert
* Added hardware allocate and free calls to midi in device
* Revision 1.3  1995/03/01 16:57:04  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 <string.h>
#include <iw.h>
#include <os.h>
#include "midi.h"
#include "interwav.h"
#include <viwd.h>

#define MPS_WFSTATUS        0
#define SPS_INACTIVE        0

WORD PortControlByte = 0;
extern int nEnabled;
void FAR PASCAL midiCallback( int nClient, WORD msg, DWORD dw1, DWORD dw2);
int MidiByteIn(int nStatus,int nData);
DWORD GetTime(void);


/****************************************************************************
FUNCTION DEFINITION:
InitMidiIn - Initializes the midi input device

DESCRIPTION:
Initializes the midi input device by registering the input handler with the 
kernel.

RETURNS: void
*/
void InitMidiIn(void)
{
	iw_add_handler(IW_IRQ_HANDLER_UART_RECV, (int (RFAR *)(void))MidiByteIn);
}


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

FUNCTION DEFINITION:
AddInputBuffer - adds a buffer to the queue of the Midi in client

DESCRIPTION:
First the buffer is checked to make sure that it had been prepared then it is
added to the end of the queue.  Because interrupt driven processes can access
and modify the queue, this is done with interrupts disabled.

RETURNS: DWORD MMSYSERR_XXX code
*/
DWORD AddInputBuffer(
	LPMIDIHDR lpBuffer)
{   
	LPMIDIHDR lpHdr;
	
	if(!(lpBuffer->dwFlags & MHDR_PREPARED))
		return MIDIERR_UNPREPARED;
	
	OS_PUSH_DISABLE();
	lpBuffer->dwFlags &= ~MHDR_DONE;
	lpBuffer->dwFlags |= MHDR_INQUEUE;
	lpHdr = MidiClients[INPORTCLIENT].lpBufferQueue;
	if(lpHdr){
		while(lpHdr->lpNext)
			lpHdr = lpHdr->lpNext;
		lpHdr->lpNext = lpBuffer;
	}
	else{
		MidiClients[INPORTCLIENT].lpBufferQueue = lpBuffer;
                MidiClients[INPORTCLIENT].lpCurPos = lpBuffer->lpData;
	}                                                  
	OS_POPF();
	return MMSYSERR_NOERROR;
}


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

FUNCTION DEFINITION:
FreeQueue - returns any buffers remaining in the queue to the wave in client

DESCRIPTION:
Because this function is only called when the input is stopped the MCS_STARTED
flag is checked to make sure that it is clear.  Then the first buffer is returned
with a MIM_LONGERROR message if it has any data in it (because the EOX byte was
never received).  Then the rest of the buffers are returned.

RETURNS: void
*/
void FreeQueue(void)
{
#ifdef DEBUG
	if(!(MidiClients[INPORTCLIENT].bClientStatus & MCS_STARTED))
		DebugBreak();
#endif               

// The first one may have some recorded sysex message data in it            
	if(MidiClients[INPORTCLIENT].dwBytesRecorded){
		SendLongError();
	}
	while(MidiClients[INPORTCLIENT].lpBufferQueue){
		MidiClients[INPORTCLIENT].lpBufferQueue->dwBytesRecorded = 0;
		MidiClients[INPORTCLIENT].lpBufferQueue->dwFlags &= ~MHDR_INQUEUE;
		MidiClients[INPORTCLIENT].lpBufferQueue->dwFlags |= MHDR_DONE;
		midiCallback(INPORTCLIENT,MIM_LONGDATA,(DWORD)MidiClients[INPORTCLIENT].lpBufferQueue,GetTime());
		MidiClients[INPORTCLIENT].lpBufferQueue = MidiClients[INPORTCLIENT].lpBufferQueue->lpNext;
	}
}


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

FUNCTION DEFINITION:
MidStart - starts the input device

DESCRIPTION:
Starts the input device by enabling the input IRQ of the UART.

RETURNS: MMSYSERR_XXX DWORD value
*/
DWORD MidStart(void)
{
	if(MidiClients[INPORTCLIENT].bClientStatus & MCS_STARTED)
		return MMSYSERR_NOERROR;

	MidiClients[INPORTCLIENT].bClientStatus |= MCS_STARTED;

	/* get a new reference time */
	MidiClients[INPORTCLIENT].dwTimeZero = timeGetTime();

	/* initialize all the parsing status variables */
	MidiClients[INPORTCLIENT].nMidiParserStatus = MPS_WFSTATUS;
	MidiClients[INPORTCLIENT].nSysexParserStatus = SPS_INACTIVE;
	MidiClients[INPORTCLIENT].bRunningStatus = 0;                

	PortControlByte |= IW_UART_RD_INT;
	iw_uart_reset(PortControlByte);
}


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

FUNCTION DEFINITION:
MidStop - stops the input device from recording 

DESCRIPTION:
Stops the input device by disabling the input IRQ of the UART.

RETURNS: MMSYSERR_XXX DWORD value
*/
DWORD MidStop(void)
{   
	if(!(MidiClients[INPORTCLIENT].bClientStatus & MCS_STARTED))
		return MMSYSERR_NOERROR;                                        
	
	PortControlByte &= ~IW_UART_RD_INT;
	iw_uart_reset(PortControlByte);
        
        // If we were recording a sysex message return the buffer with an error code
	SendLongError();
	MidiClients[INPORTCLIENT].bClientStatus &= ~MCS_STARTED;
}


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

FUNCTION DEFINITION:
midGetDevCaps - reports the midi in ports capabilities

DESCRIPTION:
Fills the MIDIINCAPS structure with the capabilities information and copies it
to the buffer provided by the client.

RETURNS: void
*/
static void NEAR PASCAL midGetDevCaps(LPBYTE lpCaps, WORD wSize)
{
MIDIINCAPS mc;

    mc.wMid = MM_ETEK;
    mc.wPid = MM_INTERWAVE_MIDIIN;
    mc.vDriverVersion = DRIVER_VERSION;
    LoadString(ghModule, STR_IWAVE_MIDIIN, (LPSTR)&(mc.szPname), sizeof(mc.szPname));

	_fmemcpy(lpCaps, (unsigned char far *)&mc, min(wSize,sizeof(mc)));
}


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

FUNCTION DEFINITION:
midMessage - the MIDI port in device's entry point

DESCRIPTION:
This function conforms to the standard MIDI input driver message proc midMessage, which is
documented in mmddk.d.

RETURNS: MMSYSERR_XXX value or message-specific DWORD value.
*/
DWORD FAR PASCAL _loadds midMessage(
	WORD id,                                // device id
	WORD msg,                               // specifies the message
	DWORD dwUser,                   // user's id (for MIDM_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;

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

    /* this driver only supports one device */
    if ( id != 0 ) {
//              D1("invalid midi device id");
		return MMSYSERR_BADDEVICEID;
    }

    switch ( msg ) {
	case MIDM_GETNUMDEVS:
//          D1("MIDM_GETNUMDEVS");
	    return 1L;

	case MIDM_GETDEVCAPS:
//          D1("MIDM_GETDEVCAPS");
	    if(wIWDriverFlags & IWDF_WINDOWS95){
	        midGetDevCaps( ((MDEVICECAPSEX FAR *)dwParam1)->pCaps, (WORD)((MDEVICECAPSEX FAR *)dwParam1)->cbSize);
	    }else{
	        midGetDevCaps((LPBYTE)dwParam1, (WORD)dwParam2);
	    }
	    return 0L;

	case MIDM_OPEN:
//          D1("MIDM_OPEN");
	    // We only support one client...
	    if (MidiClients[INPORTCLIENT].bClientStatus != MCS_CLOSED)
			return MMSYSERR_ALLOCATED;

	    if ((MidiClients[INPORTCLIENT].wHWAH=HWAllocate(IWAR_PORTIN))==-1)
			return MMSYSERR_ALLOCATED;

	    /* allocate my structure containing info about my client (global */
	    /* because I only allow one client to access midi input). */
	    MidiClients[INPORTCLIENT].dwCallback = ((LPMIDIOPENDESC)dwParam1)->dwCallback;
	    MidiClients[INPORTCLIENT].dwInstance = ((LPMIDIOPENDESC)dwParam1)->dwInstance;
	    MidiClients[INPORTCLIENT].hMidi      = ((LPMIDIOPENDESC)dwParam1)->hMidi;
	    MidiClients[INPORTCLIENT].dwFlags    = dwParam2;
	    
	    /* initialize queue stuff */
	    MidiClients[INPORTCLIENT].dwBytesRecorded = 0;
	    MidiClients[INPORTCLIENT].lpBufferQueue = NULL;

	    /*  NOTE: we must initialize reference time in case someone adds    */
	    /*  longdata buffers after opening, then resets the midi stream     */
	    /*  without starting midi input.  Otherwise, midFreeQ would give    */
	    /*  inconsistent timestamps                                                                                 */
	    MidiClients[INPORTCLIENT].dwTimeZero = timeGetTime();    
	    MidiClients[INPORTCLIENT].bClientStatus = MCS_OPEN;

	    /* notify client */
	    midiCallback(INPORTCLIENT, MIM_OPEN, 0L, 0L);

	    return MMSYSERR_NOERROR;

	case MIDM_CLOSE:
//          D1("MIDM_CLOSE");

	    if (MidiClients[INPORTCLIENT].bClientStatus == MCS_CLOSED)
			return MMSYSERR_NOERROR;

	    if ( MidiClients[INPORTCLIENT].lpBufferQueue )
			return MIDIERR_STILLPLAYING;

	    /* just in case they started input without adding buffers */
	    MidStop();
	    HWFree(MidiClients[INPORTCLIENT].wHWAH);
	    /* notify client */
	    midiCallback(INPORTCLIENT, MIM_CLOSE, 0L, 0L);           
	    MidiClients[INPORTCLIENT].bClientStatus = MCS_CLOSED;

	    return MMSYSERR_NOERROR;

	case MIDM_ADDBUFFER:
//          D1("MIDM_ADDBUFFER");

	    /* attempt to add the buffer */
	    return AddInputBuffer((LPMIDIHDR)dwParam1);

	case MIDM_START:
//          D1("MIDM_START");
	    MidStart();
	    return MMSYSERR_NOERROR;

	case MIDM_STOP:
//          D1("MIDM_STOP");
	    MidStop();
	    return MMSYSERR_NOERROR;

	case MIDM_RESET:
//          D1("MIDM_RESET");

	    /* stop if it is started and release all buffers */
	    MidStop();
	    FreeQueue();
	    return MMSYSERR_NOERROR;

	default:
	    return MMSYSERR_NOTSUPPORTED;
    }

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