/** 
 * \file Sound.cpp
 * \brief CSound class function implementation.
 * CSound uses DirectSound interface for playing wav files, providing user only simple 'Init and Play' interface.
 * 
 * \author Petar Bajic, MPE (C) All Rights Reserved, Homepage: www.mystic-peanut.com
 * \date January, 16 2007.
 */

#include "Sound.h"

/**
 * \brief Standard constructor.
 * \author Petar Bajic 
 * \date January, 16 2007.
 */
CSound::CSound()
{
	m_apDSBuffer = NULL;
	m_pWaveFile = NULL;
}

/**
 * \brief Standard destructor.
 * \author Petar Bajic 
 * \date January, 16 2007.
 */
CSound::~CSound()
{
	SAFE_DELETE(m_apDSBuffer);
	SAFE_DELETE(m_pWaveFile);
}

/**
 * \brief Loads wav file and creates CWaveFile instance.
 * \param strWaveFileName - media file
 * \author Petar Bajic 
 * \date January, 16 2007.
 */
bool CSound::Init(IDirectSound8* pDS, LPWSTR strWaveFileName)
{
	HRESULT hr;

	DWORD dwCreationFlags = 0;
	GUID guid3DAlgorithm = GUID_NULL;
	DWORD dwNumBuffers = 1;

	m_apDSBuffer = new LPDIRECTSOUNDBUFFER[dwNumBuffers];
	m_pWaveFile = new CWaveFile();
	m_pWaveFile->Open( strWaveFileName);

	// Make the DirectSound buffer the same size as the wav file
	m_dwDSBufferSize = m_pWaveFile->GetSize();
	if(m_dwDSBufferSize == 0)
		return false;

	// Create the direct sound buffer, and only request the flags needed
    // since each requires some overhead and limits if the buffer can
    // be hardware accelerated
    DSBUFFERDESC dsbd;
    ZeroMemory( &dsbd, sizeof(DSBUFFERDESC) );
    dsbd.dwSize          = sizeof(DSBUFFERDESC);
    dsbd.dwFlags         = dwCreationFlags;
    dsbd.dwBufferBytes   = m_dwDSBufferSize;
    dsbd.guid3DAlgorithm = guid3DAlgorithm;
    dsbd.lpwfxFormat     = m_pWaveFile->m_pwfx;

    // DirectSound is only guarenteed to play PCM data.  Other
    // formats may or may not work depending the sound card driver.
    if( FAILED(hr = pDS->CreateSoundBuffer( &dsbd, &m_apDSBuffer[0], NULL )))
		return false;

	VOID*   pDSLockedBuffer      = NULL; // Pointer to locked buffer memory
	DWORD   dwDSLockedBufferSize = 0;    // Size of the locked DirectSound buffer
	DWORD   dwWavDataRead        = 0;    // Amount of data read from the wav file

    // Lock the buffer down
    if( FAILED( hr = m_apDSBuffer[0]->Lock( 0, m_dwDSBufferSize, &pDSLockedBuffer, &dwDSLockedBufferSize, NULL, NULL, 0L ) ) )
        return false;

    // Reset the wave file to the beginning
    m_pWaveFile->ResetFile();

    if( FAILED( hr = m_pWaveFile->Read( (BYTE*) pDSLockedBuffer, dwDSLockedBufferSize, &dwWavDataRead )))
        return false;

    if( dwWavDataRead == 0 )
    {
        // Wav is blank, so just fill with silence
        FillMemory( (BYTE*) pDSLockedBuffer, dwDSLockedBufferSize, (BYTE)(m_pWaveFile->m_pwfx->wBitsPerSample == 8 ? 128 : 0 ) );
    }
	else if( dwWavDataRead < dwDSLockedBufferSize )
	{
		//Rest of the buffer just fill in silence
		FillMemory( (BYTE*) pDSLockedBuffer + dwWavDataRead, dwDSLockedBufferSize - dwWavDataRead, (BYTE)(m_pWaveFile->m_pwfx->wBitsPerSample == 8 ? 128 : 0 ) );
	}

	// Unlock the buffer, we don't need it anymore.
	m_apDSBuffer[0]->Unlock( pDSLockedBuffer, dwDSLockedBufferSize, NULL, 0 );

	return true;
}

