Several OpenAL improvements

- s_scaleDownAndClamp CVar so the clamping and scaling down of all
  sounds (that is done to prevent clipping) can be disabled
  (enabled by default)
- s_alOutputLimiter CVar to allow configuring ALC_SOFT_output_limiter
  which (if enabled) reduces the overall volume if it gets too loud,
  to avoid clipping (defaults to -1 = "let OpenAL decide")
- s_alHRTF to allow enabling or disabling HRTF, requires ALC_SOFT_HRTF
  (defaults to -1 = "let OpenAL decide")

Those CVars can be changed at runtime and are applied immediately
(in case of s_alHRTF and s_alOutputLimiter by resetting the OpenAL
 device, just like we do when a disconnect is detected)
This commit is contained in:
Daniel Gibson 2024-06-01 07:09:42 +02:00
parent 7b6fdc845a
commit 2c58845d28
3 changed files with 164 additions and 22 deletions

View file

@ -46,6 +46,38 @@ If you have questions concerning this license or the applicable additional terms
typedef ALCboolean (ALC_APIENTRY*LPALCRESETDEVICESOFT)(ALCdevice *device, const ALCint *attribs);
#endif
// DG: ALC_SOFT_output_mode is pretty new, provide compatibility..
#ifndef ALC_SOFT_output_mode
#define ALC_SOFT_output_mode
#define ALC_OUTPUT_MODE_SOFT 0x19AC
#define ALC_ANY_SOFT 0x19AD
#define ALC_STEREO_BASIC_SOFT 0x19AE
#define ALC_STEREO_UHJ_SOFT 0x19AF
#define ALC_STEREO_HRTF_SOFT 0x19B2
#define ALC_SURROUND_5_1_SOFT 0x1504
#define ALC_SURROUND_6_1_SOFT 0x1505
#define ALC_SURROUND_7_1_SOFT 0x1506
#endif
// the following formats are defined in https://openal-soft.org/openal-extensions/SOFT_output_mode.txt
// but commented out in OpenAL Softs current AL/alext.h
#ifndef ALC_MONO_SOFT
#define ALC_MONO_SOFT 0x1500
#endif
#ifndef ALC_STEREO_SOFT
#define ALC_STEREO_SOFT 0x1501
#endif
#ifndef ALC_QUAD_SOFT
#define ALC_QUAD_SOFT 0x1503
#endif
// DG: in case ALC_SOFT_output_limiter is not available in some headers..
#ifndef ALC_SOFT_output_limiter
#define ALC_SOFT_output_limiter
#define ALC_OUTPUT_LIMITER_SOFT 0x199A
#endif
#include "framework/UsercmdGen.h"
#include "sound/efxlib.h"
#include "sound/sound.h"
@ -709,6 +741,9 @@ public:
// otherwise it will try to recover the device and return false while it's gone
// (display audio sound devices sometimes disappear for a few seconds when switching resolution)
bool CheckDeviceAndRecoverIfNeeded();
// resets the OpenAL device, applying the settings of s_alHRTF and s_alOutputLimiter
// returns false if that failed, or the necessary OpenAL extension isn't available
bool ResetALDevice();
idSoundCache * soundCache;
@ -767,6 +802,12 @@ public:
// mark available during initialization, or through an explicit test
static int EFXAvailable;
static bool alHRTFavailable; // needs ALC_SOFT_HRTF extension
static bool alOutputLimiterAvailable; // needs ALC_SOFT_output_limiter extension (+ HRTF extension)
static bool alEnumerateAllAvailable; // needs ALC_ENUMERATE_ALL_EXT
static bool alIsDisconnectAvailable; // needs ALC_EXT_disconnect
static bool alOutputModeAvailable; // needs ALC_SOFT_output_mode
// DG: for CheckDeviceAndRecoverIfNeeded()
LPALCRESETDEVICESOFT alcResetDeviceSOFT; // needs ALC_SOFT_HRTF extension
int resetRetryCount;
@ -803,6 +844,10 @@ public:
static idCVar s_alReverbGain;
static idCVar s_scaleDownAndClamp;
static idCVar s_alOutputLimiter;
static idCVar s_alHRTF;
static idCVar s_slowAttenuate;
static idCVar s_enviroSuitCutoffFreq;

View file

