/*==========================================================================
 *
 *  Copyright (C) 1995-1999 Microsoft Corporation. All Rights Reserved.
 *
 *  File:       dsutil.cpp
 *  Content:    Routines for dealing with sounds from resources
 *
 *
 ***************************************************************************/

#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <dsound.h>

typedef struct
{
    BYTE *pbWaveData;               // pointer into wave resource (for restore)
    DWORD cbWaveSize;               // size of wave data (for restore)
    int iAlloc;                     // number of buffers.
    int iCurrent;                   // current buffer
    IDirectSoundBuffer* Buffers[1]; // list of buffers

} SNDOBJ, *HSNDOBJ;

#define _HSNDOBJ_DEFINED
#include "dsutil.h"

static const char c_szWAV[] = "WAV";

///////////////////////////////////////////////////////////////////////////////
//
// DSLoadSoundBuffer
//
///////////////////////////////////////////////////////////////////////////////

IDirectSoundBuffer *DSLoadSoundBuffer(IDirectSound *pDS, LPCTSTR lpName)
{
    IDirectSoundBuffer *pDSB = NULL;
    DSBUFFERDESC dsBD = {0};
    BYTE *pbWaveData;

    if (DSGetWaveResource(NULL, lpName, &dsBD.lpwfxFormat, &pbWaveData, &dsBD.dwBufferBytes))
    {
        dsBD.dwSize  = sizeof(dsBD);
        dsBD.dwFlags = DSBCAPS_STATIC | DSBCAPS_CTRLPAN | DSBCAPS_CTRLVOLUME | 
                       DSBCAPS_CTRLFREQUENCY | DSBCAPS_GETCURRENTPOSITION2;

        if (SUCCEEDED(IDirectSound_CreateSoundBuffer(pDS, &dsBD, &pDSB, NULL)))
        {
            if (!DSFillSoundBuffer(pDSB, pbWaveData, dsBD.dwBufferBytes))
            {
            IDirectSoundBuffer_Release(pDSB);
            pDSB = NULL;
            }
        }
        else
        {
            pDSB = NULL;
        }
    }

    return pDSB;
}

///////////////////////////////////////////////////////////////////////////////
//
// DSReloadSoundBuffer
//
///////////////////////////////////////////////////////////////////////////////

BOOL DSReloadSoundBuffer(IDirectSoundBuffer *pDSB, LPCTSTR lpName)
{
    BOOL result=FALSE;
    BYTE *pbWaveData;
    DWORD cbWaveSize;

    if (DSGetWaveResource(NULL, lpName, NULL, &pbWaveData, &cbWaveSize))
    {
    if (SUCCEEDED(IDirectSoundBuffer_Restore(pDSB)) &&
        DSFillSoundBuffer(pDSB, pbWaveData, cbWaveSize))
    {
        result = TRUE;
    }
    }

    return result;
}

///////////////////////////////////////////////////////////////////////////////
//
// DSGetWaveResource
//
///////////////////////////////////////////////////////////////////////////////

BOOL DSGetWaveResource(HMODULE hModule, LPCTSTR lpName,
    WAVEFORMATEX **ppWaveHeader, BYTE **ppbWaveData, DWORD *pcbWaveSize)
{
    HRSRC hResInfo;
    HGLOBAL hResData;
    void *pvRes;

    if (((hResInfo = FindResource(hModule, lpName, c_szWAV)) != NULL) &&
    ((hResData = LoadResource(hModule, hResInfo)) != NULL) &&
    ((pvRes = LockResource(hResData)) != NULL) &&
    DSParseWaveResource(pvRes, ppWaveHeader, ppbWaveData, pcbWaveSize))
    {
    return TRUE;
    }

    return FALSE;
}

///////////////////////////////////////////////////////////////////////////////
// SndObj fns
///////////////////////////////////////////////////////////////////////////////

