/*
 * $Id: pa_win_ds.c,v 1.1.2.1 2004/04/22 04:39:42 mbrubeck Exp $
 * Portable Audio I/O Library DirectSound implementation
 *
 * Based on the Open Source API proposed by Ross Bencina
 * Copyright (c) 1999-2002 Ross Bencina, Phil Burk
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files
 * (the "Software"), to deal in the Software without restriction,
 * including without limitation the rights to use, copy, modify, merge,
 * publish, distribute, sublicense, and/or sell copies of the Software,
 * and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * Any person wishing to distribute modifications to the Software is
 * requested to send the modifications to the original developer so that
 * they can be incorporated into the canonical version.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
 * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

/** @file

    @todo implement paInputOverflow callback status flag
    
    @todo implement paNeverDropInput.

    @todo implement host api specific extension to set i/o buffer sizes in frames

    @todo implement initialisation of PaDeviceInfo default*Latency fields (currently set to 0.)

    @todo implement ReadStream, WriteStream, GetStreamReadAvailable, GetStreamWriteAvailable

    @todo audit handling of DirectSound result codes - in many cases we could convert a HRESULT into
        a native portaudio error code. Standard DirectSound result codes are documented at msdn.

    @todo implement IsFormatSupported

    @todo implement PaDeviceInfo.defaultSampleRate;

    @todo check that CoInitialize() CoUninitialize() are always correctly
        paired, even in error cases.

    @todo call PaUtil_SetLastHostErrorInfo with a specific error string (currently just "DSound error").

    @todo make sure all buffers have been played before stopping the stream
        when the stream callback returns paComplete

    old TODOs from phil, need to work out if these have been done:
        O- fix "patest_stop.c"
*/

#include <stdio.h>
#include <string.h> /* strlen() */

#include "pa_util.h"
#include "pa_allocation.h"
#include "pa_hostapi.h"
#include "pa_stream.h"
#include "pa_cpuload.h"
#include "pa_process.h"

#include "dsound_wrapper.h"

#if (defined(WIN32) && (defined(_MSC_VER) && (_MSC_VER >= 1200))) /* MSC version 6 and above */
#pragma comment( lib, "dsound.lib" )
#pragma comment( lib, "winmm.lib" )
#endif


#define PRINT(x) /* { printf x; fflush(stdout); } */
#define ERR_RPT(x) PRINT(x)
#define DBUG(x)  /* PRINT(x) */
#define DBUGX(x) /* PRINT(x) */

#define PA_USE_HIGH_LATENCY   (0)
#if PA_USE_HIGH_LATENCY
#define PA_WIN_9X_LATENCY     (500)
#define PA_WIN_NT_LATENCY     (600)
#else
#define PA_WIN_9X_LATENCY     (140)
#define PA_WIN_NT_LATENCY     (280)
#endif

#define PA_WIN_WDM_LATENCY       (120)

#define SECONDS_PER_MSEC      (0.001)
#define MSEC_PER_SECOND       (1000)

/* prototypes for functions declared in this file */

#ifdef __cplusplus
extern "C"
{
#endif /* __cplusplus */

PaError PaWinDs_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index );

#ifdef __cplusplus
}
#endif /* __cplusplus */

static void Terminate( struct PaUtilHostApiRepresentation *hostApi );
static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
                           PaStream** s,
                           const PaStreamParameters *inputParameters,
                           const PaStreamParameters *outputParameters,
                           double sampleRate,
                           unsigned long framesPerBuffer,
                           PaStreamFlags streamFlags,
                           PaStreamCallback *streamCallback,
                           void *userData );
static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi,
                                  const PaStreamParameters *inputParameters,
                                  const PaStreamParameters *outputParameters,
                                  double sampleRate );
static PaError CloseStream( PaStream* stream );
static PaError StartStream( PaStream *stream );
static PaError StopStream( PaStream *stream );
static PaError AbortStream( PaStream *stream );
static PaError IsStreamStopped( PaStream *s );
static PaError IsStreamActive( PaStream *stream );
static PaTime GetStreamTime( PaStream *stream );
static double GetStreamCpuLoad( PaStream* stream );
static PaError ReadStream( PaStream* stream, void *buffer, unsigned long frames );
static PaError WriteStream( PaStream* stream, const void *buffer, unsigned long frames );
static signed long GetStreamReadAvailable( PaStream* stream );
static signed long GetStreamWriteAvailable( PaStream* stream );


/* FIXME: should convert hr to a string */
#define PA_DS_SET_LAST_DIRECTSOUND_ERROR( hr ) \
    PaUtil_SetLastHostErrorInfo( paDirectSound, hr, "DirectSound error" )

/************************************************* DX Prototypes **********/
static BOOL CALLBACK CollectGUIDsProc(LPGUID lpGUID,
                                     LPCTSTR lpszDesc,
                                     LPCTSTR lpszDrvName,
                                     LPVOID lpContext );

/************************************************************************************/
/********************** Structures **************************************************/
/************************************************************************************/
/* PaWinDsHostApiRepresentation - host api datastructure specific to this implementation */

typedef struct PaWinDsDeviceInfo
{
    GUID                             guid;
    GUID                            *lpGUID;
    double                           sampleRates[3];
} PaWinDsDeviceInfo;

typedef struct
{
    PaUtilHostApiRepresentation inheritedHostApiRep;
    PaUtilStreamInterface    callbackStreamInterface;
    PaUtilStreamInterface    blockingStreamInterface;

    PaUtilAllocationGroup   *allocations;

    /* implementation specific data goes here */
    PaWinDsDeviceInfo       *winDsDeviceInfos;

} PaWinDsHostApiRepresentation;

/* PaWinDsStream - a stream data structure specifically for this implementation */

typedef struct PaWinDsStream
{
    PaUtilStreamRepresentation streamRepresentation;
    PaUtilCpuLoadMeasurer cpuLoadMeasurer;
    PaUtilBufferProcessor bufferProcessor;

/* DirectSound specific data. */
    DSoundWrapper    directSoundWrapper;
    MMRESULT         timerID;
    BOOL             ifInsideCallback;  /* Test for reentrancy. */
    int              framesPerDSBuffer;
    double           framesWritten;
    double           secondsPerHostByte; /* Used to optimize latency calculation for outTime */

    PaStreamCallbackFlags callbackFlags;
    
/* FIXME - move all below to PaUtilStreamRepresentation */
    volatile int     isStarted;
    volatile int     isActive;
    volatile int     stopProcessing; /* stop thread once existing buffers have been returned */
    volatile int     abortProcessing; /* stop thread immediately */
} PaWinDsStream;


/************************************************************************************
** Duplicate the input string using the allocations allocator.
** A NULL string is converted to a zero length string.
** If memory cannot be allocated, NULL is returned.
**/
static char *DuplicateDeviceNameString( PaUtilAllocationGroup *allocations, const char* src )
{
    char *result = 0;
    
    if( src != NULL )
    {
        int len = strlen(src);
        result = (char*)PaUtil_GroupAllocateMemory( allocations, len + 1 );
        if( result )
            memcpy( (void *) result, src, len+1 );
    }
    else
    {
        result = (char*)PaUtil_GroupAllocateMemory( allocations, 1 );
        if( result )
            result[0] = '\0';
    }

    return result;
}

/************************************************************************************
** DSDeviceNameAndGUID, DSDeviceNameAndGUIDVector used for collecting preliminary
** information during device enumeration.
*/
typedef struct DSDeviceNameAndGUID{
    char *name; // allocated from parent's allocations, never deleted by this structure
    GUID guid;
    LPGUID lpGUID;
} DSDeviceNameAndGUID;

