/* DirectSound
 *
 * Copyright 1998 Marcus Meissner
 * Copyright 1998 Rob Riggs
 * Copyright 2000-2002 TransGaming Technologies, Inc.
 * Copyright 2004 Robert Reif
 *
 * 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 <assert.h>
#include <stdarg.h>
#include <stdio.h>
#include <math.h>

#define COBJMACROS

#include "windef.h"
#include "winbase.h"
#include "winuser.h"
#include "mmddk.h"
#include "wingdi.h"
#include "mmreg.h"
#include "ks.h"
#include "ksmedia.h"
#include "wine/debug.h"
#include "dsound.h"
#include "dsound_private.h"

WINE_DEFAULT_DEBUG_CHANNEL(dsound);

typedef struct IDirectSoundImpl {
    IUnknown            IUnknown_inner;
    IDirectSound8       IDirectSound8_iface;
    IUnknown           *outer_unk;      /* internal */
    LONG                ref, refds, numIfaces;
    DirectSoundDevice  *device;
    BOOL                has_ds8;
} IDirectSoundImpl;

static const char * dumpCooperativeLevel(DWORD level)
{
#define LE(x) case x: return #x
    switch (level) {
        LE(DSSCL_NORMAL);
        LE(DSSCL_PRIORITY);
        LE(DSSCL_EXCLUSIVE);
        LE(DSSCL_WRITEPRIMARY);
    }
#undef LE
    return wine_dbg_sprintf("Unknown(%08lx)", level);
}

static void _dump_DSCAPS(DWORD xmask) {
    struct {
        DWORD   mask;
        const char    *name;
    } flags[] = {
#define FE(x) { x, #x },
        FE(DSCAPS_PRIMARYMONO)
        FE(DSCAPS_PRIMARYSTEREO)
        FE(DSCAPS_PRIMARY8BIT)
        FE(DSCAPS_PRIMARY16BIT)
        FE(DSCAPS_CONTINUOUSRATE)
        FE(DSCAPS_EMULDRIVER)
        FE(DSCAPS_CERTIFIED)
        FE(DSCAPS_SECONDARYMONO)
        FE(DSCAPS_SECONDARYSTEREO)
        FE(DSCAPS_SECONDARY8BIT)
        FE(DSCAPS_SECONDARY16BIT)
#undef FE
    };
    unsigned int     i;

    for (i = 0; i < ARRAY_SIZE(flags); i++)
        if ((flags[i].mask & xmask) == flags[i].mask)
            TRACE("%s ",flags[i].name);
}

static void _dump_DSBCAPS(DWORD xmask) {
    struct {
        DWORD   mask;
        const char    *name;
    } flags[] = {
#define FE(x) { x, #x },
        FE(DSBCAPS_PRIMARYBUFFER)
        FE(DSBCAPS_STATIC)
        FE(DSBCAPS_LOCHARDWARE)
        FE(DSBCAPS_LOCSOFTWARE)
        FE(DSBCAPS_CTRL3D)
        FE(DSBCAPS_CTRLFREQUENCY)
        FE(DSBCAPS_CTRLPAN)
        FE(DSBCAPS_CTRLVOLUME)
        FE(DSBCAPS_CTRLPOSITIONNOTIFY)
        FE(DSBCAPS_CTRLFX)
        FE(DSBCAPS_STICKYFOCUS)
        FE(DSBCAPS_GLOBALFOCUS)
        FE(DSBCAPS_GETCURRENTPOSITION2)
        FE(DSBCAPS_MUTE3DATMAXDISTANCE)
        FE(DSBCAPS_LOCDEFER)
#undef FE
    };
    unsigned int     i;

    for (i = 0; i < ARRAY_SIZE(flags); i++)
        if ((flags[i].mask & xmask) == flags[i].mask)
            TRACE("%s ",flags[i].name);
}

/*******************************************************************************
 *        DirectSoundDevice
 */
static HRESULT DirectSoundDevice_Create(DirectSoundDevice ** ppDevice)
{
    DirectSoundDevice * device;
    TRACE("(%p)\n", ppDevice);

    /* Allocate memory */
    device = calloc(1, sizeof(DirectSoundDevice));
    if (device == NULL) {
        WARN("out of memory\n");
        return DSERR_OUTOFMEMORY;
    }

    device->ref            = 1;
    device->priolevel      = DSSCL_NORMAL;
    device->stopped        = 1;
    device->speaker_config = DSSPEAKER_COMBINED(DSSPEAKER_STEREO, DSSPEAKER_GEOMETRY_WIDE);

    DSOUND_ParseSpeakerConfig(device);

    /* 3D listener initial parameters */
    device->ds3dl.dwSize   = sizeof(DS3DLISTENER);
    device->ds3dl.vPosition.x = 0.0;
    device->ds3dl.vPosition.y = 0.0;
    device->ds3dl.vPosition.z = 0.0;
    device->ds3dl.vVelocity.x = 0.0;
    device->ds3dl.vVelocity.y = 0.0;
    device->ds3dl.vVelocity.z = 0.0;
    device->ds3dl.vOrientFront.x = 0.0;
    device->ds3dl.vOrientFront.y = 0.0;
    device->ds3dl.vOrientFront.z = 1.0;
    device->ds3dl.vOrientTop.x = 0.0;
    device->ds3dl.vOrientTop.y = 1.0;
    device->ds3dl.vOrientTop.z = 0.0;
    device->ds3dl.flDistanceFactor = DS3D_DEFAULTDISTANCEFACTOR;
    device->ds3dl.flRolloffFactor = DS3D_DEFAULTROLLOFFFACTOR;
    device->ds3dl.flDopplerFactor = DS3D_DEFAULTDOPPLERFACTOR;

    device->guid = GUID_NULL;

    /* Set default wave format (may need it for waveOutOpen) */
    device->primary_pwfx = calloc(1, sizeof(WAVEFORMATEXTENSIBLE));
    if (!device->primary_pwfx) {
        WARN("out of memory\n");
        free(device);
        return DSERR_OUTOFMEMORY;
    }

    device->primary_pwfx->wFormatTag = WAVE_FORMAT_PCM;
    device->primary_pwfx->nSamplesPerSec = 22050;
    device->primary_pwfx->wBitsPerSample = 8;
    device->primary_pwfx->nChannels = 2;
    device->primary_pwfx->nBlockAlign = device->primary_pwfx->wBitsPerSample * device->primary_pwfx->nChannels / 8;
    device->primary_pwfx->nAvgBytesPerSec = device->primary_pwfx->nSamplesPerSec * device->primary_pwfx->nBlockAlign;
    device->primary_pwfx->cbSize = 0;

    InitializeCriticalSectionEx(&(device->mixlock), 0, RTL_CRITICAL_SECTION_FLAG_FORCE_DEBUG_INFO);
    device->mixlock.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": DirectSoundDevice.mixlock");

    InitializeSRWLock(&device->buffer_list_lock);

   *ppDevice = device;

    return DS_OK;
}