SNDOBJ *SndObjCreate(IDirectSound *pDS, LPCTSTR lpName, int iConcurrent)
{
    SNDOBJ *pSO = NULL;
    LPWAVEFORMATEX pWaveHeader;
    BYTE *pbData;
    DWORD cbData;

    if (DSGetWaveResource(NULL, lpName, &pWaveHeader, &pbData, &cbData))
    {
    if (iConcurrent < 1)
        iConcurrent = 1;

    if ((pSO = (SNDOBJ *)LocalAlloc(LPTR, sizeof(SNDOBJ) +
        (iConcurrent-1) * sizeof(IDirectSoundBuffer *))) != NULL)
    {
        int i;

        pSO->iAlloc = iConcurrent;
        pSO->pbWaveData = pbData;
        pSO->cbWaveSize = cbData;
        pSO->Buffers[0] = DSLoadSoundBuffer(pDS, lpName);

        for (i=1; i<pSO->iAlloc; i++)
        {
        if (FAILED(IDirectSound_DuplicateSoundBuffer(pDS,
            pSO->Buffers[0], &pSO->Buffers[i])))
        {
            pSO->Buffers[i] = DSLoadSoundBuffer(pDS, lpName);
            if (!pSO->Buffers[i]) {
            SndObjDestroy(pSO);
            pSO = NULL;
            break;
            }
        }
        }
    }
    }

    return pSO;
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////

void SndObjDestroy(SNDOBJ *pSO)
{
    if (pSO)
    {
    int i;

    for (i=0; i<pSO->iAlloc; i++)
    {
        if (pSO->Buffers[i])
        {
        IDirectSoundBuffer_Release(pSO->Buffers[i]);
        pSO->Buffers[i] = NULL;
        }
    }
    LocalFree((HANDLE)pSO);
    }
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////

IDirectSoundBuffer *SndObjGetFreeBuffer(SNDOBJ *pSO)
{
    IDirectSoundBuffer *pDSB;

    if (pSO == NULL)
    return NULL;

    if (pDSB = pSO->Buffers[pSO->iCurrent])
    {
    HRESULT hres;
    DWORD dwStatus;

    hres = IDirectSoundBuffer_GetStatus(pDSB, &dwStatus);

    if (FAILED(hres))
        dwStatus = 0;

    if ((dwStatus & DSBSTATUS_PLAYING) == DSBSTATUS_PLAYING)
    {
        if (pSO->iAlloc > 1)
        {
        if (++pSO->iCurrent >= pSO->iAlloc)
            pSO->iCurrent = 0;

        pDSB = pSO->Buffers[pSO->iCurrent];
        hres = IDirectSoundBuffer_GetStatus(pDSB, &dwStatus);

        if (SUCCEEDED(hres) && (dwStatus & DSBSTATUS_PLAYING) == DSBSTATUS_PLAYING)
        {
            IDirectSoundBuffer_Stop(pDSB);
            IDirectSoundBuffer_SetCurrentPosition(pDSB, 0);
        }
        }
        else
        {
        pDSB = NULL;
        }
    }

    if (pDSB && (dwStatus & DSBSTATUS_BUFFERLOST))
    {
        if (FAILED(IDirectSoundBuffer_Restore(pDSB)) ||
        !DSFillSoundBuffer(pDSB, pSO->pbWaveData, pSO->cbWaveSize))
        {
        pDSB = NULL;
        }
    }
    }

    return pDSB;
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////

BOOL SndObjPlay(SNDOBJ *pSO, DWORD dwPlayFlags)
{
    BOOL result = FALSE;

    if (pSO == NULL)
    return FALSE;

    if ((!(dwPlayFlags & DSBPLAY_LOOPING) || (pSO->iAlloc == 1)))
    {
    IDirectSoundBuffer *pDSB = SndObjGetFreeBuffer(pSO);
    if (pDSB != NULL) {
        result = SUCCEEDED(IDirectSoundBuffer_Play(pDSB, 0, 0, dwPlayFlags));
    }
    }

    return result;
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////

BOOL SndObjStop(SNDOBJ *pSO)
{
    int i;

    if (pSO == NULL)
    return FALSE;

    for (i=0; i<pSO->iAlloc; i++)
    {
    IDirectSoundBuffer_Stop(pSO->Buffers[i]);
    IDirectSoundBuffer_SetCurrentPosition(pSO->Buffers[i], 0);
    }

    return TRUE;
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////

BOOL DSFillSoundBuffer(IDirectSoundBuffer *pDSB, BYTE *pbWaveData, DWORD cbWaveSize)
{
    if (pDSB && pbWaveData && cbWaveSize)
    {
    LPVOID pMem1, pMem2;
    DWORD dwSize1, dwSize2;

    if (SUCCEEDED(IDirectSoundBuffer_Lock(pDSB, 0, cbWaveSize,
        &pMem1, &dwSize1, &pMem2, &dwSize2, 0)))
    {
        CopyMemory(pMem1, pbWaveData, dwSize1);

        if ( 0 != dwSize2 )
        CopyMemory(pMem2, pbWaveData+dwSize1, dwSize2);

        IDirectSoundBuffer_Unlock(pDSB, pMem1, dwSize1, pMem2, dwSize2);
        return TRUE;
    }
    }

    return FALSE;
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////

BOOL DSParseWaveResource(void *pvRes, WAVEFORMATEX **ppWaveHeader, BYTE **ppbWaveData,DWORD *pcbWaveSize)
{
    DWORD *pdw;
    DWORD *pdwEnd;
    DWORD dwRiff;
    DWORD dwType;
    DWORD dwLength;

    if (ppWaveHeader)
    *ppWaveHeader = NULL;

    if (ppbWaveData)
    *ppbWaveData = NULL;

    if (pcbWaveSize)
    *pcbWaveSize = 0;

    pdw = (DWORD *)pvRes;
    dwRiff = *pdw++;
    dwLength = *pdw++;
    dwType = *pdw++;

    if (dwRiff != mmioFOURCC('R', 'I', 'F', 'F'))
    goto exit;      // not even RIFF

    if (dwType != mmioFOURCC('W', 'A', 'V', 'E'))
    goto exit;      // not a WAV

    pdwEnd = (DWORD *)((BYTE *)pdw + dwLength-4);

    while (pdw < pdwEnd)
    {
    dwType = *pdw++;
    dwLength = *pdw++;

    switch (dwType)
    {
    case mmioFOURCC('f', 'm', 't', ' '):
        if (ppWaveHeader && !*ppWaveHeader)
        {
        if (dwLength < sizeof(WAVEFORMAT))
            goto exit;      // not a WAV

        *ppWaveHeader = (WAVEFORMATEX *)pdw;

        if ((!ppbWaveData || *ppbWaveData) &&
            (!pcbWaveSize || *pcbWaveSize))
        {
            return TRUE;
        }
        }
        break;

    case mmioFOURCC('d', 'a', 't', 'a'):
        if ((ppbWaveData && !*ppbWaveData) ||
        (pcbWaveSize && !*pcbWaveSize))
        {
        if (ppbWaveData)
            *ppbWaveData = (LPBYTE)pdw;

        if (pcbWaveSize)
            *pcbWaveSize = dwLength;

        if (!ppWaveHeader || *ppWaveHeader)
            return TRUE;
        }
        break;
    }

    pdw = (DWORD *)((BYTE *)pdw + ((dwLength+1)&~1));
    }

exit:
    return FALSE;
}
