/******************************************************************************\
 * 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);
}