/**
 * \brief Plays the sound using DirectSound buffer interface.
 * \author Petar Bajic 
 * \date January, 16 2007.
 */
HRESULT CSound::Play()
{
    if( m_apDSBuffer == NULL )
        return CO_E_NOTINITIALIZED;

    return m_apDSBuffer[0]->Play( 0, 0, 0 );
}

/**
 * \brief Stop the sound.
 * \author Petar Bajic 
 * \date January, 16 2007.
 */
HRESULT CSound::Stop()
{
    if( m_apDSBuffer == NULL )
        return CO_E_NOTINITIALIZED;

    HRESULT hr = 0;

    hr = m_apDSBuffer[0]->Stop();

    return hr;
}

/**
 * \brief Reset sound buffer.
 * \author Petar Bajic 
 * \date January, 16 2007.
 */
HRESULT CSound::Reset()
{
    if( m_apDSBuffer == NULL )
        return CO_E_NOTINITIALIZED;

    HRESULT hr = 0;

    hr = m_apDSBuffer[0]->SetCurrentPosition( 0 );

    return hr;
}

/**
 * \brief Checks to see if a buffer is playing and returns TRUE if it is.
 * \author Petar Bajic 
 * \date January, 16 2007.
 */
BOOL CSound::IsSoundPlaying()
{
    BOOL bIsPlaying = FALSE;

    if( m_apDSBuffer == NULL )
        return FALSE;


	if( m_apDSBuffer[0] )
	{
		DWORD dwStatus = 0;
		m_apDSBuffer[0]->GetStatus( &dwStatus );
		bIsPlaying = ( ( dwStatus & DSBSTATUS_PLAYING ) != 0 );
	}

    return bIsPlaying;
}

/**
 * \brief Standard constructor.
 * \author Microsoft DirectX Sample Common 
 */
CWaveFile::CWaveFile()
{
    m_pwfx    = NULL;
    m_hmmio   = NULL;
    m_pResourceBuffer = NULL;
    m_dwSize  = 0;
}

/**
 * \brief Standard destructor.
 * \author Microsoft DirectX Sample Common 
 */
CWaveFile::~CWaveFile()
{
    Close();
}

/**
 * \brief Opens a wave file for reading.
 * \author Microsoft DirectX Sample Common 
 */
HRESULT CWaveFile::Open( LPWSTR strFileName)
{
    HRESULT hr;

    if( strFileName == NULL )
        return E_INVALIDARG;
    SAFE_DELETE_ARRAY( m_pwfx );

    m_hmmio = mmioOpen( strFileName, NULL, MMIO_ALLOCBUF | MMIO_READ );

    if( NULL == m_hmmio )
    {
        HRSRC   hResInfo;
        HGLOBAL hResData;
        DWORD   dwSize;
        VOID*   pvRes;

        // Loading it as a file failed, so try it as a resource
        if( NULL == ( hResInfo = FindResource( NULL, strFileName, L"WAVE" ) ) )
        {
            if( NULL == ( hResInfo = FindResource( NULL, strFileName, L"WAV" ) ) )
                return E_FAIL;
        }

        if( NULL == ( hResData = LoadResource( GetModuleHandle(NULL), hResInfo ) ) )
            return E_FAIL;

        if( 0 == ( dwSize = SizeofResource( GetModuleHandle(NULL), hResInfo ) ) )
            return E_FAIL;

        if( NULL == ( pvRes = LockResource( hResData ) ) )
            return E_FAIL;

        m_pResourceBuffer = new CHAR[ dwSize ];
        if( m_pResourceBuffer == NULL )
            return E_OUTOFMEMORY;
        memcpy( m_pResourceBuffer, pvRes, dwSize );

        MMIOINFO mmioInfo;
        ZeroMemory( &mmioInfo, sizeof(mmioInfo) );
        mmioInfo.fccIOProc = FOURCC_MEM;
        mmioInfo.cchBuffer = dwSize;
        mmioInfo.pchBuffer = (CHAR*) m_pResourceBuffer;

        m_hmmio = mmioOpen( NULL, &mmioInfo, MMIO_ALLOCBUF | MMIO_READ );
    }

    if( FAILED( hr = ReadMMIO() ) )
    {
        // ReadMMIO will fail if its an not a wave file
        mmioClose( m_hmmio, 0 );
        return hr;
    }

    if( FAILED( hr = ResetFile() ) )
        return hr;

    // After the reset, the size of the wav file is m_ck.cksize so store it now
    m_dwSize = m_ck.cksize;


    return hr;
}

