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>
135 lines
4.3 KiB
C++
135 lines
4.3 KiB
C++
//###########################################################################
|
|
//
|
|
// L4AUDEFX.cpp -- OpenAL EFX bridge (docs/SOUND.md, findings F9 and F11).
|
|
// See L4AUDEFX.h for the fidelity rationale.
|
|
//
|
|
//###########################################################################
|
|
#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"))
|
|
{
|
|
Tell("L4AUDEFX: ALC_EXT_EFX not present - filters and reverb inert\n");
|
|
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)
|
|
{
|
|
Tell("L4AUDEFX: EFX entry points missing - filters and reverb inert\n");
|
|
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 on
|
|
// every 3D channel, so one global slot gain reproduces the same uniform
|
|
// send. RP authors 0.35 (AUDIO.INI); BT used 0.3.
|
|
//
|
|
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);
|
|
Tell("L4AUDEFX: " << (s_available ? "ready" : "failed")
|
|
<< " (reverb slot gain " << global_reverb_scale << ")\n");
|
|
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 parameters are COPIED at attach time, so one scratch filter object
|
|
// serves every source -- no per-source filter allocation is needed.
|
|
//
|
|
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);
|
|
}
|