From 2c58845d284b3376222ce757dbafe1aa2360a179 Mon Sep 17 00:00:00 2001 From: Daniel Gibson Date: Sat, 1 Jun 2024 07:09:42 +0200 Subject: [PATCH] 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) --- neo/sound/snd_local.h | 45 +++++++++++++++ neo/sound/snd_system.cpp | 122 ++++++++++++++++++++++++++++++++++----- neo/sound/snd_world.cpp | 19 +++--- 3 files changed, 164 insertions(+), 22 deletions(-) diff --git a/neo/sound/snd_local.h b/neo/sound/snd_local.h index c2f5baf8..d07ee0b3 100644 --- a/neo/sound/snd_local.h +++ b/neo/sound/snd_local.h @@ -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; diff --git a/neo/sound/snd_system.cpp b/neo/sound/snd_system.cpp index 3b070a8a..fb04f7f1 100644 --- a/neo/sound/snd_system.cpp +++ b/neo/sound/snd_system.cpp @@ -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; } diff --git a/neo/sound/snd_world.cpp b/neo/sound/snd_world.cpp index 43dfa7c3..a346d6e4 100644 --- a/neo/sound/snd_world.cpp +++ b/neo/sound/snd_world.cpp @@ -1799,18 +1799,19 @@ 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 + // other weapons) when clamped + // see https://github.com/dhewm/dhewm3/issues/326#issuecomment-1366833004 + if(volume > 1.0f) { + volume = 1.0f; + } - // 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 - // other weapons) when clamped - // see https://github.com/dhewm/dhewm3/issues/326#issuecomment-1366833004 - if(volume > 1.0f) { - volume = 1.0f; + volume *= 0.333f; // (0.333 worked fine, 0.5 didn't) } - 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 volume *= soundSystemLocal.dB2Scale( idSoundSystemLocal::s_volume.GetFloat() );