/**
 * \brief Support function for reading from a multimedia I/O stream.
 *
 * m_hmmio must be valid before calling.  This function uses it to update m_ckRiff, and m_pwfx.
 * \author Microsoft DirectX Sample Common 
 */
HRESULT CWaveFile::ReadMMIO()
{
    MMCKINFO        ckIn;           // chunk info. for general use.
    PCMWAVEFORMAT   pcmWaveFormat;  // Temp PCM structure to load in.

    m_pwfx = NULL;

    if( ( 0 != mmioDescend( m_hmmio, &m_ckRiff, NULL, 0 ) ) )
        return E_FAIL;

    // Check to make sure this is a valid wave file
    if( (m_ckRiff.ckid != FOURCC_RIFF) ||
        (m_ckRiff.fccType != mmioFOURCC('W', 'A', 'V', 'E') ) )
        return E_FAIL;

    // Search the input file for for the 'fmt ' chunk.
    ckIn.ckid = mmioFOURCC('f', 'm', 't', ' ');
    if( 0 != mmioDescend( m_hmmio, &ckIn, &m_ckRiff, MMIO_FINDCHUNK ) )
        return E_FAIL;

    // Expect the 'fmt' chunk to be at least as large as <PCMWAVEFORMAT>;
    // if there are extra parameters at the end, we'll ignore them
       if( ckIn.cksize < (LONG) sizeof(PCMWAVEFORMAT) )
           return E_FAIL;

    // Read the 'fmt ' chunk into <pcmWaveFormat>.
    if( mmioRead( m_hmmio, (HPSTR) &pcmWaveFormat,
                  sizeof(pcmWaveFormat)) != sizeof(pcmWaveFormat) )
        return E_FAIL;

    // Allocate the waveformatex, but if its not pcm format, read the next
    // word, and thats how many extra bytes to allocate.
    if( pcmWaveFormat.wf.wFormatTag == WAVE_FORMAT_PCM )
    {
        m_pwfx = (WAVEFORMATEX*)new CHAR[ sizeof(WAVEFORMATEX) ];
        if( NULL == m_pwfx )
            return E_FAIL;

        // Copy the bytes from the pcm structure to the waveformatex structure
        memcpy( m_pwfx, &pcmWaveFormat, sizeof(pcmWaveFormat) );
        m_pwfx->cbSize = 0;
    }
    else
    {
        // Read in length of extra bytes.
        WORD cbExtraBytes = 0L;
        if( mmioRead( m_hmmio, (CHAR*)&cbExtraBytes, sizeof(WORD)) != sizeof(WORD) )
            return E_FAIL;

        m_pwfx = (WAVEFORMATEX*)new CHAR[ sizeof(WAVEFORMATEX) + cbExtraBytes ];
        if( NULL == m_pwfx )
            return E_FAIL;

        // Copy the bytes from the pcm structure to the waveformatex structure
        memcpy( m_pwfx, &pcmWaveFormat, sizeof(pcmWaveFormat) );
        m_pwfx->cbSize = cbExtraBytes;

        // Now, read those extra bytes into the structure, if cbExtraAlloc != 0.
        if( mmioRead( m_hmmio, (CHAR*)(((BYTE*)&(m_pwfx->cbSize))+sizeof(WORD)),
                      cbExtraBytes ) != cbExtraBytes )
        {
            SAFE_DELETE( m_pwfx );
            return E_FAIL;
        }
    }

    // Ascend the input file out of the 'fmt ' chunk.
    if( 0 != mmioAscend( m_hmmio, &ckIn, 0 ) )
    {
        SAFE_DELETE( m_pwfx );
        return E_FAIL;
    }

    return S_OK;
}