typedef struct DSDeviceNameAndGUIDVector{
    PaUtilAllocationGroup *allocations;
    PaError enumerationError;

    int count;
    int free;
    DSDeviceNameAndGUID *items; // Allocated using LocalAlloc()
} DSDeviceNameAndGUIDVector;

static PaError InitializeDSDeviceNameAndGUIDVector(
        DSDeviceNameAndGUIDVector *guidVector, PaUtilAllocationGroup *allocations )
{
    PaError result = paNoError;

    guidVector->allocations = allocations;
    guidVector->enumerationError = paNoError;

    guidVector->count = 0;
    guidVector->free = 8;
    guidVector->items = (DSDeviceNameAndGUID*)LocalAlloc( LMEM_FIXED, sizeof(DSDeviceNameAndGUID) * guidVector->free );
    if( guidVector->items == NULL )
        result = paInsufficientMemory;
    
    return result;
}

static PaError ExpandDSDeviceNameAndGUIDVector( DSDeviceNameAndGUIDVector *guidVector )
{
    PaError result = paNoError;
    DSDeviceNameAndGUID *newItems;
    int i;
    
    /* double size of vector */
    int size = guidVector->count + guidVector->free;
    guidVector->free += size;

    newItems = (DSDeviceNameAndGUID*)LocalAlloc( LMEM_FIXED, sizeof(DSDeviceNameAndGUID) * size * 2 );
    if( newItems == NULL )
    {
        result = paInsufficientMemory;
    }
    else
    {
        for( i=0; i < guidVector->count; ++i )
        {
            newItems[i].name = guidVector->items[i].name;
            if( guidVector->items[i].lpGUID == NULL )
            {
                newItems[i].lpGUID = NULL;
            }
            else
            {
                newItems[i].lpGUID = &newItems[i].guid;
                memcpy( &newItems[i].guid, guidVector->items[i].lpGUID, sizeof(GUID) );;
            }
        }

        LocalFree( guidVector->items );
        guidVector->items = newItems;
    }                                

    return result;
}

/*
    it's safe to call DSDeviceNameAndGUIDVector multiple times
*/
static PaError TerminateDSDeviceNameAndGUIDVector( DSDeviceNameAndGUIDVector *guidVector )
{
    PaError result = paNoError;

    if( guidVector->items != NULL )
    {
        if( LocalFree( guidVector->items ) != NULL )
            result = paInsufficientMemory;              /** @todo this isn't the correct error to return from a deallocation failure */

        guidVector->items = NULL;
    }

    return result;
}

/************************************************************************************
** Collect preliminary device information during DirectSound enumeration 
*/
static BOOL CALLBACK CollectGUIDsProc(LPGUID lpGUID,
                                     LPCTSTR lpszDesc,
                                     LPCTSTR lpszDrvName,
                                     LPVOID lpContext )
{
    DSDeviceNameAndGUIDVector *namesAndGUIDs = (DSDeviceNameAndGUIDVector*)lpContext;
    PaError error;

    (void) lpszDrvName; /* unused variable */

    if( namesAndGUIDs->free == 0 )
    {
        error = ExpandDSDeviceNameAndGUIDVector( namesAndGUIDs );
        if( error != paNoError )
        {
            namesAndGUIDs->enumerationError = error;
            return FALSE;
        }
    }
    
    /* Set GUID pointer, copy GUID to storage in DSDeviceNameAndGUIDVector. */
    if( lpGUID == NULL )
    {
        namesAndGUIDs->items[namesAndGUIDs->count].lpGUID = NULL;
    }
    else
    {
        namesAndGUIDs->items[namesAndGUIDs->count].lpGUID =
                &namesAndGUIDs->items[namesAndGUIDs->count].guid;
      
        memcpy( &namesAndGUIDs->items[namesAndGUIDs->count].guid, lpGUID, sizeof(GUID) );
    }

    namesAndGUIDs->items[namesAndGUIDs->count].name =
            DuplicateDeviceNameString( namesAndGUIDs->allocations, lpszDesc );
    if( namesAndGUIDs->items[namesAndGUIDs->count].name == NULL )
    {
        namesAndGUIDs->enumerationError = paInsufficientMemory;
        return FALSE;
    }

    ++namesAndGUIDs->count;
    --namesAndGUIDs->free;
    
    return TRUE;
}
              
/************************************************************************************
** Extract capabilities from an output device, and add it to the device info list
** if successful. This function assumes that there is enough room in the
** device info list to accomodate all entries.
**
** The device will not be added to the device list if any errors are encountered.
*/
static PaError AddOutputDeviceInfoFromDirectSound(
        PaWinDsHostApiRepresentation *winDsHostApi, char *name, LPGUID lpGUID )
{
    PaUtilHostApiRepresentation  *hostApi = &winDsHostApi->inheritedHostApiRep;
    PaDeviceInfo                 *deviceInfo = hostApi->deviceInfos[hostApi->info.deviceCount];
    PaWinDsDeviceInfo            *winDsDeviceInfo = &winDsHostApi->winDsDeviceInfos[hostApi->info.deviceCount];
    HRESULT                       hr;
    LPDIRECTSOUND                 lpDirectSound;
    DSCAPS                        caps;
    int                           deviceOK = TRUE;
    PaError                       result = paNoError;
    
    /* Copy GUID to the device info structure. Set pointer. */
    if( lpGUID == NULL )
    {
        winDsDeviceInfo->lpGUID = NULL;
    }
    else
    {
        memcpy( &winDsDeviceInfo->guid, lpGUID, sizeof(GUID) );
        winDsDeviceInfo->lpGUID = &winDsDeviceInfo->guid;
    }

    
    /* Create a DirectSound object for the specified GUID
        Note that using CoCreateInstance doesn't work on windows CE.
    */
    hr = dswDSoundEntryPoints.DirectSoundCreate( lpGUID, &lpDirectSound, NULL );

    /** try using CoCreateInstance because DirectSoundCreate was hanging under
        some circumstances - note this was probably related to the
        #define BOOL short bug which has now been fixed
        @todo delete this comment and the following code once we've ensured
        there is no bug.
    */
    /*
    hr = CoCreateInstance( &CLSID_DirectSound, NULL, CLSCTX_INPROC_SERVER,
            &IID_IDirectSound, (void**)&lpDirectSound );

    if( hr == S_OK )
    {
        hr = IDirectSound_Initialize( lpDirectSound, lpGUID );
    }
    */
    
    if( hr != DS_OK )
    {
        DBUG(("Cannot create DirectSound for %s. Result = 0x%x\n", name, hr ));
        deviceOK = FALSE;
    }
    else
    {
        /* Query device characteristics. */
        memset( &caps, 0, sizeof(caps) ); 
        caps.dwSize = sizeof(caps);
        hr = IDirectSound_GetCaps( lpDirectSound, &caps );
        if( hr != DS_OK )
        {
            DBUG(("Cannot GetCaps() for DirectSound device %s. Result = 0x%x\n", name, hr ));
            deviceOK = FALSE;
        }
        else
        {

#ifndef PA_NO_WMME
            if( caps.dwFlags & DSCAPS_EMULDRIVER )
            {
                /* If WMME supported, then reject Emulated drivers because they are lousy. */
                deviceOK = FALSE;
            }
#endif

            if( deviceOK )
            {
                deviceInfo->maxInputChannels = 0;
                /* Mono or stereo device? */
                deviceInfo->maxOutputChannels = ( caps.dwFlags & DSCAPS_PRIMARYSTEREO ) ? 2 : 1;

                deviceInfo->defaultLowInputLatency = 0.;    /** @todo IMPLEMENT ME */
                deviceInfo->defaultLowOutputLatency = 0.;   /** @todo IMPLEMENT ME */
                deviceInfo->defaultHighInputLatency = 0.;   /** @todo IMPLEMENT ME */
                deviceInfo->defaultHighOutputLatency = 0.;  /** @todo IMPLEMENT ME */
                deviceInfo->defaultSampleRate = 0;          /** @todo IMPLEMENT ME */


                //printf( "min %d max %d\n", caps.dwMinSecondarySampleRate, caps.dwMaxSecondarySampleRate );
                // dwFlags | DSCAPS_CONTINUOUSRATE 
            }
        }

        IDirectSound_Release( lpDirectSound );
    }

    if( deviceOK )
    {
        deviceInfo->name = name;

        if( lpGUID == NULL )
            hostApi->info.defaultOutputDevice = hostApi->info.deviceCount;
            
        hostApi->info.deviceCount++;
    }

    return result;
}


