// PlaySound.cpp : implementation file
//
#include <windows.h>
#include <mmsystem.h>
#include <math.h>
#include <process.h>
#include "PlaySound.h"

CPlaySound g_SoundDevice;

/////////////////////////////////////////////////////////////////////////////
// CPlaySound

#define BLOCKSAHEAD 3

CPlaySound::CPlaySound() :
m_hThread(NULL),
m_nThreadID(0),
m_nOutputBuffers(0),
m_nMaxOutputBuffers(0)
{
	memset(&m_WaveFormatEx, 0, sizeof(m_WaveFormatEx));
	m_WaveFormatEx.wFormatTag = WAVE_FORMAT_PCM;
	m_WaveFormatEx.nChannels = 1;
	m_WaveFormatEx.wBitsPerSample = 16;
	m_WaveFormatEx.cbSize = 0;
	m_WaveFormatEx.nSamplesPerSec = 16000;
	m_WaveFormatEx.nBlockAlign = (m_WaveFormatEx.wBitsPerSample/8) * m_WaveFormatEx.nChannels;
	m_WaveFormatEx.nAvgBytesPerSec = m_WaveFormatEx.nBlockAlign * m_WaveFormatEx.nSamplesPerSec;
	m_hCompleteEvent = CreateEvent(NULL, FALSE, FALSE, NULL);

	m_bPlay = FALSE;
    m_hPlay = NULL;
}

CPlaySound::~CPlaySound()
{
	if (m_hThread)
	{
        PostThreadMessage(WM_QUIT, 0, 0);
        if ( WAIT_TIMEOUT == WaitForSingleObject(m_hThread, 5000) )
        {
            ::TerminateThread(m_hThread,0);
        }
        CloseHandle(m_hThread);
	}
	CloseHandle(m_hCompleteEvent);
}

BOOL CPlaySound::InitInstance()
{
	return TRUE;
}

int CPlaySound::ExitInstance()
{
	if (m_hPlay)
		OnStopPlaying(0, 0);

	return TRUE;
}

BOOL CPlaySound::CreateThread(DWORD dwCreateFlags, UINT nStackSize, LPSECURITY_ATTRIBUTES lpSecurityAttrs)
{
    if(!InitInstance())
    {   
        return ExitInstance();
    }

    m_hThread = (HANDLE)_beginthreadex(lpSecurityAttrs, nStackSize, &(CPlaySound::ThreadProc), this, 0, &m_nThreadID);

    return (m_hThread != 0);
}

unsigned __stdcall CPlaySound::ThreadProc(void *p)
{
    if(p == NULL)
        return 0;

    CPlaySound *pThis = (CPlaySound*)p;

    MSG msg;
    BOOL bRet = 0;
    while(bRet = ::GetMessage(&msg, (HWND)INVALID_HANDLE_VALUE, 0, 0))
    {
        if(bRet == -1)
            break;

        switch(msg.message)
        {
        case WM_PLAYSOUND_STARTPLAYING:
            pThis->OnStartPlaying(msg.wParam, msg.lParam);
            break;
        case WM_PLAYSOUND_STOPPLAYING:
            pThis->OnStopPlaying(msg.wParam, msg.lParam);
            break;
        case WM_PLAYSOUND_RESET:
            pThis->OnReset(msg.wParam, msg.lParam);
            break;
        case WM_PLAYSOUND_PLAYBLOCK:
            pThis->OnWriteSoundData(msg.wParam, msg.lParam);
            break;
        case MM_WOM_DONE:
            pThis->OnEndPlaySoundData(msg.wParam, msg.lParam);
            break;
        default:
            break;
        }
    }

    _endthreadex(pThis->ExitInstance());
    return 0;
}

int CPlaySound::GetThreadPriority()
{
    return ::GetThreadPriority(m_hThread);
}

BOOL CPlaySound::SetThreadPriority(int nPriority)
{
    return ::SetThreadPriority(m_hThread, nPriority);
}

DWORD CPlaySound::SuspendThread()
{
    return ::SuspendThread(m_hThread);
}

DWORD CPlaySound::ResumeThread()
{
    return ::ResumeThread(m_hThread);
}

BOOL CPlaySound::PostThreadMessage(UINT message, WPARAM wParam, LPARAM lParam)
{
    return ::PostThreadMessage(m_nThreadID, message, wParam, lParam);
}

