Files
arcattackandClaude Opus 4.8 1d019e8109 Audio: footstep pulse VERIFIED end-to-end to the matcher; volume chain traced (task #50)
Footstep chain progress (each step empirically verified):
- The FootStep watcher is an AudioMatchOf<Logical> (AudioLogicalTrigger extends
  AudioMatchOf, NOT AudioTriggerOf): authored config match=1 -> Start(2.0) on a
  DirectPatchSource.  [trigcfg]/[matchcfg] ctor dumps added.
- The pulse was pinned at 1: the decay lived in IntegrateMotion, which the
  current gait path NEVER CALLS.  Moved to Mech::PerformAndWatch (provably
  per-frame) with a time-based 150ms window sized to the ~10Hz watcher poll.
  The matcher now fires per stride (14/run, was 1).
- Every footstep Start is DROPPED at the renderer: the source's volumeScale is
  0 (five VolumeAudioHandler sends of exactly 0 = inert/one-shot primes).  The
  volume arrives through the authored control chain (AudioControlMultiplier
  inputs ctl 100+; one 0 input pins the product).  Chain scales identified on
  this entity: LocalVelocity (live), LocalAcceleration (live), Myomers.
  SpeedEffect (=1.0 healthy; one authored scale maps [0,1]->[1,0] INVERTED),
  UnstablePercentage (INERT PAD -- always 0!), HeatSink.CurrentTemperature.
  PRIME SUSPECT: an UnstablePercentage-fed multiplier input primes 0 once and
  never updates (the pad never changes), pinning footstep volume at 0.
- SF2 numbering fact recorded: 38 presets in AUDIO1 / 5 in AUDIO2 have
  wPreset != file index (gap at preset 77); my table keys by wPreset.  The
  184x ProgramButton01 (bank1 patch83) storm at ~7/s needs an identity check
  against index-numbering (it may be the wrong sample for that patch id).

Diagnostics added (BT_AUDIO_SPATIAL/BT_ATTRBIND_LOG): [trigcfg]/[matchcfg]
ctor dumps, [matchfire] with component ptr+class, [volset] VolumeAudioHandler,
[mult0] multiplier zero-products, scale sends with authored boundaries,
[fswatch] dedicated footstep watcher tracer (g_btFootStepAddr), SetupPatch
ENTRY with bank/patch/state, Simulation::DebugAudioWatcherCount.

NEXT: back UnstablePercentage with a real (live) member -- it is the mech's
stability 0..1 (the stabilityAlarm/gyro family) -- or confirm via a one-run
chain dump which multiplier input pins the FS source; then the footstep
transient should clear the 0.3 drop gate at walking speed.

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

827 lines
21 KiB
C++

#include <cstdlib>
#include "munga.h"
#pragma hdrstop
#include "audsrc.h"
#include "audrend.h"
#include "objstrm.h"
#include "app.h"
#include "namelist.h"
//~~~~~~~~~~~~~~~~~~~~~~~~~~~ AudioSourceStartState ~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
//#############################################################################
//#############################################################################
//
#define DEFAULT_NOTE (60)
AudioSourceStartState::AudioSourceStartState()
{
noteValue = DEFAULT_NOTE;
isDurationSet = False;
hasDuration = False;
duration = 0.0f;
startTime = 0.0f;
endTime = 0.0f;
}
//
//#############################################################################
//#############################################################################
//
AudioSourceStartState::~AudioSourceStartState()
{
}
//
//#############################################################################
//#############################################################################
//
Logical
AudioSourceStartState::TestInstance() const
{
if (isDurationSet || hasDuration)
{
Verify(duration >= 0.0f);
}
if (hasDuration)
{
Verify(endTime >= startTime);
}
return True;
}
//
//#############################################################################
//#############################################################################
//
void
AudioSourceStartState::CalculateDuration(AudioControlValue control_value)
{
//
// Calculate duration
//
if (control_value > 0.0f)
{
hasDuration = True;
endTime = AudioTime::Now();
endTime += control_value;
}
else if (isDurationSet)
{
hasDuration = True;
endTime = AudioTime::Now();
endTime += duration;
isDurationSet = False;
}
else
{
hasDuration = False;
endTime = 0.0f;
}
}
#if 0
//
//#############################################################################
//#############################################################################
//
AudioTime
AudioSourceStartState::GetCurrentRunningTime()
{
Check(this);
AudioTime current_duration = AudioTime::Now();
return (current_duration -= startTime);
}
#endif
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ AudioSource ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
//#############################################################################
//#############################################################################
//
const Receiver::HandlerEntry
AudioSource::MessageHandlerEntries[]=
{
{
AudioSource::StartMessageID,
"Start",
(AudioSource::Handler)&AudioSource::StartMessageHandler
},
{
AudioSource::StopMessageID,
"Stop",
(AudioSource::Handler)&AudioSource::StopMessageHandler
}
};
AudioSource::MessageHandlerSet& AudioSource::GetMessageHandlers()
{
static AudioSource::MessageHandlerSet messageHandlers(ELEMENTS(AudioSource::MessageHandlerEntries), AudioSource::MessageHandlerEntries, AudioComponent::GetMessageHandlers());
return messageHandlers;
}
//
//#############################################################################
//#############################################################################
//
Derivation* AudioSource::GetClassDerivations()
{
static Derivation classDerivations(AudioComponent::GetClassDerivations(), "AudioSource");
return &classDerivations;
}
AudioSource::SharedData
AudioSource::DefaultData(
AudioSource::GetClassDerivations(),
AudioSource::GetMessageHandlers()
);
//
//#############################################################################
//#############################################################################
//
AudioSource::AudioSource(
PlugStream *stream,
Entity *entity
):
AudioComponent(stream, DefaultData)
{
AudioResource *audio_resource;
AudioLocation *audio_location;
AudioSourcePriority priority;
AudioSourceMixPresence mix_presence;
AudioControlValue volume_mix_level;
AudioPitchCents pitch_mix_offset;
AudioControlValue brightness_mix_level;
Logical has_compression_curve;
Scalar seconds;
AudioTime compression_duration;
Logical use_brightness_scale;
Logical use_attack_time_scale;
PlugStream_ReadObjectIDAndFindPlug(stream, &audio_resource);
PlugStream_ReadObjectIDAndFindPlug(stream, &audio_location);
MemoryStream_Read(stream, &priority);
MemoryStream_Read(stream, &mix_presence);
MemoryStream_Read(stream, &volume_mix_level);
MemoryStream_Read(stream, &pitch_mix_offset);
MemoryStream_Read(stream, &brightness_mix_level);
MemoryStream_Read(stream, &has_compression_curve);
MemoryStream_Read(stream, &seconds);
compression_duration = seconds;
MemoryStream_Read(stream, &use_brightness_scale);
MemoryStream_Read(stream, &use_attack_time_scale);
AudioSourceX(
audio_resource,
audio_location,
entity,
priority,
mix_presence,
volume_mix_level,
pitch_mix_offset,
brightness_mix_level,
has_compression_curve,
compression_duration,
use_brightness_scale,
use_attack_time_scale
);
}
//
//#############################################################################
//#############################################################################
//
void
AudioSource::BuildFromPage(
PlugStream *stream,
NameList *name_list,
ClassID class_ID,
ObjectID object_ID
)
{
AudioComponent::BuildFromPage(stream, name_list, class_ID, object_ID);
//
// Store fields
//
CString audio_resource_name("resource");
PlugStream_FindEntryAndWriteObjectID(stream, name_list, audio_resource_name);
CString audio_location_name("location");
PlugStream_FindEntryAndWriteObjectID(stream, name_list, audio_location_name);
MEM_STRM_WRITE_ENTRY(*stream, name_list, Enumeration, priority);
MEM_STRM_WRITE_ENTRY(*stream, name_list, Enumeration, mix_presence);
MEM_STRM_WRITE_ENTRY(*stream, name_list, AudioControlValue, volume_mix_level);
MEM_STRM_WRITE_ENTRY(*stream, name_list, AudioPitchCents, pitch_mix_offset);
MEM_STRM_WRITE_ENTRY(*stream, name_list, AudioControlValue, brightness_mix_level);
MEM_STRM_WRITE_ENTRY(*stream, name_list, Logical, has_compression_curve);
MEM_STRM_WRITE_ENTRY(*stream, name_list, Scalar, compression_duration);
MEM_STRM_WRITE_ENTRY(*stream, name_list, Logical, use_brightness_scale);
MEM_STRM_WRITE_ENTRY(*stream, name_list, Logical, use_attack_time_scale);
}
//
//#############################################################################
//#############################################################################
//
void
AudioSource::AudioSourceX(
AudioResource *audio_resource,
AudioLocation *audio_location,
Entity *entity,
AudioSourcePriority priority,
AudioSourceMixPresence mix_presence,
AudioControlValue volume_mix_level,
AudioPitchCents pitch_mix_offset,
AudioControlValue brightness_mix_level,
Logical has_compression_curve,
const AudioTime &compression_duration,
Logical use_brightness_scale,
Logical use_attack_time_scale
)
{
Check(audio_resource);
Check(audio_location);
Check(entity);
audioLocation = audio_location;
audioResource = audio_resource;
audioSourceState = StoppedAudioSourceState;
attackVolumeScale = 1.0f;
useAttackTimeScale = use_attack_time_scale;
attackTimeScale = 0.0f;
audioSourcePriority = priority;
audioSourceMixPresence = mix_presence;
volumeCompressionScale = 1.0f;
hasCompressionCurve = has_compression_curve;
compressionDuration = compression_duration;
volumeMixScale = volume_mix_level;
pitchMixOffset = pitch_mix_offset;
useBrightnessScale = use_brightness_scale;
brightnessMixScale = brightness_mix_level;
volumeScale = 1.0f;
pitchOffset = 0.0f;
brightnessScale = 1.0f;
nextExecuteFrame = NullAudioFrameCount;
suspendFinishedFrame = NullAudioFrameCount;
entity->AddAudioComponent(this);
}
//
//#############################################################################
//#############################################################################
//
AudioSource::~AudioSource()
{
Verify(audioSourceState == StoppedAudioSourceState);
}
//
//#############################################################################
//#############################################################################
//
Logical
AudioSource::TestInstance() const
{
AudioComponent::TestInstance();
return True;
}
//
// Resource methods
//
//
//#############################################################################
//#############################################################################
//
void
AudioSource::SetAudioResource(AudioResource *audio_resource)
{
Check(this);
Check(audio_resource);
Verify(audioSourceState == StoppedAudioSourceState);
Check(audioResource);
audioResource = audio_resource;
}
//
// Current State/Status Accessors
//
//
//#############################################################################
//#############################################################################
//
void
AudioSource::AssignAudioSourceState(AudioSourceState audio_source_state)
{
Check(this);
//
// Initialize the next start state if the source is now running
//
if (audio_source_state == RunningAudioSourceState)
{
AudioSourceStartState null_state;
nextStartState = null_state;
}
audioSourceState = audio_source_state;
}
//
// Mixing & Logical compression methods
//
//
//#############################################################################
//#############################################################################
//
AudioControlValue
AudioSource::CalculateSourceCompressionEffect()
{
Check(this);
//
// Get current volume level
//
AudioControlValue current_volume_level;
current_volume_level = CalculateSourceVolumeScale();
Verify(current_volume_level >= 0.0f && current_volume_level <= 1.0f);
//
// Calculate the amount of this to apply to compression
//
if (hasCompressionCurve)
{
//
// scale = 1 / (1 + ((1/comp_dur) * duration)^2)
//
Scalar current_running_time = GetCurrentRunningTime();
Scalar compression_duration = compressionDuration;
Scalar duration_factor;
Scalar denominator;
AudioControlValue compression_effect;
Verify(!Small_Enough(compression_duration));
duration_factor = pow((1.0f/compression_duration) * current_running_time, 2.0f);
// Warn(!(duration_factor > 0.0f));
denominator = 1.0f + duration_factor;
Verify(!Small_Enough(denominator));
compression_effect = 1.0f / denominator;
Verify(compression_effect >= 0.0f && compression_effect <=1.0f);
return compression_effect * current_volume_level;
}
return current_volume_level;
}
//
//#############################################################################
//#############################################################################
//
AudioControlValue
AudioSource::CalculateSourceVolumeScale()
{
Check(this);
//
// Execute the watchers that will update the volume related
// member parameters
//
ExecuteWatchers();
//
// Calculate the resulting volume scale
//
AudioControlValue
volume_scale;
volume_scale = volumeScale * volumeMixScale * volumeCompressionScale;
if (getenv("BT_AUDIO_SPATIAL") && volume_scale <= 0.0f) {
static int s_z=0; if (s_z++<40)
DEBUG_STREAM << "[spatial] vol=0 breakdown src=" << (void*)this
<< " volumeScale=" << volumeScale
<< " mixScale=" << volumeMixScale
<< " compression=" << volumeCompressionScale << "\n" << std::flush; }
Clamp(volume_scale, MinAudioVolume, MaxAudioVolume);
return volume_scale;
}
//
//#############################################################################
//#############################################################################
//
AudioControlValue
AudioSource::CalculateSourceBrightnessScale()
{
Check(this);
AudioControlValue brightness_mix_scale;
brightness_mix_scale = brightnessScale * brightnessMixScale;
Clamp(brightness_mix_scale, MinAudioBrightness, MaxAudioBrightness);
return brightness_mix_scale;
}
//
//#############################################################################
//#############################################################################
//
void
AudioSource::ReceiveControl(
AudioControlID control_ID,
AudioControlValue control_value
)
{
Check(this);
switch (control_ID)
{
//
//----------------------------------------------------------------------
// StartAudioControlID
//----------------------------------------------------------------------
//
case StartAudioControlID:
{
StartMessage message(control_value);
Check(application);
Check(application->GetAudioRenderer());
application->GetAudioRenderer()->PostAudioRequestMessage(
this,
&message
);
}
break;
//
//----------------------------------------------------------------------
// StopAudioControlID
//----------------------------------------------------------------------
//
case StopAudioControlID:
{
StopMessage message;
Check(application);
Check(application->GetAudioRenderer());
application->GetAudioRenderer()->PostAudioRequestMessage(
this,
&message
);
}
break;
//
//----------------------------------------------------------------------
// Misc...
//----------------------------------------------------------------------
//
case VolumeAudioControlID:
VolumeAudioHandler(control_value);
break;
case PitchAudioControlID:
PitchAudioHandler(control_value);
break;
case BrightnessAudioControlID:
BrightnessAudioHandler(control_value);
break;
case AttackVolumeAudioControlID:
AttackVolumeAudioHandler(control_value);
break;
case AttackTimeAudioControlID:
AttackTimeAudioHandler(control_value);
break;
case NoteAudioControlID:
NoteAudioHandler(control_value);
break;
case DurationAudioControlID:
DurationAudioHandler(control_value);
break;
case FlushMessagesAudioControlID:
Check(application);
Check(application->GetAudioRenderer());
application->GetAudioRenderer()->FlushAudioMessages(this);
break;
case NullAudioControlID:
case IdleAudioControlID:
break;
default:
Fail("AudioSource::ReceiveControl - Should never reach here");
break;
}
}
//
//#############################################################################
//#############################################################################
//
void
AudioSource::StartMessageHandler(StartMessage *message)
{
SET_AUDIO_RENDERER();
SET_AUDIO_RENDERER_START_HANDLER();
Check(this);
Check(message);
Verify(message->messageID == StartMessageID);
//
//--------------------------------------------------------------------------
// if double trigger condition exists
// then return
//--------------------------------------------------------------------------
//
AudioTime double_trigger_cutoff(AudioDoubleTriggerCutoff);
#if 0
double_trigger_cutoff = AudioDoubleTriggerCutoff;
#endif
if (
audioSourceState == RunningAudioSourceState &&
GetCurrentRunningTime() <= double_trigger_cutoff
)
{
#ifdef LAB_ONLY
Check(application);
Check(application->GetAudioRenderer());
application->GetAudioRenderer()->doubleTriggerCount++;
#endif
CLEAR_AUDIO_RENDERER_START_HANDLER();
CLEAR_AUDIO_RENDERER();
return;
}
//
//--------------------------------------------------------------------------
// If the source is not stopped then stop it
//--------------------------------------------------------------------------
//
if (audioSourceState != StoppedAudioSourceState)
{
StopMessage stop_message;
StopMessageHandler(&stop_message);
}
//
//--------------------------------------------------------------------------
// Verify that the state is stopped
// Copy the next start state into the current start state
// Calculate duration
//--------------------------------------------------------------------------
//
Verify(audioSourceState == StoppedAudioSourceState);
currentStartState = nextStartState;
currentStartState.CalculateDuration(message->controlValue);
//
//--------------------------------------------------------------------------
// Implementation specific start request
//--------------------------------------------------------------------------
//
StartMessageImplementation();
CLEAR_AUDIO_RENDERER_START_HANDLER();
CLEAR_AUDIO_RENDERER();
}
//
//#############################################################################
//#############################################################################
//
void
#if DEBUG_LEVEL>0
AudioSource::StopMessageHandler(StopMessage *message)
#else
AudioSource::StopMessageHandler(StopMessage*)
#endif
{
SET_AUDIO_RENDERER();
SET_AUDIO_RENDERER_STOP_HANDLER();
Check(this);
Check(message);
Verify(message->messageID == StopMessageID);
//
// if the source is stopped
// then return
//
if (audioSourceState == StoppedAudioSourceState)
{
CLEAR_AUDIO_RENDERER_STOP_HANDLER();
CLEAR_AUDIO_RENDERER();
return;
}
//
// Implementation specific stop request
//
StopMessageImplementation();
CLEAR_AUDIO_RENDERER_STOP_HANDLER();
CLEAR_AUDIO_RENDERER();
}
//
//#############################################################################
//#############################################################################
//
void
AudioSource::VolumeAudioHandler(AudioControlValue control_value)
{
Check(this);
Clamp(control_value, MinAudioVolume, MaxAudioVolume);
volumeScale = control_value / (MaxAudioVolume - MinAudioVolume);
if (getenv("BT_AUDIO_SPATIAL")) { static int s_vh=0; if (s_vh++<300)
DEBUG_STREAM << "[volset] src=" << (void*)this << " vol=" << volumeScale << "\n" << std::flush; }
}
//
//#############################################################################
//#############################################################################
//
void
AudioSource::PitchAudioHandler(AudioControlValue control_value)
{
Check(this);
pitchOffset = control_value;
}
//
//#############################################################################
//#############################################################################
//
void
AudioSource::BrightnessAudioHandler(AudioControlValue control_value)
{
Check(this);
Clamp(control_value, MinAudioBrightness, MaxAudioBrightness);
brightnessScale = control_value / (MaxAudioBrightness - MinAudioBrightness);
}
//
//#############################################################################
//#############################################################################
//
void
AudioSource::AttackVolumeAudioHandler(AudioControlValue control_value)
{
Check(this);
Clamp(control_value, MinAudioAttack, MaxAudioAttack);
attackVolumeScale = control_value / (MaxAudioAttack - MinAudioAttack);
}
//
//#############################################################################
//#############################################################################
//
void
AudioSource::AttackTimeAudioHandler(AudioControlValue control_value)
{
Check(this);
Clamp(control_value, MinAudioAttack, MaxAudioAttack);
attackTimeScale = control_value / (MaxAudioAttack - MinAudioAttack);
}
//
//#############################################################################
//#############################################################################
//
void
AudioSource::NoteAudioHandler(AudioControlValue control_value)
{
Check(this);
nextStartState.SetNoteValue(control_value);
}
//
//#############################################################################
//#############################################################################
//
void
AudioSource::DurationAudioHandler(AudioControlValue control_value)
{
Check(this);
nextStartState.SetDurationValue(control_value);
}
//
//#############################################################################
//#############################################################################
//
void
AudioSource::StartImplementation()
{
currentStartState.Start();
}
//
//#############################################################################
//#############################################################################
//
void
AudioSource::StopImplementation()
{
Fail("AudioSource::StopImplementation - Should never reach here");
}
//
//#############################################################################
//#############################################################################
//
void
AudioSource::SuspendImplementation()
{
StopImplementation();
Check(application);
Check(application->GetAudioRenderer());
Check(audioResource);
suspendFinishedFrame =
application->GetAudioRenderer()->GetAudioFrameCount() +
audioResource->GetSuspendDelay();
#ifdef LAB_ONLY
application->GetAudioRenderer()->sourceSuspendCount++;
#endif
}
//
//#############################################################################
//#############################################################################
//
void
AudioSource::ResumeImplementation()
{
StartImplementation();
suspendFinishedFrame = NullAudioFrameCount;
#ifdef LAB_ONLY
Check(application);
Check(application->GetAudioRenderer());
application->GetAudioRenderer()->sourceResumeCount++;
#endif
}
//
//#############################################################################
//#############################################################################
//
void
AudioSource::Execute()
{
Check(this);
//
// Execute source if this is the next execution frame
//
AudioRenderer
*audio_renderer;
Check(application);
audio_renderer = application->GetAudioRenderer();
Check(audio_renderer);
if (nextExecuteFrame <= audio_renderer->GetAudioFrameCount())
{
nextExecuteFrame =
audio_renderer->GetAudioFrameCount() + DefaultAudioFrameDelay;
//
// Update the sources spatial model
// Execute inherited method
// Execute this sources sound model
//
UpdateSpatialModel(audio_renderer->GetAudioHead());
AudioComponent::Execute();
ExecuteModel(False);
}
}