Files
BT411/engine/MUNGA_L4/L4AUDLVL.cpp
T
arcattackandClaude Opus 4.8 ea8555480e Audio: fix stuck MissileLoading loop -- ProjectileWeapon never reached a ready state (task #50)
The runaway loop the user heard was MissileLoading01: the ProjectileWeapon
(SRM/ballistic) state machine set Firing(0)/Loading(3)/Jammed(5)/NoAmmo(7) but
NEVER a ready/Loaded level, so at idle the launcher parked in Loading(3) forever.
Its WeaponState audio (weaponAlarm, now StateIndicator-firing) therefore looped
MissileLoading indefinitely -- the loop stops only when the alarm LEAVES the
loading state.  (The laser sibling was fine: the Emitter DOES reach Loaded when
its charge tops off, so LaserACharge stopped.)

Fix: in ProjectileWeaponSimulation's default (idle/ready) branch, drive the state
from readiness -- Loaded(2, charge + ammo up = silent ready) once ReadyToFire,
else Loading(3, reloading).  SetLevel fires audio only on a real change, so this
yields the natural Firing -> Loading(reload) -> Loaded(ready) cycle and the
MissileLoading loop now stops when the launcher finishes reloading.  Verified:
weapon cycles Loading(3, recoil>0)->Loaded(2, recoil<=0); the only remaining
continuous loop is EnginePower (correct).  Fire path unchanged.

Also: master volume default kept; richer BT_AUDIO_LOG StopNote/SetupPatch traces.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 22:29:05 -05:00

317 lines
8.3 KiB
C++

#include <cstdlib>
#include "mungal4.h"
#pragma hdrstop
#include "l4audlvl.h"
#include "l4audres.h"
#include "..\munga\audrend.h"
#include "..\munga\objstrm.h"
#include "..\munga\namelist.h"
#include "openal/al.h"
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PatchLevelOfDetail ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#if DEBUG_LEVEL>0
TableOf<PatchLevelOfDetail*, unsigned int>
PatchLevelOfDetail::patchTableSocket(NULL, True);
#endif
//
//#############################################################################
//#############################################################################
//
PatchLevelOfDetail::PatchLevelOfDetail(PlugStream *stream):
AudioLevelOfDetail(stream)
{
MemoryStream_Read(stream, &bankID);
MemoryStream_Read(stream, &patchID);
MemoryStream_Read(stream, &maxMIDIFilterCutoff);
Warn(GetVoiceCount() > 4); // HACK - AWE appears to only play 1st 4 voices
#ifdef LAB_ONLY
setupCount = 0;
#endif
//
// Keep table of created patchs to verify that duplicates
// are not created, could move this to tool time
//
#if DEBUG_LEVEL>0
unsigned int index_value = (bankID * 1000) + patchID;
if (patchTableSocket.Find(index_value) != NULL)
{
Dump((int)bankID);
Dump((int)patchID);
}
Verify(patchTableSocket.Find(index_value) == NULL);
patchTableSocket.AddValue(this, index_value);
#endif
}
//
//#############################################################################
//#############################################################################
//
void
PatchLevelOfDetail::BuildFromPage(
PlugStream *stream,
NameList *name_list,
ClassID class_ID,
ObjectID object_ID
)
{
AudioLevelOfDetail::BuildFromPage(stream, name_list, class_ID, object_ID);
//
// Store fields
//
MEM_STRM_WRITE_ENTRY(*stream, name_list, SBKBankID, bank_ID);
MEM_STRM_WRITE_ENTRY(*stream, name_list, SBKPatchID, patch_ID);
MEM_STRM_WRITE_ENTRY(*stream, name_list, MIDINRPNValue, max_filter_cutoff);
}
//
//#############################################################################
//#############################################################################
//
PatchLevelOfDetail::~PatchLevelOfDetail()
{
#ifdef LAB_ONLY
cout << "PatchLevelOfDetail::~PatchLevelOfDetail\t";
cout << (int)bankID << ":" << (int)patchID << "\t";
cout << "setupCount=\t" << setupCount << "\n";
#endif
/*if (m_numSources > 0 && m_sources != NULL)
{
for (int i=0; i < m_numSources; i++)
{
//Detach buffer
alSourcei(m_sources[i],AL_BUFFER,0);
//Stop source
ALenum state;
alGetSourcei(m_sources[i],AL_SOURCE_STATE,&state);
if (state == AL_PLAYING)
{
alSourceStop(m_sources[i]);
}
}
//Destroy all sources
alDeleteSources(m_numSources,m_sources);
}*/
}
//
//#############################################################################
//#############################################################################
//
Logical
PatchLevelOfDetail::TestInstance() const
{
AudioLevelOfDetail::TestInstance();
return True;
}
//
//#############################################################################
//#############################################################################
//
void
PatchLevelOfDetail::SetupPatch(SourceSet sourceSet)
{
// Check(this);
//// Check(channel);
//
// #ifdef LAB_ONLY
// setupCount++;
// #endif
SAMPLEINFO info;
//Attach buffers
for (int i=0; i < sourceSet.count; i++)
{
info = PRESET_getSampleInfo(bankID,patchID,i);
if (info.bufferIndex >= 0)
{
ALenum sourceState;
alGetSourcei(sourceSet.sources[i], AL_SOURCE_STATE, &sourceState);
if (sourceState == AL_INITIAL)
{
alSourcei(sourceSet.sources[i],AL_BUFFER,AL_getBuffer(info.bufferIndex));
{ static int s_a=0; if (getenv("BT_AUDIO_LOG") && s_a++<24) DEBUG_STREAM << "[audio] SetupPatch src=" << sourceSet.sources[i] << " file=" << (info.file?info.file:"?") << " loop=" << (info.loop==ForceStatic?0:1) << "\n" << std::flush; }
alSourcei(sourceSet.sources[i],AL_SOURCE_RELATIVE,AL_TRUE);
AudioRenderer *render = application->GetAudioRenderer();
AudioHead *head = render->GetAudioHead();
alSource3f(sourceSet.sources[i],AL_POSITION,0,0,0);
alSource3f(sourceSet.sources[i],AL_VELOCITY,0,0,0);
if (info.loop == ForceStatic)
{
alSourcei(sourceSet.sources[i],AL_LOOPING,0);
} else
{
alSourcei(sourceSet.sources[i],AL_LOOPING,1);
}
}
}
}
//
// Set the channel to the bank and program, reset patch controls
//
/* channel->SelectBank(bankID);
channel->SendProgramChange(patchID);
channel->SendController(MIDI_CONTROL_RESET, MIDI_MAX_CONTROL_VALUE);*/
//TODO: Sync up OpenAL source info appropriately
/*#if 0
Tell((int)bankID << ":" << (int)patchID << "\n");
#endif*/
}
void PatchLevelOfDetail::PlayNote(SourceSet sourceSet)
{
if (sourceSet.count > 0 && sourceSet.sources != NULL)
{
alGetError();
//DEBUG_STREAM << "Playing bank " << (int)bankID << ", patch " << (int)patchID << std::endl;
for (int i = 0; i < sourceSet.count; i++)
{
ALenum state;
alGetSourcei(sourceSet.sources[i], AL_SOURCE_STATE, &state);
if (state != AL_PLAYING)
{
alSourcePlay(sourceSet.sources[i]);
{ static int s_p=0; if (getenv("BT_AUDIO_LOG") && s_p++<30) DEBUG_STREAM << "[audio] PlayNote alSourcePlay src=" << sourceSet.sources[i] << "\n" << std::flush; }
}
ALenum error = alGetError();
if (error != AL_NO_ERROR)
{
DEBUG_STREAM << "Playing hit an error: " << error << std::endl;
return;
}
}
}
}
void PatchLevelOfDetail::StopNote(SourceSet sourceSet)
{
if (getenv("BT_AUDIO_LOG")) { static int s_s=0; if (s_s++<400) { DEBUG_STREAM << "[audio] StopNote"; for (int _i=0;_i<sourceSet.count;_i++) DEBUG_STREAM << " src=" << sourceSet.sources[_i]; DEBUG_STREAM << "\n" << std::flush; } }
if (sourceSet.count >0 && sourceSet.sources != NULL)
{
alSourceStopv(sourceSet.count, sourceSet.sources);
alSourceRewindv(sourceSet.count, sourceSet.sources);
}
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PatchResource ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
//#############################################################################
//#############################################################################
//
PatchResource::PatchResource(PlugStream *stream):
AudioResource(stream)
{
}
//
//#############################################################################
//#############################################################################
//
void
PatchResource::BuildFromPage(
PlugStream *stream,
NameList *name_list,
ClassID class_ID,
ObjectID object_ID
)
{
AudioResource::BuildFromPage(stream, name_list, class_ID, object_ID);
}
//
//#############################################################################
//#############################################################################
//
PatchResource::~PatchResource()
{
}
//
//#############################################################################
//#############################################################################
//
Logical
PatchResource::TestInstance() const
{
AudioResource::TestInstance();
return True;
}
//
//#############################################################################
//#############################################################################
//
void
PatchResource::SetupPatch(SourceSet sourceSet)
{
Check(this);
// Check(channel);
PatchLevelOfDetail *patch_level_of_detail =
Cast_Object(PatchLevelOfDetail*, GetAudioLevelOfDetail());
Check(patch_level_of_detail);
patch_level_of_detail->SetupPatch(sourceSet);
}
void PatchResource::PlayNote(SourceSet sourceSet)
{
Check(this);
// Check(channel);
PatchLevelOfDetail *patch_level_of_detail =
Cast_Object(PatchLevelOfDetail*, GetAudioLevelOfDetail());
Check(patch_level_of_detail);
patch_level_of_detail->PlayNote(sourceSet);
}
void PatchResource::StopNote(SourceSet sourceSet)
{
Check(this);
// Check(channel);
PatchLevelOfDetail *patch_level_of_detail =
Cast_Object(PatchLevelOfDetail*, GetAudioLevelOfDetail());
Check(patch_level_of_detail);
patch_level_of_detail->StopNote(sourceSet);
}
//
//#############################################################################
//#############################################################################
//
MIDINRPNValue
PatchResource::GetMaxMIDIFilterCutoff()
{
Check(this);
PatchLevelOfDetail *patch_level_of_detail =
Cast_Object(PatchLevelOfDetail*, GetAudioLevelOfDetail());
Check(patch_level_of_detail);
return patch_level_of_detail->GetMaxMIDIFilterCutoff();
}