static ULONG DirectSoundDevice_AddRef(DirectSoundDevice * device)
{
    ULONG ref = InterlockedIncrement(&(device->ref));
    TRACE("(%p) ref %ld\n", device, ref);
    return ref;
}

static ULONG DirectSoundDevice_Release(DirectSoundDevice * device)
{
    HRESULT hr;
    ULONG ref = InterlockedDecrement(&(device->ref));
    TRACE("(%p) ref %ld\n", device, ref);
    if (!ref) {
        int i;

        SetEvent(device->sleepev);
        if (device->thread) {
            WaitForSingleObject(device->thread, INFINITE);
            CloseHandle(device->thread);
        }
        if (device->mta_cookie)
            CoDecrementMTAUsage(device->mta_cookie);

        EnterCriticalSection(&DSOUND_renderers_lock);
        list_remove(&device->entry);
        LeaveCriticalSection(&DSOUND_renderers_lock);

        /* It is allowed to release this object even when buffers are playing */
        if (device->buffers) {
            WARN("%d secondary buffers not released\n", device->nrofbuffers);
            for( i=0;i<device->nrofbuffers;i++)
                secondarybuffer_destroy(device->buffers[i]);
        }

        hr = DSOUND_PrimaryDestroy(device);
        if (hr != DS_OK)
            WARN("DSOUND_PrimaryDestroy failed\n");

        if(device->client) {
            IAudioClient_Stop(device->client);
            IAudioClient_Release(device->client);
        }
        if(device->render)
            IAudioRenderClient_Release(device->render);
        if(device->volume)
            IAudioStreamVolume_Release(device->volume);
        if(device->mmdevice)
            IMMDevice_Release(device->mmdevice);
        CloseHandle(device->sleepev);
        free(device->tmp_buffer);
        free(device->cp_buffer);
        free(device->buffer);
        device->mixlock.DebugInfo->Spare[0] = 0;
        DeleteCriticalSection(&device->mixlock);
        TRACE("(%p) released\n", device);
        free(device);
    }
    return ref;
}

BOOL DSOUND_check_supported(IAudioClient *client, DWORD rate,
        DWORD depth, WORD channels)
{
    WAVEFORMATEX fmt, *junk;
    HRESULT hr;

    fmt.wFormatTag = WAVE_FORMAT_PCM;
    fmt.nChannels = channels;
    fmt.nSamplesPerSec = rate;
    fmt.wBitsPerSample = depth;
    fmt.nBlockAlign = (channels * depth) / 8;
    fmt.nAvgBytesPerSec = rate * fmt.nBlockAlign;
    fmt.cbSize = 0;

    hr = IAudioClient_IsFormatSupported(client, AUDCLNT_SHAREMODE_SHARED, &fmt, &junk);
    if(SUCCEEDED(hr))
        CoTaskMemFree(junk);

    return hr == S_OK;
}