/************************************************************************************
** Extract capabilities from an input device, and add it to the device info list
** if successful. This function assumes that there is enough room in the
** device info list to accomodate all entries.
**
** The device will not be added to the device list if any errors are encountered.
*/
static PaError AddInputDeviceInfoFromDirectSoundCapture(
        PaWinDsHostApiRepresentation *winDsHostApi, char *name, LPGUID lpGUID )
{
    PaUtilHostApiRepresentation  *hostApi = &winDsHostApi->inheritedHostApiRep;
    PaDeviceInfo                 *deviceInfo = hostApi->deviceInfos[hostApi->info.deviceCount];
    PaWinDsDeviceInfo            *winDsDeviceInfo = &winDsHostApi->winDsDeviceInfos[hostApi->info.deviceCount];
    HRESULT                       hr;
    LPDIRECTSOUNDCAPTURE          lpDirectSoundCapture;
    DSCCAPS                       caps;
    int                           deviceOK = TRUE;
    PaError                       result = paNoError;
    
    /* Copy GUID to the device info structure. Set pointer. */
    if( lpGUID == NULL )
    {
        winDsDeviceInfo->lpGUID = NULL;
    }
    else
    {
        winDsDeviceInfo->lpGUID = &winDsDeviceInfo->guid;
        memcpy( &winDsDeviceInfo->guid, lpGUID, sizeof(GUID) );
    }


    hr = dswDSoundEntryPoints.DirectSoundCaptureCreate( lpGUID, &lpDirectSoundCapture, NULL );

    /** try using CoCreateInstance because DirectSoundCreate was hanging under
        some circumstances - note this was probably related to the
        #define BOOL short bug which has now been fixed
        @todo delete this comment and the following code once we've ensured
        there is no bug.
    */
    /*
    hr = CoCreateInstance( &CLSID_DirectSoundCapture, NULL, CLSCTX_INPROC_SERVER,
            &IID_IDirectSoundCapture, (void**)&lpDirectSoundCapture );
    */
    if( hr != DS_OK )
    {
        DBUG(("Cannot create Capture for %s. Result = 0x%x\n", name, hr ));
        deviceOK = FALSE;
    }
    else
    {
        /* Query device characteristics. */
        memset( &caps, 0, sizeof(caps) );
        caps.dwSize = sizeof(caps);
        hr = IDirectSoundCapture_GetCaps( lpDirectSoundCapture, &caps );
        if( hr != DS_OK )
        {
            DBUG(("Cannot GetCaps() for Capture device %s. Result = 0x%x\n", name, hr ));
            deviceOK = FALSE;
        }
        else
        {
#ifndef PA_NO_WMME
            if( caps.dwFlags & DSCAPS_EMULDRIVER )
            {
                /* If WMME supported, then reject Emulated drivers because they are lousy. */
                deviceOK = FALSE;
            }
#endif

            if( deviceOK )
            {
                deviceInfo->maxInputChannels = caps.dwChannels;
                deviceInfo->maxOutputChannels = 0;

                deviceInfo->defaultLowInputLatency = 0.;    /** @todo IMPLEMENT ME */
                deviceInfo->defaultLowOutputLatency = 0.;   /** @todo IMPLEMENT ME */
                deviceInfo->defaultHighInputLatency = 0.;   /** @todo IMPLEMENT ME */
                deviceInfo->defaultHighOutputLatency = 0.;  /** @todo IMPLEMENT ME */
                deviceInfo->defaultSampleRate = 0;          /** @todo IMPLEMENT ME */
            }
        }
        
        IDirectSoundCapture_Release( lpDirectSoundCapture );
    }

    if( deviceOK )
    {
        deviceInfo->name = name;

        if( lpGUID == NULL )
            hostApi->info.defaultInputDevice = hostApi->info.deviceCount;

        hostApi->info.deviceCount++;
    }

    return result;
}


