// *********************************************************************
//    Copyright (c) 2002 Frank Bauernoeppel
//
// 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.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// *********************************************************************

// *********************************************************************
// HP41Sound.cpp : implementation file
// *********************************************************************

#include"StdAfx.h"
#include"V41.h"

HP41Sound WaveSound;

// time period of each buffer
#define MILLISEC_PER_BUFFER	200

// after that amount of time, silent buffers are suppressed
#define SILENCE_TIMEOUT_MS	1000

HP41Sound::HP41Sound()
{
	InitializeCriticalSection(&cs);

	hThreadWave = NULL;									// thread handle of sound message handler
	dwThreadWaveId = 0;									// thread id of sound message handler

	hWaveOut = NULL;
	lpWaveHdr = NULL;
	dwDuration = DEFAULT_INST_SPEED;		// supply a good default

	// be not silent.
	nVal = 0;
	fSilent = FALSE;
	dwSilent = 0;

	byVolume = MAX_VOLUME / 4;				// 25%
}

HP41Sound::~HP41Sound()
{
	if(hWaveOut != NULL) {
		TRACE(_T("HP41Sound::~HP41Sound: forced HP41Sound::Close\n"));
		// delete last allocated frame
		if (lpWaveHdr != NULL) {
			VERIFY(waveOutUnprepareHeader( hWaveOut, lpWaveHdr, sizeof(WAVEHDR)) == MMSYSERR_NOERROR);
			free(lpWaveHdr->lpData);
			free(lpWaveHdr);
		}
		Close();
	}
	DeleteCriticalSection(&cs);
}

//
// sound message handler thread
//
UINT __stdcall HP41Sound::SoundWndProc(LPVOID pParam)
{
	MSG msg;

	while (GetMessage(&msg, NULL, 0, 0))
	{
		if (msg.message == MM_WOM_DONE)
		{
			HWAVEOUT hwo = (HWAVEOUT) msg.wParam;
			PWAVEHDR pwh = (PWAVEHDR) msg.lParam;

			VERIFY(waveOutUnprepareHeader(hwo,pwh,sizeof(*pwh)) == MMSYSERR_NOERROR);
			free(pwh->lpData);		// free waveform data
			free(pwh);						// free wavefom header
		}
	}
	return 0;
	UNREFERENCED_PARAMETER(pParam);
}

//
// create sound message handler thread
//
bool HP41Sound::CreateWaveThread()
{
	ASSERT(hThreadWave == NULL);

	// create sound message handler thread
	hThreadWave = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)&SoundWndProc,NULL,0,&dwThreadWaveId);
	return hThreadWave != NULL;
}

//
// destroy sound message handler thread
//
void HP41Sound::DestroyWaveThread()
{
	if (hThreadWave != NULL) {			// sound message handler thread running
		// shut down thread
		while (!PostThreadMessage(dwThreadWaveId,WM_QUIT,0,0))
			Sleep(0);
		WaitForSingleObject(hThreadWave,INFINITE);
		CloseHandle(hThreadWave);
		hThreadWave = NULL;
	}
	return;
}

int HP41Sound::Open()
{
	if (hWaveOut != NULL)
		return 0;     // success, already opened

	EnterCriticalSection(&cs);

	MMRESULT wResult;

#ifdef _DEBUG
	WAVEOUTCAPS WaveOutCaps;
	wResult = waveOutGetDevCaps( WAVE_MAPPER, &WaveOutCaps, sizeof(WaveOutCaps) );
	if(wResult != MMSYSERR_NOERROR) {
		TCHAR szErrString[256];
		waveOutGetErrorText( wResult, szErrString, sizeof(szErrString)/sizeof(TCHAR) );
		TRACE(_T("ERROR waveOutGetDevCaps: %s\n"), szErrString );
		//fb: this is not a critical error, we simply contuinue
	}
#endif

	wfmt.wFormatTag = WAVE_FORMAT_PCM;
	wfmt.nChannels = 1;
	wfmt.nSamplesPerSec = 44100;
	wfmt.wBitsPerSample = 8;			// 8bit, unsigned, compatible with F_REG
	wfmt.nBlockAlign = wfmt.nChannels * wfmt.wBitsPerSample / 8;
	wfmt.nAvgBytesPerSec = wfmt.nBlockAlign * wfmt.nSamplesPerSec;
	wfmt.cbSize = 0;

	// create sound message handler
	wResult = MMSYSERR_ERROR;
	if (CreateWaveThread()) {
		// create a sound device, use the CALLBACK_THREAD flag because with the
		// CALLBACK_FUNCTION flag unfortunately the called callback function
		// can only call a specific set of Windows functions. Attempting to
		// call other functions at the wrong time will result in a deadlock.
		wResult = waveOutOpen(&hWaveOut, WAVE_MAPPER, &wfmt, dwThreadWaveId, 0, CALLBACK_THREAD);

		if(wResult != MMSYSERR_NOERROR) {
			TCHAR szErrString[256];
			waveOutGetErrorText( wResult, szErrString, sizeof(szErrString)/sizeof(TCHAR) );
			TRACE(_T("ERROR waveOutOpen: %s\n"), szErrString );
		}
	}
	if(wResult != MMSYSERR_NOERROR) {
		DestroyWaveThread();			// shut down message thread
		LeaveCriticalSection(&cs);
		return (int)wResult;
	}

	nVal = 0;	// the silent value

	Flush();

	LeaveCriticalSection(&cs);

	return 0; //OK
}

