1602 lines
50 KiB
C
1602 lines
50 KiB
C
/****************************************************************************
|
||
*
|
||
* mididd.c
|
||
*
|
||
* Multimedia kernel driver support component (mmdrv)
|
||
*
|
||
* Copyright (c) 1991-1995 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 <drvlib.h>
|
||
#include <ntddmidi.h>
|
||
|
||
/*****************************************************************************
|
||
|
||
internal declarations
|
||
|
||
****************************************************************************/
|
||
|
||
//
|
||
// 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 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)
|
||
{
|
||
MMRESULT mrc;
|
||
if (DeviceType != MIDI_IN) {
|
||
MIDIOUTCAPSW mc;
|
||
mrc = sndGetData(DeviceType, id, sizeof(mc), (LPBYTE)&mc,
|
||
IOCTL_MIDI_GET_CAPABILITIES);
|
||
|
||
if (mrc != MMSYSERR_NOERROR) {
|
||
return mrc;
|
||
}
|
||
InternalLoadString((UINT)*(LPDWORD)mc.szPname, mc.szPname,
|
||
sizeof(mc.szPname) / sizeof(WCHAR));
|
||
|
||
CopyMemory(lpCaps, &mc, min(sizeof(mc), dwSize));
|
||
} else {
|
||
MIDIINCAPSW mc;
|
||
mrc = sndGetData(DeviceType, id, sizeof(mc), (LPBYTE)&mc,
|
||
IOCTL_MIDI_GET_CAPABILITIES);
|
||
|
||
if (mrc != MMSYSERR_NOERROR) {
|
||
return mrc;
|
||
}
|
||
InternalLoadString((UINT)*(LPDWORD)mc.szPname, mc.szPname,
|
||
sizeof(mc.szPname) / sizeof(WCHAR));
|
||
|
||
CopyMemory(lpCaps, &mc, min(sizeof(mc), dwSize));
|
||
}
|
||
|
||
return MMSYSERR_NOERROR;
|
||
}
|
||
|
||
|
||
/****************************************************************************
|
||
* @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 == MIDI_OUT) {
|
||
pClient = (PMIDIALLOC)HeapAlloc(hHeap, 0, sizeof(MIDIALLOC));
|
||
|
||
if (pClient != NULL) {
|
||
memset(pClient, 0, sizeof(MIDIALLOC));
|
||
}
|
||
} else {
|
||
WinAssert(DeviceType == MIDI_IN);
|
||
pClient = (PMIDIALLOC)HeapAlloc(hHeap, 0,
|
||
sizeof(struct _xx {MIDIALLOC S1; LOCALMIDIDATA S2;}));
|
||
|
||
if (pClient != NULL) {
|
||
memset(pClient, 0, sizeof(struct _xx {MIDIALLOC S1; LOCALMIDIDATA S2;}));
|
||
}
|
||
}
|
||
|
||
if (pClient == NULL) {
|
||
return MMSYSERR_NOMEM;
|
||
}
|
||
|
||
if (DeviceType == MIDI_IN) {
|
||
PLOCALMIDIDATA pMid;
|
||
int i;
|
||
pMid = pClient->Mid = (PLOCALMIDIDATA)(pClient + 1);
|
||
for (i = 0 ;i < NUMBER_OF_LOCAL_MIDI_BUFFERS ; i++) {
|
||
pMid->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 == MIDI_IN) {
|
||
|
||
//
|
||
// 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 == MIDI_OUT ? 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
|
||
|
||
//
|
||
// BUGBUG - nice to have a semaphore for some of this !
|
||
//
|
||
|
||
//
|
||
// 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 == MIDI_IN);
|
||
|
||
//
|
||
// 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 == MIDI_IN) { // 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(MIDI_IN);
|
||
|
||
case MIDM_GETDEVCAPS:
|
||
D2(("MIDM_GETDEVCAPS"));
|
||
return midiGetDevCaps(id, MIDI_IN, (LPBYTE)dwParam1,
|
||
(DWORD)dwParam2);
|
||
|
||
case MIDM_OPEN:
|
||
D2(("MIDM_OPEN"));
|
||
return midiOpen(MIDI_IN, 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(MIDI_OUT);
|
||
|
||
case MODM_GETDEVCAPS:
|
||
D2(("MODM_GETDEVCAPS"));
|
||
return midiGetDevCaps(id, MIDI_OUT, (LPBYTE)dwParam1,
|
||
(DWORD)dwParam2);
|
||
|
||
case MODM_OPEN:
|
||
D2(("MODM_OPEN"));
|
||
return midiOpen(MIDI_OUT, 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((LPBYTE)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(MIDI_OUT, 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(MIDI_OUT, 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);
|
||
}
|
||
}
|
||
|