#ifdef _WIN32
/*
* $Id$
* PortAudio Windows WDM-KS interface
*
* Author: Andrew Baldwin, Robert Bielik (WaveRT)
* Based on the Open Source API proposed by Ross Bencina
* Copyright (c) 1999-2004 Andrew Baldwin, Ross Bencina, Phil Burk
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* 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
@brief Portaudio WDM-KS host API.

@note This is the implementation of the Portaudio host API using the
Windows WDM/Kernel Streaming API in order to enable very low latency
playback and recording on all modern Windows platforms (e.g. 2K, XP, Vista, Win7)
Note: This API accesses the device drivers below the usual KMIXER
component which is normally used to enable multi-client mixing and
format conversion. That means that it will lock out all other users
of a device for the duration of active stream using those devices
*/

#include <stdio.h>

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

/* Debugging/tracing support */

#define PA_LOGE_
#define PA_LOGL_

#ifdef __GNUC__
#include <initguid.h>
#define _WIN32_WINNT 0x0501
#define WINVER 0x0501
#endif

#include <string.h> /* strlen() */
#include <assert.h>
#include <wchar.h>  /* iswspace() */

#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 "portaudio.h"
#include "pa_debugprint.h"
#include "pa_memorybarrier.h"
#include "pa_ringbuffer.h"
#include "pa_trace.h"
#include "pa_win_waveformat.h"

#include "pa_win_wdmks.h"

#ifndef DRV_QUERYDEVICEINTERFACE
#define DRV_QUERYDEVICEINTERFACE     (DRV_RESERVED + 12)
#endif
#ifndef DRV_QUERYDEVICEINTERFACESIZE
#define DRV_QUERYDEVICEINTERFACESIZE (DRV_RESERVED + 13)
#endif

#include <windows.h>
#ifndef __GNUC__ /* Fix for ticket #257: MinGW-w64: Inclusion of <winioctl.h> triggers multiple redefinition errors. */
#include <winioctl.h>
#endif
#include <process.h>

#include <math.h>

#ifdef _MSC_VER
#define snprintf _snprintf
#define vsnprintf _vsnprintf
#endif

/* The PA_HP_TRACE macro is used in RT parts, so it can be switched off without affecting
the rest of the debug tracing */
#if 1
#define PA_HP_TRACE(x)  PaUtil_AddHighSpeedLogMessage x ;
#else
#define PA_HP_TRACE(x)
#endif

/* A define that selects whether the resulting pin names are chosen from pin category
instead of the available pin names, who sometimes can be quite cheesy, like "Volume control".
Default is to use the pin category.
*/
#ifndef PA_WDMKS_USE_CATEGORY_FOR_PIN_NAMES
#define PA_WDMKS_USE_CATEGORY_FOR_PIN_NAMES  1
#endif

#ifdef __GNUC__
#undef PA_LOGE_
#define PA_LOGE_ PA_DEBUG(("%s {\n",__FUNCTION__))
#undef PA_LOGL_
#define PA_LOGL_ PA_DEBUG(("} %s\n",__FUNCTION__))
/* These defines are set in order to allow the WIndows DirectX
* headers to compile with a GCC compiler such as MinGW
* NOTE: The headers may generate a few warning in GCC, but
* they should compile */
#define _INC_MMSYSTEM
#define _INC_MMREG
#define _NTRTL_ /* Turn off default definition of DEFINE_GUIDEX */
#define DEFINE_GUID_THUNK(name,guid) DEFINE_GUID(name,guid)
#define DEFINE_GUIDEX(n) DEFINE_GUID_THUNK( n, STATIC_##n )
#if !defined( DEFINE_WAVEFORMATEX_GUID )
#define DEFINE_WAVEFORMATEX_GUID(x) (USHORT)(x), 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71
#endif
#define  WAVE_FORMAT_ADPCM      0x0002
#define  WAVE_FORMAT_IEEE_FLOAT 0x0003
#define  WAVE_FORMAT_ALAW       0x0006
#define  WAVE_FORMAT_MULAW      0x0007
#define  WAVE_FORMAT_MPEG       0x0050
#define  WAVE_FORMAT_DRM        0x0009
#define DYNAMIC_GUID_THUNK(l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8) {l,w1,w2,{b1,b2,b3,b4,b5,b6,b7,b8}}
#define DYNAMIC_GUID(data) DYNAMIC_GUID_THUNK(data)
#endif

/* use CreateThread for CYGWIN/Windows Mobile, _beginthreadex for all others */
#if !defined(__CYGWIN__) && !defined(_WIN32_WCE)
#define CREATE_THREAD_FUNCTION (HANDLE)_beginthreadex
#define PA_THREAD_FUNC static unsigned WINAPI
#else
#define CREATE_THREAD_FUNCTION CreateThread
#define PA_THREAD_FUNC static DWORD WINAPI
#endif

#ifdef _MSC_VER
#define NOMMIDS
#define DYNAMIC_GUID(data) {data}
#define _NTRTL_ /* Turn off default definition of DEFINE_GUIDEX */
#undef DEFINE_GUID
#if defined(__clang__) || (defined(_MSVC_TRADITIONAL) && !_MSVC_TRADITIONAL) /* clang-cl and new msvc preprocessor: avoid too many arguments error */
    #define DEFINE_GUID(n, ...) EXTERN_C const GUID n = {__VA_ARGS__}
    #define DEFINE_GUID_THUNK(n, ...) DEFINE_GUID(n, __VA_ARGS__)
    #define DEFINE_GUIDEX(n) DEFINE_GUID_THUNK(n, STATIC_##n)
#else
    #define DEFINE_GUID(n, data) EXTERN_C const GUID n = {data}
    #define DEFINE_GUID_THUNK(n, data) DEFINE_GUID(n, data)
    #define DEFINE_GUIDEX(n) DEFINE_GUID_THUNK(n, STATIC_##n)
#endif /* __clang__, !_MSVC_TRADITIONAL */
#endif

#include <setupapi.h>

#ifndef EXTERN_C
#define EXTERN_C extern
#endif

#if defined(__GNUC__)

/* For MinGW we reference mingw-include files supplied with WASAPI */
#define WINBOOL BOOL

#include "../wasapi/mingw-include/ks.h"
#include "../wasapi/mingw-include/ksmedia.h"

#else

#include <mmreg.h>
#include <ks.h>

/* Note that Windows SDK V6.0A or later is needed for WaveRT specific structs to be present in
   ksmedia.h. Also make sure that the SDK include path is before other include paths (that may contain
   an "old" ksmedia.h), so the proper ksmedia.h is used */
#include <ksmedia.h>

#endif

#include <assert.h>
#include <stdio.h>

/* These next definitions allow the use of the KSUSER DLL */
typedef /*KSDDKAPI*/ DWORD WINAPI KSCREATEPIN(HANDLE, PKSPIN_CONNECT, ACCESS_MASK, PHANDLE);
extern HMODULE      DllKsUser;
extern KSCREATEPIN* FunctionKsCreatePin;

/* These definitions allows the use of AVRT.DLL on Vista and later OSs */
typedef enum _PA_AVRT_PRIORITY
{
    PA_AVRT_PRIORITY_LOW = -1,
    PA_AVRT_PRIORITY_NORMAL,
    PA_AVRT_PRIORITY_HIGH,
    PA_AVRT_PRIORITY_CRITICAL
} PA_AVRT_PRIORITY, *PPA_AVRT_PRIORITY;

typedef struct
{
    HINSTANCE hInstance;

    HANDLE  (WINAPI *AvSetMmThreadCharacteristics) (LPCSTR, LPDWORD);
    BOOL    (WINAPI *AvRevertMmThreadCharacteristics) (HANDLE);
    BOOL    (WINAPI *AvSetMmThreadPriority) (HANDLE, PA_AVRT_PRIORITY);
} PaWinWDMKSAvRtEntryPoints;

static PaWinWDMKSAvRtEntryPoints paWinWDMKSAvRtEntryPoints = {0};

/* An unspecified channel count (-1) is not treated correctly, so we replace it with
* an arbitrarily large number */
#define MAXIMUM_NUMBER_OF_CHANNELS 256

/* Forward definition to break circular type reference between pin and filter */
struct __PaWinWdmFilter;
typedef struct __PaWinWdmFilter PaWinWdmFilter;

struct __PaWinWdmPin;
typedef struct __PaWinWdmPin PaWinWdmPin;

struct __PaWinWdmStream;
typedef struct __PaWinWdmStream PaWinWdmStream;

/* Function prototype for getting audio position */
typedef PaError (*FunctionGetPinAudioPosition)(PaWinWdmPin*, unsigned long*);

/* Function prototype for memory barrier */
typedef void (*FunctionMemoryBarrier)(void);

struct __PaProcessThreadInfo;
typedef struct __PaProcessThreadInfo PaProcessThreadInfo;

typedef PaError (*FunctionPinHandler)(PaProcessThreadInfo* pInfo, unsigned eventIndex);

typedef enum __PaStreamStartEnum
{
    StreamStart_kOk,
    StreamStart_kFailed,
    StreamStart_kCnt
} PaStreamStartEnum;

/* Multiplexed input structure.
*  Very often several physical inputs are multiplexed through a MUX node (represented in the topology filter) */
typedef struct __PaWinWdmMuxedInput
{
    wchar_t                     friendlyName[MAX_PATH];
    ULONG                       muxPinId;
    ULONG                       muxNodeId;
    ULONG                       endpointPinId;
} PaWinWdmMuxedInput;

/* The Pin structure
* A pin is an input or output node, e.g. for audio flow */
struct __PaWinWdmPin
{
    HANDLE                      handle;
    PaWinWdmMuxedInput**        inputs;
    unsigned                    inputCount;
    wchar_t                     friendlyName[MAX_PATH];

    PaWinWdmFilter*             parentFilter;
    PaWDMKSSubType              pinKsSubType;
    unsigned long               pinId;
    unsigned long               endpointPinId;  /* For output pins */
    KSPIN_CONNECT*              pinConnect;
    unsigned long               pinConnectSize;
    KSDATAFORMAT_WAVEFORMATEX*  ksDataFormatWfx;
    KSPIN_COMMUNICATION         communication;
    KSDATARANGE*                dataRanges;
    KSMULTIPLE_ITEM*            dataRangesItem;
    KSPIN_DATAFLOW              dataFlow;
    KSPIN_CINSTANCES            instances;
    unsigned long               frameSize;
    int                         maxChannels;
    unsigned long               formats;
    int                         defaultSampleRate;
    ULONG                       *positionRegister;  /* WaveRT */
    ULONG                       hwLatency;          /* WaveRT */
    FunctionMemoryBarrier       fnMemBarrier;       /* WaveRT */
    FunctionGetPinAudioPosition fnAudioPosition;    /* WaveRT */
    FunctionPinHandler          fnEventHandler;
    FunctionPinHandler          fnSubmitHandler;
};

/* The Filter structure
* A filter has a number of pins and a "friendly name" */
struct __PaWinWdmFilter
{
    HANDLE         handle;
    PaWinWDMKSDeviceInfo    devInfo;  /* This will hold information that is exposed in PaDeviceInfo */

    DWORD            deviceNode;
    int            pinCount;
    PaWinWdmPin**  pins;
    PaWinWdmFilter*  topologyFilter;
    wchar_t          friendlyName[MAX_PATH];
    int              validPinCount;
    int            usageCount;
    KSMULTIPLE_ITEM* connections;
    KSMULTIPLE_ITEM* nodes;
    int              filterRefCount;
};


typedef struct __PaWinWdmDeviceInfo
{
    PaDeviceInfo    inheritedDeviceInfo;
    char            compositeName[MAX_PATH];   /* Composite name consists of pin name + device name in utf8 */
    PaWinWdmFilter* filter;
    unsigned long   pin;
    int             muxPosition;    /* Used only for input devices */
    int             endpointPinId;
}
PaWinWdmDeviceInfo;

/* PaWinWdmHostApiRepresentation - host api datastructure specific to this implementation */
typedef struct __PaWinWdmHostApiRepresentation
{
    PaUtilHostApiRepresentation  inheritedHostApiRep;
    PaUtilStreamInterface        callbackStreamInterface;
    PaUtilStreamInterface        blockingStreamInterface;

    PaUtilAllocationGroup*       allocations;
    int                          deviceCount;
}
PaWinWdmHostApiRepresentation;

typedef struct __DATAPACKET
{
    KSSTREAM_HEADER  Header;
    OVERLAPPED       Signal;
} DATAPACKET;

typedef struct __PaIOPacket
{
    DATAPACKET*     packet;
    unsigned        startByte;
    unsigned        lengthBytes;
} PaIOPacket;

typedef struct __PaWinWdmIOInfo
{
    PaWinWdmPin*        pPin;
    char*               hostBuffer;
    unsigned            hostBufferSize;
    unsigned            framesPerBuffer;
    unsigned            bytesPerFrame;
    unsigned            bytesPerSample;
    unsigned            noOfPackets;    /* Only used in WaveCyclic */
    HANDLE              *events;        /* noOfPackets handles (WaveCyclic) 1 (WaveRT) */
    DATAPACKET          *packets;       /* noOfPackets packets (WaveCyclic) 2 (WaveRT) */
    /* WaveRT polled mode */
    unsigned            lastPosition;
    unsigned            pollCntr;
} PaWinWdmIOInfo;

/* PaWinWdmStream - a stream data structure specifically for this implementation */
struct __PaWinWdmStream
{
    PaUtilStreamRepresentation  streamRepresentation;
    PaWDMKSSpecificStreamInfo   hostApiStreamInfo;    /* This holds info that is exposed through PaStreamInfo */
    PaUtilCpuLoadMeasurer       cpuLoadMeasurer;
    PaUtilBufferProcessor       bufferProcessor;

#if PA_TRACE_REALTIME_EVENTS
    LogHandle                   hLog;
#endif

    PaUtilAllocationGroup*      allocGroup;
    PaWinWdmIOInfo              capture;
    PaWinWdmIOInfo              render;
    int                         streamStarted;
    int                         streamActive;
    int                         streamStop;
    int                         streamAbort;
    int                         oldProcessPriority;
    HANDLE                      streamThread;
    HANDLE                      eventAbort;
    HANDLE                      eventStreamStart[StreamStart_kCnt];        /* 0 = OK, 1 = Failed */
    PaError                     threadResult;
    PaStreamFlags               streamFlags;

    /* Capture ring buffer */
    PaUtilRingBuffer            ringBuffer;
    char*                       ringBufferData;

    /* These values handle the case where the user wants to use fewer
    * channels than the device has */
    int                         userInputChannels;
    int                         deviceInputChannels;
    int                         userOutputChannels;
    int                         deviceOutputChannels;
};

/* Gather all processing variables in a struct */
struct __PaProcessThreadInfo
{
    PaWinWdmStream              *stream;
    PaStreamCallbackTimeInfo    ti;
    PaStreamCallbackFlags       underover;
    int                         cbResult;
    volatile int                pending;
    volatile int                priming;
    volatile int                pinsStarted;
    unsigned long               timeout;
    unsigned                    captureHead;
    unsigned                    captureTail;
    unsigned                    renderHead;
    unsigned                    renderTail;
    PaIOPacket                  capturePackets[4];
    PaIOPacket                  renderPackets[4];
};

/* Used for transferring device infos during scanning / rescanning */
typedef struct __PaWinWDMScanDeviceInfosResults
{
    PaDeviceInfo **deviceInfos;
    PaDeviceIndex defaultInputDevice;
    PaDeviceIndex defaultOutputDevice;
} PaWinWDMScanDeviceInfosResults;

static const unsigned cPacketsArrayMask = 3;

HMODULE      DllKsUser = NULL;
KSCREATEPIN* FunctionKsCreatePin = NULL;

/* prototypes for functions declared in this file */

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

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

#ifdef __cplusplus
}
#endif /* __cplusplus */

/* Low level I/O functions */
static PaError WdmSyncIoctl(HANDLE handle,
                            unsigned long ioctlNumber,
                            void* inBuffer,
                            unsigned long inBufferCount,
                            void* outBuffer,
                            unsigned long outBufferCount,
                            unsigned long* bytesReturned);

static PaError WdmGetPropertySimple(HANDLE handle,
                                    const GUID* const guidPropertySet,
                                    unsigned long property,
                                    void* value,
                                    unsigned long valueCount);

static PaError WdmSetPropertySimple(HANDLE handle,
                                    const GUID* const guidPropertySet,
                                    unsigned long property,
                                    void* value,
                                    unsigned long valueCount,
                                    void* instance,
                                    unsigned long instanceCount);

static PaError WdmGetPinPropertySimple(HANDLE  handle,
                                       unsigned long pinId,
                                       const GUID* const guidPropertySet,
                                       unsigned long property,
                                       void* value,
                                       unsigned long valueCount,
                                       unsigned long* byteCount);

static PaError WdmGetPinPropertyMulti(HANDLE  handle,
                                      unsigned long pinId,
                                      const GUID* const guidPropertySet,
                                      unsigned long property,
                                      KSMULTIPLE_ITEM** ksMultipleItem);

static PaError WdmGetPropertyMulti(HANDLE handle,
                                   const GUID* const guidPropertySet,
                                   unsigned long property,
                                   KSMULTIPLE_ITEM** ksMultipleItem);

static PaError WdmSetMuxNodeProperty(HANDLE handle,
                                     ULONG nodeId,
                                     ULONG pinId);


/** Pin management functions */
static PaWinWdmPin* PinNew(PaWinWdmFilter* parentFilter, unsigned long pinId, PaError* error);
static void PinFree(PaWinWdmPin* pin);
static void PinClose(PaWinWdmPin* pin);
static PaError PinInstantiate(PaWinWdmPin* pin);
/*static PaError PinGetState(PaWinWdmPin* pin, KSSTATE* state); NOT USED */
static PaError PinSetState(PaWinWdmPin* pin, KSSTATE state);
static PaError PinSetFormat(PaWinWdmPin* pin, const WAVEFORMATEX* format);
static PaError PinIsFormatSupported(PaWinWdmPin* pin, const WAVEFORMATEX* format);
/* WaveRT support */
static PaError PinQueryNotificationSupport(PaWinWdmPin* pPin, BOOL* pbResult);
static PaError PinGetBuffer(PaWinWdmPin* pPin, void** pBuffer, DWORD* pRequestedBufSize, BOOL* pbCallMemBarrier);
static PaError PinRegisterPositionRegister(PaWinWdmPin* pPin);
static PaError PinRegisterNotificationHandle(PaWinWdmPin* pPin, HANDLE handle);
static PaError PinUnregisterNotificationHandle(PaWinWdmPin* pPin, HANDLE handle);
static PaError PinGetHwLatency(PaWinWdmPin* pPin, ULONG* pFifoSize, ULONG* pChipsetDelay, ULONG* pCodecDelay);
static PaError PinGetAudioPositionMemoryMapped(PaWinWdmPin* pPin, ULONG* pPosition);
static PaError PinGetAudioPositionViaIOCTLRead(PaWinWdmPin* pPin, ULONG* pPosition);
static PaError PinGetAudioPositionViaIOCTLWrite(PaWinWdmPin* pPin, ULONG* pPosition);

/* Filter management functions */
static PaWinWdmFilter* FilterNew(PaWDMKSType type, DWORD devNode, const wchar_t* filterName, const wchar_t* friendlyName, PaError* error);
static PaError FilterInitializePins(PaWinWdmFilter* filter);
static void FilterFree(PaWinWdmFilter* filter);
static void FilterAddRef(PaWinWdmFilter* filter);
static PaWinWdmPin* FilterCreatePin(
                                    PaWinWdmFilter* filter,
                                    int pinId,
                                    const WAVEFORMATEX* wfex,
                                    PaError* error);
static PaError FilterUse(PaWinWdmFilter* filter);
static void FilterRelease(PaWinWdmFilter* filter);

/* Hot plug functions */
static BOOL IsDeviceTheSame(const PaWinWdmDeviceInfo* pDev1,
                            const PaWinWdmDeviceInfo* pDev2);

/* Interface functions */
static void Terminate( struct PaUtilHostApiRepresentation *hostApi );
static PaError IsFormatSupported(
struct PaUtilHostApiRepresentation *hostApi,
    const PaStreamParameters *inputParameters,
    const PaStreamParameters *outputParameters,
    double sampleRate );

static PaError ScanDeviceInfos( struct PaUtilHostApiRepresentation *hostApi, PaHostApiIndex index, void **newDeviceInfos, int *newDeviceCount );
static PaError CommitDeviceInfos( struct PaUtilHostApiRepresentation *hostApi, PaHostApiIndex index, void *deviceInfos, int deviceCount );
static PaError DisposeDeviceInfos( struct PaUtilHostApiRepresentation *hostApi, void *deviceInfos, int deviceCount );

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 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 );

/* Utility functions */
static unsigned long GetWfexSize(const WAVEFORMATEX* wfex);
static PaWinWdmFilter** BuildFilterList(int* filterCount, int* noOfPaDevices, PaError* result);
static BOOL PinWrite(HANDLE h, DATAPACKET* p);
static BOOL PinRead(HANDLE h, DATAPACKET* p);
static void DuplicateFirstChannelInt16(void* buffer, int channels, int samples);
static void DuplicateFirstChannelInt24(void* buffer, int channels, int samples);
PA_THREAD_FUNC ProcessingThread(void*);

/* Pin handler functions */
static PaError PaPinCaptureEventHandler_WaveCyclic(PaProcessThreadInfo* pInfo, unsigned eventIndex);
static PaError PaPinCaptureSubmitHandler_WaveCyclic(PaProcessThreadInfo* pInfo, unsigned eventIndex);

static PaError PaPinRenderEventHandler_WaveCyclic(PaProcessThreadInfo* pInfo, unsigned eventIndex);
static PaError PaPinRenderSubmitHandler_WaveCyclic(PaProcessThreadInfo* pInfo, unsigned eventIndex);

static PaError PaPinCaptureEventHandler_WaveRTEvent(PaProcessThreadInfo* pInfo, unsigned eventIndex);
static PaError PaPinCaptureEventHandler_WaveRTPolled(PaProcessThreadInfo* pInfo, unsigned eventIndex);
static PaError PaPinCaptureSubmitHandler_WaveRTEvent(PaProcessThreadInfo* pInfo, unsigned eventIndex);
static PaError PaPinCaptureSubmitHandler_WaveRTPolled(PaProcessThreadInfo* pInfo, unsigned eventIndex);

static PaError PaPinRenderEventHandler_WaveRTEvent(PaProcessThreadInfo* pInfo, unsigned eventIndex);
static PaError PaPinRenderEventHandler_WaveRTPolled(PaProcessThreadInfo* pInfo, unsigned eventIndex);
static PaError PaPinRenderSubmitHandler_WaveRTEvent(PaProcessThreadInfo* pInfo, unsigned eventIndex);
static PaError PaPinRenderSubmitHandler_WaveRTPolled(PaProcessThreadInfo* pInfo, unsigned eventIndex);

/* Function bodies */

#if defined(_DEBUG) && defined(PA_ENABLE_DEBUG_OUTPUT)
#define PA_WDMKS_SET_TREF
static PaTime tRef = 0;

static void PaWinWdmDebugPrintf(const char* fmt, ...)
{
    va_list list;
    char buffer[1024];
    PaTime t = PaUtil_GetTime() - tRef;
    va_start(list, fmt);
    _vsnprintf(buffer, 1023, fmt, list);
    va_end(list);
    PaUtil_DebugPrint("%6.3lf: %s", t, buffer);
}

#ifdef PA_DEBUG
#undef PA_DEBUG
#define PA_DEBUG(x)    PaWinWdmDebugPrintf x ;
#endif
#endif

static BOOL IsDeviceTheSame(const PaWinWdmDeviceInfo* pDev1,
                            const PaWinWdmDeviceInfo* pDev2)
{
    if (pDev1 == NULL || pDev2 == NULL)
        return FALSE;

    if (pDev1 == pDev2)
        return TRUE;

    if (strcmp(pDev1->compositeName, pDev2->compositeName) == 0)
        return TRUE;

    return FALSE;
}

static BOOL IsEarlierThanVista()
{
/*
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 */

    OSVERSIONINFO osvi;
    osvi.dwOSVersionInfoSize = sizeof(osvi);
    if (GetVersionEx(&osvi) && osvi.dwMajorVersion<6)
    {
        return TRUE;
    }
    return FALSE;

#pragma warning (default : 4996)
}



static void MemoryBarrierDummy(void)
{
    /* Do nothing */
}

static void MemoryBarrierRead(void)
{
    PaUtil_ReadMemoryBarrier();
}

static void MemoryBarrierWrite(void)
{
    PaUtil_WriteMemoryBarrier();
}

static unsigned long GetWfexSize(const WAVEFORMATEX* wfex)
{
    if( wfex->wFormatTag == WAVE_FORMAT_PCM )
    {
        return sizeof( WAVEFORMATEX );
    }
    else
    {
        return (sizeof( WAVEFORMATEX ) + wfex->cbSize);
    }
}

static void PaWinWDM_SetLastErrorInfo(long errCode, const char* fmt, ...)
{
    va_list list;
    char buffer[1024];
    va_start(list, fmt);
    _vsnprintf(buffer, 1023, fmt, list);
    va_end(list);
    PaUtil_SetLastHostErrorInfo(paWDMKS, errCode, buffer);
}

/*
Low level pin/filter access functions
*/
static PaError WdmSyncIoctl(
                            HANDLE handle,
                            unsigned long ioctlNumber,
                            void* inBuffer,
                            unsigned long inBufferCount,
                            void* outBuffer,
                            unsigned long outBufferCount,
                            unsigned long* bytesReturned)
{
    PaError result = paNoError;
    unsigned long dummyBytesReturned = 0;
    BOOL bRes;

    if( !bytesReturned )
    {
        /* Use a dummy as the caller hasn't supplied one */
        bytesReturned = &dummyBytesReturned;
    }

    bRes = DeviceIoControl(handle, ioctlNumber, inBuffer, inBufferCount, outBuffer, outBufferCount, bytesReturned, NULL);
    if (!bRes)
    {
        unsigned long error = GetLastError();
        if ( !(((error == ERROR_INSUFFICIENT_BUFFER ) || ( error == ERROR_MORE_DATA )) &&
            ( ioctlNumber == IOCTL_KS_PROPERTY ) &&
            ( outBufferCount == 0 ) ) )
        {
            KSPROPERTY* ksProperty = (KSPROPERTY*)inBuffer;

            PaWinWDM_SetLastErrorInfo(result, "WdmSyncIoctl: DeviceIoControl GLE = 0x%08X (prop_set = {%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}, prop_id = %u)",
                error,
                ksProperty->Set.Data1, ksProperty->Set.Data2, ksProperty->Set.Data3,
                ksProperty->Set.Data4[0], ksProperty->Set.Data4[1],
                ksProperty->Set.Data4[2], ksProperty->Set.Data4[3],
                ksProperty->Set.Data4[4], ksProperty->Set.Data4[5],
                ksProperty->Set.Data4[6], ksProperty->Set.Data4[7],
                ksProperty->Id
                );
            result = paUnanticipatedHostError;
        }
    }
    return result;
}

static PaError WdmGetPropertySimple(HANDLE handle,
                                    const GUID* const guidPropertySet,
                                    unsigned long property,
                                    void* value,
                                    unsigned long valueCount)
{
    PaError result;
    KSPROPERTY ksProperty;

    ksProperty.Set = *guidPropertySet;
    ksProperty.Id = property;
    ksProperty.Flags = KSPROPERTY_TYPE_GET;

    result = WdmSyncIoctl(
        handle,
        IOCTL_KS_PROPERTY,
        &ksProperty,
        sizeof(KSPROPERTY),
        value,
        valueCount,
        NULL);

    return result;
}

static PaError WdmSetPropertySimple(
                                    HANDLE handle,
                                    const GUID* const guidPropertySet,
                                    unsigned long property,
                                    void* value,
                                    unsigned long valueCount,
                                    void* instance,
                                    unsigned long instanceCount)
{
    PaError result;
    KSPROPERTY* ksProperty;
    unsigned long propertyCount  = 0;

    propertyCount = sizeof(KSPROPERTY) + instanceCount;
    ksProperty = (KSPROPERTY*)_alloca( propertyCount );
    if( !ksProperty )
    {
        return paInsufficientMemory;
    }

    ksProperty->Set = *guidPropertySet;
    ksProperty->Id = property;
    ksProperty->Flags = KSPROPERTY_TYPE_SET;

    if( instance )
    {
        memcpy((void*)((char*)ksProperty + sizeof(KSPROPERTY)), instance, instanceCount);
    }

    result = WdmSyncIoctl(
        handle,
        IOCTL_KS_PROPERTY,
        ksProperty,
        propertyCount,
        value,
        valueCount,
        NULL);

    return result;
}

static PaError WdmGetPinPropertySimple(
                                       HANDLE  handle,
                                       unsigned long pinId,
                                       const GUID* const guidPropertySet,
                                       unsigned long property,
                                       void* value,
                                       unsigned long valueCount,
                                       unsigned long *byteCount)
{
    PaError result;

    KSP_PIN ksPProp;
    ksPProp.Property.Set = *guidPropertySet;
    ksPProp.Property.Id = property;
    ksPProp.Property.Flags = KSPROPERTY_TYPE_GET;
    ksPProp.PinId = pinId;
    ksPProp.Reserved = 0;

    result = WdmSyncIoctl(
        handle,
        IOCTL_KS_PROPERTY,
        &ksPProp,
        sizeof(KSP_PIN),
        value,
        valueCount,
        byteCount);

    return result;
}