/***********************************************************************************/
PaError PaWinDs_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex hostApiIndex )
{
    PaError result = paNoError;
    int i, deviceCount;
    PaWinDsHostApiRepresentation *winDsHostApi;
    DSDeviceNameAndGUIDVector inputNamesAndGUIDs, outputNamesAndGUIDs;
    PaDeviceInfo *deviceInfoArray;

    HRESULT hr = CoInitialize(NULL);        /** @todo: should uninitialize too */
    if( FAILED(hr) ){
        return paUnanticipatedHostError;
    }            

    /* initialise guid vectors so they can be safely deleted on error */
    inputNamesAndGUIDs.items = NULL;
    outputNamesAndGUIDs.items = NULL;

    DSW_InitializeDSoundEntryPoints();

    winDsHostApi = (PaWinDsHostApiRepresentation*)PaUtil_AllocateMemory( sizeof(PaWinDsHostApiRepresentation) );
    if( !winDsHostApi )
    {
        result = paInsufficientMemory;
        goto error;
    }

    winDsHostApi->allocations = PaUtil_CreateAllocationGroup();
    if( !winDsHostApi->allocations )
    {
        result = paInsufficientMemory;
        goto error;
    }

    *hostApi = &winDsHostApi->inheritedHostApiRep;
    (*hostApi)->info.structVersion = 1;
    (*hostApi)->info.type = paDirectSound;
    (*hostApi)->info.name = "Windows DirectSound";
    
    (*hostApi)->info.deviceCount = 0;
    (*hostApi)->info.defaultInputDevice = paNoDevice;
    (*hostApi)->info.defaultOutputDevice = paNoDevice;

    
/* DSound - enumerate devices to count them and to gather their GUIDs */


    result = InitializeDSDeviceNameAndGUIDVector( &inputNamesAndGUIDs, winDsHostApi->allocations );
    if( result != paNoError )
        goto error;

    result = InitializeDSDeviceNameAndGUIDVector( &outputNamesAndGUIDs, winDsHostApi->allocations );
    if( result != paNoError )
        goto error;

    dswDSoundEntryPoints.DirectSoundCaptureEnumerate( (LPDSENUMCALLBACK)CollectGUIDsProc, (void *)&inputNamesAndGUIDs );

    dswDSoundEntryPoints.DirectSoundEnumerate( (LPDSENUMCALLBACK)CollectGUIDsProc, (void *)&outputNamesAndGUIDs );

    if( inputNamesAndGUIDs.enumerationError != paNoError )
    {
        result = inputNamesAndGUIDs.enumerationError;
        goto error;
    }

    if( outputNamesAndGUIDs.enumerationError != paNoError )
    {
        result = outputNamesAndGUIDs.enumerationError;
        goto error;
    }

    deviceCount = inputNamesAndGUIDs.count + outputNamesAndGUIDs.count;

    if( deviceCount > 0 )
    {
        /* allocate array for pointers to PaDeviceInfo structs */
        (*hostApi)->deviceInfos = (PaDeviceInfo**)PaUtil_GroupAllocateMemory(
                winDsHostApi->allocations, sizeof(PaDeviceInfo*) * deviceCount );
        if( !(*hostApi)->deviceInfos )
        {
            result = paInsufficientMemory;
            goto error;
        }

        /* allocate all PaDeviceInfo structs in a contiguous block */
        deviceInfoArray = (PaDeviceInfo*)PaUtil_GroupAllocateMemory(
                winDsHostApi->allocations, sizeof(PaDeviceInfo) * deviceCount );
        if( !deviceInfoArray )
        {
            result = paInsufficientMemory;
            goto error;
        }

        /* allocate all DSound specific info structs in a contiguous block */
        winDsHostApi->winDsDeviceInfos = (PaWinDsDeviceInfo*)PaUtil_GroupAllocateMemory(
                winDsHostApi->allocations, sizeof(PaWinDsDeviceInfo) * deviceCount );
        if( !winDsHostApi->winDsDeviceInfos )
        {
            result = paInsufficientMemory;
            goto error;
        }

        for( i=0; i < deviceCount; ++i )
        {
            PaDeviceInfo *deviceInfo = &deviceInfoArray[i];
            deviceInfo->structVersion = 2;
            deviceInfo->hostApi = hostApiIndex;
            deviceInfo->name = 0;
            (*hostApi)->deviceInfos[i] = deviceInfo;
        }

        for( i=0; i< inputNamesAndGUIDs.count; ++i )
        {
            result = AddInputDeviceInfoFromDirectSoundCapture( winDsHostApi,
                    inputNamesAndGUIDs.items[i].name,
                    inputNamesAndGUIDs.items[i].lpGUID );
            if( result != paNoError )
                goto error;
        }

        for( i=0; i< outputNamesAndGUIDs.count; ++i )
        {
            result = AddOutputDeviceInfoFromDirectSound( winDsHostApi,
                    outputNamesAndGUIDs.items[i].name,
                    outputNamesAndGUIDs.items[i].lpGUID );
            if( result != paNoError )
                goto error;
        }
    }    

    result = TerminateDSDeviceNameAndGUIDVector( &inputNamesAndGUIDs );
    if( result != paNoError )
        goto error;

    result = TerminateDSDeviceNameAndGUIDVector( &outputNamesAndGUIDs );
    if( result != paNoError )
        goto error;

    
    (*hostApi)->Terminate = Terminate;
    (*hostApi)->OpenStream = OpenStream;
    (*hostApi)->IsFormatSupported = IsFormatSupported;

    PaUtil_InitializeStreamInterface( &winDsHostApi->callbackStreamInterface, CloseStream, StartStream,
                                      StopStream, AbortStream, IsStreamStopped, IsStreamActive,
                                      GetStreamTime, GetStreamCpuLoad,
                                      PaUtil_DummyRead, PaUtil_DummyWrite,
                                      PaUtil_DummyGetReadAvailable, PaUtil_DummyGetWriteAvailable );

    PaUtil_InitializeStreamInterface( &winDsHostApi->blockingStreamInterface, CloseStream, StartStream,
                                      StopStream, AbortStream, IsStreamStopped, IsStreamActive,
                                      GetStreamTime, PaUtil_DummyGetCpuLoad,
                                      ReadStream, WriteStream, GetStreamReadAvailable, GetStreamWriteAvailable );

    return result;

error:
    if( winDsHostApi )
    {
        if( winDsHostApi->allocations )
        {
            PaUtil_FreeAllAllocations( winDsHostApi->allocations );
            PaUtil_DestroyAllocationGroup( winDsHostApi->allocations );
        }
                
        PaUtil_FreeMemory( winDsHostApi );
    }

    TerminateDSDeviceNameAndGUIDVector( &inputNamesAndGUIDs );
    TerminateDSDeviceNameAndGUIDVector( &outputNamesAndGUIDs );

    return result;
}


/***********************************************************************************/
static void Terminate( struct PaUtilHostApiRepresentation *hostApi )
{
    PaWinDsHostApiRepresentation *winDsHostApi = (PaWinDsHostApiRepresentation*)hostApi;

    /*
        IMPLEMENT ME:
            - clean up any resources not handled by the allocation group
    */

    if( winDsHostApi->allocations )
    {
        PaUtil_FreeAllAllocations( winDsHostApi->allocations );
        PaUtil_DestroyAllocationGroup( winDsHostApi->allocations );
    }

    PaUtil_FreeMemory( winDsHostApi );

    DSW_TerminateDSoundEntryPoints();

    CoUninitialize();
}


/* Set minimal latency based on whether NT or Win95.
 * NT has higher latency.
 */
static int PaWinDS_GetMinSystemLatency( void )
{
    int minLatencyMsec;
    /* Set minimal latency based on whether NT or other OS.
     * NT has higher latency.
     */
    OSVERSIONINFO osvi;
	osvi.dwOSVersionInfoSize = sizeof( osvi );
	GetVersionEx( &osvi );
    DBUG(("PA - PlatformId = 0x%x\n", osvi.dwPlatformId ));
    DBUG(("PA - MajorVersion = 0x%x\n", osvi.dwMajorVersion ));
    DBUG(("PA - MinorVersion = 0x%x\n", osvi.dwMinorVersion ));
    /* Check for NT */
	if( (osvi.dwMajorVersion == 4) && (osvi.dwPlatformId == 2) )
	{
		minLatencyMsec = PA_WIN_NT_LATENCY;
	}
	else if(osvi.dwMajorVersion >= 5)
	{
		minLatencyMsec = PA_WIN_WDM_LATENCY;
	}
	else
	{
		minLatencyMsec = PA_WIN_9X_LATENCY;
	}
    return minLatencyMsec;
}

/***********************************************************************************/
static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi,
                                  const PaStreamParameters *inputParameters,
                                  const PaStreamParameters *outputParameters,
                                  double sampleRate )
{
    int inputChannelCount, outputChannelCount;
    PaSampleFormat inputSampleFormat, outputSampleFormat;
    
    if( inputParameters )
    {
        inputChannelCount = inputParameters->channelCount;
        inputSampleFormat = inputParameters->sampleFormat;

        /* unless alternate device specification is supported, reject the use of
            paUseHostApiSpecificDeviceSpecification */

        if( inputParameters->device == paUseHostApiSpecificDeviceSpecification )
            return paInvalidDevice;

        /* check that input device can support inputChannelCount */
        if( inputChannelCount > hostApi->deviceInfos[ inputParameters->device ]->maxInputChannels )
            return paInvalidChannelCount;

        /* validate inputStreamInfo */
        if( inputParameters->hostApiSpecificStreamInfo )
            return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */
    }
    else
    {
        inputChannelCount = 0;
    }

    if( outputParameters )
    {
        outputChannelCount = outputParameters->channelCount;
        outputSampleFormat = outputParameters->sampleFormat;
        
        /* unless alternate device specification is supported, reject the use of
            paUseHostApiSpecificDeviceSpecification */

        if( outputParameters->device == paUseHostApiSpecificDeviceSpecification )
            return paInvalidDevice;

        /* check that output device can support inputChannelCount */
        if( outputChannelCount > hostApi->deviceInfos[ outputParameters->device ]->maxOutputChannels )
            return paInvalidChannelCount;

        /* validate outputStreamInfo */
        if( outputParameters->hostApiSpecificStreamInfo )
            return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */
    }
    else
    {
        outputChannelCount = 0;
    }
    
    /*
        IMPLEMENT ME:

            - if a full duplex stream is requested, check that the combination
                of input and output parameters is supported if necessary

            - check that the device supports sampleRate

        Because the buffer adapter handles conversion between all standard
        sample formats, the following checks are only required if paCustomFormat
        is implemented, or under some other unusual conditions.

            - check that input device can support inputSampleFormat, or that
                we have the capability to convert from outputSampleFormat to
                a native format

            - check that output device can support outputSampleFormat, or that
                we have the capability to convert from outputSampleFormat to
                a native format
    */

    return paFormatIsSupported;
}


