Files
RP412/MUNGA/AUDIO.cpp
T
CydandClaude Opus 5 ce1b0ab9c3 Sounds fade, dull and doppler with distance again
The OpenAL port kept the whole authored audio model and then threw most of
its output away. Every frame the engine computed a distance-attenuation
curve, a high-frequency rolloff, doppler cents, a reverb level and a
front/rear placement, and every one of those consumers had been commented
out when the two AWE32 cards were replaced. What reached the speakers was
OpenAL's own defaults instead: a straight-line fade to silence, no
filtering, doppler at the wrong constants with an inverted velocity, no
reverb, and every cockpit sound dead centre.

Restored, per AUDIO.INI, which is byte-identical to the file that shipped
in August 1995:

  - the authored knee/rolloff distance curve, replacing AL_LINEAR_DISTANCE.
    This also un-blinds the transient cull, the voice-steal weighting and
    the mix ducking, which all key off it and were treating far sources as
    full presence
  - the CC7 squared volume law; writing the scale linearly ran everything
    about 6 dB hot at mid-scale
  - brightness and distance muffling, and the wet-exterior/dry-cockpit
    reverb split, both through a new OpenAL EFX bridge
  - doppler on the moving-source path only, as the original had it
  - front/rear placement from the authored position enum

The larger find is that AL_PITCH was never called anywhere in the tree, so
the entire pitch chain was inert - not only doppler but pitch_mix_offset,
which our own sequences author 97 times. Doppler alone would have changed
nothing audible.

Note pitch is applied for parity with the BT engine but is identity here:
our content predates NoteAudioControlID, so every source runs at note 60.

Builds clean on VS2022 Release|Win32. Smoke-tested against vRIO on COM1 -
reaches gameplay and holds a steady frame loop. ALC_EXT_EFX is present on
the build machine with all nine entry points, so the filter and reverb work
is live rather than inert. Not yet listened to on the pod, which is the
real test: the volume law changes the level of everything.

docs/SOUND.md documents the original two-card quadraphonic design, where
the surviving original assets are, and what remains.

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

483 lines
12 KiB
C++

