From 37c97e345d12f95dde44e1d1a4c2f2aadd4615bc Mon Sep 17 00:00:00 2001
From: sanine <sanine.not@pm.me>
Date: Thu, 25 Aug 2022 14:54:53 -0500
Subject: add initial structure

---
 portaudio/src/hostapi/dsound/pa_win_ds.c         | 3259 ++++++++++++++++++++++
 portaudio/src/hostapi/dsound/pa_win_ds_dynlink.c |  224 ++
 portaudio/src/hostapi/dsound/pa_win_ds_dynlink.h |  106 +
 3 files changed, 3589 insertions(+)
 create mode 100644 portaudio/src/hostapi/dsound/pa_win_ds.c
 create mode 100644 portaudio/src/hostapi/dsound/pa_win_ds_dynlink.c
 create mode 100644 portaudio/src/hostapi/dsound/pa_win_ds_dynlink.h

(limited to 'portaudio/src/hostapi/dsound')

diff --git a/portaudio/src/hostapi/dsound/pa_win_ds.c b/portaudio/src/hostapi/dsound/pa_win_ds.c
new file mode 100644
index 0000000..2ccb4f8
--- /dev/null
+++ b/portaudio/src/hostapi/dsound/pa_win_ds.c
@@ -0,0 +1,3259 @@
+/*
+ * $Id$
+ * Portable Audio I/O Library DirectSound implementation
+ *
+ * Authors: Phil Burk, Robert Marsanyi & Ross Bencina
+ * Based on the Open Source API proposed by Ross Bencina
+ * Copyright (c) 1999-2007 Ross Bencina, Phil Burk, Robert Marsanyi
+ *
+ * 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.
+ *
+ * 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.
+ */
+
+/*
+ * The text above constitutes the entire PortAudio license; however,
+ * the PortAudio community also makes the following non-binding requests:
+ *
+ * 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. It is also
+ * requested that these non-binding requests be included along with the
+ * license above.
+ */
+
+/** @file
+ @ingroup hostapi_src
+*/
+
+/* Until May 2011 PA/DS has used a multimedia timer to perform the callback.
+   We're replacing this with a new implementation using a thread and a different timer mechanism.
+   Defining PA_WIN_DS_USE_WMME_TIMER uses the old (pre-May 2011) behavior.
+*/
+//#define PA_WIN_DS_USE_WMME_TIMER
+
+#include <assert.h>
+#include <stdio.h>
+#include <string.h> /* strlen() */
+
+#define _WIN32_WINNT 0x0400 /* required to get waitable timer APIs */
+#include <initguid.h> /* make sure ds guids get defined */
+#include <windows.h>
+#include <objbase.h>
+
+
+/*
+  Use the earliest version of DX required, no need to pollute the namespace
+*/
+#ifdef PAWIN_USE_DIRECTSOUNDFULLDUPLEXCREATE
+#define DIRECTSOUND_VERSION 0x0800
+#else
+#define DIRECTSOUND_VERSION 0x0300
+#endif
+#include <dsound.h>
+#ifdef PAWIN_USE_WDMKS_DEVICE_INFO
+#include <dsconf.h>
+#endif /* PAWIN_USE_WDMKS_DEVICE_INFO */
+#ifndef PA_WIN_DS_USE_WMME_TIMER
+#ifndef UNDER_CE
+#include <process.h>
+#endif
+#endif
+
+#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 "pa_debugprint.h"
+
+#include "pa_win_ds.h"
+#include "pa_win_ds_dynlink.h"
+#include "pa_win_waveformat.h"
+#include "pa_win_wdmks_utils.h"
+#include "pa_win_coinitialize.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" )
+#pragma comment( lib, "kernel32.lib" )
+#endif
+
+/* use CreateThread for CYGWIN, _beginthreadex for all others */
+#ifndef PA_WIN_DS_USE_WMME_TIMER
+
+#if !defined(__CYGWIN__) && !defined(UNDER_CE)
+#define CREATE_THREAD (HANDLE)_beginthreadex
+#undef CLOSE_THREAD_HANDLE /* as per documentation we don't call CloseHandle on a thread created with _beginthreadex */
+#define PA_THREAD_FUNC static unsigned WINAPI
+#define PA_THREAD_ID unsigned
+#else
+#define CREATE_THREAD CreateThread
+#define CLOSE_THREAD_HANDLE CloseHandle
+#define PA_THREAD_FUNC static DWORD WINAPI
+#define PA_THREAD_ID DWORD
+#endif
+
+#if (defined(UNDER_CE))
+#pragma comment(lib, "Coredll.lib")
+#elif (defined(WIN32) && (defined(_MSC_VER) && (_MSC_VER >= 1200))) /* MSC version 6 and above */
+#pragma comment(lib, "winmm.lib")
+#endif
+
+PA_THREAD_FUNC ProcessingThreadProc( void *pArg );
+
+#if !defined(UNDER_CE)
+#define PA_WIN_DS_USE_WAITABLE_TIMER_OBJECT /* use waitable timer where possible, otherwise we use a WaitForSingleObject timeout */
+#endif
+
+#endif /* !PA_WIN_DS_USE_WMME_TIMER */
+
+
+/*
+ provided in newer platform sdks and x64
+ */
+#ifndef DWORD_PTR
+    #if defined(_WIN64)
+        #define DWORD_PTR unsigned __int64
+    #else
+        #define DWORD_PTR unsigned long
+    #endif
+#endif
+
+#define PRINT(x) PA_DEBUG(x);
+#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_DS_WIN_9X_DEFAULT_LATENCY_     (.500)
+#define PA_DS_WIN_NT_DEFAULT_LATENCY_     (.600)
+#else
+#define PA_DS_WIN_9X_DEFAULT_LATENCY_     (.140)
+#define PA_DS_WIN_NT_DEFAULT_LATENCY_     (.280)
+#endif
+
+#define PA_DS_WIN_WDM_DEFAULT_LATENCY_    (.120)
+
+/* we allow the polling period to range between 1 and 100ms.
+   prior to August 2011 we limited the minimum polling period to 10ms.
+*/
+#define PA_DS_MINIMUM_POLLING_PERIOD_SECONDS    (0.001) /* 1ms */
+#define PA_DS_MAXIMUM_POLLING_PERIOD_SECONDS    (0.100) /* 100ms */
+#define PA_DS_POLLING_JITTER_SECONDS            (0.001) /* 1ms */
+
+#define SECONDS_PER_MSEC      (0.001)
+#define MSECS_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 CollectGUIDsProcW(LPGUID lpGUID,
+                                     LPCWSTR lpszDesc,
+                                     LPCWSTR lpszDrvName,
+                                     LPVOID lpContext );
+
+/************************************************************************************/
+/********************** Structures **************************************************/
+/************************************************************************************/
+/* PaWinDsHostApiRepresentation - host api datastructure specific to this implementation */
+
+typedef struct PaWinDsDeviceInfo
+{
+    PaDeviceInfo        inheritedDeviceInfo;
+    GUID                guid;
+    GUID                *lpGUID;
+    double              sampleRates[3];
+    char deviceInputChannelCountIsKnown; /**<< if the system returns 0xFFFF then we don't really know the number of supported channels (1=>known, 0=>unknown)*/
+    char deviceOutputChannelCountIsKnown; /**<< if the system returns 0xFFFF then we don't really know the number of supported channels (1=>known, 0=>unknown)*/
+} PaWinDsDeviceInfo;
+
+typedef struct
+{
+    PaUtilHostApiRepresentation inheritedHostApiRep;
+    PaUtilStreamInterface    callbackStreamInterface;
+    PaUtilStreamInterface    blockingStreamInterface;
+
+    PaUtilAllocationGroup   *allocations;
+
+    /* implementation specific data goes here */
+
+    PaWinUtilComInitializationResult comInitializationResult;
+
+} PaWinDsHostApiRepresentation;
+
+
+/* PaWinDsStream - a stream data structure specifically for this implementation */
+
+typedef struct PaWinDsStream
+{
+    PaUtilStreamRepresentation streamRepresentation;
+    PaUtilCpuLoadMeasurer cpuLoadMeasurer;
+    PaUtilBufferProcessor bufferProcessor;
+
+/* DirectSound specific data. */
+#ifdef PAWIN_USE_DIRECTSOUNDFULLDUPLEXCREATE
+    LPDIRECTSOUNDFULLDUPLEX8 pDirectSoundFullDuplex8;
+#endif
+
+/* Output */
+    LPDIRECTSOUND        pDirectSound;
+    LPDIRECTSOUNDBUFFER  pDirectSoundPrimaryBuffer;
+    LPDIRECTSOUNDBUFFER  pDirectSoundOutputBuffer;
+    DWORD                outputBufferWriteOffsetBytes;     /* last write position */
+    INT                  outputBufferSizeBytes;
+    INT                  outputFrameSizeBytes;
+    /* Try to detect play buffer underflows. */
+    LARGE_INTEGER        perfCounterTicksPerBuffer; /* counter ticks it should take to play a full buffer */
+    LARGE_INTEGER        previousPlayTime;
+    DWORD                previousPlayCursor;
+    UINT                 outputUnderflowCount;
+    BOOL                 outputIsRunning;
+    INT                  finalZeroBytesWritten; /* used to determine when we've flushed the whole buffer */
+
+/* Input */
+    LPDIRECTSOUNDCAPTURE pDirectSoundCapture;
+    LPDIRECTSOUNDCAPTUREBUFFER   pDirectSoundInputBuffer;
+    INT                  inputFrameSizeBytes;
+    UINT                 readOffset;      /* last read position */
+    UINT                 inputBufferSizeBytes;
+
+
+    int              hostBufferSizeFrames; /* input and output host ringbuffers have the same number of frames */
+    double           framesWritten;
+    double           secondsPerHostByte; /* Used to optimize latency calculation for outTime */
+    double           pollingPeriodSeconds;
+
+    PaStreamCallbackFlags callbackFlags;
+
+    PaStreamFlags    streamFlags;
+    int              callbackResult;
+    HANDLE           processingCompleted;
+
+/* 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 */
+
+    UINT             systemTimerResolutionPeriodMs; /* set to 0 if we were unable to set the timer period */
+
+#ifdef PA_WIN_DS_USE_WMME_TIMER
+    MMRESULT         timerID;
+#else
+
+#ifdef PA_WIN_DS_USE_WAITABLE_TIMER_OBJECT
+    HANDLE           waitableTimer;
+#endif
+    HANDLE           processingThread;
+    PA_THREAD_ID     processingThreadId;
+    HANDLE           processingThreadCompleted;
+#endif
+
+} PaWinDsStream;
+
+
+/* Set minimal latency based on the current OS version.
+ * NT has higher latency.
+ */
+static double PaWinDS_GetMinSystemLatencySeconds( void )
+{
+/*
+NOTE: GetVersionEx() is deprecated as of Windows 8.1 and can not be used to reliably detect
+versions of Windows higher than Windows 8 (due to manifest requirements for reporting higher versions).
+Microsoft recommends switching to VerifyVersionInfo (available on Win 2k and later), however GetVersionEx
+is faster, for now we just disable the deprecation warning.
+See: https://msdn.microsoft.com/en-us/library/windows/desktop/ms724451(v=vs.85).aspx
+See: http://www.codeproject.com/Articles/678606/Part-Overcoming-Windows-s-deprecation-of-GetVe
+*/
+#pragma warning (disable : 4996) /* use of GetVersionEx */
+
+    double minLatencySeconds;
+    /* 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) )
+    {
+        minLatencySeconds = PA_DS_WIN_NT_DEFAULT_LATENCY_;
+    }
+    else if(osvi.dwMajorVersion >= 5)
+    {
+        minLatencySeconds = PA_DS_WIN_WDM_DEFAULT_LATENCY_;
+    }
+    else
+    {
+        minLatencySeconds = PA_DS_WIN_9X_DEFAULT_LATENCY_;
+    }
+    return minLatencySeconds;
+
+#pragma warning (default : 4996)
+}
+
+
+/*************************************************************************
+** Return minimum workable latency required for this host. This is returned
+** As the default stream latency in PaDeviceInfo.
+** 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 double PaWinDs_GetMinLatencySeconds( double sampleRate )
+{
+    char      envbuf[PA_ENV_BUF_SIZE];
+    DWORD     hresult;
+    double    minLatencySeconds = 0;
+
+    /* Let user determine minimal latency by setting environment variable. */
+    hresult = GetEnvironmentVariableA( PA_LATENCY_ENV_NAME, envbuf, PA_ENV_BUF_SIZE );
+    if( (hresult > 0) && (hresult < PA_ENV_BUF_SIZE) )
+    {
+        minLatencySeconds = atoi( envbuf ) * SECONDS_PER_MSEC;
+    }
+    else
+    {
+        minLatencySeconds = PaWinDS_GetMinSystemLatencySeconds();
+#if PA_USE_HIGH_LATENCY
+        PRINT(("PA - Minimum Latency set to %f msec!\n", minLatencySeconds * MSECS_PER_SECOND ));
+#endif
+    }
+
+    return minLatencySeconds;
+}
+
+
+/************************************************************************************
+** Duplicate and convert the input string using the group 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 wchar_t* src )
+{
+    char *result = 0;
+
+    if( src != NULL )
+    {
+        size_t len = WideCharToMultiByte(CP_UTF8, 0, src, -1, NULL, 0, NULL, NULL);
+
+        result = (char*)PaUtil_GroupAllocateMemory( allocations, (long)(len + 1) );
+        if( result ) {
+            if (WideCharToMultiByte(CP_UTF8, 0, src, -1, result, (int)len, NULL, NULL) == 0) {
+                result = 0;
+            }
+        }
+    }
+    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;
+    void *pnpInterface;  // wchar_t* interface path, allocated using the DS host api's allocation group
+} DSDeviceNameAndGUID;
+
+typedef struct DSDeviceNameAndGUIDVector{
+    PaUtilAllocationGroup *allocations;
+    PaError enumerationError;
+
+    int count;
+    int free;
+    DSDeviceNameAndGUID *items; // Allocated using LocalAlloc()
+} DSDeviceNameAndGUIDVector;
+
+typedef struct DSDeviceNamesAndGUIDs{
+    PaWinDsHostApiRepresentation *winDsHostApi;
+    DSDeviceNameAndGUIDVector inputNamesAndGUIDs;
+    DSDeviceNameAndGUIDVector outputNamesAndGUIDs;
+} DSDeviceNamesAndGUIDs;
+
+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) );
+            }
+            newItems[i].pnpInterface = guidVector->items[i].pnpInterface;
+        }
+
+        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 CollectGUIDsProcW(LPGUID lpGUID,
+                                     LPCWSTR lpszDesc,
+                                     LPCWSTR 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->items[namesAndGUIDs->count].pnpInterface = 0;
+
+    ++namesAndGUIDs->count;
+    --namesAndGUIDs->free;
+
+    return TRUE;
+}
+
+
+#ifdef PAWIN_USE_WDMKS_DEVICE_INFO
+
+static void *DuplicateWCharString( PaUtilAllocationGroup *allocations, wchar_t *source )
+{
+    size_t len;
+    wchar_t *result;
+
+    len = wcslen( source );
+    result = (wchar_t*)PaUtil_GroupAllocateMemory( allocations, (long) ((len+1) * sizeof(wchar_t)) );
+    wcscpy( result, source );
+    return result;
+}
+
+static BOOL CALLBACK KsPropertySetEnumerateCallback( PDSPROPERTY_DIRECTSOUNDDEVICE_DESCRIPTION_W_DATA data, LPVOID context )
+{
+    int i;
+    DSDeviceNamesAndGUIDs *deviceNamesAndGUIDs = (DSDeviceNamesAndGUIDs*)context;
+
+    /*
+        Apparently data->Interface can be NULL in some cases.
+        Possibly virtual devices without hardware.
+        So we check for NULLs now. See mailing list message November 10, 2012:
+        "[Portaudio] portaudio initialization crash in KsPropertySetEnumerateCallback(pa_win_ds.c)"
+    */
+    if( data->Interface )
+    {
+        if( data->DataFlow == DIRECTSOUNDDEVICE_DATAFLOW_RENDER )
+        {
+            for( i=0; i < deviceNamesAndGUIDs->outputNamesAndGUIDs.count; ++i )
+            {
+                if( deviceNamesAndGUIDs->outputNamesAndGUIDs.items[i].lpGUID
+                    && memcmp( &data->DeviceId, deviceNamesAndGUIDs->outputNamesAndGUIDs.items[i].lpGUID, sizeof(GUID) ) == 0 )
+                {
+                    deviceNamesAndGUIDs->outputNamesAndGUIDs.items[i].pnpInterface =
+                        (char*)DuplicateWCharString( deviceNamesAndGUIDs->winDsHostApi->allocations, data->Interface );
+                    break;
+                }
+            }
+        }
+        else if( data->DataFlow == DIRECTSOUNDDEVICE_DATAFLOW_CAPTURE )
+        {
+            for( i=0; i < deviceNamesAndGUIDs->inputNamesAndGUIDs.count; ++i )
+            {
+                if( deviceNamesAndGUIDs->inputNamesAndGUIDs.items[i].lpGUID
+                    && memcmp( &data->DeviceId, deviceNamesAndGUIDs->inputNamesAndGUIDs.items[i].lpGUID, sizeof(GUID) ) == 0 )
+                {
+                    deviceNamesAndGUIDs->inputNamesAndGUIDs.items[i].pnpInterface =
+                        (char*)DuplicateWCharString( deviceNamesAndGUIDs->winDsHostApi->allocations, data->Interface );
+                    break;
+                }
+            }
+        }
+    }
+
+    return TRUE;
+}
+
+
+static GUID pawin_CLSID_DirectSoundPrivate =
+{ 0x11ab3ec0, 0x25ec, 0x11d1, 0xa4, 0xd8, 0x00, 0xc0, 0x4f, 0xc2, 0x8a, 0xca };
+
+static GUID pawin_DSPROPSETID_DirectSoundDevice =
+{ 0x84624f82, 0x25ec, 0x11d1, 0xa4, 0xd8, 0x00, 0xc0, 0x4f, 0xc2, 0x8a, 0xca };
+
+static GUID pawin_IID_IKsPropertySet =
+{ 0x31efac30, 0x515c, 0x11d0, 0xa9, 0xaa, 0x00, 0xaa, 0x00, 0x61, 0xbe, 0x93 };
+
+
+/*
+    FindDevicePnpInterfaces fills in the pnpInterface fields in deviceNamesAndGUIDs
+    with UNICODE file paths to the devices. The DS documentation mentions
+    at least two techniques by which these Interface paths can be found using IKsPropertySet on
+    the DirectSound class object. One is using the DSPROPERTY_DIRECTSOUNDDEVICE_DESCRIPTION
+    property, and the other is using DSPROPERTY_DIRECTSOUNDDEVICE_ENUMERATE.
+    I tried both methods and only the second worked. I found two postings on the
+    net from people who had the same problem with the first method, so I think the method used here is
+    more common/likely to work. The problem is that IKsPropertySet_Get returns S_OK
+    but the fields of the device description are not filled in.
+
+    The mechanism we use works by registering an enumeration callback which is called for
+    every DSound device. Our callback searches for a device in our deviceNamesAndGUIDs list
+    with the matching GUID and copies the pointer to the Interface path.
+    Note that we could have used this enumeration callback to perform the original
+    device enumeration, however we choose not to so we can disable this step easily.
+
+    Apparently the IKsPropertySet mechanism was added in DirectSound 9c 2004
+    http://www.tech-archive.net/Archive/Development/microsoft.public.win32.programmer.mmedia/2004-12/0099.html
+
+        -- rossb
+*/
+static void FindDevicePnpInterfaces( DSDeviceNamesAndGUIDs *deviceNamesAndGUIDs )
+{
+    IClassFactory *pClassFactory;
+
+    if( paWinDsDSoundEntryPoints.DllGetClassObject(&pawin_CLSID_DirectSoundPrivate, &IID_IClassFactory, (PVOID *) &pClassFactory) == S_OK ){
+        IKsPropertySet *pPropertySet;
+        if( pClassFactory->lpVtbl->CreateInstance( pClassFactory, NULL, &pawin_IID_IKsPropertySet, (PVOID *) &pPropertySet) == S_OK ){
+
+            DSPROPERTY_DIRECTSOUNDDEVICE_ENUMERATE_W_DATA data;
+            ULONG bytesReturned;
+
+            data.Callback = KsPropertySetEnumerateCallback;
+            data.Context = deviceNamesAndGUIDs;
+
+            IKsPropertySet_Get( pPropertySet,
+                &pawin_DSPROPSETID_DirectSoundDevice,
+                DSPROPERTY_DIRECTSOUNDDEVICE_ENUMERATE_W,
+                NULL,
+                0,
+                &data,
+                sizeof(data),
+                &bytesReturned
+            );
+
+            IKsPropertySet_Release( pPropertySet );
+        }
+        pClassFactory->lpVtbl->Release( pClassFactory );
+    }
+
+    /*
+        The following code fragment, which I chose not to use, queries for the
+        device interface for a device with a specific GUID:
+
+        ULONG BytesReturned;
+        DSPROPERTY_DIRECTSOUNDDEVICE_DESCRIPTION_W_DATA Property;
+
+        memset (&Property, 0, sizeof(Property));
+        Property.DataFlow = DIRECTSOUNDDEVICE_DATAFLOW_RENDER;
+        Property.DeviceId = *lpGUID;
+
+        hr = IKsPropertySet_Get( pPropertySet,
+            &pawin_DSPROPSETID_DirectSoundDevice,
+            DSPROPERTY_DIRECTSOUNDDEVICE_DESCRIPTION_W,
+            NULL,
+            0,
+            &Property,
+            sizeof(Property),
+            &BytesReturned
+        );
+
+        if( hr == S_OK )
+        {
+            //pnpInterface = Property.Interface;
+        }
+    */
+}
+#endif /* PAWIN_USE_WDMKS_DEVICE_INFO */
+
+
+/*
+    GUIDs for emulated devices which we blacklist below.
+    are there more than two of them??
+*/
+
+GUID IID_IRolandVSCEmulated1 = {0xc2ad1800, 0xb243, 0x11ce, 0xa8, 0xa4, 0x00, 0xaa, 0x00, 0x6c, 0x45, 0x01};
+GUID IID_IRolandVSCEmulated2 = {0xc2ad1800, 0xb243, 0x11ce, 0xa8, 0xa4, 0x00, 0xaa, 0x00, 0x6c, 0x45, 0x02};
+
+
+#define PA_DEFAULTSAMPLERATESEARCHORDER_COUNT_  (13) /* must match array length below */
+static double defaultSampleRateSearchOrder_[] =
+    { 44100.0, 48000.0, 32000.0, 24000.0, 22050.0, 88200.0, 96000.0, 192000.0,
+        16000.0, 12000.0, 11025.0, 9600.0, 8000.0 };
+
+/************************************************************************************
+** 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 accommodate 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, char *pnpInterface )
+{
+    PaUtilHostApiRepresentation  *hostApi = &winDsHostApi->inheritedHostApiRep;
+    PaWinDsDeviceInfo            *winDsDeviceInfo = (PaWinDsDeviceInfo*) hostApi->deviceInfos[hostApi->info.deviceCount];
+    PaDeviceInfo                 *deviceInfo = &winDsDeviceInfo->inheritedDeviceInfo;
+    HRESULT                       hr;
+    LPDIRECTSOUND                 lpDirectSound;
+    DSCAPS                        caps;
+    int                           deviceOK = TRUE;
+    PaError                       result = paNoError;
+    int                           i;
+
+    /* 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;
+    }
+
+    if( lpGUID )
+    {
+        if (IsEqualGUID (&IID_IRolandVSCEmulated1,lpGUID) ||
+            IsEqualGUID (&IID_IRolandVSCEmulated2,lpGUID) )
+        {
+            PA_DEBUG(("BLACKLISTED: %s \n",name));
+            return paNoError;
+        }
+    }
+
+    /* Create a DirectSound object for the specified GUID
+        Note that using CoCreateInstance doesn't work on windows CE.
+    */
+    hr = paWinDsDSoundEntryPoints.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 )
+    {
+        if (hr == DSERR_ALLOCATED)
+            PA_DEBUG(("AddOutputDeviceInfoFromDirectSound %s DSERR_ALLOCATED\n",name));
+        DBUG(("Cannot create DirectSound for %s. Result = 0x%x\n", name, hr ));
+        if (lpGUID)
+            DBUG(("%s's GUID: {0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x, 0x%x} \n",
+                 name,
+                 lpGUID->Data1,
+                 lpGUID->Data2,
+                 lpGUID->Data3,
+                 lpGUID->Data4[0],
+                 lpGUID->Data4[1],
+                 lpGUID->Data4[2],
+                 lpGUID->Data4[3],
+                 lpGUID->Data4[4],
+                 lpGUID->Data4[5],
+                 lpGUID->Data4[6],
+                 lpGUID->Data4[7]));
+
+        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
+        {
+
+#if PA_USE_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;
+                winDsDeviceInfo->deviceInputChannelCountIsKnown = 1;
+
+                /* DS output capabilities only indicate supported number of channels
+                   using two flags which indicate mono and/or stereo.
+                   We assume that stereo devices may support more than 2 channels
+                   (as is the case with 5.1 devices for example) and so
+                   set deviceOutputChannelCountIsKnown to 0 (unknown).
+                   In this case OpenStream will try to open the device
+                   when the user requests more than 2 channels, rather than
+                   returning an error.
+                */
+                if( caps.dwFlags & DSCAPS_PRIMARYSTEREO )
+                {
+                    deviceInfo->maxOutputChannels = 2;
+                    winDsDeviceInfo->deviceOutputChannelCountIsKnown = 0;
+                }
+                else
+                {
+                    deviceInfo->maxOutputChannels = 1;
+                    winDsDeviceInfo->deviceOutputChannelCountIsKnown = 1;
+                }
+
+                /* Guess channels count from speaker configuration. We do it only when
+                   pnpInterface is NULL or when PAWIN_USE_WDMKS_DEVICE_INFO is undefined.
+                */
+#ifdef PAWIN_USE_WDMKS_DEVICE_INFO
+                if( !pnpInterface )
+#endif
+                {
+                    DWORD spkrcfg;
+                    if( SUCCEEDED(IDirectSound_GetSpeakerConfig( lpDirectSound, &spkrcfg )) )
+                    {
+                        int count = 0;
+                        switch (DSSPEAKER_CONFIG(spkrcfg))
+                        {
+                            case DSSPEAKER_HEADPHONE:        count = 2; break;
+                            case DSSPEAKER_MONO:             count = 1; break;
+                            case DSSPEAKER_QUAD:             count = 4; break;
+                            case DSSPEAKER_STEREO:           count = 2; break;
+                            case DSSPEAKER_SURROUND:         count = 4; break;
+                            case DSSPEAKER_5POINT1:          count = 6; break;
+#ifndef DSSPEAKER_7POINT1
+#define DSSPEAKER_7POINT1 0x00000007
+#endif
+                            case DSSPEAKER_7POINT1:          count = 8; break;
+#ifndef DSSPEAKER_7POINT1_SURROUND
+#define DSSPEAKER_7POINT1_SURROUND 0x00000008
+#endif
+                            case DSSPEAKER_7POINT1_SURROUND: count = 8; break;
+#ifndef DSSPEAKER_5POINT1_SURROUND
+#define DSSPEAKER_5POINT1_SURROUND 0x00000009
+#endif
+                            case DSSPEAKER_5POINT1_SURROUND: count = 6; break;
+                        }
+                        if( count )
+                        {
+                            deviceInfo->maxOutputChannels = count;
+                            winDsDeviceInfo->deviceOutputChannelCountIsKnown = 1;
+                        }
+                    }
+                }
+
+#ifdef PAWIN_USE_WDMKS_DEVICE_INFO
+                if( pnpInterface )
+                {
+                    int count = PaWin_WDMKS_QueryFilterMaximumChannelCount( pnpInterface, /* isInput= */ 0  );
+                    if( count > 0 )
+                    {
+                        deviceInfo->maxOutputChannels = count;
+                        winDsDeviceInfo->deviceOutputChannelCountIsKnown = 1;
+                    }
+                }
+#endif /* PAWIN_USE_WDMKS_DEVICE_INFO */
+
+                /* initialize defaultSampleRate */
+
+                if( caps.dwFlags & DSCAPS_CONTINUOUSRATE )
+                {
+                    /* initialize to caps.dwMaxSecondarySampleRate incase none of the standard rates match */
+                    deviceInfo->defaultSampleRate = caps.dwMaxSecondarySampleRate;
+
+                    for( i = 0; i < PA_DEFAULTSAMPLERATESEARCHORDER_COUNT_; ++i )
+                    {
+                        if( defaultSampleRateSearchOrder_[i] >= caps.dwMinSecondarySampleRate
+                                && defaultSampleRateSearchOrder_[i] <= caps.dwMaxSecondarySampleRate )
+                        {
+                            deviceInfo->defaultSampleRate = defaultSampleRateSearchOrder_[i];
+                            break;
+                        }
+                    }
+                }
+                else if( caps.dwMinSecondarySampleRate == caps.dwMaxSecondarySampleRate )
+                {
+                    if( caps.dwMinSecondarySampleRate == 0 )
+                    {
+                        /*
+                        ** On my Thinkpad 380Z, DirectSoundV6 returns min-max=0 !!
+                        ** But it supports continuous sampling.
+                        ** So fake range of rates, and hope it really supports it.
+                        */
+                        deviceInfo->defaultSampleRate = 48000.0f;  /* assume 48000 as the default */
+
+                        DBUG(("PA - Reported rates both zero. Setting to fake values for device #%s\n", name ));
+                    }
+                    else
+                    {
+                        deviceInfo->defaultSampleRate = caps.dwMaxSecondarySampleRate;
+                    }
+                }
+                else if( (caps.dwMinSecondarySampleRate < 1000.0) && (caps.dwMaxSecondarySampleRate > 50000.0) )
+                {
+                    /* The EWS88MT drivers lie, lie, lie. The say they only support two rates, 100 & 100000.
+                    ** But we know that they really support a range of rates!
+                    ** So when we see a ridiculous set of rates, assume it is a range.
+                    */
+                  deviceInfo->defaultSampleRate = 48000.0f;  /* assume 48000 as the default */
+                  DBUG(("PA - Sample rate range used instead of two odd values for device #%s\n", name ));
+                }
+                else deviceInfo->defaultSampleRate = caps.dwMaxSecondarySampleRate;
+
+                //printf( "min %d max %d\n", caps.dwMinSecondarySampleRate, caps.dwMaxSecondarySampleRate );
+                // dwFlags | DSCAPS_CONTINUOUSRATE
+
+                deviceInfo->defaultLowInputLatency = 0.;
+                deviceInfo->defaultHighInputLatency = 0.;
+
+                deviceInfo->defaultLowOutputLatency = PaWinDs_GetMinLatencySeconds( deviceInfo->defaultSampleRate );
+                deviceInfo->defaultHighOutputLatency = deviceInfo->defaultLowOutputLatency * 2;
+            }
+        }
+
+        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 accommodate 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, char *pnpInterface )
+{
+    PaUtilHostApiRepresentation  *hostApi = &winDsHostApi->inheritedHostApiRep;
+    PaWinDsDeviceInfo            *winDsDeviceInfo = (PaWinDsDeviceInfo*) hostApi->deviceInfos[hostApi->info.deviceCount];
+    PaDeviceInfo                 *deviceInfo = &winDsDeviceInfo->inheritedDeviceInfo;
+    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 = paWinDsDSoundEntryPoints.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
+        {
+#if PA_USE_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;
+                winDsDeviceInfo->deviceInputChannelCountIsKnown = 1;
+
+                deviceInfo->maxOutputChannels = 0;
+                winDsDeviceInfo->deviceOutputChannelCountIsKnown = 1;
+
+#ifdef PAWIN_USE_WDMKS_DEVICE_INFO
+                if( pnpInterface )
+                {
+                    int count = PaWin_WDMKS_QueryFilterMaximumChannelCount( pnpInterface, /* isInput= */ 1  );
+                    if( count > 0 )
+                    {
+                        deviceInfo->maxInputChannels = count;
+                        winDsDeviceInfo->deviceInputChannelCountIsKnown = 1;
+                    }
+                }
+#endif /* PAWIN_USE_WDMKS_DEVICE_INFO */
+
+/*  constants from a WINE patch by Francois Gouget, see:
+    http://www.winehq.com/hypermail/wine-patches/2003/01/0290.html
+
+    ---
+    Date: Fri, 14 May 2004 10:38:12 +0200 (CEST)
+    From: Francois Gouget <fgouget@ ... .fr>
+    To: Ross Bencina <rbencina@ ... .au>
+    Subject: Re: Permission to use wine 48/96 wave patch in BSD licensed library
+
+    [snip]
+
+    I give you permission to use the patch below under the BSD license.
+    http://www.winehq.com/hypermail/wine-patches/2003/01/0290.html
+
+    [snip]
+*/
+#ifndef WAVE_FORMAT_48M08
+#define WAVE_FORMAT_48M08      0x00001000    /* 48     kHz, Mono,   8-bit  */
+#define WAVE_FORMAT_48S08      0x00002000    /* 48     kHz, Stereo, 8-bit  */
+#define WAVE_FORMAT_48M16      0x00004000    /* 48     kHz, Mono,   16-bit */
+#define WAVE_FORMAT_48S16      0x00008000    /* 48     kHz, Stereo, 16-bit */
+#define WAVE_FORMAT_96M08      0x00010000    /* 96     kHz, Mono,   8-bit  */
+#define WAVE_FORMAT_96S08      0x00020000    /* 96     kHz, Stereo, 8-bit  */
+#define WAVE_FORMAT_96M16      0x00040000    /* 96     kHz, Mono,   16-bit */
+#define WAVE_FORMAT_96S16      0x00080000    /* 96     kHz, Stereo, 16-bit */
+#endif
+
+                /* defaultSampleRate */
+                if( caps.dwChannels == 2 )
+                {
+                    if( caps.dwFormats & WAVE_FORMAT_4S16 )
+                        deviceInfo->defaultSampleRate = 44100.0;
+                    else if( caps.dwFormats & WAVE_FORMAT_48S16 )
+                        deviceInfo->defaultSampleRate = 48000.0;
+                    else if( caps.dwFormats & WAVE_FORMAT_2S16 )
+                        deviceInfo->defaultSampleRate = 22050.0;
+                    else if( caps.dwFormats & WAVE_FORMAT_1S16 )
+                        deviceInfo->defaultSampleRate = 11025.0;
+                    else if( caps.dwFormats & WAVE_FORMAT_96S16 )
+                        deviceInfo->defaultSampleRate = 96000.0;
+                    else
+                        deviceInfo->defaultSampleRate = 48000.0; /* assume 48000 as the default */
+                }
+                else if( caps.dwChannels == 1 )
+                {
+                    if( caps.dwFormats & WAVE_FORMAT_4M16 )
+                        deviceInfo->defaultSampleRate = 44100.0;
+                    else if( caps.dwFormats & WAVE_FORMAT_48M16 )
+                        deviceInfo->defaultSampleRate = 48000.0;
+                    else if( caps.dwFormats & WAVE_FORMAT_2M16 )
+                        deviceInfo->defaultSampleRate = 22050.0;
+                    else if( caps.dwFormats & WAVE_FORMAT_1M16 )
+                        deviceInfo->defaultSampleRate = 11025.0;
+                    else if( caps.dwFormats & WAVE_FORMAT_96M16 )
+                        deviceInfo->defaultSampleRate = 96000.0;
+                    else
+                        deviceInfo->defaultSampleRate = 48000.0; /* assume 48000 as the default */
+                }
+                else deviceInfo->defaultSampleRate = 48000.0; /* assume 48000 as the default */
+
+                deviceInfo->defaultLowInputLatency = PaWinDs_GetMinLatencySeconds( deviceInfo->defaultSampleRate );
+                deviceInfo->defaultHighInputLatency = deviceInfo->defaultLowInputLatency * 2;
+
+                deviceInfo->defaultLowOutputLatency = 0.;
+                deviceInfo->defaultHighOutputLatency = 0.;
+            }
+        }
+
+        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;
+    DSDeviceNamesAndGUIDs deviceNamesAndGUIDs;
+    PaWinDsDeviceInfo *deviceInfoArray;
+
+    PaWinDs_InitializeDSoundEntryPoints();
+
+    /* initialise guid vectors so they can be safely deleted on error */
+    deviceNamesAndGUIDs.winDsHostApi = NULL;
+    deviceNamesAndGUIDs.inputNamesAndGUIDs.items = NULL;
+    deviceNamesAndGUIDs.outputNamesAndGUIDs.items = NULL;
+
+    winDsHostApi = (PaWinDsHostApiRepresentation*)PaUtil_AllocateMemory( sizeof(PaWinDsHostApiRepresentation) );
+    if( !winDsHostApi )
+    {
+        result = paInsufficientMemory;
+        goto error;
+    }
+
+    memset( winDsHostApi, 0, sizeof(PaWinDsHostApiRepresentation) ); /* ensure all fields are zeroed. especially winDsHostApi->allocations */
+
+    result = PaWinUtil_CoInitialize( paDirectSound, &winDsHostApi->comInitializationResult );
+    if( result != paNoError )
+    {
+        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( &deviceNamesAndGUIDs.inputNamesAndGUIDs, winDsHostApi->allocations );
+    if( result != paNoError )
+        goto error;
+
+    result = InitializeDSDeviceNameAndGUIDVector( &deviceNamesAndGUIDs.outputNamesAndGUIDs, winDsHostApi->allocations );
+    if( result != paNoError )
+        goto error;
+
+    paWinDsDSoundEntryPoints.DirectSoundCaptureEnumerateW( (LPDSENUMCALLBACKW)CollectGUIDsProcW, (void *)&deviceNamesAndGUIDs.inputNamesAndGUIDs );
+
+    paWinDsDSoundEntryPoints.DirectSoundEnumerateW( (LPDSENUMCALLBACKW)CollectGUIDsProcW, (void *)&deviceNamesAndGUIDs.outputNamesAndGUIDs );
+
+    if( deviceNamesAndGUIDs.inputNamesAndGUIDs.enumerationError != paNoError )
+    {
+        result = deviceNamesAndGUIDs.inputNamesAndGUIDs.enumerationError;
+        goto error;
+    }
+
+    if( deviceNamesAndGUIDs.outputNamesAndGUIDs.enumerationError != paNoError )
+    {
+        result = deviceNamesAndGUIDs.outputNamesAndGUIDs.enumerationError;
+        goto error;
+    }
+
+    deviceCount = deviceNamesAndGUIDs.inputNamesAndGUIDs.count + deviceNamesAndGUIDs.outputNamesAndGUIDs.count;
+
+#ifdef PAWIN_USE_WDMKS_DEVICE_INFO
+    if( deviceCount > 0 )
+    {
+        deviceNamesAndGUIDs.winDsHostApi = winDsHostApi;
+        FindDevicePnpInterfaces( &deviceNamesAndGUIDs );
+    }
+#endif /* PAWIN_USE_WDMKS_DEVICE_INFO */
+
+    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 = (PaWinDsDeviceInfo*)PaUtil_GroupAllocateMemory(
+                winDsHostApi->allocations, sizeof(PaWinDsDeviceInfo) * deviceCount );
+        if( !deviceInfoArray )
+        {
+            result = paInsufficientMemory;
+            goto error;
+        }
+
+        for( i=0; i < deviceCount; ++i )
+        {
+            PaDeviceInfo *deviceInfo = &deviceInfoArray[i].inheritedDeviceInfo;
+            deviceInfo->structVersion = 2;
+            deviceInfo->hostApi = hostApiIndex;
+            deviceInfo->name = 0;
+            (*hostApi)->deviceInfos[i] = deviceInfo;
+        }
+
+        for( i=0; i < deviceNamesAndGUIDs.inputNamesAndGUIDs.count; ++i )
+        {
+            result = AddInputDeviceInfoFromDirectSoundCapture( winDsHostApi,
+                    deviceNamesAndGUIDs.inputNamesAndGUIDs.items[i].name,
+                    deviceNamesAndGUIDs.inputNamesAndGUIDs.items[i].lpGUID,
+                    deviceNamesAndGUIDs.inputNamesAndGUIDs.items[i].pnpInterface );
+            if( result != paNoError )
+                goto error;
+        }
+
+        for( i=0; i < deviceNamesAndGUIDs.outputNamesAndGUIDs.count; ++i )
+        {
+            result = AddOutputDeviceInfoFromDirectSound( winDsHostApi,
+                    deviceNamesAndGUIDs.outputNamesAndGUIDs.items[i].name,
+                    deviceNamesAndGUIDs.outputNamesAndGUIDs.items[i].lpGUID,
+                    deviceNamesAndGUIDs.outputNamesAndGUIDs.items[i].pnpInterface );
+            if( result != paNoError )
+                goto error;
+        }
+    }
+
+    result = TerminateDSDeviceNameAndGUIDVector( &deviceNamesAndGUIDs.inputNamesAndGUIDs );
+    if( result != paNoError )
+        goto error;
+
+    result = TerminateDSDeviceNameAndGUIDVector( &deviceNamesAndGUIDs.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:
+    TerminateDSDeviceNameAndGUIDVector( &deviceNamesAndGUIDs.inputNamesAndGUIDs );
+    TerminateDSDeviceNameAndGUIDVector( &deviceNamesAndGUIDs.outputNamesAndGUIDs );
+
+    Terminate( (struct PaUtilHostApiRepresentation *)winDsHostApi );
+
+    return result;
+}
+
+
+/***********************************************************************************/
+static void Terminate( struct PaUtilHostApiRepresentation *hostApi )
+{
+    PaWinDsHostApiRepresentation *winDsHostApi = (PaWinDsHostApiRepresentation*)hostApi;
+
+    if( winDsHostApi ){
+        if( winDsHostApi->allocations )
+        {
+            PaUtil_FreeAllAllocations( winDsHostApi->allocations );
+            PaUtil_DestroyAllocationGroup( winDsHostApi->allocations );
+        }
+
+        PaWinUtil_CoUninitialize( paDirectSound, &winDsHostApi->comInitializationResult );
+
+        PaUtil_FreeMemory( winDsHostApi );
+    }
+
+    PaWinDs_TerminateDSoundEntryPoints();
+}
+
+static PaError ValidateWinDirectSoundSpecificStreamInfo(
+        const PaStreamParameters *streamParameters,
+        const PaWinDirectSoundStreamInfo *streamInfo )
+{
+    if( streamInfo )
+    {
+        if( streamInfo->size != sizeof( PaWinDirectSoundStreamInfo )
+                || streamInfo->version != 2 )
+        {
+            return paIncompatibleHostApiSpecificStreamInfo;
+        }
+
+        if( streamInfo->flags & paWinDirectSoundUseLowLevelLatencyParameters )
+        {
+            if( streamInfo->framesPerBuffer <= 0 )
+                return paIncompatibleHostApiSpecificStreamInfo;
+
+        }
+    }
+
+    return paNoError;
+}
+
+/***********************************************************************************/
+static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi,
+                                  const PaStreamParameters *inputParameters,
+                                  const PaStreamParameters *outputParameters,
+                                  double sampleRate )
+{
+    PaError result;
+    PaWinDsDeviceInfo *inputWinDsDeviceInfo, *outputWinDsDeviceInfo;
+    PaDeviceInfo *inputDeviceInfo, *outputDeviceInfo;
+    int inputChannelCount, outputChannelCount;
+    PaSampleFormat inputSampleFormat, outputSampleFormat;
+    PaWinDirectSoundStreamInfo *inputStreamInfo, *outputStreamInfo;
+
+    if( inputParameters )
+    {
+        inputWinDsDeviceInfo = (PaWinDsDeviceInfo*) hostApi->deviceInfos[ inputParameters->device ];
+        inputDeviceInfo = &inputWinDsDeviceInfo->inheritedDeviceInfo;
+
+        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( inputWinDsDeviceInfo->deviceInputChannelCountIsKnown
+                && inputChannelCount > inputDeviceInfo->maxInputChannels )
+            return paInvalidChannelCount;
+
+        /* validate inputStreamInfo */
+        inputStreamInfo = (PaWinDirectSoundStreamInfo*)inputParameters->hostApiSpecificStreamInfo;
+        result = ValidateWinDirectSoundSpecificStreamInfo( inputParameters, inputStreamInfo );
+        if( result != paNoError ) return result;
+    }
+    else
+    {
+        inputChannelCount = 0;
+    }
+
+    if( outputParameters )
+    {
+        outputWinDsDeviceInfo = (PaWinDsDeviceInfo*) hostApi->deviceInfos[ outputParameters->device ];
+        outputDeviceInfo = &outputWinDsDeviceInfo->inheritedDeviceInfo;
+
+        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( outputWinDsDeviceInfo->deviceOutputChannelCountIsKnown
+                && outputChannelCount > outputDeviceInfo->maxOutputChannels )
+            return paInvalidChannelCount;
+
+        /* validate outputStreamInfo */
+        outputStreamInfo = (PaWinDirectSoundStreamInfo*)outputParameters->hostApiSpecificStreamInfo;
+        result = ValidateWinDirectSoundSpecificStreamInfo( outputParameters, outputStreamInfo );
+        if( result != paNoError ) return result;
+    }
+    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;
+}
+
+
+#ifdef PAWIN_USE_DIRECTSOUNDFULLDUPLEXCREATE
+static HRESULT InitFullDuplexInputOutputBuffers( PaWinDsStream *stream,
+                                       PaWinDsDeviceInfo *inputDevice,
+                                       PaSampleFormat hostInputSampleFormat,
+                                       WORD inputChannelCount,
+                                       int bytesPerInputBuffer,
+                                       PaWinWaveFormatChannelMask inputChannelMask,
+                                       PaWinDsDeviceInfo *outputDevice,
+                                       PaSampleFormat hostOutputSampleFormat,
+                                       WORD outputChannelCount,
+                                       int bytesPerOutputBuffer,
+                                       PaWinWaveFormatChannelMask outputChannelMask,
+                                       unsigned long nFrameRate
+                                        )
+{
+    HRESULT hr;
+    DSCBUFFERDESC  captureDesc;
+    PaWinWaveFormat captureWaveFormat;
+    DSBUFFERDESC   secondaryRenderDesc;
+    PaWinWaveFormat renderWaveFormat;
+    LPDIRECTSOUNDBUFFER8 pRenderBuffer8;
+    LPDIRECTSOUNDCAPTUREBUFFER8 pCaptureBuffer8;
+
+    // capture buffer description
+
+    // only try wave format extensible. assume it's available on all ds 8 systems
+    PaWin_InitializeWaveFormatExtensible( &captureWaveFormat, inputChannelCount,
+                hostInputSampleFormat, PaWin_SampleFormatToLinearWaveFormatTag( hostInputSampleFormat ),
+                nFrameRate, inputChannelMask );
+
+    ZeroMemory(&captureDesc, sizeof(DSCBUFFERDESC));
+    captureDesc.dwSize = sizeof(DSCBUFFERDESC);
+    captureDesc.dwFlags = 0;
+    captureDesc.dwBufferBytes = bytesPerInputBuffer;
+    captureDesc.lpwfxFormat = (WAVEFORMATEX*)&captureWaveFormat;
+
+    // render buffer description
+
+    PaWin_InitializeWaveFormatExtensible( &renderWaveFormat, outputChannelCount,
+                hostOutputSampleFormat, PaWin_SampleFormatToLinearWaveFormatTag( hostOutputSampleFormat ),
+                nFrameRate, outputChannelMask );
+
+    ZeroMemory(&secondaryRenderDesc, sizeof(DSBUFFERDESC));
+    secondaryRenderDesc.dwSize = sizeof(DSBUFFERDESC);
+    secondaryRenderDesc.dwFlags = DSBCAPS_GLOBALFOCUS | DSBCAPS_GETCURRENTPOSITION2;
+    secondaryRenderDesc.dwBufferBytes = bytesPerOutputBuffer;
+    secondaryRenderDesc.lpwfxFormat = (WAVEFORMATEX*)&renderWaveFormat;
+
+    /* note that we don't create a primary buffer here at all */
+
+    hr = paWinDsDSoundEntryPoints.DirectSoundFullDuplexCreate8(
+            inputDevice->lpGUID, outputDevice->lpGUID,
+            &captureDesc, &secondaryRenderDesc,
+            GetDesktopWindow(), /* see InitOutputBuffer() for a discussion of whether this is a good idea */
+            DSSCL_EXCLUSIVE,
+            &stream->pDirectSoundFullDuplex8,
+            &pCaptureBuffer8,
+            &pRenderBuffer8,
+            NULL /* pUnkOuter must be NULL */
+        );
+
+    if( hr == DS_OK )
+    {
+        PA_DEBUG(("DirectSoundFullDuplexCreate succeeded!\n"));
+
+        /* retrieve the pre ds 8 buffer interfaces which are used by the rest of the code */
+
+        hr = IUnknown_QueryInterface( pCaptureBuffer8, &IID_IDirectSoundCaptureBuffer, (LPVOID *)&stream->pDirectSoundInputBuffer );
+
+        if( hr == DS_OK )
+            hr = IUnknown_QueryInterface( pRenderBuffer8, &IID_IDirectSoundBuffer, (LPVOID *)&stream->pDirectSoundOutputBuffer );
+
+        /* release the ds 8 interfaces, we don't need them */
+        IUnknown_Release( pCaptureBuffer8 );
+        IUnknown_Release( pRenderBuffer8 );
+
+        if( !stream->pDirectSoundInputBuffer || !stream->pDirectSoundOutputBuffer ){
+            /* couldn't get pre ds 8 interfaces for some reason. clean up. */
+            if( stream->pDirectSoundInputBuffer )
+            {
+                IUnknown_Release( stream->pDirectSoundInputBuffer );
+                stream->pDirectSoundInputBuffer = NULL;
+            }
+
+            if( stream->pDirectSoundOutputBuffer )
+            {
+                IUnknown_Release( stream->pDirectSoundOutputBuffer );
+                stream->pDirectSoundOutputBuffer = NULL;
+            }
+
+            IUnknown_Release( stream->pDirectSoundFullDuplex8 );
+            stream->pDirectSoundFullDuplex8 = NULL;
+        }
+    }
+    else
+    {
+        PA_DEBUG(("DirectSoundFullDuplexCreate failed. hr=%d\n", hr));
+    }
+
+    return hr;
+}
+#endif /* PAWIN_USE_DIRECTSOUNDFULLDUPLEXCREATE */
+
+
+static HRESULT InitInputBuffer( PaWinDsStream *stream,
+                               PaWinDsDeviceInfo *device,
+                               PaSampleFormat sampleFormat,
+                               unsigned long nFrameRate,
+                               WORD nChannels,
+                               int bytesPerBuffer,
+                               PaWinWaveFormatChannelMask channelMask )
+{
+    DSCBUFFERDESC  captureDesc;
+    PaWinWaveFormat waveFormat;
+    HRESULT        result;
+
+    if( (result = paWinDsDSoundEntryPoints.DirectSoundCaptureCreate(
+            device->lpGUID, &stream->pDirectSoundCapture, NULL) ) != DS_OK ){
+        ERR_RPT(("PortAudio: DirectSoundCaptureCreate() failed!\n"));
+        return result;
+    }
+
+    // Setup the secondary buffer description
+    ZeroMemory(&captureDesc, sizeof(DSCBUFFERDESC));
+    captureDesc.dwSize = sizeof(DSCBUFFERDESC);
+    captureDesc.dwFlags = 0;
+    captureDesc.dwBufferBytes = bytesPerBuffer;
+    captureDesc.lpwfxFormat = (WAVEFORMATEX*)&waveFormat;
+
+    // Create the capture buffer
+
+    // first try WAVEFORMATEXTENSIBLE. if this fails, fall back to WAVEFORMATEX
+    PaWin_InitializeWaveFormatExtensible( &waveFormat, nChannels,
+                sampleFormat, PaWin_SampleFormatToLinearWaveFormatTag( sampleFormat ),
+                nFrameRate, channelMask );
+
+    if( IDirectSoundCapture_CreateCaptureBuffer( stream->pDirectSoundCapture,
+                  &captureDesc, &stream->pDirectSoundInputBuffer, NULL) != DS_OK )
+    {
+        PaWin_InitializeWaveFormatEx( &waveFormat, nChannels, sampleFormat,
+                PaWin_SampleFormatToLinearWaveFormatTag( sampleFormat ), nFrameRate );
+
+        if ((result = IDirectSoundCapture_CreateCaptureBuffer( stream->pDirectSoundCapture,
+                    &captureDesc, &stream->pDirectSoundInputBuffer, NULL)) != DS_OK) return result;
+    }
+
+    stream->readOffset = 0;  // reset last read position to start of buffer
+    return DS_OK;
+}
+
+
+static HRESULT InitOutputBuffer( PaWinDsStream *stream, PaWinDsDeviceInfo *device,
+                                PaSampleFormat sampleFormat, unsigned long nFrameRate,
+                                WORD nChannels, int bytesPerBuffer,
+                                PaWinWaveFormatChannelMask channelMask )
+{
+    HRESULT        result;
+    HWND           hWnd;
+    HRESULT        hr;
+    PaWinWaveFormat waveFormat;
+    DSBUFFERDESC   primaryDesc;
+    DSBUFFERDESC   secondaryDesc;
+
+    if( (hr = paWinDsDSoundEntryPoints.DirectSoundCreate(
+                device->lpGUID, &stream->pDirectSound, NULL )) != DS_OK ){
+        ERR_RPT(("PortAudio: DirectSoundCreate() failed!\n"));
+        return hr;
+    }
+
+    // We were using getForegroundWindow() but sometimes the ForegroundWindow may not be the
+    // applications's window. Also if that window is closed before the Buffer is closed
+    // then DirectSound can crash. (Thanks for Scott Patterson for reporting this.)
+    // So we will use GetDesktopWindow() which was suggested by Miller Puckette.
+    // hWnd = GetForegroundWindow();
+    //
+    //  FIXME: The example code I have on the net creates a hidden window that
+    //      is managed by our code - I think we should do that - one hidden
+    //      window for the whole of Pa_DS
+    //
+    hWnd = GetDesktopWindow();
+
+    // Set cooperative level to DSSCL_EXCLUSIVE so that we can get 16 bit output, 44.1 KHz.
+    // exclusive also prevents unexpected sounds from other apps during a performance.
+    if ((hr = IDirectSound_SetCooperativeLevel( stream->pDirectSound,
+              hWnd, DSSCL_EXCLUSIVE)) != DS_OK)
+    {
+        return hr;
+    }
+
+    // -----------------------------------------------------------------------
+    // Create primary buffer and set format just so we can specify our custom format.
+    // Otherwise we would be stuck with the default which might be 8 bit or 22050 Hz.
+    // Setup the primary buffer description
+    ZeroMemory(&primaryDesc, sizeof(DSBUFFERDESC));
+    primaryDesc.dwSize        = sizeof(DSBUFFERDESC);
+    primaryDesc.dwFlags       = DSBCAPS_PRIMARYBUFFER; // all panning, mixing, etc done by synth
+    primaryDesc.dwBufferBytes = 0;
+    primaryDesc.lpwfxFormat   = NULL;
+    // Create the buffer
+    if ((result = IDirectSound_CreateSoundBuffer( stream->pDirectSound,
+                  &primaryDesc, &stream->pDirectSoundPrimaryBuffer, NULL)) != DS_OK)
+        goto error;
+
+    // Set the primary buffer's format
+
+    // first try WAVEFORMATEXTENSIBLE. if this fails, fall back to WAVEFORMATEX
+    PaWin_InitializeWaveFormatExtensible( &waveFormat, nChannels,
+                sampleFormat, PaWin_SampleFormatToLinearWaveFormatTag( sampleFormat ),
+                nFrameRate, channelMask );
+
+    if( IDirectSoundBuffer_SetFormat( stream->pDirectSoundPrimaryBuffer, (WAVEFORMATEX*)&waveFormat) != DS_OK )
+    {
+        PaWin_InitializeWaveFormatEx( &waveFormat, nChannels, sampleFormat,
+                PaWin_SampleFormatToLinearWaveFormatTag( sampleFormat ), nFrameRate );
+
+        if((result = IDirectSoundBuffer_SetFormat( stream->pDirectSoundPrimaryBuffer, (WAVEFORMATEX*)&waveFormat)) != DS_OK)
+            goto error;
+    }
+
+    // ----------------------------------------------------------------------
+    // Setup the secondary buffer description
+    ZeroMemory(&secondaryDesc, sizeof(DSBUFFERDESC));
+    secondaryDesc.dwSize = sizeof(DSBUFFERDESC);
+    secondaryDesc.dwFlags = DSBCAPS_GLOBALFOCUS | DSBCAPS_GETCURRENTPOSITION2;
+    secondaryDesc.dwBufferBytes = bytesPerBuffer;
+    secondaryDesc.lpwfxFormat = (WAVEFORMATEX*)&waveFormat; /* waveFormat contains whatever format was negotiated for the primary buffer above */
+    // Create the secondary buffer
+    if ((result = IDirectSound_CreateSoundBuffer( stream->pDirectSound,
+                  &secondaryDesc, &stream->pDirectSoundOutputBuffer, NULL)) != DS_OK)
+        goto error;
+
+    return DS_OK;
+
+error:
+
+    if( stream->pDirectSoundPrimaryBuffer )
+    {
+        IDirectSoundBuffer_Release( stream->pDirectSoundPrimaryBuffer );
+        stream->pDirectSoundPrimaryBuffer = NULL;
+    }
+
+    return result;
+}
+
+
+static void CalculateBufferSettings( unsigned long *hostBufferSizeFrames,
+                                    unsigned long *pollingPeriodFrames,
+                                    int isFullDuplex,
+                                    unsigned long suggestedInputLatencyFrames,
+                                    unsigned long suggestedOutputLatencyFrames,
+                                    double sampleRate, unsigned long userFramesPerBuffer )
+{
+    unsigned long minimumPollingPeriodFrames = (unsigned long)(sampleRate * PA_DS_MINIMUM_POLLING_PERIOD_SECONDS);
+    unsigned long maximumPollingPeriodFrames = (unsigned long)(sampleRate * PA_DS_MAXIMUM_POLLING_PERIOD_SECONDS);
+    unsigned long pollingJitterFrames = (unsigned long)(sampleRate * PA_DS_POLLING_JITTER_SECONDS);
+
+    if( userFramesPerBuffer == paFramesPerBufferUnspecified )
+    {
+        unsigned long targetBufferingLatencyFrames = max( suggestedInputLatencyFrames, suggestedOutputLatencyFrames );
+
+        *pollingPeriodFrames = targetBufferingLatencyFrames / 4;
+        if( *pollingPeriodFrames < minimumPollingPeriodFrames )
+        {
+            *pollingPeriodFrames = minimumPollingPeriodFrames;
+        }
+        else if( *pollingPeriodFrames > maximumPollingPeriodFrames )
+        {
+            *pollingPeriodFrames = maximumPollingPeriodFrames;
+        }
+
+        *hostBufferSizeFrames = *pollingPeriodFrames
+                + max( *pollingPeriodFrames + pollingJitterFrames, targetBufferingLatencyFrames);
+    }
+    else
+    {
+        unsigned long targetBufferingLatencyFrames = suggestedInputLatencyFrames;
+        if( isFullDuplex )
+        {
+            /* In full duplex streams we know that the buffer adapter adds userFramesPerBuffer
+               extra fixed latency. so we subtract it here as a fixed latency before computing
+               the buffer size. being careful not to produce an unrepresentable negative result.
+
+               Note: this only works as expected if output latency is greater than input latency.
+               Otherwise we use input latency anyway since we do max(in,out).
+            */
+
+            if( userFramesPerBuffer < suggestedOutputLatencyFrames )
+            {
+                unsigned long adjustedSuggestedOutputLatencyFrames =
+                        suggestedOutputLatencyFrames - userFramesPerBuffer;
+
+                /* maximum of input and adjusted output suggested latency */
+                if( adjustedSuggestedOutputLatencyFrames > targetBufferingLatencyFrames )
+                    targetBufferingLatencyFrames = adjustedSuggestedOutputLatencyFrames;
+            }
+        }
+        else
+        {
+            /* maximum of input and output suggested latency */
+            if( suggestedOutputLatencyFrames > suggestedInputLatencyFrames )
+                targetBufferingLatencyFrames = suggestedOutputLatencyFrames;
+        }
+
+        *hostBufferSizeFrames = userFramesPerBuffer
+                + max( userFramesPerBuffer + pollingJitterFrames, targetBufferingLatencyFrames);
+
+        *pollingPeriodFrames = max( max(1, userFramesPerBuffer / 4), targetBufferingLatencyFrames / 16 );
+
+        if( *pollingPeriodFrames > maximumPollingPeriodFrames )
+        {
+            *pollingPeriodFrames = maximumPollingPeriodFrames;
+        }
+    }
+}
+
+
+static void CalculatePollingPeriodFrames( unsigned long hostBufferSizeFrames,
+                                    unsigned long *pollingPeriodFrames,
+                                    double sampleRate, unsigned long userFramesPerBuffer )
+{
+    unsigned long minimumPollingPeriodFrames = (unsigned long)(sampleRate * PA_DS_MINIMUM_POLLING_PERIOD_SECONDS);
+    unsigned long maximumPollingPeriodFrames = (unsigned long)(sampleRate * PA_DS_MAXIMUM_POLLING_PERIOD_SECONDS);
+    unsigned long pollingJitterFrames = (unsigned long)(sampleRate * PA_DS_POLLING_JITTER_SECONDS);
+
+    *pollingPeriodFrames = max( max(1, userFramesPerBuffer / 4), hostBufferSizeFrames / 16 );
+
+    if( *pollingPeriodFrames > maximumPollingPeriodFrames )
+    {
+        *pollingPeriodFrames = maximumPollingPeriodFrames;
+    }
+}
+
+
+static void SetStreamInfoLatencies( PaWinDsStream *stream,
+                                   unsigned long userFramesPerBuffer,
+                                   unsigned long pollingPeriodFrames,
+                                   double sampleRate )
+{
+    /* compute the stream info actual latencies based on framesPerBuffer, polling period, hostBufferSizeFrames,
+    and the configuration of the buffer processor */
+
+    unsigned long effectiveFramesPerBuffer = (userFramesPerBuffer == paFramesPerBufferUnspecified)
+                                             ? pollingPeriodFrames
+                                             : userFramesPerBuffer;
+
+    if( stream->bufferProcessor.inputChannelCount > 0 )
+    {
+        /* stream info input latency is the minimum buffering latency
+           (unlike suggested and default which are *maximums*) */
+        stream->streamRepresentation.streamInfo.inputLatency =
+                (double)(PaUtil_GetBufferProcessorInputLatencyFrames(&stream->bufferProcessor)
+                    + effectiveFramesPerBuffer) / sampleRate;
+    }
+    else
+    {
+        stream->streamRepresentation.streamInfo.inputLatency = 0;
+    }
+
+    if( stream->bufferProcessor.outputChannelCount > 0 )
+    {
+        stream->streamRepresentation.streamInfo.outputLatency =
+                (double)(PaUtil_GetBufferProcessorOutputLatencyFrames(&stream->bufferProcessor)
+                    + (stream->hostBufferSizeFrames - effectiveFramesPerBuffer)) / sampleRate;
+    }
+    else
+    {
+        stream->streamRepresentation.streamInfo.outputLatency = 0;
+    }
+}
+
+
+/***********************************************************************************/
+/* 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 bufferProcessorIsInitialized = 0;
+    int streamRepresentationIsInitialized = 0;
+    PaWinDsDeviceInfo *inputWinDsDeviceInfo, *outputWinDsDeviceInfo;
+    PaDeviceInfo *inputDeviceInfo, *outputDeviceInfo;
+    int inputChannelCount, outputChannelCount;
+    PaSampleFormat inputSampleFormat, outputSampleFormat;
+    PaSampleFormat hostInputSampleFormat, hostOutputSampleFormat;
+    int userRequestedHostInputBufferSizeFrames = 0;
+    int userRequestedHostOutputBufferSizeFrames = 0;
+    unsigned long suggestedInputLatencyFrames, suggestedOutputLatencyFrames;
+    PaWinDirectSoundStreamInfo *inputStreamInfo, *outputStreamInfo;
+    PaWinWaveFormatChannelMask inputChannelMask, outputChannelMask;
+    unsigned long pollingPeriodFrames = 0;
+
+    if( inputParameters )
+    {
+        inputWinDsDeviceInfo = (PaWinDsDeviceInfo*) hostApi->deviceInfos[ inputParameters->device ];
+        inputDeviceInfo = &inputWinDsDeviceInfo->inheritedDeviceInfo;
+
+        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( inputWinDsDeviceInfo->deviceInputChannelCountIsKnown
+                && inputChannelCount > inputDeviceInfo->maxInputChannels )
+            return paInvalidChannelCount;
+
+        /* validate hostApiSpecificStreamInfo */
+        inputStreamInfo = (PaWinDirectSoundStreamInfo*)inputParameters->hostApiSpecificStreamInfo;
+        result = ValidateWinDirectSoundSpecificStreamInfo( inputParameters, inputStreamInfo );
+        if( result != paNoError ) return result;
+
+        if( inputStreamInfo && inputStreamInfo->flags & paWinDirectSoundUseLowLevelLatencyParameters )
+            userRequestedHostInputBufferSizeFrames = inputStreamInfo->framesPerBuffer;
+
+        if( inputStreamInfo && inputStreamInfo->flags & paWinDirectSoundUseChannelMask )
+            inputChannelMask = inputStreamInfo->channelMask;
+        else
+            inputChannelMask = PaWin_DefaultChannelMask( inputChannelCount );
+    }
+    else
+    {
+        inputChannelCount = 0;
+        inputSampleFormat = 0;
+        suggestedInputLatencyFrames = 0;
+    }
+
+
+    if( outputParameters )
+    {
+        outputWinDsDeviceInfo = (PaWinDsDeviceInfo*) hostApi->deviceInfos[ outputParameters->device ];
+        outputDeviceInfo = &outputWinDsDeviceInfo->inheritedDeviceInfo;
+
+        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 outputChannelCount */
+        if( outputWinDsDeviceInfo->deviceOutputChannelCountIsKnown
+                && outputChannelCount > outputDeviceInfo->maxOutputChannels )
+            return paInvalidChannelCount;
+
+        /* validate hostApiSpecificStreamInfo */
+        outputStreamInfo = (PaWinDirectSoundStreamInfo*)outputParameters->hostApiSpecificStreamInfo;
+        result = ValidateWinDirectSoundSpecificStreamInfo( outputParameters, outputStreamInfo );
+        if( result != paNoError ) return result;
+
+        if( outputStreamInfo && outputStreamInfo->flags & paWinDirectSoundUseLowLevelLatencyParameters )
+            userRequestedHostOutputBufferSizeFrames = outputStreamInfo->framesPerBuffer;
+
+        if( outputStreamInfo && outputStreamInfo->flags & paWinDirectSoundUseChannelMask )
+            outputChannelMask = outputStreamInfo->channelMask;
+        else
+            outputChannelMask = PaWin_DefaultChannelMask( outputChannelCount );
+    }
+    else
+    {
+        outputChannelCount = 0;
+        outputSampleFormat = 0;
+        suggestedOutputLatencyFrames = 0;
+    }
+
+    /*
+        If low level host buffer size is specified for both input and output
+        the current code requires the sizes to match.
+    */
+
+    if( (userRequestedHostInputBufferSizeFrames > 0 && userRequestedHostOutputBufferSizeFrames > 0)
+            && userRequestedHostInputBufferSizeFrames != userRequestedHostOutputBufferSizeFrames )
+        return paIncompatibleHostApiSpecificStreamInfo;
+
+
+
+    /*
+        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;
+    }
+
+    memset( stream, 0, sizeof(PaWinDsStream) ); /* initialize all stream variables to 0 */
+
+    if( streamCallback )
+    {
+        PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation,
+                                               &winDsHostApi->callbackStreamInterface, streamCallback, userData );
+    }
+    else
+    {
+        PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation,
+                                               &winDsHostApi->blockingStreamInterface, streamCallback, userData );
+    }
+
+    streamRepresentationIsInitialized = 1;
+
+    stream->streamFlags = streamFlags;
+
+    PaUtil_InitializeCpuLoadMeasurer( &stream->cpuLoadMeasurer, sampleRate );
+
+
+    if( inputParameters )
+    {
+        /* IMPLEMENT ME - establish which  host formats are available */
+        PaSampleFormat nativeInputFormats = paInt16;
+        /* PaSampleFormat nativeFormats = paUInt8 | paInt16 | paInt24 | paInt32 | paFloat32; */
+
+        hostInputSampleFormat =
+            PaUtil_SelectClosestAvailableFormat( nativeInputFormats, inputParameters->sampleFormat );
+    }
+    else
+    {
+        hostInputSampleFormat = 0;
+    }
+
+    if( outputParameters )
+    {
+        /* IMPLEMENT ME - establish which  host formats are available */
+        PaSampleFormat nativeOutputFormats = paInt16;
+        /* PaSampleFormat nativeOutputFormats = paUInt8 | paInt16 | paInt24 | paInt32 | paFloat32; */
+
+        hostOutputSampleFormat =
+            PaUtil_SelectClosestAvailableFormat( nativeOutputFormats, outputParameters->sampleFormat );
+    }
+    else
+    {
+        hostOutputSampleFormat = 0;
+    }
+
+    result =  PaUtil_InitializeBufferProcessor( &stream->bufferProcessor,
+                    inputChannelCount, inputSampleFormat, hostInputSampleFormat,
+                    outputChannelCount, outputSampleFormat, hostOutputSampleFormat,
+                    sampleRate, streamFlags, framesPerBuffer,
+                    0, /* 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;
+
+    bufferProcessorIsInitialized = 1;
+
+
+/* DirectSound specific initialization */
+    {
+        HRESULT          hr;
+        unsigned long    integerSampleRate = (unsigned long) (sampleRate + 0.5);
+
+        stream->processingCompleted = CreateEvent( NULL, /* bManualReset = */ TRUE, /* bInitialState = */ FALSE, NULL );
+        if( stream->processingCompleted == NULL )
+        {
+            result = paInsufficientMemory;
+            goto error;
+        }
+
+#ifdef PA_WIN_DS_USE_WMME_TIMER
+        stream->timerID = 0;
+#endif
+
+#ifdef PA_WIN_DS_USE_WAITABLE_TIMER_OBJECT
+        stream->waitableTimer = (HANDLE)CreateWaitableTimer( 0, FALSE, NULL );
+        if( stream->waitableTimer == NULL )
+        {
+            result = paUnanticipatedHostError;
+            PA_DS_SET_LAST_DIRECTSOUND_ERROR( GetLastError() );
+            goto error;
+        }
+#endif
+
+#ifndef PA_WIN_DS_USE_WMME_TIMER
+        stream->processingThreadCompleted = CreateEvent( NULL, /* bManualReset = */ TRUE, /* bInitialState = */ FALSE, NULL );
+        if( stream->processingThreadCompleted == NULL )
+        {
+            result = paUnanticipatedHostError;
+            PA_DS_SET_LAST_DIRECTSOUND_ERROR( GetLastError() );
+            goto error;
+        }
+#endif
+
+        /* set up i/o parameters */
+
+        if( userRequestedHostInputBufferSizeFrames > 0 || userRequestedHostOutputBufferSizeFrames > 0 )
+        {
+            /* use low level parameters */
+
+            /* since we use the same host buffer size for input and output
+               we choose the highest user specified value.
+            */
+            stream->hostBufferSizeFrames = max( userRequestedHostInputBufferSizeFrames, userRequestedHostOutputBufferSizeFrames );
+
+            CalculatePollingPeriodFrames(
+                    stream->hostBufferSizeFrames, &pollingPeriodFrames,
+                    sampleRate, framesPerBuffer );
+        }
+        else
+        {
+            CalculateBufferSettings( (unsigned long*)&stream->hostBufferSizeFrames, &pollingPeriodFrames,
+                    /* isFullDuplex = */ (inputParameters && outputParameters),
+                    suggestedInputLatencyFrames,
+                    suggestedOutputLatencyFrames,
+                    sampleRate, framesPerBuffer );
+        }
+
+        stream->pollingPeriodSeconds = pollingPeriodFrames / sampleRate;
+
+        DBUG(("DirectSound host buffer size frames: %d, polling period seconds: %f, @ sr: %f\n",
+                stream->hostBufferSizeFrames, stream->pollingPeriodSeconds, sampleRate ));
+
+
+        /* ------------------ OUTPUT */
+        if( outputParameters )
+        {
+            LARGE_INTEGER  counterFrequency;
+
+            /*
+            PaDeviceInfo *deviceInfo = hostApi->deviceInfos[ outputParameters->device ];
+            DBUG(("PaHost_OpenStream: deviceID = 0x%x\n", outputParameters->device));
+            */
+
+            int sampleSizeBytes = Pa_GetSampleSize(hostOutputSampleFormat);
+            stream->outputFrameSizeBytes = outputParameters->channelCount * sampleSizeBytes;
+
+            stream->outputBufferSizeBytes = stream->hostBufferSizeFrames * stream->outputFrameSizeBytes;
+            if( stream->outputBufferSizeBytes < DSBSIZE_MIN )
+            {
+                result = paBufferTooSmall;
+                goto error;
+            }
+            else if( stream->outputBufferSizeBytes > DSBSIZE_MAX )
+            {
+                result = paBufferTooBig;
+                goto error;
+            }
+
+            /* Calculate value used in latency calculation to avoid real-time divides. */
+            stream->secondsPerHostByte = 1.0 /
+                (stream->bufferProcessor.bytesPerHostOutputSample *
+                outputChannelCount * sampleRate);
+
+            stream->outputIsRunning = FALSE;
+            stream->outputUnderflowCount = 0;
+
+            /* perfCounterTicksPerBuffer is used by QueryOutputSpace for overflow detection */
+            if( QueryPerformanceFrequency( &counterFrequency ) )
+            {
+                stream->perfCounterTicksPerBuffer.QuadPart = (counterFrequency.QuadPart * stream->hostBufferSizeFrames) / integerSampleRate;
+            }
+            else
+            {
+                stream->perfCounterTicksPerBuffer.QuadPart = 0;
+            }
+        }
+
+        /* ------------------ INPUT */
+        if( inputParameters )
+        {
+            /*
+            PaDeviceInfo *deviceInfo = hostApi->deviceInfos[ inputParameters->device ];
+            DBUG(("PaHost_OpenStream: deviceID = 0x%x\n", inputParameters->device));
+            */
+
+            int sampleSizeBytes = Pa_GetSampleSize(hostInputSampleFormat);
+            stream->inputFrameSizeBytes = inputParameters->channelCount * sampleSizeBytes;
+
+            stream->inputBufferSizeBytes = stream->hostBufferSizeFrames * stream->inputFrameSizeBytes;
+            if( stream->inputBufferSizeBytes < DSBSIZE_MIN )
+            {
+                result = paBufferTooSmall;
+                goto error;
+            }
+            else if( stream->inputBufferSizeBytes > DSBSIZE_MAX )
+            {
+                result = paBufferTooBig;
+                goto error;
+            }
+        }
+
+        /* open/create the DirectSound buffers */
+
+        /* interface ptrs should be zeroed when stream is zeroed. */
+        assert( stream->pDirectSoundCapture == NULL );
+        assert( stream->pDirectSoundInputBuffer == NULL );
+        assert( stream->pDirectSound == NULL );
+        assert( stream->pDirectSoundPrimaryBuffer == NULL );
+        assert( stream->pDirectSoundOutputBuffer == NULL );
+
+
+        if( inputParameters && outputParameters )
+        {
+#ifdef PAWIN_USE_DIRECTSOUNDFULLDUPLEXCREATE
+            /* try to use the full-duplex DX8 API to create the buffers.
+                if that fails we fall back to the half-duplex API below */
+
+            hr = InitFullDuplexInputOutputBuffers( stream,
+                                       (PaWinDsDeviceInfo*)hostApi->deviceInfos[inputParameters->device],
+                                       hostInputSampleFormat,
+                                       (WORD)inputParameters->channelCount, stream->inputBufferSizeBytes,
+                                       inputChannelMask,
+                                       (PaWinDsDeviceInfo*)hostApi->deviceInfos[outputParameters->device],
+                                       hostOutputSampleFormat,
+                                       (WORD)outputParameters->channelCount, stream->outputBufferSizeBytes,
+                                       outputChannelMask,
+                                       integerSampleRate
+                                        );
+            DBUG(("InitFullDuplexInputOutputBuffers() returns %x\n", hr));
+            /* ignore any error returned by InitFullDuplexInputOutputBuffers.
+                we retry opening the buffers below */
+#endif /* PAWIN_USE_DIRECTSOUNDFULLDUPLEXCREATE */
+        }
+
+        /*  create half duplex buffers. also used for full-duplex streams which didn't
+            succeed when using the full duplex API. that could happen because
+            DX8 or greater isn't installed, the i/o devices aren't the same
+            physical device. etc.
+        */
+
+        if( outputParameters && !stream->pDirectSoundOutputBuffer )
+        {
+            hr = InitOutputBuffer( stream,
+                                       (PaWinDsDeviceInfo*)hostApi->deviceInfos[outputParameters->device],
+                                       hostOutputSampleFormat,
+                                       integerSampleRate,
+                                       (WORD)outputParameters->channelCount, stream->outputBufferSizeBytes,
+                                       outputChannelMask );
+            DBUG(("InitOutputBuffer() returns %x\n", hr));
+            if( hr != DS_OK )
+            {
+                result = paUnanticipatedHostError;
+                PA_DS_SET_LAST_DIRECTSOUND_ERROR( hr );
+                goto error;
+            }
+        }
+
+        if( inputParameters && !stream->pDirectSoundInputBuffer )
+        {
+            hr = InitInputBuffer( stream,
+                                      (PaWinDsDeviceInfo*)hostApi->deviceInfos[inputParameters->device],
+                                      hostInputSampleFormat,
+                                      integerSampleRate,
+                                      (WORD)inputParameters->channelCount, stream->inputBufferSizeBytes,
+                                      inputChannelMask );
+            DBUG(("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;
+            }
+        }
+    }
+
+    SetStreamInfoLatencies( stream, framesPerBuffer, pollingPeriodFrames, sampleRate );
+
+    stream->streamRepresentation.streamInfo.sampleRate = sampleRate;
+
+    *s = (PaStream*)stream;
+
+    return result;
+
+error:
+    if( stream )
+    {
+        if( stream->processingCompleted != NULL )
+            CloseHandle( stream->processingCompleted );
+
+#ifdef PA_WIN_DS_USE_WAITABLE_TIMER_OBJECT
+        if( stream->waitableTimer != NULL )
+            CloseHandle( stream->waitableTimer );
+#endif
+
+#ifndef PA_WIN_DS_USE_WMME_TIMER
+        if( stream->processingThreadCompleted != NULL )
+            CloseHandle( stream->processingThreadCompleted );
+#endif
+
+        if( stream->pDirectSoundOutputBuffer )
+        {
+            IDirectSoundBuffer_Stop( stream->pDirectSoundOutputBuffer );
+            IDirectSoundBuffer_Release( stream->pDirectSoundOutputBuffer );
+            stream->pDirectSoundOutputBuffer = NULL;
+        }
+
+        if( stream->pDirectSoundPrimaryBuffer )
+        {
+            IDirectSoundBuffer_Release( stream->pDirectSoundPrimaryBuffer );
+            stream->pDirectSoundPrimaryBuffer = NULL;
+        }
+
+        if( stream->pDirectSoundInputBuffer )
+        {
+            IDirectSoundCaptureBuffer_Stop( stream->pDirectSoundInputBuffer );
+            IDirectSoundCaptureBuffer_Release( stream->pDirectSoundInputBuffer );
+            stream->pDirectSoundInputBuffer = NULL;
+        }
+
+        if( stream->pDirectSoundCapture )
+        {
+            IDirectSoundCapture_Release( stream->pDirectSoundCapture );
+            stream->pDirectSoundCapture = NULL;
+        }
+
+        if( stream->pDirectSound )
+        {
+            IDirectSound_Release( stream->pDirectSound );
+            stream->pDirectSound = NULL;
+        }
+
+#ifdef PAWIN_USE_DIRECTSOUNDFULLDUPLEXCREATE
+        if( stream->pDirectSoundFullDuplex8 )
+        {
+            IDirectSoundFullDuplex_Release( stream->pDirectSoundFullDuplex8 );
+            stream->pDirectSoundFullDuplex8 = NULL;
+        }
+#endif
+        if( bufferProcessorIsInitialized )
+            PaUtil_TerminateBufferProcessor( &stream->bufferProcessor );
+
+        if( streamRepresentationIsInitialized )
+            PaUtil_TerminateStreamRepresentation( &stream->streamRepresentation );
+
+        PaUtil_FreeMemory( stream );
+    }
+
+    return result;
+}
+
+
+/************************************************************************************
+ * Determine how much space can be safely written to in DS buffer.
+ * Detect underflows and overflows.
+ * Does not allow writing into safety gap maintained by DirectSound.
+ */
+static HRESULT QueryOutputSpace( PaWinDsStream *stream, long *bytesEmpty )
+{
+    HRESULT hr;
+    DWORD   playCursor;
+    DWORD   writeCursor;
+    long    numBytesEmpty;
+    long    playWriteGap;
+    // Query to see how much room is in buffer.
+    hr = IDirectSoundBuffer_GetCurrentPosition( stream->pDirectSoundOutputBuffer,
+            &playCursor, &writeCursor );
+    if( hr != DS_OK )
+    {
+        return hr;
+    }
+
+    // Determine size of gap between playIndex and WriteIndex that we cannot write into.
+    playWriteGap = writeCursor - playCursor;
+    if( playWriteGap < 0 ) playWriteGap += stream->outputBufferSizeBytes; // unwrap
+
+    /* DirectSound doesn't have a large enough playCursor so we cannot detect wrap-around. */
+    /* Attempt to detect playCursor wrap-around and correct it. */
+    if( stream->outputIsRunning && (stream->perfCounterTicksPerBuffer.QuadPart != 0) )
+    {
+        /* How much time has elapsed since last check. */
+        LARGE_INTEGER   currentTime;
+        LARGE_INTEGER   elapsedTime;
+        long            bytesPlayed;
+        long            bytesExpected;
+        long            buffersWrapped;
+
+        QueryPerformanceCounter( &currentTime );
+        elapsedTime.QuadPart = currentTime.QuadPart - stream->previousPlayTime.QuadPart;
+        stream->previousPlayTime = currentTime;
+
+        /* How many bytes does DirectSound say have been played. */
+        bytesPlayed = playCursor - stream->previousPlayCursor;
+        if( bytesPlayed < 0 ) bytesPlayed += stream->outputBufferSizeBytes; // unwrap
+        stream->previousPlayCursor = playCursor;
+
+        /* Calculate how many bytes we would have expected to been played by now. */
+        bytesExpected = (long) ((elapsedTime.QuadPart * stream->outputBufferSizeBytes) / stream->perfCounterTicksPerBuffer.QuadPart);
+        buffersWrapped = (bytesExpected - bytesPlayed) / stream->outputBufferSizeBytes;
+        if( buffersWrapped > 0 )
+        {
+            playCursor += (buffersWrapped * stream->outputBufferSizeBytes);
+            bytesPlayed += (buffersWrapped * stream->outputBufferSizeBytes);
+        }
+    }
+    numBytesEmpty = playCursor - stream->outputBufferWriteOffsetBytes;
+    if( numBytesEmpty < 0 ) numBytesEmpty += stream->outputBufferSizeBytes; // unwrap offset
+
+    /* Have we underflowed? */
+    if( numBytesEmpty > (stream->outputBufferSizeBytes - playWriteGap) )
+    {
+        if( stream->outputIsRunning )
+        {
+            stream->outputUnderflowCount += 1;
+        }
+
+        /*
+            From MSDN:
+                The write cursor indicates the position at which it is safe
+            to write new data to the buffer. The write cursor always leads the
+            play cursor, typically by about 15 milliseconds' worth of audio
+            data.
+                It is always safe to change data that is behind the position
+            indicated by the lpdwCurrentPlayCursor parameter.
+        */
+
+        stream->outputBufferWriteOffsetBytes = writeCursor;
+        numBytesEmpty = stream->outputBufferSizeBytes - playWriteGap;
+    }
+    *bytesEmpty = numBytesEmpty;
+    return hr;
+}
+
+/***********************************************************************************/
+static int TimeSlice( PaWinDsStream *stream )
+{
+    long              numFrames = 0;
+    long              bytesEmpty = 0;
+    long              bytesFilled = 0;
+    long              bytesToXfer = 0;
+    long              framesToXfer = 0; /* the number of frames we'll process this tick */
+    long              numInFramesReady = 0;
+    long              numOutFramesReady = 0;
+    long              bytesProcessed;
+    HRESULT           hresult;
+    double            outputLatency = 0;
+    double            inputLatency = 0;
+    PaStreamCallbackTimeInfo timeInfo = {0,0,0};
+
+/* 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;
+
+    /* How much input data is available? */
+    if( stream->bufferProcessor.inputChannelCount > 0 )
+    {
+        HRESULT hr;
+        DWORD capturePos;
+        DWORD readPos;
+        long  filled = 0;
+        // Query to see how much data is in buffer.
+        // We don't need the capture position but sometimes DirectSound doesn't handle NULLS correctly
+        // so let's pass a pointer just to be safe.
+        hr = IDirectSoundCaptureBuffer_GetCurrentPosition( stream->pDirectSoundInputBuffer, &capturePos, &readPos );
+        if( hr == DS_OK )
+        {
+            filled = readPos - stream->readOffset;
+            if( filled < 0 ) filled += stream->inputBufferSizeBytes; // unwrap offset
+            bytesFilled = filled;
+
+            inputLatency = ((double)bytesFilled) * stream->secondsPerHostByte;
+        }
+            // FIXME: what happens if IDirectSoundCaptureBuffer_GetCurrentPosition fails?
+
+        framesToXfer = numInFramesReady = bytesFilled / stream->inputFrameSizeBytes;
+
+        /** @todo Check for overflow */
+    }
+
+    /* How much output room is available? */
+    if( stream->bufferProcessor.outputChannelCount > 0 )
+    {
+        UINT previousUnderflowCount = stream->outputUnderflowCount;
+        QueryOutputSpace( stream, &bytesEmpty );
+        framesToXfer = numOutFramesReady = bytesEmpty / stream->outputFrameSizeBytes;
+
+        /* Check for underflow */
+        /* FIXME QueryOutputSpace should not adjust underflow count as a side effect.
+            A query function should be a const operator on the stream and return a flag on underflow. */
+        if( stream->outputUnderflowCount != previousUnderflowCount )
+            stream->callbackFlags |= paOutputUnderflow;
+
+        /* We are about to compute audio into the first byte of empty space in the output buffer.
+           This audio will reach the DAC after all of the current (non-empty) audio
+           in the buffer has played. Therefore the output time is the current time
+           plus the time it takes to play the non-empty bytes in the buffer,
+           computed here:
+        */
+        outputLatency = ((double)(stream->outputBufferSizeBytes - bytesEmpty)) * stream->secondsPerHostByte;
+    }
+
+    /* if it's a full duplex stream, set framesToXfer to the minimum of input and output frames ready */
+    if( stream->bufferProcessor.inputChannelCount > 0 && stream->bufferProcessor.outputChannelCount > 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();
+
+        PaUtil_BeginBufferProcessing( &stream->bufferProcessor, &timeInfo, stream->callbackFlags );
+        stream->callbackFlags = 0;
+
+    /* Input */
+        if( stream->bufferProcessor.inputChannelCount > 0 )
+        {
+            timeInfo.inputBufferAdcTime = timeInfo.currentTime - inputLatency;
+
+            bytesToXfer = framesToXfer * stream->inputFrameSizeBytes;
+            hresult = IDirectSoundCaptureBuffer_Lock ( stream->pDirectSoundInputBuffer,
+                stream->readOffset, bytesToXfer,
+                (void **) &lpInBuf1, &dwInSize1,
+                (void **) &lpInBuf2, &dwInSize2, 0);
+            if (hresult != DS_OK)
+            {
+                ERR_RPT(("DirectSound IDirectSoundCaptureBuffer_Lock failed, hresult = 0x%x\n",hresult));
+                /* PA_DS_SET_LAST_DIRECTSOUND_ERROR( hresult ); */
+                PaUtil_ResetBufferProcessor( &stream->bufferProcessor ); /* flush the buffer processor */
+                stream->callbackResult = paComplete;
+                goto error2;
+            }
+
+            numFrames = dwInSize1 / stream->inputFrameSizeBytes;
+            PaUtil_SetInputFrameCount( &stream->bufferProcessor, numFrames );
+            PaUtil_SetInterleavedInputChannels( &stream->bufferProcessor, 0, lpInBuf1, 0 );
+        /* Is input split into two regions. */
+            if( dwInSize2 > 0 )
+            {
+                numFrames = dwInSize2 / stream->inputFrameSizeBytes;
+                PaUtil_Set2ndInputFrameCount( &stream->bufferProcessor, numFrames );
+                PaUtil_Set2ndInterleavedInputChannels( &stream->bufferProcessor, 0, lpInBuf2, 0 );
+            }
+        }
+
+    /* Output */
+        if( stream->bufferProcessor.outputChannelCount > 0 )
+        {
+            /*
+            We don't currently add outputLatency here because it appears to produce worse
+            results than not adding it. Need to do more testing to verify this.
+            */
+            /* timeInfo.outputBufferDacTime = timeInfo.currentTime + outputLatency; */
+            timeInfo.outputBufferDacTime = timeInfo.currentTime;
+
+            bytesToXfer = framesToXfer * stream->outputFrameSizeBytes;
+            hresult = IDirectSoundBuffer_Lock ( stream->pDirectSoundOutputBuffer,
+                stream->outputBufferWriteOffsetBytes, bytesToXfer,
+                (void **) &lpOutBuf1, &dwOutSize1,
+                (void **) &lpOutBuf2, &dwOutSize2, 0);
+            if (hresult != DS_OK)
+            {
+                ERR_RPT(("DirectSound IDirectSoundBuffer_Lock failed, hresult = 0x%x\n",hresult));
+                /* PA_DS_SET_LAST_DIRECTSOUND_ERROR( hresult ); */
+                PaUtil_ResetBufferProcessor( &stream->bufferProcessor ); /* flush the buffer processor */
+                stream->callbackResult = paComplete;
+                goto error1;
+            }
+
+            numFrames = dwOutSize1 / stream->outputFrameSizeBytes;
+            PaUtil_SetOutputFrameCount( &stream->bufferProcessor, numFrames );
+            PaUtil_SetInterleavedOutputChannels( &stream->bufferProcessor, 0, lpOutBuf1, 0 );
+
+        /* Is output split into two regions. */
+            if( dwOutSize2 > 0 )
+            {
+                numFrames = dwOutSize2 / stream->outputFrameSizeBytes;
+                PaUtil_Set2ndOutputFrameCount( &stream->bufferProcessor, numFrames );
+                PaUtil_Set2ndInterleavedOutputChannels( &stream->bufferProcessor, 0, lpOutBuf2, 0 );
+            }
+        }
+
+        numFrames = PaUtil_EndBufferProcessing( &stream->bufferProcessor, &stream->callbackResult );
+        stream->framesWritten += numFrames;
+
+        if( stream->bufferProcessor.outputChannelCount > 0 )
+        {
+        /* FIXME: an underflow could happen here */
+
+        /* Update our buffer offset and unlock sound buffer */
+            bytesProcessed = numFrames * stream->outputFrameSizeBytes;
+            stream->outputBufferWriteOffsetBytes = (stream->outputBufferWriteOffsetBytes + bytesProcessed) % stream->outputBufferSizeBytes;
+            IDirectSoundBuffer_Unlock( stream->pDirectSoundOutputBuffer, lpOutBuf1, dwOutSize1, lpOutBuf2, dwOutSize2);
+        }
+
+error1:
+        if( stream->bufferProcessor.inputChannelCount > 0 )
+        {
+        /* FIXME: an overflow could happen here */
+
+        /* Update our buffer offset and unlock sound buffer */
+            bytesProcessed = numFrames * stream->inputFrameSizeBytes;
+            stream->readOffset = (stream->readOffset + bytesProcessed) % stream->inputBufferSizeBytes;
+            IDirectSoundCaptureBuffer_Unlock( stream->pDirectSoundInputBuffer, lpInBuf1, dwInSize1, lpInBuf2, dwInSize2);
+        }
+error2:
+
+        PaUtil_EndCpuLoadMeasurement( &stream->cpuLoadMeasurer, numFrames );
+    }
+
+    if( stream->callbackResult == paComplete && !PaUtil_IsBufferProcessorOutputEmpty( &stream->bufferProcessor ) )
+    {
+        /* don't return completed until the buffer processor has been drained */
+        return paContinue;
+    }
+    else
+    {
+        return stream->callbackResult;
+    }
+}
+/*******************************************************************/
+
+static HRESULT ZeroAvailableOutputSpace( PaWinDsStream *stream )
+{
+    HRESULT hr;
+    LPBYTE lpbuf1 = NULL;
+    LPBYTE lpbuf2 = NULL;
+    DWORD dwsize1 = 0;
+    DWORD dwsize2 = 0;
+    long  bytesEmpty;
+    hr = QueryOutputSpace( stream, &bytesEmpty );
+    if (hr != DS_OK) return hr;
+    if( bytesEmpty == 0 ) return DS_OK;
+    // Lock free space in the DS
+    hr = IDirectSoundBuffer_Lock( stream->pDirectSoundOutputBuffer, stream->outputBufferWriteOffsetBytes,
+                                    bytesEmpty, (void **) &lpbuf1, &dwsize1,
+                                    (void **) &lpbuf2, &dwsize2, 0);
+    if (hr == DS_OK)
+    {
+        // Copy the buffer into the DS
+        ZeroMemory(lpbuf1, dwsize1);
+        if(lpbuf2 != NULL)
+        {
+            ZeroMemory(lpbuf2, dwsize2);
+        }
+        // Update our buffer offset and unlock sound buffer
+        stream->outputBufferWriteOffsetBytes = (stream->outputBufferWriteOffsetBytes + dwsize1 + dwsize2) % stream->outputBufferSizeBytes;
+        IDirectSoundBuffer_Unlock( stream->pDirectSoundOutputBuffer, lpbuf1, dwsize1, lpbuf2, dwsize2);
+
+        stream->finalZeroBytesWritten += dwsize1 + dwsize2;
+    }
+    return hr;
+}
+
+
+static void CALLBACK TimerCallback(UINT uID, UINT uMsg, DWORD_PTR dwUser, DWORD dw1, DWORD dw2)
+{
+    PaWinDsStream *stream;
+    int isFinished = 0;
+
+    /* 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 )
+        {
+            isFinished = 1;
+        }
+        else if( stream->stopProcessing )
+        {
+            if( stream->bufferProcessor.outputChannelCount > 0 )
+            {
+                ZeroAvailableOutputSpace( stream );
+                if( stream->finalZeroBytesWritten >= stream->outputBufferSizeBytes )
+                {
+                    /* once we've flushed the whole output buffer with zeros we know all data has been played */
+                    isFinished = 1;
+                }
+            }
+            else
+            {
+                isFinished = 1;
+            }
+        }
+        else
+        {
+            int callbackResult = TimeSlice( stream );
+            if( callbackResult != paContinue )
+            {
+                /* FIXME implement handling of paComplete and paAbort if possible
+                   At the moment this should behave as if paComplete was called and
+                   flush the buffer.
+                */
+
+                stream->stopProcessing = 1;
+            }
+        }
+
+        if( isFinished )
+        {
+            if( stream->streamRepresentation.streamFinishedCallback != 0 )
+                stream->streamRepresentation.streamFinishedCallback( stream->streamRepresentation.userData );
+
+            stream->isActive = 0; /* don't set this until the stream really is inactive */
+            SetEvent( stream->processingCompleted );
+        }
+    }
+}
+
+#ifndef PA_WIN_DS_USE_WMME_TIMER
+
+#ifdef PA_WIN_DS_USE_WAITABLE_TIMER_OBJECT
+
+static void CALLBACK WaitableTimerAPCProc(
+   LPVOID lpArg,               // Data value
+   DWORD dwTimerLowValue,      // Timer low value
+   DWORD dwTimerHighValue )    // Timer high value
+
+{
+    (void)dwTimerLowValue;
+    (void)dwTimerHighValue;
+
+    TimerCallback( 0, 0, (DWORD_PTR)lpArg, 0, 0 );
+}
+
+#endif /* PA_WIN_DS_USE_WAITABLE_TIMER_OBJECT */
+
+
+PA_THREAD_FUNC ProcessingThreadProc( void *pArg )
+{
+    PaWinDsStream *stream = (PaWinDsStream *)pArg;
+    LARGE_INTEGER dueTime;
+    int timerPeriodMs;
+
+    timerPeriodMs = (int)(stream->pollingPeriodSeconds * MSECS_PER_SECOND);
+    if( timerPeriodMs < 1 )
+        timerPeriodMs = 1;
+
+#ifdef PA_WIN_DS_USE_WAITABLE_TIMER_OBJECT
+    assert( stream->waitableTimer != NULL );
+
+    /* invoke first timeout immediately */
+    dueTime.LowPart = timerPeriodMs * 1000 * 10;
+    dueTime.HighPart = 0;
+
+    /* tick using waitable timer */
+    if( SetWaitableTimer( stream->waitableTimer, &dueTime, timerPeriodMs, WaitableTimerAPCProc, pArg, FALSE ) != 0 )
+    {
+        DWORD wfsoResult = 0;
+        do
+        {
+            /* wait for processingCompleted to be signaled or our timer APC to be called */
+            wfsoResult = WaitForSingleObjectEx( stream->processingCompleted, timerPeriodMs * 10, /* alertable = */ TRUE );
+
+        }while( wfsoResult == WAIT_TIMEOUT || wfsoResult == WAIT_IO_COMPLETION );
+    }
+
+    CancelWaitableTimer( stream->waitableTimer );
+
+#else
+
+    /* tick using WaitForSingleObject timeout */
+    while ( WaitForSingleObject( stream->processingCompleted, timerPeriodMs ) == WAIT_TIMEOUT )
+    {
+        TimerCallback( 0, 0, (DWORD_PTR)pArg, 0, 0 );
+    }
+#endif /* PA_WIN_DS_USE_WAITABLE_TIMER_OBJECT */
+
+    SetEvent( stream->processingThreadCompleted );
+
+    return 0;
+}
+
+#endif /* !PA_WIN_DS_USE_WMME_TIMER */
+
+/***********************************************************************************
+    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;
+
+    CloseHandle( stream->processingCompleted );
+
+#ifdef PA_WIN_DS_USE_WAITABLE_TIMER_OBJECT
+    if( stream->waitableTimer != NULL )
+        CloseHandle( stream->waitableTimer );
+#endif
+
+#ifndef PA_WIN_DS_USE_WMME_TIMER
+    CloseHandle( stream->processingThreadCompleted );
+#endif
+
+    // Cleanup the sound buffers
+    if( stream->pDirectSoundOutputBuffer )
+    {
+        IDirectSoundBuffer_Stop( stream->pDirectSoundOutputBuffer );
+        IDirectSoundBuffer_Release( stream->pDirectSoundOutputBuffer );
+        stream->pDirectSoundOutputBuffer = NULL;
+    }
+
+    if( stream->pDirectSoundPrimaryBuffer )
+    {
+        IDirectSoundBuffer_Release( stream->pDirectSoundPrimaryBuffer );
+        stream->pDirectSoundPrimaryBuffer = NULL;
+    }
+
+    if( stream->pDirectSoundInputBuffer )
+    {
+        IDirectSoundCaptureBuffer_Stop( stream->pDirectSoundInputBuffer );
+        IDirectSoundCaptureBuffer_Release( stream->pDirectSoundInputBuffer );
+        stream->pDirectSoundInputBuffer = NULL;
+    }
+
+    if( stream->pDirectSoundCapture )
+    {
+        IDirectSoundCapture_Release( stream->pDirectSoundCapture );
+        stream->pDirectSoundCapture = NULL;
+    }
+
+    if( stream->pDirectSound )
+    {
+        IDirectSound_Release( stream->pDirectSound );
+        stream->pDirectSound = NULL;
+    }
+
+#ifdef PAWIN_USE_DIRECTSOUNDFULLDUPLEXCREATE
+    if( stream->pDirectSoundFullDuplex8 )
+    {
+        IDirectSoundFullDuplex_Release( stream->pDirectSoundFullDuplex8 );
+        stream->pDirectSoundFullDuplex8 = NULL;
+    }
+#endif
+
+    PaUtil_TerminateBufferProcessor( &stream->bufferProcessor );
+    PaUtil_TerminateStreamRepresentation( &stream->streamRepresentation );
+    PaUtil_FreeMemory( stream );
+
+    return result;
+}
+
+/***********************************************************************************/
+static HRESULT ClearOutputBuffer( PaWinDsStream *stream )
+{
+    PaError          result = paNoError;
+    unsigned char*   pDSBuffData;
+    DWORD            dwDataLen;
+    HRESULT          hr;
+
+    hr = IDirectSoundBuffer_SetCurrentPosition( stream->pDirectSoundOutputBuffer, 0 );
+    DBUG(("PaHost_ClearOutputBuffer: IDirectSoundBuffer_SetCurrentPosition returned = 0x%X.\n", hr));
+    if( hr != DS_OK )
+        return hr;
+
+    // Lock the DS buffer
+    if ((hr = IDirectSoundBuffer_Lock( stream->pDirectSoundOutputBuffer, 0, stream->outputBufferSizeBytes, (LPVOID*)&pDSBuffData,
+                                           &dwDataLen, NULL, 0, 0)) != DS_OK )
+        return hr;
+
+    // Zero the DS buffer
+    ZeroMemory(pDSBuffData, dwDataLen);
+    // Unlock the DS buffer
+    if ((hr = IDirectSoundBuffer_Unlock( stream->pDirectSoundOutputBuffer, pDSBuffData, dwDataLen, NULL, 0)) != DS_OK)
+        return hr;
+
+    // Let DSound set the starting write position because if we set it to zero, it looks like the
+    // buffer is full to begin with. This causes a long pause before sound starts when using large buffers.
+    if ((hr = IDirectSoundBuffer_GetCurrentPosition( stream->pDirectSoundOutputBuffer,
+            &stream->previousPlayCursor, &stream->outputBufferWriteOffsetBytes )) != DS_OK)
+        return hr;
+
+    /* printf("DSW_InitOutputBuffer: playCursor = %d, writeCursor = %d\n", playCursor, dsw->dsw_WriteOffset ); */
+
+    return DS_OK;
+}
+
+static PaError StartStream( PaStream *s )
+{
+    PaError          result = paNoError;
+    PaWinDsStream   *stream = (PaWinDsStream*)s;
+    HRESULT          hr;
+
+    stream->callbackResult = paContinue;
+    PaUtil_ResetBufferProcessor( &stream->bufferProcessor );
+
+    ResetEvent( stream->processingCompleted );
+
+#ifndef PA_WIN_DS_USE_WMME_TIMER
+    ResetEvent( stream->processingThreadCompleted );
+#endif
+
+    if( stream->bufferProcessor.inputChannelCount > 0 )
+    {
+        // Start the buffer capture
+        if( stream->pDirectSoundInputBuffer != NULL ) // FIXME: not sure this check is necessary
+        {
+            hr = IDirectSoundCaptureBuffer_Start( stream->pDirectSoundInputBuffer, DSCBSTART_LOOPING );
+        }
+
+        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;
+
+    if( stream->bufferProcessor.outputChannelCount > 0 )
+    {
+        QueryPerformanceCounter( &stream->previousPlayTime );
+        stream->finalZeroBytesWritten = 0;
+
+        hr = ClearOutputBuffer( stream );
+        if( hr != DS_OK )
+        {
+            result = paUnanticipatedHostError;
+            PA_DS_SET_LAST_DIRECTSOUND_ERROR( hr );
+            goto error;
+        }
+
+        if( stream->streamRepresentation.streamCallback && (stream->streamFlags & paPrimeOutputBuffersUsingStreamCallback) )
+        {
+            stream->callbackFlags = paPrimingOutput;
+
+            TimeSlice( stream );
+            /* we ignore the return value from TimeSlice here and start the stream as usual.
+                The first timer callback will detect if the callback has completed. */
+
+            stream->callbackFlags = 0;
+        }
+
+        // Start the buffer playback in a loop.
+        if( stream->pDirectSoundOutputBuffer != NULL ) // FIXME: not sure this needs to be checked here
+        {
+            hr = IDirectSoundBuffer_Play( stream->pDirectSoundOutputBuffer, 0, 0, DSBPLAY_LOOPING );
+            DBUG(("PaHost_StartOutput: IDirectSoundBuffer_Play returned = 0x%X.\n", hr));
+            if( hr != DS_OK )
+            {
+                result = paUnanticipatedHostError;
+                PA_DS_SET_LAST_DIRECTSOUND_ERROR( hr );
+                goto error;
+            }
+            stream->outputIsRunning = TRUE;
+        }
+    }
+
+    if( stream->streamRepresentation.streamCallback )
+    {
+        TIMECAPS timecaps;
+        int timerPeriodMs = (int)(stream->pollingPeriodSeconds * MSECS_PER_SECOND);
+        if( timerPeriodMs < 1 )
+            timerPeriodMs = 1;
+
+        /* set windows scheduler granularity only as fine as needed, no finer */
+        /* Although this is not fully documented by MS, it appears that
+           timeBeginPeriod() affects the scheduling granulatity of all timers
+           including Waitable Timer Objects. So we always call timeBeginPeriod, whether
+           we're using an MM timer callback via timeSetEvent or not.
+        */
+        assert( stream->systemTimerResolutionPeriodMs == 0 );
+        if( timeGetDevCaps( &timecaps, sizeof(TIMECAPS) ) == MMSYSERR_NOERROR && timecaps.wPeriodMin > 0 )
+        {
+            /* aim for resolution 4 times higher than polling rate */
+            stream->systemTimerResolutionPeriodMs = (UINT)((stream->pollingPeriodSeconds * MSECS_PER_SECOND) * .25);
+            if( stream->systemTimerResolutionPeriodMs < timecaps.wPeriodMin )
+                stream->systemTimerResolutionPeriodMs = timecaps.wPeriodMin;
+            if( stream->systemTimerResolutionPeriodMs > timecaps.wPeriodMax )
+                stream->systemTimerResolutionPeriodMs = timecaps.wPeriodMax;
+
+            if( timeBeginPeriod( stream->systemTimerResolutionPeriodMs ) != MMSYSERR_NOERROR )
+                stream->systemTimerResolutionPeriodMs = 0; /* timeBeginPeriod failed, so we don't need to call timeEndPeriod() later */
+        }
+
+
+#ifdef PA_WIN_DS_USE_WMME_TIMER
+        /* Create timer that will wake us up so we can fill the DSound buffer. */
+        /* We have deprecated timeSetEvent because all MM timer callbacks
+           are serialised onto a single thread. Which creates problems with multiple
+           PA streams, or when also using timers for other time critical tasks
+        */
+        stream->timerID = timeSetEvent( timerPeriodMs, stream->systemTimerResolutionPeriodMs, (LPTIMECALLBACK) TimerCallback,
+                                             (DWORD_PTR) stream, TIME_PERIODIC | TIME_KILL_SYNCHRONOUS );
+
+        if( stream->timerID == 0 )
+        {
+            stream->isActive = 0;
+            result = paUnanticipatedHostError;
+            PA_DS_SET_LAST_DIRECTSOUND_ERROR( GetLastError() );
+            goto error;
+        }
+#else
+        /* Create processing thread which calls TimerCallback */
+
+        stream->processingThread = CREATE_THREAD( 0, 0, ProcessingThreadProc, stream, 0, &stream->processingThreadId );
+        if( !stream->processingThread )
+        {
+            result = paUnanticipatedHostError;
+            PA_DS_SET_LAST_DIRECTSOUND_ERROR( GetLastError() );
+            goto error;
+        }
+
+        if( !SetThreadPriority( stream->processingThread, THREAD_PRIORITY_TIME_CRITICAL ) )
+        {
+            result = paUnanticipatedHostError;
+            PA_DS_SET_LAST_DIRECTSOUND_ERROR( GetLastError() );
+            goto error;
+        }
+#endif
+    }
+
+    stream->isActive = 1;
+    stream->isStarted = 1;
+
+    assert( result == paNoError );
+    return result;
+
+error:
+
+    if( stream->pDirectSoundOutputBuffer != NULL && stream->outputIsRunning )
+        IDirectSoundBuffer_Stop( stream->pDirectSoundOutputBuffer );
+    stream->outputIsRunning = FALSE;
+
+#ifndef PA_WIN_DS_USE_WMME_TIMER
+    if( stream->processingThread )
+    {
+#ifdef CLOSE_THREAD_HANDLE
+        CLOSE_THREAD_HANDLE( stream->processingThread ); /* Delete thread. */
+#endif
+        stream->processingThread = NULL;
+    }
+#endif
+
+    return result;
+}
+
+
+/***********************************************************************************/
+static PaError StopStream( PaStream *s )
+{
+    PaError result = paNoError;
+    PaWinDsStream *stream = (PaWinDsStream*)s;
+    HRESULT          hr;
+    int timeoutMsec;
+
+    if( stream->streamRepresentation.streamCallback )
+    {
+        stream->stopProcessing = 1;
+
+        /* Set timeout at 4 times maximum time we might wait. */
+        timeoutMsec = (int) (4 * MSECS_PER_SECOND * (stream->hostBufferSizeFrames / stream->streamRepresentation.streamInfo.sampleRate));
+
+        WaitForSingleObject( stream->processingCompleted, timeoutMsec );
+    }
+
+#ifdef PA_WIN_DS_USE_WMME_TIMER
+    if( stream->timerID != 0 )
+    {
+        timeKillEvent(stream->timerID);  /* Stop callback timer. */
+        stream->timerID = 0;
+    }
+#else
+    if( stream->processingThread )
+    {
+        if( WaitForSingleObject( stream->processingThreadCompleted, 30*100 ) == WAIT_TIMEOUT )
+            return paUnanticipatedHostError;
+
+#ifdef CLOSE_THREAD_HANDLE
+        CloseHandle( stream->processingThread ); /* Delete thread. */
+        stream->processingThread = NULL;
+#endif
+
+    }
+#endif
+
+    if( stream->systemTimerResolutionPeriodMs > 0 ){
+        timeEndPeriod( stream->systemTimerResolutionPeriodMs );
+        stream->systemTimerResolutionPeriodMs = 0;
+    }
+
+    if( stream->bufferProcessor.outputChannelCount > 0 )
+    {
+        // Stop the buffer playback
+        if( stream->pDirectSoundOutputBuffer != NULL )
+        {
+            stream->outputIsRunning = FALSE;
+            // FIXME: what happens if IDirectSoundBuffer_Stop returns an error?
+            hr = IDirectSoundBuffer_Stop( stream->pDirectSoundOutputBuffer );
+
+            if( stream->pDirectSoundPrimaryBuffer )
+                IDirectSoundBuffer_Stop( stream->pDirectSoundPrimaryBuffer ); /* FIXME we never started the primary buffer so I'm not sure we need to stop it */
+        }
+    }
+
+    if( stream->bufferProcessor.inputChannelCount > 0 )
+    {
+        // Stop the buffer capture
+        if( stream->pDirectSoundInputBuffer != NULL )
+        {
+            // FIXME: what happens if IDirectSoundCaptureBuffer_Stop returns an error?
+            hr = IDirectSoundCaptureBuffer_Stop( stream->pDirectSoundInputBuffer );
+        }
+    }
+
+    stream->isStarted = 0;
+
+    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;
+
+    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;
+}
diff --git a/portaudio/src/hostapi/dsound/pa_win_ds_dynlink.c b/portaudio/src/hostapi/dsound/pa_win_ds_dynlink.c
new file mode 100644
index 0000000..e54df99
--- /dev/null
+++ b/portaudio/src/hostapi/dsound/pa_win_ds_dynlink.c
@@ -0,0 +1,224 @@
+/*
+ * Interface for dynamically loading directsound and providing a dummy
+ * implementation if it isn't present.
+ *
+ * Author: Ross Bencina (some portions Phil Burk & Robert Marsanyi)
+ *
+ * For PortAudio Portable Real-Time Audio Library
+ * For more information see: http://www.portaudio.com
+ * Copyright (c) 1999-2006 Phil Burk, Robert Marsanyi and Ross Bencina
+ *
+ * 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.
+ *
+ * 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.
+ */
+
+/*
+ * The text above constitutes the entire PortAudio license; however,
+ * the PortAudio community also makes the following non-binding requests:
+ *
+ * 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. It is also
+ * requested that these non-binding requests be included along with the
+ * license above.
+ */
+
+/**
+ @file
+ @ingroup hostapi_src
+*/
+
+#include "pa_win_ds_dynlink.h"
+#include "pa_debugprint.h"
+
+PaWinDsDSoundEntryPoints paWinDsDSoundEntryPoints = { 0, 0, 0, 0, 0, 0, 0 };
+
+
+static HRESULT WINAPI DummyDllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID *ppv)
+{
+    (void)rclsid; /* unused parameter */
+    (void)riid; /* unused parameter */
+    (void)ppv; /* unused parameter */
+    return CLASS_E_CLASSNOTAVAILABLE;
+}
+
+static HRESULT WINAPI DummyDirectSoundCreate(LPGUID lpcGuidDevice, LPDIRECTSOUND *ppDS, LPUNKNOWN pUnkOuter)
+{
+    (void)lpcGuidDevice; /* unused parameter */
+    (void)ppDS; /* unused parameter */
+    (void)pUnkOuter; /* unused parameter */
+    return E_NOTIMPL;
+}
+
+static HRESULT WINAPI DummyDirectSoundEnumerateW(LPDSENUMCALLBACKW lpDSEnumCallback, LPVOID lpContext)
+{
+    (void)lpDSEnumCallback; /* unused parameter */
+    (void)lpContext; /* unused parameter */
+    return E_NOTIMPL;
+}
+
+static HRESULT WINAPI DummyDirectSoundEnumerateA(LPDSENUMCALLBACKA lpDSEnumCallback, LPVOID lpContext)
+{
+    (void)lpDSEnumCallback; /* unused parameter */
+    (void)lpContext; /* unused parameter */
+    return E_NOTIMPL;
+}
+
+static HRESULT WINAPI DummyDirectSoundCaptureCreate(LPGUID lpcGUID, LPDIRECTSOUNDCAPTURE *lplpDSC, LPUNKNOWN pUnkOuter)
+{
+    (void)lpcGUID; /* unused parameter */
+    (void)lplpDSC; /* unused parameter */
+    (void)pUnkOuter; /* unused parameter */
+    return E_NOTIMPL;
+}
+
+static HRESULT WINAPI DummyDirectSoundCaptureEnumerateW(LPDSENUMCALLBACKW lpDSCEnumCallback, LPVOID lpContext)
+{
+    (void)lpDSCEnumCallback; /* unused parameter */
+    (void)lpContext; /* unused parameter */
+    return E_NOTIMPL;
+}
+
+static HRESULT WINAPI DummyDirectSoundCaptureEnumerateA(LPDSENUMCALLBACKA lpDSCEnumCallback, LPVOID lpContext)
+{
+    (void)lpDSCEnumCallback; /* unused parameter */
+    (void)lpContext; /* unused parameter */
+    return E_NOTIMPL;
+}
+
+#ifdef PAWIN_USE_DIRECTSOUNDFULLDUPLEXCREATE
+static HRESULT WINAPI DummyDirectSoundFullDuplexCreate8(
+         LPCGUID pcGuidCaptureDevice,
+         LPCGUID pcGuidRenderDevice,
+         LPCDSCBUFFERDESC pcDSCBufferDesc,
+         LPCDSBUFFERDESC pcDSBufferDesc,
+         HWND hWnd,
+         DWORD dwLevel,
+         LPDIRECTSOUNDFULLDUPLEX * ppDSFD,
+         LPDIRECTSOUNDCAPTUREBUFFER8 * ppDSCBuffer8,
+         LPDIRECTSOUNDBUFFER8 * ppDSBuffer8,
+         LPUNKNOWN pUnkOuter)
+{
+    (void)pcGuidCaptureDevice; /* unused parameter */
+    (void)pcGuidRenderDevice; /* unused parameter */
+    (void)pcDSCBufferDesc; /* unused parameter */
+    (void)pcDSBufferDesc; /* unused parameter */
+    (void)hWnd; /* unused parameter */
+    (void)dwLevel; /* unused parameter */
+    (void)ppDSFD; /* unused parameter */
+    (void)ppDSCBuffer8; /* unused parameter */
+    (void)ppDSBuffer8; /* unused parameter */
+    (void)pUnkOuter; /* unused parameter */
+
+    return E_NOTIMPL;
+}
+#endif /* PAWIN_USE_DIRECTSOUNDFULLDUPLEXCREATE */
+
+void PaWinDs_InitializeDSoundEntryPoints(void)
+{
+    paWinDsDSoundEntryPoints.hInstance_ = LoadLibraryA("dsound.dll");
+    if( paWinDsDSoundEntryPoints.hInstance_ != NULL )
+    {
+        paWinDsDSoundEntryPoints.DllGetClassObject =
+                (HRESULT (WINAPI *)(REFCLSID, REFIID , LPVOID *))
+                GetProcAddress( paWinDsDSoundEntryPoints.hInstance_, "DllGetClassObject" );
+        if( paWinDsDSoundEntryPoints.DllGetClassObject == NULL )
+            paWinDsDSoundEntryPoints.DllGetClassObject = DummyDllGetClassObject;
+
+        paWinDsDSoundEntryPoints.DirectSoundCreate =
+                (HRESULT (WINAPI *)(LPGUID, LPDIRECTSOUND *, LPUNKNOWN))
+                GetProcAddress( paWinDsDSoundEntryPoints.hInstance_, "DirectSoundCreate" );
+        if( paWinDsDSoundEntryPoints.DirectSoundCreate == NULL )
+            paWinDsDSoundEntryPoints.DirectSoundCreate = DummyDirectSoundCreate;
+
+        paWinDsDSoundEntryPoints.DirectSoundEnumerateW =
+                (HRESULT (WINAPI *)(LPDSENUMCALLBACKW, LPVOID))
+                GetProcAddress( paWinDsDSoundEntryPoints.hInstance_, "DirectSoundEnumerateW" );
+        if( paWinDsDSoundEntryPoints.DirectSoundEnumerateW == NULL )
+            paWinDsDSoundEntryPoints.DirectSoundEnumerateW = DummyDirectSoundEnumerateW;
+
+        paWinDsDSoundEntryPoints.DirectSoundEnumerateA =
+                (HRESULT (WINAPI *)(LPDSENUMCALLBACKA, LPVOID))
+                GetProcAddress( paWinDsDSoundEntryPoints.hInstance_, "DirectSoundEnumerateA" );
+        if( paWinDsDSoundEntryPoints.DirectSoundEnumerateA == NULL )
+            paWinDsDSoundEntryPoints.DirectSoundEnumerateA = DummyDirectSoundEnumerateA;
+
+        paWinDsDSoundEntryPoints.DirectSoundCaptureCreate =
+                (HRESULT (WINAPI *)(LPGUID, LPDIRECTSOUNDCAPTURE *, LPUNKNOWN))
+                GetProcAddress( paWinDsDSoundEntryPoints.hInstance_, "DirectSoundCaptureCreate" );
+        if( paWinDsDSoundEntryPoints.DirectSoundCaptureCreate == NULL )
+            paWinDsDSoundEntryPoints.DirectSoundCaptureCreate = DummyDirectSoundCaptureCreate;
+
+        paWinDsDSoundEntryPoints.DirectSoundCaptureEnumerateW =
+                (HRESULT (WINAPI *)(LPDSENUMCALLBACKW, LPVOID))
+                GetProcAddress( paWinDsDSoundEntryPoints.hInstance_, "DirectSoundCaptureEnumerateW" );
+        if( paWinDsDSoundEntryPoints.DirectSoundCaptureEnumerateW == NULL )
+            paWinDsDSoundEntryPoints.DirectSoundCaptureEnumerateW = DummyDirectSoundCaptureEnumerateW;
+
+        paWinDsDSoundEntryPoints.DirectSoundCaptureEnumerateA =
+                (HRESULT (WINAPI *)(LPDSENUMCALLBACKA, LPVOID))
+                GetProcAddress( paWinDsDSoundEntryPoints.hInstance_, "DirectSoundCaptureEnumerateA" );
+        if( paWinDsDSoundEntryPoints.DirectSoundCaptureEnumerateA == NULL )
+            paWinDsDSoundEntryPoints.DirectSoundCaptureEnumerateA = DummyDirectSoundCaptureEnumerateA;
+
+#ifdef PAWIN_USE_DIRECTSOUNDFULLDUPLEXCREATE
+        paWinDsDSoundEntryPoints.DirectSoundFullDuplexCreate8 =
+                (HRESULT (WINAPI *)(LPCGUID, LPCGUID, LPCDSCBUFFERDESC, LPCDSBUFFERDESC,
+                                    HWND, DWORD, LPDIRECTSOUNDFULLDUPLEX *, LPDIRECTSOUNDCAPTUREBUFFER8 *,
+                                    LPDIRECTSOUNDBUFFER8 *, LPUNKNOWN))
+                GetProcAddress( paWinDsDSoundEntryPoints.hInstance_, "DirectSoundFullDuplexCreate" );
+        if( paWinDsDSoundEntryPoints.DirectSoundFullDuplexCreate8 == NULL )
+            paWinDsDSoundEntryPoints.DirectSoundFullDuplexCreate8 = DummyDirectSoundFullDuplexCreate8;
+#endif
+    }
+    else
+    {
+        DWORD errorCode = GetLastError(); // 126 (0x7E) == ERROR_MOD_NOT_FOUND
+        PA_DEBUG(("Couldn't load dsound.dll error code: %d \n",errorCode));
+
+        /* initialize with dummy entry points to make live easy when ds isn't present */
+        paWinDsDSoundEntryPoints.DirectSoundCreate = DummyDirectSoundCreate;
+        paWinDsDSoundEntryPoints.DirectSoundEnumerateW = DummyDirectSoundEnumerateW;
+        paWinDsDSoundEntryPoints.DirectSoundEnumerateA = DummyDirectSoundEnumerateA;
+        paWinDsDSoundEntryPoints.DirectSoundCaptureCreate = DummyDirectSoundCaptureCreate;
+        paWinDsDSoundEntryPoints.DirectSoundCaptureEnumerateW = DummyDirectSoundCaptureEnumerateW;
+        paWinDsDSoundEntryPoints.DirectSoundCaptureEnumerateA = DummyDirectSoundCaptureEnumerateA;
+#ifdef PAWIN_USE_DIRECTSOUNDFULLDUPLEXCREATE
+        paWinDsDSoundEntryPoints.DirectSoundFullDuplexCreate8 = DummyDirectSoundFullDuplexCreate8;
+#endif
+    }
+}
+
+
+void PaWinDs_TerminateDSoundEntryPoints(void)
+{
+    if( paWinDsDSoundEntryPoints.hInstance_ != NULL )
+    {
+        /* ensure that we crash reliably if the entry points aren't initialised */
+        paWinDsDSoundEntryPoints.DirectSoundCreate = 0;
+        paWinDsDSoundEntryPoints.DirectSoundEnumerateW = 0;
+        paWinDsDSoundEntryPoints.DirectSoundEnumerateA = 0;
+        paWinDsDSoundEntryPoints.DirectSoundCaptureCreate = 0;
+        paWinDsDSoundEntryPoints.DirectSoundCaptureEnumerateW = 0;
+        paWinDsDSoundEntryPoints.DirectSoundCaptureEnumerateA = 0;
+
+        FreeLibrary( paWinDsDSoundEntryPoints.hInstance_ );
+        paWinDsDSoundEntryPoints.hInstance_ = NULL;
+    }
+}
diff --git a/portaudio/src/hostapi/dsound/pa_win_ds_dynlink.h b/portaudio/src/hostapi/dsound/pa_win_ds_dynlink.h
new file mode 100644
index 0000000..2cdf6f0
--- /dev/null
+++ b/portaudio/src/hostapi/dsound/pa_win_ds_dynlink.h
@@ -0,0 +1,106 @@
+/*
+ * Interface for dynamically loading directsound and providing a dummy
+ * implementation if it isn't present.
+ *
+ * Author: Ross Bencina (some portions Phil Burk & Robert Marsanyi)
+ *
+ * For PortAudio Portable Real-Time Audio Library
+ * For more information see: http://www.portaudio.com
+ * Copyright (c) 1999-2006 Phil Burk, Robert Marsanyi and Ross Bencina
+ *
+ * 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.
+ *
+ * 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.
+ */
+
+/*
+ * The text above constitutes the entire PortAudio license; however,
+ * the PortAudio community also makes the following non-binding requests:
+ *
+ * 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. It is also
+ * requested that these non-binding requests be included along with the
+ * license above.
+ */
+
+/**
+ @file
+ @ingroup hostapi_src
+*/
+
+#ifndef INCLUDED_PA_DSOUND_DYNLINK_H
+#define INCLUDED_PA_DSOUND_DYNLINK_H
+
+/* on Borland compilers, WIN32 doesn't seem to be defined by default, which
+    breaks dsound.h. Adding the define here fixes the problem. - rossb. */
+#ifdef __BORLANDC__
+#if !defined(WIN32)
+#define WIN32
+#endif
+#endif
+
+/*
+  Use the earliest version of DX required, no need to pollute the namespace
+*/
+#ifdef PAWIN_USE_DIRECTSOUNDFULLDUPLEXCREATE
+#define DIRECTSOUND_VERSION 0x0800
+#else
+#define DIRECTSOUND_VERSION 0x0300
+#endif
+#include <dsound.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+
+typedef struct
+{
+    HINSTANCE hInstance_;
+
+    HRESULT (WINAPI *DllGetClassObject)(REFCLSID , REFIID , LPVOID *);
+
+    HRESULT (WINAPI *DirectSoundCreate)(LPGUID, LPDIRECTSOUND *, LPUNKNOWN);
+    HRESULT (WINAPI *DirectSoundEnumerateW)(LPDSENUMCALLBACKW, LPVOID);
+    HRESULT (WINAPI *DirectSoundEnumerateA)(LPDSENUMCALLBACKA, LPVOID);
+
+    HRESULT (WINAPI *DirectSoundCaptureCreate)(LPGUID, LPDIRECTSOUNDCAPTURE *, LPUNKNOWN);
+    HRESULT (WINAPI *DirectSoundCaptureEnumerateW)(LPDSENUMCALLBACKW, LPVOID);
+    HRESULT (WINAPI *DirectSoundCaptureEnumerateA)(LPDSENUMCALLBACKA, LPVOID);
+
+#ifdef PAWIN_USE_DIRECTSOUNDFULLDUPLEXCREATE
+    HRESULT (WINAPI *DirectSoundFullDuplexCreate8)(
+                LPCGUID, LPCGUID, LPCDSCBUFFERDESC, LPCDSBUFFERDESC,
+                HWND, DWORD, LPDIRECTSOUNDFULLDUPLEX *, LPDIRECTSOUNDCAPTUREBUFFER8 *,
+                LPDIRECTSOUNDBUFFER8 *, LPUNKNOWN );
+#endif
+}PaWinDsDSoundEntryPoints;
+
+extern PaWinDsDSoundEntryPoints paWinDsDSoundEntryPoints;
+
+void PaWinDs_InitializeDSoundEntryPoints(void);
+void PaWinDs_TerminateDSoundEntryPoints(void);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* INCLUDED_PA_DSOUND_DYNLINK_H */
-- 
cgit v1.2.1