//
// Asura_Sound.cpp
//
// Created by Kevin Lea - 12/06/2001 11:12:47
// 
// Copyright (C) 2001 Rebellion, All Rights Reserved.
//

// core
#include "Core/Asura.h"

#ifndef ASURA_USE_AUDIO_SYSTEM

// my header
#include "Sound/Asura_Sound.h"

// library headers
#include <limits.h>             // USHRT_MAX

// engine headers
#include "Camera/Asura_Camera.h"
#include "Core/Asura_LoS.h"
#include "Core/Asura_LOS_Penetration.h"
#include "Core/Asura_String.h"
#include "Core/Asura_Timers.h"       // for GameTimeScale
#include "Core/Asura_HandleTreeTemplate.h"
#include "Core/Asura_Maths.h"
#include "Core/Asura_HashTree.h"
#include "Debugging/Asura_Assert.h"
#include "Debugging/Asura_CPUProfile.h"
#include "Debugging/Asura_DebugOverlay.h"
#include "Debugging/Asura_Debug_Timers.h"
#include "Debugging/Asura_Logfile.h"
#include "FileAccess/Asura_FileAccess.h"   // to skip unused resource file chunks
#ifdef ASURA_SUPPORT_COMMAND_CONSOLE
#include "Interface/Asura_CommandConsole.h"
#endif
#include "Sound/Asura_Dynamic_Music_System.h"
#include "Sound/Asura_Music.h"
#include "Sound/Asura_MetaMusic_System.h"
#include "Sound/Asura_ModuleReverb_System.h"
#include "Sound/Asura_Phonon.h"
#include "Sound/Asura_ReverbVolume_System.h"
#include "Sound/Asura_Sound_Event_System.h"
#include "Sound/Asura_Sound_Event_Storage.h"
#include "Sound/Asura_Sound_Mixer.h"
#ifdef ASURA_USE_DIALOGUE_SYSTEM
#include "Sound/Asura_Dialogue_System.h"
#include "Sound/Asura_Dialogue.h"
#endif
#ifdef ASURA_USE_REVERBVOLUME_SYSTEM
#include "Sound/Asura_ReverbVolume_System.h"
#endif
#include "Sound/Asura_StreamingBackgroundSound_System.h"
#ifdef ASURA_TOOLS
#include "Tools_System.h"
#endif
#ifdef ASURA_USE_DEBUGOVERLAY
#include "Rendering/Asura_Viewport.h"
#endif
#include "Volume/Asura_VolumeGroup.h"

#if defined(ASURA_USE_STREAMING)
#include "Streaming/Asura_Streaming_System.h"
#endif //defined(ASURA_USE_STREAMING)

// M A C R O S ///////////////////////////////////////////////////////////////
#define Asura_Sound_OutputToDebugger(params) { if (dbg_bSoundInfo) { Asura_OutputToDebugger(params); } }
#define Asura_SoundStream_OutputToDebugger(params) { if (dbg_bSoundStreamInfo) { Asura_OutputToDebugger(params); } }

//#define ASURA_SOUND_USE_MODULE_REVERB		(1)

#ifndef ASURA_SOUND_SPEED_OF_SOUND			// Can be redefined per project if required
#define ASURA_SOUND_SPEED_OF_SOUND			343.3f
#endif

// G L O B A L S /////////////////////////////////////////////////////////////

DEBUGVAR bool dbg_bSoundInfo = false;
DEBUGVAR bool dbg_bSoundStreamInfo = false;
DEBUGVAR u_int dbg_uNumSilentFramesBeforeStopSound = 10;

static class Asura_Sound_StreamInfo* g_pxStreamInfo;
DEBUGVAR u_int dbg_uMaxLoadedSoundActiveInstances = 6;
static u_char* g_ucLoadedSoundNumActiveInstances;	// if more than the max are played you won't hear them.
static float g_fMusicVolBackup = 0.0f;	// for mute
static float g_fSoundFXVolBackup = 0.0f;

static int g_iFailedSoundsSize = 0;
static int g_iFailedReplaceableSoundsSize = 0;
const char* g_szFailedSoundLogfile = "logfiles\\failed_sounds_log.txt";

static Asura_TickCount g_xTicksSpentUpdatingOcclusions = 0;
static u_int g_uOcclusionCounter = 0;

const float fASURA_SOUND_DISTANCE_FILTER_MINIMUM_DISTANCE	= 10.0f;	//Minimum distance past inner radius to apply distance filtering
const float fASURA_SOUND_DISTANCE_FILTER_MAXIMUM_DISTANCE	= 100.0f;	//Distance at which distance filtering is applied maximally

#ifdef ASURA_DEBUG_VARIABLE_SYSTEM
	static float g_fDopplerFactor			= 1.0f;
	static float g_fSpeedOfSound			= ASURA_SOUND_SPEED_OF_SOUND;
	#define CONSOLE_VARNAME_DOPPLER_FACTOR	L"Audio.Settings.DopplerFactor"
	#define CONSOLE_VARNAME_SPEED_OF_SOUND	L"Audio.Settings.SpeedOfSound"
	static bool g_bUseEnvironmentOcclusion	= true;
	static bool g_bUseObjectOcclusion		= true;
	#define CONSOLE_VARNAME_OCCLUSION_ENVIRONMENT	L"Audio.Occlusion.Environment"
	#define CONSOLE_VARNAME_OCCLUSION_OBJECTS		L"Audio.Occlusion.Objects"
	static float g_fDistanceFilterRangeMax = fASURA_SOUND_DISTANCE_FILTER_MAXIMUM_DISTANCE;
	static float g_fDistanceFilterRangeMin = fASURA_SOUND_DISTANCE_FILTER_MINIMUM_DISTANCE;
	#define CONSOLE_VARNAME_DISTANCE_FILTER_RANGE_MIN L"Audio.Distance.MinRange"
	#define CONSOLE_VARNAME_DISTANCE_FILTER_RANGE_MAX L"Audio.Distance.MaxRange"
#else
	static const float g_fDopplerFactor		= 1.0f;
	static const float g_fSpeedOfSound		= ASURA_SOUND_SPEED_OF_SOUND;
#endif

// S T A T I C   F U N C T I O N S ///////////////////////////////////////////

#ifndef ASURA_USE_AUDIO_SYSTEM	// If the audio system is enabled this mute command is handled through the mixer system
#ifdef ASURA_SUPPORT_COMMAND_CONSOLE
#define CONSOLE_CMDNAME_MUTE		L"Mute"
static bool ConsoleCmd_Mute( bool bHelp )
{
	if (bHelp)
	{
		Asura_CommandConsole::Printf( CONSOLE_CMDNAME_MUTE L": mutes / unmutes all sound and music, independently of game options settings" );
	}
	else
	{
		bool bMuteIsOn = Asura_Sound_System::GetMuteMusic() && Asura_Sound_System::GetMuteSoundFX();
		bool bMute = !bMuteIsOn;
		Asura_Sound_System::SetMuteMusic(bMute);
		Asura_Sound_System::SetMuteSoundFX(bMute);
		Asura_CommandConsole::Printf( CONSOLE_CMDNAME_MUTE L" %s", bMute ? L"on" : L"off" );
	}
	return true;
}
#endif
#endif // ASURA_USE_AUDIO_SYSTEM

// C O N S T A N T S /////////////////////////////////////////////////////////

const float fASURA_INVALID_OCCLUSION_VALUE = -1.0f;
const float fASURA_WET_VOLUME_OCCLUSION_FACTOR = 0.5f; //Scale the wet volume for occluded sounds by this much

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

// 3d sounds will be at full volume inside the inner_range, and will fade out 
// linearly to 0 at the outer edge. Linear in this case means from 1.0 to 0.0
// as the parameter to the Asura_Sound::SetVolume() function. Volume scales
// and perceived loudness on your sound and platform may vary.

struct Asura_Spherical3dSoundData
{
	float m_fInnerRange;
	float m_fOuterRange;

    void Initialise()
    {
        m_fInnerRange = 0.0f;
        m_fOuterRange = 0.0f;
    }
};

struct Asura_Cuboid3dSoundData
{
    Asura_Vector_3 m_xInnerRanges;
    Asura_Vector_3 m_xOuterRanges;

    void Initialise()
    {
        m_xInnerRanges = Asura_Maths::ZeroVector3;
        m_xOuterRanges = Asura_Maths::ZeroVector3;
    }
};
    

struct Asura_3dSoundData
{
    Asura_Vector_3 m_xPosition;
	Asura_Vector_3 m_xVelocity;
    // these are never used together - candidates for a union?
    Asura_Spherical3dSoundData m_xSphericalData;
    Asura_Cuboid3dSoundData m_xCuboidData;

	const Asura_VolumeGroup* m_pxClipVolume;
	Asura_Quat m_xInverseOrient;
	float m_fMinVolume;		// sound is never faded down beyond this.

    void Initialise()
    {
        m_xPosition = Asura_Maths::ZeroVector3;
		m_xVelocity = Asura_Maths::ZeroVector3;
        m_xSphericalData.Initialise();
        m_xCuboidData.Initialise();
		m_pxClipVolume = 0;

		m_xInverseOrient.x = 0.0f;
		m_xInverseOrient.y = 0.0f;
		m_xInverseOrient.z = 0.0f;
		m_xInverseOrient.w = 1.0f;

		m_fMinVolume = 0.0f;
    }
};

class Asura_ActiveSoundSample
{
public:

    Asura_ActiveSoundSample()
    {
        Initialise(ASURA_SID_NO_SOUND, ASURA_INVALID_LOADED_SOUND_INDEX);
    }

 
    void Initialise(Asura_Sound_ID eSoundID, int iLoadedSoundIndex)
    {
		Asura_SoundProperties* pxSoundProperties = Asura_Sound_System::GetSoundPropertiesByIndex(iLoadedSoundIndex);

        m_iPlayingSoundHandle = ASURA_INVALID_HANDLE;
        m_eSoundID = eSoundID;
        m_iLoadedSoundIndex = iLoadedSoundIndex;
		if(pxSoundProperties)
		{
			m_fVolume = pxSoundProperties->m_fVolumeScale;
			m_fPitch = pxSoundProperties->m_fPitchScale;
		}
		else
		{
        	m_fVolume = m_fPitch = 1.0f;
		}
		m_fPitchDoppler = 1.0f;
		m_uFlags = 0;
		// These flags default to true
		m_bUse3dPan = 1;
		m_bPitchUsesTimeScale = 1;
		m_bUseDistanceFilter = 1;
		m_bUseReverb = 1;
		m_bUseDoppler = 1;
		m_bUseOcclusion = 1;
		m_fWetVolume = 1.0f;
		m_fDryVolume = 1.0f;
		m_fOcclusion = fASURA_INVALID_OCCLUSION_VALUE;
		m_fDesiredOcclusion = 0.0f;
		m_uOcclusionCounter = 0;
		m_bOcclusionSameReverbZone = false;
        m_x3dData.Initialise();
    }


    Asura_Handle m_iPlayingSoundHandle;
    Asura_Sound_ID m_eSoundID;
    int m_iLoadedSoundIndex;
    float m_fVolume;		// 0.0 - 1.0
	float m_fPitch;			// 0.0 - 1.0
	float m_fPitchDoppler;	// > 0.0
	float m_fForcedPan;		// if m_bForcedPanAndFade is set these are valid.
	float m_fForcedFade;	// pan and fade are from 0.0 to 1.0 (fade is 0.0 = back, 1.0 = front)
	float m_fOcclusion;		// 0.0 - 1.0 : to what degree is this sound occluded from the player
	float m_fDesiredOcclusion;	//0.0 - 1.0 : tend towards this occluded value
	float m_fWetVolume;		// 0.0 - 1.0 reverberated sound component
	float m_fDryVolume;		// 0.0 - 1.0 unreverberated
	bool m_bOcclusionSameReverbZone; //true if the camera is in the same reverb zone as the sound (only for occlusion)
	u_int m_uOcclusionCounter; //keep track of occlusion updates

	union
	{
		u_int m_uFlags;
		struct
		{
			u_int m_ucNumSilentFrames:8;    // to help with voice management
			u_int m_bLooping:1;
			u_int m_bUse3dVol:1;
			u_int m_bUse3dPan:1;
			u_int m_bUse3dCuboid:1;
			u_int m_bNoExternalHandle:1;  // manage sound internally
			u_int m_bUseHardPan:1;        // for split screen
			u_int m_uHardPanID:2;         // cameraID 0-3
			u_int m_bForcedPanAndFade:1;
			u_int m_bPaused:1;
			u_int m_bInvertClipVolume:1;
			u_int m_bUseOrient:1;
			u_int m_bUseEQ:1;	 // is EQ enabled for this sound? set by default for 3d sounds.
			u_int m_bPitchUsesTimeScale : 1; // If false, sound ignores time scale. If true it behaves as previously (so will still ignore time scale if the overall bool is false).
			// May want to change the previous bool so additionally a sound can use time scale even when all others are not.
			u_int m_bUseReverb : 1;
			u_int m_bUseDistanceFilter: 1;
			u_int m_bUseDoppler: 1;
			u_int m_bUseOcclusion: 1;
			u_int Unused : 5;
		};
	};
    Asura_3dSoundData m_x3dData;
};


class Asura_Sound_StreamInfo
{
public:
    
    Asura_Sound_StreamInfo() { m_bPlaying = false; }

    bool m_bPlaying;
};

class Asura_Sound_Penetration : public Asura_Penetration
{
public:

	Asura_Sound_Penetration()
	{
		m_iMaxEnvLoS = 0;
		m_iMaxEntityLoS = 0;
	}

	//We don't care about the stopping power, it isn't relevant
#if defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM
	float GetStoppingPower(Asura_Hash_ID uMaterialResponseHashID) const
	{
		return 0.0f;
	}
#else // !defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM
	virtual float GetStoppingPower(short sMaterialID) const
	{
		return 0.0f;
	}
#endif // !defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM

	float GetStoppingPower( const Asura_LOS_OutputData& xOutput, unsigned short usClassification) const
	{
		return 0.0f;
	}

	int GetMaxEnvironmentLoSHits () const
	{
		return m_iMaxEnvLoS;
	}
	int GetMaxEntityLoSHits () const
	{
		return m_iMaxEntityLoS;
	}

	void SetMaxEnvironmentLoSHits (const int iHits)
	{
		m_iMaxEnvLoS = iHits;
	}
	void SetMaxEntityLoSHits (const int iHits)
	{
		m_iMaxEntityLoS = iHits;
	}
	
	void SetDefaults ()
	{
		m_iMaxEnvLoS	= 4;
		m_iMaxEntityLoS = 4;
	}

private:

	int m_iMaxEnvLoS;
	int m_iMaxEntityLoS;
};

static Asura_Sound_Penetration g_xPenetrationProps;

// F U N C T I O N S /////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////
//
// Asura_Sound_Handler
//
//////////////////////////////////////////////////////////////////////////////

#if !defined(ASURA_TOOLS)
Asura_Handle_List	Asura_Sound_Handler::s_xLoadedIndexList(ASURA_MAX_NUMBER_OF_LOADED_SOUNDS);
#endif	// !defined(ASURA_TOOLS)


Asura_Sound_Handler::Asura_Sound_Handler (void)
{
}

void Asura_Sound_Handler::Reset(void)
{
	// Unload the sounds...
#if !defined(ASURA_TOOLS)
	m_xSoundIDToLoadedIndexTree.Traverse(Asura_Sound_System::TraverseUnloadSound);
	m_xSoundIDToLoadedIndexTree.Reset();
#endif

	m_xHashToIDTree.Reset();
}

bool Asura_Sound_Handler::AddToHashToIDTree(Asura_Hash_ID uHash, int iSoundID) 
{
#if defined(ASURA_USE_MULTIPLE_HANDLERS)
	if (m_xHashToIDTree.GetDataByHash(uHash) == NULL) //Check parent trees
#endif		
	{
		if (m_xHashToIDTree.AddNode(uHash, iSoundID))
		{
			return true;
		}
	}

	return false;
}

#if defined(ASURA_TOOLS)
Asura_Sound_Handler::~Asura_Sound_Handler (void)
{
}
#else
Asura_Sound_Handler::~Asura_Sound_Handler()
{
	Asura_Sound_Handler* pxOldHandler = Asura_Sound_System::s_pxCurrentHandler;

	// Unload the sounds...
	Asura_Sound_System::s_pxCurrentHandler = this;
	{
		m_xSoundIDToLoadedIndexTree.Traverse(Asura_Sound_System::TraverseUnloadSound);
		m_xSoundIDToLoadedIndexTree.Reset();
	}
	Asura_Sound_System::s_pxCurrentHandler = pxOldHandler;
}

bool Asura_Sound_Handler::Add (const Asura_Sound_ID eSoundID, int& iIndex)
{
	bool	bDidExist = true;
	int*	piIndex	= m_xSoundIDToLoadedIndexTree.AddOrFindNode(eSoundID, &bDidExist, true);

	// Were we able to add our index? - Or was it already there?
	if((piIndex == NULL) || bDidExist)
	{
		return(false);
	}
	else if(s_xLoadedIndexList.GetNewHandle(iIndex))
	{
		// Successful in adding our new node...
		*piIndex	= iIndex;
		return(true);
	}
	else
	{
		if( s_xLoadedIndexList.GetNumberOfActiveHandles() >= s_xLoadedIndexList.GetTotalNumberOfHandles() )
		{
			// Too many sounds loaded, assert!
			Asura_Assertf(false, ("Number of sounds exceeds ASURA_MAX_NUMBER_OF_LOADED_SOUNDS (%d).", ASURA_MAX_NUMBER_OF_LOADED_SOUNDS));
		}
		else
		{
			// Node already exists - cannot add it a second time...
			Asura_Assertf(false, ("Loaded index %d is already in the unique sound list.", iIndex));
		}

		m_xSoundIDToLoadedIndexTree.DeleteNodeByHash(eSoundID);
		return(false);
	}
}

bool Asura_Sound_Handler::Delete (const Asura_Sound_ID eSoundID)
{
	int*	piIndex	= m_xSoundIDToLoadedIndexTree.GetDataByHash(eSoundID);

	// Did we find our sound-ID?
	if(piIndex == NULL)
	{
		return(false);
	}
	else
	{
		s_xLoadedIndexList.ReleaseHandle( *piIndex );

        // We've already retrieved the data above so deleting it shouldn't
        // fail.
#ifdef ASURA_ASSERT
        bool bDeletedOK = 
#endif
		    m_xSoundIDToLoadedIndexTree.DeleteNodeByHash( eSoundID );
        Asura_Assert( bDeletedOK, "" );

        return true;
	}
}

#endif	// defined(ASURA_TOOLS)

//////////////////////////////////////////////////////////////////////////////
//
// Asura_Sound_System
//
//////////////////////////////////////////////////////////////////////////////

// S O U N D   S Y S T E M   M A N A G E M E N T /////////////////////////////

#if defined(ASURA_TOOLS)
Asura_Sound_Handle_Tree	Asura_Sound_System::s_xSoundIDToIndexTree (ASURA_MAX_NUMBER_OF_LOADED_SOUNDS);
#endif	// defined(ASURA_TOOLS)

Asura_Sound_Handler*	Asura_Sound_System::s_pxCurrentHandler	= NULL;
#if defined(ASURA_USE_MULTIPLE_HANDLERS)
Asura_TemplateHashTree<Asura_Sound_Handler>	Asura_Sound_System::s_xHandlerTree;
#else
Asura_Sound_Handler	Asura_Sound_System::s_xPermanentHandler;	// handler for permanent sounds in non-streaming builds.
Asura_Sound_Handler	Asura_Sound_System::s_xResettableHandler;	// handler for resettable sounds non-streaming builds.

bool	Asura_Sound_System::s_bSetResetPointDuringInitialisation	= true;
#endif	//defined(ASURA_USE_MULTIPLE_HANDLERS)

#if ASURA_SOUND_STORE_FILENAMES
Asura_String*   Asura_Sound_System::s_pxSoundFilenames    = NULL;
Asura_Sound_ID* Asura_Sound_System::s_peNamedSoundIDArray = NULL;
#endif

bool                                Asura_Sound_System::s_bUseAutomaticSoundCentre = true;
int                                 Asura_Sound_System::s_iNumSplitScreens = 0;
Asura_Vector_3                      Asura_Sound_System::s_axSoundCentre[ASURA_SOUND_MAX_SPLIT_SCREENS];
Asura_Matrix_3x3                    Asura_Sound_System::s_axSoundOrientation[ASURA_SOUND_MAX_SPLIT_SCREENS];
Asura_Vector_3						Asura_Sound_System::s_axSoundVelocity[ASURA_SOUND_MAX_SPLIT_SCREENS];

Asura_ActiveSoundSample*            Asura_Sound_System::s_pxActiveSoundList;
Asura_Tracking_Handle_List*         Asura_Sound_System::s_pxActiveSoundHandleList;
Asura_Tracking_Handle_List*         Asura_Sound_System::s_pxPlayingSoundHandleListWithEQ;
Asura_Tracking_Handle_List*         Asura_Sound_System::s_pxPlayingSoundHandleListWithoutEQ;
int                                 Asura_Sound_System::s_iMaxPlayingSounds;
int                                 Asura_Sound_System::s_iMaxPlayingSoundsWithEQ;
Asura_Sound_System::SoundOutputType Asura_Sound_System::s_eSoundOutputType = Asura_Sound_System::SoundOutputStereo;
Asura_Sound_System::ReverbType		Asura_Sound_System::s_eCurrentReverbType = Asura_Sound_System::ASURA_REVERB_ID_NONE;
float								Asura_Sound_System::s_fCurrentReverbAmount = 1.0f;
Asura_Sound_System::EQType			Asura_Sound_System::s_aeEQType[ASURA_SOUND_MAX_NUMBER_OF_EQ_FILTERS];
float								Asura_Sound_System::s_afEQLevel[ASURA_SOUND_MAX_NUMBER_OF_EQ_FILTERS];

bool								Asura_Sound_System::s_bUseHardware3dPan = false;// on PC this gets set to true for doing 3d mixing

float                               Asura_Sound_System::s_fPitchScale = 1.0f;
bool                                Asura_Sound_System::s_bPitchUsesGameTimeScale = true;
bool								Asura_Sound_System::s_bMuteSoundFX = false;
bool								Asura_Sound_System::s_bMuteMusic = false;
bool								Asura_Sound_System::s_b3dSoundEQDefault = true;
Asura_Tracking_Handle_List*			Asura_Sound_System::s_pxEQHandleList;

Asura_Tracking_Handle_List*			Asura_Sound_System::s_pxStreamingSoundList;
int									Asura_Sound_System::s_iNumberOfStreamingSounds;

Asura_SoundProperties*				Asura_Sound_System::s_pxSoundProperties;
u_int								Asura_Sound_System::s_uNumSystemPauses = 0;

u_int								Asura_Sound_System::s_uNextDynamicSIDIndex = 1;

Asura_Sound_OcclusionFactor_Callback		Asura_Sound_System::s_pfnGetEntityOcclusionFactor		= 0;
Asura_Sound_OcclusionFactor_Callback		Asura_Sound_System::s_pfnGetEnvironmentOcclusionFactor	= 0;

Asura_TickCount Asura_Sound_System::s_xOcclusionCPUTicksPerFrame = 0;	//Dont spend any time occluding by default

