/*
 * Copyright (c) 2015 Mark Harmstone
 * Copyright (c) 2015 Andrew Eikum for CodeWeavers
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

#include <stdarg.h>

#define NONAMELESSUNION
#define COBJMACROS

#include "xaudio_private.h"

#include "ole2.h"
#include "rpcproxy.h"

#include "xapofx.h"

#include "wine/debug.h"

WINE_DEFAULT_DEBUG_CHANNEL(xaudio2);
WINE_DECLARE_DEBUG_CHANNEL(winediag);

static ALCdevice *(ALC_APIENTRY *palcLoopbackOpenDeviceSOFT)(const ALCchar*);
static void (ALC_APIENTRY *palcRenderSamplesSOFT)(ALCdevice*, ALCvoid*, ALCsizei);
static ALCboolean (ALC_APIENTRY *palcSetThreadContext)(ALCcontext*);

static HINSTANCE instance;

#define IN_AL_PERIODS 4

#if XAUDIO2_VER == 0
#define COMPAT_E_INVALID_CALL E_INVALIDARG
#define COMPAT_E_DEVICE_INVALIDATED XAUDIO20_E_DEVICE_INVALIDATED
#else
#define COMPAT_E_INVALID_CALL XAUDIO2_E_INVALID_CALL
#define COMPAT_E_DEVICE_INVALIDATED XAUDIO2_E_DEVICE_INVALIDATED
#endif

static void dump_fmt(const WAVEFORMATEX *fmt)
{
    TRACE("wFormatTag: 0x%x (", fmt->wFormatTag);
    switch(fmt->wFormatTag){
#define DOCASE(x) case x: TRACE(#x); break;
    DOCASE(WAVE_FORMAT_PCM)
    DOCASE(WAVE_FORMAT_IEEE_FLOAT)
    DOCASE(WAVE_FORMAT_EXTENSIBLE)
#undef DOCASE
    default:
        TRACE("Unknown");
        break;
    }
    TRACE(")\n");

    TRACE("nChannels: %u\n", fmt->nChannels);
    TRACE("nSamplesPerSec: %u\n", fmt->nSamplesPerSec);
    TRACE("nAvgBytesPerSec: %u\n", fmt->nAvgBytesPerSec);
    TRACE("nBlockAlign: %u\n", fmt->nBlockAlign);
    TRACE("wBitsPerSample: %u\n", fmt->wBitsPerSample);
    TRACE("cbSize: %u\n", fmt->cbSize);

    if(fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE){
        WAVEFORMATEXTENSIBLE *fmtex = (void*)fmt;
        TRACE("dwChannelMask: %08x\n", fmtex->dwChannelMask);
        TRACE("Samples: %04x\n", fmtex->Samples.wReserved);
        TRACE("SubFormat: %s\n", wine_dbgstr_guid(&fmtex->SubFormat));
    }else if(fmt->wFormatTag == WAVE_FORMAT_ADPCM){
        ADPCMWAVEFORMAT *fmtadpcm = (void*)fmt;
        TRACE("wSamplesPerBlock: %u\n", fmtadpcm->wSamplesPerBlock);
    }
}

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD reason, void *pReserved)
{
    TRACE("(%p, %d, %p)\n", hinstDLL, reason, pReserved);

    switch (reason)
    {
    case DLL_WINE_PREATTACH:
        return FALSE;  /* prefer native version */
    case DLL_PROCESS_ATTACH:
        instance = hinstDLL;
        DisableThreadLibraryCalls( hinstDLL );

        if(!alcIsExtensionPresent(NULL, "ALC_SOFT_loopback") ||
                !(palcLoopbackOpenDeviceSOFT = alcGetProcAddress(NULL, "alcLoopbackOpenDeviceSOFT")) ||
                !(palcRenderSamplesSOFT = alcGetProcAddress(NULL, "alcRenderSamplesSOFT"))){
            ERR("XAudio2 requires the ALC_SOFT_loopback extension (OpenAL-Soft >= 1.14)\n");
            return FALSE;
        }

        if(!alcIsExtensionPresent(NULL, "ALC_EXT_thread_local_context") ||
                !(palcSetThreadContext = alcGetProcAddress(NULL, "alcSetThreadContext"))){
            ERR("XAudio2 requires the ALC_EXT_thread_local_context extension (OpenAL-Soft >= 1.12)\n");
            return FALSE;
        }

        break;
    }
    return TRUE;
}

HRESULT WINAPI DllCanUnloadNow(void)
{
    return S_FALSE;
}

HRESULT WINAPI DllRegisterServer(void)
{
    TRACE("\n");
    return __wine_register_resources(instance);
}

HRESULT WINAPI DllUnregisterServer(void)
{
    TRACE("\n");
    return __wine_unregister_resources(instance);
}

static XA2SourceImpl *impl_from_IXAudio2SourceVoice(IXAudio2SourceVoice *iface)
{
    return CONTAINING_RECORD(iface, XA2SourceImpl, IXAudio2SourceVoice_iface);
}

static XA2SubmixImpl *impl_from_IXAudio2SubmixVoice(IXAudio2SubmixVoice *iface)
{
    return CONTAINING_RECORD(iface, XA2SubmixImpl, IXAudio2SubmixVoice_iface);
}

static inline IXAudio2Impl *impl_from_IXAudio2(IXAudio2 *iface)
{
    return CONTAINING_RECORD(iface, IXAudio2Impl, IXAudio2_iface);
}

static IXAudio2Impl *impl_from_IXAudio2MasteringVoice(IXAudio2MasteringVoice *iface)
{
    return CONTAINING_RECORD(iface, IXAudio2Impl, IXAudio2MasteringVoice_iface);
}

static DWORD get_channel_mask(unsigned int channels)
{
    switch(channels){
    case 0:
        return 0;
    case 1:
        return KSAUDIO_SPEAKER_MONO;
    case 2:
        return KSAUDIO_SPEAKER_STEREO;
    case 3:
        return KSAUDIO_SPEAKER_STEREO | SPEAKER_LOW_FREQUENCY;
    case 4:
        return KSAUDIO_SPEAKER_QUAD;    /* not _SURROUND */
    case 5:
        return KSAUDIO_SPEAKER_QUAD | SPEAKER_LOW_FREQUENCY;
    case 6:
        return KSAUDIO_SPEAKER_5POINT1; /* not 5POINT1_SURROUND */
    case 7:
        return KSAUDIO_SPEAKER_5POINT1 | SPEAKER_BACK_CENTER;
    case 8:
        return KSAUDIO_SPEAKER_7POINT1_SURROUND; /* Vista deprecates 7POINT1 */
    }
    FIXME("Unknown speaker configuration: %u\n", channels);
    return 0;
}

static void WINAPI XA2SRC_GetVoiceDetails(IXAudio2SourceVoice *iface,
        XAUDIO2_VOICE_DETAILS *pVoiceDetails)
{
    XA2SourceImpl *This = impl_from_IXAudio2SourceVoice(iface);

    TRACE("%p, %p\n", This, pVoiceDetails);

    pVoiceDetails->CreationFlags = 0;
    pVoiceDetails->ActiveFlags = 0;
    pVoiceDetails->InputChannels = This->fmt->nChannels;
    pVoiceDetails->InputSampleRate = This->fmt->nSamplesPerSec;
}

static HRESULT WINAPI XA2SRC_SetOutputVoices(IXAudio2SourceVoice *iface,
        const XAUDIO2_VOICE_SENDS *pSendList)
{
    XA2SourceImpl *This = impl_from_IXAudio2SourceVoice(iface);
    int i;
    XAUDIO2_VOICE_SENDS def_send;
    XAUDIO2_SEND_DESCRIPTOR def_desc;

    TRACE("%p, %p\n", This, pSendList);

    if(!pSendList){
        def_desc.Flags = 0;
        def_desc.pOutputVoice = (IXAudio2Voice*)&This->xa2->IXAudio2MasteringVoice_iface;

        def_send.SendCount = 1;
        def_send.pSends = &def_desc;

        pSendList = &def_send;
    }

    if(TRACE_ON(xaudio2)){
        for(i = 0; i < pSendList->SendCount; ++i){
            XAUDIO2_SEND_DESCRIPTOR *desc = &pSendList->pSends[i];
            TRACE("Outputting to: 0x%x, %p\n", desc->Flags, desc->pOutputVoice);
        }
    }

    if(This->nsends < pSendList->SendCount){
        HeapFree(GetProcessHeap(), 0, This->sends);
        This->sends = HeapAlloc(GetProcessHeap(), 0, sizeof(*This->sends) * pSendList->SendCount);
        This->nsends = pSendList->SendCount;
    }else
        memset(This->sends, 0, sizeof(*This->sends) * This->nsends);

    memcpy(This->sends, pSendList->pSends, sizeof(*This->sends) * pSendList->SendCount);

    return S_OK;
}

static HRESULT WINAPI XA2SRC_SetEffectChain(IXAudio2SourceVoice *iface,
        const XAUDIO2_EFFECT_CHAIN *pEffectChain)
{
    XA2SourceImpl *This = impl_from_IXAudio2SourceVoice(iface);
    TRACE("%p, %p\n", This, pEffectChain);
    return S_OK;
}

static HRESULT WINAPI XA2SRC_EnableEffect(IXAudio2SourceVoice *iface,
        UINT32 EffectIndex, UINT32 OperationSet)
{
    XA2SourceImpl *This = impl_from_IXAudio2SourceVoice(iface);
    TRACE("%p, %u, 0x%x\n", This, EffectIndex, OperationSet);
    return S_OK;
}

static HRESULT WINAPI XA2SRC_DisableEffect(IXAudio2SourceVoice *iface,
        UINT32 EffectIndex, UINT32 OperationSet)
{
    XA2SourceImpl *This = impl_from_IXAudio2SourceVoice(iface);
    TRACE("%p, %u, 0x%x\n", This, EffectIndex, OperationSet);
    return S_OK;
}

static void WINAPI XA2SRC_GetEffectState(IXAudio2SourceVoice *iface,
        UINT32 EffectIndex, BOOL *pEnabled)
{
    XA2SourceImpl *This = impl_from_IXAudio2SourceVoice(iface);
    TRACE("%p, %u, %p\n", This, EffectIndex, pEnabled);
}

static HRESULT WINAPI XA2SRC_SetEffectParameters(IXAudio2SourceVoice *iface,
        UINT32 EffectIndex, const void *pParameters, UINT32 ParametersByteSize,
        UINT32 OperationSet)
{
    XA2SourceImpl *This = impl_from_IXAudio2SourceVoice(iface);
    TRACE("%p, %u, %p, 0x%x, 0x%x\n", This, EffectIndex, pParameters,
            ParametersByteSize, OperationSet);
    return S_OK;
}

static HRESULT WINAPI XA2SRC_GetEffectParameters(IXAudio2SourceVoice *iface,
        UINT32 EffectIndex, void *pParameters, UINT32 ParametersByteSize)
{
    XA2SourceImpl *This = impl_from_IXAudio2SourceVoice(iface);
    TRACE("%p, %u, %p, 0x%x\n", This, EffectIndex, pParameters,
            ParametersByteSize);
    return S_OK;
}

static HRESULT WINAPI XA2SRC_SetFilterParameters(IXAudio2SourceVoice *iface,
        const XAUDIO2_FILTER_PARAMETERS *pParameters, UINT32 OperationSet)
{
    XA2SourceImpl *This = impl_from_IXAudio2SourceVoice(iface);
    TRACE("%p, %p, 0x%x\n", This, pParameters, OperationSet);
    return S_OK;
}

static void WINAPI XA2SRC_GetFilterParameters(IXAudio2SourceVoice *iface,
        XAUDIO2_FILTER_PARAMETERS *pParameters)
{
    XA2SourceImpl *This = impl_from_IXAudio2SourceVoice(iface);
    TRACE("%p, %p\n", This, pParameters);
}

static HRESULT WINAPI XA2SRC_SetOutputFilterParameters(IXAudio2SourceVoice *iface,
        IXAudio2Voice *pDestinationVoice,
        const XAUDIO2_FILTER_PARAMETERS *pParameters, UINT32 OperationSet)
{
    XA2SourceImpl *This = impl_from_IXAudio2SourceVoice(iface);
    TRACE("%p, %p, %p, 0x%x\n", This, pDestinationVoice, pParameters, OperationSet);
    return S_OK;
}