int HP41Sound::Open( int valSilence )
{
	int rc = Open();
	if(rc) {
		return rc;
	}

	// let's assume that a huge number of Put(valSilence) had beeen called, i.e. freq = 0 Hz
	EnterCriticalSection(&cs);
	fSilent = TRUE;
	dwSilent = 1 + SILENCE_TIMEOUT_MS / MILLISEC_PER_BUFFER;
	nVal = valSilence;
	LeaveCriticalSection(&cs);

	return 0;
}

int HP41Sound::Close()
{
	if(hWaveOut == NULL) {
		return -1;
	}

	Flush();

	DestroyWaveThread();		// shut down message thread

	MMRESULT wResult = waveOutReset(hWaveOut);
	if(wResult != MMSYSERR_NOERROR) {
		TCHAR szErrString[256];
		waveOutGetErrorText( wResult, szErrString, sizeof(szErrString)/sizeof(TCHAR) );
		TRACE(_T("ERROR waveOutReset: %s\n"), szErrString );
	}

	wResult = waveOutClose(hWaveOut);
	if(wResult != MMSYSERR_NOERROR) {
		TCHAR szErrString[256];
		waveOutGetErrorText( wResult, szErrString, sizeof(szErrString)/sizeof(TCHAR) );
		TRACE(_T("ERROR waveOutClose: %s\n"), szErrString );
	}

	hWaveOut = NULL;

	return 0; //OK
}

int HP41Sound::Put(int val)
{
	if(hWaveOut == NULL) {
		if (Open(val))			// it's the first call to Put, lets make val the default (silent) value
			return -1;
	}

	EnterCriticalSection(&cs);

	ASSERT(lpWaveHdr);

	DWORD dwNoOfSamples = (dwDuration * wfmt.nSamplesPerSec) / 1000000;
	if( dwSize + dwNoOfSamples * (wfmt.wBitsPerSample/8) > lpWaveHdr->dwBufferLength ) {
		Flush();
	}

	DWORD i;

	if(val != nVal) {
		fSilent = FALSE;	// there is something to be played
		dwSilent = 0;
		nVal = val;		// try this for new silence candidate value
	}

	int amplitude = 0x80 + (val > 0) * byVolume;

	for( i=0; i<dwNoOfSamples; i++ ) {
		lpWaveHdr->lpData[dwSize++] = amplitude;
	}

	LeaveCriticalSection(&cs);

	return 0; //OK
}