/*************************************************************************
** Determine minimum number of buffers required for this host based
** on minimum latency. Latency can be optionally set by user by setting
** an environment variable. For example, to set latency to 200 msec, put:
**
**    set PA_MIN_LATENCY_MSEC=200
**
** in the AUTOEXEC.BAT file and reboot.
** If the environment variable is not set, then the latency will be determined
** based on the OS. Windows NT has higher latency than Win95.
*/
#define PA_LATENCY_ENV_NAME  ("PA_MIN_LATENCY_MSEC")
#define PA_ENV_BUF_SIZE  (32)

static int PaWinDs_GetMinLatencyFrames( double sampleRate )
{
    char      envbuf[PA_ENV_BUF_SIZE];
    DWORD     hresult;
    int       minLatencyMsec = 0;

    /* Let user determine minimal latency by setting environment variable. */
    hresult = GetEnvironmentVariable( PA_LATENCY_ENV_NAME, envbuf, PA_ENV_BUF_SIZE );
    if( (hresult > 0) && (hresult < PA_ENV_BUF_SIZE) )
    {
        minLatencyMsec = atoi( envbuf );
    }
    else
    {
        minLatencyMsec = PaWinDS_GetMinSystemLatency();
#if PA_USE_HIGH_LATENCY
        PRINT(("PA - Minimum Latency set to %d msec!\n", minLatencyMsec ));
#endif

    }

    return (int) (minLatencyMsec * sampleRate * SECONDS_PER_MSEC);
}

/***********************************************************************************/
/* see pa_hostapi.h for a list of validity guarantees made about OpenStream parameters */

static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
                           PaStream** s,
                           const PaStreamParameters *inputParameters,
                           const PaStreamParameters *outputParameters,
                           double sampleRate,
                           unsigned long framesPerBuffer,
                           PaStreamFlags streamFlags,
                           PaStreamCallback *streamCallback,
                           void *userData )
{
    PaError result = paNoError;
    PaWinDsHostApiRepresentation *winDsHostApi = (PaWinDsHostApiRepresentation*)hostApi;
    PaWinDsStream *stream = 0;
    int inputChannelCount, outputChannelCount;
    PaSampleFormat inputSampleFormat, outputSampleFormat;
    PaSampleFormat hostInputSampleFormat, hostOutputSampleFormat;
    unsigned long suggestedInputLatencyFrames, suggestedOutputLatencyFrames;

    if( inputParameters )
    {
        inputChannelCount = inputParameters->channelCount;
        inputSampleFormat = inputParameters->sampleFormat;
        suggestedInputLatencyFrames = (unsigned long)(inputParameters->suggestedLatency * sampleRate);

        /* IDEA: the following 3 checks could be performed by default by pa_front
            unless some flag indicated otherwise */
            
        /* unless alternate device specification is supported, reject the use of
            paUseHostApiSpecificDeviceSpecification */
        if( inputParameters->device == paUseHostApiSpecificDeviceSpecification )
            return paInvalidDevice;

        /* check that input device can support inputChannelCount */
        if( inputChannelCount > hostApi->deviceInfos[ inputParameters->device ]->maxInputChannels )
            return paInvalidChannelCount;
            
        /* validate hostApiSpecificStreamInfo */
        if( inputParameters->hostApiSpecificStreamInfo )
            return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */
    }
    else
    {
        inputChannelCount = 0;
        suggestedInputLatencyFrames = 0;
    }


    if( outputParameters )
    {
        outputChannelCount = outputParameters->channelCount;
        outputSampleFormat = outputParameters->sampleFormat;
        suggestedOutputLatencyFrames = (unsigned long)(outputParameters->suggestedLatency * sampleRate);

        /* unless alternate device specification is supported, reject the use of
            paUseHostApiSpecificDeviceSpecification */
        if( outputParameters->device == paUseHostApiSpecificDeviceSpecification )
            return paInvalidDevice;

        /* check that output device can support inputChannelCount */
        if( outputChannelCount > hostApi->deviceInfos[ outputParameters->device ]->maxOutputChannels )
            return paInvalidChannelCount;

        /* validate hostApiSpecificStreamInfo */
        if( outputParameters->hostApiSpecificStreamInfo )
            return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */            
    }
    else
    {
        outputChannelCount = 0;
        suggestedOutputLatencyFrames = 0;
    }


    /*
        IMPLEMENT ME:

        ( the following two checks are taken care of by PaUtil_InitializeBufferProcessor() )

            - check that input device can support inputSampleFormat, or that
                we have the capability to convert from outputSampleFormat to
                a native format

            - check that output device can support outputSampleFormat, or that
                we have the capability to convert from outputSampleFormat to
                a native format

            - if a full duplex stream is requested, check that the combination
                of input and output parameters is supported

            - check that the device supports sampleRate

            - alter sampleRate to a close allowable rate if possible / necessary

            - validate suggestedInputLatency and suggestedOutputLatency parameters,
                use default values where necessary
    */


    /* validate platform specific flags */
    if( (streamFlags & paPlatformSpecificFlags) != 0 )
        return paInvalidFlag; /* unexpected platform specific flag */


    stream = (PaWinDsStream*)PaUtil_AllocateMemory( sizeof(PaWinDsStream) );
    if( !stream )
    {
        result = paInsufficientMemory;
        goto error;
    }

    if( streamCallback )
    {
        PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation,
                                               &winDsHostApi->callbackStreamInterface, streamCallback, userData );
    }
    else
    {
        PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation,
                                               &winDsHostApi->blockingStreamInterface, streamCallback, userData );
    }
    
    PaUtil_InitializeCpuLoadMeasurer( &stream->cpuLoadMeasurer, sampleRate );


    if( inputParameters )
    {
        /* IMPLEMENT ME - establish which  host formats are available */
        hostInputSampleFormat =
            PaUtil_SelectClosestAvailableFormat( paInt16 /* native formats */, inputParameters->sampleFormat );
    }

    if( outputParameters )
    {
        /* IMPLEMENT ME - establish which  host formats are available */
        hostOutputSampleFormat =
            PaUtil_SelectClosestAvailableFormat( paInt16 /* native formats */, outputParameters->sampleFormat );
    }
    
    result =  PaUtil_InitializeBufferProcessor( &stream->bufferProcessor,
                    inputChannelCount, inputSampleFormat, hostInputSampleFormat,
                    outputChannelCount, outputSampleFormat, hostOutputSampleFormat,
                    sampleRate, streamFlags, framesPerBuffer,
                    framesPerBuffer, /* ignored in paUtilVariableHostBufferSizePartialUsageAllowed mode. */
                /* This next mode is required because DS can split the host buffer when it wraps around. */
                    paUtilVariableHostBufferSizePartialUsageAllowed,
                    streamCallback, userData );
    if( result != paNoError )
        goto error;


    stream->streamRepresentation.streamInfo.inputLatency =
            PaUtil_GetBufferProcessorInputLatency(&stream->bufferProcessor);   /* FIXME: not initialised anywhere else */
    stream->streamRepresentation.streamInfo.outputLatency =
            PaUtil_GetBufferProcessorOutputLatency(&stream->bufferProcessor);    /* FIXME: not initialised anywhere else */
    stream->streamRepresentation.streamInfo.sampleRate = sampleRate;

    
