// DirectSound module
#include <xtl.h>
#include "..\..\fba.h"			// change this later

//#define DIRECTSOUND_VERSION  0x0300			// Only need version from DirectX 3
// VERSION isnt needed for DSound on the XBox since its always going to be 8

#include <dsound.h>

// Sound is split into a series of 'segs', one seg for each frame
// (typically 367 samples long).
// The Loop buffer is a multiple of this seg length.



static IDirectSound8* pDS = NULL;			// DirectSound interface
static IDirectSoundBuffer8* pdsbLoop = NULL;	// (Secondary) Loop buffer
static int cbLoopLen = 0;					// Loop length (in bytes) calculated

static int nDSoundFps;						// Application fps * 100
long nDSoundVol = DSBVOLUME_MAX;

int nAppVirtualFps;			// App fps * 100
int bAppBassFilter = 0;

static int DxBlankSound()
{
	void *pData=NULL,*pData2=NULL; DWORD cbLen=0,cbLen2=0;
	int nRet=0;
	// Lock the Loop buffer
	nRet=pdsbLoop->Lock(0,cbLoopLen,&pData,&cbLen,
						&pData2,&cbLen2,0);
	if (nRet<0)	return 1;
	if (pData!=NULL) memset(pData,0,cbLen);

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

	// Also blank the nAudNextSound buffer
	if (nAudNextSound!=NULL) memset(nAudNextSound,0,nAudSegLen<<2);
	return 0;
}

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

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

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

int (*DSoundGetNextSound) (int bDraw) = DSoundGetNextSoundFiller;	// Callback used to request more sound

// This function checks the DSound loop, and if necessary gets some more sound
// and a picture.
static int DxSoundCheck()
{
	int nPlaySeg = 0, nFollowingSeg = 0;
	DWORD nPlay = 0, nWrite = 0;
	int nRet = 0;
	int nRetVal = 0;
	if (pdsbLoop == NULL) {
		nRetVal = 1;
		goto End;
	}
	
	// We should do nothing until nPlay has left nDSoundNextSeg
	nRet = 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
		goto End;
	}

	// 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
		nRet = pdsbLoop->Lock(nDSoundNextSeg * (nAudSegLen << 2), nAudSegLen << 2, &pData, &cbLen, &pData2, &cbLen2, 0);

		if (nRet >= 0 && pData != NULL) {
			// Locked the seg okay - write the sound we calculated last time
			memcpy(pData, nAudNextSound, nAudSegLen << 2);
		}
		// Unlock (2nd 0 is because we wrote nothing to second part)
		if (nRet >= 0) {
			pdsbLoop->Unlock(pData, cbLen, pData2, 0);
		}

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

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

		if (bAppBassFilter)	{
			DspDo(nAudNextSound, nAudSegLen);
		}
		nDSoundNextSeg = nFollowingSeg;
		WRAP_INC(nFollowingSeg)
	}

	End:
	return nRetVal;
}

static int DxSoundExit()
{
	DWORD Status = DSBSTATUS_PLAYING;

	DspExit();
	bAudOkay = 0;													// This module is no longer okay

	if (nAudNextSound != NULL) {
		free(nAudNextSound);
		nAudNextSound = NULL;
	}
	
	// Loop until the soundbuffer has stopped playing, otherwise
	// it may cause sync problems.

	if (pdsbLoop)
	{
		pdsbLoop->Stop ();

		while (Status == DSBSTATUS_PLAYING)
		{
			pdsbLoop->GetStatus(&Status);
		}
	}

	// Release the (Secondary) Loop Sound Buffer
	SAFE_RELEASE(pdsbLoop)
	// Release the Primary Sound Buffer
	SAFE_RELEASE(pDS)

	return 0;
}

static int DxSoundInit()
{

	// Lantus B2 Fix - Pal 50 users need 5000 (50fps) otherwise their sound is scratchy

	if (isPAL50)
		nAppVirtualFps = 5000;
	else
		nAppVirtualFps = 6000;

	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 = 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;

	// Create the DirectSound interface
	nRet = DirectSoundCreate(NULL, &pDS, NULL);
	if (nRet < 0 || pDS == NULL) {
		return 1;
	}


	// Make the loop sound buffer

	
	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;

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

 	dsbd.dwFlags = 0;
	dsbd.dwBufferBytes = cbLoopLen;
	dsbd.lpwfxFormat = &wfx;								// Same format as the primary buffer
	dsbd.lpMixBins = &dsmb;

	nRet = pDS->CreateSoundBuffer(&dsbd, &pdsbLoop, NULL);
	if (nRet < 0 || pdsbLoop == NULL) {
		AudSoundExit();
		return 1;
	}

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

	bAudOkay = 1;											// This module was initted okay
	DspInit();

	return 0;
}

static int DxSoundPlay()
{
	if (bAudOkay == 0) {
		return 1;
	}
	DxBlankSound();
//	pdsbLoop->SetVolume(nDSoundVol);
	// Play the looping buffer
	if (pdsbLoop->Play(0, 0, DSBPLAY_LOOPING) < 0) {
		return 1;
	}
	bAudPlaying = 1;
	
	return 0;
}

static int DxSoundStop()
{
	bAudPlaying = 0;
	if (bAudOkay == 0) {
		return 1;
	}
	// Stop the looping buffer
	pdsbLoop->Stop();
	
	return 0;
}

static int DxSoundSetVolume()
{
/*	if (pdsbLoop->SetVolume(nDSoundVol) != DS_OK) {
		return 0;
	} */
	return 1;
}

struct AudOut AudOutDx = {DxBlankSound, DxSoundCheck, DxSoundInit, DxSoundPlay,DxSoundStop,DxSoundExit,DxSoundSetVolume};
