//////////////////////////////////////////////////////////////////////////////
//
// Asura_Sound_Event_Playback.cpp
//
// Copyright (c) 2006 Rebellion. All rights reserved.
//
// Description:
//
//     classes to handle the runtime playback of events
//
//////////////////////////////////////////////////////////////////////////////

// I N C L U D E S ///////////////////////////////////////////////////////////

#include "Core/Asura.h"

// This file's header.
#include "Sound/Asura_Sound_Event_Playback.h"

// Asura includes
#include "Core/Asura_Mempool.h"
#include "Core/Asura_Timers.h"
#include "Debugging/Asura_Logfile.h"
#include "Interface/Asura_CommandConsole.h"
#include "Sound/Asura_Audio_System.h"
#include "Sound/Asura_Audio_Voice.h"
#include "Sound/Asura_Phonon.h"
#include "Sound/Asura_Sound.h"
#include "Sound/Asura_Sound_Mixer.h"
#include "Sound/Asura_Sound_Event_System.h"

// C L A S S E S /////////////////////////////////////////////////////////////

//Asura_Sound_VariablePlayParams contains extra settings that are only required for variable playback sound events
class Asura_Sound_VariablePlayParams
{
public :
	float m_fVolume;				//current volume
	float m_fPitch;					//current pitch
	float m_fTargetVolume;			//volume being interpolated to
	float m_fTargetPitch;			//pitch being interpolated to
	float m_fVolumeTimer;			//time until a new random volume should be chosen
	float m_fPitchTimer;			//time until a new tandom pitch should be chosen
	float m_fVolumeRateOfChange;	//change in volume per second
	float m_fPitchRateOfChange;		//change in pitch per second
	float m_fSampleVolume;			//volume from the sample , to avoid having to keep hold of the sample pointer
	float m_fSamplePitch;			//pitch from the sample , to avoid having to keep hold of the sample pointer
};

// Asura_Sound_Event_Controller is the class that actually manages playback internally
class Asura_Sound_Event_Controller
{
public:

	Asura_Sound_Event_Controller();
	~Asura_Sound_Event_Controller();

	void Initialise();
	void Shutdown();

	void Play( const Asura_Sound_Playable* pxPlayable, bool bGeneratePlayParams,
				const Asura_Sound_Event* pxEvent, Asura_Sound_Event_Instance* pxOwner );
	bool Update(bool bMixerChanged);	// returns whether still active

	bool IsLooping() const;
	float GetOuterRadius() const { return m_pxEvent ? m_pxEvent->GetOuterRadius() : 0.0f; }
	float GetInnerRadius() const { return m_pxEvent ? m_pxEvent->GetInnerRadius() : 0.0f; }
	void VolumeScaleUpdated( float fNewVolScale );
	void PitchScaleUpdated( float fNewPitchScale );
	void PositionUpdated( const Asura_Vector_3& xNewPos );

#if defined(ASURA_TOOLS) || defined(ASURA_USE_MULTIPLE_HANDLERS)
	void SoundEventDeleted(const Asura_Sound_Event* pxEventDeleted);
#endif	//defined(ASURA_TOOLS) || defined(ASURA_USE_MULTIPLE_HANDLERS)

	// for simultaneous play
	void SetCurrentItem( u_int uCurrentItem ) { m_uCurrentItem = uCurrentItem; }

	const Asura_Sound_Event* GetEvent() const { return m_pxEvent; }
	u_int GetNumSubControllers() const;
	bool IsUsingVoice() const;

private:

	void CreateSound(const Asura_Sound_Sample* pxSample, bool bLooping, const Asura_Sound_PlayParams& xParams);
	void StartPlaying();		// plays sound or next controller event
	void StopPlaying();
	void UpdateWhilePlaying(bool bMixerChanged);
	bool IsPlaying() const;		// true iff the next controller is active or the sound is playing.
	void InitialiseVariablePlayback(const Asura_Sound_PlayParams* pxPlayParams, const Asura_Sound_Sample* pxSample);

	const Asura_Sound_Event*	m_pxEvent;
	Asura_Sound_Event_Instance* m_pxOwner;				// needed for sample playback

	u_int						m_bSoundIsLooping:1;  	// set during CreateSound
	u_int						m_bSoundUsesPosition:1;	// set during CreateSound
	u_int						m_bActive:1;		  	// set during Update() based on sound status, play mode etc.
	u_int						m_bUseEventDuration:1;
	u_int						m_bUseSampleDuration:1;
	float						m_fRemainingDelayTime;	// if >0 and goes to 0 during Update(), trigger the sound play.
	float						m_fEventDurationLeft; 	// Time left to play on event 
	float						m_fSampleDurationLeft;	// Time left to play on sample (if this controller has one)
	float						m_fSoundPlayVolume;		// whatever the volume was when the sample was played.
														// note this is different to m_xPlayParams.m_fVolume as it doesn't
														// take sample volume into account.
	float						m_fSoundPlayPitch;		// same as the voluem scale above, but applies to pitch

	u_int						m_uCurrentItem;			// for "all sequential" and "simultaneous" play modes
	Asura_Sound_PlayParams		m_xPlayParams;			// parameters set by event when the first sample was played
	Asura_Sound_PlayParams		m_xInitialPlayParams;	// parameters set by event before the first sample was played

	Asura_Sound_VariablePlayParams*	m_pxVariablePlayParams;	//extra parameters for variable playback events

	// sound handle can only be used if next controller is NULL.
	Asura_Handle					m_iSoundSystemHandle;
	Asura_Sound_Event_Controller*	m_apxNextController[uASURA_SOUND_EVENT_MAX_FOR_SIMULTANEOUS_PLAY];

	// non-copyable
	Asura_Sound_Event_Controller(const Asura_Sound_Event_Controller&);
	Asura_Sound_Event_Controller* operator= (const Asura_Sound_Event_Controller&);
};


// S T A T I C   G L O B A L S ///////////////////////////////////////////////
static Asura_Mempool g_xSoundEventControllerMempool( 
						sizeof(Asura_Sound_Event_Controller), uASURA_SOUND_EVENT_MAX_CONTROLLERS );

// S T A T I C   F U N C T I O N S ///////////////////////////////////////////
static void DumpControllers(void)
{
#if !defined(ASURA_NO_DEBUG_OUTPUT) && defined(ASURA_SOUND_INCLUDE_TOOLS_DATA)
	Asura_Mempool_ConstIterator	xIterator(g_xSoundEventControllerMempool);

	Asura_OutputToDebugger_Retail(("DumpControllers() - %d sound-event controllers present:\n", g_xSoundEventControllerMempool.GetNumAllocated()));

	while(!xIterator.Done())
	{
		const u_char*						pucElement		= xIterator.GetCurrent();
		const Asura_Sound_Event_Controller*	pxController	= reinterpret_cast<const Asura_Sound_Event_Controller*>(pucElement);
		const Asura_Sound_Event*			pxEvent			= pxController->GetEvent();

		Asura_OutputToDebugger_Retail(("- %s\n", pxEvent->GetName()));
		xIterator.Next();
	}
#endif	//!defined(ASURA_NO_DEBUG_OUTPUT) && defined(ASURA_SOUND_INCLUDE_TOOLS_DATA)
}

static bool AllocateController(Asura_Sound_Event_Controller*& pxController)
{
	Asura_Assert(!pxController, "controller ptr not NULL, possible overwrite?");

	pxController = reinterpret_cast<Asura_Sound_Event_Controller*>
		(g_xSoundEventControllerMempool.Allocate());

	if (pxController)
	{
		pxController->Initialise();
		return true;
	}
	else
	{
		DumpControllers();
		Asura_Assert(false, "Run out of sound event controllers; possible leak?");
		return false;
	}
}