/**
 * \brief Reads section of data from a wave file into pBuffer and returns how much read in pdwSizeRead, 
 * reading not more than dwSizeToRead.
 *
 * This uses m_ck to determine where to start reading from. So subsequent calls will be continue where 
 * the last left off unless Reset() is called.
 * \author Microsoft DirectX Sample Common 
 */
HRESULT CWaveFile::Read( BYTE* pBuffer, DWORD dwSizeToRead, DWORD* pdwSizeRead )
{
        MMIOINFO mmioinfoIn; // current status of m_hmmio

        if( m_hmmio == NULL )
            return CO_E_NOTINITIALIZED;
        if( pBuffer == NULL || pdwSizeRead == NULL )
            return E_INVALIDARG;

        if( pdwSizeRead != NULL )
            *pdwSizeRead = 0;

        if( 0 != mmioGetInfo( m_hmmio, &mmioinfoIn, 0 ) )
            return E_FAIL;

        UINT cbDataIn = dwSizeToRead;
        if( cbDataIn > m_ck.cksize )
            cbDataIn = m_ck.cksize;

        m_ck.cksize -= cbDataIn;

        for( DWORD cT = 0; cT < cbDataIn; cT++ )
        {
            // Copy the bytes from the io to the buffer.
            if( mmioinfoIn.pchNext == mmioinfoIn.pchEndRead )
            {
                if( 0 != mmioAdvance( m_hmmio, &mmioinfoIn, MMIO_READ ) )
                    return E_FAIL;

                if( mmioinfoIn.pchNext == mmioinfoIn.pchEndRead )
                    return E_FAIL;
            }

            // Actual copy.
            *((BYTE*)pBuffer+cT) = *((BYTE*)mmioinfoIn.pchNext);
            mmioinfoIn.pchNext++;
        }

        if( 0 != mmioSetInfo( m_hmmio, &mmioinfoIn, 0 ) )
            return E_FAIL;

        if( pdwSizeRead != NULL )
            *pdwSizeRead = cbDataIn;

        return S_OK;
}

/**
 * \brief Returns size of file.
 * \author Microsoft DirectX Sample Common 
 */
DWORD CWaveFile::GetSize()
{
    return m_dwSize;
}

/**
 * \brief Resets the internal m_ck pointer so reading starts from the beginning of the file again
 * \author Microsoft DirectX Sample Common 
 */
HRESULT CWaveFile::ResetFile()
{
    if( m_hmmio == NULL )
        return CO_E_NOTINITIALIZED;

    // Seek to the data
    if( -1 == mmioSeek( m_hmmio, m_ckRiff.dwDataOffset + sizeof(FOURCC),
                    SEEK_SET ) )
        return E_FAIL;

    // Search the input file for the 'data' chunk.
    m_ck.ckid = mmioFOURCC('d', 'a', 't', 'a');
    if( 0 != mmioDescend( m_hmmio, &m_ck, &m_ckRiff, MMIO_FINDCHUNK ) )
        return E_FAIL;


    return S_OK;
}

/**
 * \brief Close the wave file
 * \author Microsoft DirectX Sample Common 
 */
HRESULT CWaveFile::Close()
{
    mmioClose( m_hmmio, 0 );
    m_hmmio = NULL;
    SAFE_DELETE_ARRAY( m_pResourceBuffer );

    return S_OK;
}