Audio Phase 3 (AUDIO_FIDELITY F9/F11/F12): EFX lowpass + reverb split + cockpit placement

New L4AUDEFX bridge (OpenAL Soft ALC_EXT_EFX): one EAXReverb aux slot at the
authentic AUDIO.INI global_reverb_scale (0.3) + a scratch AL_FILTER_LOWPASS
(params copied at attach).

F9 filters (was: computed then thrown away -- everything full-bright at all
distances): Dynamic3D ExecuteModel drives GAINHF from highFreqCutoffScale x
brightnessScale x maxMIDIFilterCutoff, UNGATED (decomp part_008.c:7496,
7589-7604 -- every moving 3D sound dulls with distance per the AUDIO.INI
knee-60/exp-2.0 model); Static3D from brightness x max (:7831-7884); Direct
inside its existing gated NRPN-rate block.  AWE 100-8000 Hz curve -> EFX
5 kHz-reference gainhf via a 2-pole approximation [T3 curve, endpoints exact].

F11 reverb (was: bone-dry everywhere): 3D patch sources attach an aux send at
Start, exactly where the original sent CC91 = global_reverb_scale
(part_008.c:7278-7394); Direct cockpit sources keep CC91=0 -- dry.  The
wet-exterior vs dry-cockpit contrast is back.

F12 placement (was: every cockpit sound dead-center): DirectPatchSource
Start places sources by the authored 6-value position enum (front/rear card
+ pan CC10, decomp @00463848/@004638a8) as listener-relative directions,
composed with the zone L/C/R pan.  New PatchResource GetBankID/GetPatchID
pass-throughs (the LOD accessor is protected).