static PaError WdmGetPinPropertyMulti(
                                      HANDLE handle,
                                      unsigned long pinId,
                                      const GUID* const guidPropertySet,
                                      unsigned long property,
                                      KSMULTIPLE_ITEM** ksMultipleItem)
{
    PaError result;
    unsigned long multipleItemSize = 0;
    KSP_PIN ksPProp;

    ksPProp.Property.Set = *guidPropertySet;
    ksPProp.Property.Id = property;
    ksPProp.Property.Flags = KSPROPERTY_TYPE_GET;
    ksPProp.PinId = pinId;
    ksPProp.Reserved = 0;

    result = WdmSyncIoctl(
        handle,
        IOCTL_KS_PROPERTY,
        &ksPProp.Property,
        sizeof(KSP_PIN),
        NULL,
        0,
        &multipleItemSize);
    if( result != paNoError )
    {
        return result;
    }

    *ksMultipleItem = (KSMULTIPLE_ITEM*)PaUtil_AllocateMemory( multipleItemSize );
    if( !*ksMultipleItem )
    {
        return paInsufficientMemory;
    }

    result = WdmSyncIoctl(
        handle,
        IOCTL_KS_PROPERTY,
        &ksPProp,
        sizeof(KSP_PIN),
        (void*)*ksMultipleItem,
        multipleItemSize,
        NULL);

    if( result != paNoError )
    {
        PaUtil_FreeMemory( ksMultipleItem );
    }

    return result;
}

static PaError WdmGetPropertyMulti(HANDLE handle,
                                   const GUID* const guidPropertySet,
                                   unsigned long property,
                                   KSMULTIPLE_ITEM** ksMultipleItem)
{
    PaError result;
    unsigned long multipleItemSize = 0;
    KSPROPERTY ksProp;

    ksProp.Set = *guidPropertySet;
    ksProp.Id = property;
    ksProp.Flags = KSPROPERTY_TYPE_GET;

    result = WdmSyncIoctl(
        handle,
        IOCTL_KS_PROPERTY,
        &ksProp,
        sizeof(KSPROPERTY),
        NULL,
        0,
        &multipleItemSize);
    if( result != paNoError )
    {
        return result;
    }

    *ksMultipleItem = (KSMULTIPLE_ITEM*)PaUtil_AllocateMemory( multipleItemSize );
    if( !*ksMultipleItem )
    {
        return paInsufficientMemory;
    }

    result = WdmSyncIoctl(
        handle,
        IOCTL_KS_PROPERTY,
        &ksProp,
        sizeof(KSPROPERTY),
        (void*)*ksMultipleItem,
        multipleItemSize,
        NULL);

    if( result != paNoError )
    {
        PaUtil_FreeMemory( ksMultipleItem );
    }

    return result;
}

static PaError WdmSetMuxNodeProperty(HANDLE handle,
                                     ULONG nodeId,
                                     ULONG pinId)
{
    PaError result = paNoError;
    KSNODEPROPERTY prop;
    prop.Property.Set = KSPROPSETID_Audio;
    prop.Property.Id = KSPROPERTY_AUDIO_MUX_SOURCE;
    prop.Property.Flags = KSPROPERTY_TYPE_SET | KSPROPERTY_TYPE_TOPOLOGY;
    prop.NodeId = nodeId;
    prop.Reserved = 0;

    result = WdmSyncIoctl(handle, IOCTL_KS_PROPERTY, &prop, sizeof(KSNODEPROPERTY), &pinId, sizeof(ULONG), NULL);

    return result;
}

/* Used when traversing topology for outputs */
static const KSTOPOLOGY_CONNECTION* GetConnectionTo(const KSTOPOLOGY_CONNECTION* pFrom, PaWinWdmFilter* filter, int muxIdx)
{
    unsigned i;
    const KSTOPOLOGY_CONNECTION* retval = NULL;
    const KSTOPOLOGY_CONNECTION* connections = (const KSTOPOLOGY_CONNECTION*)(filter->connections + 1);
    (void)muxIdx;
    PA_DEBUG(("GetConnectionTo: Checking %u connections... (pFrom = %p)", filter->connections->Count, pFrom));
    for (i = 0; i < filter->connections->Count; ++i)
    {
        const KSTOPOLOGY_CONNECTION* pConn = connections + i;
        if (pConn == pFrom)
            continue;

        if (pConn->FromNode == pFrom->ToNode)
        {
            retval = pConn;
            break;
        }
    }
    PA_DEBUG(("GetConnectionTo: Returning %p\n", retval));
    return retval;
}

/* Used when traversing topology for inputs */
static const KSTOPOLOGY_CONNECTION* GetConnectionFrom(const KSTOPOLOGY_CONNECTION* pTo, PaWinWdmFilter* filter, int muxIdx)
{
    unsigned i;
    const KSTOPOLOGY_CONNECTION* retval = NULL;
    const KSTOPOLOGY_CONNECTION* connections = (const KSTOPOLOGY_CONNECTION*)(filter->connections + 1);
    int muxCntr = 0;
    PA_DEBUG(("GetConnectionFrom: Checking %u connections... (pTo = %p)\n", filter->connections->Count, pTo));
    for (i = 0; i < filter->connections->Count; ++i)
    {
        const KSTOPOLOGY_CONNECTION* pConn = connections + i;
        if (pConn == pTo)
            continue;

        if (pConn->ToNode == pTo->FromNode)
        {
            if (muxIdx >= 0)
            {
                if (muxCntr < muxIdx)
                {
                    ++muxCntr;
                    continue;
                }
            }
            retval = pConn;
            break;
        }
    }
    PA_DEBUG(("GetConnectionFrom: Returning %p\n", retval));
    return retval;
}

static ULONG GetNumberOfConnectionsTo(const KSTOPOLOGY_CONNECTION* pTo, PaWinWdmFilter* filter)
{
    ULONG retval = 0;
    unsigned i;
    const KSTOPOLOGY_CONNECTION* connections = (const KSTOPOLOGY_CONNECTION*)(filter->connections + 1);
    PA_DEBUG(("GetNumberOfConnectionsTo: Checking %u connections...\n", filter->connections->Count));
    for (i = 0; i < filter->connections->Count; ++i)
    {
        const KSTOPOLOGY_CONNECTION* pConn = connections + i;
        if (pConn->ToNode == pTo->FromNode &&
            (pTo->FromNode != KSFILTER_NODE || pConn->ToNodePin == pTo->FromNodePin))
        {
            ++retval;
        }
    }
    PA_DEBUG(("GetNumberOfConnectionsTo: Returning %d\n", retval));
    return retval;
}

typedef const KSTOPOLOGY_CONNECTION *(*TFnGetConnection)(const KSTOPOLOGY_CONNECTION*, PaWinWdmFilter*, int);

static const KSTOPOLOGY_CONNECTION* FindStartConnectionFrom(ULONG startPin, PaWinWdmFilter* filter)
{
    unsigned i;
    const KSTOPOLOGY_CONNECTION* connections = (const KSTOPOLOGY_CONNECTION*)(filter->connections + 1);
    PA_DEBUG(("FindStartConnectionFrom: Startpin %u, Checking %u connections...\n", startPin, filter->connections->Count));
    for (i = 0; i < filter->connections->Count; ++i)
    {
        const KSTOPOLOGY_CONNECTION* pConn = connections + i;
        if (pConn->ToNode == KSFILTER_NODE && pConn->ToNodePin == startPin)
        {
            PA_DEBUG(("FindStartConnectionFrom: returning %p\n", pConn));
            return pConn;
        }
    }

    /* Some devices may report topologies that leave pins unconnected. This may be by design or driver installation
       issues. Pass the error condition back to caller. */
    PA_DEBUG(("FindStartConnectionFrom: returning NULL\n"));
    return 0;
}

static const KSTOPOLOGY_CONNECTION* FindStartConnectionTo(ULONG startPin, PaWinWdmFilter* filter)
{
    unsigned i;
    const KSTOPOLOGY_CONNECTION* connections = (const KSTOPOLOGY_CONNECTION*)(filter->connections + 1);
    PA_DEBUG(("FindStartConnectionTo: Startpin %u, Checking %u connections...\n", startPin, filter->connections->Count));
    for (i = 0; i < filter->connections->Count; ++i)
    {
        const KSTOPOLOGY_CONNECTION* pConn = connections + i;
        if (pConn->FromNode == KSFILTER_NODE && pConn->FromNodePin == startPin)
        {
            PA_DEBUG(("FindStartConnectionTo: returning %p\n", pConn));
            return pConn;
        }
    }

    /* Unconnected pin. Inform caller. */
    PA_DEBUG(("FindStartConnectionTo: returning NULL\n"));
    return 0;
}

static ULONG GetConnectedPin(ULONG startPin, BOOL forward, PaWinWdmFilter* filter, int muxPosition, ULONG *muxInputPinId, ULONG *muxNodeId)
{
    int limit=1000;
    const KSTOPOLOGY_CONNECTION *conn = NULL;
    TFnGetConnection fnGetConnection = forward ? GetConnectionTo : GetConnectionFrom ;
    PA_LOGE_;
    while (1)
    {
        limit--;
        if (limit == 0) {
            PA_DEBUG(("GetConnectedPin: LOOP LIMIT REACHED\n"));
            break;
        }

        if (conn == NULL)
        {
            conn = forward ? FindStartConnectionTo(startPin, filter) : FindStartConnectionFrom(startPin, filter);
        }
        else
        {
            conn = fnGetConnection(conn, filter, -1);
        }

        /* Handling case of erroneous connection list */
        if (conn == NULL)
        {
            break;
        }

        if (forward ? conn->ToNode == KSFILTER_NODE : conn->FromNode == KSFILTER_NODE)
        {
            return forward ? conn->ToNodePin : conn->FromNodePin;
        }
        else
        {
            PA_DEBUG(("GetConnectedPin: count=%d, forward=%d, muxPosition=%d\n", filter->nodes->Count, forward, muxPosition));
            if (filter->nodes->Count > 0 && !forward && muxPosition >= 0)
            {
                const GUID* nodes = (const GUID*)(filter->nodes + 1);
                if (IsEqualGUID(&nodes[conn->FromNode], &KSNODETYPE_MUX))
                {
                    ULONG nConn = GetNumberOfConnectionsTo(conn, filter);
                    conn = fnGetConnection(conn, filter, muxPosition);
                    if (conn == NULL)
                    {
                        break;
                    }
                    if (muxInputPinId != 0)
                    {
                        *muxInputPinId = conn->ToNodePin;
                    }
                    if (muxNodeId != 0)
                    {
                        *muxNodeId = conn->ToNode;
                    }
                }
            }
        }
    }
    PA_LOGL_;
    return KSFILTER_NODE;
}

static void DumpConnectionsAndNodes(PaWinWdmFilter* filter)
{
    unsigned i;
    const KSTOPOLOGY_CONNECTION* connections = (const KSTOPOLOGY_CONNECTION*)(filter->connections + 1);
    const GUID* nodes = (const GUID*)(filter->nodes + 1);

    PA_LOGE_;
    PA_DEBUG(("DumpConnectionsAndNodes: connections=%d, nodes=%d\n", filter->connections->Count, filter->nodes->Count));

    for (i=0; i < filter->connections->Count; ++i)
    {
        const KSTOPOLOGY_CONNECTION* pConn = connections + i;
        PA_DEBUG(("  Connection: %u - FromNode=%u,FromPin=%u -> ToNode=%u,ToPin=%u\n",
            i,
            pConn->FromNode, pConn->FromNodePin,
            pConn->ToNode, pConn->ToNodePin
            ));
    }

    for (i=0; i < filter->nodes->Count; ++i)
    {
        const GUID* pConn = nodes + i;
        PA_DEBUG(("  Node: %d - {%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}\n",
            i,
            pConn->Data1, pConn->Data2, pConn->Data3,
            pConn->Data4[0], pConn->Data4[1],
            pConn->Data4[2], pConn->Data4[3],
            pConn->Data4[4], pConn->Data4[5],
            pConn->Data4[6], pConn->Data4[7]
        ));
    }
    PA_LOGL_;

}

typedef struct __PaUsbTerminalGUIDToName
{
    USHORT     usbGUID;
    wchar_t    name[64];
} PaUsbTerminalGUIDToName;

static const PaUsbTerminalGUIDToName kNames[] =
{
    /* Types copied from: http://msdn.microsoft.com/en-us/library/ff537742(v=vs.85).aspx */
    /* Input terminal types */
    { 0x0201, L"Microphone" },
    { 0x0202, L"Desktop Microphone" },
    { 0x0203, L"Personal Microphone" },
    { 0x0204, L"Omni Directional Microphone" },
    { 0x0205, L"Microphone Array" },
    { 0x0206, L"Processing Microphone Array" },
    /* Output terminal types */
    { 0x0301, L"Speakers" },
    { 0x0302, L"Headphones" },
    { 0x0303, L"Head Mounted Display Audio" },
    { 0x0304, L"Desktop Speaker" },
    { 0x0305, L"Room Speaker" },
    { 0x0306, L"Communication Speaker" },
    { 0x0307, L"LFE Speakers" },
    /* External terminal types */
    { 0x0601, L"Analog" },
    { 0x0602, L"Digital" },
    { 0x0603, L"Line" },
    { 0x0604, L"Audio" },
    { 0x0605, L"SPDIF" },
};

static const unsigned kNamesCnt = sizeof(kNames)/sizeof(PaUsbTerminalGUIDToName);

static int PaUsbTerminalGUIDToNameCmp(const void* lhs, const void* rhs)
{
    const PaUsbTerminalGUIDToName* pL = (const PaUsbTerminalGUIDToName*)lhs;
    const PaUsbTerminalGUIDToName* pR = (const PaUsbTerminalGUIDToName*)rhs;
    return ((int)(pL->usbGUID) - (int)(pR->usbGUID));
}

static PaError GetNameFromCategory(const GUID* pGUID, BOOL input, wchar_t* name, unsigned length)
{
    PaError result = paUnanticipatedHostError;
    USHORT usbTerminalGUID = (USHORT)(pGUID->Data1 - 0xDFF219E0);

    PA_LOGE_;
    if (input && usbTerminalGUID >= 0x301 && usbTerminalGUID < 0x400)
    {
        /* Output terminal name for an input !? Set it to Line! */
        usbTerminalGUID = 0x603;
    }
    if (!input && usbTerminalGUID >= 0x201 && usbTerminalGUID < 0x300)
    {
        /* Input terminal name for an output !? Set it to Line! */
        usbTerminalGUID = 0x603;
    }
    if (usbTerminalGUID >= 0x201 && usbTerminalGUID < 0x713)
    {
        PaUsbTerminalGUIDToName s = { usbTerminalGUID };
        const PaUsbTerminalGUIDToName* ptr = bsearch(
            &s,
            kNames,
            kNamesCnt,
            sizeof(PaUsbTerminalGUIDToName),
            PaUsbTerminalGUIDToNameCmp
            );
        if (ptr != 0)
        {
            PA_DEBUG(("GetNameFromCategory: USB GUID %04X -> '%S'\n", usbTerminalGUID, ptr->name));

            if (name != NULL && length > 0)
            {
                int n = _snwprintf(name, length, L"%s", ptr->name);
                if (usbTerminalGUID >= 0x601 && usbTerminalGUID < 0x700)
                {
                    _snwprintf(name + n, length - n, L" %s", (input ? L"In":L"Out"));
                }
            }
            result = paNoError;
        }
    }
    else
    {
        PaWinWDM_SetLastErrorInfo(result, "GetNameFromCategory: usbTerminalGUID = %04X ", usbTerminalGUID);
    }
    PA_LOGL_;
    return result;
}

static BOOL IsFrequencyWithinRange(const KSDATARANGE_AUDIO* range, int frequency)
{
    if (frequency < (int)range->MinimumSampleFrequency)
        return FALSE;
    if (frequency > (int)range->MaximumSampleFrequency)
        return FALSE;
    return TRUE;
}

static BOOL IsBitsWithinRange(const KSDATARANGE_AUDIO* range, int noOfBits)
{
    if (noOfBits < (int)range->MinimumBitsPerSample)
        return FALSE;
    if (noOfBits > (int)range->MaximumBitsPerSample)
        return FALSE;
    return TRUE;
}

/* Note: Somewhat different order compared to WMME implementation, as we want to focus on fidelity first */
static const int defaultSampleRateSearchOrder[] =
{ 44100, 48000, 88200, 96000, 192000, 32000, 24000, 22050, 16000, 12000, 11025, 9600, 8000 };
static const int defaultSampleRateSearchOrderCount = sizeof(defaultSampleRateSearchOrder)/sizeof(defaultSampleRateSearchOrder[0]);

static int DefaultSampleFrequencyIndex(const KSDATARANGE_AUDIO* range)
{
    int i;

    for(i=0; i < defaultSampleRateSearchOrderCount; ++i)
    {
        int currentFrequency = defaultSampleRateSearchOrder[i];

        if (IsFrequencyWithinRange(range, currentFrequency))
        {
            return i;
        }
    }

    return -1;
}

/*
Create a new pin object belonging to a filter
The pin object holds all the configuration information about the pin
before it is opened, and then the handle of the pin after is opened
*/
static PaWinWdmPin* PinNew(PaWinWdmFilter* parentFilter, unsigned long pinId, PaError* error)
{
    PaWinWdmPin* pin;
    PaError result;
    unsigned long i;
    KSMULTIPLE_ITEM* item = NULL;
    KSIDENTIFIER* identifier;
    KSDATARANGE* dataRange;
    const ULONG streamingId = (parentFilter->devInfo.streamingType == Type_kWaveRT) ? KSINTERFACE_STANDARD_LOOPED_STREAMING : KSINTERFACE_STANDARD_STREAMING;
    int defaultSampleRateIndex = defaultSampleRateSearchOrderCount;

    PA_LOGE_;
    PA_DEBUG(("PinNew: Creating pin %d:\n",pinId));

    /* Allocate the new PIN object */
    pin = (PaWinWdmPin*)PaUtil_AllocateMemory( sizeof(PaWinWdmPin) );
    if( !pin )
    {
        result = paInsufficientMemory;
        goto error;
    }

    /* Zero the pin object */
    /* memset( (void*)pin, 0, sizeof(PaWinWdmPin) ); */

    pin->parentFilter = parentFilter;
    pin->pinId = pinId;

    /* Allocate a connect structure */
    pin->pinConnectSize = sizeof(KSPIN_CONNECT) + sizeof(KSDATAFORMAT_WAVEFORMATEX);
    pin->pinConnect = (KSPIN_CONNECT*)PaUtil_AllocateMemory( pin->pinConnectSize );
    if( !pin->pinConnect )
    {
        result = paInsufficientMemory;
        goto error;
    }

    /* Configure the connect structure with default values */
    pin->pinConnect->Interface.Set               = KSINTERFACESETID_Standard;
    pin->pinConnect->Interface.Id                = streamingId;
    pin->pinConnect->Interface.Flags             = 0;
    pin->pinConnect->Medium.Set                  = KSMEDIUMSETID_Standard;
    pin->pinConnect->Medium.Id                   = KSMEDIUM_TYPE_ANYINSTANCE;
    pin->pinConnect->Medium.Flags                = 0;
    pin->pinConnect->PinId                       = pinId;
    pin->pinConnect->PinToHandle                 = NULL;
    pin->pinConnect->Priority.PriorityClass      = KSPRIORITY_NORMAL;
    pin->pinConnect->Priority.PrioritySubClass   = 1;
    pin->ksDataFormatWfx = (KSDATAFORMAT_WAVEFORMATEX*)(pin->pinConnect + 1);
    pin->ksDataFormatWfx->DataFormat.FormatSize  = sizeof(KSDATAFORMAT_WAVEFORMATEX);
    pin->ksDataFormatWfx->DataFormat.Flags       = 0;
    pin->ksDataFormatWfx->DataFormat.Reserved    = 0;
    pin->ksDataFormatWfx->DataFormat.MajorFormat = KSDATAFORMAT_TYPE_AUDIO;
    pin->ksDataFormatWfx->DataFormat.SubFormat   = KSDATAFORMAT_SUBTYPE_PCM;
    pin->ksDataFormatWfx->DataFormat.Specifier   = KSDATAFORMAT_SPECIFIER_WAVEFORMATEX;

    pin->frameSize = 0; /* Unknown until we instantiate pin */

    /* Get the COMMUNICATION property */
    result = WdmGetPinPropertySimple(
        parentFilter->handle,
        pinId,
        &KSPROPSETID_Pin,
        KSPROPERTY_PIN_COMMUNICATION,
        &pin->communication,
        sizeof(KSPIN_COMMUNICATION),
        NULL);
    if( result != paNoError )
        goto error;

    if( /*(pin->communication != KSPIN_COMMUNICATION_SOURCE) &&*/
        (pin->communication != KSPIN_COMMUNICATION_SINK) &&
        (pin->communication != KSPIN_COMMUNICATION_BOTH) )
    {
        PA_DEBUG(("PinNew: Not source/sink\n"));
        result = paInvalidDevice;
        goto error;
    }

    /* Get dataflow information */
    result = WdmGetPinPropertySimple(
        parentFilter->handle,
        pinId,
        &KSPROPSETID_Pin,
        KSPROPERTY_PIN_DATAFLOW,
        &pin->dataFlow,
        sizeof(KSPIN_DATAFLOW),
        NULL);

    if( result != paNoError )
        goto error;

    /* Get the INTERFACE property list */
    result = WdmGetPinPropertyMulti(
        parentFilter->handle,
        pinId,
        &KSPROPSETID_Pin,
        KSPROPERTY_PIN_INTERFACES,
        &item);

    if( result != paNoError )
        goto error;

    identifier = (KSIDENTIFIER*)(item+1);

    /* Check that at least one interface is STANDARD_STREAMING */
    result = paUnanticipatedHostError;
    for( i = 0; i < item->Count; i++ )
    {
        if( IsEqualGUID(&identifier[i].Set, &KSINTERFACESETID_Standard) && ( identifier[i].Id == streamingId ) )
        {
            result = paNoError;
            break;
        }
    }

    if( result != paNoError )
    {
        PA_DEBUG(("PinNew: No %s streaming\n", streamingId==KSINTERFACE_STANDARD_LOOPED_STREAMING?"looped":"standard"));
        goto error;
    }

    /* Don't need interfaces any more */
    PaUtil_FreeMemory( item );
    item = NULL;

    /* Get the MEDIUM properties list */
    result = WdmGetPinPropertyMulti(
        parentFilter->handle,
        pinId,
        &KSPROPSETID_Pin,
        KSPROPERTY_PIN_MEDIUMS,
        &item);

    if( result != paNoError )
        goto error;

    identifier = (KSIDENTIFIER*)(item+1); /* Not actually necessary... */

    /* Check that at least one medium is STANDARD_DEVIO */
    result = paUnanticipatedHostError;
    for( i = 0; i < item->Count; i++ )
    {
        if( IsEqualGUID(&identifier[i].Set, &KSMEDIUMSETID_Standard) && ( identifier[i].Id == KSMEDIUM_STANDARD_DEVIO ) )
        {
            result = paNoError;
            break;
        }
    }

    if( result != paNoError )
    {
        PA_DEBUG(("No standard devio\n"));
        goto error;
    }
    /* Don't need mediums any more */
    PaUtil_FreeMemory( item );
    item = NULL;

    /* Get DATARANGES */
    result = WdmGetPinPropertyMulti(
        parentFilter->handle,
        pinId,
        &KSPROPSETID_Pin,
        KSPROPERTY_PIN_DATARANGES,
        &pin->dataRangesItem);

    if( result != paNoError )
        goto error;

    pin->dataRanges = (KSDATARANGE*)(pin->dataRangesItem +1);

    /* Check that at least one datarange supports audio */
    result = paUnanticipatedHostError;
    dataRange = pin->dataRanges;
    pin->maxChannels = 0;
    pin->defaultSampleRate = 0;
    pin->formats = 0;
    PA_DEBUG(("PinNew: Checking %u no of dataranges...\n", pin->dataRangesItem->Count));
    for( i = 0; i < pin->dataRangesItem->Count; i++)
    {
        PA_DEBUG(("PinNew: DR major format %x\n",*(unsigned long*)(&(dataRange->MajorFormat))));
        /* Check that subformat is WAVEFORMATEX, PCM or WILDCARD */
        if( IS_VALID_WAVEFORMATEX_GUID(&dataRange->SubFormat) ||
            IsEqualGUID(&dataRange->SubFormat, &KSDATAFORMAT_SUBTYPE_PCM) ||
            IsEqualGUID(&dataRange->SubFormat, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT) ||
            IsEqualGUID(&dataRange->SubFormat, &KSDATAFORMAT_SUBTYPE_WILDCARD) ||
            IsEqualGUID(&dataRange->MajorFormat, &KSDATAFORMAT_TYPE_AUDIO) )
        {
            int defaultIndex;
            result = paNoError;
            /* Record the maximum possible channels with this pin */
            if( ((KSDATARANGE_AUDIO*)dataRange)->MaximumChannels == (ULONG) -1 )
            {
                pin->maxChannels = MAXIMUM_NUMBER_OF_CHANNELS;
            }
            else if( (int) ((KSDATARANGE_AUDIO*)dataRange)->MaximumChannels > pin->maxChannels )
            {
                pin->maxChannels = (int) ((KSDATARANGE_AUDIO*)dataRange)->MaximumChannels;
            }
            PA_DEBUG(("PinNew: MaxChannel: %d\n",pin->maxChannels));

            /* Record the formats (bit depths) that are supported */
            if( IsBitsWithinRange((KSDATARANGE_AUDIO*)dataRange, 8) )
            {
                pin->formats |= paInt8;
                PA_DEBUG(("PinNew: Format PCM 8 bit supported\n"));
            }
            if( IsBitsWithinRange((KSDATARANGE_AUDIO*)dataRange, 16) )
            {
                pin->formats |= paInt16;
                PA_DEBUG(("PinNew: Format PCM 16 bit supported\n"));
            }
            if( IsBitsWithinRange((KSDATARANGE_AUDIO*)dataRange, 24) )
            {
                pin->formats |= paInt24;
                PA_DEBUG(("PinNew: Format PCM 24 bit supported\n"));
            }
            if( IsBitsWithinRange((KSDATARANGE_AUDIO*)dataRange, 32) )
            {
                if (IsEqualGUID(&dataRange->SubFormat, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT))
                {
                    pin->formats |= paFloat32;
                    PA_DEBUG(("PinNew: Format IEEE float 32 bit supported\n"));
                }
                else
                {
                    pin->formats |= paInt32;
                    PA_DEBUG(("PinNew: Format PCM 32 bit supported\n"));
                }
            }

            defaultIndex = DefaultSampleFrequencyIndex((KSDATARANGE_AUDIO*)dataRange);
            if (defaultIndex >= 0 && defaultIndex < defaultSampleRateIndex)
            {
                defaultSampleRateIndex = defaultIndex;
            }
        }
        dataRange = (KSDATARANGE*)( ((char*)dataRange) + dataRange->FormatSize);
    }

    if( result != paNoError )
        goto error;

    /* If none of the frequencies searched for are present, there's something seriously wrong */
    if (defaultSampleRateIndex == defaultSampleRateSearchOrderCount)
    {
        PA_DEBUG(("PinNew: No default sample rate found, skipping pin!\n"));
        PaWinWDM_SetLastErrorInfo(paUnanticipatedHostError, "PinNew: No default sample rate found");
        result = paUnanticipatedHostError;
        goto error;
    }

    /* Set the default sample rate */
    pin->defaultSampleRate = defaultSampleRateSearchOrder[defaultSampleRateIndex];
    PA_DEBUG(("PinNew: Default sample rate = %d Hz\n", pin->defaultSampleRate));

    /* Get instance information */
    result = WdmGetPinPropertySimple(
        parentFilter->handle,
        pinId,
        &KSPROPSETID_Pin,
        KSPROPERTY_PIN_CINSTANCES,
        &pin->instances,
        sizeof(KSPIN_CINSTANCES),
        NULL);

    if( result != paNoError )
        goto error;

    /* If WaveRT, check if pin supports notification mode */
    if (parentFilter->devInfo.streamingType == Type_kWaveRT)
    {
        BOOL bSupportsNotification = FALSE;
        if (PinQueryNotificationSupport(pin, &bSupportsNotification) == paNoError)
        {
            pin->pinKsSubType = bSupportsNotification ? SubType_kNotification : SubType_kPolled;
        }
    }

    /* Query pin name (which means we need to traverse to non IRP pin, via physical connection to topology filter pin, through
    its nodes to the endpoint pin, and get that ones name... phew...) */
    PA_DEBUG(("PinNew: Finding topology pin...\n"));

    {
        ULONG topoPinId = GetConnectedPin(pinId, (pin->dataFlow == KSPIN_DATAFLOW_IN), parentFilter, -1, NULL, NULL);
        const wchar_t kInputName[] = L"Input";
        const wchar_t kOutputName[] = L"Output";

        if (topoPinId != KSFILTER_NODE)
        {
            /* Get physical connection for topo pin */
            unsigned long cbBytes = 0;
            PA_DEBUG(("PinNew: Getting physical connection...\n"));
            result = WdmGetPinPropertySimple(parentFilter->handle,
                topoPinId,
                &KSPROPSETID_Pin,
                KSPROPERTY_PIN_PHYSICALCONNECTION,
                0,
                0,
                &cbBytes
                );

            if (result != paNoError)
            {
                /* No physical connection -> there is no topology filter! So we get the name of the pin! */
                PA_DEBUG(("PinNew: No physical connection! Getting the pin name\n"));
                result = WdmGetPinPropertySimple(parentFilter->handle,
                    topoPinId,
                    &KSPROPSETID_Pin,
                    KSPROPERTY_PIN_NAME,
                    pin->friendlyName,
                    MAX_PATH,
                    NULL);
                if (result != paNoError)
                {
                    GUID category = {0};

                    /* Get pin category information */
                    result = WdmGetPinPropertySimple(parentFilter->handle,
                        topoPinId,
                        &KSPROPSETID_Pin,
                        KSPROPERTY_PIN_CATEGORY,
                        &category,
                        sizeof(GUID),
                        NULL);

                    if (result == paNoError)
                    {
                        result = GetNameFromCategory(&category, (pin->dataFlow == KSPIN_DATAFLOW_OUT), pin->friendlyName, MAX_PATH);
                    }
                }

                /* Make sure pin gets a name here... */
                if (wcslen(pin->friendlyName) == 0)
                {
                    wcscpy(pin->friendlyName, (pin->dataFlow == KSPIN_DATAFLOW_IN) ? kOutputName : kInputName);
#ifdef UNICODE
                    PA_DEBUG(("PinNew: Setting pin friendly name to '%s'\n", pin->friendlyName));
#else
                    PA_DEBUG(("PinNew: Setting pin friendly name to '%S'\n", pin->friendlyName));
#endif
                }

                /* This is then == the endpoint pin */
                pin->endpointPinId = (pin->dataFlow == KSPIN_DATAFLOW_IN) ? pinId : topoPinId;
            }
            else
            {
                KSPIN_PHYSICALCONNECTION* pc = (KSPIN_PHYSICALCONNECTION*)PaUtil_AllocateMemory(cbBytes + 2);
                ULONG pcPin;
                wchar_t symbLinkName[MAX_PATH];
                PA_DEBUG(("PinNew: Physical connection found!\n"));
                if (pc == NULL)
                {
                    result = paInsufficientMemory;
                    goto error;
                }
                result = WdmGetPinPropertySimple(parentFilter->handle,
                    topoPinId,
                    &KSPROPSETID_Pin,
                    KSPROPERTY_PIN_PHYSICALCONNECTION,
                    pc,
                    cbBytes,
                    NULL
                    );

                pcPin = pc->Pin;
                wcsncpy(symbLinkName, pc->SymbolicLinkName, MAX_PATH);
                PaUtil_FreeMemory( pc );

                if (result != paNoError)
                {
                    /* Shouldn't happen, but fail if it does */
                    PA_DEBUG(("PinNew: failed to retrieve physical connection!\n"));
                    goto error;
                }

                if (symbLinkName[1] == TEXT('?'))
                {
                    symbLinkName[1] = TEXT('\\');
                }

                if (pin->parentFilter->topologyFilter == NULL)
                {
                    PA_DEBUG(("PinNew: Creating topology filter '%S'\n", symbLinkName));

                    pin->parentFilter->topologyFilter = FilterNew(Type_kNotUsed, 0, symbLinkName, L"", &result);
                    if (pin->parentFilter->topologyFilter == NULL)
                    {
                        PA_DEBUG(("PinNew: Failed creating topology filter\n"));
                        result = paUnanticipatedHostError;
                        PaWinWDM_SetLastErrorInfo(result, "Failed to create topology filter '%S'", symbLinkName);
                        goto error;
                    }

                    /* Copy info so we have it in device info */
                    wcsncpy(pin->parentFilter->devInfo.topologyPath, symbLinkName, MAX_PATH);
                }
                else
                {
                    /* Must be the same */
                    assert(wcscmp(symbLinkName, pin->parentFilter->topologyFilter->devInfo.filterPath) == 0);
                }

                PA_DEBUG(("PinNew: Opening topology filter..."));

                result = FilterUse(pin->parentFilter->topologyFilter);
                if (result == paNoError)
                {
                    unsigned long endpointPinId;

                    if (pin->dataFlow == KSPIN_DATAFLOW_IN)
                    {
                        /* The "endpointPinId" is what WASAPI looks at for pin names */
                        GUID category = {0};

                        PA_DEBUG(("PinNew: Checking for output endpoint pin id...\n"));

                        endpointPinId = GetConnectedPin(pcPin, TRUE, pin->parentFilter->topologyFilter, -1, NULL, NULL);

                        if (endpointPinId == KSFILTER_NODE)
                        {
                            result = paUnanticipatedHostError;
                            PaWinWDM_SetLastErrorInfo(result, "Failed to get endpoint pin ID on topology filter!");
                            goto error;
                        }

                        PA_DEBUG(("PinNew: Found endpoint pin id %u\n", endpointPinId));

                        /* Get pin category information */
                        result = WdmGetPinPropertySimple(pin->parentFilter->topologyFilter->handle,
                            endpointPinId,
                            &KSPROPSETID_Pin,
                            KSPROPERTY_PIN_CATEGORY,
                            &category,
                            sizeof(GUID),
                            NULL);

                        if (result == paNoError)
                        {
#if !PA_WDMKS_USE_CATEGORY_FOR_PIN_NAMES
                            wchar_t pinName[MAX_PATH];

                            PA_DEBUG(("PinNew: Getting pin name property..."));

                            /* Ok, try pin name also, and favor that if available */
                            result = WdmGetPinPropertySimple(pin->parentFilter->topologyFilter->handle,
                                endpointPinId,
                                &KSPROPSETID_Pin,
                                KSPROPERTY_PIN_NAME,
                                pinName,
                                MAX_PATH,
                                NULL);

                            if (result == paNoError && wcslen(pinName)>0)
                            {
                                wcsncpy(pin->friendlyName, pinName, MAX_PATH);
                            }
                            else
#endif
                            {
                                result = GetNameFromCategory(&category, (pin->dataFlow == KSPIN_DATAFLOW_OUT), pin->friendlyName, MAX_PATH);
                            }
                        }

                        /* Make sure we get a name for the pin */
                        if (wcslen(pin->friendlyName) == 0)
                        {
                            wcscpy(pin->friendlyName, kOutputName);
                        }
#ifdef UNICODE
                        PA_DEBUG(("PinNew: Pin name '%s'\n", pin->friendlyName));
#else
                        PA_DEBUG(("PinNew: Pin name '%S'\n", pin->friendlyName));
#endif

                        /* Set endpoint pin ID (this is the topology INPUT pin, since portmixer will always traverse the
                        filter in audio streaming direction, see http://msdn.microsoft.com/en-us/library/windows/hardware/ff536331(v=vs.85).aspx
                        for more information)
                        */
                        pin->endpointPinId = pcPin;
                    }
                    else
                    {
                        unsigned muxCount = 0;
                        int muxPos = 0;
                        /* Max 64 multiplexer inputs... sanity check :) */
                        for (i = 0; i < 64; ++i)
                        {
                            ULONG muxNodeIdTest = (unsigned)-1;
                            PA_DEBUG(("PinNew: Checking for input endpoint pin id (%d)...\n", i));

                            endpointPinId = GetConnectedPin(pcPin,
                                FALSE,
                                pin->parentFilter->topologyFilter,
                                (int)i,
                                NULL,
                                &muxNodeIdTest);

                            if (endpointPinId == KSFILTER_NODE)
                            {
                                /* We're done */
                                PA_DEBUG(("PinNew: Done with inputs.\n", endpointPinId));
                                break;
                            }
                            else
                            {
                                /* The "endpointPinId" is what WASAPI looks at for pin names */
                                GUID category = {0};

                                PA_DEBUG(("PinNew: Found endpoint pin id %u\n", endpointPinId));

                                /* Get pin category information */
                                result = WdmGetPinPropertySimple(pin->parentFilter->topologyFilter->handle,
                                    endpointPinId,
                                    &KSPROPSETID_Pin,
                                    KSPROPERTY_PIN_CATEGORY,
                                    &category,
                                    sizeof(GUID),
                                    NULL);

                                if (result == paNoError)
                                {
                                    if (muxNodeIdTest == (unsigned)-1)
                                    {
                                        /* Ok, try pin name, and favor that if available */
                                        result = WdmGetPinPropertySimple(pin->parentFilter->topologyFilter->handle,
                                            endpointPinId,
                                            &KSPROPSETID_Pin,
                                            KSPROPERTY_PIN_NAME,
                                            pin->friendlyName,
                                            MAX_PATH,
                                            NULL);

                                        if (result != paNoError)
                                        {
                                            result = GetNameFromCategory(&category, TRUE, pin->friendlyName, MAX_PATH);
                                        }
                                        break;
                                    }
                                    else
                                    {
                                        result = GetNameFromCategory(&category, TRUE, NULL, 0);

                                        if (result == paNoError)
                                        {
                                            ++muxCount;
                                        }
                                    }
                                }
                                else
                                {
                                    PA_DEBUG(("PinNew: Failed to get pin category"));
                                }
                            }
                        }

                        if (muxCount == 0)
                        {
                            pin->endpointPinId = endpointPinId;
                            /* Make sure we get a name for the pin */
                            if (wcslen(pin->friendlyName) == 0)
                            {
                                wcscpy(pin->friendlyName, kInputName);
                            }
#ifdef UNICODE
                            PA_DEBUG(("PinNew: Input friendly name '%s'\n", pin->friendlyName));
#else
                            PA_DEBUG(("PinNew: Input friendly name '%S'\n", pin->friendlyName));
#endif
                        }
                        else // muxCount > 0
                        {
                            PA_DEBUG(("PinNew: Setting up %u inputs\n", muxCount));

                            /* Now we redo the operation once known how many multiplexer positions there are */
                            pin->inputs = (PaWinWdmMuxedInput**)PaUtil_AllocateMemory(muxCount * sizeof(PaWinWdmMuxedInput*));
                            if (pin->inputs == NULL)
                            {
                                FilterRelease(pin->parentFilter->topologyFilter);
                                result = paInsufficientMemory;
                                goto error;
                            }
                            pin->inputCount = muxCount;

                            for (i = 0; i < muxCount; ++muxPos)
                            {
                                PA_DEBUG(("PinNew: Setting up input %u...\n", i));

                                if (pin->inputs[i] == NULL)
                                {
                                    pin->inputs[i] = (PaWinWdmMuxedInput*)PaUtil_AllocateMemory(sizeof(PaWinWdmMuxedInput));
                                    if (pin->inputs[i] == NULL)
                                    {
                                        FilterRelease(pin->parentFilter->topologyFilter);
                                        result = paInsufficientMemory;
                                        goto error;
                                    }
                                }

                                endpointPinId = GetConnectedPin(pcPin,
                                    FALSE,
                                    pin->parentFilter->topologyFilter,
                                    muxPos,
                                    &pin->inputs[i]->muxPinId,
                                    &pin->inputs[i]->muxNodeId);

                                if (endpointPinId != KSFILTER_NODE)
                                {
                                    /* The "endpointPinId" is what WASAPI looks at for pin names */
                                    GUID category = {0};

                                    /* Set input endpoint ID */
                                    pin->inputs[i]->endpointPinId = endpointPinId;

                                    /* Get pin category information */
                                    result = WdmGetPinPropertySimple(pin->parentFilter->topologyFilter->handle,
                                        endpointPinId,
                                        &KSPROPSETID_Pin,
                                        KSPROPERTY_PIN_CATEGORY,
                                        &category,
                                        sizeof(GUID),
                                        NULL);

                                    if (result == paNoError)
                                    {
                                        /* Try pin name first, and if that is not defined, use category instead */
                                        result = WdmGetPinPropertySimple(pin->parentFilter->topologyFilter->handle,
                                            endpointPinId,
                                            &KSPROPSETID_Pin,
                                            KSPROPERTY_PIN_NAME,
                                            pin->inputs[i]->friendlyName,
                                            MAX_PATH,
                                            NULL);

                                        if (result != paNoError)
                                        {
                                            result = GetNameFromCategory(&category, TRUE, pin->inputs[i]->friendlyName, MAX_PATH);
                                            if (result != paNoError)
                                            {
                                                /* Only specify name, let name hash in ScanDeviceInfos fix postfix enumerators */
                                                wcscpy(pin->inputs[i]->friendlyName, kInputName);
                                            }
                                        }
#ifdef UNICODE
                                        PA_DEBUG(("PinNew: Input (%u) friendly name '%s'\n", i, pin->inputs[i]->friendlyName));
#else
                                        PA_DEBUG(("PinNew: Input (%u) friendly name '%S'\n", i, pin->inputs[i]->friendlyName));
#endif
                                        ++i;
                                    }
                                }
                                else
                                {
                                    /* Unconnected pin */
                                    goto error;
                                }
                            }
                        }
                    }
                }
            }
        }
        else
        {
            PA_DEBUG(("PinNew: No topology pin id found. Bad...\n"));
            /* No TOPO pin id ??? This is bad. Ok, so we just say it is an input or output... */
            wcscpy(pin->friendlyName, (pin->dataFlow == KSPIN_DATAFLOW_IN) ? kOutputName : kInputName);
        }
    }

    /* Release topology filter if it has been used */
    if (pin->parentFilter->topologyFilter && pin->parentFilter->topologyFilter->handle != NULL)
    {
        PA_DEBUG(("PinNew: Releasing topology filter...\n"));
        FilterRelease(pin->parentFilter->topologyFilter);
    }

    /* Success */
    *error = paNoError;
    PA_DEBUG(("Pin created successfully\n"));
    PA_LOGL_;
    return pin;

error:
    PA_DEBUG(("PinNew: Error %d\n", result));
    /*
    Error cleanup
    */

    if (pin->parentFilter->topologyFilter && pin->parentFilter->topologyFilter->handle != NULL)
    {
        FilterRelease(pin->parentFilter->topologyFilter);
    }

    PaUtil_FreeMemory( item );
    PinFree(pin);

    *error = result;
    PA_LOGL_;
    return NULL;
}