int HP41Sound::Flush()
{
	EnterCriticalSection(&cs);

	MMRESULT wResult;

	if( fSilent ) {

		if( MILLISEC_PER_BUFFER * dwSilent > SILENCE_TIMEOUT_MS ) {
			// we do not send the buffer, because it's totally silent
			// we re-use it to be re-filled
			dwSize = 0;
			LeaveCriticalSection(&cs);
			return 0;
		} else {
TRACE(_T("-"));
			dwSilent++;	// the next silent buffer, just count it and continue
		}
	} else {
TRACE(_T("*"));
	}

	if( lpWaveHdr != NULL ) {	// there is packet, flush it

#ifdef _DEBUG_waveOutGetPosition
		MMTIME mmtime;

		wResult = waveOutGetPosition( hWaveOut, &mmtime, sizeof(mmtime) );
		if (wResult == MMSYSERR_NOERROR)
		{
			switch(mmtime.wType) {
			case TIME_MS:
				TRACE(_T("@ %8d ms\n"), mmtime.u.ms );
				break;
			case TIME_SAMPLES:
				TRACE(_T("@ %8d samples\n"), mmtime.u.sample );
				break;
			case TIME_BYTES:
				TRACE(_T("@ %8d bytes\n"), mmtime.u.cb );
				break;
			default:
				TRACE(_T("@ ???????? ??\n"));
			}
		}
TRACE( _T("waveOutWrite %4d bytes\n"), lpWaveHdr->dwBufferLength );
#endif

		wResult = waveOutWrite( hWaveOut, lpWaveHdr, sizeof(WAVEHDR) );
		if (wResult != MMSYSERR_NOERROR)
		{
			TCHAR szErrString[256];
			waveOutGetErrorText( wResult, szErrString, sizeof(szErrString)/sizeof(TCHAR) );
			TRACE(_T("ERROR waveOutWrite: %s\n"), szErrString );

			waveOutUnprepareHeader( hWaveOut, lpWaveHdr, sizeof(WAVEHDR) );
			free(lpWaveHdr->lpData);
			lpWaveHdr->lpData = NULL;
		}
	}

	dwSize = 0;
	fSilent = TRUE;	// still empty, hence silent

	// create a next packet
	DWORD dwDataSize = MILLISEC_PER_BUFFER * wfmt.nAvgBytesPerSec / 1000;

	lpWaveHdr = (LPWAVEHDR)calloc(1,sizeof(WAVEHDR));
	if (lpWaveHdr == NULL)
	{
		TRACE(_T("ERROR: calloc(lpWaveHdr) failed\n"));
		LeaveCriticalSection(&cs);
		return 1;
	}

	lpWaveHdr->lpData = (LPSTR)calloc(dwDataSize,1);
	lpWaveHdr->dwBufferLength = dwDataSize;
	lpWaveHdr->dwFlags = 0L;
	lpWaveHdr->dwLoops = 0L;

	if (lpWaveHdr->lpData == NULL)
	{
		TRACE(_T("ERROR: calloc(lpData) failed\n"));
		free(lpWaveHdr);
		LeaveCriticalSection(&cs);
		return 1;
	}

	wResult = waveOutPrepareHeader( hWaveOut, lpWaveHdr, sizeof(WAVEHDR) );
	if (wResult != MMSYSERR_NOERROR) {
		TCHAR szErrString[256];
		waveOutGetErrorText( wResult, szErrString, sizeof(szErrString)/sizeof(TCHAR) );
		TRACE(_T("ERROR waveOutPrepareHeader: %s\n"), szErrString );
		free(lpWaveHdr->lpData);
		lpWaveHdr->lpData = NULL;
	}

	LeaveCriticalSection(&cs);

	return 0; //OK
}

void HP41Sound::SetDuration(DWORD dwVal)
{
	dwDuration = dwVal;
}

flag HP41Sound::IsAvailable()
{
	MMRESULT wResult;
	WAVEOUTCAPS WaveOutCaps;
	wResult = waveOutGetDevCaps( WAVE_MAPPER, &WaveOutCaps, sizeof(WaveOutCaps) );
	if(wResult != MMSYSERR_NOERROR) {
		TCHAR szErrString[256];
		waveOutGetErrorText( wResult, szErrString, sizeof(szErrString)/sizeof(TCHAR) );
		TRACE(_T("ERROR waveOutGetDevCaps: %s\n"), szErrString );
	}
	return (wResult==MMSYSERR_NOERROR && (WaveOutCaps.dwFormats & WAVE_FORMAT_4M08) != 0);
}


void HP41Sound::SetVolume(byte byVolumeIn)
{
	byVolume = byVolumeIn;
}

flag PlayWaveResource(WORD wResourceId)
{
	flag fRtn;
	HINSTANCE hInst = AfxGetResourceHandle();

	// Find the WAVE resource.
	HRSRC hResInfo = ::FindResource( hInst, MAKEINTRESOURCE(wResourceId), _T("WAVE") );
	if (hResInfo == NULL)
		return FALSE;

	// Load the WAVE resource.
	HANDLE hRes = ::LoadResource(hInst, hResInfo);
	if (hRes == NULL)
		return FALSE;

	// Lock the WAVE resource and play it.
	LPVOID lpRes = ::LockResource(hRes);
	if (lpRes != NULL) {
		fRtn = 1==sndPlaySound( (LPCTSTR)lpRes, SND_MEMORY | SND_ASYNC | SND_NODEFAULT );
		UnlockResource(hRes);
	}
	else
		fRtn = FALSE;

	// Free the WAVE resource and return success or failure.
	::FreeResource(hRes);
	return fRtn;
}
