//
// audioengine/environmentsound.cpp
//
// Copyright (C) 1999-2006 Rockstar Games.  All Rights Reserved.
//

#include "environmentsound.h"


#include "audiohardware/debug.h"
#include "audiohardware/device.h"
#include "audiohardware/driverdefs.h"
#include "audiohardware/driverutil.h"
#include "audiohardware/driver.h"
#include "audiohardware/mixer.h"
#include "audiohardware/pcmsource.h"
#include "audiohardware/pcmsource_interface.h"
#include "audiohardware/syncsource.h"
#include "audioengine/category.h"
#include "audioengine/categorymanager.h"
#include "audioengine/dynamicmixmgr.h"
#include "audioengine/engine.h"
#include "audioengine/engineutil.h"
#include "audioengine/environment.h"

#include "diag/output.h"
#include "system/timemgr.h"
#include "system/param.h"
#include "profile/profiler.h"
#include "grcore/debugdraw.h"

#if __SPU
#include "audioengine/dynamicmixmgr.cpp"
#endif

namespace rage {

#if !__SPU
	PARAM(nobasecategory, "[RAGE Audio] Assert if any sounds are in the base category");
	PARAM(warnonbadcategories, "[RAGE Audio] Warn if sounds are playing from invalid categories");
#endif // __SPU

audEnvironment *g_audEnvironment = NULL;

PF_PAGE(EnvironmentSoundPage, "audEnvironmentSound");
PF_GROUP(EnvironmentSoundGroup);
PF_LINK(EnvironmentSoundPage, EnvironmentSoundGroup);

PF_TIMER(AudioUpdate, EnvironmentSoundGroup);
PF_TIMER(AudioUpdateVoice, EnvironmentSoundGroup);
PF_TIMER(CalcEnvMetrics, EnvironmentSoundGroup);

#ifndef __AUD_ENVIRONMENT_UPDATE

AUD_IMPLEMENT_STATIC_WRAPPERS(audEnvironmentSound);

#if !__SPU
Sound g_BaseEnvironmentMetadata;
#else
extern audMixGroup* g_MixGroupPoolMem;
#endif

bool audEnvironmentSound::sm_EnableMixGroupPitchFrequencyScaling = false;

#if __BANK
atArray<audEnvironmentSound::InvalidCategorySound> audEnvironmentSound::sm_InvalidCategorySoundHistory;
bool audEnvironmentSound::sm_RecordInvalidSoundHistory = false;
bool audEnvironmentSound::sm_DisableMixgroups = false;
#endif

audEnvironmentSound::~audEnvironmentSound()
{
	if(m_MixGroupPrevIndex != -1)
	{
		audMixGroup::RemoveMixGroupReference(m_MixGroupPrevIndex, audMixGroup::SOUND_REF);
	}
	if(m_MixGroupIndex != -1)
	{
		audMixGroup::RemoveMixGroupReference(m_MixGroupIndex, audMixGroup::SOUND_REF);
	}
	if(m_MetricsSlot != 0xff)
	{
		sm_Pool.InvalidateEnvironmentSound(m_InitParams.BucketId, m_MetricsSlot);
		m_MetricsSlot = 0xff;
	}
	FreeVoice();
	audMixerSyncManager::Get()->Release(m_SyncMasterId);
	audMixerSyncManager::Get()->Release(m_SyncSlaveId);
}

void audEnvironmentSound::FreeVoice()
{
	// We synchronously stop any associated voice, and we won't be updated again
	if(m_VoiceId != 0xff)
	{
		audVoiceSettings *voiceSettings = GetVoiceSettings();
		voiceSettings->Flags.ShouldStop = true;

		// NOTE: any voice free'd by the voice manager as a result of a phys voice stopping will be marked as playing virtually by the time
		// we get here, however environment sound frees voices directly whether they are playing physically or not, which is why we set the
		// ShouldStop flag here and wait until the voice is playing virtually before we actually free it
		if(voiceSettings->State == AUD_VOICE_PLAYING_VIRTUALLY)
		{
			sm_Pool.FreeVoice(m_InitParams.BucketId, m_VoiceId);
		}

		m_VoiceId = 0xff;
	}
	if(m_PcmSourceId != -1)
	{
		if(m_IsControllingPcmSource && m_HasStartedPcmSource)
		{
			audPcmSourceInterface::StopAndRelease(m_PcmSourceId);
			m_HasStartedPcmSource = false;
		}
		else
		{
			audPcmSourceInterface::Release(m_PcmSourceId);
		}
		m_PcmSourceId = -1;
	}
}

s32 audEnvironmentSound::ComputeDurationMsExcludingStartOffsetAndPredelay(bool* isLooping)
{
	if(m_IsControllingSubmix)
	{
		if(isLooping)
		{
			*isLooping = true;
		}
		return kSoundLengthUnknown;
	}
	else
	{
		if(isLooping)
		{
			*isLooping = m_IsAssetLooping;
		}
		return m_AssetLengthMs;
	}
}

#if !__SPU

void audEnvironmentSound::InitClass()
{
	g_BaseEnvironmentMetadata.ClassId = EnvironmentSound::TYPE_ID;
	g_BaseEnvironmentMetadata.SpeakerMask.Value = 0;
	g_BaseEnvironmentMetadata.ParentOverrides.Value = 0U;
	g_audEnvironment = &g_AudioEngine.GetEnvironment();
	Assert(g_audEnvironment);
}

audEnvironmentSound::audEnvironmentSound()
{
	m_FrequencyScalingFactor = 1.f;
	
	m_LastUpdateFrame = 0;
	m_PcmSourceId = -1;
	m_PcmSourceChannelId = 0;
	m_DopplerFrequencyScalingFactor = 1.0f;
	m_VoiceId = 0xff;
		
	m_ShouldPlayPhysically = false;
	m_IsControllingPcmSource = true;
	m_MetricsSlot = 0xff;
	m_MixGroupIndex = -1;
	m_MixGroupPrevIndex = -1;
	m_MixGroupInstanceId = ~0U;
	m_MixGroupPrevInstanceId = ~0U;

	m_HeadroomScalingLinear = 1.f;

	m_VoiceHasBeenUpdated = false;
	m_IsControllingSubmix = false;
	m_HasStartedPcmSource = false;

	m_IsAssetLooping = false;
	m_AssetLengthMs = kSoundLengthUnknown;
	m_InvertPhase = false;

	m_DefaultPeakLevel = 0;

	m_SyncMasterId = m_SyncSlaveId = audMixerSyncManager::InvalidId;
}

#if __BANK
void audEnvironmentSound::DebugDrawInvalidCategorySounds()
{
	// Draw a list of invalid category sounds on-screen to make them easily identifiable for designers running bankrelease
	if(sm_InvalidCategorySoundHistory.GetCount() > 0)
	{	
		const audMetadataManager &metadataManager = SOUNDFACTORY.GetMetadataManager();

		if(metadataManager.AreObjectNamesAvailable())
		{
			char tempString[256];
			f32 xCoord = 0.1f;
			f32 yCoord = 0.1f;

			formatf(tempString, "WARNING!");
			grcDebugDraw::Text(Vector2(xCoord, yCoord),  g_AudioEngine.GetSoundManager().GetGameUpdateIndex() % 2 == 0 ? Color32(255,255,255) : Color32(255,0,0), tempString);
			yCoord += 0.02f;

			formatf(tempString, "Sounds failed to play due to missing categories:");
			grcDebugDraw::Text(Vector2(xCoord, yCoord),  Color32(255,255,255), tempString);
			yCoord += 0.02f;
			xCoord += 0.02f;

			for(s32 loop = 0; loop < sm_InvalidCategorySoundHistory.GetCount(); )
			{				
				if(g_AudioEngine.GetSoundManager().GetTimeInMilliseconds(0) - sm_InvalidCategorySoundHistory[loop].timeTriggered < 15000)
				{
					formatf(tempString, "%s -> %s", metadataManager.GetObjectNameFromNameTableOffset(sm_InvalidCategorySoundHistory[loop].parentNTO), metadataManager.GetObjectNameFromNameTableOffset(sm_InvalidCategorySoundHistory[loop].soundNTO));
					grcDebugDraw::Text(Vector2(xCoord, yCoord), Color32(255,255,255), tempString);
					yCoord += 0.02f;
					loop++;
				}
				else
				{
					sm_InvalidCategorySoundHistory.Delete(loop);
				}
			}	
		}			
	}	
}
#endif


bool audEnvironmentSound::Init(const void *metadata, const audSoundInternalInitParams* initParams, audSoundScratchInitParams *scratchInitParams)
{
	bool hasMetadata = (metadata != NULL);
	if(!hasMetadata)
	{
		metadata = &g_BaseEnvironmentMetadata;
	}

	if(!audSound::Init(metadata, initParams, scratchInitParams))
	{
		return false;
	}

	m_ShouldAttenuateOverDistance = (u8)scratchInitParams->shouldAttenuateOverDistance;
	m_ShouldApplyEnvironmentalEffects = (u8)scratchInitParams->shouldApplyEnvironmentalEffects;
	m_InvertPhase = scratchInitParams->shouldInvertPhase;

	m_ShouldStopWhenVirtual = scratchInitParams->shouldStopWhenVirtual;
	m_IsUncancellable = scratchInitParams->isUncancellable;

	m_VolumeCurveScale = scratchInitParams->volCurveScale;
	SoundAssert(m_VolumeCurveScale > SMALL_FLOAT);
	
	m_VolumeCurveId = (u8)g_audEnvironment->FindVolumeCurveId(scratchInitParams->volCurveHash);
	if(m_VolumeCurveId > g_MaxCustomVolumeCurves)
	{
		SoundAssert(m_VolumeCurveId <= g_MaxCustomVolumeCurves);
		m_VolumeCurveId = 0;
	}

	m_VolumeCurvePlateau = scratchInitParams->volCurvePlateau;
	
	// Use the metadata specified channel id as a default
	if(hasMetadata)
	{
		Assign(m_PcmSourceChannelId, ((EnvironmentSound*)GetMetadata())->ChannelId);
	}
	else
	{
		// Default to channel 0
		m_PcmSourceChannelId = 0;
	}

	m_FrequencySmoother.Init(1000.f);

	m_EffectRoute = scratchInitParams->effectRoute;
	SoundAssert(m_EffectRoute == scratchInitParams->effectRoute);
	if(m_EffectRoute == EFFECT_ROUTE_AS_PARENT)
	{
		m_EffectRoute = EFFECT_ROUTE_POSITIONED;
	}

	// Set up our category
	m_CategoryIndex = scratchInitParams->categoryIndex;
	if(m_CategoryIndex == -1)
	{
		// Default to BASE if we've not been set up with anything
		m_CategoryIndex = 0;
	}

	if(m_CategoryIndex == 0 && !initParams->IsAuditioning)
	{
#if __USEDEBUGAUDIO
		const audSound *parent = GetTopLevelParent();
		static const u32 ignoreList[] = {
			0
		};

		const u32 parentSoundNameHash = atStringHash(parent->GetName());
		bool ignored = false;
		const u32 numIgnoredSounds = sizeof(ignoreList) / sizeof(ignoreList[0]);
		for(u32 i = 0; i < numIgnoredSounds; i++)
		{
			if(ignoreList[i] == parentSoundNameHash)
			{
				ignored =true;
				break;
			}
		}

		DIAG_CONTEXT_MESSAGE(parent->GetName());
		audAssertf(ignored, "Sound with top level parent \'%s\' (%s) is trying to play in the BASE category; will be rejected.", parent->GetName(), _GetParent()->GetName());

		if(!ignored && sm_RecordInvalidSoundHistory)
		{			
			InvalidCategorySound invalidCategorySound;
			invalidCategorySound.parentNTO = parent->GetNameTableOffset();
			invalidCategorySound.soundNTO = _GetParent()->GetNameTableOffset();
			invalidCategorySound.timeTriggered = g_AudioEngine.GetSoundManager().GetTimeInMilliseconds(0);
			sm_InvalidCategorySoundHistory.PushAndGrow(invalidCategorySound);
		}		
#endif
		// Don't allow sounds to play from the BASE category
		return false;
	}
	
	SoundAssertMsg(m_CategoryIndex >= 0 && m_CategoryIndex < (s32)g_AudioEngine.GetCategoryManager().GetNumberOfCategories(), "Unable to set category for environment sound");

	const audCategory *category = audCategoryManager::GetCategoryFromIndex(m_CategoryIndex);
	Assign(m_HPFDistanceCuveId, g_audEnvironment->FindHPFCurveId(category->GetHPFDistanceCurve()));
	Assign(m_LPFDistanceCuveId, g_audEnvironment->FindLPFCurveId(category->GetLPFDistanceCurve()));

	m_DisableRearAttenuation = g_AudioEngine.GetCategoryManager().GetCategoryFromIndex(m_CategoryIndex)->GetRearAttenuationDisabled();

	m_ShouldMuteOnUserMusic = (scratchInitParams->shouldMuteOnUserMusic || g_AudioEngine.GetCategoryManager().GetCategoryFromIndex(m_CategoryIndex)->GetMuteOnUserMusic());

	Assign(m_SpeakerMask, scratchInitParams->speakerMask);	
	m_DopplerFactor = scratchInitParams->dopplerFactor;

	audAssert(FPIsFinite(m_DopplerFactor));

	m_ShouldPlayPhysically = scratchInitParams->shouldPlayPhysically;

	if(scratchInitParams->sourceEffectSubmixId != -1)
	{
		InitSubmix(scratchInitParams->sourceEffectSubmixId);
		m_IsControllingPcmSource = false;
		m_IsControllingSubmix = true;
	}
	else if(scratchInitParams->shadowPcmSourceId != -1)
	{
		InitEnvironment(scratchInitParams->shadowPcmSourceId, 0.f);
		m_IsControllingPcmSource = false;
	}

	// Code can override the metadata-set channel id
	if(scratchInitParams->pcmSourceChannelId != -1)
	{
		Assign(m_PcmSourceChannelId, scratchInitParams->pcmSourceChannelId);
	}

	return true;
}
#endif // !__SPU

void audEnvironmentSound::InitEnvironment(const s32 pcmSourceId, const float assetHeadroom, const s32 assetLengthMs, const bool isAssetLooping, const u32 defaultPeakLevel, const u32 pcmSourceCost)
{
	m_HeadroomScalingLinear = audDriverUtil::ComputeLinearVolumeFromDb(-assetHeadroom);
	m_AssetLengthMs = assetLengthMs;
	m_IsAssetLooping = isAssetLooping;

	Assign(m_DefaultPeakLevel, defaultPeakLevel);
	Assign(m_PcmSourceCost, pcmSourceCost);
	audAssertf(m_PcmSourceId == -1, "The PCM Generator ID shouldn't have been specified in init params if we have a parent sound that gives us one.");
	Assign(m_PcmSourceId, pcmSourceId);
	audPcmSourceInterface::AddRef(pcmSourceId);
}

void audEnvironmentSound::InitSubmix(const s32 submixId)
{
	m_SubmixId = submixId;
	m_IsControllingSubmix = true;
}

void audEnvironmentSound::AudioPlay(u32 timeInMs, audSoundCombineBuffer &combineBuffer)
{	
	// Important to call AudioUpdate() now, otherwise we introduce an audio frame delay in kicking the sound off.
	AudioUpdate(timeInMs, combineBuffer);
}

bool audEnvironmentSound::AudioUpdate(u32 UNUSED_PARAM(timeInMs), audSoundCombineBuffer &combineBuffer)
{
	PF_FUNC(AudioUpdate);

	bool stillPlaying = false;

	
	if(!m_IsControllingSubmix && m_PcmSourceId==-1)
	{
		audErrorf("EnvironmentSound played before InitEnvironment() called: bucket: %u, slot: %u", m_InitParams.BucketId, sm_Pool.GetSoundSlotIndex(m_InitParams.BucketId, this));
		return false;
	}

	SoundAssert(GetPlayState() == AUD_SOUND_PLAYING);

	bool shouldStartVoice = false;
	
	if (m_VoiceId == 0xff)
	{
		// We haven't got a Voice yet, so we need to rustle one up and kick it off
		m_VoiceId = sm_Pool.AllocateVoice(m_InitParams.BucketId);
		if(m_VoiceId != 0xff)
		{
			audVoiceSettings voiceSettings;

			Assign(voiceSettings.PcmSourceId, m_PcmSourceId);
			Assert(m_IsControllingSubmix || m_PcmSourceId != -1);
			voiceSettings.VirtualisationGroupId = ComputeVirtualisationGroupID();
			voiceSettings.Flags.ShouldMuteOnUserMusic = m_ShouldMuteOnUserMusic;
			voiceSettings.Flags.ShouldPlayPhysically = m_ShouldPlayPhysically;
			voiceSettings.Flags.IsUncancellable = m_IsUncancellable;
			voiceSettings.Flags.ShouldStopWhenVirtual = m_ShouldStopWhenVirtual;
			voiceSettings.BucketId = m_InitParams.BucketId;
			SoundAssert(m_PcmSourceChannelId >= 0 && m_PcmSourceChannelId < 4);
			Assign(voiceSettings.PcmSourceChannelId, m_PcmSourceChannelId);
			Assign(voiceSettings.CostEstimate, m_PcmSourceCost);
			*GetVoiceSettings() = voiceSettings;

			if(m_IsControllingPcmSource)
			{
				audPcmSourceInterface::SetParam(m_PcmSourceId, audPcmSource::Params::StartOffsetMs, GetPlaybackStartOffset());
			}

			shouldStartVoice = true;
		}			
		else
		{
			audWarningf("Failed to AllocateVoice()");
		}
	}

	// We've got a Voice, and need to update params, etc
	if (m_VoiceId != 0xff)
	{
		PcmSourceState pcmSourceState;

		// If this isn't the first frame (ie shouldStartVoice is false and m_VoiceHasBeenUpdated is true) then start checking for StopWhenVirtual behaviour
		const bool shouldStop = (m_ShouldStopWhenVirtual && m_VoiceHasBeenUpdated && !shouldStartVoice && GetVoiceSettings()->State == AUD_VOICE_PLAYING_VIRTUALLY);

		if(!shouldStop && 
			(m_IsControllingSubmix || shouldStartVoice || (audPcmSourceInterface::GetState(pcmSourceState, m_PcmSourceId) && pcmSourceState.PlaytimeSamples != ~0U)))
		{
			// We're still playing it, so set params, service, and carry on
			stillPlaying = true;
			if (GetPlayState() != AUD_SOUND_PLAYING)
			{
				stillPlaying = false;
			}

			Assign(GetVoiceSettings()->CostEstimate, m_PcmSourceCost);

#if __SPU
			// add to post update list for deferred processing in the environment update
			Assert(m_MetricsSlot == 0xff);
			m_MetricsSlot = audSoundPool::RegisterEnvironmentSound(combineBuffer, audSound::GetIndexFromSound(m_InitParams.BucketId, this));
#else
			AudioUpdateVoice(combineBuffer);
#endif
			if(shouldStartVoice)
			{
				// tell the PCM generator to start playing (virtually; the voice manager will deal with giving it a physical voice
				// if it deserves one)
				if(m_IsControllingPcmSource)
				{
					audPcmSourceInterface::Start(m_PcmSourceId);
					m_HasStartedPcmSource = true;
				}

				stillPlaying = true;
			}
		}
		else
		{
			// The wave has finished playing, so dump our voice, and mark us as no longer playing. 
			FreeVoice();
			stillPlaying = false;
		}
	} // if (m_Voice)

	return stillPlaying;
}

void audEnvironmentSound::Pause(u32 UNUSED_PARAM(timeInMs))
{
	SetFrequencyScalingFactor(0.f);
}

void audEnvironmentSound::AudioKill()
{
	// We synchronously stop any associated voice, and we won't be updated again
	if (m_VoiceId != 0xff)
	{
		FreeVoice();
	}
	if(m_PcmSourceId != -1)
	{
		if(m_IsControllingPcmSource && m_HasStartedPcmSource)
		{		
			audPcmSourceInterface::StopAndRelease(m_PcmSourceId);
			m_HasStartedPcmSource = false;
		}
		else
		{
			audPcmSourceInterface::Release(m_PcmSourceId);
		}
		m_PcmSourceId = -1;
	}
}

#endif // !defined(__AUD_ENVIRONMENT_UPDATE)

#if !__SPU || defined(__AUD_ENVIRONMENT_UPDATE)

#if __SPU
extern u32 *g_MixGroupInstanceIdsEa;
extern bool g_DebugSoundUpdateJob_SPU;
#endif

BANK_ONLY(extern bool g_AuditioningOverridesSolo);

void audEnvironmentSound::AudioUpdateVoice(audSoundCombineBuffer &combineBuffer)
{
	PF_FUNC(AudioUpdateVoice);
#if !__SPU
	SoundAssertMsg(m_CategoryIndex >= 0 && m_CategoryIndex < (s32)g_AudioEngine.GetCategoryManager().GetNumberOfCategories(), "Invalid CategoryId in AudioUpdateVoice()");
#endif

	bool updated = false;
	s32 pan = combineBuffer.Pan;

	
	audRequestedSettings *topLevelRequestedSettings = ((audRequestedSettings*)sm_Pool.GetRequestedSettingsSlot(m_InitParams.BucketId, GetTopLevelRequestedSettingsIndex()));

	//  MdS 18/10/06 we now get our position out of requested settings, not the combine buffer
	const Vec3V &position = topLevelRequestedSettings->GetPosition_AudioThread();

	// pan value means something different if we're using position-relative panning
	f32 positionRelativePan = 0.0f;
	bool usePositionRelativePan = topLevelRequestedSettings->ShouldPositionRelativePan();
	if (usePositionRelativePan)
	{
		if (pan < 0)
		{
			positionRelativePan = 0.0f;
			pan = -1;
		}
		else
		{
			// Can't just scale the pan, as want 350 degs to scale 'down' to 0 (/360) degs.
			// Not totally clear how we want to scale pans near 180 - tend to 0, or tend to 180?
			// I only have use-cases for this at <+-45 degs, so deal with that well, and assume ~180->0 as well
			s32 aroundZeroPan = ((pan + 180) % 360) - 180;
			positionRelativePan = (f32)aroundZeroPan * topLevelRequestedSettings->GetPositionRelativePanDamping();
			positionRelativePan = Selectf(positionRelativePan, positionRelativePan, positionRelativePan+360.0f);
			pan = -1;
		}
	}

	const tRequestedSettings &requestedSettings = topLevelRequestedSettings->GetSettings_AudioThread();
	
	// See if we need to calculate doppler again
	if (m_LastUpdateFrame != requestedSettings.UpdateFrame)
	{
		updated = true;
		m_LastUpdateFrame = requestedSettings.UpdateFrame;
	}

	// See if we're to distance attenuate / apply environmental effects
	bool shouldApplyEnvironmentalEffects = true;
	bool shouldAttenuateOverDistance = true;
	if((m_SpeakerMask != 0) || (requestedSettings.SpeakerMask != 0) || (pan >= 0))
	{
		if(m_ShouldAttenuateOverDistance != AUD_TRISTATE_TRUE && topLevelRequestedSettings->GetShouldAttenuateOverDistance() != AUD_TRISTATE_TRUE)
		{
			shouldAttenuateOverDistance = false;
		}
	}
	else
	{
		// allow distance atten to be disabled for 3d sounds
		if(m_ShouldAttenuateOverDistance == AUD_TRISTATE_FALSE || topLevelRequestedSettings->GetShouldAttenuateOverDistance() == AUD_TRISTATE_FALSE)
		{
			shouldAttenuateOverDistance = false;
		}
	}

	if(m_ShouldApplyEnvironmentalEffects == AUD_TRISTATE_UNSPECIFIED)
	{
		shouldApplyEnvironmentalEffects = shouldAttenuateOverDistance;
	}
	else
	{
		shouldApplyEnvironmentalEffects = (m_ShouldApplyEnvironmentalEffects == AUD_TRISTATE_TRUE);
	}

	// Work out our pre-submix volume, that's the same for all listeners
	f32 vol = combineBuffer.Volume;
	// Work out our post-submix volume attenuation, that's the same for all listeners
	f32 postSubmixVolumeAttenuation = combineBuffer.PostSubmixVolumeAttenuation;

	if(audEnvironment::IsDirectMonoEffectRoute(m_EffectRoute))
	{
		shouldAttenuateOverDistance = false;
		if(m_EffectRoute >= EFFECT_ROUTE_CONVERSATION_SPEECH_MIN && m_EffectRoute <= EFFECT_ROUTE_CONVERSATION_SPEECH_MAX)
		{
			shouldApplyEnvironmentalEffects = false;
		}
		else
		{
			shouldApplyEnvironmentalEffects = true;
		}
		pan = 0;
		postSubmixVolumeAttenuation = 0.0f;
	}

	// Now let's clamp things to sensible limits, so we don't explode
	if (!FPIsFinite(vol))
	{
		vol = g_SilenceVolume;
	}


	bool invalidPosition = false;
	if(!IsFinite(MagSquared(position)).Getb())
	{
		invalidPosition = true;
		vol = g_SilenceVolume;
		postSubmixVolumeAttenuation = g_SilenceVolume;
	}

#if RSG_BANK
	if(combineBuffer.IsMuted || (!combineBuffer.IsSoloed && SOUNDFACTORY.GetMetadataManager().AreAnyObjectsSoloed() && !(m_InitParams.IsAuditioning && g_AuditioningOverridesSolo)))
	{
		// Apply mute/solo
		vol = g_SilenceVolume;
	}
#endif

	///////////////////////////
	// Set up our input metrics

	audEnvironmentSoundMetrics metrics;

	metrics.categoryIndex = m_CategoryIndex;
	// We need 2dB headroom on the pre-submix volume to allow for source waves mastered to 0dB.  This is compensated for in 
	// ComputeVoiceRoutes, and ensures that metrics.volume shouldn't go above 0.
	const f32 headroomCompensationdB = -2.f;
	metrics.volume.SetDb(vol + headroomCompensationdB);
	metrics.requestedVolume.SetDb(requestedSettings.Volume);
	metrics.postSubmixVolumeAttenuation.SetDb(postSubmixVolumeAttenuation);
	// Grab a full copy of our occlusion metric from the reqSetForPosition
	metrics.environmentGameMetric = topLevelRequestedSettings->GetEnvironmentGameMetric();
	metrics.LPFCutoff = combineBuffer.LPFCutoff;
	metrics.HPFCutoff = combineBuffer.HPFCutoff;

	if(!invalidPosition)
	{
		metrics.position = position;
	}
	else
	{
		metrics.position.ZeroComponents();
	}
	
	topLevelRequestedSettings->GetVelocity(metrics.velocity);
	Assign(metrics.pan, pan);
	metrics.positionRelativePan = positionRelativePan;
	metrics.usePositionRelativePan = usePositionRelativePan;
	// speaker mask combines with requested speaker mask
	metrics.speakerMask = m_SpeakerMask | requestedSettings.SpeakerMask;
	metrics.hasQuadSpeakerLevels = requestedSettings.HasQuadSpeakerLevels;
	metrics.shouldAttenuateOverDistance = shouldAttenuateOverDistance;
	metrics.shouldApplyEnvironmentalEffects = shouldApplyEnvironmentalEffects;
	metrics.shouldInvertPhase = m_InvertPhase;
	metrics.positionHasUpdated = updated;
	// doppler factor combines with requested doppler factor
	metrics.dopplerFactor = m_DopplerFactor * requestedSettings.DopplerFactor;

	SoundAssert(requestedSettings.VolumeCurveScale > SMALL_FLOAT);
	SoundAssert(combineBuffer.VolumeCurveScale.GetFloat32_FromFloat16() > SMALL_FLOAT);
	const float volumeCurveScale = m_VolumeCurveScale * requestedSettings.VolumeCurveScale * combineBuffer.VolumeCurveScale.GetFloat32_FromFloat16();
	SoundAssert(volumeCurveScale > SMALL_FLOAT);
	metrics.volumeCurveScale = volumeCurveScale;

	metrics.volumeCurveId = m_VolumeCurveId;
	metrics.volumeCurvePlateau = m_VolumeCurvePlateau;
	metrics.hpfCurveId = m_HPFDistanceCuveId;
	metrics.lpfCurveId = m_LPFDistanceCuveId;
	const f32 oneOver255 = (1.f/255.f);
	metrics.environmentalLoudness = requestedSettings.EnvironmentalLoudness * oneOver255;
	metrics.listenerMask = requestedSettings.ListenerMask;

	// Populate routing metric
	// TODO: move these into audRequestedSettings::RoutingMetric
	metrics.routingMetric.sourceEffectDryMix = requestedSettings.SourceEffectDryMix;
	metrics.routingMetric.sourceEffectWetMix = requestedSettings.SourceEffectWetMix;
	metrics.routingMetric.effectRoute = m_EffectRoute;
	metrics.routingMetric.sourceEffectSubmixId = -1;

	metrics.minSmallSend = combineBuffer.SmallReverbSend * 0.01f;
	metrics.minMediumSend = combineBuffer.MediumReverbSend * 0.01f;
	metrics.minLargeSend = combineBuffer.LargeReverbSend * 0.01f;

	metrics.rearAttenuationDisabled = m_DisableRearAttenuation || topLevelRequestedSettings->GetShouldDisableRearAttenuation() == AUD_TRISTATE_TRUE;
	metrics.rearFilteringDisabled = topLevelRequestedSettings->GetShouldDisableRearFiltering() == AUD_TRISTATE_TRUE;

	const f32 frequencySmoothingRate = topLevelRequestedSettings->GetFrequencySmoothingRate() / 1000.f;
	m_FrequencySmoother.SetRate(frequencySmoothingRate);

	if(m_PcmSourceId != -1)
	{
		const u32 syncMasterId = GetCurrentSyncMasterId();
		if(m_SyncMasterId != syncMasterId)
		{			
			audMixerSyncManager::Get()->Release(m_SyncMasterId);			
			audMixerSyncManager::Get()->AddOwnerRef(syncMasterId);
			Assign(m_SyncMasterId, syncMasterId);
			audPcmSourceInterface::SetParam(m_PcmSourceId, audPcmSource::Params::SyncMasterId, syncMasterId);
		}
		const u32 syncSlaveId = GetCurrentSyncSlaveId();
		if(m_SyncSlaveId != syncSlaveId)
		{
			audMixerSyncManager::Get()->Release(m_SyncSlaveId);
			audMixerSyncManager::Get()->AddOwnerRef(syncSlaveId);
			Assign(m_SyncSlaveId, syncSlaveId);
			audPcmSourceInterface::SetParam(m_PcmSourceId, audPcmSource::Params::SyncSlaveId, syncSlaveId);
		}
	}	


	//////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// PostUpdate

	audVoiceSettings* voiceSettings = GetVoiceSettings();

	//SPU_ONLY(Assert(m_MetricsSlot != 0xff));
	// add category values into our metrics
	if(!audEnvironment::IsDirectMonoEffectRoute(m_EffectRoute))
	{
		metrics.postSubmixVolumeAttenuation.ScaleLinear(audDriverUtil::ComputeLinearVolumeFromDb(g_ResolvedCategorySettings[m_CategoryIndex].Volume.GetFloat32_FromFloat16()));
	}

	metrics.LPFCutoff = Min(metrics.LPFCutoff, g_ResolvedCategorySettings[m_CategoryIndex].LPFCutoff);
	metrics.HPFCutoff = Max(metrics.HPFCutoff, g_ResolvedCategorySettings[m_CategoryIndex].HPFCutoff);

	//Update the mix group this sound is currently assigns to (if any) and handle reference increment/decrement
	UpdateMixGroup(metrics.environmentGameMetric.GetMixGroup(),m_MixGroupInstanceId,m_MixGroupIndex);


	f32 dynamicFrequencyScalingFactor = 1.f;

	//Apply dynamic mixing
	if(m_MixGroupInstanceId != ~0U BANK_ONLY(&& !sm_DisableMixgroups))
	{
		if(!audEnvironment::IsDirectMonoEffectRoute(m_EffectRoute))
		{
			if(metrics.environmentGameMetric.GetPrevMixGroup()!= -1)
			{
				UpdateMixGroup(metrics.environmentGameMetric.GetPrevMixGroup(),m_MixGroupPrevInstanceId,m_MixGroupPrevIndex);
				if(m_MixGroupPrevInstanceId != ~0U)
				{
					s16 prevICat = -1;
#if __SPU
					const s32 tag = 5;
					prevICat = static_cast<s16>(sysDmaGetUInt16((uint64_t)(g_MixGroupPoolMem[m_MixGroupPrevInstanceId].GetMap() + m_CategoryIndex),tag));
#else
					prevICat =  g_MixGroupPoolMem[m_MixGroupPrevInstanceId].GetMap()[m_CategoryIndex];
#endif
					if(prevICat != -1)
					{
						f32 vol = Lerp((f32)((f32)metrics.environmentGameMetric.GetMixGroupApplyValue()/255.f)
							,audDriverUtil::ComputeLinearVolumeFromDb(g_ResolvedCategorySettings[prevICat].Volume.GetFloat32_FromFloat16())
							,1.f);
						metrics.postSubmixVolumeAttenuation.ScaleLinear(vol);	
					}
				}
			}
		}
		
		s16 iCat = -1;
#if __SPU
		const s32 tag = 5;
		iCat = static_cast<s16>(sysDmaGetUInt16((uint64_t)(g_MixGroupPoolMem[m_MixGroupInstanceId].GetMap() + m_CategoryIndex),tag));
#else
		iCat = g_MixGroupPoolMem[m_MixGroupInstanceId].GetMap()[m_CategoryIndex];
#endif
		if(iCat != -1) //if the index in the map is -1, the category simply maps back on to itself and has no dynamic component
		{
			if(!audEnvironment::IsDirectMonoEffectRoute(m_EffectRoute))
			{
				f32 vol = Lerp((f32)((f32)metrics.environmentGameMetric.GetMixGroupApplyValue()/255.f)
					,1.f
					,audDriverUtil::ComputeLinearVolumeFromDb(g_ResolvedCategorySettings[iCat].Volume.GetFloat32_FromFloat16()));

				metrics.postSubmixVolumeAttenuation.ScaleLinear(vol);	

				f32 metricsVolScale = metrics.volumeCurveScale;
				metricsVolScale *= g_ResolvedCategorySettings[iCat].DistanceRollOffScale.GetFloat32_FromFloat16();
				metrics.volumeCurveScale = metricsVolScale;
			}
			
			// See B*3717921 for the original reason for this. The fix (ie. when sm_EnableMixGroupPitchFrequencyScaling = true) is the
			// correct behavior as frequency scaling and filtering should be applied regardless of whether this is a direct mono effect 
			// route or not, to mirror the behavior when not playing through a dynamic category. However, we're a bit wary about
			// modifying sound behavior in single player when it isn't know to be causing any ill effects, so we're just doing this in MP.
			// For future titles this will be the default behavior.
			if(sm_EnableMixGroupPitchFrequencyScaling)
			{
				dynamicFrequencyScalingFactor *= audDriverUtil::ConvertPitchToRatio(g_ResolvedCategorySettings[iCat].Pitch);
				u16 dynamicLPFCutoff = g_ResolvedCategorySettings[iCat].LPFCutoff;
				u16 dynamicHPFCutoff = g_ResolvedCategorySettings[iCat].HPFCutoff;
				metrics.LPFCutoff = Min(dynamicLPFCutoff, metrics.LPFCutoff);
				metrics.HPFCutoff = Max(dynamicHPFCutoff, metrics.HPFCutoff);
			}
		}
	}

	// pass a pointer to the voice routes so that the environment code can update them directly
	metrics.voiceSettings = voiceSettings;

	// Pull out headroom from our PCM generator and factor it into volume
	metrics.volume.ScaleLinear(m_HeadroomScalingLinear);

	PcmSourceState pcmSourceState;
	bool pcmSourceHasStartedPlayback = false;

	if(m_IsControllingSubmix)
	{
		metrics.currentPeakLevel = 32000U;
	}
	else if(m_PcmSourceId != -1 && audPcmSourceInterface::GetState(pcmSourceState, m_PcmSourceId))
	{
		if(pcmSourceState.hasStartedPlayback)
		{
			Assert(pcmSourceState.padding == 0); // sanity check
			Assign(metrics.currentPeakLevel, pcmSourceState.CurrentPeakLevel);

			pcmSourceHasStartedPlayback = pcmSourceState.PlaytimeSamples > 0;
		}
		else
		{
			Assign(metrics.currentPeakLevel, m_DefaultPeakLevel);
		}
	}

	u32 virtualisationScore = 0;
	// Call the environment function that takes these input parameters, calculates output parameters per listener, and then combines them.
	{
		PF_FUNC(CalcEnvMetrics);
		g_audEnvironment->CalculateEnvironmentSoundMetrics(&metrics, virtualisationScore);
	}

	// Convert pitch offset in cents (combined category + metadata/requested) to a frequency ratio
	f32 frequencyScalingFactor = audDriverUtil::ConvertPitchToRatio(combineBuffer.Pitch + g_ResolvedCategorySettings[m_CategoryIndex].Pitch);

	////////////////////////////////////////////
	// Work out any metrics that combine the results of the environmental calculations


	// Calculate our final smoothed doppler, if we've updated position. We now work out doppler even for frontend sounds, to deal with collapsing stereo.
	if (metrics.positionHasUpdated)
	{
		m_DopplerFrequencyScalingFactor = 0.5f * (m_DopplerFrequencyScalingFactor + metrics.dopplerFrequencyScalingFactor);
	}

	// factor in doppler and dynamic pitch.
	frequencyScalingFactor *= m_DopplerFrequencyScalingFactor;
	frequencyScalingFactor *= dynamicFrequencyScalingFactor;

	///////////////////////////////////////////////////////
	// Take our output metrics, and apply them to our voice


	if(m_PcmSourceId != -1 && m_IsControllingPcmSource)
	{
		if(m_FrequencySmoother.GetChangeRate() >= 0.f && pcmSourceHasStartedPlayback)
		{
			frequencyScalingFactor = m_FrequencySmoother.CalculateValue(frequencyScalingFactor);
		}
		SoundAssert(m_PcmSourceId != -1);
		SetFrequencyScalingFactor(frequencyScalingFactor);

		u32 playTimeMixerFrames = ~0u;
		u16 playTimeSubFrameOffset = 0xffff;

		if (topLevelRequestedSettings->HasMixerPlayTime(playTimeMixerFrames, playTimeSubFrameOffset))
		{
			audPcmSourceInterface::SetParam(m_PcmSourceId, audPcmSource::Params::PlayTimeMixerFrame, playTimeMixerFrames);

			if (playTimeSubFrameOffset != 0xffff)
			{
				audPcmSourceInterface::SetParam(m_PcmSourceId, audPcmSource::Params::PlayTimeMixerFrameSubFrameOffset, (u32)playTimeSubFrameOffset);
			}
		}
	}
#if __SPU
	else if(g_DebugSoundUpdateJob_SPU)
	{
		audDisplayf("EnvironmentSound::PostUpdate PcmSourceId %d peak level %f vscore %u vol %f", voiceSettings->PcmSourceId, pcmSourceState.CurrentPeakLevel / float(~0U), virtualisationScore, metrics.volume.GetDb());
	}
#endif


	audAssertf(metrics.LPFCutoff <= (u16)kVoiceFilterLPFMaxCutoff, "[Rage Audio] LPF cutoff is out of range: %u", metrics.LPFCutoff);

	Assign(voiceSettings->LPFCutoff, Clamp(metrics.LPFCutoff, (u16)kVoiceFilterLPFMinCutoff, (u16) kVoiceFilterLPFMaxCutoff));
	Assign(voiceSettings->HPFCutoff, metrics.HPFCutoff);

	
	m_MetricsSlot = 0xff;
	voiceSettings->VirtualisationScore = m_IsControllingSubmix ? 0 : virtualisationScore;

	if(m_IsControllingSubmix)
	{
		Assert(m_SubmixId != -1);
		for(u32 i = 0; i < audVoiceSettings::kMaxVoiceRouteDestinations; i++)
		{
			if(voiceSettings->Routes[i].submixId != audVoiceRoute::InvalidSubmixId)
			{
				audPcmSourceInterface::SetSubmixOutputVolumes(m_SubmixId, i, voiceSettings->RouteChannelVolumes[i]);
			}
		}

		g_audEnvironment->SetSubmixVirtualisationScore(m_SubmixId, virtualisationScore);
	}

	m_VoiceHasBeenUpdated = true;
	m_MetricsSlot = 0xff;
}

void audEnvironmentSound::UpdateMixGroup(s32 mixGroupId, u32 &mixGroupInstanceId,s16 &mixGroupIndex)
{
	if(mixGroupId != mixGroupIndex)
	{
		if(mixGroupIndex != -1)
		{
			audMixGroup::RemoveMixGroupReference(mixGroupIndex, audMixGroup::SOUND_REF);
		}

		Assign(mixGroupIndex,mixGroupId);

		if(mixGroupIndex != -1)
		{
			//If the ref count is zero, this mix group has been removed and the
			//metric's index is stale so dump it

			if(audMixGroup::GetReferenceCount(mixGroupIndex) != 0)
			{
				u32 mixGroupInstId = ~0U;
#if __SPU
				mixGroupInstId = sysInterlockedRead(&g_MixGroupInstanceIdsEa[mixGroupIndex]);

#else
				mixGroupInstId = g_MixGroupInstanceIds[mixGroupIndex];
#endif

				if(audVerifyf(mixGroupInstId < NUM_AUD_MIXGROUPS && g_MixGroupPoolMem[mixGroupInstId].GetIndex() >= 0, "mixGroupInstId %u either deleted or invalid (mix group index %u)", mixGroupInstId, mixGroupIndex))
				{
					//If these are out of sync a mix group has been rapidly destroyed and recreated
					//in a different slot which is possible because the environmentMetrics don't
					//reference count their mix groups.
					//TrapNE(g_MixGroupPoolMem[mixGroupInstanceId].GetIndex(), mixGroupIndex);
					//TrapEQ(mixGroupInstanceId, ~0U);
					Assign(mixGroupInstanceId, mixGroupInstId);
					audMixGroup::AddMixGroupReference(mixGroupIndex, audMixGroup::SOUND_REF);   
				}
				else
				{
					mixGroupIndex = -1;
					mixGroupInstanceId = ~0U;
				}
			}
			else
			{
				mixGroupIndex = -1;
				mixGroupInstanceId = ~0U;
			}
		}
		else
		{
			mixGroupInstanceId = ~0U;
		}
	}
}

#endif // !__SPU || defined(__AUD_ENVIRONMENT_UPDATE)

void audEnvironmentSound::SetFrequencyScalingFactor(const float frequencyScaling)
{
	if (m_PcmSourceId != -1 && frequencyScaling != m_FrequencyScalingFactor)
	{
		m_FrequencyScalingFactor = frequencyScaling;
		audPcmSourceInterface::SetParam(m_PcmSourceId, audPcmSource::Params::FrequencyScalingFactor, frequencyScaling);
	}
}

#if __SOUND_DEBUG_DRAW
bool audEnvironmentSound::DebugDrawInternal(const u32 UNUSED_PARAM(timeInMs), audDebugDrawManager &drawManager, const bool UNUSED_PARAM(drawDynamicChildren)) const
{
	audCategory *ourCategory = g_AudioEngine.GetCategoryManager().GetCategoryFromIndex(m_CategoryIndex);
	char buf[64];
	formatf(buf, "Category: %s", ourCategory->GetNameString());
	drawManager.DrawLine(buf);

	return true;
}
#endif // __SOUND_DEBUG_DRAW

} //namespace rage