Asura_FixedArray<Asura_Guid,ASURA_MAX_NO_OF_CAMERAS> Asura_Sound_System::s_auOcclusionGUIDsToIgnore;

#ifdef ASURA_TOOLS
const char* Asura_Sound_System::s_aszReverbNames[] =
{
	"NONE",						
	"GENERIC",					
	"PADDEDCELL",				
	"ROOM",						
	"BATHROOM",					
	"LIVINGROOM",				
	"STONEROOM",					
	"AUDITORIUM",				
	"CONCERTHALL",				
	"CAVE",						
	"ARENA",						
	"HANGAR",					
	"CARPETTEDHALLWAY",			
	"HALLWAY",					
	"STONECORRIDOR",				
	"ALLEY",						
	"FOREST",					
	"CITY",						
	"MOUNTAINS",					
	"QUARRY",					
	"PLAIN",						
	"PARKINGLOT",				
	"SEWERPIPE",					
	"UNDERWATER",				
	"DRUGGED",					
	"DIZZY",						
	"PSYCHOTIC",					
	"CASTLE_SMALLROOM",			
	"CASTLE_SHORTPASSAGE",		
	"CASTLE_MEDIUMROOM",			
	"CASTLE_LONGPASSAGE",		
	"CASTLE_LARGEROOM",			
	"CASTLE_HALL",				
	"CASTLE_CUPBOARD",			
	"CASTLE_COURTYARD",			
	"CASTLE_ALCOVE",				
	"FACTORY_ALCOVE",			
	"FACTORY_SHORTPASSAGE",		
	"FACTORY_MEDIUMROOM",		
	"FACTORY_LONGPASSAGE",		
	"FACTORY_LARGEROOM",			
	"FACTORY_HALL",				
	"FACTORY_CUPBOARD",			
	"FACTORY_COURTYARD",			
	"FACTORY_SMALLROOM",			
	"ICEPALACE_ALCOVE",			
	"ICEPALACE_SHORTPASSAGE",	
	"ICEPALACE_MEDIUMROOM",		
	"ICEPALACE_LONGPASSAGE",		
	"ICEPALACE_LARGEROOM",		
	"ICEPALACE_HALL",			
	"ICEPALACE_CUPBOARD",		
	"ICEPALACE_COURTYARD",		
	"ICEPALACE_SMALLROOM",		
	"SPACESTATION_ALCOVE",		
	"SPACESTATION_MEDIUMROOM",	
	"SPACESTATION_SHORTPASSAGE",	
	"SPACESTATION_LONGPASSAGE",	
	"SPACESTATION_LARGEROOM",	
	"SPACESTATION_HALL",			
	"SPACESTATION_CUPBOARD",		
	"SPACESTATION_SMALLROOM",	
	"WOODEN_ALCOVE",				
	"WOODEN_SHORTPASSAGE",		
	"WOODEN_MEDIUMROOM",			
	"WOODEN_LONGPASSAGE",		
	"WOODEN_LARGEROOM",			
	"WOODEN_HALL",				
	"WOODEN_CUPBOARD",			
	"WOODEN_SMALLROOM",			
	"WOODEN_COURTYARD",			
	"SPORT_EMPTYSTADIUM",		
	"SPORT_SQUASHCOURT",			
	"SPORT_SMALLSWIMMINGPOOL",	
	"SPORT_LARGESWIMMINGPOOL",	
	"SPORT_GYMNASIUM",			
	"SPORT_FULLSTADIUM",			
	"SPORT_STADIUMTANNOY",		
	"PREFAB_WORKSHOP",			
	"PREFAB_SCHOOLROOM",			
	"PREFAB_PRACTISEROOM",		
	"PREFAB_OUTHOUSE",			
	"PREFAB_CARAVAN",			
	"DOME_TOMB",					
	"DOME_SAINTPAULS",			
	"PIPE_SMALL",				
	"PIPE_LONGTHIN",				
	"PIPE_LARGE",				
	"PIPE_RESONANT",				
	"OUTDOORS_BACKYARD",			
	"OUTDOORS_ROLLINGPLAINS",	
	"OUTDOORS_DEEPCANYON",		
	"OUTDOORS_CREEK",			
	"OUTDOORS_VALLEY",			
	"MOOD_HEAVEN",				
	"MOOD_HELL",					
	"MOOD_MEMORY",				
	"DRIVING_COMMENTATOR",		
	"DRIVING_PITGARAGE",			
	"DRIVING_INCAR_RACER",		
	"DRIVING_INCAR_SPORTS",		
	"DRIVING_INCAR_LUXURY",		
	"DRIVING_FULLGRANDSTAND",	
	"DRIVING_EMPTYGRANDSTAND",	
	"DRIVING_TUNNEL",			
	"CITY_STREETS",				
	"CITY_SUBWAY",				
	"CITY_MUSEUM",				
	"CITY_LIBRARY",				
	"CITY_UNDERPASS",			
	"CITY_ABANDONED",			
	"DUSTYROOM",					
	"CHAPEL",					
	"SMALLWATERROOM",			
};
#endif	// ASURA_TOOLS

static int g_iLoadedSoundIndexBeingUnloaded = -1;//used by TraverseDestroyUnloadedSound


void Asura_Sound_System::Initialise(void)
{
	ASURA_CPU_PROFILE_SCOPED_TIMER( "Asura_Sound_System::Initialise");
#if !defined(ASURA_USE_MULTIPLE_HANDLERS)
	s_pxCurrentHandler = &s_xPermanentHandler;
#if !defined ASURA_TOOLS
	s_xResettableHandler.m_xSoundIDToLoadedIndexTree.SetParent(&(s_xPermanentHandler.m_xSoundIDToLoadedIndexTree));
#endif // !defined ASURA_TOOLS
	s_xResettableHandler.m_xHashToIDTree.SetParent(&(s_xPermanentHandler.m_xHashToIDTree));
#endif	//!defined(ASURA_USE_MULTIPLE_HANDLERS)

#if ASURA_SOUND_STORE_FILENAMES
	s_pxSoundFilenames = new Asura_String[ASURA_MAX_NUMBER_OF_LOADED_SOUNDS];
	s_peNamedSoundIDArray = new Asura_Sound_ID[ASURA_MAX_NUMBER_OF_LOADED_SOUNDS];
	for(int i=0;i<ASURA_MAX_NUMBER_OF_LOADED_SOUNDS;i++)
	{
		s_peNamedSoundIDArray[i] = ASURA_SID_NO_SOUND;
	}
#endif

	s_iNumberOfStreamingSounds = 0;
    int iMaxStreamingSounds = Platform_GetMaxStreamingSounds();
	s_pxStreamingSoundList = new Asura_Tracking_Handle_List(iMaxStreamingSounds);
    g_pxStreamInfo = new Asura_Sound_StreamInfo[iMaxStreamingSounds];

	g_ucLoadedSoundNumActiveInstances = new u_char[ASURA_MAX_NUMBER_OF_LOADED_SOUNDS];
	for(int i=0;i<ASURA_MAX_NUMBER_OF_LOADED_SOUNDS;i++)
	{
		g_ucLoadedSoundNumActiveInstances[i] = 0;
	}
	
	s_iMaxPlayingSounds = PlatformInitialise();
	s_iMaxPlayingSoundsWithEQ = Platform_GetMaxSoundsWithEQ();
	Asura_Assert(s_iMaxPlayingSoundsWithEQ <= s_iMaxPlayingSounds, "unexpected value from Platform_GetMaxSoundsWithEQ");
    s_pxActiveSoundList = new Asura_ActiveSoundSample[ASURA_MAX_NUMBER_OF_ACTIVE_SOUNDS];
    s_pxActiveSoundHandleList = new Asura_Tracking_Handle_List(ASURA_MAX_NUMBER_OF_ACTIVE_SOUNDS);
    s_pxPlayingSoundHandleListWithEQ = new Asura_Tracking_Handle_List(s_iMaxPlayingSoundsWithEQ);
    s_pxPlayingSoundHandleListWithoutEQ = new Asura_Tracking_Handle_List(s_iMaxPlayingSounds - s_iMaxPlayingSoundsWithEQ);

	// init EQ
	s_pxEQHandleList = new Asura_Tracking_Handle_List(ASURA_SOUND_MAX_NUMBER_OF_EQ_FILTERS);
	for (u_int u = 0; u < ASURA_SOUND_MAX_NUMBER_OF_EQ_FILTERS; ++u)
	{
		s_aeEQType[u] = EQ_OFF;
		s_afEQLevel[u] = 0.0f;
	}

	s_pxSoundProperties = new Asura_SoundProperties[ASURA_MAX_NUMBER_OF_LOADED_SOUNDS];
	for(int i=0;i<ASURA_MAX_NUMBER_OF_LOADED_SOUNDS;i++)
	{
		s_pxSoundProperties[i].Initialise();
	}

	Asura_Sound_Mixer_System::Initialise();
	Asura_Sound_Event_System::Initialise();
#ifdef ASURA_USE_DIALOGUE_SYSTEM
	Asura_Dialogue_System::Initialise();
#endif
	Asura_Phonon_System::Initialise();
    Asura_Music_System::Initialise();
	Asura_MetaMusic_System::Initialise();
	Asura_StreamingBackgroundSound_System::Initialise();
	#ifdef ASURA_SUPPORT_DYNAMIC_MUSIC_SYSTEM
	Asura_Dynamic_Music_System::Initialise();
	#endif
#ifdef ASURA_USE_REVERBVOLUME_SYSTEM
	Asura_ReverbVolume_System::Initialise();
#endif

#if !defined(ASURA_USE_MULTIPLE_HANDLERS)
	LoadCoreProjectSounds();
	if(s_bSetResetPointDuringInitialisation)
	{
		SetResetPoint();    // (MH - 02.07.2002) sounds loaded after this will be unloaded when Reset is called.
	}
#endif	//!defined(ASURA_USE_MULTIPLE_HANDLERS)



	for(u_int uEntry = 0; uEntry < ASURA_MAX_NO_OF_CAMERAS; ++uEntry)
	{
		s_auOcclusionGUIDsToIgnore[uEntry] = ASURA_GUID_UNREGISTERED;
	}

#if ( defined ASURA_SUPPORT_COMMAND_CONSOLE ) && !( defined ASURA_USE_AUDIO_SYSTEM )
	Asura_CommandConsole::AddCmd( CONSOLE_CMDNAME_MUTE,		ConsoleCmd_Mute,	  Asura_CommandConsole::REMOVE_AT_SHUTDOWN );
#endif

#if defined( ASURA_DEBUG_VARIABLE_SYSTEM )
	Asura_CommandConsole::AddVar( CONSOLE_VARNAME_DOPPLER_FACTOR, &g_fDopplerFactor, 0.1f, 5.0f, Asura_CommandConsole::REMOVE_AT_SHUTDOWN );
	Asura_CommandConsole::AddVar( CONSOLE_VARNAME_SPEED_OF_SOUND, &g_fSpeedOfSound, 300.0f, 400.0f, Asura_CommandConsole::REMOVE_AT_SHUTDOWN );
	Asura_CommandConsole::AddVar( CONSOLE_VARNAME_OCCLUSION_ENVIRONMENT, &g_bUseEnvironmentOcclusion, Asura_CommandConsole::REMOVE_AT_SHUTDOWN );
	Asura_CommandConsole::AddVar( CONSOLE_VARNAME_OCCLUSION_OBJECTS, &g_bUseObjectOcclusion, Asura_CommandConsole::REMOVE_AT_SHUTDOWN );
	Asura_CommandConsole::AddVar( CONSOLE_VARNAME_DISTANCE_FILTER_RANGE_MIN, &g_fDistanceFilterRangeMin, 0.0f, 200.0f, Asura_CommandConsole::REMOVE_AT_SHUTDOWN );
	Asura_CommandConsole::AddVar( CONSOLE_VARNAME_DISTANCE_FILTER_RANGE_MAX, &g_fDistanceFilterRangeMax, 0.0f, 200.0f, Asura_CommandConsole::REMOVE_AT_SHUTDOWN );
#endif // #if defined( ASURA_DEBUG_VARIABLE_SYSTEM )


    // (NRM - 27.06.2003) clear loggin
    Asura_SimpleLogfileClear(g_szFailedSoundLogfile);

#if defined( ASURA_USE_DEBUGOVERLAY )
	Asura_DebugOverlay::RegisterMode( "Sound",			ASURA_DO_SOUND,						DoDebugOverlay,							NULL );
	Asura_DebugOverlay::RegisterMode( "ActiveSounds",	ASURA_DO_ACTIVE_SOUNDS_OCCLUSION,	DoDebugOverlayActiveSoundsOcclusion,	NULL );
#endif // #if defined( ASURA_USE_DEBUGOVERLAY )
}

void Asura_Sound_System::Shutdown(void)
{
	Asura_Sound_Mixer_System::Shutdown();
	Asura_Phonon_System::Silence();
	Asura_Sound_Event_System::Shutdown();
#ifdef ASURA_USE_DIALOGUE_SYSTEM
	Asura_Dialogue_System::Shutdown();
#endif

    StopAll();

	// need to implement some kind of UnloadAllSounds() here?
	Asura_Phonon_System::Shutdown();

#ifdef ASURA_USE_REVERBVOLUME_SYSTEM
	Asura_ReverbVolume_System::Shutdown();
#endif
	Asura_MetaMusic_System::Shutdown();
    Asura_Music_System::Shutdown();
	#ifdef ASURA_SUPPORT_DYNAMIC_MUSIC_SYSTEM
	Asura_Dynamic_Music_System::Shutdown();
	#endif

	Asura_StreamingBackgroundSound_System::Shutdown();


#if defined(ASURA_USE_MULTIPLE_HANDLERS)
	s_xHandlerTree.Reset();
#else
	// Unload the sounds...
#ifndef ASURA_TOOLS
	s_xResettableHandler.Reset();
	s_pxCurrentHandler = &s_xPermanentHandler;
	s_xPermanentHandler.Reset();
#endif //#ifndef ASURA_TOOLS
#endif
	s_pxCurrentHandler = NULL;

	#if defined(ASURA_TOOLS)
	s_xSoundIDToIndexTree.TraverseFromRoot(TraverseUnloadSound);
	s_xSoundIDToIndexTree.FreeAll();
	#endif	// defined(ASURA_TOOLS)

	Asura_Assert((GetNumLoadedSounds() == 0), "Shutting down sound system when we still have sounds in memory.");

#if ASURA_SOUND_STORE_FILENAMES
	delete[] s_pxSoundFilenames;
	s_pxSoundFilenames = NULL;
	delete[] s_peNamedSoundIDArray;
	s_peNamedSoundIDArray = NULL;
#endif

    EnsureAllStreamingSoundsAreStopped();

	delete s_pxStreamingSoundList;
	s_iNumberOfStreamingSounds = 0;
	delete [] g_pxStreamInfo;

	delete[] g_ucLoadedSoundNumActiveInstances;

    delete [] s_pxActiveSoundList;
    delete s_pxActiveSoundHandleList;
    delete s_pxPlayingSoundHandleListWithEQ;
	delete s_pxPlayingSoundHandleListWithoutEQ;

	delete s_pxEQHandleList;

	delete [] s_pxSoundProperties;
	s_pxSoundProperties = 0;

	PlatformShutdown();
}

void Asura_Sound_System::Reset(void)
{
	// KOR - 13/03/2006 - this needs to be done before StopAll to avoid assert about active handles
	Asura_Sound_Mixer_System::Reset();
	Asura_Phonon_System::Reset();
	Asura_Sound_Event_System::Reset();
#ifdef ASURA_USE_DIALOGUE_SYSTEM
	Asura_Dialogue_System::Reset();
#endif

    StopAll();
    
	Asura_StreamingBackgroundSound_System::Reset();
	Asura_MetaMusic_System::Reset();

	#ifdef ASURA_SUPPORT_DYNAMIC_MUSIC_SYSTEM
	Asura_Dynamic_Music_System::Reset();
	#endif

#ifdef ASURA_USE_REVERBVOLUME_SYSTEM
	Asura_ReverbVolume_System::Reset();
#endif
	// restore reverb to defaults.
	SetReverb(ASURA_REVERB_ID_NONE, 1.0f);

	s_pxActiveSoundHandleList->Traverse(TraverseDestroySound);
	
#if defined(ASURA_USE_MULTIPLE_HANDLERS)
	// Reset then create the default handler...
	s_xHandlerTree.Reset();
#elif defined(ASURA_USE_MULTIPLE_HANDLERS)
	GetCurrentHandler().Reset();
#else
#ifndef ASURA_TOOLS
	s_xResettableHandler.Reset();
	#if !defined ASURA_NO_DEBUG_OUTPUT && !defined NDEBUG
		int	iNumSounds	= s_xPermanentHandler.GetNumLoadedSounds() + s_xResettableHandler.GetNumLoadedSounds();
	#endif
#endif //#ifndef ASURA_TOOLS
#endif	//defined(ASURA_USE_MULTIPLE_HANDLERS)
    PlatformReset();

    // (MH - 12.01.2003) reset pitch modifier to default
    SetPitchUsesGameTimeScale(true);

#if !defined(ASURA_USE_MULTIPLE_HANDLERS) && !defined (ASURA_TOOLS)
	#if !defined ASURA_NO_DEBUG_OUTPUT && !defined NDEBUG
		Asura_Sound_OutputToDebugger(( "Asura_Sound_System::Reset done: %d loaded sounds.\n", iNumSounds ));
	#endif
#endif	// defined(ASURA_USE_STREAMING) && !defined (ASURA_TOOLS)

	// Reset the running count of sound RAM we failed to allocate, so we can keep the count per-level.
	if (g_iFailedSoundsSize != 0)
	{
		// Put in a line break so it's clear a reset occurred.
		Asura_SimpleLogfileOutput((g_szFailedSoundLogfile, "\n"));
	}
	g_iFailedSoundsSize = 0;
	s_uNumSystemPauses = 0;
}

#if defined(ASURA_USE_MULTIPLE_HANDLERS)
void Asura_Sound_System::ReparentHandler(Asura_Sound_Handler& xSourceHandler, const Asura_Hash_ID uNewParentHash)
{
	Asura_Sound_Handler*	pxParentHandler	= s_xHandlerTree.GetDataByHash(uNewParentHash);

	if(pxParentHandler != NULL)
	{
#if !defined(ASURA_TOOLS)
		xSourceHandler.m_xSoundIDToLoadedIndexTree.SetParent(&(pxParentHandler->m_xSoundIDToLoadedIndexTree));
		xSourceHandler.m_xHashToIDTree.SetParent(&(pxParentHandler->m_xHashToIDTree));
#endif	// !defined(ASURA_TOOLS)
	}
}

void Asura_Sound_System::ReparentHandler(const Asura_Hash_ID uSourceHash, const Asura_Hash_ID uNewParentHash)
{
	Asura_Sound_Handler*	pxHandler	= s_xHandlerTree.GetDataByHash(uSourceHash);

	if(pxHandler != NULL)
	{
		ReparentHandler(*pxHandler, uNewParentHash);
	}

	// Re-parent the sub-class handlers...
	Asura_Phonon_System::ReparentHandler(uSourceHash, uNewParentHash);
	Asura_Sound_Event_Storage::ReparentHandler(uSourceHash, uNewParentHash);
#ifdef ASURA_USE_DIALOGUE_SYSTEM
	Asura_Dialogue_Storage::ReparentHandler(uSourceHash, uNewParentHash);
#endif
}

Asura_Sound_Handler* Asura_Sound_System::CreateHandler (const Asura_Hash_ID uHash, const Asura_Hash_ID uParentHandlerHash)
{
	Asura_Sound_Handler*	pxHandler	= s_xHandlerTree.GetDataByHash(uHash);

	// Do we already have this handler?
	if(pxHandler == NULL)
	{
		// No - create one...
		pxHandler	= s_xHandlerTree.AddNode(uHash);

		if(pxHandler != NULL)
		{
			ReparentHandler(*pxHandler, uParentHandlerHash);
		}

		// Create the related systems...
		Asura_Phonon_System::CreateHandler(uHash, uParentHandlerHash);

#if defined(ASURA_USE_MULTIPLE_ZONE_HANDLERS)
//		Asura_StreamingBackgroundSound_System::CreateHandler(uHash);
		Asura_MetaMusic_System::CreateHandler(uHash);

		#ifdef ASURA_USE_REVERBVOLUME_SYSTEM
		Asura_ReverbVolume_System::CreateHandler(uHash);
		#endif
#endif	//defined(ASURA_USE_MULTIPLE_ZONE_HANDLERS)

		Asura_Sound_Event_Storage::CreateHandler(uHash,uParentHandlerHash);
		#ifdef ASURA_USE_DIALOGUE_SYSTEM
		Asura_Dialogue_Storage::CreateHandler(uHash,uParentHandlerHash);
		#endif
	}
	else
	{
		// Yes - this shouldn't happen...
		Asura_Assert(false, "Already have an sound handler with this hash-ID.");
	}

	// Return the handler...
	return(pxHandler);
}

bool Asura_Sound_System::DestroyHandler (const Asura_Hash_ID uHash)
{
	// Destroy the related systems...
	Asura_Phonon_System::DestroyHandler(uHash);

#if defined(ASURA_USE_MULTIPLE_ZONE_HANDLERS)
//	Asura_StreamingBackgroundSound_System::Reset();
	Asura_MetaMusic_System::DestroyHandler(uHash);
	#ifdef ASURA_USE_REVERBVOLUME_SYSTEM
	Asura_ReverbVolume_System::DestroyHandler(uHash);
	#endif
#endif	//defined(ASURA_USE_MULTIPLE_ZONE_HANDLERS)

	Asura_Sound_Event_Storage::DestroyHandler(uHash);
	#ifdef ASURA_USE_DIALOGUE_SYSTEM
	Asura_Dialogue_Storage::DestroyHandler(uHash);
	#endif

	// Is this the current handler?
	if(s_pxCurrentHandler == s_xHandlerTree.GetDataByHash(uHash))
	{
		s_pxCurrentHandler	= NULL;
	}

	// Call the delete function...
	return(s_xHandlerTree.DeleteNodeByHash(uHash));
}

void Asura_Sound_System::CreateAndSetHandler (const Asura_Hash_ID uHash, const Asura_Hash_ID uParentHandlerHash)
{
	Asura_Sound_Handler*	pxHandler	= s_xHandlerTree.GetDataByHash(uHash);

	// Do we already have this handler?
	if(pxHandler == NULL)
	{
		// No - create one...
		pxHandler	= s_xHandlerTree.AddNode(uHash);

		if(pxHandler == NULL)
		{
			// Cannot create a handler - odd...
			Asura_Assert(false, "Failed to create sound-handler with this hash-ID.");
		}
		else
		{
			// Re-parent this handler...
			ReparentHandler(*pxHandler, uParentHandlerHash);

			// Set the current handler...
			s_pxCurrentHandler	= pxHandler;

			// Create the related systems...
			Asura_Phonon_System::CreateAndSetHandler(uHash,uParentHandlerHash);

#if defined(ASURA_USE_MULTIPLE_ZONE_HANDLERS)
//			Asura_StreamingBackgroundSound_System::CreateAndSetHandler(uHash);
			Asura_MetaMusic_System::CreateAndSetHandler(uHash);
			#ifdef ASURA_USE_REVERBVOLUME_SYSTEM
			Asura_ReverbVolume_System::CreateAndSetHandler(uHash);
			#endif
#endif	//defined(ASURA_USE_MULTIPLE_ZONE_HANDLERS)

			Asura_Sound_Event_Storage::CreateAndSetHandler(uHash,uParentHandlerHash);
			#ifdef ASURA_USE_DIALOGUE_SYSTEM
			Asura_Dialogue_Storage::CreateAndSetHandler(uHash,uParentHandlerHash);
			#endif

		}
	}
	else
	{
		// Yes - this shouldn't happen...
		Asura_Assert(false, "Already have an animation handler with this hash-ID.");
	}
}

