/******************************************************************************\
 * Copyright (c) 2004-2006
 *
 * Author(s):
 *  Volker Fischer
 *
 * Description:
 * Sound card interface for Windows operating systems
 *
 ******************************************************************************
 *
 * 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 2 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, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 *
\******************************************************************************/

#include "Sound.h"


/* Implementation *************************************************************/
/******************************************************************************\
* Wave in                                                                      *
\******************************************************************************/
bool CSound::Read(CVector<short>& psData)
{
    int     i;
    bool    bError;

    /* Check if device must be opened or reinitialized */
    if (bChangParamIn == TRUE)
    {
        OpenInDevice();

        /* Reinit sound interface */
        InitRecording(iBufferSizeIn, bBlockingRec);

        /* Reset flag */
        bChangParamIn = FALSE;
    }

    /* Wait until data is available */
    if (!(m_WaveInHeader[iWhichBufferIn].dwFlags & WHDR_DONE))
    {
        if (bBlockingRec == TRUE)
            WaitForSingleObject(m_WaveInEvent, INFINITE);
        else
            return FALSE;
    }

    /* Check if buffers got lost */
    int iNumInBufDone = 0;
    for (i = 0; i < iCurNumSndBufIn; i++)
    {
        if (m_WaveInHeader[i].dwFlags & WHDR_DONE)
            iNumInBufDone++;
    }

    /* If the number of done buffers equals the total number of buffers, it is
       very likely that a buffer got lost -> set error flag */
    if (iNumInBufDone == iCurNumSndBufIn)
        bError = TRUE;
    else
        bError = FALSE;

    /* Copy data from sound card in output buffer */
    for (i = 0; i < iBufferSizeIn; i++)
        psData[i] = psSoundcardBuffer[iWhichBufferIn][i];

    /* Add the buffer so that it can be filled with new samples */
    AddInBuffer();

    /* In case more than one buffer was ready, reset event */
    ResetEvent(m_WaveInEvent);

    return bError;
}

void CSound::AddInBuffer()
{
    /* Unprepare old wave-header */
    waveInUnprepareHeader(
        m_WaveIn, &m_WaveInHeader[iWhichBufferIn], sizeof(WAVEHDR));

    /* Prepare buffers for sending to sound interface */
    PrepareInBuffer(iWhichBufferIn);

    /* Send buffer to driver for filling with new data */
    waveInAddBuffer(m_WaveIn, &m_WaveInHeader[iWhichBufferIn], sizeof(WAVEHDR));

    /* Toggle buffers */
    iWhichBufferIn++;
    if (iWhichBufferIn == iCurNumSndBufIn)
        iWhichBufferIn = 0;
}

void CSound::PrepareInBuffer(int iBufNum)
{
    /* Set struct entries */
    m_WaveInHeader[iBufNum].lpData = (LPSTR) &psSoundcardBuffer[iBufNum][0];
    m_WaveInHeader[iBufNum].dwBufferLength = iBufferSizeIn * BYTES_PER_SAMPLE;
    m_WaveInHeader[iBufNum].dwFlags = 0;

    /* Prepare wave-header */
    waveInPrepareHeader(m_WaveIn, &m_WaveInHeader[iBufNum], sizeof(WAVEHDR));
}

void CSound::InitRecording(int iNewBufferSize, bool bNewBlocking)
{
    /* Check if device must be opened or reinitialized */
    if (bChangParamIn == TRUE)
    {
        OpenInDevice();

        /* Reset flag */
        bChangParamIn = FALSE;
    }

    /* Set internal parameter */
    iBufferSizeIn = iNewBufferSize;
    bBlockingRec = bNewBlocking;

    /* Reset interface so that all buffers are returned from the interface */
    waveInReset(m_WaveIn);
    waveInStop(m_WaveIn);
    
    /* Reset current buffer ID (it is important to do this BEFORE calling
       "AddInBuffer()" */
    iWhichBufferIn = 0;

    /* Create memory for sound card buffer */
    for (int i = 0; i < iCurNumSndBufIn; i++)
    {
        /* Unprepare old wave-header in case that we "re-initialized" this
           module. Calling "waveInUnprepareHeader()" with an unprepared
           buffer (when the module is initialized for the first time) has
           simply no effect */
        waveInUnprepareHeader(m_WaveIn, &m_WaveInHeader[i], sizeof(WAVEHDR));

        if (psSoundcardBuffer[i] != NULL)
            delete[] psSoundcardBuffer[i];

        psSoundcardBuffer[i] = new short[iBufferSizeIn];


        /* Send all buffers to driver for filling the queue ----------------- */
        /* Prepare buffers before sending them to the sound interface */
        PrepareInBuffer(i);

        AddInBuffer();
    }

    /* Notify that sound capturing can start now */
    waveInStart(m_WaveIn);

    /* This reset event is very important for initialization, otherwise we will
       get errors! */
    ResetEvent(m_WaveInEvent);
}

