Files
BT411/engine/MUNGA/AUDIO.h
T
arcattackandClaude Opus 4.8 a11a697824 Audio: footsteps arrive on the FIRST stride -- the 10-20 s warm-up bug
User report: footfalls silent for the first 10-20 s of every mission (all
mechs), then solid.  Root cause was three interlocking layers, each measured
with timestamped traces:

1. The authored footstep volume chain (LocalAcceleration [0,10]->ctl100 +
   LocalVelocity [0,0.6]->ctl101 through authored N=30/N=15
   AudioControlSmoothers, fill 0) hangs off the SOURCE's watcher chain
   (scale watches smoother watches mixer watches source), and an idle
   source's chain executes only at Start attempts -- one smoother sample
   per stride.
2. Each hop is frame-gated (AudioComponent::ExecuteWatchers,
   DefaultAudioFrameDelay), so any burst collapses to one execution.
3. The transient drop gate (vol < 0.3, AUDREND) rejected every Start while
   the smoother average crawled up 1/30th per attempt -> ~25 dropped strides
   before the first audible step, then per-frame execution while playing
   kept it warm forever ("solid after that").

Fixes (engine-level, each documented in place):
- AudioScaleOf<T>::Execute now sends EVERY poll (scales are continuous
  value-feeders; the base bitwise change-gate -- Motion::operator== is
  memcmp -- froze on our deterministic gait math, where the original's
  noisy physics floats never bit-repeated.  Triggers/matchers keep the
  change gate: their semantics are edge-based).
- Component/AudioComponent::PrimeWatchers(passes): recursive, GATE-FREE
  watcher pump; AUDREND runs 30 passes on every transient Start request so
  the authored smoothers evaluate at their true steady state before the
  drop gate reads the volume.
- localAcceleration derives via the binary's exact structure: 15-sample
  ring buffers of the raw position derivative + dt (ctor part_012.c:9836,
  derive :15169-15195), in the PerformAndWatch tail so it runs every frame.
- AttributeWatcherOf::GrabCurrentValue private -> protected (the scale
  override calls it).

Verified (30 s walk from cold start): drops 25 -> 3 (the survivors are
authentic quiet-stride gating: first gentle strides at vol ~0.28 vs the 0.3
gate), footfalls deliver from the first stride, 43 delivered with live
per-stride gain variation.  Diag traces added: [accwatch]/[fsscale]/
[smooth]/[smoothcfg]/[motionscalecfg] + timestamps on DROP/volset.

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

664 lines
15 KiB
C++