/* DirectSound specific initialization */
    {
        HRESULT          hr;
        int              bytesPerDirectSoundBuffer;
        DSoundWrapper   *dsw;
        int              userLatencyFrames;
        int              minLatencyFrames;

        stream->timerID = 0;
        dsw = &stream->directSoundWrapper;
        DSW_Init( dsw );

    /* Get system minimum latency. */
        minLatencyFrames = PaWinDs_GetMinLatencyFrames( sampleRate );

    /* Let user override latency by passing latency parameter. */
        userLatencyFrames = (suggestedInputLatencyFrames > suggestedOutputLatencyFrames)
                    ? suggestedInputLatencyFrames
                    : suggestedOutputLatencyFrames;
        if( userLatencyFrames > 0 ) minLatencyFrames = userLatencyFrames;

    /* Calculate stream->framesPerDSBuffer depending on framesPerBuffer */
        if( framesPerBuffer == paFramesPerBufferUnspecified )
        {
        /* App support variable framesPerBuffer */
            stream->framesPerDSBuffer = minLatencyFrames;

            stream->streamRepresentation.streamInfo.outputLatency = (double)(minLatencyFrames - 1) / sampleRate;
        }
        else
        {
        /* Round up to number of buffers needed to guarantee that latency. */
            int numUserBuffers = (minLatencyFrames + framesPerBuffer - 1) / framesPerBuffer;
            if( numUserBuffers < 1 ) numUserBuffers = 1;
            numUserBuffers += 1; /* So we have latency worth of buffers ahead of current buffer. */
            stream->framesPerDSBuffer = framesPerBuffer * numUserBuffers;

            stream->streamRepresentation.streamInfo.outputLatency = (double)(framesPerBuffer * (numUserBuffers-1)) / sampleRate;
        }

        {
            /** @todo REVIEW: this calculation seems incorrect to me - rossb. */
            int msecLatency = (int) ((stream->framesPerDSBuffer * MSEC_PER_SECOND) / sampleRate);
            PRINT(("PortAudio on DirectSound - Latency = %d frames, %d msec\n", stream->framesPerDSBuffer, msecLatency ));
        }


        /* ------------------ OUTPUT */
        if( outputParameters )
        {
            /*
            PaDeviceInfo *deviceInfo = hostApi->deviceInfos[ outputParameters->device ];
            DBUG(("PaHost_OpenStream: deviceID = 0x%x\n", outputParameters->device));
            */
            
            bytesPerDirectSoundBuffer = stream->framesPerDSBuffer * outputParameters->channelCount * sizeof(short);
            if( bytesPerDirectSoundBuffer < DSBSIZE_MIN )
            {
                result = paBufferTooSmall;
                goto error;
            }
            else if( bytesPerDirectSoundBuffer > DSBSIZE_MAX )
            {
                result = paBufferTooBig;
                goto error;
            }


            hr = dswDSoundEntryPoints.DirectSoundCreate( winDsHostApi->winDsDeviceInfos[outputParameters->device].lpGUID,
                &dsw->dsw_pDirectSound,   NULL );
            if( hr != DS_OK )
            {
                ERR_RPT(("PortAudio: DirectSoundCreate() failed!\n"));
                result = paUnanticipatedHostError;
                PA_DS_SET_LAST_DIRECTSOUND_ERROR( hr );
                goto error;
            }
            hr = DSW_InitOutputBuffer( dsw,
                                       (unsigned long) (sampleRate + 0.5),
                                       (WORD)outputParameters->channelCount, bytesPerDirectSoundBuffer );
            DBUG(("DSW_InitOutputBuffer() returns %x\n", hr));
            if( hr != DS_OK )
            {
                result = paUnanticipatedHostError;
                PA_DS_SET_LAST_DIRECTSOUND_ERROR( hr );
                goto error;
            }
            /* Calculate value used in latency calculation to avoid real-time divides. */
            stream->secondsPerHostByte = 1.0 /
                (stream->bufferProcessor.bytesPerHostOutputSample *
                outputChannelCount * sampleRate);
        }

        /* ------------------ INPUT */
        if( inputParameters )
        {
            /*
            PaDeviceInfo *deviceInfo = hostApi->deviceInfos[ inputParameters->device ];
            DBUG(("PaHost_OpenStream: deviceID = 0x%x\n", inputParameters->device));
            */
            
            bytesPerDirectSoundBuffer = stream->framesPerDSBuffer * inputParameters->channelCount * sizeof(short);
            if( bytesPerDirectSoundBuffer < DSBSIZE_MIN )
            {
                result = paBufferTooSmall;
                goto error;
            }
            else if( bytesPerDirectSoundBuffer > DSBSIZE_MAX )
            {
                result = paBufferTooBig;
                goto error;
            }

            hr = dswDSoundEntryPoints.DirectSoundCaptureCreate( winDsHostApi->winDsDeviceInfos[inputParameters->device].lpGUID,
                &dsw->dsw_pDirectSoundCapture,   NULL );
            if( hr != DS_OK )
            {
                ERR_RPT(("PortAudio: DirectSoundCaptureCreate() failed!\n"));
                result = paUnanticipatedHostError;
                PA_DS_SET_LAST_DIRECTSOUND_ERROR( hr );
                goto error;
            }
            hr = DSW_InitInputBuffer( dsw,
                                      (unsigned long) (sampleRate + 0.5),
                                      (WORD)inputParameters->channelCount, bytesPerDirectSoundBuffer );
            DBUG(("DSW_InitInputBuffer() returns %x\n", hr));
            if( hr != DS_OK )
            {
                ERR_RPT(("PortAudio: DSW_InitInputBuffer() returns %x\n", hr));
                result = paUnanticipatedHostError;
                PA_DS_SET_LAST_DIRECTSOUND_ERROR( hr );
                goto error;
            }
        }

    }

    *s = (PaStream*)stream;

    return result;

error:
    if( stream )
        PaUtil_FreeMemory( stream );

    return result;
}