/*
Safely free all resources associated with the pin
*/
static void PinFree(PaWinWdmPin* pin)
{
    unsigned i;
    PA_LOGE_;
    if( pin )
    {
        PinClose(pin);
        if( pin->pinConnect )
        {
            PaUtil_FreeMemory( pin->pinConnect );
        }
        if( pin->dataRangesItem )
        {
            PaUtil_FreeMemory( pin->dataRangesItem );
        }
        if( pin->inputs )
        {
            for (i = 0; i < pin->inputCount; ++i)
            {
                PaUtil_FreeMemory( pin->inputs[i] );
            }
            PaUtil_FreeMemory( pin->inputs );
        }
        PaUtil_FreeMemory( pin );
    }
    PA_LOGL_;
}

/*
If the pin handle is open, close it
*/
static void PinClose(PaWinWdmPin* pin)
{
    PA_LOGE_;
    if( pin == NULL )
    {
        PA_DEBUG(("Closing NULL pin!"));
        PA_LOGL_;
        return;
    }
    if( pin->handle != NULL )
    {
        PinSetState( pin, KSSTATE_PAUSE );
        PinSetState( pin, KSSTATE_STOP );
        CloseHandle( pin->handle );
        pin->handle = NULL;
        FilterRelease(pin->parentFilter);
    }
    PA_LOGL_;
}

/*
Set the state of this (instantiated) pin
*/
static PaError PinSetState(PaWinWdmPin* pin, KSSTATE state)
{
    PaError result = paNoError;
    KSPROPERTY prop;

    PA_LOGE_;
    prop.Set = KSPROPSETID_Connection;
    prop.Id  = KSPROPERTY_CONNECTION_STATE;
    prop.Flags = KSPROPERTY_TYPE_SET;

    if( pin == NULL )
        return paInternalError;
    if( pin->handle == NULL )
        return paInternalError;

    result = WdmSyncIoctl(pin->handle, IOCTL_KS_PROPERTY, &prop, sizeof(KSPROPERTY), &state, sizeof(KSSTATE), NULL);

    PA_LOGL_;
    return result;
}

static PaError PinInstantiate(PaWinWdmPin* pin)
{
    PaError result;
    unsigned long createResult;
    KSALLOCATOR_FRAMING ksaf;
    KSALLOCATOR_FRAMING_EX ksafex;

    PA_LOGE_;

    if( pin == NULL )
        return paInternalError;
    if(!pin->pinConnect)
        return paInternalError;

    FilterUse(pin->parentFilter);

    createResult = FunctionKsCreatePin(
        pin->parentFilter->handle,
        pin->pinConnect,
        GENERIC_WRITE | GENERIC_READ,
        &pin->handle
        );

    PA_DEBUG(("Pin create result = 0x%08x\n",createResult));
    if( createResult != ERROR_SUCCESS )
    {
        FilterRelease(pin->parentFilter);
        pin->handle = NULL;
        switch (createResult)
        {
        case ERROR_INVALID_PARAMETER:
            /* First case when pin actually don't support the format */
            return paSampleFormatNotSupported;
        case ERROR_BAD_COMMAND:
            /* Case when pin is occupied (by another application) */
            return paDeviceUnavailable;
        default:
            /* All other cases */
            return paInvalidDevice;
        }
    }

    if (pin->parentFilter->devInfo.streamingType == Type_kWaveCyclic)
    {
        /* Framing size query only valid for WaveCyclic devices */
        result = WdmGetPropertySimple(
            pin->handle,
            &KSPROPSETID_Connection,
            KSPROPERTY_CONNECTION_ALLOCATORFRAMING,
            &ksaf,
            sizeof(ksaf));

        if( result != paNoError )
        {
            result = WdmGetPropertySimple(
                pin->handle,
                &KSPROPSETID_Connection,
                KSPROPERTY_CONNECTION_ALLOCATORFRAMING_EX,
                &ksafex,
                sizeof(ksafex));
            if( result == paNoError )
            {
                pin->frameSize = ksafex.FramingItem[0].FramingRange.Range.MinFrameSize;
            }
        }
        else
        {
            pin->frameSize = ksaf.FrameSize;
        }
    }

    PA_LOGL_;

    return paNoError;
}

static PaError PinSetFormat(PaWinWdmPin* pin, const WAVEFORMATEX* format)
{
    unsigned long size;
    void* newConnect;

    PA_LOGE_;

    if( pin == NULL )
        return paInternalError;
    if( format == NULL )
        return paInternalError;

    size = GetWfexSize(format) + sizeof(KSPIN_CONNECT) + sizeof(KSDATAFORMAT_WAVEFORMATEX) - sizeof(WAVEFORMATEX);

    if( pin->pinConnectSize != size )
    {
        newConnect = PaUtil_AllocateMemory( size );
        if( newConnect == NULL )
            return paInsufficientMemory;
        memcpy( newConnect, (void*)pin->pinConnect, min(pin->pinConnectSize,size) );
        PaUtil_FreeMemory( pin->pinConnect );
        pin->pinConnect = (KSPIN_CONNECT*)newConnect;
        pin->pinConnectSize = size;
        pin->ksDataFormatWfx = (KSDATAFORMAT_WAVEFORMATEX*)((KSPIN_CONNECT*)newConnect + 1);
        pin->ksDataFormatWfx->DataFormat.FormatSize = size - sizeof(KSPIN_CONNECT);
    }

    memcpy( (void*)&(pin->ksDataFormatWfx->WaveFormatEx), format, GetWfexSize(format) );
    pin->ksDataFormatWfx->DataFormat.SampleSize = (unsigned short)(format->nChannels * (format->wBitsPerSample / 8));

    PA_LOGL_;

    return paNoError;
}

static PaError PinIsFormatSupported(PaWinWdmPin* pin, const WAVEFORMATEX* format)
{
    KSDATARANGE_AUDIO* dataRange;
    unsigned long count;
    GUID guid = DYNAMIC_GUID( DEFINE_WAVEFORMATEX_GUID(format->wFormatTag) );
    PaError result = paInvalidDevice;
    const WAVEFORMATEXTENSIBLE* pFormatExt = (format->wFormatTag == WAVE_FORMAT_EXTENSIBLE) ? (const WAVEFORMATEXTENSIBLE*)format : 0;

    PA_LOGE_;

    if( pFormatExt != 0 )
    {
        guid = pFormatExt->SubFormat;
    }
    dataRange = (KSDATARANGE_AUDIO*)pin->dataRanges;
    for(count = 0;
        count<pin->dataRangesItem->Count;
        count++,
        dataRange = (KSDATARANGE_AUDIO*)( ((char*)dataRange) + dataRange->DataRange.FormatSize)) /* Need to update dataRange here, due to 'continue' !! */
    {
        /* Check major format*/
        if (!(IsEqualGUID(&(dataRange->DataRange.MajorFormat), &KSDATAFORMAT_TYPE_AUDIO) ||
            IsEqualGUID(&(dataRange->DataRange.MajorFormat), &KSDATAFORMAT_TYPE_WILDCARD)))
        {
            continue;
        }

        /* This is an audio or wildcard datarange... */
        if (! (IsEqualGUID(&(dataRange->DataRange.SubFormat), &KSDATAFORMAT_SUBTYPE_WILDCARD) ||
            IsEqualGUID(&(dataRange->DataRange.SubFormat), &KSDATAFORMAT_SUBTYPE_PCM) ||
            IsEqualGUID(&(dataRange->DataRange.SubFormat), &guid) ))
        {
            continue;
        }

        /* Check specifier... */
        if (! (IsEqualGUID(&(dataRange->DataRange.Specifier), &KSDATAFORMAT_SPECIFIER_WILDCARD) ||
            IsEqualGUID(&(dataRange->DataRange.Specifier), &KSDATAFORMAT_SPECIFIER_WAVEFORMATEX)) )
        {
            continue;
        }

        PA_DEBUG(("Pin:%x, DataRange:%d\n",(void*)pin,count));
        PA_DEBUG(("\tFormatSize:%d, SampleSize:%d\n",dataRange->DataRange.FormatSize,dataRange->DataRange.SampleSize));
        PA_DEBUG(("\tMaxChannels:%d\n",dataRange->MaximumChannels));
        PA_DEBUG(("\tBits:%d-%d\n",dataRange->MinimumBitsPerSample,dataRange->MaximumBitsPerSample));
        PA_DEBUG(("\tSampleRate:%d-%d\n",dataRange->MinimumSampleFrequency,dataRange->MaximumSampleFrequency));

        if( dataRange->MaximumChannels != (ULONG)-1 &&
            dataRange->MaximumChannels < format->nChannels )
        {
            result = paInvalidChannelCount;
            continue;
        }

        if (pFormatExt != 0)
        {
            if (!IsBitsWithinRange(dataRange, pFormatExt->Samples.wValidBitsPerSample))
            {
                result = paSampleFormatNotSupported;
                continue;
            }
        }
        else
        {
            if (!IsBitsWithinRange(dataRange, format->wBitsPerSample))
            {
                result = paSampleFormatNotSupported;
                continue;
            }
        }

        if (!IsFrequencyWithinRange(dataRange, format->nSamplesPerSec))
        {
            result = paInvalidSampleRate;
            continue;
        }

        /* Success! */
        result = paNoError;
        break;
    }

    PA_LOGL_;
    return result;
}

static PaError PinQueryNotificationSupport(PaWinWdmPin* pPin, BOOL* pbResult)
{
    PaError result = paNoError;
    KSPROPERTY propIn;

    PA_LOGE_;

    propIn.Set = KSPROPSETID_RtAudio;
    propIn.Id = 8; /* = KSPROPERTY_RTAUDIO_QUERY_NOTIFICATION_SUPPORT */
    propIn.Flags = KSPROPERTY_TYPE_GET;

    result = WdmSyncIoctl(pPin->handle, IOCTL_KS_PROPERTY,
        &propIn,
        sizeof(KSPROPERTY),
        pbResult,
        sizeof(BOOL),
        NULL);

    if (result != paNoError)
    {
        PA_DEBUG(("Failed PinQueryNotificationSupport\n"));
    }

    PA_LOGL_;
    return result;
}

static PaError PinGetBufferWithNotification(PaWinWdmPin* pPin, void** pBuffer, DWORD* pRequestedBufSize, BOOL* pbCallMemBarrier)
{
    PaError result = paNoError;
    KSRTAUDIO_BUFFER_PROPERTY_WITH_NOTIFICATION propIn;
    KSRTAUDIO_BUFFER propOut;

    PA_LOGE_;

    propIn.BaseAddress = 0;
    propIn.NotificationCount = 2;
    propIn.RequestedBufferSize = *pRequestedBufSize;
    propIn.Property.Set = KSPROPSETID_RtAudio;
    propIn.Property.Id = KSPROPERTY_RTAUDIO_BUFFER_WITH_NOTIFICATION;
    propIn.Property.Flags = KSPROPERTY_TYPE_GET;

    result = WdmSyncIoctl(pPin->handle, IOCTL_KS_PROPERTY,
        &propIn,
        sizeof(KSRTAUDIO_BUFFER_PROPERTY_WITH_NOTIFICATION),
        &propOut,
        sizeof(KSRTAUDIO_BUFFER),
        NULL);

    if (result == paNoError)
    {
        *pBuffer = propOut.BufferAddress;
        *pRequestedBufSize = propOut.ActualBufferSize;
        *pbCallMemBarrier = propOut.CallMemoryBarrier;
    }
    else
    {
        PA_DEBUG(("Failed to get buffer with notification\n"));
    }

    PA_LOGL_;
    return result;
}

static PaError PinGetBufferWithoutNotification(PaWinWdmPin* pPin, void** pBuffer, DWORD* pRequestedBufSize, BOOL* pbCallMemBarrier)
{
    PaError result = paNoError;
    KSRTAUDIO_BUFFER_PROPERTY propIn;
    KSRTAUDIO_BUFFER propOut;

    PA_LOGE_;

    propIn.BaseAddress = NULL;
    propIn.RequestedBufferSize = *pRequestedBufSize;
    propIn.Property.Set = KSPROPSETID_RtAudio;
    propIn.Property.Id = KSPROPERTY_RTAUDIO_BUFFER;
    propIn.Property.Flags = KSPROPERTY_TYPE_GET;

    result = WdmSyncIoctl(pPin->handle, IOCTL_KS_PROPERTY,
        &propIn,
        sizeof(KSRTAUDIO_BUFFER_PROPERTY),
        &propOut,
        sizeof(KSRTAUDIO_BUFFER),
        NULL);

    if (result == paNoError)
    {
        *pBuffer = propOut.BufferAddress;
        *pRequestedBufSize = propOut.ActualBufferSize;
        *pbCallMemBarrier = propOut.CallMemoryBarrier;
    }
    else
    {
        PA_DEBUG(("Failed to get buffer without notification\n"));
    }

    PA_LOGL_;
    return result;
}

/* greatest common divisor - PGCD in French */
static unsigned long PaWinWDMGCD( unsigned long a, unsigned long b )
{
    return (b==0) ? a : PaWinWDMGCD( b, a%b);
}


/* This function will handle getting the cyclic buffer from a WaveRT driver. Certain WaveRT drivers needs to have
requested buffer size on multiples of 128 bytes:

*/
static PaError PinGetBuffer(PaWinWdmPin* pPin, void** pBuffer, DWORD* pRequestedBufSize, BOOL* pbCallMemBarrier)
{
    PaError result = paNoError;
    int limit = 1000;
    PA_LOGE_;

    while (1)
    {
        limit--;
        if (limit == 0) {
            PA_DEBUG(("PinGetBuffer: LOOP LIMIT REACHED\n"));
            break;
        }

        if (pPin->pinKsSubType != SubType_kPolled)
        {
            /* In case of unknown (or notification), we try both modes */
            result = PinGetBufferWithNotification(pPin, pBuffer, pRequestedBufSize, pbCallMemBarrier);
            if (result == paNoError)
            {
                PA_DEBUG(("PinGetBuffer: SubType_kNotification\n"));
                pPin->pinKsSubType = SubType_kNotification;
                break;
            }
        }

        result = PinGetBufferWithoutNotification(pPin, pBuffer, pRequestedBufSize, pbCallMemBarrier);
        if (result == paNoError)
        {
            PA_DEBUG(("PinGetBuffer: SubType_kPolled\n"));
            pPin->pinKsSubType = SubType_kPolled;
            break;
        }

        /* Check if requested size is on a 128 byte boundary */
        if (((*pRequestedBufSize) % 128UL) == 0)
        {
            PA_DEBUG(("Buffer size on 128 byte boundary, still fails :(\n"));
            /* Ok, can't do much more */
            break;
        }
        else
        {
            /* Compute LCM so we know which sizes are on a 128 byte boundary */
            const unsigned gcd = PaWinWDMGCD(128UL, pPin->ksDataFormatWfx->WaveFormatEx.nBlockAlign);
            const unsigned lcm = (128UL * pPin->ksDataFormatWfx->WaveFormatEx.nBlockAlign) / gcd;
            DWORD dwOldSize = *pRequestedBufSize;

            /* Align size to (next larger) LCM byte boundary, and then we try again. Note that LCM is not necessarily a
            power of 2. */
            *pRequestedBufSize = ((*pRequestedBufSize + lcm - 1) / lcm) * lcm;

            PA_DEBUG(("Adjusting buffer size from %u to %u bytes (128 byte boundary, LCM=%u)\n", dwOldSize, *pRequestedBufSize, lcm));
        }
    }

    PA_LOGL_;

    return result;
}

static PaError PinRegisterPositionRegister(PaWinWdmPin* pPin)
{
    PaError result = paNoError;
    KSRTAUDIO_HWREGISTER_PROPERTY propIn;
    KSRTAUDIO_HWREGISTER propOut;

    PA_LOGE_;

    propIn.BaseAddress = NULL;
    propIn.Property.Set = KSPROPSETID_RtAudio;
    propIn.Property.Id = KSPROPERTY_RTAUDIO_POSITIONREGISTER;
    propIn.Property.Flags = KSPROPERTY_TYPE_SET;

    result = WdmSyncIoctl(pPin->handle, IOCTL_KS_PROPERTY,
        &propIn,
        sizeof(KSRTAUDIO_HWREGISTER_PROPERTY),
        &propOut,
        sizeof(KSRTAUDIO_HWREGISTER),
        NULL);

    if (result == paNoError)
    {
        pPin->positionRegister = (ULONG*)propOut.Register;
    }
    else
    {
        PA_DEBUG(("Failed to register position register\n"));
    }

    PA_LOGL_;

    return result;
}

static PaError PinRegisterNotificationHandle(PaWinWdmPin* pPin, HANDLE handle)
{
    PaError result = paNoError;
    KSRTAUDIO_NOTIFICATION_EVENT_PROPERTY prop;

    PA_LOGE_;

    prop.NotificationEvent = handle;
    prop.Property.Set = KSPROPSETID_RtAudio;
    prop.Property.Id = KSPROPERTY_RTAUDIO_REGISTER_NOTIFICATION_EVENT;
    prop.Property.Flags = KSPROPERTY_TYPE_SET;

    result = WdmSyncIoctl(pPin->handle,
        IOCTL_KS_PROPERTY,
        &prop,
        sizeof(KSRTAUDIO_NOTIFICATION_EVENT_PROPERTY),
        &prop,
        sizeof(KSRTAUDIO_NOTIFICATION_EVENT_PROPERTY),
        NULL);

    if (result != paNoError) {
        PA_DEBUG(("Failed to register notification handle 0x%08X\n", handle));
    }

    PA_LOGL_;

    return result;
}