static void WINAPI XA2SRC_GetOutputFilterParameters(IXAudio2SourceVoice *iface,
        IXAudio2Voice *pDestinationVoice,
        XAUDIO2_FILTER_PARAMETERS *pParameters)
{
    XA2SourceImpl *This = impl_from_IXAudio2SourceVoice(iface);
    TRACE("%p, %p, %p\n", This, pDestinationVoice, pParameters);
}

static HRESULT WINAPI XA2SRC_SetVolume(IXAudio2SourceVoice *iface, float Volume,
        UINT32 OperationSet)
{
    XA2SourceImpl *This = impl_from_IXAudio2SourceVoice(iface);
    ALfloat al_gain;

    TRACE("%p, %f, 0x%x\n", This, Volume, OperationSet);

    al_gain = Volume;

    palcSetThreadContext(This->xa2->al_ctx);

    alSourcef(This->al_src, AL_GAIN, al_gain);

    return S_OK;
}

static void WINAPI XA2SRC_GetVolume(IXAudio2SourceVoice *iface, float *pVolume)
{
    XA2SourceImpl *This = impl_from_IXAudio2SourceVoice(iface);
    TRACE("%p, %p\n", This, pVolume);
}

static HRESULT WINAPI XA2SRC_SetChannelVolumes(IXAudio2SourceVoice *iface,
        UINT32 Channels, const float *pVolumes, UINT32 OperationSet)
{
    XA2SourceImpl *This = impl_from_IXAudio2SourceVoice(iface);
    TRACE("%p, %u, %p, 0x%x\n", This, Channels, pVolumes, OperationSet);
    return S_OK;
}

static void WINAPI XA2SRC_GetChannelVolumes(IXAudio2SourceVoice *iface,
        UINT32 Channels, float *pVolumes)
{
    XA2SourceImpl *This = impl_from_IXAudio2SourceVoice(iface);
    TRACE("%p, %u, %p\n", This, Channels, pVolumes);
}

static HRESULT WINAPI XA2SRC_SetOutputMatrix(IXAudio2SourceVoice *iface,
        IXAudio2Voice *pDestinationVoice, UINT32 SourceChannels,
        UINT32 DestinationChannels, const float *pLevelMatrix,
        UINT32 OperationSet)
{
    XA2SourceImpl *This = impl_from_IXAudio2SourceVoice(iface);
    TRACE("%p, %p, %u, %u, %p, 0x%x\n", This, pDestinationVoice,
            SourceChannels, DestinationChannels, pLevelMatrix, OperationSet);
    return S_OK;
}

static void WINAPI XA2SRC_GetOutputMatrix(IXAudio2SourceVoice *iface,
        IXAudio2Voice *pDestinationVoice, UINT32 SourceChannels,
        UINT32 DestinationChannels, float *pLevelMatrix)
{
    XA2SourceImpl *This = impl_from_IXAudio2SourceVoice(iface);
    TRACE("%p, %p, %u, %u, %p\n", This, pDestinationVoice,
            SourceChannels, DestinationChannels, pLevelMatrix);
}

static void WINAPI XA2SRC_DestroyVoice(IXAudio2SourceVoice *iface)
{
    XA2SourceImpl *This = impl_from_IXAudio2SourceVoice(iface);
    ALint processed;

    TRACE("%p\n", This);

    palcSetThreadContext(This->xa2->al_ctx);

    EnterCriticalSection(&This->lock);

    if(!This->in_use){
        LeaveCriticalSection(&This->lock);
        return;
    }

    This->in_use = FALSE;

    This->running = FALSE;

    IXAudio2SourceVoice_Stop(iface, 0, 0);

    alSourceStop(This->al_src);

    /* unqueue all buffers */
    alSourcei(This->al_src, AL_BUFFER, AL_NONE);

    alGetSourcei(This->al_src, AL_BUFFERS_PROCESSED, &processed);

    if(processed > 0){
        ALuint al_buffers[XAUDIO2_MAX_QUEUED_BUFFERS];

        alSourceUnqueueBuffers(This->al_src, processed, al_buffers);
    }

    HeapFree(GetProcessHeap(), 0, This->fmt);

    alDeleteBuffers(XAUDIO2_MAX_QUEUED_BUFFERS, This->al_bufs);
    alDeleteSources(1, &This->al_src);

    This->in_al_bytes = 0;
    This->al_bufs_used = 0;
    This->played_frames = 0;
    This->nbufs = 0;
    This->first_buf = 0;
    This->cur_buf = 0;

    LeaveCriticalSection(&This->lock);
}

static HRESULT WINAPI XA2SRC_Start(IXAudio2SourceVoice *iface, UINT32 Flags,
        UINT32 OperationSet)
{
    XA2SourceImpl *This = impl_from_IXAudio2SourceVoice(iface);

    TRACE("%p, 0x%x, 0x%x\n", This, Flags, OperationSet);

    EnterCriticalSection(&This->lock);

    This->running = TRUE;

    LeaveCriticalSection(&This->lock);

    return S_OK;
}

static HRESULT WINAPI XA2SRC_Stop(IXAudio2SourceVoice *iface, UINT32 Flags,
        UINT32 OperationSet)
{
    XA2SourceImpl *This = impl_from_IXAudio2SourceVoice(iface);

    TRACE("%p, 0x%x, 0x%x\n", This, Flags, OperationSet);

    EnterCriticalSection(&This->lock);

    This->running = FALSE;

    LeaveCriticalSection(&This->lock);

    return S_OK;
}

static ALenum get_al_format(const WAVEFORMATEX *fmt)
{
    WAVEFORMATEXTENSIBLE *fmtex = (WAVEFORMATEXTENSIBLE*)fmt;
    if(fmt->wFormatTag == WAVE_FORMAT_PCM ||
            (fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE &&
             IsEqualGUID(&fmtex->SubFormat, &KSDATAFORMAT_SUBTYPE_PCM))){
        switch(fmt->wBitsPerSample){
        case 8:
            switch(fmt->nChannels){
            case 1:
                return AL_FORMAT_MONO8;
            case 2:
                return AL_FORMAT_STEREO8;
            case 4:
                return AL_FORMAT_QUAD8;
            case 6:
                return AL_FORMAT_51CHN8;
            case 7:
                return AL_FORMAT_61CHN8;
            case 8:
                return AL_FORMAT_71CHN8;
            }
            break;
        case 16:
            switch(fmt->nChannels){
            case 1:
                return AL_FORMAT_MONO16;
            case 2:
                return AL_FORMAT_STEREO16;
            case 4:
                return AL_FORMAT_QUAD16;
            case 6:
                return AL_FORMAT_51CHN16;
            case 7:
                return AL_FORMAT_61CHN16;
            case 8:
                return AL_FORMAT_71CHN16;
            }
            break;
        }
    }else if(fmt->wFormatTag == WAVE_FORMAT_IEEE_FLOAT ||
            (fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE &&
             IsEqualGUID(&fmtex->SubFormat, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT))){
        if(fmt->wBitsPerSample == 32){
            switch(fmt->nChannels){
            case 1:
                return AL_FORMAT_MONO_FLOAT32;
            case 2:
                return AL_FORMAT_STEREO_FLOAT32;
            }
        }
    }
    return 0;
}

static HRESULT WINAPI XA2SRC_SubmitSourceBuffer(IXAudio2SourceVoice *iface,
        const XAUDIO2_BUFFER *pBuffer, const XAUDIO2_BUFFER_WMA *pBufferWMA)
{
    XA2SourceImpl *This = impl_from_IXAudio2SourceVoice(iface);
    XA2Buffer *buf;
    UINT32 buf_idx;

    TRACE("%p, %p, %p\n", This, pBuffer, pBufferWMA);

    if(TRACE_ON(xaudio2)){
        TRACE("Flags: 0x%x\n", pBuffer->Flags);
        TRACE("AudioBytes: %u\n", pBuffer->AudioBytes);
        TRACE("pAudioData: %p\n", pBuffer->pAudioData);
        TRACE("PlayBegin: %u\n", pBuffer->PlayBegin);
        TRACE("PlayLength: %u\n", pBuffer->PlayLength);
        TRACE("LoopBegin: %u\n", pBuffer->LoopBegin);
        TRACE("LoopLength: %u\n", pBuffer->LoopLength);
        TRACE("LoopCount: %u\n", pBuffer->LoopCount);
        TRACE("pContext: %p\n", pBuffer->pContext);
    }

    EnterCriticalSection(&This->lock);

    if(This->nbufs >= XAUDIO2_MAX_QUEUED_BUFFERS){
        TRACE("Too many buffers queued!\n");
        LeaveCriticalSection(&This->lock);
        return COMPAT_E_INVALID_CALL;
    }

    buf_idx = (This->first_buf + This->nbufs) % XAUDIO2_MAX_QUEUED_BUFFERS;
    buf = &This->buffers[buf_idx];
    memset(buf, 0, sizeof(*buf));

    /* API contract: pAudioData must remain valid until this buffer is played,
     * but pBuffer itself may be reused immediately */
    memcpy(&buf->xa2buffer, pBuffer, sizeof(*pBuffer));

#if XAUDIO2_VER == 0
    if(buf->xa2buffer.LoopCount == XAUDIO20_LOOP_INFINITE)
        buf->xa2buffer.LoopCount = XAUDIO2_LOOP_INFINITE;
#endif

    /* convert samples offsets to bytes */
    if(This->fmt->wFormatTag == WAVE_FORMAT_ADPCM){
        /* ADPCM gives us a number of samples per block, so round down to
         * nearest block and convert to bytes */
        buf->xa2buffer.PlayBegin = buf->xa2buffer.PlayBegin / ((ADPCMWAVEFORMAT*)This->fmt)->wSamplesPerBlock * This->fmt->nBlockAlign;
        buf->xa2buffer.PlayLength = buf->xa2buffer.PlayLength / ((ADPCMWAVEFORMAT*)This->fmt)->wSamplesPerBlock * This->fmt->nBlockAlign;
        buf->xa2buffer.LoopBegin = buf->xa2buffer.LoopBegin / ((ADPCMWAVEFORMAT*)This->fmt)->wSamplesPerBlock * This->fmt->nBlockAlign;
        buf->xa2buffer.LoopLength = buf->xa2buffer.LoopLength / ((ADPCMWAVEFORMAT*)This->fmt)->wSamplesPerBlock * This->fmt->nBlockAlign;
    }else{
        buf->xa2buffer.PlayBegin *= This->fmt->nBlockAlign;
        buf->xa2buffer.PlayLength *= This->fmt->nBlockAlign;
        buf->xa2buffer.LoopBegin *= This->fmt->nBlockAlign;
        buf->xa2buffer.LoopLength *= This->fmt->nBlockAlign;
    }

    if(buf->xa2buffer.PlayLength == 0)
        /* set to end of buffer */
        buf->xa2buffer.PlayLength = buf->xa2buffer.AudioBytes - buf->xa2buffer.PlayBegin;

    buf->play_end_bytes = buf->xa2buffer.PlayBegin + buf->xa2buffer.PlayLength;

    if(buf->xa2buffer.LoopCount){
        if(buf->xa2buffer.LoopLength == 0)
            /* set to end of play range */
            buf->xa2buffer.LoopLength = buf->play_end_bytes - buf->xa2buffer.LoopBegin;

        if(buf->xa2buffer.LoopBegin >= buf->play_end_bytes){
            /* this actually crashes on native xaudio 2.7 */
            LeaveCriticalSection(&This->lock);
            return COMPAT_E_INVALID_CALL;
        }

        buf->loop_end_bytes = buf->xa2buffer.LoopBegin + buf->xa2buffer.LoopLength;

        /* xaudio 2.7 allows some invalid looping setups, but later versions
         * return an error */
#if XAUDIO2_VER > 7
        if(buf->loop_end_bytes > buf->play_end_bytes){
            LeaveCriticalSection(&This->lock);
            return COMPAT_E_INVALID_CALL;
        }

        if(buf->loop_end_bytes <= buf->xa2buffer.PlayBegin){
            LeaveCriticalSection(&This->lock);
            return COMPAT_E_INVALID_CALL;
        }
#else
        if(buf->loop_end_bytes <= buf->xa2buffer.PlayBegin){
            buf->xa2buffer.LoopCount = 0;
            buf->loop_end_bytes = buf->play_end_bytes;
        }
#endif
    }else{
        buf->xa2buffer.LoopLength = buf->xa2buffer.PlayLength;
        buf->xa2buffer.LoopBegin = buf->xa2buffer.PlayBegin;
        buf->loop_end_bytes = buf->play_end_bytes;
    }

    buf->offs_bytes = buf->xa2buffer.PlayBegin;
    buf->cur_end_bytes = buf->loop_end_bytes;

    buf->latest_al_buf = -1;

    ++This->nbufs;

    TRACE("%p: queued buffer %u (%u bytes), now %u buffers held\n",
            This, buf_idx, buf->xa2buffer.AudioBytes, This->nbufs);

    LeaveCriticalSection(&This->lock);

    return S_OK;
}

