// tWave.cpp: CtWave class implementation

//#include "stdafx.h"
#include <assert.h>
#include "tWave.h"

#define DIM(rg) (sizeof(rg)/sizeof(*rg))
#ifdef _DEBUG

static
void WOTRACE(
    MMRESULT    mr,
    LPCTSTR     pszFunc)
{
    if( mr )
    {
        TCHAR   szErr[128];
        waveOutGetErrorText(mr, szErr, DIM(szErr));
        OutputDebugString(pszFunc);
        OutputDebugString("() -- ");
        OutputDebugString(szErr);
        OutputDebugString("\n");
    }
}

static
void WITRACE(
    MMRESULT    mr,
    LPCTSTR     pszFunc)
{
    if( mr )
    {
        TCHAR   szErr[128];
        waveInGetErrorText(mr, szErr, DIM(szErr));
        OutputDebugString(pszFunc);
        OutputDebugString("() -- ");
        OutputDebugString(szErr);
        OutputDebugString("\n");
    }
}

#else

inline void WOTRACE(MMRESULT, LPCSTR) {}
#define WOTRACE 1 ? 0 : WOTRACE

inline void WITRACE(MMRESULT, LPCSTR) {}
#define WITRACE 1 ? 0 : WITRACE

#endif

CtWave::CtWave(CtWaveSink* pSink):
    m_pSink(pSink),
    m_pData(0),
    m_hWaveOut(0),
    m_hWaveIn(0),
    m_pWaveHdr(0),
    m_nRecordedSize(0),
    m_pFormat(0),
    m_nFormatSize(0),
    m_bStopping(false),
    m_bOwnData(false)
{
}

CtWave::~CtWave()
{
    Close();
}

bool CtWave::Load(LPCTSTR pszFileName)
{
    // Make sure we're not open
    Close();

    assert(!m_pFormat);
    assert(!m_nFormatSize);
    assert(!m_pData);
    assert(!m_nRecordedSize);

    // Open the given file for reading using buffered I/O
    HMMIO   hmmio;
    hmmio = mmioOpen(const_cast<TCHAR*>(pszFileName), 0, MMIO_READ | MMIO_ALLOCBUF);
    if( !hmmio )
    {
        OutputDebugString("mmioOpen() -- Failed to open file: ");
        OutputDebugString(pszFileName);
        OutputDebugString("\n");
        return false;
    }

    bool    bSuccess = Load(hmmio);
    
    // We're done with the file, close it
    mmioClose(hmmio, 0);

    return bSuccess;
}

bool CtWave::Load(HMODULE hm, UINT nID)
{
    return Load(hm, MAKEINTRESOURCE(nID));
}

bool CtWave::Load(HMODULE hm, LPCTSTR pszID)
{
    // Make sure we're not open
    Close();

    assert(!m_pFormat);
    assert(!m_nFormatSize);
    assert(!m_pData);
    assert(!m_nRecordedSize);

    // Load the resource
    HRSRC   hrsrc = FindResource(hm, pszID, __TEXT("wave"));
    if( !hrsrc )
    {
        OutputDebugString("Failed to find 'wave' resource ");
        OutputDebugString(pszID);
        OutputDebugString("\n");
        return false;
    }

    assert(SizeofResource(hm, hrsrc));

    HGLOBAL hg = LoadResource(hm, hrsrc);
    if( !hg )
    {
        OutputDebugString("Failed to load 'wave' resource ");
        OutputDebugString(pszID);
        OutputDebugString("\n");
        return false;
    }

    // Open the given resource for reading
    MMIOINFO    mii = { 0 };
    mii.pchBuffer = (HPSTR)hg;
    mii.fccIOProc = FOURCC_MEM;
    mii.cchBuffer = SizeofResource(hm, hrsrc);

    HMMIO   hmmio;
    hmmio = mmioOpen(0, &mii, MMIO_READ | MMIO_ALLOCBUF);
    if( !hmmio )
    {
        OutputDebugString("mmioOpen() -- Failed to open 'wave' resource ");
        OutputDebugString(pszID);
        OutputDebugString("\n");
        return false;
    }

    bool    bSuccess = Load(hmmio);

    // We're done with the file, close it
    mmioClose(hmmio, 0);

    return bSuccess;
}
    
