windows-nt/Source/XPSP1/NT/multimedia/directx/dmusic/dmusic16/midiout.c

1020 lines
26 KiB
C
Raw Normal View History

2020-09-26 03:20:57 -05:00
/* Copyright (c) 1998 Microsoft Corporation */
/*
* @Doc DMusic16
*
* @Module MIDIOut.c - Legacy MIDI output emulation for DirectMusic |
*
* @comm
*
* BUGBUG Need to deal with timer wraparound
*
*/
#pragma warning(disable:4704) /* Inline assembly */
#include <windows.h>
#include <mmsystem.h>
#include "dmusic16.h"
#include "debug.h"
#define MIDI_CHANMSG_STATUS_CMD_MASK (0xF0)
#define MIDI_NOTE_ON (0x90)
/* How far past the current time do we send events?
*/
#define MS_TIMER_SLOP (3)
STATIC TIMECAPS gTimeCaps;
STATIC BOOL gbTimerRunning;
STATIC DWORD gdwTimerDue;
STATIC UINT guTimerID;
STATIC UINT gcActiveOutputDevices;
int PASCAL IsEventDone(LPEVENT pEvent, DWORD dwInstance);
VOID SetNextTimer();
VOID CALLBACK __loadds midiOutProc(HMIDIOUT hMidiIn, UINT wMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2);
VOID CALLBACK __loadds RunTimer(UINT uTimerID, UINT wMsg, DWORD dwUser, DWORD dw1, DWORD dw2);
STATIC VOID NEAR PASCAL MidiOutFlushQueues(NPOPENHANDLE poh);
STATIC VOID NEAR PASCAL MidiOutSendAllNow(NPOPENHANDLE poh);
#pragma alloc_text(INIT_TEXT, MidiOutOnLoad)
#pragma alloc_text(FIX_OUT_TEXT, midiOutProc)
#pragma alloc_text(FIX_OUT_TEXT, RunTimer)
/* @func Called at DLL <f LibInit>
*
* @comm
*
* Get the timer caps.
* Initialize globals.
*/
VOID PASCAL
MidiOutOnLoad()
{
/* This cannot fail
*/
timeGetDevCaps(&gTimeCaps, sizeof(gTimeCaps));
gbTimerRunning = FALSE;
}
/* @func Called at DLL <f LibExit>
*
* @comm
*
* The DLL is unloading, so kill any future timer callback.
*/
VOID PASCAL
MidiOutOnExit()
{
WORD wIntStat;
wIntStat = DisableInterrupts();
if (gbTimerRunning)
{
DPF(1, "DLL unloading, killing timer interrupts");
timeKillEvent(guTimerID);
gbTimerRunning = FALSE;
}
RestoreInterrupts(wIntStat);
}
/* @func Open a handle instance
*
* @comm
*
*/
MMRESULT PASCAL
MidiOutOnOpen(
NPOPENHANDLEINSTANCE pohi)
{
return MMSYSERR_NOERROR;
}
/* @func Close a MIDI device
*
* @comm
*
*/
VOID PASCAL
MidiOutOnClose(
NPOPENHANDLEINSTANCE pohi)
{
/* Give MIDI input a chance to turn off thruing to this handle.
*/
MidiInUnthruToInstance(pohi);
}
/* @func Activate a MIDI device
*
* @comm
*
* If this is the first activation of the device, open it using the <f midiOutOpen> legacy API.
*/
MMRESULT PASCAL
MidiOutOnActivate(
NPOPENHANDLEINSTANCE pohi)
{
NPOPENHANDLE poh = pohi->pHandle;
MMRESULT mmr;
HINSTANCE hInstance;
WORD sel;
WORD off;
HTASK FAR *lph;
DPF(1, "MidiOutActivate poh %04X device %d refcount %u",
(WORD)poh,
poh->id,
poh->uReferenceCount);
/* Only open on the first activation
*/
if (1 == poh->uActiveCount)
{
mmr = midiOutOpen(&poh->hmo,
poh->id,
(DWORD)midiOutProc,
(DWORD)(LPOPENHANDLE)poh,
CALLBACK_FUNCTION);
if (mmr)
{
return mmr;
}
/* Since mapper can't be open shared, and we don't want the first instance that opens
* mapper to take it with it on exit (due to mmsystem appexit), we do really nasty
* stuff here.
*
* The WORD immediately PRECEDING the handle in MMSYSTEM's data segment is the task
* owner of the handle. We nuke it to NULL (which is all MIDI_IO_SHARED does anyway)
* to make AppExit ignore us.
*
* God help us if anyone changes HNDL in mmsysi.h
*
*/
hInstance = LoadLibrary("mmsystem.dll");
sel = (WORD)hInstance;
/* hInstance <= 32 means LoadLibrary failed; in this case we just live with it.
*/
if (sel > 32)
{
off = ((WORD)poh->hmo) - sizeof(WORD);
lph = (HTASK FAR *)MAKELP(sel, off);
*lph = (HTASK)NULL;
FreeLibrary(hInstance);
}
/* If this is the first output device, bump up timer resolution
*/
++gcActiveOutputDevices;
if (gcActiveOutputDevices == 1)
{
SetOutputTimerRes(TRUE);
}
}
return MMSYSERR_NOERROR;
}
/* @func Deactivate a MIDI device
*
* @comm
*
* If the last client using the device is closing, then close the actual device.
* If closing the last actual device, then shut down the high precision timer
*
*/
MMRESULT PASCAL
MidiOutOnDeactivate(
NPOPENHANDLEINSTANCE pohi)
{
NPOPENHANDLE poh = pohi->pHandle;
DPF(1, "MidiOutOnDeactivate poh %04X device %d refcount %u",
(WORD)poh,
poh->id,
poh->uReferenceCount);
if (poh->uActiveCount)
{
/* Still open instances out there
*/
return MMSYSERR_NOERROR;
}
MidiOutSendAllNow(poh);
midiOutReset(poh->hmo);
midiOutClose(poh->hmo);
MidiOutFlushQueues(poh);
/* If this was the last output device, shut down precision timer resolution
*/
--gcActiveOutputDevices;
if (gcActiveOutputDevices == 0)
{
SetOutputTimerRes(FALSE);
}
return MMSYSERR_NOERROR;
}
/* @func Set the timer resolution
*
* @comm
*
* Set the resolution of the timer callbacks using the <f timeBeginPeriod> and <f timeEndPeriod>
* API's.
*
* If <p fOnOpen> is TRUE, then the timer resolution will be changed to 1 millisecond. Otherwise, it
* will be set to its previous value.
*
*/
VOID PASCAL
SetOutputTimerRes(
BOOL fOnOpen) /* @parm TRUE if we are supposed to raise precision */
{
MMRESULT mmr;
if (fOnOpen)
{
mmr = timeBeginPeriod(gTimeCaps.wPeriodMin);
if (MMSYSERR_NOERROR != mmr)
{
DPF(1, "Could not timeBeginPeriod() -> %u", (UINT)mmr);
}
}
else
{
mmr = timeEndPeriod(gTimeCaps.wPeriodMin);
if (MMSYSERR_NOERROR != mmr)
{
DPF(1, "Could not timeEndPeriod() -> %u", (UINT)mmr);
}
}
}
/* @func Submit a buffer to a device for playback
*
* @rdesc Returns one of the following
* @flag MMSYSERR_NOERROR | If the buffer was successfully queued
* @flag MMSYSERR_INVALPARAM | If the buffer is incorrectly packed or the handle is invalid
* @flag MMSYSERR_NOMEM | If there was no memory available to queue the events
*
* @comm
*
* This function is thunked to DMusic32.
*
* The DirectMusic port interface specifies that a submitted buffer not be
* kept by the system past the time of the call which submits it.
*
* This routine parses the buffer into individual events and copies them into
* local event structures, which are then queued onto the handle of the device
* specified by <p h>. The queue for each device is kept in time-increasing order.
* All local event memory is page-locked (see alloc.c) so that it can be accessed
* at interrupt time.
*
* The time stamps in the buffer are millisecond resolution and are relative to
* the absolute time <p msStartTime>.
*
*/
MMRESULT WINAPI
MidiOutSubmitPlaybackBuffer(
HANDLE h, /* @parm The handle of the device to queue these events for */
LPBYTE lpBuffer, /* @parm A pointer to the buffer as packed by the IDirectMusicBuffer interface */
DWORD cbBuffer, /* @parm The number of bytes of data in the buffer */
DWORD msStartTime, /* @parm The starting time of the buffer in absolute time */
DWORD rtStartTimeLow, /* @parm Low DWORD of starting reference time */
DWORD rtStartTimeHigh) /* @parm High DWORD of starting reference time */
{
NPOPENHANDLEINSTANCE pohi;
NPOPENHANDLE poh;
LPDMEVENT lpEventHdr;
DWORD cbEvent;
DWORD msTime;
LPEVENT pPrev;
LPEVENT pCurr;
LPEVENT pNew;
WORD wCSID;
MMRESULT mmr;
LPMIDIHDR lpmh;
QUADWORD rtStartTime;
QUADWORD rtTime;
#ifdef DUMP_EVERY_BUFFER
UINT idx;
LPDWORD lpdw;
#endif //DUMP_EVERY_BUFFER
rtStartTime.dwLow = rtStartTimeLow;
rtStartTime.dwHigh = rtStartTimeHigh;
DPF(2, "Buffer @ %08lX msStartTime %lu", (DWORD)lpBuffer, (DWORD)msStartTime);
DPF(2, "At the tone the time will be... %lu <BEEP>", (DWORD)timeGetTime());
#ifdef DUMP_EVERY_BUFFER
cbEvent = cbBuffer & 0xFFFFFFF0;
lpdw = (LPDWORD)lpBuffer;
for (idx = 0; idx < cbEvent; idx += 16) {
DPF(3, "%04X: %08lX %08lX %08lX %08lX",
(UINT)idx,
lpdw[0],
lpdw[1],
lpdw[2],
lpdw[3]);
lpdw += 4;
}
cbEvent = cbBuffer - (cbBuffer & 0xFFFFFFF0);
if (cbEvent >= 12) {
DPF(3, "%04x: %08lX %08lX %08lX",
(UINT)idx, lpdw[0], lpdw[1], lpdw[2]);
} else if (cbEvent >= 8) {
DPF(3, "%04x: %08lX %08lX",
(UINT)idx, lpdw[0], lpdw[1]);
} else if (cbEvent >= 8) {
DPF(3, "%04x: %08lX",
(UINT)idx, lpdw[0]);
}
#endif // DUMP_EVERY_BUFFER
if (!IsValidHandle(h, VA_F_OUTPUT, &pohi))
{
return MMSYSERR_INVALHANDLE;
}
/* Get the handle and lock its list
*/
poh = pohi->pHandle;
/* Dequeue and free all completed events on this handle
*/
FreeDoneHandleEvents(poh, FALSE);
wCSID = EnterCriticalSection(&poh->wCritSect, CS_BLOCKING);
assert(wCSID);
/* Get the time of the first event and position ourselves in the list
*/
if (0 == poh->qPlay.cEle)
{
pPrev = NULL;
pCurr = NULL;
}
else if (!QuadwordLT(rtStartTime, poh->qPlay.pTail->rtTime))
{
pPrev = poh->qPlay.pTail;
pCurr = NULL;
}
else
{
pPrev = NULL;
pCurr = poh->qPlay.pHead;
}
/* Walk the buffer and add the events to the handle's queue
*/
while (cbBuffer)
{
if (cbBuffer < sizeof(DMEVENT))
{
return MMSYSERR_INVALPARAM;
}
lpEventHdr = (LPDMEVENT)lpBuffer;
cbEvent = DMEVENT_SIZE(lpEventHdr->cbEvent);
DPF(2, "cbEvent now %u", (UINT)cbEvent);
if (cbEvent > cbBuffer)
{
DPF(0, "Event past end of buffer");
return MMSYSERR_INVALPARAM;
}
lpBuffer += cbEvent;
cbBuffer -= cbEvent;
/* We only play events on channel group 1 (0 is broadcast, so we
* play that as well).
*/
if (lpEventHdr->dwChannelGroup > 1)
{
continue;
}
// Time here is in 100ns for queue sorting
//
QuadwordAdd(rtStartTime, lpEventHdr->rtDelta, &rtTime);
// Also need msTime for scheduling
//
msTime = msStartTime + QuadwordDiv(lpEventHdr->rtDelta, REFTIME_TO_MS);
// BUGBUG: >64k??
//
DPF(2, "Schedule event %02X%02X%02X%02X at %lu",
(BYTE)lpEventHdr->abEvent[0],
(BYTE)lpEventHdr->abEvent[1],
(BYTE)lpEventHdr->abEvent[2],
(BYTE)lpEventHdr->abEvent[3],
msTime);
if (lpEventHdr->cbEvent <= sizeof(DWORD))
{
pNew = AllocEvent(msTime, rtTime, (WORD)lpEventHdr->cbEvent);
if (!pNew)
{
return MMSYSERR_NOMEM;
}
hmemcpy(pNew->abEvent, lpEventHdr->abEvent, lpEventHdr->cbEvent);
}
else
{
pNew = AllocEvent(msTime, rtTime, (WORD)(lpEventHdr->cbEvent + sizeof(MIDIHDR)));
if (!pNew)
{
return MMSYSERR_NOMEM;
}
pNew->wFlags |= EVENT_F_MIDIHDR;
lpmh = (LPMIDIHDR)&pNew->abEvent;
lpmh->lpData = (LPSTR)(lpmh + 1);
lpmh->dwBufferLength = lpEventHdr->cbEvent;
lpmh->dwUser = 0; /* Flag if MMSYSTEM owns this buffer */
lpmh->dwFlags = 0;
hmemcpy(lpmh->lpData, lpEventHdr->abEvent, lpEventHdr->cbEvent);
mmr = midiOutPrepareHeader(poh->hmo, lpmh, sizeof(MIDIHDR));
if (mmr)
{
DPF(2, "midiOutPrepareHeader %u", mmr);
FreeEvent(pNew);
return mmr;
}
}
while (pCurr)
{
if (QuadwordLT(rtTime, pCurr->rtTime))
{
break;
}
pPrev = pCurr;
pCurr = pCurr->lpNext;
}
if (pPrev)
{
pPrev->lpNext = pNew;
}
else
{
poh->qPlay.pHead = pNew;
}
pNew->lpNext = pCurr;
if (NULL == pCurr)
{
poh->qPlay.pTail = pNew;
}
pPrev = pNew;
pCurr = pNew->lpNext;
++poh->qPlay.cEle;
AssertQueueValid(&poh->qPlay);
}
LeaveCriticalSection(&poh->wCritSect);
SetNextTimer();
return MMSYSERR_NOERROR;
}
/* @func VOID PASCAL | FreeDoneHandleEvents | Free events that have already been played, but are still sitting in the done queue
* on this handle.
*
* @comm
*
* If fClosing is TRUE, then the events will be free'd regardless of whether they are marked as completed.
*
*/
typedef struct {
NPOPENHANDLE poh;
BOOL fClosing;
} ISEVENTDONEPARMS, FAR *LPISEVENTDONEPARMS;
VOID PASCAL
FreeDoneHandleEvents(
NPOPENHANDLE poh, /* @parm What handle? */
BOOL fClosing) /* @parm TRUE if the device is being closed. */
{
ISEVENTDONEPARMS iedp;
WORD wCSID;
iedp.poh = poh;
iedp.fClosing = fClosing;
wCSID = EnterCriticalSection(&poh->wCritSect, CS_BLOCKING);
assert(wCSID);
QueueFilter(&poh->qDone, (DWORD)(LPVOID)&iedp, IsEventDone);
LeaveCriticalSection(&poh->wCritSect);
}
/* @func
*
* @comm
*/
int PASCAL
IsEventDone(
LPEVENT pEvent,
DWORD dwInstance)
{
LPISEVENTDONEPARMS piedp = (LPISEVENTDONEPARMS)dwInstance;
MMRESULT mmr;
if (piedp->fClosing ||
pEvent->cbEvent <= sizeof(DWORD) ||
((LPMIDIHDR)(&pEvent->abEvent[0]))->dwUser == 0)
{
/* Ok to free this event
*/
if (pEvent->cbEvent > sizeof(DWORD))
{
mmr = midiOutUnprepareHeader(piedp->poh->hmo, (LPMIDIHDR)(&pEvent->abEvent[0]), sizeof(MIDIHDR));
if (mmr)
{
DPF(0, "FreeOldEvents: midiOutUnprepareHeader returned %u", (UINT)mmr);
}
}
FreeEvent(pEvent);
return QUEUE_FILTER_REMOVE;
}
return QUEUE_FILTER_KEEP;
}
/* @func Thru the given message on the given output port
*
* @comm
*
*/
VOID PASCAL
MidiOutThru(
NPOPENHANDLEINSTANCE pohi,
DWORD dwMessage)
{
NPOPENHANDLE poh = pohi->pHandle;
MMRESULT mmr;
/* !!! Verify that VMM will not interrupt a timer callback with another event
*/
mmr = midiOutShortMsg(poh->hmo, dwMessage);
if (mmr)
{
DPF(0, "Thru: midiOutShortMsg() -> %d", mmr);
}
}
/* @func Set the timer to schedule the next pending event
*
* @comm
*
* Walk the list of output handles and look at the first scheduled event on each. Save the time
* of the nearest event. If there is such an event, schedule a timer callback at that time to call
* <f RunTimer>; otherwise, schedule no callback.
*
* Any pending timer callback will be killed before the new callback is scheduled.
*/
VOID
SetNextTimer(VOID)
{
WORD wIntStat;
NPLINKNODE npLink;
NPOPENHANDLE poh;
DWORD dwLowTime;
BOOL fNeedTimer;
DWORD dwNow;
LONG lWhen;
UINT uWhen;
/* We actually need to disable interrupts here as opposed to just entering a critical section
* because we don't want the timer callback to fire.
*/
wIntStat = DisableInterrupts();
/* BUGBUG: wrap
*/
fNeedTimer = FALSE;
dwLowTime = (DWORD)(0xFFFFFFFFL);
for (npLink = gOpenHandleList; npLink; npLink = npLink->pNext)
{
poh = (NPOPENHANDLE)npLink;
if (0 == poh->qPlay.cEle)
{
continue;
}
assert(poh->qPlay.pHead);
if (poh->qPlay.pHead->msTime < dwLowTime)
{
fNeedTimer = TRUE;
dwLowTime = poh->qPlay.pHead->msTime;
}
}
if (fNeedTimer)
{
if ((!gbTimerRunning) || dwLowTime < gdwTimerDue)
{
/* We need to set the timer. Kill it now so there's no chance of it
* firing before being killed
*/
if (gbTimerRunning)
{
timeKillEvent(guTimerID);
gbTimerRunning = FALSE;
}
}
else
{
fNeedTimer = FALSE;
}
}
RestoreInterrupts(wIntStat);
if (fNeedTimer)
{
/* Guaranteed that current timer expired or dead. Reschedule.
*/
dwNow = timeGetTime();
gbTimerRunning = TRUE;
gdwTimerDue = dwLowTime;
lWhen = gdwTimerDue - dwNow;
if (lWhen < (LONG)gTimeCaps.wPeriodMin)
{
uWhen = gTimeCaps.wPeriodMin;
}
else if (lWhen > (LONG)gTimeCaps.wPeriodMax)
{
uWhen = gTimeCaps.wPeriodMax;
}
else
{
uWhen = (UINT)lWhen;
}
DPF(2, "SetNextTimer: Now %lu, setting timer for %u ms from now. dwLowTime %lu",
(DWORD)dwNow, (UINT)uWhen, (DWORD)dwLowTime);
guTimerID = timeSetEvent(uWhen,
gTimeCaps.wPeriodMin,
RunTimer,
NULL,
TIME_ONESHOT);
if (0 == guTimerID)
{
gbTimerRunning = FALSE;
}
}
else
{
DPF(2, "SetNextTimer: Timer cancelled; no pending events.");
}
}
/* @func Process a high precision timer callback
*
* @comm
*
* This is a standard callback for the <f timeSetEvent> API.
*
* Walk the list of open output handles. For each handle, look at the event queue. Play all
* the events that are due.
*
* Events are pulled from the qPlay queue on each handle. This queue (as well as the qDone queue) are
* protected by the handle's critical section. If we cannot get the critical section, then the events
* that may be due on that handle will not be played.
*
* If we do get the critical section and play events, then the events will be moved to the qDone
* queue, where they will later be returned to the free list.
*
* This intermediate step is needed because we cannot call <f FreeEvent> at interrupt time. We cannot
* just protect the free list with a critical section, because we cannot afford to fail getting the
* critical section. If we did, we would lost the memory for the event we were about to free.
*
*/
VOID CALLBACK __loadds
RunTimer(
UINT uTimerID, /* @parm The ID of the timer which fired */
UINT wMsg, /* @parm The type of callback (unused) */
DWORD dwUser, /* @parm User instance data */
DWORD dw1, /* @parm Message specific data (unused) */
DWORD dw2) /* @parm Message specific data (unused) */
{
NPLINKNODE npLink;
NPOPENHANDLE poh;
WORD wCSID;
WORD wIntStat;
DWORD msNow;
DWORD msFence;
LPEVENT pEvent;
DWORD dwEvent;
MMRESULT mmr;
/* Walk the event queues and send out pending events.
*/
msNow = timeGetTime();
msFence = msNow + MS_TIMER_SLOP;
for (npLink = gOpenHandleList; npLink; npLink = npLink->pNext)
{
poh = (NPOPENHANDLE)npLink;
/* If we can't get the critical section, don't sweat it - just reschedule
*/
wCSID = EnterCriticalSection(&poh->wCritSect, CS_NONBLOCKING);
if (!wCSID)
{
DPF(1, "Timer: Could not get critical section for '%04x'; next time.", (UINT)poh);
continue;
}
/* Now safe against foreground messing with this handle
*/
for(;;)
{
pEvent = poh->qPlay.pHead;
if (NULL == pEvent || pEvent->msTime > msFence)
{
break;
}
if (pEvent->msTime > msNow)
{
DPF(2, "Late!");
}
QueueRemoveFromFront(&poh->qPlay);
if (pEvent->cbEvent <= 4)
{
dwEvent = (pEvent->abEvent[0]) |
(((DWORD)pEvent->abEvent[1]) << 8) |
(((DWORD)pEvent->abEvent[2]) << 16);
mmr = midiOutShortMsg(poh->hmo, dwEvent);
if (mmr)
{
DPF(0, "midiOutShortMsg(%04X,%08lX) -> %u",
(UINT)poh->hmo,
dwEvent,
(UINT)mmr);
}
else
{
DPF(2, "midiOutShortMsg(%04X,%08lX) ",
(UINT)poh->hmo,
dwEvent);
}
}
else
{
/* Data contains an already prepared long message.
* DON'T leave interrupts disabled here! Most legacy MIDI drivers
* do this synchronously.
*
*/
RestoreInterrupts(wIntStat);
((LPMIDIHDR)(&pEvent->abEvent[0]))->dwUser = 1;
mmr = midiOutLongMsg(poh->hmo,
(LPMIDIHDR)(&pEvent->abEvent[0]),
sizeof(MIDIHDR));
if (mmr)
{
DPF(0, "midiOutLongMsg(%04X, %08lX, %04X) -> %u\n",
(UINT)poh->hmo,
(DWORD)(LPMIDIHDR)(&pEvent->abEvent[0]),
(UINT)sizeof(MIDIHDR),
(UINT)mmr);
}
DisableInterrupts();
}
/* We're done with this event; back to the free list with ya!
*
* Since we can't protect the free list with a critical section (what
* would we do if getting the critical section failed here?) we keep
* a temporary free list in the handle. Free events are moved from
* the handle to the master free list in user time.
*/
QueueAppend(&poh->qDone, pEvent);
}
LeaveCriticalSection(&poh->wCritSect);
}
/* Now reschedule ourselves if needed.
*/
gbTimerRunning = FALSE;
SetNextTimer();
}
VOID CALLBACK _loadds
midiOutProc(
HMIDIOUT hMidiIn,
UINT wMsg,
DWORD dwInstance,
DWORD dwParam1,
DWORD dwParam2)
{
LPOPENHANDLE poh = (LPOPENHANDLE)dwInstance;
switch(wMsg)
{
case MOM_DONE:
/* Buffer is already queued for free on the device's queue. dwUser flags if it
* is still in use by MMSYSTEM/driver.
*/
((LPMIDIHDR)dwParam1)->dwUser = 0;
break;
}
}
/* @func Return all memory from all queues to the free event list.
*
* @comm
*
*/
STATIC VOID NEAR PASCAL
MidiOutFlushQueues(
NPOPENHANDLE poh)
{
WORD wCSID;
wCSID = EnterCriticalSection(&poh->wCritSect, CS_BLOCKING);
assert(wCSID);
FreeAllQueueEvents(&poh->qPlay);
FreeAllQueueEvents(&poh->qDone);
LeaveCriticalSection(&poh->wCritSect);
}
/* @func Send all pending messages (other than note on) in preperation
* to close the port.
*
* @comm
*
*/
STATIC VOID NEAR PASCAL
MidiOutSendAllNow(
NPOPENHANDLE poh)
{
LPEVENT pEvent;
DWORD dwEvent;
MMRESULT mmr;
WORD wCSID;
wCSID = EnterCriticalSection(&poh->wCritSect, CS_BLOCKING);
assert(wCSID);
/* Now safe against foreground messing with this handle
*/
for(;;)
{
pEvent = poh->qPlay.pHead;
if (NULL == pEvent)
{
DPF(2,"MidiOutSendAllNow: No queued Messages.");
break;
}
QueueRemoveFromFront(&poh->qPlay);
if (pEvent->cbEvent <= 4)
{
dwEvent = (pEvent->abEvent[0]) |
(((DWORD)pEvent->abEvent[1]) << 8) |
(((DWORD)pEvent->abEvent[2]) << 16);
// We aren't going to process MIDI_NOTE_ON with a
// velocity of zero
//There are two kinds of short messages, Two Byte and
//Three Byte.. They pack differently in MIDI Short message
//If the first bit if the High Byte of the Low Word is SET we are
//looking at a 3 byte message.
//MIDI status messages begin with a
//set bit, and every other part of the same message starts with an
//unset bit.
if (HIBYTE(LOWORD(dwEvent) & 0x80) )
{
//This is a THREE BYTE message
// note on with a non-zero velocity is skipped
if ( (HIBYTE(LOWORD(dwEvent)) & MIDI_NOTE_ON) && (LOBYTE(LOWORD(dwEvent)) != 0 ))
{
QueueAppend(&poh->qDone, pEvent);
continue;
}
}
else
{
//This is a THREE BYTE Message
// Any note-on is skiped
if (LOBYTE(LOWORD(dwEvent)) & MIDI_NOTE_ON)
{
QueueAppend(&poh->qDone, pEvent);
continue;
}
}
mmr = midiOutShortMsg(poh->hmo, dwEvent);
if (mmr)
{
DPF(0, "midiOutShortMsg(%04X,%08lX) -> %u",
(UINT)poh->hmo,
dwEvent,
(UINT)mmr);
}
else
{
DPF(2, "midiOutShortMsg(%04X,%08lX) ",
(UINT)poh->hmo,
dwEvent);
}
}
else
{
/* Data contains an already prepared long message.
* DON'T leave interrupts disabled here! Most legacy MIDI drivers
* do this synchronously.
*
*/
((LPMIDIHDR)(&pEvent->abEvent[0]))->dwUser = 1;
mmr = midiOutLongMsg(poh->hmo,
(LPMIDIHDR)(&pEvent->abEvent[0]),
sizeof(MIDIHDR));
if (mmr)
{
DPF(0, "midiOutLongMsg(%04X, %08lX, %04X) -> %u\n",
(UINT)poh->hmo,
(DWORD)(LPMIDIHDR)(&pEvent->abEvent[0]),
(UINT)sizeof(MIDIHDR),
(UINT)mmr);
}
}
/* We're done with this event; back to the free list with ya!
*
* Since we can't protect the free list with a critical section (what
* would we do if getting the critical section failed here?) we keep
* a temporary free list in the handle. Free events are moved from
* the handle to the master free list in user time.
*/
QueueAppend(&poh->qDone, pEvent);
}
LeaveCriticalSection(&poh->wCritSect);
return;
}