/****************************************************************************
 *
 *   wavedd.c
 *
 *   Multimedia kernel driver support component (mmdrv)
 *
 *   Copyright (c) 1991-1996 Microsoft Corporation
 *
 *   Driver for wave input and output devices
 *
 *   -- Wave driver entry points (wodMessage, widMessage)
 *   -- Auxiliary task (necessary for receiving Apcs and generating
 *      callbacks ASYNCRHONOUSLY)
 *   -- Interface to kernel driver (via DeviceIoControl)
 *
 *   Note that if any error occurs then the kernel device is closed
 *   and all subsequent calls requiring calls to the kernel device
 *   return error until the device is closed by the application.
 *
 *   History
 *      01-Feb-1992 - Robin Speed (RobinSp) wrote it
 *      04-Feb-1992 - SteveDav reviewed it
 *      08-Feb-1992 - RobinSp - Redesign to chop up caller's data.
 *                          Also does loops so we can remove them from the
 *                          kernel driver.
 *
 ***************************************************************************/

#include "drvlib.h"
#include <ntddwave.h>
#include <mmreg.h>

/*****************************************************************************

    internal declarations

 ****************************************************************************/

// Stack size for our auxiliary task

#define WAVE_STACK_SIZE 300

typedef enum {
    WaveThreadInvalid,
    WaveThreadAddBuffer,
    WaveThreadSetState,
    WaveThreadSetData,
    WaveThreadGetData,
    WaveThreadBreakLoop,
    WaveThreadClose,
    WaveThreadTerminate
} WAVETHREADFUNCTION;


#define MAX_BUFFER_SIZE           8192  // Largest buffer we send to device
#define MAX_WAVE_BYTES          5*8192  // Max bytes we have queued on was 22000

//
// Structure to hide our overlapped structure in so we can get some
// context on IO completion
//

typedef struct {
    OVERLAPPED Ovl;
    LPWAVEHDR WaveHdr;
} WAVEOVL, *PWAVEOVL;