void CSound::OpenInDevice()
{
    /* Open wave-input and set call-back mechanism to event handle */
    if (m_WaveIn != NULL)
    {
        waveInReset(m_WaveIn);
        waveInClose(m_WaveIn);
    }

    MMRESULT result = waveInOpen(&m_WaveIn, iCurInDev, &sWaveFormatEx,
        (DWORD) m_WaveInEvent, NULL, CALLBACK_EVENT);

    if (result != MMSYSERR_NOERROR)
    {
        throw CGenErr("Sound Interface Start, waveInOpen() failed. This error "
            "usually occurs if another application blocks the sound in.");
    }
}

void CSound::SetInDev(int iNewDev)
{
    /* Set device to wave mapper if iNewDev is invalid */
    if ((iNewDev >= iNumDevs) || (iNewDev < 0))
        iNewDev = WAVE_MAPPER;

    /* Change only in case new device id is not already active */
    if (iNewDev != iCurInDev)
    {
        iCurInDev = iNewDev;
        bChangParamIn = TRUE;
    }
}

void CSound::SetInNumBuf(int iNewNum)
{
    /* check new parameter */
    if ((iNewNum >= MAX_SND_BUF_IN) || (iNewNum < 1))
        iNewNum = NUM_SOUND_BUFFERS_IN;

    /* Change only if parameter is different */
    if (iNewNum != iCurNumSndBufIn)
    {
        iCurNumSndBufIn = iNewNum;
        bChangParamIn = TRUE;
    }
}


/******************************************************************************\
* Wave out                                                                     *
\******************************************************************************/
bool CSound::Write(CVector<short>& psData)
{
    int     i, j;
    int     iCntPrepBuf;
    int     iIndexDoneBuf;
    bool    bError;

    /* Check if device must be opened or reinitialized */
    if (bChangParamOut == TRUE)
    {
        OpenOutDevice();

        /* Reinit sound interface */
        InitPlayback(iBufferSizeOut, bBlockingPlay);

        /* Reset flag */
        bChangParamOut = FALSE;
    }

    /* Get number of "done"-buffers and position of one of them */
    GetDoneBuffer(iCntPrepBuf, iIndexDoneBuf);

    /* Now check special cases (Buffer is full or empty) */
    if (iCntPrepBuf == 0)
    {
        if (bBlockingPlay == TRUE)
        {
            /* Blocking wave out routine. Needed for transmitter. Always
               ensure that the buffer is completely filled to avoid buffer
               underruns */
            while (iCntPrepBuf == 0)
            {
                WaitForSingleObject(m_WaveOutEvent, INFINITE);

                GetDoneBuffer(iCntPrepBuf, iIndexDoneBuf);
            }
        }
        else
        {
            /* All buffers are filled, dump new block ----------------------- */
// It would be better to kill half of the buffer blocks to set the start
// back to the middle: TODO
            return TRUE; /* An error occurred */
        }
    }
    else if (iCntPrepBuf == iCurNumSndBufOut)
    {
        /* ---------------------------------------------------------------------
           Buffer is empty -> send as many cleared blocks to the sound-
           interface until half of the buffer size is reached */
        /* Send half of the buffer size blocks to the sound-interface */
        for (j = 0; j < iCurNumSndBufOut / 2; j++)
        {
            /* First, clear these buffers */
            for (i = 0; i < iBufferSizeOut; i++)
                psPlaybackBuffer[j][i] = 0;

            /* Then send them to the interface */
            AddOutBuffer(j);
        }

        /* Set index for done buffer */
        iIndexDoneBuf = iCurNumSndBufOut / 2;

        bError = TRUE;
    }
    else
        bError = FALSE;

    /* Copy stereo data from input in soundcard buffer */
    for (i = 0; i < iBufferSizeOut; i++)
        psPlaybackBuffer[iIndexDoneBuf][i] = psData[i];

    /* Now, send the current block */
    AddOutBuffer(iIndexDoneBuf);

    return bError;
}

void CSound::GetDoneBuffer(int& iCntPrepBuf, int& iIndexDoneBuf)
{
    /* Get number of "done"-buffers and position of one of them */
    iCntPrepBuf = 0;
    for (int i = 0; i < iCurNumSndBufOut; i++)
    {
        if (m_WaveOutHeader[i].dwFlags & WHDR_DONE)
        {
            iCntPrepBuf++;
            iIndexDoneBuf = i;
        }
    }
}

