2012-09-19 11:15:01 -04:00
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
// Copyright (c) 2012 GarageGames, LLC
|
|
|
|
|
//
|
|
|
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
|
|
|
// of this software and associated documentation files (the "Software"), to
|
|
|
|
|
// deal in the Software without restriction, including without limitation the
|
|
|
|
|
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
|
|
|
// sell copies of the Software, and to permit persons to whom the Software is
|
|
|
|
|
// furnished to do so, subject to the following conditions:
|
|
|
|
|
//
|
|
|
|
|
// The above copyright notice and this permission notice shall be included in
|
|
|
|
|
// all copies or substantial portions of the Software.
|
|
|
|
|
//
|
|
|
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
|
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
|
|
|
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
|
|
|
// IN THE SOFTWARE.
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
#include "sfx/openal/sfxALDevice.h"
|
|
|
|
|
#include "sfx/openal/sfxALBuffer.h"
|
|
|
|
|
#include "platform/async/asyncUpdate.h"
|
|
|
|
|
|
2026-03-13 08:05:42 +00:00
|
|
|
#include "AL/al.h"
|
|
|
|
|
#include "AL/alc.h"
|
|
|
|
|
#include "AL/efx.h"
|
|
|
|
|
#include "AL/alext.h"
|
|
|
|
|
|
|
|
|
|
#ifndef ALC_CONNECTED
|
|
|
|
|
#define ALC_CONNECTED 0x313
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
#ifndef ALC_DEVICE_CLOCK_SOFT
|
|
|
|
|
#define ALC_DEVICE_CLOCK_SOFT 0x1600
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
#ifndef ALC_DEVICE_LATENCY_SOFT
|
|
|
|
|
#define ALC_DEVICE_LATENCY_SOFT 0x1601
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L
|
|
|
|
|
#define FUNCTION_CAST(T, ptr) (union{void *p; T f;}){ptr}.f
|
|
|
|
|
#elif defined(__cplusplus)
|
|
|
|
|
#define FUNCTION_CAST(T, ptr) reinterpret_cast<T>(ptr)
|
|
|
|
|
#else
|
|
|
|
|
#define FUNCTION_CAST(T, ptr) (T)(ptr)
|
|
|
|
|
#endif
|
|
|
|
|
|
2026-05-09 10:24:36 +01:00
|
|
|
#define SFX_LOAD_AL_PROC(T, x) \
|
|
|
|
|
(x) = FUNCTION_CAST(T, alGetProcAddress(#x)); \
|
|
|
|
|
if (!(x)) Con::warnf("SFXALDevice - Failed to load " #x);
|
|
|
|
|
|
|
|
|
|
#define SFX_LOAD_ALC_PROC(T, x) \
|
|
|
|
|
(x) = FUNCTION_CAST(T, alcGetProcAddress(mDevice, #x)); \
|
|
|
|
|
if (!(x)) Con::warnf("SFXALDevice - Failed to load " #x);
|
|
|
|
|
|
2026-03-13 08:05:42 +00:00
|
|
|
/* Effect object functions */
|
|
|
|
|
static LPALGENFILTERS alGenFilters{ nullptr };
|
|
|
|
|
static LPALDELETEFILTERS alDeleteFilters{ nullptr };
|
|
|
|
|
static LPALISFILTER alIsFilter{ nullptr };
|
|
|
|
|
static LPALFILTERF alFilterf{ nullptr };
|
|
|
|
|
static LPALFILTERFV alFilterfv{ nullptr };
|
|
|
|
|
static LPALFILTERI alFilteri{ nullptr };
|
|
|
|
|
static LPALFILTERIV alFilteriv{ nullptr };
|
|
|
|
|
static LPALGETFILTERF alGetFilterf{ nullptr };
|
|
|
|
|
static LPALGETFILTERFV alGetFilterfv{ nullptr };
|
|
|
|
|
static LPALGETFILTERI alGetFilteri{ nullptr };
|
|
|
|
|
static LPALGETFILTERIV alGetFilteriv{ nullptr };
|
|
|
|
|
static LPALGENEFFECTS alGenEffects{ nullptr };
|
|
|
|
|
static LPALDELETEEFFECTS alDeleteEffects{ nullptr };
|
|
|
|
|
static LPALISEFFECT alIsEffect{ nullptr };
|
|
|
|
|
static LPALEFFECTF alEffectf{ nullptr };
|
|
|
|
|
static LPALEFFECTFV alEffectfv{ nullptr };
|
|
|
|
|
static LPALEFFECTI alEffecti{ nullptr };
|
|
|
|
|
static LPALEFFECTIV alEffectiv{ nullptr };
|
|
|
|
|
static LPALGETEFFECTF alGetEffectf{ nullptr };
|
|
|
|
|
static LPALGETEFFECTFV alGetEffectfv{ nullptr };
|
|
|
|
|
static LPALGETEFFECTI alGetEffecti{ nullptr };
|
|
|
|
|
static LPALGETEFFECTIV alGetEffectiv{ nullptr };
|
|
|
|
|
static LPALGENAUXILIARYEFFECTSLOTS alGenAuxiliaryEffectSlots{ nullptr };
|
|
|
|
|
static LPALDELETEAUXILIARYEFFECTSLOTS alDeleteAuxiliaryEffectSlots{ nullptr };
|
|
|
|
|
static LPALISAUXILIARYEFFECTSLOT alIsAuxiliaryEffectSlot{ nullptr };
|
|
|
|
|
static LPALAUXILIARYEFFECTSLOTF alAuxiliaryEffectSlotf{ nullptr };
|
|
|
|
|
static LPALAUXILIARYEFFECTSLOTFV alAuxiliaryEffectSlotfv{ nullptr };
|
|
|
|
|
static LPALAUXILIARYEFFECTSLOTI alAuxiliaryEffectSloti{ nullptr };
|
|
|
|
|
static LPALAUXILIARYEFFECTSLOTIV alAuxiliaryEffectSlotiv{ nullptr };
|
|
|
|
|
static LPALGETAUXILIARYEFFECTSLOTF alGetAuxiliaryEffectSlotf{ nullptr };
|
|
|
|
|
static LPALGETAUXILIARYEFFECTSLOTFV alGetAuxiliaryEffectSlotfv{ nullptr };
|
|
|
|
|
static LPALGETAUXILIARYEFFECTSLOTI alGetAuxiliaryEffectSloti{ nullptr };
|
|
|
|
|
static LPALGETAUXILIARYEFFECTSLOTIV alGetAuxiliaryEffectSlotiv{ nullptr };
|
|
|
|
|
static LPALCGETSTRINGISOFT alcGetStringiSOFT{ nullptr };
|
|
|
|
|
static LPALCRESETDEVICESOFT alcResetDeviceSOFT{ nullptr };
|
|
|
|
|
static LPALCREOPENDEVICESOFT alcReopenDeviceSOFT{ nullptr };
|
|
|
|
|
static LPALCGETINTEGER64VSOFT alcGetInteger64vSOFT{ nullptr };
|
|
|
|
|
|
|
|
|
|
class SFXALRegisterProvider
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
SFXALRegisterProvider()
|
|
|
|
|
{
|
|
|
|
|
SFXSystem::getRegisterProviderSignal().notify(&SFXALDevice::enumerateProviders);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static SFXALRegisterProvider pSFXALRegisterProvider;
|
|
|
|
|
|
|
|
|
|
SFXProvider::CreateProviderInstanceDelegate SFXALDevice::mCreateDeviceInstance(SFXALDevice::createInstance);
|
|
|
|
|
|
|
|
|
|
SFXDevice* SFXALDevice::createInstance(U32 providerIndex)
|
|
|
|
|
{
|
|
|
|
|
SFXALDevice* dev = new SFXALDevice(providerIndex);
|
|
|
|
|
return dev;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-09 10:24:36 +01:00
|
|
|
void SFXALDevice::_initExtensions()
|
|
|
|
|
{
|
|
|
|
|
// Device-level soft extensions — must be loaded before context creation
|
|
|
|
|
if (alcIsExtensionPresent(mDevice, "ALC_SOFT_reopen_device"))
|
|
|
|
|
{
|
|
|
|
|
SFX_LOAD_ALC_PROC(LPALCREOPENDEVICESOFT, alcReopenDeviceSOFT);
|
|
|
|
|
SFX_LOAD_ALC_PROC(LPALCRESETDEVICESOFT, alcResetDeviceSOFT);
|
|
|
|
|
if (alcReopenDeviceSOFT && alcResetDeviceSOFT)
|
|
|
|
|
{
|
|
|
|
|
mHasSoftReopen = true;
|
|
|
|
|
mCaps |= CAPS_HotReconnect;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (alcIsExtensionPresent(mDevice, "ALC_SOFT_HRTF"))
|
|
|
|
|
{
|
|
|
|
|
SFX_LOAD_ALC_PROC(LPALCGETSTRINGISOFT, alcGetStringiSOFT);
|
|
|
|
|
if (alcGetStringiSOFT)
|
|
|
|
|
{
|
|
|
|
|
mHasSoftHRTF = true;
|
|
|
|
|
mCaps |= CAPS_HRTF;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (alcIsExtensionPresent(mDevice, "ALC_SOFT_device_clock"))
|
|
|
|
|
{
|
|
|
|
|
SFX_LOAD_ALC_PROC(LPALCGETINTEGER64VSOFT, alcGetInteger64vSOFT);
|
|
|
|
|
if (alcGetInteger64vSOFT)
|
|
|
|
|
mCaps |= CAPS_DeviceClock;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (alcIsExtensionPresent(mDevice, "ALC_EXT_disconnect"))
|
|
|
|
|
mCaps |= CAPS_DisconnectDetect;
|
|
|
|
|
|
|
|
|
|
// Context-level AL extensions — loaded after context is current
|
|
|
|
|
if (alIsExtensionPresent("AL_EXT_float32"))
|
|
|
|
|
mCaps |= CAPS_Float32;
|
|
|
|
|
if (alIsExtensionPresent("AL_EXT_MCFORMATS"))
|
|
|
|
|
mCaps |= CAPS_MonoStereo;
|
|
|
|
|
if (alIsExtensionPresent("AL_SOFT_source_spatialize"))
|
|
|
|
|
mCaps |= CAPS_SourceSpatialize;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SFXALDevice::_initEFX()
|
|
|
|
|
{
|
|
|
|
|
if (alcIsExtensionPresent(mDevice, "ALC_EXT_EFX") != AL_TRUE)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
// Load all EFX procs
|
|
|
|
|
SFX_LOAD_AL_PROC(LPALGENEFFECTS, alGenEffects);
|
|
|
|
|
SFX_LOAD_AL_PROC(LPALDELETEEFFECTS, alDeleteEffects);
|
|
|
|
|
SFX_LOAD_AL_PROC(LPALISEFFECT, alIsEffect);
|
|
|
|
|
SFX_LOAD_AL_PROC(LPALEFFECTI, alEffecti);
|
|
|
|
|
SFX_LOAD_AL_PROC(LPALEFFECTIV, alEffectiv);
|
|
|
|
|
SFX_LOAD_AL_PROC(LPALEFFECTF, alEffectf);
|
|
|
|
|
SFX_LOAD_AL_PROC(LPALEFFECTFV, alEffectfv);
|
|
|
|
|
SFX_LOAD_AL_PROC(LPALGETEFFECTI, alGetEffecti);
|
|
|
|
|
SFX_LOAD_AL_PROC(LPALGETEFFECTIV, alGetEffectiv);
|
|
|
|
|
SFX_LOAD_AL_PROC(LPALGETEFFECTF, alGetEffectf);
|
|
|
|
|
SFX_LOAD_AL_PROC(LPALGETEFFECTFV, alGetEffectfv);
|
|
|
|
|
SFX_LOAD_AL_PROC(LPALGENAUXILIARYEFFECTSLOTS, alGenAuxiliaryEffectSlots);
|
|
|
|
|
SFX_LOAD_AL_PROC(LPALDELETEAUXILIARYEFFECTSLOTS, alDeleteAuxiliaryEffectSlots);
|
|
|
|
|
SFX_LOAD_AL_PROC(LPALISAUXILIARYEFFECTSLOT, alIsAuxiliaryEffectSlot);
|
|
|
|
|
SFX_LOAD_AL_PROC(LPALAUXILIARYEFFECTSLOTI, alAuxiliaryEffectSloti);
|
|
|
|
|
SFX_LOAD_AL_PROC(LPALAUXILIARYEFFECTSLOTIV, alAuxiliaryEffectSlotiv);
|
|
|
|
|
SFX_LOAD_AL_PROC(LPALAUXILIARYEFFECTSLOTF, alAuxiliaryEffectSlotf);
|
|
|
|
|
SFX_LOAD_AL_PROC(LPALAUXILIARYEFFECTSLOTFV, alAuxiliaryEffectSlotfv);
|
|
|
|
|
SFX_LOAD_AL_PROC(LPALGETAUXILIARYEFFECTSLOTI, alGetAuxiliaryEffectSloti);
|
|
|
|
|
SFX_LOAD_AL_PROC(LPALGETAUXILIARYEFFECTSLOTIV, alGetAuxiliaryEffectSlotiv);
|
|
|
|
|
SFX_LOAD_AL_PROC(LPALGETAUXILIARYEFFECTSLOTF, alGetAuxiliaryEffectSlotf);
|
|
|
|
|
SFX_LOAD_AL_PROC(LPALGETAUXILIARYEFFECTSLOTFV, alGetAuxiliaryEffectSlotfv);
|
|
|
|
|
SFX_LOAD_AL_PROC(LPALGENFILTERS, alGenFilters);
|
|
|
|
|
SFX_LOAD_AL_PROC(LPALDELETEFILTERS, alDeleteFilters);
|
|
|
|
|
SFX_LOAD_AL_PROC(LPALISFILTER, alIsFilter);
|
|
|
|
|
SFX_LOAD_AL_PROC(LPALFILTERI, alFilteri);
|
|
|
|
|
SFX_LOAD_AL_PROC(LPALFILTERIV, alFilteriv);
|
|
|
|
|
SFX_LOAD_AL_PROC(LPALFILTERF, alFilterf);
|
|
|
|
|
SFX_LOAD_AL_PROC(LPALFILTERFV, alFilterfv);
|
|
|
|
|
SFX_LOAD_AL_PROC(LPALGETFILTERI, alGetFilteri);
|
|
|
|
|
SFX_LOAD_AL_PROC(LPALGETFILTERIV, alGetFilteriv);
|
|
|
|
|
SFX_LOAD_AL_PROC(LPALGETFILTERF, alGetFilterf);
|
|
|
|
|
SFX_LOAD_AL_PROC(LPALGETFILTERFV, alGetFilterfv);
|
|
|
|
|
|
|
|
|
|
// Verify the minimum set we can't live without
|
|
|
|
|
if (!alGenEffects || !alDeleteEffects ||
|
|
|
|
|
!alGenAuxiliaryEffectSlots || !alDeleteAuxiliaryEffectSlots ||
|
|
|
|
|
!alAuxiliaryEffectSloti)
|
|
|
|
|
{
|
|
|
|
|
Con::warnf("SFXALDevice - EFX proc loading incomplete, disabling reverb");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mCaps |= CAPS_Reverb | CAPS_Occlusion;
|
|
|
|
|
|
|
|
|
|
// Filters require their own procs
|
|
|
|
|
if (alGenFilters && alDeleteFilters && alFilteri && alFilterf)
|
|
|
|
|
mCaps |= CAPS_SourceFilters;
|
|
|
|
|
|
|
|
|
|
// Probe individual effect type support and slot count
|
|
|
|
|
_probeEFXEffectCaps();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SFXALDevice::_probeEFXEffectCaps()
|
|
|
|
|
{
|
|
|
|
|
// Probe which effect types this driver actually supports
|
|
|
|
|
// by attempting to set each type on a temporary effect object.
|
|
|
|
|
// Some drivers report ALC_EXT_EFX but don't implement all types.
|
|
|
|
|
|
|
|
|
|
struct EffectProbe { ALenum alType; U32 cap; const char* name; };
|
|
|
|
|
static const EffectProbe probes[] =
|
|
|
|
|
{
|
|
|
|
|
{ AL_EFFECT_EAXREVERB, CAPS_Reverb | CAPS_EXTReverb, "EAX Reverb" },
|
|
|
|
|
{ AL_EFFECT_REVERB, CAPS_Reverb, "EAXReverb" },
|
|
|
|
|
{ AL_EFFECT_EQUALIZER, CAPS_EFX_EQ, "Equalizer" },
|
|
|
|
|
{ AL_EFFECT_COMPRESSOR, CAPS_EFX_Compressor,"Compressor" },
|
|
|
|
|
{ AL_EFFECT_ECHO, CAPS_EFX_Echo, "Echo" },
|
|
|
|
|
{ AL_EFFECT_CHORUS, CAPS_EFX_Chorus, "Chorus" },
|
|
|
|
|
{ AL_EFFECT_DISTORTION, CAPS_EFX_Distortion,"Distortion" },
|
|
|
|
|
{ AL_EFFECT_FLANGER, CAPS_EFX_Flanger, "Flanger" },
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
ALuint testEffect = 0;
|
|
|
|
|
alGenEffects(1, &testEffect);
|
|
|
|
|
|
|
|
|
|
for (const EffectProbe& probe : probes)
|
|
|
|
|
{
|
|
|
|
|
alGetError();
|
|
|
|
|
alEffecti(testEffect, AL_EFFECT_TYPE, probe.alType);
|
|
|
|
|
if (alGetError() == AL_NO_ERROR)
|
|
|
|
|
mCaps |= probe.cap;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
alDeleteEffects(1, &testEffect);
|
|
|
|
|
alGetError();
|
|
|
|
|
|
|
|
|
|
// Probe aux slot count
|
|
|
|
|
Vector<ALuint> slots;
|
|
|
|
|
alGetError();
|
|
|
|
|
while (slots.size() < 64)
|
|
|
|
|
{
|
|
|
|
|
ALuint slot = 0;
|
|
|
|
|
alGenAuxiliaryEffectSlots(1, &slot);
|
|
|
|
|
if (alGetError() != AL_NO_ERROR)
|
|
|
|
|
break;
|
|
|
|
|
slots.push_back(slot);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Con::setIntVariable("$pref::SFX::maxEffectSlots", (S32)slots.size());
|
|
|
|
|
|
|
|
|
|
ALCint sends = 0;
|
|
|
|
|
alcGetIntegerv(mDevice, ALC_MAX_AUXILIARY_SENDS, 1, &sends);
|
|
|
|
|
Con::setIntVariable("$pref::SFX::maxSendsPerSource", (S32)sends);
|
|
|
|
|
|
|
|
|
|
for (ALuint slot : slots)
|
|
|
|
|
alDeleteAuxiliaryEffectSlots(1, &slot);
|
|
|
|
|
alGetError();
|
|
|
|
|
|
|
|
|
|
// Allocate the device-level global reverb slot
|
|
|
|
|
alGenAuxiliaryEffectSlots(1, &mAuxSlot);
|
|
|
|
|
if (alGetError() != AL_NO_ERROR)
|
|
|
|
|
{
|
|
|
|
|
Con::warnf("SFXALDevice - Failed to create primary EFX slot");
|
|
|
|
|
mCaps &= ~(CAPS_Reverb | CAPS_Occlusion);
|
|
|
|
|
mAuxSlot = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-03-13 08:05:42 +00:00
|
|
|
|
2022-08-16 08:33:26 +01:00
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
|
// STATIC OPENAL FUNCTIONS
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
|
void SFXALDevice::printALInfo(ALCdevice* device)
|
|
|
|
|
{
|
2026-05-09 10:24:36 +01:00
|
|
|
if (!device)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
Con::printBlankLine();
|
|
|
|
|
Con::printf("SFX Device Info:");
|
|
|
|
|
Con::printf("|------------------------------------------------");
|
|
|
|
|
|
|
|
|
|
// --- Device name ---
|
|
|
|
|
const ALCchar* devname = alcIsExtensionPresent(device, "ALC_ENUMERATE_ALL_EXT")
|
|
|
|
|
? alcGetString(device, ALC_ALL_DEVICES_SPECIFIER)
|
|
|
|
|
: alcGetString(device, ALC_DEVICE_SPECIFIER);
|
|
|
|
|
Con::printf("| Device: %s", devname);
|
|
|
|
|
|
|
|
|
|
// --- OpenAL version ---
|
2022-08-16 08:33:26 +01:00
|
|
|
ALCint major, minor;
|
2026-05-09 10:24:36 +01:00
|
|
|
alcGetIntegerv(device, ALC_MAJOR_VERSION, 1, &major);
|
|
|
|
|
alcGetIntegerv(device, ALC_MINOR_VERSION, 1, &minor);
|
|
|
|
|
Con::printf("| OpenAL: %d.%d", major, minor);
|
|
|
|
|
|
|
|
|
|
// --- Audio format ---
|
|
|
|
|
ALCint freq = 0;
|
|
|
|
|
alcGetIntegerv(device, ALC_FREQUENCY, 1, &freq);
|
|
|
|
|
Con::printf("| Sample Rate: %d Hz", freq > 0 ? freq : 44100);
|
|
|
|
|
Con::printf("| Bit Depth: %d-bit", (mCaps & CAPS_Float32) ? 32 : 16);
|
|
|
|
|
Con::printf("| Max Voices: %d", mMaxBuffers);
|
|
|
|
|
|
|
|
|
|
Con::printf("|------------------------------------------------");
|
|
|
|
|
Con::printf("| Capabilities:");
|
|
|
|
|
|
|
|
|
|
// --- Hot reconnect ---
|
|
|
|
|
Con::printf("| Hot Reconnect: %s", (mCaps & CAPS_HotReconnect) ? "Yes" : "No");
|
|
|
|
|
Con::printf("| Disconnect Detect: %s", (mCaps & CAPS_DisconnectDetect) ? "Yes" : "No");
|
|
|
|
|
Con::printf("| Device Clock: %s", (mCaps & CAPS_DeviceClock) ? "Yes" : "No");
|
|
|
|
|
Con::printf("| Multi-Listener: %s", (mCaps & CAPS_MultiListener) ? "Yes" : "No");
|
|
|
|
|
Con::printf("| Float32 Playback: %s", (mCaps & CAPS_Float32) ? "Yes" : "No");
|
|
|
|
|
Con::printf("| Multi-Channel: %s", (mCaps & CAPS_MonoStereo) ? "Yes" : "No");
|
|
|
|
|
Con::printf("| Spatialize: %s", (mCaps & CAPS_SourceSpatialize) ? "Yes" : "No");
|
|
|
|
|
|
|
|
|
|
// --- HRTF ---
|
|
|
|
|
Con::printf("| HRTF: %s", (mCaps & CAPS_HRTF) ? "Yes" : "No");
|
|
|
|
|
if (mCaps & CAPS_HRTF)
|
2022-08-16 08:33:26 +01:00
|
|
|
{
|
2026-05-09 10:24:36 +01:00
|
|
|
ALCint hrtfStatus = 0;
|
|
|
|
|
alcGetIntegerv(device, ALC_HRTF_STATUS_SOFT, 1, &hrtfStatus);
|
2022-08-16 08:33:26 +01:00
|
|
|
|
2026-05-09 10:24:36 +01:00
|
|
|
const char* statusStr = "Unknown";
|
|
|
|
|
switch (hrtfStatus)
|
2022-08-16 08:33:26 +01:00
|
|
|
{
|
2026-05-09 10:24:36 +01:00
|
|
|
case ALC_HRTF_DISABLED_SOFT: statusStr = "Disabled"; break;
|
|
|
|
|
case ALC_HRTF_ENABLED_SOFT: statusStr = "Enabled"; break;
|
|
|
|
|
case ALC_HRTF_DENIED_SOFT: statusStr = "Denied by device"; break;
|
|
|
|
|
case ALC_HRTF_REQUIRED_SOFT: statusStr = "Required by device"; break;
|
|
|
|
|
case ALC_HRTF_HEADPHONES_DETECTED_SOFT: statusStr = "Headphones detected"; break;
|
|
|
|
|
case ALC_HRTF_UNSUPPORTED_FORMAT_SOFT: statusStr = "Unsupported format"; break;
|
2022-08-16 08:33:26 +01:00
|
|
|
}
|
2026-05-09 10:24:36 +01:00
|
|
|
Con::printf("| Status: %s", statusStr);
|
2022-08-16 08:33:26 +01:00
|
|
|
|
2026-05-09 10:24:36 +01:00
|
|
|
ALCint profileCount = 0;
|
|
|
|
|
alcGetIntegerv(device, ALC_NUM_HRTF_SPECIFIERS_SOFT, 1, &profileCount);
|
|
|
|
|
Con::printf("| Profiles: %d", profileCount);
|
|
|
|
|
|
|
|
|
|
if (alcGetStringiSOFT && profileCount > 0)
|
|
|
|
|
for (ALCint i = 0; i < profileCount; i++)
|
|
|
|
|
Con::printf("| [%d] %s", i,
|
|
|
|
|
alcGetStringiSOFT(device, ALC_HRTF_SPECIFIER_SOFT, i));
|
2026-05-09 12:02:01 +01:00
|
|
|
|
|
|
|
|
bool use_hrtf = Con::getBoolVariable("$pref::SFX::useHRTF");
|
|
|
|
|
if (use_hrtf && (mCaps & CAPS_HotReconnect))
|
|
|
|
|
{
|
|
|
|
|
ALCint attr[5];
|
|
|
|
|
ALCint index = Con::getIntVariable("$pref::SFX::hrtfProfile");
|
|
|
|
|
ALCint i;
|
|
|
|
|
|
|
|
|
|
i = 0;
|
|
|
|
|
attr[i++] = ALC_HRTF_SOFT;
|
|
|
|
|
attr[i++] = ALC_TRUE;
|
|
|
|
|
// load the default device hrtf.
|
|
|
|
|
if (index >= 0 && index < profileCount)
|
|
|
|
|
{
|
|
|
|
|
attr[i++] = ALC_HRTF_ID_SOFT;
|
|
|
|
|
attr[i++] = index;
|
|
|
|
|
}
|
|
|
|
|
attr[i] = 0;
|
|
|
|
|
|
|
|
|
|
if (!alcResetDeviceSOFT(mDevice, attr))
|
|
|
|
|
{
|
|
|
|
|
Con::printf("Failed to reset device: %s", alcGetString(mDevice, alcGetError(mDevice)));
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-08-16 08:33:26 +01:00
|
|
|
}
|
|
|
|
|
|
2026-05-09 10:24:36 +01:00
|
|
|
// --- EFX ---
|
|
|
|
|
Con::printf("| EFX Reverb: %s", (mCaps & CAPS_Reverb) ? "Yes" : "No");
|
|
|
|
|
if (mCaps & CAPS_Reverb)
|
|
|
|
|
{
|
|
|
|
|
Con::printf("| EQ: %s", (mCaps & CAPS_EFX_EQ) ? "Yes" : "No");
|
|
|
|
|
Con::printf("| Compressor: %s", (mCaps & CAPS_EFX_Compressor) ? "Yes" : "No");
|
|
|
|
|
Con::printf("| Echo: %s", (mCaps & CAPS_EFX_Echo) ? "Yes" : "No");
|
|
|
|
|
Con::printf("| Chorus: %s", (mCaps & CAPS_EFX_Chorus) ? "Yes" : "No");
|
|
|
|
|
Con::printf("| Distortion: %s", (mCaps & CAPS_EFX_Distortion) ? "Yes" : "No");
|
|
|
|
|
Con::printf("| Flanger: %s", (mCaps & CAPS_EFX_Flanger) ? "Yes" : "No");
|
|
|
|
|
Con::printf("| Filters: %s", (mCaps & CAPS_SourceFilters) ? "Yes" : "No");
|
|
|
|
|
|
|
|
|
|
Con::printf("| Effect Slots: %d",
|
|
|
|
|
Con::getIntVariable("$pref::SFX::maxEffectSlots"));
|
|
|
|
|
Con::printf("| Sends/Source: %d",
|
|
|
|
|
Con::getIntVariable("$pref::SFX::maxSendsPerSource"));
|
|
|
|
|
}
|
2022-08-16 08:33:26 +01:00
|
|
|
|
2026-05-09 10:24:36 +01:00
|
|
|
Con::printf("|------------------------------------------------");
|
|
|
|
|
|
|
|
|
|
// --- Full extension list last so it doesn't bury the important info ---
|
|
|
|
|
const ALchar* extStr = alcGetString(device, ALC_EXTENSIONS);
|
|
|
|
|
if (extStr)
|
2022-08-16 08:33:26 +01:00
|
|
|
{
|
2026-05-09 10:24:36 +01:00
|
|
|
Con::printf("| ALC Extensions:");
|
|
|
|
|
char* extCopy = dStrdup(extStr);
|
|
|
|
|
char* token = dStrtok(extCopy, " ");
|
|
|
|
|
while (token)
|
2026-03-13 08:05:42 +00:00
|
|
|
{
|
2026-05-09 10:24:36 +01:00
|
|
|
Con::printf("| %s", token);
|
|
|
|
|
token = dStrtok(NULL, " ");
|
2026-03-13 08:05:42 +00:00
|
|
|
}
|
2026-05-09 10:24:36 +01:00
|
|
|
dFree(extCopy);
|
2022-08-16 08:33:26 +01:00
|
|
|
}
|
2026-05-09 10:24:36 +01:00
|
|
|
|
|
|
|
|
Con::printf("|------------------------------------------------");
|
|
|
|
|
Con::printBlankLine();
|
|
|
|
|
|
|
|
|
|
U32 err = alcGetError(device);
|
|
|
|
|
if (err != ALC_NO_ERROR)
|
|
|
|
|
Con::errorf("SFXALDevice - ALC error after info query: %s",
|
|
|
|
|
alcGetString(device, err));
|
2022-08-16 08:33:26 +01:00
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
S32 SFXALDevice::getMaxSources()
|
|
|
|
|
{
|
2026-03-13 08:05:42 +00:00
|
|
|
alGetError();
|
2022-08-29 18:04:55 +01:00
|
|
|
|
2022-08-16 08:33:26 +01:00
|
|
|
ALCint nummono;
|
2026-03-13 08:05:42 +00:00
|
|
|
alcGetIntegerv(mDevice, ALC_MONO_SOURCES, 1, &nummono);
|
2022-08-29 18:04:55 +01:00
|
|
|
|
2022-08-29 20:06:29 +01:00
|
|
|
if(nummono == 0)
|
|
|
|
|
nummono = getMaxSourcesOld();
|
|
|
|
|
|
2022-08-16 08:33:26 +01:00
|
|
|
return nummono;
|
|
|
|
|
}
|
2012-09-19 11:15:01 -04:00
|
|
|
|
2022-08-29 20:06:29 +01:00
|
|
|
S32 SFXALDevice::getMaxSourcesOld()
|
|
|
|
|
{
|
2023-05-01 10:37:44 -05:00
|
|
|
ALuint uiSource[256] = {};
|
2022-08-29 20:06:29 +01:00
|
|
|
S32 sourceCount = 0;
|
|
|
|
|
|
|
|
|
|
// clear errors.
|
2026-03-13 08:05:42 +00:00
|
|
|
alGetError();
|
2022-08-29 20:06:29 +01:00
|
|
|
|
|
|
|
|
for(sourceCount = 0; sourceCount < 256; sourceCount++)
|
|
|
|
|
{
|
2026-03-13 08:05:42 +00:00
|
|
|
alGenSources(1,&uiSource[sourceCount]);
|
|
|
|
|
if(alGetError() != AL_NO_ERROR)
|
2022-08-29 20:06:29 +01:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-13 08:05:42 +00:00
|
|
|
alDeleteSources(sourceCount, uiSource);
|
|
|
|
|
if(alGetError() != AL_NO_ERROR)
|
2022-08-29 20:06:29 +01:00
|
|
|
{
|
|
|
|
|
for(U32 i = 0; i < 256; i++)
|
|
|
|
|
{
|
2026-03-13 08:05:42 +00:00
|
|
|
alDeleteSources(1,&uiSource[i]);
|
2022-08-29 20:06:29 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return sourceCount;
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-13 08:05:42 +00:00
|
|
|
SFXALDevice::SFXALDevice(U32 providerIndex)
|
|
|
|
|
: mContext(NULL),
|
|
|
|
|
mDevice(NULL),
|
2020-05-11 15:24:49 -05:00
|
|
|
mDistanceModel(SFXDistanceModelLinear),
|
|
|
|
|
mDistanceFactor(1.0f),
|
2026-03-13 08:05:42 +00:00
|
|
|
mRolloffFactor(1.0f),
|
2026-03-13 18:55:55 +00:00
|
|
|
mUserRolloffFactor(1.0f),
|
|
|
|
|
mHasEFX(false),
|
2026-05-09 10:24:36 +01:00
|
|
|
mHasSoftReopen(false),
|
|
|
|
|
mHasSoftHRTF(false),
|
2026-03-13 18:55:55 +00:00
|
|
|
mEffect(0),
|
|
|
|
|
mAuxSlot(0)
|
2012-09-19 11:15:01 -04:00
|
|
|
{
|
2026-03-13 08:05:42 +00:00
|
|
|
SFXProvider* p = SFXSystem::getProvider(providerIndex);
|
2012-09-19 11:15:01 -04:00
|
|
|
|
2026-03-13 08:05:42 +00:00
|
|
|
mDevice = alcOpenDevice(p->getName());
|
2026-05-09 10:24:36 +01:00
|
|
|
if (!mDevice)
|
2012-09-19 11:15:01 -04:00
|
|
|
{
|
2026-05-09 10:24:36 +01:00
|
|
|
Con::errorf("SFXALDevice - Failed to open '%s'", p->getName());
|
|
|
|
|
return;
|
|
|
|
|
}
|
2012-09-19 11:15:01 -04:00
|
|
|
|
2026-05-09 10:24:36 +01:00
|
|
|
// Extensions that must be loaded before context creation
|
|
|
|
|
_initExtensions();
|
2022-08-16 08:33:26 +01:00
|
|
|
|
2026-05-09 10:24:36 +01:00
|
|
|
mContext = alcCreateContext(mDevice, NULL);
|
|
|
|
|
if (!mContext)
|
|
|
|
|
{
|
|
|
|
|
Con::errorf("SFXALDevice - Failed to create context (error %d)",
|
|
|
|
|
alcGetError(mDevice));
|
|
|
|
|
alcCloseDevice(mDevice);
|
|
|
|
|
mDevice = NULL;
|
|
|
|
|
return;
|
2012-09-19 11:15:01 -04:00
|
|
|
}
|
|
|
|
|
|
2026-05-09 10:24:36 +01:00
|
|
|
alcMakeContextCurrent(mContext);
|
2012-09-19 11:15:01 -04:00
|
|
|
|
2016-09-28 11:09:48 +10:00
|
|
|
#ifdef TORQUE_OS_WIN
|
2026-05-09 10:24:36 +01:00
|
|
|
|
|
|
|
|
if (!Con::getBoolVariable("$_forceAllMainThread"))
|
2012-09-19 11:15:01 -04:00
|
|
|
{
|
2026-05-09 10:24:36 +01:00
|
|
|
SFXInternal::gUpdateThread = new AsyncPeriodicUpdateThread(
|
|
|
|
|
"OpenAL Update Thread", SFXInternal::gBufferUpdateList,
|
|
|
|
|
Con::getIntVariable("$pref::SFX::updateInterval",
|
|
|
|
|
SFXInternal::DEFAULT_UPDATE_INTERVAL));
|
2012-09-19 11:15:01 -04:00
|
|
|
SFXInternal::gUpdateThread->start();
|
|
|
|
|
}
|
2015-01-18 21:35:11 +01:00
|
|
|
#endif
|
2020-05-11 15:24:49 -05:00
|
|
|
|
2026-05-09 10:24:36 +01:00
|
|
|
// Context is current — load context-level extensions and EFX
|
|
|
|
|
_initExtensions(); // second pass picks up AL_* extensions now context is current
|
|
|
|
|
_initEFX();
|
2022-08-16 08:33:26 +01:00
|
|
|
|
2026-05-09 10:24:36 +01:00
|
|
|
// Read back what the device actually settled on
|
2026-03-13 08:05:42 +00:00
|
|
|
ALCint freq = 0;
|
|
|
|
|
alcGetIntegerv(mDevice, ALC_FREQUENCY, 1, &freq);
|
2026-05-09 10:24:36 +01:00
|
|
|
Con::setIntVariable("$pref::SFX::frequency", freq > 0 ? freq : 44100);
|
|
|
|
|
Con::setIntVariable("$pref::SFX::bitrate", (mCaps & CAPS_Float32) ? 32 : 16);
|
2022-08-16 08:33:26 +01:00
|
|
|
|
2026-03-13 08:05:42 +00:00
|
|
|
printALInfo(mDevice);
|
2022-08-16 08:33:26 +01:00
|
|
|
|
2026-03-13 08:05:42 +00:00
|
|
|
mMaxBuffers = getMaxSources();
|
|
|
|
|
Con::setIntVariable("$pref::SFX::maxSoftwareBuffers", mMaxBuffers);
|
2012-09-19 11:15:01 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
SFXALDevice::~SFXALDevice()
|
|
|
|
|
{
|
|
|
|
|
_releaseAllResources();
|
2026-03-13 08:05:42 +00:00
|
|
|
|
2026-03-13 18:55:55 +00:00
|
|
|
if (alIsEffect(mEffect))
|
|
|
|
|
{
|
|
|
|
|
alDeleteAuxiliaryEffectSlots(1, &mAuxSlot);
|
|
|
|
|
alDeleteEffects(1, &mEffect);
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-29 18:01:56 +00:00
|
|
|
///cleanup of effects ends
|
2026-03-13 08:05:42 +00:00
|
|
|
alcMakeContextCurrent( NULL );
|
|
|
|
|
alcDestroyContext( mContext );
|
|
|
|
|
alcCloseDevice( mDevice );
|
2012-09-19 11:15:01 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
|
2026-03-13 08:05:42 +00:00
|
|
|
void SFXALDevice::enumerateProviders(Vector<SFXProvider*>& providerList)
|
|
|
|
|
{
|
|
|
|
|
const ALCchar* devices;
|
|
|
|
|
U32 index = providerList.size();
|
|
|
|
|
const ALCchar* defaultDeviceName;
|
|
|
|
|
|
|
|
|
|
if (alcIsExtensionPresent(NULL, "ALC_ENUMERATE_ALL_EXT") == AL_TRUE)
|
|
|
|
|
{
|
|
|
|
|
devices = alcGetString(NULL, ALC_ALL_DEVICES_SPECIFIER);
|
|
|
|
|
defaultDeviceName = alcGetString(NULL, ALC_DEFAULT_ALL_DEVICES_SPECIFIER);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
devices = alcGetString(NULL, ALC_DEVICE_SPECIFIER);
|
|
|
|
|
defaultDeviceName = alcGetString(NULL, ALC_DEFAULT_DEVICE_SPECIFIER);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const ALCchar* currentDevice = devices;
|
|
|
|
|
|
|
|
|
|
while (*currentDevice != '\0')
|
|
|
|
|
{
|
|
|
|
|
SFXProvider* toAdd = new SFXProvider;
|
|
|
|
|
toAdd->mName = String::ToString(currentDevice);
|
|
|
|
|
toAdd->mIndex = index;
|
|
|
|
|
toAdd->mDeviceType = Output;
|
|
|
|
|
toAdd->mType = OpenAL;
|
|
|
|
|
toAdd->mCreateDeviceInstanceDelegate = mCreateDeviceInstance;
|
|
|
|
|
|
|
|
|
|
if (String::compare(currentDevice, defaultDeviceName) == 0)
|
|
|
|
|
toAdd->mDefault = true;
|
|
|
|
|
|
|
|
|
|
currentDevice += dStrlen(currentDevice) + 1;
|
|
|
|
|
index++;
|
|
|
|
|
|
|
|
|
|
providerList.push_back(toAdd);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2012-09-19 11:15:01 -04:00
|
|
|
SFXBuffer* SFXALDevice::createBuffer( const ThreadSafeRef< SFXStream >& stream, SFXDescription* description )
|
|
|
|
|
{
|
|
|
|
|
AssertFatal( stream, "SFXALDevice::createBuffer() - Got null stream!" );
|
|
|
|
|
AssertFatal( description, "SFXALDevice::createBuffer() - Got null description!" );
|
|
|
|
|
|
2026-03-13 08:05:42 +00:00
|
|
|
SFXALBuffer* buffer = SFXALBuffer::create( stream,
|
2012-09-19 11:15:01 -04:00
|
|
|
description,
|
|
|
|
|
mUseHardware );
|
|
|
|
|
if ( !buffer )
|
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
|
|
_addBuffer( buffer );
|
|
|
|
|
return buffer;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
SFXVoice* SFXALDevice::createVoice( bool is3D, SFXBuffer *buffer )
|
|
|
|
|
{
|
|
|
|
|
// Don't bother going any further if we've
|
|
|
|
|
// exceeded the maximum voices.
|
|
|
|
|
if ( mVoices.size() >= mMaxBuffers )
|
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
|
|
AssertFatal( buffer, "SFXALDevice::createVoice() - Got null buffer!" );
|
|
|
|
|
|
|
|
|
|
SFXALBuffer* alBuffer = dynamic_cast<SFXALBuffer*>( buffer );
|
|
|
|
|
AssertFatal( alBuffer, "SFXALDevice::createVoice() - Got bad buffer!" );
|
|
|
|
|
|
|
|
|
|
SFXALVoice* voice = SFXALVoice::create( this, alBuffer );
|
|
|
|
|
if ( !voice )
|
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
|
|
_addVoice( voice );
|
|
|
|
|
return voice;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
void SFXALDevice::setListener( U32 index, const SFXListenerProperties& listener )
|
|
|
|
|
{
|
|
|
|
|
if( index != 0 )
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
// Torque and OpenAL are both right handed
|
|
|
|
|
// systems, so no coordinate flipping is needed.
|
|
|
|
|
|
|
|
|
|
const MatrixF &transform = listener.getTransform();
|
|
|
|
|
Point3F pos, tupple[2];
|
|
|
|
|
transform.getColumn( 3, &pos );
|
|
|
|
|
transform.getColumn( 1, &tupple[0] );
|
|
|
|
|
transform.getColumn( 2, &tupple[1] );
|
|
|
|
|
|
|
|
|
|
const VectorF &velocity = listener.getVelocity();
|
|
|
|
|
|
2026-03-13 08:05:42 +00:00
|
|
|
alListenerfv( AL_POSITION, pos );
|
|
|
|
|
alListenerfv( AL_VELOCITY, velocity );
|
|
|
|
|
alListenerfv( AL_ORIENTATION, (const F32 *)&tupple[0] );
|
2012-09-19 11:15:01 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
void SFXALDevice::setDistanceModel( SFXDistanceModel model )
|
|
|
|
|
{
|
|
|
|
|
switch( model )
|
|
|
|
|
{
|
|
|
|
|
case SFXDistanceModelLinear:
|
2026-03-13 08:05:42 +00:00
|
|
|
alDistanceModel( AL_LINEAR_DISTANCE_CLAMPED );
|
2012-09-19 11:15:01 -04:00
|
|
|
if( mRolloffFactor != 1.0f )
|
|
|
|
|
_setRolloffFactor( 1.0f ); // No rolloff on linear.
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case SFXDistanceModelLogarithmic:
|
2026-03-13 08:05:42 +00:00
|
|
|
alDistanceModel( AL_INVERSE_DISTANCE_CLAMPED );
|
2012-09-19 11:15:01 -04:00
|
|
|
if( mUserRolloffFactor != mRolloffFactor )
|
|
|
|
|
_setRolloffFactor( mUserRolloffFactor );
|
|
|
|
|
break;
|
2018-10-29 18:01:56 +00:00
|
|
|
/// create a case for our exponential distance model
|
|
|
|
|
case SFXDistanceModelExponent:
|
2026-03-13 08:05:42 +00:00
|
|
|
alDistanceModel(AL_EXPONENT_DISTANCE_CLAMPED);
|
2018-10-29 18:01:56 +00:00
|
|
|
if (mUserRolloffFactor != mRolloffFactor)
|
|
|
|
|
_setRolloffFactor(mUserRolloffFactor);
|
|
|
|
|
break;
|
|
|
|
|
|
2012-09-19 11:15:01 -04:00
|
|
|
default:
|
|
|
|
|
AssertWarn( false, "SFXALDevice::setDistanceModel - distance model not implemented" );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mDistanceModel = model;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
|
2026-03-13 18:55:55 +00:00
|
|
|
void SFXALDevice::setReverb(const SFXReverbProperties& r)
|
|
|
|
|
{
|
|
|
|
|
if (!(mCaps & CAPS_Reverb))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
/* Clear error state. */
|
|
|
|
|
alGetError();
|
|
|
|
|
|
|
|
|
|
if (alIsEffect(mEffect))
|
|
|
|
|
alDeleteEffects(1, &mEffect);
|
|
|
|
|
|
|
|
|
|
/* Create the effect object and check if we can do EAX reverb. */
|
|
|
|
|
alGenEffects(1, &mEffect);
|
|
|
|
|
|
|
|
|
|
// Map your engine's properties to EFX parameters.
|
|
|
|
|
// Using EAX Reverb as an example:
|
2026-05-09 10:24:36 +01:00
|
|
|
if (mCaps & CAPS_EXTReverb)
|
2026-03-13 18:55:55 +00:00
|
|
|
{
|
2026-05-09 10:24:36 +01:00
|
|
|
// Full EAX parameter set — panning vectors, LF params, modulation etc.
|
|
|
|
|
alEffecti(mEffect, AL_EFFECT_TYPE, AL_EFFECT_EAXREVERB);
|
|
|
|
|
if (alGetError() == AL_NO_ERROR)
|
|
|
|
|
{
|
|
|
|
|
alEffectf(mEffect, AL_EAXREVERB_DENSITY, r.flDensity);
|
|
|
|
|
alEffectf(mEffect, AL_EAXREVERB_DIFFUSION, r.flDiffusion);
|
|
|
|
|
alEffectf(mEffect, AL_EAXREVERB_GAIN, r.flGain);
|
|
|
|
|
alEffectf(mEffect, AL_EAXREVERB_GAINHF, r.flGainHF);
|
|
|
|
|
alEffectf(mEffect, AL_EAXREVERB_GAINLF, r.flGainLF);
|
|
|
|
|
alEffectf(mEffect, AL_EAXREVERB_DECAY_TIME, r.flDecayTime);
|
|
|
|
|
alEffectf(mEffect, AL_EAXREVERB_DECAY_HFRATIO, r.flDecayHFRatio);
|
|
|
|
|
alEffectf(mEffect, AL_EAXREVERB_DECAY_LFRATIO, r.flDecayLFRatio);
|
|
|
|
|
|
|
|
|
|
alEffectf(mEffect, AL_EAXREVERB_REFLECTIONS_GAIN, r.flReflectionsGain);
|
|
|
|
|
alEffectf(mEffect, AL_EAXREVERB_REFLECTIONS_DELAY, r.flReflectionsDelay);
|
|
|
|
|
alEffectfv(mEffect, AL_EAXREVERB_REFLECTIONS_PAN, r.flReflectionsPan);
|
|
|
|
|
|
|
|
|
|
alEffectf(mEffect, AL_EAXREVERB_LATE_REVERB_GAIN, r.flLateReverbGain);
|
|
|
|
|
alEffectf(mEffect, AL_EAXREVERB_LATE_REVERB_DELAY, r.flLateReverbDelay);
|
|
|
|
|
alEffectfv(mEffect, AL_EAXREVERB_LATE_REVERB_PAN, r.flLateReverbPan);
|
|
|
|
|
|
|
|
|
|
alEffectf(mEffect, AL_EAXREVERB_ECHO_TIME, r.flEchoTime);
|
|
|
|
|
alEffectf(mEffect, AL_EAXREVERB_ECHO_DEPTH, r.flEchoDepth);
|
|
|
|
|
alEffectf(mEffect, AL_EAXREVERB_MODULATION_TIME, r.flModulationTime);
|
|
|
|
|
alEffectf(mEffect, AL_EAXREVERB_MODULATION_DEPTH, r.flModulationDepth);
|
|
|
|
|
|
|
|
|
|
alEffectf(mEffect, AL_EAXREVERB_AIR_ABSORPTION_GAINHF, r.flAirAbsorptionGainHF);
|
|
|
|
|
alEffectf(mEffect, AL_EAXREVERB_HFREFERENCE, r.flHFReference);
|
|
|
|
|
alEffectf(mEffect, AL_EAXREVERB_LFREFERENCE, r.flLFReference);
|
|
|
|
|
alEffectf(mEffect, AL_EAXREVERB_ROOM_ROLLOFF_FACTOR, r.flRoomRolloffFactor);
|
|
|
|
|
alEffecti(mEffect, AL_EAXREVERB_DECAY_HFLIMIT, r.iDecayHFLimit);
|
|
|
|
|
}
|
2026-03-13 18:55:55 +00:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2026-05-09 10:24:36 +01:00
|
|
|
// Standard reverb — subset of EAX, no panning/LF/modulation params
|
2026-03-13 18:55:55 +00:00
|
|
|
alEffecti(mEffect, AL_EFFECT_TYPE, AL_EFFECT_REVERB);
|
2026-05-09 10:24:36 +01:00
|
|
|
if (alGetError() == AL_NO_ERROR)
|
|
|
|
|
{
|
|
|
|
|
alEffectf(mEffect, AL_REVERB_DENSITY, r.flDensity);
|
|
|
|
|
alEffectf(mEffect, AL_REVERB_DIFFUSION, r.flDiffusion);
|
|
|
|
|
alEffectf(mEffect, AL_REVERB_GAIN, r.flGain);
|
|
|
|
|
alEffectf(mEffect, AL_REVERB_GAINHF, r.flGainHF);
|
|
|
|
|
alEffectf(mEffect, AL_REVERB_DECAY_TIME, r.flDecayTime);
|
|
|
|
|
alEffectf(mEffect, AL_REVERB_DECAY_HFRATIO, r.flDecayHFRatio);
|
|
|
|
|
alEffectf(mEffect, AL_REVERB_REFLECTIONS_GAIN, r.flReflectionsGain);
|
|
|
|
|
alEffectf(mEffect, AL_REVERB_REFLECTIONS_DELAY, r.flReflectionsDelay);
|
|
|
|
|
alEffectf(mEffect, AL_REVERB_LATE_REVERB_GAIN, r.flLateReverbGain);
|
|
|
|
|
alEffectf(mEffect, AL_REVERB_LATE_REVERB_DELAY, r.flLateReverbDelay);
|
|
|
|
|
alEffectf(mEffect, AL_REVERB_AIR_ABSORPTION_GAINHF, r.flAirAbsorptionGainHF);
|
|
|
|
|
alEffectf(mEffect, AL_REVERB_ROOM_ROLLOFF_FACTOR, r.flRoomRolloffFactor);
|
|
|
|
|
alEffecti(mEffect, AL_REVERB_DECAY_HFLIMIT, r.iDecayHFLimit);
|
|
|
|
|
}
|
2026-03-13 18:55:55 +00:00
|
|
|
}
|
|
|
|
|
|
2026-05-09 10:24:36 +01:00
|
|
|
if (alGetError() == AL_NO_ERROR)
|
|
|
|
|
alAuxiliaryEffectSloti(mAuxSlot, AL_EFFECTSLOT_EFFECT, (ALint)mEffect);
|
|
|
|
|
else
|
2026-03-13 18:55:55 +00:00
|
|
|
{
|
2026-05-09 10:24:36 +01:00
|
|
|
Con::warnf("SFXALDevice::setReverb - failed applying reverb parameters");
|
2026-03-13 18:55:55 +00:00
|
|
|
if (alIsEffect(mEffect))
|
|
|
|
|
alDeleteEffects(1, &mEffect);
|
2026-05-09 10:24:36 +01:00
|
|
|
mEffect = 0;
|
2026-03-13 18:55:55 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
|
2012-09-19 11:15:01 -04:00
|
|
|
void SFXALDevice::setDopplerFactor( F32 factor )
|
|
|
|
|
{
|
2026-03-13 08:05:42 +00:00
|
|
|
alDopplerFactor( factor );
|
2012-09-19 11:15:01 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SFXALDevice::_setRolloffFactor( F32 factor )
|
|
|
|
|
{
|
|
|
|
|
mRolloffFactor = factor;
|
|
|
|
|
|
|
|
|
|
for( U32 i = 0, num = mVoices.size(); i < num; ++ i )
|
2026-03-13 08:05:42 +00:00
|
|
|
alSourcef( ( ( SFXALVoice* ) mVoices[ i ] )->mSourceName, AL_ROLLOFF_FACTOR, factor );
|
2012-09-19 11:15:01 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
void SFXALDevice::setRolloffFactor( F32 factor )
|
|
|
|
|
{
|
|
|
|
|
if( mDistanceModel == SFXDistanceModelLinear && factor != 1.0f )
|
|
|
|
|
Con::errorf( "SFXALDevice::setRolloffFactor - rolloff factor <> 1.0f ignored in linear distance model" );
|
|
|
|
|
else
|
|
|
|
|
_setRolloffFactor( factor );
|
|
|
|
|
|
|
|
|
|
mUserRolloffFactor = factor;
|
|
|
|
|
}
|
2018-10-29 18:01:56 +00:00
|
|
|
|
2026-03-13 08:05:42 +00:00
|
|
|
void SFXALDevice::setSpeedOfSound(F32 speedOfSound)
|
2018-10-29 18:01:56 +00:00
|
|
|
{
|
2026-03-13 08:05:42 +00:00
|
|
|
alSpeedOfSound(speedOfSound);
|
2019-05-24 11:11:09 -05:00
|
|
|
}
|
2026-05-09 10:24:36 +01:00
|
|
|
|
|
|
|
|
bool SFXALDevice::isDeviceConnected()
|
|
|
|
|
{
|
|
|
|
|
if (!mDevice)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
// ALC_CONNECTED is a standard-ish extension, check it first
|
|
|
|
|
if (alcIsExtensionPresent(mDevice, "ALC_EXT_disconnect") == AL_TRUE)
|
|
|
|
|
{
|
|
|
|
|
ALCint connected = ALC_FALSE;
|
|
|
|
|
alcGetIntegerv(mDevice, ALC_CONNECTED, 1, &connected);
|
|
|
|
|
|
|
|
|
|
if (connected == ALC_FALSE)
|
|
|
|
|
{
|
|
|
|
|
//SFXSystem::removeProvider(mDevice->getProvider());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (connected == ALC_TRUE);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If the extension isn't available, assume connected
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool SFXALDevice::reconnectDevice(U32 providerIndex)
|
|
|
|
|
{
|
|
|
|
|
if (!mDevice || !mContext)
|
|
|
|
|
{
|
|
|
|
|
Con::errorf("SFXALDevice::reconnectDevice - no active device/context to reconnect");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SFXProvider* p = SFXSystem::getProvider(providerIndex);
|
|
|
|
|
|
|
|
|
|
// If no new name given, reuse the current device name (i.e. reconnect same device)
|
|
|
|
|
if (!p->getName() || p->getName()[0] == '\0')
|
|
|
|
|
{
|
|
|
|
|
Con::errorf("SFXALDevice::reconnectDevice - unsupported provider given.");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Con::printf("SFXALDevice::reconnectDevice - reconnecting to '%s'", p->getName());
|
|
|
|
|
|
|
|
|
|
// Fast path: use ALC_SOFT_reopen_device to swap device without losing context.
|
|
|
|
|
// All sources/buffers remain valid, playback resumes automatically.
|
|
|
|
|
if (mHasSoftReopen && alcReopenDeviceSOFT)
|
|
|
|
|
{
|
|
|
|
|
// Build attribute list matching current device settings
|
|
|
|
|
ALCint attribs[] =
|
|
|
|
|
{
|
|
|
|
|
ALC_FREQUENCY, smDeviceFrequency,
|
|
|
|
|
ALC_MONO_SOURCES, mMaxBuffers,
|
|
|
|
|
0
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (alcReopenDeviceSOFT(mDevice, p->getName(), attribs) == ALC_TRUE)
|
|
|
|
|
{
|
|
|
|
|
Con::printf("SFXALDevice::reconnectDevice - soft reopen succeeded");
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Con::warnf("SFXALDevice::reconnectDevice - soft reopen failed (error %d), "
|
|
|
|
|
"falling back to full recreate", alcGetError(mDevice));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Slow path: destroy context and reopen device from scratch.
|
|
|
|
|
// SFXSystem::createDevice will handle transitioning sounds to virtual playback.
|
|
|
|
|
Con::printf("SFXALDevice::reconnectDevice - performing full device recreate");
|
|
|
|
|
|
|
|
|
|
alcMakeContextCurrent(NULL);
|
|
|
|
|
alcDestroyContext(mContext);
|
|
|
|
|
mContext = NULL;
|
|
|
|
|
alcCloseDevice(mDevice);
|
|
|
|
|
mDevice = NULL;
|
|
|
|
|
return false;
|
|
|
|
|
}
|