// per allocation structure for wave
typedef struct tag_WAVEALLOC {
    struct tag_WAVEALLOC *Next;         // Chaining
    UINT                DeviceNumber;   // Which device
    UINT                DeviceType;     // WaveInput or WaveOutput
    DWORD_PTR           dwCallback;     // client's callback
    DWORD_PTR           dwInstance;     // client's instance data
    DWORD               dwFlags;        // Open flags
    HWAVE               hWave;          // handle for stream
    DWORD               dwStatus;       // Status bits (LowPri, etc)

    HANDLE              hDev;           // Wave device handle
    LPWAVEHDR           DeviceQueue;    // Buffers queued by application
    LPWAVEHDR           NextBuffer;     // Next buffer to send to device
    DWORD               BufferPosition; // How far we're into a large buffer
    DWORD               BytesOutstanding;
                                        // Bytes being processed by device
    LPWAVEHDR           LoopHead;       // Start of loop if any
    DWORD               LoopCount;      // Number more loops to go

    WAVEOVL             DummyWaveOvl;   // For break loop
                                                                                //
    HANDLE              Event;          // Event for driver syncrhonization
                                        // and notification of auxiliary
                                        // task operation completion.
    WAVETHREADFUNCTION  AuxFunction;    // Function for thread to perform
    union {
        LPWAVEHDR       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              AuxEvent1;      // Aux thread waits on this
    HANDLE              AuxEvent2;      // Caller of Aux thread waits on this
    HANDLE              ThreadHandle;   // Handle for thread termination ONLY
    MMRESULT            AuxReturnCode;  // Return code from Aux task
}WAVEALLOC, *PWAVEALLOC;

PWAVEALLOC WaveHandleList;              // Our chain of wave handles


//
// extra flag to track buffer completion
//

#define WHDR_COMPLETE 0x80000000


//
// Status bits for WAVEALLOC.dwStatus
//
#define  WAVEALLOC_STATUS_LOWPRIORITY   0x00000001


/*****************************************************************************

    internal function prototypes

 ****************************************************************************/

STATIC MMRESULT waveGetDevCaps(DWORD id, UINT DeviceType, LPBYTE lpCaps,
                            DWORD dwSize);
STATIC DWORD    waveThread(LPVOID lpParameter);
STATIC void     waveCleanUp(PWAVEALLOC pClient);
STATIC MMRESULT waveThreadCall(WAVETHREADFUNCTION Function, PWAVEALLOC pClient);
STATIC MMRESULT waveSetState(PWAVEALLOC pClient, ULONG State);
STATIC MMRESULT waveWrite(LPWAVEHDR pHdr, PWAVEALLOC pClient);
STATIC void     waveBlockFinished(LPWAVEHDR lpHdr, DWORD MsgId);
STATIC void     waveCallback(PWAVEALLOC pWave, DWORD msg, DWORD_PTR dw1);
STATIC void     waveCompleteBuffers(PWAVEALLOC pClient);
STATIC void     waveFreeQ(PWAVEALLOC pClient);
STATIC void     waveOvl(DWORD dwErrorCode, DWORD BytesTransferred, LPOVERLAPPED pOverlapped);

/* Attempt to pre-touch up to MIN(iSize,PRETOUCHLIMIT) bytes on from pb.
   If AllowFault then keep going to fault the stuff in.
   Otherwise stop as soon as you notice the clock ticking
*/
//PreTouch(BYTE * pb, int iSize, BOOL AllowFault)
//{
//    DWORD dwTicks = GetTickCount();
//    int pages = 0;
//    static int Headway[100];
//    static int c = 0;
//    static int TotalTouches = 0;
//    static int TimesThrough = 0;   // number of times this code has run.
//
//    if (iSize > PRETOUCHLIMIT) {
//        iSize = PRETOUCHLIMIT;
//    }
//
//    ++TimesThrough;
//
//    // pre-touch the pages but get out if it's taking too long
//    // (which probably means we took a page fault.
//    // Touch at least 2 pages as we may want 2 pages per DMA 1/2 buffer.
//    while (iSize>0) {
//        volatile BYTE b;
//        b = *pb;
//        pb += 4096;    // move to next page.  Are they ALWAYS 4096?
//        iSize -= 4096; // and count it off
//        ++pages;
//        ++TotalTouches;
//        if (dwTicks<GetTickCount() && pages>1 && !AllowFault) break;
//    }
//    Headway[c] = pages;
//    ++c;
//
//    if (c==100){
//        for (c=0; c<=99; c += 10){
//            dprintf(("%5ld %5ld %5ld %5ld %5ld %5ld %5ld %5ld %5ld %5ld",Headway[c],Headway[c+1],Headway[c+2],Headway[c+3],Headway[c+4],Headway[c+5],Headway[c+6],Headway[c+7],Headway[c+8],Headway[c+9]));
//        }
//        dprintf((" "));
//        c = 0;
//    }
//}


/****************************************************************************
 * @doc INTERNAL
 *
 * @api VOID | TerminateWave | Free all wave resources
 *
 * @rdesc None
 ***************************************************************************/
VOID TerminateWave(VOID)
{
#ifdef TERMINATE

    //
    // This is all wrong - we need to find out how to terminate threads !
    //

    PWAVEALLOC pClient;

    //
    // Remove all our threads and their resources
    //

    for (pClient = WaveHandleList; pClient != NULL; pClient = pClient->Next) {
        if (pClient->ThreadHandle) {
            //
            // Kill our thread.  But be careful !  It may
            // already have gone away - so don't wait for
            // it to set its event, just wait for it
            // to finish
            //

            //
            // Set the function code
            //
            pClient->AuxFunction = WaveThreadTerminate;

            //
            // Kick off the thread
            //
            SetEvent(pClient->AuxEvent1);

            //
            // We created our threads with mmTaskCreate so it's
            // safe to wait on them
            //
            WaitForSingleObject(pClient->ThreadHandle, INFINITE);
        }
        waveCleanUp(pClient);
    }
#endif
}

/****************************************************************************
 * @doc INTERNAL
 *
 * @api void | waveGetDevCaps | Get the device capabilities.
 *
 * @parm DWORD | id | Device id
 *
 * @parm UINT | DeviceType | type of device
 *
 * @parm LPBYTE | lpCaps | Far pointer to a WAVEOUTCAPS structure to
 *      receive the information.
 *
 * @parm DWORD | dwSize | Size of the WAVEOUTCAPS structure.
 *
 * @rdesc MMSYS.. return code.
 ***************************************************************************/
STATIC MMRESULT waveGetDevCaps(DWORD id, UINT DeviceType,
                            LPBYTE lpCaps, DWORD dwSize)
{
    MMRESULT mrc;

    if (DeviceType == WAVE_OUT) {
        WAVEOUTCAPSW wc;
        mrc = sndGetData(DeviceType, id, sizeof(wc), (LPBYTE)&wc,
                         IOCTL_WAVE_GET_CAPABILITIES);

        if (mrc != MMSYSERR_NOERROR) {
            return mrc;
        }
        InternalLoadString((UINT)*(LPDWORD)wc.szPname, wc.szPname,
                           sizeof(wc.szPname) / sizeof(WCHAR));

        CopyMemory(lpCaps, &wc, min(sizeof(wc), dwSize));
    } else {
        WAVEINCAPSW wc;
        mrc = sndGetData(DeviceType, id, sizeof(wc), (LPBYTE)&wc,
                         IOCTL_WAVE_GET_CAPABILITIES);

        if (mrc != MMSYSERR_NOERROR) {
            return mrc;
        }
        InternalLoadString((UINT)*(LPDWORD)wc.szPname, wc.szPname,
                           sizeof(wc.szPname) / sizeof(WCHAR));

        CopyMemory(lpCaps, &wc, min(sizeof(wc), dwSize));
    }

    return MMSYSERR_NOERROR;
}

/****************************************************************************
 * @doc INTERNAL
 *
 * @api DWORD | waveGetPos | Get the stream position in samples.
 *
 * @parm PWAVEALLOC | pClient | Client handle.
 *
 * @parm LPMMTIME | lpmmt | Far pointer to an MMTIME structure.
 *
 * @parm DWORD | dwSize | Size of the MMTIME structure.
 *
 * @rdesc MMSYS... return value.
 ***************************************************************************/
MMRESULT waveGetPos(PWAVEALLOC pClient, LPMMTIME lpmmt, DWORD dwSize)
{
    WAVE_DD_POSITION PositionData;
    MMRESULT mErr;

    if (dwSize < sizeof(MMTIME))
        return MMSYSERR_ERROR;

    //
    // Get the current position from the driver
    //
    mErr = sndGetHandleData(pClient->hDev,
                            sizeof(PositionData),
                            &PositionData,
                            IOCTL_WAVE_GET_POSITION,
                            pClient->Event);

    if (mErr == MMSYSERR_NOERROR) {
        if (lpmmt->wType == TIME_BYTES) {
            lpmmt->u.cb = PositionData.ByteCount;
        }

        // default is samples.
        else {
            lpmmt->wType = TIME_SAMPLES;
            lpmmt->u.sample = PositionData.SampleCount;
        }
    }

    return mErr;
}

/****************************************************************************
 * @doc INTERNAL
 *
 * @api MMRESULT | waveOpen | Open wave device and set up logical device data
 *    and auxilary task for issuing requests and servicing Apc's
 *
 * @parm WAVEDEVTYPE | DeviceType | Whether it's a wave input or output device
 *
 * @parm DWORD | id | The device logical id
 *
 * @parm DWORD | msg | Input parameter to wodMessage
 *
 * @parm DWORD | dwUser | Input parameter to wodMessage - pointer to
 *   application's handle (generated by this routine)
 *
 * @parm DWORD | dwParam1 | Input parameter to wodMessage
 *
 * @parm DWORD | dwParam2 | Input parameter to wodMessage
 *
 * @rdesc wodMessage return code.
 ***************************************************************************/

STATIC MMRESULT waveOpen(UINT  DeviceType,
                         DWORD id,
                         DWORD_PTR dwUser,
                         DWORD_PTR dwParam1,
                         DWORD_PTR dwParam2)
{
    PWAVEALLOC     pClient;  // pointer to client information structure
    MMRESULT mRet;
    BOOL Result;
    DWORD BytesReturned;
    LPWAVEFORMATEX Format;

    Format = (LPWAVEFORMATEX)((LPWAVEOPENDESC)dwParam1)->lpFormat;

    // dwParam1 contains a pointer to a WAVEOPENDESC
    // dwParam2 contains wave driver specific flags in the LOWORD
    // and generic driver flags in the HIWORD

    //
    // If it's only a query to check if the device supports our format
    // we :
    //     Open the device
    //     Test the format
    //     Close the device
    //

    if (dwParam2 & WAVE_FORMAT_QUERY) {
        HANDLE hDev;

        //
        // See if we can open our device
        // Only open for read (this should always work for our devices
        // unless there are system problems).
        //

        mRet = sndOpenDev(DeviceType,
                           id,
                           &hDev,
                           GENERIC_READ);

        if (mRet != MMSYSERR_NOERROR) {
	    D2(("drvlib: waveOpen, device=%x, QUERY failed to open",id));
            return mRet;
        }

        //
        // Check the format
        //

        Result = DeviceIoControl(
                        hDev,
                        IOCTL_WAVE_QUERY_FORMAT,
                        (PVOID)Format,
                        Format->wFormatTag == WAVE_FORMAT_PCM ?
                            sizeof(PCMWAVEFORMAT) :
                            sizeof(WAVEFORMATEX) + Format->cbSize,
                                                     // Input buffer size
                        NULL,                        // Output buffer
                        0,                           // Output buffer size
                        &BytesReturned,
                        NULL);


        //
        // Only a query so close the device
        //

        CloseHandle(hDev);
#if DBG
        {
	    MMRESULT mmr;
            mmr = Result ? MMSYSERR_NOERROR :
               GetLastError() == ERROR_NOT_SUPPORTED ? WAVERR_BADFORMAT :
                                                sndTranslateStatus();
	    D2(("drvlib: waveOpen, device=%x, QUERY returning %x",id, mmr));
	    return(mmr);
	}
#else
        return Result ? MMSYSERR_NOERROR :
               GetLastError() == ERROR_NOT_SUPPORTED ? WAVERR_BADFORMAT :
                                                sndTranslateStatus();
#endif
    }

    //
    // See if we've got this device already in our list (in
    // which case we have a thread and events for it already made)
    //

    EnterCriticalSection(&mmDrvCritSec);

    for (pClient = WaveHandleList;
         pClient != NULL;
         pClient = pClient->Next) {
        if (pClient->DeviceNumber == id &&
            pClient->DeviceType == DeviceType &&
            (!(pClient->dwStatus & WAVEALLOC_STATUS_LOWPRIORITY))  ) {

            //
            // We already have a thread and resources for this device
            //

            if (pClient->hDev != INVALID_HANDLE_VALUE) {
                //
                // Someone else is using it!
                //

                LeaveCriticalSection(&mmDrvCritSec);
                return MMSYSERR_ALLOCATED;
            }
            break;
        }
    }

    //
    // allocate my per-client structure and zero it (LPTR).
    //

    if (pClient == NULL) {
        pClient = (PWAVEALLOC)HeapAlloc(hHeap, 0, sizeof(WAVEALLOC));
        if (pClient == NULL) {
            LeaveCriticalSection(&mmDrvCritSec);
	    D3(("waveopen failing... NOMEM"));
            return MMSYSERR_NOMEM;
        }

        dprintf2(("Creating new device resource for device id %d, type %s",
                 id,
                 DeviceType == WAVE_IN ? "Wave Input" : "Wave Output"));

        memset((PVOID)pClient, 0, sizeof(WAVEALLOC));

        //  Make it look free
        pClient->hDev = INVALID_HANDLE_VALUE;

        //
        // Add it to the list
        //
        pClient->DeviceNumber = id;
        pClient->DeviceType = DeviceType;
        pClient->Next = WaveHandleList;
        WaveHandleList = pClient;
    } else {
        dprintf2(("Reusing old device resource for device id %d, type %s",
                 id,
                 DeviceType == WAVE_IN ? "Wave Input" : "Wave Output"));
    }


    //
    // and fill it with info
    //

    pClient->dwCallback  = ((LPWAVEOPENDESC)dwParam1)->dwCallback;
    pClient->dwInstance  = ((LPWAVEOPENDESC)dwParam1)->dwInstance;
    pClient->hWave       = ((LPWAVEOPENDESC)dwParam1)->hWave;
    pClient->dwFlags     = (DWORD)dwParam2;

    // pClient->hDev is initialized by sndOpenDev

    pClient->DeviceQueue = NULL;
    pClient->NextBuffer  = NULL;
    pClient->BufferPosition = 0;
    pClient->BytesOutstanding = 0;
    pClient->LoopHead    = NULL;
    pClient->LoopCount   = 0;

    //
    // See if we can open our device
    // We could get ERROR_BUSY if someone else has the device open
    // for writing.
    //

    mRet = sndOpenDev(DeviceType,
                      id,
                      &pClient->hDev,
                      (GENERIC_READ | GENERIC_WRITE));

    if (mRet != MMSYSERR_NOERROR) {

        WinAssert(pClient->hDev == INVALID_HANDLE_VALUE);
        LeaveCriticalSection(&mmDrvCritSec);
        return mRet;
    }


    //
    // make sure we can handle the format and set it.
    //

    Result = DeviceIoControl(
                 pClient->hDev,
                 IOCTL_WAVE_SET_FORMAT,
                 (PVOID)Format,
                 Format->wFormatTag == WAVE_FORMAT_PCM ?
                     sizeof(PCMWAVEFORMAT) :
                     sizeof(WAVEFORMATEX) + Format->cbSize,
                 NULL,                        // Output buffer
                 0,                           // Output buffer size
                 &BytesReturned,
                 NULL);


    if (!Result) {
        CloseHandle(pClient->hDev);
        pClient->hDev = INVALID_HANDLE_VALUE;
        LeaveCriticalSection(&mmDrvCritSec);
        return GetLastError() == ERROR_NOT_SUPPORTED ? WAVERR_BADFORMAT :
                                                sndTranslateStatus();
    }

    LeaveCriticalSection(&mmDrvCritSec);

    //
    // Create our event for synchronization with the kernel driver
    //

    if (!pClient->Event) {
        pClient->Event = CreateEvent(NULL, FALSE, FALSE, NULL);
        if (pClient->Event == NULL) {
            waveCleanUp(pClient);
            return MMSYSERR_NOMEM;
        }
        //
        // Create our event for our thread to wait on
        //

        pClient->AuxEvent1 = CreateEvent(NULL, FALSE, FALSE, NULL);
        if (!pClient->AuxEvent1) {
            waveCleanUp(pClient);
            return MMSYSERR_NOMEM;
        }
        //
        // Create our event for waiting for the auxiliary thread
        //

        pClient->AuxEvent2 = CreateEvent(NULL, FALSE, FALSE, NULL);
        if (!pClient->AuxEvent2) {
            waveCleanUp(pClient);
            return MMSYSERR_NOMEM;
        }

        //
        // Create our auxiliary thread for sending buffers to the driver
        // and collecting Apcs
        //

        mRet = mmTaskCreate((LPTASKCALLBACK)waveThread,
                            &pClient->ThreadHandle,
                            (DWORD_PTR)pClient);

        if (mRet != MMSYSERR_NOERROR) {
            waveCleanUp(pClient);
            return MMSYSERR_NOMEM;
        }

        //
        // Make sure the thread has really started
        //

        WaitForSingleObject(pClient->AuxEvent2, INFINITE);
    }

    //
    // give the client my driver dw
    //
    {
        PWAVEALLOC *pUserHandle;
        pUserHandle = (PWAVEALLOC *)dwUser;
        *pUserHandle = pClient;
    }

    //
    // sent client his OPEN callback message
    //
    waveCallback(pClient, DeviceType == WAVE_OUT ? WOM_OPEN : WIM_OPEN, 0L);

    return MMSYSERR_NOERROR;
}

/****************************************************************************
 * @doc INTERNAL
 *
 * @api void | waveCleanUp | Free resources for a wave device
 *
 * @parm PWAVEALLOC | pClient | Pointer to a WAVEALLOC 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 waveCleanUp(PWAVEALLOC pClient)
{
    EnterCriticalSection(&mmDrvCritSec);
    if (pClient->hDev != INVALID_HANDLE_VALUE) {
        CloseHandle(pClient->hDev);
        pClient->hDev = INVALID_HANDLE_VALUE;
    }
    if (pClient->AuxEvent1) {
        CloseHandle(pClient->AuxEvent1);
        pClient->AuxEvent1 = NULL;
    }
    if (pClient->AuxEvent2) {
        CloseHandle(pClient->AuxEvent2);
        pClient->AuxEvent2 = NULL;
    }
    if (pClient->Event) {
        CloseHandle(pClient->Event);
        pClient->Event = NULL;
    }

    //
    //  Clean up low priority flag or our thread will be lost forever
    //
    pClient->dwStatus = 0;

    LeaveCriticalSection(&mmDrvCritSec);
}


/****************************************************************************
 * @doc INTERNAL
 *
 * @api MMRESULT | waveWrite | Pass a new buffer to the Auxiliary thread for
 *       a wave device.
 *
 * @parm LPWAVEHDR | pHdr | Pointer to a wave buffer
 *
 * @parm PWAVEALLOC | pClient | The data associated with the logical wave
 *     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 MMRESULT waveWrite(LPWAVEHDR pHdr, PWAVEALLOC pClient)
{
    //
    // Put the request at the end of our queue.
    //
    pHdr->dwFlags |= WHDR_INQUEUE;
    pHdr->dwFlags &= ~WHDR_DONE;
    pClient->AuxParam.pHdr = pHdr;
    return waveThreadCall(WaveThreadAddBuffer, pClient);
}

/****************************************************************************
 * @doc INTERNAL
 *
 * @api MMRESULT | waveSetState | Set a wave device to a given state
 *     This function is executed on the Auxiliary thread to synchronize
 *     correctly.
 *
 * @parm PWAVEALLOC | pClient | The data associated with the logical wave
 *     output device.
 *
 * @parm ULONG | State | The new state
 *
 * @rdesc A MMSYS... type return code for the application.
 ***************************************************************************/
STATIC MMRESULT waveSetState(PWAVEALLOC pClient, ULONG State)
{
    return sndSetHandleData(pClient->hDev,
                            sizeof(State),
                            &State,
                            IOCTL_WAVE_SET_STATE,
                            pClient->Event);
}

/****************************************************************************
 * @doc INTERNAL
 *
 * @api void | waveBlockFinished | This function sets the done bit and invokes
 *     the callback function if there is one.
 *
 * @parm LPWAVEHDR | lpHdr | Far pointer to the header.
 *
 * @rdesc There is no return value.
 ***************************************************************************/
STATIC void waveBlockFinished(LPWAVEHDR lpHdr, DWORD MsgId)
{
    PWAVEALLOC pWav;

    D3(("blkfin: lpHdr = %x", lpHdr));
    // Clear our private flag
    lpHdr->dwFlags &= ~WHDR_COMPLETE;

    // We are giving the block back to the application.  The header is no
    // longer in our queue, so we reset the WHDR_INQUEUE bit.  Also, we
    // clear our driver specific bit and cauterize the lpNext pointer.
    lpHdr->dwFlags &= ~WHDR_INQUEUE;
    lpHdr->lpNext = NULL;

    pWav = (PWAVEALLOC)(lpHdr->reserved);

    // set the 'done' bit - note that some people poll this bit.
    lpHdr->dwFlags |= WHDR_DONE;

    // invoke the callback function
    waveCallback(pWav, MsgId, (DWORD_PTR)lpHdr);
}


/****************************************************************************
 * @doc INTERNAL
 *
 * @api MMRESULT | waveThreadCall | Set the function for the thread to perform
 *     and 'call' the thread using the event pair mechanism.
 *
 * @parm WAVETHREADFUNCTION | Function | The function to perform
 *
 * @parm PWAVEALLOC | 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 WaveThread().
 ***************************************************************************/
STATIC MMRESULT waveThreadCall(WAVETHREADFUNCTION Function, PWAVEALLOC pClient)
{
    //
    // Trap any failures
    //
    WinAssert(pClient->hDev != INVALID_HANDLE_VALUE);

    //
    // 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.
    //

    D3(("waveThreadCall: function==%x, return=%x", Function, pClient->AuxReturnCode));
    return pClient->AuxReturnCode;
}

/****************************************************************************
 * @doc INTERNAL
 *
 * @api MMRESULT | wavePartialApc | Called when a partial buffer is complete.
 *
 * @parm DWORD | BytesTransferred | Not relevant to us
 *
 * @parm LPOVERLAPPED | pOverLapped | Overlapped structure for this callback
 *
 * @rdesc None
 *
 * @comm The IO status block is freed and the BytesOutstanding count
 *       used to limit the buffers we have locked down is updated (we
 *       know here that parital buffers are all the same size).
 *       Also the byte count for a recording buffer is updated.
 ***************************************************************************/
STATIC void wavePartialOvl(DWORD dwErrorCode, DWORD BytesTransferred, LPOVERLAPPED pOverlapped)
{
    LPWAVEHDR pHdr;
    PWAVEALLOC pClient;

    pHdr = ((PWAVEOVL)pOverlapped)->WaveHdr;
    D3(("wavePartialOvl: pHdr = %x", pHdr));

    pClient = (PWAVEALLOC)pHdr->reserved;

    //
    // We can't trust the IO system to return our buffers in the right
    // order so we set a flag in the buffer but only complete buffers
    // at the FRONT of the queue which have the flag set.  In fact
    // we don't process the stuff here - leave that for when we
    // exit the wait because calling the client's callback can
    // do nasty things inside and Apc routine
    //

    WinAssert(pHdr->dwFlags & WHDR_INQUEUE);
    WinAssert(!(pHdr->dwFlags & WHDR_COMPLETE));

    //
    // Recalculate how many bytes are outstanding on the device
    //

    pClient->BytesOutstanding -= MAX_BUFFER_SIZE;

    //
    // Work out how much was recorded if we're a recording device
    //

    if (pClient->DeviceType == WAVE_IN) {
        pHdr->dwBytesRecorded += BytesTransferred;
    }

    //
    // Free our Iosb
    //

    HeapFree(hHeap, 0, (LPSTR)pOverlapped);

}



/****************************************************************************
 * @doc INTERNAL
 *
 * @api void | waveOvl | Called when a (user) buffer is complete.
 *
 * @parm DWORD | BytesTransferred | Not relevant to us
 *
 * @parm LPOVERLAPPED | pOverLapped | Overlapped structure for this callback
 *
 * @parm PIO_STATUS_BLOCK | The Io status block we used
 *
 * @rdesc None
 *
 * @comm The IO status block is freed and the BytesOutstanding count
 *       used to limit the buffers we have locked down is updated (we
 *       know here that parital buffers are all the same size so we
 *       can compute the size of the 'last' buffer for a given user buffer).
 *       Also the byte count for a recording buffer is updated.
 *       The user buffer is marked as 'DONE'.
 ***************************************************************************/
STATIC void waveOvl(DWORD dwErrorCode, DWORD BytesTransferred, LPOVERLAPPED pOverlapped)
{
    PWAVEHDR pHdr;
    PWAVEALLOC pClient;

    pHdr = ((PWAVEOVL)pOverlapped)->WaveHdr;
    D3(("waveOvl: pHdr = %x", pHdr));
    pClient = (PWAVEALLOC)pHdr->reserved;

    //
    // We can't trust the IO system to return our buffers in the right
    // order so we set a flag in the buffer but only complete buffers
    // at the FRONT of the queue which have the flag set.  In fact
    // we don't process the stuff here - leave that for when we
    // exit the wait because calling the client's callback can
    // do nasty things inside and Apc routine
    //

    WinAssert(pHdr->dwFlags & WHDR_INQUEUE);
    WinAssert(!(pHdr->dwFlags & WHDR_COMPLETE));

    //
    // Mark buffer as done unless we're doing more loops with it
    //
    pHdr->dwFlags |= WHDR_COMPLETE;

    //
    // It's now our duty to see if there were some old loops lying
    // around earlier in the queue which are vestiges of old loops.
    //

    if (pHdr->dwFlags & WHDR_BEGINLOOP) {
        PWAVEHDR pHdrSearch;
        for (pHdrSearch = pClient->DeviceQueue ;
             pHdrSearch != pHdr ;
             pHdrSearch = pHdrSearch->lpNext) {
            WinAssert(pHdrSearch != NULL);
            pHdrSearch->dwFlags |= WHDR_COMPLETE;
        }
    }
    //
    // Recalculate how many bytes are outstanding on the device
    //

    if (pHdr->dwBufferLength) {
        pClient->BytesOutstanding -= (pHdr->dwBufferLength - 1) %
                                         MAX_BUFFER_SIZE + 1;
    }

    //
    // Work out how much was recorded if we're a recording device
    //

    if (pClient->DeviceType == WAVE_IN) {
        pHdr->dwBytesRecorded += BytesTransferred;
    }

    //
    // Free our Iosb
    //

    HeapFree(hHeap, 0, (LPSTR)pOverlapped);

}

/****************************************************************************
 * @doc INTERNAL
 *
 * @api MMRESULT | waveLoopOvl | Called when a (user) buffer is complete.
 *                               but the buffer was need for more loops.
 *
 * @parm DWORD | BytesTransferred | Not relevant to us
 *
 * @parm LPOVERLAPPED | pOverLapped | Overlapped structure for this callback
 *
 * @rdesc None
 *
 * @comm Same as waveApc but the buffer is not marked complete.
 ***************************************************************************/
STATIC void waveLoopOvl(DWORD dwErrorCode, DWORD BytesTransferred, LPOVERLAPPED pOverlapped)
{
    DWORD dwFlags;
    PWAVEHDR pHdr;

    D3(("waveLoopOvl"));
    pHdr = ((PWAVEOVL)pOverlapped)->WaveHdr;

    //
    // Do it this way to avoid falling into a hole if the Apcs are
    // in the wrong order !!!
    //
    dwFlags = pHdr->dwFlags;
    waveOvl(dwErrorCode, BytesTransferred, pOverlapped);
    pHdr->dwFlags = dwFlags;
}

/****************************************************************************
 * @doc INTERNAL
 *
 * @api MMRESULT | waveBreakOvl | Used to chase out a buffer to break a loop.
 *
 * @parm DWORD | BytesTransferred | Not relevant to us
 *
 * @parm LPOVERLAPPED | pOverLapped | Overlapped structure for this callback
 *
 * @rdesc None
 *
 * @comm Mark the relevant buffer complete
 ***************************************************************************/
STATIC void waveBreakOvl(DWORD dwErrorCode, DWORD BytesTransferred, LPOVERLAPPED pOverlapped)
{
    D3(("waveBreakOvl"));
    ((PWAVEOVL)pOverlapped)->WaveHdr->dwFlags |= WHDR_COMPLETE;
}


/****************************************************************************
 * @doc INTERNAL
 *
 * @api void | waveStart | Send more buffers to the device if possible
 *
 * @parm PWAVEALLOC | pClient | The client's handle data
 *
 * @rdesc There is no return code.
 *
 * @comm  The routine is called both when new buffers become available
 *        or when old buffers or parital buffers are completed so
 *        that device can accept more data.
 *
 *        No more that MAX_WAVE_BYTES in buffers no bigger than
 *        MAX_BUFFER_SIZE are to be outstanding on the device at
 *        any one time.
 *
 *        An additional complication is that we have to process loops
 *        which means (among other things) that the SAME BUFFER may
 *        appear multiple times in the driver's list (as different
 *        requests).  There are no loops for input devices.
 *        Loop buffers complete with Apcs which do not complete them
 *        (except for the final loop iteration) which means that if
 *        we decide unexpectedly to finish a loop (ie by waveOutBreakLoop)
 *        we must 'chase' the loop out with an artificial buffer to
 *        get our Apc going.
 *
 ***************************************************************************/
STATIC MMRESULT waveStart(PWAVEALLOC pClient)
{
    DWORD dwSize;
    BOOL Result;

    //
    // See if we can fit any more data on the device
    //

    WinAssert(pClient->hDev != INVALID_HANDLE_VALUE);

    while (pClient->NextBuffer) {
        PWAVEHDR pHdr;

        pHdr = pClient->NextBuffer;

        WinAssert(pClient->DeviceQueue != NULL);
                WinAssert(!(pHdr->dwFlags & (WHDR_DONE | WHDR_COMPLETE)));

        dwSize = pHdr->dwBufferLength - pClient->BufferPosition;
        if (dwSize > MAX_BUFFER_SIZE) {
            dwSize = MAX_BUFFER_SIZE;
        }

        if (dwSize + pClient->BytesOutstanding <= MAX_WAVE_BYTES) {
            //
            // OK - we can fit another buffer in
            //
            // Don't have our overlay structure on the stack for an
            // ASYNCHRONOUS IO !   Otherwise the IO subsystem will overwrite
            // somebody else's data when the operation completes
            //
            PWAVEOVL pWaveOvl;
	    LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine;

            if (pClient->BufferPosition == 0) {
                //
                // Start of new buffer
                // See if the buffer is the start of a new loop
                // (Check not continuation of old one)
                //
                if (pClient->NextBuffer &&
                    (pClient->NextBuffer->dwFlags & WHDR_BEGINLOOP) &&
                    pClient->NextBuffer != pClient->LoopHead) {

                    pClient->LoopHead = pClient->NextBuffer;

                    pClient->LoopCount = pClient->NextBuffer->dwLoops;

                    //
                    // Loop count is number of times to play
                    //
                    if (pClient->LoopCount > 0) {
                        pClient->LoopCount--;
                    }
                }
                //
                // See if the loop is actually finished
                //
                if (pClient->LoopCount == 0) {
                    pClient->LoopHead = NULL;
                }

            }

            pWaveOvl = (PWAVEOVL)HeapAlloc(hHeap, 0, sizeof(*pWaveOvl));

            if (pWaveOvl == NULL) {
                return MMSYSERR_NOMEM;
            }

            memset((PVOID)pWaveOvl, 0, sizeof(*pWaveOvl));

            pWaveOvl->WaveHdr = pHdr;

	    lpCompletionRoutine = pHdr->dwBufferLength !=
                                  pClient->BufferPosition + dwSize ?
				    wavePartialOvl :
				    NULL != pClient->LoopHead ?
                                      waveLoopOvl :
                                      waveOvl;

            if (pClient->DeviceType == WAVE_OUT) {
                Result =  WriteFileEx(
                              pClient->hDev,
                                (PBYTE)pHdr->lpData +        // Output buffer
                                    pClient->BufferPosition,
                              dwSize,
                              (LPOVERLAPPED)pWaveOvl,      // Overlap structure
                              lpCompletionRoutine);            // Overlap callback
            } else {
                Result =  ReadFileEx(
                              pClient->hDev,
                                (PBYTE)pHdr->lpData +        // Output buffer
                                    pClient->BufferPosition,
                              dwSize,
                              (LPOVERLAPPED)pWaveOvl,      // Overlap structure
                              lpCompletionRoutine);            // Overlap callback
            }

            dprintf3(("Sent/Read %u wave bytes to device, return code %8X",
                     dwSize, GetLastError()));

            if (!Result && GetLastError() != ERROR_IO_PENDING) {

                //
                // Free the Iosb since we won't be getting any callbacks
                //
                HeapFree(hHeap, 0, (LPSTR)pWaveOvl);

                //
                // If the driver has not got any bytes outstanding then
                // everything may grind to a halt so release everything
                // here and notify 'completion' (ie mark all buffers
                // complete).  This is unsatisfactory but there's no
                // way of telling the application what happenend.
                //

                if (pClient->BytesOutstanding == 0) {

                    //
                    // This will cause acknowlegements to be made when
                    // waveCompleteBuffers is run
                    //
                    waveFreeQ(pClient);
                }
                return sndTranslateStatus();

            } else {
                //
                // We successfully queued the buffer
                // Update our local data
                //
                pClient->BytesOutstanding += dwSize;
                pClient->BufferPosition += dwSize;
                if (pClient->BufferPosition == pHdr->dwBufferLength) {
                    //
                    // Finished this buffer - move on to the next
                    //
                    if (!pClient->LoopHead ||
                        !(pHdr->dwFlags & WHDR_ENDLOOP)) {
                        //
                        // Not end of in a loop so we can free this buffer
                        //
                        pClient->NextBuffer = pHdr->lpNext;

                    } else {
                        //
                        // Finished a loop
                        //
                        if (pClient->LoopCount != 0) {
                            pClient->LoopCount--;
                            pClient->NextBuffer = pClient->LoopHead;
                        } else {
                            //
                            // Someone's tried to kill us.  We have
                            // to 'chase out' the start of this loop
                            // so send a dummy (NULL) packet at the
                            // back of the driver's queue
                            //

                            pClient->DummyWaveOvl.WaveHdr = pClient->LoopHead;

                            Result =
                                WriteFileEx(
                                    pClient->hDev,
                                    (PVOID)pHdr->lpData,
                                    0,
                                    &pClient->DummyWaveOvl.Ovl, // Static for async
                                    waveBreakOvl);

                            if (Result || GetLastError() == ERROR_IO_PENDING) {
                                pClient->LoopHead = NULL; // Loop complete
                                pClient->NextBuffer = pHdr->lpNext;
                            }
                        }
                    }
                    pClient->BufferPosition = 0;
                }
            }
            {
            //    /* Before we go home, let's just touch ONE page - if there is one */
            //    PBYTE pb = (PBYTE)pHdr->lpData + pClient->BufferPosition;
            //    pb = ((DWORD)pb & 0xFFFFF000) + 0x1000;  /* find page start of next page */
            //
            //    if ( (PBYTE)pHdr->lpData + pHdr->dwBufferLength > pb )
            //        PreTouch( pb, 1, FALSE);

            //    /* Before we go home, let's just try to pre-touch that which we will soon want */
            //    PreTouch( (PBYTE)pHdr->lpData + pClient->BufferPosition
            //            , pHdr->dwBufferLength - pClient->BufferPosition
            //            , FALSE
            //            );
            }

        } else {
            //
            // Cannot fit any more bytes in at the moment
            //

//            /* Before we go home, let's just try to pre-touch that which we will soon want */
//            PreTouch( (PBYTE)pHdr->lpData + pClient->BufferPosition
//                    , pHdr->dwBufferLength - pClient->BufferPosition
//                    , FALSE
//                    );

            /* NOW go home! */
            break;
        }
    }
    return MMSYSERR_NOERROR;
}


/****************************************************************************
 * @doc INTERNAL
 *
 * @api void | waveCompleteBuffers | 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
 *     bug).
 *
 * @parm PWAVEALLOC | pClient | The client's handle data
 *
 * @rdesc There is no return code.
 ***************************************************************************/
STATIC void waveCompleteBuffers(PWAVEALLOC pClient)
{
    //
    // Process buffers from the front of our queue unless we're in
    // a loop
    //

    while (pClient->DeviceQueue &&
           (pClient->DeviceQueue->dwFlags & WHDR_COMPLETE)) {

        PWAVEHDR pHdr;

        pHdr = pClient->DeviceQueue;
        //
        // Release buffer
        //
        pClient->DeviceQueue = pHdr->lpNext;


        //
        // Complete our buffer - note - this can cause another
        // buffer to be marked as complete if the client's
        // callback runs into an alertable wait.
        //

        waveBlockFinished(pHdr,
                          pClient->DeviceType == WAVE_OUT ?
                          WOM_DONE : WIM_DATA);
    }

    //
    // We might be able to start some more output at this point
    //

    waveStart(pClient);
}


/****************************************************************************
 * @doc INTERNAL
 *
 * @api void | waveFreeQ | Mark all outstanding buffers complete
 *
 * @parm PWAVEALLOC | pClient | The client's handle data
 *
 * @rdesc There is no return code.
 ***************************************************************************/
STATIC void waveFreeQ(PWAVEALLOC pClient)
{
    PWAVEHDR pHdr;
    for (pHdr = pClient->DeviceQueue;
         pHdr != NULL;
         pHdr = pHdr->lpNext) {
        pHdr->dwFlags |= WHDR_COMPLETE;
    }
        //
        // Tidy up next buffer
        //
        pClient->NextBuffer = NULL;
        pClient->BufferPosition = 0;
}

#if 0
typedef struct {
        LPBYTE Addr;
        DWORD  Len;
} PRETOUCHTHREADPARM;

/* asynchronous pre-toucher thread */
DWORD PreToucher(DWORD dw)
{
    PRETOUCHTHREADPARM * pttp;

    int iSize;
    BYTE * pb;

    pttp = (PRETOUCHTHREADPARM *) dw;
    iSize = pttp->Len;
    pb = pttp->Addr;

    LocalFree(pttp);

    while (iSize>0) {
        volatile BYTE b;
        b = *pb;
        pb += 4096;    // move to next page.  Are they ALWAYS 4096?
        iSize -= 4096; // and count it off
    }
    dprintf(("All pretouched!"));
    return 0;
}
#endif //0

/****************************************************************************
 * @doc INTERNAL
 *
 * @api DWORD | waveThread | Wave device auxiliary thread.
 *
 * @parm LPVOID | lpParameter | The thread parameter.  In our case this is a
 *     pointer to our wave device data.
 *
 * @rdesc Thread return code.
 ***************************************************************************/
STATIC DWORD waveThread(LPVOID lpParameter)
{
    PWAVEALLOC pClient;
    BOOL Terminate;
//  DWORD dwThread;                   // garbage


    Terminate = FALSE;

    pClient = (PWAVEALLOC)lpParameter;

    //
    // Set our thread to high priority so we don't fail to pass
    // new buffers to the device
    //

    SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);

    //
    // We start by waiting for something signalling that we've started
    // and waiting for something to do.
    //

    SetEvent(pClient->AuxEvent2);
    WaitForSingleObject(pClient->AuxEvent1, INFINITE);

    //
    // Now we're going
    //

    for (;;) {
        WinAssert(pClient->hDev != INVALID_HANDLE_VALUE);

        //
        // Decode function number to perform
        //

        switch (pClient->AuxFunction) {
        case WaveThreadAddBuffer:
            //
            // Intialize bytes recorded
            //
            if (pClient->DeviceType == WAVE_IN) {
                pClient->AuxParam.pHdr->dwBytesRecorded = 0;
            }

            //
            // Add the buffer to our list
            //
            {
                LPWAVEHDR *pHdrSearch;

                pClient->AuxParam.pHdr->lpNext = NULL;

                pHdrSearch = &pClient->DeviceQueue;
                while (*pHdrSearch) {
                    pHdrSearch = &(*pHdrSearch)->lpNext;
                }

                *pHdrSearch = pClient->AuxParam.pHdr;
            }
//          {
//               PRETOUCHTHREADPARM * pttp;
//
//               pttp = LocalAlloc(LMEM_FIXED,8);
//
//               if (pttp!=NULL) {
//                   pttp->Addr = pClient->AuxParam.pHdr->lpData;
//                   pttp->Len = pClient->AuxParam.pHdr->dwBufferLength;
//                   CreateThread(NULL, 0, PreToucher, pttp, 0, &dwThread);
//               }
//          }
//          Would need to declutter the system by WAITing for dead threads at some point???

            //
            // See if we can send more to the driver
            //
            if (pClient->NextBuffer == NULL) {
                pClient->NextBuffer = pClient->AuxParam.pHdr;
                pClient->BufferPosition = 0;
            }


//            /* Before we waveStart, let's just try to pre-touch that which we will soon want */
//            {
//                PWAVEHDR pHdr = pClient->NextBuffer;
//                DWORD dwTick = GetTickCount();
//                PreTouch( (PBYTE)pHdr->lpData + pClient->BufferPosition
//                        , pHdr->dwBufferLength - pClient->BufferPosition
//                        , TRUE
//                        );
//                dprintf(("pre-touched out to limit. Took %d mSec", GetTickCount()-dwTick));
//            }

            pClient->AuxReturnCode = waveStart(pClient);
            break;

        case WaveThreadSetState:
            //
            // We have to make sure at least ONE buffer gets
            // completed if we're doing input and it's input.
            //



            //
            // 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 =
                waveSetState(pClient, pClient->AuxParam.State);


            //
            // Free the rest of our buffers if we're resetting
            //

            if (pClient->AuxParam.State == WAVE_DD_RESET) {
                //
                // Cancel any loops
                //
                pClient->LoopHead = NULL;

                //
                // This function must ALWAYS succeed
                // Note that waveSetState closes the device on failure
                //
                pClient->AuxReturnCode = MMSYSERR_NOERROR;

                //
                // Check this worked (even if the driver's OK the
                // IO subsystem can fail)
                //
                WinAssert(pClient->BytesOutstanding == 0);

                //
                // Free all buffers
                //
                waveFreeQ(pClient);

            } else {
                if (pClient->DeviceType == WAVE_IN &&
                    pClient->AuxReturnCode == MMSYSERR_NOERROR) {

                    if (pClient->AuxParam.State == WAVE_DD_STOP) {
                        //
                        // We're sort of stuck - we want to complete this
                        // buffer but we've got it tied up in the device
                        // We'll reset it here although this erroneously
                        // sets the position to 0
                        //
                        if (pClient->DeviceQueue) {
                            while (!(pClient->DeviceQueue->dwFlags & WHDR_COMPLETE) &&
                                   pClient->BytesOutstanding != 0) {
                                waveSetState(pClient, WAVE_DD_RECORD);
                                pClient->AuxReturnCode =
                                    waveSetState(pClient, WAVE_DD_STOP);
                                if (pClient->AuxReturnCode != MMSYSERR_NOERROR) {
                                    break;
                                }
                            }
                            if (pClient->AuxReturnCode == MMSYSERR_NOERROR) {
                                pClient->DeviceQueue->dwFlags |= WHDR_COMPLETE;
                                                                //
                                                                // Tidy up next buffer
                                                                //
                                                                if (pClient->NextBuffer ==
                                                                    pClient->DeviceQueue) {
                                                                        pClient->NextBuffer =
                                                                            pClient->DeviceQueue->lpNext;
                                                                    pClient->BufferPosition = 0;
                                                            }
                            }
                        }
                    } else {
                        //
                        // If recording restore some buffers if necessary
                        //
                        if (pClient->AuxParam.State == WAVE_DD_RECORD) {
                            pClient->AuxReturnCode = waveStart(pClient);
                        }
                    }
                }
            }
            break;

        case WaveThreadGetData:
            {
                pClient->AuxReturnCode =
                    sndGetHandleData(pClient->hDev,
                                     pClient->AuxParam.GetSetData.DataLen,
                                     pClient->AuxParam.GetSetData.pData,
                                     pClient->AuxParam.GetSetData.Function,
                                     pClient->Event);
            }
            break;

        case WaveThreadSetData:
            {
                pClient->AuxReturnCode =
                    sndSetHandleData(pClient->hDev,
                                     pClient->AuxParam.GetSetData.DataLen,
                                     pClient->AuxParam.GetSetData.pData,
                                     pClient->AuxParam.GetSetData.Function,
                                     pClient->Event);
            }
            break;

        case WaveThreadBreakLoop:
            if (pClient->LoopHead) {
                //
                // If we're in a loop then exit the loop at the
                // end of the next iteration.
                //

                pClient->LoopCount = 0;
            }
            pClient->AuxReturnCode = MMSYSERR_NOERROR;
            break;

        case WaveThreadClose:
            //
            // Try to complete.
            // If we're completed all our buffers then we can.
            // otherwise we can't
            //
            if (pClient->DeviceQueue == NULL) {
                pClient->AuxReturnCode = MMSYSERR_NOERROR;
            } else {
                pClient->AuxReturnCode = WAVERR_STILLPLAYING;
            }
            break;

        case WaveThreadTerminate:
            Terminate = TRUE;
            break;


        default:
            WinAssert(FALSE);   // Invalid call
            break;
        }
        //
        // Trap invalid callers
        //
        pClient->AuxFunction = WaveThreadInvalid;

                //
                // See if any Apcs need completing
                //
                waveCompleteBuffers(pClient);

        //
        // Complete ? - don't set the event here.
        //
        if (Terminate) {
            return 1;
        }

        //
        // Release the thread caller
        //
        SetEvent(pClient->AuxEvent2);

        //
        // Wait for more !
        //

        while (WaitForSingleObjectEx(pClient->AuxEvent1, INFINITE, TRUE) ==
                   WAIT_IO_COMPLETION) {
                waveCompleteBuffers(pClient);
        }
    }

    return 1;      // Satisfy the compiler !
}