#include "munga.h"
#pragma hdrstop
#include "audio.h"
#include "audrend.h"
#include "app.h"
#include "..\munga_l4\openal\al.h"
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Audio Constants ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
const Scalar AudioDoubleTriggerCutoff = 0.2f;
const AudioControlValue MaxAudioVolume = 1.0f;
const AudioControlValue MinAudioVolume = 0.0f;
const AudioControlValue LowAudioVolumeThreshold = 0.3f;
const AudioControlValue MaxAudioBrightness = 1.0f;
const AudioControlValue MinAudioBrightness = 0.0f;
const AudioControlValue MaxAudioAttack = 1.0f;
const AudioControlValue MinAudioAttack = 0.0f;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ AudioHead ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
//#############################################################################
//#############################################################################
//
AudioHead::AudioHead():
headEntitySocket(NULL)
{
//
// Start frame counter at 0, other counts should be at
// NullAudioFrameCount
//
audioFrameCount = 0;
//
// Other audio parameters
//
clippingRadius = 100.0f;
distanceBetweenEars = 1.0f;
amplitudeRollOffExponent = 1.0f;
amplitudeRollOffKnee = 10.0f,
amplitudeRollOffDistanceScale = 0.05f;
highFrequencyRollOffExponent = 0.5f;
highFrequencyRollOffKnee = 10.0f;
highFrequencyRollOffDistanceScale = 0.05f;
reverbToDryRatio = 0.1f;
audioSoundSpeed = 345.0f;
audioDopplerConstant = 20.0f;
sourceCompressionExponent = 0.5f;
sourceCompressionScale = 0.5f;
Check_Fpu();
}
//
//#############################################################################
//#############################################################################
//
AudioHead::~AudioHead()
{
Check(this);
Check_Fpu();
}
//
//#############################################################################
//#############################################################################
//
Logical
AudioHead::TestInstance() const
{
Check(&headEntitySocket);
return True;
}
//
//#############################################################################
//#############################################################################
//
void
AudioHead::LinkToEntity(Entity *entity)
{
Check(this);
Check(entity);
//
// Remove existing entity, add new one
//
if (headEntitySocket.GetCurrent() != NULL)
{
headEntitySocket.Remove();
}
headEntitySocket.Add(entity);
// FIDELITY (docs/SOUND.md F3/F10): the engine computes the AUTHORED distance
// attenuation curve (AUDIO.INI amplitude_rolloff knee/exponent ->
// AudioLocation::distanceVolumeScale) and the AUTHORED doppler-cents model
// (doppler_range=600 / speed_of_sound=250) on every spatial update. Disable
// OpenAL's own models so they cannot double-apply or fight them:
// AL_LINEAR_DISTANCE faded distant audio to zero on a straight line where the
// authored curve still sits near 44% at the clip edge, and AL doppler ran at
// the wrong constants with a sign-inverted velocity feed.
alDistanceModel(AL_NONE);
alDopplerFactor(0.0f);
#if 0
//
// Make the new clipping sphere
//
clippingSphere.SetCurrentOrigin(entity->localOrigin.linearPosition);
#endif
}
//
//#############################################################################
//#############################################################################
//
LinearMatrix
AudioHead::GetEarToWorld()
{
Check(this);
Entity *entity = headEntitySocket.GetCurrent();
Check(entity);
return entity->localToWorld;
}
//
//#############################################################################
//#############################################################################
//
void
AudioHead::Execute()
{
Check(this);
//
// Increment frame counter
//
audioFrameCount = Now().ticks;
Verify(audioFrameCount < LONG_MAX);
//set current listener orientation
Vector3D headVelocity;
headVelocity.MultiplyByInverse(this->GetHeadEntity()->GetWorldLinearVelocity(), this->GetHeadEntity()->localToWorld);
alListener3f(AL_VELOCITY, -headVelocity.x, -headVelocity.y, -headVelocity.z);
#if 0
//
// Get the entity
//
Entity *entity = headEntitySocket.GetCurrent();
Check(entity);
//
// Make the new clipping sphere
//
clippingSphere.SetCurrentOrigin(entity->localOrigin.linearPosition);
#endif
}
//
//#############################################################################
//#############################################################################
//
void
AudioHead::DefineClippingSphere(Scalar radius)
{
Check(this);
clippingRadius = radius;
#if 0
//
// Get entity position
//
Entity *entity;
Point3D position(0.0f, 0.0f, 0.0f);
if ((entity = headEntitySocket.GetCurrent()) != NULL)
{
Check(entity);
position = entity->localOrigin.linearPosition;
}
//
// Make the new clipping sphere
//
clippingSphere.SetCurrentOrigin(position);
clippingSphere.SetReferenceRadius(radius);
#endif
}
//
//#############################################################################
//#############################################################################
//
Logical
AudioHead::IsPointClipped(const Point3D &point)
{
Check(this);
Entity
*entity;
Vector3D
difference;
entity = headEntitySocket.GetCurrent();
Check(entity);
difference.Subtract(entity->localOrigin.linearPosition, point);
return difference.LengthSquared() > clippingRadius*clippingRadius;
}
//
//#############################################################################
//#############################################################################
//
void
AudioHead::ControlDopplerEffect(
Scalar doppler_constant,
Scalar sound_speed
)
{
Check(this);
audioDopplerConstant = doppler_constant;
audioSoundSpeed = sound_speed;
Check_Fpu();
}
//
//#############################################################################
//#############################################################################
//
void
AudioHead::ControlAmplitudeRollOff(
Scalar exponent,
Scalar amplitude_rolloff_knee,
Scalar distance_scale
)
{
Check(this);
amplitudeRollOffExponent = exponent;
amplitudeRollOffKnee = amplitude_rolloff_knee;
amplitudeRollOffDistanceScale = distance_scale;
Check_Fpu();
}
//
//#############################################################################
//#############################################################################
//
void
AudioHead::ControlHighFrequencyRollOff(
Scalar exponent,
Scalar high_frequency_rolloff_knee,
Scalar distance_scale
)
{
Check(this);
highFrequencyRollOffExponent = exponent;
highFrequencyRollOffKnee = high_frequency_rolloff_knee;
highFrequencyRollOffDistanceScale = distance_scale;
Check_Fpu();
}
//
//#############################################################################
//#############################################################################
//
void
AudioHead::ControlSourceCompression(
Scalar exponent,
Scalar scale
)
{
Check(this);
sourceCompressionExponent = exponent;
sourceCompressionScale = scale;
Check_Fpu();
}
//
//#############################################################################
//#############################################################################
//
void
AudioHead::SetPositionalCulling(Logical)
{
Check(this);
Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ AudioComponent ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
//#############################################################################
//#############################################################################
//
const Receiver::HandlerEntry
AudioComponent::MessageHandlerEntries[]=
{
{
AudioComponent::ReceiveControlMessageID,
"ReceiveControl",
(AudioComponent::Handler)&AudioComponent::ReceiveControlMessageHandler
}
};
AudioComponent::MessageHandlerSet& AudioComponent::GetMessageHandlers()
{
static AudioComponent::MessageHandlerSet messageHandlers(ELEMENTS(AudioComponent::MessageHandlerEntries), AudioComponent::MessageHandlerEntries, Component::GetMessageHandlers());
return messageHandlers;
}
//
//#############################################################################
//#############################################################################
//
Derivation* AudioComponent::GetClassDerivations()
{
static Derivation classDerivations(Component::GetClassDerivations(), "AudioComponent");
return &classDerivations;
}
AudioComponent::SharedData
AudioComponent::DefaultData(
AudioComponent::GetClassDerivations(),
AudioComponent::GetMessageHandlers()
);
//
//#############################################################################
//#############################################################################
//
AudioComponent::AudioComponent(
PlugStream *stream,
SharedData &shared_data
):
Component(stream, shared_data),
audioWatcherSocket(NULL)
{
nextExecuteWatcherFrame = NullAudioFrameCount;
}
//
//#############################################################################
//#############################################################################
//
AudioComponent::~AudioComponent()
{
}
//
//#############################################################################
//#############################################################################
//
Logical
AudioComponent::TestInstance() const
{
if (!IsDerivedFrom(*GetClassDerivations()))
{
return False;
}
Check(&audioWatcherSocket);
return True;
}
//
//#############################################################################
//#############################################################################
//
void
AudioComponent::ExecuteWatchers()
{
Check(this);
//
// Execute watchers if this is the next watcher frame
//
AudioFrameCount
audio_frame_count;
Check(application);
Check(application->GetAudioRenderer());
audio_frame_count = application->GetAudioRenderer()->GetAudioFrameCount();
if (nextExecuteWatcherFrame <= audio_frame_count)
{
nextExecuteWatcherFrame = audio_frame_count + DefaultAudioFrameDelay;
//
// Execute watchers
//
ChainIteratorOf<Component*>
iterator(&audioWatcherSocket);
Component
*component;
while ((component = iterator.ReadAndNext()) != NULL)
{
Check(component);
component->Execute();
}
}
}
//
//#############################################################################
//#############################################################################
//
void
AudioComponent::Execute()
{
Check(this);
ExecuteWatchers();
Check_Fpu();
}
//
//#############################################################################
//#############################################################################
//
void
AudioComponent::ReceiveControl(
AudioControlID,
AudioControlValue
)
{
Fail("AudioComponent::ReceiveControl - Should never reach here");
}
//
//#############################################################################
//#############################################################################
//
void
AudioComponent::PostReceiveControl(
AudioControlID control_ID,
AudioControlValue control_value
)
{
Check(this);
ReceiveControlMessage
message(control_ID, control_value);
Check(application);
// ECH 1/26/96 - application->Post(HighEventPriority, this, &message);
application->Post(DefaultEventPriority, this, &message);
Check_Fpu();
}
//
//#############################################################################
//#############################################################################
//
void
AudioComponent::ReceiveControlMessageHandler(ReceiveControlMessage *message)
{
Check(this);
Check(message);
ReceiveControl(message->controlID, message->controlValue);
Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~ AudioComponent__ReceiveControlMessage ~~~~~~~~~~~~~~~~~~~~
AudioComponent__ReceiveControlMessage::AudioComponent__ReceiveControlMessage(
AudioControlID control_ID,
AudioControlValue control_value
):
Receiver::Message(
AudioComponent::ReceiveControlMessageID,
sizeof(AudioComponent__ReceiveControlMessage)
)
{
controlID = control_ID;
controlValue = control_value;
}