void Asura_Sound_System::WriteHandlerReport(u_int uHandler, Asura_Stream& xLog)
{
	// Nothing of use to report here so pass log to the associated systems
	Asura_Phonon_System::WriteHandlerReport( uHandler, xLog );

#if defined(ASURA_USE_MULTIPLE_ZONE_HANDLERS)
	Asura_MetaMusic_System::WriteHandlerReport( uHandler, xLog );
#endif	//defined(ASURA_USE_MULTIPLE_ZONE_HANDLERS)

	Asura_Sound_Event_Storage::WriteHandlerReport( uHandler, xLog );
	Asura_Dialogue_Storage::WriteHandlerReport( uHandler, xLog );
}

void Asura_Sound_System::SetCurrentHandler (const Asura_Hash_ID uHash)
{
	// Update our handler...
	s_pxCurrentHandler	= s_xHandlerTree.GetDataByHash(uHash);

	// Set the related systems...
	Asura_Phonon_System::SetCurrentHandler(uHash);

#if defined(ASURA_USE_MULTIPLE_ZONE_HANDLERS)
//	Asura_StreamingBackgroundSound_System::SetCurrentHandler(uHash);
	Asura_MetaMusic_System::SetCurrentHandler(uHash);
	#ifdef ASURA_USE_REVERBVOLUME_SYSTEM
	Asura_ReverbVolume_System::SetCurrentHandler(uHash);
	#endif
#endif	//defined(ASURA_USE_MULTIPLE_ZONE_HANDLERS)

	Asura_Sound_Event_Storage::SetCurrentHandler(uHash);
	#ifdef ASURA_USE_DIALOGUE_SYSTEM
	Asura_Dialogue_Storage::SetCurrentHandler(uHash);
	#endif

}

#endif	// defined(ASURA_USE_MULTIPLE_HANDLERS)

void Asura_Sound_System::StopAll()
{
    s_pxActiveSoundHandleList->Traverse(StopAllCallback);
	Asura_Assert(!s_pxActiveSoundHandleList->GetNumberOfActiveHandles(), "Asura_Sound_System Reset or Shutdown called while external sound handles remain");
}

void Asura_Sound_System::StopAllCallback(Asura_Handle iSoundHandle)
{
	Asura_Assert(iSoundHandle != ASURA_INVALID_HANDLE, "invalid handle");
    if (iSoundHandle == ASURA_INVALID_HANDLE)
    {
        return;
    }
    
    if (s_pxActiveSoundList[iSoundHandle].m_bNoExternalHandle)
    {
        DestroySound(iSoundHandle);
    }
}

#if !defined(ASURA_USE_MULTIPLE_HANDLERS)

void Asura_Sound_System::SetResetPoint(void)
{
#ifndef ASURA_TOOLS
	Asura_Assert(s_pxCurrentHandler == &s_xPermanentHandler, "wrong handler set");
    s_pxCurrentHandler = &s_xResettableHandler;
#endif //#ifndef ASURA_TOOLS
    PlatformSetResetPoint();
#ifndef ASURA_TOOLS
    Asura_Sound_OutputToDebugger(( "Asura_Sound_System::SetResetPoint done: %d loaded sounds.\n", s_xPermanentHandler.GetNumLoadedSounds() ));
#endif //#ifndef ASURA_TOOLS

}

#endif

void Asura_Sound_System::SetSoundOutput(SoundOutputType eOutputType)
{
    // Might need to go through all sounds and adjust them. 
    // Might need platform call as well e.g. for CD audio output.
    s_eSoundOutputType = eOutputType;
	Asura_Tracking_Handle_List::Iterator xIterator = s_pxStreamingSoundList->GetIterator();
    for (xIterator.Begin(); !xIterator.End(); xIterator.Next())
    {
        Platform_SetStreamingSoundOutput(xIterator.GetCurrentHandle(), eOutputType);
    }
}




void Asura_Sound_System::SetReverb(enum ReverbType eType, float fReverbAmount)
{
	Asura_Assert(eType >= 0 && eType < ASURA_REVERB_ID_MAX, "invalid reverb type");
	Asura_Assert(fReverbAmount >= 0.0f && fReverbAmount <= 1.0f, "invalid reverb amount");

	if( eType < 0 || eType > ASURA_REVERB_ID_MAX )
	{
		return;
	}

	const float fAmountDiff = Asura_Maths::Abs(fReverbAmount - s_fCurrentReverbAmount);

	if (eType != s_eCurrentReverbType || fAmountDiff > 0.01f)
	{
		s_eCurrentReverbType = eType;
		s_fCurrentReverbAmount = fReverbAmount;
		Platform_SetReverb(eType, fReverbAmount);
	}
}


DEBUGVAR int dbg_iMaxEQHandles = ASURA_SOUND_MAX_NUMBER_OF_EQ_FILTERS;
Asura_Handle Asura_Sound_System::GetEQHandle()
{
	Asura_Handle iHandle = ASURA_INVALID_HANDLE;
	s_pxEQHandleList->GetNewHandle(iHandle);
	if (iHandle > dbg_iMaxEQHandles)
	{
		ReleaseEQHandle(iHandle);
	}
	return iHandle;
}

void Asura_Sound_System::ReleaseEQHandle(Asura_Handle& iHandle)
{
	if (iHandle != ASURA_INVALID_HANDLE)
	{
		SetEQType(iHandle, EQ_OFF, 0.0f);
		s_pxEQHandleList->ReleaseHandle(iHandle);
		iHandle = ASURA_INVALID_HANDLE;
	}
}

void Asura_Sound_System::SetEQType(Asura_Handle iHandle, EQType eType, float fLevel)
{
	Asura_Assert(iHandle >= 0 && iHandle < ASURA_SOUND_MAX_NUMBER_OF_EQ_FILTERS, "invalid handle");
	Asura_Assert(eType >= 0 && eType < NUM_EQ_TYPES, "invalid EQ type");
	Asura_Assert(fLevel >= 0.0f && fLevel <= 1.0f, "invalid EQ level");

	s_aeEQType[iHandle] = eType;
	s_afEQLevel[iHandle] = fLevel;
	Platform_SetEQType(iHandle, eType, fLevel);
}


Asura_Sound_System::EQType Asura_Sound_System::GetEQType(Asura_Handle iHandle, float* pfLevel /* = NULL */)
{ 
	Asura_Assert(iHandle >= 0 && iHandle < ASURA_SOUND_MAX_NUMBER_OF_EQ_FILTERS, "invalid handle");
	if (pfLevel) 
	{
		*pfLevel = s_afEQLevel[iHandle]; 
	}
	return s_aeEQType[iHandle]; 
}

#if defined( ASURA_USE_DEBUGOVERLAY )
void Asura_Sound_System::DoDebugOverlay( void* const pxIgnore )
{
	(void)pxIgnore;

	// Listener information
	Asura_DebugOverlay::DrawConsoleText( ASURA_DO_SOUND, "Listener Position (%.2f, %.2f, %.2f)",
		Asura_Maths::FtoD(s_axSoundCentre[0].x), Asura_Maths::FtoD(s_axSoundCentre[0].y), Asura_Maths::FtoD(s_axSoundCentre[0].z) );
	Asura_DebugOverlay::DrawConsoleText( ASURA_DO_SOUND, "Listener Velocity (%.2f, %.2f, %.2f)",
		Asura_Maths::FtoD(s_axSoundVelocity[0].x), Asura_Maths::FtoD(s_axSoundVelocity[0].y), Asura_Maths::FtoD(s_axSoundVelocity[0].z) );
	Asura_DebugOverlay::DrawConsoleText( ASURA_DO_SOUND, "Doppler Factor %.2f, Speed of Sound %.2f",
		Asura_Maths::FtoD(g_fDopplerFactor), Asura_Maths::FtoD(g_fSpeedOfSound) );

	// Reverb information
	#ifdef ASURA_TOOLS
		const char* reverbName = s_aszReverbNames[s_eCurrentReverbType];
	#else
		const char* reverbName = "???";
	#endif
	Asura_DebugOverlay::DrawConsoleText( ASURA_DO_SOUND, "Current reverb %d (%s)",
		s_eCurrentReverbType, reverbName );
}


void Asura_Sound_System::DoDebugOverlayActiveSoundsOcclusion(void* const pxIgnore)
{
	s_pxActiveSoundHandleList->Traverse(TraversActiveSoundsOcclusion_DebugOverlay);
}

void Asura_Sound_System::TraversActiveSoundsOcclusion_DebugOverlay (Asura_Handle iSoundHandle)
{
	Asura_Assert(iSoundHandle != ASURA_INVALID_HANDLE, "invalid handle");
	if (iSoundHandle == ASURA_INVALID_HANDLE)
	{
		return;
	}

	Asura_ActiveSoundSample& xActiveSound = s_pxActiveSoundList[iSoundHandle];
	Asura_Assert(xActiveSound.m_eSoundID != ASURA_SID_NO_SOUND, "active sound with no sound id");

	if(!xActiveSound.m_bUse3dVol || !SoundIsPlaying(iSoundHandle))
	{
		//No 3D data or not playing
		return;
	}

	//Display on debug overlay
	Asura_Vector_3 xPositionWorld = s_pxActiveSoundList[iSoundHandle].m_x3dData.m_xPosition;

	//if this sound is too close to the camera, display it in the top left of the screen
	const float fMinDistFromCamera = 2.0f;
	const float fTextOffset = 0.05f;
	const float fTextCentralise = 0.3f;
	Asura_Vector_3 xDistToSound = xPositionWorld - *Asura_Camera::GetPosition();
	if(xDistToSound.Magnitude() < fMinDistFromCamera)
	{
		Asura_Vector_2 xPositionScreen(fTextOffset, fTextOffset);
		Asura_DebugOverlay::DrawTextScreen( ASURA_DO_ACTIVE_SOUNDS_OCCLUSION, xPositionScreen, 0xFF00AAFF,
											"Vol (Wet/Dry):%.2f (%.2f/%.2f) - Occlusion:%.2f", 
											Asura_Maths::FtoD(s_pxActiveSoundList[iSoundHandle].m_fVolume),
											Asura_Maths::FtoD(s_pxActiveSoundList[iSoundHandle].m_fWetVolume),
											Asura_Maths::FtoD(s_pxActiveSoundList[iSoundHandle].m_fDryVolume),
											Asura_Maths::FtoD(s_pxActiveSoundList[iSoundHandle].m_fOcclusion));
	}
	else
	{
		if ( Asura_Camera::ProjectWorldCoordsToScreenSpace( xPositionWorld, true ) && xPositionWorld.z > 0.0f )
		{
			Asura_Vector_2 xPositionScreen( xPositionWorld.x / Asura_Viewport::GetWidth() - fTextCentralise, xPositionWorld.y / Asura_Viewport::GetHeight() );

			Asura_DebugOverlay::DrawTextScreen( ASURA_DO_ACTIVE_SOUNDS_OCCLUSION, xPositionScreen, 0xFF00AAFF,
				"Vol (Wet/Dry):%.2f (%.2f/%.2f) - Occlusion:%.2f", 
				Asura_Maths::FtoD(s_pxActiveSoundList[iSoundHandle].m_fVolume),
				Asura_Maths::FtoD(s_pxActiveSoundList[iSoundHandle].m_fWetVolume),
				Asura_Maths::FtoD(s_pxActiveSoundList[iSoundHandle].m_fDryVolume),
				Asura_Maths::FtoD(s_pxActiveSoundList[iSoundHandle].m_fOcclusion));
		}
	}
	
}


#endif // #if defined( ASURA_USE_DEBUGOVERLAY )

void Asura_Sound_System::Update(void)
{
	ASURA_CPU_PROFILE__START_LABEL(ASURA_CPU_PROFILE__SOUND_UPDATE, "Sound update");

    Project_Update();





	// update internal systems
	if (s_bUseAutomaticSoundCentre)
    {
        AutoUpdateSoundCentres();
    }
	
#ifdef ASURA_USE_REVERBVOLUME_SYSTEM
	Asura_ReverbVolume_System::Update();
#else
	UpdateModuleReverb();
#endif

	Asura_Sound_Mixer_System::Update();
	Asura_Sound_Event_System::Update();
#ifdef ASURA_USE_DIALOGUE_SYSTEM
	Asura_Dialogue_System::Update();
#endif

	if(!Asura_Phonon_System::AreUpdatesPaused())
	{
		Asura_Phonon_System::ResetNumPlayingPhonons();
		#if defined(ASURA_USE_MULTIPLE_HANDLERS)
		// Many phonon handlers - update each one in-turn...
		s_xHandlerTree.Traverse(TraverseUpdatePhonons);
		#else
		// Only one phonon handler - update it directly...
		Asura_Phonon_System::GetCurrentHandler().Update();
		#endif
	}
	
	#ifdef ASURA_TOOLS
	if( Tools_System::IsActive() )
	{
		//Handle the situation where the user quit to tools while paused
		while(s_uNumSystemPauses > 0)
		{
			s_uNumSystemPauses = 1;
			UnpauseAllSystems();
		}
	}
	#endif
	
	Asura_MetaMusic_System::Update();
    Asura_Music_System::Update();
	#ifdef ASURA_SUPPORT_DYNAMIC_MUSIC_SYSTEM
	Asura_Dynamic_Music_System::Update();
	#endif
	Asura_StreamingBackgroundSound_System::Update();

	g_xTicksSpentUpdatingOcclusions = 0;

    s_pxActiveSoundHandleList->Traverse(UpdateSound);

	if((!g_xTicksSpentUpdatingOcclusions) && (s_xOcclusionCPUTicksPerFrame != 0))
	{
		//we didnt update any occlusions this frame, increment our counter
		++g_uOcclusionCounter;
	}

    // check for any global pitch change
    if (s_bPitchUsesGameTimeScale)
    {
        float fCurTimeScale = Asura_Timers::GetGameTimeScale();
        if (fCurTimeScale != s_fPitchScale)
        {
            s_fPitchScale = fCurTimeScale;
            s_pxActiveSoundHandleList->Traverse(UpdateGlobalPitchPerSound);
        }
    }

    PlatformUpdate();

	ASURA_CPU_PROFILE__STOP( ASURA_CPU_PROFILE__SOUND_UPDATE );
}


void Asura_Sound_System::AutoUpdateSoundCentres()
{
    s_iNumSplitScreens = 0;
    for (int iCamID = 0; iCamID < Asura_Camera::GetNumberOfCameras(); iCamID++)
    {
        Asura_Camera::SetCameraIndex(iCamID);
        if (!Asura_Camera::IsCameraActive())
        {
            continue;
        }
        Asura_Assert(s_iNumSplitScreens < ASURA_SOUND_MAX_SPLIT_SCREENS, "too many cameras");

		float fGameTimeSlice = Asura_Timers::GetGameTimeSlice();
		Asura_Vector_3 xCameraPosition = *Asura_Camera::GetPosition();

		if ( fGameTimeSlice > 0.0f 
			&& ( s_axSoundCentre[s_iNumSplitScreens] != Asura_Maths::ZeroVector3 ) )	// Ignore first frame - TODO need to extend to ignore all camera snaps
		{
			s_axSoundVelocity[s_iNumSplitScreens] = xCameraPosition - s_axSoundCentre[s_iNumSplitScreens];
			s_axSoundVelocity[s_iNumSplitScreens] /= fGameTimeSlice;
		}
		else
		{
			s_axSoundVelocity[s_iNumSplitScreens].Zero();
		}

        s_axSoundCentre[s_iNumSplitScreens] = xCameraPosition;
        s_axSoundOrientation[s_iNumSplitScreens] = *Asura_Camera::GetOrientation();
        s_iNumSplitScreens++;
    }
}


#ifdef ASURA_SOUND_USE_MODULE_REVERB
DEBUGVAR Asura_Sound_System::ReverbType dbg_eReverbType = Asura_Sound_System::ASURA_REVERB_ID_MAX;
#endif //	#ifdef ASURA_SOUND_USE_MODULE_REVERB

void Asura_Sound_System::UpdateModuleReverb()
{
	// AP 10-10-07 - disabled this because spatial reverb is now controlled by triggers,
	// and in the future will be controlled by reverb zones. Modules have nothing to
	// do with it any more (and were never really suited to it anyway).
	#ifdef ASURA_SOUND_USE_MODULE_REVERB
		if (dbg_eReverbType >= 0 && dbg_eReverbType < Asura_Sound_System::ASURA_REVERB_ID_MAX)
		{
			SetReverb(dbg_eReverbType);
		}
		else
		{
			//Get the reverb settings for the current camera position
			//and then do something with them (TBD)
			
				Asura_Sound_System::ReverbType eReverbType = Asura_Sound_System::ASURA_REVERB_ID_NONE;
				if(Asura_ModuleReverb_System::IsInitialised())
				{
					for (int iCamID = 0; iCamID < s_iNumSplitScreens; iCamID++)
					{
						const Asura_ModuleReverb* pxReverb = Asura_ModuleReverb_System::GetReverbSettings(&s_axSoundCentre[iCamID]);

						if (pxReverb->m_iReverbType != Asura_Sound_System::ASURA_REVERB_ID_NONE)
						{
							eReverbType = static_cast<ReverbType>(pxReverb->m_iReverbType);
						}
					}
				}
				
				SetReverb(eReverbType);
		}
	#endif	//#ifdef ASURA_SOUND_USE_MODULE_REVERB
}	


void Asura_Sound_System::SetUseAutomaticSoundCentre(bool bEnable)
{
    s_bUseAutomaticSoundCentre = bEnable;
}

void Asura_Sound_System::SetNumSplitScreens(int iNumScreens)
{
    Asura_Assert(!s_bUseAutomaticSoundCentre, "need to call s_bUseAutomaticSoundCentre(false) first");
    Asura_Assert(iNumScreens <= ASURA_SOUND_MAX_SPLIT_SCREENS, "too many cameras");
    s_iNumSplitScreens = iNumScreens;
}

void Asura_Sound_System::SetSoundCentre(int iSplitScreenID, 
                                        const Asura_Vector_3 & xCentre, 
                                        const Asura_Matrix_3x3 & xOrientation)
{
    Asura_Assert(!s_bUseAutomaticSoundCentre, "need to call s_bUseAutomaticSoundCentre(false) first");
    Asura_Assert(iSplitScreenID < s_iNumSplitScreens, "too many cameras");

	float fGameTimeSlice = Asura_Timers::GetGameTimeSlice();

	if ( fGameTimeSlice > 0.0f 
		&& ( s_axSoundCentre[iSplitScreenID] != Asura_Maths::ZeroVector3 ) )	// Ignore first frame - TODO need to extend to ignore all camera snaps
	{
		s_axSoundVelocity[iSplitScreenID] = xCentre - s_axSoundCentre[iSplitScreenID];
		s_axSoundVelocity[iSplitScreenID] /= fGameTimeSlice;
	}
	else
	{
		s_axSoundVelocity[iSplitScreenID].Zero();
	}

    s_axSoundCentre[iSplitScreenID] = xCentre;
    s_axSoundOrientation[iSplitScreenID] = xOrientation;
}

void Asura_Sound_System::SetPitchUsesGameTimeScale(bool bEnable, float fTimeScaleToUse /* = 1.0f */)
{
    if (!bEnable)
    {
        if (fTimeScaleToUse != s_fPitchScale)
        {
            s_fPitchScale = fTimeScaleToUse;
            s_pxActiveSoundHandleList->Traverse(UpdateGlobalPitchPerSound);
        }

        PlatformUpdate();
    }
    s_bPitchUsesGameTimeScale = bEnable;
}