/****************************************************************************
 * @doc INTERNAL
 *
 * @api void | waveCallback | This calls DriverCallback for a WAVEHDR.
 *
 * @parm PWAVEALLOC | pWave | Pointer to wave device.
 *
 * @parm DWORD | msg | The message.
 *
 * @parm DWORD | dw1 | message DWORD (dw2 is always set to 0).
 *
 * @rdesc There is no return value.
 ***************************************************************************/
void waveCallback(PWAVEALLOC pWave, DWORD msg, DWORD_PTR dw1)
{

    // invoke the callback function, if it exists.  dwFlags contains
    // wave driver specific flags in the LOWORD and generic driver
    // flags in the HIWORD

    if (pWave->dwCallback)
        DriverCallback(pWave->dwCallback,       // user's callback DWORD
                       HIWORD(pWave->dwFlags),  // callback flags
                       (HDRVR)pWave->hWave,     // handle to the wave device
                       msg,                     // the message
                       pWave->dwInstance,       // user's instance data
                       dw1,                     // first DWORD
                       0L);                     // second DWORD
}



/****************************************************************************

    This function conforms to the standard Wave input driver message proc
    (widMessage), which is documented in mmddk.d.

****************************************************************************/
DWORD APIENTRY widMessage(DWORD id, DWORD msg, DWORD_PTR dwUser, DWORD_PTR dwParam1, DWORD_PTR dwParam2)
{
    PWAVEALLOC pInClient;
    MMRESULT mRet;

    switch (msg) {

        case WIDM_GETNUMDEVS:
            D2(("WIDM_GETNUMDEVS"));
            return sndGetNumDevs(WAVE_IN);

        case WIDM_GETDEVCAPS:
            D2(("WIDM_GETDEVCAPS"));
            return waveGetDevCaps(id, WAVE_IN, (LPBYTE)dwParam1,
                                  (DWORD)dwParam2);

        case WIDM_OPEN:
            D2(("WIDM_OPEN, device id==%d", id));
            return waveOpen(WAVE_IN, id, dwUser, dwParam1, dwParam2);

        case WIDM_CLOSE:
            D2(("WIDM_CLOSE, device id==%d", id));
            pInClient = (PWAVEALLOC)dwUser;

            //
            // Call our task to see if it's ready to complete
            //
            mRet = waveThreadCall(WaveThreadClose, pInClient);
            if (mRet != MMSYSERR_NOERROR) {
                return mRet;
            }

            waveCallback(pInClient, WIM_CLOSE, 0L);

            //
            // Close our device
            //
            if (pInClient->hDev != INVALID_HANDLE_VALUE) {
                CloseHandle(pInClient->hDev);
            }

            EnterCriticalSection(&mmDrvCritSec);

            /* We must set the status to 0 otherwise this thread
               will never be used again if it the
               WAVEALLOC_STATUS_LOWPRIORITY flag was set.
            */
            pInClient->dwStatus = 0;

            /*  This makes this device free
            */
            pInClient->hDev = INVALID_HANDLE_VALUE;
            LeaveCriticalSection(&mmDrvCritSec);

            return MMSYSERR_NOERROR;

        case WIDM_ADDBUFFER:
            D2(("WIDM_ADDBUFFER, device id==%d", id));
            WinAssert(dwParam1 != 0);
            WinAssert(!(((LPWAVEHDR)dwParam1)->dwFlags & ~(WHDR_INQUEUE|WHDR_DONE|WHDR_PREPARED|WHDR_BEGINLOOP|WHDR_ENDLOOP)));

            ((LPWAVEHDR)dwParam1)->dwFlags &= (WHDR_INQUEUE|WHDR_DONE|WHDR_PREPARED);

            WinAssert(((LPWAVEHDR)dwParam1)->dwFlags & WHDR_PREPARED);

            // check if it's been prepared
            if (!(((LPWAVEHDR)dwParam1)->dwFlags & WHDR_PREPARED))
                return WAVERR_UNPREPARED;

            WinAssert(!(((LPWAVEHDR)dwParam1)->dwFlags & WHDR_INQUEUE));

            // if it is already in our Q, then we cannot do this
            if ( ((LPWAVEHDR)dwParam1)->dwFlags & WHDR_INQUEUE )
                return ( WAVERR_STILLPLAYING );

            // store the pointer to my WAVEALLOC structure in the wavehdr
            pInClient = (PWAVEALLOC)dwUser;
            ((LPWAVEHDR)dwParam1)->reserved = (DWORD_PTR)(LPSTR)pInClient;

            return waveWrite((LPWAVEHDR)dwParam1, pInClient);

        case WIDM_STOP:
            D2(("WIDM_STOP, device id==%d", id));
            pInClient = (PWAVEALLOC)dwUser;
            pInClient->AuxParam.State = WAVE_DD_STOP;
            return waveThreadCall(WaveThreadSetState, pInClient);

        case WIDM_START:
            D2(("WIDM_START, device id==%d", id));
            pInClient = (PWAVEALLOC)dwUser;
            pInClient->AuxParam.State = WAVE_DD_RECORD;
            return waveThreadCall(WaveThreadSetState, pInClient);

        case WIDM_RESET:
            D2(("WIDM_RESET, device id==%d", id));
            pInClient = (PWAVEALLOC)dwUser;
            pInClient->AuxParam.State = WAVE_DD_RESET;
            return waveThreadCall(WaveThreadSetState, pInClient);

        case WIDM_GETPOS:
            D2(("WIDM_GETPOS"));
            pInClient = (PWAVEALLOC)dwUser;
            return waveGetPos(pInClient, (LPMMTIME)dwParam1, (DWORD)dwParam2);

        /*
        **  Allow WOW version of WIDM_LOWPRIORITY
        */

        case WIDM_LOWPRIORITY:
        case MAKELONG(WIDM_LOWPRIORITY, 0xFFFF):
            D2(("WIDM_LOWPRIORITY, device id==%d", id));
            pInClient = (PWAVEALLOC)dwUser;

            pInClient->dwStatus |= WAVEALLOC_STATUS_LOWPRIORITY;

            return sndSetHandleData(pInClient->hDev,
                                    0,
                                    NULL,
                                    IOCTL_WAVE_SET_LOW_PRIORITY,
                                    pInClient->Event);


        default:
            return MMSYSERR_NOTSUPPORTED;
    }

    //
    // Should not get here
    //

    WinAssert(0);
    return MMSYSERR_NOTSUPPORTED;
}

