From b9bcc5e9015908df811401deb528aa5559fae3d9 Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 28 Sep 2026 23:27:11 -0400 Subject: [PATCH] Fix audio thread race on CVar reads during preset apply (#7265) --- soh/soh/Enhancements/audio/AudioSettings.cpp | 34 ++++++++++++++++++++ soh/soh/Enhancements/audio/AudioSettings.h | 17 ++++++++++ soh/src/code/audio_playback.c | 5 +-- soh/src/code/audio_synthesis.c | 3 +- 4 files changed, 56 insertions(+), 3 deletions(-) create mode 100644 soh/soh/Enhancements/audio/AudioSettings.cpp create mode 100644 soh/soh/Enhancements/audio/AudioSettings.h diff --git a/soh/soh/Enhancements/audio/AudioSettings.cpp b/soh/soh/Enhancements/audio/AudioSettings.cpp new file mode 100644 index 0000000000..f351f36485 --- /dev/null +++ b/soh/soh/Enhancements/audio/AudioSettings.cpp @@ -0,0 +1,34 @@ +#include "AudioSettings.h" + +#include + +#include + +#include "soh/cvar_prefixes.h" +#include "soh/ShipInit.hpp" + +static std::atomic sMasterVolume{ 40.0f / 100.0f }; +static std::atomic sOctaveDrop{ 0 }; +static std::atomic sMirroredWorld{ 0 }; + +float AudioSettings_GetMasterVolume(void) { + return sMasterVolume.load(std::memory_order_relaxed); +} + +int32_t AudioSettings_GetOctaveDrop(void) { + return sOctaveDrop.load(std::memory_order_relaxed); +} + +int32_t AudioSettings_GetMirroredWorld(void) { + return sMirroredWorld.load(std::memory_order_relaxed); +} + +static void RefreshAudioSettings() { + sMasterVolume.store((float)CVarGetInteger(CVAR_SETTING("Volume.Master"), 40) / 100.0f, std::memory_order_relaxed); + sOctaveDrop.store(CVarGetInteger(CVAR_AUDIO("ExperimentalOctaveDrop"), 0), std::memory_order_relaxed); + sMirroredWorld.store(CVarGetInteger(CVAR_ENHANCEMENT("MirroredWorld"), 0), std::memory_order_relaxed); +} + +static RegisterShipInitFunc initAudioSettings(RefreshAudioSettings, + { CVAR_SETTING("Volume.Master"), CVAR_AUDIO("ExperimentalOctaveDrop"), + CVAR_ENHANCEMENT("MirroredWorld") }); diff --git a/soh/soh/Enhancements/audio/AudioSettings.h b/soh/soh/Enhancements/audio/AudioSettings.h new file mode 100644 index 0000000000..7e2d99e141 --- /dev/null +++ b/soh/soh/Enhancements/audio/AudioSettings.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// cvars used by the audio thread should go here +// direct use can result in a race condition with the main thread +float AudioSettings_GetMasterVolume(void); +int32_t AudioSettings_GetOctaveDrop(void); +int32_t AudioSettings_GetMirroredWorld(void); + +#ifdef __cplusplus +} +#endif diff --git a/soh/src/code/audio_playback.c b/soh/src/code/audio_playback.c index ba250252ca..cffe35e1ca 100644 --- a/soh/src/code/audio_playback.c +++ b/soh/src/code/audio_playback.c @@ -1,5 +1,6 @@ #include "global.h" #include "soh/ResourceManagerHelpers.h" +#include "soh/Enhancements/audio/AudioSettings.h" extern bool gUseLegacySD; @@ -94,7 +95,7 @@ void Audio_InitNoteSub(Note* note, NoteSubEu* sub, NoteSubAttributes* attrs) { vel = 0.0f > vel ? 0.0f : vel; vel = 1.0f < vel ? 1.0f : vel; - float master_vol = (float)CVarGetInteger(CVAR_SETTING("Volume.Master"), 40) / 100.0f; + float master_vol = AudioSettings_GetMasterVolume(); sub->targetVolLeft = (s32)((vel * volLeft) * (0x1000 - 0.001f)) * master_vol; sub->targetVolRight = (s32)((vel * volRight) * (0x1000 - 0.001f)) * master_vol; @@ -120,7 +121,7 @@ void Audio_NoteSetResamplingRate(NoteSubEu* noteSubEu, f32 resamplingRateInput) } else { noteSubEu->bitField1.hasTwoParts = true; if (3.99996f < resamplingRateInput) { - if (CVarGetInteger(CVAR_AUDIO("ExperimentalOctaveDrop"), 0) || noteSubEu->bitField1.isSyntheticWave) { + if (AudioSettings_GetOctaveDrop() || noteSubEu->bitField1.isSyntheticWave) { resamplingRate = resamplingRateInput * 0.5f; while (resamplingRate > 1.99998f) { diff --git a/soh/src/code/audio_synthesis.c b/soh/src/code/audio_synthesis.c index 7b2db22679..4fed4e9396 100644 --- a/soh/src/code/audio_synthesis.c +++ b/soh/src/code/audio_synthesis.c @@ -1,6 +1,7 @@ #include #include "global.h" #include "soh/mixer.h" +#include "soh/Enhancements/audio/AudioSettings.h" #define DEFAULT_LEN_1CH 0x1A0 #define DEFAULT_LEN_2CH 0x340 @@ -654,7 +655,7 @@ Acmd* AudioSynth_DoOneAudioUpdate(s16* aiBuf, s32 aiBufLen, Acmd* cmd, s32 updat } updateIndex = aiBufLen * 2; - if (CVarGetInteger(CVAR_ENHANCEMENT("MirroredWorld"), 0)) { + if (AudioSettings_GetMirroredWorld()) { aInterleave(cmd++, DMEM_TEMP, DMEM_RIGHT_CH, DMEM_LEFT_CH, updateIndex); } else { aInterleave(cmd++, DMEM_TEMP, DMEM_LEFT_CH, DMEM_RIGHT_CH, updateIndex);