/*==LICENSE==*

CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011  Cyan Worlds, Inc.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program 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 General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

Additional permissions under GNU GPL version 3 section 7

If you modify this Program, or any covered work, by linking or
combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK,
NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent
JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK
(or a modified version of those libraries),
containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA,
PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG
JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the
licensors of this Program grant you additional
permission to convey the resulting work. Corresponding Source for a
non-source form of such a combination shall include the source code for
the parts of OpenSSL and IJG JPEG Library used as well as that of the covered
work.

You can contact Cyan Worlds, Inc. by email legal@cyan.com
 or by snail mail at:
      Cyan Worlds, Inc.
      14617 N Newport Hwy
      Mead, WA   99021

*==LICENSE==*/
#include "plWavFile.h"

#ifdef HS_BUILD_FOR_WIN32

#include <dsound.h>

#include "plFileSystem.h"

static inline HRESULT DXTrace(const TCHAR* msg, const char* file, int line, HRESULT hr)
{
    ST::string error = ST::format("Error Calling: {}\n{}", msg, (hsCOMError)hr);
    ErrorAssert(line, file, error.c_str());
    return hr;
}

#define DXTRACE_ERR(msg, hr) DXTrace(msg, __FILE__, __LINE__, hr)

// if it looks like I lifted this class directly from Microsoft it's because that
// is exactly what I did.  It's okay, though.  Microsoft tells you to go ahead
// and do it in the DX8 documentation.  They are SO nice.

//-----------------------------------------------------------------------------
// Name: CWaveFile::CWaveFile()
// Desc: Constructs the class.  Call Open() to open a wave file for reading.  
//       Then call Read() as needed.  Calling the destructor or Close() 
//       will close the file.  
//-----------------------------------------------------------------------------
CWaveFile::CWaveFile()
    : m_pwfx(), m_hmmio(), m_dwSize(), m_ckRiff(),
      m_bIsReadingFromMemory(), fSecsPerSample(), m_mmioinfoOut(), m_dwFlags(),
      m_pbData(), m_pbDataCur(), m_ulDataSize(), fHeader()
{ }


//-----------------------------------------------------------------------------
// Name: CWaveFile::~CWaveFile()
// Desc: Destructs the class
//-----------------------------------------------------------------------------
CWaveFile::~CWaveFile()
{
    Close();

    if( !m_bIsReadingFromMemory )
    {
        free(m_pwfx);
    }

    int i;
    for(i = 0 ; i < fMarkers.size() ; i++)
    {
        delete [] fMarkers[i]->fName;
    }
}




//-----------------------------------------------------------------------------
// Name: CWaveFile::Open()
// Desc: Opens a wave file for reading
//-----------------------------------------------------------------------------
HRESULT CWaveFile::Open(const plFileName &fileName, WAVEFORMATEX* pwfx, DWORD dwFlags)
{
    HRESULT hr;

    m_dwFlags = dwFlags;
    m_bIsReadingFromMemory = FALSE;

    ST::wchar_buffer wFileName = fileName.WideString();

    if (m_dwFlags == WAVEFILE_READ)
    {
        free(m_pwfx);

        m_hmmio = mmioOpenW(wFileName.data(), nullptr, MMIO_ALLOCBUF | MMIO_READ);

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

            // Loading it as a file failed, so try it as a resource
            if (!(hResInfo = FindResourceW(nullptr, wFileName.data(), L"WAVE")))
            {
                if (!(hResInfo = FindResourceW(nullptr, wFileName.data(), L"WAV")))
                    return DXTRACE_ERR( TEXT("FindResource"), E_FAIL );
            }

            if (hResData = LoadResource(nullptr, hResInfo); hResData == nullptr)
                return DXTRACE_ERR( TEXT("LoadResource"), E_FAIL );

            if (dwSize = SizeofResource(nullptr, hResInfo); dwSize == 0)
                return DXTRACE_ERR( TEXT("SizeofResource"), E_FAIL );

            if (pvRes = LockResource(hResData); pvRes == nullptr)
                return DXTRACE_ERR( TEXT("LockResource"), E_FAIL );

            CHAR* pData = new CHAR[ dwSize ];
            memcpy( pData, pvRes, dwSize );

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

            m_hmmio = mmioOpenW(nullptr, &mmioInfo, MMIO_ALLOCBUF | MMIO_READ);
        }

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

        if( FAILED( hr = ResetFile() ) )
            return DXTRACE_ERR( TEXT("ResetFile"), hr );

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

    }
    else
    {
        m_hmmio = mmioOpenW(wFileName.data(), nullptr,
                            MMIO_ALLOCBUF | MMIO_READWRITE | MMIO_CREATE);
        if (m_hmmio == nullptr)
            return DXTRACE_ERR( TEXT("mmioOpen"), E_FAIL );

        if( FAILED( hr = WriteMMIO( pwfx ) ) )
        {
            mmioClose( m_hmmio, 0 );
            return DXTRACE_ERR( TEXT("WriteMMIO"), hr );
        }
                        
        if( FAILED( hr = ResetFile() ) )
            return DXTRACE_ERR( TEXT("ResetFile"), hr );
    }

    return hr;
}