/****************************************************************************

    This function conforms to the standard Wave output driver message proc
    (wodMessage), which is documented in mmddk.h.

****************************************************************************/
DWORD APIENTRY wodMessage(DWORD id, DWORD msg, DWORD_PTR dwUser, DWORD_PTR dwParam1, DWORD_PTR dwParam2)
{
    PWAVEALLOC pOutClient;
    MMRESULT mRet;

    switch (msg) {
        case WODM_GETNUMDEVS:
            D2(("WODM_GETNUMDEVS"));
            return sndGetNumDevs(WAVE_OUT);

        case WODM_GETDEVCAPS:
            D2(("WODM_GETDEVCAPS, device id==%d", id));
            return waveGetDevCaps(id, WAVE_OUT, (LPBYTE)dwParam1,
                                  (DWORD)dwParam2);

        case WODM_OPEN:
            D2(("WODM_OPEN, device id==%d", id));
            return waveOpen(WAVE_OUT, id, dwUser, dwParam1, dwParam2);

        case WODM_CLOSE:
            D2(("WODM_CLOSE, device id==%d", id));
            pOutClient = (PWAVEALLOC)dwUser;

            //
            // Call our task to see if it's ready to complete
            //
            mRet = waveThreadCall(WaveThreadClose, pOutClient);
            if (mRet != MMSYSERR_NOERROR) {
                return mRet;
            }

            waveCallback(pOutClient, WOM_CLOSE, 0L);

            //
            // Close our device
            //
            if (pOutClient->hDev != INVALID_HANDLE_VALUE) {
                CloseHandle(pOutClient->hDev);

                EnterCriticalSection(&mmDrvCritSec);
                pOutClient->hDev = INVALID_HANDLE_VALUE;
                LeaveCriticalSection(&mmDrvCritSec);
            }

            return MMSYSERR_NOERROR;

        case WODM_WRITE:
            D3(("WODM_WRITE, device id==%d", id));
            WinAssert(dwParam1 != 0);
            WinAssert(!(((LPWAVEHDR)dwParam1)->dwFlags &
                     ~(WHDR_INQUEUE|WHDR_DONE|WHDR_PREPARED|
                       WHDR_BEGINLOOP|WHDR_ENDLOOP)));

            ((LPWAVEHDR)dwParam1)->dwFlags &=
                (WHDR_INQUEUE|WHDR_DONE|WHDR_PREPARED|
                 WHDR_BEGINLOOP|WHDR_ENDLOOP);

            WinAssert(((LPWAVEHDR)dwParam1)->dwFlags & WHDR_PREPARED);

            // check if it's been prepared
            if (!(((LPWAVEHDR)dwParam1)->dwFlags & WHDR_PREPARED))
                return WAVERR_UNPREPARED;

            WinAssert(!(((LPWAVEHDR)dwParam1)->dwFlags & WHDR_INQUEUE));

            // if it is already in our Q, then we cannot do this
            if ( ((LPWAVEHDR)dwParam1)->dwFlags & WHDR_INQUEUE )
                return ( WAVERR_STILLPLAYING );

            // store the pointer to my WAVEALLOC structure in the wavehdr
            pOutClient = (PWAVEALLOC)dwUser;
            ((LPWAVEHDR)dwParam1)->reserved = (DWORD_PTR)(LPSTR)pOutClient;

            return waveWrite((LPWAVEHDR)dwParam1, pOutClient);


        case WODM_PAUSE:
            D2(("WODM_PAUSE, device id==%d", id));
            pOutClient = (PWAVEALLOC)dwUser;
            pOutClient->AuxParam.State = WAVE_DD_STOP;
            return waveThreadCall(WaveThreadSetState, pOutClient);

        case WODM_RESTART:
            D2(("WODM_RESTART, device id==%d", id));
            pOutClient = (PWAVEALLOC)dwUser;
            pOutClient->AuxParam.State = WAVE_DD_PLAY;
            return waveThreadCall(WaveThreadSetState, pOutClient);

        case WODM_RESET:
            D2(("WODM_RESET, device id==%d", id));
            pOutClient = (PWAVEALLOC)dwUser;
            pOutClient->AuxParam.State = WAVE_DD_RESET;
            return waveThreadCall(WaveThreadSetState, pOutClient);

        case WODM_BREAKLOOP:
            pOutClient = (PWAVEALLOC)dwUser;
            D2(("WODM_BREAKLOOP, device id==%d", id));
            return waveThreadCall(WaveThreadBreakLoop, pOutClient);


        case WODM_GETPOS:
            D2(("WODM_GETPOS, device id==%d", id));
            pOutClient = (PWAVEALLOC)dwUser;
            return waveGetPos(pOutClient, (LPMMTIME)dwParam1, (DWORD)dwParam2);

        case WODM_SETPITCH:
            D2(("WODM_SETPITCH, device id==%d", id));
            pOutClient = (PWAVEALLOC)dwUser;
            pOutClient->AuxParam.GetSetData.pData = (PBYTE)&dwParam1;
            pOutClient->AuxParam.GetSetData.DataLen = sizeof(DWORD);
            pOutClient->AuxParam.GetSetData.Function = IOCTL_WAVE_SET_PITCH;
            return waveThreadCall(WaveThreadSetData, pOutClient);

        case WODM_SETVOLUME:
            D2(("WODM_SETVOLUME, device id==%d", id));
            //pOutClient = (PWAVEALLOC)dwUser;
            //pOutClient->AuxParam.GetSetData.pData = *(PBYTE *)&dwParam1;
            //pOutClient->AuxParam.GetSetData.DataLen = sizeof(DWORD);
            //pOutClient->AuxParam.GetSetData.Function = IOCTL_WAVE_SET_VOLUME;
            //return waveThreadCall(WaveThreadSetData, pOutClient);

            {
                //
                // Translate to device volume structure
                //

                WAVE_DD_VOLUME Volume;
                Volume.Left = LOWORD(dwParam1) << 16;
                Volume.Right = HIWORD(dwParam1) << 16;

                return sndSetData(WAVE_OUT, id, sizeof(Volume),
                                  (PBYTE)&Volume, IOCTL_WAVE_SET_VOLUME);
            }


        case WODM_SETPLAYBACKRATE:
            D2(("WODM_SETPLAYBACKRATE, device id==%d", id));
            pOutClient = (PWAVEALLOC)dwUser;
            pOutClient->AuxParam.GetSetData.pData = (PBYTE)&dwParam1;
            pOutClient->AuxParam.GetSetData.DataLen = sizeof(DWORD);
            pOutClient->AuxParam.GetSetData.Function =
                IOCTL_WAVE_SET_PLAYBACK_RATE;
            return waveThreadCall(WaveThreadSetData, pOutClient);

        case WODM_GETPITCH:
            D2(("WODM_GETPITCH, device id==%d", id));
            pOutClient = (PWAVEALLOC)dwUser;
            pOutClient->AuxParam.GetSetData.pData = (PBYTE)dwParam1;
            pOutClient->AuxParam.GetSetData.DataLen = sizeof(DWORD);
            pOutClient->AuxParam.GetSetData.Function = IOCTL_WAVE_GET_PITCH;
            return waveThreadCall(WaveThreadGetData, pOutClient);

        case WODM_GETVOLUME:
            D2(("WODM_GETVOLUME, device id==%d", id));
            //pOutClient = (PWAVEALLOC)dwUser;
            //pOutClient->AuxParam.GetSetData.pData = *(PBYTE *)&dwParam1;
            //pOutClient->AuxParam.GetSetData.DataLen = sizeof(DWORD);
            //pOutClient->AuxParam.GetSetData.Function = IOCTL_WAVE_GET_VOLUME;
            //return waveThreadCall(WaveThreadGetData, pOutClient);

            {
                //
                // Translate to device volume structure
                //

                WAVE_DD_VOLUME Volume;
                DWORD rc;

                rc = sndGetData(WAVE_OUT, id, sizeof(Volume),
                                (PBYTE)&Volume, IOCTL_WAVE_GET_VOLUME);

                if (rc == MMSYSERR_NOERROR) {
                    *(LPDWORD)dwParam1 =
                        (DWORD)MAKELONG(HIWORD(Volume.Left),
                                        HIWORD(Volume.Right));
                }

                return rc;
            }

        case WODM_GETPLAYBACKRATE:
            D2(("WODM_GETPLAYBACKRATE, device id==%d", id));
            pOutClient = (PWAVEALLOC)dwUser;
            pOutClient->AuxParam.GetSetData.pData = (PBYTE)dwParam1;
            pOutClient->AuxParam.GetSetData.DataLen = sizeof(DWORD);
            pOutClient->AuxParam.GetSetData.Function =
                IOCTL_WAVE_GET_PLAYBACK_RATE;
            return waveThreadCall(WaveThreadGetData, pOutClient);

        default:
            return MMSYSERR_NOTSUPPORTED;
    }

    //
    // Should not get here
    //

    WinAssert(0);
    return MMSYSERR_NOTSUPPORTED;
}