static HRESULT WINAPI XA2SRC_FlushSourceBuffers(IXAudio2SourceVoice *iface)
{
    UINT i, first, last, to_flush;
    XA2SourceImpl *This = impl_from_IXAudio2SourceVoice(iface);

    TRACE("%p\n", This);

    EnterCriticalSection(&This->lock);

    if(This->running && This->nbufs > 0){
        /* when running, flush only completely unused buffers; the rest remain
         * in queue */
        last = (This->first_buf + This->nbufs) % XAUDIO2_MAX_QUEUED_BUFFERS;
        first = (This->cur_buf + 1) % XAUDIO2_MAX_QUEUED_BUFFERS;
        if(This->cur_buf == last)
            /* nothing to do */
            to_flush = 0;
        else if(last >= first)
            to_flush = last - first;
        else
            to_flush = last + XAUDIO2_MAX_QUEUED_BUFFERS - first;
    }else{
        /* when stopped, flush all buffers */
        first = This->first_buf;
        last = (This->first_buf + This->nbufs) % XAUDIO2_MAX_QUEUED_BUFFERS;
        to_flush = This->nbufs;
    }


    for(i = first;
            i < (first + to_flush) % XAUDIO2_MAX_QUEUED_BUFFERS;
            i = (i + 1) % XAUDIO2_MAX_QUEUED_BUFFERS){
        if(This->cb)
            IXAudio2VoiceCallback_OnBufferEnd(This->cb,
                    This->buffers[i].xa2buffer.pContext);
    }

    This->nbufs -= to_flush;
    This->cur_buf = (This->first_buf + This->nbufs) % XAUDIO2_MAX_QUEUED_BUFFERS;

    LeaveCriticalSection(&This->lock);

    return S_OK;
}

static HRESULT WINAPI XA2SRC_Discontinuity(IXAudio2SourceVoice *iface)
{
    XA2SourceImpl *This = impl_from_IXAudio2SourceVoice(iface);

    TRACE("%p\n", This);

    EnterCriticalSection(&This->lock);

    if(This->nbufs > 0){
        DWORD last = (This->first_buf + This->nbufs - 1) % XAUDIO2_MAX_QUEUED_BUFFERS;
        This->buffers[last].xa2buffer.Flags |= XAUDIO2_END_OF_STREAM;
    }

    LeaveCriticalSection(&This->lock);

    return S_OK;
}

static HRESULT WINAPI XA2SRC_ExitLoop(IXAudio2SourceVoice *iface, UINT32 OperationSet)
{
    XA2SourceImpl *This = impl_from_IXAudio2SourceVoice(iface);

    TRACE("%p, 0x%x\n", This, OperationSet);

    EnterCriticalSection(&This->lock);

    This->buffers[This->cur_buf].looped = XAUDIO2_LOOP_INFINITE;

    LeaveCriticalSection(&This->lock);

    return S_OK;
}

static void WINAPI XA2SRC_GetState(IXAudio2SourceVoice *iface,
        XAUDIO2_VOICE_STATE *pVoiceState, UINT32 Flags)
{
    XA2SourceImpl *This = impl_from_IXAudio2SourceVoice(iface);

    TRACE("%p, %p, 0x%x\n", This, pVoiceState, Flags);

    EnterCriticalSection(&This->lock);

    if(!(Flags & XAUDIO2_VOICE_NOSAMPLESPLAYED))
        pVoiceState->SamplesPlayed = This->played_frames;
    else
        pVoiceState->SamplesPlayed = 0;

    if(This->nbufs)
        pVoiceState->pCurrentBufferContext = This->buffers[This->first_buf].xa2buffer.pContext;
    else
        pVoiceState->pCurrentBufferContext = NULL;

    pVoiceState->BuffersQueued = This->nbufs;

    LeaveCriticalSection(&This->lock);

    TRACE("returning %s, queued: %u\n", wine_dbgstr_longlong(pVoiceState->SamplesPlayed), This->nbufs);
}

static HRESULT WINAPI XA2SRC_SetFrequencyRatio(IXAudio2SourceVoice *iface,
        float Ratio, UINT32 OperationSet)
{
    XA2SourceImpl *This = impl_from_IXAudio2SourceVoice(iface);
    ALfloat r;

    TRACE("%p, %f, 0x%x\n", This, Ratio, OperationSet);

    if(Ratio < XAUDIO2_MIN_FREQ_RATIO)
        r = XAUDIO2_MIN_FREQ_RATIO;
    else if (Ratio > XAUDIO2_MAX_FREQ_RATIO)
        r = XAUDIO2_MAX_FREQ_RATIO;
    else
        r = Ratio;

    palcSetThreadContext(This->xa2->al_ctx);

    alSourcef(This->al_src, AL_PITCH, r);

    return S_OK;
}

static void WINAPI XA2SRC_GetFrequencyRatio(IXAudio2SourceVoice *iface, float *pRatio)
{
    ALfloat ratio;
    XA2SourceImpl *This = impl_from_IXAudio2SourceVoice(iface);

    TRACE("%p, %p\n", This, pRatio);

    palcSetThreadContext(This->xa2->al_ctx);

    alGetSourcef(This->al_src, AL_PITCH, &ratio);

    *pRatio = ratio;
}

static HRESULT WINAPI XA2SRC_SetSourceSampleRate(
    IXAudio2SourceVoice *iface,
    UINT32 NewSourceSampleRate)
{
    XA2SourceImpl *This = impl_from_IXAudio2SourceVoice(iface);

    TRACE("%p, %u\n", This, NewSourceSampleRate);

    EnterCriticalSection(&This->lock);

    if(This->nbufs){
        LeaveCriticalSection(&This->lock);
        return COMPAT_E_INVALID_CALL;
    }

    This->fmt->nSamplesPerSec = NewSourceSampleRate;

    LeaveCriticalSection(&This->lock);

    return S_OK;
}

static const IXAudio2SourceVoiceVtbl XAudio2SourceVoice_Vtbl = {
    XA2SRC_GetVoiceDetails,
    XA2SRC_SetOutputVoices,
    XA2SRC_SetEffectChain,
    XA2SRC_EnableEffect,
    XA2SRC_DisableEffect,
    XA2SRC_GetEffectState,
    XA2SRC_SetEffectParameters,
    XA2SRC_GetEffectParameters,
    XA2SRC_SetFilterParameters,
    XA2SRC_GetFilterParameters,
    XA2SRC_SetOutputFilterParameters,
    XA2SRC_GetOutputFilterParameters,
    XA2SRC_SetVolume,
    XA2SRC_GetVolume,
    XA2SRC_SetChannelVolumes,
    XA2SRC_GetChannelVolumes,
    XA2SRC_SetOutputMatrix,
    XA2SRC_GetOutputMatrix,
    XA2SRC_DestroyVoice,
    XA2SRC_Start,
    XA2SRC_Stop,
    XA2SRC_SubmitSourceBuffer,
    XA2SRC_FlushSourceBuffers,
    XA2SRC_Discontinuity,
    XA2SRC_ExitLoop,
    XA2SRC_GetState,
    XA2SRC_SetFrequencyRatio,
    XA2SRC_GetFrequencyRatio,
    XA2SRC_SetSourceSampleRate
};

static void WINAPI XA2M_GetVoiceDetails(IXAudio2MasteringVoice *iface,
        XAUDIO2_VOICE_DETAILS *pVoiceDetails)
{
    IXAudio2Impl *This = impl_from_IXAudio2MasteringVoice(iface);
    TRACE("%p, %p\n", This, pVoiceDetails);
    pVoiceDetails->CreationFlags = 0;
    pVoiceDetails->ActiveFlags = 0;
    pVoiceDetails->InputChannels = This->fmt.Format.nChannels;
    pVoiceDetails->InputSampleRate = This->fmt.Format.nSamplesPerSec;
}

static HRESULT WINAPI XA2M_SetOutputVoices(IXAudio2MasteringVoice *iface,
        const XAUDIO2_VOICE_SENDS *pSendList)
{
    IXAudio2Impl *This = impl_from_IXAudio2MasteringVoice(iface);
    TRACE("%p, %p\n", This, pSendList);
    return S_OK;
}

static HRESULT WINAPI XA2M_SetEffectChain(IXAudio2MasteringVoice *iface,
        const XAUDIO2_EFFECT_CHAIN *pEffectChain)
{
    IXAudio2Impl *This = impl_from_IXAudio2MasteringVoice(iface);
    TRACE("%p, %p\n", This, pEffectChain);
    return S_OK;
}

static HRESULT WINAPI XA2M_EnableEffect(IXAudio2MasteringVoice *iface, UINT32 EffectIndex,
        UINT32 OperationSet)
{
    IXAudio2Impl *This = impl_from_IXAudio2MasteringVoice(iface);
    TRACE("%p, %u, 0x%x\n", This, EffectIndex, OperationSet);
    return S_OK;
}

static HRESULT WINAPI XA2M_DisableEffect(IXAudio2MasteringVoice *iface, UINT32 EffectIndex,
        UINT32 OperationSet)
{
    IXAudio2Impl *This = impl_from_IXAudio2MasteringVoice(iface);
    TRACE("%p, %u, 0x%x\n", This, EffectIndex, OperationSet);
    return S_OK;
}

static void WINAPI XA2M_GetEffectState(IXAudio2MasteringVoice *iface, UINT32 EffectIndex,
        BOOL *pEnabled)
{
    IXAudio2Impl *This = impl_from_IXAudio2MasteringVoice(iface);
    TRACE("%p, %u, %p\n", This, EffectIndex, pEnabled);
}

static HRESULT WINAPI XA2M_SetEffectParameters(IXAudio2MasteringVoice *iface,
        UINT32 EffectIndex, const void *pParameters, UINT32 ParametersByteSize,
        UINT32 OperationSet)
{
    IXAudio2Impl *This = impl_from_IXAudio2MasteringVoice(iface);
    TRACE("%p, %u, %p, 0x%x, 0x%x\n", This, EffectIndex, pParameters,
            ParametersByteSize, OperationSet);
    return S_OK;
}

static HRESULT WINAPI XA2M_GetEffectParameters(IXAudio2MasteringVoice *iface,
        UINT32 EffectIndex, void *pParameters, UINT32 ParametersByteSize)
{
    IXAudio2Impl *This = impl_from_IXAudio2MasteringVoice(iface);
    TRACE("%p, %u, %p, 0x%x\n", This, EffectIndex, pParameters,
            ParametersByteSize);
    return S_OK;
}

static HRESULT WINAPI XA2M_SetFilterParameters(IXAudio2MasteringVoice *iface,
        const XAUDIO2_FILTER_PARAMETERS *pParameters, UINT32 OperationSet)
{
    IXAudio2Impl *This = impl_from_IXAudio2MasteringVoice(iface);
    TRACE("%p, %p, 0x%x\n", This, pParameters, OperationSet);
    return S_OK;
}

static void WINAPI XA2M_GetFilterParameters(IXAudio2MasteringVoice *iface,
        XAUDIO2_FILTER_PARAMETERS *pParameters)
{
    IXAudio2Impl *This = impl_from_IXAudio2MasteringVoice(iface);
    TRACE("%p, %p\n", This, pParameters);
}

