The volume keys wanted a partner, and the bass trim could not be one as it stood: it scaled the sample data as it loaded, so by the time anyone pressed a key the audio was already sitting in OpenAL buffers and nothing short of a restart would move it. So the trim is now a per-zone gain applied in the mix instead. Each buffer's depth - how much of the low band it occupies - is still worked out once at load from its playback rate, but the trim itself is read every frame, which is what lets Home and End move it while sounds are playing. It is the better form regardless: no rewriting of sample data, and no quantisation on top of audio that has already been through one gain stage. Home raises, End lowers, in steps of 0.05, and the setting is written to bass.cfg beside the exe exactly as the volume writes volume.cfg. Together with PageUp and PageDown that is the amplifier and the crossover the cabinets had in hardware and a desktop does not. Builds clean, runs, and neither knob fires unprompted. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1701 lines
45 KiB
C++
1701 lines
45 KiB
C++
#include "mungal4.h"
|
|
#pragma hdrstop
|
|
|
|
#include "l4audrnd.h"
|
|
#include "l4audefx.h"
|
|
#include "..\munga\notation.h"
|
|
#include "openal/alc.h"
|
|
|
|
#include <stdio.h>
|
|
|
|
//
|
|
// Master volume limits, shared by the startup load and the PgUp/PgDn step.
|
|
// The file sits beside the exe with the other runtime state.
|
|
//
|
|
static const char kAudioVolumeFile[] = "volume.cfg";
|
|
static const float kAudioVolumeStep = 0.05f;
|
|
static const float kAudioVolumeMax = 2.0f;
|
|
|
|
//
|
|
//#############################################################################
|
|
// 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(¬ation_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",
|
|
&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)
|
|
{
|
|
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 <= kAudioVolumeMax)
|
|
{
|
|
master_volume = value;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Whatever the player last set with the volume keys wins over the
|
|
// environ.ini figure: the keys are the amplifier knob, and a knob
|
|
// stays where it was left. environ.ini sets where it starts on a
|
|
// machine that has never been touched.
|
|
//
|
|
if (FILE *cfg = fopen(kAudioVolumeFile, "rt"))
|
|
{
|
|
float value = -1.0f;
|
|
|
|
if (fscanf(cfg, "%f", &value) == 1
|
|
&& value >= 0.0f && value <= kAudioVolumeMax)
|
|
{
|
|
master_volume = value;
|
|
}
|
|
fclose(cfg);
|
|
}
|
|
|
|
gRPMasterVolume = master_volume;
|
|
alListenerf(AL_GAIN, master_volume);
|
|
Tell("Audio master volume " << (int)(master_volume * 100.0f + 0.5f) << "%\n");
|
|
}
|
|
|
|
//
|
|
// The bass trim is not set here: it is a per-zone gain owned by the
|
|
// resource manager (L4AUDRES.cpp), which needs the buffers to exist
|
|
// first. PreloadResources initialises it below.
|
|
//
|
|
}
|
|
|
|
//
|
|
//----------------------------------------------------------------------
|
|
// 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)
|
|
{
|
|
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;
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Master volume ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
// The pod ran at unity and left volume to an external amplifier, so the game
|
|
// never had a level control. Standing in for that amplifier means the player
|
|
// needs to reach it while playing, not only through environ.ini -- hence the
|
|
// PgUp/PgDn binding in L4Application::KeyCommandMessageHandler.
|
|
//
|
|
// Page keys specifically: they produce no typed character, so they cannot
|
|
// collide with any of the engine's character-keyed commands the way '+'/'-'
|
|
// would, they are bound to nothing in any RP layout, and they exist on
|
|
// tenkeyless keyboards.
|
|
//
|
|
float gRPMasterVolume = 1.0f;
|
|
|
|
void
|
|
RPAudioMasterVolumeStep(int direction)
|
|
{
|
|
gRPMasterVolume += (direction > 0) ? kAudioVolumeStep : -kAudioVolumeStep;
|
|
|
|
if (gRPMasterVolume < 0.0f) gRPMasterVolume = 0.0f;
|
|
if (gRPMasterVolume > kAudioVolumeMax) gRPMasterVolume = kAudioVolumeMax;
|
|
|
|
//
|
|
// Snap to the step grid so repeated presses cannot drift on float error and
|
|
// land somewhere that never reads back as a round number.
|
|
//
|
|
gRPMasterVolume =
|
|
(float)((int)(gRPMasterVolume / kAudioVolumeStep + 0.5f)) * kAudioVolumeStep;
|
|
|
|
alListenerf(AL_GAIN, gRPMasterVolume);
|
|
|
|
//
|
|
// Persist immediately. A pod operator setting the level expects it to still
|
|
// be there after the cabinet is power-cycled, and there is no settings UI to
|
|
// hang it off.
|
|
//
|
|
if (FILE *cfg = fopen(kAudioVolumeFile, "wt"))
|
|
{
|
|
fprintf(cfg, "%.2f\n", gRPMasterVolume);
|
|
fclose(cfg);
|
|
}
|
|
|
|
Tell("Audio master volume " << (int)(gRPMasterVolume * 100.0f + 0.5f) << "%\n");
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 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
|