|
|
/*****************************************************************************
midiemu.c
MIDI support -- routines for stream emulation
Copyright (c) 1990-1999 Microsoft Corporation
*****************************************************************************/ #define INCL_WINMM
#include "winmmi.h"
#include "muldiv32.h"
#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)
PMIDIEMU gpEmuList = NULL; UINT guMIDIInTimer = 0; UINT guMIDITimerID = TIMER_OFF; BOOL gfMinPeriod = FALSE; UINT guMIDIPeriodMin;
STATIC HMIDI FAR PASCAL mseIDtoHMidi( PMIDIEMU pme, DWORD dwStreamID);
MMRESULT FAR PASCAL mseOpen( PDWORD_PTR 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_PTR dwParam1, DWORD_PTR dwParam2);
DWORD FAR PASCAL mseTimebase( PCLOCK pclock);
#ifndef WIN32
#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)
#endif
/****************************************************************************/ /****************************************************************************/
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_PTR dwUser, DWORD_PTR dwParam1, DWORD_PTR dwParam2) { MMRESULT mmr = MMSYSERR_NOERROR; PMIDIEMU pme = (PMIDIEMU)dwUser;
switch(msg) { case MODM_OPEN: mmr = mseOpen((PDWORD_PTR)dwUser, (LPMIDIOPENDESC)dwParam1, (DWORD)dwParam2); break;
case MODM_CLOSE: mmr = mseClose(pme); break;
case MODM_GETVOLUME: mmr = mseGetVolume(pme, (LPDWORD)dwParam1); break;
case MODM_SETVOLUME: mmr = mseSetVolume(pme, (DWORD)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 = midiOutShortMsg((HMIDIOUT)pme->rIds[0].hMidi, (DWORD)dwParam1); 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, (DWORD)dwParam1, (DWORD)dwParam2); break;
case MODM_STRMDATA: mmr = mseOutSend(pme, (LPMIDIHDR)dwParam1, (UINT)dwParam2); break;
case MODM_PROPERTIES: mmr = mseProperty(pme, (LPBYTE)dwParam1, (DWORD)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( PDWORD_PTR 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;
mmrc = midiOutOpen((LPHMIDIOUT)&pme->rIds[idx].hMidi, lpmod->rgIds[idx].uDeviceID, (DWORD_PTR)midiOutCallback, 0L, CALLBACK_FUNCTION); if (MMSYSERR_NOERROR != mmrc) goto mseOpen_Cleanup; }
if (!mmInitializeCriticalSection(&pme->CritSec)) { mmrc = MMSYSERR_NOMEM; goto mseOpen_Cleanup; }
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;
for (idx = 0; idx < pme->chMidi; idx++) if (NULL != pme->rIds[idx].hMidi) midiOutClose((HMIDIOUT)pme->rIds[idx].hMidi); winmmFree(pme); } } else { pme->pNext = gpEmuList; gpEmuList = pme;
*lpdwUser = (DWORD_PTR)pme; }
return mmrc; }
MMRESULT FAR PASCAL mseClose( PMIDIEMU pme)
{ UINT idx; MMRESULT mmrc; PMIDIEMU pmePrev; PMIDIEMU pmeCurr;
#ifdef DEBUG
{ 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++) { mmrc = midiOutClose((HMIDIOUT)pme->rIds[idx].hMidi); 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 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;
}
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_PTR dwBase; UINT cNewHeaders;
dprintf2(( "mseOutSend pme %04X lpmh %08lX", (UINT_PTR)pme, (DWORD_PTR)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_PTR)(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_PTR)(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_PTR)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, (DWORD)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, (DWORD)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 = (DWORD)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_PTR)lpmhdr, 0L);
}
bEvent &= ~(MEVT_F_CALLBACK >> 24);
switch(bEvent) { case MEVT_SHORTMSG: { BYTE bEventType; 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.
//
bEventType = (BYTE)(dwMsg&0xFF);
if (!(bEventType & 0x80)) { bEventType = 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)(bEventType); } else { pme->bRunningStatus = bEventType; bNote = (BYTE)((dwMsg >> 8)&0xFF); bVelocity = (BYTE)((dwMsg >> 16)&0xFF); }
if ((bEventType&0xF0) == MIDI_NOTEON || (bEventType&0xF0) == MIDI_NOTEOFF) { BYTE bChannel = (bEventType & 0x0F); UINT cbOffset = (bChannel * NUM_NOTES + bNote) / 2;
//
// Note-on with a velocity of 0 == note off
//
if ((bEventType&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 = midiOutShortMsg(hmo, dwMsg); 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, (DWORD)lpmhdr->dwReserved[MH_STREAM]);
if (hmo) mmrError = midiOutLongMsg(hmo, lpmhdr, sizeof(MIDIHDR)); else dprintf1(( "mseIDtoHMidi() failed and returned a NULL" ));
if ((hmo) && (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; BOOL fPosted;
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_PTR)lpmidihdr));
++pme->cPostedBuffers; fPosted = PostMessage( hwndNotify, MM_POLYMSGBUFRDONE, (WPARAM)pme, (DWORD_PTR)lpmidihdr);
WinAssert(fPosted);
if (!fPosted) { GetLastError(); --pme->cPostedBuffers; } }
void FAR PASCAL midiOutNukePMBuffer( PMIDIEMU pme, LPMIDIHDR lpmh) { LPMIDIHDREXT lpExt; LPMIDIHDR lpmhWork; MMRESULT mmrc; HMIDIOUT hmo;
dprintf2(( "Nuke %08lX", (DWORD_PTR)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, (DWORD)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_PTR)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_PTR dwUser, DWORD_PTR dw1, DWORD_PTR 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 - (DWORD)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 | TIME_KILL_SYNCHRONOUS); #else
guMIDITimerID = timeSetEvent(uDelay, guMIDIPeriodMin, midiOutTimerTick, uDelay, TIME_ONESHOT | TIME_KILL_SYNCHRONOUS); #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_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR 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++) midiOutShortMsg((HMIDIOUT)pme->rIds[idx].hMidi, dwMsg);
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++) midiOutShortMsg((HMIDIOUT)pme->rIds[idx].hMidi, dwMsg); } } } } }
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_PTR dwParam1, DWORD_PTR 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(); }
|