static HRESULT WINAPI XA2M_SetOutputFilterParameters(IXAudio2MasteringVoice *iface,
        IXAudio2Voice *pDestinationVoice,
        const XAUDIO2_FILTER_PARAMETERS *pParameters, UINT32 OperationSet)
{
    IXAudio2Impl *This = impl_from_IXAudio2MasteringVoice(iface);
    TRACE("%p, %p, %p, 0x%x\n", This, pDestinationVoice, pParameters, OperationSet);
    return S_OK;
}

static void WINAPI XA2M_GetOutputFilterParameters(IXAudio2MasteringVoice *iface,
        IXAudio2Voice *pDestinationVoice,
        XAUDIO2_FILTER_PARAMETERS *pParameters)
{
    IXAudio2Impl *This = impl_from_IXAudio2MasteringVoice(iface);
    TRACE("%p, %p, %p\n", This, pDestinationVoice, pParameters);
}

static HRESULT WINAPI XA2M_SetVolume(IXAudio2MasteringVoice *iface, float Volume,
        UINT32 OperationSet)
{
    IXAudio2Impl *This = impl_from_IXAudio2MasteringVoice(iface);
    TRACE("%p, %f, 0x%x\n", This, Volume, OperationSet);
    return S_OK;
}

static void WINAPI XA2M_GetVolume(IXAudio2MasteringVoice *iface, float *pVolume)
{
    IXAudio2Impl *This = impl_from_IXAudio2MasteringVoice(iface);
    TRACE("%p, %p\n", This, pVolume);
}

static HRESULT WINAPI XA2M_SetChannelVolumes(IXAudio2MasteringVoice *iface, UINT32 Channels,
        const float *pVolumes, UINT32 OperationSet)
{
    IXAudio2Impl *This = impl_from_IXAudio2MasteringVoice(iface);
    TRACE("%p, %u, %p, 0x%x\n", This, Channels, pVolumes, OperationSet);
    return S_OK;
}

static void WINAPI XA2M_GetChannelVolumes(IXAudio2MasteringVoice *iface, UINT32 Channels,
        float *pVolumes)
{
    IXAudio2Impl *This = impl_from_IXAudio2MasteringVoice(iface);
    TRACE("%p, %u, %p\n", This, Channels, pVolumes);
}

static HRESULT WINAPI XA2M_SetOutputMatrix(IXAudio2MasteringVoice *iface,
        IXAudio2Voice *pDestinationVoice, UINT32 SourceChannels,
        UINT32 DestinationChannels, const float *pLevelMatrix,
        UINT32 OperationSet)
{
    IXAudio2Impl *This = impl_from_IXAudio2MasteringVoice(iface);
    TRACE("%p, %p, %u, %u, %p, 0x%x\n", This, pDestinationVoice,
            SourceChannels, DestinationChannels, pLevelMatrix, OperationSet);
    return S_OK;
}

static void WINAPI XA2M_GetOutputMatrix(IXAudio2MasteringVoice *iface,
        IXAudio2Voice *pDestinationVoice, UINT32 SourceChannels,
        UINT32 DestinationChannels, float *pLevelMatrix)
{
    IXAudio2Impl *This = impl_from_IXAudio2MasteringVoice(iface);
    TRACE("%p, %p, %u, %u, %p\n", This, pDestinationVoice,
            SourceChannels, DestinationChannels, pLevelMatrix);
}

static void WINAPI XA2M_DestroyVoice(IXAudio2MasteringVoice *iface)
{
    IXAudio2Impl *This = impl_from_IXAudio2MasteringVoice(iface);

    TRACE("%p\n", This);

    EnterCriticalSection(&This->lock);

    if(!This->aclient){
        LeaveCriticalSection(&This->lock);
        return;
    }

    This->running = FALSE;

    IAudioRenderClient_Release(This->render);
    This->render = NULL;

    IAudioClient_Release(This->aclient);
    This->aclient = NULL;

    alcDestroyContext(This->al_ctx);
    This->al_ctx = NULL;

    alcCloseDevice(This->al_device);
    This->al_device = NULL;

    LeaveCriticalSection(&This->lock);
}

/* not present in XAudio2 2.7 */
static void WINAPI XA2M_GetChannelMask(IXAudio2MasteringVoice *iface,
        DWORD *pChannelMask)
{
    IXAudio2Impl *This = impl_from_IXAudio2MasteringVoice(iface);

    TRACE("%p %p\n", This, pChannelMask);

    *pChannelMask = This->fmt.dwChannelMask;
}

static const struct IXAudio2MasteringVoiceVtbl XAudio2MasteringVoice_Vtbl = {
    XA2M_GetVoiceDetails,
    XA2M_SetOutputVoices,
    XA2M_SetEffectChain,
    XA2M_EnableEffect,
    XA2M_DisableEffect,
    XA2M_GetEffectState,
    XA2M_SetEffectParameters,
    XA2M_GetEffectParameters,
    XA2M_SetFilterParameters,
    XA2M_GetFilterParameters,
    XA2M_SetOutputFilterParameters,
    XA2M_GetOutputFilterParameters,
    XA2M_SetVolume,
    XA2M_GetVolume,
    XA2M_SetChannelVolumes,
    XA2M_GetChannelVolumes,
    XA2M_SetOutputMatrix,
    XA2M_GetOutputMatrix,
    XA2M_DestroyVoice,
    XA2M_GetChannelMask
};

static void WINAPI XA2SUB_GetVoiceDetails(IXAudio2SubmixVoice *iface,
        XAUDIO2_VOICE_DETAILS *pVoiceDetails)
{
    XA2SubmixImpl *This = impl_from_IXAudio2SubmixVoice(iface);

    TRACE("%p, %p\n", This, pVoiceDetails);

    *pVoiceDetails = This->details;
}

static HRESULT WINAPI XA2SUB_SetOutputVoices(IXAudio2SubmixVoice *iface,
        const XAUDIO2_VOICE_SENDS *pSendList)
{
    XA2SubmixImpl *This = impl_from_IXAudio2SubmixVoice(iface);
    TRACE("%p, %p\n", This, pSendList);
    return S_OK;
}

static HRESULT WINAPI XA2SUB_SetEffectChain(IXAudio2SubmixVoice *iface,
        const XAUDIO2_EFFECT_CHAIN *pEffectChain)
{
    XA2SubmixImpl *This = impl_from_IXAudio2SubmixVoice(iface);
    TRACE("%p, %p\n", This, pEffectChain);
    return S_OK;
}

static HRESULT WINAPI XA2SUB_EnableEffect(IXAudio2SubmixVoice *iface, UINT32 EffectIndex,
        UINT32 OperationSet)
{
    XA2SubmixImpl *This = impl_from_IXAudio2SubmixVoice(iface);
    TRACE("%p, %u, 0x%x\n", This, EffectIndex, OperationSet);
    return S_OK;
}

static HRESULT WINAPI XA2SUB_DisableEffect(IXAudio2SubmixVoice *iface, UINT32 EffectIndex,
        UINT32 OperationSet)
{
    XA2SubmixImpl *This = impl_from_IXAudio2SubmixVoice(iface);
    TRACE("%p, %u, 0x%x\n", This, EffectIndex, OperationSet);
    return S_OK;
}

static void WINAPI XA2SUB_GetEffectState(IXAudio2SubmixVoice *iface, UINT32 EffectIndex,
        BOOL *pEnabled)
{
    XA2SubmixImpl *This = impl_from_IXAudio2SubmixVoice(iface);
    TRACE("%p, %u, %p\n", This, EffectIndex, pEnabled);
}

static HRESULT WINAPI XA2SUB_SetEffectParameters(IXAudio2SubmixVoice *iface,
        UINT32 EffectIndex, const void *pParameters, UINT32 ParametersByteSize,
        UINT32 OperationSet)
{
    XA2SubmixImpl *This = impl_from_IXAudio2SubmixVoice(iface);
    TRACE("%p, %u, %p, 0x%x, 0x%x\n", This, EffectIndex, pParameters,
            ParametersByteSize, OperationSet);
    return S_OK;
}

static HRESULT WINAPI XA2SUB_GetEffectParameters(IXAudio2SubmixVoice *iface,
        UINT32 EffectIndex, void *pParameters, UINT32 ParametersByteSize)
{
    XA2SubmixImpl *This = impl_from_IXAudio2SubmixVoice(iface);
    TRACE("%p, %u, %p, 0x%x\n", This, EffectIndex, pParameters,
            ParametersByteSize);
    return S_OK;
}

static HRESULT WINAPI XA2SUB_SetFilterParameters(IXAudio2SubmixVoice *iface,
        const XAUDIO2_FILTER_PARAMETERS *pParameters, UINT32 OperationSet)
{
    XA2SubmixImpl *This = impl_from_IXAudio2SubmixVoice(iface);
    TRACE("%p, %p, 0x%x\n", This, pParameters, OperationSet);
    return S_OK;
}

static void WINAPI XA2SUB_GetFilterParameters(IXAudio2SubmixVoice *iface,
        XAUDIO2_FILTER_PARAMETERS *pParameters)
{
    XA2SubmixImpl *This = impl_from_IXAudio2SubmixVoice(iface);
    TRACE("%p, %p\n", This, pParameters);
}

static HRESULT WINAPI XA2SUB_SetOutputFilterParameters(IXAudio2SubmixVoice *iface,
        IXAudio2Voice *pDestinationVoice,
        const XAUDIO2_FILTER_PARAMETERS *pParameters, UINT32 OperationSet)
{
    XA2SubmixImpl *This = impl_from_IXAudio2SubmixVoice(iface);
    TRACE("%p, %p, %p, 0x%x\n", This, pDestinationVoice, pParameters, OperationSet);
    return S_OK;
}

static void WINAPI XA2SUB_GetOutputFilterParameters(IXAudio2SubmixVoice *iface,
        IXAudio2Voice *pDestinationVoice,
        XAUDIO2_FILTER_PARAMETERS *pParameters)
{
    XA2SubmixImpl *This = impl_from_IXAudio2SubmixVoice(iface);
    TRACE("%p, %p, %p\n", This, pDestinationVoice, pParameters);
}

static HRESULT WINAPI XA2SUB_SetVolume(IXAudio2SubmixVoice *iface, float Volume,
        UINT32 OperationSet)
{
    XA2SubmixImpl *This = impl_from_IXAudio2SubmixVoice(iface);
    TRACE("%p, %f, 0x%x\n", This, Volume, OperationSet);
    return S_OK;
}

static void WINAPI XA2SUB_GetVolume(IXAudio2SubmixVoice *iface, float *pVolume)
{
    XA2SubmixImpl *This = impl_from_IXAudio2SubmixVoice(iface);
    TRACE("%p, %p\n", This, pVolume);
}

static HRESULT WINAPI XA2SUB_SetChannelVolumes(IXAudio2SubmixVoice *iface, UINT32 Channels,
        const float *pVolumes, UINT32 OperationSet)
{
    XA2SubmixImpl *This = impl_from_IXAudio2SubmixVoice(iface);
    TRACE("%p, %u, %p, 0x%x\n", This, Channels, pVolumes, OperationSet);
    return S_OK;
}

static void WINAPI XA2SUB_GetChannelVolumes(IXAudio2SubmixVoice *iface, UINT32 Channels,
        float *pVolumes)
{
    XA2SubmixImpl *This = impl_from_IXAudio2SubmixVoice(iface);
    TRACE("%p, %u, %p\n", This, Channels, pVolumes);
}

static HRESULT WINAPI XA2SUB_SetOutputMatrix(IXAudio2SubmixVoice *iface,
        IXAudio2Voice *pDestinationVoice, UINT32 SourceChannels,
        UINT32 DestinationChannels, const float *pLevelMatrix,
        UINT32 OperationSet)
{
    XA2SubmixImpl *This = impl_from_IXAudio2SubmixVoice(iface);
    TRACE("%p, %p, %u, %u, %p, 0x%x\n", This, pDestinationVoice,
            SourceChannels, DestinationChannels, pLevelMatrix, OperationSet);
    return S_OK;
}