/////////////////////////////////////////////////////////////////////////////
// CPlaySound message handlers
void CPlaySound::OnStartPlaying(WPARAM wParam, LPARAM lParam)
{
	DWORD mmReturn = 0;

    if (!m_bPlay)
	{
		// open wavein device
		MMRESULT mmReturn = ::waveOutOpen(&m_hPlay, WAVE_MAPPER,
			&m_WaveFormatEx, ::GetCurrentThreadId(), 0, CALLBACK_THREAD);
			
		if (!mmReturn)
			m_bPlay = TRUE;
	}
}

void CPlaySound::OnStopPlaying(WPARAM wParam, LPARAM lParam)
{
  MMRESULT mmReturn = 0;

	if (m_bPlay)
	{
		mmReturn = ::waveOutReset(m_hPlay);
				
		if (!mmReturn)
        {
            mmReturn = ::waveOutClose(m_hPlay);
            m_hPlay = NULL;
        }

		if (!mmReturn)
			m_bPlay = FALSE;
	}
}

void CPlaySound::OnReset(WPARAM wParam, LPARAM lParam)
{
	MMRESULT mmReturn = 0;

	if (m_bPlay)
		::waveOutReset(m_hPlay);
}

void CPlaySound::OnEndPlaySoundData(WPARAM wParam, LPARAM lParam)
{
	LPWAVEHDR lpHdr = (LPWAVEHDR) lParam;
	if (lpHdr)
	{
		::waveOutUnprepareHeader(m_hPlay, lpHdr, sizeof(WAVEHDR));
	
		if (lpHdr->lpData)
			delete [] ((BYTE*) lpHdr->lpData);
		delete lpHdr;
	}
}

void CPlaySound::OnWriteSoundData(WPARAM wParam, LPARAM lParam)
{
	LPWAVEHDR lpHdr = (LPWAVEHDR) lParam;
	MMRESULT mmResult = 0;

	if (m_bPlay && lpHdr)
	{
		mmResult = ::waveOutPrepareHeader(m_hPlay, lpHdr, sizeof(WAVEHDR));
		mmResult = ::waveOutWrite(m_hPlay, lpHdr, sizeof(WAVEHDR));
	}
	SetEvent(m_hCompleteEvent);
}

void CPlaySound::StartPlay()
{
	PostThreadMessage(WM_PLAYSOUND_STARTPLAYING, 0, 0);
}

void CPlaySound::StopPlay()
{
	PostThreadMessage(WM_PLAYSOUND_STOPPLAYING, 0, 0);
}

void CPlaySound::Reset()
{
	PostThreadMessage(WM_PLAYSOUND_RESET, 0, 0);
}

void CPlaySound::WriteSoundData(BYTE* lpData, DWORD dwBufferLength, int nChannels, int nBitsPerSample, int nSamplesPerSec)
{
	if (m_bPlay)
	{
		if (m_WaveFormatEx.nChannels != (WORD)nChannels ||
			m_WaveFormatEx.wBitsPerSample != (WORD)nBitsPerSample ||
			m_WaveFormatEx.nSamplesPerSec != (DWORD)nSamplesPerSec )
		{
			SetWaveFormat(nChannels, nBitsPerSample, nSamplesPerSec);
		}

		LPWAVEHDR lpHdr = new WAVEHDR;
		ZeroMemory(lpHdr, sizeof(WAVEHDR));

		lpHdr->dwBufferLength = dwBufferLength;
		lpHdr->lpData = new CHAR[dwBufferLength];
		memcpy(lpHdr->lpData, lpData, dwBufferLength);
		
		PostThreadMessage(WM_PLAYSOUND_PLAYBLOCK, 0, (LPARAM)lpHdr);
		WaitForSingleObject(m_hCompleteEvent, INFINITE);
	}
}

void CPlaySound::SetWaveFormat(int nChannels, int nBitsPerSample, int nSamplesPerSec)
{
	m_WaveFormatEx.nChannels = nChannels;
	m_WaveFormatEx.wBitsPerSample = nBitsPerSample;
	m_WaveFormatEx.nSamplesPerSec = nSamplesPerSec;
	m_WaveFormatEx.nBlockAlign = (m_WaveFormatEx.wBitsPerSample / 8) * m_WaveFormatEx.nChannels;
	m_WaveFormatEx.nAvgBytesPerSec = m_WaveFormatEx.nBlockAlign * m_WaveFormatEx.nSamplesPerSec;

	StopPlay();
	StartPlay();
}

IPlaySound* GetSoundDevice()
{
	if (!g_SoundDevice.m_hThread)
	{
		g_SoundDevice.CreateThread();
		g_SoundDevice.SetThreadPriority(THREAD_PRIORITY_HIGHEST);
		Sleep(10);
	}

	return (IPlaySound *)&g_SoundDevice;
}

