Correcting my own framing in7e57816. The dropout players actually reported was fixed on 2026-07-23 bya999e5c, and nobody has complained since. I read the leftover ACQUIRE FAILED lines in the night-5 logs as the same live bug and raised the ceiling by default. They are not the same bug. What is still in the logs is transient exhaustion, not a leak: the census sits at 45-60 live, spikes during a firefight, and drains straight back (226 -> 42), which is the priority steal loop working. A voice dropped in that window is competing with ~256 already sounding, so it is very likely sub-perceptual -- which is consistent with the thing nobody is reporting. And raising the cap is not free. The 256 ceiling doubles as a governor: the steal loop only steals when the incoming source outranks a running one, so a higher cap means many more voices mixing simultaneously, and with EFX reverb and the lowpass chains live that is real CPU -- spent precisely during heavy combat, when the frame budget is already tightest. I verified the ceiling moves. I never measured frame time under a real firefight, and a solo bench cannot produce one. Shipping that to testers tonight would be gambling their framerate against a complaint nobody is making. So the knob stays and the default does not move. BT_AUDIO_SOURCES=<n> raises it with no rebuild if dropouts are ever reported again -- measure frame time while you do. Unset reports `granted mono=255 stereo=1`, exactly what testers have been running. Keeping the diagnostic read-back either way, since it earned its keep: asking for 64 grants 240, because OpenAL Soft has a floor of its own, which is also why the NULL default lands on 256. A request is not a promise. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1593 lines
44 KiB
C++
1593 lines
44 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.
|
|
//
|
|
// ⚠ OFF BY DEFAULT, DELIBERATELY. Raising the cap is NOT a free win, and
|
|
// the reported dropout was already fixed on 2026-07-23 by a999e5c (the
|
|
// atomic-delete leak) -- no field complaints since. What remains in the
|
|
// night-5 logs is transient exhaustion at the peak of a firefight, where
|
|
// a dropped voice competes with ~256 already sounding, so it is very
|
|
// likely sub-perceptual. Against that: the 256 ceiling also acts as a
|
|
// GOVERNOR. The steal loop only steals when the incoming source outranks
|
|
// a running one, so a higher cap means many more voices mixing at once --
|
|
// with EFX reverb + the lowpass chains live, that is real CPU, spent
|
|
// exactly during heavy combat when the frame budget is tightest.
|
|
//
|
|
// So this stays opt-in: set BT_AUDIO_SOURCES=<n> to raise it (no rebuild
|
|
// needed) if dropouts are ever reported again, and measure frame time
|
|
// while you do. Unset = the shipped OpenAL Soft default, unchanged.
|
|
ALCint monoWanted = 0;
|
|
if (const char *sv = getenv("BT_AUDIO_SOURCES"))
|
|
{
|
|
int n = atoi(sv);
|
|
if (n >= 64 && n <= 4096)
|
|
monoWanted = n;
|
|
}
|
|
ALCcontext *context;
|
|
if (monoWanted > 0)
|
|
{
|
|
ALCint attrs[5];
|
|
attrs[0] = ALC_MONO_SOURCES; attrs[1] = monoWanted;
|
|
attrs[2] = ALC_STEREO_SOURCES; attrs[3] = 16;
|
|
attrs[4] = 0;
|
|
context = alcCreateContext(device, attrs);
|
|
}
|
|
else
|
|
{
|
|
context = alcCreateContext(device, NULL); // default budget (256)
|
|
}
|
|
alcMakeContextCurrent(context);
|
|
// Report what the driver actually GRANTED -- a request is not a promise.
|
|
// (Asking for 64 grants 240: OpenAL Soft has a floor of its own, which is
|
|
// also why the NULL default lands on 256.)
|
|
if (getenv("BT_AUDIO_LOG") || monoWanted > 0)
|
|
{
|
|
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 > 0 ? monoWanted : 0) << " (0 = driver default)"
|
|
<< " 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;
|
|
}
|