@ -80,12 +80,53 @@ idCVar idSoundSystemLocal::s_decompressionLimit( "s_decompressionLimit", "6", CV
idCVar idSoundSystemLocal::s_alReverbGain( "s_alReverbGain", "0.5", CVAR_SOUND | CVAR_FLOAT | CVAR_ARCHIVE, "reduce reverb strength (0.0 to 1.0)", 0.0f, 1.0f );
idCVar idSoundSystemLocal::s_scaleDownAndClamp( "s_scaleDownAndClamp", "1", CVAR_SOUND | CVAR_BOOL | CVAR_ARCHIVE, "Clamp and reduce volume of all sounds to prevent clipping or temporary downscaling by OpenAL. When disabling this, you probably want to explicitly disable s_alOutputLimiter" );
idCVar idSoundSystemLocal::s_alOutputLimiter( "s_alOutputLimiter", "-1", CVAR_SOUND | CVAR_INTEGER | CVAR_ARCHIVE, "Configure OpenAL's output-limiter. 0: Disable, 1: Enable, -1: Let OpenAL decide (default)" );
idCVar idSoundSystemLocal::s_alHRTF( "s_alHRTF", "-1", CVAR_SOUND | CVAR_INTEGER | CVAR_ARCHIVE, "Enable HRTF for better surround sound with stereo *headphones*. 0: Disable, 1: Enable, -1: Let OpenAL decide (default)" );
bool idSoundSystemLocal::useEFXReverb = false;
int idSoundSystemLocal::EFXAvailable = -1;
bool idSoundSystemLocal::alHRTFavailable = false;
bool idSoundSystemLocal::alOutputLimiterAvailable = false;
bool idSoundSystemLocal::alEnumerateAllAvailable = false;
bool idSoundSystemLocal::alIsDisconnectAvailable = false;
bool idSoundSystemLocal::alOutputModeAvailable = false;
idSoundSystemLocal soundSystemLocal;
idSoundSystem *soundSystem = &soundSystemLocal;
enum { D3_ALC_ATTRLIST_LEN = 6 }; // currently we set at most two setting-pairs + terminating 0, 0
static ALCint cvarToAlcBoolish( const idCVar& cvar )
{
int val = cvar.GetInteger();
return (val < 0) ? ALC_DONT_CARE_SOFT : ( (val > 0) ? ALC_TRUE : ALC_FALSE );
}
// initialize attrList for alcCreateContext() or alcResetDeviceSOFT(),
// based on s_alHRTF and s_alOutputLimiter
static void SetAlcAttrList( ALCint attrList[D3_ALC_ATTRLIST_LEN] )
{
int idx = 0;
// ALC_HRTF_SOFT, ALC_TRUE // or ALC_FALSE or ALC_DONT_CARE_SOFT
if ( idSoundSystemLocal::alHRTFavailable ) {
attrList[idx++] = ALC_HRTF_SOFT;
attrList[idx++] = cvarToAlcBoolish( idSoundSystemLocal::s_alHRTF );
}
// TODO: ALC_HRTF_ID_SOFT, index // to select an HRTF model?
// ALC_OUTPUT_LIMITER_SOFT, ALC_FALSE // or ALC_FALSE or ALC_DONT_CARE_SOFT
if ( idSoundSystemLocal::alOutputLimiterAvailable ) {
attrList[idx++] = ALC_OUTPUT_LIMITER_SOFT;
attrList[idx++] = cvarToAlcBoolish( idSoundSystemLocal::s_alOutputLimiter );
}
// terminating 0, 0
attrList[idx++] = 0;
attrList[idx++] = 0;
}
/*
===============
SoundReloadSounds_f
@ -347,7 +388,8 @@ void idSoundSystemLocal::Init() {
else if (!idStr::Icmp(device, "default"))
device = NULL;
if ( alcIsExtensionPresent(NULL, "ALC_ENUMERATE_ALL_EXT") ) {
alEnumerateAllAvailable = alcIsExtensionPresent(NULL, "ALC_ENUMERATE_ALL_EXT");
if ( alEnumerateAllAvailable ) {
const char *devs = alcGetString(NULL, ALC_ALL_DEVICES_SPECIFIER);
bool found = false;
@ -381,7 +423,32 @@ void idSoundSystemLocal::Init() {
common->Printf( "OpenAL: failed to open default device (0x%x), disabling sound\n", alGetError() );
openalContext = NULL;
} else {
openalContext = alcCreateContext( openalDevice, NULL );
// DG: extensions needed for CheckDeviceAndRecoverIfNeeded(), HRTF, output-limiter, the info in the settings menu, ...
alHRTFavailable = alcIsExtensionPresent( openalDevice, "ALC_SOFT_HRTF" ) != AL_FALSE;
alIsDisconnectAvailable = alcIsExtensionPresent( openalDevice, "ALC_EXT_disconnect" ) != AL_FALSE;
if ( alHRTFavailable ) {
common->Printf( "OpenAL: found extension for HRTF\n" );
alcResetDeviceSOFT = (LPALCRESETDEVICESOFT)alcGetProcAddress( openalDevice, "alcResetDeviceSOFT" );
if ( alIsDisconnectAvailable ) {
common->Printf( "OpenAL: found extensions for resetting disconnected devices\n" );
}
alOutputLimiterAvailable = alcIsExtensionPresent( openalDevice, "ALC_SOFT_output_limiter" ) != AL_FALSE;
if ( alOutputLimiterAvailable ) {
common->Printf( "OpenAL: found extension to control output-limiter\n" );
}
} else {
alOutputLimiterAvailable = false;
alcResetDeviceSOFT = NULL;
}
alOutputModeAvailable = alcIsExtensionPresent( openalDevice, "ALC_SOFT_output_mode" );
ALCint attrList[D3_ALC_ATTRLIST_LEN] = {};
SetAlcAttrList( attrList );
s_alHRTF.ClearModified();
s_alOutputLimiter.ClearModified();
openalContext = alcCreateContext( openalDevice, attrList );
if ( openalContext == NULL ) {
common->Printf( "OpenAL: failed to create context (0x%x), disabling sound\n", alcGetError(openalDevice) );
alcCloseDevice( openalDevice );
@ -404,14 +471,6 @@ void idSoundSystemLocal::Init() {
common->Printf( "OpenAL renderer: %s\n", alGetString(AL_RENDERER) );
common->Printf( "OpenAL version: %s\n", alGetString(AL_VERSION) );
// DG: extensions needed for CheckDeviceAndRecoverIfNeeded()
bool hasAlcExtDisconnect = alcIsExtensionPresent( openalDevice, "ALC_EXT_disconnect" ) != AL_FALSE;
bool hasAlcSoftHrtf = alcIsExtensionPresent( openalDevice, "ALC_SOFT_HRTF" ) != AL_FALSE;
if ( hasAlcExtDisconnect && hasAlcSoftHrtf ) {
common->Printf( "OpenAL: found extensions for resetting disconnected devices\n" );
alcResetDeviceSOFT = (LPALCRESETDEVICESOFT)alcGetProcAddress( openalDevice, "alcResetDeviceSOFT" );
}
// try to obtain EFX extensions
if (alcIsExtensionPresent(openalDevice, "ALC_EXT_EFX")) {
common->Printf( "OpenAL: found EFX extension\n" );
@ -599,6 +658,37 @@ bool idSoundSystemLocal::ShutdownHW() {
return true;
}
/*
===============
idSoundSystemLocal::ResetALDevice
DG: resets the OpenAL device, applying the settings of s_alHRTF and s_alOutputLimiter
returns false if that failed, or the necessary OpenAL extension isn't available
===============
*/
bool idSoundSystemLocal::ResetALDevice()
{
s_alHRTF.ClearModified();
s_alOutputLimiter.ClearModified();
if ( alcResetDeviceSOFT == NULL ) {
common->Warning( "Can't reset OpenAL device, because OpenAL Extension (ALC_SOFT_HRTF) is missing!\nConsider using (a recent-ish version of) OpenAL-Soft!" );
return false;
}
ALCint attrList[D3_ALC_ATTRLIST_LEN] = {};
SetAlcAttrList( attrList );
if ( alcResetDeviceSOFT( openalDevice, attrList ) ) {
common->Printf( "OpenAL: resetting device succeeded!\n" );
resetRetryCount = 0;
return true;
} else if ( resetRetryCount == 0 ) {
common->Warning( "OpenAL: resetting device FAILED!\n" );
}
return false;
}
/*
===============
@ -607,6 +697,8 @@ idSoundSystemLocal::CheckDeviceAndRecoverIfNeeded
DG: returns true if openalDevice is still available,
otherwise it will try to recover the device and return false while it's gone
(display audio sound devices sometimes disappear for a few seconds when switching resolution)
As this is called every frame, it now also checks if s_alHRTF or s_alOutputLimiter are
modified and if they are, resets the device to apply the change
===============
*/
bool idSoundSystemLocal::CheckDeviceAndRecoverIfNeeded()
@ -617,6 +709,12 @@ bool idSoundSystemLocal::CheckDeviceAndRecoverIfNeeded()
return true; // we can't check or reset, just pretend everything is fine..
}
if ( s_alOutputLimiter.IsModified() || s_alHRTF.IsModified() ) {
common->Printf( "%s is modified, trying to reset OpenAL device to apply that change\n",
s_alOutputLimiter.IsModified() ? "s_alOutputLimiter" : "s_alHRTF" );
return ResetALDevice();
}
unsigned int curTime = Sys_Milliseconds();
if ( curTime - lastCheckTime >= 1000 ) // check once per second
{
@ -643,9 +741,7 @@ bool idSoundSystemLocal::CheckDeviceAndRecoverIfNeeded()
return false;
}
if ( alcResetDeviceSOFT( openalDevice, NULL ) ) {
common->Printf( "OpenAL: resetting device succeeded!\n" );
resetRetryCount = 0;
if ( ResetALDevice() ) {
return true;
}

View file

@ -1799,7 +1799,7 @@ void idSoundWorldLocal::AddChannelContribution( idSoundEmitterLocal *sound, idSo
// I guess this happens because the loud sounds mixed together are too loud so
// OpenAL just makes *everything* quiter or sth like that.
// See also https://github.com/dhewm/dhewm3/issues/179
if( soundSystemLocal.s_scaleDownAndClamp.GetBool() ) {
// First clamp it to 1.0 - that's done anyway when setting AL_GAIN below,
// for consistency it must be done before scaling, because many player-weapon
// sounds have a too high volume defined and only sound right (relative to
@ -1810,6 +1810,7 @@ void idSoundWorldLocal::AddChannelContribution( idSoundEmitterLocal *sound, idSo
}
volume *= 0.333f; // (0.333 worked fine, 0.5 didn't)
}
// global volume scale - DG: now done after clamping to 1.0, so reducing the
// global volume doesn't cause the different weapon volume issues described above