void CSound::AddOutBuffer(int iBufNum)
{
    /* Unprepare old wave-header */
    waveOutUnprepareHeader(
        m_WaveOut, &m_WaveOutHeader[iBufNum], sizeof(WAVEHDR));

    /* Prepare buffers for sending to sound interface */
    PrepareOutBuffer(iBufNum);

    /* Send buffer to driver for filling with new data */
    waveOutWrite(m_WaveOut, &m_WaveOutHeader[iBufNum], sizeof(WAVEHDR));
}

void CSound::PrepareOutBuffer(int iBufNum)
{
    /* Set Header data */
    m_WaveOutHeader[iBufNum].lpData = (LPSTR) &psPlaybackBuffer[iBufNum][0];
    m_WaveOutHeader[iBufNum].dwBufferLength = iBufferSizeOut * BYTES_PER_SAMPLE;
    m_WaveOutHeader[iBufNum].dwFlags = 0;

    /* Prepare wave-header */
    waveOutPrepareHeader(m_WaveOut, &m_WaveOutHeader[iBufNum], sizeof(WAVEHDR));
}

void CSound::InitPlayback(int iNewBufferSize, bool bNewBlocking)
{
    int i, j;

    /* Check if device must be opened or reinitialized */
    if (bChangParamOut == TRUE)
    {
        OpenOutDevice();

        /* Reset flag */
        bChangParamOut = FALSE;
    }

    /* Set internal parameters */
    iBufferSizeOut = iNewBufferSize;
    bBlockingPlay = bNewBlocking;

    /* Reset interface */
    waveOutReset(m_WaveOut);

    for (j = 0; j < iCurNumSndBufOut; j++)
    {
        /* Unprepare old wave-header (in case header was not prepared before,
           simply nothing happens with this function call */
        waveOutUnprepareHeader(m_WaveOut, &m_WaveOutHeader[j], sizeof(WAVEHDR));

        /* Create memory for playback buffer */
        if (psPlaybackBuffer[j] != NULL)
            delete[] psPlaybackBuffer[j];

        psPlaybackBuffer[j] = new short[iBufferSizeOut];

        /* Clear new buffer */
        for (i = 0; i < iBufferSizeOut; i++)
            psPlaybackBuffer[j][i] = 0;

        /* Prepare buffer for sending to the sound interface */
        PrepareOutBuffer(j);

        /* Initially, send all buffers to the interface */
        AddOutBuffer(j);
    }
}

void CSound::OpenOutDevice()
{
    if (m_WaveOut != NULL)
    {
        waveOutReset(m_WaveOut);
        waveOutClose(m_WaveOut);
    }

    MMRESULT result = waveOutOpen(&m_WaveOut, iCurOutDev, &sWaveFormatEx,
        (DWORD) m_WaveOutEvent, NULL, CALLBACK_EVENT);

    if (result != MMSYSERR_NOERROR)
        throw CGenErr("Sound Interface Start, waveOutOpen() failed.");
}

void CSound::SetOutDev(int iNewDev)
{
    /* Set device to wave mapper if iNewDev is invalid */
    if ((iNewDev >= iNumDevs) || (iNewDev < 0))
        iNewDev = WAVE_MAPPER;

    /* Change only in case new device id is not already active */
    if (iNewDev != iCurOutDev)
    {
        iCurOutDev = iNewDev;
        bChangParamOut = TRUE;
    }
}

void CSound::SetOutNumBuf(int iNewNum)
{
    /* check new parameter */
    if ((iNewNum >= MAX_SND_BUF_OUT) || (iNewNum < 1))
        iNewNum = NUM_SOUND_BUFFERS_OUT;

    /* Change only if parameter is different */
    if (iNewNum != iCurNumSndBufOut)
    {
        iCurNumSndBufOut = iNewNum;
        bChangParamOut = TRUE;
    }
}