/***********************************************************************************/
static PaError Pa_TimeSlice( PaWinDsStream *stream )
{
    PaError           result = 0;   /* FIXME: this should be declared int and this function should also return that type (same as stream callback return type)*/
    DSoundWrapper    *dsw;
    long              numFrames = 0;
    long              bytesEmpty = 0;
    long              bytesFilled = 0;
    long              bytesToXfer = 0;
    long              framesToXfer = 0;
    long              numInFramesReady = 0;
    long              numOutFramesReady = 0;
    long              bytesProcessed;
    HRESULT           hresult;
    double            outputLatency = 0;
    PaStreamCallbackTimeInfo timeInfo = {0,0,0}; /** @todo implement inputBufferAdcTime */
    
/* Input */
    LPBYTE            lpInBuf1 = NULL;
    LPBYTE            lpInBuf2 = NULL;
    DWORD             dwInSize1 = 0;
    DWORD             dwInSize2 = 0;
/* Output */
    LPBYTE            lpOutBuf1 = NULL;
    LPBYTE            lpOutBuf2 = NULL;
    DWORD             dwOutSize1 = 0;
    DWORD             dwOutSize2 = 0;

    dsw = &stream->directSoundWrapper;

    /* How much input data is available? */
    if( stream->bufferProcessor.inputChannelCount > 0 )
    {
        DSW_QueryInputFilled( dsw, &bytesFilled );
        framesToXfer = numInFramesReady = bytesFilled / dsw->dsw_BytesPerInputFrame;
        outputLatency = ((double)bytesFilled) * stream->secondsPerHostByte;

        /** @todo Check for overflow */
    }

    /* How much output room is available? */
    if( stream->bufferProcessor.outputChannelCount > 0 )
    {
        UINT previousUnderflowCount = dsw->dsw_OutputUnderflows;
        DSW_QueryOutputSpace( dsw, &bytesEmpty );
        framesToXfer = numOutFramesReady = bytesEmpty / dsw->dsw_BytesPerOutputFrame;

        /* Check for underflow */
        if( dsw->dsw_OutputUnderflows != previousUnderflowCount )
            stream->callbackFlags |= paOutputUnderflow;
    }

    if( (numInFramesReady > 0) && (numOutFramesReady > 0) )
    {
        framesToXfer = (numOutFramesReady < numInFramesReady) ? numOutFramesReady : numInFramesReady;
    }

    if( framesToXfer > 0 )
    {

        PaUtil_BeginCpuLoadMeasurement( &stream->cpuLoadMeasurer );

    /* The outputBufferDacTime parameter should indicates the time at which
        the first sample of the output buffer is heard at the DACs. */
        timeInfo.currentTime = PaUtil_GetTime();
        timeInfo.outputBufferDacTime = timeInfo.currentTime + outputLatency;


        PaUtil_BeginBufferProcessing( &stream->bufferProcessor, &timeInfo, stream->callbackFlags );
        stream->callbackFlags = 0;
        
    /* Input */
        if( stream->bufferProcessor.inputChannelCount > 0 )
        {
            bytesToXfer = framesToXfer * dsw->dsw_BytesPerInputFrame;
            hresult = IDirectSoundCaptureBuffer_Lock ( dsw->dsw_InputBuffer,
                dsw->dsw_ReadOffset, bytesToXfer,
                (void **) &lpInBuf1, &dwInSize1,
                (void **) &lpInBuf2, &dwInSize2, 0);
            if (hresult != DS_OK)
            {
                ERR_RPT(("DirectSound IDirectSoundCaptureBuffer_Lock failed, hresult = 0x%x\n",hresult));
                result = paUnanticipatedHostError;
                PA_DS_SET_LAST_DIRECTSOUND_ERROR( hresult );
                goto error2;
            }

            numFrames = dwInSize1 / dsw->dsw_BytesPerInputFrame;
            PaUtil_SetInputFrameCount( &stream->bufferProcessor, numFrames );
            PaUtil_SetInterleavedInputChannels( &stream->bufferProcessor, 0, lpInBuf1, 0 );
        /* Is input split into two regions. */
            if( dwInSize2 > 0 )
            {
                numFrames = dwInSize2 / dsw->dsw_BytesPerInputFrame;
                PaUtil_Set2ndInputFrameCount( &stream->bufferProcessor, numFrames );
                PaUtil_Set2ndInterleavedInputChannels( &stream->bufferProcessor, 0, lpInBuf2, 0 );
            }
        }

    /* Output */
        if( stream->bufferProcessor.outputChannelCount > 0 )
        {
            bytesToXfer = framesToXfer * dsw->dsw_BytesPerOutputFrame;
            hresult = IDirectSoundBuffer_Lock ( dsw->dsw_OutputBuffer,
                dsw->dsw_WriteOffset, bytesToXfer,
                (void **) &lpOutBuf1, &dwOutSize1,
                (void **) &lpOutBuf2, &dwOutSize2, 0);
            if (hresult != DS_OK)
            {
                ERR_RPT(("DirectSound IDirectSoundBuffer_Lock failed, hresult = 0x%x\n",hresult));
                result = paUnanticipatedHostError;
                PA_DS_SET_LAST_DIRECTSOUND_ERROR( hresult );
                goto error1;
            }

            numFrames = dwOutSize1 / dsw->dsw_BytesPerOutputFrame;
            PaUtil_SetOutputFrameCount( &stream->bufferProcessor, numFrames );
            PaUtil_SetInterleavedOutputChannels( &stream->bufferProcessor, 0, lpOutBuf1, 0 );

        /* Is output split into two regions. */
            if( dwOutSize2 > 0 )
            {
                numFrames = dwOutSize2 / dsw->dsw_BytesPerOutputFrame;
                PaUtil_Set2ndOutputFrameCount( &stream->bufferProcessor, numFrames );
                PaUtil_Set2ndInterleavedOutputChannels( &stream->bufferProcessor, 0, lpOutBuf2, 0 );
            }
        }

        result = paContinue;
        numFrames = PaUtil_EndBufferProcessing( &stream->bufferProcessor, &result );
        stream->framesWritten += numFrames;
        
        if( stream->bufferProcessor.outputChannelCount > 0 )
        {
        /* FIXME: an underflow could happen here */

        /* Update our buffer offset and unlock sound buffer */
            bytesProcessed = numFrames * dsw->dsw_BytesPerOutputFrame;
            dsw->dsw_WriteOffset = (dsw->dsw_WriteOffset + bytesProcessed) % dsw->dsw_OutputSize;
            IDirectSoundBuffer_Unlock( dsw->dsw_OutputBuffer, lpOutBuf1, dwOutSize1, lpOutBuf2, dwOutSize2);
            dsw->dsw_FramesWritten += numFrames;
        }

error1:
        if( stream->bufferProcessor.inputChannelCount > 0 )
        {
        /* FIXME: an overflow could happen here */

        /* Update our buffer offset and unlock sound buffer */
            bytesProcessed = numFrames * dsw->dsw_BytesPerInputFrame;
            dsw->dsw_ReadOffset = (dsw->dsw_ReadOffset + bytesProcessed) % dsw->dsw_InputSize;
            IDirectSoundCaptureBuffer_Unlock( dsw->dsw_InputBuffer, lpInBuf1, dwInSize1, lpInBuf2, dwInSize2);
        }
error2:

        PaUtil_EndCpuLoadMeasurement( &stream->cpuLoadMeasurer, numFrames );

    }
    
    return result;
}
/*******************************************************************/
static void CALLBACK Pa_TimerCallback(UINT uID, UINT uMsg, DWORD dwUser, DWORD dw1, DWORD dw2)
{
    PaWinDsStream *stream;

    /* suppress unused variable warnings */
    (void) uID;
    (void) uMsg;
    (void) dw1;
    (void) dw2;
    
    stream = (PaWinDsStream *) dwUser;
    if( stream == NULL ) return;

    if( stream->isActive )
    {
        if( stream->abortProcessing )
        {
            stream->isActive = 0;
        }
        else if( stream->stopProcessing )
        {
            DSoundWrapper   *dsw = &stream->directSoundWrapper;
            if( stream->bufferProcessor.outputChannelCount > 0 )
            {
                DSW_ZeroEmptySpace( dsw );
                /* clear isActive when all sound played */
                if( dsw->dsw_FramesPlayed >= stream->framesWritten )
                {
                    stream->isActive = 0;
                }
            }
            else
            {
                stream->isActive = 0;
            }
        }
        else
        {
            if( Pa_TimeSlice( stream ) != 0)  /* Call time slice independant of timing method. */
            {
                /* FIXME implement handling of paComplete and paAbort if possible */
                stream->stopProcessing = 1;
            }
        }

        if( !stream->isActive ){
            if( stream->streamRepresentation.streamFinishedCallback != 0 )
                stream->streamRepresentation.streamFinishedCallback( stream->streamRepresentation.userData );
        }
    }
}

