
// This module implements the voice record and compression functions 
#include <windows.h>
#include <assert.h>
#include "dsound.h"
#include "voice.h"

#include "ivoicerecord.h"

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"


// ------------------------------------------------------------------------------
// Globals.
// ------------------------------------------------------------------------------

typedef HRESULT (WINAPI *DirectSoundCaptureCreateFn)(const GUID FAR *lpGUID, LPDIRECTSOUNDCAPTURE *pCapture, LPUNKNOWN pUnkOuter);



// ------------------------------------------------------------------------------
// Static helpers
// ------------------------------------------------------------------------------



// ------------------------------------------------------------------------------
// VoiceRecord_DSound
// ------------------------------------------------------------------------------

class VoiceRecord_DSound : public IVoiceRecord
{
protected:

	virtual				~VoiceRecord_DSound();


// IVoiceRecord.
public:

						VoiceRecord_DSound();
	virtual void		Release();

	virtual bool		RecordStart();
	virtual void		RecordStop();
	
	// Initialize. The format of the data we expect from the provider is
	// 8-bit signed mono at the specified sample rate.
	virtual bool		Init(int sampleRate);

	virtual void		Idle();

	// Get the most recent N samples.
	virtual int			GetRecordedData(short *pOut, int nSamplesWanted);

private:
	void				Term();				// Delete members.
	void				Clear();			// Clear members.
	void				UpdateWrapping();

	inline DWORD		NumCaptureBufferBytes()	{return m_nCaptureBufferBytes;}


private:
	HINSTANCE					m_hInstDS;

	LPDIRECTSOUNDCAPTURE		m_pCapture;
	LPDIRECTSOUNDCAPTUREBUFFER	m_pCaptureBuffer;

	// How many bytes our capture buffer has.
	DWORD						m_nCaptureBufferBytes;
	
	// We need to know when the capture buffer loops, so we install an event and 
	// update this in the event.
	DWORD						m_WrapOffset;
	HANDLE						m_hWrapEvent;

	// This is our (unwrapped) position that tells how much data we've given to the app.
	DWORD						m_LastReadPos;
};



VoiceRecord_DSound::VoiceRecord_DSound()
{
	Clear();
}


VoiceRecord_DSound::~VoiceRecord_DSound()
{
	Term();
}


void VoiceRecord_DSound::Release()
{
	delete this;
}


bool VoiceRecord_DSound::RecordStart()
{
	return true;
}


void VoiceRecord_DSound::RecordStop()
{
}


bool VoiceRecord_DSound::Init(int sampleRate)
{
	HRESULT hr;
	DSCBUFFERDESC dscDesc;
	DirectSoundCaptureCreateFn createFn;

	
	Term();


	WAVEFORMATEX recordFormat =
	{
		WAVE_FORMAT_PCM,		// wFormatTag
		1,						// nChannels
		sampleRate,				// nSamplesPerSec
		sampleRate*2,			// nAvgBytesPerSec
		2,						// nBlockAlign
		16,						// wBitsPerSample
		sizeof(WAVEFORMATEX)	// cbSize
	};


	
	// Load the DSound DLL.
	m_hInstDS = LoadLibrary("dsound.dll");
	if(!m_hInstDS)
		goto HandleError;

	createFn = (DirectSoundCaptureCreateFn)GetProcAddress(m_hInstDS, "DirectSoundCaptureCreate");
	if(!createFn)
		goto HandleError;

	hr = createFn(NULL, &m_pCapture, NULL);
	if(FAILED(hr))
		goto HandleError;

	// Create the capture buffer.
	memset(&dscDesc, 0, sizeof(dscDesc));
	dscDesc.dwSize = sizeof(dscDesc);
	dscDesc.dwFlags = 0;
	dscDesc.dwBufferBytes = recordFormat.nAvgBytesPerSec;
	dscDesc.lpwfxFormat = &recordFormat;

	hr = m_pCapture->CreateCaptureBuffer(&dscDesc, &m_pCaptureBuffer, NULL);
	if(FAILED(hr))
		goto HandleError;


	// Figure out how many bytes we got in our capture buffer.
	DSCBCAPS caps;
	memset(&caps, 0, sizeof(caps));
	caps.dwSize = sizeof(caps);

	hr = m_pCaptureBuffer->GetCaps(&caps);
	if(FAILED(hr))
		goto HandleError;

	m_nCaptureBufferBytes = caps.dwBufferBytes;


	// Set it up so we get notification when the buffer wraps.
	m_hWrapEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
	if(!m_hWrapEvent)
		goto HandleError;

	DSBPOSITIONNOTIFY dsbNotify;
	dsbNotify.dwOffset = dscDesc.dwBufferBytes - 1;
	dsbNotify.hEventNotify = m_hWrapEvent;
	
	// Get the IDirectSoundNotify interface.
	LPDIRECTSOUNDNOTIFY pNotify;
	hr = m_pCaptureBuffer->QueryInterface(IID_IDirectSoundNotify, (void**)&pNotify);
	if(FAILED(hr))
		goto HandleError;
	
	hr = pNotify->SetNotificationPositions(1, &dsbNotify);
	pNotify->Release();
	if(FAILED(hr))
		goto HandleError;

	// Start capturing.
	hr = m_pCaptureBuffer->Start(DSCBSTART_LOOPING);
	if(FAILED(hr))
		return false;

	return true;


HandleError:;
	Term();
	return false;
}