static void DeallocateController(Asura_Sound_Event_Controller*& pxController)
{
	if (pxController)
	{
		pxController->Shutdown();
		
		g_xSoundEventControllerMempool.Deallocate( pxController );
		pxController = NULL;
	}
}


// A S U R A _ S O U N D _ E V E N T _ I N S T A N C E ///////////////////////
Asura_Sound_Event_Instance::Asura_Sound_Event_Instance()
{
	m_bNoExternalHandle = true;
	m_pxPhonon = NULL;
	for (u_int u = 0; u < uASURA_SOUND_EVENT_MAX_FOR_SIMULTANEOUS_PLAY; ++u)
	{
		m_apxFirstController[u] = NULL;
		m_apxPlayController[u] = NULL;
	}
	m_uNumPlayControllers = 0;

	Reset();
}

Asura_Sound_Event_Instance::~Asura_Sound_Event_Instance()
{
	Reset();
}

void Asura_Sound_Event_Instance::Update(bool bMixerChanged)
{
	if (m_bFadingOut)
	{
		Asura_Assert(m_fVolumeFadeoutRate > 0.0f && m_bNoExternalHandle, "shouldn't be fading out");
		m_fVolumeScale -= m_fVolumeFadeoutRate * Asura_Timers::GetFrameTime(); // unscaled.
		if (m_fVolumeScale <= 0.0f)
		{
			Stop();
			return;
		}
		else
		{
			SetVolumeScale(m_fVolumeScale);
		}
	}

	m_bPlaying = false;
	for (u_int u = 0; u < uASURA_SOUND_EVENT_MAX_FOR_SIMULTANEOUS_PLAY; ++u)
	{
		if (m_apxFirstController[u])
		{
			bool bControllerPlaying = m_apxFirstController[u]->Update(bMixerChanged);
			m_bPlaying = m_bPlaying || bControllerPlaying;
		}
		else
		{
			break;
		}
	}
}


void Asura_Sound_Event_Instance::Play(const Asura_Sound_Event* pxEvent, 
									  const Asura_Vector_3* pxPos,
									  float fVolumeScale)
{
	Asura_Assert(!m_uNumPlayControllers, "already playing");

	#ifdef ASURA_TOOLS
	//Deal with sound event mappings. RAR 30/08/2006
	pxEvent = pxEvent->GetRemappedEvent();
	#endif

	m_pxEvent = pxEvent;

	#if defined( ASURA_DEBUG_VARIABLE_SYSTEM ) && defined( ASURA_SOUND_INCLUDE_TOOLS_DATA )
		if ( Asura_Sound_Event_System::s_bPrintSoundEvents )
		{
			Asura_OutputToDebugger_Retail(( "Playing sound event %s, volume %.2f\n", pxEvent->GetName(), Asura_Maths::FtoD(fVolumeScale) ));
		}
	#endif

	m_uInstanceGroupID = pxEvent->GetInstanceGroupID();
	if(m_uInstanceGroupID)
	{
		Asura_Sound_Event_System::AddToActiveInstanceCount(m_uInstanceGroupID);
	}

	if (pxPos)
	{
		m_xPosition = *pxPos;
		m_bUsePosition = true;
	}
	else
	{
		m_bUsePosition = false;
	}
	m_fVolumeScale = fVolumeScale;
	m_bPlaying = true;

	if (pxEvent->GetPlaybackType() != ASURA_PT_SIMULTANEOUS && pxEvent->GetPlaybackType() != ASURA_PT_VARIABLE)
	{
        // (MH - 13.11.2006) in tools, we can end up with events without entries during creation.
#ifdef ASURA_TOOLS
		if (pxEvent->GetNumPlayables())
#endif
		{
			if (AllocateController(m_apxFirstController[0]))
			{
				m_apxFirstController[0]->Play( pxEvent->GetPlayableSound(), true, pxEvent, this );
			}
		}
	}
	else
	{
		// simultaneous.
		for (u_int u = 0; u < pxEvent->GetNumPlayables(); ++u)
		{
			Asura_Assert(u < uASURA_SOUND_EVENT_MAX_FOR_SIMULTANEOUS_PLAY, "too many events for simultaneous play");
			if (u >= uASURA_SOUND_EVENT_MAX_FOR_SIMULTANEOUS_PLAY)
			{
				break;
			}

			if (AllocateController(m_apxFirstController[u]))
			{
				m_apxFirstController[u]->SetCurrentItem(u);
				m_apxFirstController[u]->Play( pxEvent->GetPlayableSound(u), true, pxEvent, this );
			}
		}
	}
}

void Asura_Sound_Event_Instance::Play(const Asura_Phonon* pxPhonon)
{
	m_pxPhonon = pxPhonon;
	Play(pxPhonon->GetEvent(), pxPhonon->GetPositionPtr(), 1.0f);
}

void Asura_Sound_Event_Instance::Stop()
{
	#if defined( ASURA_DEBUG_VARIABLE_SYSTEM ) && defined( ASURA_SOUND_INCLUDE_TOOLS_DATA )
		if ( Asura_Sound_Event_System::s_bPrintSoundEvents )
		{
			if ( GetEvent() && GetEvent()->GetName() )
			{
				Asura_OutputToDebugger_Retail(( "Stopping sound event %s\n", GetEvent()->GetName() ));
			}
		}
	#endif

	if(m_uInstanceGroupID)
	{
		Asura_Sound_Event_System::RemoveFromActiveInstanceCount(m_uInstanceGroupID);
	}

	Reset();
}

void Asura_Sound_Event_Instance::FadeOut( float fFadeTime )
{
#if defined( ASURA_DEBUG_VARIABLE_SYSTEM ) && defined( ASURA_SOUND_INCLUDE_TOOLS_DATA )
	if ( Asura_Sound_Event_System::s_bPrintSoundEvents )
	{
		if ( GetEvent() && GetEvent()->GetName() )
		{
			Asura_OutputToDebugger_Retail(( "fading out sound event %s over %.02f seconds\n", 
				GetEvent()->GetName(), Asura_Maths::FtoD(fFadeTime) ));
		}
	}
#endif

	Asura_Assert(fFadeTime > 0.0f, "invalid fade time");
	if (fFadeTime > 0.0f)
	{
		m_bFadingOut = true;
		m_fVolumeFadeoutRate = m_fVolumeScale / fFadeTime;
	}
}

void Asura_Sound_Event_Instance::SetPosition( const Asura_Vector_3& xPos )
{
	Asura_Assert(m_bUsePosition, "not using position when event was started");
	m_xPosition = xPos;
	for (u_int u = 0; u < m_uNumPlayControllers; ++u)
	{
		Asura_Assert(m_apxPlayController[u], "array size and contents out of sync");
		m_apxPlayController[u]->PositionUpdated(xPos);
	}
}

void Asura_Sound_Event_Instance::SetVolumeScale( float fVolScale )
{
	m_fVolumeScale = fVolScale;
	for (u_int u = 0; u < m_uNumPlayControllers; ++u)
	{
		Asura_Assert(m_apxPlayController[u], "array size and contents out of sync");
		m_apxPlayController[u]->VolumeScaleUpdated(m_fVolumeScale);
	}
}

void Asura_Sound_Event_Instance::SetPitchScale( float fPitchScale )
{
	m_fPitchScale = fPitchScale;
	for (u_int u = 0; u < m_uNumPlayControllers; ++u)
	{
		Asura_Assert(m_apxPlayController[u], "array size and contents out of sync");
		m_apxPlayController[u]->PitchScaleUpdated(m_fPitchScale);
	}
}

bool Asura_Sound_Event_Instance::IsLooping() const
{
	// looping if any of the controllers say they are looping
	bool bLooping = false;
	for (u_int u = 0; u < uASURA_SOUND_EVENT_MAX_FOR_SIMULTANEOUS_PLAY; ++u)
	{
		if (m_apxFirstController[u])
		{
			bLooping = bLooping || m_apxFirstController[u]->IsLooping();
		}
		else
		{
			break;
		}
	}
	return bLooping;
}

