/*****************************************************************************
    map.c


    midi mapper run-time

    Copyright (c) Microsoft Corporation 1990-1991. All rights reserved

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

#include <windows.h>
#include <string.h>
#include <mmsystem.h>
#if defined(WIN32)
#include <port1632.h>
#endif
#include "hack.h"
#include <mmddk.h>
#include "midimap.h"
#include "midi.h"
#include "extern.h"
#include "mmreg.h"

#define ISSTATUS(bData)     ((bData) & 0x80)
#define FILTERCHANNEL(bStatus)  ((BYTE)((bStatus) & 0xf0))
#define FILTERSTATUS(bStatus)   ((BYTE)((bStatus) & 0x0f))

#define STATUS_NOTEOFF      0x80
#define STATUS_NOTEON       0x90
#define STATUS_POLYPHONICKEY    0xa0
#define STATUS_CONTROLCHANGE    0xb0
#define STATUS_PROGRAMCHANGE    0xc0
#define STATUS_CHANNELPRESSURE  0xd0
#define STATUS_PITCHBEND    0xe0

#define STATUS_SYS      0xf0
#define STATUS_SYSEX        0xf0
#define STATUS_QFRAME       0xf1
#define STATUS_SONGPOINTER  0xf2
#define STATUS_SONGSELECT   0xf3
#define STATUS_F4       0xf4
#define STATUS_F5       0xf5
#define STATUS_TUNEREQUEST  0xf6
#define STATUS_EOX      0xf7
#define STATUS_TIMINGCLOCK  0xf8
#define STATUS_F9       0xf9
#define STATUS_START        0xfa
#define STATUS_CONTINUE     0xfb
#define STATUS_STOP     0xfc
#define STATUS_FD       0xfd
#define STATUS_ACTIVESENSING    0xfe
#define STATUS_SYSTEMRESET  0xff

#define CONTROL_VOLUME      0x07

#define MIDIDATABUFFER      512

#define STATE_MAPNAILED     0x0001
#define STATE_DATANAILED    0x0002
#define STATE_CODENAILED    0x0004

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

    local structures

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

typedef unsigned char huge *     HPBYTE;

#define DEV_PREPARED    0x0001

typedef struct mididev_tag {
    WORD    wDeviceID;
    WORD    wChannel;
    WORD    fwFlags;
    HMIDIOUT   hMidi;
} MIDIDEV;
typedef MIDIDEV *PMIDIDEV;

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

    local data

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

/*
 * critical section used to protect the open so that there is no
 * window in which two threads could open simultaneously - otherwise
 * with all these statics there would be a major accident
 */
CRITICAL_SECTION MapperCritSec;




static HGLOBAL        hCurMap;         // handle of current midi map
static WORD           wChannelMask;    // which channels are on
static UINT           uPatchMask;      // which channels have patch maps
static MIDIDEV        mapdevs[16];     // contains device info. for each midi device in the current map.
static MIDIDEV        chnldevs[16];    // map channels to midi devices.
static LPMIDIPATCHMAP lpPMap;          // current patch map
static LPMIDIKEYMAP   lpKMap;          // current key map
static BYTE           curpatch[16];    // what is the currently selected patch for each channel
static BYTE           status;          // virtual running status
static BYTE           bCurrentStatus;  // Current message type
static BYTE           fActiveChannel;  // Channel message to active channel
static BYTE           bCurrentLen;     // Current message length, if any

static DWORD_PTR      OpenCallback;    // Open Callback parameter
static DWORD_PTR      OpenInstance;    // Open Instance parameter
static DWORD          OpenFlags;       // Open Param2
static HMIDIOUT       hmidiMapper;     // Handle of current mapper device
static LPMIDIHDR      pmidihdrMapper;  // Buffer used for mapped devices
static UINT           ufStateFlags;    // State flags for device
extern BYTE FAR       bMidiLengths[];  // Lengths in lengths.c
extern BYTE FAR       bSysLengths[];   // Lengths in lengths.c

#define MIDILENGTH(bStatus) (bMidiLengths[((bStatus) & 0x70) >> 4])
#define SYSLENGTH(bStatus)  (bSysLengths[(bStatus) & 0x07])

#define lpCurMap ((LPMIDIMAP)hCurMap)  // pointer to current midi map

UINT mapLockCount;

UINT FAR PASCAL modGetDevCaps(LPMIDIOUTCAPSW lpCaps, UINT uSize);
UINT FAR PASCAL modCachePatches(UINT msg, UINT uBank, LPPATCHARRAY lpPatchArray, UINT uFlags);
static UINT FAR PASCAL midiReadCurrentSetup(LPMIDIOPENDESC lpOpen, DWORD dwParam2);