static HRESULT DirectSoundDevice_Initialize(DirectSoundDevice ** ppDevice, LPCGUID lpcGUID)
{
    HRESULT hr = DS_OK;
    GUID devGUID;
    DirectSoundDevice *device;
    IMMDevice *mmdevice;

    TRACE("(%p,%s)\n",ppDevice,debugstr_guid(lpcGUID));

    if (*ppDevice != NULL) {
        WARN("already initialized\n");
        return DSERR_ALREADYINITIALIZED;
    }

    /* Default device? */
    if (!lpcGUID || IsEqualGUID(lpcGUID, &GUID_NULL))
        lpcGUID = &DSDEVID_DefaultPlayback;

    if(IsEqualGUID(lpcGUID, &DSDEVID_DefaultCapture) ||
            IsEqualGUID(lpcGUID, &DSDEVID_DefaultVoiceCapture))
        return DSERR_NODRIVER;

    hr = GetDeviceID(lpcGUID, &devGUID);
    if (FAILED(hr)) {
        WARN("invalid parameter: lpcGUID\n");
        return hr;
    }

    hr = get_mmdevice(eRender, &devGUID, &mmdevice);
    if(FAILED(hr))
        return hr;

    EnterCriticalSection(&DSOUND_renderers_lock);

    LIST_FOR_EACH_ENTRY(device, &DSOUND_renderers, DirectSoundDevice, entry){
        if(IsEqualGUID(&device->guid, &devGUID)){
            IMMDevice_Release(mmdevice);
            DirectSoundDevice_AddRef(device);
            *ppDevice = device;
            LeaveCriticalSection(&DSOUND_renderers_lock);
            return DS_OK;
        }
    }

    hr = DirectSoundDevice_Create(&device);
    if(FAILED(hr)){
        WARN("DirectSoundDevice_Create failed\n");
        IMMDevice_Release(mmdevice);
        LeaveCriticalSection(&DSOUND_renderers_lock);
        return hr;
    }

    device->mmdevice = mmdevice;
    device->guid = devGUID;
    device->sleepev = CreateEventW(0, 0, 0, 0);
    device->buflen = ds_hel_buflen;

    hr = DSOUND_ReopenDevice(device, FALSE);
    if (FAILED(hr))
    {
        free(device);
        LeaveCriticalSection(&DSOUND_renderers_lock);
        IMMDevice_Release(mmdevice);
        WARN("DSOUND_ReopenDevice failed: %08lx\n", hr);
        return hr;
    }
    CoIncrementMTAUsage(&device->mta_cookie);

    ZeroMemory(&device->drvcaps, sizeof(device->drvcaps));

    if(DSOUND_check_supported(device->client, 11025, 8, 1) ||
            DSOUND_check_supported(device->client, 22050, 8, 1) ||
            DSOUND_check_supported(device->client, 44100, 8, 1) ||
            DSOUND_check_supported(device->client, 48000, 8, 1) ||
            DSOUND_check_supported(device->client, 96000, 8, 1))
        device->drvcaps.dwFlags |= DSCAPS_PRIMARY8BIT | DSCAPS_PRIMARYMONO;

    if(DSOUND_check_supported(device->client, 11025, 16, 1) ||
            DSOUND_check_supported(device->client, 22050, 16, 1) ||
            DSOUND_check_supported(device->client, 44100, 16, 1) ||
            DSOUND_check_supported(device->client, 48000, 16, 1) ||
            DSOUND_check_supported(device->client, 96000, 16, 1))
        device->drvcaps.dwFlags |= DSCAPS_PRIMARY16BIT | DSCAPS_PRIMARYMONO;

    if(DSOUND_check_supported(device->client, 11025, 8, 2) ||
            DSOUND_check_supported(device->client, 22050, 8, 2) ||
            DSOUND_check_supported(device->client, 44100, 8, 2) ||
            DSOUND_check_supported(device->client, 48000, 8, 2) ||
            DSOUND_check_supported(device->client, 96000, 8, 2))
        device->drvcaps.dwFlags |= DSCAPS_PRIMARY8BIT | DSCAPS_PRIMARYSTEREO;

    if(DSOUND_check_supported(device->client, 11025, 16, 2) ||
            DSOUND_check_supported(device->client, 22050, 16, 2) ||
            DSOUND_check_supported(device->client, 44100, 16, 2) ||
            DSOUND_check_supported(device->client, 48000, 16, 2) ||
            DSOUND_check_supported(device->client, 96000, 16, 2))
        device->drvcaps.dwFlags |= DSCAPS_PRIMARY16BIT | DSCAPS_PRIMARYSTEREO;

    /* the dsound mixer supports all of the following */
    device->drvcaps.dwFlags |= DSCAPS_SECONDARY8BIT | DSCAPS_SECONDARY16BIT;
    device->drvcaps.dwFlags |= DSCAPS_SECONDARYMONO | DSCAPS_SECONDARYSTEREO;
    device->drvcaps.dwFlags |= DSCAPS_CONTINUOUSRATE;

    /* pretend that the driver is certified */
    device->drvcaps.dwFlags |= DSCAPS_CERTIFIED;

    device->drvcaps.dwPrimaryBuffers = 1;
    device->drvcaps.dwMinSecondarySampleRate = DSBFREQUENCY_MIN;
    device->drvcaps.dwMaxSecondarySampleRate = DSBFREQUENCY_MAX;
    device->drvcaps.dwMaxHwMixingAllBuffers = 1;
    device->drvcaps.dwMaxHwMixingStaticBuffers = device->drvcaps.dwMaxHwMixingAllBuffers;
    device->drvcaps.dwMaxHwMixingStreamingBuffers = device->drvcaps.dwMaxHwMixingAllBuffers;
    device->drvcaps.dwFreeHwMixingAllBuffers = 0;
    device->drvcaps.dwFreeHwMixingStaticBuffers = 0;
    device->drvcaps.dwFreeHwMixingStreamingBuffers = 0;

    ZeroMemory(&device->volpan, sizeof(device->volpan));

    device->thread = CreateThread(0, 0, DSOUND_mixthread, device, 0, 0);
    SetThreadPriority(device->thread, THREAD_PRIORITY_TIME_CRITICAL);

    *ppDevice = device;
    list_add_tail(&DSOUND_renderers, &device->entry);

    LeaveCriticalSection(&DSOUND_renderers_lock);

    return hr;
}