static PaError PinUnregisterNotificationHandle(PaWinWdmPin* pPin, HANDLE handle)
{
    PaError result = paNoError;
    KSRTAUDIO_NOTIFICATION_EVENT_PROPERTY prop;

    PA_LOGE_;

    if (handle != NULL)
    {
        prop.NotificationEvent = handle;
        prop.Property.Set = KSPROPSETID_RtAudio;
        prop.Property.Id = KSPROPERTY_RTAUDIO_UNREGISTER_NOTIFICATION_EVENT;
        prop.Property.Flags = KSPROPERTY_TYPE_SET;

        result = WdmSyncIoctl(pPin->handle,
            IOCTL_KS_PROPERTY,
            &prop,
            sizeof(KSRTAUDIO_NOTIFICATION_EVENT_PROPERTY),
            &prop,
            sizeof(KSRTAUDIO_NOTIFICATION_EVENT_PROPERTY),
            NULL);

        if (result != paNoError) {
            PA_DEBUG(("Failed to unregister notification handle 0x%08X\n", handle));
        }
    }
    PA_LOGL_;

    return result;
}

static PaError PinGetHwLatency(PaWinWdmPin* pPin, ULONG* pFifoSize, ULONG* pChipsetDelay, ULONG* pCodecDelay)
{
    PaError result = paNoError;
    KSPROPERTY propIn;
    KSRTAUDIO_HWLATENCY propOut;

    PA_LOGE_;

    propIn.Set = KSPROPSETID_RtAudio;
    propIn.Id = KSPROPERTY_RTAUDIO_HWLATENCY;
    propIn.Flags = KSPROPERTY_TYPE_GET;

    result = WdmSyncIoctl(pPin->handle, IOCTL_KS_PROPERTY,
        &propIn,
        sizeof(KSPROPERTY),
        &propOut,
        sizeof(KSRTAUDIO_HWLATENCY),
        NULL);

    if (result == paNoError)
    {
        *pFifoSize = propOut.FifoSize;
        *pChipsetDelay = propOut.ChipsetDelay;
        *pCodecDelay = propOut.CodecDelay;
    }
    else
    {
        PA_DEBUG(("Failed to retrieve hardware FIFO size!\n"));
    }

    PA_LOGL_;

    return result;
}

/* This one is used for WaveRT */
static PaError PinGetAudioPositionMemoryMapped(PaWinWdmPin* pPin, ULONG* pPosition)
{
    *pPosition = (*pPin->positionRegister);
    return paNoError;
}

/* This one also, but in case the driver hasn't implemented memory mapped access to the position register */
static PaError PinGetAudioPositionViaIOCTLRead(PaWinWdmPin* pPin, ULONG* pPosition)
{
    PaError result = paNoError;
    KSPROPERTY propIn;
    KSAUDIO_POSITION propOut;

    PA_LOGE_;

    propIn.Set = KSPROPSETID_Audio;
    propIn.Id = KSPROPERTY_AUDIO_POSITION;
    propIn.Flags = KSPROPERTY_TYPE_GET;

    result = WdmSyncIoctl(pPin->handle,
        IOCTL_KS_PROPERTY,
        &propIn, sizeof(KSPROPERTY),
        &propOut, sizeof(KSAUDIO_POSITION),
        NULL);

    if (result == paNoError)
    {
        *pPosition = (ULONG)(propOut.PlayOffset);
    }
    else
    {
        PA_DEBUG(("Failed to get audio play position!\n"));
    }

    PA_LOGL_;

    return result;

}

/* This one also, but in case the driver hasn't implemented memory mapped access to the position register */
static PaError PinGetAudioPositionViaIOCTLWrite(PaWinWdmPin* pPin, ULONG* pPosition)
{
    PaError result = paNoError;
    KSPROPERTY propIn;
    KSAUDIO_POSITION propOut;

    PA_LOGE_;

    propIn.Set = KSPROPSETID_Audio;
    propIn.Id = KSPROPERTY_AUDIO_POSITION;
    propIn.Flags = KSPROPERTY_TYPE_GET;

    result = WdmSyncIoctl(pPin->handle,
        IOCTL_KS_PROPERTY,
        &propIn, sizeof(KSPROPERTY),
        &propOut, sizeof(KSAUDIO_POSITION),
        NULL);

    if (result == paNoError)
    {
        *pPosition = (ULONG)(propOut.WriteOffset);
    }
    else
    {
        PA_DEBUG(("Failed to get audio write position!\n"));
    }

    PA_LOGL_;

    return result;

}

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

/**
* Create a new filter object.
*/
static PaWinWdmFilter* FilterNew( PaWDMKSType type, DWORD devNode, const wchar_t* filterName, const wchar_t* friendlyName, PaError* error )
{
    PaWinWdmFilter* filter = 0;
    PaError result;

    /* Allocate the new filter object */
    filter = (PaWinWdmFilter*)PaUtil_AllocateMemory( sizeof(PaWinWdmFilter) );
    if( !filter )
    {
        result = paInsufficientMemory;
        goto error;
    }

    PA_DEBUG(("FilterNew: Creating filter '%S'\n", friendlyName));

    /* Set type flag */
    filter->devInfo.streamingType = type;

    /* Store device node */
    filter->deviceNode = devNode;

    /* Zero the filter object - done by AllocateMemory */
    /* memset( (void*)filter, 0, sizeof(PaWinWdmFilter) ); */

    /* Copy the filter name */
    wcsncpy(filter->devInfo.filterPath, filterName, MAX_PATH);

    /* Copy the friendly name */
    wcsncpy(filter->friendlyName, friendlyName, MAX_PATH);

    PA_DEBUG(("FilterNew: Opening filter...\n", friendlyName));

    /* Open the filter handle */
    result = FilterUse(filter);
    if( result != paNoError )
    {
        goto error;
    }

    /* Get pin count */
    result = WdmGetPinPropertySimple
        (
        filter->handle,
        0,
        &KSPROPSETID_Pin,
        KSPROPERTY_PIN_CTYPES,
        &filter->pinCount,
        sizeof(filter->pinCount),
        NULL);

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

    /* Get connections & nodes for filter */
    result = WdmGetPropertyMulti(
        filter->handle,
        &KSPROPSETID_Topology,
        KSPROPERTY_TOPOLOGY_CONNECTIONS,
        &filter->connections);

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

    result = WdmGetPropertyMulti(
        filter->handle,
        &KSPROPSETID_Topology,
        KSPROPERTY_TOPOLOGY_NODES,
        &filter->nodes);

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

    /* For debugging purposes */
    DumpConnectionsAndNodes(filter);

    /* Get product GUID (it might not be supported) */
    {
        KSCOMPONENTID compId;
        if (WdmGetPropertySimple(filter->handle, &KSPROPSETID_General, KSPROPERTY_GENERAL_COMPONENTID, &compId, sizeof(KSCOMPONENTID)) == paNoError)
        {
            filter->devInfo.deviceProductGuid = compId.Product;
        }
    }

    /* This section is not executed for topology filters */
    if (type != Type_kNotUsed)
    {
        /* Initialize the pins */
        result = FilterInitializePins(filter);

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

    /* Close the filter handle for now
    * It will be opened later when needed */
    FilterRelease(filter);

    *error = paNoError;
    return filter;

error:
    PA_DEBUG(("FilterNew: Error %d\n", result));
    /*
    Error cleanup
    */
    FilterFree(filter);

    *error = result;
    return NULL;
}

/**
* Add reference to filter
*/
static void FilterAddRef( PaWinWdmFilter* filter )
{
    if (filter != 0)
    {
        filter->filterRefCount++;
    }
}


/**
* Initialize the pins of the filter. This is separated from FilterNew because this might fail if there is another
* process using the pin(s).
*/
PaError FilterInitializePins( PaWinWdmFilter* filter )
{
    PaError result = paNoError;
    int pinId;

    if (filter->devInfo.streamingType == Type_kNotUsed)
        return paNoError;

    if (filter->pins != NULL)
        return paNoError;

    /* Allocate pointer array to hold the pins */
    filter->pins = (PaWinWdmPin**)PaUtil_AllocateMemory( sizeof(PaWinWdmPin*) * filter->pinCount );
    if( !filter->pins )
    {
        result = paInsufficientMemory;
        goto error;
    }

    /* Create all the pins we can */
    for(pinId = 0; pinId < filter->pinCount; pinId++)
    {
        /* Create the pin with this Id */
        PaWinWdmPin* newPin;
        newPin = PinNew(filter, pinId, &result);
        if( result == paInsufficientMemory )
            goto error;
        if( newPin != NULL )
        {
            filter->pins[pinId] = newPin;
            ++filter->validPinCount;
        }
        else
        {
            filter->pins[pinId] = 0;
        }
    }

    if (filter->validPinCount == 0)
    {
        result = paDeviceUnavailable;
        goto error;
    }

    return paNoError;

error:

    if (filter->pins)
    {
        for (pinId = 0; pinId < filter->pinCount; ++pinId)
        {
            if (filter->pins[pinId])
            {
                PinFree(filter->pins[pinId]);
                filter->pins[pinId] = 0;
            }
        }
        PaUtil_FreeMemory( filter->pins );
        filter->pins = 0;
    }

    return result;
}


/**
* Free a previously created filter
*/
static void FilterFree(PaWinWdmFilter* filter)
{
    PA_LOGL_;
    if( filter )
    {
        if (--filter->filterRefCount > 0)
        {
            /* Ok, a stream has a ref count to this filter */
            return;
        }

        if ( filter->topologyFilter )
        {
            FilterFree(filter->topologyFilter);
            filter->topologyFilter = 0;
        }
        if ( filter->pins )
        {
            int pinId;
            for( pinId = 0; pinId < filter->pinCount; pinId++ )
                PinFree(filter->pins[pinId]);
            PaUtil_FreeMemory( filter->pins );
            filter->pins = 0;
        }
        if( filter->connections )
        {
            PaUtil_FreeMemory(filter->connections);
            filter->connections = 0;
        }
        if( filter->nodes )
        {
            PaUtil_FreeMemory(filter->nodes);
            filter->nodes = 0;
        }
        if( filter->handle )
            CloseHandle( filter->handle );
        PaUtil_FreeMemory( filter );
    }
    PA_LOGE_;
}

/**
* Reopen the filter handle if necessary so it can be used
**/
static PaError FilterUse(PaWinWdmFilter* filter)
{
    assert( filter );

    PA_LOGE_;
    if( filter->handle == NULL )
    {
        /* Open the filter */
        filter->handle = CreateFileW(
            filter->devInfo.filterPath,
            GENERIC_READ | GENERIC_WRITE,
            0,
            NULL,
            OPEN_EXISTING,
            FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
            NULL);

        if( filter->handle == NULL )
        {
            return paDeviceUnavailable;
        }
    }
    filter->usageCount++;
    PA_LOGL_;
    return paNoError;
}

/**
* Release the filter handle if nobody is using it
**/
static void FilterRelease(PaWinWdmFilter* filter)
{
    assert( filter );
    assert( filter->usageCount > 0 );

    PA_LOGE_;
    /* Check first topology filter, if used */
    if (filter->topologyFilter != NULL && filter->topologyFilter->handle != NULL)
    {
        FilterRelease(filter->topologyFilter);
    }

    filter->usageCount--;
    if( filter->usageCount == 0 )
    {
        if( filter->handle != NULL )
        {
            CloseHandle( filter->handle );
            filter->handle = NULL;
        }
    }
    PA_LOGL_;
}

/**
* Create a render or playback pin using the supplied format
**/
static PaWinWdmPin* FilterCreatePin(PaWinWdmFilter* filter,
                                    int pinId,
                                    const WAVEFORMATEX* wfex,
                                    PaError* error)
{
    PaError result = paNoError;
    PaWinWdmPin* pin = NULL;
    assert( filter );
    assert( pinId < filter->pinCount );
    pin = filter->pins[pinId];
    assert( pin );
    result = PinSetFormat(pin,wfex);
    if( result == paNoError )
    {
        result = PinInstantiate(pin);
    }
    *error = result;
    return result == paNoError ? pin : 0;
}

static const wchar_t kUsbPrefix[] = L"\\\\?\\USB";

static BOOL IsUSBDevice(const wchar_t* devicePath)
{
    /* Alex Lessard pointed out that different devices might present the device path with
       lower case letters. */
    return (_wcsnicmp(devicePath, kUsbPrefix, sizeof(kUsbPrefix)/sizeof(kUsbPrefix[0]) ) == 0);
}

/* This should make it more language tolerant, I hope... */
static const wchar_t kUsbNamePrefix[] = L"USB Audio";

static BOOL IsNameUSBAudioDevice(const wchar_t* friendlyName)
{
    return (_wcsnicmp(friendlyName, kUsbNamePrefix, sizeof(kUsbNamePrefix)/sizeof(kUsbNamePrefix[0])) == 0);
}

typedef enum _tag_EAlias
{
    Alias_kRender   = (1<<0),
    Alias_kCapture  = (1<<1),
    Alias_kRealtime = (1<<2),
} EAlias;

/* Trim whitespace from string */
static void TrimString(wchar_t* str, size_t length)
{
    wchar_t* s = str;
    wchar_t* e = 0;

    /* Find start of string */
    while (iswspace_portable(*s)) ++s;
    e=s+min(length,wcslen(s))-1;

    /* Find end of string */
    while(e>s && iswspace_portable(*e)) --e;
    ++e;

    length = e - s;
    memmove(str, s, length * sizeof(wchar_t));
    str[length] = 0;
}

/**
* Build the list of available filters
* Use the SetupDi API to enumerate all devices in the KSCATEGORY_AUDIO which
* have a KSCATEGORY_RENDER or KSCATEGORY_CAPTURE alias. For each of these
* devices initialise a PaWinWdmFilter structure by calling our NewFilter()
* function. We enumerate devices twice, once to count how many there are,
* and once to initialize the PaWinWdmFilter structures.
*
* Vista and later: Also check KSCATEGORY_REALTIME for WaveRT devices.
*/
//PaError BuildFilterList( PaWinWdmHostApiRepresentation* wdmHostApi, int* noOfPaDevices )
PaWinWdmFilter** BuildFilterList( int* pFilterCount, int* pNoOfPaDevices, PaError* pResult )
{
    PaWinWdmFilter** ppFilters = NULL;
    HDEVINFO handle = NULL;
    int device;
    int invalidDevices;
    int slot;
    SP_DEVICE_INTERFACE_DATA interfaceData;
    SP_DEVICE_INTERFACE_DATA aliasData;
    SP_DEVINFO_DATA devInfoData;
    int noError;
    const int sizeInterface = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA) + (MAX_PATH * sizeof(WCHAR));
    unsigned char interfaceDetailsArray[sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA) + (MAX_PATH * sizeof(WCHAR))];
    SP_DEVICE_INTERFACE_DETAIL_DATA_W* devInterfaceDetails = (SP_DEVICE_INTERFACE_DETAIL_DATA_W*)interfaceDetailsArray;
    const GUID* category = (const GUID*)&KSCATEGORY_AUDIO;
    const GUID* alias_render = (const GUID*)&KSCATEGORY_RENDER;
    const GUID* alias_capture = (const GUID*)&KSCATEGORY_CAPTURE;
    const GUID* category_realtime = (const GUID*)&KSCATEGORY_REALTIME;
    DWORD aliasFlags;
    PaWDMKSType streamingType;
    int filterCount = 0;
    int noOfPaDevices = 0;

    PA_LOGE_;

    assert(pFilterCount != NULL);
    assert(pNoOfPaDevices != NULL);
    assert(pResult != NULL);

    devInterfaceDetails->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_W);
    *pFilterCount = 0;
    *pNoOfPaDevices = 0;

    /* Open a handle to search for devices (filters) */
    handle = SetupDiGetClassDevs(category,NULL,NULL,DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
    if( handle == INVALID_HANDLE_VALUE )
    {
        *pResult = paUnanticipatedHostError;
        return NULL;
    }
    PA_DEBUG(("Setup called\n"));

    /* First let's count the number of devices so we can allocate a list */
    invalidDevices = 0;
    for( device = 0;;device++ )
    {
        interfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
        interfaceData.Reserved = 0;
        aliasData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
        aliasData.Reserved = 0;
        noError = SetupDiEnumDeviceInterfaces(handle,NULL,category,device,&interfaceData);
        PA_DEBUG(("Enum called\n"));
        if( !noError )
            break; /* No more devices */

        /* Check this one has the render or capture alias */
        aliasFlags = 0;
        noError = SetupDiGetDeviceInterfaceAlias(handle,&interfaceData,alias_render,&aliasData);
        PA_DEBUG(("noError = %d\n",noError));
        if(noError)
        {
            if(aliasData.Flags && (!(aliasData.Flags & SPINT_REMOVED)))
            {
                PA_DEBUG(("Device %d has render alias\n",device));
                aliasFlags |= Alias_kRender; /* Has render alias */
            }
            else
            {
                PA_DEBUG(("Device %d has no render alias\n",device));
            }
        }
        noError = SetupDiGetDeviceInterfaceAlias(handle,&interfaceData,alias_capture,&aliasData);
        if(noError)
        {
            if(aliasData.Flags && (!(aliasData.Flags & SPINT_REMOVED)))
            {
                PA_DEBUG(("Device %d has capture alias\n",device));
                aliasFlags |= Alias_kCapture; /* Has capture alias */
            }
            else
            {
                PA_DEBUG(("Device %d has no capture alias\n",device));
            }
        }
        if(!aliasFlags)
            invalidDevices++; /* This was not a valid capture or render audio device */
    }
    /* Remember how many there are */
    filterCount = device-invalidDevices;

    PA_DEBUG(("Interfaces found: %d\n",device-invalidDevices));

    /* Now allocate the list of pointers to devices */
    ppFilters  = (PaWinWdmFilter**)PaUtil_AllocateMemory( sizeof(PaWinWdmFilter*) * filterCount);
    if( ppFilters == 0 )
    {
        if(handle != NULL)
            SetupDiDestroyDeviceInfoList(handle);
        *pResult = paInsufficientMemory;
        return NULL;
    }

    /* Now create filter objects for each interface found */
    slot = 0;
    for( device = 0;;device++ )
    {
        interfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
        interfaceData.Reserved = 0;
        aliasData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
        aliasData.Reserved = 0;
        devInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
        devInfoData.Reserved = 0;
        streamingType = Type_kWaveCyclic;

        noError = SetupDiEnumDeviceInterfaces(handle,NULL,category,device,&interfaceData);
        if( !noError )
            break; /* No more devices */

        /* Check this one has the render or capture alias */
        aliasFlags = 0;
        noError = SetupDiGetDeviceInterfaceAlias(handle,&interfaceData,alias_render,&aliasData);
        if(noError)
        {
            if(aliasData.Flags && (!(aliasData.Flags & SPINT_REMOVED)))
            {
                PA_DEBUG(("Device %d has render alias\n",device));
                aliasFlags |= Alias_kRender; /* Has render alias */
            }
        }
        noError = SetupDiGetDeviceInterfaceAlias(handle,&interfaceData,alias_capture,&aliasData);
        if(noError)
        {
            if(aliasData.Flags && (!(aliasData.Flags & SPINT_REMOVED)))
            {
                PA_DEBUG(("Device %d has capture alias\n",device));
                aliasFlags |= Alias_kCapture; /* Has capture alias */
            }
        }
        if(!aliasFlags)
        {
            continue; /* This was not a valid capture or render audio device */
        }
        else
        {
            /* Check if filter is WaveRT, if not it is a WaveCyclic */
            noError = SetupDiGetDeviceInterfaceAlias(handle,&interfaceData,category_realtime,&aliasData);
            if (noError)
            {
                PA_DEBUG(("Device %d has realtime alias\n",device));
                aliasFlags |= Alias_kRealtime;
                streamingType = Type_kWaveRT;
            }
        }

        noError = SetupDiGetDeviceInterfaceDetailW(handle,&interfaceData,devInterfaceDetails,sizeInterface,NULL,&devInfoData);
        if( noError )
        {
            DWORD type;
            WCHAR friendlyName[MAX_PATH] = {0};
            DWORD sizeFriendlyName;
            PaWinWdmFilter* newFilter = 0;

            PaError result = paNoError;
            /* Try to get the "friendly name" for this interface */
            sizeFriendlyName = sizeof(friendlyName);

            if (IsEarlierThanVista() && IsUSBDevice(devInterfaceDetails->DevicePath))
            {
                /* XP and USB audio device needs to look elsewhere, otherwise it'll only be a "USB Audio Device". Not
                very literate. */
                if (!SetupDiGetDeviceRegistryPropertyW(handle,
                    &devInfoData,
                    SPDRP_LOCATION_INFORMATION,
                    &type,
                    (BYTE*)friendlyName,
                    sizeof(friendlyName),
                    NULL))
                {
                    friendlyName[0] = 0;
                }
            }

            if (friendlyName[0] == 0 || IsNameUSBAudioDevice(friendlyName))
            {
                /* Fix contributed by Ben Allison
                * Removed KEY_SET_VALUE from flags on following call
                * as its causes failure when running without admin rights
                * and it was not required */
                HKEY hkey=SetupDiOpenDeviceInterfaceRegKey(handle,&interfaceData,0,KEY_QUERY_VALUE);
                if(hkey!=INVALID_HANDLE_VALUE)
                {
                    noError = RegQueryValueExW(hkey,L"FriendlyName",0,&type,(BYTE*)friendlyName,&sizeFriendlyName);
                    if( noError == ERROR_SUCCESS )
                    {
                        PA_DEBUG(("Interface %d, Name: %s\n",device,friendlyName));
                        RegCloseKey(hkey);
                    }
                    else
                    {
                        friendlyName[0] = 0;
                    }
                }
            }

            TrimString(friendlyName, sizeFriendlyName);

            newFilter = FilterNew(streamingType,
                devInfoData.DevInst,
                devInterfaceDetails->DevicePath,
                friendlyName,
                &result);

            if( result == paNoError )
            {
                int pin;
                unsigned filterIOs = 0;

                /* Increment number of "devices" */
                for (pin = 0; pin < newFilter->pinCount; ++pin)
                {
                    PaWinWdmPin* pPin = newFilter->pins[pin];
                    if (pPin == NULL)
                        continue;

                    filterIOs += max(1, pPin->inputCount);
                }

                noOfPaDevices += filterIOs;

                PA_DEBUG(("Filter (%s) created with %d valid pins (total I/Os: %u)\n", ((newFilter->devInfo.streamingType==Type_kWaveRT)?"WaveRT":"WaveCyclic"), newFilter->validPinCount, filterIOs));

                assert(slot < filterCount);

                ppFilters[slot] = newFilter;

                slot++;
            }
            else
            {
                PA_DEBUG(("Filter NOT created\n"));
                /* As there are now less filters than we initially thought
                * we must reduce the count by one */
                filterCount--;
            }
        }
    }

    /* Clean up */
    if(handle != NULL)
        SetupDiDestroyDeviceInfoList(handle);

    *pFilterCount = filterCount;
    *pNoOfPaDevices = noOfPaDevices;

    return ppFilters;
}

typedef struct PaNameHashIndex
{
    unsigned index;
    unsigned count;
    ULONG    hash;
    struct PaNameHashIndex *next;
} PaNameHashIndex;

typedef struct PaNameHashObject
{
    PaNameHashIndex* list;
    PaUtilAllocationGroup* allocGroup;
} PaNameHashObject;

static ULONG GetNameHash(const wchar_t* str, const BOOL input)
{
    /* This is to make sure that a name that exists as both input & output won't get the same hash value */
    const ULONG fnv_prime = (input ? 0x811C9DD7 : 0x811FEB0B);
    ULONG hash = 0;
    for(; *str != 0; str++)
    {
        hash *= fnv_prime;
        hash ^= (*str);
    }
    assert(hash != 0);
    return hash;
}

static PaError CreateHashEntry(PaNameHashObject* obj, const wchar_t* name, const BOOL input)
{
    ULONG hash = GetNameHash(name, input);
    PaNameHashIndex * pLast = NULL;
    PaNameHashIndex * p = obj->list;
    while (p != 0)
    {
        if (p->hash == hash)
        {
            break;
        }
        pLast = p;
        p = p->next;
    }
    if (p == NULL)
    {
        p = (PaNameHashIndex*)PaUtil_GroupAllocateMemory(obj->allocGroup, sizeof(PaNameHashIndex));
        if (p == NULL)
        {
            return paInsufficientMemory;
        }
        p->hash = hash;
        p->count = 1;
        if (pLast != 0)
        {
            assert(pLast->next == 0);
            pLast->next = p;
        }
        if (obj->list == 0)
        {
            obj->list = p;
        }
    }
    else
    {
        ++p->count;
    }
    return paNoError;
}

static PaError InitNameHashObject(PaNameHashObject* obj, PaWinWdmFilter* pFilter)
{
    int i;

    obj->allocGroup = PaUtil_CreateAllocationGroup();
    if (obj->allocGroup == NULL)
    {
        return paInsufficientMemory;
    }

    for (i = 0; i < pFilter->pinCount; ++i)
    {
        unsigned m;
        PaWinWdmPin* pin = pFilter->pins[i];

        if (pin == NULL)
            continue;

        for (m = 0; m < max(1, pin->inputCount); ++m)
        {
            const BOOL isInput = (pin->dataFlow == KSPIN_DATAFLOW_OUT);
            const wchar_t* name = (pin->inputs == NULL) ? pin->friendlyName : pin->inputs[m]->friendlyName;

            PaError result = CreateHashEntry(obj, name, isInput);

            if (result != paNoError)
            {
                return result;
            }
        }
    }
    return paNoError;
}

static void DeinitNameHashObject(PaNameHashObject* obj)
{
    assert(obj != 0);
    PaUtil_FreeAllAllocations(obj->allocGroup);
    PaUtil_DestroyAllocationGroup(obj->allocGroup);
    memset(obj, 0, sizeof(PaNameHashObject));
}

static unsigned GetNameIndex(PaNameHashObject* obj, const wchar_t* name, const BOOL input)
{
    ULONG hash = GetNameHash(name, input);
    PaNameHashIndex* p = obj->list;
    while (p != NULL)
    {
        if (p->hash == hash)
        {
            if (p->count > 1)
            {
                return (++p->index);
            }
            else
            {
                return 0;
            }
        }

        p = p->next;
    }
    // Should never get here!!
    assert(FALSE);
    return 0;
}