LRESULT FAR PASCAL DriverProc(DWORD, HDRVR, UINT, LPARAM, LPARAM);

static void NEAR PASCAL modShortData(LPBYTE pbData);
static void NEAR PASCAL modLongData(HPBYTE pbData, DWORD dDataLength);
static void NEAR PASCAL modTranslateEvent(LPBYTE pbData, BYTE bStart, BYTE bLength);

DWORD FAR PASCAL _loadds modMessage(UINT id, UINT msg, DWORD dwUser, DWORD dwParam1, DWORD dwParam2);
static void PASCAL FAR CallbackNotification(UINT message, DWORD dwParam);
static void NEAR PASCAL modSendLongData(UINT uMessageLength, BOOL fBroadcast, BOOL force);
static  BOOL NEAR PASCAL modHeaderDone(void);


static  void PASCAL NEAR ReleaseResources(void);

static UINT PASCAL NEAR TranslateError(MMAPERR mmaperr)
{
    switch (mmaperr) {
    case MMAPERR_INVALIDPORT:
	    return MIDIERR_NODEVICE;
    case MMAPERR_MEMORY:
	return MMSYSERR_NOMEM;
    case MMAPERR_INVALIDSETUP:
	return MIDIERR_INVALIDSETUP;
    }
    return MIDIERR_NOMAP;
}

#if defined(WIN16)

static  BOOL NEAR PASCAL GlobalNail(
    HGLOBAL hSegment,
    UINT    uFlag)
{
    if (GlobalWire(hSegment)) {
	if (GlobalPageLock(hSegment)) {
	    ufStateFlags |= uFlag;
	    return TRUE;
	}
	GlobalUnWire(hSegment);
    }
    return FALSE;
}

static  void NEAR PASCAL GlobalUnNail(
    HGLOBAL hSegment,
    UINT    uFlag)
{
    if (ufStateFlags & uFlag) {
	GlobalPageUnlock(hSegment);
	GlobalUnWire(hSegment);
	ufStateFlags &= ~uFlag;
    }
}
#endif //WIN16

static  void PASCAL NEAR ReleaseResources(void)
{
    WORD    wDevice;

#ifdef WIN16
    GlobalUnNail((HGLOBAL)HIWORD((DWORD)(LPVOID)&hCurMap), STATE_DATANAILED);
    GlobalUnNail(hCurMap, STATE_MAPNAILED);
    GlobalUnNail((HGLOBAL)HIWORD(DriverProc), STATE_CODENAILED);
#endif // WIN16

    for (wDevice = 0; (wDevice < 16) && (mapdevs[wDevice].wDeviceID != (WORD)(-1)); wDevice++) {
	if (mapdevs[wDevice].hMidi) {
	    midiOutReset(mapdevs[wDevice].hMidi);
	    if (mapdevs[wDevice].fwFlags & DEV_PREPARED) {
		midiOutUnprepareHeader(mapdevs[wDevice].hMidi, pmidihdrMapper, sizeof(MIDIHDR));
		mapdevs[wDevice].fwFlags &= ~DEV_PREPARED;
	    }
	    midiOutClose(mapdevs[wDevice].hMidi);
	    mapdevs[wDevice].hMidi = NULL;
	    mapdevs[wDevice].wDeviceID = (WORD)(-1);
	}
    }
    if (hCurMap) {
	GlobalFree(hCurMap);
	hCurMap = NULL;
    }
    if (pmidihdrMapper) {
	HGLOBAL hmem = GlobalHandle( pmidihdrMapper );
	GlobalUnlock( hmem );
	GlobalFree( hmem );
	pmidihdrMapper = NULL;
    }
}

static  UINT PASCAL FAR CloseMidiDevice(
    void)
{
    ReleaseResources();
    CallbackNotification(MOM_CLOSE, 0);
    return 0;
}

static  void PASCAL FAR CallbackNotification(
    UINT    message,
    DWORD   dwParam)
{
    if (OpenCallback)
	DriverCallback( OpenCallback
		      , HIWORD(OpenFlags) | DCB_NOSWITCH
		      , (HANDLE)hmidiMapper
		      , message
		      , OpenInstance
		      , dwParam
		      , 0
		      );
}

