Files
RP412/MUNGA_L4/L4AUDRES.cpp
T
CydandClaude Opus 5 12f9ebefab A quiet sound at the wrong distance no longer kills the game
From Nathan's crash dump: an access violation reading 8093e920, fourteen
minutes into a session, on 4.12.115.

  rpl4opt!PatchLevelOfDetail::SetupPatch+0xbb
  rpl4opt!Static3DPatchSource::StartImplementation+0x50
  rpl4opt!AudioRenderer::ExecuteBackground+0x9e

The faulting instruction is g_buffers[index] with index = 0x20000000 -
536 million - and the array base in eax at 0093e920, which is exactly the
address it died on. So the index was garbage, and the dump says where the
garbage came from: the stack slot holding info.bufferIndex.

PRESET_getSampleInfo builds a SAMPLEINFO to return when it is asked for a
zone the preset does not have. It sets chan, file, implemented and loop -
and not bufferIndex. Every caller tests bufferIndex >= 0 before using it,
so "no such zone" was meant to be rejected there; instead the test read
whatever was on the stack, and passed whenever that happened to be
positive. AL_getBuffer then indexed the array with it, unchecked.

Why it asked for a zone that is not there: the loop runs to
sourceSet.count, which was fixed when the audio source was built, from
whichever level of detail was selected at the time. SetDistance re-picks
the level of detail by distance on the line immediately before SetupPatch
runs, and the zone counts across the recovered banks are nothing like
uniform - of 200 presets, 46 have no zones at all, and the rest run 1 to
4. So a sound that moved far enough to drop to a quieter patch could ask
that patch for a zone it never had. In the dump: count 3, died asking for
zone 2.

Fixed at all three levels, because any one of them alone would have held:
the default carries bufferIndex = -1 so the existing guard works,
AL_getBuffer returns AL_NONE rather than reading past its array, and
SetupPatch asks for no more zones than the patch it is actually using
has.

Verified: the dump's own numbers reproduce arithmetically, and two full
races run clean. The distance-dependent trigger itself was reasoned from
the dump rather than reproduced here - it needs a sound to cross a level
of detail boundary into a shorter patch - so the belt-and-braces.

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

1271 lines
35 KiB
C++