// UpdateSound is called each frame for every active sound.
// 1. If the sound is not playing and has no external handle it is destroyed.
// TODO: DestroySound messes with the list that is currently being traversed! 
//	The last entry gets skipped for each sound destroyed in this way!
// 2. Project code can adjust volume for each sound. This is used to temporarily
//	quieten certain sounds e.g. for a voice-over effect.
// 3. 3d sounds have their volume etc updated for the current position.
// 4. looping sounds which are inside their max distance but not playing are started.
void Asura_Sound_System::UpdateSound(Asura_Handle iSoundHandle)
{
	Asura_Assert(iSoundHandle != ASURA_INVALID_HANDLE, "invalid handle");
    if (iSoundHandle == ASURA_INVALID_HANDLE)
    {
        return;
    }

    Asura_ActiveSoundSample& xActiveSound = s_pxActiveSoundList[iSoundHandle];
    Asura_Assert(xActiveSound.m_eSoundID != ASURA_SID_NO_SOUND, "active sound with no sound id");

    if (xActiveSound.m_bNoExternalHandle)
    {
        if (!SoundIsPlaying(iSoundHandle))
        {
            DestroySound(iSoundHandle);
            // (MH - 12.07.2005) looks like a bug has been here for some time...
			// iSoundHandle gets set to ASURA_INVALID_HANDLE by DestroySound() so need to return;
			return;
        }
    }

    // NJD - 09/05/2005 - 2D sounds can now have their volume altered on a per-update basis
    //                    using project specific functions, so this method has been altered
    //                    and now updates all sounds whether 2D or 3D

    // For looping sounds, stop them when they go out of range to conserve resources.
    // Don't do this for non-looping ones as they will stop soon enough anyway.
    // If a non-looping sound has stopped, it is the responsibility of the sound's 
    // creator to ensure the active sound is destroyed. Normally this is done by 
    // calling FinishedWithSoundHandle(), but phonons don't do that to make it easier 
    // to stop and start their sound.
    // However, I'm going to release the playing handle for a non-looping sound if it
    // is in range and has not been playing for a while.
    if ( (xActiveSound.m_bLooping) || (xActiveSound.m_iPlayingSoundHandle != ASURA_INVALID_HANDLE) )
    {
        bool  bSoundInRange = true;
        float fVolume = 1.0f;
        float fPan    = 0.5f;
        float fFade   = 1.0f;

        if (xActiveSound.m_bUse3dVol)
        {
			Asura_Assert(!xActiveSound.m_bUseHardPan, "can't mix hard pan and 3d volume");
			bSoundInRange  = Calculate3dVolumePanAndFade(iSoundHandle, &fVolume, &fPan, &fFade);	
			bSoundInRange &= Project_Adjust3dVolumePanAndFade( iSoundHandle, fVolume, fPan, fFade );
        }
        else
        {
            if (xActiveSound.m_bUseHardPan)
            {
                fPan = GetHardPan(xActiveSound.m_uHardPanID);
            }
            fVolume = xActiveSound.m_fVolume;
			
			if(xActiveSound.m_x3dData.m_pxClipVolume)
			{
				//see if at least one camera satisfies the BB condition
				//RAR 13/07/2007
			    bool bSatisfiesBB = false;
			    for (int iCamID = 0; iCamID < s_iNumSplitScreens; iCamID++)
				{
					bool bInside = xActiveSound.m_x3dData.m_pxClipVolume->Contains(s_axSoundCentre[iCamID]);
					if(bInside == (xActiveSound.m_bInvertClipVolume ? false : true))
					{
						bSatisfiesBB = true;
						break;
					}
				}
				if(!bSatisfiesBB)
				{
					fVolume = 0.0f;	
				}
			}			
            bSoundInRange = Project_Adjust2dVolume( iSoundHandle, fVolume );
        }

		// (MH - 31.08.2006) TODO: check flag for forced pan and fade.
		if (xActiveSound.m_bForcedPanAndFade)
		{
			fPan = xActiveSound.m_fForcedPan;
			fFade = xActiveSound.m_fForcedFade;
		}

		if(bSoundInRange && xActiveSound.m_bUseOcclusion && SoundIsPlaying(iSoundHandle))
		{
			//Adjust the volume based on occlusion (Update the occlusion data if necessary)
			if((xActiveSound.m_uOcclusionCounter != g_uOcclusionCounter) && (g_xTicksSpentUpdatingOcclusions < s_xOcclusionCPUTicksPerFrame)) //This is to schedule occlusion updates
			{
				Asura_TickCount xUpdateTimer = Asura_Timers::GetCurTicks();

				UpdateOcclusionData(iSoundHandle);
				
				g_xTicksSpentUpdatingOcclusions += Asura_Timers::GetElapsedTicks(xUpdateTimer);
				xActiveSound.m_uOcclusionCounter = g_uOcclusionCounter; //set as up to date
			}
			//Tend towards our desired occlusion factor
			const float fTendSpeed = 1.0f;

			float fChange = fTendSpeed * Asura_Timers::GetFrameTime();
			
			if( Asura_Maths::Abs(xActiveSound.m_fOcclusion - xActiveSound.m_fDesiredOcclusion) < fChange )
			{
				xActiveSound.m_fOcclusion = xActiveSound.m_fDesiredOcclusion;
			}
			else
			{
				if(xActiveSound.m_fDesiredOcclusion > xActiveSound.m_fOcclusion)
				{
					xActiveSound.m_fOcclusion += fChange;
				}
				else
				{
					xActiveSound.m_fOcclusion -= fChange;
				}
			}
			float fDryVolume = AdjustVolumeByOcclusion(1.0f, xActiveSound.m_fOcclusion);
			//If we are in the same reverb zone, leave the wet path at full volume, otherwise match the dry volume
			float fWetVolume = 0.0f;
			if(xActiveSound.m_bOcclusionSameReverbZone)
			{
				fWetVolume = AdjustVolumeByOcclusion(1.0f, xActiveSound.m_fOcclusion * fASURA_WET_VOLUME_OCCLUSION_FACTOR);
			}
			else
			{
				fWetVolume = xActiveSound.m_fDryVolume;
			}
			SetSoundWetDryVolumeInternal(iSoundHandle,fWetVolume , fDryVolume);
			Platform_SetOcclusionLPF(xActiveSound.m_iPlayingSoundHandle, xActiveSound.m_fOcclusion);
		}

        if (xActiveSound.m_bLooping)
		{
			if (bSoundInRange)
			{
				xActiveSound.m_ucNumSilentFrames = 0;
				if (xActiveSound.m_iPlayingSoundHandle == ASURA_INVALID_HANDLE)
				{
					// looping sound should be playing, but isn't - restart it.
					PlaySoundInternal(iSoundHandle, fVolume, fPan, fFade);
				}
				else
				{
					// looping sound is already playing, so update volume etc. 
					// hardware 3d mixing does pan only so just set volume
					if (s_bUseHardware3dPan)
					{
						SetPlayingSoundVolume(xActiveSound.m_iPlayingSoundHandle, fVolume);
					}
					else
					{
						SetPlayingSoundVolumePanAndFade(xActiveSound.m_iPlayingSoundHandle, fVolume, fPan, fFade);
					}
				}
			}
			else
			{
				xActiveSound.m_ucNumSilentFrames++;
			}
		}
		else
		{
			if (bSoundInRange && !PlatformSoundIsPlaying(xActiveSound.m_iPlayingSoundHandle))
			{
				// Non-looping sound is in range but not playing. (This sound must have an external
				// handle as otherwise it was stopped at the top of this function).
				// Wait a few frames before telling the platform layer to stop playing the sound, 
				// in case it isn't playing because it was out of range last frame and the sound engine is
				// taking its time to get the sound started.
				xActiveSound.m_ucNumSilentFrames++;

				// TBD wait - what if it's not playing but out of range? Surely it will never
				// get destroyed? Surely the PlatformSoundIsPlaying check should be self-contained?
			}
			else
			{
				// hardware 3d mixing does pan only so just set volume
				if (s_bUseHardware3dPan)
				{
					SetPlayingSoundVolume(xActiveSound.m_iPlayingSoundHandle, fVolume);
				}
				else
				{
					SetPlayingSoundVolumePanAndFade(xActiveSound.m_iPlayingSoundHandle, fVolume, fPan, fFade);
				}
				xActiveSound.m_ucNumSilentFrames = 0;
			}
		}

		// Update doppler
		if ( xActiveSound.m_bUse3dVol && xActiveSound.m_bUseDoppler && bSoundInRange )
        {
			Calculate3dDoppler(iSoundHandle);
        }

		// Update distance filter
		if (	xActiveSound.m_iPlayingSoundHandle != ASURA_INVALID_HANDLE 
			&&	xActiveSound.m_bUse3dVol &&	xActiveSound.m_bUseDistanceFilter )
		{
			const float fGain = CalculateDistanceFilterGain (iSoundHandle);
			
			#ifdef ASURA_ASSERT
			bool bRet = 
			#endif
			Platform_SetSoundDistanceFilterGain(xActiveSound.m_iPlayingSoundHandle, fGain);
			Asura_Assert(bRet,"Call to Platform_SetSoundDistanceFilterGain failed");
		}
    }

    // if the sound is playing but has been silent for several frames, stop it playing now.
	if (xActiveSound.m_iPlayingSoundHandle != ASURA_INVALID_HANDLE)
	{
		if (xActiveSound.m_ucNumSilentFrames > dbg_uNumSilentFramesBeforeStopSound)
        {
            // stop the sound
			#ifdef ASURA_ASSERT
            bool ok = 
			#endif
			PlatformDestroySound(xActiveSound.m_iPlayingSoundHandle);
            Asura_Assert(ok, "failed to destroy sound");
            Asura_Sound_OutputToDebugger(("Release playing sound handle %d for sound %d.\n", xActiveSound.m_iPlayingSoundHandle, xActiveSound.m_iLoadedSoundIndex));
            ReleasePlayingSoundHandle(xActiveSound.m_iPlayingSoundHandle);
            xActiveSound.m_iPlayingSoundHandle = ASURA_INVALID_HANDLE;

			// decrease number of instances
			Asura_Assert(g_ucLoadedSoundNumActiveInstances[xActiveSound.m_iLoadedSoundIndex] > 0, "no active instances");
			--g_ucLoadedSoundNumActiveInstances[xActiveSound.m_iLoadedSoundIndex];
        }
	}
}

void Asura_Sound_System::UpdateGlobalPitchPerSound(Asura_Handle iSoundHandle)
{
	Asura_Assert(iSoundHandle != ASURA_INVALID_HANDLE, "invalid handle");
    if (iSoundHandle == ASURA_INVALID_HANDLE)
    {
        return;
    }
    // (MH - 15.01.2003) ignore paused sounds
    if (s_pxActiveSoundList[iSoundHandle].m_bPaused)
    {
        return;
    }
	// AJG - 01/03/05 - ignore sounds specifically ignoring global pitch changes (which, in theory, are from time scale changes - if this isn't true this ignore may be wrong).
	if (!s_pxActiveSoundList[iSoundHandle].m_bPitchUsesTimeScale)
	{
		return;
	}
    Asura_ActiveSoundSample& xActiveSound = s_pxActiveSoundList[iSoundHandle];
    Asura_Assert(xActiveSound.m_eSoundID != ASURA_SID_NO_SOUND, "active sound with no sound id");
    if (xActiveSound.m_iPlayingSoundHandle != ASURA_INVALID_HANDLE)
    {
		#ifdef ASURA_ASSERT
		bool ok = 
		#endif
		PlatformSetSoundPitch(xActiveSound.m_iPlayingSoundHandle, s_fPitchScale * xActiveSound.m_fPitch * xActiveSound.m_fPitchDoppler );
        Asura_Assert(ok, "set sound pitch failed");
    }
}

void Asura_Sound_System::PauseSound(Asura_Handle iSoundHandle)
{
	Asura_Assert(iSoundHandle != ASURA_INVALID_HANDLE, "invalid handle");
    if (iSoundHandle == ASURA_INVALID_HANDLE)
    {
        return;
    }
    // (MH - 21.01.2003) pause if not already paused
    if (!s_pxActiveSoundList[iSoundHandle].m_bPaused)
    {
        s_pxActiveSoundList[iSoundHandle].m_bPaused = 1;
        Asura_Handle playingHandle = s_pxActiveSoundList[iSoundHandle].m_iPlayingSoundHandle;
        if (playingHandle != ASURA_INVALID_HANDLE)
        {
			#ifdef ASURA_ASSERT
			bool ok = 
			#endif
			PlatformPauseSound(playingHandle);
            Asura_Assert(ok, "failed to pause sound");
        }
    }
}

void Asura_Sound_System::UnpauseSound(Asura_Handle iSoundHandle)
{
	Asura_Assert(iSoundHandle != ASURA_INVALID_HANDLE, "invalid handle");
    if (iSoundHandle == ASURA_INVALID_HANDLE)
    {
        return;
    }
    // unpause if paused
    if (s_pxActiveSoundList[iSoundHandle].m_bPaused)
    {
        s_pxActiveSoundList[iSoundHandle].m_bPaused = 0;
        Asura_Handle playingHandle = s_pxActiveSoundList[iSoundHandle].m_iPlayingSoundHandle;
        if (playingHandle != ASURA_INVALID_HANDLE)
        {
			#ifdef ASURA_ASSERT
			bool ok = 
			#endif
			PlatformUnpauseSound(playingHandle);
            Asura_Assert(ok, "failed to pause sound");
        }
    }
}

void Asura_Sound_System::PauseAll()
{
	if(s_pxActiveSoundHandleList)
	{
		s_pxActiveSoundHandleList->Traverse(PauseSound);
	}
}

void Asura_Sound_System::UnpauseAll()
{
	if(s_pxActiveSoundHandleList)
	{
		s_pxActiveSoundHandleList->Traverse(UnpauseSound);
	}
}

void Asura_Sound_System::PauseAllSystems(bool bReleaseStreams)
{
	if(s_uNumSystemPauses++ > 0) { return; }

	if(!s_pxActiveSoundList) { return; }

	s_pxStreamingSoundList->Traverse(PauseStreamingSound); //TY 19/10/07 Force all streaming sounds to pause, regardless of where they are
	Asura_Sound_System::PauseAll();
	Asura_Phonon_System::PausePhononUpdates();
	Asura_StreamingBackgroundSound_System::Pause();
	
	if(bReleaseStreams)
	{
		Asura_MetaMusic_System::PauseAndReleaseSound(true);
		Asura_Sound_System::EnsureAllStreamingSoundsAreStopped();
	}
	else
	{
		Asura_MetaMusic_System::Pause(true);
	}
}

void Asura_Sound_System::UnpauseAllSystems()
{
	if(--s_uNumSystemPauses > 0) { return; }

	if(!s_pxActiveSoundList) { return; }

	s_pxStreamingSoundList->Traverse(ResumeStreamingSound);
	Asura_Sound_System::UnpauseAll();
	Asura_Phonon_System::ResumePhononUpdates();
	Asura_StreamingBackgroundSound_System::Unpause();
	Asura_MetaMusic_System::Unpause();
}

// S O U N D   L O A D I N G /////////////////////////////////////////////////

#ifdef ASURA_TOOLS

// (MH - 23.05.2002) returns true if the sound was successfully loaded or already loaded.
bool Asura_Sound_System::LoadSound( const char * szFilename, Asura_Sound_ID eSoundID)
{	
	int index;

	if(IsHandlerFull())
	{
		Asura_Assert(0,"Unable to load any more sounds , ASURA_MAX_NUMBER_OF_LOADED_SOUNDS exceeded");
		return false;
	}

	if(eSoundID == ASURA_SID_DYNAMIC)
	{
		eSoundID = GetDynamicSoundID();
	}
		
	if (AddSoundIDToHandler(eSoundID, index))
	{
		s_pxSoundProperties[index].Initialise();
		if (PlatformLoadSound(szFilename, index))
        {
            s_pxSoundFilenames[index] = szFilename;
			s_peNamedSoundIDArray[index] = eSoundID;

			//add to sound filename hash list
			Asura_Hash_ID uHash = Asura_GetHashID(szFilename);
			if(GetCurrentHandler().AddToHashToIDTree(uHash,eSoundID))
			{
				//Store the hash ID in the sound properties , to help with deleting later
				s_pxSoundProperties[index].m_uHashID = uHash;
			}

		    return true;
        }
        else
        {
			#if defined ASURA_ASSERT
				bool ok =
			#endif
				DeleteSoundIDFromHandler(eSoundID);

            Asura_Assert(ok, "failed to delete sound");
            Asura_SimpleLogfileOutput((g_szFailedSoundLogfile, "Failed to LoadSound \tID : %i\n", eSoundID));
            return false;
        }
	}
    else
    {
        //Sound has already been loaded.
		//See if this sound has a filename associated with it yet
		if(!s_pxSoundFilenames[index].IsBlank())
		{
	        Asura_Assert(0, "sound already loaded");
			return true;
		}

		//No filename for this file.
		//This will happen if the sound was loaded through a resource file
		s_pxSoundFilenames[index] = szFilename;
		s_peNamedSoundIDArray[index] = eSoundID;
		return true;
    }
}

void Asura_Sound_System::ForceSoundToBeLoaded(Asura_Sound_ID eSoundID)
{
	int iIndex = GetSoundLoadedIndex(eSoundID);
	if(iIndex >= 0)
	{
		PlatformForceSoundToBeLoaded(iIndex);		
	}
}

void Asura_Sound_System::ReloadSound(Asura_Sound_ID eSoundID)
{
	int iIndex = GetSoundLoadedIndex(eSoundID);
	if(iIndex >= 0)
	{
		PlatformReloadSound(iIndex);		
	}
}



// for creating lists in tools of loaded sounds
int Asura_Sound_System::GetNumberOfNamedSounds(Asura_Sound_ID eMinID,Asura_Sound_ID eMaxID)
{ 
	int iCount = 0;
	for(int i=0;i<ASURA_MAX_NUMBER_OF_LOADED_SOUNDS;i++)
	{
		if(!s_pxSoundFilenames[i].IsBlank())
		{
			if(eMinID<= s_peNamedSoundIDArray[i] && eMaxID>= s_peNamedSoundIDArray[i])
			{
				iCount++;
			}
		}
	}
	return iCount;
}

void Asura_Sound_System::MakeNameList( const Asura_String** nameList, Asura_Sound_ID eMinID, Asura_Sound_ID eMaxID ) 
{ 
	int iIndex = 0;
	for(int i=0;i<ASURA_MAX_NUMBER_OF_LOADED_SOUNDS;i++)
	{
		if(!s_pxSoundFilenames[i].IsBlank())
		{
			if(eMinID<= s_peNamedSoundIDArray[i] && eMaxID>= s_peNamedSoundIDArray[i])
			{
				nameList[iIndex++] = &s_pxSoundFilenames[i];
			}
		}
	}
}

Asura_Sound_ID Asura_Sound_System::GetSoundIDFromFileName(const char * filename,Asura_Sound_ID eMinID,Asura_Sound_ID eMaxID)
{
	Asura_String xTempString = filename;
	for(int i=0;i<ASURA_MAX_NUMBER_OF_LOADED_SOUNDS;i++)
	{
		if(!s_pxSoundFilenames[i].IsBlank())
		{
			if(eMinID<= s_peNamedSoundIDArray[i] && eMaxID>= s_peNamedSoundIDArray[i])
			{
				if(s_pxSoundFilenames[i].EqualsIgnoringExtension(xTempString))
				{
					return s_peNamedSoundIDArray[i];
				}			
			}
		}
	}

    return ASURA_SID_NO_SOUND;
}

float Asura_Sound_System::GetSoundLength(Asura_Sound_ID eSoundID)
{
	int iIndex = GetSoundLoadedIndex(eSoundID);
	if(iIndex >= 0)
	{
		return PlatformGetSoundLength(iIndex);		
	}
	return 0.0f;
}

#endif // #ifdef ASURA_TOOLS

#if ASURA_SOUND_STORE_FILENAMES

Asura_Sound_ID Asura_Sound_System::GetSoundIDFromFileName(const char * filename)
{
	Asura_String xTempString = filename;
	for(int i=0;i<ASURA_MAX_NUMBER_OF_LOADED_SOUNDS;i++)
	{
		if(!s_pxSoundFilenames[i].IsBlank())
		{
			if(s_pxSoundFilenames[i].EqualsIgnoringExtension(xTempString))
			{
				return s_peNamedSoundIDArray[i];
			}			
		}
	}

    return ASURA_SID_NO_SOUND;
}

const char* Asura_Sound_System::GetFileNameFromSoundID(Asura_Sound_ID soundID)
{
	int iLoadedSoundIndex;
	
	if (FindSoundIDInHandler(soundID, iLoadedSoundIndex))
	{
		if(!s_pxSoundFilenames[iLoadedSoundIndex].IsBlank())
		{
			return s_pxSoundFilenames[iLoadedSoundIndex].GetString();
		}
	}
	return NULL;
}

const char* Asura_Sound_System::GetFileNameFromSoundIndex( int iLoadedSoundIdx )
{
    if( ASURA_INVALID_LOADED_SOUND_INDEX == iLoadedSoundIdx ) { return NULL; }

	Asura_Assert( 0 <= iLoadedSoundIdx && iLoadedSoundIdx < ASURA_MAX_NUMBER_OF_LOADED_SOUNDS, "" );
    const Asura_String& xFilename = s_pxSoundFilenames[iLoadedSoundIdx];

    return( xFilename.IsBlank() ? NULL : xFilename.GetString() );
}

void Asura_Sound_System::SetSoundFilename(int iLoadedSoundIndex,Asura_Sound_ID eSoundID,const char* pszFilename)
{
	s_pxSoundFilenames[iLoadedSoundIndex] = pszFilename;
	s_peNamedSoundIDArray[iLoadedSoundIndex] = eSoundID;
}

#endif // #if ASURA_SOUND_STORE_FILENAMES

Asura_Sound_ID Asura_Sound_System::GetSoundIDFromHash(Asura_Hash_ID uSoundHash)
{
	const int* piSoundID = GetCurrentHandler().GetIDByHash(uSoundHash);
	if(piSoundID)
	{
		return (Asura_Sound_ID) *piSoundID;
	}
	return ASURA_SID_NO_SOUND;

}

bool Asura_Sound_System::LoadSoundFromResourceFile( Asura_Handle iFileHandle, int iFileSize, u_int uSoundID, const char* szSoundFilename )
{	
	int iLoadedSoundIndex;
	Asura_Sound_ID eSoundID = (Asura_Sound_ID)uSoundID;

	if (!Project_ShouldLoadSound(eSoundID))
	{
		return false;
	}

	//remove beginning slashes , to match up with way filenames are loaded outside
	//of resourcefiles. RAR 17/12/2004
	while(*szSoundFilename == '\\' || *szSoundFilename == '/') szSoundFilename++;
        
	Asura_Hash_ID uSoundFilenameHashID = Asura_GetHashID( szSoundFilename );


	if(eSoundID == ASURA_SID_DYNAMIC)
	{
		//See if a sound with this hash value has already been loaded , to avoid having the same
		//sound loaded multiple times with different IDs each time.
		//RAR 24/09/2007
		Asura_Sound_ID eExistingSoundID = GetSoundIDFromHash(uSoundFilenameHashID);

		if(eExistingSoundID != ASURA_SID_NO_SOUND)
		{
			#ifdef ASURA_TOOLS
			//In tools , a sound ID may already have been generated for this filename , without the sound being loaded yet.
			//So attempt to load into the existing slot.
			//(this will skip the sound if it is in fact already loaded).
			//RAR 24/09/2007
			iLoadedSoundIndex = GetSoundLoadedIndex(eExistingSoundID);
			if(iLoadedSoundIndex != ASURA_INVALID_LOADED_SOUND_INDEX)
			{
				PlatformLoadSoundFromResource(iFileHandle, iFileSize, iLoadedSoundIndex);
				return true;
			}
			else
			{
				Asura_Assert(0,"Failed to get loaded sound index for a sound that is in theory already loaded");
				return false;
			}
			#else
			//Sound is already loaded , so skip it.
			//RAR 24/09/2007
			Asura_Sound_OutputToDebugger(("Load sound, already loaded. Filename = %s\n", szSoundFilename));
			return false;
			#endif
		}


		eSoundID = GetDynamicSoundID();
	}


	if (AddSoundIDToHandler(eSoundID, iLoadedSoundIndex))
	{
#if ASURA_SOUND_STORE_FILENAMES
		// Store sound filename information. Do this before anything else so filenames are accessible during load platform loading code
		if( szSoundFilename )
		{
			Asura_Assert(iLoadedSoundIndex >= 0 && iLoadedSoundIndex < ASURA_MAX_NUMBER_OF_LOADED_SOUNDS, "Invalid index");
			s_pxSoundFilenames[iLoadedSoundIndex]    = szSoundFilename;
			s_peNamedSoundIDArray[iLoadedSoundIndex] = eSoundID;
		}
#endif

		s_pxSoundProperties[iLoadedSoundIndex].Initialise();

		if (PlatformLoadSoundFromResource(iFileHandle, iFileSize, iLoadedSoundIndex))
        {
			//Add hashed filename to tree
			if( ASURA_HASH_ID_UNSET != uSoundFilenameHashID )
			{
				if( GetCurrentHandler().AddToHashToIDTree(uSoundFilenameHashID, eSoundID) )
				{
					s_pxSoundProperties[iLoadedSoundIndex].m_uHashID = uSoundFilenameHashID;
				}
			}

            Asura_Sound_OutputToDebugger(("Loaded sound. loaded index = %d. enum value = %d\n", iLoadedSoundIndex, uSoundID));
            return true;
        }

		#ifdef ASURA_ASSERT
		bool ok = 
		#endif
		DeleteSoundIDFromHandler(eSoundID);
        Asura_Assert(ok, "failed to delete sound");
        Asura_Sound_OutputToDebugger(("Failed to load sound. loaded index = %d. enum value = %d\n", iLoadedSoundIndex, uSoundID));
        g_iFailedSoundsSize += iFileSize;
        Asura_SimpleLogfileOutput((g_szFailedSoundLogfile, "Failed to LoadSoundFromResourceFile \tID %i\tSize %i \tTotal %i \t%s\n", eSoundID, iFileSize, g_iFailedSoundsSize, szSoundFilename));
	}
    else
    {
        Asura_Sound_OutputToDebugger(("Load sound, already loaded. loaded index = %d. enum value = %d\n", iLoadedSoundIndex, uSoundID));
    }

    return false;
}