static PaError ScanDeviceInfos( struct PaUtilHostApiRepresentation *hostApi, PaHostApiIndex hostApiIndex, void **scanResults, int *newDeviceCount )
{
    PaWinWdmHostApiRepresentation *wdmHostApi = (PaWinWdmHostApiRepresentation*)hostApi;
    PaError result = paNoError;
    PaWinWdmFilter** ppFilters = 0;
    PaWinWDMScanDeviceInfosResults *outArgument = 0;
    int filterCount = 0;
    int totalDeviceCount = 0;
    int idxDevice = 0;
    DWORD defaultInDevPathSize = 0;
    DWORD defaultOutDevPathSize = 0;
    wchar_t* defaultInDevPath = 0;
    wchar_t* defaultOutDevPath = 0;

    ppFilters = BuildFilterList( &filterCount, &totalDeviceCount, &result );
    if( result != paNoError )
    {
        goto error;
    }

    // Get hold of default device paths for capture & playback
    if( waveInMessage(0, DRV_QUERYDEVICEINTERFACESIZE, (DWORD_PTR)&defaultInDevPathSize, 0 ) == MMSYSERR_NOERROR )
    {
        defaultInDevPath = (wchar_t *)PaUtil_AllocateMemory((defaultInDevPathSize + 1) * sizeof(wchar_t));
        waveInMessage(0, DRV_QUERYDEVICEINTERFACE, (DWORD_PTR)defaultInDevPath, defaultInDevPathSize);
    }
    if( waveOutMessage(0, DRV_QUERYDEVICEINTERFACESIZE, (DWORD_PTR)&defaultOutDevPathSize, 0 ) == MMSYSERR_NOERROR )
    {
        defaultOutDevPath = (wchar_t *)PaUtil_AllocateMemory((defaultOutDevPathSize + 1) * sizeof(wchar_t));
        waveOutMessage(0, DRV_QUERYDEVICEINTERFACE, (DWORD_PTR)defaultOutDevPath, defaultOutDevPathSize);
    }

    if( totalDeviceCount > 0 )
    {
        PaWinWdmDeviceInfo *deviceInfoArray = 0;
        int idxFilter;
        int i;
        unsigned devIsDefaultIn = 0, devIsDefaultOut = 0;

        /* Allocate the out param for all the info we need */
        outArgument = (PaWinWDMScanDeviceInfosResults *) PaUtil_GroupAllocateMemory(
            wdmHostApi->allocations, sizeof(PaWinWDMScanDeviceInfosResults) );
        if( !outArgument )
        {
            result = paInsufficientMemory;
            goto error;
        }

        outArgument->defaultInputDevice  = paNoDevice;
        outArgument->defaultOutputDevice = paNoDevice;

        outArgument->deviceInfos = (PaDeviceInfo**)PaUtil_GroupAllocateMemory(
            wdmHostApi->allocations, sizeof(PaDeviceInfo*) * totalDeviceCount );
        if( !outArgument->deviceInfos )
        {
            result = paInsufficientMemory;
            goto error;
        }

        /* allocate all device info structs in a contiguous block */
        deviceInfoArray = (PaWinWdmDeviceInfo*)PaUtil_GroupAllocateMemory(
            wdmHostApi->allocations, sizeof(PaWinWdmDeviceInfo) * totalDeviceCount );
        if( !deviceInfoArray )
        {
            result = paInsufficientMemory;
            goto error;
        }

        /* Make sure all items in array */
        for( i = 0 ; i < totalDeviceCount; ++i )
        {
            PaDeviceInfo *deviceInfo  = &deviceInfoArray[i].inheritedDeviceInfo;
            deviceInfo->structVersion = 2;
            deviceInfo->hostApi       = hostApiIndex;
            deviceInfo->name          = 0;
            outArgument->deviceInfos[ i ] = deviceInfo;
        }

        idxDevice = 0;
        for (idxFilter = 0; idxFilter < filterCount; ++idxFilter)
        {
            PaNameHashObject nameHash = {0};
            PaWinWdmFilter* pFilter = ppFilters[idxFilter];
            if( pFilter == NULL )
                continue;

            if (InitNameHashObject(&nameHash, pFilter) != paNoError)
            {
                DeinitNameHashObject(&nameHash);
                continue;
            }

            devIsDefaultIn = (defaultInDevPath && (_wcsicmp(pFilter->devInfo.filterPath, defaultInDevPath) == 0));
            devIsDefaultOut = (defaultOutDevPath && (_wcsicmp(pFilter->devInfo.filterPath, defaultOutDevPath) == 0));

            for (i = 0; i < pFilter->pinCount; ++i)
            {
                unsigned m;
                ULONG nameIndex = 0;
                ULONG nameIndexHash = 0;
                PaWinWdmPin* pin = pFilter->pins[i];

                if (pin == NULL)
                    continue;

                for (m = 0; m < max(1, pin->inputCount); ++m)
                {
                    PaWinWdmDeviceInfo *wdmDeviceInfo = (PaWinWdmDeviceInfo *)outArgument->deviceInfos[idxDevice];
                    PaDeviceInfo *deviceInfo = &wdmDeviceInfo->inheritedDeviceInfo;
                    wchar_t localCompositeName[MAX_PATH];
                    unsigned nameIndex = 0;
                    const BOOL isInput = (pin->dataFlow == KSPIN_DATAFLOW_OUT);

                    wdmDeviceInfo->filter = pFilter;

                    deviceInfo->structVersion = 2;
                    deviceInfo->hostApi = hostApiIndex;
                    deviceInfo->name = wdmDeviceInfo->compositeName;
                    /* deviceInfo->hostApiSpecificDeviceInfo = &pFilter->devInfo; */

                    wdmDeviceInfo->pin = pin->pinId;

                    /* Get the name of the "device" */
                    if (pin->inputs == NULL)
                    {
                        wcsncpy(localCompositeName, pin->friendlyName, MAX_PATH);
                        wdmDeviceInfo->muxPosition = -1;
                        wdmDeviceInfo->endpointPinId = pin->endpointPinId;
                    }
                    else
                    {
                        PaWinWdmMuxedInput* input = pin->inputs[m];
                        wcsncpy(localCompositeName, input->friendlyName, MAX_PATH);
                        wdmDeviceInfo->muxPosition = (int)m;
                        wdmDeviceInfo->endpointPinId = input->endpointPinId;
                    }

                    {
                        /* Get base length */
                        size_t n = wcslen(localCompositeName);

                        /* Check if there are more entries with same name (which might very well be the case), if there
                        are, the name will be postfixed with an index. */
                        nameIndex = GetNameIndex(&nameHash, localCompositeName, isInput);
                        if (nameIndex > 0)
                        {
                            /* This name has multiple instances, so we post fix with a number */
                            n += _snwprintf(localCompositeName + n, MAX_PATH - n, L" %u", nameIndex);
                        }
                        /* Postfix with filter name */
                        _snwprintf(localCompositeName + n, MAX_PATH - n, L" (%s)", pFilter->friendlyName);
                    }

                    /* Convert wide char string to utf-8 */
                    WideCharToMultiByte(CP_UTF8, 0, localCompositeName, -1, wdmDeviceInfo->compositeName, MAX_PATH, NULL, NULL);

                    /* NB! WDM/KS has no concept of a full-duplex device, each pin is either an input or an output */
                    if (isInput)
                    {
                        /* INPUT ! */
                        deviceInfo->maxInputChannels  = pin->maxChannels;
                        deviceInfo->maxOutputChannels = 0;

                        /* RoBi NB: Due to the fact that input audio endpoints in Vista (& later OSs) can be the same device, but with
                           different input mux settings, there might be a discrepancy between the default input device chosen, and
                           that which will be used by Portaudio. Not much to do about that unfortunately.
                        */
                        if ((defaultInDevPath == 0 || devIsDefaultIn) &&
                             outArgument->defaultInputDevice == paNoDevice)
                        {
                            outArgument->defaultInputDevice = idxDevice;
                        }
                    }
                    else
                    {
                        /* OUTPUT ! */
                        deviceInfo->maxInputChannels  = 0;
                        deviceInfo->maxOutputChannels = pin->maxChannels;

                        if ((defaultOutDevPath == 0 || devIsDefaultOut) &&
                            outArgument->defaultOutputDevice == paNoDevice)
                        {
                            outArgument->defaultOutputDevice = idxDevice;
                        }
                    }

                    /* These low values are not very useful because
                    * a) The lowest latency we end up with can depend on many factors such
                    *    as the device buffer sizes/granularities, sample rate, channels and format
                    * b) We cannot know the device buffer sizes until we try to open/use it at
                    *    a particular setting
                    * So: we give 512x48000Hz frames as the default low input latency
                    **/
                    switch (pFilter->devInfo.streamingType)
                    {
                    case Type_kWaveCyclic:
                        if (IsEarlierThanVista())
                        {
                            /* XP doesn't tolerate low latency, unless the Process Priority Class is set to REALTIME_PRIORITY_CLASS
                            through SetPriorityClass, then 10 ms is quite feasible. However, one should then bear in mind that ALL of
                            the process is running in REALTIME_PRIORITY_CLASS, which might not be appropriate for an application with
                            a GUI . In this case it is advisable to separate the audio engine in another process and use IPC to communicate
                            with it. */
                            deviceInfo->defaultLowInputLatency = 0.02;
                            deviceInfo->defaultLowOutputLatency = 0.02;
                        }
                        else
                        {
                            /* This is a conservative estimate. Most WaveCyclic drivers will limit the available latency, but f.i. my Edirol
                            PCR-A30 can reach 3 ms latency easily... */
                            deviceInfo->defaultLowInputLatency = 0.01;
                            deviceInfo->defaultLowOutputLatency = 0.01;
                        }
                        deviceInfo->defaultHighInputLatency = (4096.0/48000.0);
                        deviceInfo->defaultHighOutputLatency = (4096.0/48000.0);
                        deviceInfo->defaultSampleRate = (double)(pin->defaultSampleRate);
                        break;
                    case Type_kWaveRT:
                        /* This is also a conservative estimate, based on WaveRT polled mode. In polled mode, the latency will be dictated
                        by the buffer size given by the driver. */
                        deviceInfo->defaultLowInputLatency = 0.01;
                        deviceInfo->defaultLowOutputLatency = 0.01;
                        deviceInfo->defaultHighInputLatency = 0.04;
                        deviceInfo->defaultHighOutputLatency = 0.04;
                        deviceInfo->defaultSampleRate = (double)(pin->defaultSampleRate);
                        break;
                    default:
                        assert(0);
                        break;
                    }

                    /* Add reference to filter */
                    FilterAddRef(wdmDeviceInfo->filter);

                    assert(idxDevice < totalDeviceCount);
                    ++idxDevice;
                }
            }

            /* If no one has add ref'd the filter, drop it */
            if (pFilter->filterRefCount == 0)
            {
                FilterFree(pFilter);
            }

            /* Deinitialize name hash object */
            DeinitNameHashObject(&nameHash);
        }
    }

    *scanResults = outArgument;
    *newDeviceCount = idxDevice;
    return result;

error:
    result = DisposeDeviceInfos(hostApi, outArgument, totalDeviceCount);

    return result;
}

static PaError CommitDeviceInfos( struct PaUtilHostApiRepresentation *hostApi, PaHostApiIndex index, void *scanResults, int deviceCount )
{
    PaWinWdmHostApiRepresentation *wdmHostApi = (PaWinWdmHostApiRepresentation*)hostApi;

    hostApi->info.deviceCount = 0;
    hostApi->info.defaultInputDevice = paNoDevice;
    hostApi->info.defaultOutputDevice = paNoDevice;

    /* Free any old memory which might be in the device info */
    if( hostApi->deviceInfos )
    {
        PaWinWDMScanDeviceInfosResults* localScanResults = (PaWinWDMScanDeviceInfosResults*)PaUtil_GroupAllocateMemory(
            wdmHostApi->allocations, sizeof(PaWinWDMScanDeviceInfosResults));
        localScanResults->deviceInfos = hostApi->deviceInfos;

        DisposeDeviceInfos(hostApi, &localScanResults, hostApi->info.deviceCount);

        hostApi->deviceInfos = NULL;
    }

    if( scanResults != NULL )
    {
        PaWinWDMScanDeviceInfosResults *scanDeviceInfosResults = ( PaWinWDMScanDeviceInfosResults * ) scanResults;

        if( deviceCount > 0 )
        {
            /* use the array allocated in ScanDeviceInfos() as our deviceInfos */
            hostApi->deviceInfos = scanDeviceInfosResults->deviceInfos;

            hostApi->info.defaultInputDevice = scanDeviceInfosResults->defaultInputDevice;
            hostApi->info.defaultOutputDevice = scanDeviceInfosResults->defaultOutputDevice;

            hostApi->info.deviceCount = deviceCount;
        }

        PaUtil_GroupFreeMemory( wdmHostApi->allocations, scanDeviceInfosResults );
    }

    return paNoError;

}

static PaError DisposeDeviceInfos( struct PaUtilHostApiRepresentation *hostApi, void *scanResults, int deviceCount )
{
    PaWinWdmHostApiRepresentation *winDsHostApi = (PaWinWdmHostApiRepresentation*)hostApi;

    if( scanResults != NULL )
    {
        PaWinWDMScanDeviceInfosResults *scanDeviceInfosResults = ( PaWinWDMScanDeviceInfosResults * ) scanResults;

        if( scanDeviceInfosResults->deviceInfos )
        {
            int i;
            for (i = 0; i < deviceCount; ++i)
            {
                PaWinWdmDeviceInfo* pDevice = (PaWinWdmDeviceInfo*)scanDeviceInfosResults->deviceInfos[i];
                if (pDevice->filter != 0)
                {
                    FilterFree(pDevice->filter);
                }
            }

            PaUtil_GroupFreeMemory( winDsHostApi->allocations, scanDeviceInfosResults->deviceInfos[0] ); /* all device info structs are allocated in a block so we can destroy them here */
            PaUtil_GroupFreeMemory( winDsHostApi->allocations, scanDeviceInfosResults->deviceInfos );
        }

        PaUtil_GroupFreeMemory( winDsHostApi->allocations, scanDeviceInfosResults );
    }

    return paNoError;

}

PaError PaWinWdm_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex hostApiIndex )
{
    PaError result = paNoError;
    int deviceCount = 0;
    void *scanResults = 0;
    PaWinWdmHostApiRepresentation *wdmHostApi = NULL;

    PA_LOGE_;

#ifdef PA_WDMKS_SET_TREF
    tRef = PaUtil_GetTime();
#endif

    /*
    Attempt to load the KSUSER.DLL without which we cannot create pins
    We will unload this on termination
    */
    if(DllKsUser == NULL)
    {
        DllKsUser = LoadLibrary(TEXT("ksuser.dll"));
        if(DllKsUser == NULL)
            goto error;
    }
    FunctionKsCreatePin = (KSCREATEPIN*)GetProcAddress(DllKsUser, "KsCreatePin");
    if(FunctionKsCreatePin == NULL)
        goto error;

    /* Attempt to load AVRT.DLL, if we can't, then we'll just use time critical prio instead... */
    if(paWinWDMKSAvRtEntryPoints.hInstance == NULL)
    {
        paWinWDMKSAvRtEntryPoints.hInstance = LoadLibrary(TEXT("avrt.dll"));
        if (paWinWDMKSAvRtEntryPoints.hInstance != NULL)
        {
            paWinWDMKSAvRtEntryPoints.AvSetMmThreadCharacteristics =
                (HANDLE(WINAPI*)(LPCSTR,LPDWORD))GetProcAddress(paWinWDMKSAvRtEntryPoints.hInstance,"AvSetMmThreadCharacteristicsA");
            paWinWDMKSAvRtEntryPoints.AvRevertMmThreadCharacteristics =
                (BOOL(WINAPI*)(HANDLE))GetProcAddress(paWinWDMKSAvRtEntryPoints.hInstance, "AvRevertMmThreadCharacteristics");
            paWinWDMKSAvRtEntryPoints.AvSetMmThreadPriority =
                (BOOL(WINAPI*)(HANDLE,PA_AVRT_PRIORITY))GetProcAddress(paWinWDMKSAvRtEntryPoints.hInstance, "AvSetMmThreadPriority");
        }
    }

    wdmHostApi = (PaWinWdmHostApiRepresentation*)PaUtil_AllocateMemory( sizeof(PaWinWdmHostApiRepresentation) );
    if( !wdmHostApi )
    {
        result = paInsufficientMemory;
        goto error;
    }

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

    *hostApi = &wdmHostApi->inheritedHostApiRep;
    (*hostApi)->info.structVersion = 1;
    (*hostApi)->info.type = paWDMKS;
    (*hostApi)->info.name = "Windows WDM-KS";

    /* these are all updated by CommitDeviceInfos() */
    (*hostApi)->info.deviceCount = 0;
    (*hostApi)->info.defaultInputDevice = paNoDevice;
    (*hostApi)->info.defaultOutputDevice = paNoDevice;
    (*hostApi)->deviceInfos = 0;

    result = ScanDeviceInfos(&wdmHostApi->inheritedHostApiRep, hostApiIndex, &scanResults, &deviceCount);
    if (result != paNoError)
    {
        goto error;
    }

    CommitDeviceInfos(&wdmHostApi->inheritedHostApiRep, hostApiIndex, scanResults, deviceCount);

    (*hostApi)->Terminate = Terminate;
    (*hostApi)->OpenStream = OpenStream;
    (*hostApi)->IsFormatSupported = IsFormatSupported;
    /* In preparation for hotplug
    (*hostApi)->ScanDeviceInfos = ScanDeviceInfos;
    (*hostApi)->CommitDeviceInfos = CommitDeviceInfos;
    (*hostApi)->DisposeDeviceInfos = DisposeDeviceInfos;
    */
    PaUtil_InitializeStreamInterface( &wdmHostApi->callbackStreamInterface, CloseStream, StartStream,
        StopStream, AbortStream, IsStreamStopped, IsStreamActive,
        GetStreamTime, GetStreamCpuLoad,
        PaUtil_DummyRead, PaUtil_DummyWrite,
        PaUtil_DummyGetReadAvailable, PaUtil_DummyGetWriteAvailable );

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

    PA_LOGL_;
    return result;

error:
    Terminate( (PaUtilHostApiRepresentation*)wdmHostApi );

    PA_LOGL_;
    return result;
}


static void Terminate( struct PaUtilHostApiRepresentation *hostApi )
{
    PaWinWdmHostApiRepresentation *wdmHostApi = (PaWinWdmHostApiRepresentation*)hostApi;
    PA_LOGE_;

    /* Do not unload the libraries */
    if( DllKsUser != NULL )
    {
        FreeLibrary( DllKsUser );
        DllKsUser = NULL;
    }

    if( paWinWDMKSAvRtEntryPoints.hInstance != NULL )
    {
        FreeLibrary( paWinWDMKSAvRtEntryPoints.hInstance );
        paWinWDMKSAvRtEntryPoints.hInstance = NULL;
    }

    if( wdmHostApi)
    {
        PaWinWDMScanDeviceInfosResults* localScanResults = (PaWinWDMScanDeviceInfosResults*)PaUtil_GroupAllocateMemory(
            wdmHostApi->allocations, sizeof(PaWinWDMScanDeviceInfosResults));
        localScanResults->deviceInfos = hostApi->deviceInfos;
        DisposeDeviceInfos(hostApi, localScanResults, hostApi->info.deviceCount);

        if( wdmHostApi->allocations )
        {
            PaUtil_FreeAllAllocations( wdmHostApi->allocations );
            PaUtil_DestroyAllocationGroup( wdmHostApi->allocations );
        }
        PaUtil_FreeMemory( wdmHostApi );
    }
    PA_LOGL_;
}