Regression (35s drive+fire): EFX READY, stable, deliveries unregressed, no AL
errors.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-16 11:59:39 -05:00
co-authored by Claude Opus 4.8
parent aa004eb7b8
commit 0ca7d269d2
7 changed files with 266 additions and 0 deletions
+1
View File
@@ -179,6 +179,7 @@ add_library(munga_engine STATIC
"engine/MUNGA/WRHOUS.cpp"
"engine/MUNGA_L4/DXUtils.cpp"
"engine/MUNGA_L4/L4APP.cpp"
"engine/MUNGA_L4/L4AUDEFX.cpp"
"engine/MUNGA_L4/L4AUDHDW.cpp"
"engine/MUNGA_L4/L4AUDIO.cpp"
"engine/MUNGA_L4/L4AUDLVL.cpp"
+129
View File
@@ -0,0 +1,129 @@
//###########################################################################
//
// L4AUDEFX.cpp -- OpenAL EFX bridge (task #50, AUDIO_FIDELITY F9/F11).
// See L4AUDEFX.h for the fidelity rationale.
//
//###########################################################################
#include <cstdlib>
#include "mungal4.h"
#pragma hdrstop
#include "l4audefx.h"
#include "openal/alc.h"
#include "openal/efx.h"
#ifndef AL_EFFECT_EAXREVERB
#define AL_EFFECT_EAXREVERB 0x8000 // newer efx.h constant; OpenAL Soft supports it
#endif
namespace
{
bool s_available = false;
ALuint s_reverbSlot = 0;
ALuint s_reverbEffect = 0;
ALuint s_scratchFilter = 0;
LPALGENEFFECTS p_alGenEffects = 0;
LPALEFFECTI p_alEffecti = 0;
LPALEFFECTF p_alEffectf = 0;
LPALGENAUXILIARYEFFECTSLOTS p_alGenAuxiliaryEffectSlots = 0;
LPALAUXILIARYEFFECTSLOTI p_alAuxiliaryEffectSloti = 0;
LPALAUXILIARYEFFECTSLOTF p_alAuxiliaryEffectSlotf = 0;
LPALGENFILTERS p_alGenFilters = 0;
LPALFILTERI p_alFilteri = 0;
LPALFILTERF p_alFilterf = 0;
}
bool EFX_Available()
{
return s_available;
}
bool EFX_Initialize(float global_reverb_scale)
{
ALCcontext *context = alcGetCurrentContext();
if (context == 0)
{
return false;
}
ALCdevice *device = alcGetContextsDevice(context);
if (device == 0 || !alcIsExtensionPresent(device, "ALC_EXT_EFX"))
{
if (getenv("BT_AUDIO_LOG"))
DEBUG_STREAM << "[audio] EFX: ALC_EXT_EFX NOT present -- filters/reverb inert\n" << std::flush;
return false;
}
p_alGenEffects = (LPALGENEFFECTS)alGetProcAddress("alGenEffects");
p_alEffecti = (LPALEFFECTI)alGetProcAddress("alEffecti");
p_alEffectf = (LPALEFFECTF)alGetProcAddress("alEffectf");
p_alGenAuxiliaryEffectSlots = (LPALGENAUXILIARYEFFECTSLOTS)alGetProcAddress("alGenAuxiliaryEffectSlots");
p_alAuxiliaryEffectSloti = (LPALAUXILIARYEFFECTSLOTI)alGetProcAddress("alAuxiliaryEffectSloti");
p_alAuxiliaryEffectSlotf = (LPALAUXILIARYEFFECTSLOTF)alGetProcAddress("alAuxiliaryEffectSlotf");
p_alGenFilters = (LPALGENFILTERS)alGetProcAddress("alGenFilters");
p_alFilteri = (LPALFILTERI)alGetProcAddress("alFilteri");
p_alFilterf = (LPALFILTERF)alGetProcAddress("alFilterf");
if (!p_alGenEffects || !p_alEffecti || !p_alEffectf
|| !p_alGenAuxiliaryEffectSlots || !p_alAuxiliaryEffectSloti || !p_alAuxiliaryEffectSlotf
|| !p_alGenFilters || !p_alFilteri || !p_alFilterf)
{
if (getenv("BT_AUDIO_LOG"))
DEBUG_STREAM << "[audio] EFX: entry points missing -- filters/reverb inert\n" << std::flush;
return false;
}
alGetError();
p_alGenAuxiliaryEffectSlots(1, &s_reverbSlot);
p_alGenEffects(1, &s_reverbEffect);
if (alGetError() != AL_NO_ERROR)
{
return false;
}
// EAXReverb where available (OpenAL Soft: yes), plain reverb otherwise.
p_alEffecti(s_reverbEffect, AL_EFFECT_TYPE, AL_EFFECT_EAXREVERB);
if (alGetError() != AL_NO_ERROR)
{
p_alEffecti(s_reverbEffect, AL_EFFECT_TYPE, AL_EFFECT_REVERB);
}
p_alAuxiliaryEffectSloti(s_reverbSlot, AL_EFFECTSLOT_EFFECT, (ALint)s_reverbEffect);
// The authentic wet level: the original sent CC91 = global_reverb_scale
// (0.3 -> value 38) on every 3D channel; one global slot gain reproduces
// the same uniform send.
p_alAuxiliaryEffectSlotf(s_reverbSlot, AL_EFFECTSLOT_GAIN,
(global_reverb_scale < 0.0f) ? 0.0f :
(global_reverb_scale > 1.0f) ? 1.0f : global_reverb_scale);
p_alGenFilters(1, &s_scratchFilter);
p_alFilteri(s_scratchFilter, AL_FILTER_TYPE, AL_FILTER_LOWPASS);
s_available = (alGetError() == AL_NO_ERROR);
if (getenv("BT_AUDIO_LOG"))
DEBUG_STREAM << "[audio] EFX: " << (s_available ? "READY" : "FAILED")
<< " (reverb slot gain=" << global_reverb_scale << ")\n" << std::flush;
return s_available;
}
void EFX_SetSourceLowpassGainHF(ALuint source, float gainhf)
{
if (!s_available)
{
return;
}
if (gainhf < 0.001f) gainhf = 0.001f;
if (gainhf > 1.0f) gainhf = 1.0f;
// filter params are COPIED at attach, so one scratch filter serves all
p_alFilterf(s_scratchFilter, AL_LOWPASS_GAIN, 1.0f);
p_alFilterf(s_scratchFilter, AL_LOWPASS_GAINHF, gainhf);
alSourcei(source, AL_DIRECT_FILTER, (ALint)s_scratchFilter);
}
void EFX_AttachReverbSend(ALuint source)
{
if (!s_available)
{
return;
}
alSource3i(source, AL_AUXILIARY_SEND_FILTER, (ALint)s_reverbSlot, 0, AL_FILTER_NULL);
}
+44
View File
@@ -0,0 +1,44 @@
#pragma once
//###########################################################################
//
// L4AUDEFX.h -- OpenAL EFX bridge for the authored filter/reverb chains
// (task #50, AUDIO_FIDELITY F9/F11).
//
// The original drove the AWE32 initial-filter-cutoff NRPN per frame
// (brightness x distance HF rolloff) and sent CC91 reverb on the 3D
// channels (global_reverb_scale=0.3, AUDIO.INI) while keeping the cockpit
// Direct channels dry. This bridge reproduces both through OpenAL Soft's
// EFX extension: one EAXReverb aux slot + a scratch AL_FILTER_LOWPASS
// whose parameters are copied at attach time.
//
//###########################################################################
#include "openal/al.h"
// Load the EFX entry points, create the reverb slot (gain = the authentic
// global_reverb_scale) and the scratch lowpass. Call once, with the AL
// context current. Returns false (and stays inert) without ALC_EXT_EFX.
bool EFX_Initialize(float global_reverb_scale);
bool EFX_Available();
// Per-frame direct-path lowpass: gainhf is the linear HF gain at the EFX
// 5 kHz reference. Callers map the AWE cutoff (100 + scale*7900/127 Hz)
// through EFX_CutoffScaleToGainHF below.
void EFX_SetSourceLowpassGainHF(ALuint source, float gainhf);
// AWE NRPN 21 curve -> EFX gainhf: cutoff_scale in [0,1] of the 100-8000 Hz
// span; approximated as the attenuation of a 2-pole lowpass at the 5 kHz
// reference [T3 -- curve shape approximate, endpoints exact].
inline float EFX_CutoffScaleToGainHF(float cutoff_scale)
{
if (cutoff_scale < 0.0f) cutoff_scale = 0.0f;
if (cutoff_scale > 1.0f) cutoff_scale = 1.0f;
float cutoff_hz = 100.0f + cutoff_scale * 7900.0f;
float g = (cutoff_hz / 5000.0f) * (cutoff_hz / 5000.0f);
return (g > 1.0f) ? 1.0f : ((g < 0.001f) ? 0.001f : g);
}
// Wet-exterior routing: attach the source's aux send to the reverb slot
// (Dynamic3D/Static3D). Direct cockpit sources stay dry by default.
void EFX_AttachReverbSend(ALuint source);
+62
View File
@@ -5,6 +5,7 @@
#include "l4audio.h"
#include "l4audlvl.h"
#include "l4audefx.h"
#include "l4app.h"
#include "l4audrnd.h"
#include "..\munga\namelist.h"
@@ -964,6 +965,31 @@ void
channel->SendController(MIDI_PAN_CONTROL, MIDI_RIGHT_PAN_VALUE);
break;
}*/
// (task #50, AUDIO_FIDELITY F12) the original routed each Direct cockpit
// source by its authored 6-value position enum (front/rear CARD + pan
// CC10 left/center/right). Reproduce as a listener-relative placement;
// a zone's own L/R pan (stereo-pair presets) offsets on top. The
// distance model is AL_NONE, so this affects direction only, never gain.
{
float px = 0.0f, pz = -1.0f; // Front, centered
switch (audioPosition)
{
case FrontDirectPatchPosition: px = 0.0f; pz = -1.0f; break;
case RearDirectPatchPosition: px = 0.0f; pz = 1.0f; break;
case FrontLeftDirectPatchPosition: px = -0.7f; pz = -0.7f; break;
case FrontRightDirectPatchPosition: px = 0.7f; pz = -0.7f; break;
case RearLeftDirectPatchPosition: px = -0.7f; pz = 0.7f; break;
case RearRightDirectPatchPosition: px = 0.7f; pz = 0.7f; break;
}
for (int _i = 0; _i < channelSet.count; ++_i)
{
SAMPLEINFO zinfo = PRESET_getSampleInfo(
patch_resource->GetBankID(), patch_resource->GetPatchID(), _i);
float pan_x = (zinfo.chan == CHANNEL_LEFT) ? -0.5f :
(zinfo.chan == CHANNEL_RIGHT) ? 0.5f : 0.0f;
alSource3f(channelSet.sources[_i], AL_POSITION, px + pan_x, 0.0f, pz);
}
}
//
// Set midi history to default control values
@@ -1082,6 +1108,13 @@ void
if (Abs(filter_difference) >= filter_resolution)
{
lastMIDIFilterCutoff = midi_filter_cutoff;
// (task #50, AUDIO_FIDELITY F9) the original sent this cutoff as
// AWE NRPN 21; the EFX lowpass is the modern sink (was a dead
// bookkeeping write -- ctl-5 Brightness played full-bright).
float gainhf = EFX_CutoffScaleToGainHF(
(float)lastMIDIFilterCutoff / (float)MIDI_MAX_CONTROL_VALUE);
for (int _i = 0; _i < channelSet.count; ++_i)
EFX_SetSourceLowpassGainHF(channelSet.sources[_i], gainhf);
}
}
@@ -1260,6 +1293,12 @@ void
channel->SendController(MIDI_REVERB_CONTROL, midi_reverb_level);
channel->SendController(MIDI_CHORUS_CONTROL, MIDI_MIN_CONTROL_VALUE);
}*/
// (task #50, AUDIO_FIDELITY F11) the CC91 send above is the authentic
// wet-exterior contrast (global_reverb_scale on every 3D channel, decomp
// part_008.c:7278-7394); the EFX aux slot is the modern sink. Cockpit
// Direct sources stay dry (their CC91 was 0).
for (int _i = 0; _i < channelSet.count; ++_i)
EFX_AttachReverbSend(channelSet.sources[_i]);
//
// Set the channels to correct pan
@@ -1459,11 +1498,22 @@ void
<< " loc=(" << locationPosition.x << "," << locationPosition.z << ")"
<< " dist=" << posMag << " vol=" << volume_scale << "\n" << std::flush; }
// (task #50, AUDIO_FIDELITY F9) the original drove the AWE filter cutoff
// from highFreqCutoffScale x brightnessScale on this dynamic path --
// UNGATED, all channels (part_008.c:7496,7589-7604): every moving 3D
// sound gets duller with distance (AUDIO.INI knee 60 / exponent 2.0).
float dyn_gainhf = EFX_CutoffScaleToGainHF(
(float)(GetAudioLocation()->GetHighFreqCutoffScale()
* CalculateSourceBrightnessScale()
* (Scalar)patch_resource->GetMaxMIDIFilterCutoff()
/ (Scalar)MIDI_MAX_CONTROL_VALUE));
for (int i=0; i < channelSet.count; i++)
{
alSource3f(channelSet.sources[i],AL_POSITION,pos.x,pos.y,pos.z);
alSourcef(channelSet.sources[i], AL_GAIN, volume_scale * volume_scale); // FIDELITY F4: CC7 squared law (linear was ~+6 dB at mid volumes)
alSource3f(channelSet.sources[i],AL_VELOCITY,-relative_velocity.x,-relative_velocity.y,-relative_velocity.z);
EFX_SetSourceLowpassGainHF(channelSet.sources[i], dyn_gainhf);
}
}
@@ -1786,6 +1836,10 @@ void
channel->SendController(MIDI_CHORUS_CONTROL, MIDI_MIN_CONTROL_VALUE);
}
}*/
// (task #50, AUDIO_FIDELITY F11) static 3D sources are wet like the
// dynamic ones (the CC91 block above); Direct cockpit stays dry.
for (int _i = 0; _i < channelSet.count; ++_i)
EFX_AttachReverbSend(channelSet.sources[_i]);
//
// Set the channels to correct pan
@@ -2006,11 +2060,19 @@ void
relative_position = audio_location->GetVectorToSource();
}
// (task #50, AUDIO_FIDELITY F9) static sources: brightness-driven cutoff
// only (part_008.c:7831-7884 -- the NRPN block commented below).
float static_gainhf = EFX_CutoffScaleToGainHF(
(float)(CalculateSourceBrightnessScale()
* (Scalar)patch_resource->GetMaxMIDIFilterCutoff()
/ (Scalar)MIDI_MAX_CONTROL_VALUE));
//Static models have their position freely available as relative positions and stand still
for (int i=0; i < channelSet.count; i++)
{
alSourcef(channelSet.sources[i], AL_GAIN, volume_scale * volume_scale); // FIDELITY F4: CC7 squared law (linear was ~+6 dB at mid volumes)
alSource3f(channelSet.sources[i],AL_POSITION,relative_position.x,relative_position.y,relative_position.z);
EFX_SetSourceLowpassGainHF(channelSet.sources[i], static_gainhf);
}
//
+18
View File
@@ -439,6 +439,24 @@ void PatchResource::StopNote(SourceSet sourceSet)
patch_level_of_detail->StopNote(sourceSet);
}
//
//#############################################################################
//#############################################################################
//
int PatchResource::GetBankID()
{
PatchLevelOfDetail *patch_level_of_detail =
Cast_Object(PatchLevelOfDetail*, GetAudioLevelOfDetail());
return (patch_level_of_detail != 0) ? patch_level_of_detail->GetBankID() : 0;
}
int PatchResource::GetPatchID()
{
PatchLevelOfDetail *patch_level_of_detail =
Cast_Object(PatchLevelOfDetail*, GetAudioLevelOfDetail());
return (patch_level_of_detail != 0) ? patch_level_of_detail->GetPatchID() : 0;
}
//
//#############################################################################
//#############################################################################
+5
View File
@@ -186,5 +186,10 @@ public:
MIDINRPNValue
GetMaxMIDIFilterCutoff();
// (task #50) authored bank/patch pass-throughs (the LOD accessor is
// protected on AudioResource) -- zone metadata lookups at the source layer.
int GetBankID();
int GetPatchID();
};
+7
View File
@@ -388,6 +388,13 @@ void
if (const char *v = getenv("BT_AUDIO_VOLUME")) { float f = (float)atof(v); if (f >= 0.0f) masterVol = f; }
alListenerf(AL_GAIN, masterVol);
if (getenv("BT_AUDIO_LOG")) DEBUG_STREAM << "[audio] OpenAL device OPENED + context current; master gain=" << masterVol << "\n" << std::flush;
// (task #50, AUDIO_FIDELITY F9/F11) EFX bridge: the authored lowpass
// chains + the wet-exterior/dry-cockpit reverb split, at the authentic
// AUDIO.INI global_reverb_scale read above.
{
extern bool EFX_Initialize(float global_reverb_scale);
EFX_Initialize(global_reverb_scale);
}
}
else
{