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>
1627 lines
43 KiB
C++
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(¬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 <= 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(¬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;
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 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
|