static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi,
                                 const PaStreamParameters *inputParameters,
                                 const PaStreamParameters *outputParameters,
                                 double sampleRate )
{
    int inputChannelCount, outputChannelCount;
    PaSampleFormat inputSampleFormat, outputSampleFormat;
    PaWinWdmHostApiRepresentation *wdmHostApi = (PaWinWdmHostApiRepresentation*)hostApi;
    PaWinWdmFilter* pFilter;
    int result = paFormatIsSupported;
    WAVEFORMATEXTENSIBLE wfx;
    PaWinWaveFormatChannelMask channelMask;

    PA_LOGE_;

    if( inputParameters )
    {
        PaWinWdmDeviceInfo* pDeviceInfo = (PaWinWdmDeviceInfo*)wdmHostApi->inheritedHostApiRep.deviceInfos[inputParameters->device];
        PaWinWdmPin* pin;
        unsigned fmt;
        unsigned long testFormat = 0;
        unsigned validBits = 0;

        inputChannelCount = inputParameters->channelCount;
        inputSampleFormat = inputParameters->sampleFormat;

        /* all standard sample formats are supported by the buffer adapter,
        this implementation doesn't support any custom sample formats */
        if( inputSampleFormat & paCustomFormat )
        {
            PaWinWDM_SetLastErrorInfo(paSampleFormatNotSupported, "IsFormatSupported: Custom input format not supported");
            return paSampleFormatNotSupported;
        }

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

        if( inputParameters->device == paUseHostApiSpecificDeviceSpecification )
        {
            PaWinWDM_SetLastErrorInfo(paInvalidDevice, "IsFormatSupported: paUseHostApiSpecificDeviceSpecification not supported");
            return paInvalidDevice;
        }

        /* check that input device can support inputChannelCount */
        if( inputChannelCount > hostApi->deviceInfos[ inputParameters->device ]->maxInputChannels )
        {
            PaWinWDM_SetLastErrorInfo(paInvalidChannelCount, "IsFormatSupported: Invalid input channel count");
            return paInvalidChannelCount;
        }

        /* validate inputStreamInfo */
        if( inputParameters->hostApiSpecificStreamInfo )
        {
            PaWinWDM_SetLastErrorInfo(paIncompatibleHostApiSpecificStreamInfo, "Host API stream info not supported");
            return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */
        }

        pFilter = pDeviceInfo->filter;
        pin = pFilter->pins[pDeviceInfo->pin];

        /* Find out the testing format */
        for (fmt = paFloat32; fmt <= paUInt8; fmt <<= 1)
        {
            if ((fmt & pin->formats) != 0)
            {
                /* Found a matching format! */
                testFormat = fmt;
                break;
            }
        }
        if (testFormat == 0)
        {
            PaWinWDM_SetLastErrorInfo(result, "IsFormatSupported(capture) failed: no testformat found!");
            return paUnanticipatedHostError;
        }

        /* Due to special considerations, WaveRT devices with paInt24 should be tested with paInt32 and
        valid bits = 24 (instead of 24 bit samples) */
        if (pFilter->devInfo.streamingType == Type_kWaveRT && testFormat == paInt24)
        {
            PA_DEBUG(("IsFormatSupported (capture): WaveRT overriding testFormat paInt24 with paInt32 (24 valid bits)"));
            testFormat = paInt32;
            validBits = 24;
        }

        /* Check that the input format is supported */
        channelMask = PaWin_DefaultChannelMask(inputChannelCount);
        PaWin_InitializeWaveFormatExtensible((PaWinWaveFormat*)&wfx,
            inputChannelCount,
            testFormat,
            PaWin_SampleFormatToLinearWaveFormatTag(testFormat),
            sampleRate,
            channelMask );
        if (validBits != 0)
        {
            wfx.Samples.wValidBitsPerSample = validBits;
        }

        result = PinIsFormatSupported(pin, (const WAVEFORMATEX*)&wfx);
        if( result != paNoError )
        {
            /* Try a WAVE_FORMAT_PCM instead */
            PaWin_InitializeWaveFormatEx((PaWinWaveFormat*)&wfx,
                inputChannelCount,
                testFormat,
                PaWin_SampleFormatToLinearWaveFormatTag(testFormat),
                sampleRate);

            if (validBits != 0)
            {
                wfx.Samples.wValidBitsPerSample = validBits;
            }

            result = PinIsFormatSupported(pin, (const WAVEFORMATEX*)&wfx);
            if( result != paNoError )
            {
                PaWinWDM_SetLastErrorInfo(result, "IsFormatSupported(capture) failed: sr=%u,ch=%u,bits=%u", wfx.Format.nSamplesPerSec, wfx.Format.nChannels, wfx.Format.wBitsPerSample);
                return result;
            }
        }
    }
    else
    {
        inputChannelCount = 0;
    }

    if( outputParameters )
    {
        PaWinWdmDeviceInfo* pDeviceInfo = (PaWinWdmDeviceInfo*)wdmHostApi->inheritedHostApiRep.deviceInfos[outputParameters->device];
        PaWinWdmPin* pin;
        unsigned fmt;
        unsigned long testFormat = 0;
        unsigned validBits = 0;

        outputChannelCount = outputParameters->channelCount;
        outputSampleFormat = outputParameters->sampleFormat;

        /* all standard sample formats are supported by the buffer adapter,
        this implementation doesn't support any custom sample formats */
        if( outputSampleFormat & paCustomFormat )
        {
            PaWinWDM_SetLastErrorInfo(paSampleFormatNotSupported, "IsFormatSupported: Custom output format not supported");
            return paSampleFormatNotSupported;
        }

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

        if( outputParameters->device == paUseHostApiSpecificDeviceSpecification )
        {
            PaWinWDM_SetLastErrorInfo(paInvalidDevice, "IsFormatSupported: paUseHostApiSpecificDeviceSpecification not supported");
            return paInvalidDevice;
        }

        /* check that output device can support outputChannelCount */
        if( outputChannelCount > hostApi->deviceInfos[ outputParameters->device ]->maxOutputChannels )
        {
            PaWinWDM_SetLastErrorInfo(paInvalidChannelCount, "Invalid output channel count");
            return paInvalidChannelCount;
        }

        /* validate outputStreamInfo */
        if( outputParameters->hostApiSpecificStreamInfo )
        {
            PaWinWDM_SetLastErrorInfo(paIncompatibleHostApiSpecificStreamInfo, "Host API stream info not supported");
            return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */
        }

        pFilter = pDeviceInfo->filter;
        pin = pFilter->pins[pDeviceInfo->pin];

        /* Find out the testing format */
        for (fmt = paFloat32; fmt <= paUInt8; fmt <<= 1)
        {
            if ((fmt & pin->formats) != 0)
            {
                /* Found a matching format! */
                testFormat = fmt;
                break;
            }
        }
        if (testFormat == 0)
        {
            PaWinWDM_SetLastErrorInfo(result, "IsFormatSupported(render) failed: no testformat found!");
            return paUnanticipatedHostError;
        }

        /* Due to special considerations, WaveRT devices with paInt24 should be tested with paInt32 and
        valid bits = 24 (instead of 24 bit samples) */
        if (pFilter->devInfo.streamingType == Type_kWaveRT && testFormat == paInt24)
        {
            PA_DEBUG(("IsFormatSupported (render): WaveRT overriding testFormat paInt24 with paInt32 (24 valid bits)"));
            testFormat = paInt32;
            validBits = 24;
        }

        /* Check that the output format is supported */
        channelMask = PaWin_DefaultChannelMask(outputChannelCount);
        PaWin_InitializeWaveFormatExtensible((PaWinWaveFormat*)&wfx,
            outputChannelCount,
            testFormat,
            PaWin_SampleFormatToLinearWaveFormatTag(testFormat),
            sampleRate,
            channelMask );

        if (validBits != 0)
        {
            wfx.Samples.wValidBitsPerSample = validBits;
        }

        result = PinIsFormatSupported(pin, (const WAVEFORMATEX*)&wfx);
        if( result != paNoError )
        {
            /* Try a WAVE_FORMAT_PCM instead */
            PaWin_InitializeWaveFormatEx((PaWinWaveFormat*)&wfx,
                outputChannelCount,
                testFormat,
                PaWin_SampleFormatToLinearWaveFormatTag(testFormat),
                sampleRate);

            if (validBits != 0)
            {
                wfx.Samples.wValidBitsPerSample = validBits;
            }

            result = PinIsFormatSupported(pin, (const WAVEFORMATEX*)&wfx);
            if( result != paNoError )
            {
                PaWinWDM_SetLastErrorInfo(result, "IsFormatSupported(render) failed: %u,%u,%u", wfx.Format.nSamplesPerSec, wfx.Format.nChannels, wfx.Format.wBitsPerSample);
                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 inputSampleFormat 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((inputChannelCount == 0)&&(outputChannelCount == 0))
    {
        PaWinWDM_SetLastErrorInfo(paSampleFormatNotSupported, "No input or output channels defined");
        result = paSampleFormatNotSupported; /* Not right error */
    }

    PA_LOGL_;
    return result;
}

static void ResetStreamEvents(PaWinWdmStream* stream)
{
    unsigned i;
    ResetEvent(stream->eventAbort);
    ResetEvent(stream->eventStreamStart[StreamStart_kOk]);
    ResetEvent(stream->eventStreamStart[StreamStart_kFailed]);

    for (i=0; i<stream->capture.noOfPackets; ++i)
    {
        if (stream->capture.events && stream->capture.events[i])
        {
            ResetEvent(stream->capture.events[i]);
        }
    }

    for (i=0; i<stream->render.noOfPackets; ++i)
    {
        if (stream->render.events && stream->render.events[i])
        {
            ResetEvent(stream->render.events[i]);
        }
    }
}

static void CloseStreamEvents(PaWinWdmStream* stream)
{
    unsigned i;
    PaWinWdmIOInfo* ios[2] = { &stream->capture, &stream->render };

    if (stream->eventAbort)
    {
        CloseHandle(stream->eventAbort);
        stream->eventAbort = 0;
    }
    if (stream->eventStreamStart[StreamStart_kOk])
    {
        CloseHandle(stream->eventStreamStart[StreamStart_kOk]);
    }
    if (stream->eventStreamStart[StreamStart_kFailed])
    {
        CloseHandle(stream->eventStreamStart[StreamStart_kFailed]);
    }

    for (i = 0; i < 2; ++i)
    {
        unsigned j;
        /* Unregister notification handles for WaveRT */
        if (ios[i]->pPin && ios[i]->pPin->parentFilter->devInfo.streamingType == Type_kWaveRT &&
            ios[i]->pPin->pinKsSubType == SubType_kNotification &&
            ios[i]->events != 0)
        {
            PinUnregisterNotificationHandle(ios[i]->pPin, ios[i]->events[0]);
        }

        for (j=0; j < ios[i]->noOfPackets; ++j)
        {
            if (ios[i]->events && ios[i]->events[j])
            {
                CloseHandle(ios[i]->events[j]);
                ios[i]->events[j] = 0;
            }
        }
    }
}

static unsigned NextPowerOf2(unsigned val)
{
    val--;
    val = (val >> 1) | val;
    val = (val >> 2) | val;
    val = (val >> 4) | val;
    val = (val >> 8) | val;
    val = (val >> 16) | val;
    return ++val;
}

static PaError ValidateSpecificStreamParameters(
    const PaStreamParameters *streamParameters,
    const PaWinWDMKSInfo *streamInfo,
    unsigned isInput)
{
    if( streamInfo )
    {
        if( streamInfo->size != sizeof( PaWinWDMKSInfo )
            || streamInfo->version != 1 )
        {
            PA_DEBUG(("Stream parameters: size or version not correct"));
            return paIncompatibleHostApiSpecificStreamInfo;
        }

        if (!!(streamInfo->flags & ~(paWinWDMKSOverrideFramesize | paWinWDMKSUseGivenChannelMask)))
        {
            PA_DEBUG(("Stream parameters: non supported flags set"));
            return paIncompatibleHostApiSpecificStreamInfo;
        }

        if (streamInfo->noOfPackets != 0 &&
            (streamInfo->noOfPackets < 2 || streamInfo->noOfPackets > 8))
        {
            PA_DEBUG(("Stream parameters: noOfPackets %u out of range [2,8]", streamInfo->noOfPackets));
            return paIncompatibleHostApiSpecificStreamInfo;
        }

        if (streamInfo->flags & paWinWDMKSUseGivenChannelMask)
        {
            if (isInput)
            {
                PA_DEBUG(("Stream parameters: Channels mask setting not supported for input stream"));
                return paIncompatibleHostApiSpecificStreamInfo;
            }

            if (streamInfo->channelMask & PAWIN_SPEAKER_RESERVED)
            {
                PA_DEBUG(("Stream parameters: Given channels mask 0x%08X not supported", streamInfo->channelMask));
                return paIncompatibleHostApiSpecificStreamInfo;
            }
        }

    }

    return paNoError;
}




/* 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 framesPerUserBuffer,
                          PaStreamFlags streamFlags,
                          PaStreamCallback *streamCallback,
                          void *userData )
{
    PaError result = paNoError;
    PaWinWdmHostApiRepresentation *wdmHostApi = (PaWinWdmHostApiRepresentation*)hostApi;
    PaWinWdmStream *stream = 0;
    /* unsigned long framesPerHostBuffer; these may not be equivalent for all implementations */
    PaSampleFormat inputSampleFormat, outputSampleFormat;
    PaSampleFormat hostInputSampleFormat, hostOutputSampleFormat;
    int userInputChannels,userOutputChannels;
    WAVEFORMATEXTENSIBLE wfx;

    PA_LOGE_;
    PA_DEBUG(("OpenStream:sampleRate = %f\n",sampleRate));
    PA_DEBUG(("OpenStream:framesPerBuffer = %lu\n",framesPerUserBuffer));

    if( inputParameters )
    {
        userInputChannels = inputParameters->channelCount;
        inputSampleFormat = inputParameters->sampleFormat;

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

        if( inputParameters->device == paUseHostApiSpecificDeviceSpecification )
        {
            PaWinWDM_SetLastErrorInfo(paInvalidDevice, "paUseHostApiSpecificDeviceSpecification(in) not supported");
            return paInvalidDevice;
        }

        /* check that input device can support stream->userInputChannels */
        if( userInputChannels > hostApi->deviceInfos[ inputParameters->device ]->maxInputChannels )
        {
            PaWinWDM_SetLastErrorInfo(paInvalidChannelCount, "Invalid input channel count");
            return paInvalidChannelCount;
        }

        /* validate inputStreamInfo */
        result = ValidateSpecificStreamParameters(inputParameters, inputParameters->hostApiSpecificStreamInfo, 1 );
        if(result != paNoError)
        {
            PaWinWDM_SetLastErrorInfo(result, "Host API stream info not supported (in)");
            return result; /* this implementation doesn't use custom stream info */
        }
    }
    else
    {
        userInputChannels = 0;
        inputSampleFormat = paInt16; /* Suppress 'uninitialised var' warnings. */
    }

    if( outputParameters )
    {
        userOutputChannels = outputParameters->channelCount;
        outputSampleFormat = outputParameters->sampleFormat;

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

        if( outputParameters->device == paUseHostApiSpecificDeviceSpecification )
        {
            PaWinWDM_SetLastErrorInfo(paInvalidDevice, "paUseHostApiSpecificDeviceSpecification(out) not supported");
            return paInvalidDevice;
        }

        /* check that output device can support stream->userInputChannels */
        if( userOutputChannels > hostApi->deviceInfos[ outputParameters->device ]->maxOutputChannels )
        {
            PaWinWDM_SetLastErrorInfo(paInvalidChannelCount, "Invalid output channel count");
            return paInvalidChannelCount;
        }

        /* validate outputStreamInfo */
        result = ValidateSpecificStreamParameters( outputParameters, outputParameters->hostApiSpecificStreamInfo, 0 );
        if (result != paNoError)
        {
            PaWinWDM_SetLastErrorInfo(result, "Host API stream info not supported (out)");
            return result; /* this implementation doesn't use custom stream info */
        }
    }
    else
    {
        userOutputChannels = 0;
        outputSampleFormat = paInt16; /* Suppress 'uninitialized var' warnings. */
    }

    /* validate platform specific flags */
    if( (streamFlags & paPlatformSpecificFlags) != 0 )
    {
        PaWinWDM_SetLastErrorInfo(paInvalidFlag, "Invalid flag supplied");
        return paInvalidFlag; /* unexpected platform specific flag */
    }

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

    /* Create allocation group */
    stream->allocGroup = PaUtil_CreateAllocationGroup();
    if( !stream->allocGroup )
    {
        result = paInsufficientMemory;
        goto error;
    }

    /* Zero the stream object */
    /* memset((void*)stream,0,sizeof(PaWinWdmStream)); */

    if( streamCallback )
    {
        PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation,
            &wdmHostApi->callbackStreamInterface, streamCallback, userData );
    }
    else
    {
        /* PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation,
        &wdmHostApi->blockingStreamInterface, streamCallback, userData ); */

        /* We don't support the blocking API yet */
        PA_DEBUG(("Blocking API not supported yet!\n"));
        PaWinWDM_SetLastErrorInfo(paUnanticipatedHostError, "Blocking API not supported yet");
        result = paUnanticipatedHostError;
        goto error;
    }

    PaUtil_InitializeCpuLoadMeasurer( &stream->cpuLoadMeasurer, sampleRate );

    /* Instantiate the input pin if necessary */
    if(userInputChannels > 0)
    {
        PaWinWdmFilter* pFilter;
        PaWinWdmDeviceInfo* pDeviceInfo;
        PaWinWdmPin* pPin;
        unsigned validBitsPerSample = 0;
        PaWinWaveFormatChannelMask channelMask = PaWin_DefaultChannelMask( userInputChannels );

        result = paSampleFormatNotSupported;
        pDeviceInfo = (PaWinWdmDeviceInfo*)wdmHostApi->inheritedHostApiRep.deviceInfos[inputParameters->device];
        pFilter = pDeviceInfo->filter;
        pPin = pFilter->pins[pDeviceInfo->pin];

        stream->userInputChannels = userInputChannels;

        hostInputSampleFormat = PaUtil_SelectClosestAvailableFormat( pPin->formats, inputSampleFormat );
        if (hostInputSampleFormat == paSampleFormatNotSupported)
        {
            result = paUnanticipatedHostError;
            PaWinWDM_SetLastErrorInfo(result, "PU_SCAF(%X,%X) failed (input)", pPin->formats, inputSampleFormat);
            goto error;
        }
        else if (pFilter->devInfo.streamingType == Type_kWaveRT && hostInputSampleFormat == paInt24)
        {
            /* For WaveRT, we choose 32 bit format instead of paInt24, since we MIGHT need to align buffer on a
            128 byte boundary (see PinGetBuffer) */
            hostInputSampleFormat = paInt32;
            /* But we'll tell the driver that it's 24 bit in 32 bit container */
            validBitsPerSample = 24;
        }

        while (hostInputSampleFormat <= paUInt8)
        {
            unsigned channelsToProbe = stream->userInputChannels;
            /* Some or all KS devices can only handle the exact number of channels
            * they specify. But PortAudio clients expect to be able to
            * at least specify mono I/O on a multi-channel device
            * If this is the case, then we will do the channel mapping internally
            * The following loop tests this case
            **/
            while (1)
            {
                PaWin_InitializeWaveFormatExtensible((PaWinWaveFormat*)&wfx,
                    channelsToProbe,
                    hostInputSampleFormat,
                    PaWin_SampleFormatToLinearWaveFormatTag(hostInputSampleFormat),
                    sampleRate,
                    channelMask );
                stream->capture.bytesPerFrame = wfx.Format.nBlockAlign;
                if (validBitsPerSample != 0)
                {
                    wfx.Samples.wValidBitsPerSample = validBitsPerSample;
                }
                stream->capture.pPin = FilterCreatePin(pFilter, pPin->pinId, (WAVEFORMATEX*)&wfx, &result);
                stream->deviceInputChannels = channelsToProbe;

                if( result != paNoError && result != paDeviceUnavailable )
                {
                    /* Try a WAVE_FORMAT_PCM instead */
                    PaWin_InitializeWaveFormatEx((PaWinWaveFormat*)&wfx,
                        channelsToProbe,
                        hostInputSampleFormat,
                        PaWin_SampleFormatToLinearWaveFormatTag(hostInputSampleFormat),
                        sampleRate);
                    if (validBitsPerSample != 0)
                    {
                        wfx.Samples.wValidBitsPerSample = validBitsPerSample;
                    }
                    stream->capture.pPin = FilterCreatePin(pFilter, pPin->pinId, (const WAVEFORMATEX*)&wfx, &result);
                }

                if (result == paDeviceUnavailable) goto occupied;

                if (result == paNoError)
                {
                    /* We're done */
                    break;
                }

                if (channelsToProbe < (unsigned)pPin->maxChannels)
                {
                    /* Go to next multiple of 2 */
                    channelsToProbe = min((((channelsToProbe>>1)+1)<<1), (unsigned)pPin->maxChannels);
                    continue;
                }

                break;
            }

            if (result == paNoError)
            {
                /* We're done */
                break;
            }

            /* Go to next format in line with lower resolution */
            hostInputSampleFormat <<= 1;
        }

        if(stream->capture.pPin == NULL)
        {
            PaWinWDM_SetLastErrorInfo(result, "Failed to create capture pin: sr=%u,ch=%u,bits=%u,align=%u",
                wfx.Format.nSamplesPerSec, wfx.Format.nChannels, wfx.Format.wBitsPerSample, wfx.Format.nBlockAlign);
            goto error;
        }

        /* Select correct mux input on MUX node of topology filter */
        if (pDeviceInfo->muxPosition >= 0)
        {
            assert(pPin->parentFilter->topologyFilter != NULL);

            result = FilterUse(pPin->parentFilter->topologyFilter);
            if (result != paNoError)
            {
                PaWinWDM_SetLastErrorInfo(result, "Failed to open topology filter");
                goto error;
            }

            result = WdmSetMuxNodeProperty(pPin->parentFilter->topologyFilter->handle,
                pPin->inputs[pDeviceInfo->muxPosition]->muxNodeId,
                pPin->inputs[pDeviceInfo->muxPosition]->muxPinId);

            FilterRelease(pPin->parentFilter->topologyFilter);

            if(result != paNoError)
            {
                PaWinWDM_SetLastErrorInfo(result, "Failed to set topology mux node");
                goto error;
            }
        }

        stream->capture.bytesPerSample = stream->capture.bytesPerFrame / stream->deviceInputChannels;
        stream->capture.pPin->frameSize /= stream->capture.bytesPerFrame;
        PA_DEBUG(("Capture pin frames: %d\n",stream->capture.pPin->frameSize));
    }
    else
    {
        hostInputSampleFormat = (PaSampleFormat)0; /* Avoid uninitialized variable warning */

        stream->capture.pPin = NULL;
        stream->capture.bytesPerFrame = 0;
    }

    /* Instantiate the output pin if necessary */
    if(userOutputChannels > 0)
    {
        PaWinWdmFilter* pFilter;
        PaWinWdmDeviceInfo* pDeviceInfo;
        PaWinWdmPin* pPin;
        PaWinWDMKSInfo* pInfo = (PaWinWDMKSInfo*)(outputParameters->hostApiSpecificStreamInfo);
        unsigned validBitsPerSample = 0;
        PaWinWaveFormatChannelMask channelMask = PaWin_DefaultChannelMask( userOutputChannels );
        if (pInfo && (pInfo->flags & paWinWDMKSUseGivenChannelMask))
        {
            PA_DEBUG(("Using channelMask 0x%08X instead of default 0x%08X\n",
                pInfo->channelMask,
                channelMask));
            channelMask = pInfo->channelMask;
        }

        result = paSampleFormatNotSupported;
        pDeviceInfo = (PaWinWdmDeviceInfo*)wdmHostApi->inheritedHostApiRep.deviceInfos[outputParameters->device];
        pFilter = pDeviceInfo->filter;
        pPin = pFilter->pins[pDeviceInfo->pin];

        stream->userOutputChannels = userOutputChannels;

        hostOutputSampleFormat = PaUtil_SelectClosestAvailableFormat( pPin->formats, outputSampleFormat );
        if (hostOutputSampleFormat == paSampleFormatNotSupported)
        {
            result = paUnanticipatedHostError;
            PaWinWDM_SetLastErrorInfo(result, "PU_SCAF(%X,%X) failed (output)", pPin->formats, hostOutputSampleFormat);
            goto error;
        }
        else if (pFilter->devInfo.streamingType == Type_kWaveRT && hostOutputSampleFormat == paInt24)
        {
            /* For WaveRT, we choose 32 bit format instead of paInt24, since we MIGHT need to align buffer on a
            128 byte boundary (see PinGetBuffer) */
            hostOutputSampleFormat = paInt32;
            /* But we'll tell the driver that it's 24 bit in 32 bit container */
            validBitsPerSample = 24;
        }

        while (hostOutputSampleFormat <= paUInt8)
        {
            unsigned channelsToProbe = stream->userOutputChannels;
            /* Some or all KS devices can only handle the exact number of channels
            * they specify. But PortAudio clients expect to be able to
            * at least specify mono I/O on a multi-channel device
            * If this is the case, then we will do the channel mapping internally
            * The following loop tests this case
            **/
            while (1)
            {
                PaWin_InitializeWaveFormatExtensible((PaWinWaveFormat*)&wfx,
                    channelsToProbe,
                    hostOutputSampleFormat,
                    PaWin_SampleFormatToLinearWaveFormatTag(hostOutputSampleFormat),
                    sampleRate,
                    channelMask );
                stream->render.bytesPerFrame = wfx.Format.nBlockAlign;
                if (validBitsPerSample != 0)
                {
                    wfx.Samples.wValidBitsPerSample = validBitsPerSample;
                }
                stream->render.pPin = FilterCreatePin(pFilter, pPin->pinId, (WAVEFORMATEX*)&wfx, &result);
                stream->deviceOutputChannels = channelsToProbe;

                if( result != paNoError && result != paDeviceUnavailable )
                {
                    PaWin_InitializeWaveFormatEx((PaWinWaveFormat*)&wfx,
                        channelsToProbe,
                        hostOutputSampleFormat,
                        PaWin_SampleFormatToLinearWaveFormatTag(hostOutputSampleFormat),
                        sampleRate);
                    if (validBitsPerSample != 0)
                    {
                        wfx.Samples.wValidBitsPerSample = validBitsPerSample;
                    }
                    stream->render.pPin = FilterCreatePin(pFilter, pPin->pinId, (const WAVEFORMATEX*)&wfx, &result);
                }

                if (result == paDeviceUnavailable) goto occupied;

                if (result == paNoError)
                {
                    /* We're done */
                    break;
                }

                if (channelsToProbe < (unsigned)pPin->maxChannels)
                {
                    /* Go to next multiple of 2 */
                    channelsToProbe = min((((channelsToProbe>>1)+1)<<1), (unsigned)pPin->maxChannels);
                    continue;
                }

                break;
            };

            if (result == paNoError)
            {
                /* We're done */
                break;
            }

            /* Go to next format in line with lower resolution */
            hostOutputSampleFormat <<= 1;
        }

        if(stream->render.pPin == NULL)
        {
            PaWinWDM_SetLastErrorInfo(result, "Failed to create render pin: sr=%u,ch=%u,bits=%u,align=%u",
                wfx.Format.nSamplesPerSec, wfx.Format.nChannels, wfx.Format.wBitsPerSample, wfx.Format.nBlockAlign);
            goto error;
        }

        stream->render.bytesPerSample = stream->render.bytesPerFrame / stream->deviceOutputChannels;
        stream->render.pPin->frameSize /= stream->render.bytesPerFrame;
        PA_DEBUG(("Render pin frames: %d\n",stream->render.pPin->frameSize));
    }
    else
    {
        hostOutputSampleFormat = (PaSampleFormat)0; /* Avoid uninitialized variable warning */

        stream->render.pPin = NULL;
        stream->render.bytesPerFrame = 0;
    }

    /* Calculate the framesPerHostXxxxBuffer size based upon the suggested latency values */
    /* Record the buffer length */
    if(inputParameters)
    {
        /* Calculate the frames from the user's value - add a bit to round up */
        stream->capture.framesPerBuffer = (unsigned long)((inputParameters->suggestedLatency*sampleRate)+0.0001);
        if(stream->capture.framesPerBuffer > (unsigned long)sampleRate)
        { /* Upper limit is 1 second */
            stream->capture.framesPerBuffer = (unsigned long)sampleRate;
        }
        else if(stream->capture.framesPerBuffer < stream->capture.pPin->frameSize)
        {
            stream->capture.framesPerBuffer = stream->capture.pPin->frameSize;
        }
        PA_DEBUG(("Input frames chosen:%ld\n",stream->capture.framesPerBuffer));

        /* Setup number of packets to use */
        stream->capture.noOfPackets = 2;

        if (inputParameters->hostApiSpecificStreamInfo)
        {
            PaWinWDMKSInfo* pInfo = (PaWinWDMKSInfo*)inputParameters->hostApiSpecificStreamInfo;

            if (stream->capture.pPin->parentFilter->devInfo.streamingType == Type_kWaveCyclic &&
                pInfo->noOfPackets != 0)
            {
                stream->capture.noOfPackets = pInfo->noOfPackets;
            }
        }
    }

    if(outputParameters)
    {
        /* Calculate the frames from the user's value - add a bit to round up */
        stream->render.framesPerBuffer = (unsigned long)((outputParameters->suggestedLatency*sampleRate)+0.0001);
        if(stream->render.framesPerBuffer > (unsigned long)sampleRate)
        { /* Upper limit is 1 second */
            stream->render.framesPerBuffer = (unsigned long)sampleRate;
        }
        else if(stream->render.framesPerBuffer < stream->render.pPin->frameSize)
        {
            stream->render.framesPerBuffer = stream->render.pPin->frameSize;
        }
        PA_DEBUG(("Output frames chosen:%ld\n",stream->render.framesPerBuffer));

        /* Setup number of packets to use */
        stream->render.noOfPackets = 2;

        if (outputParameters->hostApiSpecificStreamInfo)
        {
            PaWinWDMKSInfo* pInfo = (PaWinWDMKSInfo*)outputParameters->hostApiSpecificStreamInfo;

            if (stream->render.pPin->parentFilter->devInfo.streamingType == Type_kWaveCyclic &&
                pInfo->noOfPackets != 0)
            {
                stream->render.noOfPackets = pInfo->noOfPackets;
            }
        }
    }

    /* Host buffer size is bound to the largest of the input and output frame sizes */
    result =  PaUtil_InitializeBufferProcessor( &stream->bufferProcessor,
        stream->userInputChannels, inputSampleFormat, hostInputSampleFormat,
        stream->userOutputChannels, outputSampleFormat, hostOutputSampleFormat,
        sampleRate, streamFlags, framesPerUserBuffer,
        max(stream->capture.framesPerBuffer, stream->render.framesPerBuffer),
        paUtilBoundedHostBufferSize,
        streamCallback, userData );
    if( result != paNoError )
    {
        PaWinWDM_SetLastErrorInfo(result, "PaUtil_InitializeBufferProcessor failed: ich=%u, isf=%u, hisf=%u, och=%u, osf=%u, hosf=%u, sr=%lf, flags=0x%X, fpub=%u, fphb=%u",
            stream->userInputChannels, inputSampleFormat, hostInputSampleFormat,
            stream->userOutputChannels, outputSampleFormat, hostOutputSampleFormat,
            sampleRate, streamFlags, framesPerUserBuffer,
            max(stream->capture.framesPerBuffer, stream->render.framesPerBuffer));
        goto error;
    }

    /* Allocate/get all the buffers for host I/O */
    if (stream->userInputChannels > 0)
    {
        stream->streamRepresentation.streamInfo.inputLatency = stream->capture.framesPerBuffer / sampleRate;

        switch (stream->capture.pPin->parentFilter->devInfo.streamingType)
        {
        case Type_kWaveCyclic:
            {
                unsigned size = stream->capture.noOfPackets * stream->capture.framesPerBuffer * stream->capture.bytesPerFrame;
                /* Allocate input host buffer */
                stream->capture.hostBuffer = (char*)PaUtil_GroupAllocateMemory(stream->allocGroup, size);
                PA_DEBUG(("Input buffer allocated (size = %u)\n", size));
                if( !stream->capture.hostBuffer )
                {
                    PA_DEBUG(("Cannot allocate host input buffer!\n"));
                    PaWinWDM_SetLastErrorInfo(paInsufficientMemory, "Failed to allocate input buffer");
                    result = paInsufficientMemory;
                    goto error;
                }
                stream->capture.hostBufferSize = size;
                PA_DEBUG(("Input buffer start = %p (size=%u)\n",stream->capture.hostBuffer, stream->capture.hostBufferSize));
                stream->capture.pPin->fnEventHandler = PaPinCaptureEventHandler_WaveCyclic;
                stream->capture.pPin->fnSubmitHandler = PaPinCaptureSubmitHandler_WaveCyclic;
            }
            break;
        case Type_kWaveRT:
            {
                const DWORD dwTotalSize = 2 * stream->capture.framesPerBuffer * stream->capture.bytesPerFrame;
                DWORD dwRequestedSize = dwTotalSize;
                BOOL bCallMemoryBarrier = FALSE;
                ULONG hwFifoLatency = 0;
                ULONG dummy;
                result = PinGetBuffer(stream->capture.pPin, (void**)&stream->capture.hostBuffer, &dwRequestedSize, &bCallMemoryBarrier);
                if (!result)
                {
                    PA_DEBUG(("Input buffer start = %p, size = %u\n", stream->capture.hostBuffer, dwRequestedSize));
                    if (dwRequestedSize != dwTotalSize)
                    {
                        PA_DEBUG(("Buffer length changed by driver from %u to %u !\n", dwTotalSize, dwRequestedSize));
                        /* Recalculate to what the driver has given us */
                        stream->capture.framesPerBuffer = dwRequestedSize / (2 * stream->capture.bytesPerFrame);
                    }
                    stream->capture.hostBufferSize = dwRequestedSize;

                    if (stream->capture.pPin->pinKsSubType == SubType_kPolled)
                    {
                        stream->capture.pPin->fnEventHandler = PaPinCaptureEventHandler_WaveRTPolled;
                        stream->capture.pPin->fnSubmitHandler = PaPinCaptureSubmitHandler_WaveRTPolled;
                    }
                    else
                    {
                        stream->capture.pPin->fnEventHandler = PaPinCaptureEventHandler_WaveRTEvent;
                        stream->capture.pPin->fnSubmitHandler = PaPinCaptureSubmitHandler_WaveRTEvent;
                    }

                    stream->capture.pPin->fnMemBarrier = bCallMemoryBarrier ? MemoryBarrierRead : MemoryBarrierDummy;
                }
                else
                {
                    PA_DEBUG(("Failed to get input buffer (WaveRT)\n"));
                    PaWinWDM_SetLastErrorInfo(paUnanticipatedHostError, "Failed to get input buffer (WaveRT)");
                    result = paUnanticipatedHostError;
                    goto error;
                }

                /* Get latency */
                result = PinGetHwLatency(stream->capture.pPin, &hwFifoLatency, &dummy, &dummy);
                if (result == paNoError)
                {
                    stream->capture.pPin->hwLatency = hwFifoLatency;

                    /* Add HW latency into total input latency */
                    stream->streamRepresentation.streamInfo.inputLatency += ((hwFifoLatency / stream->capture.bytesPerFrame) / sampleRate);
                }
                else
                {
                    PA_DEBUG(("Failed to get size of FIFO hardware buffer (is set to zero)\n"));
                    stream->capture.pPin->hwLatency = 0;
                }
            }
            break;
        default:
            /* Undefined wave type!! */
            assert(0);
            result = paInternalError;
            PaWinWDM_SetLastErrorInfo(result, "Wave type %u ??", stream->capture.pPin->parentFilter->devInfo.streamingType);
            goto error;
        }
    }
    else
    {
        stream->capture.hostBuffer = 0;
    }

    if (stream->userOutputChannels > 0)
    {
        stream->streamRepresentation.streamInfo.outputLatency = stream->render.framesPerBuffer / sampleRate;

        switch (stream->render.pPin->parentFilter->devInfo.streamingType)
        {
        case Type_kWaveCyclic:
            {
                unsigned size = stream->render.noOfPackets * stream->render.framesPerBuffer * stream->render.bytesPerFrame;
                /* Allocate output device buffer */
                stream->render.hostBuffer = (char*)PaUtil_GroupAllocateMemory(stream->allocGroup, size);
                PA_DEBUG(("Output buffer allocated (size = %u)\n", size));
                if( !stream->render.hostBuffer )
                {
                    PA_DEBUG(("Cannot allocate host output buffer!\n"));
                    PaWinWDM_SetLastErrorInfo(paInsufficientMemory, "Failed to allocate output buffer");
                    result = paInsufficientMemory;
                    goto error;
                }
                stream->render.hostBufferSize = size;
                PA_DEBUG(("Output buffer start = %p (size=%u)\n",stream->render.hostBuffer, stream->render.hostBufferSize));

                stream->render.pPin->fnEventHandler = PaPinRenderEventHandler_WaveCyclic;
                stream->render.pPin->fnSubmitHandler = PaPinRenderSubmitHandler_WaveCyclic;
            }
            break;
        case Type_kWaveRT:
            {
                const DWORD dwTotalSize = 2 * stream->render.framesPerBuffer * stream->render.bytesPerFrame;
                DWORD dwRequestedSize = dwTotalSize;
                BOOL bCallMemoryBarrier = FALSE;
                ULONG hwFifoLatency = 0;
                ULONG dummy;
                result = PinGetBuffer(stream->render.pPin, (void**)&stream->render.hostBuffer, &dwRequestedSize, &bCallMemoryBarrier);
                if (!result)
                {
                    PA_DEBUG(("Output buffer start = %p, size = %u, membarrier = %u\n", stream->render.hostBuffer, dwRequestedSize, bCallMemoryBarrier));
                    if (dwRequestedSize != dwTotalSize)
                    {
                        PA_DEBUG(("Buffer length changed by driver from %u to %u !\n", dwTotalSize, dwRequestedSize));
                        /* Recalculate to what the driver has given us */
                        stream->render.framesPerBuffer = dwRequestedSize / (2 * stream->render.bytesPerFrame);
                    }
                    stream->render.hostBufferSize = dwRequestedSize;

                    if (stream->render.pPin->pinKsSubType == SubType_kPolled)
                    {
                        stream->render.pPin->fnEventHandler = PaPinRenderEventHandler_WaveRTPolled;
                        stream->render.pPin->fnSubmitHandler = PaPinRenderSubmitHandler_WaveRTPolled;
                    }
                    else
                    {
                        stream->render.pPin->fnEventHandler = PaPinRenderEventHandler_WaveRTEvent;
                        stream->render.pPin->fnSubmitHandler = PaPinRenderSubmitHandler_WaveRTEvent;
                    }

                    stream->render.pPin->fnMemBarrier = bCallMemoryBarrier ? MemoryBarrierWrite : MemoryBarrierDummy;
                }
                else
                {
                    PA_DEBUG(("Failed to get output buffer (with notification)\n"));
                    PaWinWDM_SetLastErrorInfo(paUnanticipatedHostError, "Failed to get output buffer (with notification)");
                    result = paUnanticipatedHostError;
                    goto error;
                }

                /* Get latency */
                result = PinGetHwLatency(stream->render.pPin, &hwFifoLatency, &dummy, &dummy);
                if (result == paNoError)
                {
                    stream->render.pPin->hwLatency = hwFifoLatency;

                    /* Add HW latency into total output latency */
                    stream->streamRepresentation.streamInfo.outputLatency += ((hwFifoLatency / stream->render.bytesPerFrame) / sampleRate);
                }
                else
                {
                    PA_DEBUG(("Failed to get size of FIFO hardware buffer (is set to zero)\n"));
                    stream->render.pPin->hwLatency = 0;
                }
            }
            break;
        default:
            /* Undefined wave type!! */
            assert(0);
            result = paInternalError;
            PaWinWDM_SetLastErrorInfo(result, "Wave type %u ??", stream->capture.pPin->parentFilter->devInfo.streamingType);
            goto error;
        }
    }
    else
    {
        stream->render.hostBuffer = 0;
    }

    stream->streamRepresentation.streamInfo.sampleRate = sampleRate;

    PA_DEBUG(("BytesPerInputFrame = %d\n",stream->capture.bytesPerFrame));
    PA_DEBUG(("BytesPerOutputFrame = %d\n",stream->render.bytesPerFrame));

    /* memset(stream->hostBuffer,0,size); */

    /* Abort */
    stream->eventAbort          = CreateEvent(NULL, TRUE, FALSE, NULL);
    if (stream->eventAbort == 0)
    {
        result = paInsufficientMemory;
        goto error;
    }
    stream->eventStreamStart[0] = CreateEvent(NULL, TRUE, FALSE, NULL);
    if (stream->eventStreamStart[0] == 0)
    {
        result = paInsufficientMemory;
        goto error;
    }
    stream->eventStreamStart[1] = CreateEvent(NULL, TRUE, FALSE, NULL);
    if (stream->eventStreamStart[1] == 0)
    {
        result = paInsufficientMemory;
        goto error;
    }

    if(stream->userInputChannels > 0)
    {
        const unsigned bufferSizeInBytes = stream->capture.framesPerBuffer * stream->capture.bytesPerFrame;
        const unsigned ringBufferFrameSize = NextPowerOf2( 1024 + 2 * max(stream->capture.framesPerBuffer, stream->render.framesPerBuffer) );

        stream->capture.events = (HANDLE*)PaUtil_GroupAllocateMemory(stream->allocGroup, stream->capture.noOfPackets * sizeof(HANDLE));
        if (stream->capture.events == NULL)
        {
            result = paInsufficientMemory;
            goto error;
        }

        stream->capture.packets = (DATAPACKET*)PaUtil_GroupAllocateMemory(stream->allocGroup, stream->capture.noOfPackets * sizeof(DATAPACKET));
        if (stream->capture.packets == NULL)
        {
            result = paInsufficientMemory;
            goto error;
        }

        switch(stream->capture.pPin->parentFilter->devInfo.streamingType)
        {
        case Type_kWaveCyclic:
            {
                /* WaveCyclic case */
                unsigned i;
                for (i = 0; i < stream->capture.noOfPackets; ++i)
                {
                    /* Set up the packets */
                    DATAPACKET *p = stream->capture.packets + i;

                    /* Record event */
                    stream->capture.events[i] = CreateEvent(NULL, TRUE, FALSE, NULL);

                    p->Signal.hEvent = stream->capture.events[i];
                    p->Header.Data = stream->capture.hostBuffer + (i*bufferSizeInBytes);
                    p->Header.FrameExtent = bufferSizeInBytes;
                    p->Header.DataUsed = 0;
                    p->Header.Size = sizeof(p->Header);
                    p->Header.PresentationTime.Numerator = 1;
                    p->Header.PresentationTime.Denominator = 1;
                }
            }
            break;
        case Type_kWaveRT:
            {
                /* Set up the "packets" */
                DATAPACKET *p = stream->capture.packets + 0;

                /* Record event: WaveRT has a single event for 2 notification per buffer */
                stream->capture.events[0] = CreateEvent(NULL, FALSE, FALSE, NULL);

                p->Header.Data = stream->capture.hostBuffer;
                p->Header.FrameExtent = bufferSizeInBytes;
                p->Header.DataUsed = 0;
                p->Header.Size = sizeof(p->Header);
                p->Header.PresentationTime.Numerator = 1;
                p->Header.PresentationTime.Denominator = 1;

                ++p;
                p->Header.Data = stream->capture.hostBuffer + bufferSizeInBytes;
                p->Header.FrameExtent = bufferSizeInBytes;
                p->Header.DataUsed = 0;
                p->Header.Size = sizeof(p->Header);
                p->Header.PresentationTime.Numerator = 1;
                p->Header.PresentationTime.Denominator = 1;

                if (stream->capture.pPin->pinKsSubType == SubType_kNotification)
                {
                    result = PinRegisterNotificationHandle(stream->capture.pPin, stream->capture.events[0]);

                    if (result != paNoError)
                    {
                        PA_DEBUG(("Failed to register capture notification handle\n"));
                        PaWinWDM_SetLastErrorInfo(paUnanticipatedHostError, "Failed to register capture notification handle");
                        result = paUnanticipatedHostError;
                        goto error;
                    }
                }

                result = PinRegisterPositionRegister(stream->capture.pPin);

                if (result != paNoError)
                {
                    unsigned long pos = 0xdeadc0de;
                    PA_DEBUG(("Failed to register capture position register, using PinGetAudioPositionViaIOCTLWrite\n"));
                    stream->capture.pPin->fnAudioPosition = PinGetAudioPositionViaIOCTLWrite;
                    /* Test position function */
                    result = (stream->capture.pPin->fnAudioPosition)(stream->capture.pPin, &pos);
                    if (result != paNoError || pos != 0x0)
                    {
                        PA_DEBUG(("Failed to read capture position register (IOCTL)\n"));
                        PaWinWDM_SetLastErrorInfo(paUnanticipatedHostError, "Failed to read capture position register (IOCTL)");
                        result = paUnanticipatedHostError;
                        goto error;
                    }
                }
                else
                {
                    stream->capture.pPin->fnAudioPosition = PinGetAudioPositionMemoryMapped;
                }
            }
            break;
        default:
            /* Undefined wave type!! */
            assert(0);
            result = paInternalError;
            PaWinWDM_SetLastErrorInfo(result, "Wave type %u ??", stream->capture.pPin->parentFilter->devInfo.streamingType);
            goto error;
        }

        /* Setup the input ring buffer here */
        stream->ringBufferData = (char*)PaUtil_GroupAllocateMemory(stream->allocGroup, ringBufferFrameSize * stream->capture.bytesPerFrame);
        if (stream->ringBufferData == NULL)
        {
            result = paInsufficientMemory;
            goto error;
        }
        PaUtil_InitializeRingBuffer(&stream->ringBuffer, stream->capture.bytesPerFrame, ringBufferFrameSize, stream->ringBufferData);
    }
    if(stream->userOutputChannels > 0)
    {
        const unsigned bufferSizeInBytes = stream->render.framesPerBuffer * stream->render.bytesPerFrame;

        stream->render.events = (HANDLE*)PaUtil_GroupAllocateMemory(stream->allocGroup, stream->render.noOfPackets * sizeof(HANDLE));
        if (stream->render.events == NULL)
        {
            result = paInsufficientMemory;
            goto error;
        }

        stream->render.packets = (DATAPACKET*)PaUtil_GroupAllocateMemory(stream->allocGroup, stream->render.noOfPackets * sizeof(DATAPACKET));
        if (stream->render.packets == NULL)
        {
            result = paInsufficientMemory;
            goto error;
        }

        switch(stream->render.pPin->parentFilter->devInfo.streamingType)
        {
        case Type_kWaveCyclic:
            {
                /* WaveCyclic case */
                unsigned i;
                for (i = 0; i < stream->render.noOfPackets; ++i)
                {
                    /* Set up the packets */
                    DATAPACKET *p = stream->render.packets + i;

                    /* Playback event */
                    stream->render.events[i] = CreateEvent(NULL, TRUE, FALSE, NULL);

                    /* In this case, we just use the packets as ptr to the device buffer */
                    p->Signal.hEvent = stream->render.events[i];
                    p->Header.Data = stream->render.hostBuffer + (i*bufferSizeInBytes);
                    p->Header.FrameExtent = bufferSizeInBytes;
                    p->Header.DataUsed = bufferSizeInBytes;
                    p->Header.Size = sizeof(p->Header);
                    p->Header.PresentationTime.Numerator = 1;
                    p->Header.PresentationTime.Denominator = 1;
                }
            }
            break;
        case Type_kWaveRT:
            {
                /* WaveRT case */

                /* Set up the "packets" */
                DATAPACKET *p = stream->render.packets;

                /* The only playback event */
                stream->render.events[0] = CreateEvent(NULL, FALSE, FALSE, NULL);

                /* In this case, we just use the packets as ptr to the device buffer */
                p->Header.Data = stream->render.hostBuffer;
                p->Header.FrameExtent = stream->render.framesPerBuffer*stream->render.bytesPerFrame;
                p->Header.DataUsed = stream->render.framesPerBuffer*stream->render.bytesPerFrame;
                p->Header.Size = sizeof(p->Header);
                p->Header.PresentationTime.Numerator = 1;
                p->Header.PresentationTime.Denominator = 1;

                ++p;
                p->Header.Data = stream->render.hostBuffer + stream->render.framesPerBuffer*stream->render.bytesPerFrame;
                p->Header.FrameExtent = stream->render.framesPerBuffer*stream->render.bytesPerFrame;
                p->Header.DataUsed = stream->render.framesPerBuffer*stream->render.bytesPerFrame;
                p->Header.Size = sizeof(p->Header);
                p->Header.PresentationTime.Numerator = 1;
                p->Header.PresentationTime.Denominator = 1;

                if (stream->render.pPin->pinKsSubType == SubType_kNotification)
                {
                    result = PinRegisterNotificationHandle(stream->render.pPin, stream->render.events[0]);

                    if (result != paNoError)
                    {
                        PA_DEBUG(("Failed to register rendering notification handle\n"));
                        PaWinWDM_SetLastErrorInfo(paUnanticipatedHostError, "Failed to register rendering notification handle");
                        result = paUnanticipatedHostError;
                        goto error;
                    }
                }

                result = PinRegisterPositionRegister(stream->render.pPin);

                if (result != paNoError)
                {
                    unsigned long pos = 0xdeadc0de;
                    PA_DEBUG(("Failed to register rendering position register, using PinGetAudioPositionViaIOCTLRead\n"));
                    stream->render.pPin->fnAudioPosition = PinGetAudioPositionViaIOCTLRead;
                    /* Test position function */
                    result = (stream->render.pPin->fnAudioPosition)(stream->render.pPin, &pos);
                    if (result != paNoError || pos != 0x0)
                    {
                        PA_DEBUG(("Failed to read render position register (IOCTL)\n"));
                        PaWinWDM_SetLastErrorInfo(paUnanticipatedHostError, "Failed to read render position register (IOCTL)");
                        result = paUnanticipatedHostError;
                        goto error;
                    }
                }
                else
                {
                    stream->render.pPin->fnAudioPosition = PinGetAudioPositionMemoryMapped;
                }
            }
            break;
        default:
            /* Undefined wave type!! */
            assert(0);
            result = paInternalError;
            PaWinWDM_SetLastErrorInfo(result, "Wave type %u ??", stream->capture.pPin->parentFilter->devInfo.streamingType);
            goto error;
        }
    }

    stream->streamStarted = 0;
    stream->streamActive = 0;
    stream->streamStop = 0;
    stream->streamAbort = 0;
    stream->streamFlags = streamFlags;
    stream->oldProcessPriority = REALTIME_PRIORITY_CLASS;

    /* Increase ref count on filters in use, so that a CommitDeviceInfos won't delete them */
    if (stream->capture.pPin != 0)
    {
        FilterAddRef(stream->capture.pPin->parentFilter);
    }
    if (stream->render.pPin != 0)
    {
        FilterAddRef(stream->render.pPin->parentFilter);
    }

    /* Ok, now update our host API specific stream info */
    if (stream->userInputChannels)
    {
        PaWinWdmDeviceInfo *pDeviceInfo = (PaWinWdmDeviceInfo*)wdmHostApi->inheritedHostApiRep.deviceInfos[inputParameters->device];

        stream->hostApiStreamInfo.input.device = Pa_HostApiDeviceIndexToDeviceIndex(Pa_HostApiTypeIdToHostApiIndex(paWDMKS), inputParameters->device);
        stream->hostApiStreamInfo.input.channels = stream->deviceInputChannels;
        stream->hostApiStreamInfo.input.muxNodeId = -1;
        if (stream->capture.pPin->inputs)
        {
            stream->hostApiStreamInfo.input.muxNodeId = stream->capture.pPin->inputs[pDeviceInfo->muxPosition]->muxNodeId;
        }
        stream->hostApiStreamInfo.input.endpointPinId = pDeviceInfo->endpointPinId;
        stream->hostApiStreamInfo.input.framesPerHostBuffer = stream->capture.framesPerBuffer;
        stream->hostApiStreamInfo.input.streamingSubType = stream->capture.pPin->pinKsSubType;
    }
    else
    {
        stream->hostApiStreamInfo.input.device = paNoDevice;
    }
    if (stream->userOutputChannels)
    {
        stream->hostApiStreamInfo.output.device = Pa_HostApiDeviceIndexToDeviceIndex(Pa_HostApiTypeIdToHostApiIndex(paWDMKS), outputParameters->device);
        stream->hostApiStreamInfo.output.channels = stream->deviceOutputChannels;
        stream->hostApiStreamInfo.output.framesPerHostBuffer = stream->render.framesPerBuffer;
        stream->hostApiStreamInfo.output.endpointPinId = stream->render.pPin->endpointPinId;
        stream->hostApiStreamInfo.output.streamingSubType = stream->render.pPin->pinKsSubType;
    }
    else
    {
        stream->hostApiStreamInfo.output.device = paNoDevice;
    }
    /*stream->streamRepresentation.streamInfo.hostApiTypeId = paWDMKS;
    stream->streamRepresentation.streamInfo.hostApiSpecificStreamInfo = &stream->hostApiStreamInfo;*/
    stream->streamRepresentation.streamInfo.structVersion = 2;

    *s = (PaStream*)stream;

    PA_LOGL_;
    return result;

occupied:
    /* Ok, someone else is hogging the pin, bail out */
    assert (result == paDeviceUnavailable);
    PaWinWDM_SetLastErrorInfo(result, "Device is occupied");

error:
    PaUtil_TerminateBufferProcessor( &stream->bufferProcessor );

    CloseStreamEvents(stream);

    if (stream->allocGroup)
    {
        PaUtil_FreeAllAllocations(stream->allocGroup);
        PaUtil_DestroyAllocationGroup(stream->allocGroup);
        stream->allocGroup = 0;
    }

    if(stream->render.pPin)
        PinClose(stream->render.pPin);
    if(stream->capture.pPin)
        PinClose(stream->capture.pPin);

    PaUtil_FreeMemory( stream );

    PA_LOGL_;
    return result;
}

/*
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;
    PaWinWdmStream *stream = (PaWinWdmStream*)s;

    PA_LOGE_;

    assert(!stream->streamStarted);
    assert(!stream->streamActive);

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

    CloseStreamEvents(stream);

    if (stream->allocGroup)
    {
        PaUtil_FreeAllAllocations(stream->allocGroup);
        PaUtil_DestroyAllocationGroup(stream->allocGroup);
        stream->allocGroup = 0;
    }

    if(stream->render.pPin)
    {
        PinClose(stream->render.pPin);
    }
    if(stream->capture.pPin)
    {
        PinClose(stream->capture.pPin);
    }

    if (stream->render.pPin)
    {
        FilterFree(stream->render.pPin->parentFilter);
    }
    if (stream->capture.pPin)
    {
        FilterFree(stream->capture.pPin->parentFilter);
    }

    PaUtil_FreeMemory( stream );

    PA_LOGL_;
    return result;
}

/*
Write the supplied packet to the pin
Asynchronous
Should return paNoError on success
*/
static PaError PinWrite(HANDLE h, DATAPACKET* p)
{
    PaError result = paNoError;
    unsigned long cbReturned = 0;
    BOOL fRes = DeviceIoControl(h,
        IOCTL_KS_WRITE_STREAM,
        NULL,
        0,
        &p->Header,
        p->Header.Size,
        &cbReturned,
        &p->Signal);
    if (!fRes)
    {
        unsigned long error = GetLastError();
        if (error != ERROR_IO_PENDING)
        {
            result = paInternalError;
        }
    }
    return result;
}

/*
Read to the supplied packet from the pin
Asynchronous
Should return paNoError on success
*/
static PaError PinRead(HANDLE h, DATAPACKET* p)
{
    PaError result = paNoError;
    unsigned long cbReturned = 0;
    BOOL fRes = DeviceIoControl(h,
        IOCTL_KS_READ_STREAM,
        NULL,
        0,
        &p->Header,
        p->Header.Size,
        &cbReturned,
        &p->Signal);
    if (!fRes)
    {
        unsigned long error = GetLastError();
        if (error != ERROR_IO_PENDING)
        {
            result = paInternalError;
        }
    }
    return result;
}

/*
Copy the first interleaved channel of 16 bit data to the other channels
*/
static void DuplicateFirstChannelInt16(void* buffer, int channels, int samples)
{
    unsigned short* data = (unsigned short*)buffer;
    int channel;
    unsigned short sourceSample;
    while( samples-- )
    {
        sourceSample = *data++;
        channel = channels-1;
        while( channel-- )
        {
            *data++ = sourceSample;
        }
    }
}

/*
Copy the first interleaved channel of 24 bit data to the other channels
*/
static void DuplicateFirstChannelInt24(void* buffer, int channels, int samples)
{
    unsigned char* data = (unsigned char*)buffer;
    int channel;
    unsigned char sourceSample[3];
    while( samples-- )
    {
        sourceSample[0] = data[0];
        sourceSample[1] = data[1];
        sourceSample[2] = data[2];
        data += 3;
        channel = channels-1;
        while( channel-- )
        {
            data[0] = sourceSample[0];
            data[1] = sourceSample[1];
            data[2] = sourceSample[2];
            data += 3;
        }
    }
}

/*
Copy the first interleaved channel of 32 bit data to the other channels
*/
static void DuplicateFirstChannelInt32(void* buffer, int channels, int samples)
{
    unsigned long* data = (unsigned long*)buffer;
    int channel;
    unsigned long sourceSample;
    while( samples-- )
    {
        sourceSample = *data++;
        channel = channels-1;
        while( channel-- )
        {
            *data++ = sourceSample;
        }
    }
}

/*
Increase the priority of the calling thread to RT
*/
static HANDLE BumpThreadPriority()
{
    HANDLE hThread = GetCurrentThread();
    DWORD dwTask = 0;
    HANDLE hAVRT = NULL;

    /* If we have access to AVRT.DLL (Vista and later), use it */
    if (paWinWDMKSAvRtEntryPoints.AvSetMmThreadCharacteristics != NULL)
    {
        hAVRT = paWinWDMKSAvRtEntryPoints.AvSetMmThreadCharacteristics("Pro Audio", &dwTask);
        if (hAVRT != NULL && hAVRT != INVALID_HANDLE_VALUE)
        {
            BOOL bret = paWinWDMKSAvRtEntryPoints.AvSetMmThreadPriority(hAVRT, PA_AVRT_PRIORITY_CRITICAL);
            if (!bret)
            {
                PA_DEBUG(("Set mm thread prio to critical failed!\n"));
            }
            else
            {
                return hAVRT;
            }
        }
        else
        {
            PA_DEBUG(("Set mm thread characteristic to 'Pro Audio' failed, reverting to SetThreadPriority\n"));
        }
    }

    /* For XP and earlier, or if AvSetMmThreadCharacteristics fails (MMCSS disabled ?) */
    if (timeBeginPeriod(1) != TIMERR_NOERROR) {
        PA_DEBUG(("timeBeginPeriod(1) failed!\n"));
    }

    if (!SetThreadPriority(hThread, THREAD_PRIORITY_TIME_CRITICAL)) {
        PA_DEBUG(("SetThreadPriority failed!\n"));
    }

    return hAVRT;
}

/*
Decrease the priority of the calling thread to normal
*/
static void DropThreadPriority(HANDLE hAVRT)
{
    HANDLE hThread = GetCurrentThread();

    if (hAVRT != NULL)
    {
        paWinWDMKSAvRtEntryPoints.AvSetMmThreadPriority(hAVRT, PA_AVRT_PRIORITY_NORMAL);
        paWinWDMKSAvRtEntryPoints.AvRevertMmThreadCharacteristics(hAVRT);
        return;
    }

    SetThreadPriority(hThread, THREAD_PRIORITY_NORMAL);
    timeEndPeriod(1);
}

static PaError PreparePinForStart(PaWinWdmPin* pin)
{
    PaError result;
    result = PinSetState(pin, KSSTATE_ACQUIRE);
    if (result != paNoError)
    {
        goto error;
    }
    result = PinSetState(pin, KSSTATE_PAUSE);
    if (result != paNoError)
    {
        goto error;
    }
    return result;

error:
    PinSetState(pin, KSSTATE_STOP);
    return result;
}

static PaError PreparePinsForStart(PaProcessThreadInfo* pInfo)
{
    PaError result = paNoError;
    /* Submit buffers */
    if (pInfo->stream->capture.pPin)
    {
        if ((result = PreparePinForStart(pInfo->stream->capture.pPin)) != paNoError)
        {
            goto error;
        }

        if (pInfo->stream->capture.pPin->parentFilter->devInfo.streamingType == Type_kWaveCyclic)
        {
            unsigned i;
            for(i=0; i < pInfo->stream->capture.noOfPackets; ++i)
            {
                if ((result = PinRead(pInfo->stream->capture.pPin->handle, pInfo->stream->capture.packets + i)) != paNoError)
                {
                    goto error;
                }
                ++pInfo->pending;
            }
        }
        else
        {
            pInfo->pending = 2;
        }
    }

    if(pInfo->stream->render.pPin)
    {
        if ((result = PreparePinForStart(pInfo->stream->render.pPin)) != paNoError)
        {
            goto error;
        }

        pInfo->priming += pInfo->stream->render.noOfPackets;
        ++pInfo->pending;
        SetEvent(pInfo->stream->render.events[0]);
        if (pInfo->stream->render.pPin->parentFilter->devInfo.streamingType == Type_kWaveCyclic)
        {
            unsigned i;
            for(i=1; i < pInfo->stream->render.noOfPackets; ++i)
            {
                SetEvent(pInfo->stream->render.events[i]);
                ++pInfo->pending;
            }
        }
    }

error:
    PA_DEBUG(("PreparePinsForStart = %d\n", result));
    return result;
}

static PaError StartPin(PaWinWdmPin* pin)
{
    return PinSetState(pin, KSSTATE_RUN);
}

static PaError StartPins(PaProcessThreadInfo* pInfo)
{
    PaError result = paNoError;
    /* Start the pins as synced as possible */
    if (pInfo->stream->capture.pPin)
    {
        result = StartPin(pInfo->stream->capture.pPin);
    }
    if(pInfo->stream->render.pPin)
    {
        result = StartPin(pInfo->stream->render.pPin);
    }
    PA_DEBUG(("StartPins = %d\n", result));
    return result;
}


static PaError StopPin(PaWinWdmPin* pin)
{
    PinSetState(pin, KSSTATE_PAUSE);
    PinSetState(pin, KSSTATE_STOP);
    return paNoError;
}


static PaError StopPins(PaProcessThreadInfo* pInfo)
{
    PaError result = paNoError;
    if(pInfo->stream->render.pPin)
    {
        StopPin(pInfo->stream->render.pPin);
    }
    if(pInfo->stream->capture.pPin)
    {
        StopPin(pInfo->stream->capture.pPin);
    }
    return result;
}

typedef void (*TSetInputFrameCount)(PaUtilBufferProcessor*, unsigned long);
typedef void (*TSetInputChannel)(PaUtilBufferProcessor*, unsigned int, void *, unsigned int);
static const TSetInputFrameCount fnSetInputFrameCount[2] = { PaUtil_SetInputFrameCount, PaUtil_Set2ndInputFrameCount };
static const TSetInputChannel fnSetInputChannel[2] = { PaUtil_SetInputChannel, PaUtil_Set2ndInputChannel };

static PaError PaDoProcessing(PaProcessThreadInfo* pInfo)
{
    PaError result = paNoError;
    int i, framesProcessed = 0, doChannelCopy = 0;
    ring_buffer_size_t inputFramesAvailable = PaUtil_GetRingBufferReadAvailable(&pInfo->stream->ringBuffer);

    /* Do necessary buffer processing (which will invoke user callback if necessary) */
    if (pInfo->cbResult == paContinue &&
        (pInfo->renderHead != pInfo->renderTail || inputFramesAvailable))
    {
        unsigned processFullDuplex = pInfo->stream->capture.pPin && pInfo->stream->render.pPin && (!pInfo->priming);

        PA_HP_TRACE((pInfo->stream->hLog, "DoProcessing: InputFrames=%u", inputFramesAvailable));

        PaUtil_BeginCpuLoadMeasurement( &pInfo->stream->cpuLoadMeasurer );

        pInfo->ti.currentTime = PaUtil_GetTime();

        PaUtil_BeginBufferProcessing(&pInfo->stream->bufferProcessor, &pInfo->ti, pInfo->underover);
        pInfo->underover = 0; /* Reset the (under|over)flow status */

        if (pInfo->renderTail != pInfo->renderHead)
        {
            DATAPACKET* packet = pInfo->renderPackets[pInfo->renderTail & cPacketsArrayMask].packet;

            assert(packet != 0);
            assert(packet->Header.Data != 0);

            PaUtil_SetOutputFrameCount(&pInfo->stream->bufferProcessor, pInfo->stream->render.framesPerBuffer);

            for(i=0;i<pInfo->stream->userOutputChannels;i++)
            {
                /* Only write the user output channels. Leave the rest blank */
                PaUtil_SetOutputChannel(&pInfo->stream->bufferProcessor,
                    i,
                    ((unsigned char*)(packet->Header.Data))+(i*pInfo->stream->render.bytesPerSample),
                    pInfo->stream->deviceOutputChannels);
            }

            /* We will do a copy to the other channels after the data has been written */
            doChannelCopy = ( pInfo->stream->userOutputChannels == 1 );
        }

        if (inputFramesAvailable && (!pInfo->stream->userOutputChannels || inputFramesAvailable >= (int)pInfo->stream->render.framesPerBuffer))
        {
            unsigned wrapCntr = 0;
            void* data[2] = {0};
            ring_buffer_size_t size[2] = {0};

            /* If full-duplex, we just extract output buffer number of frames */
            if (pInfo->stream->userOutputChannels)
            {
                inputFramesAvailable = min(inputFramesAvailable, (int)pInfo->stream->render.framesPerBuffer);
            }

            inputFramesAvailable = PaUtil_GetRingBufferReadRegions(&pInfo->stream->ringBuffer,
                inputFramesAvailable,
                &data[0],
                &size[0],
                &data[1],
                &size[1]);

            for (wrapCntr = 0; wrapCntr < 2; ++wrapCntr)
            {
                if (size[wrapCntr] == 0)
                    break;

                fnSetInputFrameCount[wrapCntr](&pInfo->stream->bufferProcessor, size[wrapCntr]);
                for(i=0;i<pInfo->stream->userInputChannels;i++)
                {
                    /* Only read as many channels as the user wants */
                    fnSetInputChannel[wrapCntr](&pInfo->stream->bufferProcessor,
                        i,
                        ((unsigned char*)(data[wrapCntr]))+(i*pInfo->stream->capture.bytesPerSample),
                        pInfo->stream->deviceInputChannels);
                }
            }
        }
        else
        {
            /* We haven't consumed anything from the ring buffer... */
            inputFramesAvailable = 0;
            /* If we have full-duplex, this is at startup, so mark no-input! */
            if (pInfo->stream->userOutputChannels>0 && pInfo->stream->userInputChannels>0)
            {
                PA_HP_TRACE((pInfo->stream->hLog, "Input startup, marking no input."));
                PaUtil_SetNoInput(&pInfo->stream->bufferProcessor);
            }
        }

        if (processFullDuplex) /* full duplex */
        {
            /* Only call the EndBufferProcessing function when the total input frames == total output frames */
            const unsigned long totalInputFrameCount = pInfo->stream->bufferProcessor.hostInputFrameCount[0] + pInfo->stream->bufferProcessor.hostInputFrameCount[1];
            const unsigned long totalOutputFrameCount = pInfo->stream->bufferProcessor.hostOutputFrameCount[0] + pInfo->stream->bufferProcessor.hostOutputFrameCount[1];

            if(totalInputFrameCount == totalOutputFrameCount && totalOutputFrameCount != 0)
            {
                framesProcessed = PaUtil_EndBufferProcessing(&pInfo->stream->bufferProcessor, &pInfo->cbResult);
            }
            else
            {
                framesProcessed = 0;
            }
        }
        else
        {
            framesProcessed = PaUtil_EndBufferProcessing(&pInfo->stream->bufferProcessor, &pInfo->cbResult);
        }

        PA_HP_TRACE((pInfo->stream->hLog, "Frames processed: %u %s", framesProcessed, (pInfo->priming ? "(priming)":"")));

        if( doChannelCopy )
        {
            DATAPACKET* packet = pInfo->renderPackets[pInfo->renderTail & cPacketsArrayMask].packet;
            /* Copy the first output channel to the other channels */
            switch (pInfo->stream->render.bytesPerSample)
            {
            case 2:
                DuplicateFirstChannelInt16(packet->Header.Data, pInfo->stream->deviceOutputChannels, pInfo->stream->render.framesPerBuffer);
                break;
            case 3:
                DuplicateFirstChannelInt24(packet->Header.Data, pInfo->stream->deviceOutputChannels, pInfo->stream->render.framesPerBuffer);
                break;
            case 4:
                DuplicateFirstChannelInt32(packet->Header.Data, pInfo->stream->deviceOutputChannels, pInfo->stream->render.framesPerBuffer);
                break;
            default:
                assert(0); /* Unsupported format! */
                break;
            }
        }
        PaUtil_EndCpuLoadMeasurement( &pInfo->stream->cpuLoadMeasurer, framesProcessed );

        if (inputFramesAvailable)
        {
            PaUtil_AdvanceRingBufferReadIndex(&pInfo->stream->ringBuffer, inputFramesAvailable);
        }

        if (pInfo->renderTail != pInfo->renderHead)
        {
            if (!pInfo->stream->streamStop)
            {
                result = pInfo->stream->render.pPin->fnSubmitHandler(pInfo, pInfo->renderTail);
                if (result != paNoError)
                {
                    PA_HP_TRACE((pInfo->stream->hLog, "Capture submit handler failed with result %d", result));
                    return result;
                }
            }
            pInfo->renderTail++;
            if (!pInfo->pinsStarted && pInfo->priming == 0)
            {
                /* We start the pins here to allow "prime time" */
                if ((result = StartPins(pInfo)) == paNoError)
                {
                    PA_HP_TRACE((pInfo->stream->hLog, "Starting pins!"));
                    pInfo->pinsStarted = 1;
                }
            }
        }
    }

    return result;
}

static VOID CALLBACK TimerAPCWaveRTPolledMode(
    LPVOID lpArgToCompletionRoutine,
    DWORD dwTimerLowValue,
    DWORD dwTimerHighValue)
{
    HANDLE* pHandles = (HANDLE*)lpArgToCompletionRoutine;
    if (pHandles[0]) SetEvent(pHandles[0]);
    if (pHandles[1]) SetEvent(pHandles[1]);
}

static DWORD GetCurrentTimeInMillisecs()
{
    return timeGetTime();
}

PA_THREAD_FUNC ProcessingThread(void* pParam)
{
    PaError result = paNoError;
    HANDLE hAVRT = NULL;
    HANDLE hTimer = NULL;
    HANDLE *handleArray = NULL;
    HANDLE timerEventHandles[2] = {0};
    unsigned noOfHandles = 0;
    unsigned captureEvents = 0;
    unsigned renderEvents = 0;
    unsigned timerPeriod = 0;
    DWORD timeStamp[2] = {0};

    PaProcessThreadInfo info;
    memset(&info, 0, sizeof(PaProcessThreadInfo));
    info.stream = (PaWinWdmStream*)pParam;

    info.stream->threadResult = paNoError;

    PA_LOGE_;

    info.ti.inputBufferAdcTime = 0.0;
    info.ti.currentTime = 0.0;
    info.ti.outputBufferDacTime = 0.0;

    PA_DEBUG(("In  buffer len: %.3f ms\n",(2000*info.stream->capture.framesPerBuffer) / info.stream->streamRepresentation.streamInfo.sampleRate));
    PA_DEBUG(("Out buffer len: %.3f ms\n",(2000*info.stream->render.framesPerBuffer) / info.stream->streamRepresentation.streamInfo.sampleRate));
    info.timeout = (DWORD)max(
        (2000*info.stream->render.framesPerBuffer/info.stream->streamRepresentation.streamInfo.sampleRate + 0.5),
        (2000*info.stream->capture.framesPerBuffer/info.stream->streamRepresentation.streamInfo.sampleRate + 0.5));
    info.timeout = max(info.timeout*8, 100);
    timerPeriod = info.timeout;
    PA_DEBUG(("Timeout = %ld ms\n",info.timeout));

    /* Allocate handle array */
    handleArray = (HANDLE*)PaUtil_AllocateMemory((info.stream->capture.noOfPackets + info.stream->render.noOfPackets + 1) * sizeof(HANDLE));

    /* Setup handle array for WFMO */
    if (info.stream->capture.pPin != 0)
    {
        handleArray[noOfHandles++] = info.stream->capture.events[0];
        if (info.stream->capture.pPin->parentFilter->devInfo.streamingType == Type_kWaveCyclic)
        {
            unsigned i;
            for(i=1; i < info.stream->capture.noOfPackets; ++i)
            {
                handleArray[noOfHandles++] = info.stream->capture.events[i];
            }
        }
        captureEvents = noOfHandles;
        renderEvents = noOfHandles;
    }

    if (info.stream->render.pPin != 0)
    {
        handleArray[noOfHandles++] = info.stream->render.events[0];
        if (info.stream->render.pPin->parentFilter->devInfo.streamingType == Type_kWaveCyclic)
        {
            unsigned i;
            for(i=1; i < info.stream->render.noOfPackets; ++i)
            {
                handleArray[noOfHandles++] = info.stream->render.events[i];
            }
        }
        renderEvents = noOfHandles;
    }
    handleArray[noOfHandles++] = info.stream->eventAbort;
    assert(noOfHandles <= (info.stream->capture.noOfPackets + info.stream->render.noOfPackets + 1));

    /* Prepare render and capture pins */
    if ((result = PreparePinsForStart(&info)) != paNoError)
    {
        PA_DEBUG(("Failed to prepare device(s)!\n"));
        goto error;
    }

    /* Init high speed logger */
    if (PaUtil_InitializeHighSpeedLog(&info.stream->hLog, 1000000) != paNoError)
    {
        PA_DEBUG(("Failed to init high speed logger!\n"));
        goto error;
    }

    /* Heighten priority here */
    hAVRT = BumpThreadPriority();

    /* If input only, we start the pins immediately */
    if (info.stream->render.pPin == 0)
    {
        if ((result = StartPins(&info)) != paNoError)
        {
            PA_DEBUG(("Failed to start device(s)!\n"));
            goto error;
        }
        info.pinsStarted = 1;
    }

    /* Handle WaveRT polled mode */
    {
        const unsigned fs = (unsigned)info.stream->streamRepresentation.streamInfo.sampleRate;
        if (info.stream->capture.pPin != 0 && info.stream->capture.pPin->pinKsSubType == SubType_kPolled)
        {
            timerEventHandles[0] = info.stream->capture.events[0];
            timerPeriod = min(timerPeriod, (1000*info.stream->capture.framesPerBuffer)/fs);
        }

        if (info.stream->render.pPin != 0 && info.stream->render.pPin->pinKsSubType == SubType_kPolled)
        {
            timerEventHandles[1] = info.stream->render.events[0];
            timerPeriod = min(timerPeriod, (1000*info.stream->render.framesPerBuffer)/fs);
        }

        if (timerEventHandles[0] || timerEventHandles[1])
        {
            LARGE_INTEGER dueTime = {0};

            timerPeriod=max(timerPeriod/5,1);
            PA_DEBUG(("Timer event handles=0x%04X,0x%04X period=%u ms", timerEventHandles[0], timerEventHandles[1], timerPeriod));
            hTimer = CreateWaitableTimer(0, FALSE, NULL);
            if (hTimer == NULL)
            {
                result = paUnanticipatedHostError;
                goto error;
            }
            /* invoke first timeout immediately */
            if (!SetWaitableTimer(hTimer, &dueTime, timerPeriod, TimerAPCWaveRTPolledMode, timerEventHandles, FALSE))
            {
                result = paUnanticipatedHostError;
                goto error;
            }
            PA_DEBUG(("Waitable timer started, period = %u ms\n", timerPeriod));
        }
    }

    /* Mark stream as active */
    info.stream->streamActive = 1;
    info.stream->threadResult = paNoError;

    /* Up and running... */
    SetEvent(info.stream->eventStreamStart[StreamStart_kOk]);

    /* Take timestamp here */
    timeStamp[0] = timeStamp[1] = GetCurrentTimeInMillisecs();

    while(!info.stream->streamAbort)
    {
        unsigned doProcessing = 1;
        unsigned wait = WaitForMultipleObjects(noOfHandles, handleArray, FALSE, 0);
        unsigned eventSignalled = wait - WAIT_OBJECT_0;
        DWORD dwCurrentTime = 0;

        if (wait == WAIT_FAILED)
        {
            PA_DEBUG(("Wait failed = %ld! \n",wait));
            break;
        }
        if (wait == WAIT_TIMEOUT)
        {
            wait = WaitForMultipleObjectsEx(noOfHandles, handleArray, FALSE, 50, TRUE);
            eventSignalled = wait - WAIT_OBJECT_0;
        }
        else
        {
            if (eventSignalled < captureEvents)
            {
                if (PaUtil_GetRingBufferWriteAvailable(&info.stream->ringBuffer) == 0)
                {
                    PA_HP_TRACE((info.stream->hLog, "!!!!! Input overflow !!!!!"));
                    info.underover |= paInputOverflow;
                }
            }
            else if (eventSignalled < renderEvents)
            {
                if (!info.priming && info.renderHead - info.renderTail > 1)
                {
                    PA_HP_TRACE((info.stream->hLog, "!!!!! Output underflow !!!!!"));
                    info.underover |= paOutputUnderflow;
                }
            }
        }

        /* Get event time */
        dwCurrentTime = GetCurrentTimeInMillisecs();

        /* Since we can mix capture/render devices between WaveCyclic, WaveRT polled and WaveRT notification (3x3 combinations),
        we can't rely on the timeout of WFMO to check for device timeouts, we need to keep tally. */
        if (info.stream->capture.pPin && (dwCurrentTime - timeStamp[0]) >= info.timeout)
        {
            PA_DEBUG(("Timeout for capture device (%u ms)!", info.timeout, (dwCurrentTime - timeStamp[0])));
            result = paTimedOut;
            break;
        }
        if (info.stream->render.pPin && (dwCurrentTime - timeStamp[1]) >= info.timeout)
        {
            PA_DEBUG(("Timeout for render device (%u ms)!", info.timeout, (dwCurrentTime - timeStamp[1])));
            result = paTimedOut;
            break;
        }

        if (wait == WAIT_IO_COMPLETION)
        {
            /* Waitable timer has fired! */
            PA_HP_TRACE((info.stream->hLog, "WAIT_IO_COMPLETION"));
            continue;
        }

        if (wait == WAIT_TIMEOUT)
        {
            continue;
        }
        else
        {
            if (eventSignalled < captureEvents)
            {
                if (info.stream->capture.pPin->fnEventHandler(&info, eventSignalled) == paNoError)
                {
                    timeStamp[0] = dwCurrentTime;

                    /* Since we use the ring buffer, we can submit the buffers directly */
                    if (!info.stream->streamStop)
                    {
                        result = info.stream->capture.pPin->fnSubmitHandler(&info, info.captureTail);
                        if (result != paNoError)
                        {
                            PA_HP_TRACE((info.stream->hLog, "Capture submit handler failed with result %d", result));
                            break;
                        }
                    }
                    ++info.captureTail;
                    /* If full-duplex, let _only_ render event trigger processing. We still need the stream stop
                    handling working, so let that be processed anyways... */
                    if (info.stream->userOutputChannels > 0)
                    {
                        doProcessing = 0;
                    }
                }
            }
            else if (eventSignalled < renderEvents)
            {
                timeStamp[1] = dwCurrentTime;
                eventSignalled -= captureEvents;
                info.stream->render.pPin->fnEventHandler(&info, eventSignalled);
            }
            else
            {
                assert(info.stream->streamAbort);
                PA_HP_TRACE((info.stream->hLog, "Stream abort!"));
                continue;
            }
        }

        /* Handle processing */
        if (doProcessing)
        {
            result = PaDoProcessing(&info);
            if (result != paNoError)
            {
                PA_HP_TRACE((info.stream->hLog, "PaDoProcessing failed!"));
                break;
            }
        }

        if(info.stream->streamStop && info.cbResult != paComplete)
        {
            PA_HP_TRACE((info.stream->hLog, "Stream stop! pending=%d",info.pending));
            info.cbResult = paComplete; /* Stop, but play remaining buffers */
        }

        if(info.pending<=0)
        {
            PA_HP_TRACE((info.stream->hLog, "pending==0 finished..."));
            break;
        }
        if((!info.stream->render.pPin)&&(info.cbResult!=paContinue))
        {
            PA_HP_TRACE((info.stream->hLog, "record only cbResult=%d...",info.cbResult));
            break;
        }
    }

    PA_DEBUG(("Finished processing loop\n"));

    info.stream->threadResult = result;
    goto bailout;

error:
    PA_DEBUG(("Error starting processing thread\n"));
    /* Set the "error" event together with result */
    info.stream->threadResult = result;
    SetEvent(info.stream->eventStreamStart[StreamStart_kFailed]);

bailout:
    if (hTimer)
    {
        PA_DEBUG(("Waitable timer stopped\n", timerPeriod));
        CancelWaitableTimer(hTimer);
        CloseHandle(hTimer);
        hTimer = 0;
    }

    if (info.pinsStarted)
    {
        StopPins(&info);
    }

    /* Lower prio here */
    DropThreadPriority(hAVRT);

    if (handleArray != NULL)
    {
        PaUtil_FreeMemory(handleArray);
    }

#if PA_TRACE_REALTIME_EVENTS
    if (info.stream->hLog)
    {
        PA_DEBUG(("Dumping highspeed trace...\n"));
        PaUtil_DumpHighSpeedLog(info.stream->hLog, "hp_trace.log");
        PaUtil_DiscardHighSpeedLog(info.stream->hLog);
        info.stream->hLog = 0;
    }
#endif
    info.stream->streamActive = 0;

    if((!info.stream->streamStop)&&(!info.stream->streamAbort))
    {
        /* Invoke the user stream finished callback */
        /* Only do it from here if not being stopped/aborted by user */
        if( info.stream->streamRepresentation.streamFinishedCallback != 0 )
            info.stream->streamRepresentation.streamFinishedCallback( info.stream->streamRepresentation.userData );
    }
    info.stream->streamStop = 0;
    info.stream->streamAbort = 0;

    PA_LOGL_;
    return 0;
}


static PaError StartStream( PaStream *s )
{
    PaError result = paNoError;
    PaWinWdmStream *stream = (PaWinWdmStream*)s;

    PA_LOGE_;

    if (stream->streamThread != NULL)
    {
        return paStreamIsNotStopped;
    }

    stream->streamStop = 0;
    stream->streamAbort = 0;

    ResetStreamEvents(stream);

    PaUtil_ResetBufferProcessor( &stream->bufferProcessor );

    stream->oldProcessPriority = GetPriorityClass(GetCurrentProcess());
    /* Uncomment the following line to enable dynamic boosting of the process
    * priority to real time for best low latency support
    * Disabled by default because RT processes can easily block the OS */
    /*ret = SetPriorityClass(GetCurrentProcess(),REALTIME_PRIORITY_CLASS);
    PA_DEBUG(("Class ret = %d;",ret));*/

    stream->streamThread = CREATE_THREAD_FUNCTION (NULL, 0, ProcessingThread, stream, CREATE_SUSPENDED, NULL);
    if(stream->streamThread == NULL)
    {
        result = paInsufficientMemory;
        goto end;
    }
    ResumeThread(stream->streamThread);

    switch (WaitForMultipleObjects(2, stream->eventStreamStart, FALSE, 5000))
    {
    case WAIT_OBJECT_0 + StreamStart_kOk:
        PA_DEBUG(("Processing thread started!\n"));
        result = paNoError;
        /* streamActive is set in processing thread */
        stream->streamStarted = 1;
        break;
    case WAIT_OBJECT_0 + StreamStart_kFailed:
        PA_DEBUG(("Processing thread start failed! (result=%d)\n", stream->threadResult));
        result = stream->threadResult;
        /* Wait for the stream to really exit */
        WaitForSingleObject(stream->streamThread, 200);
        CloseHandle(stream->streamThread);
        stream->streamThread = 0;
        break;
    case WAIT_TIMEOUT:
    default:
        result = paTimedOut;
        PaWinWDM_SetLastErrorInfo(result, "Failed to start processing thread (timeout)!");
        break;
    }

end:
    PA_LOGL_;
    return result;
}


static PaError StopStream( PaStream *s )
{
    PaError result = paNoError;
    PaWinWdmStream *stream = (PaWinWdmStream*)s;
    BOOL doCb = FALSE;

    PA_LOGE_;

    if(stream->streamActive)
    {
        DWORD dwExitCode;
        doCb = TRUE;
        stream->streamStop = 1;
        if (GetExitCodeThread(stream->streamThread, &dwExitCode) && dwExitCode == STILL_ACTIVE)
        {
            if (WaitForSingleObject(stream->streamThread, INFINITE) != WAIT_OBJECT_0)
            {
                PA_DEBUG(("StopStream: stream thread terminated\n"));
                TerminateThread(stream->streamThread, -1);
                result = paTimedOut;
            }
        }
        else
        {
            PA_DEBUG(("StopStream: GECT says not active, but streamActive is not false ??"));
            result = paUnanticipatedHostError;
            PaWinWDM_SetLastErrorInfo(result, "StopStream: GECT says not active, but streamActive = %d", stream->streamActive);
        }
    }
    else
    {
        if (stream->threadResult != paNoError)
        {
            PA_DEBUG(("StopStream: Stream not active (%d)\n", stream->threadResult));
            result = stream->threadResult;
            stream->threadResult = paNoError;
        }
    }

    if (stream->streamThread != NULL)
    {
        CloseHandle(stream->streamThread);
        stream->streamThread = 0;
    }
    stream->streamStarted = 0;
    stream->streamActive = 0;

    if(doCb)
    {
        /* Do user callback now after all state has been reset */
        /* This means it should be safe for the called function */
        /* to invoke e.g. StartStream */
        if( stream->streamRepresentation.streamFinishedCallback != 0 )
            stream->streamRepresentation.streamFinishedCallback( stream->streamRepresentation.userData );
    }

    PA_LOGL_;
    return result;
}

static PaError AbortStream( PaStream *s )
{
    PaError result = paNoError;
    PaWinWdmStream *stream = (PaWinWdmStream*)s;
    int doCb = 0;

    PA_LOGE_;

    if(stream->streamActive)
    {
        doCb = 1;
        stream->streamAbort = 1;
        SetEvent(stream->eventAbort); /* Signal immediately */
        if (WaitForSingleObject(stream->streamThread, 10000) != WAIT_OBJECT_0)
        {
            TerminateThread(stream->streamThread, -1);
            result = paTimedOut;

            PA_DEBUG(("AbortStream: stream thread terminated\n"));
        }
        assert(!stream->streamActive);
    }
    CloseHandle(stream->streamThread);
    stream->streamThread = NULL;
    stream->streamStarted = 0;

    if(doCb)
    {
        /* Do user callback now after all state has been reset */
        /* This means it should be safe for the called function */
        /* to invoke e.g. StartStream */
        if( stream->streamRepresentation.streamFinishedCallback != 0 )
            stream->streamRepresentation.streamFinishedCallback( stream->streamRepresentation.userData );
    }

    stream->streamActive = 0;
    stream->streamStarted = 0;

    PA_LOGL_;
    return result;
}


static PaError IsStreamStopped( PaStream *s )
{
    PaWinWdmStream *stream = (PaWinWdmStream*)s;
    int result = 0;

    PA_LOGE_;

    if(!stream->streamStarted)
        result = 1;

    PA_LOGL_;
    return result;
}


static PaError IsStreamActive( PaStream *s )
{
    PaWinWdmStream *stream = (PaWinWdmStream*)s;
    int result = 0;

    PA_LOGE_;

    if(stream->streamActive)
        result = 1;

    PA_LOGL_;
    return result;
}


static PaTime GetStreamTime( PaStream* s )
{
    PA_LOGE_;
    PA_LOGL_;
    (void)s;
    return PaUtil_GetTime();
}


static double GetStreamCpuLoad( PaStream* s )
{
    PaWinWdmStream *stream = (PaWinWdmStream*)s;
    double result;
    PA_LOGE_;
    result = PaUtil_GetCpuLoad( &stream->cpuLoadMeasurer );
    PA_LOGL_;
    return result;
}


/*
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 )
{
    PaWinWdmStream *stream = (PaWinWdmStream*)s;

    PA_LOGE_;

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

    /* IMPLEMENT ME, see portaudio.h for required behavior*/
    PA_LOGL_;
    return paInternalError;
}


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

    PA_LOGE_;

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

    /* IMPLEMENT ME, see portaudio.h for required behavior*/
    PA_LOGL_;
    return paInternalError;
}


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

    PA_LOGE_;

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

    /* IMPLEMENT ME, see portaudio.h for required behavior*/
    PA_LOGL_;
    return 0;
}


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

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

    /* IMPLEMENT ME, see portaudio.h for required behavior*/
    PA_LOGL_;
    return 0;
}

