HRTF does not appear to work

I’m using the low level API and attempting to get the simplistic built-in HRTF working.

First, let make sure I’m understanding what should happen correctly. I expect that a 3D sound positioned directly behind the listener should have a low pass applied filter to it and sound slightly muffled. I also expect that a 3D sound directly in front of the listener should not have the low pass filter applied to it and should sound normal.

If that’s correct, then here’s what I’ve tried.

  • Initialize the device with FMOD_INIT_CHANNEL_LOWPASS
  • Create the sound with FMOD_3D | FMOD_3D_WORLDRELATIVE | FMOD_3D_INVERSEROLLOFF
  • Use FMOD_System_Set3DListenerAttributes to set the position of the listener either directly in front of, or directly behind the sound. I’m instantly setting the position of the listener and setting the velocity to zero.

If I set the listener the same distance from the sound either in front or in back it sounds precisely the same. No low pass filtering appears to be applied. I’ve tried mucking with the HRTF advanced settings with no luck. I’ve also tried modifying the 3D example to listen for the HRTF effect and can’t hear it there either. I do see the low pass filter in FMOD Profiler and using set3dOcclusion works as expected, but the angle-dependent HRTF does not.

Here’s the modified 3D example. Use the up and down arrows to move the sound in front or behind the listener.

/*==============================================================================
3D Example
Copyright (c), Firelight Technologies Pty, Ltd 2004-2017.

This example shows how to basic 3D positioning of sounds.
==============================================================================*/
#include "fmod.hpp"
#include "common.h"

const int   INTERFACE_UPDATETIME = 50;      // 50ms update for interface
const float DISTANCEFACTOR = 1.0f;          // Units per meter.  I.e feet would = 3.28.  centimeters would = 100.