static HRESULT DirectSoundDevice_CreateSoundBuffer(
    DirectSoundDevice * device,
    LPCDSBUFFERDESC dsbd,
    LPLPDIRECTSOUNDBUFFER ppdsb,
    LPUNKNOWN lpunk,
    BOOL from8)
{
    HRESULT hres = DS_OK;
    TRACE("(%p,%p,%p,%p)\n",device,dsbd,ppdsb,lpunk);

    if (device == NULL) {
        WARN("not initialized\n");
        return DSERR_UNINITIALIZED;
    }

    if (dsbd == NULL) {
        WARN("invalid parameter: dsbd == NULL\n");
        return DSERR_INVALIDPARAM;
    }

    if (dsbd->dwSize != sizeof(DSBUFFERDESC) &&
        dsbd->dwSize != sizeof(DSBUFFERDESC1)) {
        WARN("invalid parameter: dsbd\n");
        return DSERR_INVALIDPARAM;
    }

    if (ppdsb == NULL) {
        WARN("invalid parameter: ppdsb == NULL\n");
        return DSERR_INVALIDPARAM;
    }
    *ppdsb = NULL;

    if (TRACE_ON(dsound)) {
        TRACE("(structsize=%ld)\n",dsbd->dwSize);
        TRACE("(flags=0x%08lx:\n",dsbd->dwFlags);
        _dump_DSBCAPS(dsbd->dwFlags);
        TRACE(")\n");
        TRACE("(bufferbytes=%ld)\n",dsbd->dwBufferBytes);
        TRACE("(lpwfxFormat=%p)\n",dsbd->lpwfxFormat);
    }

    if (!(dsbd->dwFlags & DSBCAPS_PRIMARYBUFFER) &&
        dsbd->dwFlags & DSBCAPS_LOCHARDWARE)
    {
        WARN("unable to create hardware buffer\n");
        return DSERR_UNSUPPORTED;
    }

    if (dsbd->dwFlags & DSBCAPS_PRIMARYBUFFER) {
        if (dsbd->lpwfxFormat != NULL) {
            WARN("invalid parameter: dsbd->lpwfxFormat must be NULL for "
                 "primary buffer\n");
            return DSERR_INVALIDPARAM;
        }

        if (dsbd->dwFlags & DSBCAPS_CTRLFX)
        {
            WARN("Invalid parameter DSBCAPS_CTRLFX\n");
            return DSERR_INVALIDPARAM;
        }

        if (device->primary) {
            WARN("Primary Buffer already created\n");
            IDirectSoundBuffer8_AddRef(&device->primary->IDirectSoundBuffer8_iface);
            *ppdsb = (IDirectSoundBuffer *)&device->primary->IDirectSoundBuffer8_iface;
        } else {
            hres = primarybuffer_create(device, &device->primary, dsbd);
            if (device->primary) {
                *ppdsb = (IDirectSoundBuffer*)&device->primary->IDirectSoundBuffer8_iface;
                device->primary->dsbd.dwFlags &= ~(DSBCAPS_LOCHARDWARE | DSBCAPS_LOCSOFTWARE);
                device->primary->dsbd.dwFlags |= DSBCAPS_LOCSOFTWARE;
            } else
                WARN("primarybuffer_create() failed\n");
        }
    } else {
        if (dsbd->lpwfxFormat == NULL) {
            WARN("invalid parameter: dsbd->lpwfxFormat can't be NULL for "
                 "secondary buffer\n");
            return DSERR_INVALIDPARAM;
        }

        if(dsbd->lpwfxFormat->wFormatTag != WAVE_FORMAT_PCM &&
                dsbd->lpwfxFormat->wFormatTag != WAVE_FORMAT_IEEE_FLOAT &&
                dsbd->lpwfxFormat->wFormatTag != WAVE_FORMAT_EXTENSIBLE) {
            WARN("We can't mix this format: 0x%x\n", dsbd->lpwfxFormat->wFormatTag);
            return E_NOTIMPL;
        }

        if(dsbd->lpwfxFormat->wBitsPerSample < 8 || dsbd->lpwfxFormat->wBitsPerSample % 8 != 0 ||
                dsbd->lpwfxFormat->nChannels == 0 || dsbd->lpwfxFormat->nSamplesPerSec == 0 ||
                dsbd->lpwfxFormat->nAvgBytesPerSec == 0 ||
                dsbd->lpwfxFormat->nBlockAlign != dsbd->lpwfxFormat->nChannels * dsbd->lpwfxFormat->wBitsPerSample / 8) {
            WARN("Format inconsistency\n");
            return DSERR_INVALIDPARAM;
        }

        if (dsbd->lpwfxFormat->nChannels > 2 && dsbd->lpwfxFormat->wFormatTag != WAVE_FORMAT_EXTENSIBLE)
            return DSERR_INVALIDPARAM;

        if (dsbd->lpwfxFormat->wFormatTag == WAVE_FORMAT_EXTENSIBLE)
        {
            WAVEFORMATEXTENSIBLE *pwfxe = (WAVEFORMATEXTENSIBLE*)dsbd->lpwfxFormat;

            /* check if cbSize is at least 22 bytes */
            if (pwfxe->Format.cbSize < (sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX)))
            {
                WARN("Too small a cbSize %u\n", pwfxe->Format.cbSize);
                return DSERR_INVALIDPARAM;
            }

            /* cbSize should be 22 bytes, with one possible exception */
            if (pwfxe->Format.cbSize > (sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX)) &&
                !((IsEqualGUID(&pwfxe->SubFormat, &KSDATAFORMAT_SUBTYPE_PCM) || IsEqualGUID(&pwfxe->SubFormat, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)) &&
                pwfxe->Format.cbSize == sizeof(WAVEFORMATEXTENSIBLE)))
            {
                WARN("Too big a cbSize %u\n", pwfxe->Format.cbSize);
                return DSERR_CONTROLUNAVAIL;
            }

            if ((!IsEqualGUID(&pwfxe->SubFormat, &KSDATAFORMAT_SUBTYPE_PCM)) && (!IsEqualGUID(&pwfxe->SubFormat, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)))
            {
                if (!IsEqualGUID(&pwfxe->SubFormat, &GUID_NULL))
                    FIXME("SubFormat %s not supported right now.\n", debugstr_guid(&pwfxe->SubFormat));
                return DSERR_INVALIDPARAM;
            }
            if (pwfxe->Samples.wValidBitsPerSample > dsbd->lpwfxFormat->wBitsPerSample)
            {
                WARN("Samples.wValidBitsPerSample(%d) > Format.wBitsPerSample (%d)\n", pwfxe->Samples.wValidBitsPerSample, pwfxe->Format.wBitsPerSample);
                return DSERR_INVALIDPARAM;
            }
            if (pwfxe->Samples.wValidBitsPerSample && pwfxe->Samples.wValidBitsPerSample < dsbd->lpwfxFormat->wBitsPerSample)
            {
                WARN("Non-packed formats may not function : %d/%d\n", pwfxe->Samples.wValidBitsPerSample, dsbd->lpwfxFormat->wBitsPerSample);
            }
        }

        TRACE("(formattag=0x%04x,chans=%d,samplerate=%ld,"
              "bytespersec=%ld,blockalign=%d,bitspersamp=%d,cbSize=%d)\n",
              dsbd->lpwfxFormat->wFormatTag, dsbd->lpwfxFormat->nChannels,
              dsbd->lpwfxFormat->nSamplesPerSec,
              dsbd->lpwfxFormat->nAvgBytesPerSec,
              dsbd->lpwfxFormat->nBlockAlign,
              dsbd->lpwfxFormat->wBitsPerSample, dsbd->lpwfxFormat->cbSize);

        if (from8 && (dsbd->dwFlags & DSBCAPS_CTRL3D) && (dsbd->lpwfxFormat->nChannels != 1)) {
            WARN("invalid parameter: 3D buffer format must be mono\n");
            return DSERR_INVALIDPARAM;
        }

        if (from8 && (dsbd->dwFlags & DSBCAPS_CTRL3D) && (dsbd->dwFlags & DSBCAPS_CTRLPAN)) {
            WARN("invalid parameter: DSBCAPS_CTRL3D and DSBCAPS_CTRLPAN cannot be used together\n");
            return DSERR_INVALIDPARAM;
        }

        hres = secondarybuffer_create(device, dsbd, ppdsb);
        if (SUCCEEDED(hres)) {
            if (dsbd->dwFlags & DSBCAPS_LOCHARDWARE)
                device->drvcaps.dwFreeHwMixingAllBuffers--;
        } else
            WARN("IDirectSoundBufferImpl_Create failed\n");
   }

   return hres;
}