static void WINAPI XA2SUB_GetOutputMatrix(IXAudio2SubmixVoice *iface,
        IXAudio2Voice *pDestinationVoice, UINT32 SourceChannels,
        UINT32 DestinationChannels, float *pLevelMatrix)
{
    XA2SubmixImpl *This = impl_from_IXAudio2SubmixVoice(iface);
    TRACE("%p, %p, %u, %u, %p\n", This, pDestinationVoice,
            SourceChannels, DestinationChannels, pLevelMatrix);
}

static void WINAPI XA2SUB_DestroyVoice(IXAudio2SubmixVoice *iface)
{
    XA2SubmixImpl *This = impl_from_IXAudio2SubmixVoice(iface);

    TRACE("%p\n", This);

    EnterCriticalSection(&This->lock);

    This->in_use = FALSE;

    LeaveCriticalSection(&This->lock);
}

static const struct IXAudio2SubmixVoiceVtbl XAudio2SubmixVoice_Vtbl = {
    XA2SUB_GetVoiceDetails,
    XA2SUB_SetOutputVoices,
    XA2SUB_SetEffectChain,
    XA2SUB_EnableEffect,
    XA2SUB_DisableEffect,
    XA2SUB_GetEffectState,
    XA2SUB_SetEffectParameters,
    XA2SUB_GetEffectParameters,
    XA2SUB_SetFilterParameters,
    XA2SUB_GetFilterParameters,
    XA2SUB_SetOutputFilterParameters,
    XA2SUB_GetOutputFilterParameters,
    XA2SUB_SetVolume,
    XA2SUB_GetVolume,
    XA2SUB_SetChannelVolumes,
    XA2SUB_GetChannelVolumes,
    XA2SUB_SetOutputMatrix,
    XA2SUB_GetOutputMatrix,
    XA2SUB_DestroyVoice
};

static HRESULT WINAPI IXAudio2Impl_QueryInterface(IXAudio2 *iface, REFIID riid,
        void **ppvObject)
{
    IXAudio2Impl *This = impl_from_IXAudio2(iface);

    TRACE("(%p)->(%s, %p)\n", This, debugstr_guid(riid), ppvObject);

    if(IsEqualGUID(riid, &IID_IUnknown) ||
            IsEqualGUID(riid, &IID_IXAudio28) ||
            IsEqualGUID(riid, &IID_IXAudio2))
        *ppvObject = &This->IXAudio2_iface;
    else if(IsEqualGUID(riid, &IID_IXAudio27)){
        /* all xaudio versions before 28 share an IID */
#if XAUDIO2_VER == 0
        *ppvObject = &This->IXAudio20_iface;
#elif XAUDIO2_VER <= 2
        *ppvObject = &This->IXAudio22_iface;
#elif XAUDIO2_VER <= 7
        *ppvObject = &This->IXAudio27_iface;
#else
        *ppvObject = NULL;
#endif
    }else
        *ppvObject = NULL;

    if(*ppvObject){
        IUnknown_AddRef((IUnknown*)*ppvObject);
        return S_OK;
    }

    FIXME("(%p)->(%s,%p), not found\n", This,debugstr_guid(riid), ppvObject);

    return E_NOINTERFACE;
}

static ULONG WINAPI IXAudio2Impl_AddRef(IXAudio2 *iface)
{
    IXAudio2Impl *This = impl_from_IXAudio2(iface);
    ULONG ref = InterlockedIncrement(&This->ref);
    TRACE("(%p)->(): Refcount now %u\n", This, ref);
    return ref;
}

static ULONG WINAPI IXAudio2Impl_Release(IXAudio2 *iface)
{
    IXAudio2Impl *This = impl_from_IXAudio2(iface);
    ULONG ref = InterlockedDecrement(&This->ref);

    TRACE("(%p)->(): Refcount now %u\n", This, ref);

    if (!ref) {
        int i;
        XA2SourceImpl *src, *src2;
        XA2SubmixImpl *sub, *sub2;

        if(This->engine){
            This->stop_engine = TRUE;
            SetEvent(This->mmevt);
            WaitForSingleObject(This->engine, INFINITE);
            CloseHandle(This->engine);
        }

        LIST_FOR_EACH_ENTRY_SAFE(src, src2, &This->source_voices, XA2SourceImpl, entry){
            HeapFree(GetProcessHeap(), 0, src->sends);
            IXAudio2SourceVoice_DestroyVoice(&src->IXAudio2SourceVoice_iface);
            src->lock.DebugInfo->Spare[0] = 0;
            DeleteCriticalSection(&src->lock);
            HeapFree(GetProcessHeap(), 0, src);
        }

        LIST_FOR_EACH_ENTRY_SAFE(sub, sub2, &This->submix_voices, XA2SubmixImpl, entry){
            IXAudio2SubmixVoice_DestroyVoice(&sub->IXAudio2SubmixVoice_iface);
            sub->lock.DebugInfo->Spare[0] = 0;
            DeleteCriticalSection(&sub->lock);
            HeapFree(GetProcessHeap(), 0, sub);
        }

        IXAudio2MasteringVoice_DestroyVoice(&This->IXAudio2MasteringVoice_iface);

        if(This->devenum)
            IMMDeviceEnumerator_Release(This->devenum);
        for(i = 0; i < This->ndevs; ++i)
            CoTaskMemFree(This->devids[i]);
        HeapFree(GetProcessHeap(), 0, This->devids);
        HeapFree(GetProcessHeap(), 0, This->cbs);

        CloseHandle(This->mmevt);

        This->lock.DebugInfo->Spare[0] = 0;
        DeleteCriticalSection(&This->lock);

        HeapFree(GetProcessHeap(), 0, This);
    }
    return ref;
}

static HRESULT WINAPI IXAudio2Impl_RegisterForCallbacks(IXAudio2 *iface,
        IXAudio2EngineCallback *pCallback)
{
    IXAudio2Impl *This = impl_from_IXAudio2(iface);
    int i;

    TRACE("(%p)->(%p)\n", This, pCallback);

    EnterCriticalSection(&This->lock);

    for(i = 0; i < This->ncbs; ++i){
        if(!This->cbs[i] || This->cbs[i] == pCallback){
            This->cbs[i] = pCallback;
            LeaveCriticalSection(&This->lock);
            return S_OK;
        }
    }

    This->ncbs *= 2;
    This->cbs = HeapReAlloc(GetProcessHeap(), 0, This->cbs, This->ncbs * sizeof(*This->cbs));

    This->cbs[i] = pCallback;

    LeaveCriticalSection(&This->lock);

    return S_OK;
}

static void WINAPI IXAudio2Impl_UnregisterForCallbacks(IXAudio2 *iface,
        IXAudio2EngineCallback *pCallback)
{
    IXAudio2Impl *This = impl_from_IXAudio2(iface);
    int i;

    TRACE("(%p)->(%p)\n", This, pCallback);

    EnterCriticalSection(&This->lock);

    for(i = 0; i < This->ncbs; ++i){
        if(This->cbs[i] == pCallback)
            break;
    }

    for(; i < This->ncbs - 1 && This->cbs[i + 1]; ++i)
        This->cbs[i] = This->cbs[i + 1];

    if(i < This->ncbs)
        This->cbs[i] = NULL;

    LeaveCriticalSection(&This->lock);
}

static WAVEFORMATEX *copy_waveformat(const WAVEFORMATEX *wfex)
{
    WAVEFORMATEX *pwfx;

    if(wfex->wFormatTag == WAVE_FORMAT_PCM){
        pwfx = HeapAlloc(GetProcessHeap(), 0, sizeof(WAVEFORMATEX));
        CopyMemory(pwfx, wfex, sizeof(PCMWAVEFORMAT));
        pwfx->cbSize = 0;
    }else{
        pwfx = HeapAlloc(GetProcessHeap(), 0, sizeof(WAVEFORMATEX) + wfex->cbSize);
        CopyMemory(pwfx, wfex, sizeof(WAVEFORMATEX) + wfex->cbSize);
    }

    return pwfx;
}

static HRESULT WINAPI IXAudio2Impl_CreateSourceVoice(IXAudio2 *iface,
        IXAudio2SourceVoice **ppSourceVoice, const WAVEFORMATEX *pSourceFormat,
        UINT32 flags, float maxFrequencyRatio,
        IXAudio2VoiceCallback *pCallback, const XAUDIO2_VOICE_SENDS *pSendList,
        const XAUDIO2_EFFECT_CHAIN *pEffectChain)
{
    IXAudio2Impl *This = impl_from_IXAudio2(iface);
    XA2SourceImpl *src;
    HRESULT hr;

    TRACE("(%p)->(%p, %p, 0x%x, %f, %p, %p, %p)\n", This, ppSourceVoice,
            pSourceFormat, flags, maxFrequencyRatio, pCallback, pSendList,
            pEffectChain);

    dump_fmt(pSourceFormat);

    palcSetThreadContext(This->al_ctx);

    EnterCriticalSection(&This->lock);

    LIST_FOR_EACH_ENTRY(src, &This->source_voices, XA2SourceImpl, entry){
        EnterCriticalSection(&src->lock);
        if(!src->in_use)
            break;
        LeaveCriticalSection(&src->lock);
    }

    if(&src->entry == &This->source_voices){
        src = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*src));
        if(!src){
            LeaveCriticalSection(&This->lock);
            return E_OUTOFMEMORY;
        }

        list_add_head(&This->source_voices, &src->entry);

        src->IXAudio2SourceVoice_iface.lpVtbl = &XAudio2SourceVoice_Vtbl;

#if XAUDIO2_VER == 0
        src->IXAudio20SourceVoice_iface.lpVtbl = &XAudio20SourceVoice_Vtbl;
#elif XAUDIO2_VER <= 3
        src->IXAudio23SourceVoice_iface.lpVtbl = &XAudio23SourceVoice_Vtbl;
#elif XAUDIO2_VER <= 7
        src->IXAudio27SourceVoice_iface.lpVtbl = &XAudio27SourceVoice_Vtbl;
#endif

        InitializeCriticalSection(&src->lock);
        src->lock.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": XA2SourceImpl.lock");

        src->xa2 = This;

        EnterCriticalSection(&src->lock);
    }

    src->in_use = TRUE;
    src->running = FALSE;

    LeaveCriticalSection(&This->lock);

    src->cb = pCallback;

    src->al_fmt = get_al_format(pSourceFormat);
    if(!src->al_fmt){
        src->in_use = FALSE;
        LeaveCriticalSection(&src->lock);
        WARN("OpenAL can't convert this format!\n");
        return AUDCLNT_E_UNSUPPORTED_FORMAT;
    }

    src->submit_blocksize = pSourceFormat->nBlockAlign;

    src->fmt = copy_waveformat(pSourceFormat);

    hr = XA2SRC_SetOutputVoices(&src->IXAudio2SourceVoice_iface, pSendList);
    if(FAILED(hr)){
        HeapFree(GetProcessHeap(), 0, src->fmt);
        src->in_use = FALSE;
        LeaveCriticalSection(&src->lock);
        return hr;
    }

    alGenSources(1, &src->al_src);
    if(!src->al_src){
        static int once = 0;
        if(!once++)
            ERR_(winediag)("OpenAL ran out of sources, consider increasing its source limit.\n");
        HeapFree(GetProcessHeap(), 0, src->fmt);
        src->in_use = FALSE;
        LeaveCriticalSection(&src->lock);
        return E_OUTOFMEMORY;
    }

    alGenBuffers(XAUDIO2_MAX_QUEUED_BUFFERS, src->al_bufs);

    alSourcePlay(src->al_src);

    LeaveCriticalSection(&src->lock);

#if XAUDIO2_VER == 0
    *ppSourceVoice = (IXAudio2SourceVoice*)&src->IXAudio20SourceVoice_iface;
#elif XAUDIO2_VER <= 3
    *ppSourceVoice = (IXAudio2SourceVoice*)&src->IXAudio23SourceVoice_iface;
#elif XAUDIO2_VER <= 7
    *ppSourceVoice = (IXAudio2SourceVoice*)&src->IXAudio27SourceVoice_iface;
