// DirectSound modul
#include <xtl.h>
#include "burner.h"
#include "aud_dsp.h"
#include <math.h>

// Sound is split into a series of 'segs', one seg for each frame
// The Loop buffer is a multiple of this seg length.

IDirectSound* pDS = NULL;			// DirectSound interface
static IDirectSoundBuffer* pdsbPrim = NULL;	// Primary buffer
static IDirectSoundBuffer* pdsbLoop = NULL;	// (Secondary) Loop buffer
static int cbLoopLen = 0;					// Loop length (in bytes) calculated
int (*DSoundGetNextSound)(int);				// Callback used to request more sound
static int nDSoundFps;						// Application fps * 100
static long nDSoundVol = 0;
extern int bAlwaysDrawFrames;
extern int nAppVirtualFps;
extern "C" int dprintf(char *format, ...);

static int DSoundGetNextSoundFiller(int)							// int bDraw
{
	if (nAudNextSound == NULL) {
		return 1;
	}
	memset(nAudNextSound, 0, nAudSegLen << 2);						// Write silence into the buffer

	return 0;
}

static int DxSetCallback(int (*pCallback)(int))
{
	if (pCallback == NULL) {
		DSoundGetNextSound = DSoundGetNextSoundFiller;
	} else {
		DSoundGetNextSound = pCallback;
	}

	return 0;
}

static int DxBlankSound()
{
	void *pData = NULL, *pData2 = NULL;
	DWORD cbLen = 0, cbLen2 = 0;

	// Lock the Loop buffer
	if (FAILED(pdsbLoop->Lock(0, cbLoopLen, &pData, &cbLen, &pData2, &cbLen2, 0))) {
		return 1;
	}
	memset(pData, 0, cbLen);

	// Unlock (2nd 0 is because we wrote nothing to second part)
	pdsbLoop->Unlock(pData, cbLen, pData2, 0);

	// Also blank the nAudNextSound buffer
	if (nAudNextSound) {
		memset(nAudNextSound, 0, nAudSegLen << 2);
	}

	return 0;
}

static int nDSoundNextSeg = 0;										// We have filled the sound in the loop up to the beginning of 'nNextSeg'

#define WRAP_INC(x) { x++; if (x >= nAudSegCount) x = 0; }

// This function checks the DSound loop, and if necessary does a callback to update the emulation
static int DxSoundCheck()
{
	//dprintf("DxSoundCheck\n");
	int nPlaySeg = 0, nFollowingSeg = 0;
	DWORD nPlay = 0, nWrite = 0;

	if (pdsbLoop == NULL) {
		return 1;
	}

	// We should do nothing until nPlay has left nDSoundNextSeg
	pdsbLoop->GetCurrentPosition(&nPlay, &nWrite);

	nPlaySeg = nPlay / (nAudSegLen << 2);

	if (nPlaySeg > nAudSegCount -1 ) {
		nPlaySeg = nAudSegCount - 1;
	}
	if (nPlaySeg < 0) {												// important to ensure nPlaySeg clipped for below
		nPlaySeg = 0;
	}

	if (nDSoundNextSeg == nPlaySeg) {
		Sleep(2);													// Don't need to do anything for a bit

		return 0;
	}

	// work out which seg we will fill next
	nFollowingSeg = nDSoundNextSeg;
	WRAP_INC(nFollowingSeg);

	while (nDSoundNextSeg != nPlaySeg) {
		void *pData = NULL, *pData2 = NULL;
		DWORD cbLen = 0, cbLen2 = 0;
		int bDraw;

		// fill nNextSeg

		// Lock the relevant seg of the loop buffer
		if (SUCCEEDED(pdsbLoop->Lock(nDSoundNextSeg * (nAudSegLen << 2), nAudSegLen << 2, &pData, &cbLen, &pData2, &cbLen2, 0))) {
			// Locked the segment, so write the sound we calculated last time
			memcpy(pData, nAudNextSound, nAudSegLen << 2);

			// Unlock (2nd 0 is because we wrote nothing to second part)
			pdsbLoop->Unlock(pData, cbLen, pData2, 0);
		}

		bDraw = (nFollowingSeg == nPlaySeg)	|| bAlwaysDrawFrames;	// If this is the last seg of sound, flag bDraw (to draw the graphics)

		DSoundGetNextSound(bDraw);									// get more sound into nAudNextSound

		if (nAudDSPModule)	{
			DspDo(nAudNextSound, nAudSegLen);
		}

		nDSoundNextSeg = nFollowingSeg;
		WRAP_INC(nFollowingSeg);
	}

	return 0;
}

