From 7e57816d3969a386ddc2c4f1f70179da5e01d61d Mon Sep 17 00:00:00 2001 From: Joe DiPrima Date: Tue, 28 Jul 2026 16:24:50 -0500 Subject: [PATCH] audio cut out in firefights because nobody ever asked OpenAL for more than 256 voices Field report from 2026-07-23 was "audio cutting in and out toward the end of the match". The night-5 logs say it precisely: 2017 ACQUIRE FAILED lines in a single match, and every last one of them at live=256. It is not a leak, which is where I started and where the code comments still pointed. The source census sits at a healthy 45-60 live, spikes during firefights, and drains right back down afterwards -- 226 back to 42 in one window -- so the engine's priority steal loop is doing exactly what it should: AudioSourceStop/SuspendMaintenance -> ReleaseChannels -> ReleaseSourceSet -> alDeleteSources, with the live counter following it down. The 2026-07-23 fix (a999e5c, deleting sources one at a time instead of the spec-atomic bulk call) was real and is what makes that drain work. It just was not the ceiling. The ceiling was ours. MakeAudioRenderer called alcCreateContext(device, NULL) -- no attribute list at all -- so OpenAL Soft applied its default budget of 256 mono sources. That number has nothing to do with the 1995 audio hardware; it is just what you get for not asking. Meanwhile a busy match logs about 7200 explosions, each spawning three DPLIndependantEffect voices, so the pool pins at the cap and every acquire during that window fails outright and drops its sound. The peak we actually observed was 226 against a 256 cap, i.e. the bursts were already scraping the ceiling. So ask. ALC_MONO_SOURCES at 1024 by default, BT_AUDIO_SOURCES to override for A/B testing, and read back what the driver GRANTED rather than assuming the request was honoured. That last part earns its keep: asking for 64 grants 240, because OpenAL Soft has a floor of its own -- which is also the reason the NULL default landed on 256 in the first place. requested 1024 -> granted 1024 requested 2048 -> granted 2048 requested 64 -> granted 240 (driver floor) Costs mixing headroom, not hardware voices -- OpenAL Soft mixes in software and only touches voices that are actually sounding. Co-Authored-By: Claude Fable 5 --- context/wintesla-port.md | 15 +++++++++++++ engine/MUNGA_L4/L4AUDRND.cpp | 41 +++++++++++++++++++++++++++++++++++- 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/context/wintesla-port.md b/context/wintesla-port.md index 0260f8d..269263a 100644 --- a/context/wintesla-port.md +++ b/context/wintesla-port.md @@ -16,6 +16,21 @@ on top of it. Full detail: `docs/PROGRESS_LOG.md §5b, §8`. ## What WinTesla already did (do NOT rebuild) - **Renderer bypass: DONE** — `MUNGA_L4/L4D3D.cpp` + `DXUtils` replace libDPL / the IG board with Direct3D9. No `libdpl.lib` in the tree. (The early from-scratch D3D9 viewer was retired and removed.) [T1] +- **Audio SOURCE BUDGET — dropouts in heavy combat, FIXED 2026-07-28** [T2]. Field report "audio + cutting in and out toward the end of the match"; night-5 logs carried **2017 `ACQUIRE FAILED` lines in + a single match, every one at `live=256`**. It is **NOT a leak** — the census sits at a healthy 45-60 + live, spikes during firefights, and drains straight back, so the engine's priority steal loop + (`AudioSourceStop`/`SuspendMaintenance` → `ReleaseChannels` → `ReleaseSourceSet` → + `alDeleteSources`) works. The cause: `L4AudioRenderer` called `alcCreateContext(device, NULL)` with + **no attribute list**, so OpenAL Soft applied its DEFAULT budget of 256 mono sources — an OpenAL + default, unrelated to the 1995 audio hardware. A busy match logs ~7200 explosions × 3 + `DPLIndependantEffect` voices each; while the pool is pinned every acquire fails and that sound is + dropped outright. FIX: pass `ALC_MONO_SOURCES` (default **1024**, `BT_AUDIO_SOURCES` overrides) and + **read back what was granted** — a request is not a promise. Measured: 1024→1024, 2048→2048, and + 64→**240** (OpenAL Soft's own floor, which is why the NULL default landed at 256). Observed bursts + peaked at 226 against the old 256 cap, i.e. they were clipping the ceiling. Note the earlier + 2026-07-23 fix (`a999e5c`, per-source delete instead of the spec-atomic bulk `alDeleteSources`) was + real and is still needed — it is what makes the pool drain; it just was not the ceiling problem. - **Audio: BACKEND now REAL + soundbank loaded; only game-triggering remains** [T2, updated 2026-07-15]. OpenAL replaces the HMI "SOS" engine and the renderer→device→buffer→source→play chain IS all implemented (`L4AUDRND`/`L4AUDRES`/`L4AUDLVL`; `PatchLevelOfDetail::PlayNote` really calls diff --git a/engine/MUNGA_L4/L4AUDRND.cpp b/engine/MUNGA_L4/L4AUDRND.cpp index 09abb06..386aee4 100644 --- a/engine/MUNGA_L4/L4AUDRND.cpp +++ b/engine/MUNGA_L4/L4AUDRND.cpp @@ -380,8 +380,47 @@ void ALCdevice *device = alcOpenDevice(NULL); if (device) { - ALCcontext *context = alcCreateContext(device,NULL); + // AUDIO DROPOUTS IN HEAVY COMBAT (field: "audio cutting in and out toward + // the end of the match"; night-5 logs show 2017 ACQUIRE FAILED lines in a + // single match, every one of them at live=256). + // + // We were passing NULL for the attribute list, so OpenAL Soft applied its + // DEFAULT budget of 256 mono sources -- an OpenAL default, nothing to do + // with the 1995 audio hardware. The pool is not leaking: the night-5 + // census sits at a healthy 45-60 live, spikes to the 256 ceiling during + // firefights, and drains straight back afterwards, so the engine's + // priority steal loop (AudioSourceStop/SuspendMaintenance -> ReleaseChannels + // -> ReleaseSourceSet) is doing its job. It simply cannot keep up with the + // burst: a busy match logs ~7200 explosions, each spawning three + // DPLIndependantEffect voices, and while the pool is pinned every new + // acquire fails outright and that sound is silently dropped. + // + // Ask for a budget that fits the content. OpenAL Soft mixes in software, + // so this costs mixing headroom, not hardware voices, and it clamps to what + // it can honour -- hence the read-back below rather than an assumption. + // BT_AUDIO_SOURCES overrides for A/B testing. + ALCint monoWanted = 1024; + if (const char *sv = getenv("BT_AUDIO_SOURCES")) + { + int n = atoi(sv); + if (n >= 64 && n <= 4096) + monoWanted = n; + } + ALCint attrs[5]; + attrs[0] = ALC_MONO_SOURCES; attrs[1] = monoWanted; + attrs[2] = ALC_STEREO_SOURCES; attrs[3] = 16; + attrs[4] = 0; + ALCcontext *context = alcCreateContext(device, attrs); alcMakeContextCurrent(context); + // Report what the driver actually GRANTED -- a request is not a promise. + { + ALCint monoGot = 0, stereoGot = 0; + alcGetIntegerv(device, ALC_MONO_SOURCES, 1, &monoGot); + alcGetIntegerv(device, ALC_STEREO_SOURCES, 1, &stereoGot); + DEBUG_STREAM << "[audio] source budget: requested mono=" << monoWanted + << " granted mono=" << monoGot << " stereo=" << stereoGot + << std::endl << std::flush; + } // Master volume: AL_GAIN on the listener scales EVERY source. Default 0.6 // (the raw samples are hot). Priority: BT_AUDIO_VOLUME env > // content\volume.cfg (written by the runtime -/+ keys, issue #26) > 0.6.