float Asura_Sound_Event_Instance::GetInnerRadius() const
{
	float fRadius = 0.0f;
	for ( u_int u = 0; u < uASURA_SOUND_EVENT_MAX_FOR_SIMULTANEOUS_PLAY; ++u )
	{
		if ( !m_apxFirstController[u] ) break;
		
		fRadius = Asura_Maths::Max( fRadius, m_apxFirstController[u]->GetInnerRadius() );
	}
	return fRadius;
}

float Asura_Sound_Event_Instance::GetOuterRadius() const
{
	float fRadius = 0.0f;
	for ( u_int u = 0; u < uASURA_SOUND_EVENT_MAX_FOR_SIMULTANEOUS_PLAY; ++u )
	{
		if ( !m_apxFirstController[u] ) break;
		
		fRadius = Asura_Maths::Max( fRadius, m_apxFirstController[u]->GetOuterRadius() );
	}

	return fRadius;
}

#if defined(ASURA_TOOLS) || defined(ASURA_USE_MULTIPLE_HANDLERS)
void Asura_Sound_Event_Instance::SoundEventDeleted(const Asura_Sound_Event* pxEventDeleted)
{
	for (u_int u = 0; u < uASURA_SOUND_EVENT_MAX_FOR_SIMULTANEOUS_PLAY; ++u)
	{
		if (m_apxFirstController[u])
		{
			m_apxFirstController[u]->SoundEventDeleted(pxEventDeleted);
		}
		else
		{
			break;
		}
	}
	if(pxEventDeleted == m_pxEvent)
	{
		m_pxEvent = 0;
	}
}
#endif	// defined(ASURA_TOOLS) || defined(ASURA_USE_MULTIPLE_HANDLERS)

void Asura_Sound_Event_Instance::AddPlayController(Asura_Sound_Event_Controller* pxController)
{
	Asura_Assert(m_uNumPlayControllers < uASURA_SOUND_EVENT_MAX_FOR_SIMULTANEOUS_PLAY, "too many simultaneous sounds");
	if (m_uNumPlayControllers < uASURA_SOUND_EVENT_MAX_FOR_SIMULTANEOUS_PLAY)
	{
		m_apxPlayController[m_uNumPlayControllers++] = pxController;
	}
}

void Asura_Sound_Event_Instance::RemovePlayController(Asura_Sound_Event_Controller* pxController)
{
#ifdef ASURA_ASSERT
	bool bFound = false;
#endif
	for (u_int u = 0; u < m_uNumPlayControllers; ++u)
	{
		Asura_Assert(m_apxPlayController[u], "null play controller");
		if (m_apxPlayController[u] == pxController)
		{
			// swap with last
			m_apxPlayController[u] = m_apxPlayController[m_uNumPlayControllers - 1];
			m_apxPlayController[m_uNumPlayControllers - 1] = NULL;
			--m_uNumPlayControllers;
#ifdef ASURA_ASSERT
			bFound = true;
#endif
		}
	}

	Asura_Assert(bFound, "failed to find play controller");
}

u_int Asura_Sound_Event_Instance::GetNumControllers() const
{
	u_int uNum = 0;
	for (u_int u = 0; u < uASURA_SOUND_EVENT_MAX_FOR_SIMULTANEOUS_PLAY; ++u)
	{
		if (m_apxFirstController[u])
		{
			uNum += 1 + m_apxFirstController[u]->GetNumSubControllers();
		}
		else
		{
			break;
		}
	}
	return uNum;
}

u_int Asura_Sound_Event_Instance::GetNumVoices() const
{
	u_int uNum = 0;
	for (u_int u = 0; u < m_uNumPlayControllers; ++u)
	{
		if (m_apxPlayController[u]->IsUsingVoice())
		{
			++uNum;
		}
	}
	return uNum;
}

void Asura_Sound_Event_Instance::Reset()
{
	m_pxEvent = NULL;
	m_pxPhonon = NULL;
	m_bPlaying = false;
	m_bUsePosition = false;
	m_bFadingOut = false;
	m_fVolumeScale = 1.0f;
	m_fPitchScale = 1.0f;
	m_fVolumeFadeoutRate = 0.0f;
	m_uInstanceGroupID = 0;


	for (u_int u = 0; u < uASURA_SOUND_EVENT_MAX_FOR_SIMULTANEOUS_PLAY; ++u)
	{
		if (m_apxFirstController[u])
		{
			DeallocateController(m_apxFirstController[u]);
			Asura_Assert(!m_apxFirstController[u], "DeallocateController should NULLify ptr");
		}
		else
		{
			break;
		}
	}

#ifdef ASURA_ASSERT
	for (u_int u = 0; u < uASURA_SOUND_EVENT_MAX_FOR_SIMULTANEOUS_PLAY; ++u)
	{
		Asura_Assert(!m_apxPlayController[u], "shutdown of first controller didn't clear play controller");
	}
#endif
}


// A S U R A _ S O U N D _ E V E N T _ C O N T R O L L E R ///////////////////
Asura_Sound_Event_Controller::Asura_Sound_Event_Controller()
{
	Initialise();
}

Asura_Sound_Event_Controller::~Asura_Sound_Event_Controller()
{
	Shutdown();
}

void Asura_Sound_Event_Controller::Initialise()
{
	m_pxEvent = NULL;
	m_pxOwner = NULL;

	m_bSoundIsLooping = false;
	m_bSoundUsesPosition = false;
	m_bActive = false;
	m_bUseSampleDuration = false;
	m_bUseEventDuration = false;
	m_fRemainingDelayTime = 0.0f;
	m_fEventDurationLeft = 0.0f;
	m_fSampleDurationLeft = 0.0f;
	m_fSoundPlayVolume = 0.0f;
	m_fSoundPlayPitch = 0.0f;

	m_uCurrentItem = 0;
	m_xPlayParams.Initialise();
	m_pxVariablePlayParams = 0;

	// sound handle can only be used if next controllers are NULL.
	m_iSoundSystemHandle = ASURA_INVALID_HANDLE;
	for (u_int u = 0; u < uASURA_SOUND_EVENT_MAX_FOR_SIMULTANEOUS_PLAY; ++u)
	{
		m_apxNextController[u] = NULL;
	}
}

void Asura_Sound_Event_Controller::Shutdown()
{
	StopPlaying();
	delete m_pxVariablePlayParams;
	m_pxVariablePlayParams = 0;
}