//-----------------------------------------------------------------------------
// Name: CWaveFile::OpenFromMemory()
// Desc: copy data to CWaveFile member variable from memory
//-----------------------------------------------------------------------------
HRESULT CWaveFile::OpenFromMemory( BYTE* pbData, ULONG ulDataSize, 
                                   WAVEFORMATEX* pwfx, DWORD dwFlags )
{
    m_pwfx       = pwfx;
    m_ulDataSize = ulDataSize;
    m_pbData     = pbData;
    m_pbDataCur  = m_pbData;
    m_bIsReadingFromMemory = TRUE;
    
    if( dwFlags != WAVEFILE_READ )
        return E_NOTIMPL;       
    
    return S_OK;
}



/*

    This defintion for a CuePoint was ripped from the internet somewhere. There are more defs at the end of this file which attempt to document 
    these wave file format extensions for storing markers.

    Cue Point--

    The dwIdentifier field contains a unique number (ie, different than the ID number of any other CuePoint structure). This is used to associate
    a CuePoint structure with other structures used in other chunks which will be described later. 

    The dwPosition field specifies the position of the cue point within the "play order" (as determined by the Playlist chunk. See that chunk for 
    a discussion of the play order).

    The fccChunk field specifies the chunk ID of the Data or Wave List chunk which actually contains the waveform data to which this CuePoint 
    refers. If there is only one Data chunk in the file, then this field is set to the ID 'data'. On the other hand, if the file contains a Wave 
    List (which can contain both 'data' and 'slnt' chunks), then fccChunk will specify 'data' or 'slnt' depending upon in which type of chunk the
    referenced waveform data is found.

    The dwChunkStart and dwBlockStart fields are set to 0 for an uncompressed WAVE file that contains one 'data' chunk. These fields are used 
    only for WAVE files that contain a Wave List (with multiple 'data' and 'slnt' chunks), or for a compressed file containing a 'data' chunk.
    (Actually, in the latter case, dwChunkStart is also set to 0, and only dwBlockStart is used). Again, I want to emphasize that you can avoid
    all of this unnecessary crap if you avoid hassling with compressed files, or Wave Lists, and instead stick to the sensible basics.

    The dwChunkStart field specifies the uint8_t offset of the start of the 'data' or 'slnt' chunk which actually contains the waveform data to
    which this CuePoint refers. This offset is relative to the start of the first chunk within the Wave List. (ie, It's the uint8_t offset, within
    the Wave List, of where the 'data' or 'slnt' chunk of interest appears. The first chunk within the List would be at an offset of 0).

    The dwBlockStart field specifies the uint8_t offset of the start of the block containing the position. This offset is relative to the start of
    the waveform data within the 'data' or 'slnt' chunk.

    The dwSampleOffset field specifies the sample offset of the cue point relative to the start of the block. In an uncompressed file, this 
    equates to simply being the offset within the waveformData array. Unfortunately, the WAVE documentation is much too ambiguous, and doesn't
    define what it means by the term "sample offset". This could mean a uint8_t offset, or it could mean counting the sample points (for example,
    in a 16-bit wave, every 2 bytes would be 1 sample point), or it could even mean sample frames (as the loop offsets in AIFF are specified).
    Who knows? The guy who conjured up the Cue chunk certainly isn't saying. I'm assuming that it's a uint8_t offset, like the above 2 fields.
*/

class CuePoint 
{
public:
  DWORD   dwIdentifier;
  DWORD   dwPosition;
  FOURCC  fccChunk;     
  DWORD   dwChunkStart;
  DWORD   dwBlockStart;
  DWORD   dwSampleOffset;

public:
    CuePoint(DWORD id, DWORD pos, FOURCC chk, DWORD ckSt, DWORD BkSt, DWORD SO)
        : dwIdentifier(id), dwPosition(pos), fccChunk(chk),
          dwChunkStart(ckSt), dwBlockStart(BkSt), dwSampleOffset(SO)
        { }
    CuePoint()
        : dwIdentifier(), dwPosition(), fccChunk(),
          dwChunkStart(), dwBlockStart(), dwSampleOffset()
        { }
};



