/**************************************************************************** * * mididd.c * * Multimedia kernel driver support component (mmdrv) * * Copyright (c) 1991-1998 Microsoft Corporation * * Driver for midi input and output devices * * -- Midi driver entry points (modMessage, midMessage) * -- Auxiliary task (necessary for receiving Apcs and generating * callbacks ASYNCRHONOUSLY) * -- Interface to kernel driver (NtDeviceIoControlFile) * -- Midi parsing code (ported from Windows 3.1). * * History * 01-Feb-1992 - Robin Speed (RobinSp) wrote it * ***************************************************************************/ #include "mmdrv.h" #include /***************************************************************************** internal declarations ****************************************************************************/ #define D1 dprintf1 #define D2 dprintf2 #define D3 dprintf3 // // Stack size for our auxiliary task // #define MIDI_STACK_SIZE 300 #define SYSEX_ERROR 0xFF // internal error for sysex's on input // // Functions for auxiliary thread to perform // typedef enum { MidiThreadInvalid, MidiThreadAddBuffer, MidiThreadSetState, MidiThreadSetData, MidiThreadClose, MidiThreadTerminate } MIDITHREADFUNCTION; // // Our local buffers for interfacing to midi input // #define LOCAL_MIDI_DATA_SIZE 20 typedef struct _LOCALMIDIHDR { OVERLAPPED Ovl; DWORD BytesReturned; struct _LOCALMIDIHDR *lpNext; // Queueing (really debug only) BOOL Done; // Driver completed buffer PVOID pClient; // Our instance data for Apcs MIDI_DD_INPUT_DATA MidiData; // What the driver wants to process BYTE ExtraData[LOCAL_MIDI_DATA_SIZE - sizeof(ULONG)]; // The rest of our input buffer } LOCALMIDIHDR, *PLOCALMIDIHDR; // // Midi input data // #define NUMBER_OF_LOCAL_MIDI_BUFFERS 8 typedef struct { // // Static data for managing midi input // BOOL fMidiInStarted; // Do we think midi in is running ? DWORD dwMsg; // Current short msg DWORD dwCurData; // Position in long message BYTE status; // Running status byte BOOLEAN fSysex; // Processing extended message BOOLEAN Bad; // Input not working properly BYTE bBytesLeft; // Bytes left in short message BYTE bBytePos; // Position in short message DWORD dwCurTime; // Latest time from driver DWORD dwMsgTime; // Time to insert into message // in milliseconds since device // was opened PLOCALMIDIHDR DeviceQueue; // Keep track of what the device // has (debugging only) LOCALMIDIHDR // Driver interface buffers Bufs[NUMBER_OF_LOCAL_MIDI_BUFFERS];// When input is active these // are queued on the device // except while data is being // processed from them } LOCALMIDIDATA, *PLOCALMIDIDATA; // // per allocation structure for Midi // typedef struct tag_MIDIALLOC { struct tag_MIDIALLOC *Next; // Chain of devices UINT DeviceNumber; // Number of device UINT DeviceType; // MidiInput or MidiOutput DWORD_PTR dwCallback; // client's callback DWORD_PTR dwInstance; // client's instance data HMIDI hMidi; // handle for stream HANDLE hDev; // Midi device handle LPMIDIHDR lpMIQueue; // Buffers sent to device // This is only required so that // CLOSE knows when things have // really finished. // notify. This is only accessed // on the device thread and its // apcs so does not need any // synchronized access. HANDLE Event; // Event for driver syncrhonization // and notification of auxiliary // task operation completion. MIDITHREADFUNCTION AuxFunction; // Function for thread to perform union { LPMIDIHDR pHdr; // Buffer to pass in aux task ULONG State; // State to set struct { ULONG Function; // IOCTL to use PBYTE pData; // Data to set or get ULONG DataLen; // Length of data } GetSetData; } AuxParam; // 0 means terminate task. HANDLE ThreadHandle; // Handle for termination ONLY HANDLE AuxEvent1; // Aux thread waits on this HANDLE AuxEvent2; // Aux thread caller waits on this DWORD AuxReturnCode; // Return code from Aux task DWORD dwFlags; // Open flags PLOCALMIDIDATA Mid; // Extra midi input structures int l; // Helper global for modMidiLength } MIDIALLOC, *PMIDIALLOC; PMIDIALLOC MidiHandleList; // Our chain of wave handles /***************************************************************************** internal function prototypes ****************************************************************************/ STATIC DWORD midiGetDevCaps(DWORD id, UINT DeviceType, LPBYTE lpCaps, DWORD dwSize); STATIC DWORD midiThread(LPVOID lpParameter); STATIC void midiCleanUp(PMIDIALLOC pClient); STATIC DWORD midiThreadCall(MIDITHREADFUNCTION Function, PMIDIALLOC pClient); STATIC DWORD midiSetState(PMIDIALLOC pClient, ULONG State); STATIC void midiInOvl(DWORD dwRet, DWORD dwBytes, LPOVERLAPPED pOverlap); STATIC DWORD midiInWrite(LPMIDIHDR pHdr, PMIDIALLOC pClient); STATIC DWORD midiOutWrite(PBYTE pData, ULONG Len, PMIDIALLOC pClient); STATIC void midiBlockFinished(LPMIDIHDR lpHdr, DWORD MsgId); STATIC void midiCallback(PMIDIALLOC pMidi, DWORD msg, DWORD_PTR dw1, DWORD_PTR dw2); STATIC int modMIDIlength(PMIDIALLOC pClient, BYTE b); STATIC void midByteRec(PMIDIALLOC pClient, BYTE byte); STATIC void midSendPartBuffer(PMIDIALLOC pClient); STATIC void midFreeQ(PMIDIALLOC pClient); STATIC void midiFlush(PMIDIALLOC pClient); /**************************************************************************** * @doc INTERNAL * * @api VOID | TerminateMidi | Free all midi resources for mmdrv.dll * * @rdesc None ***************************************************************************/ VOID TerminateMidi(VOID) { // // Don't do any cleanup - Midi input resources cleaned up on Close. // } /**************************************************************************** * @doc INTERNAL * * @api void | midiGetDevCaps | Get the device capabilities. * * @parm DWORD | id | Device id * * @parm UINT | DeviceType | type of device * * @parm LPBYTE | lpCaps | Far pointer to a MIDIOUTCAPS structure to * receive the information. * * @parm DWORD | dwSize | Size of the MIDIOUTCAPS structure. * * @rdesc There is no return value. ***************************************************************************/ STATIC DWORD midiGetDevCaps(DWORD id, UINT DeviceType, LPBYTE lpCaps, DWORD dwSize) { return sndGetData(DeviceType, id, dwSize, lpCaps, IOCTL_MIDI_GET_CAPABILITIES); } /**************************************************************************** * @doc INTERNAL * * @api DWORD | midiOpen | Open midi device and set up logical device data * and auxilary task for issuing requests and servicing Apc's * * @parm MIDIDEVTYPE | DeviceType | Whether it's a midi input or output device * * @parm DWORD | id | The device logical id * * @parm DWORD | msg | Input parameter to modMessage * * @parm DWORD | dwUser | Input parameter to modMessage - pointer to * application's handle (generated by this routine) * * @parm DWORD | dwParam1 | Input parameter to modMessage * * @parm DWORD | dwParam2 | Input parameter to modMessage * * @rdesc modMessage return code. ***************************************************************************/ STATIC DWORD midiOpen(UINT DeviceType, DWORD id, DWORD_PTR dwUser, DWORD_PTR dwParam1, DWORD_PTR dwParam2) { PMIDIALLOC pClient; // pointer to client information structure MMRESULT mRet; // dwParam1 contains a pointer to a MIDIOPENDESC // dwParam2 contains midi driver specific flags in the LOWORD // and generic driver flags in the HIWORD // // allocate my per-client structure // if (DeviceType == MidiOutDevice) { pClient = (PMIDIALLOC)HeapAlloc(hHeap, 0, sizeof(MIDIALLOC)); if (pClient != NULL) { memset(pClient, 0, sizeof(MIDIALLOC)); } } else { WinAssert(DeviceType == MidiInDevice); pClient = (PMIDIALLOC)HeapAlloc(hHeap, 0, sizeof(struct _x{MIDIALLOC S1; LOCALMIDIDATA S2;})); if (pClient != NULL) { memset(pClient, 0, sizeof(struct _x{MIDIALLOC S1; LOCALMIDIDATA S2;})); } } if (pClient == NULL) { return MMSYSERR_NOMEM; } if (DeviceType == MidiInDevice) { int i; pClient->Mid = (PLOCALMIDIDATA)(pClient + 1); for (i = 0 ;i < NUMBER_OF_LOCAL_MIDI_BUFFERS ; i++) { pClient->Mid->Bufs[i].pClient = pClient; } } // // and fill it with info // // (note that setting everything to 0 correctly initialized our // midi input processing static data). pClient->DeviceType = DeviceType; pClient->dwCallback = ((LPMIDIOPENDESC)dwParam1)->dwCallback; pClient->dwInstance = ((LPMIDIOPENDESC)dwParam1)->dwInstance; pClient->hMidi = ((LPMIDIOPENDESC)dwParam1)->hMidi; pClient->dwFlags = (DWORD)dwParam2; // // See if we can open our device // If it's only a query be sure only to open for read, otherwise // we could get STATUS_BUSY if someone else is writing to the // device. // mRet = sndOpenDev(DeviceType, id, &pClient->hDev, (GENERIC_READ | GENERIC_WRITE)); if (mRet != MMSYSERR_NOERROR) { midiCleanUp(pClient); return mRet; } // // Create our event for syncrhonization with the device driver // pClient->Event = CreateEvent(NULL, FALSE, FALSE, NULL); if (pClient->Event == NULL) { midiCleanUp(pClient); return MMSYSERR_NOMEM; } if (DeviceType == MidiInDevice) { // // Create our event pair for synchronization with the auxiliary // thread // pClient->AuxEvent1 = CreateEvent(NULL, FALSE, FALSE, NULL); if (pClient->AuxEvent1 == NULL) { midiCleanUp(pClient); return MMSYSERR_NOMEM; } pClient->AuxEvent2 = CreateEvent(NULL, FALSE, FALSE, NULL); if (pClient->AuxEvent2 == NULL) { midiCleanUp(pClient); return MMSYSERR_NOMEM; } // // Create our auxiliary thread for sending buffers to the driver // and collecting Apcs // mRet = mmTaskCreate((LPTASKCALLBACK)midiThread, &pClient->ThreadHandle, (DWORD_PTR)pClient); if (mRet != MMSYSERR_NOERROR) { midiCleanUp(pClient); return MMSYSERR_NOMEM; } // // Make sure the thread has really started // WaitForSingleObject(pClient->AuxEvent2, INFINITE); } // // give the client my driver dw // { PMIDIALLOC *pUserHandle; pUserHandle = (PMIDIALLOC *)dwUser; *pUserHandle = pClient; } // // sent client his OPEN callback message // midiCallback(pClient, DeviceType == MidiOutDevice ? MOM_OPEN : MIM_OPEN, 0L, 0L); return MMSYSERR_NOERROR; } /**************************************************************************** * @doc INTERNAL * * @api void | midiCleanUp | Free resources for a midi device * * @parm PMIDIALLOC | pClient | Pointer to a MIDIALLOC structure describing * resources to be freed. * * @rdesc There is no return value. * * @comm If the pointer to the resource is NULL then the resource has not * been allocated. ***************************************************************************/ STATIC void midiCleanUp(PMIDIALLOC pClient) { if (pClient->hDev != INVALID_HANDLE_VALUE) { CloseHandle(pClient->hDev); } if (pClient->AuxEvent1) { CloseHandle(pClient->AuxEvent1); } if (pClient->AuxEvent2) { CloseHandle(pClient->AuxEvent2); } if (pClient->Event) { CloseHandle(pClient->Event); } HeapFree(hHeap, 0, (LPSTR)pClient); } /**************************************************************************** * @doc INTERNAL * * @api DWORD | midiOutWrite | Synchronously process a midi output * buffer. * * @parm LPMIDIHDR | pHdr | Pointer to a midi buffer * * @parm PMIDIALLOC | pClient | The data associated with the logical midi * device. * * @rdesc A MMSYS... type return code for the application. ***************************************************************************/ STATIC DWORD midiOutWrite(PBYTE pData, ULONG Len, PMIDIALLOC pClient) { DWORD BytesReturned; // // Try passing the request to our driver // We operate synchronously but allow for the driver to operate // asynchronously by waiting on an event. // if (!DeviceIoControl( pClient->hDev, IOCTL_MIDI_PLAY, (PVOID)pData, // Input buffer Len, // Input buffer size NULL, // Output buffer 0, // Output buffer size &BytesReturned, NULL)) { return sndTranslateStatus(); } return MMSYSERR_NOERROR; } /**************************************************************************** * @doc INTERNAL * * @api DWORD | midiInPutBuffer | Pass a buffer to receive midi input * * @parm LPMIDIHDR | pHdr | Pointer to a midi buffer * * @parm PMIDIALLOC | pClient | The data associated with the logical midi * device. * * @rdesc A MMSYS... type return code for the application. ***************************************************************************/ STATIC MMRESULT midiInPutBuffer(PLOCALMIDIHDR pHdr, PMIDIALLOC pClient) { DWORD BytesReturned; BOOL Result; WinAssert(!pHdr->Done); // Flag should be clear ready for setting by Apc // // midiMessages serializes these calls using ENTER_MM_HANDLE // // // Try passing the request to our driver // We operate synchronously but allow for the driver to operate // asynchronously by waiting on an event. // Result = ReadFileEx( pClient->hDev, (LPVOID)&pHdr->MidiData, sizeof(pHdr->ExtraData) + sizeof(MIDI_DD_INPUT_DATA), &pHdr->Ovl, midiInOvl); // // Put the buffer in our queue // if (Result || GetLastError() == ERROR_IO_PENDING) { PLOCALMIDIHDR *ppHdr; pHdr->lpNext = NULL; ppHdr = &pClient->Mid->DeviceQueue; while (*ppHdr) { ppHdr = &(*ppHdr)->lpNext; } *ppHdr = pHdr; return MMSYSERR_NOERROR; } return sndTranslateStatus(); } /**************************************************************************** * @doc INTERNAL * * @api DWORD | midiInWrite | Pass a new buffer to the Auxiliary thread for * a midi device. * * @parm LPMIDIHDR | pHdr | Pointer to a midit buffer * * @parm PMIDIALLOC | pClient | The data associated with the logical midi * device. * * @rdesc A MMSYS... type return code for the application. * * @comm The buffer flags are set and the buffer is passed to the auxiliary * device task for processing. ***************************************************************************/ STATIC DWORD midiInWrite(LPMIDIHDR pHdr, PMIDIALLOC pClient) { // // Put the request at the end of our queue. // pHdr->dwFlags |= MHDR_INQUEUE; pHdr->dwFlags &= ~MHDR_DONE; pClient->AuxParam.pHdr = pHdr; return midiThreadCall(MidiThreadAddBuffer, pClient); } /**************************************************************************** * @doc INTERNAL * * @api DWORD | midiSetState | Set a midi device to a given state * * @parm PMIDIALLOC | pClient | The data associated with the logical midi * output device. * * @parm ULONG | State | The new state * * @rdesc A MMSYS... type return code for the application. ***************************************************************************/ STATIC DWORD midiSetState(PMIDIALLOC pClient, ULONG State) { MMRESULT mRc; mRc = sndSetHandleData(pClient->hDev, sizeof(State), &State, IOCTL_MIDI_SET_STATE, pClient->Event); midiFlush(pClient); return mRc; } /**************************************************************************** * @doc INTERNAL * * @api DWORD | midiThreadCall | Set the function for the thread to perform * and 'call' the thread using the event pair mechanism. * * @parm MIDITHREADFUNCTION | Function | The function to perform * * @parm PMIDIALLOC | Our logical device data * * @rdesc An MMSYS... type return value suitable for returning to the * application * * @comm The AuxParam field in the device data is the 'input' to * the function processing loop in MidiThread(). ***************************************************************************/ STATIC DWORD midiThreadCall(MIDITHREADFUNCTION Function, PMIDIALLOC pClient) { // // Set the function code // pClient->AuxFunction = Function; // // Kick off the thread // SetEvent(pClient->AuxEvent1); // // Wait for it to complete // WaitForSingleObject(pClient->AuxEvent2, INFINITE); // // Return the return code that our task set. // return pClient->AuxReturnCode; } /**************************************************************************** * @doc INTERNAL * * @api void | midiInApc | Apc routine. Called when a kernel sound driver * completes processing of a midi buffer. * * @parm PVOID | ApcContext | The Apc parameter. In our case this is a * pointer to our midi device data. * * @parm PIO_STATUS_BLOCK | pIosb | Pointer to the Io status block * used for the request. * * @rdesc There is no return code. ***************************************************************************/ STATIC void midiInOvl(DWORD dwRet, DWORD dwBytesReturned, LPOVERLAPPED pOverlap) { PLOCALMIDIHDR pHdr; pHdr = ((PLOCALMIDIHDR)pOverlap); WinAssert(((PMIDIALLOC)pHdr->pClient)->DeviceType == MidiInDevice); // // Note that the buffer is complete. We don't do anything else here // because funny things happen if we call the client's callback // routine from within an Apc. // pHdr->BytesReturned = dwBytesReturned; pHdr->Done = TRUE; } /**************************************************************************** * @doc INTERNAL * * @api void | midiFlush | Buffer completion routine. This completes * the work of the Apc routine at below Apc priority. This gets * round the nasty situations arising when the user's callback * causes more apcs to run (I strongly suspect this is a kernel * but). * * @parm PMIDIALLOC | pClient | The client's handle data * * @rdesc There is no return code. ***************************************************************************/ STATIC void midiFlush(PMIDIALLOC pClient) { // // Process any completed buffers - the Apc routine // set the 'Done' flag in any completed requests. // Note that the call to the user's callback can // cause more requests to become complete // if (pClient->DeviceType == MidiInDevice) { // Output is synchronous while (pClient->Mid->DeviceQueue && pClient->Mid->DeviceQueue->Done) { PLOCALMIDIHDR pHdr; pHdr = pClient->Mid->DeviceQueue; // // Clear our flag ready for next time // pHdr->Done = FALSE; // // Take buffer off the device queue // pClient->Mid->DeviceQueue = pHdr->lpNext; // // Grab the latest time estimate - convert from 100ns units // to milliseconds // pClient->Mid->dwCurTime = (DWORD)(pHdr->MidiData.Time.QuadPart / 10000); // // Complete our buffer // if (!pClient->Mid->Bad) { int i; for (i = 0; i + sizeof(LARGE_INTEGER) < pHdr->BytesReturned; i++) { midByteRec(pClient, pHdr->MidiData.Data[i]); } // // Requeue our buffer if we're still recording // if (pClient->Mid->fMidiInStarted) { if (midiInPutBuffer(pHdr, pClient) != MMSYSERR_NOERROR) { pClient->Mid->Bad = TRUE; } } } } // End of processing completed buffers } } /**************************************************************************** * @doc INTERNAL * * @api DWORD | midiThread | Midi device auxiliary thread. * * @parm LPVOID | lpParameter | The thread parameter. In our case this is a * pointer to our midi device data. * * @rdesc Thread return code. ***************************************************************************/ STATIC DWORD midiThread(LPVOID lpParameter) { PMIDIALLOC pClient; BOOL Close; Close = FALSE; pClient = (PMIDIALLOC)lpParameter; // // Set our thread to high priority so we don't fail to pass // new buffers to the device when we get them back. Also // we don't want any gaps if callbacks are meant to play // notes just received. // SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL); // // We start notifying our creator we have started and // waiting for something to do. // SetEvent(pClient->AuxEvent2); WaitForSingleObject(pClient->AuxEvent1, INFINITE); // // Now we're going // for(;;) { // // Initialize our return code // pClient->AuxReturnCode = MMSYSERR_NOERROR; // // Decode function number to perform // switch (pClient->AuxFunction) { case MidiThreadAddBuffer: // // Add the buffer to our list to be processed // { LPMIDIHDR *pHdrSearch; pClient->AuxParam.pHdr->lpNext = NULL; pHdrSearch = &pClient->lpMIQueue; while (*pHdrSearch) { pHdrSearch = &(*pHdrSearch)->lpNext; } *pHdrSearch = pClient->AuxParam.pHdr; } break; case MidiThreadSetState: switch (pClient->AuxParam.State) { case MIDI_DD_RECORD: // // Start means we must add our buffers to the driver's list // if (!pClient->Mid->fMidiInStarted && !pClient->Mid->Bad) { int i; for (i = 0; i < NUMBER_OF_LOCAL_MIDI_BUFFERS; i++) { pClient->AuxReturnCode = midiInPutBuffer(&pClient->Mid->Bufs[i], pClient); if (pClient->AuxReturnCode != MMSYSERR_NOERROR) { // // Failed to add our buffer so give up and // get our buffers back ! // pClient->Mid->Bad = TRUE; break; } } // // Set Device state. By issuing state changes on THIS // thread the calling thread can be sure that all Apc's // generated by buffer completions will complete // BEFORE this function completes. // pClient->AuxReturnCode = midiSetState(pClient, pClient->AuxParam.State); // // If this failed then get our buffers back, // otherwise set our new state // if (pClient->AuxReturnCode != MMSYSERR_NOERROR) { pClient->Mid->Bad = TRUE; } else { pClient->Mid->fMidiInStarted = TRUE; } } else { // // Already started or bad // } break; case MIDI_DD_STOP: // // Set Device state. By issuing state changes on THIS // thread the calling thread can be sure that all Apc's // generated by buffer completions will complete // BEFORE this function completes. // if (pClient->Mid->fMidiInStarted) { pClient->Mid->fMidiInStarted = FALSE; // // RESET so we get our buffers back // pClient->AuxReturnCode = midiSetState(pClient, MIDI_DD_RESET); WinAssert(!pClient->Mid->DeviceQueue); if (pClient->AuxReturnCode == MMSYSERR_NOERROR) { midSendPartBuffer(pClient); } } break; case MIDI_DD_RESET: // // Set Device state. By issuing state changes on THIS // thread the calling thread can be sure that all Apc's // generated by buffer completions will complete // BEFORE this function completes. // if (pClient->Mid->fMidiInStarted) { pClient->Mid->fMidiInStarted = FALSE; pClient->AuxReturnCode = midiSetState(pClient, pClient->AuxParam.State); WinAssert(!pClient->Mid->DeviceQueue); if (pClient->AuxReturnCode == MMSYSERR_NOERROR) { pClient->Mid->Bad = FALSE; // Recovered !! midSendPartBuffer(pClient); } } // // We zero the input queue anyway - compatibility with // windows 3.1 // midFreeQ(pClient); break; } break; case MidiThreadSetData: { pClient->AuxReturnCode = sndSetHandleData(pClient->hDev, pClient->AuxParam.GetSetData.DataLen, pClient->AuxParam.GetSetData.pData, pClient->AuxParam.GetSetData.Function, pClient->Event); } break; case MidiThreadClose: // // Try to complete. // If we're completed all our buffers then we can. // otherwise we can't // if (pClient->lpMIQueue == NULL) { pClient->AuxReturnCode = MMSYSERR_NOERROR; Close = TRUE; } else { pClient->AuxReturnCode = MIDIERR_STILLPLAYING; } break; default: WinAssert(FALSE); // Invalid call break; } // // Trap invalid callers // pClient->AuxFunction = MidiThreadInvalid; // // See if apcs completed // midiFlush(pClient); // // Release the caller // SetEvent(pClient->AuxEvent2); // // Complete ? // if (Close) { break; } // // Wait for more ! // while (WaitForSingleObjectEx(pClient->AuxEvent1, INFINITE, TRUE) == WAIT_IO_COMPLETION) { // // Complete buffers whose Apcs ran // midiFlush(pClient); } } // // We've been asked to terminte // return 1; } /**************************************************************************** * @doc INTERNAL * * @api void | midiCallback | This calls DriverCallback for a MIDIHDR. * * @parm PMIDIALLOC | pMidi | Pointer to midi device. * * @parm DWORD | msg | The message. * * @parm DWORD | dw1 | message DWORD (dw2 is always set to 0). * * @rdesc There is no return value. ***************************************************************************/ void midiCallback(PMIDIALLOC pMidi, DWORD msg, DWORD_PTR dw1, DWORD_PTR dw2) { // invoke the callback function, if it exists. dwFlags contains // midi driver specific flags in the LOWORD and generic driver // flags in the HIWORD if (pMidi->dwCallback) DriverCallback(pMidi->dwCallback, // user's callback DWORD HIWORD(pMidi->dwFlags), // callback flags (HDRVR)pMidi->hMidi, // handle to the midi device msg, // the message pMidi->dwInstance, // user's instance data dw1, // first DWORD dw2); // second DWORD } /**************************************************************************** This function conforms to the standard Midi input driver message proc (midMessage), which is documented in mmddk.d. ****************************************************************************/ DWORD APIENTRY midMessage(DWORD id, DWORD msg, DWORD_PTR dwUser, DWORD_PTR dwParam1, DWORD_PTR dwParam2) { PMIDIALLOC pInClient; switch (msg) { case MIDM_GETNUMDEVS: D2(("MIDM_GETNUMDEVS")); return sndGetNumDevs(MidiInDevice); case MIDM_GETDEVCAPS: D2(("MIDM_GETDEVCAPS")); return midiGetDevCaps(id, MidiInDevice, (LPBYTE)dwParam1, (DWORD)dwParam2); case MIDM_OPEN: D2(("MIDM_OPEN")); return midiOpen(MidiInDevice, id, dwUser, dwParam1, dwParam2); case MIDM_CLOSE: D2(("MIDM_CLOSE")); pInClient = (PMIDIALLOC)dwUser; // // Call our task to see if it's ready to complete // if (midiThreadCall(MidiThreadClose, pInClient) != 0L) { return MIDIERR_STILLPLAYING; } // // Wait for our thread to terminate and close our device // WaitForSingleObject(pInClient->ThreadHandle, INFINITE); CloseHandle(pInClient->ThreadHandle); // // Tell the caller we're done // midiCallback(pInClient, MIM_CLOSE, 0L, 0L); midiCleanUp(pInClient); return MMSYSERR_NOERROR; case MIDM_ADDBUFFER: D2(("MIDM_ADDBUFFER")); // check if it's been prepared if (!(((LPMIDIHDR)dwParam1)->dwFlags & MHDR_PREPARED)) return MIDIERR_UNPREPARED; WinAssert(!(((LPMIDIHDR)dwParam1)->dwFlags & MHDR_INQUEUE)); // if it is already in our Q, then we cannot do this if ( ((LPMIDIHDR)dwParam1)->dwFlags & MHDR_INQUEUE ) return ( MIDIERR_STILLPLAYING ); // store the pointer to my MIDIALLOC structure in the midihdr pInClient = (PMIDIALLOC)dwUser; ((LPMIDIHDR)dwParam1)->reserved = (DWORD_PTR)(LPSTR)pInClient; return midiInWrite((LPMIDIHDR)dwParam1, pInClient); case MIDM_STOP: D2(("MIDM_PAUSE")); pInClient = (PMIDIALLOC)dwUser; pInClient->AuxParam.State = MIDI_DD_STOP; return midiThreadCall(MidiThreadSetState, pInClient); case MIDM_START: D2(("MIDM_RESTART")); pInClient = (PMIDIALLOC)dwUser; pInClient->AuxParam.State = MIDI_DD_RECORD; return midiThreadCall(MidiThreadSetState, pInClient); case MIDM_RESET: D2(("MIDM_RESET")); pInClient = (PMIDIALLOC)dwUser; pInClient->AuxParam.State = MIDI_DD_RESET; return midiThreadCall(MidiThreadSetState, pInClient); default: return MMSYSERR_NOTSUPPORTED; } // // Should not get here // WinAssert(0); return MMSYSERR_NOTSUPPORTED; } /**************************************************************************** This function conforms to the standard Midi output driver message proc (modMessage), which is documented in mmddk.d. ****************************************************************************/ DWORD APIENTRY modMessage(DWORD id, DWORD msg, DWORD_PTR dwUser, DWORD_PTR dwParam1, DWORD_PTR dwParam2) { PMIDIALLOC pOutClient; switch (msg) { case MODM_GETNUMDEVS: D2(("MODM_GETNUMDEVS")); return sndGetNumDevs(MidiOutDevice); case MODM_GETDEVCAPS: D2(("MODM_GETDEVCAPS")); return midiGetDevCaps(id, MidiOutDevice, (LPBYTE)dwParam1, (DWORD)dwParam2); case MODM_OPEN: D2(("MODM_OPEN")); return midiOpen(MidiOutDevice, id, dwUser, dwParam1, dwParam2); case MODM_CLOSE: D2(("MODM_CLOSE")); pOutClient = (PMIDIALLOC)dwUser; midiCallback(pOutClient, MOM_CLOSE, 0L, 0L); // // Close our device // midiCleanUp(pOutClient); return MMSYSERR_NOERROR; case MODM_DATA: D2(("MODM_DATA")); { int i; BYTE b[4]; for (i = 0; i < 4; i ++) { b[i] = (BYTE)(dwParam1 % 256); dwParam1 /= 256; } return midiOutWrite(b, modMIDIlength((PMIDIALLOC)dwUser, b[0]), (PMIDIALLOC)dwUser); } case MODM_LONGDATA: D2(("MODM_LONGDATA")); pOutClient = (PMIDIALLOC)dwUser; { LPMIDIHDR lpHdr; MMRESULT mRet; // // check if it's been prepared // lpHdr = (LPMIDIHDR)dwParam1; if (!(lpHdr->dwFlags & MHDR_PREPARED)) { return MIDIERR_UNPREPARED; } // // // mRet = midiOutWrite(lpHdr->lpData, lpHdr->dwBufferLength, pOutClient); // note that clearing the done bit or setting the inqueue bit // isn't necessary here since this function is synchronous - // the client will not get control back until it's done. lpHdr->dwFlags |= MHDR_DONE; // notify client if (mRet == MMSYSERR_NOERROR) { midiCallback(pOutClient, MOM_DONE, (DWORD_PTR)lpHdr, 0L); } return mRet; } case MODM_RESET: D2(("MODM_RESET")); return midiSetState((PMIDIALLOC)dwUser, MIDI_DD_RESET); case MODM_SETVOLUME: D2(("MODM_SETVOLUME")); //pOutClient = (PMIDIALLOC)dwUser; //pOutClient->AuxParam.GetSetData.pData = *(PBYTE *)&dwParam1; //pOutClient->AuxParam.GetSetData.DataLen = sizeof(DWORD); //pOutClient->AuxParam.GetSetData.Function = IOCTL_MIDI_SET_VOLUME; //return midiThreadCall(MidiThreadSetData, pOutClient); return sndSetData(MidiOutDevice, id, sizeof(DWORD), (PBYTE)&dwParam1, IOCTL_MIDI_SET_VOLUME); case MODM_GETVOLUME: D2(("MODM_GETVOLUME")); //pOutClient = (PMIDIALLOC)dwUser; //pOutClient->AuxParam.GetSetData.pData = *(PBYTE *)&dwParam1; //pOutClient->AuxParam.GetSetData.DataLen = sizeof(DWORD); //pOutClient->AuxParam.GetSetData.Function = IOCTL_MIDI_GET_VOLUME; //return midiThreadCall(MidiThreadGetData, pOutClient); return sndGetData(MidiOutDevice, id, sizeof(DWORD), (PBYTE)dwParam1, IOCTL_MIDI_GET_VOLUME); case MODM_CACHEPATCHES: D2(("MODM_CACHEPATCHES")); pOutClient = (PMIDIALLOC)dwUser; { MIDI_DD_CACHE_PATCHES AppData; DWORD BytesReturned; AppData.Bank = HIWORD(dwParam2); AppData.Flags = LOWORD(dwParam2); memcpy(AppData.Patches, (PVOID)dwParam1, sizeof(AppData.Patches)); return DeviceIoControl( pOutClient->hDev, IOCTL_MIDI_CACHE_PATCHES, (PVOID)&AppData, sizeof(AppData), NULL, 0, &BytesReturned, NULL) ? MMSYSERR_NOERROR : sndTranslateStatus(); } case MODM_CACHEDRUMPATCHES: D2(("MODM_CACHEDRUMPATCHES")); pOutClient = (PMIDIALLOC)dwUser; { MIDI_DD_CACHE_DRUM_PATCHES AppData; DWORD BytesReturned; AppData.Patch = HIWORD(dwParam2); AppData.Flags = LOWORD(dwParam2); memcpy(AppData.DrumPatches, (PVOID)dwParam1, sizeof(AppData.DrumPatches)); return DeviceIoControl( pOutClient->hDev, IOCTL_MIDI_CACHE_DRUM_PATCHES, (PVOID)&AppData, sizeof(AppData), NULL, 0, &BytesReturned, NULL) ? MMSYSERR_NOERROR : sndTranslateStatus(); } default: return MMSYSERR_NOTSUPPORTED; } // // Should not get here // WinAssert(0); return MMSYSERR_NOTSUPPORTED; } /*********************************************************************** UTILITY ROUTINES PORTED DIRECTLY FROM WIN 3.1 ***********************************************************************/ /**************************************************************************** * @doc INTERNAL * * @api int | modMIDIlength | Get the length of a short midi message. * * @parm DWORD | dwMessage | The message. * * @rdesc Returns the length of the message. ***************************************************************************/ STATIC int modMIDIlength(PMIDIALLOC pClient, BYTE b) { if (b >= 0xF8) { // system realtime /* for realtime messages, leave running status untouched */ return 1; // write one byte } switch (b) { case 0xF0: case 0xF4: case 0xF5: case 0xF6: case 0xF7: pClient->l = 1; return pClient->l; case 0xF1: case 0xF3: pClient->l = 2; return pClient->l; case 0xF2: pClient->l = 3; return pClient->l; } switch (b & 0xF0) { case 0x80: case 0x90: case 0xA0: case 0xB0: case 0xE0: pClient->l = 3; return pClient->l; case 0xC0: case 0xD0: pClient->l = 2; return pClient->l; } return (pClient->l - 1); // uses previous value if data byte (running status) } /**************************************************************************** * @doc INTERNAL * * @api void | midBufferWrite | This function writes a byte into the long * message buffer. If the buffer is full or a SYSEX_ERROR or * end-of-sysex byte is received, the buffer is marked as 'done' and * it's owner is called back. * * @parm BYTE | byte | The byte received. * * @rdesc There is no return value ***************************************************************************/ STATIC void midBufferWrite(PMIDIALLOC pClient, BYTE byte) { LPMIDIHDR lpmh; UINT msg; // if no buffers, nothing happens if (pClient->lpMIQueue == NULL) return; lpmh = pClient->lpMIQueue; if (byte == SYSEX_ERROR) { D2(("sysexerror")); msg = MIM_LONGERROR; } else { D2(("bufferwrite")); msg = MIM_LONGDATA; *((LPSTR)(lpmh->lpData) + pClient->Mid->dwCurData++) = byte; } // if end of sysex, buffer full or error, send them back the buffer if ((byte == SYSEX_ERROR) || (byte == 0xF7) || (pClient->Mid->dwCurData >= lpmh->dwBufferLength)) { D2(("bufferdone")); pClient->lpMIQueue = pClient->lpMIQueue->lpNext; lpmh->dwBytesRecorded = pClient->Mid->dwCurData; pClient->Mid->dwCurData = 0L; lpmh->dwFlags |= MHDR_DONE; lpmh->dwFlags &= ~MHDR_INQUEUE; midiCallback(pClient, msg, (DWORD_PTR)lpmh, pClient->Mid->dwMsgTime); } return; } /**************************************************************************** * @doc INTERNAL * * @api void | midByteRec | This function constructs the complete midi * messages from the individual bytes received and passes the message * to the client via his callback. * * @parm WORD | word | The byte received is in the low order byte. * * @rdesc There is no return value * * @comm Note that currently running status isn't turned off on errors. ***************************************************************************/ STATIC void midByteRec(PMIDIALLOC pClient, BYTE byte) { if (!pClient->Mid->fMidiInStarted) return; // if it's a system realtime message, send it // this does not affect running status or any current message if (byte >= 0xF8) { D2((" rt")); midiCallback(pClient, MIM_DATA, (DWORD)byte, pClient->Mid->dwCurTime); } // else if it's a system common message else if (byte >= 0xF0) { if (pClient->Mid->fSysex) { // if we're in a sysex pClient->Mid->fSysex = FALSE; // status byte during sysex ends it if (byte == 0xF7) { midBufferWrite(pClient, 0xF7); // write in long message buffer return; } else midBufferWrite(pClient, SYSEX_ERROR); // secret code indicating error } if (pClient->Mid->dwMsg) { // throw away any incomplete short data midiCallback(pClient, MIM_ERROR, pClient->Mid->dwMsg, pClient->Mid->dwMsgTime); pClient->Mid->dwMsg = 0L; } pClient->Mid->status = 0; // kill running status pClient->Mid->dwMsgTime = pClient->Mid->dwCurTime; // save timestamp switch(byte) { case 0xF0: D2((" F0")); pClient->Mid->fSysex = TRUE; midBufferWrite(pClient, 0xF0); break; case 0xF7: D2((" F7")); if (!pClient->Mid->fSysex) midiCallback(pClient, MIM_ERROR, (DWORD)byte, pClient->Mid->dwMsgTime); // else already took care of it above break; case 0xF4: // system common, no data bytes case 0xF5: case 0xF6: D2((" status0")); midiCallback(pClient, MIM_DATA, (DWORD)byte, pClient->Mid->dwMsgTime); pClient->Mid->bBytePos = 0; break; case 0xF1: // system common, one data byte case 0xF3: D2((" status1")); pClient->Mid->dwMsg |= byte; pClient->Mid->bBytesLeft = 1; pClient->Mid->bBytePos = 1; break; case 0xF2: // system common, two data bytes D2((" status2")); pClient->Mid->dwMsg |= byte; pClient->Mid->bBytesLeft = 2; pClient->Mid->bBytePos = 1; break; } } // else if it's a channel message else if (byte >= 0x80) { if (pClient->Mid->fSysex) { // if we're in a sysex pClient->Mid->fSysex = FALSE; // status byte during sysex ends it midBufferWrite(pClient, SYSEX_ERROR); // secret code indicating error } if (pClient->Mid->dwMsg) { // throw away any incomplete data midiCallback(pClient, MIM_ERROR, pClient->Mid->dwMsg, pClient->Mid->dwMsgTime); pClient->Mid->dwMsg = 0L; } pClient->Mid->status = byte; // save for running status pClient->Mid->dwMsgTime = pClient->Mid->dwCurTime; // save timestamp pClient->Mid->dwMsg |= byte; pClient->Mid->bBytePos = 1; switch(byte & 0xF0) { case 0xC0: // channel message, one data byte case 0xD0: D2((" status1")); pClient->Mid->bBytesLeft = 1; break; case 0x80: // channel message, two data bytes case 0x90: case 0xA0: case 0xB0: case 0xE0: D2((" status2")); pClient->Mid->bBytesLeft = 2; break; } } // else if it's an expected data byte for a long message else if (pClient->Mid->fSysex) { D2((" sxdata")); midBufferWrite(pClient, byte); // write in long message buffer } // else if it's an expected data byte for a short message else if (pClient->Mid->bBytePos != 0) { D2((" data")); if ((pClient->Mid->status) && (pClient->Mid->bBytePos == 1)) { // if running status pClient->Mid->dwMsg |= pClient->Mid->status; pClient->Mid->dwMsgTime = pClient->Mid->dwCurTime; // save timestamp } pClient->Mid->dwMsg += (DWORD)byte << ((pClient->Mid->bBytePos++) * 8); if (--pClient->Mid->bBytesLeft == 0) { midiCallback(pClient, MIM_DATA, pClient->Mid->dwMsg, pClient->Mid->dwMsgTime); pClient->Mid->dwMsg = 0L; if (pClient->Mid->status) { pClient->Mid->bBytesLeft = pClient->Mid->bBytePos - (BYTE)1; pClient->Mid->bBytePos = 1; } else { pClient->Mid->bBytePos = 0; } } } // else if it's an unexpected data byte else { D2((" baddata")); midiCallback(pClient, MIM_ERROR, (DWORD)byte, pClient->Mid->dwMsgTime); } return; } /**************************************************************************** * @doc INTERNAL * * @api void | midFreeQ | Free all buffers in the MIQueue. * * @comm Currently this is only called after sending off any partially filled * buffers, so all buffers here are empty. The timestamp value is 0 in * this case. * * @rdesc There is no return value. ***************************************************************************/ STATIC void midFreeQ(PMIDIALLOC pClient) { LPMIDIHDR lpH, lpN; lpH = pClient->lpMIQueue; // point to top of the queue pClient->lpMIQueue = NULL; // mark the queue as empty pClient->Mid->dwCurData = 0L; while (lpH) { lpN = lpH->lpNext; lpH->dwFlags |= MHDR_DONE; lpH->dwFlags &= ~MHDR_INQUEUE; lpH->dwBytesRecorded = 0; midiCallback(pClient, MIM_LONGDATA, (DWORD_PTR)lpH, pClient->Mid->dwCurTime); lpH = lpN; } } /**************************************************************************** * @doc INTERNAL * * @api void | midSendPartBuffer | This function is called from midStop(). * It looks at the buffer at the head of the queue and, if it contains * any data, marks it as done as sends it back to the client. * * @rdesc The return value is the number of bytes transfered. A value of zero * indicates that there was no more data in the input queue. ***************************************************************************/ STATIC void midSendPartBuffer(PMIDIALLOC pClient) { LPMIDIHDR lpH; if (pClient->lpMIQueue && pClient->Mid->dwCurData) { lpH = pClient->lpMIQueue; pClient->lpMIQueue = pClient->lpMIQueue->lpNext; lpH->dwFlags |= MHDR_DONE; lpH->dwFlags &= ~MHDR_INQUEUE; pClient->Mid->dwCurData = 0L; midiCallback(pClient, MIM_LONGERROR, (DWORD_PTR)lpH, pClient->Mid->dwMsgTime); } }