/***************************************************************************************/
/* Event and submit handlers for WaveCyclic                                            */
/***************************************************************************************/

static PaError PaPinCaptureEventHandler_WaveCyclic(PaProcessThreadInfo* pInfo, unsigned eventIndex)
{
    PaError result = paNoError;
    ring_buffer_size_t frameCount;
    DATAPACKET* packet = pInfo->stream->capture.packets + eventIndex;

    assert( eventIndex < pInfo->stream->capture.noOfPackets );

    if (packet->Header.DataUsed == 0)
    {
        PA_HP_TRACE((pInfo->stream->hLog, ">>> Capture bogus event (no data): idx=%u", eventIndex));

        /* Bogus event, reset! This is to handle the behavior of this USB mic: http://shop.xtz.se/measurement-system/microphone-to-dirac-live-room-correction-suite
           on startup of streaming, where it erroneously sets the event without the corresponding buffer being filled (DataUsed == 0) */
        ResetEvent(packet->Signal.hEvent);

        result = -1;    /* Only need this to be NOT paNoError */
    }
    else
    {
        pInfo->capturePackets[pInfo->captureHead & cPacketsArrayMask].packet = packet;

        frameCount = PaUtil_WriteRingBuffer(&pInfo->stream->ringBuffer, packet->Header.Data, pInfo->stream->capture.framesPerBuffer);

        PA_HP_TRACE((pInfo->stream->hLog, ">>> Capture event: idx=%u (frames=%u)", eventIndex, frameCount));
        ++pInfo->captureHead;
    }

    --pInfo->pending; /* This needs to be done in either case */
    return result;
}