/***************************************************************************
 * @doc INTERNAL
 *
 * @api LRESULT | DriverProc | The entry point for an installable driver.
 *
 * @parm DWORD | dwDriverId | For most messages, <p dwDriverId> is the DWORD
 *     value that the driver returns in response to a <m DRV_OPEN> message.
 *     Each time that the driver is opened, through the <f DrvOpen> API,
 *     the driver receives a <m DRV_OPEN> message and can return an
 *     arbitrary, non-zero value. The installable driver interface
 *     saves this value and returns a unique driver handle to the
 *     application. Whenever the application sends a message to the
 *     driver using the driver handle, the interface routes the message
 *     to this entry point and passes the corresponding <p dwDriverId>.
 *     This mechanism allows the driver to use the same or different
 *     identifiers for multiple opens but ensures that driver handles
 *     are unique at the application interface layer.
 *
 *     The following messages are not related to a particular open
 *     instance of the driver. For these messages, the dwDriverId
 *     will always be zero.
 *
 *         DRV_LOAD, DRV_FREE, DRV_ENABLE, DRV_DISABLE, DRV_OPEN
 *
 * @parm HDRVR  | hDriver | This is the handle returned to the
 *     application by the driver interface.
 *
 * @parm UINT | wMessage | The requested action to be performed. Message
 *     values below <m DRV_RESERVED> are used for globally defined messages.
 *     Message values from <m DRV_RESERVED> to <m DRV_USER> are used for
 *     defined driver protocols. Messages above <m DRV_USER> are used
 *     for driver specific messages.
 *
 * @parm LPARAM | lParam1 | Data for this message.  Defined separately for
 *     each message
 *
 * @parm LPARAM | lParam2 | Data for this message.  Defined separately for
 *     each message
 *
 * @rdesc Defined separately for each message.
 ***************************************************************************/

LRESULT FAR PASCAL DriverProc(DWORD dwDriverID, HDRVR hDriver, UINT wMessage, LPARAM lParam1, LPARAM lParam2)
{
    //
    //  NOTE DS is not valid here.
    //
    switch (wMessage) {

	case DRV_LOAD:
	    InitializeCriticalSection(&MapperCritSec);
	    return (LRESULT)TRUE;

	case DRV_FREE:
	    DeleteCriticalSection(&MapperCritSec);
	    return (LRESULT)TRUE;

	case DRV_OPEN:
	case DRV_CLOSE:
	    return (LRESULT)TRUE;
	case DRV_INSTALL:
	case DRV_REMOVE:
	    return (LRESULT)DRVCNF_RESTART;

	default:
	    return DefDriverProc(dwDriverID, hDriver, wMessage,lParam1,lParam2);
	}
}

DWORD FAR PASCAL _loadds modMessage(UINT id, UINT msg, DWORD dwUser, DWORD dwParam1, DWORD dwParam2)
{
int         i;
DWORD       dResult;

    // this driver only supports one device
    if (id != 0)
	return MMSYSERR_BADDEVICEID;

    switch (msg) {

       case MODM_GETNUMDEVS:
	    return 1;

	case MODM_GETDEVCAPS:
	    return modGetDevCaps((LPMIDIOUTCAPSW)dwParam1, LOWORD(dwParam2));

	case MODM_OPEN:
	    EnterCriticalSection(&MapperCritSec);
	    if( hCurMap || mapLockCount ) {
		dResult = MMSYSERR_ALLOCATED;
	    } else {
		dResult =  midiReadCurrentSetup((LPMIDIOPENDESC)dwParam1, dwParam2);
	    }
	    LeaveCriticalSection(&MapperCritSec);
	    return dResult;

	case MODM_CLOSE:
	    EnterCriticalSection(&MapperCritSec);
	    dResult = CloseMidiDevice();
	    LeaveCriticalSection(&MapperCritSec);
	    return dResult;

	case MODM_CACHEPATCHES:
	case MODM_CACHEDRUMPATCHES:
	    return modCachePatches(msg, HIWORD(dwParam2), (LPPATCHARRAY)dwParam1, LOWORD(dwParam2));

///////////////////////////////////////////////////////////////////////////
//
//  INTERRUPT TIME CODE
//
//  MODM_LONGDATA, MODM_DATA, and MODM_RESET are callable at interupt time!
//
///////////////////////////////////////////////////////////////////////////

	case MODM_DATA:
	    modShortData((LPBYTE)&dwParam1);
	    return 0;

	case MODM_LONGDATA:
	    modLongData( (HPBYTE)((LPMIDIHDR)dwParam1)->lpData
		       , ((LPMIDIHDR)dwParam1)->dwBufferLength
		       );
	    ((LPMIDIHDR)dwParam1)->dwFlags |= MHDR_DONE;
	    CallbackNotification(MOM_DONE, dwParam1);
	    return 0;

///////////////////////////////////////////////////////////////////////////

	case MODM_PREPARE:
	case MODM_UNPREPARE:
	    return MMSYSERR_NOTSUPPORTED;

	//case MODM_RESET:
	//case MODM_GETVOLUME:
	//case MODM_SETVOLUME:

	default:
	    //
	    //  !!!this is in trouble if a map goes to multiple physical devices
	    //  we return the *last* dResult, this is
	    //  totally random for some messages (like MODM_GETVOLUME).

	    // pass the message on un-translated to all mapped physical
	    // devices.
	    //
	    for (dResult = 0, i = 0; i < 16 && mapdevs[i].hMidi; i++)
		switch (msg) {
		//
		// Avoid nasty overlaps with open devices
		//
		case MODM_GETVOLUME:
		    dResult = midiOutGetVolume((HMIDIOUT)(mapdevs[i].wDeviceID), (LPDWORD)dwParam1);
		    break;

		case MODM_SETVOLUME:
		    dResult = midiOutSetVolume((HMIDIOUT)(mapdevs[i].wDeviceID), dwParam1);
		    break;

		default:
		    dResult = midiOutMessage(mapdevs[i].hMidi, msg, dwParam1, dwParam2);
		    break;
		}

	    return dResult;
    }
}

