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 <noreply@anthropic.com>
This commit is contained in:
Joe DiPrima
2026-07-28 16:24:50 -05:00
co-authored by Claude Fable 5
parent 819772f974
commit 7e57816d39
2 changed files with 55 additions and 1 deletions
+15
View File
@@ -16,6 +16,21 @@ on top of it. Full detail: `docs/PROGRESS_LOG.md §5b, §8`.
## What WinTesla already did (do NOT rebuild) ## What WinTesla already did (do NOT rebuild)
- **Renderer bypass: DONE** — `MUNGA_L4/L4D3D.cpp` + `DXUtils` replace libDPL / the IG board with - **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] 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 - **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 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 IS all implemented (`L4AUDRND`/`L4AUDRES`/`L4AUDLVL`; `PatchLevelOfDetail::PlayNote` really calls
+40 -1
View File
@@ -380,8 +380,47 @@ void
ALCdevice *device = alcOpenDevice(NULL); ALCdevice *device = alcOpenDevice(NULL);
if (device) 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); 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 // Master volume: AL_GAIN on the listener scales EVERY source. Default 0.6
// (the raw samples are hot). Priority: BT_AUDIO_VOLUME env > // (the raw samples are hot). Priority: BT_AUDIO_VOLUME env >
// content\volume.cfg (written by the runtime -/+ keys, issue #26) > 0.6. // content\volume.cfg (written by the runtime -/+ keys, issue #26) > 0.6.