2564 lines
72 KiB
C
2564 lines
72 KiB
C
/*****************************************************************************
|
|
midiemu.c
|
|
|
|
MIDI support -- routines for stream emulation
|
|
|
|
Copyright (c) Microsoft Corporation 1990. All rights reserved
|
|
|
|
*****************************************************************************/
|
|
#define INCL_WINMM
|
|
#include "winmmi.h"
|
|
#include "muldiv32.h"
|
|
|
|
#define MDC_F_VALID 0x00000001L
|
|
|
|
typedef struct midcache_tag *PMIDCACHE;
|
|
typedef struct midcache_tag {
|
|
PMIDCACHE pNext;
|
|
UINT cRef; // # streams using this handle
|
|
DWORD fdwCache; // Flags
|
|
UINT uDeviceID; // Base device ID for driver
|
|
UINT uPort; // Relative to driver
|
|
HMIDI hmidi; // Handle if it's open
|
|
char szDevKey[64]; // DevKey from mreg stuff
|
|
} MIDCACHE;
|
|
|
|
#define NUM_NOTES (128)
|
|
#define NUM_CHANNELS (16)
|
|
#define MEMU_CB_NOTEON (NUM_CHANNELS*NUM_NOTES/2) // 16 chan * 128 notes (4 bits/note)
|
|
#define MAX_NOTES_ON (0xF)
|
|
|
|
#define TIMER_OFF (0)
|
|
|
|
PMIDCACHE gpMIdCache = NULL;
|
|
PMIDIEMU gpEmuList = NULL;
|
|
UINT guMIDIInTimer = 0;
|
|
UINT guMIDITimerID = TIMER_OFF;
|
|
BOOL gfMinPeriod = FALSE;
|
|
UINT guMIDIPeriodMin;
|
|
|
|
STATIC HMIDI FAR PASCAL mseIDtoHMidi(
|
|
PMIDIEMU pme,
|
|
DWORD dwStreamID);
|
|
|
|
#ifdef ALLOW_MULTIPLE_OPENS
|
|
STATIC void NEAR PASCAL mseUpdateIDCache(
|
|
void);
|
|
|
|
STATIC void NEAR PASCAL mseValidateCacheItems(
|
|
UINT cDev,
|
|
WORD wClass);
|
|
|
|
MMRESULT NEAR PASCAL mseGetOpenHandle(
|
|
LPHMIDI lph,
|
|
UINT uDeviceID);
|
|
|
|
MMRESULT NEAR PASCAL mseReleaseOpenHandle(
|
|
HMIDI hmidi);
|
|
#endif
|
|
|
|
MMRESULT FAR PASCAL mseOpen(
|
|
LPDWORD lpdwUser,
|
|
LPMIDIOPENDESC lpmod,
|
|
DWORD fdwOpen);
|
|
|
|
MMRESULT FAR PASCAL mseClose(
|
|
PMIDIEMU pme);
|
|
|
|
MMRESULT FAR PASCAL mseProperty(
|
|
PMIDIEMU pme,
|
|
LPBYTE lpbProp,
|
|
DWORD fdwProp);
|
|
|
|
MMRESULT FAR PASCAL mseGetPosition(
|
|
PMIDIEMU pme,
|
|
LPMMTIME lpmmt);
|
|
|
|
MMRESULT FAR PASCAL mseGetVolume(
|
|
PMIDIEMU pme,
|
|
LPDWORD lpdwVolume);
|
|
|
|
MMRESULT FAR PASCAL mseSetVolume(
|
|
PMIDIEMU pme,
|
|
DWORD dwVolume);
|
|
|
|
MMRESULT FAR PASCAL mseOutStop(
|
|
PMIDIEMU pme);
|
|
|
|
MMRESULT FAR PASCAL mseOutReset(
|
|
PMIDIEMU pme);
|
|
|
|
MMRESULT FAR PASCAL mseOutPause(
|
|
PMIDIEMU pme);
|
|
|
|
MMRESULT FAR PASCAL mseOutRestart(
|
|
PMIDIEMU pme,
|
|
DWORD msTime,
|
|
DWORD tkTime);
|
|
|
|
MMRESULT FAR PASCAL mseOutCachePatches(
|
|
PMIDIEMU pme,
|
|
UINT uBank,
|
|
LPWORD pwpa,
|
|
UINT fuCache);
|
|
|
|
MMRESULT FAR PASCAL mseOutCacheDrumPatches(
|
|
PMIDIEMU pme,
|
|
UINT uPatch,
|
|
LPWORD pwkya,
|
|
UINT fuCache);
|
|
|
|
DWORD FAR PASCAL mseOutBroadcast(
|
|
PMIDIEMU pme,
|
|
UINT msg,
|
|
DWORD dwParam1,
|
|
DWORD dwParam2);
|
|
|
|
DWORD FAR PASCAL mseTimebase(
|
|
PCLOCK pclock);
|
|
|
|
#pragma alloc_text(FIXMIDI, mseIDtoHMidi)
|
|
#pragma alloc_text(FIXMIDI, mseMessage)
|
|
#pragma alloc_text(FIXMIDI, mseOutReset)
|
|
|
|
#pragma alloc_text(FIXMIDI, midiOutScheduleNextEvent)
|
|
#pragma alloc_text(FIXMIDI, midiOutPlayNextPolyEvent)
|
|
#pragma alloc_text(FIXMIDI, midiOutDequeueAndCallback)
|
|
#pragma alloc_text(FIXMIDI, midiOutTimerTick)
|
|
#pragma alloc_text(FIXMIDI, midiOutCallback)
|
|
#pragma alloc_text(FIXMIDI, midiOutSetClockRate)
|
|
#pragma alloc_text(INIT,midiEmulatorInit)
|
|
#pragma alloc_text(FIXMIDI, mseTimebase)
|
|
|
|
|
|
/****************************************************************************/
|
|
/****************************************************************************/
|
|
|
|
INLINE LONG PDEVLOCK(PMIDIEMU pdev)
|
|
{
|
|
LONG lTemp;
|
|
|
|
lTemp = InterlockedIncrement(&(pdev->lLockCount));
|
|
|
|
EnterCriticalSection(&(pdev->CritSec));
|
|
|
|
return lTemp;
|
|
}
|
|
|
|
INLINE LONG PDEVUNLOCK(PMIDIEMU pdev)
|
|
{
|
|
LONG lTemp;
|
|
|
|
lTemp = InterlockedDecrement(&(pdev->lLockCount));
|
|
|
|
LeaveCriticalSection(&(pdev->CritSec));
|
|
|
|
return lTemp;
|
|
}
|
|
|
|
|
|
/****************************************************************************/
|
|
/****************************************************************************/
|
|
DWORD FAR PASCAL mseMessage(
|
|
UINT msg,
|
|
DWORD dwUser,
|
|
DWORD dwParam1,
|
|
DWORD dwParam2)
|
|
{
|
|
MMRESULT mmr = MMSYSERR_NOERROR;
|
|
PMIDIEMU pme = (PMIDIEMU)(UINT)dwUser;
|
|
|
|
|
|
switch(msg)
|
|
{
|
|
case MODM_OPEN:
|
|
mmr = mseOpen((LPDWORD)dwUser, (LPMIDIOPENDESC)dwParam1, dwParam2);
|
|
break;
|
|
|
|
case MODM_CLOSE:
|
|
mmr = mseClose(pme);
|
|
break;
|
|
|
|
case MODM_GETVOLUME:
|
|
mmr = mseGetVolume(pme, (LPDWORD)dwParam1);
|
|
break;
|
|
|
|
case MODM_SETVOLUME:
|
|
mmr = mseSetVolume(pme, dwParam1);
|
|
break;
|
|
|
|
case MODM_PREPARE:
|
|
case MODM_UNPREPARE:
|
|
mmr = MMSYSERR_NOTSUPPORTED;
|
|
break;
|
|
|
|
case MODM_DATA:
|
|
//#pragma FIXMSG("How to route async short messages to other stream-ids?")
|
|
|
|
if (!(dwParam1 & 0x80))
|
|
mmr = MIDIERR_BADOPENMODE;
|
|
else
|
|
mmr = (MMRESULT)midiMessage((HMIDI)pme->rIds[0].hMidi, MODM_DATA, dwParam1, 0);
|
|
break;
|
|
|
|
case MODM_RESET:
|
|
mmr = mseOutReset(pme);
|
|
break;
|
|
|
|
case MODM_STOP:
|
|
mmr = mseOutStop(pme);
|
|
break;
|
|
|
|
case MODM_CACHEPATCHES:
|
|
mmr = mseOutCachePatches(pme, HIWORD(dwParam2), (LPWORD)dwParam1, LOWORD(dwParam2));
|
|
break;
|
|
|
|
case MODM_CACHEDRUMPATCHES:
|
|
mmr = mseOutCacheDrumPatches(pme, HIWORD(dwParam2), (LPWORD)dwParam1, LOWORD(dwParam2));
|
|
break;
|
|
|
|
case MODM_PAUSE:
|
|
mmr = mseOutPause(pme);
|
|
break;
|
|
|
|
case MODM_RESTART:
|
|
mmr = mseOutRestart(pme, dwParam1, dwParam2);
|
|
break;
|
|
|
|
case MODM_STRMDATA:
|
|
mmr = mseOutSend(pme, (LPMIDIHDR)dwParam1, (UINT)dwParam2);
|
|
break;
|
|
|
|
case MODM_PROPERTIES:
|
|
mmr = mseProperty(pme, (LPBYTE)dwParam1, dwParam2);
|
|
break;
|
|
|
|
case MODM_GETPOS:
|
|
mmr = mseGetPosition(pme, (LPMMTIME)dwParam1);
|
|
break;
|
|
|
|
default:
|
|
if ((msg < DRVM_IOCTL) ||
|
|
(msg >= DRVM_IOCTL_LAST) && (msg < DRVM_MAPPER))
|
|
{
|
|
dprintf1(("Unknown message [%04X] in MIDI emulator", (WORD)msg));
|
|
mmr = MMSYSERR_NOTSUPPORTED;
|
|
}
|
|
else
|
|
mmr = mseOutBroadcast(pme, msg, dwParam1, dwParam2);
|
|
}
|
|
|
|
return mmr;
|
|
}
|
|
|
|
MMRESULT FAR PASCAL mseOpen(
|
|
LPDWORD lpdwUser,
|
|
LPMIDIOPENDESC lpmod,
|
|
DWORD fdwOpen)
|
|
{
|
|
MMRESULT mmrc = MMSYSERR_NOERROR;
|
|
DWORD cbHandle;
|
|
PMIDIEMU pme = NULL;
|
|
UINT idx;
|
|
|
|
mmrc = MMSYSERR_NOMEM;
|
|
cbHandle = sizeof(MIDIEMU) + lpmod->cIds * ELESIZE(MIDIEMU, rIds[0]);
|
|
if (cbHandle >= 65536L)
|
|
{
|
|
dprintf1(("mSEO: cbHandle >= 64K!"));
|
|
goto mseOpen_Cleanup;
|
|
}
|
|
|
|
if (NULL == (pme = (PMIDIEMU)winmmAlloc(cbHandle)))
|
|
{
|
|
dprintf1(("mSEO: !winmmAlloc(cbHandle)"));
|
|
goto mseOpen_Cleanup;
|
|
}
|
|
|
|
if (NULL == (pme->rbNoteOn = winmmAlloc(MEMU_CB_NOTEON)))
|
|
{
|
|
dprintf1(("mSEO: !GlobalAlloc(MEMU_CB_NOTEON"));
|
|
goto mseOpen_Cleanup;
|
|
}
|
|
|
|
pme->fdwDev |= MDV_F_LOCKED;
|
|
|
|
pme->hStream = (HMIDISTRM)lpmod->hMidi;
|
|
pme->dwTimeDiv = DEFAULT_TIMEDIV;
|
|
pme->dwTempo = DEFAULT_TEMPO;
|
|
pme->dwCallback = lpmod->dwCallback;
|
|
pme->dwFlags = fdwOpen;
|
|
pme->dwInstance = lpmod->dwInstance;
|
|
pme->dwPolyMsgState = PM_STATE_PAUSED;
|
|
pme->chMidi = (UINT)lpmod->cIds;
|
|
pme->dwSavedState = PM_STATE_STOPPED;
|
|
pme->tkPlayed = 0;
|
|
pme->lLockCount = -1;
|
|
pme->dwSignature = MSE_SIGNATURE;
|
|
|
|
for (idx = 0; idx < pme->chMidi; idx++)
|
|
{
|
|
pme->rIds[idx].dwStreamID = lpmod->rgIds[idx].dwStreamID;
|
|
|
|
#ifdef ALLOW_MULTIPLE_OPENS
|
|
mmrc = mseGetOpenHandle(&pme->rIds[idx].hMidi,
|
|
lpmod->rgIds[idx].uDeviceID);
|
|
#else
|
|
mmrc = midiOutOpen((LPHMIDIOUT)&pme->rIds[idx].hMidi,
|
|
lpmod->rgIds[idx].uDeviceID,
|
|
(DWORD)midiOutCallback,
|
|
0L,
|
|
CALLBACK_FUNCTION);
|
|
#endif
|
|
|
|
if (MMSYSERR_NOERROR != mmrc)
|
|
goto mseOpen_Cleanup;
|
|
}
|
|
|
|
InitializeCriticalSection(&pme->CritSec);
|
|
|
|
clockInit(&pme->clock, 0, 0, mseTimebase);
|
|
dprintf2(("midiOutOpen: midiOutSetClockRate()"));
|
|
midiOutSetClockRate(pme, 0);
|
|
|
|
|
|
mseOpen_Cleanup:
|
|
if (MMSYSERR_NOERROR != mmrc)
|
|
{
|
|
if (pme)
|
|
{
|
|
if (pme->rbNoteOn)
|
|
{
|
|
winmmFree(pme->rbNoteOn);
|
|
}
|
|
|
|
DeleteCriticalSection(&pme->CritSec);
|
|
|
|
pme->dwSignature = 0L;
|
|
|
|
winmmFree(pme);
|
|
|
|
for (idx = 0; idx < pme->chMidi; idx++)
|
|
if (NULL != pme->rIds[idx].hMidi)
|
|
#ifdef ALLOW_MULTIPLE_OPENS
|
|
mseReleaseOpenHandle(pme->rIds[idx].hMidi);
|
|
#else
|
|
midiOutClose((HMIDIOUT)pme->rIds[idx].hMidi);
|
|
#endif
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pme->pNext = gpEmuList;
|
|
gpEmuList = pme;
|
|
|
|
*lpdwUser = (DWORD)(UINT)pme;
|
|
}
|
|
|
|
return mmrc;
|
|
}
|
|
|
|
MMRESULT FAR PASCAL mseClose(
|
|
PMIDIEMU pme)
|
|
|
|
{
|
|
UINT idx;
|
|
MMRESULT mmrc;
|
|
PMIDIEMU pmePrev;
|
|
PMIDIEMU pmeCurr;
|
|
|
|
#ifdef DEBUG
|
|
{
|
|
UINT idx;
|
|
|
|
dprintf2(("cEvents %lu", pme->cEvents));
|
|
|
|
for (idx = 0; idx < MEM_MAX_LATENESS; idx++)
|
|
dprintf2(("%5u: %u", idx, pme->auLateness[idx]));
|
|
}
|
|
#endif
|
|
|
|
if ((PM_STATE_STOPPED != pme->dwPolyMsgState &&
|
|
PM_STATE_PAUSED != pme->dwPolyMsgState &&
|
|
PM_STATE_EMPTY != pme->dwPolyMsgState))
|
|
{
|
|
dprintf1(("mseClose: Started playing again since close query!!!"));
|
|
|
|
mseOutStop(pme);
|
|
}
|
|
|
|
midiOutAllNotesOff(pme);
|
|
|
|
for (idx = 0; idx < pme->chMidi; idx++)
|
|
{
|
|
#ifdef ALLOW_MULTIPLE_OPENS
|
|
mmrc = mseReleaseOpenHandle(pme->rIds[idx].hMidi);
|
|
#else
|
|
mmrc = midiOutClose((HMIDIOUT)pme->rIds[idx].hMidi);
|
|
#endif
|
|
if (MMSYSERR_NOERROR != mmrc)
|
|
{
|
|
dprintf1(( "mseClose: HMIDI %04X returned %u for close", pme->rIds[idx].hMidi, mmrc));
|
|
}
|
|
}
|
|
|
|
winmmFree(pme->rbNoteOn);
|
|
|
|
pmePrev = NULL;
|
|
pmeCurr = gpEmuList;
|
|
|
|
while (pmeCurr)
|
|
{
|
|
if (pmeCurr == pme)
|
|
break;
|
|
|
|
pmePrev = pmeCurr;
|
|
pmeCurr = pmeCurr->pNext;
|
|
}
|
|
|
|
if (pmeCurr)
|
|
{
|
|
if (pmePrev)
|
|
pmePrev->pNext = pmeCurr->pNext;
|
|
else
|
|
gpEmuList = pmeCurr->pNext;
|
|
}
|
|
|
|
//
|
|
// Make sure that we don't have the critical section before
|
|
// we try to delete it. Otherwise we will leak critical section
|
|
// handles in the kernel.
|
|
//
|
|
while ( pme->lLockCount >= 0 )
|
|
{
|
|
PDEVUNLOCK( pme );
|
|
}
|
|
|
|
DeleteCriticalSection(&pme->CritSec);
|
|
|
|
pme->dwSignature = 0L;
|
|
|
|
winmmFree(pme);
|
|
|
|
return MMSYSERR_NOERROR;
|
|
}
|
|
|
|
STATIC HMIDI FAR PASCAL mseIDtoHMidi(
|
|
PMIDIEMU pme,
|
|
DWORD dwStreamID)
|
|
{
|
|
UINT idx;
|
|
PMIDIEMUSID pmesi;
|
|
|
|
for (idx = 0, pmesi = pme->rIds; idx < pme->chMidi; idx++, pmesi++)
|
|
if (pmesi->dwStreamID == dwStreamID)
|
|
return pmesi->hMidi;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
MMRESULT FAR PASCAL mseProperty(
|
|
PMIDIEMU pme,
|
|
LPBYTE lppropdata,
|
|
DWORD fdwProp)
|
|
{
|
|
PMIDISTRM pms;
|
|
|
|
pms = (PMIDISTRM)(pme->hStream);
|
|
|
|
if ((!(fdwProp&MIDIPROP_SET)) && (!(fdwProp&MIDIPROP_GET)))
|
|
return MMSYSERR_INVALPARAM;
|
|
|
|
V_RPOINTER(lppropdata, sizeof(DWORD), MMSYSERR_INVALPARAM);
|
|
|
|
if (fdwProp & MIDIPROP_SET)
|
|
{
|
|
V_RPOINTER(lppropdata, (UINT)(*(LPDWORD)(lppropdata)), MMSYSERR_INVALPARAM);
|
|
}
|
|
else
|
|
{
|
|
V_WPOINTER(lppropdata, (UINT)(*(LPDWORD)(lppropdata)), MMSYSERR_INVALPARAM);
|
|
}
|
|
|
|
switch(fdwProp & MIDIPROP_PROPVAL)
|
|
{
|
|
case MIDIPROP_TIMEDIV:
|
|
if (((LPMIDIPROPTIMEDIV)lppropdata)->cbStruct < sizeof(MIDIPROPTIMEDIV))
|
|
return MMSYSERR_INVALPARAM;
|
|
|
|
if (fdwProp & MIDIPROP_GET)
|
|
{
|
|
((LPMIDIPROPTIMEDIV)lppropdata)->dwTimeDiv = pme->dwTimeDiv;
|
|
return MMSYSERR_NOERROR;
|
|
}
|
|
|
|
if (PM_STATE_STOPPED != pme->dwPolyMsgState &&
|
|
PM_STATE_PAUSED != pme->dwPolyMsgState)
|
|
return MMSYSERR_INVALPARAM;
|
|
|
|
pme->dwTimeDiv = ((LPMIDIPROPTIMEDIV)lppropdata)->dwTimeDiv;
|
|
dprintf1(( "dwTimeDiv %08lX", pme->dwTimeDiv));
|
|
midiOutSetClockRate(pme, 0);
|
|
|
|
return MMSYSERR_NOERROR;
|
|
|
|
case MIDIPROP_TEMPO:
|
|
if (((LPMIDIPROPTEMPO)lppropdata)->cbStruct < sizeof(MIDIPROPTEMPO))
|
|
return MMSYSERR_INVALPARAM;
|
|
|
|
if (fdwProp & MIDIPROP_GET)
|
|
{
|
|
((LPMIDIPROPTEMPO)lppropdata)->dwTempo = pme->dwTempo;
|
|
return MMSYSERR_NOERROR;
|
|
}
|
|
|
|
pme->dwTempo = ((LPMIDIPROPTEMPO)lppropdata)->dwTempo;
|
|
midiOutSetClockRate(pme, pme->tkPlayed);
|
|
|
|
return MMSYSERR_NOERROR;
|
|
|
|
default:
|
|
return MMSYSERR_INVALPARAM;
|
|
}
|
|
}
|
|
|
|
MMRESULT FAR PASCAL mseGetPosition(
|
|
PMIDIEMU pme,
|
|
LPMMTIME pmmt)
|
|
{
|
|
DWORD tkTime;
|
|
DWORD dw10Min;
|
|
DWORD dw10MinCycle;
|
|
DWORD dw1Min;
|
|
DWORD dwDropMe;
|
|
|
|
//
|
|
// Figure out position in stream based on emulation.
|
|
//
|
|
|
|
//
|
|
// Validate wType parameter and change it if needed.
|
|
//
|
|
if (pmmt->wType != TIME_TICKS && pmmt->wType != TIME_MS)
|
|
{
|
|
if (pme->dwTimeDiv & IS_SMPTE)
|
|
{
|
|
if (pmmt->wType != TIME_SMPTE)
|
|
{
|
|
pmmt->wType = TIME_MS;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (pmmt->wType != TIME_MIDI)
|
|
{
|
|
pmmt->wType = TIME_MS;
|
|
}
|
|
}
|
|
}
|
|
|
|
switch(pmmt->wType)
|
|
{
|
|
case TIME_TICKS:
|
|
//
|
|
// We interpret samples to be straight MIDI ticks.
|
|
//
|
|
tkTime = (DWORD)clockTime(&pme->clock);
|
|
pmmt->u.ticks = (((TICKS)tkTime) < 0) ? 0 : tkTime;
|
|
|
|
break;
|
|
|
|
case TIME_MIDI:
|
|
//
|
|
// Song position pointer is number of 1/16th notes we've
|
|
// played which we can get from number of ticks played and
|
|
// number of 1/4 notes per tick.
|
|
//
|
|
tkTime = (DWORD)clockTime(&pme->clock);
|
|
if (((TICKS)tkTime) < 0)
|
|
tkTime = 0;
|
|
|
|
pmmt->u.midi.songptrpos =
|
|
muldiv32(
|
|
tkTime,
|
|
4,
|
|
TICKS_PER_QN(pme->dwTimeDiv));
|
|
|
|
break;
|
|
|
|
|
|
case TIME_SMPTE:
|
|
|
|
tkTime = (DWORD)clockTime(&pme->clock);
|
|
if (((TICKS)tkTime) < 0)
|
|
tkTime = 0;
|
|
|
|
pmmt->u.smpte.fps = (BYTE)(-SMPTE_FORMAT(pme->dwTimeDiv));
|
|
|
|
//
|
|
// If this has managed to get set to something bizarre, just
|
|
// do normal 30 nondrop.
|
|
//
|
|
if ((pmmt->u.smpte.fps != SMPTE_24) &&
|
|
(pmmt->u.smpte.fps != SMPTE_25) &&
|
|
(pmmt->u.smpte.fps != SMPTE_30DROP) &&
|
|
(pmmt->u.smpte.fps != SMPTE_30))
|
|
{
|
|
pmmt->u.smpte.fps = SMPTE_30;
|
|
}
|
|
|
|
switch(pmmt->u.smpte.fps)
|
|
{
|
|
case SMPTE_24:
|
|
pmmt->u.smpte.frame = (BYTE)(tkTime%24);
|
|
tkTime /= 24;
|
|
break;
|
|
|
|
case SMPTE_25:
|
|
pmmt->u.smpte.frame = (BYTE)(tkTime%25);
|
|
tkTime /= 25;
|
|
break;
|
|
|
|
case SMPTE_30DROP:
|
|
//
|
|
// Calculate stupid drop-frame stuff.
|
|
//
|
|
// We add 2 frames per 1-minute interval except
|
|
// on every 10th minute.
|
|
//
|
|
dw10Min = tkTime/S30D_FRAMES_PER_10MIN;
|
|
dw10MinCycle = tkTime%S30D_FRAMES_PER_10MIN;
|
|
dw1Min = (dw10MinCycle < 2
|
|
? 0 :
|
|
(dw10MinCycle-2)/S30D_FRAMES_PER_MIN);
|
|
dwDropMe = 18*dw10Min + 2*dw1Min;
|
|
|
|
tkTime += dwDropMe;
|
|
|
|
//
|
|
// !!! Falling through to 30-nondrop case !!!
|
|
//
|
|
|
|
case SMPTE_30:
|
|
pmmt->u.smpte.frame = (BYTE)(tkTime%30);
|
|
tkTime /= 30;
|
|
break;
|
|
}
|
|
pmmt->u.smpte.sec = (BYTE)(tkTime%60);
|
|
tkTime /= 60;
|
|
pmmt->u.smpte.min = (BYTE)(tkTime%60);
|
|
tkTime /= 60;
|
|
pmmt->u.smpte.hour = (BYTE)(tkTime);
|
|
|
|
break;
|
|
|
|
case TIME_MS:
|
|
//
|
|
// Use msTotal + ms since time parms last updated; this
|
|
// takes starvation/paused time into account.
|
|
//
|
|
pmmt->u.ms =
|
|
clockMsTime(&pme->clock);
|
|
|
|
break;
|
|
|
|
default:
|
|
dprintf1(( "midiOutGetPosition: unexpected wType!!!"));
|
|
return MMSYSERR_INVALPARAM;
|
|
}
|
|
|
|
return MMSYSERR_NOERROR;
|
|
}
|
|
|
|
MMRESULT FAR PASCAL mseGetVolume(
|
|
PMIDIEMU pme,
|
|
LPDWORD lpdwVolume)
|
|
{
|
|
MMRESULT mmr = MMSYSERR_NOTSUPPORTED;
|
|
UINT idx;
|
|
|
|
// Walk the device list underneath us until someone knows the volume
|
|
//
|
|
for (idx = 0; idx < pme->chMidi; ++idx)
|
|
if (MMSYSERR_NOERROR ==
|
|
(midiOutGetVolume((HMIDIOUT)pme->rIds[idx].hMidi, lpdwVolume)))
|
|
{
|
|
mmr = MMSYSERR_NOERROR;
|
|
break;
|
|
}
|
|
|
|
return mmr;
|
|
}
|
|
|
|
MMRESULT FAR PASCAL mseSetVolume(
|
|
PMIDIEMU pme,
|
|
DWORD dwVolume)
|
|
{
|
|
MMRESULT mmr = MMSYSERR_NOERROR;
|
|
MMRESULT mmr2;
|
|
UINT idx;
|
|
|
|
// Try to set everyone's volume
|
|
//
|
|
for (idx = 0; idx < pme->chMidi; ++idx)
|
|
if (MMSYSERR_NOERROR !=
|
|
(mmr2 = midiOutSetVolume((HMIDIOUT)pme->rIds[idx].hMidi, dwVolume)))
|
|
mmr = mmr2;
|
|
|
|
return mmr;
|
|
|
|
}
|
|
|
|
#ifdef ALLOW_MULTIPLE_OPENS
|
|
/*****************************************************************************
|
|
|
|
@doc INTERNAL MIDI
|
|
|
|
@func void | mseUpdateIDCache | This function updates all of the entries
|
|
in the MIDI ID cache.
|
|
|
|
@comm
|
|
The ID cache holds the relationship between a particular device and its
|
|
current device ID (which might change due to the wonders of Plug n' Play).
|
|
|
|
The emulator needs to be able to open handles on behalf of a stream, and
|
|
to support multiple streams. The ID cache provides the mechanism by which
|
|
we can hold a handle open for a long time and still guarantee the user of
|
|
getting the correct device, since we refer to it by the driver key from
|
|
mreg rather than just a device ID.
|
|
|
|
To update the ID cache, we
|
|
- mark all entries invalid
|
|
- walk the cache for output. This will remark entries as valid
|
|
after making sure their device id's are consistent, and add new entries
|
|
as need be.
|
|
- walk the cache and remove anything not marked valid.
|
|
|
|
Actually we allow things to stick around after they're no longer valid if
|
|
there's a handle open on them.
|
|
|
|
****************************************************************************/
|
|
STATIC void NEAR PASCAL mseUpdateIDCache(
|
|
void)
|
|
{
|
|
PMIDCACHE pmidcPrev;
|
|
PMIDCACHE pmidcCurr;
|
|
PMIDCACHE pmidcWork;
|
|
|
|
for (pmidcCurr = gpMIdCache; pmidcCurr; pmidcCurr = pmidcCurr->pNext)
|
|
pmidcCurr->fdwCache &= ~MDC_F_VALID;
|
|
|
|
mseValidateCacheItems(midiOutGetNumDevs(), MMDRVI_CLASS_MIDI|MMDRVI_MIDIOUT);
|
|
|
|
pmidcPrev = NULL;
|
|
pmidcCurr = gpMIdCache;
|
|
|
|
while (pmidcCurr)
|
|
{
|
|
if (NULL == pmidcCurr->hmidi &&
|
|
(!(pmidcCurr->fdwCache & MDC_F_VALID)) )
|
|
{
|
|
if (NULL == pmidcPrev)
|
|
{
|
|
gpMIdCache = pmidcCurr->pNext;
|
|
}
|
|
else
|
|
{
|
|
pmidcPrev->pNext = pmidcCurr->pNext;
|
|
}
|
|
|
|
pmidcWork = pmidcCurr;
|
|
pmidcCurr = pmidcCurr->pNext;
|
|
|
|
winmmFree(pmidcWork);
|
|
}
|
|
else
|
|
{
|
|
pmidcPrev = pmidcCurr;
|
|
pmidcCurr = pmidcCurr->pNext;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/*****************************************************************************
|
|
|
|
@doc INTERNAL MIDI
|
|
|
|
@func void | mseValidateCacheItems | Walk the cache and validate each entry.
|
|
|
|
@parm UINT | cDev | The number of devices in the class.
|
|
|
|
@parm WORD | wClass | The device class for mreg calls.
|
|
|
|
@comm
|
|
Walk the device list via mreg.
|
|
For each port on each device,
|
|
Walk the cache looking for the port on current device by devkey
|
|
If found,
|
|
update device ID in cache entry and mark valid
|
|
else
|
|
add a new cache entry for this device/port combo and mark it valid
|
|
|
|
|
|
****************************************************************************/
|
|
STATIC void NEAR PASCAL mseValidateCacheItems(
|
|
UINT cDev,
|
|
WORD wClass)
|
|
{
|
|
UINT idx;
|
|
UINT idxPort;
|
|
DWORD cPorts;
|
|
PMIDCACHE pmidcPrev;
|
|
PMIDCACHE pmidcCurr;
|
|
char szDevKey[64];
|
|
|
|
for (idx = 0; idx < cDev; )
|
|
{
|
|
if (MMSYSERR_NOERROR == (MMRESULT)mregDriverInformation(idx, wClass, MODM_GETNUMDEVS, (DWORD)(LPDWORD)&cPorts, 0) &&
|
|
0 != cPorts &&
|
|
MMSYSERR_NOERROR == (MMRESULT)mregDriverInformation(idx, wClass, DRV_QUERYDRVENTRY, (DWORD)(LPSTR)szDevKey, sizeof(szDevKey)))
|
|
{
|
|
dprintf2(( "mVCI_InitSearch: DeviceID %u is now DevKey %s", idx, (LPSTR)szDevKey));
|
|
|
|
for (idxPort = 0; idxPort < cPorts; idxPort++)
|
|
{
|
|
for (pmidcPrev = NULL, pmidcCurr = gpMIdCache;
|
|
pmidcCurr;
|
|
pmidcPrev = pmidcCurr, pmidcCurr = pmidcCurr->pNext)
|
|
{
|
|
if (pmidcCurr->uPort == idxPort &&
|
|
!lstrcmp(pmidcCurr->szDevKey, szDevKey))
|
|
{
|
|
dprintf2(( "mVCI_Found: %04XH", (WORD)pmidcCurr));
|
|
pmidcCurr->uDeviceID = idx;
|
|
pmidcCurr->fdwCache |= MDC_F_VALID;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (NULL == pmidcCurr)
|
|
{
|
|
pmidcCurr = (PMIDCACHE)winmmAlloc(sizeof(MIDCACHE));
|
|
|
|
if (NULL != pmidcCurr)
|
|
{
|
|
pmidcCurr->pNext = NULL;
|
|
pmidcCurr->cRef = 0;
|
|
pmidcCurr->fdwCache = MDC_F_VALID;
|
|
pmidcCurr->uDeviceID = idx;
|
|
pmidcCurr->uPort = idxPort;
|
|
pmidcCurr->hmidi = NULL;
|
|
lstrcpy(pmidcCurr->szDevKey, szDevKey);
|
|
|
|
if (NULL != pmidcPrev)
|
|
pmidcPrev->pNext = pmidcCurr;
|
|
else
|
|
gpMIdCache = pmidcCurr;
|
|
}
|
|
else
|
|
dprintf1(( "mVCI_Err: No memory for cache entry!"));
|
|
}
|
|
}
|
|
|
|
idx += (UINT)cPorts;
|
|
}
|
|
else
|
|
{
|
|
dprintf1(( "mVCI_Err: cPorts==0 or could not get DevKey"));
|
|
++idx;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************
|
|
|
|
@doc INTERNAL MIDI
|
|
|
|
@func MMRESULT | mseGetOpenHandle | Open a MIDI device with cache awareness.
|
|
|
|
@parm LPHMIDI | lph | Will contain the handle on success.
|
|
|
|
@parm UINT | uDeviceID | The current device ID.
|
|
|
|
@comm
|
|
Using mreg, figure out the devkey and port for this device ID.
|
|
Update the contents of the cache.
|
|
Walk the cache looking for the devkey and port.
|
|
If the cache entry is found, either return the handle if it's already open
|
|
or open it.
|
|
|
|
****************************************************************************/
|
|
MMRESULT NEAR PASCAL mseGetOpenHandle(
|
|
LPHMIDI lph,
|
|
UINT uDeviceID)
|
|
{
|
|
PMIDCACHE pmidc;
|
|
WORD wClass;
|
|
char szDevKey[64];
|
|
MMRESULT mmrc;
|
|
HMD hmd;
|
|
UINT uPort;
|
|
|
|
*lph = NULL;
|
|
|
|
wClass = MMDRVI_CLASS_MIDI|MMDRVI_MIDIOUT;
|
|
|
|
mmrc = (MMRESULT)mregDriverInformation(uDeviceID,
|
|
wClass,
|
|
DRV_QUERYDRVENTRY,
|
|
(DWORD)(LPSTR)szDevKey,
|
|
sizeof(szDevKey));
|
|
if (MMSYSERR_NOERROR != mmrc)
|
|
{
|
|
dprintf1(( "mGOH_Err: mregDriverInformation() %u", mmrc));
|
|
return mmrc;
|
|
}
|
|
|
|
mmrc = (MMRESULT)mregFindDevice(uDeviceID,
|
|
wClass,
|
|
&hmd,
|
|
&uPort);
|
|
|
|
if (MMSYSERR_NOERROR != mmrc)
|
|
{
|
|
dprintf1(( "mGOH_Err: mregFindDevice() %u", mmrc));
|
|
return mmrc;
|
|
}
|
|
|
|
mseUpdateIDCache();
|
|
|
|
for (pmidc = gpMIdCache; pmidc; pmidc=pmidc->pNext)
|
|
{
|
|
if (uPort == pmidc->uPort &&
|
|
!lstrcmp(pmidc->szDevKey, szDevKey) )
|
|
break;
|
|
}
|
|
|
|
if (!pmidc)
|
|
{
|
|
dprintf1(( "mGOH_Err: Could not locate <%s> in cache", (LPSTR)szDevKey));
|
|
return MMSYSERR_ALLOCATED;
|
|
}
|
|
|
|
dprintf2(( "mGOH: DeviceID %u is now %u", uDeviceID, pmidc->uDeviceID + uPort));
|
|
|
|
|
|
if (1 == ++pmidc->cRef)
|
|
{
|
|
mmrc = midiOutOpen((LPHMIDIOUT)&pmidc->hmidi,
|
|
pmidc->uDeviceID + uPort,
|
|
(DWORD)midiOutCallback,
|
|
0L,
|
|
CALLBACK_FUNCTION);
|
|
|
|
if (MMSYSERR_NOERROR != mmrc)
|
|
{
|
|
dprintf1(( "gMOH_Err: midiXXXOpen() %u", mmrc));
|
|
|
|
pmidc->hmidi = NULL;
|
|
return mmrc;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
dprintf2(( "mGOH: Device already open, using cached handle"));
|
|
}
|
|
|
|
*lph = pmidc->hmidi;
|
|
|
|
return MMSYSERR_NOERROR;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
|
|
@doc INTERNAL MIDI
|
|
|
|
@func MMRESULT | mseReleaseOpenHandle | Release a handle from the cache.
|
|
|
|
@parm HMIDI | hmidi | The handle to release.
|
|
|
|
@comm
|
|
Search for the handle in cache.
|
|
Decrement the handle reference count. If it's zero,
|
|
close the handle for real.
|
|
|
|
****************************************************************************/
|
|
MMRESULT NEAR PASCAL mseReleaseOpenHandle(
|
|
HMIDI hmidi)
|
|
{
|
|
PMIDCACHE pmidc;
|
|
MMRESULT mmrc;
|
|
|
|
for (pmidc = gpMIdCache; pmidc; pmidc = pmidc->pNext)
|
|
if (pmidc->hmidi == hmidi)
|
|
break;
|
|
|
|
if (NULL == pmidc)
|
|
{
|
|
dprintf1(( "mROH_Err: Told to free a handle not in cache!!!"));
|
|
return MMSYSERR_INVALHANDLE;
|
|
}
|
|
|
|
if (0 == --pmidc->cRef)
|
|
{
|
|
mmrc = midiOutClose((HMIDIOUT)pmidc->hmidi);
|
|
|
|
if (MMSYSERR_NOERROR != mmrc)
|
|
dprintf1(( "mROH_Err: midiXXXClose returned %u", mmrc));
|
|
|
|
pmidc->hmidi = NULL;
|
|
|
|
return mmrc;
|
|
}
|
|
|
|
return MMSYSERR_NOERROR;
|
|
}
|
|
#endif // ALLOW_MULTIPLE_OPENS
|
|
|
|
MMRESULT FAR PASCAL mseOutReset(
|
|
PMIDIEMU pme)
|
|
{
|
|
LPMIDIHDR lpmh;
|
|
LPMIDIHDR lpmhWork;
|
|
UINT idx;
|
|
MSG msg;
|
|
|
|
// If we have anything posted to mmtask to be cleaned up, process
|
|
// it first
|
|
//
|
|
while (pme->cPostedBuffers)
|
|
{
|
|
Sleep(0);
|
|
}
|
|
|
|
//
|
|
// If we're running the timer, interrupt and force a reschedule
|
|
// of all remaining channels.
|
|
//
|
|
if (guMIDITimerID != TIMER_OFF)
|
|
{
|
|
dprintf2(( "mOR: About to take %u", guMIDITimerID));
|
|
if (MMSYSERR_NOERROR != timeKillEvent(guMIDITimerID))
|
|
{
|
|
dprintf1(( "timeKillEvent() failed in midiOutPolyMsg"));
|
|
}
|
|
else
|
|
{
|
|
guMIDITimerID = TIMER_OFF;
|
|
}
|
|
|
|
midiOutTimerTick(
|
|
guMIDITimerID, // ID of our timer
|
|
0, // wMsg is unused
|
|
timeGetTime(), // dwUser unused
|
|
0L, // dw1 unused
|
|
0L); // dw2 unused
|
|
dprintf2(( "mOR: mOTT"));
|
|
|
|
if (gfMinPeriod)
|
|
{
|
|
gfMinPeriod = FALSE;
|
|
timeEndPeriod(guMIDIPeriodMin);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Kill anything queued for midiOutPolyMsg. This will ensure that
|
|
// sending will stop after the current buffer.
|
|
//
|
|
PDEVLOCK( pme );
|
|
lpmh = pme->lpmhFront;
|
|
pme->lpmhFront = NULL;
|
|
pme->lpmhRear = NULL;
|
|
pme->dwPolyMsgState = PM_STATE_EMPTY;
|
|
|
|
while (lpmh)
|
|
{
|
|
lpmh->dwFlags &= ~MHDR_INQUEUE;
|
|
lpmh->dwFlags |= MHDR_DONE;
|
|
lpmhWork = lpmh->lpNext;
|
|
|
|
dprintf2(( "mOR: Next buffer to nuke %08lx", lpmhWork));
|
|
|
|
midiOutNukePMBuffer(pme, lpmh);
|
|
|
|
lpmh = lpmhWork;
|
|
}
|
|
|
|
//
|
|
// Check to see if our pme structure is still valid. Someone
|
|
// might have called midiStreamClose in their callback and we
|
|
// don't want to touch it after it's closed and freed. This
|
|
// is what the MidiPlyr sample application does.
|
|
//
|
|
try
|
|
{
|
|
if (MSE_SIGNATURE != pme->dwSignature) // must have been freed
|
|
return MMSYSERR_NOERROR;
|
|
|
|
PDEVUNLOCK( pme ); // keep it in try for extra protection
|
|
}
|
|
except(EXCEPTION_EXECUTE_HANDLER)
|
|
{
|
|
return MMSYSERR_NOERROR;
|
|
}
|
|
|
|
//
|
|
// We've just reset the stream; restart the tick clock at 0 and invalidate
|
|
// the time division to force the time stuff to be reset when the next
|
|
// polymsg comes in.
|
|
//
|
|
dprintf2(( "midiOutReset: clockInit()/ midiOutSetClockRate()"));
|
|
clockInit(&pme->clock, 0, 0, mseTimebase);
|
|
midiOutSetClockRate(pme, 0);
|
|
|
|
pme->tkPlayed = 0;
|
|
|
|
// Have a reset party on all the drivers under us
|
|
//
|
|
for (idx = 0; idx < pme->chMidi; idx++)
|
|
midiOutReset((HMIDIOUT)pme->rIds[idx].hMidi);
|
|
|
|
pme->dwPolyMsgState = PM_STATE_PAUSED;
|
|
|
|
return MMSYSERR_NOERROR;
|
|
}
|
|
|
|
MMRESULT FAR PASCAL mseOutStop(
|
|
PMIDIEMU pme)
|
|
{
|
|
LPMIDIHDR lpmh;
|
|
LPMIDIHDR lpmhWork;
|
|
MSG msg;
|
|
BOOL fSetEvent = FALSE;
|
|
|
|
// If we have anything posted to mmtask to be cleaned up, process
|
|
// it first
|
|
//
|
|
while (pme->cPostedBuffers)
|
|
{
|
|
Sleep(0);
|
|
}
|
|
|
|
//
|
|
// If we're running the timer, interrupt and force a reschedule
|
|
// of all remaining channels.
|
|
//
|
|
if (guMIDITimerID != TIMER_OFF)
|
|
{
|
|
dprintf2(( "mOS: About to take %u", guMIDITimerID));
|
|
if (MMSYSERR_NOERROR != timeKillEvent(guMIDITimerID))
|
|
{
|
|
dprintf1(( "timeKillEvent() failed in midiOutPolyMsg"));
|
|
}
|
|
else
|
|
{
|
|
guMIDITimerID = TIMER_OFF;
|
|
}
|
|
|
|
dprintf2(( "mOS: take -- About to mOTT"));
|
|
|
|
midiOutTimerTick(
|
|
guMIDITimerID, // ID of our timer
|
|
0, // wMsg is unused
|
|
timeGetTime(), // dwUser unused
|
|
0L, // dw1 unused
|
|
0L); // dw2 unused
|
|
|
|
dprintf2(( "mOS: mOTT"));
|
|
|
|
if (gfMinPeriod)
|
|
{
|
|
gfMinPeriod = FALSE;
|
|
timeEndPeriod(guMIDIPeriodMin);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Kill anything queued for midiOutPolyMsg. This will ensure that
|
|
// sending will stop after the current buffer.
|
|
//
|
|
PDEVLOCK( pme );
|
|
lpmh = pme->lpmhFront;
|
|
pme->lpmhFront = NULL;
|
|
pme->lpmhRear = NULL;
|
|
pme->dwPolyMsgState = PM_STATE_EMPTY;
|
|
|
|
while (lpmh)
|
|
{
|
|
lpmh->dwFlags &= ~MHDR_INQUEUE;
|
|
lpmh->dwFlags |= MHDR_DONE;
|
|
lpmhWork = lpmh->lpNext;
|
|
|
|
dprintf2(( "mOS: Next buffer to nuke %08lx", lpmhWork));
|
|
|
|
midiOutNukePMBuffer(pme, lpmh);
|
|
|
|
lpmh = lpmhWork;
|
|
}
|
|
|
|
//
|
|
// Check to see if our pme structure is still valid. Someone
|
|
// might have called midiStreamClose in their callback and we
|
|
// don't want to touch it after it's closed and freed. This
|
|
// is what the MidiPlyr sample application does.
|
|
//
|
|
try
|
|
{
|
|
if (MSE_SIGNATURE != pme->dwSignature) // must have been freed
|
|
return MMSYSERR_NOERROR;
|
|
|
|
PDEVUNLOCK( pme ); // keep it in try for extra protection
|
|
}
|
|
except(EXCEPTION_EXECUTE_HANDLER)
|
|
{
|
|
return MMSYSERR_NOERROR;
|
|
}
|
|
|
|
//
|
|
// We've just reset the stream; restart the tick clock at 0 and invalidate
|
|
// the time division to force the time stuff to be reset when the next
|
|
// polymsg comes in.
|
|
//
|
|
|
|
dprintf2(( "midiOutStop: clockInit()/ midiOutSetClockRate()"));
|
|
clockInit(&pme->clock, 0, 0, mseTimebase);
|
|
midiOutSetClockRate(pme, 0);
|
|
|
|
pme->tkPlayed = 0;
|
|
|
|
//
|
|
// In case someone queues up headers during the stop
|
|
// operation we want to make sure that all they have to
|
|
// do is restart the stream to get started again.
|
|
//
|
|
mseOutPause(pme);
|
|
|
|
//midiOutAllNotesOff(pme);
|
|
|
|
//pme->dwPolyMsgState = PM_STATE_STOPPED;
|
|
|
|
return MMSYSERR_NOERROR;
|
|
}
|
|
|
|
MMRESULT FAR PASCAL mseOutPause(
|
|
PMIDIEMU pme)
|
|
{
|
|
//
|
|
// Emulating on this handle - do the pause ourselves.
|
|
//
|
|
if (pme->dwPolyMsgState == PM_STATE_PAUSED)
|
|
return MMSYSERR_NOERROR;
|
|
|
|
pme->dwSavedState = pme->dwPolyMsgState;
|
|
pme->dwPolyMsgState = PM_STATE_PAUSED;
|
|
|
|
clockPause(&pme->clock, CLK_TK_NOW);
|
|
|
|
midiOutAllNotesOff(pme);
|
|
|
|
return MMSYSERR_NOERROR;
|
|
}
|
|
|
|
MMRESULT FAR PASCAL mseOutRestart(
|
|
PMIDIEMU pme,
|
|
DWORD msTime,
|
|
DWORD tkTime)
|
|
{
|
|
//
|
|
// Emulating on this handle - do the pause ourselves.
|
|
//
|
|
if (pme->dwPolyMsgState != PM_STATE_PAUSED)
|
|
return MMSYSERR_NOERROR;
|
|
|
|
pme->dwPolyMsgState = pme->dwSavedState;
|
|
|
|
clockRestart(&pme->clock, tkTime, msTime);
|
|
|
|
dprintf2(( "restart: state->%lu", pme->dwPolyMsgState));
|
|
|
|
midiOutTimerTick(
|
|
guMIDITimerID, // ID of our timer
|
|
0, // wMsg is unused
|
|
timeGetTime(),
|
|
0L, // dw1 unused
|
|
0L); // dw2 unused
|
|
|
|
return MMSYSERR_NOERROR;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
* @doc INTERNAL MIDI
|
|
*
|
|
* @api void | midiEmulatorInit | This function is called at init time to
|
|
* allow MMSYSTEM to initialize anything it needs to for the polymsg
|
|
* emulators. Right now, all we do is find the minimum period of the
|
|
* timeGetTime clock.
|
|
*
|
|
* @rdesc Currently always returns MMSYSERR_NOERROR.
|
|
****************************************************************************/
|
|
|
|
#ifdef DEBUG
|
|
STATIC SZCODE aszInit[] = "midiEmulatorInit: Using clock res of %lums.";
|
|
#endif
|
|
|
|
void NEAR PASCAL midiEmulatorInit
|
|
(
|
|
void
|
|
)
|
|
{
|
|
TIMECAPS tc;
|
|
|
|
if (MMSYSERR_NOERROR != timeGetDevCaps(&tc, sizeof(tc)))
|
|
{
|
|
dprintf1(( "*** MMSYSTEM IS HORKED ***"));
|
|
dprintf1(( "*** timeGetDevCaps failed in midiEmulatorInit ***"));
|
|
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Select the larger of the period we would like to have or
|
|
// the minimum period the timer supports.
|
|
//
|
|
guMIDIPeriodMin = max(MIN_PERIOD, tc.wPeriodMin);
|
|
|
|
// guMIDIPeriodMin = MIN_PERIOD;
|
|
|
|
#ifdef DEBUG
|
|
dprintf2(( aszInit, (DWORD)guMIDIPeriodMin));
|
|
#endif
|
|
}
|
|
|
|
/*****************************************************************************
|
|
* @doc EXTERNAL MIDI M4
|
|
*
|
|
* @api UINT | mseOutSend | Plays or queues a buffer of
|
|
* MIDI data to a MIDI output device.
|
|
*
|
|
* @parm PMIDIEMU | pme | Specifies the stream instance the data should
|
|
* go to.
|
|
*
|
|
* @parm LPMIDIHDR | lpMidiOutHdr | Specifies a far pointer to a <t MIDIHDR>
|
|
* structure that identifies the MIDI data buffer.
|
|
*
|
|
* @parm UINT | cbMidiHdr | Specifies the size of the <t MIDIHDR> structure.
|
|
*
|
|
* @rdesc The return value is zero if the function is successful. Otherwise,
|
|
* it returns an error number. Possible error values include the following:
|
|
*
|
|
* @flag MMSYSERR_INVALHANDLE | The specified device handle is invalid.
|
|
* @flag MMSYSERR_INVALPARAM | The value of <p lpMidiOutHdr> is invalid.
|
|
* @flag MIDIERR_UNPREPARED | The output buffer header <p lpMidiOutHdr> has
|
|
* not been prepared.
|
|
* @flag MIDIERR_STILLPLAYING | <p lpMidiOutHdr> is still playing or
|
|
* queued from a previous call to <f midiOutPolyMsg>.
|
|
*
|
|
* @comm The polymessage buffer contains one or more MIDI messages. Entries in the
|
|
* buffer can be of the following three types:
|
|
*
|
|
* @flag Short Message | Is two DWORDs. One contains time data, the other
|
|
* contains message content. Time information is the time to wait between the
|
|
* previous event and the event being described. Time units are based on the
|
|
* time-division header in the MIDI file.
|
|
*
|
|
* Message content for short messages occupy the 24 least-significant bits of
|
|
* the DWORD; the high-order byte contains a zero.
|
|
*
|
|
* @flag System Message | Is a multiple of two DWORDs. The first DWORD contains
|
|
* time information that specifies the amount of time to wait between the
|
|
* previous event and the event being described. Time units are based on the
|
|
* time-division header in the MIDI file.
|
|
*
|
|
* The second DWORD contains the length of the system-message data (SysEx) in
|
|
* the 24 least-significant bits of the DWORD; the high-order bit contains
|
|
* a one.
|
|
*
|
|
* Remaining DWORDs in the system message contain SysEx data.
|
|
*
|
|
* @flag End-of-Buffer | Is two DWORDs, each with the value -1. This entry
|
|
* indicates the end of data in the poly-message buffer. This message is not passed
|
|
* to MIDI devices.
|
|
*
|
|
* @comm This function cannot be called at interrupt time.
|
|
*
|
|
* @xref <f midiOutLongMsg> <f midiOutPrepareHeader>
|
|
****************************************************************************/
|
|
|
|
#define ERROR_EXIT(x) \
|
|
{ \
|
|
uRet = (x); \
|
|
goto CLEANUP; \
|
|
}
|
|
|
|
#define SKIP_BYTES(x,s) \
|
|
{ \
|
|
if (dwLength < (x)) \
|
|
{ \
|
|
dprintf1(( "!midiOutPolyMsg: ran off end of polymsg buffer in parse!\r\n%ls\r\nOffset %lu", (LPSTR)(s), (DWORD)(((LPBYTE)lpdwBuffer) - lpMidiHdr->lpData))); \
|
|
uRet = MMSYSERR_INVALPARAM; \
|
|
goto CLEANUP; \
|
|
} \
|
|
((LPBYTE)lpdwBuffer) += (x); \
|
|
dwLength -= (x); \
|
|
}
|
|
|
|
MMRESULT FAR PASCAL mseOutSend(
|
|
PMIDIEMU pme,
|
|
LPMIDIHDR lpMidiHdr,
|
|
UINT cbMidiHdr)
|
|
{
|
|
UINT uRet = MMSYSERR_NOERROR;
|
|
UINT idx;
|
|
LPDWORD lpdwBuffer;
|
|
DWORD dwLength;
|
|
LPMIDIHDR lpmhWork;
|
|
LPMIDIHDREXT lpExt;
|
|
BOOL fQueueWasEmpty;
|
|
BYTE bEvent;
|
|
DWORD dwParm;
|
|
DWORD dwStreamID;
|
|
HMIDIOUT hmo;
|
|
DWORD dwBase;
|
|
UINT cNewHeaders;
|
|
|
|
dprintf2(( "mseOutSend pme %04X lpmh %08lX", (WORD)pme, (DWORD)lpMidiHdr));
|
|
|
|
dwBase = lpMidiHdr->reserved;
|
|
|
|
if ((lpExt = winmmAlloc(sizeof(MIDIHDREXT))) == NULL)
|
|
{
|
|
dprintf1(( "midiOutPolyMsg: No room for shadow"));
|
|
ERROR_EXIT(MMSYSERR_NOMEM);
|
|
}
|
|
|
|
//
|
|
// This needs to be done ASAP in case we error out.
|
|
//
|
|
lpMidiHdr->reserved = (DWORD)(lpExt);
|
|
lpMidiHdr->dwReserved[MH_BUFIDX] = 0;
|
|
|
|
lpExt->nHeaders = 0;
|
|
lpExt->lpmidihdr = (LPMIDIHDR)(lpExt+1);
|
|
|
|
//
|
|
// Parse the poly msg buffer and see if there are any long msgs.
|
|
// If there are, allocate MIDIHDR's for them on the end of the
|
|
// main MIDIHDR extension and fill them in and prepare them.
|
|
//
|
|
lpdwBuffer = (LPDWORD)lpMidiHdr->lpData;
|
|
dwLength = lpMidiHdr->dwBytesRecorded;
|
|
|
|
while (dwLength)
|
|
{
|
|
//
|
|
// Skip over the delta time stamp
|
|
//
|
|
SKIP_BYTES(sizeof(DWORD), "d-time");
|
|
dwStreamID = *lpdwBuffer;
|
|
SKIP_BYTES(sizeof(DWORD), "stream-id");
|
|
|
|
//
|
|
// Extract the event type and parameter and skip the event DWORD
|
|
//
|
|
bEvent = MEVT_EVENTTYPE(*lpdwBuffer) & (BYTE)~(MEVT_F_CALLBACK >> 24);
|
|
dwParm = MEVT_EVENTPARM(*lpdwBuffer);
|
|
SKIP_BYTES(sizeof(DWORD), "event");
|
|
|
|
if (bEvent == MEVT_LONGMSG)
|
|
{
|
|
LPMIDIHDREXT lpExtRealloc;
|
|
|
|
if (dwParm > dwLength)
|
|
{
|
|
dprintf1(( "parse: I don't like stuff that sucks!"));
|
|
ERROR_EXIT(MMSYSERR_INVALPARAM);
|
|
}
|
|
|
|
cNewHeaders = 1;
|
|
if (dwStreamID == (DWORD)-1L)
|
|
cNewHeaders = pme->chMidi;
|
|
|
|
lpExt->nHeaders += cNewHeaders;
|
|
|
|
if ((lpExtRealloc = (LPMIDIHDREXT)HeapReAlloc(hHeap,
|
|
HEAP_ZERO_MEMORY, lpExt,
|
|
sizeof(MIDIHDREXT)+sizeof(MIDIHDR)*lpExt->nHeaders))
|
|
== NULL)
|
|
{
|
|
lpExt->nHeaders -= cNewHeaders;
|
|
ERROR_EXIT(MMSYSERR_NOMEM);
|
|
}
|
|
|
|
lpExt = lpExtRealloc;
|
|
lpMidiHdr->reserved = (DWORD)(lpExt);
|
|
|
|
lpmhWork = ((LPMIDIHDR)(lpExt+1)) + lpExt->nHeaders - cNewHeaders;
|
|
|
|
while (cNewHeaders--)
|
|
{
|
|
lpmhWork->lpData = (LPSTR)lpdwBuffer;
|
|
lpmhWork->dwBufferLength = dwParm;
|
|
lpmhWork->dwBytesRecorded = 0;
|
|
lpmhWork->dwUser = 0;
|
|
lpmhWork->dwFlags =
|
|
(lpMidiHdr->dwFlags & MHDR_MAPPED) | MHDR_SHADOWHDR;
|
|
|
|
if (dwStreamID == (DWORD)-1L)
|
|
lpmhWork->dwReserved[MH_STREAM] = cNewHeaders;
|
|
else
|
|
lpmhWork->dwReserved[MH_STREAM] = dwStreamID;
|
|
|
|
lpmhWork->dwReserved[MH_STRMPME] = (DWORD)(UINT)pme;
|
|
++lpmhWork;
|
|
}
|
|
dwParm = (dwParm+3)&~3;
|
|
SKIP_BYTES(dwParm, "longmsg parm");
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Skip any additional paramters for other length-class messages
|
|
//
|
|
if (bEvent & (MEVT_F_LONG >> 24))
|
|
{
|
|
dwParm = (dwParm+3)&~3;
|
|
// dprintf1(( "Length [%lu] rounded [%lu]", dwParm, (dwParm+3)&~3));
|
|
SKIP_BYTES(dwParm, "generic long event data");
|
|
}
|
|
}
|
|
}
|
|
|
|
// Now prepare any headers we allocated
|
|
//
|
|
lpmhWork = (LPMIDIHDR)(lpExt+1);
|
|
for (idx = 0; idx < lpExt->nHeaders; idx++, lpmhWork++)
|
|
{
|
|
hmo = (HMIDIOUT)mseIDtoHMidi(pme, lpmhWork->dwReserved[MH_STREAM]);
|
|
if (NULL != hmo)
|
|
{
|
|
if ((uRet = midiOutPrepareHeader(hmo,
|
|
lpmhWork,
|
|
sizeof(MIDIHDR))) != MMSYSERR_NOERROR)
|
|
{
|
|
dprintf1(( "parse: pre-prepare of embedded long msg failed! (%lu)", (DWORD)uRet));
|
|
ERROR_EXIT(uRet);
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Reset lpExt->lpmidihdr to the next header to play
|
|
//
|
|
lpExt->lpmidihdr = (LPMIDIHDR)(lpExt+1);
|
|
|
|
//
|
|
// Prepare to update handle information to contain this header
|
|
//
|
|
PDEVLOCK( pme );
|
|
|
|
//
|
|
// Shove the block in the queue, noting if it was empty
|
|
//
|
|
|
|
fQueueWasEmpty = FALSE;
|
|
if (pme->lpmhRear == NULL)
|
|
{
|
|
fQueueWasEmpty = TRUE;
|
|
pme->lpmhRear = pme->lpmhFront = lpMidiHdr;
|
|
}
|
|
else
|
|
{
|
|
pme->lpmhRear->lpNext = lpMidiHdr;
|
|
pme->lpmhRear = lpMidiHdr;
|
|
}
|
|
|
|
lpMidiHdr->lpNext = NULL;
|
|
lpMidiHdr->dwFlags |= MHDR_INQUEUE;
|
|
|
|
PDEVUNLOCK( pme );
|
|
|
|
if (pme->dwPolyMsgState == PM_STATE_PAUSED)
|
|
{
|
|
if (fQueueWasEmpty)
|
|
pme->dwSavedState = PM_STATE_READY;
|
|
}
|
|
else
|
|
{
|
|
if (fQueueWasEmpty)
|
|
{
|
|
// We want to schedule this now. If the there's no timer
|
|
// or we can kill the current one, send. If we can't kill the
|
|
// pending timer, it's in the process of being scheduled anyway
|
|
//
|
|
if (guMIDITimerID == TIMER_OFF ||
|
|
MMSYSERR_NOERROR == timeKillEvent(guMIDITimerID))
|
|
{
|
|
guMIDITimerID = TIMER_OFF;
|
|
pme->dwPolyMsgState = PM_STATE_READY;
|
|
|
|
dprintf2(( "mseSend take -- about to mot"));
|
|
|
|
midiOutTimerTick(
|
|
guMIDITimerID, // ID of our timer
|
|
0, // wMsg is unused
|
|
timeGetTime(), // dwUser unused
|
|
0L, // dw1 unused
|
|
0L); // dw2 unused
|
|
|
|
dprintf2(( "mseSend mot"));
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
CLEANUP:
|
|
if (uRet != MMSYSERR_NOERROR)
|
|
{
|
|
if (lpExt != NULL)
|
|
{
|
|
lpMidiHdr = (LPMIDIHDR)(lpExt+1);
|
|
while (lpExt->nHeaders--)
|
|
{
|
|
hmo = (HMIDIOUT)mseIDtoHMidi(pme, lpMidiHdr->dwReserved[MH_STREAM]);
|
|
#ifdef DEBUG
|
|
if (NULL == hmo)
|
|
dprintf1(( "stream-id disappeared during cleanup!!!"));
|
|
#endif
|
|
midiOutUnprepareHeader(hmo, lpMidiHdr++, sizeof(MIDIHDR));
|
|
}
|
|
|
|
winmmFree(lpExt);
|
|
}
|
|
}
|
|
|
|
return uRet;
|
|
|
|
} /* midiOutPolyMsg() */
|
|
|
|
/** void FAR PASCAL midiOutSetClockRate(PMIDIEMU pme, TICKS tkWhen)
|
|
*
|
|
* DESCRIPTION:
|
|
*
|
|
* This function is called whenever the clock rate for the stream
|
|
* needs to be changed.
|
|
*
|
|
* ARGUMENTS:
|
|
* (PMIDIEMU pme, TICKS tkWhen)
|
|
*
|
|
* pme indicates the handle to change the clock rate of.
|
|
*
|
|
* tkWhen is the absolute tick time at which the time change occurs.
|
|
*
|
|
** jfg */
|
|
|
|
|
|
void FAR PASCAL midiOutSetClockRate(
|
|
PMIDIEMU pme,
|
|
TICKS tkWhen)
|
|
{
|
|
DWORD dwNum;
|
|
DWORD dwDenom;
|
|
|
|
|
|
if (pme->dwTimeDiv&IS_SMPTE)
|
|
{
|
|
switch(-SMPTE_FORMAT(pme->dwTimeDiv))
|
|
{
|
|
case SMPTE_24:
|
|
dwNum = 24L;
|
|
dwDenom = 1L;
|
|
break;
|
|
|
|
case SMPTE_25:
|
|
dwNum = 25L;
|
|
dwDenom = 1L;
|
|
break;
|
|
|
|
case SMPTE_30DROP:
|
|
case SMPTE_30:
|
|
//
|
|
// Actual frame rate for 30 fps (color television) is
|
|
// 29.97 fps.
|
|
//
|
|
dwNum = 2997L;
|
|
dwDenom = 100L;
|
|
break;
|
|
|
|
default:
|
|
dprintf1(( "Invalid SMPTE frames/sec in midiOutSetClockRate! (using 30)"));
|
|
dwNum = 2997L;
|
|
dwDenom = 100L;
|
|
break;
|
|
}
|
|
|
|
dwNum *= (DWORD)TICKS_PER_FRAME(pme->dwTimeDiv);
|
|
dwDenom *= 1000L;
|
|
}
|
|
else
|
|
{
|
|
dwNum = 1000L * TICKS_PER_QN(pme->dwTimeDiv);
|
|
dwDenom = pme->dwTempo;
|
|
}
|
|
|
|
clockSetRate(&pme->clock, tkWhen, dwNum, dwDenom);
|
|
}
|
|
|
|
/** BOOL NEAR PASCAL midiOutScheduleNextEvent(PMIDIEMU pme)
|
|
*
|
|
* DESCRIPTION:
|
|
*
|
|
* Determine when (in ticks defined for this device) the next event
|
|
* is due.
|
|
*
|
|
* ARGUMENTS:
|
|
* (PMIDIEMU pme)
|
|
*
|
|
* RETURN (BOOL):
|
|
*
|
|
* TRUE if there was an event in this buffer to schedule.
|
|
*
|
|
* NOTES:
|
|
*
|
|
* Just calculate how many ticks till next event and store in the
|
|
* device struct.
|
|
*
|
|
* This function does NOT schedule across buffers; caller must
|
|
* link to next buffer if needed.
|
|
*
|
|
** jfg */
|
|
|
|
BOOL NEAR PASCAL midiOutScheduleNextEvent(
|
|
PMIDIEMU pme)
|
|
{
|
|
LPMIDIHDR lpmhdr;
|
|
LPBYTE lpb;
|
|
DWORD tkDelta;
|
|
|
|
if ((lpmhdr = pme->lpmhFront) == NULL ||
|
|
lpmhdr->dwReserved[MH_BUFIDX] == lpmhdr->dwBytesRecorded)
|
|
{
|
|
pme->dwPolyMsgState = PM_STATE_EMPTY;
|
|
return FALSE;
|
|
}
|
|
|
|
lpb = (LPBYTE)lpmhdr->lpData;
|
|
tkDelta = *(LPDWORD)(lpb+lpmhdr->dwReserved[MH_BUFIDX]);
|
|
|
|
pme->tkNextEventDue = pme->tkPlayed + tkDelta;
|
|
pme->dwPolyMsgState = PM_STATE_READY;
|
|
|
|
return TRUE;
|
|
} /* ScheduleNextEvent() */
|
|
|
|
|
|
/** void NEAR PASCAL midiOutPlayNextPolyEvent(PMIDIEMU pme)
|
|
*
|
|
* DESCRIPTION:
|
|
*
|
|
* Play the next event if there is one. Current buffer must
|
|
* be pointing at an event (*NOT* end-of-buffer).
|
|
*
|
|
* - Plays all events which are due
|
|
*
|
|
* - Schedules next event
|
|
*
|
|
* ARGUMENTS:
|
|
* (PMIDIEMU pme)
|
|
*
|
|
* NOTES:
|
|
*
|
|
* First, play the event. If it's a short msg, just do it.
|
|
* If it's a SysEx, pull the appropriate (already prepared)
|
|
* header from the extension block and send it. Mark the state
|
|
* of the device as blocked so nothing else will be played
|
|
* until the SysEx is done.
|
|
*
|
|
* Update dwReserved[MH_BUFIDX] to point at the next event.
|
|
*
|
|
* Determine the next event and schedule it, crossing to the
|
|
* next buffer if needed. If the next event is already due
|
|
* (i.e. had a delta-time of zero), stick around and send that,
|
|
* too.
|
|
*
|
|
*
|
|
*
|
|
** jfg */
|
|
|
|
void NEAR PASCAL midiOutPlayNextPolyEvent(
|
|
PMIDIEMU pme
|
|
#ifdef DEBUG
|
|
,DWORD dwStartTime
|
|
#endif
|
|
)
|
|
{
|
|
LPBYTE lpb;
|
|
LPMIDIHDR lpmhdr;
|
|
DWORD dwMsg;
|
|
LPMIDIHDREXT lpExt;
|
|
MMRESULT mmrError;
|
|
DWORD tkDelta;
|
|
BYTE bEvent;
|
|
DWORD dwOffset;
|
|
DWORD dwStreamID;
|
|
HMIDIOUT hmo;
|
|
UINT cToSend;
|
|
|
|
#if 0
|
|
if (NULL != pme->lpmhFront)
|
|
{
|
|
lpb = (LPBYTE)(pme->lpmhFront->lpData);
|
|
_asm
|
|
{
|
|
mov ax, word ptr lpb
|
|
mov dx, word ptr lpb+2
|
|
int 3
|
|
}
|
|
}
|
|
#endif
|
|
|
|
while (pme->dwPolyMsgState == PM_STATE_READY)
|
|
{
|
|
for(;;)
|
|
{
|
|
lpmhdr = pme->lpmhFront;
|
|
if (!lpmhdr)
|
|
return;
|
|
|
|
// Make sure next buffer contains valid data and skip if it
|
|
// doesn't
|
|
//
|
|
if (midiOutScheduleNextEvent(pme))
|
|
break;
|
|
|
|
// That buffer is done or empty
|
|
//
|
|
midiOutDequeueAndCallback(pme);
|
|
}
|
|
|
|
lpb = lpmhdr->lpData;
|
|
tkDelta = *(LPDWORD)(lpb+lpmhdr->dwReserved[MH_BUFIDX]);
|
|
|
|
// dprintf2(( "dwReserved[MH_BUFIDX] %lu tkDelta %lu", lpmhdr->dwReserved[0], tkDelta));
|
|
|
|
pme->tkNextEventDue = pme->tkPlayed + tkDelta;
|
|
if (pme->tkNextEventDue > pme->tkTime)
|
|
{
|
|
return;
|
|
}
|
|
|
|
//
|
|
// There is an event pending and it's due; send it and update pointers
|
|
//
|
|
dwOffset = lpmhdr->dwReserved[MH_BUFIDX];
|
|
|
|
pme->tkPlayed += tkDelta;
|
|
|
|
// Skip tkDelta and stream-id
|
|
//
|
|
|
|
lpmhdr->dwReserved[MH_BUFIDX] += sizeof(DWORD);
|
|
dwStreamID = *(LPDWORD)(lpb+lpmhdr->dwReserved[MH_BUFIDX]);
|
|
lpmhdr->dwReserved[MH_BUFIDX] += sizeof(DWORD);
|
|
|
|
// Will be NULL if dwStreamID == -1 (all IDs)
|
|
//
|
|
hmo = (HMIDIOUT)mseIDtoHMidi(pme, dwStreamID);
|
|
|
|
//
|
|
// Extract event type and parms and update past event
|
|
//
|
|
dwMsg = *(LPDWORD)(lpb+lpmhdr->dwReserved[MH_BUFIDX]);
|
|
bEvent = MEVT_EVENTTYPE(dwMsg);
|
|
dwMsg = MEVT_EVENTPARM(dwMsg);
|
|
|
|
lpmhdr->dwReserved[MH_BUFIDX] += sizeof(DWORD);
|
|
|
|
if (hmo && (bEvent & (MEVT_F_CALLBACK >> 24)))
|
|
{
|
|
lpmhdr->dwOffset = dwOffset;
|
|
DriverCallback(
|
|
pme->dwCallback,
|
|
HIWORD(pme->dwFlags),
|
|
(HDRVR)pme->hStream,
|
|
MM_MOM_POSITIONCB,
|
|
pme->dwInstance,
|
|
(DWORD)lpmhdr,
|
|
0L);
|
|
|
|
}
|
|
|
|
bEvent &= ~(MEVT_F_CALLBACK >> 24);
|
|
|
|
switch(bEvent)
|
|
{
|
|
case MEVT_SHORTMSG:
|
|
{
|
|
BYTE bEvent;
|
|
BYTE bNote;
|
|
BYTE bVelocity;
|
|
LPBYTE pbEntry = pme->rbNoteOn;
|
|
|
|
if (NULL == hmo)
|
|
{
|
|
dprintf1(( "Event skipped - not ours"));
|
|
break;
|
|
}
|
|
|
|
//
|
|
// If we're sending a note on or note off, track note-on
|
|
// count.
|
|
//
|
|
bEvent = (BYTE)(dwMsg&0xFF);
|
|
|
|
if (!(bEvent & 0x80))
|
|
{
|
|
bEvent = pme->bRunningStatus;
|
|
bNote = (BYTE)(dwMsg&0xFF);
|
|
bVelocity = (BYTE)((dwMsg >> 8)&0xFF);
|
|
|
|
// ALWAYS expand running status - individual dev's can't
|
|
// track running status of entire stream.
|
|
//
|
|
dwMsg = (dwMsg << 8) | (DWORD)(bEvent);
|
|
}
|
|
else
|
|
{
|
|
pme->bRunningStatus = bEvent;
|
|
bNote = (BYTE)((dwMsg >> 8)&0xFF);
|
|
bVelocity = (BYTE)((dwMsg >> 16)&0xFF);
|
|
}
|
|
|
|
if ((bEvent&0xF0) == MIDI_NOTEON ||
|
|
(bEvent&0xF0) == MIDI_NOTEOFF)
|
|
{
|
|
BYTE bChannel = (bEvent & 0x0F);
|
|
UINT cbOffset = (bChannel * NUM_NOTES + bNote) / 2;
|
|
|
|
//
|
|
// Note-on with a velocity of 0 == note off
|
|
//
|
|
if ((bEvent&0xF0) == MIDI_NOTEOFF || bVelocity == 0)
|
|
{
|
|
if (bNote&0x01) // odd
|
|
{
|
|
if ((*(pbEntry + cbOffset)&0xF0) != 0)
|
|
*(pbEntry + cbOffset) -= 0x10;
|
|
}
|
|
else //even
|
|
{
|
|
if ((*(pbEntry + cbOffset)&0xF) != 0)
|
|
*(pbEntry + cbOffset) -= 0x01;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (bNote&0x01) // odd
|
|
{
|
|
if ((*(pbEntry + cbOffset)&0xF0) != 0xF0)
|
|
*(pbEntry + cbOffset) += 0x10;
|
|
}
|
|
else //even
|
|
{
|
|
if ((*(pbEntry + cbOffset)&0xF) != 0xF)
|
|
*(pbEntry + cbOffset) += 0x01;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
mmrError = (MMRESULT)midiMessage((HMIDI)hmo, MODM_DATA, dwMsg, 0L);
|
|
if (MMSYSERR_NOERROR != mmrError)
|
|
{
|
|
dprintf(("Short msg returned %08lX!!!", (DWORD)mmrError));
|
|
}
|
|
}
|
|
break;
|
|
|
|
case MEVT_TEMPO:
|
|
pme->dwTempo = dwMsg;
|
|
dprintf1(( "dwTempo %lu", pme->dwTempo));
|
|
midiOutSetClockRate((PMIDIEMU)pme, pme->tkPlayed);
|
|
break;
|
|
|
|
case MEVT_LONGMSG:
|
|
//
|
|
// Advance lpmhdr past the message; the header is already
|
|
// prepared with the proper address and length, so we set
|
|
// the polymsg header so that it points at the next message
|
|
// when this long msg completes.
|
|
//
|
|
// Keep low 24 bits of dwMsg (SysEx length, byte aligned),
|
|
// round to next DWORD (buffer must be padded to match this),
|
|
// and skip past dwMsg and the SysEx buffer.
|
|
//
|
|
dwMsg = (dwMsg+3)&~3;
|
|
|
|
lpmhdr->dwReserved[MH_BUFIDX] += dwMsg;
|
|
|
|
|
|
cToSend = 1;
|
|
if (dwStreamID == (DWORD)-1L)
|
|
cToSend = pme->chMidi;
|
|
|
|
lpExt = (LPMIDIHDREXT)lpmhdr->reserved;
|
|
|
|
pme->cSentLongMsgs = 0;
|
|
pme->dwPolyMsgState = PM_STATE_BLOCKED;
|
|
pme->fdwDev |= MDV_F_SENDING;
|
|
|
|
while (cToSend--)
|
|
{
|
|
lpmhdr = lpExt->lpmidihdr;
|
|
++lpExt->lpmidihdr;
|
|
|
|
hmo = (HMIDIOUT)mseIDtoHMidi(pme,
|
|
lpmhdr->dwReserved[MH_STREAM]);
|
|
|
|
|
|
mmrError = (MMRESULT)midiMessage(
|
|
(HMIDI)hmo,
|
|
MODM_LONGDATA,
|
|
(DWORD)lpmhdr,
|
|
(DWORD)sizeof(MIDIHDR));
|
|
|
|
if (MMSYSERR_NOERROR == mmrError)
|
|
++pme->cSentLongMsgs;
|
|
else
|
|
dprintf1(( "MODM_LONGDATA returned %u in emulator!",
|
|
(UINT)mmrError));
|
|
}
|
|
|
|
if (0 == pme->cSentLongMsgs)
|
|
pme->dwPolyMsgState = PM_STATE_READY;
|
|
pme->fdwDev &= ~MDV_F_SENDING;
|
|
|
|
break;
|
|
|
|
default:
|
|
//
|
|
// If we didn't understand a length-class message, skip it.
|
|
//
|
|
if (bEvent&(MEVT_F_LONG >> 24))
|
|
{
|
|
dwMsg = (dwMsg+3)&~3;
|
|
lpmhdr->dwReserved[MH_BUFIDX] += dwMsg;
|
|
}
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Find the next schedulable polyMsg
|
|
//
|
|
while (!midiOutScheduleNextEvent(pme))
|
|
{
|
|
midiOutDequeueAndCallback(pme);
|
|
if (pme->lpmhFront == NULL)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/** void NEAR PASCAL midiOutDequeueAndCallback(PMIDIEMU pme)
|
|
*
|
|
* DESCRIPTION:
|
|
*
|
|
* The current polymsg buffer has finished. Pull it off the queue
|
|
* and do a callback.
|
|
*
|
|
* ARGUMENTS:
|
|
* (PMIDIEMU pme)
|
|
*
|
|
* NOTES:
|
|
*
|
|
** jfg */
|
|
|
|
void NEAR PASCAL midiOutDequeueAndCallback(
|
|
PMIDIEMU pme)
|
|
{
|
|
LPMIDIHDR lpmidihdr;
|
|
|
|
dprintf2(( "DQ"));
|
|
//
|
|
// A polymsg buffer has finished. Pull it off the queue and
|
|
// call back the app.
|
|
//
|
|
if ((lpmidihdr = pme->lpmhFront) == NULL)
|
|
return;
|
|
|
|
if ((pme->lpmhFront = lpmidihdr->lpNext) == NULL)
|
|
{
|
|
dprintf2(( "DQ/CB -- last buffer"));
|
|
pme->lpmhRear = NULL;
|
|
}
|
|
|
|
//
|
|
// Can't be at interrupt callback time to unprepare possible
|
|
// embedded long messages in this thing. The notify window's
|
|
// wndproc will call midiOutNukePMBuffer to clean up.
|
|
//
|
|
dprintf2(( "!DQ/CB %08lX", (DWORD)lpmidihdr));
|
|
|
|
++pme->cPostedBuffers;
|
|
PostMessage(
|
|
hwndNotify,
|
|
MM_POLYMSGBUFRDONE,
|
|
(WPARAM)pme,
|
|
(DWORD)lpmidihdr);
|
|
|
|
}
|
|
|
|
void FAR PASCAL midiOutNukePMBuffer(
|
|
PMIDIEMU pme,
|
|
LPMIDIHDR lpmh)
|
|
{
|
|
LPMIDIHDREXT lpExt;
|
|
LPMIDIHDR lpmhWork;
|
|
MMRESULT mmrc;
|
|
HMIDIOUT hmo;
|
|
|
|
dprintf2(( "Nuke %08lX", (DWORD)lpmh));
|
|
|
|
//
|
|
// Unprepare internal stuff and do user callback
|
|
//
|
|
lpExt = (LPMIDIHDREXT)(lpmh->reserved);
|
|
lpmhWork = (LPMIDIHDR)(lpExt+1);
|
|
|
|
while (lpExt->nHeaders--)
|
|
{
|
|
if ((lpmhWork->dwFlags&MHDR_PREPARED) &&
|
|
(!(lpmhWork->dwFlags&MHDR_INQUEUE)))
|
|
{
|
|
hmo = (HMIDIOUT)mseIDtoHMidi(pme, lpmhWork->dwReserved[MH_STREAM]);
|
|
mmrc = midiOutUnprepareHeader(hmo, lpmhWork, sizeof(*lpmhWork));
|
|
#ifdef DEBUG
|
|
if (MMSYSERR_NOERROR != mmrc)
|
|
{
|
|
dprintf1(( "midiOutNukePMBuffer: Could not unprepare! (%lu)", (DWORD)mmrc));
|
|
}
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
dprintf1(( "midiOutNukePMBuffer: Emulation header flags bogus!!!"));
|
|
}
|
|
|
|
lpmhWork++;
|
|
}
|
|
|
|
winmmFree(lpExt);
|
|
lpmh->reserved = 0L;
|
|
|
|
lpmh->dwFlags &= ~MHDR_INQUEUE;
|
|
lpmh->dwFlags |= MHDR_DONE;
|
|
|
|
// dprintf2(( "Nuke: callback"));
|
|
|
|
DriverCallback(
|
|
pme->dwCallback,
|
|
HIWORD(pme->dwFlags),
|
|
(HDRVR)pme->hStream,
|
|
MM_MOM_DONE,
|
|
pme->dwInstance,
|
|
(DWORD)lpmh,
|
|
0L);
|
|
}
|
|
|
|
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* @doc INTERNAL MIDI
|
|
*
|
|
* @api void | midiOutTimerTick |
|
|
* This function handles the timing of polymsg out buffers. One timer instance
|
|
* is shared by all polymsg out streams. When <f midiOutPolyMsg> is called
|
|
* and the timer is not running, or <f midiOutTimerTick> finished processing,
|
|
* the timer is set to go off based on the time until the event with the
|
|
* shortest time remaining of all events. All timers are one-shot timers.
|
|
*
|
|
* @parm UINT | uTimerID |
|
|
* The timer ID of the timer that fired.
|
|
*
|
|
* @parm UINT | wMsg |
|
|
* Unused.
|
|
*
|
|
* @parm DWORD | dwUser |
|
|
* User instance data for the timer callback (unused).
|
|
*
|
|
* @parm DWORD | dwParam1 |
|
|
* Unused.
|
|
*
|
|
* @parm DWORD | dwParam2 |
|
|
* Unused.
|
|
*
|
|
* @comm Determine elapsed microseconds using <f timeGetTime>.
|
|
*
|
|
* Traverse the list of output handles. Update the tick clock for each handle. If there are
|
|
* events to do on that handle, start them.
|
|
*
|
|
* Determine the next event due on any stream. Start another one-shot timer
|
|
* to call <f midiOutTimerTick> when this interval has expired.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
STATIC UINT uTimesIn = 0;
|
|
|
|
void CALLBACK midiOutTimerTick(
|
|
UINT uTimerID,
|
|
UINT wMsg,
|
|
DWORD dwUser,
|
|
DWORD dw1,
|
|
DWORD dw2)
|
|
{
|
|
PMIDIEMU pme;
|
|
DWORD msNextEventMin = (DWORD)-1L;
|
|
DWORD msNextEvent;
|
|
UINT uDelay;
|
|
#ifdef DEBUG
|
|
DWORD dwNow = timeGetTime();
|
|
#endif
|
|
|
|
if (guMIDIInTimer)
|
|
{
|
|
dprintf2(( "midiOutTimerTick() re-entered (%u)", guMIDIInTimer));
|
|
return;
|
|
}
|
|
|
|
guMIDIInTimer++;
|
|
|
|
#ifdef DEBUG
|
|
{
|
|
DWORD dwDelta = dwNow - dwUser;
|
|
if (dwDelta > 1)
|
|
dprintf2(( "Timer event delivered %lu ms late", dwDelta));
|
|
}
|
|
#endif
|
|
|
|
for (pme = gpEmuList; pme; pme = pme->pNext)
|
|
{
|
|
pme->tkTime = clockTime(&pme->clock);
|
|
|
|
//
|
|
// Play all events on this pdev that are due
|
|
//
|
|
if (pme->dwPolyMsgState == PM_STATE_READY)
|
|
{
|
|
//
|
|
// Lock starts at -1. When incrementing the lock
|
|
// if we are the only one with the lock the count
|
|
// will be 0, otherwise it will be some non-zero
|
|
// value determined by InterlockedIncrement.
|
|
//
|
|
if (PDEVLOCK( pme ) == 0)
|
|
|
|
midiOutPlayNextPolyEvent(pme
|
|
#ifdef DEBUG
|
|
,dwNow
|
|
#endif
|
|
);
|
|
|
|
PDEVUNLOCK( pme );
|
|
}
|
|
|
|
//
|
|
// If there's still data to play on this stream, figure out when
|
|
// it'll be due so we can schedule the next nearest event.
|
|
//
|
|
if (pme->dwPolyMsgState != PM_STATE_EMPTY)
|
|
{
|
|
// dprintf1(( "tkNextEventDue %lu pdev->tkTime %lu", pme->tkNextEventDue, pme->tkTime));
|
|
if (pme->tkNextEventDue <= pme->tkTime)
|
|
{
|
|
//
|
|
// This can happen if we send a long embedded SysEx and the
|
|
// next event is scheduled a short time away (comes due before
|
|
// SysEx finishes). In this case, we want the timer to fire
|
|
// again ASAP.
|
|
//
|
|
msNextEvent = 0;
|
|
}
|
|
else
|
|
{
|
|
msNextEvent =
|
|
clockOffsetTo(&pme->clock, pme->tkNextEventDue);
|
|
}
|
|
|
|
if (msNextEvent < msNextEventMin)
|
|
{
|
|
msNextEventMin = msNextEvent;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
dprintf1(( "dwPolyMsgState == PM_STATE_EMPTY"));
|
|
}
|
|
}
|
|
|
|
if (0 == msNextEventMin)
|
|
{
|
|
dprintf1(( "midiEmu: Next event due now!!!"));
|
|
}
|
|
|
|
--guMIDIInTimer;
|
|
|
|
//
|
|
// Schedule the next event. In no case schedule an event less than
|
|
// guMIDIPeriodMin away (no point in coming back w/ no time elapsed).
|
|
//
|
|
if (msNextEventMin != (DWORD)-1L)
|
|
{
|
|
uDelay = max(guMIDIPeriodMin, (UINT)msNextEventMin);
|
|
|
|
// dprintf1(("PM Resched %u ms (ID=%u)", uDelay, guMIDITimerID));
|
|
|
|
if (!gfMinPeriod)
|
|
{
|
|
timeBeginPeriod(guMIDIPeriodMin);
|
|
gfMinPeriod = TRUE;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
guMIDITimerID = timeSetEvent(uDelay, guMIDIPeriodMin, midiOutTimerTick, timeGetTime()+uDelay, TIME_ONESHOT);
|
|
#else
|
|
guMIDITimerID = timeSetEvent(uDelay, guMIDIPeriodMin, midiOutTimerTick, uDelay, TIME_ONESHOT);
|
|
#endif
|
|
|
|
dprintf2(( "mOTT tse(%u) = %u", guMIDIPeriodMin, guMIDITimerID));
|
|
|
|
if (guMIDITimerID == TIMER_OFF)
|
|
dprintf1(( "timeSetEvent(%u) failed in midiOutTimerTick!!!", uDelay));
|
|
}
|
|
else
|
|
{
|
|
dprintf1(( "Stop in the name of all that which does not suck!"));
|
|
guMIDITimerID = TIMER_OFF;
|
|
if (gfMinPeriod)
|
|
{
|
|
dprintf1(( "timeEndPeriod"));
|
|
gfMinPeriod = FALSE;
|
|
timeEndPeriod(guMIDIPeriodMin);
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
{
|
|
DWORD dwDelta = timeGetTime() - dwNow;
|
|
if (dwDelta > 1)
|
|
dprintf2(( "Spent %lu ms in midiOutTimerTick", dwDelta));
|
|
}
|
|
#endif
|
|
} /* TimerTick() */
|
|
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* @doc INTERNAL MIDI
|
|
*
|
|
* @api void | midiOutCallback |
|
|
* This function is called by the midi output driver whenever an event
|
|
* completes. It filters long message completions when we are emulating
|
|
* polymsg out.
|
|
*
|
|
* @parm HMIDIOUT | hMidiOut |
|
|
* Handle of the device which completed something.
|
|
*
|
|
* @parm UINT | wMsg |
|
|
* Specifies the event which completed.
|
|
*
|
|
* @parm DWORD | dwInstance |
|
|
* User instance data for the callback.
|
|
*
|
|
* @parm DWORD | dwParam1 |
|
|
* Message specific parameter.
|
|
*
|
|
* @parm DWORD | dwParam2 |
|
|
* Message specific parameter.
|
|
*
|
|
* @comm
|
|
*
|
|
* If this is a completion for a long message buffer on a stream we are
|
|
* emulating polymsg out for, mark the stream as ready to play.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
void CALLBACK midiOutCallback(
|
|
HMIDIOUT hMidiOut,
|
|
WORD wMsg,
|
|
DWORD dwInstance,
|
|
DWORD dwParam1,
|
|
DWORD dwParam2)
|
|
{
|
|
PMIDIEMU pme;
|
|
LPMIDIHDR lpmh;
|
|
|
|
if (MM_MOM_DONE != wMsg)
|
|
return;
|
|
|
|
lpmh = (LPMIDIHDR)dwParam1;
|
|
pme = (PMIDIEMU)lpmh->dwReserved[MH_STRMPME];
|
|
|
|
#ifdef DEBUG
|
|
if (lpmh->dwFlags & MHDR_ISSTRM)
|
|
dprintf1(( "Uh-oh, got stream header back from 3.1 driver???"));
|
|
#endif
|
|
|
|
if (MM_MOM_DONE == wMsg)
|
|
{
|
|
if (0 == --pme->cSentLongMsgs &&
|
|
!(pme->fdwDev & MDV_F_SENDING))
|
|
pme->dwPolyMsgState = PM_STATE_READY;
|
|
}
|
|
|
|
}
|
|
|
|
/*****************************************************************************
|
|
* @doc INTERNAL MIDI
|
|
*
|
|
* @api void | midiOutAllNotesOff | This function turns off all notes
|
|
* by using the map kept in polymsg emulation. It only works if we're
|
|
* opened with MIDI_IO_COOKED and are emulating on that device.
|
|
*
|
|
* @parm PMIDIEMU | pme | The device to turn off notes on.
|
|
*
|
|
* @xref midiOutPause midiOutStop
|
|
****************************************************************************/
|
|
void NEAR PASCAL midiOutAllNotesOff(
|
|
PMIDIEMU pme)
|
|
{
|
|
UINT uChannel;
|
|
UINT uNote;
|
|
BYTE bCount;
|
|
DWORD dwMsg;
|
|
UINT idx;
|
|
LPBYTE pbEntry = pme->rbNoteOn;
|
|
|
|
for (uChannel=0; uChannel < NUM_CHANNELS; uChannel++)
|
|
{
|
|
// Turn off any sustained notes so the note off won't be ignored
|
|
//
|
|
dwMsg = ((DWORD)MIDI_CONTROLCHANGE) |
|
|
((DWORD)uChannel)|
|
|
(((DWORD)MIDI_SUSTAIN)<<8);
|
|
|
|
for (idx = 0; idx < pme->chMidi; idx++)
|
|
midiMessage(pme->rIds[idx].hMidi, MODM_DATA, dwMsg, 0L);
|
|
|
|
for (uNote=0; uNote < NUM_NOTES; uNote++)
|
|
{
|
|
if (uNote&0x01) // odd
|
|
{
|
|
bCount = (*(pbEntry + (uChannel * NUM_NOTES + uNote)/2) & 0xF0)>>4;
|
|
}
|
|
else // even
|
|
{
|
|
bCount = *(pbEntry + (uChannel * NUM_NOTES + uNote)/2) & 0xF;
|
|
}
|
|
|
|
if (bCount != 0)
|
|
{
|
|
//
|
|
// Message is Note off on this channel and note
|
|
// with a turn off velocity of 127
|
|
//
|
|
dwMsg =
|
|
((DWORD)MIDI_NOTEOFF)|
|
|
((DWORD)uChannel)|
|
|
((DWORD)(uNote<<8))|
|
|
0x007F0000L;
|
|
|
|
dprintf1(( "mOANO: dwMsg %08lX count %u", dwMsg, (UINT)bCount));
|
|
|
|
while (bCount--)
|
|
{
|
|
for (idx = 0; idx < pme->chMidi; idx++)
|
|
midiMessage(pme->rIds[idx].hMidi, MODM_DATA, dwMsg, 0L);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
MMRESULT FAR PASCAL mseOutCachePatches(
|
|
PMIDIEMU pme,
|
|
UINT uBank,
|
|
LPWORD pwpa,
|
|
UINT fuCache)
|
|
{
|
|
UINT cmesi;
|
|
PMIDIEMUSID pmesi;
|
|
MMRESULT mmrc;
|
|
MMRESULT mmrc2;
|
|
|
|
cmesi = pme->chMidi;
|
|
pmesi = pme->rIds;
|
|
|
|
mmrc2 = MMSYSERR_NOERROR;
|
|
while (cmesi--)
|
|
{
|
|
mmrc = midiOutCachePatches((HMIDIOUT)pmesi->hMidi, uBank, pwpa, fuCache);
|
|
if (MMSYSERR_NOERROR != mmrc && MMSYSERR_NOTSUPPORTED != mmrc)
|
|
mmrc2 = mmrc;
|
|
}
|
|
|
|
return mmrc2;
|
|
}
|
|
|
|
|
|
MMRESULT FAR PASCAL mseOutCacheDrumPatches(
|
|
PMIDIEMU pme,
|
|
UINT uPatch,
|
|
LPWORD pwkya,
|
|
UINT fuCache)
|
|
{
|
|
UINT cmesi;
|
|
PMIDIEMUSID pmesi;
|
|
MMRESULT mmrc;
|
|
MMRESULT mmrc2;
|
|
|
|
cmesi = pme->chMidi;
|
|
pmesi = pme->rIds;
|
|
|
|
mmrc2 = MMSYSERR_NOERROR;
|
|
while (cmesi--)
|
|
{
|
|
mmrc = midiOutCacheDrumPatches((HMIDIOUT)pmesi->hMidi, uPatch, pwkya, fuCache);
|
|
if (MMSYSERR_NOERROR != mmrc && MMSYSERR_NOTSUPPORTED != mmrc)
|
|
mmrc2 = mmrc;
|
|
}
|
|
|
|
return mmrc2;
|
|
}
|
|
|
|
DWORD FAR PASCAL mseOutBroadcast(
|
|
PMIDIEMU pme,
|
|
UINT msg,
|
|
DWORD dwParam1,
|
|
DWORD dwParam2)
|
|
{
|
|
UINT idx;
|
|
DWORD dwRet;
|
|
DWORD dwRetImmed;
|
|
|
|
dwRet = 0;
|
|
for (idx = 0; idx < pme->chMidi; idx++)
|
|
{
|
|
dwRetImmed = midiOutMessage((HMIDIOUT)pme->rIds[idx].hMidi, msg, dwParam1, dwParam2);
|
|
if (dwRetImmed)
|
|
dwRet = dwRetImmed;
|
|
}
|
|
|
|
return dwRet;
|
|
}
|
|
|
|
DWORD FAR PASCAL mseTimebase(
|
|
PCLOCK pclock)
|
|
{
|
|
return timeGetTime();
|
|
}
|