/******************************************************************************\
* Common                                                                       *
\******************************************************************************/
void CSound::Close()
{
    int         i;
    MMRESULT    result;

    /* Reset audio driver */
    if (m_WaveOut != NULL)
    {
        result = waveOutReset(m_WaveOut);
        if (result != MMSYSERR_NOERROR)
            throw CGenErr("Sound Interface, waveOutReset() failed.");
    }

    if (m_WaveIn != NULL)
    {
        result = waveInReset(m_WaveIn);
        if (result != MMSYSERR_NOERROR)
            throw CGenErr("Sound Interface, waveInReset() failed.");
    }

    /* Set event to ensure that thread leaves the waiting function */
    if (m_WaveInEvent != NULL)
        SetEvent(m_WaveInEvent);

    /* Wait for the thread to terminate */
    Sleep(500);

    /* Unprepare wave-headers */
    if (m_WaveIn != NULL)
    {
        for (i = 0; i < iCurNumSndBufIn; i++)
        {
            result = waveInUnprepareHeader(
                m_WaveIn, &m_WaveInHeader[i], sizeof(WAVEHDR));

            if (result != MMSYSERR_NOERROR)
            {
                throw CGenErr("Sound Interface, waveInUnprepareHeader()"
                    " failed.");
            }
        }

        /* Close the sound in device */
        result = waveInClose(m_WaveIn);
        if (result != MMSYSERR_NOERROR)
            throw CGenErr("Sound Interface, waveInClose() failed.");
    }

    if (m_WaveOut != NULL)
    {
        for (i = 0; i < iCurNumSndBufOut; i++)
        {
            result = waveOutUnprepareHeader(
                m_WaveOut, &m_WaveOutHeader[i], sizeof(WAVEHDR));

            if (result != MMSYSERR_NOERROR)
            {
                throw CGenErr("Sound Interface, waveOutUnprepareHeader()"
                    " failed.");
            }
        }

        /* Close the sound out device */
        result = waveOutClose(m_WaveOut);
        if (result != MMSYSERR_NOERROR)
            throw CGenErr("Sound Interface, waveOutClose() failed.");
    }

    /* Set flag to open devices the next time it is initialized */
    bChangParamIn = TRUE;
    bChangParamOut = TRUE;
}

CSound::CSound()
{
    int i;

    /* init number of sound buffers */
    iCurNumSndBufIn = NUM_SOUND_BUFFERS_IN;
    iCurNumSndBufOut = NUM_SOUND_BUFFERS_OUT;

    /* Should be initialized because an error can occur during init */
    m_WaveInEvent = NULL;
    m_WaveOutEvent = NULL;
    m_WaveIn = NULL;
    m_WaveOut = NULL;

    /* Init buffer pointer to zero */
    for (i = 0; i < MAX_SND_BUF_IN; i++)
    {
        memset(&m_WaveInHeader[i], 0, sizeof(WAVEHDR));
        psSoundcardBuffer[i] = NULL;
    }

    for (i = 0; i < MAX_SND_BUF_OUT; i++)
    {
        memset(&m_WaveOutHeader[i], 0, sizeof(WAVEHDR));
        psPlaybackBuffer[i] = NULL;
    }

    /* Init wave-format structure */
    sWaveFormatEx.wFormatTag = WAVE_FORMAT_PCM;
    sWaveFormatEx.nChannels = NUM_IN_OUT_CHANNELS;
    sWaveFormatEx.wBitsPerSample = BITS_PER_SAMPLE;
    sWaveFormatEx.nSamplesPerSec = SND_CRD_SAMPLE_RATE;
    sWaveFormatEx.nBlockAlign = sWaveFormatEx.nChannels *
        sWaveFormatEx.wBitsPerSample / 8;
    sWaveFormatEx.nAvgBytesPerSec = sWaveFormatEx.nBlockAlign *
        sWaveFormatEx.nSamplesPerSec;
    sWaveFormatEx.cbSize = 0;

    /* Get the number of digital audio devices in this computer, check range */
    iNumDevs = waveInGetNumDevs();

    if (iNumDevs > MAX_NUMBER_SOUND_CARDS)
        iNumDevs = MAX_NUMBER_SOUND_CARDS;

    /* At least one device must exist in the system */
    if (iNumDevs == 0)
        throw CGenErr("No audio device found.");

    /* Get info about the devices and store the names */
    for (i = 0; i < iNumDevs; i++)
    {
        if (!waveInGetDevCaps(i, &m_WaveInDevCaps, sizeof(WAVEINCAPS)))
            pstrDevices[i] = m_WaveInDevCaps.szPname;
    }

    /* We use an event controlled wave-in (wave-out) structure */
    /* Create events */
    m_WaveInEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
    m_WaveOutEvent = CreateEvent(NULL, FALSE, FALSE, NULL);

    /* Set flag to open devices */
    bChangParamIn = TRUE;
    bChangParamOut = TRUE;

    /* Default device number, "wave mapper" */
    iCurInDev = WAVE_MAPPER;
    iCurOutDev = WAVE_MAPPER;

    /* Non-blocking wave out is default */
    bBlockingPlay = FALSE;

    /* Blocking wave in is default */
    bBlockingRec = TRUE;
}

CSound::~CSound()
{
    int i;

    /* Delete allocated memory */
    for (i = 0; i < iCurNumSndBufIn; i++)
    {
        if (psSoundcardBuffer[i] != NULL)
            delete[] psSoundcardBuffer[i];
    }

    for (i = 0; i < iCurNumSndBufOut; i++)
    {
        if (psPlaybackBuffer[i] != NULL)
            delete[] psPlaybackBuffer[i];
    }

    /* Close the handle for the events */
    if (m_WaveInEvent != NULL)
        CloseHandle(m_WaveInEvent);

    if (m_WaveOutEvent != NULL)
        CloseHandle(m_WaveOutEvent);
}