//
// audiosoundtypes/sound.cpp
//
// Copyright (C) 1999-2013 Rockstar Games.  All Rights Reserved.
//

#include "sound.h"
#include "sounddefs.h"

#include "audiohardware/driverutil.h"
#include "audiohardware/syncsource.h"
#include "audiohardware/waveslot.h" 
#include "audioengine/categorymanager.h"
#include "audioengine/curverepository.h"
#include "audioengine/engine.h"
#include "audioengine/entity.h"
#include "audioengine/environment.h"
#include "audioengine/metadatamanager.h"
#include "audioengine/soundfactory.h"
#include "audioengine/soundmanager.h"
#include "audioengine/spuutil.h"
#include "audioengine/widgets.h"

#include "diag/tracker.h"

#include "system/param.h"
#include "profile/profiler.h"

namespace rage {
#if !__SPU
PARAM(fullsoundhierarchy, "[RAGE Audio] Don't remove sounds from hierarchy at runtime.  This needs to be set to allow runtime RAVE tweaking of playing sounds to work");
#endif

EXT_PF_GROUP(AudioSoundsAndVoices);
PF_COUNTER(RemovedSounds,AudioSoundsAndVoices);
PF_COUNTER(UnremoveableSounds,AudioSoundsAndVoices);

PF_PAGE(audSoundTimingsPage, "audSoundTimings Page");
PF_GROUP(audSoundTimings);
PF_LINK(audSoundTimingsPage, audSoundTimings);

PF_TIMER(UpdateSound, audSoundTimings);
PF_TIMER(UpdateOcclusionMetric, audSoundTimings);
PF_TIMER(SetRequestedPosition, audSoundTimings);
PF_TIMER(SetRequestedOrientation, audSoundTimings);
PF_TIMER(Update, audSoundTimings);

bool audSound::sm_ShouldPreserveFullHierarchy = true;
u32 audSound::sm_CurrentSyncMasterId = audMixerSyncManager::InvalidId;
u32 audSound::sm_CurrentSyncSlaveId = audMixerSyncManager::InvalidId;

#define AUD_CLEAR_TRISTATE_VALUE(flagvar, flagid) (flagvar &= ~(0 | ((AUD_GET_TRISTATE_VALUE(flagvar, flagid)&0x03) << (flagid<<1))))

bool audSound::FindVariableValue(u32 nameHash, f32& value)
{
	audVariableHandle handle = _FindVariableDownHierarchy(nameHash, ReadOnly);
	if(handle)
	{
		value = AUD_GET_VARIABLE(handle);
		return true;
	}
	return false;
}

f32 audSound::ComputeDynamicValue(audDynamicValueId type)const 
{
	const audRequestedSettings *settings = GetRequestedSettingsFromIndex(m_InitParams.BucketId, m_TopLevelRequestedSettingsIndex);

	//Velocity values are converted to m/s

	switch(type)
	{
	case AUD_DYNAMICVALUE_LISTENER_DISTANCE:
		{
			Vec3V pos = settings->GetPosition_AudioThread();

			return g_audEnvironment->ComputeDistanceToClosestVolumeListener(pos);
		}
	case AUD_DYNAMICVALUE_POSX:
		{
			Vec3V pos = settings->GetPosition_AudioThread();
			return pos.GetXf();
		}
	case AUD_DYNAMICVALUE_POSY:
		{
			Vec3V pos = settings->GetPosition_AudioThread();
			return pos.GetYf();
		}
	case AUD_DYNAMICVALUE_POSZ:
		{
			Vec3V pos = settings->GetPosition_AudioThread();
			return pos.GetZf();
		}
	case AUD_DYNAMICVALUE_POSSEED:
		{
			Vec3V pos = settings->GetPosition_AudioThread();
			ScalarV seed = Modulus(Mag(pos), ScalarV(V_ONE));
			return seed.Getf();
		}
	case AUD_DYNAMICVALUE_VELX:
		{
			Vec3V velocity;
			settings->GetVelocity(velocity);
			return velocity.GetXf() * 33.33f;
		}
	case AUD_DYNAMICVALUE_VELY:
		{
			Vec3V velocity;
			settings->GetVelocity(velocity);
			return velocity.GetYf() * 33.33f;
		}
	case AUD_DYNAMICVALUE_VELZ:
		{
			Vec3V velocity;
			settings->GetVelocity(velocity);
			return velocity.GetZf() * 33.33f;
		}
	case AUD_DYNAMICVALUE_SPEED:
		{
			Vec3V velocity;
			settings->GetVelocity(velocity);
			return Mag(Scale(velocity, ScalarV(33.33f))).Getf();
		}
	case AUD_DYNAMICVALUE_FWDSPEED:
		{
			Vec3V velocity;
			settings->GetVelocity(velocity);
			QuatV orientation;
			settings->GetOrientation(orientation);
			Vec3V fwdDir = Transform(orientation, Vec3V(V_Y_AXIS_WZERO));
			return Dot(fwdDir, Scale(velocity, ScalarV(33.33f))).Getf();
		}
	case AUD_DYNAMICVALUE_UPSPEED:
		{
			Vec3V velocity;
			settings->GetVelocity(velocity);
			QuatV orientation;
			settings->GetOrientation(orientation);
			Vec3V upDir = Transform(orientation, Vec3V(V_Z_AXIS_WZERO));
			return Dot(Scale(velocity, ScalarV(33.33f)), upDir).Getf();
		}
	case AUD_DYNAMICVALUE_CLIENTVAR:
		{
			f32 value;
			settings->GetClientVariable(value);
			return value;
		}
	case AUD_DYNAMICVALUE_PLAYTIME:
		{
#if __SPU
			const u32 now = g_Context.timers[m_InitParams.TimerId].timeInMs;
#else
			const u32 now = g_AudioEngine.GetSoundManager().GetTimeInMilliseconds(m_InitParams.TimerId);
#endif
			// all time sound variables should be in seconds
			return (now - m_TimeTriggered) * 0.001f;
		}
	default:
		break;
	}
	return 0.f;
}

f32 audSound::GetVariableValue(audVariableHandle handle) const
{
	if((size_t)handle < AUD_DYNAMICVALUE_MAX)
	{
		return ComputeDynamicValue((audDynamicValueId)((size_t)handle));
	}

	return *((f32*)handle);
}

void audSound::SetVariableValue(audVariableHandle handle, f32 value)
{
	if((size_t)handle < AUD_DYNAMICVALUE_MAX)
	{
		audAssertf(0, "Trying to set sound variable value using a variable type");
		return; 
	}

#if __SPU
	char *x = (char*)handle;
	float * adr = ((f32*)ResolveSoundVariablePtr(handle));
	if (x >= (char*)g_EntityVariableEAStart && x < (char*)g_EntityVariableEAEnd)
	{
		g_EntityVariableCache.SetVariableValue((float*)handle, value);
	}
	else
#else
	f32 * adr = ((f32*)handle);
#endif
	{
		*adr = value;
	}
}

#if __SPU

audVariableHandle ResolveSoundVariablePtr(audVariableHandle handle)
{
	if((size_t)handle < AUD_DYNAMICVALUE_MAX)
	{
		//Not a pointer so pass straight along
		return handle; 
	}

	float * ea = (float*)handle;

	TrapZ((size_t)ea);
	char *x = (char*)ea;
	if(x >= (char*)g_GlobalVariableEAStart && x < (char*)g_GlobalVariableEAEnd)
	{
		return (f32 *)(x - (char*)g_GlobalVariableEAStart + (char*)g_GlobalSoundVariables);
	}
	else if (x >= (char*)g_EntityVariableEAStart && x < (char*)g_EntityVariableEAEnd)
	{
		return g_EntityVariableCache.ResolveVariablePtr(ea);
	}
	else
	{
		return (f32 *)(x + g_TaskBucketMemOffset);
	}
}



#endif

audSoundPool audSound::sm_Pool;

#if __BANK && !__SPU

bool g_BreakOnSoundUpdate = false;
bool g_BreakOnSoundPlay = false;
u32 g_BreakOnSoundBucketId = 0xff;
u32 g_BreakOnSoundSlotId = 0xff;
u32 g_BreakOnNameTableOffset = ~0U;
char g_BreakOnNameBuf[128];
#endif // __BANK

#if __SOUND_DEBUG_DRAW
atRangeArray<audSoundCombineBuffer, 256> audSound::sm_CachedCombineBuffers;
#endif

#if !__SPU

audSound::audSound()
{
	for (s32 i = 0; i < audSound::kMaxChildren; i++)
	{
		m_ChildSounds[i] = 0xff;
	}

	m_PlayState = AUD_SOUND_DORMANT;	
	m_TimeTriggered = -1;

	m_PrepareTimeLimit = 0;

	m_CachedVirtualisationGroupID = 0;

	// predelay related vars
	m_PredelayState = AUD_PREDELAY_NOT_STARTED;
	m_PlaybackPredelay = 0;
	m_PlaybackStartOffset = 0;

	// Requested settings that aren't cached because they're set once then left
	m_RequestedPredelay = 0;
	m_RequestedStartOffset = 0;

	// by default we don't have requested settings
	m_RequestedSettingsIndex = 0xff;
	m_TopLevelRequestedSettingsIndex = 0xff;

	m_TypeMetadataOffset = 0;
	m_BaseMetadata = NULL;

	m_ParentIndex = 0xff;

	m_Tracker = NULL;
	m_Entity = NULL;
	m_EntitySoundReference = NULL;

	m_SimpleReleaseStartTime = 0;
	m_SimpleReleaseTime = kNoSimpleRelease;

	m_SimpleAttackTime = 0;

	m_PredelayVariable = NULL;
	m_StartOffsetVariable = NULL;
	
#ifdef GTA_REPLAY_RAGE
	m_replayId				= -1;
	m_pIsReplayIdTaken		= NULL;
	m_pReplaySoundHandle	= NULL;
#endif
}
#endif // !__SPU

audSound::~audSound()
{
#if !__FINAL
	const u32* uintPtr = (u32*)this;
	if (*uintPtr == 0xadadadad)
	{
		Quitf("[audSound::~audSound] This unknown sound has already been marked deleted. This is bad.");
	}
#endif

	// should never delete a sound referenced by game
	//SoundAssert(!m_SoundFlags.ReferencedDirectlyByGame);
#if __BANK
	if(IsReferencedByGame())
	{
#if __DEV
		audWarningf("Deleting sound referenced by game: %p (%u:%u) %s", this, m_InitParams.BucketId, sm_Pool.GetSoundSlotIndex(m_InitParams.BucketId, this),GetName());
#else
		audWarningf("Deleting sound referenced by game: %p (%u:%u) %s", this, m_InitParams.BucketId, sm_Pool.GetSoundSlotIndex(m_InitParams.BucketId, this),"");
#endif
	}
#endif

#if !AUD_SOUND_USE_VIRTUALS
	// prevent infinite recursion - invoking the sound type destructor from here will in turn invoke
	// this base class destructor
	if(!IsDestructing())
	{
		SetIsDestructing();
		InvokeDestructor();
	}
	else
	{
		DeleteRequestedSettings();
	}
#else

	// Using virtual functions - free resources as normal
	DeleteRequestedSettings();

#endif

#ifdef GTA_REPLAY_RAGE
	m_replayId = -1;

	if( m_pIsReplayIdTaken )
	{
		*m_pIsReplayIdTaken	= false;
		m_pIsReplayIdTaken	= NULL;
	}

	if( m_pReplaySoundHandle )
	{
		CReplaySoundHandle *replaySoundHandle	= reinterpret_cast<CReplaySoundHandle*> (m_pReplaySoundHandle);
		replaySoundHandle->m_pData				= NULL;
		m_pReplaySoundHandle					= NULL;
	}
#endif
}

#if !__SPU
bool audSound::Init(const void *pMetadata, const audSoundInternalInitParams* initParams, audSoundScratchInitParams *scratchInitParams)
{
#if __USEDEBUGAUDIO
	SoundAssert(m_SoundFlags.IsInitialising);
#endif

	if (!initParams || !pMetadata)
	{
		SoundAssert(0);
		return false;
	}
	Sound *metadata = (Sound*)pMetadata;
	m_BaseMetadata = metadata;
	m_InitParams = *initParams;

#if AUD_DEBUG_SOUNDS	
	GetVarianceCache()->TimeInitialised = g_AudioEngine.GetTimeInMilliseconds();
#endif

	Sound uncompressedMetadata;
	PullMetadataValues(metadata, false, uncompressedMetadata);
	PopulateScratchInitParams(scratchInitParams, uncompressedMetadata);
	
	SoundAssert(initParams->BucketId < 255);
	
	m_SoundTypeId = metadata->ClassId;

	if(m_ParentIndex == 0xff)
	{
		AllocateRequestedSettings(); 
		if(m_RequestedSettingsIndex == 0xff)
		{
			return false;
		}

		scratchInitParams->topLevelRequestedSettingsIndex = m_RequestedSettingsIndex;
		m_TopLevelRequestedSettingsIndex = m_RequestedSettingsIndex;

		audRequestedSettings *reqSet = GetRequestedSettings();
				
		if(m_Entity)
		{
			reqSet->SetEntityVariableBlock(m_Entity->GetVariableBlockIndex());
		}

		// If we don't have a parent, we care what timer to use, so check our category.
		// code can override the timer to use, only check category if this is left unset
		if(m_InitParams.TimerId == 0x1f)
		{	
			audCategory* ourCategory = audCategoryManager::GetCategoryFromIndex(scratchInitParams->categoryIndex);
			
			if(ourCategory)
			{
				// use the timer specified by the category
				m_InitParams.TimerId = ourCategory->GetTimerId();
				SoundAssert(m_InitParams.TimerId == ourCategory->GetTimerId());
			}
			else
			{
				// use normal timer
				m_InitParams.TimerId = 0;
			}
		}
	}
	else
	{
		// grab parent timer so that it is correct at every level in the hierarchy
		m_InitParams.TimerId = (u8)_GetParent()->GetTimerId();
		SoundAssert(m_InitParams.TimerId == _GetParent()->GetTimerId());
		m_TopLevelRequestedSettingsIndex = scratchInitParams->topLevelRequestedSettingsIndex;
		SoundAssert(m_TopLevelRequestedSettingsIndex != 0xff);
	}

	// Set up our local params based on the InitParams struct
	SetCanBeRemovedFromHierarchy(initParams->RemoveHierarchy);

#if __USEDEBUGAUDIO
	if(audSoundManager::ShouldBreakOnCreate(this))
	{
		audWarningf("Debug breaking on sound %s creation", this->GetName());
		audWarningf("Sound hierarchy: \n%s", ComputeHierarchyString(true));
		__debugbreak();
	}
#endif

	// Return false if effect route is invalid to suppress derived class ::Init behavior
	m_SoundFlags.IsEffectRouteValid = uncompressedMetadata.EffectRoute < NUM_EFFECTROUTES;

	if(uncompressedMetadata.EffectRoute == EFFECT_ROUTE_PADHAPTICS_1)
	{
		m_SoundFlags.IsEffectRouteValid = false;
	}

	return m_SoundFlags.IsEffectRouteValid;
}

void audSound::InitAsParent(const audSoundParentInitParams *parentInitParams)
{
	m_Entity = parentInitParams->Entity;
	m_EntitySoundReference = parentInitParams->EntitySoundRef;
	m_RequestedPredelay = parentInitParams->Predelay;
	m_RequestedStartOffset = parentInitParams->StartOffset;
	m_SoundFlags.RequestedStartOffsetPercentage = parentInitParams->IsStartOffsetPercentage;
	Assign(m_SimpleAttackTime, parentInitParams->AttackTime);
	m_SoundFlags.TrackEntityPosition = parentInitParams->TrackEntityPosition;
}

void audSound::PostInitAsParent(const audSoundParentInitParams *parentInitParams)
{
	if (parentInitParams->Tracker)
	{
		parentInitParams->Tracker->IncrementReferenceCount();
	}
	m_Tracker = parentInitParams->Tracker;
}

void audSound::PopulateScratchInitParams(audSoundScratchInitParams *scratchInitParams, Sound &uncompressedMetadata)
{
	// update scratch init params, combine with parent supplied values where appropriate

	// Child category overrides parent, unless the special 'overriden' flag is set (in which case game code is pushing a
	// category down).
	if(!scratchInitParams->overriddenCategory && uncompressedMetadata.Category != 0)
	{
		audCategory *ourCategory = g_AudioEngine.GetCategoryManager().GetCategoryPtr(uncompressedMetadata.Category);
		Assign(scratchInitParams->categoryIndex, audCategoryManager::GetCategoryIndex(ourCategory));
	}

	if(m_ParentIndex == 0xff || m_ParentOverrides.BitFields.DopplerFactorOverridesParent)
	{
		scratchInitParams->dopplerFactor = (f32)uncompressedMetadata.DopplerFactor * 0.01f;
	}
	else
	{
		scratchInitParams->dopplerFactor *= (f32)uncompressedMetadata.DopplerFactor * 0.01f;
	}


	if(m_ParentIndex == 0xff)
	{
		if(uncompressedMetadata.VolumeCurve != 0)
		{
			scratchInitParams->volCurveHash = uncompressedMetadata.VolumeCurve;
		}
		else
		{
			static const u32 defaultRolloff = ATSTRINGHASH("DEFAULT_ROLLOFF", 1003704407u);
			scratchInitParams->volCurveHash = defaultRolloff;
		}
	}
	else
	{
		if(uncompressedMetadata.VolumeCurve != 0)
		{
			scratchInitParams->volCurveHash = uncompressedMetadata.VolumeCurve;
		}
	}

	if(m_ParentIndex == 0xff || m_ParentOverrides.BitFields.VolumeCurveScaleOverridesParent)
	{
		scratchInitParams->volCurveScale = (f32)uncompressedMetadata.VolumeCurveScale * 0.01f;
	}
	else
	{
		scratchInitParams->volCurveScale *= (f32)uncompressedMetadata.VolumeCurveScale * 0.01f;
	}

	if(m_ParentIndex == 0xff || m_ParentOverrides.BitFields.VolumeCurvePlateauOverridesParent)
	{
		scratchInitParams->volCurvePlateau = uncompressedMetadata.VolumeCurvePlateau;
	}
	else
	{
		scratchInitParams->volCurvePlateau += uncompressedMetadata.VolumeCurvePlateau;
	}

	if(m_ParentIndex == 0xff || m_ParentOverrides.BitFields.SpeakerMaskOverridesParent)
	{
		scratchInitParams->speakerMask = uncompressedMetadata.SpeakerMask.Value;
	}
	else
	{
		scratchInitParams->speakerMask |= uncompressedMetadata.SpeakerMask.Value;
	}

	if (scratchInitParams->effectRoute == EFFECT_ROUTE_AS_PARENT)
	{
		scratchInitParams->effectRoute = uncompressedMetadata.EffectRoute;
	}

	const TristateValue shouldAttenuateOverDistance = AUD_GET_TRISTATE_VALUE(m_MetadataFlags, FLAG_ID_SOUND_DISTANCEATTENUATION);
	if(shouldAttenuateOverDistance != AUD_TRISTATE_UNSPECIFIED)
	{
		scratchInitParams->shouldAttenuateOverDistance = shouldAttenuateOverDistance;
	}

	const TristateValue shouldApplyEnvironmentalEffects = AUD_GET_TRISTATE_VALUE(m_MetadataFlags, FLAG_ID_SOUND_ENVIRONMENTALEFFECTS);
	if(shouldApplyEnvironmentalEffects != AUD_TRISTATE_UNSPECIFIED)
	{
		scratchInitParams->shouldApplyEnvironmentalEffects = shouldApplyEnvironmentalEffects;
	}

	const TristateValue shouldInvertPhase = AUD_GET_TRISTATE_VALUE(m_MetadataFlags, FLAG_ID_SOUND_INVERTPHASE);	
	if(shouldInvertPhase == AUD_TRISTATE_TRUE)
	{
		scratchInitParams->shouldInvertPhase = true;
	}
	else if(shouldInvertPhase == AUD_TRISTATE_FALSE)
	{
		scratchInitParams->shouldInvertPhase = false;
	}

	const TristateValue muteOnUserMusic = AUD_GET_TRISTATE_VALUE(m_MetadataFlags, FLAG_ID_SOUND_MUTEONUSERMUSIC);
	if(muteOnUserMusic == AUD_TRISTATE_TRUE)
	{
		scratchInitParams->shouldMuteOnUserMusic = true;
	}
	else if(muteOnUserMusic == AUD_TRISTATE_FALSE)
	{
		scratchInitParams->shouldMuteOnUserMusic = false;
	}
}

#define AUD_UNCOMPRESS(type,field) {if(compressedMetadata->Compression&curMask) { uncompressedMetadata.field = *(type*)ptr; ptr+=sizeof(type); } curMask <<= 1; }
const void *audSound::DecompressMetadata_Untyped(const Sound *compressedMetadata, Sound &uncompressedMetadata)
{
	const char *ptr = (char*)&compressedMetadata->Flags;
	u32 curMask = 1;

	// Default speaker mask to 0; we can't do it via the constructor due to violating POD constraints
	uncompressedMetadata.SpeakerMask.Value = 0U;
	uncompressedMetadata.ParentOverrides.Value = 0U;

	AUD_UNCOMPRESS(u32, Flags);
	AUD_UNCOMPRESS(u16, ParentOverrides.Value);
	AUD_UNCOMPRESS(u16, Volume);
	AUD_UNCOMPRESS(u16, VolumeVariance);
	AUD_UNCOMPRESS(s16, Pitch);
	AUD_UNCOMPRESS(u16, PitchVariance);
	AUD_UNCOMPRESS(s16, Pan);
	AUD_UNCOMPRESS(u16, PanVariance);
	AUD_UNCOMPRESS(u16, preDelay);
	AUD_UNCOMPRESS(u16, preDelayVariance);
	AUD_UNCOMPRESS(u32, StartOffset);
	AUD_UNCOMPRESS(u32, StartOffsetVariance);
	AUD_UNCOMPRESS(u16, AttackTime);
	AUD_UNCOMPRESS(s16, ReleaseTime);
	AUD_UNCOMPRESS(u16, DopplerFactor);
	AUD_UNCOMPRESS(u32, Category);
	AUD_UNCOMPRESS(u16, LPFCutoff);
	AUD_UNCOMPRESS(u16, LPFCutoffVariance);
	AUD_UNCOMPRESS(u16, HPFCutoff);
	AUD_UNCOMPRESS(u16, HPFCutoffVariance);
	AUD_UNCOMPRESS(u32, VolumeCurve);
	AUD_UNCOMPRESS(u16, VolumeCurveScale);
	AUD_UNCOMPRESS(u8, VolumeCurvePlateau);
	AUD_UNCOMPRESS(u8, SpeakerMask.Value);
	AUD_UNCOMPRESS(u8, EffectRoute);
	AUD_UNCOMPRESS(u32, PreDelayVariable);
	AUD_UNCOMPRESS(u32, StartOffsetVariable);
	AUD_UNCOMPRESS(u16, SmallReverbSend);
	AUD_UNCOMPRESS(u16, MediumReverbSend);
	AUD_UNCOMPRESS(u16, LargeReverbSend);

	return ptr;
}

void audSound::PullMetadataValues(void *metadata, bool useCache, Sound &uncompressedMetadata)
{
	Sound *compressedMetadata = (Sound*)metadata;
		
	const u8 *typeMetadata = reinterpret_cast<const u8*>(audSound::DecompressMetadata_Untyped(compressedMetadata, uncompressedMetadata));
	u8 typeMetadataOffset;
	Assign(typeMetadataOffset, typeMetadata - (u8*)m_BaseMetadata);

	m_ParentOverrides.Value = uncompressedMetadata.ParentOverrides.Value;
	m_MetadataFlags = uncompressedMetadata.Flags;

	m_TypeMetadataOffset = typeMetadataOffset;
	SoundAssert(m_TypeMetadataOffset == typeMetadataOffset);

	// Cache our variables, if any are set
	if(uncompressedMetadata.PreDelayVariable)
	{
		m_PredelayVariable = _FindVariableUpHierarchy(uncompressedMetadata.PreDelayVariable);
	}
	if(uncompressedMetadata.StartOffsetVariable)
	{
		m_StartOffsetVariable = _FindVariableUpHierarchy(uncompressedMetadata.StartOffsetVariable);
	}

	// Stored in rave fixed point, so 100 = 1, 1 = 0.01
	Assign(m_MetadataSmallReverb, uncompressedMetadata.SmallReverbSend);
	Assign(m_MetadataMediumReverb, uncompressedMetadata.MediumReverbSend);
	Assign(m_MetadataLargeReverb, uncompressedMetadata.LargeReverbSend);

	ComputeOffsetsFromVariances(&uncompressedMetadata, useCache);
	// if this is the first run through, store the metadata release time in our simpleReleaseStartTime
	if (!useCache)
	{
		// Store the simple release time
		s16 releaseTime = uncompressedMetadata.ReleaseTime;
		if (releaseTime < 0)
		{
			m_SimpleReleaseTime = kNoSimpleRelease;
		}
		else
		{
			m_SimpleReleaseTime = static_cast<u16>(releaseTime);
		}

		// grab the attack time only if one hasn't been set by code
		if(m_SimpleAttackTime == 0)
		{
			m_SimpleAttackTime = uncompressedMetadata.AttackTime;
		}
	}
}

void audSound::InitClass(u32 soundSlotSize, u32 numSoundSlotsPerBucket, u32 requestedSettingsSlotSize, u32 numRequestedSettingsSlotsPerBucket,u32 numBuckets, u32 numReservedBuckets)
{
	sm_Pool.Init(soundSlotSize, numSoundSlotsPerBucket, requestedSettingsSlotSize, numRequestedSettingsSlotsPerBucket, numBuckets, numReservedBuckets);
	sm_ShouldPreserveFullHierarchy = PARAM_fullsoundhierarchy.Get();
}

void audSound::ShutdownClass()
{
	sm_Pool.Shutdown();
}

#endif // !__SPU

#if __BANK && !__SPU
void PrintSoundPoolCB()
{
	audSound::GetStaticPool().DebugPrintSoundPool();
}

void PrintDormantSoundsCB()
{
	audSound::GetStaticPool().DebugPrintDormantSounds();
}

void PrintWaitingToBeDeletedSoundsCB()
{
	audSound::GetStaticPool().DebugPrintWaitingToBeDeletedSounds();
}

void PrintOldSoundsCB()
{
	audSound::GetStaticPool().DebugPrintSoundPoolOldSounds();
}

void audSound::SetForceMuteEnabled(bool enabled)
{
	AUD_CLEAR_TRISTATE_VALUE(m_MetadataFlags, FLAG_ID_SOUND_MUTE);
	AUD_SET_TRISTATE_VALUE(m_MetadataFlags, FLAG_ID_SOUND_MUTE, enabled? AUD_TRISTATE_TRUE : AUD_TRISTATE_FALSE);
}

void BreakOnNameChanged()
{
#if __USEDEBUGAUDIO
	const Sound* sound = SOUNDFACTORY.GetMetadataPtr(atStringHash(g_BreakOnNameBuf));
	if (sound)
	{
		g_BreakOnNameTableOffset = sound->NameTableOffset;
		g_BreakOnSoundUpdate = true;
		g_BreakOnSoundPlay = true;
		audDisplayf("Breakpoint set");
	}
	else
	{
		audDisplayf("Invalid breakpoint sound name: %s", g_BreakOnNameBuf);
	}
#endif
}

void audSound::AddWidgets(bkBank &bank)
{
	bank.PushGroup("audSound");
	bank.AddToggle("PreserveFullHierarchy", &sm_ShouldPreserveFullHierarchy);
	bank.AddButton("PrintSoundPool", CFA(PrintSoundPoolCB));
	bank.AddButton("PrintDormantSounds", CFA(PrintDormantSoundsCB));
	bank.AddButton("PrintWaitingToBeDeletedSounds", CFA(PrintWaitingToBeDeletedSoundsCB));
	bank.AddButton("PrintOldSounds", CFA(PrintOldSoundsCB));
	bank.AddText("BreakOnSoundName", &g_BreakOnNameBuf[0], sizeof(g_BreakOnNameBuf));
	bank.AddButton("EnabledBreakOnSoundName", CFA(BreakOnNameChanged));
	bank.PopGroup();
}
#endif

void audSound::CombineBuffers(audSoundCombineBuffer &combineBuffer)
{
#if AUD_SUPPORT_RAVE_EDITING && !__SPU
	// catch any RAVE changes
	if(sm_ShouldPreserveFullHierarchy && SOUNDFACTORY.GetNumEditedSounds() > 0)
	{
		// NOTE: this uses m_BaseMetadata directly rather than calling GetMetadata() in order to avoid
		// the IsInitialising assert - this is a very special case ...

		// See if our metadata has been edited
		const u32 nameHash = atStringHash(GetName());
		if(SOUNDFACTORY.IsSoundNameInEditedList(nameHash))
		{
			Sound *newMetadataPtr = const_cast<Sound*>(SOUNDFACTORY.GetMetadataPtr(nameHash));
			if(newMetadataPtr && newMetadataPtr->ClassId == m_SoundTypeId && newMetadataPtr != m_BaseMetadata)
			{
				m_BaseMetadata = newMetadataPtr;
				// PullMetadataValues will update m_TypeMetadataOffset
			}			
		}

		Sound uncompressedMetadata;
		PullMetadataValues(m_BaseMetadata, true, uncompressedMetadata);
	}
#endif // AUD_SUPPORT_RAVE_EDITING

#if RSG_BANK
	const audMetadataManager &metadataManager = SOUNDFACTORY.GetMetadataManager();
	if(metadataManager.IsRAVEConnected())
	{
		const u32 nameHash = atStringHash(GetName());
		combineBuffer.IsSoloed |= metadataManager.IsObjectSoloed(nameHash);
		combineBuffer.IsMuted |= metadataManager.IsObjectMuted(nameHash);
	}

	combineBuffer.IsMuted |= g_AudioEngine.GetSoundManager().ShouldMuteSound(this);
#endif // RSG_BANK

	s32 requestedPan = -1;
	s32 requestedPitch = 0;
	float requestedVolume = 0.f;
	float requestedPostSubmixAtten = 0.f;
	u32 requestedLPFCutoff = kVoiceFilterLPFMaxCutoff;
	u32 requestedHPFCutoff = 0;
		
	if(m_RequestedSettingsIndex != 0xff)
	{
		const tRequestedSettings &settings = GetRequestedSettings()->GetSettings_AudioThread();
		
		requestedPan = settings.Pan == tRequestedSettings::InvalidPanValue ? -1 : static_cast<s32>(settings.Pan);
		requestedPitch = settings.Pitch;
		requestedVolume = settings.Volume;
		requestedPostSubmixAtten = settings.PostSubmixVolumeAttenuation;
		requestedLPFCutoff = settings.LPFCutoff;
		requestedHPFCutoff = settings.HPFCutoff;
	}

	combineBuffer.Volume = IsMuted() ? g_SilenceVolume : ComputePlaybackVolume(combineBuffer.Volume, requestedVolume);
	combineBuffer.PostSubmixVolumeAttenuation = ComputePlaybackPostSubmixVolumeAttenuation(combineBuffer.PostSubmixVolumeAttenuation, requestedPostSubmixAtten);
	const s16 s16pan = static_cast<s16>(ComputePlaybackPan(combineBuffer.Pan, requestedPan));
	combineBuffer.Pan = s16pan;
	const s16 s16pitch = static_cast<s16>(ComputePlaybackPitch(combineBuffer.Pitch, requestedPitch));
	combineBuffer.Pitch = s16pitch;
	Assign(combineBuffer.LPFCutoff, ComputePlaybackLPFCutoff(combineBuffer.LPFCutoff, requestedLPFCutoff));
	Assign(combineBuffer.HPFCutoff, ComputePlaybackHPFCutoff(combineBuffer.HPFCutoff, requestedHPFCutoff));

	combineBuffer.SmallReverbSend = Max(combineBuffer.SmallReverbSend, m_MetadataSmallReverb);
	combineBuffer.MediumReverbSend = Max(combineBuffer.MediumReverbSend, m_MetadataMediumReverb);
	combineBuffer.LargeReverbSend = Max(combineBuffer.LargeReverbSend, m_MetadataLargeReverb);

#if __SOUND_DEBUG_DRAW
	if(m_InitParams.IsAuditioning)
	{
		sm_CachedCombineBuffers[GetIndexFromSound(m_InitParams.BucketId,this)] = combineBuffer;
	}
#endif
}

void audSound::Play()
{
	SoundAssert(m_RequestedSettingsIndex != 0xff);
	SoundAssert(!IsSetToPlay());

	audRequestedSettings *reqSet = GetRequestedSettings();

	if(m_SoundFlags.TrackEntityPosition && m_Entity && !reqSet->IsEntityPtrInvalid())
	{
		const Vec3V pos = m_Entity->GetPosition();
		reqSet->SetPosition(pos);
		reqSet->SetOrientation(m_Entity->GetOrientation());
	}
	else if(m_Tracker && !reqSet->IsTrackerPtrInvalid())
	{
		reqSet->SetPosition(VECTOR3_TO_VEC3V(m_Tracker->GetPosition()));
		reqSet->SetOrientation(m_Tracker->GetOrientation());
	}

	// Not waiting for anything, so set to play within our requested settings structure.
	// This ensures sounds played the same frame will start on the same audio frame. We should NEVER be
	// calling Play() from the audio thread. We then catch the reqSet IsSetToPlay from the SoundManager Update
	reqSet->SetIsSetToPlay(true);
}

#if !__SPU
void audSound::PrepareAndPlay(audWaveSlot* waveSlot, bool allowLoad, s32 timeLimit, const u32 priority)
{
	// NOTE: this function runs on the game thread, so can't modify any sound member variables directly
	if(!m_SoundFlags.PrepareFailed)
	{
		Play();
	}
	
	if(!m_SoundFlags.Prepared)
	{
		// store this request in our requested settings struct to be picked up by the sound manager
		SoundAssert(m_RequestedSettingsIndex != 0xff);
		s16 waveSlotIndex;
		Assign(waveSlotIndex, audWaveSlot::GetWaveSlotIndex(waveSlot));
		
		// 30 seconds should be plenty time to prepare
		enum {kMaxPrepareTimeLimit = 30000};
		SoundAssert(timeLimit == -1 || (timeLimit >= 0 && timeLimit <= kMaxPrepareTimeLimit));
		if(timeLimit == -1)
		{
			timeLimit = kMaxPrepareTimeLimit;
		}
		else
		{
			timeLimit = Clamp<s32>(timeLimit, 0, kMaxPrepareTimeLimit);
		}
		
		// note: using a timer that doesn't pause
		const u32 prepareTimeLimit = g_AudioEngine.GetTimeInMilliseconds() + static_cast<u32>(timeLimit);
		
		GetRequestedSettings()->RequestPrepare(waveSlotIndex, allowLoad, prepareTimeLimit, priority);
	}
}
#endif //!__SPU

void audSound::_ManagedAudioPlay(u32 timeInMs, audSoundCombineBuffer combineBuffer)
{
#if __USEDEBUGAUDIO && !__SPU
	if(g_BreakOnSoundPlay && ((g_BreakOnNameTableOffset == m_BaseMetadata->NameTableOffset) || (m_InitParams.BucketId == g_BreakOnSoundBucketId && sm_Pool.GetSoundSlotIndex(m_InitParams.BucketId, this) == g_BreakOnSoundSlotId)))
	{
		__debugbreak();
	}
#endif

	// Insist on only playing from a DORMANT state
	SoundAssertf(GetPlayState() == AUD_SOUND_DORMANT || GetPlayState() == AUD_SOUND_PREPARING, "[audSound::_ManagedAudioPlay] Playstate: %i", GetPlayState());

	CombineBuffers(combineBuffer);

	// We've just started, so set timeTriggered
	m_TimeTriggered = timeInMs;

	// Calculate total predelay, from requested and meta predelay and start offset.
	// This is now also done during Prepare(), so streams can prepare properly - should be safe to do it again here.
	_ComputePlaybackPredelayAndStartOffset();

	// See if we've got a predelay, and if so, start working through it
	SoundAssertf(GetPredelayState() == AUD_PREDELAY_NOT_STARTED, "[audSound::_ManagedAudioPlay] PredelayState: %i", GetPredelayState());
	u32 playbackPredelay = IsEffectRouteValid() ? m_PlaybackPredelay : 0;

	SetPlayState(AUD_SOUND_PLAYING);

	ApplySimpleAttackEnvelope(timeInMs, combineBuffer);

	// We want to special case no predelay, so sounds that have none aren't delayed by a frame
	if (playbackPredelay <= kPredelayStartThresholdMs)
	{
		// We have no predelay, so start immediately
		SetPredelayState(AUD_PREDELAY_FINISHED);

		audSoundSyncSetSlaveId slaveSyncId;
		audSoundSyncSetMasterId masterSyncId;

		if(m_RequestedSettingsIndex != 0xff)
		{
			const audRequestedSettings *settings = GetRequestedSettings();
			slaveSyncId.Set(settings->GetSyncSlaveId());
			masterSyncId.Set(settings->GetSyncMasterId());
		}

		if(IsEffectRouteValid())
		{
			InvokeAudioPlay(timeInMs, combineBuffer);
		}		
	}
	else
	{
		// Set up our predelay state, but don't call AudioPlay
		SetPredelayState(AUD_PREDELAY_ONGOING);
	}
}

void audSound::_ActionStopRequest()
{
	SoundAssert(m_RequestedSettingsIndex != 0xff);
	
	if (GetPlayState() == AUD_SOUND_PLAYING)
	{
		_Stop();
	}
	else
	{
		SetToBeDestroyed();
	}
}

void audSound::_Stop()
{
	SetIsToBeStopped();
	_Release();
}

void audSound::Stop()
{
	SoundAssert(m_RequestedSettingsIndex != 0xff);
	GetRequestedSettings()->SetIsSetToStop(true);
}

void audSound::StopAndForget(bool continueUpdatingEntity)
{
#if __USEDEBUGAUDIO
	if(audSoundManager::ShouldBreakOnStop(this))
	{
		audWarningf("Debug breaking on sound %s stopping", this->GetName());
		audWarningf("Sound hierarchy: \n%s", ComputeHierarchyString(true));
		__debugbreak();
	}
#endif

	SoundAssert(m_RequestedSettingsIndex != 0xff);
	audRequestedSettings *reqSets = GetRequestedSettings();
	reqSets->SetUpdateEntity(continueUpdatingEntity);
	reqSets->SetAllowOrphaned(true);
	if (m_EntitySoundReference!=NULL && !reqSets->IsEntityRefPtrInvalid())
	{
		// NULL the pointer within the audio entity immediately
		(*m_EntitySoundReference) = NULL;
	}
	reqSets->InvalidateEntityRefPtr();
	if(!continueUpdatingEntity)
	{
		reqSets->InvalidateEntityPtr();
	}
	reqSets->SetIsSetToStop(true);
}

void audSound::InvalidateTracker(audRequestedSettings *reqSets)
{	
	if(m_Tracker && !reqSets->IsTrackerPtrInvalid())
	{
		m_Tracker->DecrementReferenceCount();
	}
	reqSets->InvalidateTrackerPtr();
}

void audSound::BaseActionReleaseRequest(const u32 timeInMs)
{
	// This doesn't need to be speedy, as we only do it on the one frame when we're first asked to release
	SetWasSetToReleaseLastFrame(true);
	SetIsBeingReleased();

	if(m_RequestedSettingsIndex != 0xff)
	{
		// override the metadata release time
		s32 requestedRelease = GetRequestedSettings()->GetReleaseTime();
		if(requestedRelease != -1)
		{
			Assign(m_SimpleReleaseTime, static_cast<u32>(requestedRelease));
		}
	}
	// If we have no release envelope, we release via children	
	if (m_SimpleReleaseTime == kNoSimpleRelease)
	{
		// Set this to zero, just to be safer (I don't think this should be used after this point).
		m_SimpleReleaseTime = 0;
		// Tell child sounds to stop, so that they all start releasing
		ManagedAudioStopChildren();
		SetIsWaitingForChildrenToRelease(true);
	}
	else
	{
		f32 simpleAttackTimeFactor = 1.0f;

		// If we are still in the middle of a simple attack, adjust our simple release start point so that the fade
		// out starts from the same point. Logic to calculate the timefactor mirrors that in ApplySimpleAttackEnvelope.
		if(m_SimpleAttackTime>0)
		{
			const s32 startOffset = GetStartOffset();
			s32 timeIntoSoundAtStart = startOffset - m_MetadataPredelay;

			const s32 attackTime = static_cast<s32>(m_SimpleAttackTime);

			if(timeIntoSoundAtStart <= 0 || timeIntoSoundAtStart <= attackTime)
			{
				u32 playTime = timeInMs - m_TimeTriggered;

				s32 predelay = GetPredelay();
				if(predelay <= kPredelayStartThresholdMs)
				{
					predelay = 0;
				}

				if(playTime < m_SimpleAttackTime + static_cast<u32>(predelay - timeIntoSoundAtStart))
				{
					if(playTime >= (u32)predelay)
					{
						simpleAttackTimeFactor = Clamp(((s32)playTime - predelay + timeIntoSoundAtStart) / (f32)attackTime, 0.0f, 1.0f);
					}
				}
			}
		}
		
		// Set up our simple release time
		m_SimpleReleaseStartTime = timeInMs - (u32)(m_SimpleReleaseTime * (1.0f - simpleAttackTimeFactor));
	}
}

bool audSound::ApplySimpleReleaseEnvelope(const u32 timeInMs, audSoundCombineBuffer &combineBuffer)
{
	bool finished = false;
	f32 releaseAttenuation = 0.0f;
	const u32 simpleReleaseStopTime = m_SimpleReleaseStartTime + m_SimpleReleaseTime;
	if (simpleReleaseStopTime <= m_SimpleReleaseStartTime)
	{
		finished = true;
		releaseAttenuation = 0.0f;
	}
	else if (timeInMs >= simpleReleaseStopTime)
	{
		finished = true;
		releaseAttenuation = 0.0f;
	}
	else
	{
		releaseAttenuation = ((f32)(simpleReleaseStopTime - timeInMs)) / ((f32)(simpleReleaseStopTime - m_SimpleReleaseStartTime));/*sm_ReleaseCurve.CalculateValue(1.f - ((f32)(m_SimpleReleaseStopTime - timeInMs)) / ((f32)(m_SimpleReleaseStopTime - m_SimpleReleaseStartTime)));*/
	}

	combineBuffer.Volume += audDriverUtil::ComputeDbVolumeFromLinear(releaseAttenuation);
	if (finished)
	{
		return false;
	}
	else
	{
		return true;
	}
}

bool audSound::ApplySimpleAttackEnvelope(const u32 timeInMs, audSoundCombineBuffer &combineBuffer)
{
	if(m_SimpleAttackTime>0)
	{
		const s32 startOffset = GetStartOffset();
		s32 timeIntoSoundAtStart = startOffset - m_MetadataPredelay;

		const s32 attackTime = static_cast<s32>(m_SimpleAttackTime);
		// That's how far into the attack we need to skip. If that's negative, we're good. If positive, we need to ignore that much of the attack.
		if (timeIntoSoundAtStart > 0)
		{
			if (timeIntoSoundAtStart > attackTime)
			{
				return false;
			}
			// So we're skipping into the middle of the attack
		}
		u32 playTime = timeInMs - m_TimeTriggered;

		s32 predelay = GetPredelay();
		if(predelay <= kPredelayStartThresholdMs)
		{
			// Ignore 'residual' predelay, since we're going to pass that down to the voice
			predelay = 0;
		}
		if(playTime < m_SimpleAttackTime + static_cast<u32>(predelay - timeIntoSoundAtStart))
		{
			if(playTime >= (u32)predelay)
			{
				f32 timeFactor = Clamp(((s32)playTime - predelay + timeIntoSoundAtStart) / (f32)attackTime, 0.0f, 1.0f);
				combineBuffer.Volume += audDriverUtil::ComputeDbVolumeFromLinear(timeFactor);
			}
			return true;
		}
	}
	return false;
}

SPU_ONLY(extern const u8 *g_DisabledMetadataPtr);
SPU_ONLY(extern u32 g_DisabledMetadataSize);

bool IsMetadataDisabled(const void *metadata)
{
#if RSG_SPU
	return (metadata >= g_DisabledMetadataPtr && metadata < g_DisabledMetadataPtr + g_DisabledMetadataSize);
#else
	return g_AudioEngine.GetSoundManager().IsMetadataDisabled(metadata);
#endif
}

bool audSound::_ManagedAudioUpdate(u32 timeInMs, audSoundCombineBuffer combineBuffer)
{
	bool shouldRemoveFromHierarchy = false;

#if __USEDEBUGAUDIO && !__SPU
	if(g_BreakOnSoundUpdate && ((g_BreakOnNameTableOffset == m_BaseMetadata->NameTableOffset) || (m_InitParams.BucketId == g_BreakOnSoundBucketId && sm_Pool.GetSoundSlotIndex(m_InitParams.BucketId, this) == g_BreakOnSoundSlotId)))
	{
		__debugbreak();
	}
#endif
	// Insist on only audio updating if we're playing (game updates will still happen)
	SoundAssert(GetPlayState() == AUD_SOUND_PLAYING);

	// When we unload metadata chunks (for example in SinglePlayer to MultiPlayer transition) we need to kill any playing sounds
	// that reference the unloaded metadata.
	if(IsMetadataDisabled(m_BaseMetadata))
	{
		InvokeAudioKill();
		SetPlayState(AUD_SOUND_WAITING_TO_BE_DELETED);    
		return false;
	}

	audSoundSyncSetSlaveId slaveSyncId;
	audSoundSyncSetMasterId masterSyncId;

	if(m_RequestedSettingsIndex != 0xff)
	{
		const audRequestedSettings *settings = GetRequestedSettings();
		slaveSyncId.Set(settings->GetSyncSlaveId());
		masterSyncId.Set(settings->GetSyncMasterId());
	}

	CombineBuffers(combineBuffer);

	if (IsSetToRelease() && !WasSetToReleaseLastFrame())
	{
		// This will be overridden for EnvironmentSound, in such a way that default handling below won't be called.
		InvokeActionReleaseRequest(timeInMs);
	}

	// If we're waiting on our children to release, and they have, stop ourselves.
	if (IsWaitingForChildrenToRelease())
	{
		if (!AreChildrenPlaying())
		{
			InvokeAudioKill();
			SetPlayState(AUD_SOUND_WAITING_TO_BE_DELETED);    
			return false;
		}
	}
	else if (IsBeingReleased())
	{
		// We're not an EnvelopeSound, and we're not releasing via children, so we have a simple release env applied
		SoundAssert(m_SimpleReleaseStartTime>0);
		SoundAssert(m_SimpleReleaseTime != kNoSimpleRelease);
		// If the envelope attenuation is full, and we've not specified to ignore that (eg OnStopSound) return that we're finished
		if (!ApplySimpleReleaseEnvelope(timeInMs, combineBuffer))
		{
			if(ShouldFinishWhenEnvelopeAttenuationFull())
			{
 				InvokeAudioKill();
				SetPlayState(AUD_SOUND_WAITING_TO_BE_DELETED);    
				return false;
			}
			else if(!IsWaitingForChildrenToRelease())
			{
				// We've finished the release envelope so tell our children to stop and mark this sound as waiting for children to release 
				// (exact same behavior as if we were using kNoSimpleRelease). Individual sound types can override ManagedAudioStopChildren 
				// if they want to restrict exactly which children are stopped here.
				ManagedAudioStopChildren();
				SetIsWaitingForChildrenToRelease(true);
			}
		}
	}

	// dont apply attack if we're releasing
	if(!IsBeingReleased() && !ApplySimpleAttackEnvelope(timeInMs, combineBuffer) && IsWaitingToBeRemovedFromHierarchy())
	{
		shouldRemoveFromHierarchy = true;
	}

	// See if we're still in the predelay
	if (GetPredelayState() == AUD_PREDELAY_ONGOING)
	{
		// See if we're done, and if we are, kick off the sound properly
		if (GetPredelayFinishTime() <= timeInMs + kPredelayStartThresholdMs)
		{
			SetPredelayState(AUD_PREDELAY_FINISHED);
			InvokeAudioPlay(timeInMs, combineBuffer);
		}
		return true; // either way, we're still playing
	}
	else
	{
		bool playing = IsEffectRouteValid() ? InvokeAudioUpdate(timeInMs, combineBuffer) : false;

		if (!playing)
		{
			SetPlayState(AUD_SOUND_WAITING_TO_BE_DELETED);
		}
		else
		{
			if(shouldRemoveFromHierarchy)
			{		
				// action the removal request
				ActionHierarchyRemovalRequest();
			}
		}
		return playing;
	}	
}

void audSound::_ManagedAudioKill()
{
	// I guess PlayState can be anything and Stop is still valid

	// Don't bother doing Stop() on sounds that are already waiting to be deleted. This feels like it should actually
	// be safe, but it was causing a safety-check assert in scripted sound.

	if (GetPlayState() == AUD_SOUND_WAITING_TO_BE_DELETED)
	{
		return;
	}

	InvokeAudioKill();
	SetPlayState(AUD_SOUND_WAITING_TO_BE_DELETED);
}

void audSound::_ManagedAudioDestroy()
{
	switch (m_PlayState)
	{
	case AUD_SOUND_DORMANT:
		SetPlayState(AUD_SOUND_WAITING_TO_BE_DELETED);
		break;
	case AUD_SOUND_PREPARING:
		_ManagedAudioKill();
		break;
	case AUD_SOUND_PLAYING:
		_ManagedAudioKill();
		break;
	default:
		SetPlayState(AUD_SOUND_WAITING_TO_BE_DELETED);
		break;
	}
}

audPrepareState audSound::Prepare(audWaveSlot *waveSlot, const bool allowLoad, const u32 priority)
{
	SoundAssert(m_RequestedSettingsIndex != 0xff);
	if(m_SoundFlags.PrepareFailed)
	{
		return AUD_PREPARE_FAILED;
	}
	else if(m_SoundFlags.Prepared)
	{
		return AUD_PREPARED;
	}	
	else
	{
		// NOTE: this doesn't change any states but checks if this hierarchy is actually already prepared, ie for simple sound
		// that can mark itself as prepared in Init() but the parent sound doesnt know as ManagedAudioPrepare hasn't yet been called.
		// This is only necessary so that game code knows if something is prepared instantly
		audPrepareState prepareState = _ManagedAudioPrepare(waveSlot, allowLoad, true, priority);
		if(prepareState != AUD_PREPARE_FAILED)
		{
			// request prepare even if we're about to return PREPARED as even statically loaded sounds need a full prepare before they can
			// play
			s16 waveSlotIndex;
			Assign(waveSlotIndex, audWaveSlot::GetWaveSlotIndex(waveSlot));
			GetRequestedSettings()->RequestPrepare(waveSlotIndex, allowLoad, ~0U, priority);
		}
		return prepareState;
	}
}

audPrepareState audSound::_ManagedAudioPrepareFromSoundManager()
{
	SoundAssert(m_RequestedSettingsIndex!=0xff);
	const audRequestedSettings *reqSets = GetRequestedSettings();
	SetPrepareTimeLimit(reqSets->GetPrepareTimeLimit());
	return _ManagedAudioPrepare(audWaveSlot::GetWaveSlotFromIndex(reqSets->GetWaveSlotIndex()), reqSets->IsAllowedToLoad(), false, reqSets->GetWaveLoadPriority());
}

audPrepareState audSound::_ManagedAudioPrepare(audWaveSlot * waveSlot, const bool allowLoad, const bool checkStateOnly, const u32 priority)
{
	SoundAssert(GetPlayState() == AUD_SOUND_DORMANT || GetPlayState() == AUD_SOUND_PREPARING);

	//Assert(m_ParentIndex != 0xff || sm_Pool.IsParentSoundSlot(m_InitParams.BucketId, GetIndexFromSound(m_InitParams.BucketId,this)));
	
	if(HasPrepared())
	{
		return AUD_PREPARED;
	}
	else if(HasPrepareFailed())
	{
		return AUD_PREPARE_FAILED;
	}
	// Cheeky short-cut, where we use our pre-stored m_WaveSlot if passed in null, which should be fine :-)
	// Do this because we might be calling Prepare() from SoundManager::Update() without knowing the 
	// right wave slot.
	if (waveSlot == NULL)
	{
		waveSlot = audWaveSlot::GetWaveSlotFromIndex(m_InitParams.WaveSlotIndex);
	}

	if(!checkStateOnly)
	{
		SetCanDynamicallyLoad(allowLoad);
		SetWaveLoadPriority(priority);
		SetPlayState(AUD_SOUND_PREPARING);
		// We need to store this waveSlot, because if we're dynamically preparing, we need to know it later
		Assign(m_InitParams.WaveSlotIndex,audWaveSlot::GetWaveSlotIndex(waveSlot));
	}


	// Prepare like normal
	audPrepareState prepared = IsEffectRouteValid() ? InvokeAudioPrepare(waveSlot, checkStateOnly) : AUD_PREPARED;
	if(!checkStateOnly)
	{
		if (prepared==AUD_PREPARED)
		{
			SetPlayState(AUD_SOUND_DORMANT);
			// for now compute this stuff after we've prepared
			_ComputePlaybackPredelayAndStartOffset();
			m_SoundFlags.Prepared = true;
		}
		else if(prepared == AUD_PREPARE_FAILED)
		{
			m_SoundFlags.PrepareFailed = true;
		}
		else
		{
			// streaming sound can change its mind about whether its prepared based on code parameters, so we might need to
			// invalidate a previous positive result
			m_SoundFlags.Prepared = false;
		}
	}
	return prepared;
}

void audSound::BasePause(u32 timeInMs)
{
	for(u32 i = 0; i < audSound::kMaxChildren; i++)
	{
		audSound* childSound = GetChildSound(i);
		if(childSound)
		{
			childSound->InvokePause(timeInMs);
		}	
	}
}

#if !__SPU

void audSound::Update(const u32 timeInMs, const audRequestedSettings::Indices &reqSetIndices)
{
	// Look at our flags, and see if we're calling out to an audEntity, audTracker, etc
	PF_FUNC(Update);
	DIAG_CONTEXT_MESSAGE(GetName());

	// Do a normal update. This might not happen, if just deleted, and flags changed.
	SoundAssert(m_RequestedSettingsIndex != 0xff);
	if(m_RequestedSettingsIndex != 0xff)
	{
		audRequestedSettings *reqSets = GetRequestedSettings();

		// Whether or not we update an audEntity, we go to our tracker if we have one.
		if(m_SoundFlags.TrackEntityPosition && m_Entity && !reqSets->IsEntityPtrInvalid())
		{
			const Vec3V pos = m_Entity->GetPosition();
			reqSets->SetPosition(pos);
			reqSets->SetOrientation(m_Entity->GetOrientation());
		}
		else if (m_Tracker && !reqSets->IsTrackerPtrInvalid())
		{
			{
				PF_FUNC(SetRequestedPosition);
				reqSets->SetPosition(VECTOR3_TO_VEC3V(m_Tracker->GetPosition()));
			}

			{
				PF_FUNC(SetRequestedOrientation);
				reqSets->SetOrientation(m_Tracker->GetOrientation());
			}
		}
		
		{
			// Update the environment game metric before calling UpdateSound on the owning entity, so that the entity can override parameters
			// on a per sound basis where necessary
			PF_FUNC(UpdateOcclusionMetric);
			reqSets->UpdateEnvironmentGameMetric();
		}

		if (m_Entity && reqSets->ShouldUpdateEntity() && !reqSets->IsEntityPtrInvalid())
		{
			PF_FUNC(UpdateSound);
			// check that StopAndForget() hasn't been called but not actioned
			m_Entity->UpdateSound(this, reqSets, timeInMs);
		}

		reqSets->Commit(timeInMs, reqSetIndices);
	}
}

void audSound::ComputeOffsetsFromVariances(const Sound *metadata, const bool useCached)
{
	// initialise offsets from variances

	f32 _volumeVariance;
	if( metadata->VolumeVariance )
		_volumeVariance = ComputeMetadataVolumeOffset(metadata->VolumeVariance, useCached);
	else
		_volumeVariance = 0.0f;

	s16 _pitchVariance;
	if( metadata->PitchVariance )
		_pitchVariance = ComputeMetadataPitchOffset(metadata->PitchVariance, useCached);
	else
		_pitchVariance = 0;

	m_MetadataVolume = (metadata->Volume * 0.01f) + _volumeVariance;
	m_MetadataPitch = metadata->Pitch + _pitchVariance;
	if (metadata->Pan!=-1)
	{
		// We need this check so that we can vary around zero and get the desired result
			
		s16 _panVariance;
		if( metadata->PanVariance )
			_panVariance = ComputeMetadataPanOffset(metadata->PanVariance, useCached);
		else
			_panVariance = 0;

		m_MetadataPan = (metadata->Pan + _panVariance + 360) % 360;
	}
	else
	{
		m_MetadataPan = -1;
	}

	s32 _preDelayVariance;
	if(metadata->preDelayVariance)
		_preDelayVariance = ComputeMetadataPredelayOffset(metadata->preDelayVariance, useCached);
	else
		_preDelayVariance = 0;

	m_MetadataPredelay = u16(Max(0, metadata->preDelay + _preDelayVariance));

	s32 _startOffsetVariance;
	if(metadata->StartOffsetVariance)
		_startOffsetVariance = ComputeMetadataStartOffsetOffset(metadata->StartOffsetVariance, useCached);
	else
		_startOffsetVariance = 0;

	m_MetadataStartOffset = metadata->StartOffset + _startOffsetVariance;
	SoundAssertMsg(m_MetadataStartOffset >= 0, "MetadataStartOffset is negative!");

	s32 cutoffVariance = ComputeMetadataLPFCutoffOffset(metadata->LPFCutoffVariance, useCached);
	Assign(m_MetadataLPFCutoff, Clamp<u32>(metadata->LPFCutoff + cutoffVariance, kVoiceFilterLPFMinCutoff, kVoiceFilterLPFMaxCutoff));

	cutoffVariance = ComputeMetadataHPFCutoffOffset(metadata->HPFCutoffVariance, useCached);
	Assign(m_MetadataHPFCutoff, Clamp<u32>(metadata->HPFCutoff + cutoffVariance, kVoiceFilterHPFMinCutoff, kVoiceFilterHPFMaxCutoff));
}
#endif // !__SPU

s32 audSound::ComputeDurationMsIncludingStartOffsetAndPredelay(bool* isLooping)
{
	return _ComputeDurationMsIncludingStartOffsetAndPredelay(isLooping);
}

s32 audSound::_ComputeDurationMsIncludingStartOffsetAndPredelay(bool* isLooping)
{
	s32 length = InvokeComputeDurationMsExcludingStartOffsetAndPredelay(isLooping);

	bool startOffsetPercentage = IsStartOffsetPercentage();
	u32 startOffset = m_PlaybackStartOffset;
	if (m_StartOffsetVariable)
	{
		if(!startOffsetPercentage)
		{
			startOffset += (u32)((AUD_GET_VARIABLE(m_StartOffsetVariable)) * 1000.0f);
		}
		else
		{
			// don't scale the variable if its a percentage so the sound designer can type 50 and mean 50%
			startOffset += (u32)((AUD_GET_VARIABLE(m_StartOffsetVariable)));
		}
		
	}

	u32 predelay = m_PlaybackPredelay;
	if (m_PredelayVariable)
	{
		predelay += (s32)((AUD_GET_VARIABLE(m_PredelayVariable)) * 1000.0f);
	}

	if (length == kSoundLengthUnknown)
	{
		return kSoundLengthUnknown;
	}

	if (startOffsetPercentage)
	{
		return ((s32)(predelay + (f32)length * ((f32)startOffset*0.01f)));
	}
	else
	{
		if ((u32)length <= startOffset)
		{
			return (predelay);
		}
		else
		{
			return (predelay + length - startOffset);
		}
	}
}


/////////////////////////////////
// tristate flag accessor functions

// note:  isstartoffsetpercentage doesnt combine with parent
bool audSound::IsStartOffsetPercentage() const
{
	TristateValue val = AUD_GET_TRISTATE_VALUE(m_MetadataFlags, FLAG_ID_SOUND_STARTOFFSETPERCENTAGE);
	bool ret = false;
	
	if(val == AUD_TRISTATE_UNSPECIFIED)
	{
		// unspecified - default to false
		ret = false;
	}
	else if(val == AUD_TRISTATE_TRUE)
	{
		ret = true;
	}
	else
	{
		// val == AUD_TRISTATE_FALSE
		ret = false;
	}

	return ret;
}

bool audSound::ShouldVirtualiseAsGroup() const
{
	TristateValue val = AUD_GET_TRISTATE_VALUE(m_MetadataFlags, FLAG_ID_SOUND_VIRTUALISEASGROUP);
	bool ret = false;

	if(val == AUD_TRISTATE_UNSPECIFIED)
	{
		// unspecified default to false
		// NOTE:  this shouldn't combine with parent, since the actual virtualisation group 
		// combines instead
		ret = false;
	}
	else if(val == AUD_TRISTATE_TRUE)
	{
		ret = true;
	}
	else
	{
		// val == AUD_TRISTATE_FALSE
		ret = false;
	}

	return ret;
}

audDynamicValueId audSound::GetDynamicVariableHandleFromName(const u32 nameHash)
{
	if(nameHash == ATSTRINGHASH("DistanceFromListener", 0x3FCA7758) || nameHash == ATSTRINGHASH("Sound.DistanceFromListener", 0xA72E8703))
	{
		return AUD_DYNAMICVALUE_LISTENER_DISTANCE;
	}
	if(nameHash == ATSTRINGHASH("Sound.PositionX", 0x422DAAF4) || nameHash == ATSTRINGHASH("Sound.Position.x", 0xB06ACED0))
	{
		return AUD_DYNAMICVALUE_POSX;
	}
	if(nameHash == ATSTRINGHASH("Sound.PositionY", 0x6855F744) || nameHash == ATSTRINGHASH("Sound.Position.y", 0x774FCDF))
	{
		return AUD_DYNAMICVALUE_POSY;
	}
	if(nameHash == ATSTRINGHASH("Sound.PositionZ", 0x9DAFE1FF) || nameHash == ATSTRINGHASH("Sound.Position.z", 0xD5FF19F4))
	{
		return AUD_DYNAMICVALUE_POSZ;
	}
	if(nameHash == ATSTRINGHASH("Sound.VelocityX", 0xD4189CBC) || nameHash == ATSTRINGHASH("Sound.Velocity.x", 0x4529CE50))
	{
		return AUD_DYNAMICVALUE_VELX;
	}
	if(nameHash == ATSTRINGHASH("Sound.Position.Seed", 0x538E2201))
	{
		return AUD_DYNAMICVALUE_POSSEED;
	}
	if(nameHash == ATSTRINGHASH("Sound.VelocityY", 0x23F7909) || nameHash == ATSTRINGHASH("Sound.Velocity.y", 0x5770F2DE))
	{
		return AUD_DYNAMICVALUE_VELY;
	}
	if(nameHash == ATSTRINGHASH("Sound.VelocityZ", 0xE88D45A5) || nameHash == ATSTRINGHASH("Sound.Velocity.z", 0x284C1495))
	{
		return AUD_DYNAMICVALUE_VELZ;
	}
	if(nameHash == ATSTRINGHASH("Sound.Speed", 0x36EF1E61))
	{
		return AUD_DYNAMICVALUE_SPEED;
	}
	if(nameHash == ATSTRINGHASH("Sound.ForwardSpeed", 0x51384371))
	{
		return AUD_DYNAMICVALUE_FWDSPEED;
	}
	if(nameHash == ATSTRINGHASH("Sound.UpSpeed", 0xB6451949))
	{
		return AUD_DYNAMICVALUE_UPSPEED;
	}
	if(nameHash == ATSTRINGHASH("Sound.ClientVar", 0xa77c46f7))
	{
		return AUD_DYNAMICVALUE_CLIENTVAR;
	}
	if(nameHash == ATSTRINGHASH("Sound.PlayTime", 0xA69EF7A1))
	{
		return AUD_DYNAMICVALUE_PLAYTIME;
	}

	return AUD_DYNAMICVALUE_NONE;
}

#if !__SPU
audVariableHandle audSound::_FindVariableUpHierarchy(const u32 nameHash)
{
	audDynamicValueId dynamicValue = GetDynamicVariableHandleFromName(nameHash);

	if(dynamicValue != AUD_DYNAMICVALUE_NONE)
	{
		return (void*)dynamicValue;
	}

	if(nameHash != 0)
	{
		if (m_ParentIndex!=0xff)
		{
			return (_GetParent()->_FindVariableUpHierarchy(nameHash));
		}
		else if(m_Entity && m_RequestedSettingsIndex != 0xFF && !GetRequestedSettings()->IsEntityPtrInvalid())
		{
			audVariableBlock * varBlock = m_Entity->GetVariableBlock();
			if(varBlock)
			{
				audVariableHandle value = varBlock->FindVariableAddress(nameHash);
				if(value)
				{
					return value;
				}
			}
			return g_AudioEngine.GetSoundManager().GetVariableAddress(nameHash);
		}
		else
		{
			return g_AudioEngine.GetSoundManager().GetVariableAddress(nameHash);
		}
	}
	else
	{
		return NULL;
	}
}
#endif

audVariableHandle audSound::_FindVariableDownHierarchy(const u32 nameHash, const VariableUsage usage)
{
	// We don't allow searching down if we're able to remove ourselves from the hierarchy and are playing
	// Parent sounds cannot be removed, so its always safe to search at least one level down.
	if (m_ParentIndex != 0xff && CanBeRemovedFromHierarchy() && GetPlayState()==AUD_SOUND_PLAYING)
	{
		// We can't safely search through a hierarchy from the game thread while it is playing, since sounds could be
		// removed at any time.
		//SoundAssertMsg(false, "Searching for a sound variable down a playing hierachy is not supported - "
		//	"move the VariableBlock sound containing this variable to one of the top two levels of the hierarchy");
		return NULL;
	}

	audDynamicValueId dynamicValue = GetDynamicVariableHandleFromName(nameHash);

	if(dynamicValue != AUD_DYNAMICVALUE_NONE)
	{
		SoundAssert(usage == ReadOnly);
		return (void*)dynamicValue;
	}
	
	if (!HasDynamicChildren())
	{
		for (int i=0; i<audSound::kMaxChildren; i++)
		{
			audSound *child = GetChildSound(i);
			if (child)
			{
				audVariableHandle variable = child->_FindVariableDownHierarchy(nameHash, usage);
				if (variable)
				{
					return variable;
				}
			}
		}
	}
	// we didn't find the variable anywhere in our children
	return NULL;
}

void audSound::SetVariableValueDownHierarchyFromName(const char* name, const f32 newValue)
{
	FindAndSetVariableValue(atStringHash(name), newValue);
}

void audSound::SetVariableValueDownHierarchyFromName(const u32 nameHash, const f32 newValue)
{
	FindAndSetVariableValue(nameHash, newValue);
}

void audSound::FindAndSetVariableValue(const u32 nameHash, const f32 newValue)
{
	audVariableHandle variable = _FindVariableDownHierarchy(nameHash, SetFromGame);
	if (variable)
	{
		AUD_SET_VARIABLE(variable, newValue);
	}
}

/////////////////////////////////
// Pitch
/////////////////////////////////
f32	audSound::GetRequestedFrequencyRatio() const
{
	SoundAssert(m_RequestedSettingsIndex!=0xff);
	return (audDriverUtil::ConvertPitchToRatio(GetRequestedSettings()->GetPitch()));
}
void audSound::SetRequestedFrequencyRatio(const f32 frequencyRatio)
{
	SoundAssert(m_RequestedSettingsIndex!=0xff);
	GetRequestedSettings()->SetPitch(audDriverUtil::ConvertRatioToPitch(frequencyRatio));
}

/////////////////////////////////
// Predelay and Start Offset
/////////////////////////////////
s32 audSound::GetVariablePredelay() const
{
	s32 predelay = 0;
	if (m_PredelayVariable)
	{
		predelay += (s32)((AUD_GET_VARIABLE(m_PredelayVariable)) * 1000.0f);
	}
	return predelay;
}

void audSound::_ComputePlaybackPredelayAndStartOffset()
{
	// From the requested and (variance applied) metadata for StartOffset and PreDelay, work out how much
	// predelay and start offset we actually want. Essentially, we view metadata first, and apply the start
	// offset, then add on its predelay. Once that's calculated, we hop into that by the requested start offset,
	// and then add on the requested predelay. Relies on custom sound types correctly working out the requested
	// StartOffset for child sounds (and predelays for snazzy effects only).

	// For simplicity (and because I'm not sure what a complicated answer would even be...) this just adds the variable
	// start offset and predelay onto the metadata ones. 
	
	// We can only do this before the sound has started playing
	//SoundAssert(GetPlayState() == AUD_SOUND_DORMANT);

	m_PlaybackStartOffset = 0;
	s32 metadataPredelay = m_MetadataPredelay;
	if (m_PredelayVariable)
	{
		metadataPredelay += (s32)((AUD_GET_VARIABLE(m_PredelayVariable)) * 1000.0f);
	}
	u32 metadataStartOffset = m_MetadataStartOffset;
	const bool isStartOffsetPercentage = IsStartOffsetPercentage();
	if (m_StartOffsetVariable)
	{
		f32 startOffsetVariableContrib = AUD_GET_VARIABLE(m_StartOffsetVariable);
		if(!isStartOffsetPercentage)
		{
			// Convert Seconds into Milliseconds
			startOffsetVariableContrib *= 1000.f;
		}
		metadataStartOffset += (u32)startOffsetVariableContrib;
	}

	s32 requestedPredelay    = GetPredelay();
	u32 requestedStartOffset = GetStartOffset();

	// If our StartOffsets are percentages, we need to know how long we are, and then turn them into absolutes
	if (isStartOffsetPercentage)
	{
		const s32 duration = InvokeComputeDurationMsExcludingStartOffsetAndPredelay(NULL);
		const f32 f32Duration = static_cast<f32>(duration);
		const f32 f32MetadataSO = static_cast<f32>(metadataStartOffset);
		if (duration == kSoundLengthUnknown)
		{
			// We don't know how long the sound is, so ignore all start offsets

			m_PlaybackPredelay = requestedPredelay + metadataPredelay;
			if (m_ParentOverrides.BitFields.preDelayOverridesParent)
			{
				m_PlaybackPredelay = metadataPredelay;
			}		
			m_PlaybackStartOffset = 0; // TODO: can we do better than this? Probably.
			return;
		}

		metadataStartOffset = static_cast<u32>(f32MetadataSO * 0.01f * f32Duration);
	}

	if (IsRequestedStartOffsetPercentage())
	{
		const f32 f32requestedSO = static_cast<f32>(requestedStartOffset);
		const s32 duration = _ComputeDurationMsIncludingStartOffsetAndPredelay(NULL);
		const f32 f32Duration = static_cast<f32>(duration);		
		requestedStartOffset = static_cast<u32>(f32requestedSO * 0.01f * f32Duration);
	}

	if (requestedStartOffset >= (u32)metadataPredelay)
	{
		m_PlaybackPredelay = requestedPredelay;
		m_PlaybackStartOffset = metadataStartOffset + requestedStartOffset - metadataPredelay;
	}
	else
	{
		m_PlaybackPredelay = requestedPredelay + metadataPredelay - requestedStartOffset;
		m_PlaybackStartOffset = metadataStartOffset;
	}
	
	// And then override either of these things if they're set to OverrideParent - I don't think there's any more
	//  sensible way of combining them if you're using OverrideParent.
	// All Playback ones are in ms, so convert if we're a percentage.
	if (m_ParentOverrides.BitFields.preDelayOverridesParent)
	{
		m_PlaybackPredelay = metadataPredelay;
	}		
	if (m_ParentOverrides.BitFields.StartOffsetOverridesParent)
	{
		// All Playback ones are in ms, so convert if we're a percentage.
		if (IsStartOffsetPercentage())
		{
			const s32 duration = InvokeComputeDurationMsExcludingStartOffsetAndPredelay(NULL);
			const f32 f32Duration = static_cast<f32>(duration);
			const f32 f32MetadataSO = static_cast<f32>(metadataStartOffset);
			if (duration == kSoundLengthUnknown)
			{
				// We don't know how long the sound is, so ignore all start offsets
				m_PlaybackPredelay = requestedPredelay + metadataPredelay;
				if (m_ParentOverrides.BitFields.preDelayOverridesParent)
				{
					m_PlaybackPredelay = metadataPredelay;
				}		
				m_PlaybackStartOffset = 0; // TODO: can we do better than this? Probably.
				return;
			}

			metadataStartOffset = static_cast<u32>(f32MetadataSO * 0.01f * f32Duration);
		}
		m_PlaybackStartOffset = metadataStartOffset;
	}		
}

void audSound::SetPredelayState(s32 state)
{
	SoundAssert(state >= AUD_PREDELAY_NOT_STARTED && state <= AUD_PREDELAY_FINISHED);
	m_PredelayState = (u8)state;
	SoundAssert(static_cast<s32>(m_PredelayState) == state);
}

u32 audSound::GetPredelayFinishTime() const
{
	return m_TimeTriggered + m_PlaybackPredelay;
}

u32 audSound::GetPredelayRemainingMs() const
{
	return GetPredelayRemainingMs(g_AudioEngine.GetSoundManager().GetTimeInMilliseconds(GetTimerId()));
}

/////////////////////////////////
// Start Offset
/////////////////////////////////
void audSound::SetStartOffset(const s32 startOffset, const bool isPercentage)
{
	// We can only do this before the sound has started playing
	SoundAssertf(GetPlayState() == AUD_SOUND_DORMANT || GetPlayState() == AUD_SOUND_PREPARING, "[audSound::SetStartOffset] Playstate: %i", GetPlayState());
	m_RequestedStartOffset = startOffset;
	SetIsRequestedStartOffsetPercentage(isPercentage);
}

bool audSound::AreChildrenPlaying() const
{
	for (int i=0; i<audSound::kMaxChildren; i++)
	{
		const audSound *child = GetChildSound(i);
		if (child)
		{
			if (child->GetPlayState() == AUD_SOUND_PLAYING)
			{
				return true;
			}
		}
	}
	return false;
}

void audSound::ManagedAudioStopChildren()
{
	for (int i=0; i<audSound::kMaxChildren; i++)
	{
		audSound *child=GetChildSound(i);
		if (child)
		{
			if (child->GetPlayState() == AUD_SOUND_PLAYING)
			{
				child->SetIsToBeStopped();
				child->_Release();
			}
		}
	}
}

void audSound::_SearchForSoundsByType(u32 soundType, audSound** soundList, int maxFoundSounds, bool allowDynamicChildren)
{
	// We don't allow searching if we're able to remove ourselves from the hierarchy and are playing
	if (CanBeRemovedFromHierarchy() && GetPlayState()==AUD_SOUND_PLAYING)
	{
		Assert(0); // shouldn't be trying
		return;
	}

	if (!soundList)
	{
		Assert(0);
		return;
	}
	if (m_SoundTypeId == soundType)
	{
		int i=0;
		while (i<maxFoundSounds)
		{
			if (soundList[i]==NULL)
			{
				soundList[i]=this;
				break;
			}
			i++;
		}
		//Assert(i<maxFoundSounds);
	}

	if (allowDynamicChildren || !HasDynamicChildren())
	{
		for (int i=0; i<audSound::kMaxChildren; i++)
		{
			audSound *child = GetChildSound(i);
			if (child)
			{
				child->_SearchForSoundsByType(soundType, soundList, maxFoundSounds, allowDynamicChildren);
			}
		}
	}
}

void audSound::RequestChildSoundFromOffset(const audMetadataRef offset, const u32 childIndex, const audSoundScratchInitParams *scratchInitParams)
{
	// invalidate this child sound to catch any code expecting a synchronous instantiation
	if(offset.IsValid())
	{	
		SoundAssert(m_ChildSounds[childIndex] == 0xff);
		SoundAssert(childIndex < audSound::kMaxChildren);
		m_ChildSounds[childIndex] = 0xff;
	}
	m_SoundFlags.PendingChildInstantation = true;
	sm_Pool.RequestChildSound(m_InitParams.BucketId, sm_Pool.GetSoundSlotIndex(m_InitParams.BucketId, this), offset, m_InitParams, childIndex, scratchInitParams);
}

#if __SPU
void audSound::RequestPPUCallback()
{
	m_SoundFlags.PendingChildInstantation = true;
	sm_Pool.RequestChildSound(m_InitParams.BucketId, sm_Pool.GetSoundSlotIndex(m_InitParams.BucketId, this), audMetadataRef(), m_InitParams, 0, NULL);
}
#endif

void audSound::SetEntityAndEntityReference(audEntity *entity, audSound **entitySoundRef)
{
	m_Entity = entity;
	m_EntitySoundReference = entitySoundRef;
}

u32 audSound::ComputeVirtualisationGroupID(u32 defaultGroup)
{
	u32 id;
	
	if(ShouldVirtualiseAsGroup())
	{
		// We may get called multiple times (in fact, we always will for this to be any use!)
		// So cache our unique id the first time we're asked.
		if (m_CachedVirtualisationGroupID)
		{
			id = m_CachedVirtualisationGroupID;
		}
		else
		{
			m_CachedVirtualisationGroupID = static_cast<u16>(sm_Pool.GetUniqueId(m_InitParams.BucketId));
			id = m_CachedVirtualisationGroupID;
		}
	}
	else
	{
		id = defaultGroup;
	}

	if(m_ParentIndex != 0xff)
	{
		return _GetParent()->ComputeVirtualisationGroupID(id);
	}
	else
	{
		return id;
	}
}

audSound* audSound::GetTopLevelParent() const
{
	audSound* parent = _GetParent();
	audSound* lastParent = (audSound*)this;
	while (parent)
	{
		lastParent = parent;
		parent = lastParent->_GetParent();
	}
	return lastParent;
}

int audSound::SoundAssertFailed(const char *ASSERT_ONLY(msg), const char *ASSERT_ONLY(exp), const char *ASSERT_ONLY(file), const int ASSERT_ONLY(line)) const
{
#if __ASSERT
#if AUD_DEBUG_SOUNDS
#if __SPU
	audErrorf("SoundAssert(%s) failed: %s\nSound Stack:", (exp?exp:"?"), (msg?msg:""));
	ComputeHierarchyString(true);
	int ret = AssertFailed(msg, file, line);	
	return ret;
#else
	RAGE_TRACK(SoundAssertFailed);
	char *heirarchy = ComputeHierarchyString(true);
	char *message = (char*)g_AudioEngine.AllocateVirtual(strlen(heirarchy) + (msg?strlen(msg):0) + 128);

	sprintf(message, "SoundAssert(%s) failed:\nSound stack: %s\nMessage: %s\n", exp, heirarchy, (msg?msg:""));
	g_AudioEngine.FreeVirtual(heirarchy);
	int ret = diagAssertHelper(file, line, "%s", message);	
	g_AudioEngine.FreeVirtual(message);
	return ret;
#endif//__SOUND_TASK

#else // !AUD_DEBUG_SOUNDS
#if __DEV && __PPU
	audErrorf("SoundAssertFailed: %s bucket: %u slot %u", GetName(), m_InitParams.BucketId, sm_Pool.GetSoundSlotIndex(m_InitParams.BucketId, this));
#else
	audErrorf("SoundAssertFailed: Type: %u bucket: %u slot %u", m_SoundTypeId, m_InitParams.BucketId, sm_Pool.GetSoundSlotIndex(m_InitParams.BucketId, this));
#endif
#if __SPU
	return AssertFailed(exp, file, line);
#else
	return diagAssertHelper(file, line, "%s: %s", exp, msg);
#endif// __SPU
#endif // AUD_DEBUG_SOUNDS
#else//__ASSERT
	return 1;
#endif
}

int audSound::SoundAssertFailed(const char *ASSERT_ONLY(exp), const char *ASSERT_ONLY(file), const int ASSERT_ONLY(line), const char *ASSERT_ONLY(fmt),...) const
{
#if __ASSERT
#if AUD_DEBUG_SOUNDS
	va_list args;
	char buffer[512] = {0};
	if (fmt)
	{
		va_start(args,fmt);
		vformatf(buffer,sizeof(buffer),fmt,args);
		va_end(args);
	}

#if __SPU
	audErrorf("SoundAssert(%s) failed: %s\nSound Stack:", (exp?exp:"?"), buffer);
	ComputeHierarchyString(true);
	int ret = AssertFailed(buffer, file, line);	
	return ret;
#else
	RAGE_TRACK(SoundAssertFailed);
	char *heirarchy = ComputeHierarchyString(true);


	char *message = (char*)g_AudioEngine.AllocateVirtual(strlen(heirarchy) + strlen(buffer) + 128);

	sprintf(message, "SoundAssert(%s) failed:\nSound stack: %s\nMessage: %s\n", exp, heirarchy, buffer);
	g_AudioEngine.FreeVirtual(heirarchy);
	int ret = diagAssertHelper(file, line, "%s", buffer);	
	g_AudioEngine.FreeVirtual(message);
	return ret;
#endif//__SOUND_TASK

#else // !AUD_DEBUG_SOUNDS
#if __DEV && __PPU
	audErrorf("SoundAssertFailed: %s bucket: %u slot %u", GetName(), m_InitParams.BucketId, sm_Pool.GetSoundSlotIndex(m_InitParams.BucketId, this));
#else
	audErrorf("SoundAssertFailed: Type: %u bucket: %u slot %u", m_SoundTypeId, m_InitParams.BucketId, sm_Pool.GetSoundSlotIndex(m_InitParams.BucketId, this));
#endif
#if __SPU
	return AssertFailed(exp, file, line);
#else
# if AUD_DEBUG_SOUNDS
	return diagAssertHelper(file, line, "%s: %s", exp, buffer);
#else
	return diagAssertHelper(file, line, "%s: %s", exp, fmt);
#endif
#endif// __SPU
#endif // AUD_DEBUG_SOUNDS
#else//__ASSERT
	return 1;
#endif
}

#if AUD_DEBUG_SOUNDS
char *audSound::ComputeHierarchyString(bool multiline) const
{
#if __SPU
	return NULL;
#else
	RAGE_TRACK(HierarchyString);

	char *buf = (char*)g_AudioEngine.AllocateVirtual(2);
	size_t newSize;
	char *newBuf;

	if(multiline)
	{
		strcpy(buf, "\n");
	}
	else
	{
		strcpy(buf, "");
	}

	const audSound *ptr = this;

	while(ptr)
	{
		newSize = strlen(buf)+ strlen(ptr->GetName()) + 16;

		newBuf =  (char*)g_AudioEngine.AllocateVirtual(newSize);
		if(multiline)
		{
			sprintf(newBuf, "%s\n%s", ptr->GetName(), buf);
		}
		else
		{
			sprintf(newBuf, "%s\\%s", ptr->GetName(), buf);
		}
		g_AudioEngine.FreeVirtual(buf);
		buf = newBuf;

		ptr = ptr->_GetParent();
	}

	// strip off trailing character
	buf[strlen(buf)-1] = 0;
	return buf;
#endif // !__SPU
}
#endif // AUD_DEBUG_SOUNDS

void audSound::RemoveMyselfFromHierarchy()
{
	audSound* child = GetChildSound(0);
	SoundAssert(child);
	if(!sm_ShouldPreserveFullHierarchy)
	{
		// we cant remove this sound from the hierarchy if both it and its child have requested settings,
		// and currently we can only remove it if it has a parent and isn't referenced by game.
		if(!IsReferencedByGame() && m_ParentIndex != 0xff && (m_RequestedSettingsIndex==0xff || child->m_RequestedSettingsIndex==0xff) && CanBeRemovedFromHierarchy())
		{
			// if we have an attack set then need to wait until its finished to actually remove
			if(m_SimpleAttackTime == 0)
			{
				ActionHierarchyRemovalRequest();
			}
			else
			{
				SetIsWaitingToBeRemovedFromHierarchy();
			}		
		}
		else
		{
			PF_INCREMENT(UnremoveableSounds);
		}
	}
}

void audSound::ActionHierarchyRemovalRequest()
{
	PF_INCREMENT(RemovedSounds);

	// This code assumes that any sound being removed from the hierarchy has just one child. If we try to remove a sound with > 1
	// child then the additional children will be orphaned from the hierarchy and will never be destroyed.
	SoundAssert(GetNumChildSounds() == 1);
	audSound *child = GetChildSound(0);
	SoundAssert(child);
	child->m_ParentIndex = m_ParentIndex;
	
	// combine our metadata with the child sound - it seems fairly arbitrary whether this goes up or down however
	// going down removes any special casing if our parent is actually an audio entity
	child->CombineMetadata(m_MetadataVolume, m_MetadataPitch, m_MetadataPan, m_SimpleReleaseTime, m_MetadataLPFCutoff, m_MetadataHPFCutoff);
	
	// if we have a requested settings block then give it to our child
	if(m_RequestedSettingsIndex != 0xff)
	{
		child->m_RequestedSettingsIndex = m_RequestedSettingsIndex;
		m_RequestedSettingsIndex = 0xff;
	}
	_GetParent()->ReplaceChild(this, child);
	SetIsBeingRemovedFromHierarchy();
	delete this;	
}

void audSound::CombineMetadata(f32 volume, s32 pitch, s32 pan, u32 simpleReleaseTime, u32 lpfCutoff, u32 hpfCutoff)
{
	m_MetadataVolume += volume;
	m_MetadataPitch = static_cast<s16>(m_MetadataPitch + pitch);
	m_MetadataPan = static_cast<s16>(CombinePan(m_MetadataPan, pan));
	m_MetadataHPFCutoff = static_cast<u16>(Max<u32>(m_MetadataHPFCutoff, hpfCutoff));
	m_MetadataLPFCutoff = static_cast<u16>(Min<u32>(m_MetadataLPFCutoff, lpfCutoff));

	// If our simpleReleaseTime is set then we override our children - this should
	// have no no different effect.
	if (simpleReleaseTime != kNoSimpleRelease)
	{
		Assign(m_SimpleReleaseTime, simpleReleaseTime);
	}
}

void audSound::ReplaceChild(audSound *oldChild, audSound *newChild)
{
	SoundAssert(oldChild);
	SoundAssert(newChild);

	for(u32 i = 0 ; i < audSound::kMaxChildren; i++)
	{
		if(GetChildSound(i) == oldChild)
		{
			SetChildSound(i, newChild);
			break;
		}
	}
}

audWaveSlot *audSound::GetWaveSlot() const
{
	return audWaveSlot::GetWaveSlotFromIndex(m_InitParams.WaveSlotIndex);
}

#if RSG_BANK && RSG_SPU
extern const char *g_SoundNameTable;
char g_SoundName[128];
#endif

const char *audSound::GetName() const
{
#if __USEDEBUGAUDIO

#if RSG_SPU

	if(g_SoundNameTable)
	{
		ALIGNAS(16) char tempBuffer[128] ;

		u32 objectHeader = 0;
		dmaUnalignedGet(tempBuffer, sizeof(tempBuffer), &objectHeader, 4, (u32)m_BaseMetadata);
		
		// top 8 bits are ClassID, then three bits of ChunkId
		const u32 chunkId = (objectHeader&0xFFFFFF) >> 21;
		const u32 nto = objectHeader & 0x1FFFFF;

		if(chunkId != 0)
		{
			return "(chunk unavailable)";
		}
		else
		{
			const char *namePtr = g_SoundNameTable + nto;
			
			dmaUnalignedGet(tempBuffer, sizeof(tempBuffer), g_SoundName, 64, (u32)namePtr);
			g_SoundName[64] = 0;
			return g_SoundName;
		}
	}
	else
	{
		return "(unavailable)";
	}

#else
	if(m_BaseMetadata)
	{
		const audMetadataManager &metadataManager = SOUNDFACTORY.GetMetadataManager();
		if(metadataManager.AreObjectNamesAvailable())
		{
			return metadataManager.GetObjectNameFromNameTableOffset(m_BaseMetadata->NameTableOffset);
		}
	}
#endif

#endif // RSG_BANK
	return "(unknown)";
}

u32 audSound::GetCurrentPlayTime() const
{
	return GetCurrentPlayTime(g_AudioEngine.GetSoundManager().GetTimeInMilliseconds(m_InitParams.TimerId));
}

#if __SOUND_DEBUG_DRAW

void audSound::DebugDraw(audDebugDrawManager &drawMgr, const bool drawDynamicChildren, const bool drawHierarchyOnly) const
{	
	const u32 now = g_AudioEngine.GetSoundManager().GetTimeInMilliseconds(m_InitParams.TimerId);

	grcColor(Color32(255,255,255,255));
	char sectionHeader[128];
	formatf(sectionHeader, "%s (%s)%s", GetName(), SOUNDFACTORY.GetTypeName(GetSoundTypeID()), IsMuted() ? " (MUTED)" : "");
	drawMgr.PushSection(sectionHeader);

	if(!drawHierarchyOnly)
	{
		if(m_RequestedSettingsIndex != 0xff)
		{
			const Vec3V &pos = GetRequestedSettings()->GetPosition_AudioThread();
			const float dist = g_audEnvironment->ComputeDistanceToClosestVolumeListener(pos);
			formatf(sectionHeader, "Pos: (%.2f,%.2f,%.2f) distance from listener: %.2f", pos.GetXf(),pos.GetYf(),pos.GetZf(),dist);
			drawMgr.DrawLine(sectionHeader);
		}

		if(GetPredelayState() == AUD_PREDELAY_ONGOING)
		{
			const float predelayTimeLeft = GetPredelayRemainingMs(now) * 0.001f;
			formatf(sectionHeader, "- In predelay, %02.02f seconds remaining", predelayTimeLeft);
			drawMgr.DrawLine(sectionHeader);
		}
		else
		{
			const char *stateNames[] = {"Dormant","Preparing","Playing","AwaitingDeletion"};

			if(m_InitParams.IsAuditioning)
			{
				const audSoundCombineBuffer &cachedCombineBuffer = sm_CachedCombineBuffers[GetIndexFromSound(m_InitParams.BucketId, this)];

				formatf(sectionHeader, "- %s, vol: %.2fdB pitch: %d postSubmix: %.2fdB LPF %u HPF %u Pan %d", 
					stateNames[GetPlayState()], 
					cachedCombineBuffer.Volume,
					cachedCombineBuffer.Pitch,
					cachedCombineBuffer.PostSubmixVolumeAttenuation,
					cachedCombineBuffer.LPFCutoff,
					cachedCombineBuffer.HPFCutoff,
					cachedCombineBuffer.Pan
					);

				drawMgr.DrawLine(sectionHeader);
			}
			else
			{
				drawMgr.DrawLine(stateNames[GetPlayState()]);
			}
		}

		if(DebugDrawInternal(now, drawMgr, drawDynamicChildren))
		{
			if(drawDynamicChildren || !HasDynamicChildren())
			{
				for(s32 i = 0 ; i < kMaxChildren; i++)
				{
					if(HasChildSound(i))
					{
						GetChildSound(i)->DebugDraw(drawMgr, drawDynamicChildren, drawHierarchyOnly);
					}
				}
			}
		}
	}
	else
	{
		for(s32 i = 0 ; i < kMaxChildren; i++)
		{
			if(HasChildSound(i))
			{
				GetChildSound(i)->DebugDraw(drawMgr, drawDynamicChildren, drawHierarchyOnly);
			}
		}
	}

	drawMgr.PopSection();
}

#endif // __SOUND_DEBUG_DRAW
} // namespace rage