/*****************************************************************************
 * @doc EXTERNAL  MIDI
 *
 * @api UINT | modGetDevCaps | This function returns the mappers device caps
 *
 * @parm LPMIDIOUTCAPS | lpCaps | Specifies a far pointer to a <t MIDIOUTCAPS>
 *   structure.  This structure is filled with information about the
 *   capabilities of the device.
 *
 * @parm UINT | wSize | Specifies the size of the <t MIDIOUTCAPS> structure.
 *
 * @rdesc Returns zero if the function was successful.  Otherwise, it returns
 *   an error number.  Possible error returns are:
 *   @flag MMSYSERR_BADDEVICEID | Specified device ID is out of range.
 *   @flag MMSYSERR_NODRIVER | The driver was not installed.
 *
 ****************************************************************************/
UINT FAR PASCAL modGetDevCaps(LPMIDIOUTCAPSW lpCaps, UINT uSize)
{
MIDIOUTCAPSW mc;
int i;

    if (uSize != 0) {

	i=LoadStringW( hLibInst,
		       IDS_MIDIMAPPER,
		       mc.szPname,
		       sizeof(lpCaps->szPname) / sizeof(WCHAR) );

	mc.wMid = MM_MICROSOFT;
	mc.wPid = MM_MIDI_MAPPER;
	mc.vDriverVersion = 0x0100;
	mc.wTechnology = MOD_MAPPER;
	mc.wVoices = 0;
	mc.wNotes = 0;
	mc.wChannelMask = wChannelMask;  // 0 if mapper not opened yet
	mc.dwSupport = MIDICAPS_CACHE;

	_fmemcpy((LPSTR)lpCaps, (LPSTR)&mc, min(uSize, sizeof(mc)));
    }

return 0;
}

static void PASCAL NEAR TranslatePatchArray(
	LPPATCHARRAY    lpSource,
	LPPATCHARRAY    lpDest,
	BOOL    fToMaps)
{
	int     i;

	_fmemset(lpDest, 0, sizeof(PATCHARRAY));
	for (i = 0; i < 16; i++) {
		UINT    curmask;
		int     j;

		curmask = 1 << i;
		if (uPatchMask & curmask) {
			lpPMap = (LPMIDIPATCHMAP)((LPSTR)lpCurMap + lpCurMap->chMap[i].oPMap);
			if (fToMaps)
				for (j = 0; j < MIDIPATCHSIZE; j++)
					lpDest[LOBYTE(lpPMap->wPMap[j])] |= (lpSource[j] & curmask) ? curmask : 0;
			else
				for (j = 0; j < MIDIPATCHSIZE; j++)
					lpDest[j] |= (lpSource[LOBYTE(lpPMap->wPMap[j])] & curmask) ? curmask : 0;
		} else
			for (j = 0; j < MIDIPATCHSIZE; j++)
				lpDest[j] |= (lpSource[j] & curmask) ? curmask : 0;
	}
}