#else
    *ppSourceVoice = &src->IXAudio2SourceVoice_iface;
#endif

    TRACE("Created source voice: %p\n", src);

    return S_OK;
}

static HRESULT WINAPI IXAudio2Impl_CreateSubmixVoice(IXAudio2 *iface,
        IXAudio2SubmixVoice **ppSubmixVoice, UINT32 inputChannels,
        UINT32 inputSampleRate, UINT32 flags, UINT32 processingStage,
        const XAUDIO2_VOICE_SENDS *pSendList,
        const XAUDIO2_EFFECT_CHAIN *pEffectChain)
{
    IXAudio2Impl *This = impl_from_IXAudio2(iface);
    XA2SubmixImpl *sub;

    TRACE("(%p)->(%p, %u, %u, 0x%x, %u, %p, %p)\n", This, ppSubmixVoice,
            inputChannels, inputSampleRate, flags, processingStage, pSendList,
            pEffectChain);

    EnterCriticalSection(&This->lock);

    LIST_FOR_EACH_ENTRY(sub, &This->submix_voices, XA2SubmixImpl, entry){
        EnterCriticalSection(&sub->lock);
        if(!sub->in_use)
            break;
        LeaveCriticalSection(&sub->lock);
    }

    if(&sub->entry == &This->submix_voices){
        sub = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*sub));
        if(!sub){
            LeaveCriticalSection(&This->lock);
            return E_OUTOFMEMORY;
        }

        list_add_head(&This->submix_voices, &sub->entry);

        sub->IXAudio2SubmixVoice_iface.lpVtbl = &XAudio2SubmixVoice_Vtbl;

#if XAUDIO2_VER == 0
        sub->IXAudio20SubmixVoice_iface.lpVtbl = &XAudio20SubmixVoice_Vtbl;
#elif XAUDIO2_VER <= 3
        sub->IXAudio23SubmixVoice_iface.lpVtbl = &XAudio23SubmixVoice_Vtbl;
#elif XAUDIO2_VER <= 7
        sub->IXAudio27SubmixVoice_iface.lpVtbl = &XAudio27SubmixVoice_Vtbl;
#endif

        InitializeCriticalSection(&sub->lock);
        sub->lock.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": XA2SubmixImpl.lock");

        EnterCriticalSection(&sub->lock);
    }

    sub->in_use = TRUE;

    sub->details.CreationFlags = flags;
    sub->details.ActiveFlags = flags;
    sub->details.InputChannels = inputChannels;
    sub->details.InputSampleRate = inputSampleRate;

    LeaveCriticalSection(&This->lock);
    LeaveCriticalSection(&sub->lock);

#if XAUDIO2_VER == 0
    *ppSubmixVoice = (IXAudio2SubmixVoice*)&sub->IXAudio20SubmixVoice_iface;
#elif XAUDIO2_VER <= 3
    *ppSubmixVoice = (IXAudio2SubmixVoice*)&sub->IXAudio23SubmixVoice_iface;
#elif XAUDIO2_VER <= 7
    *ppSubmixVoice = (IXAudio2SubmixVoice*)&sub->IXAudio27SubmixVoice_iface;
#else
    *ppSubmixVoice = &sub->IXAudio2SubmixVoice_iface;
#endif

    TRACE("Created submix voice: %p\n", sub);

    return S_OK;
}

static ALenum al_get_loopback_format(const WAVEFORMATEXTENSIBLE *fmt)
{
    if(fmt->Format.wFormatTag == WAVE_FORMAT_PCM ||
            (fmt->Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE &&
             IsEqualGUID(&fmt->SubFormat, &KSDATAFORMAT_SUBTYPE_PCM))){
        switch(fmt->Format.wBitsPerSample){
        case 8:
            return ALC_UNSIGNED_BYTE_SOFT;
        case 16:
            return ALC_SHORT_SOFT;
        case 32:
            return ALC_INT_SOFT;
        }
    }else if(fmt->Format.wFormatTag == WAVE_FORMAT_IEEE_FLOAT ||
            (fmt->Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE &&
             IsEqualGUID(&fmt->SubFormat, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT))){
        if(fmt->Format.wBitsPerSample == 32)
            return ALC_FLOAT_SOFT;
    }
    return 0;
}

static HRESULT WINAPI IXAudio2Impl_CreateMasteringVoice(IXAudio2 *iface,
        IXAudio2MasteringVoice **ppMasteringVoice, UINT32 inputChannels,
        UINT32 inputSampleRate, UINT32 flags, const WCHAR *deviceId,
        const XAUDIO2_EFFECT_CHAIN *pEffectChain,
        AUDIO_STREAM_CATEGORY streamCategory)
{
    IXAudio2Impl *This = impl_from_IXAudio2(iface);
    IMMDevice *dev;
    HRESULT hr;
    WAVEFORMATEX *fmt;
    ALCint attrs[7];
    REFERENCE_TIME period, bufdur;

    TRACE("(%p)->(%p, %u, %u, 0x%x, %s, %p, 0x%x)\n", This,
            ppMasteringVoice, inputChannels, inputSampleRate, flags,
            wine_dbgstr_w(deviceId), pEffectChain, streamCategory);

    if(flags != 0)
        WARN("Unknown flags set: 0x%x\n", flags);

    if(pEffectChain)
        WARN("Effect chain is unimplemented\n");

    EnterCriticalSection(&This->lock);

    /* there can only be one Mastering Voice, so just build it into XA2 */
    if(This->aclient){
        LeaveCriticalSection(&This->lock);
        return COMPAT_E_INVALID_CALL;
    }

    if(!deviceId){
        if(This->ndevs == 0){
            LeaveCriticalSection(&This->lock);
            return E_NOTFOUND;
        }
        deviceId = This->devids[0];
    }

    hr = IMMDeviceEnumerator_GetDevice(This->devenum, deviceId, &dev);
    if(FAILED(hr)){
        WARN("GetDevice failed: %08x\n", hr);
        hr = COMPAT_E_DEVICE_INVALIDATED;
        goto exit;
    }

    hr = IMMDevice_Activate(dev, &IID_IAudioClient,
            CLSCTX_INPROC_SERVER, NULL, (void**)&This->aclient);
    if(FAILED(hr)){
        WARN("Activate(IAudioClient) failed: %08x\n", hr);
        IMMDevice_Release(dev);
        hr = COMPAT_E_DEVICE_INVALIDATED;
        goto exit;
    }

    IMMDevice_Release(dev);

    hr = IAudioClient_GetMixFormat(This->aclient, &fmt);
    if(FAILED(hr)){
        WARN("GetMixFormat failed: %08x\n", hr);
        hr = COMPAT_E_DEVICE_INVALIDATED;
        goto exit;
    }

    if(sizeof(WAVEFORMATEX) + fmt->cbSize > sizeof(WAVEFORMATEXTENSIBLE)){
        FIXME("Mix format doesn't fit into WAVEFORMATEXTENSIBLE!\n");
        hr = COMPAT_E_DEVICE_INVALIDATED;
        goto exit;
    }

    if(inputChannels == XAUDIO2_DEFAULT_CHANNELS)
        inputChannels = fmt->nChannels;
    if(inputSampleRate == XAUDIO2_DEFAULT_SAMPLERATE)
        inputSampleRate = fmt->nSamplesPerSec;

    memcpy(&This->fmt, fmt, sizeof(WAVEFORMATEX) + fmt->cbSize);
    This->fmt.Format.nChannels = inputChannels;
    This->fmt.Format.nSamplesPerSec = inputSampleRate;
    This->fmt.Format.nBlockAlign = This->fmt.Format.nChannels * This->fmt.Format.wBitsPerSample / 8;
    This->fmt.Format.nAvgBytesPerSec = This->fmt.Format.nSamplesPerSec * This->fmt.Format.nBlockAlign;
    This->fmt.dwChannelMask = get_channel_mask(This->fmt.Format.nChannels);

    CoTaskMemFree(fmt);
    fmt = NULL;

    hr = IAudioClient_IsFormatSupported(This->aclient,
            AUDCLNT_SHAREMODE_SHARED, &This->fmt.Format, &fmt);
    if(hr == S_FALSE){
        if(sizeof(WAVEFORMATEX) + fmt->cbSize > sizeof(WAVEFORMATEXTENSIBLE)){
            FIXME("Mix format doesn't fit into WAVEFORMATEXTENSIBLE!\n");
            hr = COMPAT_E_DEVICE_INVALIDATED;
            goto exit;
        }
        memcpy(&This->fmt, fmt, sizeof(WAVEFORMATEX) + fmt->cbSize);
    }

    CoTaskMemFree(fmt);

    hr = IAudioClient_GetDevicePeriod(This->aclient, &period, NULL);
    if(FAILED(hr)){
        WARN("GetDevicePeriod failed: %08x\n", hr);
        hr = COMPAT_E_DEVICE_INVALIDATED;
        goto exit;
    }

    /* 3 periods or 0.1 seconds */
    bufdur = max(3 * period, 1000000);

    hr = IAudioClient_Initialize(This->aclient, AUDCLNT_SHAREMODE_SHARED,
            AUDCLNT_STREAMFLAGS_EVENTCALLBACK, bufdur,
            0, &This->fmt.Format, NULL);
    if(FAILED(hr)){
        WARN("Initialize failed: %08x\n", hr);
        hr = COMPAT_E_DEVICE_INVALIDATED;
        goto exit;
    }

    This->period_frames = MulDiv(period, inputSampleRate, 10000000);

    hr = IAudioClient_SetEventHandle(This->aclient, This->mmevt);
    if(FAILED(hr)){
        WARN("Initialize failed: %08x\n", hr);
        hr = COMPAT_E_DEVICE_INVALIDATED;
        goto exit;
    }

    hr = IAudioClient_GetService(This->aclient, &IID_IAudioRenderClient,
            (void**)&This->render);
    if(FAILED(hr)){
        WARN("GetService(IAudioRenderClient) failed: %08x\n", hr);
        hr = COMPAT_E_DEVICE_INVALIDATED;
        goto exit;
    }

    /* setup openal context */
    attrs[0] = ALC_FORMAT_CHANNELS_SOFT;
    switch(inputChannels){
    case 1:
        attrs[1] = ALC_MONO_SOFT;
        break;
    case 2:
        attrs[1] = ALC_STEREO_SOFT;
        break;
    case 4:
        attrs[1] = ALC_QUAD_SOFT;
        break;
    case 6:
        attrs[1] = ALC_5POINT1_SOFT;
        break;
    case 7:
        attrs[1] = ALC_6POINT1_SOFT;
        break;
    case 8:
        attrs[1] = ALC_7POINT1_SOFT;
        break;
    default:
        WARN("OpenAL doesn't support %u channels\n", inputChannels);
        LeaveCriticalSection(&This->lock);
        return AUDCLNT_E_UNSUPPORTED_FORMAT;
    }
    attrs[2] = ALC_FREQUENCY;
    attrs[3] = inputSampleRate;
    attrs[4] = ALC_FORMAT_TYPE_SOFT;
    attrs[5] = al_get_loopback_format(&This->fmt);
    attrs[6] = 0;

    if(!attrs[5]){
        WARN("OpenAL can't output samples in this format\n");
        hr = COMPAT_E_DEVICE_INVALIDATED;
        goto exit;
    }

    This->al_device = palcLoopbackOpenDeviceSOFT(NULL);
    if(!This->al_device){
        WARN("alcLoopbackOpenDeviceSOFT failed\n");
        hr = COMPAT_E_DEVICE_INVALIDATED;
        goto exit;
    }

    This->al_ctx = alcCreateContext(This->al_device, attrs);
    if(!This->al_ctx){
        WARN("alcCreateContext failed\n");
        hr = COMPAT_E_DEVICE_INVALIDATED;
        goto exit;
    }

    hr = IAudioClient_Start(This->aclient);
    if (FAILED(hr))
    {
        WARN("Start(IAudioClient) failed: %08x\n", hr);
        hr = COMPAT_E_DEVICE_INVALIDATED;
        goto exit;
    }

#if XAUDIO2_VER == 0
    *ppMasteringVoice = (IXAudio2MasteringVoice*)&This->IXAudio20MasteringVoice_iface;
#elif XAUDIO2_VER <= 3
    *ppMasteringVoice = (IXAudio2MasteringVoice*)&This->IXAudio23MasteringVoice_iface;
#elif XAUDIO2_VER <= 7
    *ppMasteringVoice = (IXAudio2MasteringVoice*)&This->IXAudio27MasteringVoice_iface;
#else
    *ppMasteringVoice = &This->IXAudio2MasteringVoice_iface;
#endif

exit:
    if(FAILED(hr)){
        if(This->render){
            IAudioRenderClient_Release(This->render);
            This->render = NULL;
        }
        if(This->aclient){
            IAudioClient_Release(This->aclient);
            This->aclient = NULL;
        }
        if(This->al_ctx){
            alcDestroyContext(This->al_ctx);
            This->al_ctx = NULL;
        }
        if(This->al_device){
            alcCloseDevice(This->al_device);
            This->al_device = NULL;
        }
    }

    LeaveCriticalSection(&This->lock);

    return hr;
}