static HRESULT DirectSoundDevice_DuplicateSoundBuffer(
    DirectSoundDevice * device,
    LPDIRECTSOUNDBUFFER psb,
    LPLPDIRECTSOUNDBUFFER ppdsb)
{
    HRESULT hres = DS_OK;
    IDirectSoundBufferImpl* dsb;
    TRACE("(%p,%p,%p)\n",device,psb,ppdsb);

    if (device == NULL) {
        WARN("not initialized\n");
        return DSERR_UNINITIALIZED;
    }

    if (psb == NULL) {
        WARN("invalid parameter: psb == NULL\n");
        return DSERR_INVALIDPARAM;
    }

    if (ppdsb == NULL) {
        WARN("invalid parameter: ppdsb == NULL\n");
        return DSERR_INVALIDPARAM;
    }

    /* make sure we have a secondary buffer */
    if (psb == (IDirectSoundBuffer *)&device->primary->IDirectSoundBuffer8_iface) {
        WARN("trying to duplicate primary buffer\n");
        *ppdsb = NULL;
        return DSERR_INVALIDCALL;
    }

    /* duplicate the actual buffer implementation */
    hres = IDirectSoundBufferImpl_Duplicate(device, &dsb, (IDirectSoundBufferImpl*)psb);
    if (hres == DS_OK)
        *ppdsb = (IDirectSoundBuffer*)&dsb->IDirectSoundBuffer8_iface;
    else
        WARN("IDirectSoundBufferImpl_Duplicate failed\n");

    return hres;
}

/*
 * Add secondary buffer to buffer list.
 * Gets exclusive access to buffer for writing.
 */
HRESULT DirectSoundDevice_AddBuffer(
    DirectSoundDevice * device,
    IDirectSoundBufferImpl * pDSB)
{
    IDirectSoundBufferImpl **newbuffers;
    HRESULT hr = DS_OK;

    TRACE("(%p, %p)\n", device, pDSB);

    AcquireSRWLockExclusive(&device->buffer_list_lock);

    newbuffers = realloc(device->buffers, sizeof(IDirectSoundBufferImpl*) * (device->nrofbuffers + 1));

    if (newbuffers) {
        device->buffers = newbuffers;
        device->buffers[device->nrofbuffers] = pDSB;
        device->nrofbuffers++;
        TRACE("buffer count is now %d\n", device->nrofbuffers);
    } else {
        ERR("out of memory for buffer list! Current buffer count is %d\n", device->nrofbuffers);
        hr = DSERR_OUTOFMEMORY;
    }

    ReleaseSRWLockExclusive(&device->buffer_list_lock);

    return hr;
}

/*
 * Remove secondary buffer from buffer list.
 * Gets exclusive access to buffer for writing.
 */
void DirectSoundDevice_RemoveBuffer(DirectSoundDevice * device, IDirectSoundBufferImpl * pDSB)
{
    int i;

    TRACE("(%p, %p)\n", device, pDSB);

    AcquireSRWLockExclusive(&device->buffer_list_lock);

    if (device->nrofbuffers == 1) {
        assert(device->buffers[0] == pDSB);
        free(device->buffers);
        device->buffers = NULL;
    } else {
        for (i = 0; i < device->nrofbuffers; i++) {
            if (device->buffers[i] == pDSB) {
                /* Put the last buffer of the list in the (now empty) position */
                device->buffers[i] = device->buffers[device->nrofbuffers - 1];
                break;
            }
        }
    }
    device->nrofbuffers--;
    TRACE("buffer count is now %d\n", device->nrofbuffers);

    ReleaseSRWLockExclusive(&device->buffer_list_lock);
}

/*******************************************************************************
 *      IUnknown Implementation for DirectSound
 */

static void directsound_destroy(IDirectSoundImpl *This)
{
    if (This->device)
        DirectSoundDevice_Release(This->device);
    TRACE("(%p) released\n", This);
    free(This);
}

static inline IDirectSoundImpl *impl_from_IUnknown(IUnknown *iface)
{
    return CONTAINING_RECORD(iface, IDirectSoundImpl, IUnknown_inner);
}

static HRESULT WINAPI IUnknownImpl_QueryInterface(IUnknown *iface, REFIID riid, void **ppv)
{
    IDirectSoundImpl *This = impl_from_IUnknown(iface);

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

    if (!ppv) {
        WARN("invalid parameter\n");
        return E_INVALIDARG;
    }
    *ppv = NULL;

    if (IsEqualIID(riid, &IID_IUnknown))
        *ppv = &This->IUnknown_inner;
    else if (IsEqualIID(riid, &IID_IDirectSound) ||
            (IsEqualIID(riid, &IID_IDirectSound8) && This->has_ds8))
        *ppv = &This->IDirectSound8_iface;
    else {
        WARN("unknown IID %s\n", debugstr_guid(riid));
        return E_NOINTERFACE;
    }

    IUnknown_AddRef((IUnknown*)*ppv);
    return S_OK;
}

static ULONG WINAPI IUnknownImpl_AddRef(IUnknown *iface)
{
    IDirectSoundImpl *This = impl_from_IUnknown(iface);
    ULONG ref = InterlockedIncrement(&This->ref);

    TRACE("(%p) ref=%ld\n", This, ref);

    if(ref == 1)
        InterlockedIncrement(&This->numIfaces);

    return ref;
}

static ULONG WINAPI IUnknownImpl_Release(IUnknown *iface)
{
    IDirectSoundImpl *This = impl_from_IUnknown(iface);
    ULONG ref = InterlockedDecrement(&This->ref);

    TRACE("(%p) ref=%ld\n", This, ref);

    if (!ref && !InterlockedDecrement(&This->numIfaces))
        directsound_destroy(This);

    return ref;
}

static const IUnknownVtbl unk_vtbl =
{
    IUnknownImpl_QueryInterface,
    IUnknownImpl_AddRef,
    IUnknownImpl_Release
};

/*******************************************************************************
 *      IDirectSound and IDirectSound8 Implementation
 */
static inline IDirectSoundImpl *impl_from_IDirectSound8(IDirectSound8 *iface)
{
    return CONTAINING_RECORD(iface, IDirectSoundImpl, IDirectSound8_iface);
}