UINT FAR PASCAL modCachePatches(UINT msg, UINT uBank, LPPATCHARRAY lpPatchArray, UINT uFlags)
{
int         i;
PATCHARRAY  patchlist;
PATCHARRAY  retpatchlist;
UINT        uResult = 0;

    TranslatePatchArray(lpPatchArray, patchlist, TRUE);

    // send to drivers
    _fmemset(retpatchlist, 0, sizeof(PATCHARRAY));

    for( i = 0; ((i < 16) && (mapdevs[i].wDeviceID != (WORD)(-1))); i++ ) {
	PATCHARRAY  curpatchlist;
	int     j;

	for (j = 0; j < MIDIPATCHSIZE; j++ )
	    curpatchlist[j] = patchlist[j] & mapdevs[i].wChannel;


	uResult = ( (msg == MODM_CACHEPATCHES)
		  ?  midiOutCachePatches( mapdevs[i].hMidi
					, uBank
					, curpatchlist
					, uFlags
					)
		  : midiOutCacheDrumPatches( mapdevs[i].hMidi
					   , uBank
					   , curpatchlist
					   , uFlags
					   )
		  );

	// combine the returned info
	for (j = 0; j < MIDIPATCHSIZE; j++ )
	    retpatchlist[j] |= (curpatchlist[j] & mapdevs[i].wChannel);
    }

    TranslatePatchArray(retpatchlist, lpPatchArray, FALSE);

    return uResult;
}



///////////////////////////////////////////////////////////////////////////
//
//  INTERRUPT TIME CODE
//
//  MODM_LONGDATA, and MODM_DATA are callable at interupt time!
//
///////////////////////////////////////////////////////////////////////////

static  BOOL NEAR PASCAL modHeaderDone(
    void)
{
    if (pmidihdrMapper->dwFlags & MHDR_DONE)
	return TRUE;
    else
	return FALSE;
}



static  void NEAR PASCAL modSendLongData(
    UINT    uMessageLength,
    BOOL    fBroadcast,
    BOOL    fForce)             // Used on final invocation
{

static BYTE LongBuffer[200];           // Cache the stuff for performance
static DWORD nLongData;                // How much we've got
static BOOL LastBroadcast;             // What we were asked to do last time
static BYTE LastStatus;                // Last status we had

    if (nLongData &&
	(fForce ||
	 FILTERSTATUS(status) != FILTERSTATUS(LastStatus) ||
	 uMessageLength + nLongData > sizeof(LongBuffer) ||
	 LastBroadcast != fBroadcast)) {

	LPBYTE lpSave = pmidihdrMapper->lpData;
	pmidihdrMapper->lpData = LongBuffer;

	pmidihdrMapper->dwBufferLength = nLongData;
	if (LastBroadcast) {
	    WORD    wDevice;

	    for (wDevice = 0; (wDevice < 16) && (mapdevs[wDevice].wDeviceID != (WORD)(-1)); wDevice++) {
		pmidihdrMapper->dwFlags &= ~MHDR_DONE;
		if (MMSYSERR_NOERROR ==
		    midiOutLongMsg(mapdevs[wDevice].hMidi,
				   pmidihdrMapper,
				   sizeof(MIDIHDR))) {
		    while (!modHeaderDone())
			Sleep(1);
		}
	    }
	} else {
	    pmidihdrMapper->dwFlags &= ~MHDR_DONE;
	    if (MMSYSERR_NOERROR ==
		midiOutLongMsg(chnldevs[FILTERSTATUS(LastStatus)].hMidi,
			       pmidihdrMapper,
			       sizeof(MIDIHDR))) {
		while (!modHeaderDone())
		    Sleep(1);
	    }
	}

	pmidihdrMapper->lpData = lpSave;
	nLongData = 0;
    }

    //
    // Pull in our new data
    //
    LastStatus = status;
    LastBroadcast = fBroadcast;
    if (fBroadcast || fActiveChannel) {
	memcpy(LongBuffer + nLongData, pmidihdrMapper->lpData, uMessageLength);
	nLongData += uMessageLength;
    }
}