void Asura_Sound_Event_Controller::Play( const Asura_Sound_Playable* pxPlayable, 
										bool bGeneratePlayParams,
										const Asura_Sound_Event* pxEvent, 
										Asura_Sound_Event_Instance* pxOwner )
{
	if (!pxPlayable)
	{
		return;
	}

	m_pxEvent = pxEvent;
	m_pxOwner = pxOwner;

	if(!m_bUseEventDuration && m_pxEvent->HasDuration())
	{
		//the parent event is just starting, so generate a duration for it
		m_bUseEventDuration = true;
		m_fEventDurationLeft = m_pxEvent->GetRandomDuration();
	}
	m_bUseSampleDuration = false;

	if (bGeneratePlayParams)
	{
		// this controller's play params are taken from the event
		m_xInitialPlayParams = m_xPlayParams;
		pxEvent->FillInPlayParams(m_xPlayParams);
	}
	
	if (pxPlayable->IsEvent())
	{
		pxEvent = static_cast<const Asura_Sound_Event*>(pxPlayable);

		#ifdef ASURA_TOOLS
		//Deal with sound event mappings. RAR 30/08/2006
		pxEvent = pxEvent->GetRemappedEvent();
		#endif

		if(pxEvent->CheckPlayProbability())
		{
			if (pxEvent->GetPlaybackType() != ASURA_PT_SIMULTANEOUS && pxEvent->GetPlaybackType() != ASURA_PT_VARIABLE)
			{
				if (AllocateController(m_apxNextController[0]))
				{
					// recursive play
					m_apxNextController[0]->m_xPlayParams = m_xPlayParams;
					m_apxNextController[0]->Play( pxEvent->GetPlayableSound(), true, pxEvent, pxOwner );
				}
			}
			else
			{
				// simultaneous.
				for (u_int u = 0; u < pxEvent->GetNumPlayables(); ++u)
				{
					Asura_Assert(u < uASURA_SOUND_EVENT_MAX_FOR_SIMULTANEOUS_PLAY, "too many events for simultaneous play");
					if (u >= uASURA_SOUND_EVENT_MAX_FOR_SIMULTANEOUS_PLAY)
					{
						break;
					}

					if (AllocateController(m_apxNextController[u]))
					{
						m_apxNextController[u]->m_uCurrentItem = u;
						m_apxNextController[u]->Play( pxEvent->GetPlayableSound(u), true, pxEvent, pxOwner );
					}
				}
			}

		}

		m_bActive = true;
	}
	else
	{
		Asura_Assert(pxPlayable->IsSample(), "must be a sample if not an event"); 
		const Asura_Sound_Sample* pxSample = static_cast<const Asura_Sound_Sample*>(pxPlayable);

		// modify event's play params with sample's
		Asura_Sound_PlayParams xParams = m_xPlayParams;
		pxPlayable->FillInPlayParams(xParams);

		if(pxSample->m_fDuration)
		{
			m_bUseSampleDuration = true;
			m_fSampleDurationLeft = pxSample->m_fDuration;
		}

		if(m_pxEvent->GetPlaybackType() == ASURA_PT_VARIABLE)
		{
			InitialiseVariablePlayback(&xParams,pxSample);
		}

		CreateSound(pxSample, pxSample->GetLooping(), xParams );

		// only set active if CreateSound succeeded, ditto for adding play controller.
		m_bActive = m_iSoundSystemHandle != ASURA_INVALID_HANDLE;
		if (m_bActive)
		{
			pxOwner->AddPlayController(this);
		}
	}

}


bool Asura_Sound_Event_Controller::Update(bool bMixerChanged)
{
	// recursive update
	for (u_int u = 0; u < uASURA_SOUND_EVENT_MAX_FOR_SIMULTANEOUS_PLAY; ++u)
	{
		if (m_apxNextController[u])
		{
			m_apxNextController[u]->Update(bMixerChanged);
		}
		else
		{
			break;
		}
	}

	if (m_bActive)
	{
		if(m_bUseEventDuration)
		{
			m_fEventDurationLeft -= Asura_Timers::GetFrameTime();
		}
		if(m_bUseEventDuration && m_fEventDurationLeft <= 0.0f)
		{
			StopPlaying();
		}
		else if (m_fRemainingDelayTime>0.0f)
		{
			const bool bIgnoreGameSpeedPitch = Asura_Sound_Mixer_System::IsIgnoreGameSpeedPitch( m_xPlayParams.m_pxMixingChannel );
			m_fRemainingDelayTime -= bIgnoreGameSpeedPitch ? Asura_Timers::GetFrameTime() : Asura_Timers::GetGameTimeSlice();
			if (m_fRemainingDelayTime <= 0.0f)
			{
				m_fRemainingDelayTime = 0.0f;
				StartPlaying();
			}
		}
		else
		{
			bool bStopSample = false;
			if(m_bUseSampleDuration)
			{
				//This is a sample with a maximum duration attached.
				//See if the time is up.
				m_fSampleDurationLeft -= Asura_Timers::GetFrameTime();
				if(m_fSampleDurationLeft <= 0.0f)
				{
					bStopSample = true;
				}
			}
			
			if (IsPlaying() && !bStopSample)
			{
				UpdateWhilePlaying(bMixerChanged);
			}
			else
			{
				StopPlaying();

				// the sound or event has stopped, depending on play mode we're done, or we need to start another one.
				bool bFinished;
				if (ASURA_PT_ALLSEQUENTIAL == m_pxEvent->GetPlaybackType())
				{
					u_int uNextSample = m_uCurrentItem + 1;
					if (uNextSample < m_pxEvent->GetNumPlayables())
					{
						m_uCurrentItem = uNextSample;
						const Asura_Sound_Playable* pxPlayable = m_pxEvent->GetPlayableSound(uNextSample);
						// this Play() call should use the same event play parameters as the last Play() call
						Play( pxPlayable, false, m_pxEvent, m_pxOwner );
						bFinished = false;
					}
					else
					{
						bFinished = true;
					}
				}
				else
				{
					bFinished = true;
				}

				if (bFinished)
				{
					if (m_pxEvent->IsLooping())
					{
						// restart using play params from before.
						m_xPlayParams = m_xInitialPlayParams;
						if (m_pxEvent->GetPlaybackType() != ASURA_PT_SIMULTANEOUS && m_pxEvent->GetPlaybackType() != ASURA_PT_VARIABLE)
						{
							m_uCurrentItem = 0;
							const Asura_Sound_Playable* pxPlayable = m_pxEvent->GetPlayableSound();
							Play( pxPlayable, true, m_pxEvent, m_pxOwner );
						}
						else
						{
							// simultaneous, just repeat the one we're playing
							const Asura_Sound_Playable* pxPlayable = m_pxEvent->GetPlayableSound(m_uCurrentItem);
							Play( pxPlayable, true, m_pxEvent, m_pxOwner );
						}
					}
					else
					{
						m_bActive = false;
					}
				}
			}
		}
	}// end if (m_bActive)

	return m_bActive;
}

bool Asura_Sound_Event_Controller::IsPlaying() const
{
    // (MH - 14.09.2006) bugfix: this used to check m_apxNextController[0] and return
	// m_apxNextController[0]->m_bActive. Obviously if it was false and we were doing
	// simultaneous play then we could still be playing but false would be returned. So
	// now we check all the controllers if any are set.
	if (m_apxNextController[0])
	{
		for (u_int u = 0; u < uASURA_SOUND_EVENT_MAX_FOR_SIMULTANEOUS_PLAY; ++u)
		{
			if (m_apxNextController[u])
			{
				if (m_apxNextController[u]->m_bActive)
				{
					return true;
				}
			}
			else
			{
				break;
			}
		}
		return false;
	}
	else
	{
		// if we have a sound system handle and are looping, the sound is "playing" 
		// even if the sound system has stopped the sound because it went out of range.
		if (m_iSoundSystemHandle != ASURA_INVALID_HANDLE)
		{
#if defined(ASURA_USE_AUDIO_SYSTEM)
			const Asura_Audio_Voice* const pxVoice = Asura_Audio_System::GetVoice(m_iSoundSystemHandle);
			return m_bSoundIsLooping ? true : (pxVoice ? pxVoice->IsPlaying() : false);
#else
			return m_bSoundIsLooping ? true : Asura_Sound_System::SoundIsPlaying(m_iSoundSystemHandle);
#endif
		}
		else
		{
			return false;
		}
	}
}

void Asura_Sound_Event_Controller::StartPlaying()
{
#if defined(ASURA_USE_AUDIO_SYSTEM)
	Asura_Audio_Voice* const pxVoice = Asura_Audio_System::GetVoice(m_iSoundSystemHandle);
	if (pxVoice)
	{
		pxVoice->Start();
	}
#else
	if (m_iSoundSystemHandle != ASURA_INVALID_HANDLE)
	{
		Asura_Sound_System::PlaySound(m_iSoundSystemHandle, m_bSoundIsLooping);
	}
#endif
}

