1580 lines
49 KiB
C
1580 lines
49 KiB
C
/****************************************************************************
|
|
*
|
|
* 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 <ntddmidi.h>
|
|
|
|
/*****************************************************************************
|
|
|
|
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);
|
|
}
|
|
}
|