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>
1571 lines
43 KiB
C++
1571 lines
43 KiB
C++
#include <cstdlib>
|
|
#include "mungal4.h"
|
|
#pragma hdrstop
|
|
|
|
#include "l4audrnd.h"
|
|
#include "..\munga\notation.h"
|
|
#include "openal/alc.h"
|
|
|
|
//
|
|
//#############################################################################
|
|
// L4AudioRenderer
|
|
//#############################################################################
|
|
//
|
|
L4AudioRenderer::L4AudioRenderer(
|
|
RendererRate render_rate,
|
|
Logical mission_review_mode
|
|
):
|
|
AudioRenderer(render_rate),
|
|
runningAudioSourceSocket(NULL, False),
|
|
dormantAudioSourceSocket(NULL),
|
|
mixingAudioSourceSocket(NULL, False)
|
|
{
|
|
missionReviewMode = mission_review_mode;
|
|
nextCalculateMixFrame = NullAudioFrameCount;
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// ~L4AudioRenderer
|
|
//#############################################################################
|
|
//
|
|
L4AudioRenderer::~L4AudioRenderer()
|
|
{
|
|
//Close OpenAL
|
|
ALCcontext *context = alcGetCurrentContext();
|
|
ALCdevice *device = alcGetContextsDevice(context);
|
|
alcMakeContextCurrent(NULL);
|
|
alcDestroyContext(context);
|
|
alcCloseDevice(device);
|
|
|
|
|
|
//
|
|
//----------------------------------------------------------------------
|
|
// Verify that all of entities have become uninteresting
|
|
//----------------------------------------------------------------------
|
|
//
|
|
#if DEBUG_LEVEL>0
|
|
{
|
|
RunningSourceIterator iterator(&runningAudioSourceSocket);
|
|
Verify(iterator.GetSize() == 0);
|
|
}
|
|
{
|
|
DormantSourceIterator iterator(&dormantAudioSourceSocket);
|
|
Verify(iterator.GetSize() == 0);
|
|
}
|
|
#endif
|
|
|
|
//
|
|
//----------------------------------------------------------------------
|
|
// Unload resources
|
|
//----------------------------------------------------------------------
|
|
//
|
|
Check(&audioResourceManager);
|
|
// audioResourceManager.UnloadResources(&audioHardware);
|
|
|
|
//
|
|
//----------------------------------------------------------------------
|
|
// Close audio hardware
|
|
//----------------------------------------------------------------------
|
|
//
|
|
// Check(&audioHardware);
|
|
// audioHardware.Close();
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// TestInstance
|
|
//#############################################################################
|
|
//
|
|
Logical
|
|
L4AudioRenderer::TestInstance() const
|
|
{
|
|
AudioRenderer::TestInstance();
|
|
|
|
Check(&audioHardware);
|
|
Check(&runningAudioSourceSocket);
|
|
Check(&dormantAudioSourceSocket);
|
|
Check(&mixingAudioSourceSocket);
|
|
|
|
return True;
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// Initialize
|
|
//#############################################################################
|
|
//
|
|
void
|
|
L4AudioRenderer::Initialize()
|
|
{
|
|
Check(this);
|
|
if (getenv("BT_AUDIO_LOG")) DEBUG_STREAM << "[audio] L4AudioRenderer::Initialize ENTERED\n" << std::flush;
|
|
|
|
//
|
|
//----------------------------------------------------------------------
|
|
// Call inherited method
|
|
//----------------------------------------------------------------------
|
|
//
|
|
AudioRenderer::Initialize();
|
|
|
|
//
|
|
//----------------------------------------------------------------------
|
|
// Get the audio head
|
|
//----------------------------------------------------------------------
|
|
//
|
|
AudioHead *audio_head = GetAudioHead();
|
|
Check(audio_head);
|
|
|
|
//
|
|
//----------------------------------------------------------------------
|
|
// Open the audio renderer notation file
|
|
//----------------------------------------------------------------------
|
|
//
|
|
int ret;
|
|
CString audio_file_name;
|
|
|
|
if (missionReviewMode)
|
|
{
|
|
audio_file_name = "audio\\audiomr.ini";
|
|
}
|
|
else
|
|
{
|
|
audio_file_name = "audio\\audio.ini";
|
|
}
|
|
|
|
NotationFile notation_file(audio_file_name);
|
|
Check(¬ation_file);
|
|
if (getenv("BT_AUDIO_LOG")) DEBUG_STREAM << "[audio] notation file opened; about to read scalars\n" << std::flush;
|
|
|
|
//
|
|
//----------------------------------------------------------------------
|
|
// Define clipping sphere (m)
|
|
//----------------------------------------------------------------------
|
|
//
|
|
Scalar clipping_radius;
|
|
|
|
ret = notation_file.GetEntry(
|
|
"AudioRenderer",
|
|
"clipping_radius",
|
|
&clipping_radius
|
|
);
|
|
if (!ret)
|
|
{
|
|
Fail("L4AudioRenderer::Initialize - clipping_radius not defined");
|
|
}
|
|
Check(audio_head);
|
|
audio_head->DefineClippingSphere(clipping_radius);
|
|
|
|
//
|
|
//----------------------------------------------------------------------
|
|
// Distance to speakers or width of head entity (m)
|
|
//----------------------------------------------------------------------
|
|
//
|
|
Scalar distance_between_ears;
|
|
ret = notation_file.GetEntry(
|
|
"AudioRenderer",
|
|
"distance_between_ears",
|
|
&distance_between_ears
|
|
);
|
|
if (!ret)
|
|
{
|
|
Fail("L4AudioRenderer::Initialize - distance_between_ears not defined");
|
|
}
|
|
Check(audio_head);
|
|
audio_head->SetDistanceBetweenEars(distance_between_ears);
|
|
|
|
//
|
|
//----------------------------------------------------------------------
|
|
// Define amplitude rolloff characteristics
|
|
//----------------------------------------------------------------------
|
|
//
|
|
Scalar amplitude_rolloff;
|
|
Scalar amplitude_rolloff_knee;
|
|
Scalar amplitude_rolloff_distance_scale;
|
|
|
|
ret = notation_file.GetEntry(
|
|
"AudioRenderer",
|
|
"amplitude_rolloff",
|
|
&litude_rolloff
|
|
);
|
|
if (!ret)
|
|
{
|
|
Fail("L4AudioRenderer::Initialize - amplitude_rolloff not defined");
|
|
}
|
|
ret = notation_file.GetEntry(
|
|
"AudioRenderer",
|
|
"amplitude_rolloff_distance_scale",
|
|
&litude_rolloff_distance_scale
|
|
);
|
|
if (!ret)
|
|
{
|
|
Fail("L4AudioRenderer::Initialize - amplitude_rolloff_distance_scale not defined");
|
|
}
|
|
ret = notation_file.GetEntry(
|
|
"AudioRenderer",
|
|
"amplitude_rolloff_knee",
|
|
&litude_rolloff_knee
|
|
);
|
|
if (!ret)
|
|
{
|
|
Fail("L4AudioRenderer::Initialize - amplitude_rolloff_knee not defined");
|
|
}
|
|
|
|
Check(audio_head);
|
|
audio_head->ControlAmplitudeRollOff(
|
|
amplitude_rolloff,
|
|
amplitude_rolloff_knee,
|
|
amplitude_rolloff_distance_scale
|
|
);
|
|
|
|
//
|
|
//----------------------------------------------------------------------
|
|
// Define high frequency rolloff characteristics
|
|
//----------------------------------------------------------------------
|
|
//
|
|
Scalar high_frequency_rolloff;
|
|
Scalar high_frequency_rolloff_knee;
|
|
Scalar high_frequency_rolloff_distance_scale;
|
|
|
|
ret = notation_file.GetEntry(
|
|
"AudioRenderer",
|
|
"high_frequency_rolloff",
|
|
&high_frequency_rolloff
|
|
);
|
|
if (!ret)
|
|
{
|
|
Fail("L4AudioRenderer::Initialize - high_frequency_rolloff not defined");
|
|
}
|
|
ret = notation_file.GetEntry(
|
|
"AudioRenderer",
|
|
"high_frequency_rolloff_knee",
|
|
&high_frequency_rolloff_knee
|
|
);
|
|
if (!ret)
|
|
{
|
|
Fail("L4AudioRenderer::Initialize - high_frequency_rolloff_knee not defined");
|
|
}
|
|
ret = notation_file.GetEntry(
|
|
"AudioRenderer",
|
|
"high_frequency_rolloff_distance_scale",
|
|
&high_frequency_rolloff_distance_scale
|
|
);
|
|
if (!ret)
|
|
{
|
|
Fail("L4AudioRenderer::Initialize - high_frequency_rolloff_distance_scale not defined");
|
|
}
|
|
|
|
Check(audio_head);
|
|
audio_head->ControlHighFrequencyRollOff(
|
|
high_frequency_rolloff,
|
|
high_frequency_rolloff_knee,
|
|
high_frequency_rolloff_distance_scale
|
|
);
|
|
|
|
//
|
|
//----------------------------------------------------------------------
|
|
// Define Doppler characterisitics
|
|
//----------------------------------------------------------------------
|
|
//
|
|
Scalar doppler_range;
|
|
Scalar speed_of_sound;
|
|
|
|
ret = notation_file.GetEntry(
|
|
"AudioRenderer",
|
|
"doppler_range",
|
|
&doppler_range
|
|
);
|
|
if (!ret)
|
|
{
|
|
Fail("L4AudioRenderer::Initialize - doppler_range not defined");
|
|
}
|
|
ret = notation_file.GetEntry(
|
|
"AudioRenderer",
|
|
"speed_of_sound",
|
|
&speed_of_sound
|
|
);
|
|
if (!ret)
|
|
{
|
|
Fail("L4AudioRenderer::Initialize - speed_of_sound not defined");
|
|
}
|
|
|
|
Check(audio_head);
|
|
audio_head->ControlDopplerEffect(
|
|
doppler_range,
|
|
speed_of_sound
|
|
);
|
|
|
|
//
|
|
//----------------------------------------------------------------------
|
|
// Define source compression characterisitics
|
|
//----------------------------------------------------------------------
|
|
//
|
|
Scalar compression_exponent;
|
|
Scalar compression_scale;
|
|
|
|
ret = notation_file.GetEntry(
|
|
"AudioRenderer",
|
|
"compression_exponent",
|
|
&compression_exponent
|
|
);
|
|
if (!ret)
|
|
{
|
|
Fail("L4AudioRenderer::Initialize - compression_exponent not defined");
|
|
}
|
|
ret = notation_file.GetEntry(
|
|
"AudioRenderer",
|
|
"compression_scale",
|
|
&compression_scale
|
|
);
|
|
if (!ret)
|
|
{
|
|
Fail("L4AudioRenderer::Initialize - compression_scale not defined");
|
|
}
|
|
|
|
Check(audio_head);
|
|
audio_head->ControlSourceCompression(
|
|
compression_exponent,
|
|
compression_scale
|
|
);
|
|
|
|
//
|
|
//----------------------------------------------------------------------
|
|
// Set ITD difference
|
|
//----------------------------------------------------------------------
|
|
//
|
|
Scalar itd_difference;
|
|
|
|
ret = notation_file.GetEntry(
|
|
"AudioRenderer",
|
|
"itd_difference",
|
|
&itd_difference
|
|
);
|
|
if (!ret)
|
|
{
|
|
Fail("L4AudioRenderer::Initialize - itd_difference not defined");
|
|
}
|
|
|
|
Check(audio_head);
|
|
audio_head->SetITDDifference(itd_difference);
|
|
|
|
//
|
|
//----------------------------------------------------------------------
|
|
// Set global reverb scale
|
|
//----------------------------------------------------------------------
|
|
//
|
|
Scalar global_reverb_scale;
|
|
|
|
ret = notation_file.GetEntry(
|
|
"AudioRenderer",
|
|
"global_reverb_scale",
|
|
&global_reverb_scale
|
|
);
|
|
if (!ret)
|
|
{
|
|
Fail("L4AudioRenderer::Initialize - global_reverb_scale not defined");
|
|
}
|
|
|
|
Check(audio_head);
|
|
audio_head->SetGlobalReverbScale(global_reverb_scale);
|
|
|
|
//
|
|
//----------------------------------------------------------------------
|
|
// Initialize the audio hardware
|
|
//----------------------------------------------------------------------
|
|
//
|
|
// Check(&audioHardware);
|
|
// audioHardware.Initialize();
|
|
|
|
//Start OpenAL
|
|
ALCdevice *device = alcOpenDevice(NULL);
|
|
if (device)
|
|
{
|
|
// 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.
|
|
float masterVol = 0.6f;
|
|
{
|
|
FILE *cfg = fopen("volume.cfg", "rt");
|
|
if (cfg != NULL)
|
|
{
|
|
float f = -1.0f;
|
|
if (fscanf(cfg, "%f", &f) == 1 && f >= 0.0f && f <= 1.5f)
|
|
masterVol = f;
|
|
fclose(cfg);
|
|
}
|
|
}
|
|
if (const char *v = getenv("BT_AUDIO_VOLUME")) { float f = (float)atof(v); if (f >= 0.0f) masterVol = f; }
|
|
extern float gBTMasterVolume;
|
|
gBTMasterVolume = masterVol;
|
|
alListenerf(AL_GAIN, masterVol);
|
|
if (getenv("BT_AUDIO_LOG")) DEBUG_STREAM << "[audio] OpenAL device OPENED + context current; master gain=" << masterVol << "\n" << std::flush;
|
|
// (task #50, AUDIO_FIDELITY F9/F11) EFX bridge: the authored lowpass
|
|
// chains + the wet-exterior/dry-cockpit reverb split, at the authentic
|
|
// AUDIO.INI global_reverb_scale read above.
|
|
{
|
|
extern bool EFX_Initialize(float global_reverb_scale);
|
|
EFX_Initialize(global_reverb_scale);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (getenv("BT_AUDIO_LOG")) DEBUG_STREAM << "[audio] OpenAL device FAILED to open (alcOpenDevice NULL)\n" << std::flush;
|
|
}
|
|
|
|
//
|
|
//----------------------------------------------------------------------
|
|
// Preload resources
|
|
//----------------------------------------------------------------------
|
|
//
|
|
Check(&audioResourceManager);
|
|
audioResourceManager.PreloadResources(¬ation_file);
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// NotifyOfNewInterestingEntity
|
|
//#############################################################################
|
|
//
|
|
void
|
|
L4AudioRenderer::NotifyOfNewInterestingEntity(Entity *interesting_entity)
|
|
{
|
|
SET_AUDIO_RENDERER();
|
|
SET_AUDIO_RENDERER_CREATE_OBJECTS();
|
|
|
|
Check(this);
|
|
Check(interesting_entity);
|
|
|
|
Entity *linked_entity = GetLinkedEntity();
|
|
Check(linked_entity);
|
|
|
|
AudioRepresentation audio_representation =
|
|
(AudioRepresentation)interesting_entity->GetAudioRepresentation(
|
|
linked_entity
|
|
);
|
|
|
|
Check(&audioResourceManager);
|
|
audioResourceManager.CreateEntityAudioObjects(
|
|
interesting_entity,
|
|
audio_representation
|
|
);
|
|
|
|
CLEAR_AUDIO_RENDERER_CREATE_OBJECTS();
|
|
CLEAR_AUDIO_RENDERER();
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// NotifyOfBecomingUninterestingEntity
|
|
//#############################################################################
|
|
//
|
|
void
|
|
L4AudioRenderer::NotifyOfBecomingUninterestingEntity(
|
|
Entity *uninteresting_entity
|
|
)
|
|
{
|
|
SET_AUDIO_RENDERER();
|
|
SET_AUDIO_RENDERER_DESTROY_OBJECTS();
|
|
|
|
Check(this);
|
|
Check(uninteresting_entity);
|
|
|
|
Check(&audioResourceManager);
|
|
audioResourceManager.DestroyEntityAudioObjects(uninteresting_entity);
|
|
|
|
CLEAR_AUDIO_RENDERER_DESTROY_OBJECTS();
|
|
CLEAR_AUDIO_RENDERER();
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// LoadMissionImplementation
|
|
//#############################################################################
|
|
//
|
|
void
|
|
L4AudioRenderer::LoadMissionImplementation(Mission*)
|
|
{
|
|
Check(this);
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// ShutdownImplementation
|
|
//#############################################################################
|
|
//
|
|
void
|
|
L4AudioRenderer::ShutdownImplementation()
|
|
{
|
|
Check(this);
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// SuspendImplementation
|
|
//#############################################################################
|
|
//
|
|
void
|
|
L4AudioRenderer::SuspendImplementation()
|
|
{
|
|
Check(this);
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// ResumeImplementation
|
|
//#############################################################################
|
|
//
|
|
void
|
|
L4AudioRenderer::ResumeImplementation()
|
|
{
|
|
Check(this);
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// StartRequest
|
|
//#############################################################################
|
|
//
|
|
void
|
|
L4AudioRenderer::StartRequest(L4AudioSource *audio_source)
|
|
{
|
|
Check(this);
|
|
Check(audio_source);
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Verify that the source is stopped
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
Verify(audio_source->GetAudioSourceState() == StoppedAudioSourceState);
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Request audio resources
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
Logical
|
|
resources_available;
|
|
/* AudioChannelSet
|
|
channel_set_result;*/
|
|
SourceSet *source_result = audio_source->GetAudioChannelSet();
|
|
|
|
resources_available =
|
|
RequestAudioResources(
|
|
audio_source,
|
|
source_result
|
|
);
|
|
if (!resources_available)
|
|
{
|
|
// Audio-dropout fix: a failed acquisition can leave a PARTIAL set
|
|
// (alGenSources succeeded for some slots before the pool ran dry).
|
|
// A dropped transient never plays and nothing else ever released it,
|
|
// so the partial sources dangled forever -- once the pool exhausted
|
|
// ONCE, every drop leaked a little more and it never recovered.
|
|
audio_source->ReleaseChannels();
|
|
if (audio_source->GetAudioRenderType() == SustainedAudioRenderType)
|
|
{
|
|
//
|
|
// Set state to dormant and add the source to the dormant list
|
|
//
|
|
audio_source->AssignAudioSourceState(DormantAudioSourceState);
|
|
dormantAudioSourceSocket.Add(audio_source);
|
|
}
|
|
return;
|
|
}
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Start the audio source
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
|
|
//
|
|
// Update source spatial model
|
|
// Set channel set of source
|
|
// Source start implementation
|
|
// Set state of source to running
|
|
//
|
|
audio_source->UpdateSpatialModel(GetAudioHead());
|
|
audio_source->SetAudioChannelSet(*source_result);
|
|
audio_source->StartImplementation();
|
|
audio_source->AssignAudioSourceState(RunningAudioSourceState);
|
|
|
|
//
|
|
// Add source to the running list and the mixing list
|
|
//
|
|
runningAudioSourceSocket.AddValue(
|
|
audio_source,
|
|
audio_source->CalculateAudioWeighting()
|
|
);
|
|
if (
|
|
audio_source->GetAudioSourceMixPresence() !=
|
|
ManualAudioSourceMixPresence
|
|
)
|
|
{
|
|
mixingAudioSourceSocket.AddValue(
|
|
audio_source,
|
|
audio_source->GetAudioSourceMixPresence()
|
|
);
|
|
}
|
|
#ifdef LAB_ONLY
|
|
sourceStartCount++;
|
|
#endif
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// StopRequest
|
|
//#############################################################################
|
|
//
|
|
void
|
|
L4AudioRenderer::StopRequest(L4AudioSource *audio_source)
|
|
{
|
|
Check(this);
|
|
Check(audio_source);
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Verify that the source is not stopped
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
Verify(audio_source->GetAudioSourceState() != StoppedAudioSourceState);
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// If state of the source is dormant or suspended
|
|
// Then set source to stop state and remove from dormant list
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
if (
|
|
audio_source->GetAudioSourceState() == DormantAudioSourceState ||
|
|
audio_source->GetAudioSourceState() == SuspendedAudioSourceState
|
|
)
|
|
{
|
|
//
|
|
// Set state of source to stopped
|
|
// Verify that source is in dormant list
|
|
// Remove source from dormant list
|
|
// Verify that source is not in dormant list
|
|
//
|
|
audio_source->AssignAudioSourceState(StoppedAudioSourceState);
|
|
#if DEBUG_LEVEL>0
|
|
{
|
|
DormantSourceIterator iterator(&dormantAudioSourceSocket);
|
|
Verify(iterator.IsPlugMember(audio_source) == True);
|
|
}
|
|
#endif
|
|
PlugIterator remover(audio_source);
|
|
remover.RemoveSocket(&dormantAudioSourceSocket);
|
|
#if DEBUG_LEVEL>0
|
|
{
|
|
DormantSourceIterator iterator(&dormantAudioSourceSocket);
|
|
Verify(iterator.IsPlugMember(audio_source) == False);
|
|
}
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Stop the source
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
AudioSourceStopMaintenance(audio_source);
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// ExecuteImplementation
|
|
//#############################################################################
|
|
//
|
|
void
|
|
L4AudioRenderer::ExecuteImplementation(
|
|
RendererComplexity complexity_update,
|
|
RendererOrigin::InterestingEntityIterator *iterator
|
|
)
|
|
{
|
|
SET_AUDIO_RENDERER();
|
|
SET_AUDIO_RENDERER_EXECUTE();
|
|
|
|
Check(this);
|
|
|
|
AudioRenderer::ExecuteImplementation(complexity_update, iterator);
|
|
CalculateMix();
|
|
RunningSourceCheckup();
|
|
// RunningAudioSourceSort();
|
|
DormantSourceCheckup();
|
|
|
|
CLEAR_AUDIO_RENDERER_EXECUTE();
|
|
CLEAR_AUDIO_RENDERER();
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// CalculateMix
|
|
//#############################################################################
|
|
//
|
|
void
|
|
L4AudioRenderer::CalculateMix()
|
|
{
|
|
SET_AUDIO_CALCULATE_MIX();
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Is this the next calculate mix frame
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
#if 0
|
|
if (nextCalculateMixFrame > GetAudioFrameCount())
|
|
{
|
|
CLEAR_AUDIO_CALCULATE_MIX();
|
|
return;
|
|
}
|
|
nextCalculateMixFrame = GetAudioFrameCount() + DefaultAudioFrameDelay;
|
|
#endif
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Calculate the compression volume scaling
|
|
//
|
|
// For each source, calculate the amount to scale its volume by
|
|
// according to those sources which have a higher mix presence
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
{
|
|
AudioControlValue
|
|
compression_scale = 1.0f;
|
|
AudioControlValue
|
|
compression_effect = 0.0f;
|
|
AudioSourceMixPresence
|
|
current_mix_presence = MedAudioSourceMixPresence;
|
|
|
|
MixingSourceIterator
|
|
iterator(&mixingAudioSourceSocket);
|
|
L4AudioSource
|
|
*audio_source;
|
|
|
|
Check(GetAudioHead());
|
|
Scalar comp_scale = GetAudioHead()->GetSourceCompressionScale();
|
|
Scalar comp_exp = GetAudioHead()->GetSourceCompressionExponent();
|
|
|
|
//
|
|
// Get mix presence of the first source
|
|
//
|
|
if ((audio_source = iterator.GetCurrent()) != NULL)
|
|
{
|
|
Check(audio_source);
|
|
current_mix_presence = audio_source->GetAudioSourceMixPresence();
|
|
}
|
|
|
|
//
|
|
//-----------------------------------------------------------------------
|
|
// For each audio source, calculate the compression scale
|
|
//-----------------------------------------------------------------------
|
|
//
|
|
while ((audio_source = iterator.ReadAndNext()) != NULL)
|
|
{
|
|
Check(audio_source);
|
|
|
|
//
|
|
//--------------------------------------------------------------------
|
|
// If the mix presence of this source is greater then the last
|
|
// source then we have stepped to a lower mix presence.
|
|
//--------------------------------------------------------------------
|
|
//
|
|
if (
|
|
current_mix_presence !=
|
|
audio_source->GetAudioSourceMixPresence()
|
|
)
|
|
{
|
|
Verify(
|
|
current_mix_presence <
|
|
audio_source->GetAudioSourceMixPresence()
|
|
);
|
|
current_mix_presence = audio_source->GetAudioSourceMixPresence();
|
|
|
|
//
|
|
// Calculate the compression scale for this mix presence
|
|
//
|
|
// scale = 1 - ((comp_scale*x)^comp_exp)
|
|
//
|
|
compression_scale =
|
|
1.0f - pow(comp_scale * compression_effect, comp_exp);
|
|
Verify(compression_scale >= 0.0f && compression_scale <= 1.0f);
|
|
}
|
|
|
|
//
|
|
//--------------------------------------------------------------------
|
|
// Set the compression scale for this source
|
|
//--------------------------------------------------------------------
|
|
//
|
|
audio_source->SetVolumeCompressionScale(compression_scale);
|
|
|
|
//
|
|
//--------------------------------------------------------------------
|
|
// Calculate the compression effect for this source
|
|
//--------------------------------------------------------------------
|
|
//
|
|
AudioControlValue source_compression_effect;
|
|
|
|
source_compression_effect =
|
|
audio_source->CalculateSourceCompressionEffect();
|
|
Verify(
|
|
source_compression_effect >= 0.0f &&
|
|
source_compression_effect <= 1.0f
|
|
);
|
|
|
|
//
|
|
//--------------------------------------------------------------------
|
|
// Update the compression effect for this mix presence
|
|
//--------------------------------------------------------------------
|
|
//
|
|
compression_effect =
|
|
Max(compression_effect, source_compression_effect);
|
|
Verify(compression_effect >= 0.0f && compression_effect <= 1.0f);
|
|
|
|
#if 0
|
|
Tell(
|
|
"compression_effect " << compression_effect <<
|
|
" : compression_scale " << compression_scale << "\n"
|
|
);
|
|
#endif
|
|
}
|
|
}
|
|
CLEAR_AUDIO_CALCULATE_MIX();
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// RunningSourceCheckup
|
|
//#############################################################################
|
|
//
|
|
void
|
|
L4AudioRenderer::RunningSourceCheckup()
|
|
{
|
|
SET_AUDIO_RUNNING_SOURCES();
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Iterate through all running sources
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
ChainOf<L4AudioSource*>
|
|
resort_socket(NULL);
|
|
RunningSourceIterator
|
|
running_iterator(&runningAudioSourceSocket);
|
|
L4AudioSource
|
|
*audio_source;
|
|
|
|
while ((audio_source = running_iterator.GetCurrent()) != NULL)
|
|
{
|
|
Check(audio_source);
|
|
|
|
//
|
|
//-----------------------------------------------------------------------
|
|
// If the source does not require maintenance then skip it
|
|
//-----------------------------------------------------------------------
|
|
//
|
|
if (!audio_source->RequiresMaintenance(GetAudioHead()))
|
|
{
|
|
running_iterator.Next();
|
|
continue;
|
|
}
|
|
|
|
//Update object position
|
|
|
|
|
|
//
|
|
//-----------------------------------------------------------------------
|
|
// If the source is clipped then either stop or suspend
|
|
//-----------------------------------------------------------------------
|
|
//
|
|
if (audio_source->IsAudioSourceClipped(GetAudioHead()))
|
|
{
|
|
if (audio_source->GetAudioRenderType() == TransientAudioRenderType)
|
|
{
|
|
AudioSourceStopMaintenance(audio_source);
|
|
}
|
|
else
|
|
{
|
|
Verify(
|
|
audio_source->GetAudioRenderType() ==
|
|
SustainedAudioRenderType
|
|
);
|
|
AudioSourceSuspendMaintenance(audio_source);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
//
|
|
//-----------------------------------------------------------------------
|
|
// If the source is finished playing then stop it
|
|
//-----------------------------------------------------------------------
|
|
//
|
|
if (audio_source->IsFinishedPlaying())
|
|
{
|
|
AudioSourceStopMaintenance(audio_source);
|
|
continue;
|
|
}
|
|
|
|
|
|
|
|
//
|
|
//-----------------------------------------------------------------------
|
|
// Execute
|
|
//-----------------------------------------------------------------------
|
|
//
|
|
SET_AUDIO_EXECUTE_SOURCES();
|
|
audio_source->Execute();
|
|
CLEAR_AUDIO_EXECUTE_SOURCES();
|
|
|
|
//
|
|
//-----------------------------------------------------------------------
|
|
// Add to resort list if weight has changed
|
|
//-----------------------------------------------------------------------
|
|
//
|
|
if (
|
|
audio_source->CalculateAudioWeighting() !=
|
|
running_iterator.GetValue()
|
|
)
|
|
{
|
|
running_iterator.Remove();
|
|
resort_socket.Add(audio_source);
|
|
}
|
|
else
|
|
{
|
|
running_iterator.Next();
|
|
}
|
|
}
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Sort resort socket back into running socket
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
ChainIteratorOf<L4AudioSource*>
|
|
resort_iterator(&resort_socket);
|
|
|
|
while ((audio_source = resort_iterator.ReadAndNext()) != NULL)
|
|
{
|
|
Check(audio_source);
|
|
Verify(running_iterator.IsPlugMember(audio_source) == False);
|
|
runningAudioSourceSocket.AddValue(
|
|
audio_source,
|
|
audio_source->CalculateAudioWeighting()
|
|
);
|
|
}
|
|
|
|
CLEAR_AUDIO_RUNNING_SOURCES();
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// DormantSourceCheckup
|
|
//#############################################################################
|
|
//
|
|
void
|
|
L4AudioRenderer::DormantSourceCheckup()
|
|
{
|
|
SET_AUDIO_DORMANT_SOURCES();
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Assemble the request socket
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
VChainOf<L4AudioSource*, AudioWeighting>
|
|
resume_request_socket(NULL, False);
|
|
DormantSourceIterator
|
|
dormant_iterator(&dormantAudioSourceSocket);
|
|
L4AudioSource
|
|
*audio_source;
|
|
|
|
while ((audio_source = dormant_iterator.ReadAndNext()) != NULL)
|
|
{
|
|
Check(audio_source);
|
|
|
|
//
|
|
// If the suspend gate is not up then it can not resume
|
|
//
|
|
if (!audio_source->CanResume(GetAudioHead()))
|
|
continue;
|
|
|
|
//
|
|
// If the audio source is clipped then it can not resume
|
|
//
|
|
if (audio_source->IsAudioSourceClipped(GetAudioHead()))
|
|
continue;
|
|
|
|
//
|
|
// Add to request socket
|
|
//
|
|
resume_request_socket.AddValue(
|
|
audio_source,
|
|
audio_source->CalculateAudioWeighting()
|
|
);
|
|
}
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Iterate through request socket
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
VChainIteratorOf<L4AudioSource*, AudioWeighting>
|
|
resume_request_iterator(&resume_request_socket);
|
|
|
|
while ((audio_source = resume_request_iterator.GetCurrent()) != NULL)
|
|
{
|
|
Check(audio_source);
|
|
|
|
//
|
|
// Request audio resources
|
|
//
|
|
/* AudioChannelSet
|
|
channel_set_result;*/
|
|
|
|
SourceSet *source_result = audio_source->GetAudioChannelSet();
|
|
|
|
if (!RequestAudioResources(audio_source,
|
|
source_result)
|
|
)
|
|
{
|
|
// Audio-dropout fix: hand back any partial acquisition (see
|
|
// StartRequest) -- the source stays dormant and retries later.
|
|
audio_source->ReleaseChannels();
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Remove from dormant list and request list
|
|
//
|
|
PlugIterator remover(audio_source);
|
|
remover.RemoveSocket(&dormantAudioSourceSocket);
|
|
AudioWeighting audio_weighting = resume_request_iterator.GetValue();
|
|
resume_request_iterator.Remove();
|
|
|
|
//
|
|
// Update Spatial model
|
|
//
|
|
audio_source->UpdateSpatialModel(GetAudioHead());
|
|
|
|
//
|
|
// Set channel set of source
|
|
// Source resume implementation
|
|
// Set state of source to running
|
|
//
|
|
audio_source->SetAudioChannelSet(*source_result);
|
|
audio_source->ResumeImplementation();
|
|
audio_source->AssignAudioSourceState(RunningAudioSourceState);
|
|
|
|
//
|
|
// Put on running socket and mixing socket
|
|
//
|
|
runningAudioSourceSocket.AddValue(audio_source, audio_weighting);
|
|
if (
|
|
audio_source->GetAudioSourceMixPresence() !=
|
|
ManualAudioSourceMixPresence
|
|
)
|
|
{
|
|
mixingAudioSourceSocket.AddValue(
|
|
audio_source,
|
|
audio_source->GetAudioSourceMixPresence()
|
|
);
|
|
}
|
|
}
|
|
CLEAR_AUDIO_DORMANT_SOURCES();
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// AudioSourceStopMaintenance
|
|
//#############################################################################
|
|
//
|
|
void
|
|
L4AudioRenderer::AudioSourceStopMaintenance(L4AudioSource *source)
|
|
{
|
|
Check(source);
|
|
Verify(source->GetAudioSourceState() == RunningAudioSourceState);
|
|
#if DEBUG_LEVEL>0
|
|
{
|
|
RunningSourceIterator iterator(&runningAudioSourceSocket);
|
|
Verify(iterator.IsPlugMember(source) == True);
|
|
}
|
|
#endif
|
|
|
|
source->StopImplementation();
|
|
source->AssignAudioSourceState(StoppedAudioSourceState);
|
|
source->ReleaseChannels();
|
|
|
|
PlugIterator remover(source);
|
|
remover.RemoveSocket(&runningAudioSourceSocket);
|
|
remover.RemoveSocket(&mixingAudioSourceSocket);
|
|
|
|
#if DEBUG_LEVEL>0
|
|
{
|
|
RunningSourceIterator iterator(&runningAudioSourceSocket);
|
|
Verify(iterator.IsPlugMember(source) == False);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// AudioSourceSuspendMaintenance
|
|
//#############################################################################
|
|
//
|
|
void
|
|
L4AudioRenderer::AudioSourceSuspendMaintenance(L4AudioSource *source)
|
|
{
|
|
Check(source);
|
|
Verify(source->GetAudioRenderType() == SustainedAudioRenderType);
|
|
Verify(source->GetAudioSourceState() == RunningAudioSourceState);
|
|
#if DEBUG_LEVEL>0
|
|
{
|
|
RunningSourceIterator iterator(&runningAudioSourceSocket);
|
|
Verify(iterator.IsPlugMember(source) == True);
|
|
}
|
|
#endif
|
|
|
|
source->SuspendImplementation();
|
|
source->AssignAudioSourceState(SuspendedAudioSourceState);
|
|
source->ReleaseChannels();
|
|
|
|
PlugIterator remover(source);
|
|
remover.RemoveSocket(&runningAudioSourceSocket);
|
|
remover.RemoveSocket(&mixingAudioSourceSocket);
|
|
|
|
#if DEBUG_LEVEL>0
|
|
{
|
|
RunningSourceIterator iterator(&runningAudioSourceSocket);
|
|
Verify(iterator.IsPlugMember(source) == False);
|
|
}
|
|
#endif
|
|
|
|
dormantAudioSourceSocket.Add(source);
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// RequestAudioResources
|
|
//#############################################################################
|
|
//
|
|
Logical
|
|
L4AudioRenderer::RequestAudioResources(
|
|
L4AudioSource *audio_source,
|
|
SourceSet *source_result
|
|
)
|
|
{
|
|
Check(this);
|
|
Check(audio_source);
|
|
Check(source_result);
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// This source must be within the culling volume
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
if (audio_source->IsAudioSourceClipped(GetAudioHead()))
|
|
return False;
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Request audio channels for this source
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
Logical
|
|
resources_available;
|
|
|
|
source_result = audio_source->GetAudioChannelSet();
|
|
Check(source_result);
|
|
|
|
resources_available =
|
|
RequestAudioChannels(
|
|
audio_source->GetAudioVoiceCount(),
|
|
source_result
|
|
);
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// If audio channels are not available then check running sources for
|
|
// lower weight sources
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
if (!resources_available)
|
|
{
|
|
RunningSourceIterator
|
|
iterator(&runningAudioSourceSocket);
|
|
L4AudioSource
|
|
*running_audio_source;
|
|
AudioWeighting
|
|
audio_source_weighting;
|
|
|
|
iterator.Last();
|
|
running_audio_source = iterator.GetCurrent();
|
|
audio_source_weighting = audio_source->CalculateAudioWeighting();
|
|
|
|
//
|
|
//-----------------------------------------------------------------------
|
|
// while
|
|
// request for channels is not satisfied and
|
|
// there exist running sources that have lower weighting
|
|
//-----------------------------------------------------------------------
|
|
//
|
|
while (
|
|
!resources_available &&
|
|
running_audio_source != NULL &&
|
|
audio_source_weighting < iterator.GetValue() // Actual value is inverted
|
|
)
|
|
{
|
|
Check(running_audio_source);
|
|
Verify(
|
|
running_audio_source->GetAudioSourcePriority() <=
|
|
audio_source->GetAudioSourcePriority()
|
|
);
|
|
|
|
//
|
|
//--------------------------------------------------------------------
|
|
// Collect Statistics
|
|
//--------------------------------------------------------------------
|
|
//
|
|
#ifdef LAB_ONLY
|
|
if (
|
|
running_audio_source->GetAudioSourcePriority() <
|
|
audio_source->GetAudioSourcePriority()
|
|
)
|
|
{
|
|
resourceStealPriorityCount++;
|
|
}
|
|
else
|
|
{
|
|
Verify(
|
|
running_audio_source->GetAudioSourcePriority() ==
|
|
audio_source->GetAudioSourcePriority()
|
|
);
|
|
Warn(
|
|
running_audio_source->CalculateSourceVolumeScale() >=
|
|
audio_source->CalculateSourceVolumeScale()
|
|
);
|
|
resourceStealVolumeCount++;
|
|
}
|
|
AudioTime duration_cutoff(0.1f);
|
|
if (
|
|
running_audio_source->GetCurrentRunningTime() <=
|
|
duration_cutoff
|
|
)
|
|
{
|
|
stealShortDurationCount++;
|
|
}
|
|
resourceStealCount++;
|
|
#endif
|
|
|
|
//
|
|
//--------------------------------------------------------------------
|
|
// If running source is transient
|
|
// Then stop the source
|
|
// Else suspend the source
|
|
//--------------------------------------------------------------------
|
|
//
|
|
if (
|
|
running_audio_source->GetAudioRenderType() ==
|
|
TransientAudioRenderType
|
|
)
|
|
{
|
|
AudioSourceStopMaintenance(running_audio_source);
|
|
}
|
|
else
|
|
{
|
|
Verify(
|
|
running_audio_source->GetAudioRenderType() ==
|
|
SustainedAudioRenderType
|
|
);
|
|
AudioSourceSuspendMaintenance(running_audio_source);
|
|
}
|
|
|
|
//
|
|
//--------------------------------------------------------------------
|
|
// Request resources again
|
|
//--------------------------------------------------------------------
|
|
//
|
|
resources_available =
|
|
RequestAudioChannels(
|
|
audio_source->GetAudioVoiceCount(),
|
|
source_result
|
|
);
|
|
|
|
iterator.Last();
|
|
running_audio_source = iterator.GetCurrent();
|
|
}
|
|
}
|
|
return resources_available;
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// RequestAudioChannels
|
|
//#############################################################################
|
|
//
|
|
Logical
|
|
L4AudioRenderer::RequestAudioChannels(
|
|
int voices_requested,
|
|
SourceSet *source_request
|
|
)
|
|
{
|
|
Check(this);
|
|
Check(source_request);
|
|
|
|
//Do we have enough?
|
|
int requested = source_request->count;
|
|
|
|
// SOURCE-POOL CENSUS (field 2026-07-23: "audio cutting in and out toward
|
|
// the end of the match"). OpenAL's mixing capacity is finite; sources
|
|
// release only at entity teardown, so long matches accumulate looping
|
|
// occupants (wreck burn/smoke) until transients lose the voice fight.
|
|
// Track generated/deleted/failed and print a 30s health line + EVERY
|
|
// acquisition failure (rare + the smoking gun).
|
|
extern long gBTAudioSourcesLive, gBTAudioAcquireFails;
|
|
{
|
|
static unsigned long s_censusAt = 0;
|
|
unsigned long now_ms = GetTickCount();
|
|
if (now_ms - s_censusAt > 30000)
|
|
{
|
|
s_censusAt = now_ms;
|
|
DEBUG_STREAM << "[audio] source census: live=" << gBTAudioSourcesLive
|
|
<< " acquireFails=" << gBTAudioAcquireFails
|
|
<< std::endl << std::flush;
|
|
}
|
|
}
|
|
|
|
// Gitea #12: hard cap at the SourceSet capacity so alGenSources can never
|
|
// write past sources[] (the death-crash heap overflow). count is set from
|
|
// the streamed voiceCount and is normally already clamped in the
|
|
// L4AudioSource ctor; this guards the acquisition site itself.
|
|
if (requested > AUDIO_SOURCESET_CAPACITY)
|
|
requested = AUDIO_SOURCESET_CAPACITY;
|
|
|
|
bool failed = true;
|
|
|
|
alGetError();
|
|
|
|
for (int i = 0; i < requested; i++)
|
|
{
|
|
if (!alIsSource(source_request->sources[i]))
|
|
{
|
|
alGenSources(1, source_request->sources + i);
|
|
if (alIsSource(source_request->sources[i]))
|
|
++gBTAudioSourcesLive;
|
|
}
|
|
}
|
|
|
|
ALenum error = alGetError();
|
|
if (error == AL_NO_ERROR)
|
|
{
|
|
failed = false;
|
|
}
|
|
|
|
if (failed)
|
|
{
|
|
++gBTAudioAcquireFails;
|
|
DEBUG_STREAM << "[audio] ACQUIRE FAILED (requested=" << requested
|
|
<< " live=" << gBTAudioSourcesLive
|
|
<< " fails=" << gBTAudioAcquireFails
|
|
<< ") -- the pool is exhausted; expect dropouts"
|
|
<< std::endl << std::flush;
|
|
return False;
|
|
}
|
|
|
|
return True;
|
|
|
|
//
|
|
//----------------------------------------------------------------------
|
|
// Attempt to allocate channels from audio hardware
|
|
//----------------------------------------------------------------------
|
|
//
|
|
/* AudioChannel *channel;
|
|
Logical success = False;
|
|
AudioCard *front_card = GetFrontCard();
|
|
AudioCard *rear_card = GetRearCard();
|
|
|
|
Check(front_card);
|
|
Check(rear_card);
|
|
|
|
while (True)
|
|
{
|
|
if (channel_set_request->IsFrontLeftEnabled())
|
|
{
|
|
if ((channel =
|
|
front_card->RequestAudioChannel(voices_requested)) == NULL)
|
|
break;
|
|
Check(channel);
|
|
channel_set_request->SetFrontLeft(channel);
|
|
}
|
|
|
|
if (channel_set_request->IsFrontRightEnabled())
|
|
{
|
|
if ((channel =
|
|
front_card->RequestAudioChannel(voices_requested)) == NULL)
|
|
break;
|
|
Check(channel);
|
|
channel_set_request->SetFrontRight(channel);
|
|
}
|
|
|
|
if (channel_set_request->IsRearLeftEnabled())
|
|
{
|
|
if ((channel =
|
|
rear_card->RequestAudioChannel(voices_requested)) == NULL)
|
|
break;
|
|
Check(channel);
|
|
channel_set_request->SetRearLeft(channel);
|
|
}
|
|
|
|
if (channel_set_request->IsRearRightEnabled())
|
|
{
|
|
if ((channel =
|
|
rear_card->RequestAudioChannel(voices_requested)) == NULL)
|
|
break;
|
|
Check(channel);
|
|
channel_set_request->SetRearRight(channel);
|
|
}
|
|
|
|
success = True;
|
|
break;
|
|
}
|
|
|
|
//
|
|
//----------------------------------------------------------------------
|
|
// If did not succeed then release allocated channels
|
|
//----------------------------------------------------------------------
|
|
//
|
|
if (!success)
|
|
{
|
|
channel_set_request->ReleaseAll();
|
|
return False;
|
|
}
|
|
|
|
//
|
|
//----------------------------------------------------------------------
|
|
// else, return allocated channels
|
|
//----------------------------------------------------------------------
|
|
//
|
|
return True;*/
|
|
}
|
|
|
|
long gBTAudioSourcesLive = 0;
|
|
long gBTAudioAcquireFails = 0;
|
|
|
|
void L4AudioRenderer::ReleaseSourceSet(SourceSet &sourceSet)
|
|
{
|
|
// Audio-dropout fix: the old bulk alDeleteSources(count, sources) is
|
|
// ATOMIC per the AL spec -- ONE invalid name in the array (an empty slot
|
|
// of a partial set, or the old -1 sentinel on a double release) and
|
|
// NOTHING is deleted. After the first pool-exhaustion event every
|
|
// partial release leaked its real sources and the pool never recovered
|
|
// ("audio cutting in and out toward the end of the match"). Delete each
|
|
// valid source individually and park the slot at 0 (never a valid name).
|
|
extern long gBTAudioSourcesLive;
|
|
for (int i = 0; i < sourceSet.count; i++)
|
|
{
|
|
if (alIsSource(sourceSet.sources[i]))
|
|
{
|
|
ALint state = AL_STOPPED;
|
|
alGetSourcei(sourceSet.sources[i], AL_SOURCE_STATE, &state);
|
|
if (state == AL_PLAYING)
|
|
alSourceStop(sourceSet.sources[i]);
|
|
alDeleteSources(1, sourceSet.sources + i);
|
|
--gBTAudioSourcesLive;
|
|
}
|
|
sourceSet.sources[i] = 0;
|
|
}
|
|
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~ L4AudioRenderer profile bits ~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
#if defined(TRACE_AUDIO_RENDERER_RUNNING_SOURCES)
|
|
BitTrace Audio_Renderer_Running_Sources("Audio Renderer Running Sources");
|
|
#endif
|
|
|
|
#if defined(TRACE_AUDIO_RENDERER_RUNNING_SORT)
|
|
BitTrace Audio_Renderer_Running_Sort("Audio Renderer Running Sort");
|
|
#endif
|
|
|
|
#if defined(TRACE_AUDIO_RENDERER_CALCULATE_MIX)
|
|
BitTrace Audio_Renderer_Calculate_Mix("Audio Renderer Calculate Mix");
|
|
#endif
|
|
|
|
#if defined(TRACE_AUDIO_RENDERER_EXECUTE_SOURCES)
|
|
BitTrace Audio_Renderer_Execute_Sources("Audio Renderer Execute Sources");
|
|
#endif
|
|
|
|
#if defined(TRACE_AUDIO_RENDERER_DORMANT_SOURCES)
|
|
BitTrace Audio_Renderer_Dormant_Sources("Audio Renderer Dormant Sources");
|
|
#endif
|
|
|
|
//
|
|
//#############################################################################
|
|
// Master-volume runtime control (issue #26, 2026-07-23). The pod had a
|
|
// physical volume knob on the operator side; desktop players had NO way to
|
|
// duck the (hot) game audio under voice chat short of the Windows mixer.
|
|
// The -/= keys step the OpenAL listener gain (the master scale for every
|
|
// source); the value persists in content\volume.cfg (CWD) and reloads at
|
|
// the next boot (BT_AUDIO_VOLUME env still wins when set).
|
|
//#############################################################################
|
|
//
|
|
float gBTMasterVolume = 0.6f;
|
|
|
|
void BTAudioMasterVolumeStep(int direction)
|
|
{
|
|
gBTMasterVolume += (direction > 0) ? 0.05f : -0.05f;
|
|
if (gBTMasterVolume < 0.0f) gBTMasterVolume = 0.0f;
|
|
if (gBTMasterVolume > 1.5f) gBTMasterVolume = 1.5f;
|
|
alListenerf(AL_GAIN, gBTMasterVolume);
|
|
FILE *cfg = fopen("volume.cfg", "wt");
|
|
if (cfg != NULL)
|
|
{
|
|
fprintf(cfg, "%.2f\n", gBTMasterVolume);
|
|
fclose(cfg);
|
|
}
|
|
DEBUG_STREAM << "[audio] master volume "
|
|
<< (int)(gBTMasterVolume * 100.0f + 0.5f) << "%" << std::endl << std::flush;
|
|
}
|