static void NEAR PASCAL modTranslateEvent(
    LPBYTE  pbData,
    BYTE    bStart,
    BYTE    bLength)
{
    static BYTE fControlVol;

    if (wChannelMask & (1 << FILTERSTATUS(status))) {
	fActiveChannel = TRUE;
	bCurrentStatus = FILTERCHANNEL(status) + (BYTE)lpCurMap->chMap[FILTERSTATUS(status)].wChannel;
	if (!bStart) {
	    *(pbData++) = bCurrentStatus;
	    bStart++;
	    bLength--;
	    if (!bLength)
		return;
	}
	if (uPatchMask & (1 << FILTERSTATUS(status))) {
	    lpPMap = (LPMIDIPATCHMAP)((LPSTR)lpCurMap + lpCurMap->chMap[FILTERSTATUS(status)].oPMap);
	    switch (FILTERCHANNEL(status)) {
	    case STATUS_NOTEOFF:
	    case STATUS_NOTEON:
	    case STATUS_POLYPHONICKEY:
		if ((bStart > 1) || !lpPMap->okMaps[curpatch[FILTERSTATUS(status)]])
		    break;
		lpKMap = (LPMIDIKEYMAP)((LPSTR)lpPMap + lpPMap->okMaps[curpatch[FILTERSTATUS(status)]]);
		*pbData = lpKMap->bKMap[*pbData];
		break;
	    case STATUS_CONTROLCHANGE:
		if (bStart == 1) {
		    if (*pbData != CONTROL_VOLUME)
			break;
		    pbData++;
		    bStart++;
		    bLength--;
		    fControlVol = TRUE;
		    if (!bLength)
			return;
		}
		*pbData = (BYTE)((DWORD)*pbData * (DWORD)HIBYTE(lpPMap->wPMap[curpatch[FILTERSTATUS(status)]]) / lpPMap->bVMax);
		fControlVol = FALSE;
		break;
	    case STATUS_PROGRAMCHANGE:
		curpatch[FILTERSTATUS(status)] = *pbData;
		*pbData = (BYTE)lpPMap->wPMap[*pbData];
		break;
	    }
	}
    } else
	fActiveChannel = FALSE;
}

static  void NEAR PASCAL modShortData( LPBYTE  pbData)
{
    BOOL    fBroadcast;
    BYTE    bStart = 0;
    BYTE    bLength = 0;

    if (*pbData >= STATUS_TIMINGCLOCK)
	fBroadcast = TRUE;
    else {
	bCurrentLen = 0;
	if (ISSTATUS(*pbData)) {
	    bCurrentStatus = *pbData;
	    if (bCurrentStatus >= STATUS_SYSEX) {
		status = 0;
		fBroadcast = TRUE;
	    } else {
		status = bCurrentStatus;
		bLength = MIDILENGTH(bCurrentStatus);
		fBroadcast = FALSE;
		bStart = 0;
	    }
	} else if (!status)
	    return;
	else {
	    fBroadcast = FALSE;
	    bLength = (BYTE)(MIDILENGTH(status) - 1);
	    bStart = 1;
	}
    }
    if (fBroadcast) {
	WORD    wDevice;

	for (wDevice = 0; (wDevice < 16) && (mapdevs[wDevice].wDeviceID != (WORD)(-1)); wDevice++)
	    midiOutShortMsg(mapdevs[wDevice].hMidi, *(LPDWORD)pbData);
    } else {
	modTranslateEvent(pbData, bStart, bLength);
	if (fActiveChannel)
	    midiOutShortMsg(chnldevs[FILTERSTATUS(status)].hMidi, *(LPDWORD)pbData);
    }
}