bool CtWave::Load(HMMIO hmmio)
{
    MMRESULT    mr;
    MMCKINFO    mmckinfoParent;
    MMCKINFO    mmckinfoSubchunk;

    // Locate a 'RIFF' chunk with a 'WAVE' form type to make sure it's a WAVE file
    mmckinfoParent.fccType = mmioFOURCC('W', 'A', 'V', 'E');
    mr = mmioDescend(hmmio, &mmckinfoParent, 0, MMIO_FINDRIFF);
    if( mr )
    {
        WOTRACE(mr, __TEXT("mmioDescend"));
        mmioClose(hmmio, 0);
        return false;
    }
    
    //  Now, find the format chunk (form type 'fmt '). It should be
    //  a subchunk of the 'RIFF' parent chunk.
    mmckinfoSubchunk.ckid = mmioFOURCC('f', 'm', 't', ' ');
    mr = mmioDescend(hmmio, &mmckinfoSubchunk, &mmckinfoParent, MMIO_FINDCHUNK);
    if( mr )
    {
        WOTRACE(mr, __TEXT("mmioDescend"));
        mmioClose(hmmio, 0);
        return false;
    }
    
    // Get the size of the format chunk and allocate memory for it
    DWORD           nFormatSize = mmckinfoSubchunk.cksize;
    WAVEFORMATEX*   pFormat = (WAVEFORMATEX*)(new BYTE[nFormatSize]);
    if( !pFormat )
    {
        OutputDebugString("new[] -- Out of memory\n");
        mmioClose(hmmio, 0);
        return false;
    }
    
    // Read the format chunk
    if( mmioRead(hmmio, (HPSTR)pFormat, nFormatSize) != (LONG)nFormatSize )
    {
        OutputDebugString("mmioRead() -- Failed to read FMT chunk\n");
        delete[] pFormat;
        mmioClose(hmmio, 0);
        return false;
    }
    
    /*
    TRACE1("wFormatTag = %lu\n",        (DWORD) pFormat->wFormatTag);
    TRACE1("nChannels = %lu\n",         (DWORD) pFormat->nChannels );
    TRACE1("nSamplesPerSec = %lu\n",    (DWORD) pFormat->nSamplesPerSec);
    TRACE1("nAvgBytesPerSec = %lu\n",   (DWORD) pFormat->nAvgBytesPerSec);
    TRACE1("nBlockAlign = %lu\n",       (DWORD) pFormat->nBlockAlign);
    TRACE1("wBitsPerSample = %lu\n",    (DWORD) pFormat->wBitsPerSample);
    TRACE1("cbSize = %lu\n",            (DWORD) pFormat->cbSize);
    */

    // Ascend out of the format subchunk
    mmioAscend(hmmio, &mmckinfoSubchunk, 0);
    
    // Find the data subchunk
    mmckinfoSubchunk.ckid = mmioFOURCC('d', 'a', 't', 'a');
    mr = mmioDescend(hmmio, &mmckinfoSubchunk, &mmckinfoParent, MMIO_FINDCHUNK);
    if( mr )
    {
        WOTRACE(mr, __TEXT("mmioDescend"));
        delete[] pFormat;
        mmioClose(hmmio, 0);
        return false;
    }
    
    //  Get the size of the data subchunk
    DWORD   nDataSize = mmckinfoSubchunk.cksize;
    if( !nDataSize )
    {
        OutputDebugString("Data chunk actually has no data\n");
        delete[] pFormat;
        mmioClose(hmmio, 0);
        return false;
    }
    
    //TRACE1("Size of data is %lu\n", nDataSize);
    
    // Allocate memory for the waveform data
    HPSTR   pData = (HPSTR)(new BYTE[nDataSize]);
    if( !pData )
    {
        OutputDebugString("new[] -- Out of memory\n");
        mmioClose(hmmio, 0);
        delete[] pFormat;
        return false;
    }
    
    // Read the waveform data subchunk
    if( mmioRead(hmmio, pData, nDataSize) != (LONG)nDataSize )
    {
        OutputDebugString("mmioRead() -- Failed to read waveform data subchunk\n");
        delete[] pData;
        delete[] pFormat;
        mmioClose(hmmio, 0);
        return false;
    }
    
    // Update the object state
    m_pFormat = pFormat;
    m_nFormatSize = nFormatSize;
    m_nRecordedSize = nDataSize;
    m_pData = pData;
    m_bOwnData = true;

    return true;
}