static PaError PaPinCaptureSubmitHandler_WaveCyclic(PaProcessThreadInfo* pInfo, unsigned eventIndex)
{
    PaError result = paNoError;
    DATAPACKET* packet = pInfo->capturePackets[pInfo->captureTail & cPacketsArrayMask].packet;
    pInfo->capturePackets[pInfo->captureTail & cPacketsArrayMask].packet = 0;
    assert(packet != 0);
    PA_HP_TRACE((pInfo->stream->hLog, "Capture submit: %u", eventIndex));
    packet->Header.DataUsed = 0; /* Reset for reuse */
    packet->Header.OptionsFlags = 0; /* Reset for reuse. Required for e.g. Focusrite Scarlett 2i4 (1st Gen) see #310 */
    ResetEvent(packet->Signal.hEvent);
    result = PinRead(pInfo->stream->capture.pPin->handle, packet);
    ++pInfo->pending;
    return result;
}

static PaError PaPinRenderEventHandler_WaveCyclic(PaProcessThreadInfo* pInfo, unsigned eventIndex)
{
    assert( eventIndex < pInfo->stream->render.noOfPackets );

    pInfo->renderPackets[pInfo->renderHead & cPacketsArrayMask].packet = pInfo->stream->render.packets + eventIndex;
    PA_HP_TRACE((pInfo->stream->hLog, "<<< Render event : idx=%u head=%u", eventIndex, pInfo->renderHead));
    ++pInfo->renderHead;
    --pInfo->pending;
    return paNoError;
}

static PaError PaPinRenderSubmitHandler_WaveCyclic(PaProcessThreadInfo* pInfo, unsigned eventIndex)
{
    PaError result = paNoError;
    DATAPACKET* packet = pInfo->renderPackets[pInfo->renderTail & cPacketsArrayMask].packet;
    pInfo->renderPackets[pInfo->renderTail & cPacketsArrayMask].packet = 0;
    assert(packet != 0);

    PA_HP_TRACE((pInfo->stream->hLog, "Render submit : %u idx=%u", pInfo->renderTail, (unsigned)(packet - pInfo->stream->render.packets)));
    ResetEvent(packet->Signal.hEvent);
    result = PinWrite(pInfo->stream->render.pPin->handle, packet);
    /* Reset event, just in case we have an analogous situation to capture (see PaPinCaptureSubmitHandler_WaveCyclic) */
    ++pInfo->pending;
    if (pInfo->priming)
    {
        --pInfo->priming;
    }
    return result;
}

/***************************************************************************************/
/* Event and submit handlers for WaveRT                                                */
/***************************************************************************************/

static PaError PaPinCaptureEventHandler_WaveRTEvent(PaProcessThreadInfo* pInfo, unsigned eventIndex)
{
    unsigned long pos;
    unsigned realInBuf;
    unsigned frameCount;
    PaWinWdmIOInfo* pCapture = &pInfo->stream->capture;
    const unsigned halfInputBuffer = pCapture->hostBufferSize >> 1;
    PaWinWdmPin* pin = pCapture->pPin;
    DATAPACKET* packet = 0;

    /* Get hold of current ADC position */
    pin->fnAudioPosition(pin, &pos);
    /* Wrap it (robi: why not use hw latency compensation here ?? because pos then gets _way_ off from
    where it should be, i.e. at beginning or half buffer position. Why? No idea.)  */

    pos %= pCapture->hostBufferSize;
    /* Then realInBuf will point to "other" half of double buffer */
    realInBuf = pos < halfInputBuffer ? 1U : 0U;

    packet = pInfo->stream->capture.packets + realInBuf;

    /* Call barrier (or dummy) */
    pin->fnMemBarrier();

    /* Put it in queue */
    frameCount = PaUtil_WriteRingBuffer(&pInfo->stream->ringBuffer, packet->Header.Data, pCapture->framesPerBuffer);

    pInfo->capturePackets[pInfo->captureHead & cPacketsArrayMask].packet = packet;

    PA_HP_TRACE((pInfo->stream->hLog, "Capture event (WaveRT): idx=%u head=%u (pos = %4.1lf%%, frames=%u)", realInBuf, pInfo->captureHead, (pos * 100.0 / pCapture->hostBufferSize), frameCount));

    ++pInfo->captureHead;
    --pInfo->pending;

    return paNoError;
}

static PaError PaPinCaptureEventHandler_WaveRTPolled(PaProcessThreadInfo* pInfo, unsigned eventIndex)
{
    unsigned long pos;
    unsigned bytesToRead;
    PaWinWdmIOInfo* pCapture = &pInfo->stream->capture;
    const unsigned halfInputBuffer = pCapture->hostBufferSize>>1;
    PaWinWdmPin* pin = pInfo->stream->capture.pPin;

    /* Get hold of current ADC position */
    pin->fnAudioPosition(pin, &pos);
    /* Wrap it (robi: why not use hw latency compensation here ?? because pos then gets _way_ off from
    where it should be, i.e. at beginning or half buffer position. Why? No idea.)  */
    /* Compensate for HW FIFO to get to last read buffer position */
    pos += pin->hwLatency;
    pos %= pCapture->hostBufferSize;
    /* Need to align position on frame boundary */
    pos &= ~(pCapture->bytesPerFrame - 1);

    /* Call barrier (or dummy) */
    pin->fnMemBarrier();

    /* Put it in "queue" */
    bytesToRead = (pCapture->hostBufferSize + pos - pCapture->lastPosition) % pCapture->hostBufferSize;
    if (bytesToRead > 0)
    {
        unsigned frameCount = PaUtil_WriteRingBuffer(&pInfo->stream->ringBuffer,
            pCapture->hostBuffer + pCapture->lastPosition,
            bytesToRead / pCapture->bytesPerFrame);

        pCapture->lastPosition = (pCapture->lastPosition + frameCount * pCapture->bytesPerFrame) % pCapture->hostBufferSize;

        PA_HP_TRACE((pInfo->stream->hLog, "Capture event (WaveRTPolled): pos = %4.1lf%%, framesRead=%u", (pos * 100.0 / pCapture->hostBufferSize), frameCount));
        ++pInfo->captureHead;
        --pInfo->pending;
    }
    return paNoError;
}

static PaError PaPinCaptureSubmitHandler_WaveRTEvent(PaProcessThreadInfo* pInfo, unsigned eventIndex)
{
    pInfo->capturePackets[pInfo->captureTail & cPacketsArrayMask].packet = 0;
    ++pInfo->pending;
    return paNoError;
}

static PaError PaPinCaptureSubmitHandler_WaveRTPolled(PaProcessThreadInfo* pInfo, unsigned eventIndex)
{
    pInfo->capturePackets[pInfo->captureTail & cPacketsArrayMask].packet = 0;
    ++pInfo->pending;
    return paNoError;
}

static PaError PaPinRenderEventHandler_WaveRTEvent(PaProcessThreadInfo* pInfo, unsigned eventIndex)
{
    unsigned long pos;
    unsigned realOutBuf;
    PaWinWdmIOInfo* pRender = &pInfo->stream->render;
    const unsigned halfOutputBuffer = pRender->hostBufferSize >> 1;
    PaWinWdmPin* pin = pInfo->stream->render.pPin;
    PaIOPacket* ioPacket = &pInfo->renderPackets[pInfo->renderHead & cPacketsArrayMask];

    /* Get hold of current DAC position */
    pin->fnAudioPosition(pin, &pos);
    /* Compensate for HW FIFO to get to last read buffer position */
    pos += pin->hwLatency;
    /* Wrap it */
    pos %= pRender->hostBufferSize;
    /* And align it, not sure its really needed though */
    pos &= ~(pRender->bytesPerFrame - 1);
    /* Then realOutBuf will point to "other" half of double buffer */
    realOutBuf = pos < halfOutputBuffer ? 1U : 0U;

    if (pInfo->priming)
    {
        realOutBuf = pInfo->renderHead & 0x1;
    }
    ioPacket->packet = pInfo->stream->render.packets + realOutBuf;
    ioPacket->startByte = realOutBuf * halfOutputBuffer;
    ioPacket->lengthBytes = halfOutputBuffer;

    PA_HP_TRACE((pInfo->stream->hLog, "Render event (WaveRT) : idx=%u head=%u (pos = %4.1lf%%)", realOutBuf, pInfo->renderHead, (pos * 100.0 / pRender->hostBufferSize) ));

    ++pInfo->renderHead;
    --pInfo->pending;
    return paNoError;
}

static PaError PaPinRenderEventHandler_WaveRTPolled(PaProcessThreadInfo* pInfo, unsigned eventIndex)
{
    unsigned long pos;
    unsigned realOutBuf;
    unsigned bytesToWrite;

    PaWinWdmIOInfo* pRender = &pInfo->stream->render;
    const unsigned halfOutputBuffer = pRender->hostBufferSize >> 1;
    PaWinWdmPin* pin = pInfo->stream->render.pPin;
    PaIOPacket* ioPacket = &pInfo->renderPackets[pInfo->renderHead & cPacketsArrayMask];

    /* Get hold of current DAC position */
    pin->fnAudioPosition(pin, &pos);
    /* Compensate for HW FIFO to get to last read buffer position */
    pos += pin->hwLatency;
    /* Wrap it */
    pos %= pRender->hostBufferSize;
    /* And align it, not sure its really needed though */
    pos &= ~(pRender->bytesPerFrame - 1);

    if (pInfo->priming)
    {
        realOutBuf = pInfo->renderHead & 0x1;
        ioPacket->packet = pInfo->stream->render.packets + realOutBuf;
        ioPacket->startByte = realOutBuf * halfOutputBuffer;
        ioPacket->lengthBytes = halfOutputBuffer;
        ++pInfo->renderHead;
        --pInfo->pending;
    }
    else
    {
        bytesToWrite = (pRender->hostBufferSize + pos - pRender->lastPosition) % pRender->hostBufferSize;
        ++pRender->pollCntr;
        if (bytesToWrite >= halfOutputBuffer)
        {
            realOutBuf = (pos < halfOutputBuffer) ? 1U : 0U;
            ioPacket->packet = pInfo->stream->render.packets + realOutBuf;
            pRender->lastPosition = realOutBuf ? 0U : halfOutputBuffer;
            ioPacket->startByte = realOutBuf * halfOutputBuffer;
            ioPacket->lengthBytes = halfOutputBuffer;
            ++pInfo->renderHead;
            --pInfo->pending;
            PA_HP_TRACE((pInfo->stream->hLog, "Render event (WaveRTPolled) : idx=%u head=%u (pos = %4.1lf%%, cnt=%u)", realOutBuf, pInfo->renderHead, (pos * 100.0 / pRender->hostBufferSize), pRender->pollCntr));
            pRender->pollCntr = 0;
        }
    }
    return paNoError;
}

static PaError PaPinRenderSubmitHandler_WaveRTEvent(PaProcessThreadInfo* pInfo, unsigned eventIndex)
{
    PaWinWdmPin* pin = pInfo->stream->render.pPin;
    pInfo->renderPackets[pInfo->renderTail & cPacketsArrayMask].packet = 0;
    /* Call barrier (if needed) */
    pin->fnMemBarrier();
    PA_HP_TRACE((pInfo->stream->hLog, "Render submit (WaveRT) : submit=%u", pInfo->renderTail));
    ++pInfo->pending;
    if (pInfo->priming)
    {
        --pInfo->priming;
        if (pInfo->priming)
        {
            PA_HP_TRACE((pInfo->stream->hLog, "Setting WaveRT event for priming (2)"));
            SetEvent(pInfo->stream->render.events[0]);
        }
    }
    return paNoError;
}

static PaError PaPinRenderSubmitHandler_WaveRTPolled(PaProcessThreadInfo* pInfo, unsigned eventIndex)
{
    PaWinWdmPin* pin = pInfo->stream->render.pPin;
    pInfo->renderPackets[pInfo->renderTail & cPacketsArrayMask].packet = 0;
    /* Call barrier (if needed) */
    pin->fnMemBarrier();
    PA_HP_TRACE((pInfo->stream->hLog, "Render submit (WaveRTPolled) : submit=%u", pInfo->renderTail));
    ++pInfo->pending;
    if (pInfo->priming)
    {
        --pInfo->priming;
        if (pInfo->priming)
        {
            PA_HP_TRACE((pInfo->stream->hLog, "Setting WaveRT event for priming (2)"));
            SetEvent(pInfo->stream->render.events[0]);
        }
    }
    return paNoError;
}
#endif