static DWORD WINAPI engine_threadproc(void *arg);

static HRESULT WINAPI IXAudio2Impl_StartEngine(IXAudio2 *iface)
{
    IXAudio2Impl *This = impl_from_IXAudio2(iface);

    TRACE("(%p)->()\n", This);

    This->running = TRUE;

    if(!This->engine)
        This->engine = CreateThread(NULL, 0, engine_threadproc, This, 0, NULL);

    return S_OK;
}

static void WINAPI IXAudio2Impl_StopEngine(IXAudio2 *iface)
{
    IXAudio2Impl *This = impl_from_IXAudio2(iface);

    TRACE("(%p)->()\n", This);

    This->running = FALSE;
}

static HRESULT WINAPI IXAudio2Impl_CommitChanges(IXAudio2 *iface,
        UINT32 operationSet)
{
    IXAudio2Impl *This = impl_from_IXAudio2(iface);

    TRACE("(%p)->(0x%x): stub!\n", This, operationSet);

    return E_NOTIMPL;
}

static void WINAPI IXAudio2Impl_GetPerformanceData(IXAudio2 *iface,
        XAUDIO2_PERFORMANCE_DATA *pPerfData)
{
    IXAudio2Impl *This = impl_from_IXAudio2(iface);

    TRACE("(%p)->(%p): stub!\n", This, pPerfData);

    memset(pPerfData, 0, sizeof(*pPerfData));
}

static void WINAPI IXAudio2Impl_SetDebugConfiguration(IXAudio2 *iface,
        const XAUDIO2_DEBUG_CONFIGURATION *pDebugConfiguration,
        void *pReserved)
{
    IXAudio2Impl *This = impl_from_IXAudio2(iface);

    FIXME("(%p)->(%p, %p): stub!\n", This, pDebugConfiguration, pReserved);
}

/* XAudio2 2.8 */
static const IXAudio2Vtbl XAudio2_Vtbl =
{
    IXAudio2Impl_QueryInterface,
    IXAudio2Impl_AddRef,
    IXAudio2Impl_Release,
    IXAudio2Impl_RegisterForCallbacks,
    IXAudio2Impl_UnregisterForCallbacks,
    IXAudio2Impl_CreateSourceVoice,
    IXAudio2Impl_CreateSubmixVoice,
    IXAudio2Impl_CreateMasteringVoice,
    IXAudio2Impl_StartEngine,
    IXAudio2Impl_StopEngine,
    IXAudio2Impl_CommitChanges,
    IXAudio2Impl_GetPerformanceData,
    IXAudio2Impl_SetDebugConfiguration
};

struct xaudio2_cf {
    IClassFactory IClassFactory_iface;
    LONG ref;
};

static struct xaudio2_cf *impl_from_IClassFactory(IClassFactory *iface)
{
    return CONTAINING_RECORD(iface, struct xaudio2_cf, IClassFactory_iface);
}

static HRESULT WINAPI XAudio2CF_QueryInterface(IClassFactory *iface, REFIID riid, void **ppobj)
{
    if(IsEqualGUID(riid, &IID_IUnknown)
            || IsEqualGUID(riid, &IID_IClassFactory))
    {
        IClassFactory_AddRef(iface);
        *ppobj = iface;
        return S_OK;
    }

    *ppobj = NULL;
    WARN("(%p)->(%s, %p): interface not found\n", iface, debugstr_guid(riid), ppobj);
    return E_NOINTERFACE;
}

static ULONG WINAPI XAudio2CF_AddRef(IClassFactory *iface)
{
    struct xaudio2_cf *This = impl_from_IClassFactory(iface);
    ULONG ref = InterlockedIncrement(&This->ref);
    TRACE("(%p)->(): Refcount now %u\n", This, ref);
    return ref;
}

static ULONG WINAPI XAudio2CF_Release(IClassFactory *iface)
{
    struct xaudio2_cf *This = impl_from_IClassFactory(iface);
    ULONG ref = InterlockedDecrement(&This->ref);
    TRACE("(%p)->(): Refcount now %u\n", This, ref);
    if (!ref)
        HeapFree(GetProcessHeap(), 0, This);
    return ref;
}

static HRESULT initialize_mmdevices(IXAudio2Impl *This)
{
    IMMDeviceCollection *devcoll;
    UINT devcount;
    HRESULT hr;

    if(!This->devenum){
        hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL,
                CLSCTX_INPROC_SERVER, &IID_IMMDeviceEnumerator, (void**)&This->devenum);
        if(FAILED(hr))
            return hr;
    }

    hr = IMMDeviceEnumerator_EnumAudioEndpoints(This->devenum, eRender,
            DEVICE_STATE_ACTIVE, &devcoll);
    if(FAILED(hr)){
        return hr;
    }

    hr = IMMDeviceCollection_GetCount(devcoll, &devcount);
    if(FAILED(hr)){
        IMMDeviceCollection_Release(devcoll);
        return hr;
    }

    if(devcount > 0){
        UINT i, count = 1;
        IMMDevice *dev, *def_dev;

        /* make sure that device 0 is the default device */
        IMMDeviceEnumerator_GetDefaultAudioEndpoint(This->devenum, eRender, eConsole, &def_dev);

        This->devids = HeapAlloc(GetProcessHeap(), 0, sizeof(WCHAR *) * devcount);

        for(i = 0; i < devcount; ++i){
            hr = IMMDeviceCollection_Item(devcoll, i, &dev);
            if(SUCCEEDED(hr)){
                UINT idx;

                if(dev == def_dev)
                    idx = 0;
                else{
                    idx = count;
                    ++count;
                }

                hr = IMMDevice_GetId(dev, &This->devids[idx]);
                if(FAILED(hr)){
                    WARN("GetId failed: %08x\n", hr);
                    HeapFree(GetProcessHeap(), 0, This->devids);
                    This->devids = NULL;
                    IMMDevice_Release(dev);
                    return hr;
                }

                IMMDevice_Release(dev);
            }else{
                WARN("Item failed: %08x\n", hr);
                HeapFree(GetProcessHeap(), 0, This->devids);
                This->devids = NULL;
                IMMDeviceCollection_Release(devcoll);
                return hr;
            }
        }
    }

    IMMDeviceCollection_Release(devcoll);

    This->ndevs = devcount;

    return S_OK;
}

static HRESULT WINAPI XAudio2CF_CreateInstance(IClassFactory *iface, IUnknown *pOuter,
                                               REFIID riid, void **ppobj)
{
    struct xaudio2_cf *This = impl_from_IClassFactory(iface);
    HRESULT hr;
    IXAudio2Impl *object;

    TRACE("(%p)->(%p,%s,%p)\n", This, pOuter, debugstr_guid(riid), ppobj);

    *ppobj = NULL;

    if(pOuter)
        return CLASS_E_NOAGGREGATION;

    object = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*object));
    if(!object)
        return E_OUTOFMEMORY;

    object->IXAudio2_iface.lpVtbl = &XAudio2_Vtbl;
    object->IXAudio2MasteringVoice_iface.lpVtbl = &XAudio2MasteringVoice_Vtbl;

#if XAUDIO2_VER == 0
    object->IXAudio20_iface.lpVtbl = &XAudio20_Vtbl;
#elif XAUDIO2_VER <= 2
    object->IXAudio22_iface.lpVtbl = &XAudio22_Vtbl;
#elif XAUDIO2_VER <= 7
    object->IXAudio27_iface.lpVtbl = &XAudio27_Vtbl;
#endif

#if XAUDIO2_VER == 0
    object->IXAudio20MasteringVoice_iface.lpVtbl = &XAudio20MasteringVoice_Vtbl;
#elif XAUDIO2_VER <= 3
    object->IXAudio23MasteringVoice_iface.lpVtbl = &XAudio23MasteringVoice_Vtbl;
#elif XAUDIO2_VER <= 7
    object->IXAudio27MasteringVoice_iface.lpVtbl = &XAudio27MasteringVoice_Vtbl;
#endif

    list_init(&object->source_voices);
    list_init(&object->submix_voices);

    object->mmevt = CreateEventW(NULL, FALSE, FALSE, NULL);
    InitializeCriticalSection(&object->lock);
    object->lock.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": IXAudio2Impl.lock");

    hr = IXAudio2_QueryInterface(&object->IXAudio2_iface, riid, ppobj);
    if(FAILED(hr)){
        HeapFree(GetProcessHeap(), 0, object);
        return hr;
    }

    hr = initialize_mmdevices(object);
    if(FAILED(hr)){
        IUnknown_Release((IUnknown*)*ppobj);
        return hr;
    }

    object->ncbs = 4;
    object->cbs = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, object->ncbs * sizeof(*object->cbs));

    IXAudio2_StartEngine(&object->IXAudio2_iface);

    TRACE("Created XAudio version %u: %p\n", 20 + XAUDIO2_VER, object);

    return hr;
}

static HRESULT WINAPI XAudio2CF_LockServer(IClassFactory *iface, BOOL dolock)
{
    FIXME("(static)->(%d): stub!\n", dolock);
    return S_OK;
}

static const IClassFactoryVtbl XAudio2CF_Vtbl =
{
    XAudio2CF_QueryInterface,
    XAudio2CF_AddRef,
    XAudio2CF_Release,
    XAudio2CF_CreateInstance,
    XAudio2CF_LockServer
};

static IClassFactory *make_xaudio2_factory(void)
{
    struct xaudio2_cf *ret = HeapAlloc(GetProcessHeap(), 0, sizeof(struct xaudio2_cf));
    ret->IClassFactory_iface.lpVtbl = &XAudio2CF_Vtbl;
    ret->ref = 0;
    return &ret->IClassFactory_iface;
}

HRESULT WINAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, void **ppv)
{
    IClassFactory *factory = NULL;

    TRACE("(%s, %s, %p)\n", debugstr_guid(rclsid), debugstr_guid(riid), ppv);

    if(IsEqualGUID(rclsid, &CLSID_XAudio20) ||
            IsEqualGUID(rclsid, &CLSID_XAudio21) ||
            IsEqualGUID(rclsid, &CLSID_XAudio22) ||
            IsEqualGUID(rclsid, &CLSID_XAudio23) ||
            IsEqualGUID(rclsid, &CLSID_XAudio24) ||
            IsEqualGUID(rclsid, &CLSID_XAudio25) ||
            IsEqualGUID(rclsid, &CLSID_XAudio26) ||
            IsEqualGUID(rclsid, &CLSID_XAudio27)){
        factory = make_xaudio2_factory();

    }else if(IsEqualGUID(rclsid, &CLSID_AudioVolumeMeter20) ||
                IsEqualGUID(rclsid, &CLSID_AudioVolumeMeter21) ||
                IsEqualGUID(rclsid, &CLSID_AudioVolumeMeter22) ||
                IsEqualGUID(rclsid, &CLSID_AudioVolumeMeter23) ||
                IsEqualGUID(rclsid, &CLSID_AudioVolumeMeter24) ||
                IsEqualGUID(rclsid, &CLSID_AudioVolumeMeter25) ||
                IsEqualGUID(rclsid, &CLSID_AudioVolumeMeter26) ||
                IsEqualGUID(rclsid, &CLSID_AudioVolumeMeter27)){
        factory = make_xapo_factory(&CLSID_AudioVolumeMeter27);

    }else if(IsEqualGUID(rclsid, &CLSID_AudioReverb20) ||
                IsEqualGUID(rclsid, &CLSID_AudioReverb21) ||
                IsEqualGUID(rclsid, &CLSID_AudioReverb22) ||
                IsEqualGUID(rclsid, &CLSID_AudioReverb23) ||
                IsEqualGUID(rclsid, &CLSID_AudioReverb24) ||
                IsEqualGUID(rclsid, &CLSID_AudioReverb25) ||
                IsEqualGUID(rclsid, &CLSID_AudioReverb26) ||
                IsEqualGUID(rclsid, &CLSID_AudioReverb27)){
        factory = make_xapo_factory(&CLSID_FXReverb);
    }

    if(!factory) return CLASS_E_CLASSNOTAVAILABLE;

    return IClassFactory_QueryInterface(factory, riid, ppv);
}