int Asura_Sound_System::SoundLoadedByPlatformCode(int iSoundID, Asura_Hash_ID uSoundFilenameHashID /* = ASURA_HASH_ID_UNSET */ )
{
    Asura_Sound_ID eSoundID = (Asura_Sound_ID)iSoundID;
	if(eSoundID == ASURA_SID_DYNAMIC)
	{
		eSoundID = GetDynamicSoundID();
		Asura_Assert( ASURA_HASH_ID_UNSET != uSoundFilenameHashID, "sound will not be found" );
	}

	int iLoadedSoundIndex;

	if (AddSoundIDToHandler(eSoundID, iLoadedSoundIndex))
	{
		//Add hashed filename to tree
		if (ASURA_HASH_ID_UNSET != uSoundFilenameHashID)
		{
			if( GetCurrentHandler().AddToHashToIDTree(uSoundFilenameHashID, eSoundID) )
			{
				s_pxSoundProperties[iLoadedSoundIndex].m_uHashID = uSoundFilenameHashID;
			}
		}

		s_pxSoundProperties[iLoadedSoundIndex].Initialise();
		return iLoadedSoundIndex;
    }
    else
    {
        Asura_Assert(false, "sound already loaded");
        return ASURA_INVALID_LOADED_SOUND_INDEX;
    }
}

int Asura_Sound_System::GetSoundLoadedIndex(int iSoundID)
{
	int iLoadedSoundIndex = -1;
    Asura_Sound_ID eSoundID = (Asura_Sound_ID)iSoundID;

	// SD: 07/04/06 : Use FindSoundIDInHandler returns true if found.
	if (FindSoundIDInHandler(eSoundID, iLoadedSoundIndex))
	{
		return iLoadedSoundIndex;
	}
	else
	{
		return ASURA_INVALID_LOADED_SOUND_INDEX;
	}
}

bool Asura_Sound_System::SoundIsLoaded(const Asura_Sound_ID eSoundID)
{
    return IsSoundIDInHandler(eSoundID);
}

void Asura_Sound_System::LoadSoundIDMapping(Asura_Sound_ID eNewSoundID,Asura_Sound_ID eExistingSoundID,Asura_Hash_ID uExistingSoundHash)
{
	//get the index of the destination sound
	int iExistingIndex = -1;
	if(uExistingSoundHash)
	{
		//find a soundID to go with this hash
		eExistingSoundID = GetSoundIDFromHash(uExistingSoundHash);
		if(eExistingSoundID == ASURA_SID_NO_SOUND)
		{
			Asura_Assert(0,"Failed to find existing wav file by hash for sound mapping.");
			return;
		}
	}
	
	if (!FindSoundIDInHandler(eExistingSoundID, iExistingIndex))
	{
		Asura_Assert(0,"Existing destination sound for mapping hasn't been loaded yet");
		return;		
	}

	//Just to be safe , make sure the destination sound isn't being mapped to something else in turn
	while(s_pxSoundProperties[iExistingIndex].m_iLoadedSoundIndexMapping != ASURA_INVALID_LOADED_SOUND_INDEX)
	{
		iExistingIndex = s_pxSoundProperties[iExistingIndex].m_iLoadedSoundIndexMapping;
	}

	//get an index to load the new sound to
	int iNewIndex;
	if (!AddSoundIDToHandler(eNewSoundID, iNewIndex))
	{
		// check the data matches, if so this is OK (it happens when reloading 
		// sounds on PSP after borrowing the sound RAM for FMV playback).
        Asura_Assert(s_pxSoundProperties[iNewIndex].m_iLoadedSoundIndexMapping == iExistingIndex, 
			"sound mappings being reloaded but don't match");
		return;
	}

	//set the mapping details in the sound properties
	s_pxSoundProperties[iNewIndex].Initialise();
	s_pxSoundProperties[iNewIndex].m_iLoadedSoundIndexMapping = iExistingIndex;
}

// (MH - 23.05.2002) returns whether the sound was successfully unloaded.
#if defined(ASURA_TOOLS)
bool Asura_Sound_System::TraverseUnloadSound (const Asura_Sound_ID eSoundID)
{	
	int iLoadedSoundIndex;

	if (FindSoundIDInHandler(eSoundID, iLoadedSoundIndex))
	{
		//make sure this sound isn't being played. RAR 28/07/2006
		g_iLoadedSoundIndexBeingUnloaded = iLoadedSoundIndex;
		s_pxActiveSoundHandleList->Traverse(TraverseDestroyUnloadedSound);


		bool bSoundHasNoPlatformData = false;
		const Asura_SoundProperties* pxSoundProperties = GetSoundPropertiesByIndex(iLoadedSoundIndex);
		if(pxSoundProperties && pxSoundProperties->m_iLoadedSoundIndexMapping != ASURA_INVALID_LOADED_SOUND_INDEX)
		{
			//This sound shares sound data with an earlier sound , so won't have any platform
			//sound data of its own. RAR 25/04/03
			bSoundHasNoPlatformData = true;
		}


		if (bSoundHasNoPlatformData || PlatformUnloadSound(iLoadedSoundIndex))
		{
			bool ok = DeleteSoundIDFromHandler(eSoundID);
			Asura_Assert(ok, "Failed to delete node");

#if ASURA_SOUND_STORE_FILENAMES
			s_pxSoundFilenames[iLoadedSoundIndex].MakeNull();
			s_peNamedSoundIDArray[iLoadedSoundIndex] = ASURA_SID_NO_SOUND;

#endif
			if(pxSoundProperties && pxSoundProperties->m_uHashID != ASURA_HASH_ID_UNSET)
			{
				//delete from the hash to index conversion tree
				if(&GetCurrentHandler())//RAR 20/10/2006 The handlers may already have been remove already
				{
					GetCurrentHandler().RemoveFromHashToIDTree(pxSoundProperties->m_uHashID);
				}
			}


			Asura_Sound_OutputToDebugger(("Unload sound. loaded index = %d. enum value = %d\n", iLoadedSoundIndex, eSoundID));
			return ok;
		}
		else
		{
			Asura_Sound_OutputToDebugger(("Unload sound, platform failed. loaded index = %d. enum value = %d\n", iLoadedSoundIndex, eSoundID));
		}
	}
	else
	{
		Asura_Assert(0, "failed to unload sound: sound not found");
	}

	return false;
}
#else
void Asura_Sound_System::TraverseUnloadSound(Asura_Hash_ID eSoundID, int* piLoadedSoundIndex)
{	
	//make sure this sound isn't being played. RAR 28/07/2006
	g_iLoadedSoundIndexBeingUnloaded = *piLoadedSoundIndex;
	s_pxActiveSoundHandleList->Traverse(TraverseDestroyUnloadedSound);
	

	bool bSoundHasNoPlatformData = false;
	const Asura_SoundProperties* pxSoundProperties = GetSoundPropertiesByIndex(*piLoadedSoundIndex);
	if(pxSoundProperties && pxSoundProperties->m_iLoadedSoundIndexMapping != ASURA_INVALID_LOADED_SOUND_INDEX)
	{
		//This sound shares sound data with an earlier sound , so won't have any platform
		//sound data of its own. RAR 25/04/03
		bSoundHasNoPlatformData = true;
	}

	
	if (bSoundHasNoPlatformData || PlatformUnloadSound(*piLoadedSoundIndex))
    {
		#if defined ASURA_ASSERT
	        const bool bOK =
		#endif
			DeleteSoundIDFromHandler(static_cast<Asura_Sound_ID>(eSoundID));

        Asura_Assert(bOK, "Failed to delete node");

#if ASURA_SOUND_STORE_FILENAMES
		s_pxSoundFilenames[*piLoadedSoundIndex].MakeNull();
		s_peNamedSoundIDArray[*piLoadedSoundIndex] = ASURA_SID_NO_SOUND;
#endif
		if(pxSoundProperties && pxSoundProperties->m_uHashID != ASURA_HASH_ID_UNSET)
		{
			//delete from the hash to index conversion tree
			GetCurrentHandler().RemoveFromHashToIDTree(pxSoundProperties->m_uHashID);
		}

        Asura_Sound_OutputToDebugger(("Unload sound. loaded index = %d. enum value = %d\n", *piLoadedSoundIndex, eSoundID));
    }
    else
    {
        Asura_Sound_OutputToDebugger(("Unload sound, platform failed. loaded index = %d. enum value = %d\n", *piLoadedSoundIndex, eSoundID));
    }
}
#endif	// defined(ASURA_TOOLS)

#if defined(ASURA_USE_MULTIPLE_HANDLERS)
// ART - 31/07/2006 - We now update phonon handlers individually from here...
void Asura_Sound_System::TraverseUpdatePhonons (Asura_Hash_ID uHash, Asura_Sound_Handler* pxHandler)
{
	Asura_Phonon_Handler*	pxPhononHandler	= Asura_Phonon_System::GetHandler(uHash);

	// Do we need to bother?
	if(pxPhononHandler != NULL)
	{
		Asura_Sound_Handler*	pxOldHandler	= s_pxCurrentHandler;

		// Ensure that we're using the correct sound-handler when updating these phonons...
		s_pxCurrentHandler	= pxHandler;
		{
			pxPhononHandler->Update();
		}
		s_pxCurrentHandler	= pxOldHandler;
	}
}
#endif	//defined(ASURA_USE_MULTIPLE_HANDLERS)


DEBUGVAR bool dbg_bAllowReplaceSound = true;
bool Asura_Sound_System::ReplaceSound(const Asura_Sound_ID eExistingSoundID, const Asura_Sound_ID eNewSoundID)
{
    if (dbg_bAllowReplaceSound)
    {
        int iLoadedIndex1 = -1;
		int iLoadedIndex2 = -1;
        if (!FindSoundIDInHandler(eExistingSoundID, iLoadedIndex1))
        {
            Asura_Assert(false, "existing sound not loaded");
            return false;
        }
        else if (eExistingSoundID == eNewSoundID)
        {
            return true;
        }
        else if (!FindSoundIDInHandler(eNewSoundID, iLoadedIndex2))
        {
            Asura_Assert(false, "new sound not loaded");
            return false;
        }
		else
		{
			Asura_Sound_OutputToDebugger(("replace sound %d with %d.\n", eExistingSoundID, eNewSoundID));
			return PlatformReplaceSound(iLoadedIndex1, iLoadedIndex2);
		}
    }
    else
    {
        return false;
    }
}

bool Asura_Sound_System::LoadReplaceableSound(const Asura_Sound_ID eSoundID)
{
	Asura_Assert(eSoundID != ASURA_SID_DYNAMIC, "invalid Sound ID");
    int iLoadedIndex = -1;
    if (!FindSoundIDInHandler(eSoundID, iLoadedIndex))
    {
        Asura_Assertf(false, ("sound not loaded. ID %d", u_int( eSoundID)));
        return false;
    }
    bool bLoad = PlatformLoadReplaceableSound(iLoadedIndex);
    if(!bLoad)
    {
        int iFileSize = Platform_GetReplaceableSoundSize(iLoadedIndex);
        g_iFailedReplaceableSoundsSize += iFileSize;
        Asura_SimpleLogfileOutput((g_szFailedSoundLogfile, "Failed to LoadReplaceableSound \tID %i\tSize %i \tTotal %i \n", eSoundID, iFileSize, g_iFailedReplaceableSoundsSize));
    }
    return bLoad;
}

// S O U N D   P L A Y I N G /////////////////////////////////////////////////

#if defined(ASURA_USE_MULTIPLE_HANDLERS)
Asura_Handle Asura_Sound_System::CreateSound(const Asura_Sound_ID eSoundID, Asura_Sound_Handler* pxHandler /*= 0*/)
#else
Asura_Handle Asura_Sound_System::CreateSound(const Asura_Sound_ID eSoundID)
#endif
{
	Asura_Handle handle = ASURA_INVALID_HANDLE;
	int loadedSoundIndex = -1;

	bool bFound = false;

#if !defined(ASURA_TOOLS) && (defined(ASURA_USE_MULTIPLE_HANDLERS))
	if(pxHandler)
	{
		bFound = pxHandler->FindIndex(eSoundID, loadedSoundIndex);
	}
	else
#endif	//!defined(ASURA_TOOLS) && (defined(ASURA_USE_MULTIPLE_HANDLERS))
	{
		bFound = FindSoundIDInHandler(eSoundID, loadedSoundIndex);
	}

	if (bFound)
	{
	    bool ok = s_pxActiveSoundHandleList->GetNewHandle(handle);
        if (ok)
        {
            s_pxActiveSoundList[handle].Initialise(eSoundID, loadedSoundIndex);
        }
	}
	return handle;

}

void Asura_Sound_System::TraverseDestroySound(Asura_Handle iSoundHandle)
{
	if (iSoundHandle == ASURA_INVALID_HANDLE)
    {
        return;
    }

    Asura_ActiveSoundSample& xActiveSound = s_pxActiveSoundList[iSoundHandle];
    if (xActiveSound.m_iPlayingSoundHandle != ASURA_INVALID_HANDLE)
    {
        PlatformDestroySound(xActiveSound.m_iPlayingSoundHandle);
		Asura_Sound_OutputToDebugger(("Release playing sound handle %d for sound %d.\n", xActiveSound.m_iPlayingSoundHandle, xActiveSound.m_iLoadedSoundIndex));
        ReleasePlayingSoundHandle(xActiveSound.m_iPlayingSoundHandle);

		// decrease number of instances
		Asura_Assert(g_ucLoadedSoundNumActiveInstances[xActiveSound.m_iLoadedSoundIndex] > 0, "no active instances");
		--g_ucLoadedSoundNumActiveInstances[xActiveSound.m_iLoadedSoundIndex];
    }
    xActiveSound.Initialise(ASURA_SID_NO_SOUND, ASURA_INVALID_LOADED_SOUND_INDEX);
    s_pxActiveSoundHandleList->ReleaseHandle(iSoundHandle);
    //iSoundHandle = ASURA_INVALID_HANDLE;
}

void Asura_Sound_System::TraverseDestroyUnloadedSound(Asura_Handle iSoundHandle)
{
	if (iSoundHandle == ASURA_INVALID_HANDLE)
    {
        return;
    }
	//stop this sound if it is using the sound ID being unloaded
    Asura_ActiveSoundSample& xActiveSound = s_pxActiveSoundList[iSoundHandle];
	if(xActiveSound.m_iLoadedSoundIndex == g_iLoadedSoundIndexBeingUnloaded)
	{
		Asura_Assert(xActiveSound.m_bNoExternalHandle,"Stopping a sound with an external handle");
		DestroySound(iSoundHandle);
	}
}

void Asura_Sound_System::DestroySound(Asura_Handle& iSoundHandle)
{
	if (iSoundHandle == ASURA_INVALID_HANDLE)
    {
        return;
    }

    Asura_ActiveSoundSample& xActiveSound = s_pxActiveSoundList[iSoundHandle];
    if (xActiveSound.m_iPlayingSoundHandle != ASURA_INVALID_HANDLE)
    {
        PlatformDestroySound(xActiveSound.m_iPlayingSoundHandle);
        Asura_Sound_OutputToDebugger(("Release playing sound handle %d for sound %d.\n", xActiveSound.m_iPlayingSoundHandle, xActiveSound.m_iLoadedSoundIndex));
        ReleasePlayingSoundHandle(xActiveSound.m_iPlayingSoundHandle);

		// decrease number of instances
		Asura_Assert(g_ucLoadedSoundNumActiveInstances[xActiveSound.m_iLoadedSoundIndex] > 0, "no active instances");
		--g_ucLoadedSoundNumActiveInstances[xActiveSound.m_iLoadedSoundIndex];
    }
    xActiveSound.Initialise(ASURA_SID_NO_SOUND, ASURA_INVALID_LOADED_SOUND_INDEX);
    s_pxActiveSoundHandleList->ReleaseHandle(iSoundHandle);
    iSoundHandle = ASURA_INVALID_HANDLE;
}

/// DWE - 27/11/2002 10:06:06 - This function is used to tell the sound system to deal with the sound from 
/// now on, so the user doesn't have to manage the sound handle after calling this. 
void Asura_Sound_System::FinishedWithSoundHandle(Asura_Handle& iSoundHandle)
{
	if(iSoundHandle != ASURA_INVALID_HANDLE)
	{
        Asura_Assert(!s_pxActiveSoundList[iSoundHandle].m_bLooping, "looping sounds must have external handle");
		s_pxActiveSoundList[iSoundHandle].m_bNoExternalHandle = 1;
		iSoundHandle = ASURA_INVALID_HANDLE;
	}
	else
	{
		Asura_Assert(0, "Finished with sound can't take invalid handle");
	}
}

bool Asura_Sound_System::PlaySound(Asura_Handle iSoundHandle, bool looping)
{
	Asura_Assert(iSoundHandle != ASURA_INVALID_HANDLE, "invalid handle");
    if (iSoundHandle == ASURA_INVALID_HANDLE)
    {
        return false;
    }
    Asura_ActiveSoundSample& xActiveSound = s_pxActiveSoundList[iSoundHandle];

    // (MH - 16.01.2003) if this is called again after we already have a playing sound handle
    // but the sound hasn't been started by the platform, just ignore it.
    if (xActiveSound.m_iPlayingSoundHandle != ASURA_INVALID_HANDLE) 
    {
        return true;
    }

    xActiveSound.m_bLooping = looping;
    
    // init 2d or 3d sound volume
    bool  bSoundIsPlaying = true;
    float fVolume = 1.0f;
    float fPan    = 0.5f;
    float fFade   = 1.0f;
    if (xActiveSound.m_bUse3dVol)
    {
        Asura_Assert(!xActiveSound.m_bUseHardPan, "can't mix hard pan and 3d volume");
		bSoundIsPlaying  = Calculate3dVolumePanAndFade(iSoundHandle, &fVolume, &fPan, &fFade);		
		bSoundIsPlaying &= Project_Adjust3dVolumePanAndFade( iSoundHandle, fVolume, fPan, fFade );
    }
    else
    {
        if (xActiveSound.m_bUseHardPan)
        {
            fPan = GetHardPan(xActiveSound.m_uHardPanID);
        }
        fVolume = xActiveSound.m_fVolume;		
		bSoundIsPlaying = Project_Adjust2dVolume( iSoundHandle, fVolume );
    }

    // (MH - 11.10.2002) note: non looping sounds will not be started when they come into range.
	// So if they don't start now they won't ever be heard.
	if (bSoundIsPlaying)
    {
		return PlaySoundInternal(iSoundHandle, fVolume, fPan, fFade);
    }
    else
    {
        return looping;
    }
}
DEBUGVAR bool dbg_bTestEQ = false;
bool Asura_Sound_System::PlaySoundInternal(Asura_Handle iSoundHandle, float fVolume, float fPan, float fFade)
{
    Asura_ActiveSoundSample& xActiveSound = s_pxActiveSoundList[iSoundHandle];
    Asura_Assert(xActiveSound.m_iPlayingSoundHandle == ASURA_INVALID_HANDLE, "sound already has playing handle");
    if (xActiveSound.m_iPlayingSoundHandle != ASURA_INVALID_HANDLE) return false;

	// (MH - 08.07.2003) limit on number of instances of each loaded sound
    // (NJD - 27/08/2004) fed up with this triggering spuriously and cancelling legitimate sounds
    //                    changed it to advise in the debugging window and still play the sound
	if (g_ucLoadedSoundNumActiveInstances[xActiveSound.m_iLoadedSoundIndex] >= dbg_uMaxLoadedSoundActiveInstances)
	{
		static int ls_iLastWarning = -1;
		if (ls_iLastWarning != xActiveSound.m_iLoadedSoundIndex)
		{
			ls_iLastWarning = xActiveSound.m_iLoadedSoundIndex;
			Asura_OutputToDebugger(("sound with loaded index %d has many instances active\n", xActiveSound.m_iLoadedSoundIndex));
		}
	}

    bool ok = GetPlayingSoundHandle(xActiveSound.m_iPlayingSoundHandle, xActiveSound.m_bUseEQ);
    if (ok)
	{
		Asura_Sound_OutputToDebugger(("Got playing sound handle %d for sound %d.\n", xActiveSound.m_iPlayingSoundHandle, xActiveSound.m_iLoadedSoundIndex));
	}
	else
	{
		Asura_Sound_OutputToDebugger(("Failed to get playing sound handle for sound %d.\n", xActiveSound.m_iLoadedSoundIndex));
	}

	if (ok)
    {
		// increase number of instances
		++g_ucLoadedSoundNumActiveInstances[xActiveSound.m_iLoadedSoundIndex];
		Asura_Sound_OutputToDebugger(("active instances for loaded index %d = %d.\n", xActiveSound.m_iLoadedSoundIndex, g_ucLoadedSoundNumActiveInstances[xActiveSound.m_iLoadedSoundIndex]));
		
		//See if this sound index has been mapped to another one. RAR 25/04/03
		int iRemappedSoundIndex = xActiveSound.m_iLoadedSoundIndex;
		const Asura_SoundProperties* pxSoundProperties = GetSoundPropertiesByIndex(xActiveSound.m_iLoadedSoundIndex);
		if(pxSoundProperties && pxSoundProperties->m_iLoadedSoundIndexMapping != ASURA_INVALID_LOADED_SOUND_INDEX)
		{
			iRemappedSoundIndex = pxSoundProperties->m_iLoadedSoundIndexMapping;
		}


		Asura_Assert(ASURA_INVALID_LOADED_SOUND_INDEX != iRemappedSoundIndex, "sound is no longer loaded");
		ok = (ASURA_INVALID_LOADED_SOUND_INDEX != iRemappedSoundIndex);

		// (DJG - 07/03/2007) Determine whether or not to use full 3d functionality based on settings for volume and pan.
		// This allows phonons to carry ASURA_SOUND_NOPAN sounds that also have a 3d position and allow for distance attenuation.
		const bool bUseHardware3dPlayback = xActiveSound.m_bUse3dVol && xActiveSound.m_bUse3dPan && s_bUseHardware3dPan;
		if (ok)
		{
			ok = PlatformCreateSound(iRemappedSoundIndex, xActiveSound.m_iPlayingSoundHandle, 
									dbg_bTestEQ ? false : xActiveSound.m_bUseEQ, 
									bUseHardware3dPlayback );
			Asura_Assert(ok, "PlatformCreateSound failed.\n");
		}
        if (ok)
        {
			int iPlayingSoundHandle = xActiveSound.m_iPlayingSoundHandle;
			if (s_bUseHardware3dPan && xActiveSound.m_bForcedPanAndFade)
			{
				ok = Platform_HardwarePanSetSoundForcedPanAndFade(iPlayingSoundHandle, xActiveSound.m_fForcedPan, xActiveSound.m_fForcedFade);
				// hardware 3d mixing does pan only so just set volume
				ok = ok && SetPlayingSoundVolume(iPlayingSoundHandle, fVolume);
			}
			else if ( bUseHardware3dPlayback )
			{
				// since the sound hasn't started yet, need to initialise pos and range.
				ok = Platform_HardwarePanSetSoundPosition(iPlayingSoundHandle, xActiveSound.m_x3dData.m_xPosition);

				ok = ok && Platform_HardwarePanSetSoundRadii(iPlayingSoundHandle, 
					xActiveSound.m_x3dData.m_xSphericalData.m_fInnerRange,
					xActiveSound.m_x3dData.m_xSphericalData.m_fOuterRange);

				{
					// hardware 3d mixing does pan only so just set volume
					ok = ok && SetPlayingSoundVolume(iPlayingSoundHandle, fVolume);
				}

				// intialise the setting of 3d pan
				Platform_HardwarePanSetSoundSpatial(iPlayingSoundHandle, xActiveSound.m_bUse3dPan);
			}
			else
			{
				ok = SetPlayingSoundVolumePanAndFade(iPlayingSoundHandle, fVolume, fPan, fFade);
			}
			if (ok)
			{
				// (MH - 16.08.2002) handle pitch scale here
				float fPitch = !xActiveSound.m_bPitchUsesTimeScale ? xActiveSound.m_fPitch : xActiveSound.m_fPitch * s_fPitchScale;
				fPitch *= xActiveSound.m_fPitchDoppler;
				PlatformSetSoundPitch(iPlayingSoundHandle, fPitch);

				Platform_SetSoundUsesReverb(iPlayingSoundHandle, xActiveSound.m_bUseReverb);
				Platform_SetSoundUsesDistanceFilter(iPlayingSoundHandle, xActiveSound.m_bUseDistanceFilter);
				
				xActiveSound.m_fOcclusion = 0.0f;
				if (xActiveSound.m_bUse3dVol )
				{
					//Update distance filter
					if(xActiveSound.m_bUseDistanceFilter)
					{
						const float fGain = CalculateDistanceFilterGain (iSoundHandle);
						#ifdef ASURA_ASSERT
						bool bRet = 
						#endif
						Platform_SetSoundDistanceFilterGain(xActiveSound.m_iPlayingSoundHandle, fGain);
						Asura_Assert(bRet,"Call to Platform_SetSoundDistanceFilterGain failed");
					}
					//Update occlusion
					if(xActiveSound.m_bUseOcclusion)
					{
						//If we are definately going to update the occlusion, set it to an invalid value (to set immediately rather than tending)
						xActiveSound.m_fOcclusion = fASURA_INVALID_OCCLUSION_VALUE;
						UpdateOcclusionData (iSoundHandle);

						xActiveSound.m_fDryVolume = AdjustVolumeByOcclusion(1.0f, xActiveSound.m_fOcclusion);
						//If we are in the same reverb zone, leave the wet path at full volume, otherwise match the dry volume
						if(xActiveSound.m_bOcclusionSameReverbZone)
						{
							xActiveSound.m_fWetVolume = AdjustVolumeByOcclusion(1.0f, xActiveSound.m_fOcclusion * fASURA_WET_VOLUME_OCCLUSION_FACTOR);
						}
						else
						{
							xActiveSound.m_fWetVolume = xActiveSound.m_fDryVolume;
						}
					}
				}
				Platform_SetSoundWetDryVolume(iPlayingSoundHandle,xActiveSound.m_fWetVolume,xActiveSound.m_fDryVolume);
				Platform_SetOcclusionLPF(iPlayingSoundHandle, xActiveSound.m_fOcclusion);
				
				ok = PlatformPlaySound(iPlayingSoundHandle, xActiveSound.m_bLooping);
			}
        }
        else
        {
            ReleasePlayingSoundHandle(xActiveSound.m_iPlayingSoundHandle);
            xActiveSound.m_iPlayingSoundHandle = ASURA_INVALID_HANDLE;

			// decrease number of instances
			Asura_Assert(g_ucLoadedSoundNumActiveInstances[xActiveSound.m_iLoadedSoundIndex] > 0, "no active instances");
			--g_ucLoadedSoundNumActiveInstances[xActiveSound.m_iLoadedSoundIndex];
        }
    }
    return ok;
}

