//////////////////////////////////////////////////////////////////////////////
//
// Asura_Platform_Sound_Streaming.cpp
//
// Copyright (c) 2006 Rebellion. All rights reserved.
//
//////////////////////////////////////////////////////////////////////////////

/******************************************************************************/
/* Included Files                                                             */
/******************************************************************************/

#include "Core/Asura.h"

#include "Sound/Asura_StreamingBackgroundSound_System.h"

#ifndef ASURA_USE_AUDIO_SYSTEM

// Asura Platform-independent
#include "ChunkSystem/Asura_Chunk_Stream.h"
#include "Core/Asura_List.h"
#include "Core/Asura_String.h"
#include "Core/Asura_Timers.h"
#include "Core/Asura_LocalisedText.h"
#include "Core/Memory/Asura_Mem.h"
#include "Debugging/Asura_Assert.h"
#include "FileAccess/Asura_FileAccess.h"
#include "Sound/Asura_Sound.h"

// Asura Xbox 360
#include "Sound/Asura_Xbox360_Sound.h"

// External
#include <xact.h>
#include <Xtl.h>

/******************************************************************************/
/* Constants                                                                  */
/******************************************************************************/

#if !defined ASURA_XBOX360_SOUND_MAX_STREAMS
	#define ASURA_XBOX360_SOUND_MAX_STREAMS 4
#endif

const u_int uASURA_MAX_RESIDENT_WAVEBANKS = 5;

/******************************************************************************/
/* Global Variables 1                                                         */
/******************************************************************************/

static IXACTEngine* g_pxXACTEngine = NULL;

static float g_fWaveVolumeCoefficients[36];	// Maximum 6 channels x 6 channels

/******************************************************************************/
/* Classes                                                                    */
/******************************************************************************/

/******************************************************************************/
/* Asura_XACT_Wave & Asura_XACT_WaveBank                                      */
/******************************************************************************/

class Asura_XACT_Wave
{
public:
	
	Asura_Hash_ID	m_uNameHash;			//Hash of the wav friendly name
	WAVEBANKENTRY	m_xWaveBankEntry;		//Entry data from the header
	int				m_iLoadedIndex;			//Loaded sound index from the sound system (only used for non-traditional streaming banks)

	Asura_XACT_Wave()
	{
		m_uNameHash = 0;
		m_iLoadedIndex = -1;
		Asura_Mem::Set(&m_xWaveBankEntry,0,sizeof(WAVEBANKENTRY));
	};
};


class Asura_XACT_WaveBank
{
public:

	Asura_XACT_WaveBank				();
	~Asura_XACT_WaveBank			();

	HRESULT				Create				(const XACT_STREAMING_PARAMETERS* pxParameters);
	HRESULT				Prepare				(XACTINDEX nWaveIndex, DWORD dwFlags, DWORD dwPlayOffset, XACTLOOPCOUNT nLoopCount, IXACTWave **ppWave);
	bool				Prepared			();
	Asura_XACT_Wave*	AddWave				();
	void				RemoveWave			(Asura_XACT_Wave* pWave);
	Asura_XACT_Wave*	GetWaveByIndex		(u_int uIndex);
	u_int				GetNumWaves			();
	
	void				SetSystemStreamed	(bool bSystemStreamed)	{ m_bIsSystemStreamed = bSystemStreamed; }
	bool				IsSystemStreamed	()						{ return m_bIsSystemStreamed; }

	void				SetFileHandle		(HANDLE uFileHandle)	{ m_uFileHandle = uFileHandle; }
	HANDLE				GetFileHandle		() const				{ return m_uFileHandle; }

private:

	IXACTWaveBank*				m_pxInternalWaveBank;	//XACT data
	Asura_List<Asura_XACT_Wave>	m_xWaves;				//All wavs in this bank
	HANDLE						m_uFileHandle;			//Bank file
	bool						m_bIsSystemStreamed;	//Is this a 'traditional' streaming bank (i.e. Asura_Sound_System knows it has streaming wavs)
};


Asura_XACT_WaveBank::Asura_XACT_WaveBank()
{
	m_pxInternalWaveBank = NULL;
	m_uFileHandle = INVALID_HANDLE_VALUE;
	m_bIsSystemStreamed = true;
}


Asura_XACT_WaveBank::~Asura_XACT_WaveBank()
{
	if(m_pxInternalWaveBank)
	{
		m_pxInternalWaveBank->Destroy();
		m_pxInternalWaveBank = NULL;
	}
	if(m_uFileHandle != INVALID_HANDLE_VALUE)
	{
		CloseHandle(m_uFileHandle);
	}
	Asura_XACT_Wave* pWave;
	while(m_xWaves.GetNumEntries())
	{
		pWave = m_xWaves.GetEntryByIndex(0);
		m_xWaves.RemoveFromList(pWave);
		delete pWave;
	}
}


Asura_XACT_Wave* Asura_XACT_WaveBank::AddWave()
{
	Asura_XACT_Wave* pxNewWav = new Asura_XACT_Wave();
	if(pxNewWav)
	{
		m_xWaves.AddToList(pxNewWav);
		return pxNewWav;
	}
	return 0;
}

Asura_XACT_Wave* Asura_XACT_WaveBank::GetWaveByIndex(u_int uIndex)
{
	return m_xWaves.GetEntryByIndex(uIndex);
}

u_int Asura_XACT_WaveBank::GetNumWaves ()
{
	return m_xWaves.GetNumEntries();
}

void Asura_XACT_WaveBank::RemoveWave (Asura_XACT_Wave* pWave)
{
	m_xWaves.RemoveFromList(pWave);
	delete pWave;
}

HRESULT	Asura_XACT_WaveBank::Prepare (XACTINDEX nWaveIndex, DWORD dwFlags, DWORD dwPlayOffset, XACTLOOPCOUNT nLoopCount, IXACTWave **ppWave)
{
	HRESULT uRet = m_pxInternalWaveBank->Prepare(nWaveIndex, dwFlags, dwPlayOffset, nLoopCount, ppWave);
	return uRet;
}