HRESULT xaudio2_initialize(IXAudio2Impl *This, UINT32 flags, XAUDIO2_PROCESSOR proc)
{
    if(flags)
        FIXME("Unimplemented flags: 0x%x\n", flags);
    return S_OK;
}

#if XAUDIO2_VER >= 8
HRESULT WINAPI XAudio2Create(IXAudio2 **ppxa2, UINT32 flags, XAUDIO2_PROCESSOR proc)
{
    HRESULT hr;
    IXAudio2 *xa2;
    IClassFactory *cf;

    TRACE("%p 0x%x 0x%x\n", ppxa2, flags, proc);

    cf = make_xaudio2_factory();

    hr = IClassFactory_CreateInstance(cf, NULL, &IID_IXAudio2, (void**)&xa2);
    IClassFactory_Release(cf);
    if(FAILED(hr))
        return hr;

    hr = xaudio2_initialize(impl_from_IXAudio2(xa2), flags, proc);
    if(FAILED(hr)){
        IXAudio2_Release(xa2);
        return hr;
    }

    *ppxa2 = xa2;

    return S_OK;
}
#endif /* XAUDIO2_VER >= 8 */

/* returns TRUE if there is more data available in the buffer, FALSE if the
 * buffer's data has all been queued */
static BOOL xa2buffer_queue_period(XA2SourceImpl *src, XA2Buffer *buf, ALuint al_buf)
{
    UINT32 submit_bytes;
    const BYTE *submit_buf = NULL;

    if(buf->offs_bytes >= buf->cur_end_bytes){
        WARN("Shouldn't happen: Trying to push frames from a spent buffer?\n");
        return FALSE;
    }

    submit_bytes = min(src->xa2->period_frames * src->submit_blocksize, buf->cur_end_bytes - buf->offs_bytes);
    submit_buf = buf->xa2buffer.pAudioData + buf->offs_bytes;
    buf->offs_bytes += submit_bytes;

    alBufferData(al_buf, src->al_fmt, submit_buf, submit_bytes,
            src->fmt->nSamplesPerSec);

    alSourceQueueBuffers(src->al_src, 1, &al_buf);

    src->in_al_bytes += submit_bytes;
    src->al_bufs_used++;

    buf->latest_al_buf = al_buf;

    TRACE("queueing %u bytes, now %u in AL\n", submit_bytes, src->in_al_bytes);

    return buf->offs_bytes < buf->cur_end_bytes;
}

#if XAUDIO2_VER > 0
static UINT32 get_underrun_warning(XA2SourceImpl *src)
{
    UINT32 period_bytes = src->xa2->period_frames * src->submit_blocksize;
    UINT32 total = 0, i;

    for(i = 0; i < src->nbufs && total < IN_AL_PERIODS * period_bytes; ++i){
        XA2Buffer *buf = &src->buffers[(src->first_buf + i) % XAUDIO2_MAX_QUEUED_BUFFERS];
        total += buf->cur_end_bytes - buf->offs_bytes;
        if(buf->xa2buffer.LoopCount == XAUDIO2_LOOP_INFINITE)
            return 0;
        if(buf->xa2buffer.LoopCount > 0){
            total += (buf->loop_end_bytes - buf->xa2buffer.LoopBegin) * (buf->xa2buffer.LoopCount - buf->looped);
            total += buf->play_end_bytes - buf->loop_end_bytes;
        }
    }

    if(total >= IN_AL_PERIODS * period_bytes)
        return 0;

    return ((IN_AL_PERIODS * period_bytes - total) / period_bytes + 1) * period_bytes;
}
#endif

/* Looping:
 *
 * The looped section of a buffer is a subset of the play area which is looped
 * LoopCount times.
 *
 *       v PlayBegin
 *       vvvvvvvvvvvvvvvvvv PlayLength
 *                        v (PlayEnd)
 * [-----PPPLLLLLLLLPPPPPPP------]
 *          ^ LoopBegin
 *          ^^^^^^^^ LoopLength
 *                 ^ (LoopEnd)
 *
 * In the simple case, playback will start at PlayBegin. At LoopEnd, playback
 * will move to LoopBegin and repeat that loop LoopCount times. Then, playback
 * will cease at PlayEnd.
 *
 * If PlayLength is zero, then PlayEnd is the end of the buffer.
 *
 * If LoopLength is zero, then LoopEnd is PlayEnd.
 *
 * For corner cases and version differences, see tests.
 */
static void update_source_state(XA2SourceImpl *src)
{
    int i;
    ALint processed;
    ALint bufpos;

    alGetSourcei(src->al_src, AL_BUFFERS_PROCESSED, &processed);

    if(processed > 0){
        ALuint al_buffers[XAUDIO2_MAX_QUEUED_BUFFERS];

        alSourceUnqueueBuffers(src->al_src, processed, al_buffers);
        src->first_al_buf += processed;
        src->first_al_buf %= XAUDIO2_MAX_QUEUED_BUFFERS;
        src->al_bufs_used -= processed;

        for(i = 0; i < processed; ++i){
            ALint bufsize;

            alGetBufferi(al_buffers[i], AL_SIZE, &bufsize);

            src->in_al_bytes -= bufsize;
            src->played_frames += bufsize / src->submit_blocksize;

            if(al_buffers[i] == src->buffers[src->first_buf].latest_al_buf){
                DWORD old_buf = src->first_buf;

                src->first_buf++;
                src->first_buf %= XAUDIO2_MAX_QUEUED_BUFFERS;
                src->nbufs--;

                TRACE("%p: done with buffer %u\n", src, old_buf);

                if(src->buffers[old_buf].xa2buffer.Flags & XAUDIO2_END_OF_STREAM)
                    src->played_frames = 0;

                if(src->cb){
                    IXAudio2VoiceCallback_OnBufferEnd(src->cb,
                            src->buffers[old_buf].xa2buffer.pContext);
                    if(src->buffers[old_buf].xa2buffer.Flags & XAUDIO2_END_OF_STREAM)
                        IXAudio2VoiceCallback_OnStreamEnd(src->cb);

                    if(src->nbufs > 0)
                        IXAudio2VoiceCallback_OnBufferStart(src->cb,
                                src->buffers[src->first_buf].xa2buffer.pContext);
                }
            }
        }
    }

    alGetSourcei(src->al_src, AL_BYTE_OFFSET, &bufpos);

    /* maintain IN_AL_PERIODS periods in AL */
    while(src->cur_buf != (src->first_buf + src->nbufs) % XAUDIO2_MAX_QUEUED_BUFFERS &&
            src->in_al_bytes - bufpos < IN_AL_PERIODS * src->xa2->period_frames * src->submit_blocksize){
        TRACE("%p: going to queue a period from buffer %u\n", src, src->cur_buf);

        /* starting from an empty buffer */
        if(src->cb && src->cur_buf == src->first_buf && src->buffers[src->cur_buf].offs_bytes == 0 && !src->buffers[src->cur_buf].looped)
            IXAudio2VoiceCallback_OnBufferStart(src->cb,
                    src->buffers[src->first_buf].xa2buffer.pContext);

        if(!xa2buffer_queue_period(src, &src->buffers[src->cur_buf],
                    src->al_bufs[(src->first_al_buf + src->al_bufs_used) % XAUDIO2_MAX_QUEUED_BUFFERS])){
            XA2Buffer *cur = &src->buffers[src->cur_buf];

            if(cur->looped < cur->xa2buffer.LoopCount){
                if(cur->xa2buffer.LoopCount != XAUDIO2_LOOP_INFINITE)
                    ++cur->looped;
                else
                    cur->looped = 1; /* indicate that we are executing a loop */

                cur->offs_bytes = cur->xa2buffer.LoopBegin;
                if(cur->looped == cur->xa2buffer.LoopCount)
                    cur->cur_end_bytes = cur->play_end_bytes;
                else
                    cur->cur_end_bytes = cur->loop_end_bytes;

                if(src->cb)
                    IXAudio2VoiceCallback_OnLoopEnd(src->cb,
                            src->buffers[src->cur_buf].xa2buffer.pContext);

            }else{
                /* buffer is spent, move on */
                src->cur_buf++;
                src->cur_buf %= XAUDIO2_MAX_QUEUED_BUFFERS;
            }
        }
    }
}

static void do_engine_tick(IXAudio2Impl *This)
{
    BYTE *buf;
    XA2SourceImpl *src;
    HRESULT hr;
    UINT32 nframes, i, pad;

    /* maintain up to 3 periods in mmdevapi */
    hr = IAudioClient_GetCurrentPadding(This->aclient, &pad);
    if(FAILED(hr)){
        WARN("GetCurrentPadding failed: 0x%x\n", hr);
        return;
    }

    nframes = This->period_frames * 3 - pad;

    TRACE("frames available: %u\n", nframes);

    if(nframes < This->period_frames)
        return;

    if(!nframes)
        return;

    for(i = 0; i < This->ncbs && This->cbs[i]; ++i)
        IXAudio2EngineCallback_OnProcessingPassStart(This->cbs[i]);

    LIST_FOR_EACH_ENTRY(src, &This->source_voices, XA2SourceImpl, entry){
        ALint st = 0;

        EnterCriticalSection(&src->lock);

        if(!src->in_use || !src->running){
            LeaveCriticalSection(&src->lock);
            continue;
        }

        if(src->cb){
#if XAUDIO2_VER == 0
            IXAudio20VoiceCallback_OnVoiceProcessingPassStart((IXAudio20VoiceCallback*)src->cb);
#else
            UINT32 underrun;
            underrun = get_underrun_warning(src);
            if(underrun > 0)
                TRACE("Calling OnVoiceProcessingPassStart with BytesRequired: %u\n", underrun);
            IXAudio2VoiceCallback_OnVoiceProcessingPassStart(src->cb, underrun);
#endif
        }

        update_source_state(src);

        alGetSourcei(src->al_src, AL_SOURCE_STATE, &st);
        if(st != AL_PLAYING)
            alSourcePlay(src->al_src);

        if(src->cb)
            IXAudio2VoiceCallback_OnVoiceProcessingPassEnd(src->cb);

        LeaveCriticalSection(&src->lock);
    }

    hr = IAudioRenderClient_GetBuffer(This->render, nframes, &buf);
    if(FAILED(hr))
        WARN("GetBuffer failed: %08x\n", hr);

    palcRenderSamplesSOFT(This->al_device, buf, nframes);

    hr = IAudioRenderClient_ReleaseBuffer(This->render, nframes, 0);
    if(FAILED(hr))
        WARN("ReleaseBuffer failed: %08x\n", hr);

    for(i = 0; i < This->ncbs && This->cbs[i]; ++i)
        IXAudio2EngineCallback_OnProcessingPassEnd(This->cbs[i]);
}

static DWORD WINAPI engine_threadproc(void *arg)
{
    IXAudio2Impl *This = arg;
    while(1){
        WaitForSingleObject(This->mmevt, INFINITE);

        if(This->stop_engine)
            break;

        EnterCriticalSection(&This->lock);

        if(!This->running || !This->aclient){
            LeaveCriticalSection(&This->lock);
            continue;
        }

        palcSetThreadContext(This->al_ctx);

        do_engine_tick(This);

        LeaveCriticalSection(&This->lock);
    }
    return 0;
}