void Asura_Sound_System::SetSoundVolume(Asura_Handle iSoundHandle, float fVolume)
{
	Asura_Assert(iSoundHandle != ASURA_INVALID_HANDLE, "invalid handle");
    if (iSoundHandle == ASURA_INVALID_HANDLE)
    {
        return;
    }

    const Asura_SoundProperties* pxSoundProperties = GetSoundPropertiesByIndex(s_pxActiveSoundList[iSoundHandle].m_iLoadedSoundIndex);
    if( pxSoundProperties )
    {
        fVolume *= pxSoundProperties->m_fVolumeScale;
    }
    s_pxActiveSoundList[iSoundHandle].m_fVolume = fVolume;

    
    if (SoundIsPlaying(iSoundHandle) && !s_pxActiveSoundList[iSoundHandle].m_bUse3dVol)
    {
        Asura_Assert(s_pxActiveSoundList[iSoundHandle].m_iPlayingSoundHandle != ASURA_INVALID_HANDLE, "invalid handle");
        // this sound won't be updated so call platform function now
        float fPan;
		float fFade = 1.0f;	// fully at the front
        if (s_pxActiveSoundList[iSoundHandle].m_bUseHardPan)
        {
            fPan = GetHardPan(s_pxActiveSoundList[iSoundHandle].m_uHardPanID);
        }
        else
        {
            fPan = 0.5f;
        }

		#ifdef ASURA_ASSERT
        bool ok = 
		#endif
		SetPlayingSoundVolumePanAndFade(s_pxActiveSoundList[iSoundHandle].m_iPlayingSoundHandle, fVolume, fPan, fFade);
        Asura_Assert(ok, "failed to set sound volume");
    }
}

void Asura_Sound_System::SetSoundPitch(Asura_Handle iSoundHandle, float pitch)
{
	Asura_Assert(iSoundHandle != ASURA_INVALID_HANDLE, "invalid handle");
    if (iSoundHandle == ASURA_INVALID_HANDLE)
    {
        return;
    }

    s_pxActiveSoundList[iSoundHandle].m_fPitch = pitch;

	const Asura_SoundProperties* pxSoundProperties = GetSoundPropertiesByIndex(s_pxActiveSoundList[iSoundHandle].m_iLoadedSoundIndex);
    if(pxSoundProperties) s_pxActiveSoundList[iSoundHandle].m_fPitch *= pxSoundProperties->m_fPitchScale;


    if (SoundIsPlaying(iSoundHandle))
    {
        Asura_Assert(s_pxActiveSoundList[iSoundHandle].m_iPlayingSoundHandle != ASURA_INVALID_HANDLE, "invalid handle");
		float fPitch = !s_pxActiveSoundList[iSoundHandle].m_bPitchUsesTimeScale ? pitch : pitch * s_fPitchScale;
		fPitch *= s_pxActiveSoundList[iSoundHandle].m_fPitchDoppler;
		#ifdef ASURA_ASSERT
		bool ok = 
		#endif
		PlatformSetSoundPitch(s_pxActiveSoundList[iSoundHandle].m_iPlayingSoundHandle, fPitch);
        Asura_Assert(ok, "failed to set sound pitch");
    }
}

void Asura_Sound_System::SetSoundPosition(Asura_Handle iSoundHandle, const Asura_Vector_3& xPosition, bool bEnableEQ /* = s_b3dSoundEQDefault */)
{
	Asura_Assert(iSoundHandle != ASURA_INVALID_HANDLE, "invalid handle");
    if (iSoundHandle == ASURA_INVALID_HANDLE)
    {
        return;
    }
	Asura_ActiveSoundSample& xActiveSound = s_pxActiveSoundList[iSoundHandle];
    Asura_Assert(!xActiveSound.m_bUseHardPan, "can't mix hard pan and 3d volume");

	if ( xActiveSound.m_bUse3dVol )
	{
		const float fTimeSlice = Asura_Timers::GetGameTimeSlice();
		xActiveSound.m_x3dData.m_xVelocity = xPosition - xActiveSound.m_x3dData.m_xPosition;
		if(fTimeSlice > 0.f)
		{
			xActiveSound.m_x3dData.m_xVelocity /= fTimeSlice;
		}
	}

    xActiveSound.m_bUse3dVol = 1;
    xActiveSound.m_x3dData.m_xPosition = xPosition;
	xActiveSound.m_bUseEQ = bEnableEQ;

	int iPlayingHandle = xActiveSound.m_iPlayingSoundHandle;
    if (iPlayingHandle != ASURA_INVALID_HANDLE)
    {
		// only set position if this platform is doing hardware 3d panning.
		if (s_bUseHardware3dPan && (!xActiveSound.m_bForcedPanAndFade))
		{
			Platform_HardwarePanSetSoundPosition(iPlayingHandle, xPosition);
		}
	}
}

void Asura_Sound_System::SetSoundRadius(Asura_Handle iSoundHandle, float fInner, float fOuter)
{
	Asura_Assert(iSoundHandle != ASURA_INVALID_HANDLE, "invalid handle");
    if (iSoundHandle == ASURA_INVALID_HANDLE)
    {
        return;
    }

	//get default inner/outer radius if needed
	const Asura_SoundProperties* pxSoundProperties = GetSoundPropertiesByIndex(s_pxActiveSoundList[iSoundHandle].m_iLoadedSoundIndex);

	if(fInner == ASURA_SOUND_USE_DEFAULT_RADIUS)
	{
		fInner = pxSoundProperties ? pxSoundProperties->m_fDefaultInnerRadius : fASURA_SOUND_DEFAULT_INNER_RADIUS;
	}
	if(fOuter == ASURA_SOUND_USE_DEFAULT_RADIUS)
	{
		fOuter = pxSoundProperties ? pxSoundProperties->m_fDefaultOuterRadius : fASURA_SOUND_DEFAULT_OUTER_RADIUS;
	}



    Asura_Assert(fInner <= fOuter, "invalid parameters");
    Asura_Assert(fInner >= 0.5f, "inner radius too small");
    Asura_Assert(!s_pxActiveSoundList[iSoundHandle].m_bUseHardPan, "can't mix hard pan and 3d volume");
    Asura_Assert(s_pxActiveSoundList[iSoundHandle].m_bUse3dVol, "need to set sound position first");
    
    // set inner and outer radius
    s_pxActiveSoundList[iSoundHandle].m_x3dData.m_xSphericalData.m_fInnerRange = fInner;
    s_pxActiveSoundList[iSoundHandle].m_x3dData.m_xSphericalData.m_fOuterRange = fOuter;

	if (s_bUseHardware3dPan)
	{
		Asura_Handle iPlayingHandle = s_pxActiveSoundList[iSoundHandle].m_iPlayingSoundHandle;
		if (iPlayingHandle != ASURA_INVALID_HANDLE)
		{
			Asura_Assert(iPlayingHandle != ASURA_INVALID_HANDLE, "invalid handle");
			Platform_HardwarePanSetSoundRadii( iPlayingHandle, fInner, fOuter );
		}
	}
}

void Asura_Sound_System::SetSoundCuboidParameters(Asura_Handle iSoundHandle, 
                                                  const Asura_Vector_3& xInner, 
                                                  const Asura_Vector_3& xOuter)
{
	Asura_Assert(iSoundHandle != ASURA_INVALID_HANDLE, "invalid handle");
    if (iSoundHandle == ASURA_INVALID_HANDLE)
    {
        return;
    }
    Asura_Assert(xInner.x <= xOuter.x && xInner.y <= xOuter.y && xInner.z <= xOuter.z, "invalid parameters");
    Asura_Assert(xInner.x >= 0.5f && xInner.y >= 0.5f && xInner.z >= 0.5f, "inner radius too small");

    // set inner and outer radius for each axis
    s_pxActiveSoundList[iSoundHandle].m_x3dData.m_xCuboidData.m_xInnerRanges = xInner;
    s_pxActiveSoundList[iSoundHandle].m_x3dData.m_xCuboidData.m_xOuterRanges = xOuter;
}

void Asura_Sound_System::SetSoundUseCuboidFalloff(Asura_Handle iSoundHandle, bool bUseCuboid)
{
	Asura_Assert(iSoundHandle != ASURA_INVALID_HANDLE, "invalid handle");
    if (iSoundHandle == ASURA_INVALID_HANDLE)
    {
        return;
    }
    Asura_Assert(!s_pxActiveSoundList[iSoundHandle].m_bUseHardPan, "can't mix hard pan and 3d volume");
    Asura_Assert(s_pxActiveSoundList[iSoundHandle].m_bUse3dVol, "need to set sound position first");
    s_pxActiveSoundList[iSoundHandle].m_bUse3dCuboid = bUseCuboid;
}

void Asura_Sound_System::SetSoundSpatial(Asura_Handle iSoundHandle, bool bIsSpatial)
{
	Asura_Assert(iSoundHandle != ASURA_INVALID_HANDLE, "invalid handle");
    if (iSoundHandle == ASURA_INVALID_HANDLE)
    {
        return;
    }
    s_pxActiveSoundList[iSoundHandle].m_bUse3dPan = bIsSpatial;
	
	if (s_bUseHardware3dPan)
	{
		int iPlayingHandle = s_pxActiveSoundList[iSoundHandle].m_iPlayingSoundHandle;
		if (iPlayingHandle != ASURA_INVALID_HANDLE)
		{
			Platform_HardwarePanSetSoundSpatial( iPlayingHandle, bIsSpatial );
		}
	}
}

void Asura_Sound_System::SetSoundHardPan(Asura_Handle iSoundHandle, unsigned int iCameraID)
{
	Asura_Assert(iSoundHandle != ASURA_INVALID_HANDLE, "invalid handle");
    if (iSoundHandle == ASURA_INVALID_HANDLE)
    {
        return;
    }
    Asura_Assert(iCameraID < 4, "sound system can't handle more than 4 split screens");
    Asura_Assert(!s_pxActiveSoundList[iSoundHandle].m_bUse3dVol, "can't mix hard pan and 3d volume");
	Asura_Assert(!s_pxActiveSoundList[iSoundHandle].m_bForcedPanAndFade, "can't mix hard pan and forced pan/fade");
    s_pxActiveSoundList[iSoundHandle].m_bUseHardPan = 1;
    s_pxActiveSoundList[iSoundHandle].m_uHardPanID = iCameraID;
}

void Asura_Sound_System::SetSoundForcedPanAndFade(Asura_Handle iSoundHandle, float fPan, float fFade)
{
	Asura_Assert(iSoundHandle != ASURA_INVALID_HANDLE, "invalid handle");
    if (iSoundHandle == ASURA_INVALID_HANDLE)
    {
        return;
    }
	Asura_Assert(!s_pxActiveSoundList[iSoundHandle].m_bUseHardPan, "can't mix hard pan and forced pan/fade");
    s_pxActiveSoundList[iSoundHandle].m_bForcedPanAndFade = 1;
    s_pxActiveSoundList[iSoundHandle].m_fForcedPan = fPan;
	s_pxActiveSoundList[iSoundHandle].m_fForcedFade = fFade;

	if (s_bUseHardware3dPan)
	{
		int iPlayingHandle = s_pxActiveSoundList[iSoundHandle].m_iPlayingSoundHandle;
		if (iPlayingHandle != ASURA_INVALID_HANDLE)
		{
			Platform_HardwarePanSetSoundForcedPanAndFade( iPlayingHandle, fPan, fFade );
		}
	}
}

bool Asura_Sound_System::SoundIsPlaying(Asura_Handle iSoundHandle)
{
    if (iSoundHandle == ASURA_INVALID_HANDLE)
    {
        return false;
    }
    Asura_Handle iPlayingHandle = s_pxActiveSoundList[iSoundHandle].m_iPlayingSoundHandle;
    if (iPlayingHandle != ASURA_INVALID_HANDLE)
    {
        return PlatformSoundIsPlaying(iPlayingHandle);
    }
    else
    {
        return false;
    }
}

bool Asura_Sound_System::SoundIsPreparing(Asura_Handle iSoundHandle)
{
	if (iSoundHandle == ASURA_INVALID_HANDLE)
	{
		return false;
	}
	Asura_Handle iPlayingHandle = s_pxActiveSoundList[iSoundHandle].m_iPlayingSoundHandle;
	if (iPlayingHandle != ASURA_INVALID_HANDLE)
	{
		return PlatformSoundIsPreparing(iPlayingHandle);
	}
	else
	{
		return false;
	}
}

bool Asura_Sound_System::SoundHasVoiceAllocated(Asura_Handle iSoundHandle)
{
	if (iSoundHandle == ASURA_INVALID_HANDLE)
	{
		return false;
	}
	return s_pxActiveSoundList[iSoundHandle].m_iPlayingSoundHandle != ASURA_INVALID_HANDLE;
}

bool Asura_Sound_System::SoundIsLooping(Asura_Handle iSoundHandle)
{
	Asura_Assert(iSoundHandle != ASURA_INVALID_HANDLE, "invalid handle");
    if (iSoundHandle == ASURA_INVALID_HANDLE)
    {
        return false;
    }
    return s_pxActiveSoundList[iSoundHandle].m_bLooping;
}

Asura_Sound_ID Asura_Sound_System::GetSoundID(Asura_Handle iSoundHandle)
{
	Asura_Assert(iSoundHandle != ASURA_INVALID_HANDLE, "invalid handle");
    if (iSoundHandle == ASURA_INVALID_HANDLE)
    {
        return ASURA_SID_NO_SOUND;
    }
    return s_pxActiveSoundList[iSoundHandle].m_eSoundID;
}


void Asura_Sound_System::SetSoundClipVolume(Asura_Handle iSoundHandle,const Asura_VolumeGroup* pxClipVolume)
{
	Asura_Assert(iSoundHandle != ASURA_INVALID_HANDLE, "invalid handle");
    if (iSoundHandle == ASURA_INVALID_HANDLE)
    {
        return;
    }
    s_pxActiveSoundList[iSoundHandle].m_x3dData.m_pxClipVolume = pxClipVolume;
}



void Asura_Sound_System::SetSoundInvertClipVolume(Asura_Handle iSoundHandle,bool bInvertClipVolume)
{
	Asura_Assert(iSoundHandle != ASURA_INVALID_HANDLE, "invalid handle");
    if (iSoundHandle == ASURA_INVALID_HANDLE)
    {
        return;
    }
    s_pxActiveSoundList[iSoundHandle].m_bInvertClipVolume = bInvertClipVolume;
}


void Asura_Sound_System::SetSoundOrient(Asura_Handle iSoundHandle,const Asura_Quat& xOrient)
{
	Asura_Assert(iSoundHandle != ASURA_INVALID_HANDLE, "invalid handle");
    if (iSoundHandle == ASURA_INVALID_HANDLE)
    {
        return;
    }
    s_pxActiveSoundList[iSoundHandle].m_x3dData.m_xInverseOrient = xOrient;
    s_pxActiveSoundList[iSoundHandle].m_x3dData.m_xInverseOrient.w = -xOrient.w;
}

void Asura_Sound_System::SetSoundUseOrient(Asura_Handle iSoundHandle,bool bUseOrient)
{
	Asura_Assert(iSoundHandle != ASURA_INVALID_HANDLE, "invalid handle");
    if (iSoundHandle == ASURA_INVALID_HANDLE)
    {
        return;
    }
    s_pxActiveSoundList[iSoundHandle].m_bUseOrient = bUseOrient;
}

void Asura_Sound_System::SetSoundMinVolume(Asura_Handle iSoundHandle, float fMinVolume)
{
//	Asura_Assert(iSoundHandle != ASURA_INVALID_HANDLE, "invalid handle");
//    if (iSoundHandle == ASURA_INVALID_HANDLE)
//    {
//        return;
//    }
//	Asura_Assert(s_pxActiveSoundList[iSoundHandle].m_bUse3dVol, "not a 3d sound");
//    s_pxActiveSoundList[iSoundHandle].m_x3dData.m_fMinVolume = fMinVolume;
	Asura_Assert(false, "this function is no longer supported. Set appropriate radii instead");
}

void Asura_Sound_System::SetSoundUseEQ(Asura_Handle iSoundHandle, bool bEnable)
{
	Asura_Assert(iSoundHandle != ASURA_INVALID_HANDLE, "invalid handle");
    if (iSoundHandle == ASURA_INVALID_HANDLE)
    {
        return;
    }
    s_pxActiveSoundList[iSoundHandle].m_bUseEQ = bEnable;
}

void Asura_Sound_System::SetSoundPitchUsesTimeScale(Asura_Handle iSoundHandle, bool bUseTimeScale, float fTimeScaleToUse/* = 1.0f*/)
{
	Asura_Assert(iSoundHandle != ASURA_INVALID_HANDLE, "invalid handle");
    if (iSoundHandle == ASURA_INVALID_HANDLE)
    {
        return;
    }
	s_pxActiveSoundList[iSoundHandle].m_bPitchUsesTimeScale = bUseTimeScale;
	if (!bUseTimeScale)
	{
		SetSoundPitch(iSoundHandle, fTimeScaleToUse);
	}
}

void Asura_Sound_System::SetSoundUsesReverb(Asura_Handle iSoundHandle, bool bUseReverb)
{
	Asura_Assert(iSoundHandle != ASURA_INVALID_HANDLE, "invalid handle");
    if (iSoundHandle == ASURA_INVALID_HANDLE)
    {
        return;
    }
	s_pxActiveSoundList[iSoundHandle].m_bUseReverb = bUseReverb;
}


void Asura_Sound_System::SetSoundWetDryVolumeInternal(Asura_Handle iSoundHandle, float fWetVolume, float fDryVolume)
{
	Asura_Assert(iSoundHandle != ASURA_INVALID_HANDLE, "invalid handle");
	Asura_Assert((fWetVolume >= 0.0f) && (fWetVolume <= 1.0f),"Wet volume should be in the range 0 - 1");
	Asura_Assert((fDryVolume >= 0.0f) && (fDryVolume <= 1.0f),"Dry volume should be in the range 0 - 1");

	s_pxActiveSoundList[iSoundHandle].m_fWetVolume = fWetVolume;
	s_pxActiveSoundList[iSoundHandle].m_fDryVolume = fDryVolume;

	if(SoundIsPlaying(iSoundHandle))
	{
		Platform_SetSoundWetDryVolume(s_pxActiveSoundList[iSoundHandle].m_iPlayingSoundHandle,fWetVolume,fDryVolume);
	}
}

void Asura_Sound_System::SetSoundUsesDistanceFilter(Asura_Handle iSoundHandle, bool bUseDistanceFilter)
{
	Asura_Assert(iSoundHandle != ASURA_INVALID_HANDLE, "invalid handle");
    if (iSoundHandle == ASURA_INVALID_HANDLE)
    {
        return;
    }
	s_pxActiveSoundList[iSoundHandle].m_bUseDistanceFilter = bUseDistanceFilter;
}

void Asura_Sound_System::SetSoundUsesDoppler(Asura_Handle iSoundHandle, bool bUseDoppler)
{
	Asura_Assert(iSoundHandle != ASURA_INVALID_HANDLE, "invalid handle");
	if (iSoundHandle == ASURA_INVALID_HANDLE)
	{
		return;
	}
	s_pxActiveSoundList[iSoundHandle].m_bUseDoppler = bUseDoppler;
}

void Asura_Sound_System::SetSoundUsesOcclusion(Asura_Handle iSoundHandle, bool bUseOcclusion)
{
	Asura_Assert(iSoundHandle != ASURA_INVALID_HANDLE, "invalid handle");
	if (iSoundHandle == ASURA_INVALID_HANDLE)
	{
		return;
	}
	s_pxActiveSoundList[iSoundHandle].m_bUseOcclusion = bUseOcclusion;
}