void VoiceRecord_DSound::Term()
{
	if(m_pCaptureBuffer)
		m_pCaptureBuffer->Release();

	if(m_pCapture)
		m_pCapture->Release();

	if(m_hWrapEvent)
		DeleteObject(m_hWrapEvent);

	if(m_hInstDS)
	{
		FreeLibrary(m_hInstDS);
		m_hInstDS = NULL;
	}

	Clear();
}


void VoiceRecord_DSound::Clear()
{
	m_pCapture = NULL;
	m_pCaptureBuffer = NULL;
	m_WrapOffset = 0;
	m_LastReadPos = 0;
	m_hWrapEvent = NULL;
	m_hInstDS = NULL;
}


void VoiceRecord_DSound::Idle()
{
	UpdateWrapping();
}


int VoiceRecord_DSound::GetRecordedData(short *pOut, int nSamplesInt)
{
	if(!m_pCaptureBuffer)
	{
		assert(false);
		return 0;
	}

	DWORD dwStatus;
	HRESULT hr = m_pCaptureBuffer->GetStatus(&dwStatus);
	if(FAILED(hr) || !(dwStatus & DSCBSTATUS_CAPTURING))
		return 0;
	
	Idle();	// Update wrapping..

	DWORD nSamplesWanted = (DWORD)nSamplesInt;

	DWORD dwCapturePos, dwReadPos;
	hr = m_pCaptureBuffer->GetCurrentPosition(&dwCapturePos, &dwReadPos);
	if(FAILED(hr))
		return 0;

	dwCapturePos += m_WrapOffset;
	dwReadPos += m_WrapOffset;

	// Read the range (dwReadPos-nSamplesWanted, dwReadPos), but don't re-read data we've already read.
	DWORD readStart;
	if(dwReadPos > nSamplesWanted)
		readStart = dwReadPos - nSamplesWanted;
	else
		readStart = 0;
	
	if(readStart < m_LastReadPos)
		readStart = m_LastReadPos;

	// Lock the buffer.
	LPVOID pData[2];
	DWORD dataLen[2];

	hr = m_pCaptureBuffer->Lock(
		readStart % NumCaptureBufferBytes(),	// Offset.
		dwReadPos - readStart,					// Number of bytes to lock.
		&pData[0],								// Buffer 1.
		&dataLen[0],							// Buffer 1 length.
		&pData[1],								// Buffer 2.
		&dataLen[1],							// Buffer 2 length.
		0										// Flags.
	);

	if(FAILED(hr))
		return 0;

	// Hopefully we didn't get too much data back!
	if((dataLen[0]+dataLen[1]) > nSamplesWanted)
	{
		assert(false);
		m_pCaptureBuffer->Unlock(pData[0], dataLen[0], pData[1], dataLen[1]);
		return 0;
	}

	// Copy the data to the output.
	memcpy(pOut, pData[0], dataLen[0]);
	memcpy(&pOut[dataLen[0]/2], pData[1], dataLen[1]);

	m_pCaptureBuffer->Unlock(pData[0], dataLen[0], pData[1], dataLen[1]);

	// Update the shiz.
	m_LastReadPos = dwReadPos;
	return (dataLen[0] + dataLen[1]) >> 1;
}


void VoiceRecord_DSound::UpdateWrapping()
{
	if(!m_pCaptureBuffer)
		return;

	// Has the buffer wrapped?
	if(WaitForSingleObject(m_hWrapEvent, 0) == WAIT_OBJECT_0)
	{
		m_WrapOffset += m_nCaptureBufferBytes;
	}
}



IVoiceRecord* CreateVoiceRecord_DSound(int sampleRate)
{
	VoiceRecord_DSound *pRecord = new VoiceRecord_DSound;
	if(pRecord && pRecord->Init(sampleRate))
	{
		return pRecord;
	}
	else
	{
		if(pRecord)
			pRecord->Release();

		return NULL;
	}
}

