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:
co-authored by
Claude Opus 4.8
parent
aa004eb7b8
commit
0ca7d269d2
@@ -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"
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
//#############################################################################
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user