static void NEAR PASCAL modLongData(
    HPBYTE  pbData,
    DWORD   dDataLength)
{
    static BYTE bStart;
    UINT    uMessageLength;
    LPBYTE  pbHdrData;

    pbHdrData = pmidihdrMapper->lpData;
    uMessageLength = 0;
    for (; dDataLength;) {
	if (ISSTATUS(*pbData)) {
	    if (bCurrentStatus == STATUS_SYSEX) {
		bCurrentStatus = *pbData;
		if ((bCurrentStatus == STATUS_EOX) || (bCurrentStatus == STATUS_SYSEX) || (bCurrentStatus >= STATUS_TIMINGCLOCK)) {
		    *(pbHdrData++) = bCurrentStatus;
		    dDataLength--;
		    if (bCurrentStatus >= STATUS_TIMINGCLOCK)
			bCurrentStatus = STATUS_SYSEX;
		} else
		    uMessageLength--;
	    } else {
		if (bCurrentLen) {
		    if (status) {
			BYTE    bMessageLength;

			bMessageLength = (BYTE)(MIDILENGTH(status) - bCurrentLen - bStart);
			modTranslateEvent(pmidihdrMapper->lpData - bMessageLength, bStart, bMessageLength);
		    }
		    modSendLongData(uMessageLength, !status, FALSE);
		    pbHdrData = pmidihdrMapper->lpData;
		    uMessageLength = 0;
		}
		*pbHdrData = *(pbData++);
		dDataLength--;
		if (*pbHdrData >= STATUS_TIMINGCLOCK) {
		    modSendLongData(1, TRUE, FALSE);
		    continue;
		}
		bCurrentStatus = *(pbHdrData++);
		if (bCurrentStatus >= STATUS_SYSEX) {
		    status = 0;
		    bCurrentLen = (BYTE)(SYSLENGTH(bCurrentStatus) - 1);
		} else {
		    status = bCurrentStatus;
		    bCurrentLen = (BYTE)(MIDILENGTH(bCurrentStatus) - 1);
		    bStart = 0;
		}
	    }
	} else {
	    *(pbHdrData++) = *(pbData++);
	    dDataLength--;
	    if (bCurrentLen)
		bCurrentLen--;
	    else if (status) {
		bStart = 1;
		bCurrentLen = (BYTE)(MIDILENGTH(status) - 2);
	    }

	}
	uMessageLength++;
	if (!bCurrentLen && ((bCurrentStatus != STATUS_SYSEX) || (uMessageLength == MIDIDATABUFFER))) {
	    if (status) {
		BYTE    bMessageLength;

		bMessageLength = (BYTE)(MIDILENGTH(status) - bStart);
		modTranslateEvent(pmidihdrMapper->lpData - bStart, bStart, bMessageLength);
	    }
	    modSendLongData(uMessageLength, !status, FALSE);
	    pbHdrData = pmidihdrMapper->lpData;
	    uMessageLength = 0;
	}
    }
    if (uMessageLength) {
	if (status) {
	    BYTE    bMessageLength;

	    bMessageLength = (BYTE)(MIDILENGTH(status) - bCurrentLen - bStart);
	    modTranslateEvent(pmidihdrMapper->lpData - bStart, bStart, bMessageLength);
	    bStart += bMessageLength;
	}
	modSendLongData(uMessageLength, !status, FALSE);
    }

    modSendLongData(0, 0, TRUE);
}

/*****************************************************************************
 * @doc INTERNAL  MIDI
 *
 * @api BOOL | mapLock | This function prevents anyone from opening the
 *   mapper.
 *
 * @rdesc Returns TRUE if successful and FALSE otherwise (i.e. the mapper is
 *   already open.
 *
 * @comm This is a private function for the control panel applet to call
 *   while a setup is being edited.  There is a lock count kept - you must
 *   call mapUnlock once for each call to mapLock.
 ****************************************************************************/
BOOL FAR PASCAL mapLock(VOID)
{
    // if someone has the mapper open, return FALSE
    if( hCurMap )
	return FALSE;

    mapLockCount++;
    return TRUE;
}

/*****************************************************************************
 * @doc INTERNAL  MIDI
 *
 * @api VOID | mapUnlock | This function unlocks the mapper if it's locked.
 *
 * @rdesc There is no return value.
 *
 * @comm This is a private function for the control panel applet to call
 *   while a setup is being edited.  There is a lock count kept but
 *   underflow will not generate an error, and lock count will remain 0.
 ****************************************************************************/
VOID FAR PASCAL mapUnlock(VOID)
{
    if( mapLockCount )
	mapLockCount--;
    return;
}

/*****************************************************************************
 * @doc INTERNAL  MIDI
 *
 * @api UINT | midiReadCurrentSetup | This function reads in the current setup.
 *
 * @parm DWORD | dwParam1 | The first DWORD from the <f midiOutOpen> call.
 *
 * @parm DWORD | dwParam2 | The second DWORD from the <f midiOutOpen> call.
 *
 * @rdesc Returns zero if the function was successful.  Otherwise, it returns
 *   an error number.
 ****************************************************************************/