HRESULT Asura_XACT_WaveBank::Create (const XACT_STREAMING_PARAMETERS* pxParameters)
{
	//Create the wavebank, thread safe
	
	HRESULT uRet = E_FAIL;
	if(g_pxXACTEngine)
	{
		uRet = g_pxXACTEngine->CreateStreamingWaveBank( pxParameters, &m_pxInternalWaveBank);
		if(!m_pxInternalWaveBank)
		{
			uRet = E_FAIL;
		}
	}
	
	return uRet;
}

bool Asura_XACT_WaveBank::Prepared ()
{
	//Check if this wavebank is prepared, thread safe
	bool bRet = false;
		
	if(!m_pxInternalWaveBank)
	{
		bRet = true; //as prepared as we are ever going to be
	}
	else
	{
		DWORD uState;
		HRESULT uCheck = m_pxInternalWaveBank->GetState( &uState );
		if(SUCCEEDED(uCheck) && (uState == XACT_WAVEBANKSTATE_PREPARED))
		{
			bRet = true;
		}
	}
		
	return bRet;
}

/******************************************************************************/
/* Stream Class                                                               */
/******************************************************************************/

enum ASURA_STREAM_QUEUE_ACTIONS
{
	ASURA_STREAM_QUEUE_ACTION_PLAY,
	ASURA_STREAM_QUEUE_ACTION_PAUSE,
	ASURA_STREAM_QUEUE_ACTION_RESUME,
	ASURA_STREAM_QUEUE_ACTION_STOP,
	ASURA_STREAM_QUEUE_ACTION_VOLUME,
	ASURA_STREAM_QUEUE_ACTION_PITCH,
	ASURA_STREAM_QUEUE_ACTION_VOLUME_REFRESH,

	ASURA_STREAM_QUEUE_ACTION_NUM_ITEMS
};

class Stream_Queued_Action
{
public:

	DWORD uIdentifier;
	union Stream_Queued_Action_Data
	{
		float fVolume;

		float fPitch;
		bool bLoop;
	} xData;
};

class Stream
{
public:

	Stream();
	~Stream();

	DWORD GetCurrentState		() const;
	void Reset					();
	void Update					();
	void SetData				(Asura_XACT_WaveBank* pxWaveBank
								, DWORD	uFlags
								, XACTINDEX xWaveIndex
								, IXACTWave* &pxWaveData
								, int iActiveSoundIndex

								, float fVolume
								, float fDuration);
	
	void Play					(bool bLoop);
	void Stop					();
	void Pause					();
	void Resume					();
	void SetVolume				(float fVolume);
	void SetPitch				(float fPitch);


	float GetCurrentPosition	() const;	
	void SetCurrentPosition		( const float fPosition );

	float GetDuration			() const;
	void  SetDuration			( float fDuration)	{	m_fDuration = fDuration;	}

	bool IsActiveSoundNumber	(Asura_Handle iNum);
	bool IsInUse				();
	bool IsFromWaveBank			(Asura_XACT_WaveBank* pxBank);
	bool IsPlaying				();

private:

	IXACTWave*							m_pxWave;
	Asura_XACT_WaveBank*				m_pxWaveBank;
	float								m_fDuration;
	float								m_fCurrentPosition;	
	DWORD								m_uFlags;
	XACTINDEX							m_sWaveIndex;
	Asura_Handle						m_iActiveSoundIndex; //only used for level streaming sounds
	Asura_List<Stream_Queued_Action>	m_xActionQueue;
	bool								m_bLooping;

	float								m_fVolume;
};


Stream::Stream()
: m_pxWave				(NULL)
, m_pxWaveBank			(NULL)
, m_fDuration			(0.1f)
, m_fCurrentPosition	(0.0f)
, m_uFlags				(0)
, m_sWaveIndex			(XACTINDEX_INVALID)
, m_iActiveSoundIndex	(ASURA_INVALID_HANDLE)
, m_bLooping			(false)

, m_fVolume				(1.0f)
{
}


Stream::~Stream()
{
	//Clear down our action list
	while(m_xActionQueue.GetNumEntries() > 0)
	{
		Stream_Queued_Action* pxAction = m_xActionQueue.PopHeadObject();
		delete pxAction;
	}
}


bool Stream::IsFromWaveBank			(Asura_XACT_WaveBank* pxBank)
{
	return (pxBank == m_pxWaveBank);
}


bool Stream::IsInUse ()
{
	bool bRet = false;
	
	if(m_pxWave)
	{
		DWORD uState;
		HRESULT xResult =  m_pxWave->GetState( &uState );
		if(SUCCEEDED(xResult) && !(uState & XACT_STATE_STOPPED) )
		{
			//not stopped, so in use
			bRet = true;
		}
	}
	return bRet;
}


bool Stream::IsActiveSoundNumber	(Asura_Handle iNum)
{
	return (m_iActiveSoundIndex == iNum);
}


void Stream::Play (bool bLoop)
{
	Stream_Queued_Action* pxAction = new Stream_Queued_Action;
	
	pxAction->uIdentifier = ASURA_STREAM_QUEUE_ACTION_PLAY;
	pxAction->xData.bLoop = bLoop;

	m_xActionQueue.AddToList(pxAction);
}


void Stream::SetVolume (float fVolume)
{
	Stream_Queued_Action* pxAction = new Stream_Queued_Action;
	
	pxAction->uIdentifier = ASURA_STREAM_QUEUE_ACTION_VOLUME;
	pxAction->xData.fVolume	= fVolume;

	m_xActionQueue.AddToList(pxAction);
}


void Stream::Stop ()
{
	Stream_Queued_Action* pxAction = new Stream_Queued_Action;
	pxAction->uIdentifier = ASURA_STREAM_QUEUE_ACTION_STOP;
	m_xActionQueue.AddToList(pxAction);
}


