Files
RP412/MUNGA_L4/L4AUDRND.cpp
T
CydandClaude Opus 5 523f713a30 Volume and bass knobs, for players without an amplifier
The cabinets ran the game at unity and shaped volume and tone outside it,
in an external amplifier and a 3-way crossover. That is why there is no
master volume anywhere in the original code and none in AUDIO.INI - an
operator turned a knob on an amp. A desktop player has no amp and no
crossover, and the recovered soundbanks are a good deal livelier than what
4.12 shipped with, so the game has to offer the two controls the pod got
from hardware.

RP412AUDIOVOLUME, 0.0 to 4.0, is the amplifier: a listener gain, which the
port had never set at all. RP412AUDIOBASS, 0.0 to 1.0, is the crossover's
low band. Both default to leaving the mix exactly as the pod played it, so
neither changes anything for anyone who does not go looking.

The bass trim is not a filter, and the reason is worth writing down: the
OpenAL we ship is Creative's, not OpenAL Soft, and it implements only
AL_FILTER_LOWPASS. It rejects highpass and bandpass outright. A bandpass
would have been the tidy answer, carrying the authored brightness model on
GAINHF and the trim on GAINLF across the single direct filter a source
gets. It is not on offer.

So the trim scales sample data as it loads, which suits how this low end is
actually built: the weight lives in discrete deep layer zones whose per-zone
tuning bakes out to a very low playback rate - thirteen zones below 8kHz,
three to five octaves under their recorded pitch, against four fifths of the
set at 22kHz and up. Baked rate is a dependable proxy for band, so pulling
down the low-rate zones is a real low-band trim and not a blunt cut. It eases
in below 22kHz and reaches full depth at 5.5kHz.

Caught while building this, and the reason for the probe: EFX_Initialize
checks alGetError after configuring the scratch filter, so asking for a
filter type the driver refuses leaves an error pending and takes the entire
bridge down - reverb included. The bandpass attempt did precisely that and
would have silently killed the reverb and brightness work. Initialize now
survives losing the filter and says so.

Builds clean, runs with both knobs set and with neither.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-05 23:45:29 -05:00

1627 lines
43 KiB
C++