void Asura_Sound_Event_Controller::StopPlaying()
{
	for (u_int u = 0; u < uASURA_SOUND_EVENT_MAX_FOR_SIMULTANEOUS_PLAY; ++u)
	{
		if (m_apxNextController[u])
		{
			DeallocateController(m_apxNextController[u]);
		}
		else
		{
			break;
		}
	}

	if (m_iSoundSystemHandle != ASURA_INVALID_HANDLE)
	{
#if defined(ASURA_USE_AUDIO_SYSTEM)
		Asura_Audio_System::DestroyVoice(m_iSoundSystemHandle);
#else
		Asura_Sound_System::DestroySound(m_iSoundSystemHandle);
#endif
		m_pxOwner->RemovePlayController(this);
	}

	m_bActive = false;
}

void Asura_Sound_Event_Controller::UpdateWhilePlaying(bool bMixerChanged)
{
	if (m_iSoundSystemHandle != ASURA_INVALID_HANDLE)
	{
#if defined(ASURA_USE_AUDIO_SYSTEM)
		Asura_Audio_Voice* const pxVoice = Asura_Audio_System::GetVoice(m_iSoundSystemHandle);
#endif

		if(m_pxEvent->GetPlaybackType() == ASURA_PT_VARIABLE)
		{
			if(!m_pxVariablePlayParams)
			{
				//May not have m_pxVariablePlayParams in tools if the event is being edited while this is playing.
				//RAR 11/01/2007
				#ifndef ASURA_TOOLS
				Asura_Assert(m_pxVariablePlayParams,"");
				#endif
				
				StopPlaying();
				return;
			}

			const bool bIgnoreGameSpeedPitch = Asura_Sound_Mixer_System::IsIgnoreGameSpeedPitch( m_xPlayParams.m_pxMixingChannel );
			const float fFrameTime = bIgnoreGameSpeedPitch ? Asura_Timers::GetFrameTime() : Asura_Timers::GetGameTimeSlice();
			
			bool bVolumeChanged = bMixerChanged || m_pxVariablePlayParams->m_fTargetVolume != m_pxVariablePlayParams->m_fVolume;
			bool bPitchChanged = m_pxVariablePlayParams->m_fTargetPitch != m_pxVariablePlayParams->m_fPitch;

			const Asura_Sound_VariableParams* pxVariableParams = m_pxEvent->GetVariableParams();

			//update volume

			m_pxVariablePlayParams->m_fVolumeTimer -= fFrameTime;
			if(m_pxVariablePlayParams->m_fVolumeTimer < 0.0f)
			{
				//time to choose a new target volume

				//set current value to old target , in case we haven't reached it yet
				m_pxVariablePlayParams->m_fVolume = m_pxVariablePlayParams->m_fTargetVolume;


				//choose a new value by combinining the initial settings  , a new random value from the event
				//and the value from the sample
				m_pxVariablePlayParams->m_fTargetVolume	= m_xInitialPlayParams.m_fVolume
														* Asura_Random::GetFloat(m_pxEvent->GetMinVolume(),m_pxEvent->GetMaxVolume())
														* m_pxVariablePlayParams->m_fSampleVolume;

				//get time until a new value should be chosen
				m_pxVariablePlayParams->m_fVolumeTimer = Asura_Random::GetFloat(pxVariableParams->m_fMinInterval,pxVariableParams->m_fMaxInterval);

				//determine the time over which the interpolation is going to be done
				float fTime = m_pxVariablePlayParams->m_fVolumeTimer * Asura_Random::GetFloat(pxVariableParams->m_fMinVolumeChangeTime,pxVariableParams->m_fMaxVolumeChangeTime);

				//set the rate of change so as to reach the desired value in the alocated time
				if(fTime > 0.0f)
				{
					m_pxVariablePlayParams->m_fVolumeRateOfChange = (m_pxVariablePlayParams->m_fTargetVolume-m_pxVariablePlayParams->m_fVolume) / fTime;
				}
				else
				{
					m_pxVariablePlayParams->m_fVolume = m_pxVariablePlayParams->m_fTargetVolume;
					bVolumeChanged = true;
				}
												

			}
			else if(m_pxVariablePlayParams->m_fTargetVolume != m_pxVariablePlayParams->m_fVolume)
			{
				//adjust volume
				m_pxVariablePlayParams->m_fVolume += m_pxVariablePlayParams->m_fVolumeRateOfChange * fFrameTime;
				//overshot desired value?
				if(((m_pxVariablePlayParams->m_fVolume > m_pxVariablePlayParams->m_fTargetVolume) && (m_pxVariablePlayParams->m_fVolumeRateOfChange > 0.0f)) ||
					((m_pxVariablePlayParams->m_fVolume < m_pxVariablePlayParams->m_fTargetVolume) && (m_pxVariablePlayParams->m_fVolumeRateOfChange < 0.0f)))
				{
					m_pxVariablePlayParams->m_fVolume = m_pxVariablePlayParams->m_fTargetVolume;
				}
			}
			m_fSoundPlayVolume = m_pxVariablePlayParams->m_fVolume;//keep the two copies of the current volume in sync

			//update pitch

			m_pxVariablePlayParams->m_fPitchTimer -= fFrameTime;
			if(m_pxVariablePlayParams->m_fPitchTimer < 0.0f)
			{
				//time to choose a new target pitch

				//set current value to old target , in case we haven't reached it yet
				m_pxVariablePlayParams->m_fPitch = m_pxVariablePlayParams->m_fTargetPitch;


				//choose a new value by combinining the initial settings  , a new random value from the event
				//and the value from the sample
				m_pxVariablePlayParams->m_fTargetPitch	= m_xInitialPlayParams.m_fPitch
														* Asura_Random::GetFloat(m_pxEvent->GetMinPitch(),m_pxEvent->GetMaxPitch())
														* m_pxVariablePlayParams->m_fSamplePitch;

				//get time until a new value should be chosen
				m_pxVariablePlayParams->m_fPitchTimer = Asura_Random::GetFloat(pxVariableParams->m_fMinInterval,pxVariableParams->m_fMaxInterval);

				//determine the time over which the interpolation is going to be done
				float fTime = m_pxVariablePlayParams->m_fPitchTimer * Asura_Random::GetFloat(pxVariableParams->m_fMinPitchChangeTime,pxVariableParams->m_fMaxPitchChangeTime);

				//set the rate of change so as to reach the desired value in the alocated time
				if(fTime > 0.0f)
				{
					m_pxVariablePlayParams->m_fPitchRateOfChange = (m_pxVariablePlayParams->m_fTargetPitch-m_pxVariablePlayParams->m_fPitch) / fTime;
				}
				else
				{
					m_pxVariablePlayParams->m_fPitch = m_pxVariablePlayParams->m_fTargetPitch;
					bPitchChanged = true;
				}
												

			}
			else if(m_pxVariablePlayParams->m_fTargetPitch != m_pxVariablePlayParams->m_fPitch)
			{
				//adjust pitch
				m_pxVariablePlayParams->m_fPitch += m_pxVariablePlayParams->m_fPitchRateOfChange * fFrameTime;
				//overshot desired value?
				if(((m_pxVariablePlayParams->m_fPitch > m_pxVariablePlayParams->m_fTargetPitch) && (m_pxVariablePlayParams->m_fPitchRateOfChange > 0.0f)) ||
					((m_pxVariablePlayParams->m_fPitch < m_pxVariablePlayParams->m_fTargetPitch) && (m_pxVariablePlayParams->m_fPitchRateOfChange < 0.0f)))
				{
					m_pxVariablePlayParams->m_fPitch = m_pxVariablePlayParams->m_fTargetPitch;
				}
			}
			m_fSoundPlayPitch = m_pxVariablePlayParams->m_fPitch;//keep the two copies of the current pitch in sync

			if(bVolumeChanged)
			{
				float fMixerVolumeScale = Asura_Sound_Mixer_System::GetMixerVolume(m_xPlayParams.m_pxMixingChannel);
				float fVolume = m_pxVariablePlayParams->m_fVolume * m_pxOwner->GetVolumeScale() * fMixerVolumeScale;

#if defined(ASURA_USE_AUDIO_SYSTEM)
				if(pxVoice)
				{
					pxVoice->SetVolume(fVolume);
				}
#else
				Asura_Sound_System::SetSoundVolume(m_iSoundSystemHandle, fVolume);
#endif
			}

			if(bPitchChanged)
			{
				float fPitch = m_pxVariablePlayParams->m_fPitch * m_pxOwner->GetPitchScale();

#if defined(ASURA_USE_AUDIO_SYSTEM)
				if(pxVoice)
				{
					pxVoice->SetPitch(fPitch);
				}
#else
				Asura_Sound_System::SetSoundPitch(m_iSoundSystemHandle, fPitch);
#endif
			}
		
		}
		else if (bMixerChanged)
		{
			float fMixerVolumeScale = Asura_Sound_Mixer_System::GetMixerVolume(m_xPlayParams.m_pxMixingChannel);
			float fVolume = m_fSoundPlayVolume * m_pxOwner->GetVolumeScale() * fMixerVolumeScale;

#if defined(ASURA_USE_AUDIO_SYSTEM)
			if (pxVoice)
			{
				pxVoice->SetVolume(fVolume);
			}
#else
			Asura_Sound_System::SetSoundVolume(m_iSoundSystemHandle, fVolume);
#endif

			// JAT 26/02/07 enable/disable game speed pitch scaling based on the flag in the mixer 
			// The global mixer doesn't update the ignore game speed pitch variable so don't update here
			//const bool bIgnoreGameSpeedPitch = Asura_Sound_Mixer_System::IsIgnoreGameSpeedPitch( m_xPlayParams.m_pxMixingChannel );
			//Asura_Sound_System::SetPitchUsesGameTimeScale( !bIgnoreGameSpeedPitch );
			// NOTE similar comment will apply to UseReverb and UseDistanceFilter
		}
	}
}