#include "mungal4.h"
#pragma hdrstop
#include "l4audres.h"
#include "l4audhdw.h"
#include "l4audio.h"
#include "l4audlvl.h"
#include "l4audwtr.h"
#include "..\munga\audcmp.h"
#include "..\munga\audseq.h"
#include "..\munga\audrend.h"
#include "..\munga\namelist.h"
#include "..\munga\app.h"
#include "..\munga\notation.h"
#include "openal\al.h"
#include "sndfile.h"
ALuint *g_buffers;
int g_numBuffers;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Bass trim ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
// RP412AUDIOBASS, 0.0..1.0, default 1.0 (the mix exactly as authored), stepped
// live by the Home/End keys.
//
// The arcade pod ran the game at unity and did its volume and tone shaping in
// hardware -- an external amplifier and a 3-way crossover. A desktop player has
// neither, so the low band needs a control in software. This is the crossover's
// low trim; the master volume (L4AUDRND.cpp) is the amplifier's.
//
// It cannot be an EFX filter: the OpenAL this game ships implements only
// AL_FILTER_LOWPASS, so there is no low shelf or bandpass to lean on, and the
// one direct filter a source gets is already carrying the authored brightness
// model. So the trim is a GAIN, applied per zone in the mix.
//
// That works because of HOW the low end is built. RP's soundbanks carry their
// weight in discrete deep layer zones whose per-zone tuning bakes out to a very
// low playback rate -- 13 zones sit below 8 kHz, between 3.4 and 5.2 octaves
// below their recorded pitch, against 81% of the set at 22 kHz and up. A zone's
// baked rate is therefore a reliable proxy for which band it occupies, so
// attenuating the low-rate zones is a genuine low-band trim rather than a blunt
// overall cut.
//
// Ramp: untouched at or above 22050 Hz, full trim at or below 5512 Hz, log
// interpolated between, so nothing steps abruptly at a threshold. Each buffer's
// DEPTH is fixed at load; the trim itself is read at mix time, which is what
// lets the keys move it while sounds are playing.
//
static const ALsizei kBassTrimFullRate = 5512; // at/below: full trim
static const ALsizei kBassTrimNoneRate = 22050; // at/above: untouched
static const char kBassTrimFile[] = "bass.cfg";
static const float kBassTrimStep = 0.05f;
static float *g_bufferBassDepth = NULL; // one per loaded buffer
static float g_bassTrim = 1.0f;
//
// How much of the trim a buffer at this rate takes: 0 = untouched, 1 = fully.
//
static float
RPBassDepthForRate(ALsizei rate)
{
if (rate >= kBassTrimNoneRate) return 0.0f;
if (rate <= kBassTrimFullRate) return 1.0f;
const float span = (float)log((double)kBassTrimNoneRate / (double)kBassTrimFullRate);
return (float)log((double)kBassTrimNoneRate / (double)rate) / span;
}
void
RPBassTrimInitialize()
{
g_bassTrim = 1.0f;
if (const char *setting = getenv("RP412AUDIOBASS"))
{
float value = (float)atof(setting);
if (value >= 0.0f && value <= 1.0f)
{
g_bassTrim = value;
}
}
//
// Whatever the player last set with the keys wins, exactly as the master
// volume behaves -- environ.ini only decides where an untouched machine
// starts out.
//
if (FILE *cfg = fopen(kBassTrimFile, "rt"))
{
float value = -1.0f;
if (fscanf(cfg, "%f", &value) == 1 && value >= 0.0f && value <= 1.0f)
{
g_bassTrim = value;
}
fclose(cfg);
}
Tell("Audio bass trim " << (int)(g_bassTrim * 100.0f + 0.5f) << "%\n");
}
void
RPBassTrimStep(int direction)
{
g_bassTrim += (direction > 0) ? kBassTrimStep : -kBassTrimStep;
if (g_bassTrim < 0.0f) g_bassTrim = 0.0f;
if (g_bassTrim > 1.0f) g_bassTrim = 1.0f;
g_bassTrim = (float)((int)(g_bassTrim / kBassTrimStep + 0.5f)) * kBassTrimStep;
if (FILE *cfg = fopen(kBassTrimFile, "wt"))
{
fprintf(cfg, "%.2f\n", g_bassTrim);
fclose(cfg);
}
Tell("Audio bass trim " << (int)(g_bassTrim * 100.0f + 0.5f) << "%\n");
}
float
RPBassTrim()
{
return g_bassTrim;
}
//
// The gain a zone takes at the current trim. 1.0 whenever the player has not
// touched it, so the default costs one multiply by one.
//
float
RPBufferBassGain(int buffer_index)
{
if (g_bassTrim >= 0.999f || g_bufferBassDepth == NULL
|| buffer_index < 0 || buffer_index >= g_numBuffers)
{
return 1.0f;
}
return 1.0f - (1.0f - g_bassTrim) * g_bufferBassDepth[buffer_index];
}
//#############################################################################
//####################### AudioObjectStream #############################
//#############################################################################
//
//#############################################################################
// AudioObjectStream
//#############################################################################
//
AudioObjectStream::AudioObjectStream(
ResourceDescription *resource_description,
Entity *the_entity
):
PlugStream(resource_description)
{
entity = the_entity;
}
//
//#############################################################################
// AudioObjectStream
//#############################################################################
//
AudioObjectStream::AudioObjectStream()
{
entity = NULL;
}
//
//#############################################################################
// ~AudioObjectStream
//#############################################################################
//
AudioObjectStream::~AudioObjectStream()
{
}
//
//#############################################################################
// TestInstance
//#############################################################################
//
Logical
AudioObjectStream::TestInstance() const
{
PlugStream::TestInstance();
if (entity != NULL)
{
Check(entity);
}
return True;
}
//
//#############################################################################
// MakeObjectImplementation
//#############################################################################
//
RegisteredClass*
AudioObjectStream::MakeObjectImplementation(Enumeration class_ID)
{
Check(this);
Verify(class_ID != RegisteredClass::NullClassID);
//
//-----------------------------------------------------------------------
// HACK - Constructors should be referenced through registration, for
// now just use a switch statement
//-----------------------------------------------------------------------
//
RegisteredClass *object;
switch (class_ID)
{
//
// Static resources ...
//
case RegisteredClass::PatchLevelOfDetailClassID:
object = new PatchLevelOfDetail(this);
break;
case RegisteredClass::PatchResourceClassID:
object = new PatchResource(this);
break;
case RegisteredClass::AudioResourceIndexClassID:
object = new AudioResourceIndex(this);
break;
//
// Location and sources ...
//
case RegisteredClass::L4AudioLocationClassID:
object = new L4AudioLocation(this, entity);
break;
case RegisteredClass::DirectPatchSourceClassID:
object = new DirectPatchSource(this, entity);
break;
case RegisteredClass::Dynamic3DPatchSourceClassID:
object = new Dynamic3DPatchSource(this, entity);
break;
case RegisteredClass::Static3DPatchSourceClassID:
object = new Static3DPatchSource(this, entity);
break;
//
// Audio components ...
//
case RegisteredClass::AudioControlMixerClassID:
object = new AudioControlMixer(this, entity);
break;
case RegisteredClass::AudioControlMultiplierClassID:
object = new AudioControlMultiplier(this, entity);
break;
case RegisteredClass::AudioResourceSelectorClassID:
object = new AudioResourceSelector(this, entity);
break;
case RegisteredClass::AudioControlSmootherClassID:
object = new AudioControlSmoother(this, entity);
break;
case RegisteredClass::AudioSampleAndHoldClassID:
object = new AudioSampleAndHold(this, entity);
break;
case RegisteredClass::AudioControlSendClassID:
object = new AudioControlSend(this, entity);
break;
case RegisteredClass::AudioControlSequenceClassID:
object = new AudioControlSequence(this, entity);
break;
case RegisteredClass::AudioControlSplitterClassID:
object = new AudioControlSplitter(this, entity);
break;
case RegisteredClass::AudioLFOClassID:
object = new AudioLFO(this, entity);
break;
//
// Audio watchers ...
//
case RegisteredClass::AudioMotionTriggerClassID:
object = new AudioMotionTrigger(this, entity);
break;
case RegisteredClass::AudioMotionScaleClassID:
object = new AudioMotionScale(this, entity);
break;
case RegisteredClass::AudioHingeScaleClassID:
object = new AudioHingeScale(this, entity);
break;
case RegisteredClass::AudioScalarTriggerClassID:
object = new AudioScalarTrigger(this, entity);
break;
case RegisteredClass::AudioScalarScaleClassID:
object = new AudioScalarScale(this, entity);
break;
case RegisteredClass::AudioLogicalTriggerClassID:
object = new AudioLogicalTrigger(this, entity);
break;
case RegisteredClass::AudioEnumerationTriggerClassID:
object = new AudioEnumerationTrigger(this, entity);
break;
case RegisteredClass::AudioIntegerTriggerClassID:
object = new AudioIntegerTrigger(this, entity);
break;
case RegisteredClass::AudioControlsButtonTriggerClassID:
object = new AudioControlsButtonTrigger(this, entity);
break;
case RegisteredClass::AudioStateTriggerClassID:
object = new AudioStateTrigger(this, entity);
break;
case RegisteredClass::AudioIdleWatcherClassID:
object = new AudioIdleWatcher(this, entity);
break;
case RegisteredClass::AudioEnumerationDeltaTriggerClassID:
object = new AudioEnumerationDeltaTrigger(this, entity);
break;
case RegisteredClass::AudioScalarDeltaTriggerClassID:
object = new AudioScalarDeltaTrigger(this, entity);
break;
case RegisteredClass::L4AudioCollisionTriggerClassID:
object = new L4AudioCollisionTrigger(this, entity);
break;
case RegisteredClass::AudioMessageWatcherClassID:
object = new AudioMessageWatcher(this, entity);
break;
case RegisteredClass::AudioControlsButtonMessageWatcherClassID:
object = new AudioControlsButtonMessageWatcher(this, entity);
break;
//
// Inherited behavior
//
default:
object = PlugStream::MakeObjectImplementation(class_ID);
break;
}
return object;
}
//
//#############################################################################
// CreatedObjectImplementation
//#############################################################################
//
void
AudioObjectStream::CreatedObjectImplementation(
RegisteredClass *object,
ObjectID object_ID
)
{
Check(this);
Check(object);
Verify(object_ID != NullObjectID);
//
// std::Decide which index to add object to
//
switch (object->GetClassID())
{
case RegisteredClass::PatchLevelOfDetailClassID:
case RegisteredClass::PatchResourceClassID:
case RegisteredClass::AudioResourceIndexClassID:
AddGlobalPlug(Cast_Object(Plug*, object), object_ID);
break;
default:
AddLocalPlug(Cast_Object(Plug*, object), object_ID);
break;
}
}
//
//#############################################################################
// BuildFromPageImplementation
//#############################################################################
//
void
AudioObjectStream::BuildFromPageImplementation(
NameList *name_list,
Enumeration class_ID_enumeration,
ObjectID object_ID
)
{
Check(this);
Verify(class_ID_enumeration != RegisteredClass::NullClassID);
Verify(object_ID != NullObjectID);
//
//-----------------------------------------------------------------------
// HACK - Page interpreters should be referenced through registration,
// for now just use a switch statement
//-----------------------------------------------------------------------
//
RegisteredClass::ClassID
class_ID = (RegisteredClass::ClassID)class_ID_enumeration;
switch (class_ID)
{
//
// Static resources ...
//
case RegisteredClass::PatchLevelOfDetailClassID:
PatchLevelOfDetail::BuildFromPage(this, name_list, class_ID, object_ID);
break;
case RegisteredClass::PatchResourceClassID:
PatchResource::BuildFromPage(this, name_list, class_ID, object_ID);
break;
case RegisteredClass::AudioResourceIndexClassID:
AudioResourceIndex::BuildFromPage(this, name_list, class_ID, object_ID);
break;
//
// Location and sources ...
//
case RegisteredClass::L4AudioLocationClassID:
L4AudioLocation::BuildFromPage(this, name_list, class_ID, object_ID);
break;
case RegisteredClass::DirectPatchSourceClassID:
DirectPatchSource::BuildFromPage(this, name_list, class_ID, object_ID);
break;
case RegisteredClass::Dynamic3DPatchSourceClassID:
Dynamic3DPatchSource::BuildFromPage(this, name_list, class_ID, object_ID);
break;
case RegisteredClass::Static3DPatchSourceClassID:
Static3DPatchSource::BuildFromPage(this, name_list, class_ID, object_ID);
break;
//
// Audio components ...
//
case RegisteredClass::AudioControlMixerClassID:
AudioControlMixer::BuildFromPage(this, name_list, class_ID, object_ID);
break;
case RegisteredClass::AudioControlMultiplierClassID:
AudioControlMultiplier::BuildFromPage(this, name_list, class_ID, object_ID);
break;
case RegisteredClass::AudioResourceSelectorClassID:
AudioResourceSelector::BuildFromPage(this, name_list, class_ID, object_ID);
break;
case RegisteredClass::AudioControlSmootherClassID:
AudioControlSmoother::BuildFromPage(this, name_list, class_ID, object_ID);
break;
case RegisteredClass::AudioSampleAndHoldClassID:
AudioSampleAndHold::BuildFromPage(this, name_list, class_ID, object_ID);
break;
case RegisteredClass::AudioControlSendClassID:
AudioControlSend::BuildFromPage(this, name_list, class_ID, object_ID);
break;
case RegisteredClass::AudioControlSequenceClassID:
AudioControlSequence::BuildFromPage(this, name_list, class_ID, object_ID);
break;
case RegisteredClass::AudioControlSplitterClassID:
AudioControlSplitter::BuildFromPage(this, name_list, class_ID, object_ID);
break;
case RegisteredClass::AudioLFOClassID:
AudioLFO::BuildFromPage(this, name_list, class_ID, object_ID);
break;
//
// Audio watchers ...
//
case RegisteredClass::AudioMotionTriggerClassID:
AudioMotionTrigger::BuildFromPage(this, name_list, class_ID, object_ID);
break;
case RegisteredClass::AudioMotionScaleClassID:
AudioMotionScale::BuildFromPage(this, name_list, class_ID, object_ID);
break;
case RegisteredClass::AudioHingeScaleClassID:
AudioHingeScale::BuildFromPage(this, name_list, class_ID, object_ID);
break;
case RegisteredClass::AudioScalarTriggerClassID:
AudioScalarTrigger::BuildFromPage(this, name_list, class_ID, object_ID);
break;
case RegisteredClass::AudioScalarScaleClassID:
AudioScalarScale::BuildFromPage(this, name_list, class_ID, object_ID);
break;
case RegisteredClass::AudioLogicalTriggerClassID:
AudioLogicalTrigger::BuildFromPage(this, name_list, class_ID, object_ID);
break;
case RegisteredClass::AudioEnumerationTriggerClassID:
AudioEnumerationTrigger::BuildFromPage(this, name_list, class_ID, object_ID);
break;
case RegisteredClass::AudioIntegerTriggerClassID:
AudioIntegerTrigger::BuildFromPage(this, name_list, class_ID, object_ID);
break;
case RegisteredClass::AudioControlsButtonTriggerClassID:
AudioControlsButtonTrigger::BuildFromPage(this, name_list, class_ID, object_ID);
break;
case RegisteredClass::AudioStateTriggerClassID:
AudioStateTrigger::BuildFromPage(this, name_list, class_ID, object_ID);
break;
case RegisteredClass::AudioIdleWatcherClassID:
AudioIdleWatcher::BuildFromPage(this, name_list, class_ID, object_ID);
break;
case RegisteredClass::AudioEnumerationDeltaTriggerClassID:
AudioEnumerationDeltaTrigger::BuildFromPage(this, name_list, class_ID, object_ID);
break;
case RegisteredClass::AudioScalarDeltaTriggerClassID:
AudioScalarDeltaTrigger::BuildFromPage(this, name_list, class_ID, object_ID);
break;
case RegisteredClass::L4AudioCollisionTriggerClassID:
L4AudioCollisionTrigger::BuildFromPage(this, name_list, class_ID, object_ID);
break;
case RegisteredClass::AudioMessageWatcherClassID:
AudioMessageWatcher::BuildFromPage(this, name_list, class_ID, object_ID);
break;
case RegisteredClass::AudioControlsButtonMessageWatcherClassID:
AudioControlsButtonMessageWatcher::BuildFromPage(this, name_list, class_ID, object_ID);
break;
//
// Inherited behavior
//
default:
PlugStream::BuildFromPageImplementation(name_list, class_ID, object_ID);
break;
}
}
//
//#############################################################################
// BuiltFromPageImplementation
//#############################################################################
//
void
AudioObjectStream::BuiltFromPageImplementation(
Enumeration class_ID,
ObjectID object_ID,
const CString &object_name
)
{
Check(this);
Verify(class_ID != RegisteredClass::NullClassID);
Verify(object_ID != NullObjectID);
Check(&object_name);
//
// std::Decide which index to add object to
//
if (object_name.Length() == 0)
return;
switch (class_ID)
{
case RegisteredClass::PatchLevelOfDetailClassID:
case RegisteredClass::PatchResourceClassID:
case RegisteredClass::AudioResourceIndexClassID:
AddGlobalObjectID(object_ID, object_name);
break;
default:
AddLocalObjectID(object_ID, object_name);
break;
}
}
//#############################################################################
//###################### L4AudioResourceManager #########################
//#############################################################################
//
//#############################################################################
// L4AudioResourceManager
//#############################################################################
//
L4AudioResourceManager::L4AudioResourceManager()
{
g_buffers = NULL;
g_numBuffers = 0;
}
//
//#############################################################################
// ~L4AudioResourceManager
//#############################################################################
//
L4AudioResourceManager::~L4AudioResourceManager()
{
}
//
//#############################################################################
// TestInstance
//#############################################################################
//
Logical
L4AudioResourceManager::TestInstance() const
{
Node::TestInstance();
return True;
}
//
//#############################################################################
// PreloadResources
//#############################################################################
//
void
L4AudioResourceManager::PreloadResources(
NotationFile *notation_file
)
{
Check(this);
Check(notation_file);
//Check(audio_hardware);
//Determine how many loadable samples exist
g_numBuffers=0;
for(int i=1; i <= 2; i++)
{
for (int j=0; j < 100; j++)
{
if (PRESET_isImplemented(i,j))
{
g_numBuffers += PRESET_getNumSamples(i,j);
}
}
}
//Generate the buffers
if (g_numBuffers > 0)
{
//Generate buffers
alGetError();
g_buffers = new ALuint[g_numBuffers];
alGenBuffers(g_numBuffers,g_buffers);
ALenum error;
if ((error = alGetError()) != AL_NO_ERROR)
{
DEBUG_STREAM << "OpenAL has encountered an error while initializing the buffers." << std::endl;
delete [] g_buffers;
g_buffers = NULL;
g_numBuffers = 0;
}
else
{
//
// Parallel to g_buffers: how much of the bass trim each zone takes.
//
RPBassTrimInitialize();
g_bufferBassDepth = new float[g_numBuffers];
for (int b = 0; b < g_numBuffers; b++)
{
g_bufferBassDepth[b] = 0.0f;
}
}
}
int bufferInd = 0;
//Load buffers
for(int i=1; i<=2 && bufferInd < g_numBuffers; i++)
{
for(int j=0; j<100 && bufferInd < g_numBuffers; j++)
{
if (PRESET_isImplemented(i,j))
{
for (int k=0; k < PRESET_getNumSamples(i,j) && bufferInd < g_numBuffers; k++)
{
SAMPLEINFO info = PRESET_getSampleInfo(i,j,k);
//Load WAV to buffer
char *data;
int size;
//Open WAV
char fullname[50];
strcpy_s(fullname,50,"AUDIO\\");
strcat_s(fullname,50,info.file);
SF_INFO *sfInfo = new SF_INFO[2];
//SF_INFO is working different than expected!
sfInfo->format = 0;
SNDFILE *file = sf_open(fullname,SFM_READ,sfInfo);
if (file == NULL)
{
DEBUG_STREAM << "Failed to open." << std::endl;
}
unsigned long formatBits = 0;
unsigned long sampleRate = sfInfo->samplerate;
size = sfInfo->frames;
bool isMono = (sfInfo->channels == 1);
if (sfInfo->format & SF_FORMAT_PCM_S8)
{
formatBits = 8;
} else if (sfInfo->format & SF_FORMAT_PCM_16)
{
formatBits = 16;
} else
{
DEBUG_STREAM << "BAD FORMAT" << std::endl;
}
ALenum format;
if (formatBits == 8)
{
if (isMono)
{
format = AL_FORMAT_MONO8;
} else
{
size *= 2;
format = AL_FORMAT_STEREO8;
}
} else if (formatBits == 16)
{
size *= 2;
if (isMono)
{
format = AL_FORMAT_MONO16;
} else
{
size *= 2;
format = AL_FORMAT_STEREO16;
}
}
ALsizei alSampleRate = sampleRate;
//Load size & data
delete [] sfInfo;
data = new char[size];
sf_read_raw(file,data,size);
sf_close(file);
//
// Record which band this zone sits in, for the Home/End bass
// trim. Fixed per buffer; the trim itself is read at mix time.
//
if (g_bufferBassDepth != NULL)
{
g_bufferBassDepth[bufferInd] = RPBassDepthForRate(alSampleRate);
}
//Feed the buffer
alBufferData(g_buffers[bufferInd],format,data,size,alSampleRate);
PRESET_setBufferIndex(i,j,k,bufferInd);
bufferInd++;
delete [] data;
}
}
}
}
//
//----------------------------------------------------------------------
// Load the sound banks
//----------------------------------------------------------------------
//
/*NameList *name_list;
NameList::Entry *entry;
Verify(notation_file->EntryCount("AudioResources") >= 2);
name_list = notation_file->MakeEntryList("AudioResources");
Register_Object(name_list);
entry = name_list->GetFirstEntry();
Check(entry);
while (entry != NULL)
{
Check(entry);
if (entry->IsName("front_audio_resource"))
{
Check_Pointer(entry->GetChar());
Check(audio_hardware->GetFrontCard());
audio_hardware->GetFrontCard()->LoadSBK(entry->GetChar());
}
else
{
Verify(entry->IsName("rear_audio_resource"));
Check_Pointer(entry->GetChar());
Check(audio_hardware->GetRearCard());
audio_hardware->GetRearCard()->LoadSBK(entry->GetChar());
}
entry = entry->GetNextEntry();
}
Unregister_Object(name_list);
delete name_list;*/
//
//----------------------------------------------------------------------
// Create static audio objects
//----------------------------------------------------------------------
//
//
// Get static audio stream resource
//
ResourceDescription *resource_description;
Check(application);
Check(application->GetResourceFile());
resource_description =
application->GetResourceFile()->FindResourceDescription(
"StaticAudioStream",
ResourceDescription::StaticAudioStreamResourceType
);
Check(resource_description);
resource_description->Lock();
//
// Parse the audio object stream
//
AudioObjectStream audio_object_stream(resource_description, NULL);
Check(&audio_object_stream);
audio_object_stream.CreateObjects();
resource_description->Unlock();
}
ALuint AL_getBuffer(int index)
{
//
// 0 is AL_NONE - "no buffer" - which alSourcei accepts and which detaches
// the source rather than crashing. An index that is out of range means a
// zone that does not exist, and the only thing an unchecked lookup here
// can do about it is read whatever lies past the array.
//
if (g_buffers == NULL || index < 0 || index >= g_numBuffers)
{
return 0;
}
return g_buffers[index];
}
//
//#############################################################################
// UnloadResources
//#############################################################################
//
void
L4AudioResourceManager::UnloadResources(
)
{
Check(this);
// Check(audio_hardware);
//Unload wav data
if (g_numBuffers > 0)
{
alGetError();
alDeleteBuffers(g_numBuffers,g_buffers);
ALenum error = alGetError();
if (error != AL_NO_ERROR)
{
DEBUG_STREAM << "OpenAL reached an error when attempting to delete its buffers." << std::endl;
}
}
//
//----------------------------------------------------------------------
// Release banks
//----------------------------------------------------------------------
//
// Check(audio_hardware->GetFrontCard());
// audio_hardware->GetFrontCard()->ReleaseAllBanks();
// Check(audio_hardware->GetRearCard());
// audio_hardware->GetRearCard()->ReleaseAllBanks();
}
//
//#############################################################################
// CreateStaticAudioStreamResource
//#############################################################################
//
ResourceDescription::ResourceID
L4AudioResourceManager::CreateStaticAudioStreamResource(
ResourceFile *resource_file
)
{
Check(resource_file);
//
//----------------------------------------------------------------------
// Open the notation file for the static stream resource
//----------------------------------------------------------------------
//
Tell("Opening Audio Script audio\\static.scp\n");
NotationFile notation_file("audio\\static.scp");
Check(&notation_file);
//
//----------------------------------------------------------------------
// Build the static audio object stream
//----------------------------------------------------------------------
//
AudioObjectStream audio_object_stream;
Check(&audio_object_stream);
audio_object_stream.BuildFromNotationFile(&notation_file, resource_file);
Tell("Closed Audio Script\n");
//
//----------------------------------------------------------------------
// Add the resource
//----------------------------------------------------------------------
//
ResourceDescription *res_description;
res_description =
resource_file->AddResourceMemoryStream(
"StaticAudioStream",
ResourceDescription::StaticAudioStreamResourceType,
1,
ResourceDescription::Preload,
&audio_object_stream
);
Check(res_description);
return res_description->resourceID;
}
//
//#############################################################################
// CreateEntityAudioObjects
//#############################################################################
//
void
L4AudioResourceManager::CreateEntityAudioObjects(
Entity *entity,
AudioRepresentation audio_representation
)
{
Check(this);
Check(entity);
Verify(
audio_representation == InternalAudioRepresentation ||
audio_representation == ExternalAudioRepresentation
);
//
//------------------------------------------------------------------------
// Read the audio resource for this entity
//------------------------------------------------------------------------
//
ResourceDescription *audio_resource_description;
Check(application);
Check(application->GetResourceFile());
audio_resource_description =
application->GetResourceFile()->SearchList(
entity->GetResourceID(),
ResourceDescription::AudioStreamListResourceType
);
if (audio_resource_description == NULL)
return;
//
//------------------------------------------------------------------------
// Read the appropriate resource for an internal or external
// representation
//------------------------------------------------------------------------
//
ResourceDescription *stream_resource_description;
Check(application);
Check(application->GetResourceFile());
Check(audio_resource_description);
stream_resource_description =
application->GetResourceFile()->SearchList(
audio_resource_description->resourceID,
(audio_representation == InternalAudioRepresentation) ?
ResourceDescription::InternalAudioStreamResourceType :
ResourceDescription::ExternalAudioStreamResourceType
);
if (stream_resource_description == NULL)
return;
Check(stream_resource_description);
stream_resource_description->Lock();
audio_resource_description->Lock();
//
//------------------------------------------------------------------------
// Make the audio object stream
//------------------------------------------------------------------------
//
AudioObjectStream
audio_object_stream(
stream_resource_description,
entity
);
Check(&audio_object_stream);
audio_object_stream.CreateObjects();
stream_resource_description->Unlock();
audio_resource_description->Unlock();
}
//
//#############################################################################
// DestroyEntityAudioObjects
//#############################################################################
//
void
L4AudioResourceManager::DestroyEntityAudioObjects(Entity *entity)
{
Check(this);
Check(entity);
//
//--------------------------------------------------------------------------
// Stop and delete all audio objects
//--------------------------------------------------------------------------
//
Entity::AudioSocketIterator
iterator(entity);
Component
*component;
AudioComponent
*audio_component;
Check(&iterator);
while ((component = iterator.ReadAndNext()) != NULL)
{
audio_component = Cast_Object(AudioComponent*, component);
Check(audio_component);
audio_component->ReceiveControl(StopAudioControlID, 0.0f);
audio_component->ReceiveControl(FlushMessagesAudioControlID, 0.0f);
}
iterator.DeletePlugs();
}
//
//#############################################################################
// CreateModelAudioResource
//#############################################################################
//
ResourceDescription::ResourceID
L4AudioResourceManager::CreateModelAudioStreamResource(
ResourceFile *resource_file,
const char *model_name,
NotationFile *model_file,
const ResourceDirectories*
)
{
Check(resource_file);
Check_Pointer(model_name);
Check(model_file);
const char *script_name = NULL;
ResourceDescription::ResourceID resource_id_list[3];
size_t list_length = 0;
resource_id_list[0] = ResourceDescription::NullResourceID;
resource_id_list[1] = ResourceDescription::NullResourceID;
resource_id_list[2] = ResourceDescription::NullResourceID;
//
//--------------------------------------------------------------------------
// Check for the external audio script
//--------------------------------------------------------------------------
//
if (model_file->GetEntry("audio", "external", &script_name))
{
//
// See if the resource already exists
//
ResourceDescription *res_description =
resource_file->FindResourceDescription(
script_name,
ResourceDescription::ExternalAudioStreamResourceType
);
//
//-----------------------------------------------------------------------
// If not, create it
//-----------------------------------------------------------------------
//
if (res_description == NULL)
{
//
// Make the file name
//
CString file_name(script_name);
CString prefix("audio\\");
file_name = prefix + file_name;
//
// Build the memory stream
//
Tell("Opening Audio Script " << file_name << "\n");
NotationFile script_file(file_name);
AudioObjectStream audio_object_stream;
Check(&script_file);
Check(&audio_object_stream);
audio_object_stream.BuildFromNotationFile(&script_file, resource_file);
Tell("Closed Audio Script\n");
//
// Add the resource
//
res_description =
resource_file->AddResourceMemoryStream(
script_name,
ResourceDescription::ExternalAudioStreamResourceType,
1,
ResourceDescription::Preload,
&audio_object_stream
);
}
Check(res_description);
resource_id_list[list_length++] = res_description->resourceID;
}
//
//--------------------------------------------------------------------------
// Check for the internal audio script
//--------------------------------------------------------------------------
//
if (model_file->GetEntry("audio", "internal", &script_name))
{
//
// See if the resource already exists
//
ResourceDescription *res_description =
resource_file->FindResourceDescription(
script_name,
ResourceDescription::InternalAudioStreamResourceType
);
//
// If not, create it
//
if (res_description == NULL)
{
//
// Make the file name
//
CString file_name(script_name);
CString prefix("audio\\");
file_name = prefix + file_name;
//
// Build the memory stream
//
Tell("Opening Audio Script " << file_name << "\n");
NotationFile script_file(file_name);
AudioObjectStream audio_object_stream;
Check(&script_file);
Check(&audio_object_stream);
audio_object_stream.BuildFromNotationFile(&script_file, resource_file);
Tell("Closed Audio Script\n");
//
// Add the resource
//
res_description =
resource_file->AddResourceMemoryStream(
script_name,
ResourceDescription::InternalAudioStreamResourceType,
1,
ResourceDescription::Preload,
&audio_object_stream
);
}
Check(res_description);
resource_id_list[list_length++] = res_description->resourceID;
}
//
//--------------------------------------------------------------------------
// Check for the audio entity model
//--------------------------------------------------------------------------
//
const char *audio_entity_model = NULL;
if (model_file->GetEntry("audio", "model", &audio_entity_model))
{
//
// The resource should already exist
//
ResourceDescription *res_description =
resource_file->FindResourceDescription(
audio_entity_model,
ResourceDescription::ModelListResourceType
);
if (res_description == NULL)
{
std::cout << "audio_entity_model == " << audio_entity_model;
Fail("L4AudioResourceManager::CreateModelAudioStreamResource - model not found");
}
Check(res_description);
resource_id_list[list_length++] = res_description->resourceID;
}
//
//--------------------------------------------------------------------------
// If either script has been created, then create the resource list
//--------------------------------------------------------------------------
//
if (list_length > 0)
{
ResourceDescription *res_description =
resource_file->AddResourceList(
model_name,
ResourceDescription::AudioStreamListResourceType,
1,
ResourceDescription::Preload,
resource_id_list,
list_length
);
Check(res_description);
return res_description->resourceID;
}
return ResourceDescription::NullResourceID;
}