static HRESULT WINAPI IDirectSound8Impl_QueryInterface(IDirectSound8 *iface, REFIID riid,
        void **ppv)
{
    IDirectSoundImpl *This = impl_from_IDirectSound8(iface);
    TRACE("(%p,%s,%p)\n", This, debugstr_guid(riid), ppv);
    return IUnknown_QueryInterface(This->outer_unk, riid, ppv);
}

static ULONG WINAPI IDirectSound8Impl_AddRef(IDirectSound8 *iface)
{
    IDirectSoundImpl *This = impl_from_IDirectSound8(iface);
    ULONG ref = InterlockedIncrement(&This->refds);

    TRACE("(%p) refds=%ld\n", This, ref);

    if(ref == 1)
        InterlockedIncrement(&This->numIfaces);

    return ref;
}

static ULONG WINAPI IDirectSound8Impl_Release(IDirectSound8 *iface)
{
    IDirectSoundImpl *This = impl_from_IDirectSound8(iface);
    ULONG ref = InterlockedDecrement(&(This->refds));

    TRACE("(%p) refds=%ld\n", This, ref);

    if (!ref && !InterlockedDecrement(&This->numIfaces))
        directsound_destroy(This);

    return ref;
}

static HRESULT WINAPI IDirectSound8Impl_CreateSoundBuffer(IDirectSound8 *iface,
        const DSBUFFERDESC *dsbd, IDirectSoundBuffer **ppdsb, IUnknown *lpunk)
{
    IDirectSoundImpl *This = impl_from_IDirectSound8(iface);
    TRACE("(%p,%p,%p,%p)\n", This, dsbd, ppdsb, lpunk);
    return DirectSoundDevice_CreateSoundBuffer(This->device, dsbd, ppdsb, lpunk, This->has_ds8);
}

static HRESULT WINAPI IDirectSound8Impl_GetCaps(IDirectSound8 *iface, DSCAPS *dscaps)
{
    IDirectSoundImpl *This = impl_from_IDirectSound8(iface);

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

    if (!This->device) {
        WARN("not initialized\n");
        return DSERR_UNINITIALIZED;
    }
    if (!dscaps) {
        WARN("invalid parameter: dscaps = NULL\n");
        return DSERR_INVALIDPARAM;
    }
    if (dscaps->dwSize < sizeof(*dscaps)) {
        WARN("invalid parameter: dscaps->dwSize = %ld\n", dscaps->dwSize);
        return DSERR_INVALIDPARAM;
    }

    dscaps->dwFlags                        = This->device->drvcaps.dwFlags;
    dscaps->dwMinSecondarySampleRate       = This->device->drvcaps.dwMinSecondarySampleRate;
    dscaps->dwMaxSecondarySampleRate       = This->device->drvcaps.dwMaxSecondarySampleRate;
    dscaps->dwPrimaryBuffers               = This->device->drvcaps.dwPrimaryBuffers;
    dscaps->dwMaxHwMixingAllBuffers        = This->device->drvcaps.dwMaxHwMixingAllBuffers;
    dscaps->dwMaxHwMixingStaticBuffers     = This->device->drvcaps.dwMaxHwMixingStaticBuffers;
    dscaps->dwMaxHwMixingStreamingBuffers  = This->device->drvcaps.dwMaxHwMixingStreamingBuffers;
    dscaps->dwFreeHwMixingAllBuffers       = This->device->drvcaps.dwFreeHwMixingAllBuffers;
    dscaps->dwFreeHwMixingStaticBuffers    = This->device->drvcaps.dwFreeHwMixingAllBuffers;
    dscaps->dwFreeHwMixingStreamingBuffers = This->device->drvcaps.dwFreeHwMixingAllBuffers;

    dscaps->dwMaxHw3DAllBuffers            = This->device->drvcaps.dwMaxHw3DAllBuffers;
    dscaps->dwMaxHw3DStaticBuffers         = This->device->drvcaps.dwMaxHw3DStaticBuffers;
    dscaps->dwMaxHw3DStreamingBuffers      = This->device->drvcaps.dwMaxHw3DStreamingBuffers;
    dscaps->dwFreeHw3DAllBuffers           = This->device->drvcaps.dwFreeHw3DAllBuffers;
    dscaps->dwFreeHw3DStaticBuffers        = This->device->drvcaps.dwFreeHw3DStaticBuffers;
    dscaps->dwFreeHw3DStreamingBuffers     = This->device->drvcaps.dwFreeHw3DStreamingBuffers;
    dscaps->dwTotalHwMemBytes              = This->device->drvcaps.dwTotalHwMemBytes;
    dscaps->dwFreeHwMemBytes               = This->device->drvcaps.dwFreeHwMemBytes;
    dscaps->dwMaxContigFreeHwMemBytes      = This->device->drvcaps.dwMaxContigFreeHwMemBytes;
    dscaps->dwUnlockTransferRateHwBuffers  = This->device->drvcaps.dwUnlockTransferRateHwBuffers;
    dscaps->dwPlayCpuOverheadSwBuffers     = This->device->drvcaps.dwPlayCpuOverheadSwBuffers;

    if (TRACE_ON(dsound)) {
        TRACE("(flags=0x%08lx:\n", dscaps->dwFlags);
        _dump_DSCAPS(dscaps->dwFlags);
        TRACE(")\n");
    }

    return DS_OK;
}

static HRESULT WINAPI IDirectSound8Impl_DuplicateSoundBuffer(IDirectSound8 *iface,
        IDirectSoundBuffer *psb, IDirectSoundBuffer **ppdsb)
{
    IDirectSoundImpl *This = impl_from_IDirectSound8(iface);
    TRACE("(%p,%p,%p)\n", This, psb, ppdsb);
    return DirectSoundDevice_DuplicateSoundBuffer(This->device, psb, ppdsb);
}