static int DxSoundExit()
{
	DspExit();

	free(nAudNextSound);
	nAudNextSound = NULL;

	DSoundGetNextSound = NULL;

	//// Release the (Secondary) Loop Sound Buffer
	//RELEASE(pdsbLoop);
	//// Release the Primary Sound Buffer
	//RELEASE(pdsbPrim);
	//// Release the DirectSound interface
	//RELEASE(pDS);

	return 0;
}

void DirectSoundInit() {
	DirectSoundCreate(NULL, &pDS, NULL);
}

static int DxSoundInit()
{
	int nRet = 0;
	DSBUFFERDESC dsbd;
	WAVEFORMATEX wfx;

	if (nAudSampleRate <= 0) {
		return 1;
	}


	nDSoundFps = nAppVirtualFps;

	// Calculate the Seg Length and Loop length (round to nearest sample)
	nAudSegLen = (nAudSampleRate * 100 + (nDSoundFps >> 1)) / nDSoundFps;
	cbLoopLen = (nAudSegLen * nAudSegCount) << 2;

	// Make the format of the sound
	memset(&wfx, 0, sizeof(wfx));
	wfx.cbSize = 0;
	wfx.wFormatTag = WAVE_FORMAT_PCM;
	wfx.nChannels = 2;										  // stereo
	wfx.nSamplesPerSec = nAudSampleRate;					  // sample rate
	wfx.wBitsPerSample = 16;								  // 16-bit
	wfx.nBlockAlign = 4;									  // bytes per sample
	wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign;

	////dprintf("Create Sound\n");
	// Create the DirectSound interface

	////dprintf("Create Sound Success\n");

	// Make the primary sound buffer
	memset(&dsbd, 0, sizeof(DSBUFFERDESC));
	dsbd.dwSize = sizeof(DSBUFFERDESC);
	dsbd.dwBufferBytes = 1024*8;
	dsbd.lpwfxFormat = &wfx;

	//dsbd.dwFlags = DSBCAPS_PRIMARYBUFFER;
	if (FAILED(pDS->CreateSoundBuffer(&dsbd, &pdsbPrim, NULL))) {
		DxSoundExit();
		////dprintf("Create CreateSoundBuffer 1 Error\n");
		return 1;
	}

	// Make the loop sound buffer
	memset(&dsbd, 0, sizeof(DSBUFFERDESC));
	dsbd.dwSize = sizeof(DSBUFFERDESC);
	dsbd.dwBufferBytes = cbLoopLen;
	dsbd.lpwfxFormat = &wfx;								// Same format as the primary buffer
	if (FAILED(pDS->CreateSoundBuffer(&dsbd, &pdsbLoop, NULL))) {
		AudSoundExit();
		////dprintf("Create CreateSoundBuffer 2 Error\n");
		return 1;
	}
	DSMIXBINVOLUMEPAIR dsmbvp[8] = {
		{DSMIXBIN_FRONT_LEFT, DSBVOLUME_MAX},   // left channel
		{DSMIXBIN_FRONT_RIGHT, DSBVOLUME_MAX},  // right channel
		{DSMIXBIN_FRONT_CENTER, DSBVOLUME_MAX}, // left channel
		{DSMIXBIN_FRONT_CENTER, DSBVOLUME_MAX}, // right channel
		{DSMIXBIN_BACK_LEFT, DSBVOLUME_MAX},    // left channel
		{DSMIXBIN_BACK_RIGHT, DSBVOLUME_MAX},   // right channel
		{DSMIXBIN_LOW_FREQUENCY, DSBVOLUME_MAX},    // left channel
		{DSMIXBIN_LOW_FREQUENCY, DSBVOLUME_MAX}};   // right channel
		DSMIXBINS dsmb;
		dsmb.dwMixBinCount = 8;
		dsmb.lpMixBinVolumePairs = dsmbvp;
		pdsbLoop->SetMixBins(&dsmb);


		nAudNextSound = (short*)osd_malloc(nAudSegLen << 2);		// The next sound block to put in the stream
		if (nAudNextSound == NULL) {
			DxSoundExit();
			return 1;
		}

		DxSetCallback(NULL);

		DspInit();
		////dprintf("Audio End\n");
		return 0;
}