void Asura_Sound_Event_Controller::CreateSound( const Asura_Sound_Sample* pxSample, bool bLooping, const Asura_Sound_PlayParams& xParams )
{
	Asura_Assert(m_iSoundSystemHandle == ASURA_INVALID_HANDLE, "handle already set");

	const ASURA_3DSOUND_TYPE e3dSoundType = m_pxEvent->Get3dSoundType();

#if defined(ASURA_USE_AUDIO_SYSTEM)
	const bool bSpatialType = (e3dSoundType != ASURA_SOUND_DIRECT && e3dSoundType != ASURA_SOUND_RANDOMPOS);

	// we need to switch on voice filtering for occlusion OR distance filtering
	const bool bFilter = bSpatialType && 
		( ( xParams.m_bUseDistanceFilter && Asura_Sound_Mixer_System::GetMixerUseDistanceFilter( m_xPlayParams.m_pxMixingChannel ) )
		|| ( xParams.m_bUseOcclusion && Asura_Sound_Mixer_System::GetMixerUseOcclusion( m_xPlayParams.m_pxMixingChannel ) ) );

	m_iSoundSystemHandle = Asura_Audio_System::CreateVoice(pxSample->GetWaveHash(), bFilter, false, xParams.m_uDSPChain );
#else

#if defined(ASURA_USE_MULTIPLE_HANDLERS)
	m_iSoundSystemHandle = Asura_Sound_System::CreateSound(pxSample->GetSoundID(),pxSample->GetSoundHandler());
#else
	m_iSoundSystemHandle = Asura_Sound_System::CreateSound(pxSample->GetSoundID());
#endif	//defined(ASURA_USE_MULTIPLE_HANDLERS)

#endif //defined(ASURA_USE_AUDIO_SYSTEM)

	Asura_Assert(m_iSoundSystemHandle != ASURA_INVALID_HANDLE, "failed to create sound for sound event");
	if (m_iSoundSystemHandle != ASURA_INVALID_HANDLE)
	{
		m_bSoundIsLooping = bLooping;
		m_fRemainingDelayTime = xParams.m_fDelay;

		m_fSoundPlayVolume = xParams.m_fVolume;		
		float fMixerVolumeScale = Asura_Sound_Mixer_System::GetMixerVolume(xParams.m_pxMixingChannel);
		float fVolume = xParams.m_fVolume * m_pxOwner->GetVolumeScale() * fMixerVolumeScale;

#if defined(ASURA_USE_AUDIO_SYSTEM)
		Asura_Audio_Voice* const pxVoice = Asura_Audio_System::GetVoice(m_iSoundSystemHandle);
		if (pxVoice)
		{
			pxVoice->SetVolume(fVolume);
		}
#else
		Asura_Sound_System::SetSoundVolume(m_iSoundSystemHandle, fVolume);
#endif

		m_fSoundPlayPitch = xParams.m_fPitch;
		float fPitch = xParams.m_fPitch * m_pxOwner->GetPitchScale();

#if defined(ASURA_USE_AUDIO_SYSTEM)
		if (pxVoice)
		{
			pxVoice->SetPitch(fPitch);
		}

#else
		Asura_Sound_System::SetSoundPitch(m_iSoundSystemHandle, fPitch);
#endif

		const bool bIgnoreGameSpeedPitch = Asura_Sound_Mixer_System::IsIgnoreGameSpeedPitch( m_xPlayParams.m_pxMixingChannel );

#if !defined(ASURA_USE_AUDIO_SYSTEM)
        if(xParams.m_bForcedPan)
		{
			Asura_Sound_System::SetSoundForcedPanAndFade(m_iSoundSystemHandle, xParams.m_xSurroundPan.m_fPan, xParams.m_xSurroundPan.m_fFade);
		}

		// TODO: mixer pan.
		// TODO: perhaps get the mixer channel pointer or mixer channel data

		// JAT 26/02/07 enable/disable game speed pitch scaling based on the flag in the mixer 
		if ( bIgnoreGameSpeedPitch == true )
		{
			Asura_Sound_System::SetSoundPitchUsesTimeScale( m_iSoundSystemHandle, !bIgnoreGameSpeedPitch );
		}
#else
		if ( pxVoice )
		{
			pxVoice->SetPitchUsesTimescale( !bIgnoreGameSpeedPitch );
		}
#endif

		// By default reverb, doppler, occlusion & distance filtering are enabled so only change if they are different to the default
		{
			const bool bApplyReverb = xParams.m_bUseEnvReverb && Asura_Sound_Mixer_System::GetMixerUseReverb( m_xPlayParams.m_pxMixingChannel );

#if defined(ASURA_USE_AUDIO_SYSTEM)
			if ( pxVoice )
			{
				pxVoice->SetUsingReverb( bApplyReverb );
			}
#else
			if ( bApplyReverb == false )
			{
				Asura_Sound_System::SetSoundUsesReverb( m_iSoundSystemHandle, bApplyReverb );
			}
#endif
		}

		{
			const bool bDistanceFilter = xParams.m_bUseDistanceFilter && Asura_Sound_Mixer_System::GetMixerUseDistanceFilter( m_xPlayParams.m_pxMixingChannel );

#if defined(ASURA_USE_AUDIO_SYSTEM)
			if ( pxVoice )
			{
				pxVoice->SetUsingDistanceFiltering( bDistanceFilter && bSpatialType );
			}
#else
			if ( bDistanceFilter == false )
			{
				Asura_Sound_System::SetSoundUsesDistanceFilter( m_iSoundSystemHandle, bDistanceFilter );
			}
#endif
		}
#if !defined(ASURA_USE_AUDIO_SYSTEM)
		{
			const bool bDoppler = xParams.m_bUseDoppler && Asura_Sound_Mixer_System::GetMixerUseDoppler( m_xPlayParams.m_pxMixingChannel );
			if ( bDoppler == false )
			{
				Asura_Sound_System::SetSoundUsesDoppler( m_iSoundSystemHandle, bDoppler );
			}
		}
#endif
		{
			const bool bOcclusion = xParams.m_bUseOcclusion && Asura_Sound_Mixer_System::GetMixerUseOcclusion( m_xPlayParams.m_pxMixingChannel );

#if defined(ASURA_USE_AUDIO_SYSTEM)
			if ( pxVoice )
			{
				pxVoice->SetUsingOcclusion( bSpatialType && bOcclusion );
			}
#else
			if ( bOcclusion == false )
			{
				Asura_Sound_System::SetSoundUsesOcclusion( m_iSoundSystemHandle, bOcclusion );
			}
#endif
		}

		{
#if defined(ASURA_USE_AUDIO_SYSTEM)
			const bool bMasterDSP = xParams.m_bUseMasterDSP && Asura_Sound_Mixer_System::GetMixerUseMasterDSP( m_xPlayParams.m_pxMixingChannel );

			if ( pxVoice )
			{
				pxVoice->SetUsingMasterDSP( bMasterDSP );
			}
#endif
		}

		const Asura_Phonon* pxPhonon = m_pxOwner->GetPhonon();

		if (e3dSoundType == ASURA_SOUND_RANDOMPOS)
		{
			#if defined(ASURA_USE_AUDIO_SYSTEM)
			if( pxPhonon && pxVoice )
			{
				pxVoice->SetPos(pxPhonon->GetPosition());
				// falloff type
				bool bUseCuboidFalloff = pxPhonon->GetUseCuboidFalloff();
				if (bUseCuboidFalloff)
				{
					const Asura_Quat& xOrient = (pxPhonon->GetUseOrient() ? pxPhonon->GetOrient() : Asura_Maths::IdentityQuat);
					pxVoice->SetCuboid(pxPhonon->GetInnerCuboidRadius(), pxPhonon->GetOuterCuboidRadius(), xOrient);
				}
				else
				{
					pxVoice->SetRadii(pxPhonon->GetInnerRadius(), pxPhonon->GetOuterRadius());
				}
			}
			#else
			if (!xParams.m_bForcedPan)
			{
				float fRandomAngle = Asura_Random::GetFloat(0.0f, Asura_Maths::Deg360);
				float fPan, fFade;
				Asura_Maths::SinCos(fRandomAngle, fPan, fFade);
				// remap range [-1.0f,1.0f] to range [0.0f,1.0f]
				fPan = (fPan + 1.0f) * 0.5f;
				fFade = (fFade + 1.0f) * 0.5f;

#if !defined(ASURA_USE_AUDIO_SYSTEM)
				Asura_Sound_System::SetSoundForcedPanAndFade(m_iSoundSystemHandle, fPan, fFade);
#endif
			}
			#endif
		}
		else if (e3dSoundType != ASURA_SOUND_DIRECT)
		{
			if (pxPhonon)
			{
				m_bSoundUsesPosition = true;

#if defined(ASURA_USE_AUDIO_SYSTEM)
				if (pxVoice)
				{
					pxVoice->SetPos(pxPhonon->GetPosition());

					// falloff type
					bool bUseCuboidFalloff = pxPhonon->GetUseCuboidFalloff();
					if (bUseCuboidFalloff)
					{
						const Asura_Quat& xOrient = (pxPhonon->GetUseOrient() ? pxPhonon->GetOrient() : Asura_Maths::IdentityQuat);
						pxVoice->SetCuboid(pxPhonon->GetInnerCuboidRadius(), pxPhonon->GetOuterCuboidRadius(), xOrient);
					}
					else
					{
						pxVoice->SetRadii(pxPhonon->GetInnerRadius(), pxPhonon->GetOuterRadius());
					}
				}
#else
				Asura_Sound_System::SetSoundPosition(m_iSoundSystemHandle, pxPhonon->GetPosition());
				
				// falloff type
				bool bUseCuboidFalloff = pxPhonon->GetUseCuboidFalloff();
				if (bUseCuboidFalloff)
				{
					Asura_Sound_System::SetSoundCuboidParameters(m_iSoundSystemHandle, pxPhonon->GetInnerCuboidRadius(), pxPhonon->GetOuterCuboidRadius());
				}
				else
				{
					Asura_Sound_System::SetSoundRadius(m_iSoundSystemHandle, pxPhonon->GetInnerRadius(), pxPhonon->GetOuterRadius());
				}
				Asura_Sound_System::SetSoundUseCuboidFalloff(m_iSoundSystemHandle, bUseCuboidFalloff);

				// orient
				bool bUseOrient = pxPhonon->GetUseOrient();
				Asura_Sound_System::SetSoundUseOrient(m_iSoundSystemHandle, bUseOrient);
				if (bUseOrient)
				{
					Asura_Sound_System::SetSoundOrient(m_iSoundSystemHandle, pxPhonon->GetOrient());
				}
#endif
			}
			else
			{
				if (m_pxOwner->GetUsePosition())
				{
					m_bSoundUsesPosition = true;
#if defined(ASURA_USE_AUDIO_SYSTEM)
					if (pxVoice)
					{
						pxVoice->SetPos(m_pxOwner->GetPosition());
						pxVoice->SetRadii(m_pxEvent->GetInnerRadius(), m_pxEvent->GetOuterRadius());
					}
#else
					Asura_Sound_System::SetSoundPosition(m_iSoundSystemHandle, m_pxOwner->GetPosition());
					Asura_Sound_System::SetSoundRadius(m_iSoundSystemHandle, m_pxEvent->GetInnerRadius(), m_pxEvent->GetOuterRadius());
#endif
				}
			}
			
#if !defined(ASURA_USE_AUDIO_SYSTEM)
			bool bIsSpatial = e3dSoundType != ASURA_SOUND_NOPAN;
			Asura_Sound_System::SetSoundSpatial(m_iSoundSystemHandle, bIsSpatial);
#endif
		}

#if defined(ASURA_USE_AUDIO_SYSTEM)
		
		if(pxVoice)
		{
			switch( e3dSoundType )
			{
				case ASURA_SOUND_POINT:
				{
					pxVoice->SetSpeakerMappingType( ASURA_ASMT_SPACIAL );
					pxVoice->SetHasPosition( true );
				}
				break;

				case ASURA_SOUND_SOLID:
				{
					pxVoice->SetSpeakerMappingType( ASURA_ASMT_SPACIAL );
					pxVoice->SetHasPosition( true );
					pxVoice->SetInnerRadiusDoesNotSpacialise( true );
				}
				break;

				case ASURA_SOUND_NOPAN:
				{
					pxVoice->SetSpeakerMappingType( ASURA_ASMT_DIRECT );
					pxVoice->SetHasPosition( true );
				}
				break;

				case ASURA_SOUND_DIRECT:
				{
					if( xParams.m_bForcedPan )
					{
						pxVoice->SetSpeakerMappingType( ASURA_ASMT_FIXED_SPACIAL );
						pxVoice->SetFixedSoundFieldPos( Asura_Vector_2( xParams.m_xSurroundPan.m_fPan * 2.0f - 1.0f, xParams.m_xSurroundPan.m_fFade * 2.0f - 1.0f ) );
					}
					else
					{
						pxVoice->SetSpeakerMappingType( ASURA_ASMT_DIRECT );
					}
					pxVoice->SetHasPosition( false );
				}
				break;

				case ASURA_SOUND_RANDOMPOS:
				{
					pxVoice->SetSpeakerMappingType( ASURA_ASMT_FIXED_SPACIAL );
					pxVoice->SetHasPosition( true );

					// Pick a random angle to play this from
					float fRandomAngle = Asura_Random::GetFloat(0.0f, Asura_Maths::Deg360);
					float fPan, fFade;
					Asura_Maths::SinCos(fRandomAngle, fPan, fFade);
					
					pxVoice->SetFixedSoundFieldPos( Asura_Vector_2( fPan , fFade ) );
				}
				break;

				default:
				{
					Asura_Assert( false, "This sound event type is no longer supported" );
				}
			}
		
			const Asura_Hash_ID uChannel = (xParams.m_pxMixingChannel ? xParams.m_pxMixingChannel->GetHashID() : NULL);
			pxVoice->SetMixerChannel(uChannel);
		}
#endif

		// bounding box
		if(pxPhonon)
		{
			if (pxPhonon->GetUseClipVolume())
			{
#if defined(ASURA_USE_AUDIO_SYSTEM)
				if(pxVoice)
				{
					pxVoice->SetInvertClipVolume(pxPhonon->GetInvertClipVolume());
					pxVoice->SetClipVolume(pxPhonon->GetClipVolume());
				}
#else
				Asura_Sound_System::SetSoundInvertClipVolume(m_iSoundSystemHandle, pxPhonon->GetInvertClipVolume());
				Asura_Sound_System::SetSoundClipVolume(m_iSoundSystemHandle, pxPhonon->GetClipVolume());
#endif
			}
		}
			
		if (m_fRemainingDelayTime == 0.0f)
		{
#if defined(ASURA_USE_AUDIO_SYSTEM)
			if (pxVoice)
			{
				pxVoice->Start();
			}
#else
			Asura_Sound_System::PlaySound(m_iSoundSystemHandle, m_bSoundIsLooping);
#endif
		}
	}
}