static HRESULT WINAPI IDirectSound8Impl_SetCooperativeLevel(IDirectSound8 *iface, HWND hwnd,
        DWORD level)
{
    IDirectSoundImpl *This = impl_from_IDirectSound8(iface);
    DirectSoundDevice *device = This->device;
    HRESULT hr = S_OK;

    TRACE("(%p,%p,%s)\n", This, hwnd, dumpCooperativeLevel(level));

    if (!device) {
        WARN("not initialized\n");
        return DSERR_UNINITIALIZED;
    }

    if (level == DSSCL_PRIORITY || level == DSSCL_EXCLUSIVE) {
        WARN("level=%s not fully supported\n",
             level==DSSCL_PRIORITY ? "DSSCL_PRIORITY" : "DSSCL_EXCLUSIVE");
    }

    AcquireSRWLockExclusive(&device->buffer_list_lock);
    EnterCriticalSection(&device->mixlock);
    if ((level == DSSCL_WRITEPRIMARY) != (device->priolevel == DSSCL_WRITEPRIMARY))
        hr = DSOUND_ReopenDevice(device, level == DSSCL_WRITEPRIMARY);
    if (SUCCEEDED(hr))
        device->priolevel = level;
    LeaveCriticalSection(&device->mixlock);
    ReleaseSRWLockExclusive(&device->buffer_list_lock);
    return hr;
}

static HRESULT WINAPI IDirectSound8Impl_Compact(IDirectSound8 *iface)
{
    IDirectSoundImpl *This = impl_from_IDirectSound8(iface);

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

    if (!This->device) {
        WARN("not initialized\n");
        return DSERR_UNINITIALIZED;
    }

    if (This->device->priolevel < DSSCL_PRIORITY) {
        WARN("incorrect priority level\n");
        return DSERR_PRIOLEVELNEEDED;
    }
    return DS_OK;
}

static HRESULT WINAPI IDirectSound8Impl_GetSpeakerConfig(IDirectSound8 *iface, DWORD *config)
{
    IDirectSoundImpl *This = impl_from_IDirectSound8(iface);

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

    if (!This->device) {
        WARN("not initialized\n");
        return DSERR_UNINITIALIZED;
    }
    if (!config) {
        WARN("invalid parameter: config == NULL\n");
        return DSERR_INVALIDPARAM;
    }

    WARN("not fully functional\n");
    *config = This->device->speaker_config;
    return DS_OK;
}

static HRESULT WINAPI IDirectSound8Impl_SetSpeakerConfig(IDirectSound8 *iface, DWORD config)
{
    IDirectSoundImpl *This = impl_from_IDirectSound8(iface);

    TRACE("(%p,0x%08lx)\n", This, config);

    if (!This->device) {
        WARN("not initialized\n");
        return DSERR_UNINITIALIZED;
    }

    /* NOP on Vista and above */

    return DS_OK;
}

static HRESULT WINAPI IDirectSound8Impl_Initialize(IDirectSound8 *iface, const GUID *lpcGuid)
{
    IDirectSoundImpl *This = impl_from_IDirectSound8(iface);
    TRACE("(%p, %s)\n", This, debugstr_guid(lpcGuid));
    return DirectSoundDevice_Initialize(&This->device, lpcGuid);
}

static HRESULT WINAPI IDirectSound8Impl_VerifyCertification(IDirectSound8 *iface, DWORD *certified)
{
    IDirectSoundImpl *This = impl_from_IDirectSound8(iface);

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

    if (!This->device) {
        WARN("not initialized\n");
        return DSERR_UNINITIALIZED;
    }

    if (This->device->drvcaps.dwFlags & DSCAPS_CERTIFIED)
        *certified = DS_CERTIFIED;
    else
        *certified = DS_UNCERTIFIED;

    return DS_OK;
}

static const IDirectSound8Vtbl ds8_vtbl =
{
    IDirectSound8Impl_QueryInterface,
    IDirectSound8Impl_AddRef,
    IDirectSound8Impl_Release,
    IDirectSound8Impl_CreateSoundBuffer,
    IDirectSound8Impl_GetCaps,
    IDirectSound8Impl_DuplicateSoundBuffer,
    IDirectSound8Impl_SetCooperativeLevel,
    IDirectSound8Impl_Compact,
    IDirectSound8Impl_GetSpeakerConfig,
    IDirectSound8Impl_SetSpeakerConfig,
    IDirectSound8Impl_Initialize,
    IDirectSound8Impl_VerifyCertification
};

HRESULT IDirectSoundImpl_Create(IUnknown *outer_unk, REFIID riid, void **ppv, BOOL has_ds8)
{
    IDirectSoundImpl *obj;
    HRESULT hr;

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

    *ppv = NULL;
    obj = calloc(1, sizeof(*obj));
    if (!obj) {
        WARN("out of memory\n");
        return DSERR_OUTOFMEMORY;
    }

    setup_dsound_options();

    obj->IUnknown_inner.lpVtbl = &unk_vtbl;
    obj->IDirectSound8_iface.lpVtbl = &ds8_vtbl;
    obj->ref = 1;
    obj->refds = 0;
    obj->numIfaces = 1;
    obj->device = NULL;
    obj->has_ds8 = has_ds8;

    /* COM aggregation supported only internally */
    if (outer_unk)
        obj->outer_unk = outer_unk;
    else
        obj->outer_unk = &obj->IUnknown_inner;

    hr = IUnknown_QueryInterface(&obj->IUnknown_inner, riid, ppv);
    IUnknown_Release(&obj->IUnknown_inner);

    return hr;
}

HRESULT DSOUND_Create(REFIID riid, void **ppv)
{
    return IDirectSoundImpl_Create(NULL, riid, ppv, FALSE);
}

HRESULT DSOUND_Create8(REFIID riid, void **ppv)
{
    return IDirectSoundImpl_Create(NULL, riid, ppv, TRUE);
}

/*******************************************************************************
 *		DirectSoundCreate (DSOUND.1)
 *
 *  Creates and initializes a DirectSound interface.
 *
 *  PARAMS
 *     lpcGUID   [I] Address of the GUID that identifies the sound device.
 *     ppDS      [O] Address of a variable to receive the interface pointer.
 *     pUnkOuter [I] Must be NULL.
 *
 *  RETURNS
 *     Success: DS_OK
 *     Failure: DSERR_ALLOCATED, DSERR_INVALIDPARAM, DSERR_NOAGGREGATION,
 *              DSERR_NODRIVER, DSERR_OUTOFMEMORY
 */