void Stream::Pause ()
{
	Stream_Queued_Action* pxAction = new Stream_Queued_Action;
	pxAction->uIdentifier = ASURA_STREAM_QUEUE_ACTION_PAUSE;
	m_xActionQueue.AddToList(pxAction);
}


void Stream::Resume ()
{
	Stream_Queued_Action* pxAction = new Stream_Queued_Action;
	pxAction->uIdentifier = ASURA_STREAM_QUEUE_ACTION_RESUME;
	m_xActionQueue.AddToList(pxAction);
}


void Stream::SetPitch (float fPitch)
{
	Stream_Queued_Action* pxAction = new Stream_Queued_Action;

	pxAction->uIdentifier = ASURA_STREAM_QUEUE_ACTION_PITCH;
	pxAction->xData.fPitch = fPitch;

	m_xActionQueue.AddToList(pxAction);
}





float Stream::GetCurrentPosition	() const
{ 
	return m_fCurrentPosition; 
}


void Stream::SetCurrentPosition		( const float fPosition )
{ 
	m_fCurrentPosition = fPosition; 
}


float Stream::GetDuration			() const
{
	return m_fDuration;
}


DWORD Stream::GetCurrentState() const
{
	//Get the current state
	DWORD uState = XACT_STATE_PREPAREFAILED;

	HRESULT xResult = E_FAIL;
	if(m_pxWave)
	{
		xResult =  m_pxWave->GetState( &uState );
		if( FAILED( xResult ) )
		{
			uState = XACT_STATE_PREPAREFAILED;
		}
	}
	
	return uState;
}


void Stream::Reset()
{
	if( m_pxWave )
	{
		m_pxWave->Destroy();
		m_pxWave = NULL;		
	}

	m_pxWaveBank = NULL;
	m_fDuration = 0.1f;
	m_fCurrentPosition = 0.0f;
	m_uFlags = 0;
	m_sWaveIndex = XACTINDEX_INVALID;
	m_iActiveSoundIndex = ASURA_INVALID_HANDLE;

	//Clear down our action list
	while(m_xActionQueue.GetNumEntries() > 0)
	{
		Stream_Queued_Action* pxAction = m_xActionQueue.PopHeadObject();
		delete pxAction;
	}
}

bool Stream::IsPlaying ()
{
	//Are we playing, paused, or queued to play
	DWORD uState = GetCurrentState();
	if((uState & XACT_STATE_PLAYING) || (uState & XACT_STATE_PAUSED))
	{
		return true;
	}
	//check if we are scheduled to play
	for(int i=0;i<m_xActionQueue.GetNumEntries();++i)
	{
		Stream_Queued_Action* pxAction = m_xActionQueue.GetEntryByIndex (i);
		if(pxAction->uIdentifier == ASURA_STREAM_QUEUE_ACTION_PLAY)
		{
			return true;
		}
	}
	return false;
}


void Stream::Update()
{
	//Update the stream depending on the state
	if( !m_pxWave )
	{
		return;
	}
	
	const DWORD uState = GetCurrentState();
	
	if((uState & XACT_STATE_CREATED) || (uState & XACT_STATE_PREPARING))
	{
		//Cant do anything until we are prepared
		return;
	}

	if( (uState & XACT_STATE_PLAYING) && !(uState & XACT_STATE_PAUSED) )
	{
		// The XACT API doesn't seem to have a way of querying the current playback position,
		// so we need to keep track of it ourselves
		m_fCurrentPosition += Asura_Timers::GetFrameTime();

		if((m_fCurrentPosition >= m_fDuration) && !m_bLooping)
		{
			//This is rather naf but its very awkward to manually loop an IXACTWave
			//so we mark all streams to loop indefinately and manually stop the unlooped ones
			m_pxWave->Stop( XACT_FLAG_STOP_IMMEDIATE );
		}
	}	

	//Check what actions are pending
	while(m_xActionQueue.GetNumEntries() > 0)
	{
		Stream_Queued_Action* pxAction = m_xActionQueue.PopTailObject();
		if(pxAction)
		{
			switch(pxAction->uIdentifier)
			{
				case ASURA_STREAM_QUEUE_ACTION_PLAY:
				{
					m_bLooping = pxAction->xData.bLoop;
					
					//Set the volumes
					XACT_WAVE_INSTANCE_PROPERTIES xProperties;
					HRESULT uPropsResult = m_pxWave->GetProperties( &xProperties );
					if( SUCCEEDED( uPropsResult ) )
					{
						UINT32 uChannels = xProperties.properties.format.nChannels;
						//Create an identity matrix (i.e. 1:1 speaker volume mapping)
						Asura_Mem::Set(g_fWaveVolumeCoefficients, 0, sizeof(float) * 36);
						
						for(u_int uSpeaker = 0; uSpeaker < uChannels; ++uSpeaker)
						{
							g_fWaveVolumeCoefficients[(uChannels * uSpeaker) + uSpeaker] = 1.0f;
						}
						m_pxWave->SetMatrixCoefficients(uChannels, 6, g_fWaveVolumeCoefficients);
					}

					//...and play
					const HRESULT xResult = m_pxWave->Play();
					Asura_Assert( SUCCEEDED( xResult ), "Failed to play streaming sound");
					if(SUCCEEDED( xResult ))
					{
						HRESULT uPropsResult = m_pxWave->GetProperties( &xProperties );
						if( SUCCEEDED( uPropsResult ) )
						{
							// Might as well store the duration now, to avoid doing the casts and division each frame		
							const float fDurationInSamples	= static_cast< float >( xProperties.properties.durationInSamples );
							const float fSamplesPerSec		= static_cast< float >( xProperties.properties.format.nSamplesPerSec );

							if(fSamplesPerSec > 0)
							{
								m_fDuration = fDurationInSamples / fSamplesPerSec; 
							}
						}
						else
						{
							Asura_Assertf( false, ("Failed to get streaming sound properties.") );
							// Don't want to use 0 here in case of divide-by-zero problems
							m_fDuration = 0.1f;
						}
					}
				}
				break;

				case ASURA_STREAM_QUEUE_ACTION_PAUSE:
				{
					#if defined ASURA_ASSERT
					const HRESULT xResult = m_pxWave->Pause(true);
					Asura_Assert( SUCCEEDED( xResult ), "Failed to pause streaming sound");
					#else
					m_pxWave->Pause(true);
					#endif
				}
				break;

				case ASURA_STREAM_QUEUE_ACTION_RESUME:
				{
					#if defined ASURA_ASSERT
					const HRESULT xResult = m_pxWave->Pause(false);
					Asura_Assert( SUCCEEDED( xResult ), "Failed to resume streaming sound");
					#else
					m_pxWave->Pause(false);
					#endif
				}
				break;

				case ASURA_STREAM_QUEUE_ACTION_STOP:
				{
					#if defined ASURA_ASSERT
					const HRESULT xResult = m_pxWave->Stop( XACT_FLAG_STOP_IMMEDIATE );
					Asura_Assert( SUCCEEDED( xResult ), "Failed to stop streaming sound");
					#else
					m_pxWave->Stop( XACT_FLAG_STOP_IMMEDIATE );
					#endif
				}
				break;





				case ASURA_STREAM_QUEUE_ACTION_PITCH:
				{
					const XAUDIOPITCH fOctaveAdjustment = XAudioSampleRateToPitch( static_cast< XAUDIOSAMPLERATE >( pxAction->xData.fPitch * 48000.0f ), 48000 );
					#if defined ASURA_ASSERT
					const HRESULT xResult = m_pxWave->SetPitch(static_cast<XACTPITCH>(fOctaveAdjustment));
					Asura_Assert( SUCCEEDED( xResult ), "Failed to set streaming sound pitch");
					#else
					m_pxWave->SetPitch(static_cast<XACTPITCH>(fOctaveAdjustment));
					#endif
				}
				break;

				default:
				{
					Asura_Assert(false,"Unexpected item on stream action queue");
				}
			}
			delete pxAction;
		}
	}
}