//
// this struct is used to hold cue pts temporarily while we wait for the labels that match them
//
struct myCuePoint
{
    DWORD fId;
    DWORD fOffset;
};



//-----------------------------------------------------------------------------
// Name: CWaveFile::ReadMMIO()
// Desc: 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. 
//-----------------------------------------------------------------------------
HRESULT CWaveFile::ReadMMIO()
{
    MMCKINFO        ckIn;           // chunk info. for general use.
    PCMWAVEFORMAT   pcmWaveFormat;  // Temp PCM structure to load in.       

    m_pwfx = nullptr;

    if (mmioDescend(m_hmmio, &m_ckRiff, nullptr, 0) != 0)
        return DXTRACE_ERR( TEXT("mmioDescend"), 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 DXTRACE_ERR( TEXT("mmioFOURCC"), 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 DXTRACE_ERR( TEXT("mmioDescend"), 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 DXTRACE_ERR( TEXT("sizeof(PCMWAVEFORMAT)"), E_FAIL );

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

    // Allocate the waveformatex, but if its not pcm format, read the next
    // uint16_t, and thats how many extra bytes to allocate.
    if( pcmWaveFormat.wf.wFormatTag == WAVE_FORMAT_PCM )
    {
        m_pwfx = (WAVEFORMATEX*)malloc(sizeof(WAVEFORMATEX));
        if (m_pwfx == nullptr)
            return DXTRACE_ERR( TEXT("m_pwfx"), 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 DXTRACE_ERR( TEXT("mmioRead"), E_FAIL );

        m_pwfx = (WAVEFORMATEX*)malloc(sizeof(WAVEFORMATEX) + cbExtraBytes);
        if (m_pwfx == nullptr)
            return DXTRACE_ERR( TEXT("new"), 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 )
        {
            free(m_pwfx);
            return DXTRACE_ERR( TEXT("mmioRead"), E_FAIL );
        }
    }

    fHeader.fAvgBytesPerSec = m_pwfx->nAvgBytesPerSec;
    fHeader.fBlockAlign = m_pwfx->nBlockAlign;
    fHeader.fNumChannels = m_pwfx->nChannels;
    fHeader.fNumSamplesPerSec = m_pwfx->nSamplesPerSec;
    fHeader.fBitsPerSample = m_pwfx->wBitsPerSample;
    fHeader.fFormatTag = m_pwfx->wFormatTag;

    fSecsPerSample = 1.0/ (double)(pcmWaveFormat.wf.nSamplesPerSec) ; // * (((double)pcmWaveFormat.wBitsPerSample)/8.0);


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



    //
    // Here is where we attempt to parse sound indicies from the file for loop points.
    // If there is no cue chunk then we just return OK
    //

    ckIn.ckid = mmioFOURCC('c', 'u', 'e', ' ');
    if( 0 != mmioDescend( m_hmmio, &ckIn, &m_ckRiff, MMIO_FINDCHUNK ) )
        return S_OK; // No Cue Chunck so no point in reading the rest

#if 0
   // Expect the 'cue ' 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(FOURCC) + 2*(sizeof(DWORD)) + sizeof(CuePoint) ) )
         return DXTRACE_ERR( TEXT("sizeof(CueChunk)"), E_FAIL );
#endif

    DWORD* CueBuff = new DWORD[ckIn.cksize];
    DWORD Results;
    Read((BYTE*)CueBuff, ckIn.cksize, &Results);

    std::vector<myCuePoint> myCueList; // Place to hold the cue points

    int numCuePoints = (ckIn.cksize - sizeof(DWORD))/sizeof(CuePoint); // this is how many there should be.
    unsigned int i, j;  
                
    for (i = 1, j = 0; i <= ckIn.cksize && j < numCuePoints ; i += sizeof(CuePoint)/(sizeof(DWORD)), j++)

    {
        myCuePoint p;
        p.fId = CueBuff[i];         // dwIdentifier
        p.fOffset = CueBuff[i+1];   // dwPosition
        myCueList.push_back(p);
    }

    delete[] CueBuff;

    if( 0 != mmioAscend( m_hmmio, &ckIn, 0 ) )
    {
        free(m_pwfx);
        return DXTRACE_ERR( TEXT("mmioAscend"), E_FAIL );
    }

    

    // Goal --> Grab the label information below
    ckIn.ckid = mmioFOURCC('a', 'd', 't', 'l');
    if( 0 != mmioDescend( m_hmmio, &ckIn, &m_ckRiff, MMIO_FINDLIST ) )
        return DXTRACE_ERR( TEXT("mmioDescend, with list"), E_FAIL );


    plSoundMarker *newMarker;

    BYTE *labelBuf = new BYTE [ckIn.cksize];

    // Read the entire lable chunk and then lets parse out the individual lables.
    Read(labelBuf, ckIn.cksize-4, &Results);

    BYTE *bp = labelBuf;
    // Keep looking for labl chunks till we run out.
    while(!strncmp("labl",(char*)bp,4))
    {
        DWORD size = *(DWORD*)(bp + 4);
        DWORD id = *(DWORD*)(bp + 8);
        newMarker = new plSoundMarker; // Grab a new label
    
        int i;

        int numPts = myCueList.size();
        //
        // Do we have a matching cue point for this label?
        //
        for(i = 0 ; i < numPts;  i++)
        {
            if(id == myCueList[i].fId)
            {
                newMarker->fOffset = myCueList[i].fOffset * fSecsPerSample;
            }
        }   
        int stringSize = size - sizeof(DWORD); // text string is size of chunck - size of the size uint16_t
        newMarker->fName = new char[ stringSize];

        strcpy(newMarker->fName, (char*)(bp + 12));
        
        fMarkers.push_back(newMarker);
        bp += size + 8; 

        // crappy fixup hack for odd length label records
        if(size & 1 && !strncmp("labl", (char*)(bp +1), 4))
            bp++;
     
        fprintf(stderr,"Label name=%s Time =%f\n",newMarker->fName, newMarker->fOffset);
    }

    delete [] labelBuf;

    if( 0 != mmioAscend( m_hmmio, &ckIn, 0 ) )
    {
        free(m_pwfx);
        return DXTRACE_ERR( TEXT("mmioAscend"), E_FAIL );
    }


    return S_OK;
}




//-----------------------------------------------------------------------------
// Name: CWaveFile::GetSize()
// Desc: Retuns the size of the read access wave file 
//-----------------------------------------------------------------------------
DWORD CWaveFile::GetSize()
{
    return m_dwSize;
}




//-----------------------------------------------------------------------------
// Name: CWaveFile::ResetFile()
// Desc: Resets the internal m_ck pointer so reading starts from the 
//       beginning of the file again 
//-----------------------------------------------------------------------------
HRESULT CWaveFile::ResetFile()
{
    if( m_bIsReadingFromMemory )
    {
        m_pbDataCur = m_pbData;
    }
    else 
    {
        if (m_hmmio == nullptr)
            return CO_E_NOTINITIALIZED;

        if( m_dwFlags == WAVEFILE_READ )
        {
            // Seek to the data
            if( -1 == mmioSeek( m_hmmio, m_ckRiff.dwDataOffset + sizeof(FOURCC),
                            SEEK_SET ) )
                return DXTRACE_ERR( TEXT("mmioSeek"), 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 DXTRACE_ERR( TEXT("mmioDescend"), E_FAIL );
        }
        else
        {
            // Create the 'data' chunk that holds the waveform samples.  
            m_ck.ckid = mmioFOURCC('d', 'a', 't', 'a');
            m_ck.cksize = 0;

            if( 0 != mmioCreateChunk( m_hmmio, &m_ck, 0 ) ) 
                return DXTRACE_ERR( TEXT("mmioCreateChunk"), E_FAIL );

            if( 0 != mmioGetInfo( m_hmmio, &m_mmioinfoOut, 0 ) )
                return DXTRACE_ERR( TEXT("mmioGetInfo"), E_FAIL );
        }
    }
    
    return S_OK;
}


#define MCN_USE_NEW_READ_METHOD 0

//-----------------------------------------------------------------------------
// Name: CWaveFile::Read()
// Desc: 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.
//-----------------------------------------------------------------------------
HRESULT CWaveFile::Read( BYTE* pBuffer, DWORD dwSizeToRead, DWORD* pdwSizeRead )
{
    if( m_bIsReadingFromMemory )
    {
        if (m_pbDataCur == nullptr)
            return CO_E_NOTINITIALIZED;
        if (pdwSizeRead != nullptr)
            *pdwSizeRead = 0;

        if( (BYTE*)(m_pbDataCur + dwSizeToRead) > 
            (BYTE*)(m_pbData + m_ulDataSize) )
        {
            dwSizeToRead = m_ulDataSize - (DWORD)(m_pbDataCur - m_pbData);
        }
        
        CopyMemory( pBuffer, m_pbDataCur, dwSizeToRead );
        
        if (pdwSizeRead != nullptr)
            *pdwSizeRead = dwSizeToRead;

        return S_OK;
    }
    else 
    {
        MMIOINFO mmioinfoIn; // current status of m_hmmio

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

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

        if( 0 != mmioGetInfo( m_hmmio, &mmioinfoIn, 0 ) )
            return DXTRACE_ERR( TEXT("mmioGetInfo"), E_FAIL );
                
        UINT cbDataIn = dwSizeToRead;
        if( cbDataIn > m_ck.cksize ) 
            cbDataIn = m_ck.cksize;       

        m_ck.cksize -= cbDataIn;
    
#if !(MCN_USE_NEW_READ_METHOD)
        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 DXTRACE_ERR( TEXT("mmioAdvance"), E_FAIL );

                if( mmioinfoIn.pchNext == mmioinfoIn.pchEndRead )
                    return DXTRACE_ERR( TEXT("mmioinfoIn.pchNext"), E_FAIL );
            }

            // Actual copy.
            *((BYTE*)pBuffer+cT) = *((BYTE*)mmioinfoIn.pchNext);
            mmioinfoIn.pchNext++;
        }
#else
        // Attempt to do this a bit faster... 9.12.2001 mcn
        for( DWORD cT = 0; cT < cbDataIn; )
        {
            // Copy the bytes from the io to the buffer.
            if( mmioinfoIn.pchNext == mmioinfoIn.pchEndRead )
            {
                if( 0 != mmioAdvance( m_hmmio, &mmioinfoIn, MMIO_READ ) )
                    return DXTRACE_ERR( TEXT("mmioAdvance"), E_FAIL );

                if( mmioinfoIn.pchNext == mmioinfoIn.pchEndRead )
                    return DXTRACE_ERR( TEXT("mmioinfoIn.pchNext"), E_FAIL );
            }

            // Actual copy
            DWORD   length = (DWORD)mmioinfoIn.pchEndRead - (DWORD)mmioinfoIn.pchNext;
            if( cT + length > cbDataIn )
                length = cbDataIn - cT;

            memcpy( (BYTE*)pBuffer + cT, mmioinfoIn.pchNext, length );
            mmioinfoIn.pchNext += length;
            cT += length;
        }
#endif

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

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

        return S_OK;
    }
}

//-----------------------------------------------------------------------------
// Name: CWaveFile::AdvanceWithoutRead()
// Desc: Identical to Read(), only doesn't actually read any data in.
//-----------------------------------------------------------------------------
HRESULT CWaveFile::AdvanceWithoutRead( DWORD dwSizeToRead, DWORD* pdwSizeRead )
{
    if( m_bIsReadingFromMemory )
    {
        if (m_pbDataCur == nullptr)
            return CO_E_NOTINITIALIZED;
        if (pdwSizeRead != nullptr)
            *pdwSizeRead = 0;

        if( (BYTE*)(m_pbDataCur + dwSizeToRead) > 
            (BYTE*)(m_pbData + m_ulDataSize) )
        {
            dwSizeToRead = m_ulDataSize - (DWORD)(m_pbDataCur - m_pbData);
        }
        
        if (pdwSizeRead != nullptr)
            *pdwSizeRead = dwSizeToRead;

        return S_OK;
    }
    else 
    {
        MMIOINFO mmioinfoIn; // current status of m_hmmio

        if (m_hmmio == nullptr)
            return CO_E_NOTINITIALIZED;
        if (pdwSizeRead == nullptr)
            return E_INVALIDARG;

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

        if( 0 != mmioGetInfo( m_hmmio, &mmioinfoIn, 0 ) )
            return DXTRACE_ERR( TEXT("mmioGetInfo"), E_FAIL );
                
        UINT cbDataIn = dwSizeToRead;
        if( cbDataIn > m_ck.cksize ) 
            cbDataIn = m_ck.cksize;       

        m_ck.cksize -= cbDataIn;
    
#if !(MCN_USE_NEW_READ_METHOD)
        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 DXTRACE_ERR( TEXT("mmioAdvance"), E_FAIL );

                if( mmioinfoIn.pchNext == mmioinfoIn.pchEndRead )
                    return DXTRACE_ERR( TEXT("mmioinfoIn.pchNext"), E_FAIL );
            }

            mmioinfoIn.pchNext++;
        }
#else
        // Attempt to do this a bit faster... 9.12.2001 mcn
        for( DWORD cT = 0; cT < cbDataIn; )
        {
            if( mmioinfoIn.pchNext == mmioinfoIn.pchEndRead )
            {
                if( 0 != mmioAdvance( m_hmmio, &mmioinfoIn, MMIO_READ ) )
                    return DXTRACE_ERR( TEXT("mmioAdvance"), E_FAIL );

                if( mmioinfoIn.pchNext == mmioinfoIn.pchEndRead )
                    return DXTRACE_ERR( TEXT("mmioinfoIn.pchNext"), E_FAIL );
            }

            // Advance
            DWORD   length = (DWORD)mmioinfoIn.pchEndRead - (DWORD)mmioinfoIn.pchNext;
            if( cT + length > cbDataIn )
                length = cbDataIn - cT;

            mmioinfoIn.pchNext += length;
            cT += length;
        }
#endif

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

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

        return S_OK;
    }
}


//-----------------------------------------------------------------------------
// Name: CWaveFile::IClose()
// Desc: Closes the wave file 
//-----------------------------------------------------------------------------
HRESULT CWaveFile::IClose()
{
    if( m_dwFlags == WAVEFILE_READ )
    {
        mmioClose( m_hmmio, 0 );
        m_hmmio = nullptr;
    }
    else
    {
        m_mmioinfoOut.dwFlags |= MMIO_DIRTY;

        if (m_hmmio == nullptr)
            return CO_E_NOTINITIALIZED;

        if( 0 != mmioSetInfo( m_hmmio, &m_mmioinfoOut, 0 ) )
            return DXTRACE_ERR( TEXT("mmioSetInfo"), E_FAIL );
    
        // Ascend the output file out of the 'data' chunk -- this will cause
        // the chunk size of the 'data' chunk to be written.
        if( 0 != mmioAscend( m_hmmio, &m_ck, 0 ) )
            return DXTRACE_ERR( TEXT("mmioAscend"), E_FAIL );
    
        // Do this here instead...
        if( 0 != mmioAscend( m_hmmio, &m_ckRiff, 0 ) )
            return DXTRACE_ERR( TEXT("mmioAscend"), E_FAIL );
        
        mmioSeek( m_hmmio, 0, SEEK_SET );

        if ((INT)mmioDescend(m_hmmio, &m_ckRiff, nullptr, 0) != 0)
            return DXTRACE_ERR( TEXT("mmioDescend"), E_FAIL );
    
        m_ck.ckid = mmioFOURCC('f', 'a', 'c', 't');

        if( 0 == mmioDescend( m_hmmio, &m_ck, &m_ckRiff, MMIO_FINDCHUNK ) ) 
        {
            DWORD dwSamples = 0;
            mmioWrite( m_hmmio, (HPSTR)&dwSamples, sizeof(DWORD) );
            mmioAscend( m_hmmio, &m_ck, 0 ); 
        }
    
        // Ascend the output file out of the 'RIFF' chunk -- this will cause
        // the chunk size of the 'RIFF' chunk to be written.
        if( 0 != mmioAscend( m_hmmio, &m_ckRiff, 0 ) )
            return DXTRACE_ERR( TEXT("mmioAscend"), E_FAIL );
    
        mmioClose( m_hmmio, 0 );
        m_hmmio = nullptr;
    }

    return S_OK;
}




//-----------------------------------------------------------------------------
// Name: CWaveFile::WriteMMIO()
// Desc: Support function for reading from a multimedia I/O stream
//       pwfxDest is the WAVEFORMATEX for this new wave file.  
//       m_hmmio must be valid before calling.  This function uses it to
//       update m_ckRiff, and m_ck.  
//-----------------------------------------------------------------------------
HRESULT CWaveFile::WriteMMIO( WAVEFORMATEX *pwfxDest )
{
    DWORD    dwFactChunk; // Contains the actual fact chunk. Garbage until WaveCloseWriteFile.
    MMCKINFO ckOut1;
    
    dwFactChunk = (DWORD)-1;

    // Create the output file RIFF chunk of form type 'WAVE'.
    m_ckRiff.fccType = mmioFOURCC('W', 'A', 'V', 'E');       
    m_ckRiff.cksize = 0;

    if( 0 != mmioCreateChunk( m_hmmio, &m_ckRiff, MMIO_CREATERIFF ) )
        return DXTRACE_ERR( TEXT("mmioCreateChunk"), E_FAIL );
    
    // We are now descended into the 'RIFF' chunk we just created.
    // Now create the 'fmt ' chunk. Since we know the size of this chunk,
    // specify it in the MMCKINFO structure so MMIO doesn't have to seek
    // back and set the chunk size after ascending from the chunk.
    m_ck.ckid = mmioFOURCC('f', 'm', 't', ' ');
    m_ck.cksize = sizeof(PCMWAVEFORMAT);   

    if( 0 != mmioCreateChunk( m_hmmio, &m_ck, 0 ) )
        return DXTRACE_ERR( TEXT("mmioCreateChunk"), E_FAIL );
    
    // Write the PCMWAVEFORMAT structure to the 'fmt ' chunk if its that type. 
    if( pwfxDest->wFormatTag == WAVE_FORMAT_PCM )
    {
        if( mmioWrite( m_hmmio, (HPSTR) pwfxDest, 
                       sizeof(PCMWAVEFORMAT)) != sizeof(PCMWAVEFORMAT))
            return DXTRACE_ERR( TEXT("mmioWrite"), E_FAIL );
    }   
    else 
    {
        // Write the variable length size.
        if( (UINT)mmioWrite( m_hmmio, (HPSTR) pwfxDest, 
                             sizeof(*pwfxDest) + pwfxDest->cbSize ) != 
                             ( sizeof(*pwfxDest) + pwfxDest->cbSize ) )
            return DXTRACE_ERR( TEXT("mmioWrite"), E_FAIL );
    }  
    
    // Ascend out of the 'fmt ' chunk, back into the 'RIFF' chunk.
    if( 0 != mmioAscend( m_hmmio, &m_ck, 0 ) )
        return DXTRACE_ERR( TEXT("mmioAscend"), E_FAIL );
    
    // Now create the fact chunk, not required for PCM but nice to have.  This is filled
    // in when the close routine is called.
    ckOut1.ckid = mmioFOURCC('f', 'a', 'c', 't');
    ckOut1.cksize = 0;

    if( 0 != mmioCreateChunk( m_hmmio, &ckOut1, 0 ) )
        return DXTRACE_ERR( TEXT("mmioCreateChunk"), E_FAIL );
    
    if( mmioWrite( m_hmmio, (HPSTR)&dwFactChunk, sizeof(dwFactChunk)) != 
                    sizeof(dwFactChunk) )
         return DXTRACE_ERR( TEXT("mmioWrite"), E_FAIL );
    
    // Now ascend out of the fact chunk...
    if( 0 != mmioAscend( m_hmmio, &ckOut1, 0 ) )
        return DXTRACE_ERR( TEXT("mmioAscend"), E_FAIL );
       
    return S_OK;
}




//-----------------------------------------------------------------------------
// Name: CWaveFile::Write()
// Desc: Writes data to the open wave file
//-----------------------------------------------------------------------------
HRESULT CWaveFile::Write( UINT nSizeToWrite, BYTE* pbSrcData, UINT* pnSizeWrote )
{
    UINT cT;

    if( m_bIsReadingFromMemory )
        return E_NOTIMPL;
    if (m_hmmio == nullptr)
        return CO_E_NOTINITIALIZED;
    if (pnSizeWrote == nullptr || pbSrcData == nullptr)
        return E_INVALIDARG;

    *pnSizeWrote = 0;
    
    for( cT = 0; cT < nSizeToWrite; cT++ )
    {       
        if( m_mmioinfoOut.pchNext == m_mmioinfoOut.pchEndWrite )
        {
            m_mmioinfoOut.dwFlags |= MMIO_DIRTY;
            if( 0 != mmioAdvance( m_hmmio, &m_mmioinfoOut, MMIO_WRITE ) )
                return DXTRACE_ERR( TEXT("mmioAdvance"), E_FAIL );
        }

        *((BYTE*)m_mmioinfoOut.pchNext) = *((BYTE*)pbSrcData+cT);
        (BYTE*)m_mmioinfoOut.pchNext++;

        (*pnSizeWrote)++;
    }

    return S_OK;
}


// Overloads for plAudioFileReader (we only support writing for CWaveFile for now)
CWaveFile::CWaveFile(const plFileName &path, plAudioCore::ChannelSelect whichChan)
    : m_pwfx(), m_hmmio(), m_dwSize(), m_ckRiff(),
      m_bIsReadingFromMemory(), fSecsPerSample(), m_mmioinfoOut(), m_dwFlags(),
      m_pbData(), m_pbDataCur(), m_ulDataSize(), fHeader()
{
    // Just a stub--do nothing
}

bool CWaveFile::OpenForWriting(const plFileName &path, plWAVHeader &header)
{
    fHeader = header;

    WAVEFORMATEX    winFormat;
    winFormat.cbSize = 0;
    winFormat.nAvgBytesPerSec = header.fAvgBytesPerSec;
    winFormat.nBlockAlign = header.fBlockAlign;
    winFormat.nChannels = header.fNumChannels;
    winFormat.nSamplesPerSec = header.fNumSamplesPerSec;
    winFormat.wBitsPerSample = header.fBitsPerSample;
    winFormat.wFormatTag = header.fFormatTag;

    if( SUCCEEDED( Open( path, &winFormat, WAVEFILE_WRITE ) ) )
        return true;

    return false;
}

plWAVHeader &CWaveFile::GetHeader()
{
    return fHeader;
}

void    CWaveFile::Close()
{
    IClose();
}

uint32_t  CWaveFile::GetDataSize()
{
    hsAssert( false, "Unsupported" );
    return 0;
}

float   CWaveFile::GetLengthInSecs()
{
    hsAssert( false, "Unsupported" );
    return 0.f;
}

bool    CWaveFile::SetPosition( uint32_t numBytes )
{
    hsAssert( false, "Unsupported" );
    return false;
}

bool    CWaveFile::Read( uint32_t numBytes, void *buffer )
{
    hsAssert( false, "Unsupported" );
    return false;
}

uint32_t  CWaveFile::NumBytesLeft()
{
    hsAssert( false, "Unsupported" );
    return 0;
}

uint32_t  CWaveFile::Write( uint32_t bytes, void *buffer )
{
    UINT written;
    Write( (DWORD)bytes, (BYTE *)buffer, &written );
    return (uint32_t)written;
}

bool    CWaveFile::IsValid()
{
    return true;
}


#if 0

THIS IS MORE STUFF having to do with WAV FILE format. It is just sitting here for documentation purposes.
/*
    Cue Chunk--

    The ID is always 'cue '. chunkSize is the number of bytes in the chunk, not counting the 8 bytes used by ID and Size fields. 
    The dwCuePoints field is the number of CuePoint structures in the Cue Chunk. If dwCuePoints is not 0, it is followed by that many 
    CuePoint structures, one after the other. Because all fields in a CuePoint structure are an even number of bytes, the length of any 
    CuePoint will always be even. Thus, CuePoints are packed together with no unused bytes between them. The CuePoints need not be placed 
    in any particular order.

    The Cue chunk is optional. No more than one Cue chunk can appear in a WAVE.
*/

class CueChunk 
{
 
public:
  FOURCC                    chunkID;
  DWORD                     chunkSize;
  DWORD                     dwCuePoints;
  std::vector<CuePoint*>    points;

public:
    CueChunk(DWORD ChunkSize)
    {
        chunkID = mmioFOURCC('c','u','e',' ');
        chunkSize = ChunkSize;
        dwCuePoints = (ChunkSize - (sizeof(DWORD)*1))/(sizeof(CuePoint));
        //points = nullptr;
        //points = new CuePoint[dwCuePoints];
        
    }
    //Cue

    ~CueChunk() {} //for(int i = 0; i < (int) dwCuePoints; i++) points.erase(i);  }
};


/*
    LabelChunk--

    The ID is always 'labl'. chunkSize is the number of bytes in the chunk, not counting the 8 bytes used by ID and Size fields nor any possible 
    pad uint8_t needed to make the chunk an even size (ie, chunkSize is the number of remaining bytes in the chunk after the chunkSize field, not
    counting any trailing pad byte). 
    The dwIdentifier field contains a unique number (ie, different than the ID number of any other Label chunk). This field should correspond 
    with the dwIndentifier field of some CuePoint stored in the Cue chunk. In other words, this Label chunk contains the text label associated
    with that CuePoint structure with the same ID number.

    The dwText array contains the text label. It should be a null-terminated string. (The null uint8_t is included in the chunkSize, therefore the 
    length of the string, including the null uint8_t, is chunkSize - 4).
*/


class LabelChunk
{

public:
  FOURCC     chunkID;
  DWORD     chunkSize;

  DWORD    dwIdentifier;
  char*    dwText;
public:
    LabelChunk(DWORD ChunkSize) 
    {
        chunkID = mmioFOURCC('l','a','b','l');
        chunkSize = ChunkSize;
        dwIdentifier = 0;
        dwText = nullptr;
    }

    LabelChunk() 
    {
        chunkID = mmioFOURCC('l','a','b','l');
        chunkSize = 0;
        dwIdentifier = 0;
        dwText = nullptr;
    }


    ~LabelChunk() { delete[] dwText; }

};

#endif

#endif // HS_BUILD_FOR_WIN32