int FMOD_Main()
{
    FMOD::System    *system;
    FMOD::Sound     *sound1, *sound2, *sound3;
    FMOD::Channel   *channel1 = 0, *channel2 = 0, *channel3 = 0;
    FMOD_RESULT      result;
    bool             listenerflag = false;
    FMOD_VECTOR      listenerpos  = { 0.0f, 0.0f, -1.0f * DISTANCEFACTOR };
    unsigned int     version;
    void            *extradriverdata = 0;

    Common_Init(&extradriverdata);

    /*
        Create a System object and initialize.
    */
    result = FMOD::System_Create(&system);
    ERRCHECK(result);
    
    result = system->getVersion(&version);
    ERRCHECK(result);

    if (version < FMOD_VERSION)
    {
        Common_Fatal("FMOD lib version %08x doesn't match header version %08x", version, FMOD_VERSION);
    }
    
    result = system->init(100, FMOD_INIT_NORMAL | FMOD_INIT_CHANNEL_LOWPASS, extradriverdata);
    ERRCHECK(result);
    
    /*
        Set the distance units. (meters/feet etc).
    */
    result = system->set3DSettings(1.0, DISTANCEFACTOR, 1.0f);
    ERRCHECK(result);

    /*
        Load some sounds
    */
    result = system->createSound(Common_MediaPath("drumloop.wav"), FMOD_3D, 0, &sound1);
    ERRCHECK(result);
    result = sound1->set3DMinMaxDistance(0.5f * DISTANCEFACTOR, 5000.0f * DISTANCEFACTOR);
    ERRCHECK(result);
    result = sound1->setMode(FMOD_LOOP_NORMAL);
    ERRCHECK(result);

    result = system->createSound(Common_MediaPath("jaguar.wav"), FMOD_3D, 0, &sound2);
    ERRCHECK(result);
    result = sound2->set3DMinMaxDistance(0.5f * DISTANCEFACTOR, 5000.0f * DISTANCEFACTOR);
    ERRCHECK(result);
    result = sound2->setMode(FMOD_LOOP_NORMAL);
    ERRCHECK(result);

    result = system->createSound(Common_MediaPath("swish.wav"), FMOD_2D, 0, &sound3);
    ERRCHECK(result);

    /*
        Play sounds at certain positions
    */
    {
        FMOD_VECTOR pos = { -10.0f * DISTANCEFACTOR, 0.0f, 0.0f };
        FMOD_VECTOR vel = {  0.0f, 0.0f, 0.0f };

        result = system->playSound(sound1, 0, true, &channel1);
        ERRCHECK(result);
        result = channel1->set3DAttributes(&pos, &vel);
        ERRCHECK(result);
        result = channel1->setPaused(false);
        ERRCHECK(result);
    }

    {
        FMOD_VECTOR pos = { 15.0f * DISTANCEFACTOR, 0.0f, 0.0f };
        FMOD_VECTOR vel = { 0.0f, 0.0f, 0.0f };

        result = system->playSound(sound2, 0, true, &channel2);
        ERRCHECK(result);
        result = channel2->set3DAttributes(&pos, &vel);
        ERRCHECK(result);
        //result = channel2->setPaused(false);
        //ERRCHECK(result);
    }

    /*
        Main loop
    */
    do
    {
        Common_Update();

        if (Common_BtnPress(BTN_ACTION1))
        {
            bool paused;
            channel1->getPaused(&paused);
            channel1->setPaused(!paused);
        }

        if (Common_BtnPress(BTN_ACTION2))
        {
            bool paused;
            channel2->getPaused(&paused);
            channel2->setPaused(!paused);
        }

        if (Common_BtnPress(BTN_ACTION3))
        {
            result = system->playSound(sound3, 0, false, &channel3);
            ERRCHECK(result);
        }

        if (Common_BtnPress(BTN_MORE))
        {
            listenerflag = !listenerflag;
        }

        if (!listenerflag)
        {
            if (Common_BtnDown(BTN_LEFT))
            {
                listenerpos.x -= 1.0f * DISTANCEFACTOR;
                if (listenerpos.x < -24 * DISTANCEFACTOR)
                {
                    listenerpos.x = -24 * DISTANCEFACTOR;
                }
            }

            if (Common_BtnDown(BTN_RIGHT))
            {
                listenerpos.x += 1.0f * DISTANCEFACTOR;
                if (listenerpos.x > 23 * DISTANCEFACTOR)
                {
                    listenerpos.x = 23 * DISTANCEFACTOR;
                }
            }

			if (Common_BtnDown(BTN_UP))
			{
				FMOD_VECTOR pos = listenerpos;
				pos.z += 10.0f * DISTANCEFACTOR;
				FMOD_VECTOR vel = { 0.0f, 0.0f, 0.0f };

				result = channel1->set3DAttributes(&pos, &vel);
				ERRCHECK(result);
			}

			if (Common_BtnDown(BTN_DOWN))
			{
				FMOD_VECTOR pos = listenerpos;
				pos.z -= 10.0f * DISTANCEFACTOR;
				FMOD_VECTOR vel = { 0.0f, 0.0f, 0.0f };

				result = channel1->set3DAttributes(&pos, &vel);
				ERRCHECK(result);
			}
        }

        // ==========================================================================================
        // UPDATE THE LISTENER
        // ==========================================================================================
        {
            static float t = 0;
            static FMOD_VECTOR lastpos = { 0.0f, 0.0f, 0.0f };
            FMOD_VECTOR forward        = { 0.0f, 0.0f, 1.0f };
            FMOD_VECTOR up             = { 0.0f, 1.0f, 0.0f };
            FMOD_VECTOR vel;

            if (listenerflag)
            {
                listenerpos.x = (float)sin(t * 0.05f) * 24.0f * DISTANCEFACTOR; // left right pingpong
            }

            // ********* NOTE ******* READ NEXT COMMENT!!!!!
            // vel = how far we moved last FRAME (m/f), then time compensate it to SECONDS (m/s).
            //vel.x = (listenerpos.x - lastpos.x) * (1000 / INTERFACE_UPDATETIME);
            //vel.y = (listenerpos.y - lastpos.y) * (1000 / INTERFACE_UPDATETIME);
            //vel.z = (listenerpos.z - lastpos.z) * (1000 / INTERFACE_UPDATETIME);
            vel            = { 0.0f, 0.0f, 0.0f };

            // store pos for next time
            lastpos = listenerpos;

            result = system->set3DListenerAttributes(0, &listenerpos, &vel, &forward, &up);
            ERRCHECK(result);

            t += (30 * (1.0f / (float)INTERFACE_UPDATETIME));    // t is just a time value .. it increments in 30m/s steps in this example
        }

        result = system->update();
        ERRCHECK(result);

        // Create small visual display.
        char s[80] = "|.............<1>......................<2>.......|";
        s[(int)(listenerpos.x / DISTANCEFACTOR) + 25] = 'L';

        Common_Draw("==================================================");
        Common_Draw("3D Example.");
        Common_Draw("Copyright (c) Firelight Technologies 2004-2017.");
        Common_Draw("==================================================");
        Common_Draw("");
        Common_Draw("Press %s to toggle sound 1 (16bit Mono 3D)", Common_BtnStr(BTN_ACTION1));
        Common_Draw("Press %s to toggle sound 2 (8bit Mono 3D)", Common_BtnStr(BTN_ACTION2));
        Common_Draw("Press %s to play a sound (16bit Stereo 2D)", Common_BtnStr(BTN_ACTION3));
        Common_Draw("Press %s or %s to move listener in still mode", Common_BtnStr(BTN_LEFT), Common_BtnStr(BTN_RIGHT));
        Common_Draw("Press %s to toggle listener auto movement", Common_BtnStr(BTN_MORE));
        Common_Draw("Press %s to quit", Common_BtnStr(BTN_QUIT));
        Common_Draw("");
        Common_Draw(s);

        Common_Sleep(INTERFACE_UPDATETIME - 1);
    } while (!Common_BtnPress(BTN_QUIT));

    /*
        Shut down
    */
    result = sound1->release();
    ERRCHECK(result);
    result = sound2->release();
    ERRCHECK(result);
    result = sound3->release();
    ERRCHECK(result);

    result = system->close();
    ERRCHECK(result);
    result = system->release();
    ERRCHECK(result);

    Common_Close();

    return 0;
}