/***********************************************************************************
    When CloseStream() is called, the multi-api layer ensures that
    the stream has already been stopped or aborted.
*/
static PaError CloseStream( PaStream* s )
{
    PaError result = paNoError;
    PaWinDsStream *stream = (PaWinDsStream*)s;

    DSW_Term( &stream->directSoundWrapper );

    PaUtil_TerminateBufferProcessor( &stream->bufferProcessor );
    PaUtil_TerminateStreamRepresentation( &stream->streamRepresentation );
    PaUtil_FreeMemory( stream );

    return result;
}

/***********************************************************************************/
static PaError StartStream( PaStream *s )
{
    PaError          result = paNoError;
    PaWinDsStream   *stream = (PaWinDsStream*)s;
    HRESULT          hr;

    PaUtil_ResetBufferProcessor( &stream->bufferProcessor );
    
    if( stream->bufferProcessor.inputChannelCount > 0 )
    {
        hr = DSW_StartInput( &stream->directSoundWrapper );
        DBUG(("StartStream: DSW_StartInput returned = 0x%X.\n", hr));
        if( hr != DS_OK )
        {
            result = paUnanticipatedHostError;
            PA_DS_SET_LAST_DIRECTSOUND_ERROR( hr );
            goto error;
        }
    }

    stream->framesWritten = 0;
    stream->callbackFlags = 0;

    stream->abortProcessing = 0;
    stream->stopProcessing = 0;
    stream->isActive = 1;

    if( stream->bufferProcessor.outputChannelCount > 0 )
    {
        /* Give user callback a chance to pre-fill buffer. REVIEW - i thought we weren't pre-filling, rb. */
        result = Pa_TimeSlice( stream );
        if( result != paNoError ) return result; // FIXME - what if finished?

        hr = DSW_StartOutput( &stream->directSoundWrapper );
        DBUG(("PaHost_StartOutput: DSW_StartOutput returned = 0x%X.\n", hr));
        if( hr != DS_OK )
        {
            result = paUnanticipatedHostError;
            PA_DS_SET_LAST_DIRECTSOUND_ERROR( hr );
            goto error;
        }
    }


    /* Create timer that will wake us up so we can fill the DSound buffer. */
    {
        int resolution;
        int framesPerWakeup = stream->framesPerDSBuffer / 4;
        int msecPerWakeup = MSEC_PER_SECOND * framesPerWakeup / (int) stream->streamRepresentation.streamInfo.sampleRate;
        if( msecPerWakeup < 10 ) msecPerWakeup = 10;
        else if( msecPerWakeup > 100 ) msecPerWakeup = 100;
        resolution = msecPerWakeup/4;
        stream->timerID = timeSetEvent( msecPerWakeup, resolution, (LPTIMECALLBACK) Pa_TimerCallback,
                                             (DWORD) stream, TIME_PERIODIC );
    }
    if( stream->timerID == 0 )
    {
        stream->isActive = 0;
        result = paUnanticipatedHostError;
        PA_DS_SET_LAST_DIRECTSOUND_ERROR( hr );
        goto error;
    }

    stream->isStarted = TRUE;

error:
    return result;
}


/***********************************************************************************/
static PaError StopStream( PaStream *s )
{
    PaError result = paNoError;
    PaWinDsStream *stream = (PaWinDsStream*)s;
    HRESULT          hr;
    int timeoutMsec;

    stream->stopProcessing = 1;
    /* Set timeout at 20% beyond maximum time we might wait. */
    timeoutMsec = (int) (1200.0 * stream->framesPerDSBuffer / stream->streamRepresentation.streamInfo.sampleRate);
    while( stream->isActive && (timeoutMsec > 0)  )
    {
        Sleep(10);
        timeoutMsec -= 10;
    }
    if( stream->timerID != 0 )
    {
        timeKillEvent(stream->timerID);  /* Stop callback timer. */
        stream->timerID = 0;
    }


    if( stream->bufferProcessor.outputChannelCount > 0 )
    {
        hr = DSW_StopOutput( &stream->directSoundWrapper );
    }

    if( stream->bufferProcessor.inputChannelCount > 0 )
    {
        hr = DSW_StopInput( &stream->directSoundWrapper );
    }

    stream->isStarted = FALSE;

    return result;
}


/***********************************************************************************/
static PaError AbortStream( PaStream *s )
{
    PaWinDsStream *stream = (PaWinDsStream*)s;

    stream->abortProcessing = 1;
    return StopStream( s );
}


/***********************************************************************************/
static PaError IsStreamStopped( PaStream *s )
{
    PaWinDsStream *stream = (PaWinDsStream*)s;

    return !stream->isStarted;
}


/***********************************************************************************/
static PaError IsStreamActive( PaStream *s )
{
    PaWinDsStream *stream = (PaWinDsStream*)s;

    return stream->isActive;
}

/***********************************************************************************/
static PaTime GetStreamTime( PaStream *s )
{
    /* suppress unused variable warnings */
    (void) s;

    
/*
    new behavior for GetStreamTime is to return a stream based seconds clock
    used for the outTime parameter to the callback.
    FIXME: delete this comment when the other unnecessary related code has
    been cleaned from this file.

    PaWinDsStream *stream = (PaWinDsStream*)s;
    DSoundWrapper   *dsw;
    dsw = &stream->directSoundWrapper;
    return dsw->dsw_FramesPlayed;
*/
    return PaUtil_GetTime();
}


/***********************************************************************************/
static double GetStreamCpuLoad( PaStream* s )
{
    PaWinDsStream *stream = (PaWinDsStream*)s;

    return PaUtil_GetCpuLoad( &stream->cpuLoadMeasurer );
}



/***********************************************************************************
    As separate stream interfaces are used for blocking and callback
    streams, the following functions can be guaranteed to only be called
    for blocking streams.
*/

static PaError ReadStream( PaStream* s,
                           void *buffer,
                           unsigned long frames )
{
    PaWinDsStream *stream = (PaWinDsStream*)s;

    /* suppress unused variable warnings */
    (void) buffer;
    (void) frames;
    (void) stream;

    /* IMPLEMENT ME, see portaudio.h for required behavior*/

    return paNoError;
}


/***********************************************************************************/
static PaError WriteStream( PaStream* s,
                            const void *buffer,
                            unsigned long frames )
{
    PaWinDsStream *stream = (PaWinDsStream*)s;

    /* suppress unused variable warnings */
    (void) buffer;
    (void) frames;
    (void) stream;

    /* IMPLEMENT ME, see portaudio.h for required behavior*/

    return paNoError;
}


/***********************************************************************************/
static signed long GetStreamReadAvailable( PaStream* s )
{
    PaWinDsStream *stream = (PaWinDsStream*)s;

    /* suppress unused variable warnings */
    (void) stream;

    /* IMPLEMENT ME, see portaudio.h for required behavior*/

    return 0;
}


/***********************************************************************************/
static signed long GetStreamWriteAvailable( PaStream* s )
{
    PaWinDsStream *stream = (PaWinDsStream*)s;

    /* suppress unused variable warnings */
    (void) stream;
    
    /* IMPLEMENT ME, see portaudio.h for required behavior*/

    return 0;
}