bool Asura_Sound_Event_Controller::IsLooping() const
{
#if 1
	return m_pxEvent->IsEndlessPlayback();
#else
	if (m_pxEvent->IsLooping())
	{
		return true;
	}

	for (u_int u = 0; u < uASURA_SOUND_EVENT_MAX_FOR_SIMULTANEOUS_PLAY; ++u)
	{
		if (m_apxNextController[u])
		{
			if (m_apxNextController[u]->IsLooping())
			{
				return true;
			}
		}
		else
		{
			break;
		}
	}

	if (m_iSoundSystemHandle != ASURA_INVALID_HANDLE)
	{
		return Asura_Sound_System::SoundIsLooping(m_iSoundSystemHandle);
	}
	return false;
#endif
}

void Asura_Sound_Event_Controller::VolumeScaleUpdated( float fNewVolScale )
{
	if (m_iSoundSystemHandle != ASURA_INVALID_HANDLE)
	{
		float fMixerVolumeScale = Asura_Sound_Mixer_System::GetMixerVolume(m_xPlayParams.m_pxMixingChannel);
		const float fVolume = m_fSoundPlayVolume * fNewVolScale * fMixerVolumeScale;

#if defined(ASURA_USE_AUDIO_SYSTEM)
		Asura_Audio_Voice* const pxVoice = Asura_Audio_System::GetVoice(m_iSoundSystemHandle);
		if (pxVoice)
		{
			pxVoice->SetVolume(fVolume);
		}
#else
		Asura_Sound_System::SetSoundVolume(m_iSoundSystemHandle, fVolume);
#endif
	}
}