void Stream::SetData (Asura_XACT_WaveBank* pxWaveBank, DWORD uFlags, XACTINDEX xWaveIndex, IXACTWave* &pxWaveData, int iActiveSoundIndex/*, Asura_Sound_System::SoundGroup eGroup*/, float fVolume, float fDuration)
{
	//Set this local data
	m_pxWave			= pxWaveData;
	m_pxWaveBank		= pxWaveBank;
	m_uFlags			= uFlags;
	m_sWaveIndex		= xWaveIndex;
	m_iActiveSoundIndex = iActiveSoundIndex;

	m_fVolume			= fVolume;
	m_fDuration			= fDuration;
}

/******************************************************************************/
/* Global Variables 2                                                         */
/******************************************************************************/

static Asura_XACT_WaveBank* g_apxWaveBanks[uASURA_MAX_RESIDENT_WAVEBANKS];

static Stream g_axStreams[ ASURA_XBOX360_SOUND_MAX_STREAMS ];

/******************************************************************************/
/* Local Functions                                                            */
/******************************************************************************/

static Asura_XACT_WaveBank* InternalCreateWaveBank ()
{
	u_int uFree = 0;
	while(g_apxWaveBanks[uFree])
	{
		++uFree;
		if(uFree == uASURA_MAX_RESIDENT_WAVEBANKS)
		{
			return 0;
		}
	}
	g_apxWaveBanks[uFree] = new Asura_XACT_WaveBank();
	return g_apxWaveBanks[uFree];
}

/******************************************************************************/
/* Asura_Xbox360_XACT Static Class Functions                                  */
/******************************************************************************/

////////////////////////////////////////////////////////////////////////////////
//
// Asura_Xbox360_XACT::Initialise()
//
// Initialises XACT for streaming playback, and loads the appropriate banks
//
// Parameters:		None
// Return value:	None
//
////////////////////////////////////////////////////////////////////////////////