static UINT FAR PASCAL midiReadCurrentSetup(LPMIDIOPENDESC lpOpen, DWORD dwParam2)
{
int      i,j;
WORD     devid;
MMAPERR  mmaperr;
DWORD    dwSize;
UINT     uResult;
char     szCurSetup[MMAP_MAXNAME];
WORD     wChan;

    // get current setup
    mmaperr = mapGetCurrentSetup(szCurSetup, MMAP_MAXNAME);
    if (mmaperr != MMAPERR_SUCCESS)
	return TranslateError(mmaperr);

    dwSize = mapGetSize(MMAP_SETUP, szCurSetup);
    if (dwSize < (DWORD)MMAPERR_MAXERROR)
	return TranslateError((UINT)dwSize);

    // allocate memory
    hCurMap = GlobalAlloc(GMEM_MOVEABLE, dwSize);
    if( !hCurMap )
	return MMSYSERR_NOMEM;

    hCurMap = (HGLOBAL)GlobalLock(hCurMap);

    mmaperr = mapRead (MMAP_SETUP, szCurSetup, lpCurMap);
    if( mmaperr != MMAPERR_SUCCESS ) {
	ReleaseResources();
	return TranslateError(mmaperr);
    }

    // initialize channel and patch masks
    wChannelMask = 0;
    uPatchMask = 0;

    // initialize device list
    for (i = 0; i < 16; i++) {
	mapdevs[i].wDeviceID = (WORD)(-1);
	mapdevs[i].hMidi = NULL;
	mapdevs[i].fwFlags = 0;
    }

    // go through each source channel
    for( wChan = 0; wChan < 16; wChan++ ) {
	if( ((lpCurMap->chMap[wChan]).dwFlags) & MMAP_ACTIVE ) {

	    // set channel mask
	    wChannelMask |= (1 << wChan);

	    // set patch mask
	    if( ((lpCurMap->chMap[wChan]).dwFlags) & MMAP_PATCHMAP )
		uPatchMask |= (1 << wChan);

	    // map device id
	    devid = lpCurMap->chMap[wChan].wDeviceID;

	    // save driver and device ids for channel messages
	    chnldevs[wChan].wDeviceID = devid;

	    // algorithm for device list
	    // wChannel will have the channel mask for the device
	    for( j = 0; j < 16; j++ ) {
		if( mapdevs[j].wDeviceID == devid ) {
		    mapdevs[j].wChannel |= 0x0001 << wChan;
		    break;     // from for loop
		}
		if( mapdevs[j].wDeviceID == (WORD)(-1) ) {
		    mapdevs[j].wDeviceID = devid;
		    mapdevs[j].wChannel = (WORD)(1 << wChan);  // first channel
		    break;
		}
	    }
	}
    }

    // create a long message buffer for translation of long messages.
    {  HANDLE hMem;
       hMem = GlobalAlloc( GMEM_MOVEABLE | GMEM_ZEROINIT
			 , (LONG)(sizeof(MIDIHDR) + MIDIDATABUFFER)
			 );
       if (hMem)
       pmidihdrMapper = ( hMem ? (LPMIDIHDR)GlobalLock(hMem) : NULL);
    }
    if (!pmidihdrMapper) {
	ReleaseResources();
	return MMSYSERR_NOMEM;
    }
    pmidihdrMapper->lpData = (LPSTR)(pmidihdrMapper + 1);
    pmidihdrMapper->dwBufferLength = MIDIDATABUFFER;

    // open all devices in new map
    for( i = 0; ((i < 16) && (mapdevs[i].wDeviceID != (WORD)(-1)) ); i++ ) {
	uResult = midiOutOpen(&mapdevs[i].hMidi,
			       mapdevs[i].wDeviceID,
			       0,
			       0,
			       dwParam2 & ~CALLBACK_TYPEMASK);

	if(uResult != 0 ){  // if any opens fail, return now
	    ReleaseResources();
	    return uResult;
	}

    uResult = midiOutPrepareHeader(mapdevs[i].hMidi, pmidihdrMapper, sizeof(MIDIHDR));
    if (uResult) {
	    ReleaseResources();
	    return uResult;
	}
	mapdevs[i].fwFlags |= DEV_PREPARED;

	for( j = 0; j < 16; j++ ) {
	    if( mapdevs[i].wDeviceID == chnldevs[j].wDeviceID )
	       chnldevs[j].hMidi = mapdevs[i].hMidi;
	}
    }

    OpenCallback = lpOpen->dwCallback;
    OpenInstance = lpOpen->dwInstance;
    OpenFlags = dwParam2;
    hmidiMapper = (HMIDIOUT)lpOpen->hMidi;
    status = 0;
    bCurrentLen = 0;
    bCurrentStatus = 0;

#if defined(WIN16)
    if (  GlobalNail((HGLOBAL)HIWORD(DriverProc), STATE_CODENAILED)
       && GlobalNail(hCurMap, STATE_MAPNAILED)
       && GlobalNail((HGLOBAL)HIWORD((DWORD)(LPVOID)&hCurMap), STATE_DATANAILED)
       )
    {
#endif //WIN16
       CallbackNotification(MOM_OPEN, 0);
       return MMSYSERR_NOERROR;
#if defined(WIN16)
    }
    ReleaseResources();
    return MMSYSERR_NOMEM;
#endif //WIN16
}