void Asura_Sound_System::GetSoundPosition(Asura_Handle iSoundHandle, Asura_Vector_3 &xPosition)
{
	Asura_Assert(iSoundHandle != ASURA_INVALID_HANDLE, "invalid handle");
    if (iSoundHandle == ASURA_INVALID_HANDLE)
    {
		xPosition.Zero();
        return;
    }
    Asura_Assert(!s_pxActiveSoundList[iSoundHandle].m_bUseHardPan, "can't mix hard pan and 3d volume");
    xPosition = s_pxActiveSoundList[iSoundHandle].m_x3dData.m_xPosition;
}

float Asura_Sound_System::GetSoundVolume(Asura_Handle iSoundHandle)
{
	Asura_Assert(iSoundHandle != ASURA_INVALID_HANDLE, "invalid handle");
    if (iSoundHandle == ASURA_INVALID_HANDLE)
    {
        return 0.0f;
    }
    Asura_Assert(!s_pxActiveSoundList[iSoundHandle].m_bUseHardPan, "can't mix hard pan and 3d volume");
    return s_pxActiveSoundList[iSoundHandle].m_fVolume;
}

// (MH - 05.06.2002) some functions to make sound play calls easier
Asura_Handle Asura_Sound_System::Create3dSound(Asura_Sound_ID sound_id, const Asura_Vector_3 * pPosition, float outer_radius, float inner_radius)
{
    Asura_Handle iSoundHandle = CreateSound(sound_id);
    bool ok = (iSoundHandle != ASURA_INVALID_HANDLE);
    if (ok)
    {
        if (pPosition)
        {
            SetSoundPosition(iSoundHandle, *pPosition);
            SetSoundRadius(iSoundHandle, inner_radius, outer_radius);
        }
    }
    return iSoundHandle;
}

bool Asura_Sound_System::PlayOneShotSound(Asura_Sound_ID sound_id, const Asura_Vector_3 * pPosition, float outer_radius, float inner_radius, float fVolume/*=1.0f*/, float fPitch/*=1.0f*/ )
{
    Asura_Handle iSoundHandle = Create3dSound(sound_id, pPosition, outer_radius, inner_radius);		
	bool ok = (iSoundHandle != ASURA_INVALID_HANDLE);
    if (ok)
    {
		Asura_Sound_System::SetSoundVolume( iSoundHandle, fVolume );
		Asura_Sound_System::SetSoundPitch( iSoundHandle, fPitch );
        ok = PlaySound(iSoundHandle, false);
        Asura_Sound_System::FinishedWithSoundHandle(iSoundHandle);
    }
    return ok;
}

bool Asura_Sound_System::Play2DSound(Asura_Sound_ID sound_id, float fVolume /*= 1.f*/)
{
    Asura_Handle iSoundHandle = CreateSound(sound_id);
    bool ok = (iSoundHandle != ASURA_INVALID_HANDLE);
    if (ok)
    {
		Asura_Sound_System::SetSoundVolume(iSoundHandle, fVolume);
        ok = PlaySound(iSoundHandle, false);
        Asura_Sound_System::FinishedWithSoundHandle(iSoundHandle);
    }
    return ok;
}

Asura_Handle Asura_Sound_System::PlayLoopingSound(Asura_Sound_ID sound_id, const Asura_Vector_3 * pPosition, float outer_radius, float inner_radius)
{
    Asura_Handle iSoundHandle = Create3dSound(sound_id, pPosition, outer_radius, inner_radius);
    if (iSoundHandle != ASURA_INVALID_HANDLE)
    {
        bool ok = PlaySound(iSoundHandle, true);
        if (!ok)
        {
            DestroySound(iSoundHandle);
        }
    }
    return iSoundHandle;
}

float Asura_Sound_System::IsPositionInRange( const Asura_Vector_3& xPosition,
                                             const float fOuterRadius,
                                             const float fInnerRadius )
{
    float fSmallestRangeSq = Asura_Maths::LargeFloat;
    for ( int iCamID = 0; iCamID < s_iNumSplitScreens; ++iCamID )
    {
        const float fRangeSq = ( xPosition - s_axSoundCentre[ iCamID ] ).MagnitudeSquared();
        if ( fRangeSq < fSmallestRangeSq )
        {
            fSmallestRangeSq = fRangeSq;
        }
    }

    if ( fSmallestRangeSq < fOuterRadius * fOuterRadius )
    {
        // assume a linear falloff
        return GetLinearVolumeFalloff( fInnerRadius, fOuterRadius, Asura_Maths::Sqrt( fSmallestRangeSq ) );
    }
    else
    {
        return 0.0f;
    }
}

float Asura_Sound_System::GetRequiredOuterRadius( const Asura_Vector_3& xPosition,
                                                  const float fVolumeRequired,
                                                  const float fInnerRadius )
{
    float fSmallestRange = Asura_Maths::LargeFloat;
    for ( int iCamID = 0; iCamID < s_iNumSplitScreens; ++iCamID )
    {
        const float fRange = ( xPosition - s_axSoundCentre[ iCamID ] ).Magnitude();
        if ( fRange < fSmallestRange )
        {
            fSmallestRange = fRange;
        }
    }

    const float fDistanceToInnerRadius = fSmallestRange - fInnerRadius;
    if ( fDistanceToInnerRadius < 0.0f )
    {
        // if the sound is nearer to a camera than its inner radius then it will play at full volume
        // return a default outer radius
        return ( fASURA_SOUND_DEFAULT_OUTER_RADIUS > fInnerRadius ?
                 fASURA_SOUND_DEFAULT_OUTER_RADIUS :
                 fInnerRadius + 1.0f );
    }
    else
    {
        // this assumes the volume is in the range 0.0f -> 1.0f
        const float fNewSlope = ( fVolumeRequired - 1.0f ) / Asura_Maths::Abs( fDistanceToInnerRadius );
        return fInnerRadius + ( -1.0f / fNewSlope );
    }
}

// IsSphereInRange() and IsBBInRange() return true is the volume intersects any listener.
bool Asura_Sound_System::IsSphereInRange( const Asura_Vector_3& xPosition, float fRadius )
{
	float fRadiusSquared = fRadius * fRadius;

    for ( int iCamID = 0; iCamID < s_iNumSplitScreens; ++iCamID )
    {
        if ( ( xPosition - s_axSoundCentre[iCamID] ).MagnitudeSquared() < fRadiusSquared)
		{
			return true;
		}
	}
	return false;
}

bool Asura_Sound_System::IsBBInRange( const Asura_Bounding_Box& xBB )
{
    for ( int iCamID = 0; iCamID < s_iNumSplitScreens; ++iCamID )
    {
		if (xBB.Contains(s_axSoundCentre[iCamID]))
		{
			return true;
		}
	}
	return false;
}

bool Asura_Sound_System::IsCuboidVolumeInRange(const Asura_Vector_3& xPosition, const Asura_Vector_3&  xExtents, const Asura_Quat* pxOrient)
{
	if(pxOrient)
	{
		Asura_Quat xInverseOrient(*pxOrient);
		xInverseOrient.Invert();
		for ( int iCamID = 0; iCamID < s_iNumSplitScreens; ++iCamID )
		{
			Asura_Vector_3 xRelativePosition = xPosition - s_axSoundCentre[iCamID];

			xRelativePosition = xInverseOrient.RotateVector(xRelativePosition);

			if (Asura_Maths::Abs(xRelativePosition.x) <= xExtents.x &&
				Asura_Maths::Abs(xRelativePosition.y) <= xExtents.y &&
				Asura_Maths::Abs(xRelativePosition.z) <= xExtents.z)
			{
				return true;
			}
		}
	}
	else
	{
		for ( int iCamID = 0; iCamID < s_iNumSplitScreens; ++iCamID )
		{
			Asura_Vector_3 xRelativePosition = xPosition - s_axSoundCentre[iCamID];

			if (Asura_Maths::Abs(xRelativePosition.x) <= xExtents.x &&
				Asura_Maths::Abs(xRelativePosition.y) <= xExtents.y &&
				Asura_Maths::Abs(xRelativePosition.z) <= xExtents.z)
			{
				return true;
			}
		}
	}
    
	return false;
}

bool Asura_Sound_System::Calculate3dVolumePanAndFade(Asura_Handle iSoundHandle, float* pfVolume, float* pfPan, float* pfFade)
{
	const Asura_ActiveSoundSample& xActiveSound = s_pxActiveSoundList[iSoundHandle];
    const Asura_3dSoundData* px3dData = &xActiveSound.m_x3dData;
    const float fVolume = xActiveSound.m_fVolume;
    const bool b3dPan = xActiveSound.m_bUse3dPan;

    Asura_Vector_3 xRelativePosn;
	Asura_Vector_3 xRotatedRelativePosn;
    float fPan = 0.0f;
	float fFade = 0.0f;
    float fScaledVolume = 0.0f;

    // for each camera, find the volume and pan. add them up and later divide by number of cameras.
    for (int iCamID = 0; iCamID < s_iNumSplitScreens; iCamID++)
    {
        const Asura_Vector_3* pxSoundCentre = &s_axSoundCentre[iCamID];

        // find the distance of the sound 
        xRelativePosn = px3dData->m_xPosition - *pxSoundCentre;

        float fDistance = xRelativePosn.Magnitude();

		//Check for sound bounding box.. if point is outside the box (inside if inverted) 
		//then we can skip this sound for this camera.

		if(px3dData->m_pxClipVolume)
		{
			bool bInside = px3dData->m_pxClipVolume->Contains(*pxSoundCentre);
			if(bInside == (xActiveSound.m_bInvertClipVolume ? true : false))
			{
				continue;	
			}
		}

        // (MH - 15.08.2002) added check for tiny or zero distances
        if (fDistance < 0.001f)
        {
            fScaledVolume += 1.0f;
            fPan += 0.5f;
			fFade += 0.5f;
            continue;
        }


		if(xActiveSound.m_bUseOrient && xActiveSound.m_bUse3dCuboid)
		{
			//apply the inverse of the orienation to the position , so it can
			//be checked against the cuboid
			xRotatedRelativePosn = xActiveSound.m_x3dData.m_xInverseOrient.RotateVector(xRelativePosn);

		}
		else
		{
			xRotatedRelativePosn = xRelativePosn;
		}
        
        // 1. calculate the volume of the sound
        float fVolume3dscale;
        // (MH - 08.08.2002) changed this to be either-or. There could be an option to combine 
        // these 2 falloffs, but it doesn't exist in tools yet, which caused cuboid sounds to
        // have zero volume unless they had spherical falloff set.
        // Just multiply fVolume3dscale by the falloff to combine.
        if (xActiveSound.m_bUse3dCuboid)
        {
            fVolume3dscale = GetCuboidVolumeFalloff(&px3dData->m_xCuboidData, &xRotatedRelativePosn);
        }
        else
        {
            fVolume3dscale = GetLinearVolumeFalloff( px3dData->m_xSphericalData.m_fInnerRange, 
                                                     px3dData->m_xSphericalData.m_fOuterRange, 
                                                     fDistance );
        }

        fScaledVolume += fVolume3dscale;

        // 2. calculate the pan of the sound. Don't bother if mono, 2d, or using hardware.
		if (s_eSoundOutputType == SoundOutputMono || (!b3dPan) || (s_bUseHardware3dPan))
        {
            fPan += 0.5f;
			fFade += 1.0f;	// leave sound at front
        }
        else    // find the pan of the sound 
        {
            Asura_Angle fFullangle;
            Asura_Angle fPanAngle;
			Asura_Angle fFadeAngle;

			// RCD: added these instead of repeatedly referencing them directly, because EVEN 
			// fully optimised code ends up regenerating them repeatedly, wasting many cycles.
			const float f360 = Asura_Maths::Deg360; 
			const float f270 = Asura_Maths::Deg270; 
			const float f180 = Asura_Maths::Deg180; 
			const float f90  = Asura_Maths::Deg90; 

	        const Asura_Matrix_3x3 * pSoundOrientation = &s_axSoundOrientation[iCamID];
            // ? rotate xRelativePosn by camera orientation first ?
            fFullangle = Asura_Maths::ArcTangent(xRelativePosn.x, xRelativePosn.z);

			const float fHeading = pSoundOrientation->Heading();

			fFullangle -= fHeading;
			if (fFullangle < 0.0f)
			{
				fFullangle += f360;
			}
            Asura_Assert(fFullangle >= 0.0f, "sanity check failed");
            Asura_Assert(fFullangle <= f360, "sanity check failed");
		
			// map fPanAngle onto [-Deg90 = full left,Deg90 = full right]
			// map fFadeAngle onto [-Deg90 = full back, Deg90 = full front]
            if (fFullangle > f270)
            {
                fPanAngle = (fFullangle - f360);
				fFadeAngle = (fFullangle - f270);
            }
            else if (fFullangle > f180)
            {
                fPanAngle = (f180 - fFullangle);
				fFadeAngle = (fFullangle - f270);
            }
			else if (fFullangle > f90)
            {
                fPanAngle = (f180 - fFullangle);
				fFadeAngle = (f90 - fFullangle);
            }
            else
            {
                fPanAngle = fFullangle;
				fFadeAngle = (f90 - fFullangle);
            }

#ifdef ASURA_ASSERT
            const float fDeg90 = f90 + 1.0e-6f;// allow for float inaccuracies
#endif
			Asura_Assert((fPanAngle >= -fDeg90) && (fPanAngle <= fDeg90), "sanity check failed");
            Asura_Assert((fFadeAngle >= -fDeg90) && (fFadeAngle <= fDeg90), "sanity check failed");

            // Check if we are within a small distance from the sound radius. If so, scale the 
            // pan back to 0 depending on how close we are to the sound source

            // This stops the sound from panning from left<->right discontinuously as
            // you pass over the sound source

            const float fPanDampingRange = px3dData->m_xSphericalData.m_fInnerRange;
            if (fDistance <= fPanDampingRange)
            {
                float fRatio = fDistance / fPanDampingRange;
				fPanAngle = fPanAngle * fRatio;
				fFadeAngle = fFadeAngle * fRatio;
            }

            // map fPanAngle and fFadeAngle to [0.0f,1.0f]
            fPanAngle += f90;
            fPanAngle *= (1.f / f180);
            fFadeAngle += f90;
            fFadeAngle *= (1.f / f180);

            fPan += fPanAngle;
			fFade += fFadeAngle;

        }//end stereo
    }//for iCamID

    fScaledVolume *= fVolume;
    if (s_iNumSplitScreens)
    {
        const float one_over_num_cameras = 1.0f/(float)s_iNumSplitScreens;
        fPan *= one_over_num_cameras;
		fFade *= one_over_num_cameras;
        fScaledVolume *= one_over_num_cameras;
    }

	// check if this sound should always be heard, even if out-of-range
	fScaledVolume = Asura_Maths::Max(px3dData->m_fMinVolume, fScaledVolume);

	// Make sure everything is in range.
	fPan = Asura_Maths::ClampToRange( fPan, 0.0f, 1.0f );
	fFade = Asura_Maths::ClampToRange( fFade, 0.0f, 1.0f );
	fScaledVolume = Asura_Maths::ClampToRange( fScaledVolume, 0.0f, 1.0f );

    // set sound pan and volume
    *pfVolume = fScaledVolume;
    *pfPan = fPan;
	*pfFade = fFade;

    return fScaledVolume != 0.0f;
}

float Asura_Sound_System::GetLinearVolumeFalloff(float inner_range, float outer_range, float distance)
{
	Asura_Assert( outer_range > 0.0f && outer_range >= inner_range, "Ranges are both zero? That makes no sense" );

	// JAT 11/10/07 - if inner_range = out_range = 0.0f e.g. voice-over data then set the scale to 1.0f
    float fVolume3dscale = 1.0f;

    if(distance < inner_range)
    {
        fVolume3dscale = 1.0f;
    }
    else
    {
        if(distance < outer_range)
        {
            float dis_to_outer_edge = outer_range - distance;
            float range_diff = outer_range - inner_range;
            Asura_Assert((outer_range - inner_range) > 0.0f, "dodgy sound ranges");
            fVolume3dscale = dis_to_outer_edge / range_diff;
        }
        else
        {
            fVolume3dscale = 0.0f;
            // (MH - 07.06.2002) this will tend to quieten sounds in split screen.
            // Probably a good idea given how much will be going on. 
        }
    }

    return fVolume3dscale;
}

float Asura_Sound_System::GetCuboidVolumeFalloff(const Asura_Cuboid3dSoundData * m_xCuboidData, 
                                                 const Asura_Vector_3 * relativePosition)
{
    // find axis which has furthest dist from inner range cuboid.
    Asura_Vector_3 absolute_relative_pos;
    Asura_Vector_3 falloff;
    for (int i = 0; i < 3; i++)
    {
        absolute_relative_pos[i] = Asura_Maths::Abs(relativePosition->GetElement(i));
        if (absolute_relative_pos[i] > m_xCuboidData->m_xOuterRanges[i])
        {
            // out of range, early out to avoid more fabsf
            return 0.0f;
        }
        falloff[i] = GetLinearVolumeFalloff(m_xCuboidData->m_xInnerRanges[i], 
                                                    m_xCuboidData->m_xOuterRanges[i], 
                                                    absolute_relative_pos[i] );
    }

    // return min(x,y,z)
    if (falloff.y < falloff.x)
    {
        if (falloff.z < falloff.y)
        {
            return falloff.z;
        }
        else
        {
            return falloff.y;
        }
    }
    else if (falloff.z < falloff.x)
    {
        return falloff.z;
    }
    else
    {
        return falloff.x;
    }
}


float Asura_Sound_System::CalculateDistanceFilterGain (Asura_Handle iSoundHandle)
{
	Asura_Assert(iSoundHandle != ASURA_INVALID_HANDLE, "invalid handle");
	if (iSoundHandle == ASURA_INVALID_HANDLE)
	{
		return 0.0f;
	}

	Asura_ActiveSoundSample& xActiveSound = s_pxActiveSoundList[iSoundHandle];
	Asura_Assert(xActiveSound.m_eSoundID != ASURA_SID_NO_SOUND, "active sound with no sound id");

	Asura_Vector_3 xDisplacement = xActiveSound.m_x3dData.m_xPosition;

	// (AAM - 04.12.2007) Split-screen is awkward. For now we'll just use the distance
	// from the first listener
	xDisplacement -= s_axSoundCentre[0];

	float fGain = 0.0f;
	float fMinDistance = 0.0f;
	if (xActiveSound.m_bUse3dCuboid)
	{
		//It is not worth wasting cpu cycles getting a mathematically accurate distance to the nearest point on the inner
		//range planes and applying our distance filter falloff based on that, especially since it isnt technically accurate anyway
		//so for consistancy we just take the maximum extent to an inner range and treat that like an inner radius
		fMinDistance = Asura_Maths::Max( xActiveSound.m_x3dData.m_xCuboidData.m_xInnerRanges.GetX(), xActiveSound.m_x3dData.m_xCuboidData.m_xInnerRanges.GetY() );
		fMinDistance = Asura_Maths::Max( xActiveSound.m_x3dData.m_xCuboidData.m_xInnerRanges.GetZ(), fMinDistance );
	}
	else //using spherical
	{
		fMinDistance = xActiveSound.m_x3dData.m_xSphericalData.m_fInnerRange;
	}
	#ifdef CONSOLE_VARNAME_DISTANCE_FILTER_RANGE_MAX			
	float fMaxDistance = fMinDistance + g_fDistanceFilterRangeMax;
	#else
	float fMaxDistance = fMinDistance + fASURA_SOUND_DISTANCE_FILTER_MAXIMUM_DISTANCE;
	#endif
	#ifdef CONSOLE_VARNAME_DISTANCE_FILTER_RANGE_MIN
	fMinDistance += g_fDistanceFilterRangeMin;
	#else
	fMinDistance += fASURA_SOUND_DISTANCE_FILTER_MINIMUM_DISTANCE;
	#endif

	const float fDistance = xDisplacement.Magnitude();
	if((fMinDistance < fMaxDistance) && (fDistance > fMinDistance))
	{
		//Only do this logic if the listener is outside of the inner radius + our fixed minimum distance for filtering
		if (fDistance < fMaxDistance)
		{
			//Linearly interpolate to the correct gain value (rather cheekily reuse the linear volume function)
			fGain = 1.0f - GetLinearVolumeFalloff ( fMinDistance, fMaxDistance, fDistance );
		}
		else if (fMaxDistance > fMinDistance)
		{
			//Listener is past max distance so apply full filtering
			fGain = 1.0f;
		}
	}
	return fGain;
}


bool Asura_Sound_System::Calculate3dDoppler(Asura_Handle iSoundHandle)
{
#ifdef ASURA_SOUND_USE_DOPPLER
	Asura_ActiveSoundSample& xActiveSound = s_pxActiveSoundList[iSoundHandle];

	Asura_Vector_3 xSourceToListener = xActiveSound.m_x3dData.m_xPosition - s_axSoundCentre[0];
	float fSourceToListenerMagnitude = xSourceToListener.Magnitude();

	if ( fSourceToListenerMagnitude > 0.0f )	// Prevent divide by zero
	{
		float fListenerVelocityScalar = ( xSourceToListener * s_axSoundVelocity[0] ) / fSourceToListenerMagnitude;
		float fSourceVelocityScalar = ( xSourceToListener * xActiveSound.m_x3dData.m_xVelocity ) / fSourceToListenerMagnitude;

		float fMaxScalar = g_fSpeedOfSound / g_fDopplerFactor;
		fListenerVelocityScalar = Asura_Maths::Min( fListenerVelocityScalar, fMaxScalar );
		fSourceVelocityScalar = Asura_Maths::Min( fSourceVelocityScalar, fMaxScalar );

		xActiveSound.m_fPitchDoppler = 
			( g_fSpeedOfSound - g_fDopplerFactor * fListenerVelocityScalar ) / ( g_fSpeedOfSound + g_fDopplerFactor * fSourceVelocityScalar );
		xActiveSound.m_fPitchDoppler = Asura_Maths::ClampToRange( xActiveSound.m_fPitchDoppler, 0.01f, 2.0f );

		PlatformSetSoundPitch( xActiveSound.m_iPlayingSoundHandle, s_fPitchScale * xActiveSound.m_fPitch * xActiveSound.m_fPitchDoppler );
	}
#else
	static_cast<void>( iSoundHandle );
#endif
	return true;
}

//////////////////////////////////////////////////////////////////////////////
//
// FUNCTION     Asura_Sound_System::GetHardPan().
// PARAMETERS   iCamID - camera ID
// RETURNS      pan from 0.0 to 1.0
// DESCRIPTION  finds the panning value for the camera's viewing area
// AUTHOR       Mike Healey - 11.06.2002.
//
//////////////////////////////////////////////////////////////////////////////
float Asura_Sound_System::GetHardPan(unsigned int iCamID)
{
    if (s_eSoundOutputType == SoundOutputMono)
    {
        return 0.5f;
    }

    Asura_Camera::SetCameraIndex(iCamID);
    Asura_Assert(Asura_Camera::IsCameraActive(), "camera not active");
    const Asura_ViewingArea * pxViewingArea = Asura_Camera::GetViewingArea();
    float x = pxViewingArea->m_fXOffset;
    float w = pxViewingArea->m_fWidth;
    float effective_width = 1.0f - w;
    if (effective_width == 0.0f)
    {
        return 0.5f;
    }
    else
    {
        float pan = x / effective_width;
        Asura_Assert(pan >= 0.0f && pan <= 1.0f, "pan out of range");
        return pan;
    }
}
// End function Asura_Sound_System::GetHardPan().


