#include "waveplayer.h"

WavePlayer::WavePlayer( bool verb ):
	verbose( verb ),
	bytesWritten( 0 ),
	waveOutHandle( NULL ),
	blockBase( 0 ),
	lastReadBlock( 0 ),
	blockFreeEvent( NULL )
{
}

WavePlayer::~WavePlayer()
{
}

bool WavePlayer::open( Input& input )
{
	// This is called when some playing is required.
	// Do all allocation here so that we do not hold resources
	// while we are just sitting waiting for something to happen.

	// Create sync object
	blockFreeEvent = CreateEvent( NULL, FALSE, FALSE, NULL );

	WAVEFORMATEX waveformatex;
	waveformatex.wFormatTag = WAVE_FORMAT_PCM;
	waveformatex.nChannels = input.getChannels();
	waveformatex.nSamplesPerSec = input.getSampleRate();
	waveformatex.wBitsPerSample = input.getBitsPerSample();
	waveformatex.nBlockAlign = (waveformatex.nChannels*waveformatex.wBitsPerSample) >> 3;
	waveformatex.nAvgBytesPerSec = waveformatex.nSamplesPerSec * waveformatex.nBlockAlign;
	waveformatex.cbSize = 0;

	MMRESULT mmErr = waveOutOpen(
		&waveOutHandle,
		WAVE_MAPPER,
		&waveformatex,
		(DWORD)blockFreeEvent,
		0,
		CALLBACK_EVENT
	);

	if( mmErr != MMSYSERR_NOERROR )
	{
		puts( "waveoutopen error" );

		waveOutHandle = NULL;
		//DeleteObject( blockFreeEvent );
		CloseHandle( blockFreeEvent );
		return false;
	}

	// We are going to make life easer for Windows here by ensuring that each of our
	// wave blocks lies on a memory page of it's own (and that it fills that page)
	// These blocks will be locked for DMA by the sound driver.

	blockBase = (unsigned char*)VirtualAlloc( NULL, OUTPUT_BLOCK_SIZE * (OUTPUT_BLOCKS<<1), MEM_COMMIT, PAGE_READWRITE );
	lastReadBlock = 0;

	// Create wave blocks
	unsigned char* blockCursor = blockBase;
	for ( int idx=0; idx < OUTPUT_BLOCKS; ++idx )
	{
		waveBlocks[idx].dwFlags = 0;
		waveBlocks[idx].lpData = (char*)blockCursor;
		waveBlocks[idx].dwBufferLength = OUTPUT_BLOCK_SIZE;

		// Prepare output block (lock memory page)
		waveOutPrepareHeader( waveOutHandle, waveBlocks + idx, sizeof(*waveBlocks) );
		blockCursor = blockCursor + OUTPUT_BLOCK_SIZE;
	}

	// Setup thread prioity to max - If you are having problems during
	// debugging (with a big hang!) then comment this next line out
	//SetThreadPriority( GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL );

	return true;
}

bool WavePlayer::close( Input& input )
{
	//SetThreadPriority( GetCurrentThread(), THREAD_PRIORITY_NORMAL );

	// If there is a wave device
	if ( waveOutHandle )
	{
		// Stop any pending playing
		waveOutRestart( waveOutHandle );
		waveOutReset( waveOutHandle );

		// Clean up wave blocks
		for( int idx=0; idx < OUTPUT_BLOCKS; ++idx )
			waveOutUnprepareHeader( waveOutHandle, waveBlocks + idx, sizeof(*waveBlocks) );

		// Clean up wave buffers
		VirtualFree( blockBase, 0, MEM_RELEASE );

		// Clean up
		waveOutClose( waveOutHandle );
		//DeleteObject( blockFreeEvent );
		CloseHandle( blockFreeEvent );
	}

	return true;
}

bool WavePlayer::play( Input& input )
{
	if ( input.isOpened() )
		return false;

	if ( ! input.open() )
		return false;

	if ( ! open( input ) )
		return false;

	// Scan ring buffer and fill any empty blocks - any blocks that become free
	// while this loop is running will retrigger the event - the worse that can
	// happen is that we enter this loop with no blocks free

	bool moreData;
	do
	{
		for ( int blockIdx = 0; blockIdx < OUTPUT_BLOCKS; blockIdx++ )
		{
			WAVEHDR* outputBlock = waveBlocks + blockIdx;
			if ( (outputBlock->dwFlags & WHDR_INQUEUE) == 0 )
			{
				// Get block and then just send it to the device
				unsigned long read;
				moreData = input.getPCMBlock(
					(unsigned char*)outputBlock->lpData,
					OUTPUT_BLOCK_SIZE,
					read );
				outputBlock->dwBufferLength = read;

				// Store "pure" block data
				lastReadBlock = blockIdx;
				blockSizes[blockIdx] = outputBlock->dwBufferLength;
				memcpy( ((BYTE*)outputBlock->lpData) + (OUTPUT_BLOCK_SIZE * OUTPUT_BLOCKS),
						outputBlock->lpData,
						outputBlock->dwBufferLength );

				if ( outputBlock->dwBufferLength > 0 )
					waveOutWrite( waveOutHandle, outputBlock, sizeof(*outputBlock) );

				if ( ! moreData )
					break;
			}
		}
		MsgWaitForMultipleObjects( 1, &blockFreeEvent, FALSE, 1000, QS_POSTMESSAGE );
	}
	while ( moreData );

	return true;
}
