High Dynamic Range Audio

I was stumbling over the excellent sound design in the Battlefield games and thier usage of “High Dynamic Range” Audio.

Link here: https://de.slideshare.net/aclerwall/how-high-dynamic-range-audio-makes-battlefield-bad-company-go-boom-1292018

Basicly they use dbfs levels as input and choose the loudest sound based on listener position and other things. Then they scale these volume levels back into usable 0-1 Amplitude range. The result is an elegant ducking system and automation of audio mixing.

Does FMOD provide a similar system or is there a way to manually perform “High Dynamic Range” with FMOD?

Not sure if this is the correct approach, but I would group all of my events in the mixer. Then in each event, I’d use a unique global parameter float that sends to a separate BUS. This is my distance parameter (unique to each event).

Then, I’d add the gain of mixer group (A) to the global distance parameter. It would have to have automation for every event with a distance parameter in your scene.

Then in your game engine (unity in my case), I’d write a script that effects the distance parameter based on the distance to the closest event. With maybe a slower seek speed, in case the nearest game object is quickly changing.

As the distance increases to the object, the event send-to-bus parameter goes up, and mixer group volume goes down.

I’d love to hear if anyone knows a better way, as I’m going to implement something similar in my game.

Hi amaribrahim,

thanks for the reply, that is an interesting approach. I just thought, that the redundant distance calculation could be avoided since FMOD already perform these.

I think DSP Loudness meter could be utilisied to perform high dynamic range audio:

  1. Play all sounds with an apropiate db amplitude, e.g. footstep 30 db, gunshot 120 db on a sfx chain.
  2. Measure the peak loudness with the dsp or alternativly with setMeteringEnabled() of the channelGroup head-dsp and set the window range.
  3. Convert the loudness back for each channel into 0 to 1 amplitude range with formula given in presentation.

But maybe I am missing a step?

Here is a proof of concept:

Declare a absolute DB value for each sound effect, e.g. footstep 30 DB, gunshot 120 DB. When playing a sound effect, convert this DB value into relative DB scale of the FMOD Fader (which is [-80, 10]) and then into the linear value. Use this value for setVolume().

Each soundeffect gets a custom DSP attached. Here we do two things:

  1. Get the current audio signal and save this linear value into an loudness array. This measures how load our soundeffect is. Thankfully it has already the correct volume in the 3D space.
  2. Scale our audio signal depending how loud the current frame is.

Each frame we need to calculate the current upper loudness ceiling, by summing over the linear values which got pushed by our custom DSP into the loudness array.


float AudioSystem::DBLoudnessWindowTop;
float AudioSystem::DBLoudnessWindowMin;
std::vector<float> AudioSystem::channelLinearLoudness;

void AudioSystem::computeLoudnessWindow()
{
	// Exponential decay.
	float expStrength = 0.1f;
	DBLoudnessWindowTop = DBLoudnessWindowTop * ( 1.0f - expStrength * deltaTime);

	float newLinearLoudness = 0.0f;
	
	for (auto linearValue : channelLinearLoudness)
	{
		if (std::isinf(linearValue))
		{
			continue;
		}
		newLinearLoudness += linearValue;
	}
	float newFMODDBLoudness = 20.0f * std::log10f(newLinearLoudness);
	float newDBLoudness = AudioSystemStatic::remapFMODDBtoDB(newFMODDBLoudness);

	DBLoudnessWindowTop = glm::max(newDBLoudness, DBLoudnessWindowTop);
	DBLoudnessWindowMin = DBLoudnessWindowTop - 40.0f;
	Log("Linear:", newLinearLoudness, "Current DB:", newDBLoudness, "Top DB:", DBLoudnessWindowTop, "Min DB:", DBLoudnessWindowMin);
	
	channelLinearLoudness.clear();
}

FMOD_RESULT F_CALLBACK AudioSystem::callbackDspFromHDRtoLDR(FMOD_DSP_STATE *dsp_state, float *inbuffer, float *outbuffer, unsigned int length, int inchannels, int *outchannels)
{
	FMOD::DSP *thisdsp = (FMOD::DSP *)dsp_state->instance;
	thisdsp->setMeteringEnabled(true, true);
	FMOD_DSP_METERING_INFO infoInput;
	FMOD_DSP_METERING_INFO infoOutput;

	thisdsp->getMeteringInfo(&infoInput, &infoOutput);
	
	for (int i = 0; i < infoInput.numchannels; ++i)
	{
		//channelLinearLoudness.push_back(glm::abs(infoInput.rmslevel[i]));
		channelLinearLoudness.push_back(infoInput.peaklevel[i]);
	}

	// Alternativly measure whole audio signal.
	float peakValue = 0.0f;
	float samples = 0.0f;
	float rmsValue = 0.0f;

	for (unsigned int samp = 0; samp < length; samp++)
	{
		for (int chan = 0; chan < inchannels; chan++)
		{
			float sampleLinear = inbuffer[(samp * inchannels) + chan];
			rmsValue += (sampleLinear * sampleLinear);
			samples += 1.0f;
			peakValue = glm::abs(sampleLinear) > peakValue ? glm::abs(sampleLinear) : peakValue;
		}
	}
	rmsValue = glm::sqrt(rmsValue / samples);

	//channelLinearLoudness.push_back(rmsValue);
	//channelLinearLoudness.push_back(peakValue * 1.5f);

	for (unsigned int samp = 0; samp < length; samp++)
	{
		for (int chan = 0; chan < *outchannels; chan++)
		{
			// Sample is in linear [-1, 1] range. Convert first to FMOD DB [-80, 0] Range and then DB Range [0, 120].
			// Take absolut value to correctly convert to db.
			float sampleLinear = inbuffer[(samp * inchannels) + chan];

			float isNeg = sampleLinear < 0.0f ? -1.0f : 1.0f;
			
			float sampleFMODDB = 20.0f * std::log10f(glm::abs(sampleLinear));
			float sampleDB = AudioSystemStatic::remapFMODDBtoDB(sampleFMODDB);
			
			float headroom = 6.0f;
			// Convert DB -> Linear, no need for FMOD DB as iterim step, since it is delta DB values.
			float newDB = sampleDB - DBLoudnessWindowMin - headroom;
			float newFMODDB = newDB;
			float newLinear = glm::pow(10.0f, newFMODDB / 20.0f);

			outbuffer[(samp * *outchannels) + chan] = newLinear * isNeg;
		}
	}

	return FMOD_OK;
}