[openal] Porting from XAudio2 to OpenAL - few questions

Chris Robinson chris.kcat at gmail.com
Mon Mar 24 07:23:32 EDT 2014


On 03/24/2014 12:37 AM, Mariusz 'MX' Szaflik wrote:
> Hi
>
> I'm porting some XAudio2 code to OpenAL (latest OpenAL on ubuntu) ...
> and I have some trouble with reverb.
> First of all my reverb parameters are expressed in i3dl standard (xaudio
> uses ReverbConvertI3DL2ToNative to convert from i3dl to it's native format)
> is there around some source code for doing that same and converting from
> i3dl to AL_EFFECT_EAXREVERB set of params ?
> (once uppon time there was lib from creative to do that, but it was
> windows only and no source code was provided)

The conversion is pretty simple. Presuming you have structs like this:

/* EAX Reverb (input) */
typedef struct _EAXREVERBPROPERTIES {
     unsigned long ulEnvironment;
     float flEnvironmentSize;
     float flEnvironmentDiffusion;
     long lRoom;
     long lRoomHF;
     long lRoomLF;
     float flDecayTime;
     float flDecayHFRatio;
     float flDecayLFRatio;
     long lReflections;
     float flReflectionsDelay;
     EAXVECTOR vReflectionsPan;
     long lReverb;
     float flReverbDelay;
     EAXVECTOR vReverbPan;
     float flEchoTime;
     float flEchoDepth;
     float flModulationTime;
     float flModulationDepth;
     float flAirAbsorptionHF;
     float flHFReference;
     float flLFReference;
     float flRoomRolloffFactor;
     unsigned long ulFlags;
} EAXREVERBPROPERTIES, *LPEAXREVERBPROPERTIES;

#define EAXLISTENERFLAGS_DECAYHFLIMIT  0x20

/* EFX EAX Reverb (output) */
typedef struct {
     float flDensity;
     float flDiffusion;
     float flGain;
     float flGainHF;
     float flGainLF;
     float flDecayTime;
     float flDecayHFRatio;
     float flDecayLFRatio;
     float flReflectionsGain;
     float flReflectionsDelay;
     float flReflectionsPan[3];
     float flLateReverbGain;
     float flLateReverbDelay;
     float flLateReverbPan[3];
     float flEchoTime;
     float flEchoDepth;
     float flModulationTime;
     float flModulationDepth;
     float flAirAbsorptionGainHF;
     float flHFReference;
     float flLFReference;
     float flRoomRolloffFactor;
     int iDecayHFLimit;
} EFXEAXREVERBPROPERTIES, *LPEFXEAXREVERBPROPERTIES;

And this helper function to convert millibels to linear gain:

inline float mB_to_gain(float mb)
{
     return powf(10.0f, mb / 2000.0f);
}

The conversion is mostly a straight copy, taking care to convert 
millibel volumes to linear gain, and calculating a density from the 
environment size:

void ConvertEAXToEFX(const EAXREVERBPROPERTIES *eax,
                      EFXEAXREVERBPROPERTIES *efx)
{
     float density;

     density = powf(eax->flEnvironmentSize, 3.0f) / 16.0f;
     if(density > 1.0f) density = 1.0f;
     efx->flDensity = density;
     efx->flDiffusion = eax->flEnvironmentDiffusion;
     efx->flGain = mB_to_gain(eax->lRoom);
     efx->flGainHF = mB_to_gain(eax->lRoomHF);
     efx->flGainLF = mB_to_gain(eax->lRoomLF);
     efx->flDecayTime = eax->flDecayTime;
     efx->flDecayHFRatio = eax->flDecayHFRatio;
     efx->flDecayLFRatio = eax->flDecayLFRatio;
     efx->flReflectionsGain = mB_to_gain(eax->lReflections);
     efx->flReflectionsDelay = eax->flReflectionsDelay;
     efx->flReflectionsPan[0] = eax->vReflectionsPan.x;
     efx->flReflectionsPan[1] = eax->vReflectionsPan.y;
     efx->flReflectionsPan[2] = eax->vReflectionsPan.z;
     efx->flLateReverbGain = mB_to_gain(eax->lReverb);
     efx->flLateReverbDelay = eax->flReverbDelay;
     efx->flLateReverbPan[0] = eax->vReverbPan.x;
     efx->flLateReverbPan[1] = eax->vReverbPan.y;
     efx->flLateReverbPan[2] = eax->vReverbPan.z;
     efx->flEchoTime = eax->flEchoTime;
     efx->flEchoDepth = eax->flEchoDepth;
     efx->flModulationTime = eax->flModulationTime;
     efx->flModulationDepth = eax->flModulationDepth;
     efx->flAirAbsorptionGainHF = mB_to_gain(eax->flAirAbsorptionHF);
     efx->flHFReference = eax->flHFReference;
     efx->flLFReference = eax->flLFReference;
     efx->flRoomRolloffFactor = eax->flRoomRolloffFactor;
     efx->iDecayHFLimit =
         (eax->ulFlags&EAXLISTENERFLAGS_DECAYHFLIMIT) ? 1 : 0;
}

 From there, it's dead easy to load into an AL effect object:

void LoadReverb(ALuint effect, const EFXEAXREVERBPROPERTIES *reverb)
{
     alEffecti(effect, AL_EFFECT_TYPE, AL_EFFECT_EAXREVERB);

     alEffectf(effect, AL_EAXREVERB_DENSITY, reverb->flDensity);
     alEffectf(effect, AL_EAXREVERB_DIFFUSION, reverb->flDiffusion);
     alEffectf(effect, AL_EAXREVERB_GAIN, reverb->flGain);
     alEffectf(effect, AL_EAXREVERB_GAINHF, reverb->flGainHF);
     alEffectf(effect, AL_EAXREVERB_GAINLF, reverb->flGainLF);
     alEffectf(effect, AL_EAXREVERB_DECAY_TIME, reverb->flDecayTime);
     alEffectf(effect, AL_EAXREVERB_DECAY_HFRATIO,
               reverb->flDecayHFRatio);
     alEffectf(effect, AL_EAXREVERB_DECAY_LFRATIO,
               reverb->flDecayLFRatio);
     alEffectf(effect, AL_EAXREVERB_REFLECTIONS_GAIN,
               reverb->flReflectionsGain);
     alEffectf(effect, AL_EAXREVERB_REFLECTIONS_DELAY,
               reverb->flReflectionsDelay);
     alEffectfv(effect, AL_EAXREVERB_REFLECTIONS_PAN,
                reverb->flReflectionsPan);
     alEffectf(effect, AL_EAXREVERB_LATE_REVERB_GAIN,
               reverb->flLateReverbGain);
     alEffectf(effect, AL_EAXREVERB_LATE_REVERB_DELAY,
               reverb->flLateReverbDelay);
     alEffectfv(effect, AL_EAXREVERB_LATE_REVERB_PAN,
                reverb->flLateReverbPan);
     alEffectf(effect, AL_EAXREVERB_ECHO_TIME, reverb->flEchoTime);
     alEffectf(effect, AL_EAXREVERB_ECHO_DEPTH, reverb->flEchoDepth);
     alEffectf(effect, AL_EAXREVERB_MODULATION_TIME,
               reverb->flModulationTime);
     alEffectf(effect, AL_EAXREVERB_MODULATION_DEPTH,
               reverb->flModulationDepth);
     alEffectf(effect, AL_EAXREVERB_AIR_ABSORPTION_GAINHF,
               reverb->flAirAbsorptionGainHF);
     alEffectf(effect, AL_EAXREVERB_HFREFERENCE, reverb->flHFReference);
     alEffectf(effect, AL_EAXREVERB_LFREFERENCE, reverb->flLFReference);
     alEffectf(effect, AL_EAXREVERB_ROOM_ROLLOFF_FACTOR,
               reverb->flRoomRolloffFactor);
     alEffecti(effect, AL_EAXREVERB_DECAY_HFLIMIT,
               reverb->iDecayHFLimit);

     if(alGetError() != AL_NO_ERROR)
         std::cerr<< "Error loading reverb effect!" <<std::endl;
}

You could even combine those two functions, to convert and load at the 
same time.

> My second problem is that XAudio2 is setup in the way that each voice
> (source) have two sends one to the master voice (dry path)
> and one to the reverb voice - then to the master voice (wet path).
>
> Is there any way to do this in OpenAL ?

That's pretty much how OpenAL works, too. Each source has a dry path 
that mixes directly to the output buffer, and one or more wet paths 
(auxiliary sends) that each mix to an auxiliary effect slot (which 
applies an effect, such as reverb) which is then mixed to the output 
buffer. Each path also has its own filter, so to visualize:


source-->-filter---------->-output
   |                         ^
   v                         |
   +->filter-->-effect slot--+
   |                         ^
   ~                         ~
   v                         |
   +->filter-->-effect slot--+

Multiple sources can use the same effect slot.

> for now i have:
>
> alGenEffects( 1, &reverbEffect );
> alGenAuxiliaryEffectSlots( 1, &reverbSlotID );
> alEffecti( reverbEffect, AL_EFFECT_TYPE, AL_EFFECT_EAXREVERB );
>
> alEffect*(reverbEffect, AL_EAXREVERB_*, ...); // set all reverb params
>
>
> alGenSources( 1, &source1 );
> alSource3i( source1, AL_AUXILIARY_SEND_FILTER, reverbSlotID, 0, NULL );
>
> alGenSources( 1, &source2 );
> alSource3i( source2, AL_AUXILIARY_SEND_FILTER, reverbSlotID, 0, NULL );
>
>
>
> ... this works fine but gives me only wet path

Don't forget to set the effect on the auxiliary effect slot after 
setting the parameters:

alEffect*(reverbEffect, AL_EAXREVERB_*, ...);

alAuxiliaryEffectSloti(reverbSlotID, AL_EFFECTSLOT_EFFECT,
                                      reverbEffect);

Other than that, it should work. The dry path supplies a non-reverb 
sound, while the wet path supplies a reverb sound on top of it. Note 
that the two are mixed together, so you will still hear the reverb from 
the wet path in addition to the non-reverb dry path.

> and what should the setting looks like when i want for example the
> source1 to be 50% wet and 50% dry in the final mix ?

If you want the dry and wet paths to use the same percentage, setting 
the source's AL_GAIN will affect the dry path and wet paths equally. If 
you want them to be different, you can use filters. Like this:

enum {
     DryFilter = 0,
     WetFilter = 1
};

// Create filters (one for the dry path, one for the wet path)
ALuint filters[2];
alGenFilters(2, filters);

// Any filter type would do, although low-pass is the type most likely
// to be supported
alFilteri(filters[DryFilter], AL_FILTER_TYPE, AL_FILTER_LOWPASS);
alFilteri(filters[WetFilter], AL_FILTER_TYPE, AL_FILTER_LOWPASS);

// Set the amount of gain for each path. AL_LOWPASS_GAIN attenuates all
// frequencies, AL_LOWPASS_GAINHF would attenuate high frequencies only
alFilterf(filters[DryFilter], AL_LOWPASS_GAIN, drymix);
alFilterf(filters[WetFilter], AL_LOWPASS_GAIN, wetmix);

// Apply the filters to the source (changing the filters after this
// will *NOT* alter playback, until they're applied like this again)
alSourcei(source, AL_DIRECT_FILTER, filters[DryFilter]);
alSource3i(source, AL_AUXILIARY_SEND_FILTER,
            reverbSlotID, 0, filters[WetFilter]);

> and another question:
> what is most efficient way to set LowPass filter per source (assuming
> that i need 'global' reverb to)
> -- per source since it's parameters depends on distance from listener
> (and other in-engine-calculated-factors like occlusion) ?

OpenAL already attenuates the dry and wet paths according to distance, 
presuming you have the source's AL_REFERENCE_DISTANCE and 
AL_ROLLOFF_FACTOR set appropriately. It will also further attenuate high 
frequencies with distance if you set the source's 
AL_AIR_ABSORPTION_FACTOR (the default is 0, which means high frequencies 
attenuate at the same rate as low frequencies; setting it to 1 means 
high frequencies attenuate an extra -0.05dB per meter, while 10 gives an 
extra -0.5dB per meter).

If you want more control, to handle occlusion and obstruction for 
instance, you'd use low-pass filters pretty much as shown above. Create 
two low-pass filters with each source, set their AL_LOWPASS_GAIN and 
AL_LOWPASS_GAINHF properties depending on how much attenuation the dry 
and wet paths should get, then apply them to the source. When they need 
updating, set the new values and apply the filters to the source again.

> Sorry if the questions are basic ones, but I did'n played much with
> openAL until now (i'm mainly XAudio / FMOD programmer ;))

No problem. :) If you have any more questions, feel free to ask.


More information about the openal mailing list