I’ve referred to this question: https://www.fmod.org/questions/question/fmod_init_channel_lowpass-not-working/ and while it explains how to enable low pass and occlusion and confirm the low pass filter is in place it doesn’t actually address the HRTF side.

Hi Adam,
Apologies, It looks like this feature was removed 10 years ago :expressionless:

The variables remained, but they arent used. If you’re interested in the old code I can give you what FMOD 4 used to use (the feature came from back then).

I’ve updated the docs for the init flags

#define FMOD_INIT_CHANNEL_LOWPASS            0x00000100 /* Enables usage of Channel::setLowPassGain,  Channel::set3DOcclusion, or automatic usage by the Geometry API.  All voices will add a software lowpass filter effect into the DSP chain which is idle unless one of the previous functions/features are used. */
#define FMOD_INIT_CHANNEL_DISTANCEFILTER     0x00000200 /* All FMOD_3D based voices will add a software lowpass and highpass filter effect into the DSP chain which will act as a distance-automated bandpass filter. Use System::setAdvancedSettings to adjust the center frequency. */

Ouch. I spent entirely too long trying to understand why it wasn’t working. I’m guessing it was removed a little more recently than that, as there are answers from 4-5 years ago still referencing it:
https://www.fmod.org/questions/question/forum-38493/
https://www.fmod.org/questions/question/forum-39326/

Please note that there are references to this functionality in FMOD_ADVANCEDSETTINGS as well.
https://www.fmod.com/resources/documentation-api?page=content/generated/FMOD_ADVANCEDSETTINGS.html

In older posts it looks like a real HRTF was in the works some number of years ago but fell through. Is there anything on the horizon regarding HRTF functionality?
https://www.fmod.org/questions/question/forum-29423/
https://www.fmod.org/questions/question/forum-40029/

Hi, The ‘feature’ never made it into FMOD Studio after some archaeology here. Just the reference to it in FMOD_ADVANCEDSETTINGS. I’ve cleaned it all up for the next release.
This is a feature in FMOD4 that was removed because it was only a fudge at that time. We have several options in using real HRTF, through 3rd party plugins. For example see Oculus / facebook plugin at

https://developer.oculus.com/documentation/audiosdk/latest/concepts/book-osp-fmod/

and

https://developers.google.com/vr/audio/fmod-getting-started

Note that they reference studio but are perfectly usable in the low level (you’d have to pass parameters to the plugins directly) as they are basically low level DSP plugins.