void Asura_Xbox360_XACT::Initialise()
{
	u_int u;
	for(u = 0; u < uASURA_MAX_RESIDENT_WAVEBANKS; ++u)
	{
		g_apxWaveBanks[u] = NULL;
	}
	
	const HRESULT xResult = XACTCreateEngine( 0, &g_pxXACTEngine );
	if( FAILED( xResult ) || !g_pxXACTEngine )
	{
		Asura_Assert( false, "Failed to initialise XACT." );
		return;
	}

	XACT_RUNTIME_PARAMETERS xRuntimeParameters;
	Asura_Mem::Set(&xRuntimeParameters,0,sizeof(XACT_RUNTIME_PARAMETERS));

	xRuntimeParameters.lookAheadTime = XACT_ENGINE_LOOKAHEAD_DEFAULT;
	
	g_pxXACTEngine->Initialize( &xRuntimeParameters );

	XACT_STREAMING_PARAMETERS xParameters;
	xParameters.offset = 0;
	xParameters.flags = 0;
	xParameters.packetSize = 16;
	
	Asura_String xFilename;
	Asura_XACT_WaveBank* pxWaveBank = 0;
	Asura_XACT_WaveBank* pxCommonWaveBank = 0;
	Asura_XACT_WaveBank* pxLocalWaveBank = 0;

	//Loop over twice, once for common, once for localised
	for(int i=0;i<2;++i)
	{
		switch(i)
		{
			case 0:	
			{
				xFilename = "game:\\Sounds\\Common.xwb";

				pxWaveBank = InternalCreateWaveBank();
				pxCommonWaveBank = pxWaveBank;
			}
			break;
			
			case 1:
			{
				xFilename = "game:\\Sounds\\";
				xFilename += Asura_LocalisedText::GetNameForCurLang();
				xFilename += ".xwb";

				pxWaveBank = InternalCreateWaveBank();
				pxLocalWaveBank = pxWaveBank;
			}
			break;
		}
		
		if(!pxWaveBank)
		{
			Asura_Assert(false, "Failed to create internal wavebank");
			continue;
		}
		// Must use FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING to open the files, required by XACT
		HANDLE uFile = CreateFile( xFilename.GetString(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING, NULL );
		if( uFile == INVALID_HANDLE_VALUE )
		{
			// (AAM - 21.11.2008) Removing this assert for now. All 360 projects are using the new
			// audio system, but this system runs alongside and we don't want it complaining about
			// missing wavebanks. The sound system will be removed in the future

			//Asura_Assertf(false, ("Failed to open file %s",xFilename.GetString()));
			continue;
		}
		xParameters.file = uFile;
		
		const HRESULT xResult =	pxWaveBank->Create(&xParameters);
		if(SUCCEEDED( xResult ))
		{
			pxWaveBank->SetFileHandle(uFile);
			//Load the wavebank header details (MS Library bug workaround)
			if(!BuildWavDetails(xFilename.GetString(),0,pxWaveBank))
			{
				Asura_Assertf(false, ("failed to  get friendly names from wavebank %s",xFilename.GetString()));
			}
		}
		else
		{
			CloseHandle(uFile);
			Asura_Assertf(false ,("CreateStreamingWaveBank failed for %s", xFilename.GetString()));
		}
	}
	
	// JAT 19/10/07 - loop around waiting for the banks to be ready to be used!
	
	bool bCommonReady = false;
	bool bLocalisedReady = false;
	
	const u_int uASURA_MAX_INIT_WAVEBANK_COUNTS = 10;
	const u_int uASURA_XACT_DOWORK_DELAY = 50;
	for(u_int u = 0; u < uASURA_MAX_INIT_WAVEBANK_COUNTS; ++u)
	{
		if ( ( bCommonReady == false ) && pxCommonWaveBank->Prepared() )
		{
			bCommonReady = true;
		}
		if ( ( bLocalisedReady == false ) && pxLocalWaveBank->Prepared() )
		{
			bLocalisedReady = true;
		}
		// Update the XACT engine hoping it will get things done
		g_pxXACTEngine->DoWork();
		Sleep(uASURA_XACT_DOWORK_DELAY);

		// Stop when both sound banks are ready
		if ( ( bCommonReady == true ) && ( bLocalisedReady == true ) )
		{
			break;
		}
		Asura_Assert( u < uASURA_MAX_INIT_WAVEBANK_COUNTS - 1, "Failed to init the common and/or localised wavebanks" );
	}
	//Ugly hack, there always seems to be a pause the first time you prepare a wave
	//so do it here to make sure we dont glitch in game
	if(u < uASURA_MAX_INIT_WAVEBANK_COUNTS)
	{
		IXACTWave* pxWave = 0;
		if(bCommonReady)
		{
			for(u_int u = 0; u < pxCommonWaveBank->GetNumWaves(); ++u )
			{
				pxCommonWaveBank->Prepare (static_cast<XACTINDEX>(u),0,0,0,&pxWave);
				if(pxWave)
				{
					pxWave->Destroy();
					pxWave = 0;
				}
			}
		}
		if(bLocalisedReady)
		{
			for(u_int u = 0; u < pxLocalWaveBank->GetNumWaves(); ++u )
			{
				pxLocalWaveBank->Prepare (static_cast<XACTINDEX>(u),0,0,0,&pxWave);
				if(pxWave)
				{
					pxWave->Destroy();
					pxWave = 0;
				}
			}
		}
	}
}


////////////////////////////////////////////////////////////////////////////////
//
// Asura_Xbox360_XACT::Shutdown()
//
// Shuts XACT down, and unloads the appropriate banks
//
// Parameters:		None
// Return value:	None
//
////////////////////////////////////////////////////////////////////////////////

void Asura_Xbox360_XACT::Shutdown()
{
	//common
	for(u_int u=0;u<uASURA_MAX_RESIDENT_WAVEBANKS;++u)
	{
		delete g_apxWaveBanks[u];
	}
	
	//XACT engine
	if( g_pxXACTEngine )
	{
		g_pxXACTEngine->ShutDown();
		g_pxXACTEngine->Release();
		g_pxXACTEngine = NULL;
	}
}

////////////////////////////////////////////////////////////////////////////////
//
// Asura_Xbox360_XACT::Update()
//
// Once-per-frame update of the XACT system
//
// Parameters:		None
// Return value:	None
//
////////////////////////////////////////////////////////////////////////////////

void Asura_Xbox360_XACT::Update()
{
	if( g_pxXACTEngine )
	{
		g_pxXACTEngine->DoWork();
	}

	u_int u;
	for( u = 0; u < ASURA_XBOX360_SOUND_MAX_STREAMS; ++u )
	{
		g_axStreams[u].Update();
	}
}


bool Asura_Xbox360_XACT::BuildWavDetails(const char* szFilename, int iFileOffset, Asura_XACT_WaveBank* pxWaveBank)
{
	DWORD uRead;

	HANDLE xFileHandle = CreateFile( szFilename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL );	

	if(xFileHandle!=INVALID_HANDLE_VALUE)
	{
		if(pxWaveBank)
		{
			if(SetFilePointer(xFileHandle,iFileOffset,NULL,FILE_BEGIN) != INVALID_SET_FILE_POINTER)
			{
				WAVEBANKHEADER xHeader;
				//Get the header
				if(ReadFile( xFileHandle, &xHeader, sizeof(WAVEBANKHEADER), &uRead, NULL))
				{
					WAVEBANKREGION xRegion;
					WAVEBANKDATA xBankdata;
					xRegion = xHeader.Segments[WAVEBANK_SEGIDX_BANKDATA];
					if(xRegion.dwLength != 0)
					{
						//Get the wave bank data segment
						if(SetFilePointer(xFileHandle,xRegion.dwOffset + iFileOffset,NULL,FILE_BEGIN) != INVALID_SET_FILE_POINTER)
						{
							if(ReadFile( xFileHandle, &xBankdata, sizeof(WAVEBANKDATA), &uRead, NULL))
							{						
								//Get the entry names segment							
								for(XACTINDEX x=0;x<static_cast<XACTINDEX>(xBankdata.dwEntryCount);++x)
								{
									pxWaveBank->AddWave();
								}
								
								xRegion = xHeader.Segments[WAVEBANK_SEGIDX_ENTRYNAMES];
								if(xRegion.dwLength != 0)
								{
									if(SetFilePointer(xFileHandle,xRegion.dwOffset + iFileOffset,NULL,FILE_BEGIN) != INVALID_SET_FILE_POINTER)
									{
										char szBuffer[WAVEBANK_ENTRYNAME_LENGTH];
										for(XACTINDEX x=0;x<static_cast<XACTINDEX>(xBankdata.dwEntryCount);++x)
										{
											if(ReadFile( xFileHandle, szBuffer, WAVEBANK_ENTRYNAME_LENGTH, &uRead, NULL))
											{
												//Got the name hash
												Asura_Assert(szBuffer[0],"Friendly name is empty, did you export friendly names in the wavebank?");
												Asura_XACT_Wave* pxWave = pxWaveBank->GetWaveByIndex(x);
												if(pxWave)
												{
													pxWave->m_uNameHash = Asura_GetHashID(szBuffer);

													//We get an index from the sound system at this point if we need to
													if(!pxWaveBank->IsSystemStreamed())
													{
														int iSoundID = atoi(szBuffer);
														int iLoadedIndex = Asura_Sound_System::SoundLoadedByPlatformCode(ASURA_SID_DYNAMIC, iSoundID);
														pxWave->m_iLoadedIndex = iLoadedIndex;
													}
												}
											}
										}							
									}
								}
								xRegion = xHeader.Segments[WAVEBANK_SEGIDX_ENTRYMETADATA];
								if(xRegion.dwLength != 0)
								{
									if(SetFilePointer(xFileHandle,xRegion.dwOffset + iFileOffset,NULL,FILE_BEGIN) != INVALID_SET_FILE_POINTER)
									{
										for(XACTINDEX x=0;x<static_cast<XACTINDEX>(xBankdata.dwEntryCount);++x)
										{
											Asura_XACT_Wave* pxWave = pxWaveBank->GetWaveByIndex(x);
											if(pxWave)
											{
												if(!ReadFile( xFileHandle, &pxWave->m_xWaveBankEntry, sizeof(WAVEBANKENTRY), &uRead, NULL))
												{
													Asura_Assert(false, "Failed to read wave bank entry data");
												}
											}
										}
									}
								}
							}
						}
					}
				}
			}
		}
		CloseHandle(xFileHandle);
	}
	return true;
}




/******************************************************************************/
/* Asura_Sound_System Static Class Functions                                  */
/******************************************************************************/

////////////////////////////////////////////////////////////////////////////////
//
// Asura_Sound_System::Platform_CreateStreamingSound()
//
// Prepares a streaming sound for playback
//
// Parameters:		iStreamHandle - Handle to assign the new sound stream
//					szFilename - Filename to play
//					eSoundGroup - Group in which to place the sound during playback
//
// Return value:	true iff successful
//
////////////////////////////////////////////////////////////////////////////////

bool Asura_Sound_System::Platform_CreateStreamingSound( Asura_Handle iStreamHandle, const char* szFilename)
{
	if( iStreamHandle < 0 || iStreamHandle >= ASURA_XBOX360_SOUND_MAX_STREAMS )
	{
		Asura_Assertf( false, ("Invalid stream handle (%d) passed to Asura_Sound_System::Platform_CreateStreamingSound().", iStreamHandle) );
		return false;
	}
	
	char szWaveName[64];

	{
		// PAZ - work with either slash
		const char* szToCopy = szFilename;		// default, if there are no slashes
		for( int i = 0; szFilename[ i ] != 0; i++ )
		{
			if( ( szFilename[ i ] == '\\' ) || ( szFilename[ i ] == '/' ) )
			{
				szToCopy = szFilename + i + 1;
			}
		}
		Asura_PrintfToBfr( szWaveName, "%s", 64, true, szToCopy );

		char* const szFinalDot = strrchr( szWaveName, '.' );
		if( szFinalDot )
		{
			*szFinalDot = '\0';
		}
	}

	XACTINDEX xWaveIndex = XACTINDEX_INVALID;

	Asura_XACT_WaveBank* pxWaveBank = 0;
	Asura_XACT_Wave* pWave = 0;
	u_int u,v;
	Asura_Hash_ID uHash = Asura_GetHashID(szWaveName);

	//loop over all banks, but only check the system streaming ones

	for(u=0;u<uASURA_MAX_RESIDENT_WAVEBANKS;++u)
	{
		pxWaveBank = g_apxWaveBanks[u];
		if(pxWaveBank && pxWaveBank->IsSystemStreamed())
		{
			for(v=0;v<pxWaveBank->GetNumWaves();++v)
			{
				pWave = pxWaveBank->GetWaveByIndex(v);
				if(pWave->m_uNameHash == uHash)
				{
					xWaveIndex = static_cast<XACTINDEX>(v);
					break;
				}
			}
		}
		if(xWaveIndex != XACTINDEX_INVALID)
		{
			//we are done
			break;
		}
	}

	if( pxWaveBank && (xWaveIndex != XACTINDEX_INVALID) )
	{
		const DWORD uFlags = 0;

		//Prepare the stream
		IXACTWave* pxWave;		
		const HRESULT xResult = pxWaveBank->Prepare(xWaveIndex,uFlags,0,XACTLOOPCOUNT_INFINITE,&pxWave);
		if(SUCCEEDED(xResult))
		{
			DWORD uState;
			if(SUCCEEDED(pxWave->GetState(&uState)))
			{
				if( uState & XACT_STATE_PREPAREFAILED )
				{
					Asura_Assert(false,"Failed to prepare streaming wave");
					return false;
				}
				else
				{
					float fDuration = 0.0f;

					XACT_WAVE_INSTANCE_PROPERTIES xProperties;
					const HRESULT xPropsResult = pxWave->GetProperties( &xProperties );
					if( SUCCEEDED( xPropsResult ) )
					{
						const float fDurationInSamples	= static_cast< float >( xProperties.properties.durationInSamples );
						const float fSamplesPerSec		= static_cast< float >( xProperties.properties.format.nSamplesPerSec );

						if(fSamplesPerSec > 0)
						{
							fDuration = fDurationInSamples / fSamplesPerSec; 
						}
					}
					else
					{
						Asura_Assertf( false, ("Failed to get streaming sound properties.") );
						// Don't want to use 0 here in case of divide-by-zero problems
						fDuration = 0.1f;
					}

					g_axStreams[iStreamHandle].SetData(pxWaveBank, uFlags, xWaveIndex, pxWave, ASURA_INVALID_HANDLE, 1.0f, fDuration);
					return true;
				}
			}
		}
	}

	// Don't assert here, the caller will handle it
	return false;
}

////////////////////////////////////////////////////////////////////////////////
//
// Asura_Sound_System::Platform_PlayStreamingSound()
//
// Begins playback of a given streaming sound
//
// Parameters:		iStreamHandle - Handle of streaming sound
//					bLooping - true iff the sound should loop until stopped
//					bPreload - true iff the sound has been preloaded
//
// Return value:	None
//
////////////////////////////////////////////////////////////////////////////////

void Asura_Sound_System::Platform_PlayStreamingSound( Asura_Handle iStreamHandle, bool bLooping, bool bPreload )
{
	if( iStreamHandle < 0 || iStreamHandle >= ASURA_XBOX360_SOUND_MAX_STREAMS )
	{
		Asura_Assertf( false, ("Invalid stream handle (%d) passed to Asura_Sound_System::Platform_PlayStreamingSound().", iStreamHandle) );
		return;
	}

	//TY 22/10/07 - We take preload to mean 'play immediately', otherwise we only play once the file has finished loading
	if(bPreload)
	{
		while( true )
		{
			g_pxXACTEngine->DoWork();

			const DWORD uState = g_axStreams[ iStreamHandle ].GetCurrentState();
			if( uState & XACT_STATE_PREPAREFAILED )
			{
				Asura_Assertf( false, ("Failed to prepare sound stream %d for playback.", iStreamHandle) );
				return;
			}

			if( uState & XACT_STATE_PREPARED ) break;
		}
	}	
	g_axStreams[ iStreamHandle ].Play(bLooping);
}

////////////////////////////////////////////////////////////////////////////////
//
// Asura_Sound_System::Platform_StopStreamingSound()
//
// Stops a streaming sound
//
// Parameters:		iStreamHandle - Handle of stream to stop
// Return value:	None
//
////////////////////////////////////////////////////////////////////////////////

void Asura_Sound_System::Platform_StopStreamingSound( Asura_Handle iStreamHandle )
{
	if( iStreamHandle < 0 || iStreamHandle >= ASURA_XBOX360_SOUND_MAX_STREAMS )
	{
		Asura_Assertf( false, ("Invalid stream handle (%d) passed to Asura_Sound_System::Platform_StopStreamingSound().", iStreamHandle) );
		return;
	}

	g_axStreams[ iStreamHandle ].Stop();
}

////////////////////////////////////////////////////////////////////////////////
//
// Asura_Sound_System::Platform_PauseStreamingSound()
//
// Pauses a streaming sound
//
// Parameters:		iStreamHandle - Handle of streaming sound to pause
// Return value:	None
//
////////////////////////////////////////////////////////////////////////////////

void Asura_Sound_System::Platform_PauseStreamingSound( Asura_Handle iStreamHandle )
{
	if( iStreamHandle < 0 || iStreamHandle >= ASURA_XBOX360_SOUND_MAX_STREAMS )
	{
		Asura_Assertf( false, ("Invalid stream handle (%d) passed to Asura_Sound_System::Platform_PauseStreamingSound().", iStreamHandle) );
		return;
	}

	g_axStreams[ iStreamHandle ].Pause();
}

////////////////////////////////////////////////////////////////////////////////
//
// Asura_Sound_System::Platform_ResumeStreamingSound()
//
// Resumes a streaming sound
//
// Parameters:		iStreamHandle - Handle of streaming sound to resume
// Return value:	None
//
////////////////////////////////////////////////////////////////////////////////

void Asura_Sound_System::Platform_ResumeStreamingSound( Asura_Handle iStreamHandle )
{
	if( iStreamHandle < 0 || iStreamHandle >= ASURA_XBOX360_SOUND_MAX_STREAMS )
	{
		Asura_Assertf( false, ("Invalid stream handle (%d) passed to Asura_Sound_System::Platform_ResumeStreamingSound().", iStreamHandle) );
		return;
	}

	g_axStreams[ iStreamHandle ].Resume();
}

////////////////////////////////////////////////////////////////////////////////
//
// Asura_Sound_System::Platform_ReleaseStreamingSound()
//
// Releases a streaming sound
//
// Parameters:		None
// Return value:	None
//
////////////////////////////////////////////////////////////////////////////////

void Asura_Sound_System::Platform_ReleaseStreamingSound( Asura_Handle iStreamHandle )
{
	if( iStreamHandle < 0 || iStreamHandle >= ASURA_XBOX360_SOUND_MAX_STREAMS )
	{
		Asura_Assertf( false, ("Invalid stream handle (%d) passed to Asura_Sound_System::Platform_ReleaseStreamingSound().", iStreamHandle) );
		return;
	}

	g_axStreams[ iStreamHandle ].Reset();
}

////////////////////////////////////////////////////////////////////////////////
//
// Asura_Sound_System::Platform_StreamingSoundIsReady()
//
// Queries whether a streaming sound is ready for playback yet, after creation
//
// Parameters:		iStreamHandle - Handle of streaming sound to query
// Return value:	None
//
////////////////////////////////////////////////////////////////////////////////

bool Asura_Sound_System::Platform_StreamingSoundIsReady( Asura_Handle iStreamHandle )
{
	if( iStreamHandle < 0 || iStreamHandle >= ASURA_XBOX360_SOUND_MAX_STREAMS )
	{
		Asura_Assertf( false, ("Invalid stream handle (%d) passed to Asura_Sound_System::Platform_StreamingSoundIsReady().", iStreamHandle) );
		return false;
	}

	const DWORD uState = g_axStreams[ iStreamHandle ].GetCurrentState();
	return uState != XACT_STATE_CREATED && uState != XACT_STATE_PREPARING && uState != XACT_STATE_PREPAREFAILED;
}

////////////////////////////////////////////////////////////////////////////////
//
// Asura_Sound_System::Platform_StartPreloadedStreamingSound()
//
// Starts a preloaded streaming sound
//
// Parameters:		iStreamHandle
// Return value:	None
//
////////////////////////////////////////////////////////////////////////////////

void Asura_Sound_System::Platform_StartPreloadedStreamingSound( Asura_Handle iStreamHandle )
{
	// This doesn't seem to be used
}

////////////////////////////////////////////////////////////////////////////////
//
// Asura_Sound_System::Platform_SetStreamingSoundOutput()
//
// Sets the speaker output type for a streaming sound. No effect on 360
//
// Parameters:		iStreamHandle - Handle to stream to alter
//					eOutputType - Speaker type to use
//
// Return value:	None
//
////////////////////////////////////////////////////////////////////////////////

void Asura_Sound_System::Platform_SetStreamingSoundOutput( Asura_Handle iStreamHandle, SoundOutputType eOutputType )
{
}

////////////////////////////////////////////////////////////////////////////////
//
// Asura_Sound_System::Platform_StreamingSoundIsFinished()
//
// Queries whether a streaming sound has finished playing yet
//
// Parameters:		iStreamHandle - Stream handle to query
// Return value:	true iff the sound has finished
//
////////////////////////////////////////////////////////////////////////////////

bool Asura_Sound_System::Platform_StreamingSoundIsFinished( Asura_Handle iStreamHandle )
{ 
	if( iStreamHandle < 0 || iStreamHandle >= ASURA_XBOX360_SOUND_MAX_STREAMS )
	{
		Asura_Assertf( false, ("Invalid stream handle (%d) passed to Asura_Sound_System::Platform_StreamingSoundIsFinished().", iStreamHandle) );
		return true;
	}

	const DWORD uState = g_axStreams[ iStreamHandle ].GetCurrentState();
	return (uState & XACT_STATE_STOPPED) != 0;
}

////////////////////////////////////////////////////////////////////////////////
//
// Asura_Sound_System::Platform_GetStreamingSoundPosition()
//
// Determines the current position of a streaming sound
//
// Parameters:		iStreamHandle - Handle of stream to query
// Return value:	The current playback position
//
////////////////////////////////////////////////////////////////////////////////

float Asura_Sound_System::Platform_GetStreamingSoundPosition( Asura_Handle iStreamHandle )
{ 
	if( iStreamHandle < 0 || iStreamHandle >= ASURA_XBOX360_SOUND_MAX_STREAMS )
	{
		Asura_Assertf( false, ("Invalid stream handle (%d) passed to Asura_Sound_System::Platform_GetStreamingSoundPosition().", iStreamHandle) );
		return 0.0f;
	}

	return g_axStreams[ iStreamHandle ].GetCurrentPosition();
}

////////////////////////////////////////////////////////////////////////////////
//
// Asura_Xbox360_XACT_Initialise()
//
// Initialises XACT for streaming playback, and loads the appropriate banks
//
// Parameters:		None
// Return value:	None
//
////////////////////////////////////////////////////////////////////////////////

float Asura_Sound_System::Platform_GetStreamingSoundLength( Asura_Handle iStreamHandle )
{ 
	if( iStreamHandle < 0 || iStreamHandle >= ASURA_XBOX360_SOUND_MAX_STREAMS )
	{
		Asura_Assertf( false, ("Invalid stream handle (%d) passed to Asura_Sound_System::Platform_GetStreamingSoundLength().", iStreamHandle) );
		return 0.1f;
	}

	return g_axStreams[ iStreamHandle ].GetDuration();
}

////////////////////////////////////////////////////////////////////////////////
//
// Asura_Sound_System::Platform_GetMaxStreamingSounds()
//
// Queries the maximum number of simultaneously streaming sounds available
//
// Parameters:		None
// Return value:	The maximum number of streams
//
////////////////////////////////////////////////////////////////////////////////

int Asura_Sound_System::Platform_GetMaxStreamingSounds()
{ 
	return ASURA_XBOX360_SOUND_MAX_STREAMS;
}

////////////////////////////////////////////////////////////////////////////////
//
// Asura_Sound_System::Platform_EnsureAllStreamingSoundsAreStopped()
//
// Ensures all streaming sounds have been stopped
//
// Parameters:		None
// Return value:	None
//
////////////////////////////////////////////////////////////////////////////////

void Asura_Sound_System::Platform_EnsureAllStreamingSoundsAreStopped()
{
}

////////////////////////////////////////////////////////////////////////////////
//
// Asura_Sound_System::Platform_SetStreamingSoundVolume()
//
// Sets the volume level of a streaming sound
//
// Parameters:		iStreamHandle - Handle to stream
//					fVolume - New volume level [0,1]
//
// Return value:	None
//
////////////////////////////////////////////////////////////////////////////////

void Asura_Sound_System::Platform_SetStreamingSoundVolume( Asura_Handle iStreamHandle, float fVolume )
{
	if( iStreamHandle < 0 || iStreamHandle >= ASURA_XBOX360_SOUND_MAX_STREAMS )
	{
		Asura_Assertf( false, ("Invalid stream handle (%d) passed to Asura_Sound_System::Platform_SetStreamingSoundVolume().", iStreamHandle) );
		return;
	}

	g_axStreams[ iStreamHandle ].SetVolume(fVolume);
}

#endif // #ifndef ASURA_USE_AUDIO_SYSTEM