// internal functions to manage 2 lists of playing sound handles: ones that need to have
// EQ enabled and ones that don't care. If you run out of EQ handles they will start to 
// be allocated from the other list; this is probably better than them not playing at all.
bool Asura_Sound_System::GetPlayingSoundHandle(Asura_Handle& iHandle, bool bNeedEQ)
{
	bool bOK = false;
	if (bNeedEQ)
	{
		bOK = s_pxPlayingSoundHandleListWithEQ->GetNewHandle(iHandle);
	}
	if (!bOK)
	{
		bOK = s_pxPlayingSoundHandleListWithoutEQ->GetNewHandle(iHandle);
		if (bOK)
		{
			// move the handle into the higher range
			iHandle += s_iMaxPlayingSoundsWithEQ;
		}
		else if (!bNeedEQ)
		{
			// we can perhaps still allocate this from the other list; it will have the EQ removed when it is played.
			bOK = s_pxPlayingSoundHandleListWithEQ->GetNewHandle(iHandle);
		}
	}
	return bOK;
}

void Asura_Sound_System::ReleasePlayingSoundHandle(Asura_Handle iHandle)
{
	if (iHandle < 0) return;
	if (iHandle < s_iMaxPlayingSoundsWithEQ)
	{
		s_pxPlayingSoundHandleListWithEQ->ReleaseHandle(iHandle);
	}
	else
	{
		s_pxPlayingSoundHandleListWithoutEQ->ReleaseHandle(iHandle - s_iMaxPlayingSoundsWithEQ);
	}
}

void Asura_Sound_System::TraversePlayingSoundHandles(void (*function)(Asura_Handle iHandle))
{
	// sounds with EQ allowed have actual handle
	s_pxPlayingSoundHandleListWithEQ->Traverse(function);

	// sounds without EQ allowed have handle in range [s_iMaxPlayingSoundsWithEQ, s_iMaxPlayingSounds)
	Asura_Tracking_Handle_List_Iterator xTraversePosition = s_pxPlayingSoundHandleListWithoutEQ->GetIterator();
	for ( xTraversePosition.Begin(); !xTraversePosition.End(); xTraversePosition.Next() )
	{
		function(s_iMaxPlayingSoundsWithEQ + xTraversePosition.GetCurrentHandle());
	}
}

bool Asura_Sound_System::IsValidPlayingSoundHandle(Asura_Handle iHandle)
{
	if(iHandle < 0) return false;

	if(iHandle < s_iMaxPlayingSoundsWithEQ)
	{
		return s_pxPlayingSoundHandleListWithEQ->IsValidHandle(iHandle);
	}
	else
	{
		return s_pxPlayingSoundHandleListWithoutEQ->IsValidHandle(iHandle - s_iMaxPlayingSoundsWithEQ);
	}
}

Asura_Handle Asura_Sound_System::GetPlayingHandleForActiveSound( const Asura_Handle iActiveSoundHandle )
{
	if( iActiveSoundHandle < 0 || iActiveSoundHandle >= ASURA_MAX_NUMBER_OF_ACTIVE_SOUNDS )
	{
		Asura_Assertf( false, ("Invalid sound handle (%d) passed to Asura_Sound_System::GetPlayingHandleForActiveHandle().", iActiveSoundHandle) );
		return ASURA_INVALID_HANDLE;
	}

	const Asura_ActiveSoundSample& xActiveSound = s_pxActiveSoundList[ iActiveSoundHandle ];
	return xActiveSound.m_iPlayingSoundHandle;
}


// S T R E A M I N G   S O U N D S ///////////////////////////////////////////

// Kevin Lea - 25/04/2002 10:57:47 - streaming sounds
Asura_Handle Asura_Sound_System::CreateStreamingSound( const char* szFilename)
{
	Asura_Handle iHandle = ASURA_INVALID_HANDLE;

	if (!s_pxStreamingSoundList->GetNewHandle(iHandle))
	{
		/// Kevin Lea - 25/04/2002 12:48:06 - failed to get handle
		Asura_Assert(0,"Too many streaming sounds");
		return iHandle;
	}

	if (!Platform_CreateStreamingSound(iHandle, szFilename))
	{
		/// Kevin Lea - 25/04/2002 12:41:40 - could not find file (or other platform error)
		Asura_Assertf(false, ("Unable to find streaming sound %s.", szFilename));
		s_pxStreamingSoundList->ReleaseHandle(iHandle);
		return ASURA_INVALID_HANDLE;
	}

	g_pxStreamInfo[iHandle].m_bPlaying = false;

	//Set the volume to the default maximum now , to ensure that ASURA_SOUND_GLOBAL_VOLUME_SCALE gets applied
	//RAR 21/11/2005
	StreamingSoundSetVolume(iHandle,1.0f);

    Asura_SoundStream_OutputToDebugger(("Stream %s given handle %i.\n", szFilename, iHandle));

	return iHandle;
}

void Asura_Sound_System::PlayStreamingSound(Asura_Handle iHandle, bool bLooping, bool bPreload/*=false*/)
{
	if (iHandle==ASURA_INVALID_HANDLE) return;

	Platform_PlayStreamingSound(iHandle,bLooping,bPreload);

	g_pxStreamInfo[iHandle].m_bPlaying = true;
	
    Asura_SoundStream_OutputToDebugger(("Stream with handle %i playing, loop = %d, preload = %d.\n", iHandle, bLooping, bPreload));
}

void Asura_Sound_System::PauseStreamingSound(Asura_Handle iHandle)
{
	if (iHandle==ASURA_INVALID_HANDLE) return;
	
	Platform_PauseStreamingSound(iHandle);

    Asura_SoundStream_OutputToDebugger(("Stream with handle %i paused.\n", iHandle));
}

void Asura_Sound_System::ResumeStreamingSound(Asura_Handle iHandle)
{
	if (iHandle==ASURA_INVALID_HANDLE) return;
	
	Platform_ResumeStreamingSound(iHandle);

    Asura_SoundStream_OutputToDebugger(("Stream with handle %i resumed.\n", iHandle));
}

void Asura_Sound_System::StopStreamingSound(Asura_Handle iHandle)
{
	if (iHandle==ASURA_INVALID_HANDLE) return;

	Platform_StopStreamingSound(iHandle);

	g_pxStreamInfo[iHandle].m_bPlaying = false;

    Asura_SoundStream_OutputToDebugger(("Stream with handle %i stopped.\n", iHandle));
}

void Asura_Sound_System::ReleaseStreamingSound(Asura_Handle iHandle)
{
	if (iHandle==ASURA_INVALID_HANDLE) return;

	Platform_ReleaseStreamingSound(iHandle);
    
    // (MH - 18.11.2002) for some reason this wasn't called before.
    s_pxStreamingSoundList->ReleaseHandle(iHandle);

    Asura_SoundStream_OutputToDebugger(("Stream with handle %i released.\n", iHandle));
}

bool Asura_Sound_System::StreamingSoundIsReady(Asura_Handle iHandle)
{
	if (iHandle==ASURA_INVALID_HANDLE) return true;// prevent any waits for things that won't happen

    bool bReady = Platform_StreamingSoundIsReady(iHandle);
    Asura_SoundStream_OutputToDebugger(("Stream with handle %i ready = %d.\n", iHandle, bReady));
    return bReady;
}

void Asura_Sound_System::StartPreloadedStreamingSound(Asura_Handle iHandle)
{
	if (iHandle==ASURA_INVALID_HANDLE) return;

    Platform_StartPreloadedStreamingSound(iHandle);
    Asura_SoundStream_OutputToDebugger(("Stream with handle %i preloaded, start now.\n", iHandle));
}

Asura_Sound_ID Asura_Sound_System::GetSoundIDFromSoundHandle( Asura_Handle iHandle )
{
    if( ASURA_INVALID_HANDLE == iHandle )
    {
        return ASURA_SID_NO_SOUND;
    }
    else
    {
        return s_pxActiveSoundList[iHandle].m_eSoundID;
    }
}

void Asura_Sound_System::EnsureAllStreamingSoundsAreStopped()
{
    s_pxStreamingSoundList->Traverse(StopStreamingSound);
    Platform_EnsureAllStreamingSoundsAreStopped();

    Asura_SoundStream_OutputToDebugger(("All streams stopped.\n"));
}


void Asura_Sound_System::StreamingSoundSetVolume(Asura_Handle iHandle, float fVolume)
{
    if (iHandle == ASURA_INVALID_HANDLE) return;

	//scale volume and cap to 1. RAR 16/12/2004
	fVolume *= ASURA_SOUND_GLOBAL_VOLUME_SCALE;
	fVolume = Asura_Maths::ClampToRange( fVolume, 0.0f, 1.0f );

    Platform_SetStreamingSoundVolume(iHandle, fVolume);
}

bool Asura_Sound_System::StreamingSoundIsPlaying(Asura_Handle iHandle)
{
    if (iHandle == ASURA_INVALID_HANDLE) return false;
	return g_pxStreamInfo[iHandle].m_bPlaying;
}

bool Asura_Sound_System::StreamingSoundIsFinished(Asura_Handle iHandle)
{
    if (iHandle == ASURA_INVALID_HANDLE) return false;
	return Platform_StreamingSoundIsFinished(iHandle);
}

float Asura_Sound_System::GetStreamingSoundPosition(Asura_Handle iHandle)
{
    if (iHandle == ASURA_INVALID_HANDLE) return 0;
	return Platform_GetStreamingSoundPosition(iHandle);
}

#ifdef ASURA_TOOLS
void Asura_Sound_System::SetStreamingSoundPosition(Asura_Handle iHandle,float fTime)
{
    if (iHandle == ASURA_INVALID_HANDLE) return;
	return Platform_SetStreamingSoundPosition(iHandle,fTime);
}

#endif

float Asura_Sound_System::GetStreamingSoundLength(Asura_Handle iHandle)
{
    if (iHandle == ASURA_INVALID_HANDLE) return 0;
	return Platform_GetStreamingSoundLength(iHandle);
}


Asura_SoundProperties* Asura_Sound_System::GetSoundPropertiesByIndex(int iLoadedSoundIndex)
{
	if(iLoadedSoundIndex == ASURA_INVALID_LOADED_SOUND_INDEX) return 0;

	Asura_Assert(iLoadedSoundIndex>=0 && iLoadedSoundIndex<ASURA_MAX_NUMBER_OF_LOADED_SOUNDS,"Invalid loaded sound index");
	return &s_pxSoundProperties[iLoadedSoundIndex];

}

Asura_SoundProperties* Asura_Sound_System::GetSoundPropertiesByID(const Asura_Sound_ID eSoundID)
{
	//get the index from the ID
	int iLoadedSoundIndex = -1;
	if (FindSoundIDInHandler(eSoundID, iLoadedSoundIndex))
	{
		return GetSoundPropertiesByIndex(iLoadedSoundIndex);
	}
	else
	{
		return NULL;
	}
}


bool Asura_Sound_System::SetPlayingSoundVolumePanAndFade(Asura_Handle iPlayingSoundHandle, float fVolume, float fPan, float fFade)
{
	//Scale volume and cap to 1. RAR 16/12/2004
	fVolume *= ASURA_SOUND_GLOBAL_VOLUME_SCALE;
	fVolume = Asura_Maths::ClampToRange( fVolume, 0.0f, 1.0f );

	return PlatformSetSoundVolumePanAndFade(iPlayingSoundHandle,fVolume,fPan,fFade);
}

bool Asura_Sound_System::SetPlayingSoundVolume(Asura_Handle iPlayingSoundHandle, float fVolume)
{
	Asura_Assert(s_bUseHardware3dPan, "should only be called if using hardware pan");

	//Scale volume and cap to 1. RAR 16/12/2004
	fVolume *= ASURA_SOUND_GLOBAL_VOLUME_SCALE;
	fVolume = Asura_Maths::ClampToRange( fVolume, 0.0f, 1.0f );

	return Platform_HardwarePanSetSoundVolume(iPlayingSoundHandle,fVolume);
}

float Asura_Sound_System::AdjustVolumeByOcclusion (const float fVolume, const float fOcclusion)
{
	//Adjust the volume to take account of the occlusion factor
	//TY 05.02.08 - use a non-linear scaling to maintain the volume a bit more
	Asura_Assert(fOcclusion >= 0.0f, "Occlusion factor is negative, sound not set up properly");
	return fVolume * (1.0f - fOcclusion * fOcclusion);
}

void Asura_Sound_System::UpdateOcclusionData (int iSoundHandle)
{
	const float fLargeInvalidOcclusion = 2.0f;

	//update the occlusion data for this sound
	//this involves doing LoS tests and then calling project code to determine the occlusion factor of the hit item
	Asura_ActiveSoundSample& xActiveSound = s_pxActiveSoundList[iSoundHandle];
	Asura_Assert(xActiveSound.m_eSoundID != ASURA_SID_NO_SOUND, "active sound with no sound id");

	if((!s_pfnGetEntityOcclusionFactor && !s_pfnGetEnvironmentOcclusionFactor) || !xActiveSound.m_bUse3dVol || !xActiveSound.m_bUseOcclusion)
	{
		//No callbacks have been set, or this sound has no 3D positional data
		if(xActiveSound.m_fOcclusion == fASURA_INVALID_OCCLUSION_VALUE)
		{
			xActiveSound.m_fOcclusion = 0.0f;
		}
		return;
	}

	//Set the default penetration properties
	g_xPenetrationProps.SetDefaults();

	#ifdef ASURA_TOOLS
	//Dont enable occlusion for most modes, can cause big slowdowns
	if(Tools_System::IsActive())
	{
		const int iMode = Tools_System::GetCurrentMode();
		switch (iMode)
		{
			case TOOLS_MODE_OBJECT:		break;
			case TOOLS_MODE_CUTSCENE:	break;

			case TOOLS_MODE_STANDBY:	//drop down
			case TOOLS_MODE_SOUND:
						
				//We want these modes to use sound occlusion, but they dont render
				//objects so ignore object LoS tests
				g_xPenetrationProps.SetMaxEntityLoSHits(0);
				break;
			
			default:
			{
				//Disable occlusion for all other modes
				xActiveSound.m_fOcclusion = 0.0f;
				return;
			}
		}
	}
	#endif

	float fLeastOcclusion = fLargeInvalidOcclusion;

	//loop over all all cameras and take the least occluded as our occlusion factor
	int iFocalCamera = -1;
	for (int iCamID = 0; iCamID < s_iNumSplitScreens; ++iCamID)
	{
		Asura_Camera::SetCameraIndex(iCamID);
		float fCameraOcclusion = fLargeInvalidOcclusion;
		if (!Asura_Camera::IsCameraActive())
		{
			continue;
		}
		//Fire a ray from the sound centre to the sound and check the occlusion factor of everything it intersects
		Asura_Vector_3 xDirection = xActiveSound.m_x3dData.m_xPosition - s_axSoundCentre[iCamID];
		const float fMagnitude = xDirection.Normalise();
		
		const Asura_LOS_InputData xInput(s_axSoundCentre[iCamID], xDirection, s_auOcclusionGUIDsToIgnore[iCamID], fMagnitude);

#ifdef ASURA_DEBUG_VARIABLE_SYSTEM
		if (!g_bUseEnvironmentOcclusion)
		{
			g_xPenetrationProps.SetMaxEnvironmentLoSHits(0);
		}
		if (!g_bUseObjectOcclusion)
		{
			g_xPenetrationProps.SetMaxEntityLoSHits(0);
		}
#endif
		Asura_LOS_Penetration::TestAgainstEverything(&xInput,g_xPenetrationProps);

		for(int i = 0; i < Asura_LOS_Penetration::GetNumberOfHits(); ++i)
		{
			const Asura_LOS_OutputData* pxOutput = Asura_LOS_Penetration::GetHit(i);
			if( pxOutput && ( pxOutput->m_bHitEnvironment || pxOutput->m_uHitEntity ) )
			{
				float fHitOcclusion = 2.0f;
				if( pxOutput->m_bHitEnvironment )
				{
					if ( s_pfnGetEnvironmentOcclusionFactor)
					{
						fHitOcclusion = s_pfnGetEnvironmentOcclusionFactor(pxOutput);
						Asura_Assert((fHitOcclusion >= 0.0f) && (fHitOcclusion <= 1.0f), "Invalid occlusiton factor returned from project 'get occlusion' function");
						#ifdef ASURA_DEBUG_VARIABLE_SYSTEM
						if ( g_bUseEnvironmentOcclusion == false )
						{
							fHitOcclusion = 0.0f;
						}
						#endif // #ifdef ASURA_DEBUG_VARIABLE_SYSTEM
					}
				}
				else if ( s_pfnGetEntityOcclusionFactor )
				{
					fHitOcclusion = s_pfnGetEntityOcclusionFactor(pxOutput);
					Asura_Assert((fHitOcclusion >= 0.0f) && (fHitOcclusion <= 1.0f), "Invalid occlusiton factor returned from project 'get occlusion' function");
					#ifdef ASURA_DEBUG_VARIABLE_SYSTEM
					if ( g_bUseObjectOcclusion == false )
					{
						fHitOcclusion = 0.0f;
					}
					#endif // #ifdef ASURA_DEBUG_VARIABLE_SYSTEM
				}
				if(fHitOcclusion < fLargeInvalidOcclusion) //assuming we have set it to something reasonable
				{
					if(fCameraOcclusion == fLargeInvalidOcclusion)
					{
						//Hasnt been set yet
						fCameraOcclusion = 1.0f - fHitOcclusion; //store the inverse so we can just multiply them up
					}
					else
					{
						//Has been set, so attenuate
						fCameraOcclusion *= (1.0f - fHitOcclusion);
					}
				}
			}
		}
		//Check that we set the camera occlusion first
		if(fCameraOcclusion <= 1.0f)
		{
			fCameraOcclusion = 1.0f - fCameraOcclusion;
			if(fCameraOcclusion < fLeastOcclusion)
			{
				fLeastOcclusion = fCameraOcclusion;
				iFocalCamera = iCamID;
			}
		}
	}
	if(fLeastOcclusion > 1.0f)
	{
		//Didnt assign fLeastOcclusion, default to no occlusion
		fLeastOcclusion = 0.0f;
	}

	//Now check if we are in the same reverb zone as the camera
	if( ( iFocalCamera != -1 ) && ( fLeastOcclusion != 0.0f ) )
	{
		Asura_Camera::SetCameraIndex(iFocalCamera);

		xActiveSound.m_bOcclusionSameReverbZone = false;
#ifdef ASURA_USE_REVERBVOLUME_SYSTEM
		Asura_ReverbVolume_Handler xHandler = Asura_ReverbVolume_System::GetCurrentHandler();
		
		const Asura_ReverbVolume* pxCamVolume = xHandler.GetReverbVolumeFromPosition(*Asura_Camera::GetPosition());
		if(pxCamVolume)
		{
			const Asura_ReverbVolume* pxSoundVolume = xHandler.GetReverbVolumeFromPosition(xActiveSound.m_x3dData.m_xPosition);
			if(pxCamVolume == pxSoundVolume)
			{
				xActiveSound.m_bOcclusionSameReverbZone = true;
			}
		}
#endif
	}
	
	xActiveSound.m_fDesiredOcclusion = fLeastOcclusion;
	if(xActiveSound.m_fOcclusion == fASURA_INVALID_OCCLUSION_VALUE)
	{
		//If the occlusion is uninitialised, set it now
		xActiveSound.m_fOcclusion = fLeastOcclusion;
	}
}


void Asura_Sound_System::SetEntityOcclusionFactorCallback		(Asura_Sound_OcclusionFactor_Callback pfnCallback)	
{
	s_pfnGetEntityOcclusionFactor = pfnCallback; 
}

void Asura_Sound_System::SetEnvironmentOcclusionFactorCallback	(Asura_Sound_OcclusionFactor_Callback pfnCallback)
{
	s_pfnGetEnvironmentOcclusionFactor = pfnCallback;
}

void Asura_Sound_System::SetOcclusionCPUTimePerFrame (float fSeconds)	
{ 
	s_xOcclusionCPUTicksPerFrame = Asura_Timers::SecondsToTicks(fSeconds);
}

void Asura_Sound_System::SetGUIDToIgnoreForOcclusion (Asura_Guid uIgnore, u_int uCameraIndex /* = 0 */)
{
	Asura_Assert(uCameraIndex < ASURA_MAX_NO_OF_CAMERAS, "Attempting to set guid to ignore for occlusion with an invalid camera id");

	s_auOcclusionGUIDsToIgnore[uCameraIndex] = uIgnore;
}

float Asura_Sound_System::GetDefaultInnerRadius( Asura_Sound_ID eSoundID )
{
	const Asura_SoundProperties* pxSoundProperties = GetSoundPropertiesByID( eSoundID );
	return pxSoundProperties ? pxSoundProperties->m_fDefaultInnerRadius : fASURA_SOUND_DEFAULT_INNER_RADIUS;
}

float Asura_Sound_System::GetDefaultOuterRadius( Asura_Sound_ID eSoundID )
{
	const Asura_SoundProperties* pxSoundProperties = GetSoundPropertiesByID( eSoundID );
	return pxSoundProperties ? pxSoundProperties->m_fDefaultOuterRadius : fASURA_SOUND_DEFAULT_OUTER_RADIUS;
}

Asura_Sound_ID Asura_Sound_System::GetDynamicSoundID()
{
	ASURA_STATIC_ASSERT(ASURA_SID_NUM_DYNAMIC==65536);//assumed by the bit swapping
	
	while(true)
	{
		//get the next (zero based) dynamic sound index
		if(s_uNextDynamicSIDIndex >= ASURA_SID_NUM_DYNAMIC) s_uNextDynamicSIDIndex = 0;
		u_int uIndex = s_uNextDynamicSIDIndex++;

		//swap the bits around a bit so that the sound id tree doesn't become too unbalanced
		uIndex = ((uIndex & 0x5555)<<1)|((uIndex & 0xAAAA)>>1);
		uIndex = ((uIndex & 0x3333)<<2)|((uIndex & 0xCCCC)>>2);
		uIndex = ((uIndex & 0x0F0F)<<4)|((uIndex & 0xF0F0)>>4);
		uIndex = ((uIndex & 0x00FF)<<8)|((uIndex & 0xFF00)>>8);


		Asura_Sound_ID eSoundID = Asura_Sound_ID(ASURA_SID_FIRST_DYNAMIC+uIndex);

		//make sure that the sound is indeed free
		if(SoundIsLoaded(eSoundID)) continue;

		return eSoundID;

	}
}

float Asura_Sound_System::AmplitudeToDecibels( float fAmplitude )
{
	if ( fAmplitude <= 0.0f )
	{
		return -96.0f;	// Effectively silence
	}
	else
	{
		return 20.0f * log10f( fAmplitude );
	}
}

float Asura_Sound_System::DecibelsToAmplitude( float fDecibels )
{
	return Asura_Maths::Power( 10, fDecibels / 20.0f );
}

#endif // ASURA_USE_AUDIO_SYSTEM

/// End of file. ///