void Asura_Sound_Event_Controller::PitchScaleUpdated( float fNewPitchScale )
{
	if (m_iSoundSystemHandle != ASURA_INVALID_HANDLE)
	{
		const float fPitch = m_fSoundPlayPitch * fNewPitchScale;

#if defined(ASURA_USE_AUDIO_SYSTEM)
		Asura_Audio_Voice* const pxVoice = Asura_Audio_System::GetVoice(m_iSoundSystemHandle);
		if (pxVoice)
		{
			pxVoice->SetPitch(fPitch);
		}
#else
		Asura_Sound_System::SetSoundPitch(m_iSoundSystemHandle, fPitch);
#endif
	}
}

void Asura_Sound_Event_Controller::PositionUpdated( const Asura_Vector_3& xNewPos )
{
	if (m_iSoundSystemHandle != ASURA_INVALID_HANDLE && m_bSoundUsesPosition)
	{
#if defined(ASURA_USE_AUDIO_SYSTEM)
		Asura_Audio_Voice* const pxVoice = Asura_Audio_System::GetVoice(m_iSoundSystemHandle);
		if (pxVoice)
		{
			pxVoice->SetPos(xNewPos);
		}
#else
		Asura_Sound_System::SetSoundPosition(m_iSoundSystemHandle, xNewPos);
#endif
	}
}

#if defined(ASURA_TOOLS) || defined(ASURA_USE_MULTIPLE_HANDLERS)
void Asura_Sound_Event_Controller::SoundEventDeleted(const Asura_Sound_Event* pxEventDeleted)
{
	for (u_int u = 0; u < uASURA_SOUND_EVENT_MAX_FOR_SIMULTANEOUS_PLAY; ++u)
	{
		if (m_apxNextController[u])
		{
			m_apxNextController[u]->SoundEventDeleted(pxEventDeleted);
		}
		else
		{
			break;
		}
	}
	if (pxEventDeleted == m_pxEvent)
	{
		StopPlaying();
	}
}
#endif	// defined(ASURA_TOOLS) || defined(ASURA_USE_MULTIPLE_HANDLERS)

// GetNumSubControllers() recursively works out how many controllers are owned by this one.
u_int Asura_Sound_Event_Controller::GetNumSubControllers() const
{
	u_int uNum = 0;
	for (u_int u = 0; u < uASURA_SOUND_EVENT_MAX_FOR_SIMULTANEOUS_PLAY; ++u)
	{
		if (m_apxNextController[u])
		{
			uNum += 1 + m_apxNextController[u]->GetNumSubControllers();
		}
		else
		{
			break;
		}
	}
	return uNum;
}

bool Asura_Sound_Event_Controller::IsUsingVoice() const
{
#if defined(ASURA_USE_AUDIO_SYSTEM)
	return true;
#else
	return Asura_Sound_System::SoundHasVoiceAllocated(m_iSoundSystemHandle);
#endif
}

void Asura_Sound_Event_Controller::InitialiseVariablePlayback(const Asura_Sound_PlayParams* pxPlayParams, const Asura_Sound_Sample* pxSample)
{
	//allocate storage if not done so yet
	if(!m_pxVariablePlayParams) m_pxVariablePlayParams = new Asura_Sound_VariablePlayParams;

	//grab the initial volume/pitch settings
	m_pxVariablePlayParams->m_fPitch = pxPlayParams->m_fPitch;
	m_pxVariablePlayParams->m_fVolume = pxPlayParams->m_fVolume;
	m_pxVariablePlayParams->m_fTargetPitch = pxPlayParams->m_fPitch;
	m_pxVariablePlayParams->m_fTargetVolume = pxPlayParams->m_fVolume;

	//get the volume/pitch scale from the sample
	m_pxVariablePlayParams->m_fSampleVolume = pxSample->m_fVolume;
	m_pxVariablePlayParams->m_fSamplePitch = pxSample->m_fPitch;

	//set timers so a new value will be chosen on the next update
	m_pxVariablePlayParams->m_fVolumeTimer = 0.0f;
	m_pxVariablePlayParams->m_fPitchTimer = 0.0f;

	//initialise rate of change values (doesn't really matter since they will be changed on the next update anyway)
	m_pxVariablePlayParams->m_fVolumeRateOfChange = 0.0f;
	m_pxVariablePlayParams->m_fPitchRateOfChange = 0.0f;

}

//eof