#include "mungal4.h"
#pragma hdrstop
#include "l4audrnd.h"
#include "l4audefx.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);
//
//----------------------------------------------------------------------
// 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(&notation_file);
//
//----------------------------------------------------------------------
// 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",
&amplitude_rolloff
);
if (!ret)
{
Fail("L4AudioRenderer::Initialize - amplitude_rolloff not defined");
}
ret = notation_file.GetEntry(
"AudioRenderer",
"amplitude_rolloff_distance_scale",
&amplitude_rolloff_distance_scale
);
if (!ret)
{
Fail("L4AudioRenderer::Initialize - amplitude_rolloff_distance_scale not defined");
}
ret = notation_file.GetEntry(
"AudioRenderer",
"amplitude_rolloff_knee",
&amplitude_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)
{
ALCcontext *context = alcCreateContext(device,NULL);
alcMakeContextCurrent(context);
//
// FIDELITY (docs/SOUND.md F9/F11): bring up the EFX bridge that carries
// the authored brightness/distance lowpass and the wet-exterior reverb
// send. Needs the context current, and the reverb gain has already been
// read from AUDIO.INI into the head above. Inert without ALC_EXT_EFX.
//
EFX_Initialize(audio_head->GetGlobalReverbScale());
//
// Master volume. There was no listener gain at all before -- the mix
// always ran at unity -- so restoring the authored dynamics gave players
// no way to pull the whole thing down. This lives in environ.ini rather
// than AUDIO.INI deliberately: AUDIO.INI is byte-identical to the file
// that shipped in 1995 and is worth keeping that way.
//
// Default is 1.0, i.e. exactly the previous behaviour -- the knob only
// does something when someone asks for it.
//
{
float master_volume = 1.0f;
if (const char *setting = getenv("RP412AUDIOVOLUME"))
{
float value = (float)atof(setting);
if (value >= 0.0f && value <= 4.0f)
{
master_volume = value;
Tell("Audio master volume set to " << master_volume << "\n");
}
}
alListenerf(AL_GAIN, master_volume);
}
//
// The bass trim is not set here: it scales sample data as buffers are
// loaded, so it lives in the resource manager (RPAudioBassTrim,
// L4AUDRES.cpp) and is read there. PreloadResources runs below.
//
}
//
//----------------------------------------------------------------------
// Preload resources
//----------------------------------------------------------------------
//
Check(&audioResourceManager);
audioResourceManager.PreloadResources(&notation_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)
{
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)
)
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;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ OpenAL source pool ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
// Sources are expensive to create and destroy and are a HARD per-context
// resource (this driver grants 256 mono). Generating one per sound event and
// deleting it on release burns through that ceiling during busy play even
// though steady-state demand is modest, which shows up as sounds silently
// failing to start. Generate once, recycle forever.
//
// The cap sits below the driver grant with a reserve, so growth stops on our
// terms rather than on an alGenSources failure. Growth also stops by itself if
// a driver offers fewer sources than the cap -- a failed generate simply ends
// growth and the pool recycles what it already has.
//
static const int kAudioPoolMax = 512; // free-list array size
static const int kAudioPoolCap = 240; // grow no further than this
static ALuint gAudioPoolFree[kAudioPoolMax];
static int gAudioPoolFreeCount = 0; // entries parked in gAudioPoolFree
static int gAudioPoolTotal = 0; // sources ever generated (<= cap)
static long gAudioPoolReuses = 0; // diagnostics
int RPAudioPoolSize() { return gAudioPoolTotal; }
int RPAudioPoolFree() { return gAudioPoolFreeCount; }
long RPAudioPoolReuses() { return gAudioPoolReuses; }
//
// Reset a source to a neutral state so nothing carries across owners.
//
static void
RPAudioScrubSource(ALuint src)
{
ALint state = AL_STOPPED;
alGetSourcei(src, AL_SOURCE_STATE, &state);
if (state == AL_PLAYING || state == AL_PAUSED)
{
alSourceStop(src);
}
alSourcei(src, AL_BUFFER, 0); // detach (nothing is queued here)
alSourcei(src, AL_LOOPING, AL_FALSE); // or the next owner inherits a loop
alSourcef(src, AL_GAIN, 1.0f);
alSourcef(src, AL_PITCH, 1.0f);
alSourcei(src, AL_SOURCE_RELATIVE, AL_FALSE);
alSource3f(src, AL_POSITION, 0.0f, 0.0f, 0.0f);
alSource3f(src, AL_VELOCITY, 0.0f, 0.0f, 0.0f);
//
// Drop the EFX state too. Without this a recycled name can carry a 3D
// source's reverb send into a dry cockpit sound, or a distant source's
// lowpass into a close one.
//
EFX_ClearSourceEffects(src);
alGetError(); // swallow any property complaint
}
//
// Hand out a source: recycle first, generate only while under the cap.
// False means genuinely out, and the caller retries after the steal loop runs.
//
Logical
RPAudioPoolAcquire(ALuint *out)
{
Check_Pointer(out);
while (gAudioPoolFreeCount > 0)
{
ALuint src = gAudioPoolFree[--gAudioPoolFreeCount];
if (alIsSource(src)) // a context reset invalidates names
{
++gAudioPoolReuses;
*out = src;
return True;
}
--gAudioPoolTotal; // stale name: forget it
}
if (gAudioPoolTotal >= kAudioPoolCap)
{
return False;
}
ALuint src = 0;
alGetError();
alGenSources(1, &src);
if (alGetError() != AL_NO_ERROR || !alIsSource(src))
{
return False; // driver said no before our cap
}
++gAudioPoolTotal;
#if DEBUG_LEVEL>0
{
//
// One line per high-water band, so a log shows how close real play gets
// to the ceiling without spamming.
//
static int s_notified = 0;
if (gAudioPoolTotal >= s_notified + 25)
{
s_notified = gAudioPoolTotal;
Tell("Audio source pool high-water: " << gAudioPoolTotal
<< " of " << kAudioPoolCap << "\n");
}
}
#endif
*out = src;
return True;
}
//
// Take a source back. Scrubbed and parked, never deleted.
//
void
RPAudioPoolRelease(ALuint src)
{
if (!alIsSource(src))
{
return;
}
RPAudioScrubSource(src);
if (gAudioPoolFreeCount < kAudioPoolMax)
{
gAudioPoolFree[gAudioPoolFreeCount++] = src;
return;
}
alDeleteSources(1, &src); // unreachable: cap < array size
--gAudioPoolTotal;
}
//
//#############################################################################
// RequestAudioChannels
//#############################################################################
//
Logical
L4AudioRenderer::RequestAudioChannels(
int voices_requested,
SourceSet *source_request
)
{
Check(this);
Check(source_request);
//
// SOURCE POOLING (docs/SOUND.md). This used to alGenSources per sound
// event, with ReleaseSourceSet alDeleteSources'ing on release -- so play
// activity CHURNED through OpenAL's per-context source limit (the driver
// grants 256 mono here). Recovering the soundbanks took the voice count
// per sound from about 1.1 zones to about 2.6, roughly doubling that churn.
//
// The BT tree measured this exact problem: raising the budget was NOT the
// fix, recycling was, and it was a net CPU win besides. Sources are now
// generated once and handed back to a free list, so steady-state play costs
// no allocation at all.
//
int requested = source_request->count;
if (requested > (int)(sizeof(source_request->sources) / sizeof(source_request->sources[0])))
{
requested = (int)(sizeof(source_request->sources) / sizeof(source_request->sources[0]));
source_request->count = requested;
}
for (int i = 0; i < requested; i++)
{
if (source_request->sources[i] != 0 && alIsSource(source_request->sources[i]))
{
continue; // slot already holds a live source
}
ALuint src = 0;
if (!RPAudioPoolAcquire(&src))
{
//
// Out of sources. Hand back everything acquired on THIS attempt so a
// failed request cannot strand voices -- the renderer's steal loop
// will free some and retry.
//
for (int j = 0; j < i; j++)
{
if (source_request->sources[j] != 0)
{
RPAudioPoolRelease(source_request->sources[j]);
source_request->sources[j] = 0;
}
}
return False;
}
source_request->sources[i] = src;
}
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;*/
}
void L4AudioRenderer::ReleaseSourceSet(SourceSet &sourceSet)
{
//
// SOURCE POOLING (docs/SOUND.md): park each source on the free list rather
// than destroying it. RPAudioPoolRelease stops it, detaches its buffer and
// scrubs the state -- including the EFX filter and reverb send -- so the
// next owner starts clean.
//
// The bulk alDeleteSources(count, sources) this replaces was also a leak
// waiting to happen: per the AL spec it is ATOMIC, so ONE invalid name in
// the array (an empty slot of a partial set, or the old -1 sentinel on a
// double release) meant NOTHING was deleted and the whole set leaked.
// Slots are parked at 0, which is never a valid AL name -- unlike -1, which
// alIsSource would be asked about as 0xFFFFFFFF.
//
for (int i = 0; i < sourceSet.count; i++)
{
if (sourceSet.sources[i] != 0)
{
RPAudioPoolRelease(sourceSet.sources[i]);
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