User: "when I start moving all the sounds fade away, no footsteps." Diagnosis chain (all empirical, BT_AUDIO_SPATIAL/BT_ATTRBIND_LOG traces): - The audio head DOES track the mech (listener-relative positioning verified while driving) -- not a spatial bug. - The idle sounds correctly STOP on leaving the standing state; the MOVING sounds never started because every POLLED audio watcher was dead: the reconstructed Mech::PerformAndWatch replaced the engine performance but dropped the ExecuteWatchers() step from the engine tail (Simulation:: PerformAndWatch = Perform -> ExecuteWatchers -> WriteSimulationUpdate). Only PUSHED StateIndicator watchers (SetState->Execute) ever fired -- which is exactly why state sounds worked but footsteps/motion-scaled audio didn't. FIX: poll ExecuteWatchers() (AreWatchersDelayed-gated) before the update write. Immediately unlocks the polled family: MissileLoaded01/LaserLoaded01 ready dings, ProgramButton01, motion scales (PlayNote 15 -> 30 per run). - FootStep (0x1e) backed REAL: was the inert attrPad (an AudioLogicalTrigger polls it -- threshold-crossing pulse per foot plant). New footStep member, pulsed by SetBodyAnimation on entering any locomotion clip 1..0x17 (runtime stride alternation MEASURED as body states 12<->13 -- the enum-name numbering does not match runtime clip ids), decayed by IntegrateMotion (~1/3 s). - Footstep volume is speed-scaled (AudioMotionScale on LocalVelocity): at standstill the transient start computes volume 0 and the renderer drops it (LowAudioVolumeThreshold) -- so footfalls are audible at real walking speed. Diagnostics kept (BT_AUDIO_SPATIAL): spatial dist/vol per source, CLIPPED/DROP/ START at the request gate, vol=0 factor breakdown, scale->0 sends, trigger notifications, watcher poll-rate probe. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
531 lines
14 KiB
C++
531 lines
14 KiB
C++
#include "munga.h"
|
|
#pragma hdrstop
|
|
|
|
#include "audrend.h"
|
|
#include "audent.h"
|
|
#include "jmover.h"
|
|
#include "app.h"
|
|
|
|
//#############################################################################
|
|
//########################### AudioRenderer #############################
|
|
//#############################################################################
|
|
|
|
//
|
|
//#############################################################################
|
|
// AudioRenderer
|
|
//#############################################################################
|
|
//
|
|
AudioRenderer::AudioRenderer(RendererRate render_rate):
|
|
Renderer(
|
|
render_rate,
|
|
MaxRendererComplexity,
|
|
DefaultRendererPriority,
|
|
AudioInterestType,
|
|
DefaultInterestDepth,
|
|
AudioRendererClassID
|
|
),
|
|
audioEventSocket(NULL, False)
|
|
{
|
|
audioHead = NULL;
|
|
|
|
#ifdef LAB_ONLY
|
|
sourceClippedCount = 0;
|
|
sourceStartCount = 0;
|
|
sourceSuspendCount = 0;
|
|
sourceResumeCount = 0;
|
|
doubleTriggerCount = 0;
|
|
resourceStealCount = 0;
|
|
resourceStealPriorityCount = 0;
|
|
resourceStealVolumeCount = 0;
|
|
stealShortDurationCount = 0;
|
|
#endif
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// ~AudioRenderer
|
|
//#############################################################################
|
|
//
|
|
AudioRenderer::~AudioRenderer()
|
|
{
|
|
Unregister_Object(audioHead);
|
|
delete audioHead;
|
|
|
|
#ifdef LAB_ONLY
|
|
cout << "AudioRenderer::~AudioRenderer - sourceClippedCount="
|
|
<< sourceClippedCount << "\n";
|
|
cout << "AudioRenderer::~AudioRenderer - sourceStartCount="
|
|
<< sourceStartCount << "\n";
|
|
cout << "AudioRenderer::~AudioRenderer - sourceSuspendCount="
|
|
<< sourceSuspendCount << "\n";
|
|
cout << "AudioRenderer::~AudioRenderer - sourceResumeCount="
|
|
<< sourceResumeCount << "\n";
|
|
cout << "AudioRenderer::~AudioRenderer - doubleTriggerCount="
|
|
<< doubleTriggerCount << "\n";
|
|
cout << "AudioRenderer::~AudioRenderer - resourceStealCount="
|
|
<< resourceStealCount << "\n";
|
|
cout << "AudioRenderer::~AudioRenderer - resourceStealPriorityCount="
|
|
<< resourceStealPriorityCount << "\n";
|
|
cout << "AudioRenderer::~AudioRenderer - resourceStealVolumeCount="
|
|
<< resourceStealVolumeCount << "\n";
|
|
cout << "AudioRenderer::~AudioRenderer - stealShortDurationCount="
|
|
<< stealShortDurationCount << "\n";
|
|
#endif
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// Initialize
|
|
//#############################################################################
|
|
//
|
|
void
|
|
AudioRenderer::Initialize()
|
|
{
|
|
audioHead = MakeAudioHead();
|
|
Register_Object(audioHead);
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// TestInstance
|
|
//#############################################################################
|
|
//
|
|
Logical
|
|
AudioRenderer::TestInstance() const
|
|
{
|
|
Renderer::TestInstance();
|
|
if (audioHead != NULL)
|
|
{
|
|
Check(audioHead);
|
|
}
|
|
Check(&audioEventSocket);
|
|
return True;
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// LinkToEntity
|
|
//#############################################################################
|
|
//
|
|
void
|
|
AudioRenderer::LinkToEntity(Entity *entity)
|
|
{
|
|
Check(this);
|
|
Check(entity);
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Link the head to the entity
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
Check(audioHead);
|
|
audioHead->LinkToEntity(entity);
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Call inherited method
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
Renderer::LinkToEntity(entity);
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// ExecuteImplementation
|
|
//#############################################################################
|
|
//
|
|
void
|
|
AudioRenderer::ExecuteImplementation(
|
|
RendererComplexity,
|
|
RendererOrigin::InterestingEntityIterator*
|
|
)
|
|
{
|
|
Check(this);
|
|
Check(audioHead);
|
|
audioHead->Execute();
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// PostAudioRequestMessage
|
|
//#############################################################################
|
|
//
|
|
void
|
|
AudioRenderer::PostAudioRequestMessage(
|
|
AudioSource *audio_source,
|
|
AudioSource::RequestMessage *message
|
|
)
|
|
{
|
|
Check(this);
|
|
Check(audio_source);
|
|
Check(message);
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// If audio source is clipped and the source is transient
|
|
// Then ignore
|
|
// Else set priority and volume
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
AudioSourcePriority
|
|
audio_source_priority;
|
|
AudioControlValue
|
|
audio_source_volume_scale;
|
|
|
|
if (audio_source->IsAudioSourceClipped(GetAudioHead()))
|
|
{
|
|
if (getenv("BT_AUDIO_SPATIAL")) { static int s_cl=0; if (s_cl++<40)
|
|
DEBUG_STREAM << "[spatial] CLIPPED src=" << (void*)audio_source
|
|
<< " headPos=(" << GetAudioHead()->GetHeadEntity()->localOrigin.linearPosition.x
|
|
<< "," << GetAudioHead()->GetHeadEntity()->localOrigin.linearPosition.z << ")\n" << std::flush; }
|
|
//
|
|
// If it is a transient source then ignore request
|
|
//
|
|
if (audio_source->GetAudioRenderType() == TransientAudioRenderType)
|
|
{
|
|
#ifdef LAB_ONLY
|
|
sourceClippedCount++;
|
|
#endif
|
|
return;
|
|
}
|
|
audio_source_priority = audio_source->GetAudioSourcePriority();
|
|
audio_source_volume_scale = 0.0f;
|
|
}
|
|
else
|
|
{
|
|
audio_source_priority = audio_source->GetAudioSourcePriority();
|
|
audio_source_volume_scale = audio_source->CalculateSourceVolumeScale();
|
|
if (getenv("BT_AUDIO_SPATIAL")) { static int s_vs=0; if ((s_vs++ % 120)==0)
|
|
DEBUG_STREAM << "[spatial] request src=" << (void*)audio_source
|
|
<< " vol=" << audio_source_volume_scale
|
|
<< " headPos=(" << GetAudioHead()->GetHeadEntity()->localOrigin.linearPosition.x
|
|
<< "," << GetAudioHead()->GetHeadEntity()->localOrigin.linearPosition.z << ")\n" << std::flush; }
|
|
}
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// If this a transient source and volume is lower than threshold
|
|
// Then return
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
if (
|
|
message->controlID == StartAudioControlID &&
|
|
audio_source->GetAudioRenderType() == TransientAudioRenderType &&
|
|
audio_source_volume_scale < LowAudioVolumeThreshold
|
|
)
|
|
{
|
|
if (getenv("BT_AUDIO_SPATIAL")) { static int s_dr=0; if (s_dr++<40)
|
|
DEBUG_STREAM << "[spatial] DROP transient start src=" << (void*)audio_source
|
|
<< " vol=" << audio_source_volume_scale
|
|
<< " (below threshold " << LowAudioVolumeThreshold << ")\n" << std::flush; }
|
|
#ifdef LAB_ONLY
|
|
sourceClippedCount++;
|
|
#endif
|
|
return;
|
|
}
|
|
if (getenv("BT_AUDIO_SPATIAL") && message->controlID == StartAudioControlID) {
|
|
static int s_st=0; if (s_st++<40)
|
|
DEBUG_STREAM << "[spatial] START request src=" << (void*)audio_source
|
|
<< " vol=" << audio_source_volume_scale << "\n" << std::flush; }
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Create audio weight based on priority and volume
|
|
// Add audio event
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
AudioWeighting
|
|
audio_weight(audio_source_priority, audio_source_volume_scale);
|
|
AudioEvent
|
|
*audio_event;
|
|
|
|
audio_event = new AudioEvent(audio_source, message);
|
|
Register_Object(audio_event);
|
|
audioEventSocket.AddValue(audio_event, audio_weight);
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// ProcessAudioRequestMessage
|
|
//#############################################################################
|
|
//
|
|
Logical
|
|
AudioRenderer::ProcessAudioRequestMessage()
|
|
{
|
|
//
|
|
// Process high priority, high volume event
|
|
//
|
|
AudioEventIterator
|
|
iterator(&audioEventSocket);
|
|
AudioEvent
|
|
*audio_event;
|
|
|
|
Logical stuff = False;
|
|
while ((audio_event = iterator.GetCurrent()) != NULL)
|
|
{
|
|
stuff = True;
|
|
Check(audio_event);
|
|
audio_event->Process();
|
|
//return True;
|
|
}
|
|
return stuff;
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// FlushAudioMessages
|
|
//#############################################################################
|
|
//
|
|
void
|
|
AudioRenderer::FlushAudioMessages(AudioSource *audio_source)
|
|
{
|
|
Check(this);
|
|
Check(audio_source);
|
|
|
|
//
|
|
// Process matching events
|
|
//
|
|
AudioEventIterator
|
|
iterator(&audioEventSocket);
|
|
AudioEvent
|
|
*audio_event;
|
|
|
|
while ((audio_event = iterator.ReadAndNext()) != NULL)
|
|
{
|
|
Check(audio_event);
|
|
if (audio_event->targetReceiver.GetCurrent() == audio_source)
|
|
{
|
|
if (audio_event->messageToSend->controlID == StopAudioControlID)
|
|
{
|
|
audio_event->Process();
|
|
}
|
|
else
|
|
{
|
|
Unregister_Object(audio_event);
|
|
delete audio_event;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// MakeAudioHead
|
|
//#############################################################################
|
|
//
|
|
AudioHead*
|
|
AudioRenderer::MakeAudioHead()
|
|
{
|
|
return new AudioHead;
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// StartEntityEffectImplementation
|
|
//#############################################################################
|
|
//
|
|
void
|
|
AudioRenderer::StartEntityEffectImplementation(
|
|
Entity *parent_entity,
|
|
DamageZone *damage_zone,
|
|
ResourceDescription::ResourceID resource_ID
|
|
)
|
|
{
|
|
SET_AUDIO_RENDERER();
|
|
|
|
Check(this);
|
|
Check(parent_entity);
|
|
Check(damage_zone);
|
|
Verify(resource_ID != ResourceDescription::NullResourceID);
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Get the audio resource for this effect
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
ResourceDescription *audio_resource_description;
|
|
|
|
Check(application);
|
|
Check(application->GetResourceFile());
|
|
audio_resource_description =
|
|
application->GetResourceFile()->SearchList(
|
|
resource_ID,
|
|
ResourceDescription::AudioStreamListResourceType
|
|
);
|
|
if (audio_resource_description == NULL)
|
|
{
|
|
CLEAR_AUDIO_RENDERER();
|
|
return;
|
|
}
|
|
Check(audio_resource_description);
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Get the resource ID for the model resource for the audio effect entity
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
ResourceDescription *model_resource_description;
|
|
|
|
Check(application);
|
|
Check(application->GetResourceFile());
|
|
Check(audio_resource_description);
|
|
model_resource_description =
|
|
application->GetResourceFile()->SearchList(
|
|
audio_resource_description->resourceID,
|
|
ResourceDescription::ModelListResourceType
|
|
);
|
|
if (model_resource_description == NULL)
|
|
{
|
|
CLEAR_AUDIO_RENDERER();
|
|
return;
|
|
}
|
|
Check(model_resource_description);
|
|
model_resource_description->Lock();
|
|
audio_resource_description->Lock();
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Get the entity segment
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
Entity *linked_entity;
|
|
AudioRepresentation audio_representation;
|
|
EntitySegment *parent_entity_segment;
|
|
|
|
linked_entity = GetLinkedEntity();
|
|
Check(linked_entity);
|
|
audio_representation =
|
|
(AudioRepresentation)parent_entity->GetAudioRepresentation(
|
|
linked_entity
|
|
);
|
|
|
|
parent_entity_segment = damage_zone->GetCurrentEffectSite(
|
|
(audio_representation == InternalAudioRepresentation) ?
|
|
DamageZone::InternalAudioEffectSite :
|
|
DamageZone::ExternalAudioEffectSite
|
|
);
|
|
Check(parent_entity_segment);
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Create the audio effect entity with this resource
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
JointedMover *jointed_mover;
|
|
|
|
Verify(parent_entity->IsDerivedFrom(JointedMover::GetClassDerivations()));
|
|
jointed_mover = Cast_Object(JointedMover*, parent_entity);
|
|
Check(jointed_mover);
|
|
|
|
AudioEntity::MakeMessage
|
|
make_message(
|
|
model_resource_description->resourceID,
|
|
jointed_mover,
|
|
parent_entity_segment
|
|
);
|
|
#if DEBUG_LEVEL>0
|
|
AudioEntity *audio_entity =
|
|
#endif
|
|
AudioEntity::Make(&make_message);
|
|
Register_Object(audio_entity);
|
|
|
|
model_resource_description->Unlock();
|
|
audio_resource_description->Unlock();
|
|
|
|
CLEAR_AUDIO_RENDERER();
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~ AudioRenderer profile bits ~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
#if defined(TRACE_AUDIO_RENDERER)
|
|
BitTrace Audio_Renderer("Audio Renderer");
|
|
#endif
|
|
|
|
#if defined(TRACE_AUDIO_RENDERER_CREATE_OBJECTS)
|
|
BitTrace Audio_Renderer_Create_Objects("Audio Renderer Create Objects");
|
|
#endif
|
|
|
|
#if defined(TRACE_AUDIO_RENDERER_DESTROY_OBJECTS)
|
|
BitTrace Audio_Renderer_Destroy_Objects("Audio Renderer Destroy Objects");
|
|
#endif
|
|
|
|
#if defined(TRACE_AUDIO_RENDERER_START_HANDLER)
|
|
BitTrace Audio_Renderer_Start_Handler("Audio Renderer Start Handler");
|
|
#endif
|
|
|
|
#if defined(TRACE_AUDIO_RENDERER_STOP_HANDLER)
|
|
BitTrace Audio_Renderer_Stop_Handler("Audio Renderer Stop Handler");
|
|
#endif
|
|
|
|
#if defined(TRACE_AUDIO_RENDERER_EXECUTE)
|
|
BitTrace Audio_Renderer_Execute("Audio Renderer Execute");
|
|
#endif
|
|
|
|
//#############################################################################
|
|
//############################## AudioEvent #############################
|
|
//#############################################################################
|
|
|
|
MemoryBlock *AudioEvent::GetAllocatedMemory()
|
|
{
|
|
static MemoryBlock allocatedMemory(sizeof(Event), AUDIOEVENT_MEMORYBLOCK_ALLOCATION, AUDIOEVENT_MEMORYBLOCK_ALLOCATION, "AudioEvents");
|
|
return &allocatedMemory;
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
AudioEvent::AudioEvent(
|
|
AudioSource *target,
|
|
AudioSource::RequestMessage *message
|
|
):
|
|
targetReceiver(this)
|
|
{
|
|
Check(target);
|
|
Check(message);
|
|
|
|
//
|
|
// Store the message
|
|
//
|
|
size_t long_size = (message->messageLength+3)>>2;
|
|
messageToSend = (AudioSource::RequestMessage*)new long[long_size];
|
|
Check_Pointer(messageToSend);
|
|
Register_Pointer(messageToSend);
|
|
|
|
Mem_Copy(
|
|
messageToSend,
|
|
message,
|
|
message->messageLength,
|
|
long_size*sizeof(long)
|
|
);
|
|
|
|
//
|
|
// Store the target receiver
|
|
//
|
|
targetReceiver.Add(target);
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
AudioEvent::~AudioEvent()
|
|
{
|
|
Unregister_Pointer(messageToSend);
|
|
delete messageToSend;
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
void
|
|
AudioEvent::ReleaseLinkHandler(
|
|
Socket*,
|
|
Plug*
|
|
)
|
|
{
|
|
Unregister_Object(this);
|
|
delete this;
|
|
}
|