HRESULT WINAPI DirectSoundCreate(
    LPCGUID lpcGUID,
    LPDIRECTSOUND *ppDS,
    IUnknown *pUnkOuter)
{
    HRESULT hr;
    LPDIRECTSOUND pDS;

    TRACE("(%s,%p,%p)\n",debugstr_guid(lpcGUID),ppDS,pUnkOuter);

    if (ppDS == NULL) {
        WARN("invalid parameter: ppDS == NULL\n");
        return DSERR_INVALIDPARAM;
    }

    if (pUnkOuter != NULL) {
        WARN("invalid parameter: pUnkOuter != NULL\n");
        *ppDS = 0;
        return DSERR_INVALIDPARAM;
    }

    hr = DSOUND_Create(&IID_IDirectSound, (void **)&pDS);
    if (hr == DS_OK) {
        hr = IDirectSound_Initialize(pDS, lpcGUID);
        if (hr != DS_OK) {
            if (hr != DSERR_ALREADYINITIALIZED) {
                IDirectSound_Release(pDS);
                pDS = 0;
            } else
                hr = DS_OK;
        }
    }

    *ppDS = pDS;

    return hr;
}

/*******************************************************************************
 *        DirectSoundCreate8 (DSOUND.11)
 *
 *  Creates and initializes a DirectSound8 interface.
 *
 *  PARAMS
 *     lpcGUID   [I] Address of the GUID that identifies the sound device.
 *     ppDS      [O] Address of a variable to receive the interface pointer.
 *     pUnkOuter [I] Must be NULL.
 *
 *  RETURNS
 *     Success: DS_OK
 *     Failure: DSERR_ALLOCATED, DSERR_INVALIDPARAM, DSERR_NOAGGREGATION,
 *              DSERR_NODRIVER, DSERR_OUTOFMEMORY
 */
HRESULT WINAPI DirectSoundCreate8(
    LPCGUID lpcGUID,
    LPDIRECTSOUND8 *ppDS,
    IUnknown *pUnkOuter)
{
    HRESULT hr;
    LPDIRECTSOUND8 pDS;

    TRACE("(%s,%p,%p)\n",debugstr_guid(lpcGUID),ppDS,pUnkOuter);

    if (ppDS == NULL) {
        WARN("invalid parameter: ppDS == NULL\n");
        return DSERR_INVALIDPARAM;
    }

    if (pUnkOuter != NULL) {
        WARN("invalid parameter: pUnkOuter != NULL\n");
        *ppDS = 0;
        return DSERR_INVALIDPARAM;
    }

    hr = DSOUND_Create8(&IID_IDirectSound8, (void **)&pDS);
    if (hr == DS_OK) {
        hr = IDirectSound8_Initialize(pDS, lpcGUID);
        if (hr != DS_OK) {
            if (hr != DSERR_ALREADYINITIALIZED) {
                IDirectSound8_Release(pDS);
                pDS = 0;
            } else
                hr = DS_OK;
        }
    }

    *ppDS = pDS;

    return hr;
}

void DSOUND_ParseSpeakerConfig(DirectSoundDevice *device)
{
    switch (DSSPEAKER_CONFIG(device->speaker_config)) {
        case DSSPEAKER_MONO:
            device->speaker_angles[0] = M_PI/180.0f * 0.0f;
            device->speaker_num[0] = 0;
            device->num_speakers = 1;
            device->lfe_channel = -1;
        break;

        case DSSPEAKER_STEREO:
        case DSSPEAKER_HEADPHONE:
            device->speaker_angles[0] = M_PI/180.0f * -90.0f;
            device->speaker_angles[1] = M_PI/180.0f *  90.0f;
            device->speaker_num[0] = 0; /* Left */
            device->speaker_num[1] = 1; /* Right */
            device->num_speakers = 2;
            device->lfe_channel = -1;
        break;

        case DSSPEAKER_QUAD:
            device->speaker_angles[0] = M_PI/180.0f * -135.0f;
            device->speaker_angles[1] = M_PI/180.0f *  -45.0f;
            device->speaker_angles[2] = M_PI/180.0f *   45.0f;
            device->speaker_angles[3] = M_PI/180.0f *  135.0f;
            device->speaker_num[0] = 2; /* Rear left */
            device->speaker_num[1] = 0; /* Front left */
            device->speaker_num[2] = 1; /* Front right */
            device->speaker_num[3] = 3; /* Rear right */
            device->num_speakers = 4;
            device->lfe_channel = -1;
        break;

        case DSSPEAKER_5POINT1_BACK:
            device->speaker_angles[0] = M_PI/180.0f * -135.0f;
            device->speaker_angles[1] = M_PI/180.0f *  -45.0f;
            device->speaker_angles[2] = M_PI/180.0f *    0.0f;
            device->speaker_angles[3] = M_PI/180.0f *   45.0f;
            device->speaker_angles[4] = M_PI/180.0f *  135.0f;
            device->speaker_angles[5] = 9999.0f;
            device->speaker_num[0] = 4; /* Rear left */
            device->speaker_num[1] = 0; /* Front left */
            device->speaker_num[2] = 2; /* Front centre */
            device->speaker_num[3] = 1; /* Front right */
            device->speaker_num[4] = 5; /* Rear right */
            device->speaker_num[5] = 3; /* LFE */
            device->num_speakers = 6;
            device->lfe_channel = 3;
        break;

        case DSSPEAKER_5POINT1_SURROUND:
            device->speaker_angles[0] = M_PI/180.0f *  -90.0f;
            device->speaker_angles[1] = M_PI/180.0f *  -30.0f;
            device->speaker_angles[2] = M_PI/180.0f *    0.0f;
            device->speaker_angles[3] = M_PI/180.0f *   30.0f;
            device->speaker_angles[4] = M_PI/180.0f *   90.0f;
            device->speaker_angles[5] = 9999.0f;
            device->speaker_num[0] = 4; /* Rear left */
            device->speaker_num[1] = 0; /* Front left */
            device->speaker_num[2] = 2; /* Front centre */
            device->speaker_num[3] = 1; /* Front right */
            device->speaker_num[4] = 5; /* Rear right */
            device->speaker_num[5] = 3; /* LFE */
            device->num_speakers = 6;
            device->lfe_channel = 3;
        break;

        default:
            WARN("unknown speaker_config %lu\n", device->speaker_config);
    }
}