#pragma once
#include "style.h"
#include "entity.h"
#include "sphere.h"
#include "slot.h"
#include "table.h"
class PlugStream;
//
//--------------------------------------------------------------------------
// Support types
//--------------------------------------------------------------------------
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Time types ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
typedef long AudioTick;
typedef long AudioDivisionsPerBeat;
typedef long AudioTempo;
typedef long AudioFrameCount;
const AudioFrameCount NullAudioFrameCount = -1;
const AudioFrameCount DefaultAudioFrameDelay = 2;
extern const Scalar AudioDoubleTriggerCutoff;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Resource types ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
typedef int AudioResourceID;
typedef int AudioVoiceCount;
typedef Scalar AudioRadiationProfile;
typedef Scalar AudioPitchCents;
enum AudioRenderType
{
TransientAudioRenderType = 0,
SustainedAudioRenderType = 1
};
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Source types ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
enum AudioRepresentation
{
InternalAudioRepresentation = 0,
ExternalAudioRepresentation = 1
};
#define AUDIO_SOURCE_PRIORITY_COUNT (5)
enum AudioSourcePriority
{
MinAudioSourcePriority = 0,
LowAudioSourcePriority = 1,
MedAudioSourcePriority = 2,
HighAudioSourcePriority = 3,
MaxAudioSourcePriority = 4
};
enum AudioSourceState
{
StoppedAudioSourceState = 0,
DormantAudioSourceState = 1,
RunningAudioSourceState = 2,
SuspendedAudioSourceState = 3
};
enum AudioSourceMixPresence
{
ManualAudioSourceMixPresence = 0,
MaxAudioSourceMixPresence = 1,
HighAudioSourceMixPresence = 2,
MedAudioSourceMixPresence = 3,
LowAudioSourceMixPresence = 4,
MinAudioSourceMixPresence = 5
};
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Control types ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
enum AudioControlID
{
NullAudioControlID = 0,
StartAudioControlID,
StopAudioControlID,
VolumeAudioControlID,
PitchAudioControlID,
BrightnessAudioControlID,
AttackVolumeAudioControlID,
AttackTimeAudioControlID,
NoteAudioControlID,
IdleAudioControlID,
DurationAudioControlID,
FlushMessagesAudioControlID,
TempoAudioControlID,
AudioControlIDCount
};
typedef Scalar AudioControlValue;
extern const AudioControlValue MaxAudioVolume;
extern const AudioControlValue MinAudioVolume;
extern const AudioControlValue LowAudioVolumeThreshold;
extern const AudioControlValue MaxAudioBrightness;
extern const AudioControlValue MinAudioBrightness;
extern const AudioControlValue MaxAudioAttack;
extern const AudioControlValue MinAudioAttack;
#if 0
//~~~~~~~~~~~~~~~~~~~~~~~~~~~ AudioClippingSphere ~~~~~~~~~~~~~~~~~~~~~~~~~~
class AudioClippingSphere SIGNATURED
{
public:
AudioClippingSphere();
~AudioClippingSphere();
void
SetReferenceRadius(Scalar radius);
void
SetCurrentOrigin(const Point3D &origin);
void
SetCurrentScale(Scalar scale);
Logical
Contains(const Point3D &point) const;
private:
Scalar
referenceRadius;
Sphere
currentSphere;
};
//~~~~~~~~~~~~~~~~~~~~~~~ AudioClippingSphere inlines ~~~~~~~~~~~~~~~~~~~~~~
inline AudioClippingSphere::AudioClippingSphere():
referenceRadius(100.0f),
currentSphere(0.0f, 0.0f, 0.0f, 100.0f)
{
}
inline AudioClippingSphere::~AudioClippingSphere()
{
}
inline void
AudioClippingSphere::SetReferenceRadius(Scalar radius)
{
Check(this);
referenceRadius = radius;
currentSphere.radius = radius;
}
inline void
AudioClippingSphere::SetCurrentOrigin(const Point3D &origin)
{
Check(this);
currentSphere.center = origin;
}
inline void
AudioClippingSphere::SetCurrentScale(Scalar scale)
{
Check(this);
currentSphere.radius = scale * referenceRadius;
}
inline Logical
AudioClippingSphere::Contains(const Point3D &point) const
{
Check(this);
return currentSphere.Contains(point);
}
#endif
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ AudioHead ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
class AudioSource;
class AudioHead SIGNATURED
{
public:
//
//-----------------------------------------------------------------------
// Construction, Destruction, Testing
//-----------------------------------------------------------------------
//
AudioHead();
~AudioHead();
Logical
TestInstance() const;
//
//-----------------------------------------------------------------------
// Get current frame of audio simulation
//-----------------------------------------------------------------------
//
AudioFrameCount
GetAudioFrameCount()
{return audioFrameCount;}
//
//-----------------------------------------------------------------------
// Sets the location and orientation of the head
//-----------------------------------------------------------------------
//
virtual void
LinkToEntity(Entity *entity);
Entity*
GetHeadEntity();
//
//-----------------------------------------------------------------------
// Get the ear to world linear matrix
//-----------------------------------------------------------------------
//
virtual LinearMatrix
GetEarToWorld();
//
//-----------------------------------------------------------------------
// Execute
//-----------------------------------------------------------------------
//
virtual void
Execute();
//
//-----------------------------------------------------------------------
// Sets the distance between ears. Expressed in meters.
//-----------------------------------------------------------------------
//
void
SetDistanceBetweenEars(Scalar distance)
{distanceBetweenEars = distance;}
Scalar
GetDistanceBetweenEars()
{return distanceBetweenEars;}
//
//-----------------------------------------------------------------------
// Controls Doppler effects
//-----------------------------------------------------------------------
//
void
ControlDopplerEffect(
Scalar doppler_constant,
Scalar sound_speed
);
Scalar
GetAudioSoundSpeed()
{return audioSoundSpeed;}
AudioPitchCents
GetAudioDopplerConstant()
{return audioDopplerConstant;}
//
//-----------------------------------------------------------------------
// Reverb scaling
//-----------------------------------------------------------------------
//
void
SetReverbToDryRatio(Scalar ratio)
{reverbToDryRatio = ratio;}
Scalar
GetReverbToDryRatio()
{return reverbToDryRatio;}
// HACK
void
SetGlobalReverbScale(Scalar global_reverb_scale)
{globalReverbScale = global_reverb_scale;}
Scalar
GetGlobalReverbScale()
{return globalReverbScale;}
//
//-----------------------------------------------------------------------
// Controls the attenuation of an audio source with respect to its
// distance from the head.
//-----------------------------------------------------------------------
//
void
ControlAmplitudeRollOff(
Scalar exponent,
Scalar amplitude_rolloff_knee,
Scalar distance_scale
);
Scalar
GetAmplitudeRollOffExponent()
{return amplitudeRollOffExponent;}
Scalar
GetAmplitudeRollOffKnee()
{return amplitudeRollOffKnee;}
Scalar
GetAmplitudeRollOffDistanceScale()
{return amplitudeRollOffDistanceScale;}
//
//-----------------------------------------------------------------------
// Controls the attenuation of the high frequencies
// of an audio source with respect to its distance from
// the head.
//-----------------------------------------------------------------------
//
void
ControlHighFrequencyRollOff(
Scalar exponent,
Scalar high_frequency_rolloff_knee,
Scalar distance_scale
);
Scalar
GetHighFrequencyRollOffExponent()
{return highFrequencyRollOffExponent;}
Scalar
GetHighFrequencyRollOffKnee()
{return highFrequencyRollOffKnee;}
Scalar
GetHighFrequencyRollOffDistanceScale()
{return highFrequencyRollOffDistanceScale;}
//
//-----------------------------------------------------------------------
// Controls source compression
//-----------------------------------------------------------------------
//
void
ControlSourceCompression(
Scalar exponent,
Scalar scale
);
Scalar
GetSourceCompressionExponent()
{return sourceCompressionExponent;}
Scalar
GetSourceCompressionScale()
{return sourceCompressionScale;}
//
//-----------------------------------------------------------------------
// Defines the audio clipping sphere
//-----------------------------------------------------------------------
//
void
DefineClippingSphere(Scalar radius);
Logical
IsPointClipped(const Point3D &point);
Scalar
GetClippingRadius()
{return clippingRadius;}
//
//-----------------------------------------------------------------------
// ITD Difference
//-----------------------------------------------------------------------
//
void
SetITDDifference(Scalar itd_difference)
{itdDifference = itd_difference;}
Scalar
GetITDDifference()
{return itdDifference;}
//
//-----------------------------------------------------------------------
// The SetPositionalCulling method allows the rendering process to
// not play audio locations whose positions are masked by other
// audio sources. This has found to be useful for controlling
// audio when resources are limited, but must be
// selectable globally or on a per audio effect basis.
//-----------------------------------------------------------------------
//
void
SetPositionalCulling(Logical turn_on);
private:
//
//-----------------------------------------------------------------------
// Private data
//-----------------------------------------------------------------------
//
AudioFrameCount
audioFrameCount;
SlotOf<Entity*>
headEntitySocket;
Scalar
clippingRadius;
Scalar
distanceBetweenEars;
Scalar
amplitudeRollOffExponent,
amplitudeRollOffKnee,
amplitudeRollOffDistanceScale;
Scalar
highFrequencyRollOffExponent,
highFrequencyRollOffKnee,
highFrequencyRollOffDistanceScale;
Scalar
reverbToDryRatio;
Scalar
audioSoundSpeed;
Scalar
audioDopplerConstant;
Scalar
sourceCompressionExponent,
sourceCompressionScale;
Scalar
itdDifference;
Scalar
globalReverbScale;
};
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ AudioHead inlines ~~~~~~~~~~~~~~~~~~~~~~~~~
inline Entity*
AudioHead::GetHeadEntity()
{
Check(this);
return headEntitySocket.GetCurrent();
}
//##########################################################################
//######################### AudioComponent ###########################
//##########################################################################
class AudioComponent__ReceiveControlMessage;
class AudioComponent:
public Component
{
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Construction, Destruction, Testing
//
public:
AudioComponent(
PlugStream *stream,
SharedData &shared_data = DefaultData
);
~AudioComponent();
Logical
TestInstance() const;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Audio Component Methods
//
public:
void
AddWatcher(Component *component);
void
ExecuteWatchers();
// (task #50, AUDIO_FIDELITY F19) gate-free watcher pump for the transient
// cold-start prime: ExecuteWatchers above is frame-gated
// (DefaultAudioFrameDelay), so N calls in one tick collapse to one.
// Recurses through the watcher CHAIN (source <- mixer <- smoother <-
// scale), each hop gate-free.
virtual void
PrimeWatchers(int passes);
void
Execute();
virtual void
ReceiveControl(
AudioControlID control_ID,
AudioControlValue control_value
);
void
PostReceiveControl(
AudioControlID control_ID,
AudioControlValue control_value
);
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Message Support
//
public:
enum {
ReceiveControlMessageID = Component::NextMessageID,
NextMessageID
};
typedef AudioComponent__ReceiveControlMessage
ReceiveControlMessage;
static const HandlerEntry
MessageHandlerEntries[];
//static MessageHandlerSet MessageHandlers;
static MessageHandlerSet& GetMessageHandlers();
void
ReceiveControlMessageHandler(
ReceiveControlMessage *message
);
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Shared Data Support
//
public:
static Derivation *GetClassDerivations();
static SharedData
DefaultData;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Private Data
//
private:
AudioFrameCount
nextExecuteWatcherFrame;
ChainOf<Component*>
audioWatcherSocket;
};
//~~~~~~~~~~~~~~~~~~~~~~~~~~ AudioComponent inlines ~~~~~~~~~~~~~~~~~~~~~~~~
inline void
AudioComponent::AddWatcher(Component *component)
{
Check(component);
Check(&audioWatcherSocket);
audioWatcherSocket.Add(component);
}
//~~~~~~~~~~~~~~~~~~ AudioComponent__ReceiveControlMessage ~~~~~~~~~~~~~~~~~
class AudioComponent__ReceiveControlMessage:
public Receiver::Message
{
friend class AudioComponent;
public:
AudioComponent__ReceiveControlMessage(
AudioControlID control_ID,
AudioControlValue control_value
);
private:
AudioControlID
controlID;
AudioControlValue
controlValue;
};
//~~~~~~~~~~~~~~~~~~~~~~~~ Resource type inlines ~~~~~~~~~~~~~~~~~~~~~~~~~~~
inline MemoryStream&
MemoryStream_Read(
MemoryStream* stream,
AudioRenderType *output
)
{
return stream->ReadBytes(output, sizeof(*output));
}
inline MemoryStream&
MemoryStream_Write(
MemoryStream* stream,
const AudioRenderType *input
)
{
return stream->WriteBytes(input, sizeof(*input));
}
inline MemoryStream&
MemoryStream_Read(
MemoryStream* stream,
AudioRepresentation *output
)
{
return stream->ReadBytes(output, sizeof(*output));
}
inline MemoryStream&
MemoryStream_Write(
MemoryStream* stream,
const AudioRepresentation *input
)
{
return stream->WriteBytes(input, sizeof(*input));
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~ Source type inlines ~~~~~~~~~~~~~~~~~~~~~~~~~~
inline MemoryStream&
MemoryStream_Read(
MemoryStream* stream,
AudioSourceState *output
)
{
return stream->ReadBytes(output, sizeof(*output));
}
inline MemoryStream&
MemoryStream_Write(
MemoryStream* stream,
const AudioSourceState *input
)
{
return stream->WriteBytes(input, sizeof(*input));
}
inline MemoryStream&
MemoryStream_Read(
MemoryStream* stream,
AudioSourcePriority *output
)
{
return stream->ReadBytes(output, sizeof(*output));
}
inline MemoryStream&
MemoryStream_Write(
MemoryStream* stream,
const AudioSourcePriority *input
)
{
return stream->WriteBytes(input, sizeof(*input));
}
inline MemoryStream&
MemoryStream_Read(
MemoryStream* stream,
AudioSourceMixPresence *output
)
{
return stream->ReadBytes(output, sizeof(*output));
}
inline MemoryStream&
MemoryStream_Write(
MemoryStream* stream,
const AudioSourceMixPresence *input
)
{
return stream->WriteBytes(input, sizeof(*input));
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~ Control type inlines ~~~~~~~~~~~~~~~~~~~~~~~~~
inline MemoryStream&
MemoryStream_Read(
MemoryStream* stream,
AudioControlID *output
)
{
return stream->ReadBytes(output, sizeof(*output));
}
inline MemoryStream&
MemoryStream_Write(
MemoryStream* stream,
const AudioControlID *input
)
{
return stream->WriteBytes(input, sizeof(*input));
}