int DxSoundPlay()
{
	DxBlankSound();
	pdsbLoop->SetVolume(nDSoundVol);

	if (FAILED(pdsbLoop->Play(0, 0, DSBPLAY_LOOPING))) {
		//dprintf("Unable to Play Sound\n");
		return 1;
	}
	bAudPlaying = 1;

	//dprintf("Play Sound Success\n");
	return 0;
}

int DxSoundStop()
{
	bAudPlaying = 0;

	if (bAudOkay == 0) {
		return 1;
	}

	// Stop the looping buffer
	if(pdsbLoop)
		pdsbLoop->Stop();

	return 0;
}

void ResetSound() {
	DSBUFFERDESC dsbd;
	WAVEFORMATEX wfx;
	bAudPlaying = 0;
	if (pdsbLoop)
	{
		pdsbLoop->Stop();
		pdsbLoop->Release();
		pdsbLoop = NULL;
	}

	nAudSegLen = (nAudSampleRate * 100 + (nDSoundFps >> 1)) / nDSoundFps;
	cbLoopLen = (nAudSegLen * nAudSegCount) << 2;

	memset(&dsbd, 0, sizeof(DSBUFFERDESC));
	dsbd.dwSize = sizeof(DSBUFFERDESC);

	memset(&wfx, 0, sizeof(wfx));
	wfx.cbSize = 0;
	wfx.wFormatTag = WAVE_FORMAT_PCM;
	wfx.nChannels = 2;										  // stereo

	wfx.nSamplesPerSec = nAudSampleRate;
	wfx.wBitsPerSample = 16;								  // 16-bit
	wfx.nBlockAlign = 4;									  // bytes per sample
	wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign;

	dsbd.lpwfxFormat = &wfx;
	dsbd.dwBufferBytes = cbLoopLen;
	pDS->CreateSoundBuffer(&dsbd, &pdsbLoop, NULL);
	//free(nAudNextSound);
	nAudNextSound = (short*)realloc(nAudNextSound, nAudSegLen << 2);		// The next sound block to put in the stream
	DxSoundPlay();
	nBurnSoundRate = nAudSampleRate;
	nBurnSoundLen = nAudSegLen;
}

static int DxSoundSetVolume()
{
	if (nAudVolume == 10000) {
		nDSoundVol = DSBVOLUME_MAX;
	} else {
		if (nAudVolume == 0) {
			nDSoundVol = DSBVOLUME_MIN;
		} else {
			nDSoundVol = DSBVOLUME_MAX - (long)(10000.0 * pow(10.0, nAudVolume / -5000.0)) + 100;
		}
	}

	if (nDSoundVol < DSBVOLUME_MIN) {
		nDSoundVol = DSBVOLUME_MIN;
	}

	if (FAILED(pdsbLoop->SetVolume(nDSoundVol))) {
		return 0;
	}

	return 1;
}

static int DxGetSettings(InterfaceInfo* pInfo)
{
	TCHAR szString[260] = _T("");

	_sntprintf(szString, 260, _T("Audio is delayed by approx. %ims"), int(100000.0 / (nDSoundFps / (nAudSegCount - 1.0))));
	IntInfoAddStringModule(pInfo, szString);

	return 0;
}

struct AudOut AudOutDx = { DxBlankSound, DxSoundCheck, DxSoundInit, DxSetCallback, DxSoundPlay, DxSoundStop, DxSoundExit, DxSoundSetVolume, DxGetSettings, _T("DirectSound3 audio output") };