bool CtWave::Save(LPCTSTR pszFileName)
{
    // Make sure we've got some data
    if( !m_pData )
    {
        return false;
    }

    assert(m_pFormat);
    assert(m_nFormatSize);
    assert(m_pData);

    HANDLE  hFile;
    DWORD   nBytesWritten;

    hFile = CreateFile(pszFileName, GENERIC_WRITE, 0, 0,
                       CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
    if( hFile == INVALID_HANDLE_VALUE )
    {
        OutputDebugString("CreateFile() -- Can't open file: ");
        OutputDebugString(pszFileName);
        OutputDebugString("\n");
        return false;
    }
    
    BYTE    rgbRiffChunk[] =        { 'R', 'I', 'F', 'F', 0, 0, 0, 0, 'W', 'A', 'V', 'E', };
    BYTE    rgbFormatChunkTag[] =   { 'f', 'm', 't', ' ', 0, 0, 0, 0, };
    BYTE    rgbDataChunkTag[] =     { 'd', 'a', 't', 'a', 0, 0, 0, 0, };

    // Write out the RIFF chunk
    *((DWORD*)&rgbRiffChunk[4]) = 4 + sizeof(rgbFormatChunkTag) +
                                  m_nFormatSize + sizeof(rgbDataChunkTag) +
                                  m_nRecordedSize;
    if( !WriteFile(hFile, rgbRiffChunk, sizeof(rgbRiffChunk), &nBytesWritten, 0) )
    {
        OutputDebugString("Can't write RIFF chunk\n");
        CloseHandle(hFile);
        return false;
    }

    // Write tag
    *((DWORD*)&rgbFormatChunkTag[4]) = m_nFormatSize;
    if( !WriteFile(hFile, rgbFormatChunkTag, sizeof(rgbFormatChunkTag), &nBytesWritten, 0) )
    {
        OutputDebugString("Can't write fmt chunk\n");
        CloseHandle(hFile);
        return false;
    }
    
    // Write out the canned format header
    if( !WriteFile(hFile, m_pFormat, m_nFormatSize, &nBytesWritten, 0) )
    {
        OutputDebugString("Can't write WAVEFORMATEX chunk\n");
        CloseHandle(hFile);
        return false;
    }
    
    // Write out the data chunk tag
    *((DWORD *)&rgbDataChunkTag[4]) = m_nRecordedSize;
    if( !WriteFile(hFile, rgbDataChunkTag, sizeof(rgbDataChunkTag), &nBytesWritten, 0) )
    {
        OutputDebugString("Can't write data chunk tag\n");
        CloseHandle(hFile);
        return false;
    }
    
    // Write out the data chunk
    if( !WriteFile(hFile, m_pData, m_nRecordedSize, &nBytesWritten, 0) )
    {
        OutputDebugString("Can't write data chunk\n");
        CloseHandle(hFile);
        return false;
    }

    // Close message file
    CloseHandle(hFile);
    return true;
}

bool CtWave::Play(UINT nWaveOut, bool bLoop)
{
    // Make sure we've got some data
    if( !m_pData )
    {
        return false;
    }

    MMRESULT    mr;

    // Make sure we're stopped
    Stop();

    assert(!m_hWaveOut);
    assert(!m_pWaveHdr);
    assert(m_pFormat);

    // Make sure a waveform output device supports this format
    mr = waveOutOpen(0, nWaveOut, m_pFormat, 0, 0L,
                     WAVE_FORMAT_QUERY | (nWaveOut == WAVE_MAPPER ? 0 : WAVE_MAPPED));
    if( mr )
    {
        WOTRACE(mr, __TEXT("waveOutOpen"));
        return false;
    }

    // Make sure window is created
    if( !m_wnd.GetHwnd() && !m_wnd.Create(this) )
    {
        OutputDebugString("Couldn't create window for callbacks\n");
        return false;
    }

    // Open a waveform output device
    HWAVEOUT    hWaveOut;
    DWORD       fdwOpen = CALLBACK_WINDOW | (nWaveOut == WAVE_MAPPER ? 0 : WAVE_MAPPED);

    mr = waveOutOpen(&hWaveOut, nWaveOut, m_pFormat, (DWORD)m_wnd.GetHwnd(), 0, fdwOpen);

    if( mr )
    {
        WOTRACE(mr, __TEXT("waveOutOpen"));
        return false;
    }
    
    // Set up WAVEHDR structure and prepare it to be written to wave device
    WAVEHDR*    pWaveHdr = new WAVEHDR;
    if( !pWaveHdr )
    {
        OutputDebugString("new[] -- Out of memory\n");
        Stop();
        return false;
    }

    pWaveHdr->lpData = m_pData;
    pWaveHdr->dwBufferLength = m_nRecordedSize;
    pWaveHdr->dwFlags = (bLoop ? WHDR_BEGINLOOP | WHDR_ENDLOOP : 0);
    pWaveHdr->dwLoops = (bLoop ? 0xffffffff : 0);
    
    mr = waveOutPrepareHeader(hWaveOut, pWaveHdr, sizeof(WAVEHDR));
    if( mr )
    {
        WOTRACE(mr, __TEXT("waveOutPrepareHeader"));
        Stop();
        return false;
    }
    
    // Then the data block can be sent to the output device
    mr = waveOutWrite(hWaveOut, pWaveHdr, sizeof(WAVEHDR));
    if( mr )
    {
        WOTRACE(mr, __TEXT("waveOutWrite"));
        Stop();
        return false;
    }

    // Cache results
    m_pWaveHdr = pWaveHdr;
    m_hWaveOut = hWaveOut;

    return true;
}

bool CtWave::Record(UINT nWaveIn, UINT nSecs)
{
    // Make sure we're closed first
    Close();

    assert(nSecs);
    assert(!m_pData);
    assert(!m_nRecordedSize);
    assert(!m_nFormatSize);
    assert(!m_pFormat);
    assert(!m_pWaveHdr);
    assert(!m_hWaveIn);

    MMRESULT        mr;
    WAVEFORMATEX*   pFormat = (WAVEFORMATEX*)(new BYTE[sizeof(WAVEFORMATEX)]);
    if( !pFormat )
    {
        OutputDebugString("new[] -- Out of memory\n");
        return false;
    }

    pFormat->wFormatTag = WAVE_FORMAT_PCM;  // Pulse Code Modulation
    pFormat->nChannels = 1;                 // Mono
    pFormat->nSamplesPerSec = 8000;         // 8.0 kHz
    pFormat->wBitsPerSample = 16;           // 16 bits/sample

    // PCM required calculations
    assert(pFormat->wFormatTag == WAVE_FORMAT_PCM);
    pFormat->nBlockAlign = pFormat->nChannels * pFormat->wBitsPerSample/8;
    pFormat->nAvgBytesPerSec = pFormat->nSamplesPerSec * pFormat->nBlockAlign;

    // No user data
    pFormat->cbSize = 0;

    // Check support for format
    mr = waveInOpen(0, nWaveIn, pFormat, 0, 0,
                    WAVE_FORMAT_QUERY | (nWaveIn == WAVE_MAPPER ? 0 : WAVE_MAPPED));
    if( mr )
    {
        WITRACE(mr, __TEXT("waveInOpen"));
        delete[] pFormat;
        return false;
    }
    
    // Make sure window is created
    if( !m_wnd.GetHwnd() && !m_wnd.Create(this) )
    {
        OutputDebugString("Couldn't create window for callbacks\n");
        delete[] pFormat;
        return false;
    }

    // Open recorder
    HWAVEIN     hWaveIn;
    DWORD       fdwOpen = CALLBACK_WINDOW | (nWaveIn == WAVE_MAPPER ? 0 : WAVE_MAPPED);

    mr = waveInOpen(&hWaveIn, nWaveIn, pFormat, (DWORD)m_wnd.GetHwnd(), 0, fdwOpen);
    if( mr )
    {
        WITRACE(mr, __TEXT("waveInOpen"));
        delete[] pFormat;
        return false;
    }
    
    // Allocate message buffer
    WAVEHDR*    pWaveHdr = new WAVEHDR;
    if( !pWaveHdr )
    {
        OutputDebugString("new[] -- Out of memory\n");
        delete[] pFormat;
        return false;
    }

    ZeroMemory(pWaveHdr, sizeof(WAVEHDR));

    DWORD   nDataSize = nSecs * pFormat->nSamplesPerSec * pFormat->wBitsPerSample/8;
    HPSTR   pData = (HPSTR)(new BYTE[nDataSize]);

    if( !pData )
    {
        OutputDebugString("new[] -- Out of memory\n");
        delete pWaveHdr;
        delete[] pFormat;
        return false;
    }

    pWaveHdr->dwBufferLength = nDataSize;
    pWaveHdr->lpData = pData;

    mr = waveInPrepareHeader(hWaveIn, pWaveHdr, sizeof(WAVEHDR));
    if( mr )
    {
        WITRACE(mr, __TEXT("waveInPrepareHeader"));
        delete[] pData;
        delete pWaveHdr;
        delete[] pFormat;
        return false;
    }
    
    // Pass down the buffer to record into
    mr = waveInAddBuffer(hWaveIn, pWaveHdr, sizeof(WAVEHDR));
    if( mr )
    {
        WITRACE(mr, __TEXT("waveInAddBuffer"));
        delete[] pData;
        delete pWaveHdr;
        delete[] pFormat;
        return false;
    }
    
    // Start recording
    mr = waveInStart(hWaveIn);
    if( mr )
    {
        WITRACE(mr, __TEXT("waveInStart"));
        delete[] pData;
        delete pWaveHdr;
        delete[] pFormat;
        return false;
    }

    // Cache results
    m_hWaveIn = hWaveIn;
    m_pData = pData;
    m_nRecordedSize = 0;
    m_nFormatSize = sizeof(WAVEFORMATEX);
    m_pFormat = pFormat;
    m_pWaveHdr = pWaveHdr;
    m_bOwnData = true;
    
    return true;
}

bool CtWave::Stop()
{
    // Only stop if we're playing/recording
    if( m_pWaveHdr && !m_bStopping )
    {
        MMRESULT mr;
    
        // Important to avoid a manual Stop(),
        // followed by a stop notification,
        // followed by an automatic stop,
        // followed by a stop notification, etc.
        m_bStopping = true;

        if( m_hWaveOut )
        {
            mr = waveOutReset(m_hWaveOut);
            WOTRACE(mr, __TEXT("waveOutReset"));

            mr = waveOutUnprepareHeader(m_hWaveOut, m_pWaveHdr, sizeof(WAVEHDR));
            WOTRACE(mr, __TEXT("waveOutUnprepareHeader"));
            
            mr = waveOutClose(m_hWaveOut);
            WOTRACE(mr, __TEXT("waveOutClose"));
            m_hWaveOut = 0;
        }
        else if( m_hWaveIn )
        {
            mr = waveInReset(m_hWaveIn);
            WITRACE(mr, __TEXT("waveInReset"));
        
            mr = waveInUnprepareHeader(m_hWaveIn, m_pWaveHdr, sizeof(WAVEHDR));
            WITRACE(mr, __TEXT("waveInUnprepareHeader"));
            
            // Cache recorded length
            m_nRecordedSize = m_pWaveHdr->dwBytesRecorded;
        
            mr = waveInClose(m_hWaveIn);
            WITRACE(mr, __TEXT("waveInClose"));
            m_hWaveIn = 0;
        }

        delete m_pWaveHdr;
        m_pWaveHdr = 0;
        m_bStopping = false;
    }

    return true;
}

bool CtWave::Close()
{
    // Make sure we're stopped
    Stop();

    delete[] m_pFormat;
    m_pFormat = 0;
    m_nFormatSize = 0;

    if( m_bOwnData ) delete[] m_pData;
    m_pData = 0;
    m_nRecordedSize = 0;
    m_bOwnData = false;

    return true;
}

// Event handlers
LRESULT CtWave::OnWindowMessage(HWND hwnd, UINT nMsg, WPARAM wparam, LPARAM lparam)
{
    CtWave* pw = reinterpret_cast<CtWave*>(GetWindowLong(hwnd, GWL_USERDATA));
    if( pw )
    {
        CtWaveSink* pSink = pw->m_pSink;
        
        switch( nMsg )
        {
        case WOM_CLOSE: if( pSink ) pSink->OnWaveOutClose(); return 0;
        case WOM_DONE:  pw->Stop(); if( pSink ) pSink->OnWaveOutDone(); return 0;
        case WOM_OPEN:  if( pSink ) pSink->OnWaveOutOpen(); return 0;

        case WIM_CLOSE: if( pSink ) pSink->OnWaveInClose(); return 0;
        case WIM_DATA:  pw->Stop(); if( pSink ) pSink->OnWaveInData(); return 0;
        case WIM_OPEN:  if( pSink ) pSink->OnWaveInOpen(); return 0;
        }
    }

    return DefWindowProc(hwnd, nMsg, wparam, lparam);
}

