|
|
/* 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; }
|