aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt30
-rw-r--r--al/auxeffectslot.cpp834
-rw-r--r--al/auxeffectslot.h221
-rw-r--r--al/buffer.cpp292
-rw-r--r--al/buffer.h9
-rw-r--r--al/eax_api.cpp1151
-rw-r--r--al/eax_api.h1542
-rw-r--r--al/eax_eax_call.cpp370
-rw-r--r--al/eax_eax_call.h128
-rw-r--r--al/eax_effect.cpp1
-rw-r--r--al/eax_effect.h28
-rw-r--r--al/eax_exception.cpp61
-rw-r--r--al/eax_exception.h25
-rw-r--r--al/eax_fx_slot_index.cpp164
-rw-r--r--al/eax_fx_slot_index.h69
-rw-r--r--al/eax_fx_slots.cpp81
-rw-r--r--al/eax_fx_slots.h46
-rw-r--r--al/eax_globals.cpp18
-rw-r--r--al/eax_globals.h22
-rw-r--r--al/eax_utils.cpp35
-rw-r--r--al/eax_utils.h164
-rw-r--r--al/eax_x_ram.cpp1
-rw-r--r--al/eax_x_ram.h38
-rw-r--r--al/effect.cpp143
-rw-r--r--al/effect.h53
-rw-r--r--al/effects/autowah.cpp471
-rw-r--r--al/effects/chorus.cpp1240
-rw-r--r--al/effects/compressor.cpp272
-rw-r--r--al/effects/distortion.cpp536
-rw-r--r--al/effects/echo.cpp534
-rw-r--r--al/effects/effects.cpp106
-rw-r--r--al/effects/effects.h11
-rw-r--r--al/effects/equalizer.cpp866
-rw-r--r--al/effects/fshifter.cpp414
-rw-r--r--al/effects/modulator.cpp411
-rw-r--r--al/effects/null.cpp57
-rw-r--r--al/effects/pshifter.cpp338
-rw-r--r--al/effects/reverb.cpp1922
-rw-r--r--al/effects/vmorpher.cpp624
-rw-r--r--al/extension.cpp117
-rw-r--r--al/filter.cpp81
-rw-r--r--al/filter.h40
-rw-r--r--al/listener.cpp22
-rw-r--r--al/listener.h7
-rw-r--r--al/source.cpp2488
-rw-r--r--al/source.h689
-rw-r--r--al/state.cpp42
-rw-r--r--alc/alc.cpp44
-rw-r--r--alc/context.cpp1228
-rw-r--r--alc/context.h386
-rw-r--r--alc/device.h8
-rw-r--r--alsoftrc.sample8
-rw-r--r--common/alnumeric.h45
53 files changed, 18533 insertions, 0 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 93f2c7ec..9480910b 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -95,6 +95,8 @@ option(ALSOFT_INSTALL_EXAMPLES "Install example programs (alplay, alstream, ...)
option(ALSOFT_INSTALL_UTILS "Install utility programs (openal-info, alsoft-config, ...)" ON)
option(ALSOFT_UPDATE_BUILD_VERSION "Update git build version info" ON)
+option(ALSOFT_EAX "Enable EAX extensions." ON)
+
if(DEFINED SHARE_INSTALL_DIR)
message(WARNING "SHARE_INSTALL_DIR is deprecated. Use the variables provided by the GNUInstallDirs module instead")
set(CMAKE_INSTALL_DATADIR "${SHARE_INSTALL_DIR}")
@@ -764,6 +766,7 @@ set(OPENAL_OBJS
al/effects/dedicated.cpp
al/effects/distortion.cpp
al/effects/echo.cpp
+ al/effects/effects.cpp
al/effects/effects.h
al/effects/equalizer.cpp
al/effects/fshifter.cpp
@@ -813,6 +816,30 @@ set(ALC_OBJS
alc/inprogext.h
alc/panning.cpp)
+if (ALSOFT_EAX)
+ set(OPENAL_OBJS
+ ${OPENAL_OBJS}
+ al/eax_api.cpp
+ al/eax_api.h
+ al/eax_eax_call.cpp
+ al/eax_eax_call.h
+ al/eax_effect.cpp
+ al/eax_effect.h
+ al/eax_exception.cpp
+ al/eax_exception.h
+ al/eax_fx_slot_index.cpp
+ al/eax_fx_slot_index.h
+ al/eax_fx_slots.cpp
+ al/eax_fx_slots.h
+ al/eax_globals.cpp
+ al/eax_globals.h
+ al/eax_utils.cpp
+ al/eax_utils.h
+ al/eax_x_ram.cpp
+ al/eax_x_ram.h
+)
+endif ()
+
# Include SIMD mixers
set(CPU_EXTS "Default")
if(HAVE_SSE)
@@ -1289,6 +1316,7 @@ target_include_directories(common PRIVATE ${OpenAL_BINARY_DIR} ${OpenAL_SOURCE_D
target_compile_definitions(common PRIVATE ${CPP_DEFS})
target_compile_options(common PRIVATE ${C_FLAGS})
set_target_properties(common PROPERTIES POSITION_INDEPENDENT_CODE TRUE)
+target_compile_definitions(common PRIVATE "ALSOFT_EAX=$<BOOL:${ALSOFT_EAX}>")
unset(HAS_ROUTER)
@@ -1299,6 +1327,7 @@ if(LIBTYPE STREQUAL "STATIC")
add_library(${IMPL_TARGET} STATIC ${COMMON_OBJS} ${OPENAL_OBJS} ${ALC_OBJS} ${CORE_OBJS})
target_compile_definitions(${IMPL_TARGET} PUBLIC AL_LIBTYPE_STATIC)
target_link_libraries(${IMPL_TARGET} PRIVATE ${LINKER_FLAGS} ${EXTRA_LIBS} ${MATH_LIB})
+
if(WIN32)
# This option is for static linking OpenAL Soft into another project
# that already defines the IDs. It is up to that project to ensure all
@@ -1409,6 +1438,7 @@ set_target_properties(${IMPL_TARGET} PROPERTIES OUTPUT_NAME ${LIBNAME}
target_compile_definitions(${IMPL_TARGET}
PRIVATE AL_BUILD_LIBRARY AL_ALEXT_PROTOTYPES "ALC_API=${EXPORT_DECL}" "AL_API=${EXPORT_DECL}"
${CPP_DEFS})
+target_compile_definitions(${IMPL_TARGET} PRIVATE "ALSOFT_EAX=$<BOOL:${ALSOFT_EAX}>")
target_compile_options(${IMPL_TARGET} PRIVATE ${C_FLAGS})
if(TARGET build_version)
diff --git a/al/auxeffectslot.cpp b/al/auxeffectslot.cpp
index 16e4cbea..40949aa3 100644
--- a/al/auxeffectslot.cpp
+++ b/al/auxeffectslot.cpp
@@ -50,6 +50,10 @@
#include "effect.h"
#include "opthelpers.h"
+#if ALSOFT_EAX
+#include "eax_exception.h"
+#include "eax_utils.h"
+#endif // ALSOFT_EAX
namespace {
@@ -1042,3 +1046,833 @@ EffectSlotSubList::~EffectSlotSubList()
al_free(EffectSlots);
EffectSlots = nullptr;
}
+
+#if ALSOFT_EAX
+namespace
+{
+
+
+class EaxFxSlotException :
+ public EaxException
+{
+public:
+ explicit EaxFxSlotException(
+ const char* message)
+ :
+ EaxException{"EAX_FX_SLOT", message}
+ {
+ }
+}; // EaxFxSlotException
+
+
+} // namespace
+
+
+void ALeffectslot::eax_initialize(
+ ALCcontext& al_context,
+ EaxFxSlotIndexValue index)
+{
+ eax_al_context_ = &al_context;
+
+ if (index >= EAX_MAX_FXSLOTS)
+ {
+ eax_fail("Index out of range.");
+ }
+
+ eax_fx_slot_index_ = index;
+
+ eax_initialize_eax();
+ eax_initialize_effects();
+ eax_set_default_slots_defaults();
+}
+
+void ALeffectslot::eax_uninitialize() noexcept
+{
+ eax_al_effect_ = nullptr;
+}
+
+const EAX50FXSLOTPROPERTIES& ALeffectslot::eax_get_eax_fx_slot() const noexcept
+{
+ return eax_eax_fx_slot_;
+}
+
+void ALeffectslot::eax_validate_fx_slot_effect(
+ const GUID& eax_effect_id)
+{
+ if (eax_effect_id != EAX_NULL_GUID &&
+ eax_effect_id != EAX_REVERB_EFFECT)
+ {
+ eax_fail("Unsupported EAX effect GUID.");
+ }
+}
+
+void ALeffectslot::eax_validate_fx_slot_volume(
+ long eax_volume)
+{
+ eax_validate_range<EaxFxSlotException>(
+ "Volume",
+ eax_volume,
+ EAXFXSLOT_MINVOLUME,
+ EAXFXSLOT_MAXVOLUME);
+}
+
+void ALeffectslot::eax_validate_fx_slot_lock(
+ long eax_lock)
+{
+ eax_validate_range<EaxFxSlotException>(
+ "Lock",
+ eax_lock,
+ EAXFXSLOT_MINLOCK,
+ EAXFXSLOT_MAXLOCK);
+}
+
+void ALeffectslot::eax_validate_fx_slot_lock_state(
+ long eax_lock,
+ const GUID& eax_effect_id)
+{
+ if (eax_lock == EAXFXSLOT_LOCKED && eax_effect_id != eax_eax_fx_slot_.guidLoadEffect)
+ {
+ eax_fail("Loading an effect in a locked slot.");
+ }
+}
+
+void ALeffectslot::eax_validate_fx_slot_flags(
+ unsigned long eax_flags,
+ int eax_version)
+{
+ eax_validate_range<EaxFxSlotException>(
+ "Flags",
+ eax_flags,
+ 0UL,
+ ~(eax_version == 4 ? EAX40FXSLOTFLAGS_RESERVED : EAX50FXSLOTFLAGS_RESERVED));
+}
+
+void ALeffectslot::eax_validate_fx_slot_occlusion(
+ long eax_occlusion)
+{
+ eax_validate_range<EaxFxSlotException>(
+ "Occlusion",
+ eax_occlusion,
+ EAXFXSLOT_MINOCCLUSION,
+ EAXFXSLOT_MAXOCCLUSION);
+}
+
+void ALeffectslot::eax_validate_fx_slot_occlusion_lf_ratio(
+ float eax_occlusion_lf_ratio)
+{
+ eax_validate_range<EaxFxSlotException>(
+ "Occlusion LF Ratio",
+ eax_occlusion_lf_ratio,
+ EAXFXSLOT_MINOCCLUSIONLFRATIO,
+ EAXFXSLOT_MAXOCCLUSIONLFRATIO);
+}
+
+void ALeffectslot::eax_validate_fx_slot_all(
+ const EAX40FXSLOTPROPERTIES& fx_slot,
+ int eax_version)
+{
+ eax_validate_fx_slot_effect(fx_slot.guidLoadEffect);
+ eax_validate_fx_slot_volume(fx_slot.lVolume);
+ eax_validate_fx_slot_lock(fx_slot.lLock);
+ eax_validate_fx_slot_flags(fx_slot.ulFlags, eax_version);
+}
+
+void ALeffectslot::eax_validate_fx_slot_all(
+ const EAX50FXSLOTPROPERTIES& fx_slot,
+ int eax_version)
+{
+ eax_validate_fx_slot_all(static_cast<const EAX40FXSLOTPROPERTIES&>(fx_slot), eax_version);
+
+ eax_validate_fx_slot_occlusion(fx_slot.lOcclusion);
+ eax_validate_fx_slot_occlusion_lf_ratio(fx_slot.flOcclusionLFRatio);
+}
+
+void ALeffectslot::eax_set_fx_slot_effect(
+ const GUID& eax_effect_id)
+{
+ if (eax_eax_fx_slot_.guidLoadEffect == eax_effect_id)
+ {
+ return;
+ }
+
+ eax_eax_fx_slot_.guidLoadEffect = eax_effect_id;
+
+ eax_set_fx_slot_effect();
+}
+
+void ALeffectslot::eax_set_fx_slot_volume(
+ long eax_volume)
+{
+ if (eax_eax_fx_slot_.lVolume == eax_volume)
+ {
+ return;
+ }
+
+ eax_eax_fx_slot_.lVolume = eax_volume;
+
+ eax_set_fx_slot_volume();
+}
+
+void ALeffectslot::eax_set_fx_slot_lock(
+ long eax_lock)
+{
+ if (eax_eax_fx_slot_.lLock == eax_lock)
+ {
+ return;
+ }
+
+ eax_eax_fx_slot_.lLock = eax_lock;
+}
+
+void ALeffectslot::eax_set_fx_slot_flags(
+ unsigned long eax_flags)
+{
+ if (eax_eax_fx_slot_.ulFlags == eax_flags)
+ {
+ return;
+ }
+
+ eax_eax_fx_slot_.ulFlags = eax_flags;
+
+ eax_set_fx_slot_flags();
+}
+
+// [[nodiscard]]
+bool ALeffectslot::eax_set_fx_slot_occlusion(
+ long eax_occlusion)
+{
+ if (eax_eax_fx_slot_.lOcclusion == eax_occlusion)
+ {
+ return false;
+ }
+
+ eax_eax_fx_slot_.lOcclusion = eax_occlusion;
+
+ return true;
+}
+
+// [[nodiscard]]
+bool ALeffectslot::eax_set_fx_slot_occlusion_lf_ratio(
+ float eax_occlusion_lf_ratio)
+{
+ if (eax_eax_fx_slot_.flOcclusionLFRatio == eax_occlusion_lf_ratio)
+ {
+ return false;
+ }
+
+ eax_eax_fx_slot_.flOcclusionLFRatio = eax_occlusion_lf_ratio;
+
+ return true;
+}
+
+void ALeffectslot::eax_set_fx_slot_all(
+ const EAX40FXSLOTPROPERTIES& eax_fx_slot)
+{
+ eax_set_fx_slot_effect(eax_fx_slot.guidLoadEffect);
+ eax_set_fx_slot_volume(eax_fx_slot.lVolume);
+ eax_set_fx_slot_lock(eax_fx_slot.lLock);
+ eax_set_fx_slot_flags(eax_fx_slot.ulFlags);
+}
+
+// [[nodiscard]]
+bool ALeffectslot::eax_set_fx_slot_all(
+ const EAX50FXSLOTPROPERTIES& eax_fx_slot)
+{
+ eax_set_fx_slot_all(static_cast<const EAX40FXSLOTPROPERTIES&>(eax_fx_slot));
+
+ const auto is_occlusion_modified = eax_set_fx_slot_occlusion(eax_fx_slot.lOcclusion);
+ const auto is_occlusion_lf_ratio_modified = eax_set_fx_slot_occlusion_lf_ratio(eax_fx_slot.flOcclusionLFRatio);
+
+ return is_occlusion_modified || is_occlusion_lf_ratio_modified;
+}
+
+// [[nodiscard]]
+bool ALeffectslot::eax_dispatch(
+ const EaxEaxCall& eax_call)
+{
+ return eax_call.is_get() ? eax_get(eax_call) : eax_set(eax_call);
+}
+
+[[noreturn]]
+void ALeffectslot::eax_fail(
+ const char* message)
+{
+ throw EaxFxSlotException{message};
+}
+
+void ALeffectslot::eax_set_eax_fx_slot_defaults()
+{
+ eax_eax_fx_slot_.guidLoadEffect = EAX_NULL_GUID;
+ eax_eax_fx_slot_.lVolume = EAXFXSLOT_DEFAULTVOLUME;
+ eax_eax_fx_slot_.lLock = EAXFXSLOT_UNLOCKED;
+ eax_eax_fx_slot_.ulFlags = EAX50FXSLOT_DEFAULTFLAGS;
+ eax_eax_fx_slot_.lOcclusion = EAXFXSLOT_DEFAULTOCCLUSION;
+ eax_eax_fx_slot_.flOcclusionLFRatio = EAXFXSLOT_DEFAULTOCCLUSIONLFRATIO;
+}
+
+void ALeffectslot::eax_initialize_eax()
+{
+ eax_set_eax_fx_slot_defaults();
+}
+
+void ALeffectslot::eax_initialize_effects()
+{
+ eax_set_fx_slot_effect();
+}
+
+void ALeffectslot::eax_set_default_slot_0_defaults()
+{
+ eax_set_fx_slot_effect(EAX_REVERB_EFFECT);
+}
+
+void ALeffectslot::eax_set_default_slot_1_defaults()
+{
+ eax_set_fx_slot_effect(EAX_CHORUS_EFFECT);
+}
+
+void ALeffectslot::eax_set_default_slots_defaults()
+{
+ switch (eax_fx_slot_index_)
+ {
+ case 0:
+ eax_set_default_slot_0_defaults();
+ break;
+
+ case 1:
+ eax_set_default_slot_1_defaults();
+ break;
+
+ case 2:
+ case 3:
+ break;
+
+ default:
+ eax_fail("FX slot index out of range.");
+ }
+}
+
+void ALeffectslot::eax_get_fx_slot_all(
+ const EaxEaxCall& eax_call) const
+{
+ switch (eax_call.get_version())
+ {
+ case 4:
+ eax_call.set_value<EaxFxSlotException, EAX40FXSLOTPROPERTIES>(eax_eax_fx_slot_);
+ break;
+
+ case 5:
+ eax_call.set_value<EaxFxSlotException, EAX50FXSLOTPROPERTIES>(eax_eax_fx_slot_);
+ break;
+
+ default:
+ eax_fail("Unsupported EAX version.");
+ }
+}
+
+void ALeffectslot::eax_get_fx_slot(
+ const EaxEaxCall& eax_call) const
+{
+ switch (eax_call.get_property_id())
+ {
+ case EAXFXSLOT_ALLPARAMETERS:
+ eax_get_fx_slot_all(eax_call);
+ break;
+
+ case EAXFXSLOT_LOADEFFECT:
+ eax_call.set_value<EaxFxSlotException>(eax_eax_fx_slot_.guidLoadEffect);
+ break;
+
+ case EAXFXSLOT_VOLUME:
+ eax_call.set_value<EaxFxSlotException>(eax_eax_fx_slot_.lVolume);
+ break;
+
+ case EAXFXSLOT_LOCK:
+ eax_call.set_value<EaxFxSlotException>(eax_eax_fx_slot_.lLock);
+ break;
+
+ case EAXFXSLOT_FLAGS:
+ eax_call.set_value<EaxFxSlotException>(eax_eax_fx_slot_.ulFlags);
+ break;
+
+ case EAXFXSLOT_OCCLUSION:
+ eax_call.set_value<EaxFxSlotException>(eax_eax_fx_slot_.lOcclusion);
+ break;
+
+ case EAXFXSLOT_OCCLUSIONLFRATIO:
+ eax_call.set_value<EaxFxSlotException>(eax_eax_fx_slot_.flOcclusionLFRatio);
+ break;
+
+ default:
+ eax_fail("Unsupported FX slot property id.");
+ }
+}
+
+// [[nodiscard]]
+bool ALeffectslot::eax_get(
+ const EaxEaxCall& eax_call)
+{
+ switch (eax_call.get_property_set_id())
+ {
+ case EaxEaxCallPropertySetId::fx_slot:
+ eax_get_fx_slot(eax_call);
+ break;
+
+ case EaxEaxCallPropertySetId::fx_slot_effect:
+ eax_dispatch_effect(eax_call);
+ break;
+
+ default:
+ eax_fail("Unsupported property id.");
+ }
+
+ return false;
+}
+
+void ALeffectslot::eax_set_fx_slot_effect(
+ ALenum al_effect_type)
+{
+ if (!eax_al_effect_)
+ {
+ eax_al_effect_ = eax_create_al_effect(*eax_al_context_, al_effect_type);
+ }
+ else
+ {
+ auto& device = eax_al_context_->mALDevice;
+ std::lock_guard<std::mutex> effect_lock{device->EffectLock};
+
+ eax_al_effect_->eax_al_set_effect(al_effect_type);
+ }
+
+ eax_al_effect_->eax_initialize();
+
+ eax_set_effect_slot_effect(*eax_al_effect_);
+}
+
+void ALeffectslot::eax_set_fx_slot_effect()
+{
+ auto al_effect_type = ALenum{};
+
+ if (false)
+ {
+ }
+ else if (eax_eax_fx_slot_.guidLoadEffect == EAX_NULL_GUID)
+ {
+ al_effect_type = AL_EFFECT_NULL;
+ }
+ else if (eax_eax_fx_slot_.guidLoadEffect == EAX_AUTOWAH_EFFECT)
+ {
+ al_effect_type = AL_EFFECT_AUTOWAH;
+ }
+ else if (eax_eax_fx_slot_.guidLoadEffect == EAX_CHORUS_EFFECT)
+ {
+ al_effect_type = AL_EFFECT_CHORUS;
+ }
+ else if (eax_eax_fx_slot_.guidLoadEffect == EAX_AGCCOMPRESSOR_EFFECT)
+ {
+ al_effect_type = AL_EFFECT_COMPRESSOR;
+ }
+ else if (eax_eax_fx_slot_.guidLoadEffect == EAX_DISTORTION_EFFECT)
+ {
+ al_effect_type = AL_EFFECT_DISTORTION;
+ }
+ else if (eax_eax_fx_slot_.guidLoadEffect == EAX_REVERB_EFFECT)
+ {
+ al_effect_type = AL_EFFECT_EAXREVERB;
+ }
+ else if (eax_eax_fx_slot_.guidLoadEffect == EAX_ECHO_EFFECT)
+ {
+ al_effect_type = AL_EFFECT_ECHO;
+ }
+ else if (eax_eax_fx_slot_.guidLoadEffect == EAX_EQUALIZER_EFFECT)
+ {
+ al_effect_type = AL_EFFECT_EQUALIZER;
+ }
+ else if (eax_eax_fx_slot_.guidLoadEffect == EAX_FLANGER_EFFECT)
+ {
+ al_effect_type = AL_EFFECT_FLANGER;
+ }
+ else if (eax_eax_fx_slot_.guidLoadEffect == EAX_FREQUENCYSHIFTER_EFFECT)
+ {
+ al_effect_type = AL_EFFECT_FREQUENCY_SHIFTER;
+ }
+ else if (eax_eax_fx_slot_.guidLoadEffect == EAX_PITCHSHIFTER_EFFECT)
+ {
+ al_effect_type = AL_EFFECT_PITCH_SHIFTER;
+ }
+ else if (eax_eax_fx_slot_.guidLoadEffect == EAX_RINGMODULATOR_EFFECT)
+ {
+ al_effect_type = AL_EFFECT_RING_MODULATOR;
+ }
+ else if (eax_eax_fx_slot_.guidLoadEffect == EAX_VOCALMORPHER_EFFECT)
+ {
+ al_effect_type = AL_EFFECT_VOCAL_MORPHER;
+ }
+ else
+ {
+ eax_fail("Unsupported effect.");
+ }
+
+ eax_set_fx_slot_effect(al_effect_type);
+}
+
+void ALeffectslot::eax_set_efx_effect_slot_gain()
+{
+ const auto gain = level_mb_to_gain(
+ static_cast<float>(clamp(
+ eax_eax_fx_slot_.lVolume,
+ EAXFXSLOT_MINVOLUME,
+ EAXFXSLOT_MAXVOLUME)));
+
+ eax_set_effect_slot_gain(gain);
+}
+
+void ALeffectslot::eax_set_fx_slot_volume()
+{
+ eax_set_efx_effect_slot_gain();
+}
+
+void ALeffectslot::eax_set_effect_slot_send_auto()
+{
+ eax_set_effect_slot_send_auto((eax_eax_fx_slot_.ulFlags & EAXFXSLOTFLAGS_ENVIRONMENT) != 0);
+}
+
+void ALeffectslot::eax_set_fx_slot_flags()
+{
+ eax_set_effect_slot_send_auto();
+}
+
+void ALeffectslot::eax_set_fx_slot_effect(
+ const EaxEaxCall& eax_call)
+{
+ const auto& eax_effect_id =
+ eax_call.get_value<EaxFxSlotException, const decltype(EAX40FXSLOTPROPERTIES::guidLoadEffect)>();
+
+ eax_validate_fx_slot_effect(eax_effect_id);
+ eax_validate_fx_slot_lock_state(eax_eax_fx_slot_.lLock, eax_effect_id);
+
+ eax_set_fx_slot_effect(eax_effect_id);
+}
+
+void ALeffectslot::eax_set_fx_slot_volume(
+ const EaxEaxCall& eax_call)
+{
+ const auto& eax_volume =
+ eax_call.get_value<EaxFxSlotException, const decltype(EAX40FXSLOTPROPERTIES::lVolume)>();
+
+ eax_validate_fx_slot_volume(eax_volume);
+ eax_set_fx_slot_volume(eax_volume);
+}
+
+void ALeffectslot::eax_set_fx_slot_lock(
+ const EaxEaxCall& eax_call)
+{
+ const auto& eax_lock =
+ eax_call.get_value<EaxFxSlotException, const decltype(EAX40FXSLOTPROPERTIES::lLock)>();
+
+ eax_validate_fx_slot_lock(eax_lock);
+ eax_set_fx_slot_lock(eax_lock);
+}
+
+void ALeffectslot::eax_set_fx_slot_flags(
+ const EaxEaxCall& eax_call)
+{
+ const auto& eax_flags =
+ eax_call.get_value<EaxFxSlotException, const decltype(EAX40FXSLOTPROPERTIES::ulFlags)>();
+
+ eax_validate_fx_slot_flags(eax_flags, eax_call.get_version());
+ eax_set_fx_slot_flags(eax_flags);
+}
+
+// [[nodiscard]]
+bool ALeffectslot::eax_set_fx_slot_occlusion(
+ const EaxEaxCall& eax_call)
+{
+ const auto& eax_occlusion =
+ eax_call.get_value<EaxFxSlotException, const decltype(EAX50FXSLOTPROPERTIES::lOcclusion)>();
+
+ eax_validate_fx_slot_occlusion(eax_occlusion);
+
+ return eax_set_fx_slot_occlusion(eax_occlusion);
+}
+
+// [[nodiscard]]
+bool ALeffectslot::eax_set_fx_slot_occlusion_lf_ratio(
+ const EaxEaxCall& eax_call)
+{
+ const auto& eax_occlusion_lf_ratio =
+ eax_call.get_value<EaxFxSlotException, const decltype(EAX50FXSLOTPROPERTIES::flOcclusionLFRatio)>();
+
+ eax_validate_fx_slot_occlusion_lf_ratio(eax_occlusion_lf_ratio);
+
+ return eax_set_fx_slot_occlusion_lf_ratio(eax_occlusion_lf_ratio);
+}
+
+// [[nodiscard]]
+bool ALeffectslot::eax_set_fx_slot_all(
+ const EaxEaxCall& eax_call)
+{
+ switch (eax_call.get_version())
+ {
+ case 4:
+ {
+ const auto& eax_all =
+ eax_call.get_value<EaxFxSlotException, const EAX40FXSLOTPROPERTIES>();
+
+ eax_validate_fx_slot_all(eax_all, eax_call.get_version());
+ eax_set_fx_slot_all(eax_all);
+
+ return false;
+ }
+
+ case 5:
+ {
+ const auto& eax_all =
+ eax_call.get_value<EaxFxSlotException, const EAX50FXSLOTPROPERTIES>();
+
+ eax_validate_fx_slot_all(eax_all, eax_call.get_version());
+ return eax_set_fx_slot_all(eax_all);
+ }
+
+ default:
+ eax_fail("Unsupported EAX version.");
+ }
+}
+
+bool ALeffectslot::eax_set_fx_slot(
+ const EaxEaxCall& eax_call)
+{
+ switch (eax_call.get_property_id())
+ {
+ case EAXFXSLOT_NONE:
+ return false;
+
+ case EAXFXSLOT_ALLPARAMETERS:
+ return eax_set_fx_slot_all(eax_call);
+
+ case EAXFXSLOT_LOADEFFECT:
+ eax_set_fx_slot_effect(eax_call);
+ return false;
+
+ case EAXFXSLOT_VOLUME:
+ eax_set_fx_slot_volume(eax_call);
+ return false;
+
+ case EAXFXSLOT_LOCK:
+ eax_set_fx_slot_lock(eax_call);
+ return false;
+
+ case EAXFXSLOT_FLAGS:
+ eax_set_fx_slot_flags(eax_call);
+ return false;
+
+ case EAXFXSLOT_OCCLUSION:
+ return eax_set_fx_slot_occlusion(eax_call);
+
+ case EAXFXSLOT_OCCLUSIONLFRATIO:
+ return eax_set_fx_slot_occlusion_lf_ratio(eax_call);
+
+
+ default:
+ eax_fail("Unsupported FX slot property id.");
+ }
+}
+
+// [[nodiscard]]
+bool ALeffectslot::eax_set(
+ const EaxEaxCall& eax_call)
+{
+ switch (eax_call.get_property_set_id())
+ {
+ case EaxEaxCallPropertySetId::fx_slot:
+ return eax_set_fx_slot(eax_call);
+
+ case EaxEaxCallPropertySetId::fx_slot_effect:
+ eax_dispatch_effect(eax_call);
+ return false;
+
+ default:
+ eax_fail("Unsupported property id.");
+ }
+}
+
+void ALeffectslot::eax_dispatch_effect(
+ const EaxEaxCall& eax_call)
+{
+ auto is_changed = false;
+
+ {
+ std::lock_guard<std::mutex> effect_lock{eax_al_context_->mALDevice->EffectLock};
+
+ if (!eax_al_effect_->eax_effect)
+ {
+ return;
+ }
+
+ is_changed = eax_al_effect_->eax_effect->dispatch(eax_call);
+ }
+
+ if (is_changed)
+ {
+ eax_set_effect_slot_effect(*eax_al_effect_);
+ }
+}
+
+void ALeffectslot::eax_set_effect_slot_effect(
+ ALeffect& effect)
+{
+#define EAX_PREFIX "[EAX_SET_EFFECT_SLOT_EFFECT] "
+
+ auto& device = *eax_al_context_->mALDevice;
+
+ std::lock_guard<std::mutex> effect_slot_lock{eax_al_context_->mEffectSlotLock};
+ std::lock_guard<std::mutex> effect_lock{device.EffectLock};
+
+ const auto error = initEffect(&effect, eax_al_context_);
+
+ if (error != AL_NO_ERROR)
+ {
+ ERR(EAX_PREFIX "%s\n", "Failed to initialize an effect.");
+ return;
+ }
+
+ if (mState == SlotState::Initial)
+ {
+ mPropsDirty.test_and_clear(std::memory_order_acq_rel);
+ updateProps(eax_al_context_);
+
+ auto effect_slot_ptr = this;
+
+ AddActiveEffectSlots({&effect_slot_ptr, 1}, eax_al_context_);
+ mState = SlotState::Playing;
+
+ return;
+ }
+
+ {
+ auto context = EaxAlContextWrapper{*eax_al_context_};
+ auto slot = this;
+
+ DO_UPDATEPROPS();
+ }
+
+#undef EAX_PREFIX
+}
+
+void ALeffectslot::eax_set_effect_slot_send_auto(
+ bool is_send_auto)
+{
+ std::lock_guard<std::mutex> effect_slot_lock{eax_al_context_->mEffectSlotLock};
+
+ const auto is_changed = (AuxSendAuto != is_send_auto);
+
+ AuxSendAuto = is_send_auto;
+
+ if (is_changed)
+ {
+ auto context = EaxAlContextWrapper{*eax_al_context_};
+ auto slot = this;
+
+ DO_UPDATEPROPS();
+ }
+}
+
+void ALeffectslot::eax_set_effect_slot_gain(
+ ALfloat gain)
+{
+#define EAX_PREFIX "[EAX_SET_EFFECT_SLOT_GAIN] "
+
+ if (gain < 0.0F || gain > 1.0F)
+ {
+ ERR(EAX_PREFIX "%s\n", "Gain out of range.");
+ return;
+ }
+
+ std::lock_guard<std::mutex> effect_slot_lock{eax_al_context_->mEffectSlotLock};
+
+ const auto is_changed = (Gain != gain);
+
+ Gain = gain;
+
+ if (is_changed)
+ {
+ auto context = EaxAlContextWrapper{*eax_al_context_};
+ auto slot = this;
+
+ DO_UPDATEPROPS();
+ }
+
+#undef EAX_PREFIX
+}
+
+
+EaxAlEffectSlotDeleter::EaxAlEffectSlotDeleter(
+ ALCcontext& context) noexcept
+ :
+ context_{&context}
+{
+}
+
+void EaxAlEffectSlotDeleter::operator()(
+ ALeffectslot* effect_slot)
+{
+ assert(effect_slot);
+ eax_delete_al_effect_slot(*context_, *effect_slot);
+}
+
+
+EaxAlEffectSlotUPtr eax_create_al_effect_slot(
+ ALCcontext& context)
+{
+#define EAX_PREFIX "[EAX_MAKE_EFFECT_SLOT] "
+
+ std::unique_lock<std::mutex> effect_slot_lock{context.mEffectSlotLock};
+
+ auto& device = *context.mALDevice;
+
+ if (context.mNumEffectSlots == device.AuxiliaryEffectSlotMax)
+ {
+ ERR(EAX_PREFIX "%s\n", "Out of memory.");
+ return nullptr;
+ }
+
+ if (!EnsureEffectSlots(&context, 1))
+ {
+ ERR(EAX_PREFIX "%s\n", "Failed to ensure.");
+ return nullptr;
+ }
+
+ auto effect_slot = EaxAlEffectSlotUPtr{AllocEffectSlot(&context), EaxAlEffectSlotDeleter{context}};
+
+ if (!effect_slot)
+ {
+ ERR(EAX_PREFIX "%s\n", "Failed to allocate.");
+ return nullptr;
+ }
+
+ return effect_slot;
+
+#undef EAX_PREFIX
+}
+
+void eax_delete_al_effect_slot(
+ ALCcontext& context,
+ ALeffectslot& effect_slot)
+{
+#define EAX_PREFIX "[EAX_DELETE_EFFECT_SLOT] "
+
+ std::lock_guard<std::mutex> effect_slot_lock{context.mEffectSlotLock};
+
+ if (ReadRef(effect_slot.ref) != 0)
+ {
+ ERR(EAX_PREFIX "Deleting in-use effect slot %u.\n", effect_slot.id);
+ return;
+ }
+
+ auto effect_slot_ptr = &effect_slot;
+
+ RemoveActiveEffectSlots({&effect_slot_ptr, 1}, &context);
+ FreeEffectSlot(&context, &effect_slot);
+
+#undef EAX_PREFIX
+}
+#endif // ALSOFT_EAX
diff --git a/al/auxeffectslot.h b/al/auxeffectslot.h
index 4de1df7a..54f1987d 100644
--- a/al/auxeffectslot.h
+++ b/al/auxeffectslot.h
@@ -16,6 +16,15 @@
#include "intrusive_ptr.h"
#include "vector.h"
+#if ALSOFT_EAX
+#include <memory>
+
+#include "al/effect.h"
+
+#include "eax_eax_call.h"
+#include "eax_fx_slot_index.h"
+#endif // ALSOFT_EAX
+
struct ALbuffer;
struct ALeffect;
struct WetBuffer;
@@ -61,8 +70,220 @@ struct ALeffectslot {
/* This can be new'd for the context's default effect slot. */
DEF_NEWDEL(ALeffectslot)
+
+
+#if ALSOFT_EAX
+public:
+ void eax_initialize(
+ ALCcontext& al_context,
+ EaxFxSlotIndexValue index);
+
+ void eax_uninitialize() noexcept;
+
+
+ const EAX50FXSLOTPROPERTIES& eax_get_eax_fx_slot() const noexcept;
+
+
+ // [[nodiscard]]
+ bool eax_dispatch(
+ const EaxEaxCall& eax_call);
+
+
+private:
+ ALCcontext* eax_al_context_{};
+
+ EaxFxSlotIndexValue eax_fx_slot_index_{};
+
+ EAX50FXSLOTPROPERTIES eax_eax_fx_slot_{};
+
+ EaxAlEffectUPtr eax_al_effect_{};
+
+
+ [[noreturn]]
+ static void eax_fail(
+ const char* message);
+
+
+ void eax_set_eax_fx_slot_defaults();
+
+ void eax_initialize_eax();
+
+
+ void eax_initialize_effects();
+
+
+ void eax_set_default_slot_0_defaults();
+
+ void eax_set_default_slot_1_defaults();
+
+ void eax_set_default_slots_defaults();
+
+
+ void eax_get_fx_slot_all(
+ const EaxEaxCall& eax_call) const;
+
+ void eax_get_fx_slot(
+ const EaxEaxCall& eax_call) const;
+
+ // [[nodiscard]]
+ bool eax_get(
+ const EaxEaxCall& eax_call);
+
+
+ void eax_set_fx_slot_effect(
+ ALenum effect_type);
+
+ void eax_set_fx_slot_effect();
+
+
+ void eax_set_efx_effect_slot_gain();
+
+ void eax_set_fx_slot_volume();
+
+
+ void eax_set_effect_slot_send_auto();
+
+ void eax_set_fx_slot_flags();
+
+
+ void eax_validate_fx_slot_effect(
+ const GUID& eax_effect_id);
+
+ void eax_validate_fx_slot_volume(
+ long eax_volume);
+
+ void eax_validate_fx_slot_lock(
+ long eax_lock);
+
+ void eax_validate_fx_slot_lock_state(
+ long eax_lock,
+ const GUID& eax_effect_id);
+
+ void eax_validate_fx_slot_flags(
+ unsigned long eax_flags,
+ int eax_version);
+
+ void eax_validate_fx_slot_occlusion(
+ long eax_occlusion);
+
+ void eax_validate_fx_slot_occlusion_lf_ratio(
+ float eax_occlusion_lf_ratio);
+
+ void eax_validate_fx_slot_all(
+ const EAX40FXSLOTPROPERTIES& fx_slot,
+ int eax_version);
+
+ void eax_validate_fx_slot_all(
+ const EAX50FXSLOTPROPERTIES& fx_slot,
+ int eax_version);
+
+
+ void eax_set_fx_slot_effect(
+ const GUID& eax_effect_id);
+
+ void eax_set_fx_slot_volume(
+ long eax_volume);
+
+ void eax_set_fx_slot_lock(
+ long eax_lock);
+
+ void eax_set_fx_slot_flags(
+ unsigned long eax_flags);
+
+ // [[nodiscard]]
+ bool eax_set_fx_slot_occlusion(
+ long eax_occlusion);
+
+ // [[nodiscard]]
+ bool eax_set_fx_slot_occlusion_lf_ratio(
+ float eax_occlusion_lf_ratio);
+
+ void eax_set_fx_slot_all(
+ const EAX40FXSLOTPROPERTIES& eax_fx_slot);
+
+ // [[nodiscard]]
+ bool eax_set_fx_slot_all(
+ const EAX50FXSLOTPROPERTIES& eax_fx_slot);
+
+
+ void eax_set_fx_slot_effect(
+ const EaxEaxCall& eax_call);
+
+ void eax_set_fx_slot_volume(
+ const EaxEaxCall& eax_call);
+
+ void eax_set_fx_slot_lock(
+ const EaxEaxCall& eax_call);
+
+ void eax_set_fx_slot_flags(
+ const EaxEaxCall& eax_call);
+
+ // [[nodiscard]]
+ bool eax_set_fx_slot_occlusion(
+ const EaxEaxCall& eax_call);
+
+ // [[nodiscard]]
+ bool eax_set_fx_slot_occlusion_lf_ratio(
+ const EaxEaxCall& eax_call);
+
+ // [[nodiscard]]
+ bool eax_set_fx_slot_all(
+ const EaxEaxCall& eax_call);
+
+ bool eax_set_fx_slot(
+ const EaxEaxCall& eax_call);
+
+ // [[nodiscard]]
+ bool eax_set(
+ const EaxEaxCall& eax_call);
+
+
+ void eax_dispatch_effect(
+ const EaxEaxCall& eax_call);
+
+
+ // `alAuxiliaryEffectSloti(effect_slot, AL_EFFECTSLOT_EFFECT, effect)`
+ void eax_set_effect_slot_effect(
+ ALeffect& effect);
+
+ // `alAuxiliaryEffectSloti(effect_slot, AL_EFFECTSLOT_AUXILIARY_SEND_AUTO, value)`
+ void eax_set_effect_slot_send_auto(
+ bool is_send_auto);
+
+ // `alAuxiliaryEffectSlotf(effect_slot, AL_EFFECTSLOT_GAIN, gain)`
+ void eax_set_effect_slot_gain(
+ ALfloat gain);
+#endif // ALSOFT_EAX
};
void UpdateAllEffectSlotProps(ALCcontext *context);
+#if ALSOFT_EAX
+class EaxAlEffectSlotDeleter
+{
+public:
+ EaxAlEffectSlotDeleter() noexcept = default;
+
+ EaxAlEffectSlotDeleter(
+ ALCcontext& context) noexcept;
+
+ void operator()(
+ ALeffectslot* effect_slot);
+
+
+private:
+ ALCcontext* context_{};
+}; // EaxAlEffectSlotDeleter
+
+using EaxAlEffectSlotUPtr = std::unique_ptr<ALeffectslot, EaxAlEffectSlotDeleter>;
+
+
+EaxAlEffectSlotUPtr eax_create_al_effect_slot(
+ ALCcontext& context);
+
+void eax_delete_al_effect_slot(
+ ALCcontext& context,
+ ALeffectslot& effect_slot);
+#endif // ALSOFT_EAX
+
#endif
diff --git a/al/buffer.cpp b/al/buffer.cpp
index a473c3f9..a4967223 100644
--- a/al/buffer.cpp
+++ b/al/buffer.cpp
@@ -56,6 +56,11 @@
#include "core/voice.h"
#include "opthelpers.h"
+#if ALSOFT_EAX
+#include "eax_globals.h"
+#include "eax_x_ram.h"
+#endif // ALSOFT_EAX
+
namespace {
@@ -412,6 +417,15 @@ ALbuffer *AllocBuffer(ALCdevice *device)
void FreeBuffer(ALCdevice *device, ALbuffer *buffer)
{
+#if ALSOFT_EAX
+ if (buffer->eax_x_ram_is_hardware)
+ {
+ const auto buffer_size = static_cast<ALsizei>(buffer->OriginalSize);
+ assert((device->eax_x_ram_free_size + buffer_size) <= eax_x_ram_max_size);
+ device->eax_x_ram_free_size += buffer_size;
+ }
+#endif // ALSOFT_EAX
+
const ALuint id{buffer->id - 1};
const size_t lidx{id >> 6};
const ALuint slidx{id & 0x3f};
@@ -485,6 +499,102 @@ const ALchar *NameFromUserFmtType(UserFmtType type)
return "<internal type error>";
}
+#if ALSOFT_EAX
+bool eax_x_ram_validate_buffer(
+ ALCdevice& al_device,
+ ALbuffer& al_buffer)
+{
+ switch (al_buffer.eax_x_ram_mode)
+ {
+ case AL_STORAGE_HARDWARE:
+ if (al_buffer.OriginalSize > static_cast<ALuint>(al_device.eax_x_ram_free_size))
+ {
+ return false;
+ }
+
+ break;
+
+ case AL_STORAGE_AUTOMATIC:
+ case AL_STORAGE_ACCESSIBLE:
+ break;
+
+ default:
+ assert(false && "Unsupported X-RAM mode.");
+ return false;
+ }
+
+ return true;
+}
+
+void eax_x_ram_update_buffer(
+ ALCdevice& al_device,
+ ALbuffer& al_buffer)
+{
+ const auto buffer_size = static_cast<ALsizei>(al_buffer.OriginalSize);
+
+ auto is_hardware = al_buffer.eax_x_ram_is_hardware;
+ auto size_delta = ALsizei{};
+
+ switch (al_buffer.eax_x_ram_mode)
+ {
+ case AL_STORAGE_AUTOMATIC:
+ if (!al_buffer.eax_x_ram_is_dirty)
+ {
+ // First usage.
+
+ if (buffer_size <= al_device.eax_x_ram_free_size)
+ {
+ // Have enough X-RAM memory.
+
+ is_hardware = true;
+ size_delta = -buffer_size;
+ }
+ }
+ else
+ {
+ // Used at least once.
+ // From now on, use only system memory.
+
+ is_hardware = false;
+
+ if (al_buffer.eax_x_ram_is_hardware)
+ {
+ // First allocation was in X-RAM.
+ // Free that block.
+
+ size_delta = buffer_size;
+ }
+ }
+
+ break;
+
+ case AL_STORAGE_HARDWARE:
+ is_hardware = true;
+ size_delta = buffer_size;
+
+ break;
+
+ case AL_STORAGE_ACCESSIBLE:
+ // Always use system memory.
+ is_hardware = false;
+ break;
+
+ default:
+ break;
+ }
+
+ al_buffer.eax_x_ram_is_hardware = is_hardware;
+ al_buffer.eax_x_ram_is_dirty = true;
+
+ assert(
+ (al_device.eax_x_ram_free_size + size_delta) >= eax_x_ram_min_size &&
+ (al_device.eax_x_ram_free_size + size_delta) <= eax_x_ram_max_size
+ );
+
+ al_device.eax_x_ram_free_size += size_delta;
+}
+#endif // ALSOFT_EAX
+
/** Loads the specified data into the buffer, using the specified format. */
void LoadData(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, ALuint size,
UserFmtChannels SrcChannels, UserFmtType SrcType, const al::byte *SrcData,
@@ -620,6 +730,13 @@ void LoadData(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, ALuint size,
ALBuf->mSampleLen = frames;
ALBuf->mLoopStart = 0;
ALBuf->mLoopEnd = ALBuf->mSampleLen;
+
+#if ALSOFT_EAX
+ if (eax_g_is_enabled)
+ {
+ eax_x_ram_update_buffer(*context->mALDevice, *ALBuf);
+ }
+#endif // ALSOFT_EAX
}
/** Prepares the buffer to use the specified callback, using the specified format. */
@@ -889,8 +1006,24 @@ START_API_FUNC
if UNLIKELY(!usrfmt)
context->setError(AL_INVALID_ENUM, "Invalid format 0x%04x", format);
else
+#if ALSOFT_EAX
+ {
+ if (eax_g_is_enabled)
+ {
+ const auto is_buffer_valid = eax_x_ram_validate_buffer(*device, *albuf);
+
+ if (!is_buffer_valid)
+ {
+ context->setError(AL_OUT_OF_MEMORY, "Out of X-RAM memory.");
+ return;
+ }
+ }
+#endif // ALSOFT_EAX
LoadData(context.get(), albuf, freq, static_cast<ALuint>(size), usrfmt->channels,
usrfmt->type, static_cast<const al::byte*>(data), flags);
+#if ALSOFT_EAX
+ }
+#endif // ALSOFT_EAX
}
}
END_API_FUNC
@@ -1642,3 +1775,162 @@ BufferSubList::~BufferSubList()
al_free(Buffers);
Buffers = nullptr;
}
+
+
+#if ALSOFT_EAX
+ALboolean AL_APIENTRY EAXSetBufferMode(
+ ALsizei n,
+ const ALuint* buffers,
+ ALint value)
+START_API_FUNC
+{
+#define EAX_PREFIX "[EAXSetBufferMode] "
+
+ if (n == 0)
+ {
+ return ALC_TRUE;
+ }
+
+ if (n < 0)
+ {
+ ERR(EAX_PREFIX "Buffer count %d out of range.\n", n);
+ return ALC_FALSE;
+ }
+
+ if (!buffers)
+ {
+ ERR(EAX_PREFIX "%s\n", "Null AL buffers.");
+ return ALC_FALSE;
+ }
+
+ switch (value)
+ {
+ case AL_STORAGE_AUTOMATIC:
+ case AL_STORAGE_HARDWARE:
+ case AL_STORAGE_ACCESSIBLE:
+ break;
+
+ default:
+ ERR(EAX_PREFIX "Unsupported X-RAM mode %d.\n", value);
+ return ALC_FALSE;
+ }
+
+ const auto context = ContextRef{GetContextRef()};
+
+ if (!context)
+ {
+ ERR(EAX_PREFIX "%s\n", "No current context.");
+ return ALC_FALSE;
+ }
+
+ if (!eax_g_is_enabled)
+ {
+ ERR(EAX_PREFIX "%s\n", "EAX not enabled.");
+ return ALC_FALSE;
+ }
+
+ auto device = context->mALDevice.get();
+ std::lock_guard<std::mutex> device_lock{device->BufferLock};
+
+ // Validate the buffers.
+ //
+ for (auto i = 0; i < n; ++i)
+ {
+ const auto buffer = buffers[i];
+
+ if (buffer == AL_NONE)
+ {
+ continue;
+ }
+
+ const auto al_buffer = LookupBuffer(device, buffer);
+
+ if (!al_buffer)
+ {
+ ERR(EAX_PREFIX "Invalid buffer ID %u.\n", buffer);
+ return ALC_FALSE;
+ }
+
+ if (al_buffer->eax_x_ram_is_dirty)
+ {
+ ERR(EAX_PREFIX "Buffer %u has audio data.\n", buffer);
+ return ALC_FALSE;
+ }
+ }
+
+ // Update the mode.
+ //
+ for (auto i = 0; i < n; ++i)
+ {
+ const auto buffer = buffers[i];
+
+ if (buffer == AL_NONE)
+ {
+ continue;
+ }
+
+ const auto al_buffer = LookupBuffer(device, buffer);
+ assert(al_buffer);
+ assert(!al_buffer->eax_x_ram_is_dirty);
+
+ al_buffer->eax_x_ram_mode = value;
+ }
+
+ return AL_TRUE;
+
+#undef EAX_PREFIX
+}
+END_API_FUNC
+
+ALenum AL_APIENTRY EAXGetBufferMode(
+ ALuint buffer,
+ ALint* pReserved)
+START_API_FUNC
+{
+#define EAX_PREFIX "[EAXGetBufferMode] "
+
+ if (buffer == AL_NONE)
+ {
+ ERR(EAX_PREFIX "%s\n", "Null AL buffer.");
+ return AL_NONE;
+ }
+
+ if (pReserved)
+ {
+ ERR(EAX_PREFIX "%s\n", "Non-null reserved parameter.");
+ return AL_NONE;
+ }
+
+ const auto context = ContextRef{GetContextRef()};
+
+ if (!context)
+ {
+ ERR(EAX_PREFIX "%s\n", "No current context.");
+ return AL_NONE;
+ }
+
+ if (!eax_g_is_enabled)
+ {
+ ERR(EAX_PREFIX "%s\n", "EAX not enabled.");
+ return AL_NONE;
+ }
+
+ auto device = context->mALDevice.get();
+ std::lock_guard<std::mutex> device_lock{device->BufferLock};
+
+ const auto al_buffer = LookupBuffer(device, buffer);
+
+ if (!al_buffer)
+ {
+ ERR(EAX_PREFIX "Invalid buffer ID %u.\n", buffer);
+ return AL_NONE;
+ }
+
+ return al_buffer->eax_x_ram_mode;
+
+#undef EAX_PREFIX
+}
+END_API_FUNC
+
+
+#endif // ALSOFT_EAX
diff --git a/al/buffer.h b/al/buffer.h
index a78c65c6..0514d984 100644
--- a/al/buffer.h
+++ b/al/buffer.h
@@ -12,6 +12,9 @@
#include "core/buffer_storage.h"
#include "vector.h"
+#if ALSOFT_EAX
+#include "eax_x_ram.h"
+#endif // ALSOFT_EAX
/* User formats */
enum UserFmtType : unsigned char {
@@ -68,6 +71,12 @@ struct ALbuffer : public BufferStorage {
ALuint id{0};
DISABLE_ALLOC()
+
+#if ALSOFT_EAX
+ ALenum eax_x_ram_mode{AL_STORAGE_AUTOMATIC};
+ bool eax_x_ram_is_hardware{};
+ bool eax_x_ram_is_dirty{};
+#endif // ALSOFT_EAX
};
#endif
diff --git a/al/eax_api.cpp b/al/eax_api.cpp
new file mode 100644
index 00000000..9907dd4d
--- /dev/null
+++ b/al/eax_api.cpp
@@ -0,0 +1,1151 @@
+//
+// EAX API.
+//
+// Based on headers `eax[2-5].h` included in Doom 3 source code:
+// https://github.com/id-Software/DOOM-3/tree/master/neo/openal/include
+//
+
+
+#include <algorithm>
+
+#include "al/eax_api.h"
+
+
+const GUID DSPROPSETID_EAX20_ListenerProperties =
+{
+ 0x306A6A8,
+ 0xB224,
+ 0x11D2,
+ {0x99, 0xE5, 0x00, 0x00, 0xE8, 0xD8, 0xC7, 0x22}
+};
+
+const GUID DSPROPSETID_EAX20_BufferProperties =
+{
+ 0x306A6A7,
+ 0xB224,
+ 0x11D2,
+ {0x99, 0xE5, 0x00, 0x00, 0xE8, 0xD8, 0xC7, 0x22}
+};
+
+const GUID DSPROPSETID_EAX30_ListenerProperties =
+{
+ 0xA8FA6882,
+ 0xB476,
+ 0x11D3,
+ {0xBD, 0xB9, 0x00, 0xC0, 0xF0, 0x2D, 0xDF, 0x87}
+};
+
+const GUID DSPROPSETID_EAX30_BufferProperties =
+{
+ 0xA8FA6881,
+ 0xB476,
+ 0x11D3,
+ {0xBD, 0xB9, 0x00, 0xC0, 0xF0, 0x2D, 0xDF, 0x87}
+};
+
+const GUID EAX_NULL_GUID =
+{
+ 0x00000000,
+ 0x0000,
+ 0x0000,
+ {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
+};
+
+const GUID EAX_PrimaryFXSlotID =
+{
+ 0xF317866D,
+ 0x924C,
+ 0x450C,
+ {0x86, 0x1B, 0xE6, 0xDA, 0xA2, 0x5E, 0x7C, 0x20}
+};
+
+const GUID EAXPROPERTYID_EAX40_Context =
+{
+ 0x1D4870AD,
+ 0xDEF,
+ 0x43C0,
+ {0xA4, 0xC, 0x52, 0x36, 0x32, 0x29, 0x63, 0x42}
+};
+
+const GUID EAXPROPERTYID_EAX50_Context =
+{
+ 0x57E13437,
+ 0xB932,
+ 0x4AB2,
+ {0xB8, 0xBD, 0x52, 0x66, 0xC1, 0xA8, 0x87, 0xEE}
+};
+
+const GUID EAXPROPERTYID_EAX40_FXSlot0 =
+{
+ 0xC4D79F1E,
+ 0xF1AC,
+ 0x436B,
+ {0xA8, 0x1D, 0xA7, 0x38, 0xE7, 0x04, 0x54, 0x69}
+};
+
+const GUID EAXPROPERTYID_EAX50_FXSlot0 =
+{
+ 0x91F9590F,
+ 0xC388,
+ 0x407A,
+ {0x84, 0xB0, 0x1B, 0xAE, 0xE, 0xF7, 0x1A, 0xBC}
+};
+
+const GUID EAXPROPERTYID_EAX40_FXSlot1 =
+{
+ 0x8C00E96,
+ 0x74BE,
+ 0x4491,
+ {0x93, 0xAA, 0xE8, 0xAD, 0x35, 0xA4, 0x91, 0x17}
+};
+
+const GUID EAXPROPERTYID_EAX50_FXSlot1 =
+{
+ 0x8F5F7ACA,
+ 0x9608,
+ 0x4965,
+ {0x81, 0x37, 0x82, 0x13, 0xC7, 0xB9, 0xD9, 0xDE}
+};
+
+const GUID EAXPROPERTYID_EAX40_FXSlot2 =
+{
+ 0x1D433B88,
+ 0xF0F6,
+ 0x4637,
+ {0x91, 0x9F, 0x60, 0xE7, 0xE0, 0x6B, 0x5E, 0xDD}
+};
+
+const GUID EAXPROPERTYID_EAX50_FXSlot2 =
+{
+ 0x3C0F5252,
+ 0x9834,
+ 0x46F0,
+ {0xA1, 0xD8, 0x5B, 0x95, 0xC4, 0xA0, 0xA, 0x30}
+};
+
+const GUID EAXPROPERTYID_EAX40_FXSlot3 =
+{
+ 0xEFFF08EA,
+ 0xC7D8,
+ 0x44AB,
+ {0x93, 0xAD, 0x6D, 0xBD, 0x5F, 0x91, 0x00, 0x64}
+};
+
+const GUID EAXPROPERTYID_EAX50_FXSlot3 =
+{
+ 0xE2EB0EAA,
+ 0xE806,
+ 0x45E7,
+ {0x9F, 0x86, 0x06, 0xC1, 0x57, 0x1A, 0x6F, 0xA3}
+};
+
+const GUID EAXPROPERTYID_EAX40_Source =
+{
+ 0x1B86B823,
+ 0x22DF,
+ 0x4EAE,
+ {0x8B, 0x3C, 0x12, 0x78, 0xCE, 0x54, 0x42, 0x27}
+};
+
+const GUID EAXPROPERTYID_EAX50_Source =
+{
+ 0x5EDF82F0,
+ 0x24A7,
+ 0x4F38,
+ {0x8E, 0x64, 0x2F, 0x09, 0xCA, 0x05, 0xDE, 0xE1}
+};
+
+const GUID EAX_REVERB_EFFECT =
+{
+ 0xCF95C8F,
+ 0xA3CC,
+ 0x4849,
+ {0xB0, 0xB6, 0x83, 0x2E, 0xCC, 0x18, 0x22, 0xDF}
+};
+
+const GUID EAX_AGCCOMPRESSOR_EFFECT =
+{
+ 0xBFB7A01E,
+ 0x7825,
+ 0x4039,
+ {0x92, 0x7F, 0x03, 0xAA, 0xBD, 0xA0, 0xC5, 0x60}
+};
+
+const GUID EAX_AUTOWAH_EFFECT =
+{
+ 0xEC3130C0,
+ 0xAC7A,
+ 0x11D2,
+ {0x88, 0xDD, 0x00, 0xA0, 0x24, 0xD1, 0x3C, 0xE1}
+};
+
+const GUID EAX_CHORUS_EFFECT =
+{
+ 0xDE6D6FE0,
+ 0xAC79,
+ 0x11D2,
+ {0x88, 0xDD, 0x00, 0xA0, 0x24, 0xD1, 0x3C, 0xE1}
+};
+
+const GUID EAX_DISTORTION_EFFECT =
+{
+ 0x975A4CE0,
+ 0xAC7E,
+ 0x11D2,
+ {0x88, 0xDD, 0x00, 0xA0, 0x24, 0xD1, 0x3C, 0xE1}
+};
+
+const GUID EAX_ECHO_EFFECT =
+{
+ 0xE9F1BC0,
+ 0xAC82,
+ 0x11D2,
+ {0x88, 0xDD, 0x00, 0xA0, 0x24, 0xD1, 0x3C, 0xE1}
+};
+
+const GUID EAX_EQUALIZER_EFFECT =
+{
+ 0x65F94CE0,
+ 0x9793,
+ 0x11D3,
+ {0x93, 0x9D, 0x00, 0xC0, 0xF0, 0x2D, 0xD6, 0xF0}
+};
+
+const GUID EAX_FLANGER_EFFECT =
+{
+ 0xA70007C0,
+ 0x7D2,
+ 0x11D3,
+ {0x9B, 0x1E, 0x00, 0xA0, 0x24, 0xD1, 0x3C, 0xE1}
+};
+
+const GUID EAX_FREQUENCYSHIFTER_EFFECT =
+{
+ 0xDC3E1880,
+ 0x9212,
+ 0x11D3,
+ {0x93, 0x9D, 0x00, 0xC0, 0xF0, 0x2D, 0xD6, 0xF0}
+};
+
+const GUID EAX_VOCALMORPHER_EFFECT =
+{
+ 0xE41CF10C,
+ 0x3383,
+ 0x11D2,
+ {0x88, 0xDD, 0x00, 0xA0, 0x24, 0xD1, 0x3C, 0xE1}
+};
+
+const GUID EAX_PITCHSHIFTER_EFFECT =
+{
+ 0xE7905100,
+ 0xAFB2,
+ 0x11D2,
+ {0x88, 0xDD, 0x00, 0xA0, 0x24, 0xD1, 0x3C, 0xE1}
+};
+
+const GUID EAX_RINGMODULATOR_EFFECT =
+{
+ 0xB89FE60,
+ 0xAFB5,
+ 0x11D2,
+ {0x88, 0xDD, 0x00, 0xA0, 0x24, 0xD1, 0x3C, 0xE1}
+};
+
+
+bool operator==(
+ const EAXVECTOR& lhs,
+ const EAXVECTOR& rhs) noexcept
+{
+ return lhs.x == rhs.x && lhs.y == rhs.y && lhs.z == rhs.z;
+}
+
+bool operator!=(
+ const EAXVECTOR& lhs,
+ const EAXVECTOR& rhs) noexcept
+{
+ return !(lhs == rhs);
+}
+
+
+bool operator==(
+ const EAX40CONTEXTPROPERTIES& lhs,
+ const EAX40CONTEXTPROPERTIES& rhs) noexcept
+{
+ return
+ lhs.guidPrimaryFXSlotID == rhs.guidPrimaryFXSlotID &&
+ lhs.flDistanceFactor == rhs.flDistanceFactor &&
+ lhs.flAirAbsorptionHF == rhs.flAirAbsorptionHF &&
+ lhs.flHFReference == rhs.flHFReference;
+}
+
+bool operator==(
+ const EAX50CONTEXTPROPERTIES& lhs,
+ const EAX50CONTEXTPROPERTIES& rhs) noexcept
+{
+ return
+ static_cast<const EAX40CONTEXTPROPERTIES&>(lhs) == static_cast<const EAX40CONTEXTPROPERTIES&>(rhs) &&
+ lhs.flMacroFXFactor == rhs.flMacroFXFactor;
+}
+
+
+const GUID EAXCONTEXT_DEFAULTPRIMARYFXSLOTID = EAXPROPERTYID_EAX40_FXSlot0;
+
+bool operator==(
+ const EAX40FXSLOTPROPERTIES& lhs,
+ const EAX40FXSLOTPROPERTIES& rhs) noexcept
+{
+ return
+ lhs.guidLoadEffect == rhs.guidLoadEffect &&
+ lhs.lVolume == rhs.lVolume &&
+ lhs.lLock == rhs.lLock &&
+ lhs.ulFlags == rhs.ulFlags;
+}
+
+bool operator==(
+ const EAX50FXSLOTPROPERTIES& lhs,
+ const EAX50FXSLOTPROPERTIES& rhs) noexcept
+{
+ return
+ static_cast<const EAX40FXSLOTPROPERTIES&>(lhs) == static_cast<const EAX40FXSLOTPROPERTIES&>(rhs) &&
+ lhs.lOcclusion == rhs.lOcclusion &&
+ lhs.flOcclusionLFRatio == rhs.flOcclusionLFRatio;
+}
+
+const EAX50ACTIVEFXSLOTS EAX40SOURCE_DEFAULTACTIVEFXSLOTID = EAX50ACTIVEFXSLOTS
+{{
+ EAX_NULL_GUID,
+ EAXPROPERTYID_EAX40_FXSlot0,
+}};
+
+bool operator==(
+ const EAX50ACTIVEFXSLOTS& lhs,
+ const EAX50ACTIVEFXSLOTS& rhs) noexcept
+{
+ return std::equal(
+ std::cbegin(lhs.guidActiveFXSlots),
+ std::cend(lhs.guidActiveFXSlots),
+ std::begin(rhs.guidActiveFXSlots));
+}
+
+bool operator!=(
+ const EAX50ACTIVEFXSLOTS& lhs,
+ const EAX50ACTIVEFXSLOTS& rhs) noexcept
+{
+ return !(lhs == rhs);
+}
+
+
+const EAX50ACTIVEFXSLOTS EAX50SOURCE_3DDEFAULTACTIVEFXSLOTID = EAX50ACTIVEFXSLOTS
+{{
+ EAX_NULL_GUID,
+ EAX_PrimaryFXSlotID,
+ EAX_NULL_GUID,
+ EAX_NULL_GUID,
+}};
+
+
+const EAX50ACTIVEFXSLOTS EAX50SOURCE_2DDEFAULTACTIVEFXSLOTID = EAX50ACTIVEFXSLOTS
+{{
+ EAX_NULL_GUID,
+ EAX_NULL_GUID,
+ EAX_NULL_GUID,
+ EAX_NULL_GUID,
+}};
+
+bool operator==(
+ const EAXREVERBPROPERTIES& lhs,
+ const EAXREVERBPROPERTIES& rhs) noexcept
+{
+ return
+ lhs.ulEnvironment == rhs.ulEnvironment &&
+ lhs.flEnvironmentSize == rhs.flEnvironmentSize &&
+ lhs.flEnvironmentDiffusion == rhs.flEnvironmentDiffusion &&
+ lhs.lRoom == rhs.lRoom &&
+ lhs.lRoomHF == rhs.lRoomHF &&
+ lhs.lRoomLF == rhs.lRoomLF &&
+ lhs.flDecayTime == rhs.flDecayTime &&
+ lhs.flDecayHFRatio == rhs.flDecayHFRatio &&
+ lhs.flDecayLFRatio == rhs.flDecayLFRatio &&
+ lhs.lReflections == rhs.lReflections &&
+ lhs.flReflectionsDelay == rhs.flReflectionsDelay &&
+ lhs.vReflectionsPan == rhs.vReflectionsPan &&
+ lhs.lReverb == rhs.lReverb &&
+ lhs.flReverbDelay == rhs.flReverbDelay &&
+ lhs.vReverbPan == rhs.vReverbPan &&
+ lhs.flEchoTime == rhs.flEchoTime &&
+ lhs.flEchoDepth == rhs.flEchoDepth &&
+ lhs.flModulationTime == rhs.flModulationTime &&
+ lhs.flModulationDepth == rhs.flModulationDepth &&
+ lhs.flAirAbsorptionHF == rhs.flAirAbsorptionHF &&
+ lhs.flHFReference == rhs.flHFReference &&
+ lhs.flLFReference == rhs.flLFReference &&
+ lhs.flRoomRolloffFactor == rhs.flRoomRolloffFactor &&
+ lhs.ulFlags == rhs.ulFlags;
+}
+
+bool operator!=(
+ const EAXREVERBPROPERTIES& lhs,
+ const EAXREVERBPROPERTIES& rhs) noexcept
+{
+ return !(lhs == rhs);
+}
+
+
+const EAXREVERBPROPERTIES EAXREVERB_PRESET_GENERIC =
+{
+ EAXREVERB_DEFAULTENVIRONMENT,
+ EAXREVERB_DEFAULTENVIRONMENTSIZE,
+ EAXREVERB_DEFAULTENVIRONMENTDIFFUSION,
+ EAXREVERB_DEFAULTROOM,
+ EAXREVERB_DEFAULTROOMHF,
+ EAXREVERB_DEFAULTROOMLF,
+ EAXREVERB_DEFAULTDECAYTIME,
+ EAXREVERB_DEFAULTDECAYHFRATIO,
+ EAXREVERB_DEFAULTDECAYLFRATIO,
+ EAXREVERB_DEFAULTREFLECTIONS,
+ EAXREVERB_DEFAULTREFLECTIONSDELAY,
+ EAXREVERB_DEFAULTREFLECTIONSPAN,
+ EAXREVERB_DEFAULTREVERB,
+ EAXREVERB_DEFAULTREVERBDELAY,
+ EAXREVERB_DEFAULTREVERBPAN,
+ EAXREVERB_DEFAULTECHOTIME,
+ EAXREVERB_DEFAULTECHODEPTH,
+ EAXREVERB_DEFAULTMODULATIONTIME,
+ EAXREVERB_DEFAULTMODULATIONDEPTH,
+ EAXREVERB_DEFAULTAIRABSORPTIONHF,
+ EAXREVERB_DEFAULTHFREFERENCE,
+ EAXREVERB_DEFAULTLFREFERENCE,
+ EAXREVERB_DEFAULTROOMROLLOFFFACTOR,
+ EAXREVERB_DEFAULTFLAGS,
+};
+
+const EAXREVERBPROPERTIES EAXREVERB_PRESET_PADDEDCELL =
+{
+ EAX_ENVIRONMENT_PADDEDCELL,
+ 1.4F,
+ 1.0F,
+ -1'000L,
+ -6'000L,
+ 0L,
+ 0.17F,
+ 0.10F,
+ 1.0F,
+ -1'204L,
+ 0.001F,
+ EAXVECTOR{},
+ 207L,
+ 0.002F,
+ EAXVECTOR{},
+ 0.250F,
+ 0.0F,
+ 0.250F,
+ 0.0F,
+ -5.0F,
+ 5'000.0F,
+ 250.0F,
+ 0.0F,
+ 0x3FUL,
+};
+
+const EAXREVERBPROPERTIES EAXREVERB_PRESET_ROOM =
+{
+ EAX_ENVIRONMENT_ROOM,
+ 1.9F,
+ 1.0F,
+ -1'000L,
+ -454L,
+ 0L,
+ 0.40F,
+ 0.83F,
+ 1.0F,
+ -1'646L,
+ 0.002F,
+ EAXVECTOR{},
+ 53L,
+ 0.003F,
+ EAXVECTOR{},
+ 0.250F,
+ 0.0F,
+ 0.250F,
+ 0.0F,
+ -5.0F,
+ 5'000.0F,
+ 250.0F,
+ 0.0F,
+ 0x3FUL,
+};
+
+const EAXREVERBPROPERTIES EAXREVERB_PRESET_BATHROOM =
+{
+ EAX_ENVIRONMENT_BATHROOM,
+ 1.4F,
+ 1.0F,
+ -1'000L,
+ -1'200L,
+ 0L,
+ 1.49F,
+ 0.54F,
+ 1.0F,
+ -370L,
+ 0.007F,
+ EAXVECTOR{},
+ 1'030L,
+ 0.011F,
+ EAXVECTOR{},
+ 0.250F,
+ 0.0F,
+ 0.250F,
+ 0.0F,
+ -5.0F,
+ 5'000.0F,
+ 250.0F,
+ 0.0F,
+ 0x3FUL,
+};
+
+const EAXREVERBPROPERTIES EAXREVERB_PRESET_LIVINGROOM =
+{
+ EAX_ENVIRONMENT_LIVINGROOM,
+ 2.5F,
+ 1.0F,
+ -1'000L,
+ -6'000L,
+ 0L,
+ 0.50F,
+ 0.10F,
+ 1.0F,
+ -1'376,
+ 0.003F,
+ EAXVECTOR{},
+ -1'104L,
+ 0.004F,
+ EAXVECTOR{},
+ 0.250F,
+ 0.0F,
+ 0.250F,
+ 0.0F,
+ -5.0F,
+ 5'000.0F,
+ 250.0F,
+ 0.0F,
+ 0x3FUL,
+};
+
+const EAXREVERBPROPERTIES EAXREVERB_PRESET_STONEROOM =
+{
+ EAX_ENVIRONMENT_STONEROOM,
+ 11.6F,
+ 1.0F,
+ -1'000L,
+ -300L,
+ 0L,
+ 2.31F,
+ 0.64F,
+ 1.0F,
+ -711L,
+ 0.012F,
+ EAXVECTOR{},
+ 83L,
+ 0.017F,
+ EAXVECTOR{},
+ 0.250F,
+ 0.0F,
+ 0.250F,
+ 0.0F,
+ -5.0F,
+ 5'000.0F,
+ 250.0F,
+ 0.0F,
+ 0x3FUL,
+};
+
+const EAXREVERBPROPERTIES EAXREVERB_PRESET_AUDITORIUM =
+{
+ EAX_ENVIRONMENT_AUDITORIUM,
+ 21.6F,
+ 1.0F,
+ -1'000L,
+ -476L,
+ 0L,
+ 4.32F,
+ 0.59F,
+ 1.0F,
+ -789L,
+ 0.020F,
+ EAXVECTOR{},
+ -289L,
+ 0.030F,
+ EAXVECTOR{},
+ 0.250F,
+ 0.0F,
+ 0.250F,
+ 0.0F,
+ -5.0F,
+ 5'000.0F,
+ 250.0F,
+ 0.0F,
+ 0x3FUL,
+};
+
+const EAXREVERBPROPERTIES EAXREVERB_PRESET_CONCERTHALL =
+{
+ EAX_ENVIRONMENT_CONCERTHALL,
+ 19.6F,
+ 1.0F,
+ -1'000L,
+ -500L,
+ 0L,
+ 3.92F,
+ 0.70F,
+ 1.0F,
+ -1'230L,
+ 0.020F,
+ EAXVECTOR{},
+ -2L,
+ 0.029F,
+ EAXVECTOR{},
+ 0.250F,
+ 0.0F,
+ 0.250F,
+ 0.0F,
+ -5.0F,
+ 5'000.0F,
+ 250.0F,
+ 0.0F,
+ 0x3FUL,
+};
+
+const EAXREVERBPROPERTIES EAXREVERB_PRESET_CAVE =
+{
+ EAX_ENVIRONMENT_CAVE,
+ 14.6F,
+ 1.0F,
+ -1'000L,
+ 0L,
+ 0L,
+ 2.91F,
+ 1.30F,
+ 1.0F,
+ -602L,
+ 0.015F,
+ EAXVECTOR{},
+ -302L,
+ 0.022F,
+ EAXVECTOR{},
+ 0.250F,
+ 0.0F,
+ 0.250F,
+ 0.0F,
+ -5.0F,
+ 5'000.0F,
+ 250.0F,
+ 0.0F,
+ 0x1FUL,
+};
+
+const EAXREVERBPROPERTIES EAXREVERB_PRESET_ARENA =
+{
+ EAX_ENVIRONMENT_ARENA,
+ 36.2F,
+ 1.0F,
+ -1'000L,
+ -698L,
+ 0L,
+ 7.24F,
+ 0.33F,
+ 1.0F,
+ -1'166L,
+ 0.020F,
+ EAXVECTOR{},
+ 16L,
+ 0.030F,
+ EAXVECTOR{},
+ 0.250F,
+ 0.0F,
+ 0.250F,
+ 0.0F,
+ -5.0F,
+ 5'000.0F,
+ 250.0F,
+ 0.0F,
+ 0x3FUL,
+};
+
+const EAXREVERBPROPERTIES EAXREVERB_PRESET_HANGAR =
+{
+ EAX_ENVIRONMENT_HANGAR,
+ 50.3F,
+ 1.0F,
+ -1'000L,
+ -1'000L,
+ 0L,
+ 10.05F,
+ 0.23F,
+ 1.0F,
+ -602L,
+ 0.020F,
+ EAXVECTOR{},
+ 198L,
+ 0.030F,
+ EAXVECTOR{},
+ 0.250F,
+ 0.0F,
+ 0.250F,
+ 0.0F,
+ -5.0F,
+ 5'000.0F,
+ 250.0F,
+ 0.0F,
+ 0x3FUL,
+};
+
+const EAXREVERBPROPERTIES EAXREVERB_PRESET_CARPETTEDHALLWAY =
+{
+ EAX_ENVIRONMENT_CARPETEDHALLWAY,
+ 1.9F,
+ 1.0F,
+ -1'000L,
+ -4'000L,
+ 0L,
+ 0.30F,
+ 0.10F,
+ 1.0F,
+ -1'831L,
+ 0.002F,
+ EAXVECTOR{},
+ -1'630L,
+ 0.030F,
+ EAXVECTOR{},
+ 0.250F,
+ 0.0F,
+ 0.250F,
+ 0.0F,
+ -5.0F,
+ 5'000.0F,
+ 250.0F,
+ 0.0F,
+ 0x3FUL,
+};
+
+const EAXREVERBPROPERTIES EAXREVERB_PRESET_HALLWAY =
+{
+ EAX_ENVIRONMENT_HALLWAY,
+ 1.8F,
+ 1.0F,
+ -1'000L,
+ -300L,
+ 0L,
+ 1.49F,
+ 0.59F,
+ 1.0F,
+ -1'219L,
+ 0.007F,
+ EAXVECTOR{},
+ 441L,
+ 0.011F,
+ EAXVECTOR{},
+ 0.250F,
+ 0.0F,
+ 0.250F,
+ 0.0F,
+ -5.0F,
+ 5'000.0F,
+ 250.0F,
+ 0.0F,
+ 0x3FUL,
+};
+
+const EAXREVERBPROPERTIES EAXREVERB_PRESET_STONECORRIDOR =
+{
+ EAX_ENVIRONMENT_STONECORRIDOR,
+ 13.5F,
+ 1.0F,
+ -1'000L,
+ -237L,
+ 0L,
+ 2.70F,
+ 0.79F,
+ 1.0F,
+ -1'214L,
+ 0.013F,
+ EAXVECTOR{},
+ 395L,
+ 0.020F,
+ EAXVECTOR{},
+ 0.250F,
+ 0.0F,
+ 0.250F,
+ 0.0F,
+ -5.0F,
+ 5'000.0F,
+ 250.0F,
+ 0.0F,
+ 0x3FUL,
+};
+
+const EAXREVERBPROPERTIES EAXREVERB_PRESET_ALLEY =
+{
+ EAX_ENVIRONMENT_ALLEY,
+ 7.5F,
+ 0.300F,
+ -1'000L,
+ -270L,
+ 0L,
+ 1.49F,
+ 0.86F,
+ 1.0F,
+ -1'204L,
+ 0.007F,
+ EAXVECTOR{},
+ -4L,
+ 0.011F,
+ EAXVECTOR{},
+ 0.125F,
+ 0.950F,
+ 0.250F,
+ 0.0F,
+ -5.0F,
+ 5'000.0F,
+ 250.0F,
+ 0.0F,
+ 0x3FUL,
+};
+
+const EAXREVERBPROPERTIES EAXREVERB_PRESET_FOREST =
+{
+ EAX_ENVIRONMENT_FOREST,
+ 38.0F,
+ 0.300F,
+ -1'000L,
+ -3'300L,
+ 0L,
+ 1.49F,
+ 0.54F,
+ 1.0F,
+ -2'560L,
+ 0.162F,
+ EAXVECTOR{},
+ -229L,
+ 0.088F,
+ EAXVECTOR{},
+ 0.125F,
+ 1.0F,
+ 0.250F,
+ 0.0F,
+ -5.0F,
+ 5'000.0F,
+ 250.0F,
+ 0.0F,
+ 0x3FUL,
+};
+
+const EAXREVERBPROPERTIES EAXREVERB_PRESET_CITY =
+{
+ EAX_ENVIRONMENT_CITY,
+ 7.5F,
+ 0.500F,
+ -1'000L,
+ -800L,
+ 0L,
+ 1.49F,
+ 0.67F,
+ 1.0F,
+ -2'273L,
+ 0.007F,
+ EAXVECTOR{},
+ -1'691L,
+ 0.011F,
+ EAXVECTOR{},
+ 0.250F,
+ 0.0F,
+ 0.250F,
+ 0.0F,
+ -5.0F,
+ 5'000.0F,
+ 250.0F,
+ 0.0F,
+ 0x3FUL,
+};
+
+const EAXREVERBPROPERTIES EAXREVERB_PRESET_MOUNTAINS =
+{
+ EAX_ENVIRONMENT_MOUNTAINS,
+ 100.0F,
+ 0.270F,
+ -1'000L,
+ -2'500L,
+ 0L,
+ 1.49F,
+ 0.21F,
+ 1.0F,
+ -2'780L,
+ 0.300F,
+ EAXVECTOR{},
+ -1'434L,
+ 0.100F,
+ EAXVECTOR{},
+ 0.250F,
+ 1.0F,
+ 0.250F,
+ 0.0F,
+ -5.0F,
+ 5'000.0F,
+ 250.0F,
+ 0.0F,
+ 0x1FUL,
+};
+
+const EAXREVERBPROPERTIES EAXREVERB_PRESET_QUARRY =
+{
+ EAX_ENVIRONMENT_QUARRY,
+ 17.5F,
+ 1.0F,
+ -1'000L,
+ -1'000L,
+ 0L,
+ 1.49F,
+ 0.83F,
+ 1.0F,
+ -10'000L,
+ 0.061F,
+ EAXVECTOR{},
+ 500L,
+ 0.025F,
+ EAXVECTOR{},
+ 0.125F,
+ 0.700F,
+ 0.250F,
+ 0.0F,
+ -5.0F,
+ 5'000.0F,
+ 250.0F,
+ 0.0F,
+ 0x3FUL,
+};
+
+const EAXREVERBPROPERTIES EAXREVERB_PRESET_PLAIN =
+{
+ EAX_ENVIRONMENT_PLAIN,
+ 42.5F,
+ 0.210F,
+ -1'000L,
+ -2'000L,
+ 0L,
+ 1.49F,
+ 0.50F,
+ 1.0F,
+ -2'466L,
+ 0.179F,
+ EAXVECTOR{},
+ -1'926L,
+ 0.100F,
+ EAXVECTOR{},
+ 0.250F,
+ 1.0F,
+ 0.250F,
+ 0.0F,
+ -5.0F,
+ 5'000.0F,
+ 250.0F,
+ 0.0F,
+ 0x3FUL,
+};
+
+const EAXREVERBPROPERTIES EAXREVERB_PRESET_PARKINGLOT =
+{
+ EAX_ENVIRONMENT_PARKINGLOT,
+ 8.3F,
+ 1.0F,
+ -1'000L,
+ 0L,
+ 0L,
+ 1.65F,
+ 1.50F,
+ 1.0F,
+ -1'363L,
+ 0.008F,
+ EAXVECTOR{},
+ -1'153L,
+ 0.012F,
+ EAXVECTOR{},
+ 0.250F,
+ 0.0F,
+ 0.250F,
+ 0.0F,
+ -5.0F,
+ 5'000.0F,
+ 250.0F,
+ 0.0F,
+ 0x1FUL,
+};
+
+const EAXREVERBPROPERTIES EAXREVERB_PRESET_SEWERPIPE =
+{
+ EAX_ENVIRONMENT_SEWERPIPE,
+ 1.7F,
+ 0.800F,
+ -1'000L,
+ -1'000L,
+ 0L,
+ 2.81F,
+ 0.14F,
+ 1.0F,
+ 429L,
+ 0.014F,
+ EAXVECTOR{},
+ 1'023L,
+ 0.021F,
+ EAXVECTOR{},
+ 0.250F,
+ 0.0F,
+ 0.250F,
+ 0.0F,
+ -5.0F,
+ 5'000.0F,
+ 250.0F,
+ 0.0F,
+ 0x3FUL,
+};
+
+const EAXREVERBPROPERTIES EAXREVERB_PRESET_UNDERWATER =
+{
+ EAX_ENVIRONMENT_UNDERWATER,
+ 1.8F,
+ 1.0F,
+ -1'000L,
+ -4'000L,
+ 0L,
+ 1.49F,
+ 0.10F,
+ 1.0F,
+ -449L,
+ 0.007F,
+ EAXVECTOR{},
+ 1'700L,
+ 0.011F,
+ EAXVECTOR{},
+ 0.250F,
+ 0.0F,
+ 1.180F,
+ 0.348F,
+ -5.0F,
+ 5'000.0F,
+ 250.0F,
+ 0.0F,
+ 0x3FUL,
+};
+
+const EAXREVERBPROPERTIES EAXREVERB_PRESET_DRUGGED =
+{
+ EAX_ENVIRONMENT_DRUGGED,
+ 1.9F,
+ 0.500F,
+ -1'000L,
+ 0L,
+ 0L,
+ 8.39F,
+ 1.39F,
+ 1.0F,
+ -115L,
+ 0.002F,
+ EAXVECTOR{},
+ 985L,
+ 0.030F,
+ EAXVECTOR{},
+ 0.250F,
+ 0.0F,
+ 0.250F,
+ 1.0F,
+ -5.0F,
+ 5'000.0F,
+ 250.0F,
+ 0.0F,
+ 0x1FUL,
+};
+
+const EAXREVERBPROPERTIES EAXREVERB_PRESET_DIZZY =
+{
+ EAX_ENVIRONMENT_DIZZY,
+ 1.8F,
+ 0.600F,
+ -1'000L,
+ -400L,
+ 0L,
+ 17.23F,
+ 0.56F,
+ 1.0F,
+ -1'713L,
+ 0.020F,
+ EAXVECTOR{},
+ -613L,
+ 0.030F,
+ EAXVECTOR{},
+ 0.250F,
+ 1.0F,
+ 0.810F,
+ 0.310F,
+ -5.0F,
+ 5'000.0F,
+ 250.0F,
+ 0.0F,
+ 0x1FUL,
+};
+
+const EAXREVERBPROPERTIES EAXREVERB_PRESET_PSYCHOTIC =
+{
+ EAX_ENVIRONMENT_PSYCHOTIC,
+ 1.0F,
+ 0.500F,
+ -1'000L,
+ -151L,
+ 0L,
+ 7.56F,
+ 0.91F,
+ 1.0F,
+ -626L,
+ 0.020F,
+ EAXVECTOR{},
+ 774L,
+ 0.030F,
+ EAXVECTOR{},
+ 0.250F,
+ 0.0F,
+ 4.0F,
+ 1.0F,
+ -5.0F,
+ 5'000.0F,
+ 250.0F,
+ 0.0F,
+ 0x1FUL,
+};
+
+
+const EaxReverbPresets EAXREVERB_PRESETS =
+{
+ EAXREVERB_PRESET_GENERIC,
+ EAXREVERB_PRESET_PADDEDCELL,
+ EAXREVERB_PRESET_ROOM,
+ EAXREVERB_PRESET_BATHROOM,
+ EAXREVERB_PRESET_LIVINGROOM,
+ EAXREVERB_PRESET_STONEROOM,
+ EAXREVERB_PRESET_AUDITORIUM,
+ EAXREVERB_PRESET_CONCERTHALL,
+ EAXREVERB_PRESET_CAVE,
+ EAXREVERB_PRESET_ARENA,
+ EAXREVERB_PRESET_HANGAR,
+ EAXREVERB_PRESET_CARPETTEDHALLWAY,
+ EAXREVERB_PRESET_HALLWAY,
+ EAXREVERB_PRESET_STONECORRIDOR,
+ EAXREVERB_PRESET_ALLEY,
+ EAXREVERB_PRESET_FOREST,
+ EAXREVERB_PRESET_CITY,
+ EAXREVERB_PRESET_MOUNTAINS,
+ EAXREVERB_PRESET_QUARRY,
+ EAXREVERB_PRESET_PLAIN,
+ EAXREVERB_PRESET_PARKINGLOT,
+ EAXREVERB_PRESET_SEWERPIPE,
+ EAXREVERB_PRESET_UNDERWATER,
+ EAXREVERB_PRESET_DRUGGED,
+ EAXREVERB_PRESET_DIZZY,
+ EAXREVERB_PRESET_PSYCHOTIC,
+};
diff --git a/al/eax_api.h b/al/eax_api.h
new file mode 100644
index 00000000..98eb1fc5
--- /dev/null
+++ b/al/eax_api.h
@@ -0,0 +1,1542 @@
+#ifndef EAX_API_INCLUDED
+#define EAX_API_INCLUDED
+
+
+//
+// EAX API.
+//
+// Based on headers `eax[2-5].h` included in Doom 3 source code:
+// https://github.com/id-Software/DOOM-3/tree/master/neo/openal/include
+//
+
+
+#include <cfloat>
+#include <cstdint>
+
+#include <array>
+
+#include "AL/al.h"
+
+
+#ifndef GUID_DEFINED
+#define GUID_DEFINED
+typedef struct _GUID
+{
+ std::uint32_t Data1;
+ std::uint16_t Data2;
+ std::uint16_t Data3;
+ std::uint8_t Data4[8];
+} GUID;
+
+inline constexpr bool operator==(
+ const GUID& lhs,
+ const GUID& rhs) noexcept
+{
+ return
+ lhs.Data1 == rhs.Data1 &&
+ lhs.Data2 == rhs.Data2 &&
+ lhs.Data3 == rhs.Data3 &&
+ lhs.Data4[0] == rhs.Data4[0] &&
+ lhs.Data4[1] == rhs.Data4[1] &&
+ lhs.Data4[2] == rhs.Data4[2] &&
+ lhs.Data4[3] == rhs.Data4[3] &&
+ lhs.Data4[4] == rhs.Data4[4] &&
+ lhs.Data4[5] == rhs.Data4[5] &&
+ lhs.Data4[6] == rhs.Data4[6] &&
+ lhs.Data4[7] == rhs.Data4[7];
+}
+
+inline constexpr bool operator!=(
+ const GUID& lhs,
+ const GUID& rhs) noexcept
+{
+ return !(lhs == rhs);
+}
+#endif // GUID_DEFINED
+
+
+extern "C" const GUID DSPROPSETID_EAX20_ListenerProperties;
+
+enum DSPROPERTY_EAX20_LISTENERPROPERTY :
+ unsigned int
+{
+ DSPROPERTY_EAX20LISTENER_NONE,
+ DSPROPERTY_EAX20LISTENER_ALLPARAMETERS,
+ DSPROPERTY_EAX20LISTENER_ROOM,
+ DSPROPERTY_EAX20LISTENER_ROOMHF,
+ DSPROPERTY_EAX20LISTENER_ROOMROLLOFFFACTOR,
+ DSPROPERTY_EAX20LISTENER_DECAYTIME,
+ DSPROPERTY_EAX20LISTENER_DECAYHFRATIO,
+ DSPROPERTY_EAX20LISTENER_REFLECTIONS,
+ DSPROPERTY_EAX20LISTENER_REFLECTIONSDELAY,
+ DSPROPERTY_EAX20LISTENER_REVERB,
+ DSPROPERTY_EAX20LISTENER_REVERBDELAY,
+ DSPROPERTY_EAX20LISTENER_ENVIRONMENT,
+ DSPROPERTY_EAX20LISTENER_ENVIRONMENTSIZE,
+ DSPROPERTY_EAX20LISTENER_ENVIRONMENTDIFFUSION,
+ DSPROPERTY_EAX20LISTENER_AIRABSORPTIONHF,
+ DSPROPERTY_EAX20LISTENER_FLAGS
+}; // DSPROPERTY_EAX20_LISTENERPROPERTY
+
+struct EAX20LISTENERPROPERTIES
+{
+ long lRoom; // room effect level at low frequencies
+ long lRoomHF; // room effect high-frequency level re. low frequency level
+ float flRoomRolloffFactor; // like DS3D flRolloffFactor but for room effect
+ float flDecayTime; // reverberation decay time at low frequencies
+ float flDecayHFRatio; // high-frequency to low-frequency decay time ratio
+ long lReflections; // early reflections level relative to room effect
+ float flReflectionsDelay; // initial reflection delay time
+ long lReverb; // late reverberation level relative to room effect
+ float flReverbDelay; // late reverberation delay time relative to initial reflection
+ unsigned long dwEnvironment; // sets all listener properties
+ float flEnvironmentSize; // environment size in meters
+ float flEnvironmentDiffusion; // environment diffusion
+ float flAirAbsorptionHF; // change in level per meter at 5 kHz
+ unsigned long dwFlags; // modifies the behavior of properties
+}; // EAX20LISTENERPROPERTIES
+
+
+extern "C" const GUID DSPROPSETID_EAX20_BufferProperties;
+
+
+enum DSPROPERTY_EAX20_BUFFERPROPERTY :
+ unsigned int
+{
+ DSPROPERTY_EAX20BUFFER_NONE,
+ DSPROPERTY_EAX20BUFFER_ALLPARAMETERS,
+ DSPROPERTY_EAX20BUFFER_DIRECT,
+ DSPROPERTY_EAX20BUFFER_DIRECTHF,
+ DSPROPERTY_EAX20BUFFER_ROOM,
+ DSPROPERTY_EAX20BUFFER_ROOMHF,
+ DSPROPERTY_EAX20BUFFER_ROOMROLLOFFFACTOR,
+ DSPROPERTY_EAX20BUFFER_OBSTRUCTION,
+ DSPROPERTY_EAX20BUFFER_OBSTRUCTIONLFRATIO,
+ DSPROPERTY_EAX20BUFFER_OCCLUSION,
+ DSPROPERTY_EAX20BUFFER_OCCLUSIONLFRATIO,
+ DSPROPERTY_EAX20BUFFER_OCCLUSIONROOMRATIO,
+ DSPROPERTY_EAX20BUFFER_OUTSIDEVOLUMEHF,
+ DSPROPERTY_EAX20BUFFER_AIRABSORPTIONFACTOR,
+ DSPROPERTY_EAX20BUFFER_FLAGS
+}; // DSPROPERTY_EAX20_BUFFERPROPERTY
+
+
+struct EAX20BUFFERPROPERTIES
+{
+ long lDirect; // direct path level
+ long lDirectHF; // direct path level at high frequencies
+ long lRoom; // room effect level
+ long lRoomHF; // room effect level at high frequencies
+ float flRoomRolloffFactor; // like DS3D flRolloffFactor but for room effect
+ long lObstruction; // main obstruction control (attenuation at high frequencies)
+ float flObstructionLFRatio; // obstruction low-frequency level re. main control
+ long lOcclusion; // main occlusion control (attenuation at high frequencies)
+ float flOcclusionLFRatio; // occlusion low-frequency level re. main control
+ float flOcclusionRoomRatio; // occlusion room effect level re. main control
+ long lOutsideVolumeHF; // outside sound cone level at high frequencies
+ float flAirAbsorptionFactor; // multiplies DSPROPERTY_EAXLISTENER_AIRABSORPTIONHF
+ unsigned long dwFlags; // modifies the behavior of properties
+}; // EAX20BUFFERPROPERTIES
+
+
+extern "C" const GUID DSPROPSETID_EAX30_ListenerProperties;
+
+extern "C" const GUID DSPROPSETID_EAX30_BufferProperties;
+
+
+constexpr auto EAX_MAX_FXSLOTS = 4;
+
+constexpr auto EAX40_MAX_ACTIVE_FXSLOTS = 2;
+constexpr auto EAX50_MAX_ACTIVE_FXSLOTS = 4;
+
+
+constexpr auto EAX_OK = 0L;
+constexpr auto EAXERR_INVALID_OPERATION = -1L;
+constexpr auto EAXERR_INVALID_VALUE = -2L;
+constexpr auto EAXERR_NO_EFFECT_LOADED = -3L;
+constexpr auto EAXERR_UNKNOWN_EFFECT = -4L;
+constexpr auto EAXERR_INCOMPATIBLE_SOURCE_TYPE = -5L;
+constexpr auto EAXERR_INCOMPATIBLE_EAX_VERSION = -6L;
+
+
+extern "C" const GUID EAX_NULL_GUID;
+
+extern "C" const GUID EAX_PrimaryFXSlotID;
+
+
+struct EAXVECTOR
+{
+ float x;
+ float y;
+ float z;
+}; // EAXVECTOR
+
+bool operator==(
+ const EAXVECTOR& lhs,
+ const EAXVECTOR& rhs) noexcept;
+
+bool operator!=(
+ const EAXVECTOR& lhs,
+ const EAXVECTOR& rhs) noexcept;
+
+
+extern "C" const GUID EAXPROPERTYID_EAX40_Context;
+
+extern "C" const GUID EAXPROPERTYID_EAX50_Context;
+
+// EAX50
+enum :
+ unsigned long
+{
+ HEADPHONES = 0,
+ SPEAKERS_2,
+ SPEAKERS_4,
+ SPEAKERS_5, // 5.1 speakers
+ SPEAKERS_6, // 6.1 speakers
+ SPEAKERS_7, // 7.1 speakers
+};
+
+// EAX50
+enum :
+ unsigned long
+{
+ EAX_40 = 5, // EAX 4.0
+ EAX_50 = 6, // EAX 5.0
+};
+
+constexpr auto EAXCONTEXT_MINEAXSESSION = EAX_40;
+constexpr auto EAXCONTEXT_MAXEAXSESSION = EAX_50;
+constexpr auto EAXCONTEXT_DEFAULTEAXSESSION = EAX_40;
+
+constexpr auto EAXCONTEXT_MINMAXACTIVESENDS = 2UL;
+constexpr auto EAXCONTEXT_MAXMAXACTIVESENDS = 4UL;
+constexpr auto EAXCONTEXT_DEFAULTMAXACTIVESENDS = 2UL;
+
+// EAX50
+struct EAXSESSIONPROPERTIES
+{
+ unsigned long ulEAXVersion;
+ unsigned long ulMaxActiveSends;
+}; // EAXSESSIONPROPERTIES
+
+enum EAXCONTEXT_PROPERTY :
+ unsigned int
+{
+ EAXCONTEXT_NONE = 0,
+ EAXCONTEXT_ALLPARAMETERS,
+ EAXCONTEXT_PRIMARYFXSLOTID,
+ EAXCONTEXT_DISTANCEFACTOR,
+ EAXCONTEXT_AIRABSORPTIONHF,
+ EAXCONTEXT_HFREFERENCE,
+ EAXCONTEXT_LASTERROR,
+
+ // EAX50
+ EAXCONTEXT_SPEAKERCONFIG,
+ EAXCONTEXT_EAXSESSION,
+ EAXCONTEXT_MACROFXFACTOR,
+}; // EAXCONTEXT_PROPERTY
+
+struct EAX40CONTEXTPROPERTIES
+{
+ GUID guidPrimaryFXSlotID;
+ float flDistanceFactor;
+ float flAirAbsorptionHF;
+ float flHFReference;
+}; // EAX40CONTEXTPROPERTIES
+
+struct EAX50CONTEXTPROPERTIES :
+ public EAX40CONTEXTPROPERTIES
+{
+ float flMacroFXFactor;
+}; // EAX40CONTEXTPROPERTIES
+
+
+bool operator==(
+ const EAX40CONTEXTPROPERTIES& lhs,
+ const EAX40CONTEXTPROPERTIES& rhs) noexcept;
+
+bool operator==(
+ const EAX50CONTEXTPROPERTIES& lhs,
+ const EAX50CONTEXTPROPERTIES& rhs) noexcept;
+
+
+constexpr auto EAXCONTEXT_MINDISTANCEFACTOR = FLT_MIN;
+constexpr auto EAXCONTEXT_MAXDISTANCEFACTOR = FLT_MAX;
+constexpr auto EAXCONTEXT_DEFAULTDISTANCEFACTOR = 1.0F;
+
+constexpr auto EAXCONTEXT_MINAIRABSORPTIONHF = -100.0F;
+constexpr auto EAXCONTEXT_MAXAIRABSORPTIONHF = 0.0F;
+constexpr auto EAXCONTEXT_DEFAULTAIRABSORPTIONHF = -5.0F;
+
+constexpr auto EAXCONTEXT_MINHFREFERENCE = 1000.0F;
+constexpr auto EAXCONTEXT_MAXHFREFERENCE = 20000.0F;
+constexpr auto EAXCONTEXT_DEFAULTHFREFERENCE = 5000.0F;
+
+constexpr auto EAXCONTEXT_MINMACROFXFACTOR = 0.0F;
+constexpr auto EAXCONTEXT_MAXMACROFXFACTOR = 1.0F;
+constexpr auto EAXCONTEXT_DEFAULTMACROFXFACTOR = 0.0F;
+
+
+extern "C" const GUID EAXPROPERTYID_EAX40_FXSlot0;
+
+extern "C" const GUID EAXPROPERTYID_EAX50_FXSlot0;
+
+extern "C" const GUID EAXPROPERTYID_EAX40_FXSlot1;
+
+extern "C" const GUID EAXPROPERTYID_EAX50_FXSlot1;
+
+extern "C" const GUID EAXPROPERTYID_EAX40_FXSlot2;
+
+extern "C" const GUID EAXPROPERTYID_EAX50_FXSlot2;
+
+extern "C" const GUID EAXPROPERTYID_EAX40_FXSlot3;
+
+extern "C" const GUID EAXPROPERTYID_EAX50_FXSlot3;
+
+extern "C" const GUID EAXCONTEXT_DEFAULTPRIMARYFXSLOTID;
+
+enum EAXFXSLOT_PROPERTY :
+ unsigned int
+{
+ EAXFXSLOT_PARAMETER = 0,
+
+ EAXFXSLOT_NONE = 0x10000,
+ EAXFXSLOT_ALLPARAMETERS,
+ EAXFXSLOT_LOADEFFECT,
+ EAXFXSLOT_VOLUME,
+ EAXFXSLOT_LOCK,
+ EAXFXSLOT_FLAGS,
+
+ // EAX50
+ EAXFXSLOT_OCCLUSION,
+ EAXFXSLOT_OCCLUSIONLFRATIO,
+}; // EAXFXSLOT_PROPERTY
+
+constexpr auto EAXFXSLOTFLAGS_ENVIRONMENT = 0x00000001UL;
+// EAX50
+constexpr auto EAXFXSLOTFLAGS_UPMIX = 0x00000002UL;
+
+constexpr auto EAX40FXSLOTFLAGS_RESERVED = 0xFFFFFFFEUL; // reserved future use
+constexpr auto EAX50FXSLOTFLAGS_RESERVED = 0xFFFFFFFCUL; // reserved future use
+
+
+constexpr auto EAXFXSLOT_MINVOLUME = -10'000L;
+constexpr auto EAXFXSLOT_MAXVOLUME = 0L;
+constexpr auto EAXFXSLOT_DEFAULTVOLUME = 0L;
+
+constexpr auto EAXFXSLOT_MINLOCK = 0L;
+constexpr auto EAXFXSLOT_MAXLOCK = 1L;
+
+enum :
+ long
+{
+ EAXFXSLOT_UNLOCKED = 0,
+ EAXFXSLOT_LOCKED = 1
+};
+
+constexpr auto EAXFXSLOT_MINOCCLUSION = -10'000L;
+constexpr auto EAXFXSLOT_MAXOCCLUSION = 0L;
+constexpr auto EAXFXSLOT_DEFAULTOCCLUSION = 0L;
+
+constexpr auto EAXFXSLOT_MINOCCLUSIONLFRATIO = 0.0F;
+constexpr auto EAXFXSLOT_MAXOCCLUSIONLFRATIO = 1.0F;
+constexpr auto EAXFXSLOT_DEFAULTOCCLUSIONLFRATIO = 0.25F;
+
+constexpr auto EAX40FXSLOT_DEFAULTFLAGS = EAXFXSLOTFLAGS_ENVIRONMENT;
+
+constexpr auto EAX50FXSLOT_DEFAULTFLAGS =
+ EAXFXSLOTFLAGS_ENVIRONMENT |
+ EAXFXSLOTFLAGS_UPMIX; // ignored for reverb;
+
+struct EAX40FXSLOTPROPERTIES
+{
+ GUID guidLoadEffect;
+ long lVolume;
+ long lLock;
+ unsigned long ulFlags;
+}; // EAX40FXSLOTPROPERTIES
+
+struct EAX50FXSLOTPROPERTIES :
+ public EAX40FXSLOTPROPERTIES
+{
+ long lOcclusion;
+ float flOcclusionLFRatio;
+}; // EAX50FXSLOTPROPERTIES
+
+bool operator==(
+ const EAX40FXSLOTPROPERTIES& lhs,
+ const EAX40FXSLOTPROPERTIES& rhs) noexcept;
+
+bool operator==(
+ const EAX50FXSLOTPROPERTIES& lhs,
+ const EAX50FXSLOTPROPERTIES& rhs) noexcept;
+
+extern "C" const GUID EAXPROPERTYID_EAX40_Source;
+
+extern "C" const GUID EAXPROPERTYID_EAX50_Source;
+
+// Source object properties
+enum EAXSOURCE_PROPERTY :
+ unsigned int
+{
+ // EAX30
+
+ EAXSOURCE_NONE,
+ EAXSOURCE_ALLPARAMETERS,
+ EAXSOURCE_OBSTRUCTIONPARAMETERS,
+ EAXSOURCE_OCCLUSIONPARAMETERS,
+ EAXSOURCE_EXCLUSIONPARAMETERS,
+ EAXSOURCE_DIRECT,
+ EAXSOURCE_DIRECTHF,
+ EAXSOURCE_ROOM,
+ EAXSOURCE_ROOMHF,
+ EAXSOURCE_OBSTRUCTION,
+ EAXSOURCE_OBSTRUCTIONLFRATIO,
+ EAXSOURCE_OCCLUSION,
+ EAXSOURCE_OCCLUSIONLFRATIO,
+ EAXSOURCE_OCCLUSIONROOMRATIO,
+ EAXSOURCE_OCCLUSIONDIRECTRATIO,
+ EAXSOURCE_EXCLUSION,
+ EAXSOURCE_EXCLUSIONLFRATIO,
+ EAXSOURCE_OUTSIDEVOLUMEHF,
+ EAXSOURCE_DOPPLERFACTOR,
+ EAXSOURCE_ROLLOFFFACTOR,
+ EAXSOURCE_ROOMROLLOFFFACTOR,
+ EAXSOURCE_AIRABSORPTIONFACTOR,
+ EAXSOURCE_FLAGS,
+
+ // EAX40
+
+ EAXSOURCE_SENDPARAMETERS,
+ EAXSOURCE_ALLSENDPARAMETERS,
+ EAXSOURCE_OCCLUSIONSENDPARAMETERS,
+ EAXSOURCE_EXCLUSIONSENDPARAMETERS,
+ EAXSOURCE_ACTIVEFXSLOTID,
+
+ // EAX50
+
+ EAXSOURCE_MACROFXFACTOR,
+ EAXSOURCE_SPEAKERLEVELS,
+ EAXSOURCE_ALL2DPARAMETERS,
+}; // EAXSOURCE_PROPERTY
+
+
+constexpr auto EAXSOURCEFLAGS_DIRECTHFAUTO = 0x00000001UL; // relates to EAXSOURCE_DIRECTHF
+constexpr auto EAXSOURCEFLAGS_ROOMAUTO = 0x00000002UL; // relates to EAXSOURCE_ROOM
+constexpr auto EAXSOURCEFLAGS_ROOMHFAUTO = 0x00000004UL; // relates to EAXSOURCE_ROOMHF
+// EAX50
+constexpr auto EAXSOURCEFLAGS_3DELEVATIONFILTER = 0x00000008UL;
+// EAX50
+constexpr auto EAXSOURCEFLAGS_UPMIX = 0x00000010UL;
+// EAX50
+constexpr auto EAXSOURCEFLAGS_APPLYSPEAKERLEVELS = 0x00000020UL;
+
+constexpr auto EAX20SOURCEFLAGS_RESERVED = 0xFFFFFFF8UL; // reserved future use
+constexpr auto EAX50SOURCEFLAGS_RESERVED = 0xFFFFFFC0UL; // reserved future use
+
+
+constexpr auto EAXSOURCE_MINSEND = -10'000L;
+constexpr auto EAXSOURCE_MAXSEND = 0L;
+constexpr auto EAXSOURCE_DEFAULTSEND = 0L;
+
+constexpr auto EAXSOURCE_MINSENDHF = -10'000L;
+constexpr auto EAXSOURCE_MAXSENDHF = 0L;
+constexpr auto EAXSOURCE_DEFAULTSENDHF = 0L;
+
+constexpr auto EAXSOURCE_MINDIRECT = -10'000L;
+constexpr auto EAXSOURCE_MAXDIRECT = 1'000L;
+constexpr auto EAXSOURCE_DEFAULTDIRECT = 0L;
+
+constexpr auto EAXSOURCE_MINDIRECTHF = -10'000L;
+constexpr auto EAXSOURCE_MAXDIRECTHF = 0L;
+constexpr auto EAXSOURCE_DEFAULTDIRECTHF = 0L;
+
+constexpr auto EAXSOURCE_MINROOM = -10'000L;
+constexpr auto EAXSOURCE_MAXROOM = 1'000L;
+constexpr auto EAXSOURCE_DEFAULTROOM = 0L;
+
+constexpr auto EAXSOURCE_MINROOMHF = -10'000L;
+constexpr auto EAXSOURCE_MAXROOMHF = 0L;
+constexpr auto EAXSOURCE_DEFAULTROOMHF = 0L;
+
+constexpr auto EAXSOURCE_MINOBSTRUCTION = -10'000L;
+constexpr auto EAXSOURCE_MAXOBSTRUCTION = 0L;
+constexpr auto EAXSOURCE_DEFAULTOBSTRUCTION = 0L;
+
+constexpr auto EAXSOURCE_MINOBSTRUCTIONLFRATIO = 0.0F;
+constexpr auto EAXSOURCE_MAXOBSTRUCTIONLFRATIO = 1.0F;
+constexpr auto EAXSOURCE_DEFAULTOBSTRUCTIONLFRATIO = 0.0F;
+
+constexpr auto EAXSOURCE_MINOCCLUSION = -10'000L;
+constexpr auto EAXSOURCE_MAXOCCLUSION = 0L;
+constexpr auto EAXSOURCE_DEFAULTOCCLUSION = 0L;
+
+constexpr auto EAXSOURCE_MINOCCLUSIONLFRATIO = 0.0F;
+constexpr auto EAXSOURCE_MAXOCCLUSIONLFRATIO = 1.0F;
+constexpr auto EAXSOURCE_DEFAULTOCCLUSIONLFRATIO = 0.25F;
+
+constexpr auto EAXSOURCE_MINOCCLUSIONROOMRATIO = 0.0F;
+constexpr auto EAXSOURCE_MAXOCCLUSIONROOMRATIO = 10.0F;
+constexpr auto EAXSOURCE_DEFAULTOCCLUSIONROOMRATIO = 1.5F;
+
+constexpr auto EAXSOURCE_MINOCCLUSIONDIRECTRATIO = 0.0F;
+constexpr auto EAXSOURCE_MAXOCCLUSIONDIRECTRATIO = 10.0F;
+constexpr auto EAXSOURCE_DEFAULTOCCLUSIONDIRECTRATIO = 1.0F;
+
+constexpr auto EAXSOURCE_MINEXCLUSION = -10'000L;
+constexpr auto EAXSOURCE_MAXEXCLUSION = 0L;
+constexpr auto EAXSOURCE_DEFAULTEXCLUSION = 0L;
+
+constexpr auto EAXSOURCE_MINEXCLUSIONLFRATIO = 0.0F;
+constexpr auto EAXSOURCE_MAXEXCLUSIONLFRATIO = 1.0F;
+constexpr auto EAXSOURCE_DEFAULTEXCLUSIONLFRATIO = 1.0F;
+
+constexpr auto EAXSOURCE_MINOUTSIDEVOLUMEHF = -10'000L;
+constexpr auto EAXSOURCE_MAXOUTSIDEVOLUMEHF = 0L;
+constexpr auto EAXSOURCE_DEFAULTOUTSIDEVOLUMEHF = 0L;
+
+constexpr auto EAXSOURCE_MINDOPPLERFACTOR = 0.0F;
+constexpr auto EAXSOURCE_MAXDOPPLERFACTOR = 10.0F;
+constexpr auto EAXSOURCE_DEFAULTDOPPLERFACTOR = 1.0F;
+
+constexpr auto EAXSOURCE_MINROLLOFFFACTOR = 0.0F;
+constexpr auto EAXSOURCE_MAXROLLOFFFACTOR = 10.0F;
+constexpr auto EAXSOURCE_DEFAULTROLLOFFFACTOR = 0.0F;
+
+constexpr auto EAXSOURCE_MINROOMROLLOFFFACTOR = 0.0F;
+constexpr auto EAXSOURCE_MAXROOMROLLOFFFACTOR = 10.0F;
+constexpr auto EAXSOURCE_DEFAULTROOMROLLOFFFACTOR = 0.0F;
+
+constexpr auto EAXSOURCE_MINAIRABSORPTIONFACTOR = 0.0F;
+constexpr auto EAXSOURCE_MAXAIRABSORPTIONFACTOR = 10.0F;
+constexpr auto EAXSOURCE_DEFAULTAIRABSORPTIONFACTOR = 0.0F;
+
+// EAX50
+
+constexpr auto EAXSOURCE_MINMACROFXFACTOR = 0.0F;
+constexpr auto EAXSOURCE_MAXMACROFXFACTOR = 1.0F;
+constexpr auto EAXSOURCE_DEFAULTMACROFXFACTOR = 1.0F;
+
+// EAX50
+
+constexpr auto EAXSOURCE_MINSPEAKERLEVEL = -10'000L;
+constexpr auto EAXSOURCE_MAXSPEAKERLEVEL = 0L;
+constexpr auto EAXSOURCE_DEFAULTSPEAKERLEVEL = -10'000L;
+
+constexpr auto EAXSOURCE_DEFAULTFLAGS =
+ EAXSOURCEFLAGS_DIRECTHFAUTO |
+ EAXSOURCEFLAGS_ROOMAUTO |
+ EAXSOURCEFLAGS_ROOMHFAUTO;
+
+enum :
+ long
+{
+ EAXSPEAKER_FRONT_LEFT = 1,
+ EAXSPEAKER_FRONT_CENTER = 2,
+ EAXSPEAKER_FRONT_RIGHT = 3,
+ EAXSPEAKER_SIDE_RIGHT = 4,
+ EAXSPEAKER_REAR_RIGHT = 5,
+ EAXSPEAKER_REAR_CENTER = 6,
+ EAXSPEAKER_REAR_LEFT = 7,
+ EAXSPEAKER_SIDE_LEFT = 8,
+ EAXSPEAKER_LOW_FREQUENCY = 9
+};
+
+// EAXSOURCEFLAGS_DIRECTHFAUTO, EAXSOURCEFLAGS_ROOMAUTO and EAXSOURCEFLAGS_ROOMHFAUTO are ignored for 2D sources
+// EAXSOURCEFLAGS_UPMIX is ignored for 3D sources
+constexpr auto EAX50SOURCE_DEFAULTFLAGS =
+ EAXSOURCEFLAGS_DIRECTHFAUTO |
+ EAXSOURCEFLAGS_ROOMAUTO |
+ EAXSOURCEFLAGS_ROOMHFAUTO |
+ EAXSOURCEFLAGS_UPMIX;
+
+struct EAX30SOURCEPROPERTIES
+{
+ long lDirect; // direct path level (at low and mid frequencies)
+ long lDirectHF; // relative direct path level at high frequencies
+ long lRoom; // room effect level (at low and mid frequencies)
+ long lRoomHF; // relative room effect level at high frequencies
+ long lObstruction; // main obstruction control (attenuation at high frequencies)
+ float flObstructionLFRatio; // obstruction low-frequency level re. main control
+ long lOcclusion; // main occlusion control (attenuation at high frequencies)
+ float flOcclusionLFRatio; // occlusion low-frequency level re. main control
+ float flOcclusionRoomRatio; // relative occlusion control for room effect
+ float flOcclusionDirectRatio; // relative occlusion control for direct path
+ long lExclusion; // main exlusion control (attenuation at high frequencies)
+ float flExclusionLFRatio; // exclusion low-frequency level re. main control
+ long lOutsideVolumeHF; // outside sound cone level at high frequencies
+ float flDopplerFactor; // like DS3D flDopplerFactor but per source
+ float flRolloffFactor; // like DS3D flRolloffFactor but per source
+ float flRoomRolloffFactor; // like DS3D flRolloffFactor but for room effect
+ float flAirAbsorptionFactor; // multiplies EAXREVERB_AIRABSORPTIONHF
+ unsigned long ulFlags; // modifies the behavior of properties
+}; // EAX30SOURCEPROPERTIES
+
+struct EAX50SOURCEPROPERTIES :
+ public EAX30SOURCEPROPERTIES
+{
+ float flMacroFXFactor;
+}; // EAX50SOURCEPROPERTIES
+
+struct EAXSOURCEALLSENDPROPERTIES
+{
+ GUID guidReceivingFXSlotID;
+ long lSend; // send level (at low and mid frequencies)
+ long lSendHF; // relative send level at high frequencies
+ long lOcclusion;
+ float flOcclusionLFRatio;
+ float flOcclusionRoomRatio;
+ float flOcclusionDirectRatio;
+ long lExclusion;
+ float flExclusionLFRatio;
+}; // EAXSOURCEALLSENDPROPERTIES
+
+struct EAXSOURCE2DPROPERTIES
+{
+ long lDirect; // direct path level (at low and mid frequencies)
+ long lDirectHF; // relative direct path level at high frequencies
+ long lRoom; // room effect level (at low and mid frequencies)
+ long lRoomHF; // relative room effect level at high frequencies
+ unsigned long ulFlags; // modifies the behavior of properties
+}; // EAXSOURCE2DPROPERTIES
+
+struct EAXSPEAKERLEVELPROPERTIES
+{
+ long lSpeakerID;
+ long lLevel;
+}; // EAXSPEAKERLEVELPROPERTIES
+
+struct EAX40ACTIVEFXSLOTS
+{
+ GUID guidActiveFXSlots[EAX40_MAX_ACTIVE_FXSLOTS];
+}; // EAX40ACTIVEFXSLOTS
+
+struct EAX50ACTIVEFXSLOTS
+{
+ GUID guidActiveFXSlots[EAX50_MAX_ACTIVE_FXSLOTS];
+}; // EAX50ACTIVEFXSLOTS
+
+bool operator==(
+ const EAX50ACTIVEFXSLOTS& lhs,
+ const EAX50ACTIVEFXSLOTS& rhs) noexcept;
+
+bool operator!=(
+ const EAX50ACTIVEFXSLOTS& lhs,
+ const EAX50ACTIVEFXSLOTS& rhs) noexcept;
+
+// Use this structure for EAXSOURCE_OBSTRUCTIONPARAMETERS property.
+struct EAXOBSTRUCTIONPROPERTIES
+{
+ long lObstruction;
+ float flObstructionLFRatio;
+}; // EAXOBSTRUCTIONPROPERTIES
+
+// Use this structure for EAXSOURCE_OCCLUSIONPARAMETERS property.
+struct EAXOCCLUSIONPROPERTIES
+{
+ long lOcclusion;
+ float flOcclusionLFRatio;
+ float flOcclusionRoomRatio;
+ float flOcclusionDirectRatio;
+}; // EAXOCCLUSIONPROPERTIES
+
+// Use this structure for EAXSOURCE_EXCLUSIONPARAMETERS property.
+struct EAXEXCLUSIONPROPERTIES
+{
+ long lExclusion;
+ float flExclusionLFRatio;
+}; // EAXEXCLUSIONPROPERTIES
+
+// Use this structure for EAXSOURCE_SENDPARAMETERS properties.
+struct EAXSOURCESENDPROPERTIES
+{
+ GUID guidReceivingFXSlotID;
+ long lSend;
+ long lSendHF;
+}; // EAXSOURCESENDPROPERTIES
+
+// Use this structure for EAXSOURCE_OCCLUSIONSENDPARAMETERS
+struct EAXSOURCEOCCLUSIONSENDPROPERTIES
+{
+ GUID guidReceivingFXSlotID;
+ long lOcclusion;
+ float flOcclusionLFRatio;
+ float flOcclusionRoomRatio;
+ float flOcclusionDirectRatio;
+}; // EAXSOURCEOCCLUSIONSENDPROPERTIES
+
+// Use this structure for EAXSOURCE_EXCLUSIONSENDPARAMETERS
+struct EAXSOURCEEXCLUSIONSENDPROPERTIES
+{
+ GUID guidReceivingFXSlotID;
+ long lExclusion;
+ float flExclusionLFRatio;
+}; // EAXSOURCEEXCLUSIONSENDPROPERTIES
+
+extern const EAX50ACTIVEFXSLOTS EAX40SOURCE_DEFAULTACTIVEFXSLOTID;
+
+extern const EAX50ACTIVEFXSLOTS EAX50SOURCE_3DDEFAULTACTIVEFXSLOTID;
+
+extern const EAX50ACTIVEFXSLOTS EAX50SOURCE_2DDEFAULTACTIVEFXSLOTID;
+
+
+// EAX Reverb Effect
+
+extern "C" const GUID EAX_REVERB_EFFECT;
+
+// Reverb effect properties
+enum EAXREVERB_PROPERTY :
+ unsigned int
+{
+ EAXREVERB_NONE,
+ EAXREVERB_ALLPARAMETERS,
+ EAXREVERB_ENVIRONMENT,
+ EAXREVERB_ENVIRONMENTSIZE,
+ EAXREVERB_ENVIRONMENTDIFFUSION,
+ EAXREVERB_ROOM,
+ EAXREVERB_ROOMHF,
+ EAXREVERB_ROOMLF,
+ EAXREVERB_DECAYTIME,
+ EAXREVERB_DECAYHFRATIO,
+ EAXREVERB_DECAYLFRATIO,
+ EAXREVERB_REFLECTIONS,
+ EAXREVERB_REFLECTIONSDELAY,
+ EAXREVERB_REFLECTIONSPAN,
+ EAXREVERB_REVERB,
+ EAXREVERB_REVERBDELAY,
+ EAXREVERB_REVERBPAN,
+ EAXREVERB_ECHOTIME,
+ EAXREVERB_ECHODEPTH,
+ EAXREVERB_MODULATIONTIME,
+ EAXREVERB_MODULATIONDEPTH,
+ EAXREVERB_AIRABSORPTIONHF,
+ EAXREVERB_HFREFERENCE,
+ EAXREVERB_LFREFERENCE,
+ EAXREVERB_ROOMROLLOFFFACTOR,
+ EAXREVERB_FLAGS,
+}; // EAXREVERB_PROPERTY
+
+// used by EAXREVERB_ENVIRONMENT
+enum :
+ unsigned long
+{
+ EAX_ENVIRONMENT_GENERIC,
+ EAX_ENVIRONMENT_PADDEDCELL,
+ EAX_ENVIRONMENT_ROOM,
+ EAX_ENVIRONMENT_BATHROOM,
+ EAX_ENVIRONMENT_LIVINGROOM,
+ EAX_ENVIRONMENT_STONEROOM,
+ EAX_ENVIRONMENT_AUDITORIUM,
+ EAX_ENVIRONMENT_CONCERTHALL,
+ EAX_ENVIRONMENT_CAVE,
+ EAX_ENVIRONMENT_ARENA,
+ EAX_ENVIRONMENT_HANGAR,
+ EAX_ENVIRONMENT_CARPETEDHALLWAY,
+ EAX_ENVIRONMENT_HALLWAY,
+ EAX_ENVIRONMENT_STONECORRIDOR,
+ EAX_ENVIRONMENT_ALLEY,
+ EAX_ENVIRONMENT_FOREST,
+ EAX_ENVIRONMENT_CITY,
+ EAX_ENVIRONMENT_MOUNTAINS,
+ EAX_ENVIRONMENT_QUARRY,
+ EAX_ENVIRONMENT_PLAIN,
+ EAX_ENVIRONMENT_PARKINGLOT,
+ EAX_ENVIRONMENT_SEWERPIPE,
+ EAX_ENVIRONMENT_UNDERWATER,
+ EAX_ENVIRONMENT_DRUGGED,
+ EAX_ENVIRONMENT_DIZZY,
+ EAX_ENVIRONMENT_PSYCHOTIC,
+
+ // EAX30
+ EAX_ENVIRONMENT_UNDEFINED,
+
+ EAX_ENVIRONMENT_COUNT,
+};
+
+
+// reverberation decay time
+constexpr auto EAXREVERBFLAGS_DECAYTIMESCALE = 0x00000001UL;
+
+// reflection level
+constexpr auto EAXREVERBFLAGS_REFLECTIONSSCALE = 0x00000002UL;
+
+// initial reflection delay time
+constexpr auto EAXREVERBFLAGS_REFLECTIONSDELAYSCALE = 0x00000004UL;
+
+// reflections level
+constexpr auto EAXREVERBFLAGS_REVERBSCALE = 0x00000008UL;
+
+// late reverberation delay time
+constexpr auto EAXREVERBFLAGS_REVERBDELAYSCALE = 0x00000010UL;
+
+// echo time
+// EAX30+
+constexpr auto EAXREVERBFLAGS_ECHOTIMESCALE = 0x00000040UL;
+
+// modulation time
+// EAX30+
+constexpr auto EAXREVERBFLAGS_MODULATIONTIMESCALE = 0x00000080UL;
+
+// This flag limits high-frequency decay time according to air absorption.
+constexpr auto EAXREVERBFLAGS_DECAYHFLIMIT = 0x00000020UL;
+
+constexpr auto EAXREVERBFLAGS_RESERVED = 0xFFFFFF00UL; // reserved future use
+
+
+struct EAXREVERBPROPERTIES
+{
+ unsigned long ulEnvironment; // sets all reverb properties
+ float flEnvironmentSize; // environment size in meters
+ float flEnvironmentDiffusion; // environment diffusion
+ long lRoom; // room effect level (at mid frequencies)
+ long lRoomHF; // relative room effect level at high frequencies
+ long lRoomLF; // relative room effect level at low frequencies
+ float flDecayTime; // reverberation decay time at mid frequencies
+ float flDecayHFRatio; // high-frequency to mid-frequency decay time ratio
+ float flDecayLFRatio; // low-frequency to mid-frequency decay time ratio
+ long lReflections; // early reflections level relative to room effect
+ float flReflectionsDelay; // initial reflection delay time
+ EAXVECTOR vReflectionsPan; // early reflections panning vector
+ long lReverb; // late reverberation level relative to room effect
+ float flReverbDelay; // late reverberation delay time relative to initial reflection
+ EAXVECTOR vReverbPan; // late reverberation panning vector
+ float flEchoTime; // echo time
+ float flEchoDepth; // echo depth
+ float flModulationTime; // modulation time
+ float flModulationDepth; // modulation depth
+ float flAirAbsorptionHF; // change in level per meter at high frequencies
+ float flHFReference; // reference high frequency
+ float flLFReference; // reference low frequency
+ float flRoomRolloffFactor; // like DS3D flRolloffFactor but for room effect
+ unsigned long ulFlags; // modifies the behavior of properties
+}; // EAXREVERBPROPERTIES
+
+bool operator==(
+ const EAXREVERBPROPERTIES& lhs,
+ const EAXREVERBPROPERTIES& rhs) noexcept;
+
+bool operator!=(
+ const EAXREVERBPROPERTIES& lhs,
+ const EAXREVERBPROPERTIES& rhs) noexcept;
+
+
+constexpr auto EAXREVERB_MINENVIRONMENT = 0UL;
+constexpr auto EAX20REVERB_MAXENVIRONMENT = EAX_ENVIRONMENT_COUNT - 2UL;
+constexpr auto EAX30REVERB_MAXENVIRONMENT = EAX_ENVIRONMENT_COUNT - 1UL;
+constexpr auto EAXREVERB_DEFAULTENVIRONMENT = EAX_ENVIRONMENT_GENERIC;
+
+constexpr auto EAXREVERB_MINENVIRONMENTSIZE = 1.0F;
+constexpr auto EAXREVERB_MAXENVIRONMENTSIZE = 100.0F;
+constexpr auto EAXREVERB_DEFAULTENVIRONMENTSIZE = 7.5F;
+
+constexpr auto EAXREVERB_MINENVIRONMENTDIFFUSION = 0.0F;
+constexpr auto EAXREVERB_MAXENVIRONMENTDIFFUSION = 1.0F;
+constexpr auto EAXREVERB_DEFAULTENVIRONMENTDIFFUSION = 1.0F;
+
+constexpr auto EAXREVERB_MINROOM = -10'000L;
+constexpr auto EAXREVERB_MAXROOM = 0L;
+constexpr auto EAXREVERB_DEFAULTROOM = -1'000L;
+
+constexpr auto EAXREVERB_MINROOMHF = -10'000L;
+constexpr auto EAXREVERB_MAXROOMHF = 0L;
+constexpr auto EAXREVERB_DEFAULTROOMHF = -100L;
+
+constexpr auto EAXREVERB_MINROOMLF = -10'000L;
+constexpr auto EAXREVERB_MAXROOMLF = 0L;
+constexpr auto EAXREVERB_DEFAULTROOMLF = 0L;
+
+constexpr auto EAXREVERB_MINDECAYTIME = 0.1F;
+constexpr auto EAXREVERB_MAXDECAYTIME = 20.0F;
+constexpr auto EAXREVERB_DEFAULTDECAYTIME = 1.49F;
+
+constexpr auto EAXREVERB_MINDECAYHFRATIO = 0.1F;
+constexpr auto EAXREVERB_MAXDECAYHFRATIO = 2.0F;
+constexpr auto EAXREVERB_DEFAULTDECAYHFRATIO = 0.83F;
+
+constexpr auto EAXREVERB_MINDECAYLFRATIO = 0.1F;
+constexpr auto EAXREVERB_MAXDECAYLFRATIO = 2.0F;
+constexpr auto EAXREVERB_DEFAULTDECAYLFRATIO = 1.0F;
+
+constexpr auto EAXREVERB_MINREFLECTIONS = -10'000L;
+constexpr auto EAXREVERB_MAXREFLECTIONS = 1'000L;
+constexpr auto EAXREVERB_DEFAULTREFLECTIONS = -2'602L;
+
+constexpr auto EAXREVERB_MINREFLECTIONSDELAY = 0.0F;
+constexpr auto EAXREVERB_MAXREFLECTIONSDELAY = 0.3F;
+constexpr auto EAXREVERB_DEFAULTREFLECTIONSDELAY = 0.007F;
+
+constexpr auto EAXREVERB_DEFAULTREFLECTIONSPAN = EAXVECTOR{0.0F, 0.0F, 0.0F};
+
+constexpr auto EAXREVERB_MINREVERB = -10'000L;
+constexpr auto EAXREVERB_MAXREVERB = 2'000L;
+constexpr auto EAXREVERB_DEFAULTREVERB = 200L;
+
+constexpr auto EAXREVERB_MINREVERBDELAY = 0.0F;
+constexpr auto EAXREVERB_MAXREVERBDELAY = 0.1F;
+constexpr auto EAXREVERB_DEFAULTREVERBDELAY = 0.011F;
+
+constexpr auto EAXREVERB_DEFAULTREVERBPAN = EAXVECTOR{0.0F, 0.0F, 0.0F};
+
+constexpr auto EAXREVERB_MINECHOTIME = 0.075F;
+constexpr auto EAXREVERB_MAXECHOTIME = 0.25F;
+constexpr auto EAXREVERB_DEFAULTECHOTIME = 0.25F;
+
+constexpr auto EAXREVERB_MINECHODEPTH = 0.0F;
+constexpr auto EAXREVERB_MAXECHODEPTH = 1.0F;
+constexpr auto EAXREVERB_DEFAULTECHODEPTH = 0.0F;
+
+constexpr auto EAXREVERB_MINMODULATIONTIME = 0.04F;
+constexpr auto EAXREVERB_MAXMODULATIONTIME = 4.0F;
+constexpr auto EAXREVERB_DEFAULTMODULATIONTIME = 0.25F;
+
+constexpr auto EAXREVERB_MINMODULATIONDEPTH = 0.0F;
+constexpr auto EAXREVERB_MAXMODULATIONDEPTH = 1.0F;
+constexpr auto EAXREVERB_DEFAULTMODULATIONDEPTH = 0.0F;
+
+constexpr auto EAXREVERB_MINAIRABSORPTIONHF = -100.0F;
+constexpr auto EAXREVERB_MAXAIRABSORPTIONHF = 0.0F;
+constexpr auto EAXREVERB_DEFAULTAIRABSORPTIONHF = -5.0F;
+
+constexpr auto EAXREVERB_MINHFREFERENCE = 1'000.0F;
+constexpr auto EAXREVERB_MAXHFREFERENCE = 20'000.0F;
+constexpr auto EAXREVERB_DEFAULTHFREFERENCE = 5'000.0F;
+
+constexpr auto EAXREVERB_MINLFREFERENCE = 20.0F;
+constexpr auto EAXREVERB_MAXLFREFERENCE = 1'000.0F;
+constexpr auto EAXREVERB_DEFAULTLFREFERENCE = 250.0F;
+
+constexpr auto EAXREVERB_MINROOMROLLOFFFACTOR = 0.0F;
+constexpr auto EAXREVERB_MAXROOMROLLOFFFACTOR = 10.0F;
+constexpr auto EAXREVERB_DEFAULTROOMROLLOFFFACTOR = 0.0F;
+
+constexpr auto EAXREVERB_DEFAULTFLAGS =
+ EAXREVERBFLAGS_DECAYTIMESCALE |
+ EAXREVERBFLAGS_REFLECTIONSSCALE |
+ EAXREVERBFLAGS_REFLECTIONSDELAYSCALE |
+ EAXREVERBFLAGS_REVERBSCALE |
+ EAXREVERBFLAGS_REVERBDELAYSCALE |
+ EAXREVERBFLAGS_DECAYHFLIMIT;
+
+
+extern const EAXREVERBPROPERTIES EAXREVERB_PRESET_GENERIC;
+extern const EAXREVERBPROPERTIES EAXREVERB_PRESET_PADDEDCEL;
+extern const EAXREVERBPROPERTIES EAXREVERB_PRESET_ROOM;
+extern const EAXREVERBPROPERTIES EAXREVERB_PRESET_BATHROOM;
+extern const EAXREVERBPROPERTIES EAXREVERB_PRESET_LIVINGROOM;
+extern const EAXREVERBPROPERTIES EAXREVERB_PRESET_STONEROOM;
+extern const EAXREVERBPROPERTIES EAXREVERB_PRESET_AUDITORIUM;
+extern const EAXREVERBPROPERTIES EAXREVERB_PRESET_CONCERTHAL;
+extern const EAXREVERBPROPERTIES EAXREVERB_PRESET_CAVE;
+extern const EAXREVERBPROPERTIES EAXREVERB_PRESET_ARENA;
+extern const EAXREVERBPROPERTIES EAXREVERB_PRESET_HANGAR;
+extern const EAXREVERBPROPERTIES EAXREVERB_PRESET_CARPETTEDHALLWAY;
+extern const EAXREVERBPROPERTIES EAXREVERB_PRESET_HALLWAY;
+extern const EAXREVERBPROPERTIES EAXREVERB_PRESET_STONECORRIDOR;
+extern const EAXREVERBPROPERTIES EAXREVERB_PRESET_ALLEY;
+extern const EAXREVERBPROPERTIES EAXREVERB_PRESET_FOREST;
+extern const EAXREVERBPROPERTIES EAXREVERB_PRESET_CITY;
+extern const EAXREVERBPROPERTIES EAXREVERB_PRESET_MOUNTAINS;
+extern const EAXREVERBPROPERTIES EAXREVERB_PRESET_QUARRY;
+extern const EAXREVERBPROPERTIES EAXREVERB_PRESET_PLAIN;
+extern const EAXREVERBPROPERTIES EAXREVERB_PRESET_PARKINGLOT;
+extern const EAXREVERBPROPERTIES EAXREVERB_PRESET_SEWERPIPE;
+extern const EAXREVERBPROPERTIES EAXREVERB_PRESET_UNDERWATER;
+extern const EAXREVERBPROPERTIES EAXREVERB_PRESET_DRUGGED;
+extern const EAXREVERBPROPERTIES EAXREVERB_PRESET_DIZZY;
+extern const EAXREVERBPROPERTIES EAXREVERB_PRESET_PSYCHOTIC;
+
+
+using EaxReverbPresets = std::array<EAXREVERBPROPERTIES, EAX_ENVIRONMENT_UNDEFINED>;
+extern const EaxReverbPresets EAXREVERB_PRESETS;
+
+
+// AGC Compressor Effect
+
+extern "C" const GUID EAX_AGCCOMPRESSOR_EFFECT;
+
+enum EAXAGCCOMPRESSOR_PROPERTY :
+ unsigned int
+{
+ EAXAGCCOMPRESSOR_NONE,
+ EAXAGCCOMPRESSOR_ALLPARAMETERS,
+ EAXAGCCOMPRESSOR_ONOFF,
+}; // EAXAGCCOMPRESSOR_PROPERTY
+
+struct EAXAGCCOMPRESSORPROPERTIES
+{
+ unsigned long ulOnOff; // Switch Compressor on or off
+}; // EAXAGCCOMPRESSORPROPERTIES
+
+
+constexpr auto EAXAGCCOMPRESSOR_MINONOFF = 0UL;
+constexpr auto EAXAGCCOMPRESSOR_MAXONOFF = 1UL;
+constexpr auto EAXAGCCOMPRESSOR_DEFAULTONOFF = EAXAGCCOMPRESSOR_MAXONOFF;
+
+
+// Autowah Effect
+
+extern "C" const GUID EAX_AUTOWAH_EFFECT;
+
+enum EAXAUTOWAH_PROPERTY :
+ unsigned int
+{
+ EAXAUTOWAH_NONE,
+ EAXAUTOWAH_ALLPARAMETERS,
+ EAXAUTOWAH_ATTACKTIME,
+ EAXAUTOWAH_RELEASETIME,
+ EAXAUTOWAH_RESONANCE,
+ EAXAUTOWAH_PEAKLEVEL,
+}; // EAXAUTOWAH_PROPERTY
+
+struct EAXAUTOWAHPROPERTIES
+{
+ float flAttackTime; // Attack time (seconds)
+ float flReleaseTime; // Release time (seconds)
+ long lResonance; // Resonance (mB)
+ long lPeakLevel; // Peak level (mB)
+}; // EAXAUTOWAHPROPERTIES
+
+
+constexpr auto EAXAUTOWAH_MINATTACKTIME = 0.0001F;
+constexpr auto EAXAUTOWAH_MAXATTACKTIME = 1.0F;
+constexpr auto EAXAUTOWAH_DEFAULTATTACKTIME = 0.06F;
+
+constexpr auto EAXAUTOWAH_MINRELEASETIME = 0.0001F;
+constexpr auto EAXAUTOWAH_MAXRELEASETIME = 1.0F;
+constexpr auto EAXAUTOWAH_DEFAULTRELEASETIME = 0.06F;
+
+constexpr auto EAXAUTOWAH_MINRESONANCE = 600L;
+constexpr auto EAXAUTOWAH_MAXRESONANCE = 6000L;
+constexpr auto EAXAUTOWAH_DEFAULTRESONANCE = 6000L;
+
+constexpr auto EAXAUTOWAH_MINPEAKLEVEL = -9000L;
+constexpr auto EAXAUTOWAH_MAXPEAKLEVEL = 9000L;
+constexpr auto EAXAUTOWAH_DEFAULTPEAKLEVEL = 2100L;
+
+
+// Chorus Effect
+
+extern "C" const GUID EAX_CHORUS_EFFECT;
+
+
+enum EAXCHORUS_PROPERTY :
+ unsigned int
+{
+ EAXCHORUS_NONE,
+ EAXCHORUS_ALLPARAMETERS,
+ EAXCHORUS_WAVEFORM,
+ EAXCHORUS_PHASE,
+ EAXCHORUS_RATE,
+ EAXCHORUS_DEPTH,
+ EAXCHORUS_FEEDBACK,
+ EAXCHORUS_DELAY,
+}; // EAXCHORUS_PROPERTY
+
+enum :
+ unsigned long
+{
+ EAX_CHORUS_SINUSOID,
+ EAX_CHORUS_TRIANGLE,
+};
+
+struct EAXCHORUSPROPERTIES
+{
+ unsigned long ulWaveform; // Waveform selector - see enum above
+ long lPhase; // Phase (Degrees)
+ float flRate; // Rate (Hz)
+ float flDepth; // Depth (0 to 1)
+ float flFeedback; // Feedback (-1 to 1)
+ float flDelay; // Delay (seconds)
+}; // EAXCHORUSPROPERTIES
+
+
+constexpr auto EAXCHORUS_MINWAVEFORM = 0UL;
+constexpr auto EAXCHORUS_MAXWAVEFORM = 1UL;
+constexpr auto EAXCHORUS_DEFAULTWAVEFORM = 1UL;
+
+constexpr auto EAXCHORUS_MINPHASE = -180L;
+constexpr auto EAXCHORUS_MAXPHASE = 180L;
+constexpr auto EAXCHORUS_DEFAULTPHASE = 90L;
+
+constexpr auto EAXCHORUS_MINRATE = 0.0F;
+constexpr auto EAXCHORUS_MAXRATE = 10.0F;
+constexpr auto EAXCHORUS_DEFAULTRATE = 1.1F;
+
+constexpr auto EAXCHORUS_MINDEPTH = 0.0F;
+constexpr auto EAXCHORUS_MAXDEPTH = 1.0F;
+constexpr auto EAXCHORUS_DEFAULTDEPTH = 0.1F;
+
+constexpr auto EAXCHORUS_MINFEEDBACK = -1.0F;
+constexpr auto EAXCHORUS_MAXFEEDBACK = 1.0F;
+constexpr auto EAXCHORUS_DEFAULTFEEDBACK = 0.25F;
+
+constexpr auto EAXCHORUS_MINDELAY = 0.0002F;
+constexpr auto EAXCHORUS_MAXDELAY = 0.016F;
+constexpr auto EAXCHORUS_DEFAULTDELAY = 0.016F;
+
+
+// Distortion Effect
+
+extern "C" const GUID EAX_DISTORTION_EFFECT;
+
+enum EAXDISTORTION_PROPERTY :
+ unsigned int
+{
+ EAXDISTORTION_NONE,
+ EAXDISTORTION_ALLPARAMETERS,
+ EAXDISTORTION_EDGE,
+ EAXDISTORTION_GAIN,
+ EAXDISTORTION_LOWPASSCUTOFF,
+ EAXDISTORTION_EQCENTER,
+ EAXDISTORTION_EQBANDWIDTH,
+}; // EAXDISTORTION_PROPERTY
+
+
+struct EAXDISTORTIONPROPERTIES
+{
+ float flEdge; // Controls the shape of the distortion (0 to 1)
+ long lGain; // Controls the post distortion gain (mB)
+ float flLowPassCutOff; // Controls the cut-off of the filter pre-distortion (Hz)
+ float flEQCenter; // Controls the center frequency of the EQ post-distortion (Hz)
+ float flEQBandwidth; // Controls the bandwidth of the EQ post-distortion (Hz)
+}; // EAXDISTORTIONPROPERTIES
+
+
+constexpr auto EAXDISTORTION_MINEDGE = 0.0F;
+constexpr auto EAXDISTORTION_MAXEDGE = 1.0F;
+constexpr auto EAXDISTORTION_DEFAULTEDGE = 0.2F;
+
+constexpr auto EAXDISTORTION_MINGAIN = -6000L;
+constexpr auto EAXDISTORTION_MAXGAIN = 0L;
+constexpr auto EAXDISTORTION_DEFAULTGAIN = -2600L;
+
+constexpr auto EAXDISTORTION_MINLOWPASSCUTOFF = 80.0F;
+constexpr auto EAXDISTORTION_MAXLOWPASSCUTOFF = 24000.0F;
+constexpr auto EAXDISTORTION_DEFAULTLOWPASSCUTOFF = 8000.0F;
+
+constexpr auto EAXDISTORTION_MINEQCENTER = 80.0F;
+constexpr auto EAXDISTORTION_MAXEQCENTER = 24000.0F;
+constexpr auto EAXDISTORTION_DEFAULTEQCENTER = 3600.0F;
+
+constexpr auto EAXDISTORTION_MINEQBANDWIDTH = 80.0F;
+constexpr auto EAXDISTORTION_MAXEQBANDWIDTH = 24000.0F;
+constexpr auto EAXDISTORTION_DEFAULTEQBANDWIDTH = 3600.0F;
+
+
+// Echo Effect
+
+extern "C" const GUID EAX_ECHO_EFFECT;
+
+
+enum EAXECHO_PROPERTY :
+ unsigned int
+{
+ EAXECHO_NONE,
+ EAXECHO_ALLPARAMETERS,
+ EAXECHO_DELAY,
+ EAXECHO_LRDELAY,
+ EAXECHO_DAMPING,
+ EAXECHO_FEEDBACK,
+ EAXECHO_SPREAD,
+}; // EAXECHO_PROPERTY
+
+
+struct EAXECHOPROPERTIES
+{
+ float flDelay; // Controls the initial delay time (seconds)
+ float flLRDelay; // Controls the delay time between the first and second taps (seconds)
+ float flDamping; // Controls a low-pass filter that dampens the echoes (0 to 1)
+ float flFeedback; // Controls the duration of echo repetition (0 to 1)
+ float flSpread; // Controls the left-right spread of the echoes
+}; // EAXECHOPROPERTIES
+
+
+constexpr auto EAXECHO_MINDAMPING = 0.0F;
+constexpr auto EAXECHO_MAXDAMPING = 0.99F;
+constexpr auto EAXECHO_DEFAULTDAMPING = 0.5F;
+
+constexpr auto EAXECHO_MINDELAY = 0.002F;
+constexpr auto EAXECHO_MAXDELAY = 0.207F;
+constexpr auto EAXECHO_DEFAULTDELAY = 0.1F;
+
+constexpr auto EAXECHO_MINLRDELAY = 0.0F;
+constexpr auto EAXECHO_MAXLRDELAY = 0.404F;
+constexpr auto EAXECHO_DEFAULTLRDELAY = 0.1F;
+
+constexpr auto EAXECHO_MINFEEDBACK = 0.0F;
+constexpr auto EAXECHO_MAXFEEDBACK = 1.0F;
+constexpr auto EAXECHO_DEFAULTFEEDBACK = 0.5F;
+
+constexpr auto EAXECHO_MINSPREAD = -1.0F;
+constexpr auto EAXECHO_MAXSPREAD = 1.0F;
+constexpr auto EAXECHO_DEFAULTSPREAD = -1.0F;
+
+
+// Equalizer Effect
+
+extern "C" const GUID EAX_EQUALIZER_EFFECT;
+
+
+enum EAXEQUALIZER_PROPERTY :
+ unsigned int
+{
+ EAXEQUALIZER_NONE,
+ EAXEQUALIZER_ALLPARAMETERS,
+ EAXEQUALIZER_LOWGAIN,
+ EAXEQUALIZER_LOWCUTOFF,
+ EAXEQUALIZER_MID1GAIN,
+ EAXEQUALIZER_MID1CENTER,
+ EAXEQUALIZER_MID1WIDTH,
+ EAXEQUALIZER_MID2GAIN,
+ EAXEQUALIZER_MID2CENTER,
+ EAXEQUALIZER_MID2WIDTH,
+ EAXEQUALIZER_HIGHGAIN,
+ EAXEQUALIZER_HIGHCUTOFF,
+}; // EAXEQUALIZER_PROPERTY
+
+
+struct EAXEQUALIZERPROPERTIES
+{
+ long lLowGain; // (mB)
+ float flLowCutOff; // (Hz)
+ long lMid1Gain; // (mB)
+ float flMid1Center; // (Hz)
+ float flMid1Width; // (octaves)
+ long lMid2Gain; // (mB)
+ float flMid2Center; // (Hz)
+ float flMid2Width; // (octaves)
+ long lHighGain; // (mB)
+ float flHighCutOff; // (Hz)
+}; // EAXEQUALIZERPROPERTIES
+
+
+constexpr auto EAXEQUALIZER_MINLOWGAIN = -1800L;
+constexpr auto EAXEQUALIZER_MAXLOWGAIN = 1800L;
+constexpr auto EAXEQUALIZER_DEFAULTLOWGAIN = 0L;
+
+constexpr auto EAXEQUALIZER_MINLOWCUTOFF = 50.0F;
+constexpr auto EAXEQUALIZER_MAXLOWCUTOFF = 800.0F;
+constexpr auto EAXEQUALIZER_DEFAULTLOWCUTOFF = 200.0F;
+
+constexpr auto EAXEQUALIZER_MINMID1GAIN = -1800L;
+constexpr auto EAXEQUALIZER_MAXMID1GAIN = 1800L;
+constexpr auto EAXEQUALIZER_DEFAULTMID1GAIN = 0L;
+
+constexpr auto EAXEQUALIZER_MINMID1CENTER = 200.0F;
+constexpr auto EAXEQUALIZER_MAXMID1CENTER = 3000.0F;
+constexpr auto EAXEQUALIZER_DEFAULTMID1CENTER = 500.0F;
+
+constexpr auto EAXEQUALIZER_MINMID1WIDTH = 0.01F;
+constexpr auto EAXEQUALIZER_MAXMID1WIDTH = 1.0F;
+constexpr auto EAXEQUALIZER_DEFAULTMID1WIDTH = 1.0F;
+
+constexpr auto EAXEQUALIZER_MINMID2GAIN = -1800L;
+constexpr auto EAXEQUALIZER_MAXMID2GAIN = 1800L;
+constexpr auto EAXEQUALIZER_DEFAULTMID2GAIN = 0L;
+
+constexpr auto EAXEQUALIZER_MINMID2CENTER = 1000.0F;
+constexpr auto EAXEQUALIZER_MAXMID2CENTER = 8000.0F;
+constexpr auto EAXEQUALIZER_DEFAULTMID2CENTER = 3000.0F;
+
+constexpr auto EAXEQUALIZER_MINMID2WIDTH = 0.01F;
+constexpr auto EAXEQUALIZER_MAXMID2WIDTH = 1.0F;
+constexpr auto EAXEQUALIZER_DEFAULTMID2WIDTH = 1.0F;
+
+constexpr auto EAXEQUALIZER_MINHIGHGAIN = -1800L;
+constexpr auto EAXEQUALIZER_MAXHIGHGAIN = 1800L;
+constexpr auto EAXEQUALIZER_DEFAULTHIGHGAIN = 0L;
+
+constexpr auto EAXEQUALIZER_MINHIGHCUTOFF = 4000.0F;
+constexpr auto EAXEQUALIZER_MAXHIGHCUTOFF = 16000.0F;
+constexpr auto EAXEQUALIZER_DEFAULTHIGHCUTOFF = 6000.0F;
+
+
+// Flanger Effect
+
+extern "C" const GUID EAX_FLANGER_EFFECT;
+
+enum EAXFLANGER_PROPERTY :
+ unsigned int
+{
+ EAXFLANGER_NONE,
+ EAXFLANGER_ALLPARAMETERS,
+ EAXFLANGER_WAVEFORM,
+ EAXFLANGER_PHASE,
+ EAXFLANGER_RATE,
+ EAXFLANGER_DEPTH,
+ EAXFLANGER_FEEDBACK,
+ EAXFLANGER_DELAY,
+}; // EAXFLANGER_PROPERTY
+
+enum :
+ unsigned long
+{
+ EAX_FLANGER_SINUSOID,
+ EAX_FLANGER_TRIANGLE,
+};
+
+struct EAXFLANGERPROPERTIES
+{
+ unsigned long ulWaveform; // Waveform selector - see enum above
+ long lPhase; // Phase (Degrees)
+ float flRate; // Rate (Hz)
+ float flDepth; // Depth (0 to 1)
+ float flFeedback; // Feedback (0 to 1)
+ float flDelay; // Delay (seconds)
+}; // EAXFLANGERPROPERTIES
+
+
+constexpr auto EAXFLANGER_MINWAVEFORM = 0UL;
+constexpr auto EAXFLANGER_MAXWAVEFORM = 1UL;
+constexpr auto EAXFLANGER_DEFAULTWAVEFORM = 1UL;
+
+constexpr auto EAXFLANGER_MINPHASE = -180L;
+constexpr auto EAXFLANGER_MAXPHASE = 180L;
+constexpr auto EAXFLANGER_DEFAULTPHASE = 0L;
+
+constexpr auto EAXFLANGER_MINRATE = 0.0F;
+constexpr auto EAXFLANGER_MAXRATE = 10.0F;
+constexpr auto EAXFLANGER_DEFAULTRATE = 0.27F;
+
+constexpr auto EAXFLANGER_MINDEPTH = 0.0F;
+constexpr auto EAXFLANGER_MAXDEPTH = 1.0F;
+constexpr auto EAXFLANGER_DEFAULTDEPTH = 1.0F;
+
+constexpr auto EAXFLANGER_MINFEEDBACK = -1.0F;
+constexpr auto EAXFLANGER_MAXFEEDBACK = 1.0F;
+constexpr auto EAXFLANGER_DEFAULTFEEDBACK = -0.5F;
+
+constexpr auto EAXFLANGER_MINDELAY = 0.0002F;
+constexpr auto EAXFLANGER_MAXDELAY = 0.004F;
+constexpr auto EAXFLANGER_DEFAULTDELAY = 0.002F;
+
+
+// Frequency Shifter Effect
+
+extern "C" const GUID EAX_FREQUENCYSHIFTER_EFFECT;
+
+enum EAXFREQUENCYSHIFTER_PROPERTY :
+ unsigned int
+{
+ EAXFREQUENCYSHIFTER_NONE,
+ EAXFREQUENCYSHIFTER_ALLPARAMETERS,
+ EAXFREQUENCYSHIFTER_FREQUENCY,
+ EAXFREQUENCYSHIFTER_LEFTDIRECTION,
+ EAXFREQUENCYSHIFTER_RIGHTDIRECTION,
+}; // EAXFREQUENCYSHIFTER_PROPERTY
+
+enum :
+ unsigned long
+{
+ EAX_FREQUENCYSHIFTER_DOWN,
+ EAX_FREQUENCYSHIFTER_UP,
+ EAX_FREQUENCYSHIFTER_OFF
+};
+
+struct EAXFREQUENCYSHIFTERPROPERTIES
+{
+ float flFrequency; // (Hz)
+ unsigned long ulLeftDirection; // see enum above
+ unsigned long ulRightDirection; // see enum above
+}; // EAXFREQUENCYSHIFTERPROPERTIES
+
+
+constexpr auto EAXFREQUENCYSHIFTER_MINFREQUENCY = 0.0F;
+constexpr auto EAXFREQUENCYSHIFTER_MAXFREQUENCY = 24000.0F;
+constexpr auto EAXFREQUENCYSHIFTER_DEFAULTFREQUENCY = EAXFREQUENCYSHIFTER_MINFREQUENCY;
+
+constexpr auto EAXFREQUENCYSHIFTER_MINLEFTDIRECTION = 0UL;
+constexpr auto EAXFREQUENCYSHIFTER_MAXLEFTDIRECTION = 2UL;
+constexpr auto EAXFREQUENCYSHIFTER_DEFAULTLEFTDIRECTION = EAXFREQUENCYSHIFTER_MINLEFTDIRECTION;
+
+constexpr auto EAXFREQUENCYSHIFTER_MINRIGHTDIRECTION = 0UL;
+constexpr auto EAXFREQUENCYSHIFTER_MAXRIGHTDIRECTION = 2UL;
+constexpr auto EAXFREQUENCYSHIFTER_DEFAULTRIGHTDIRECTION = EAXFREQUENCYSHIFTER_MINRIGHTDIRECTION;
+
+
+// Vocal Morpher Effect
+
+extern "C" const GUID EAX_VOCALMORPHER_EFFECT;
+
+enum EAXVOCALMORPHER_PROPERTY :
+ unsigned int
+{
+ EAXVOCALMORPHER_NONE,
+ EAXVOCALMORPHER_ALLPARAMETERS,
+ EAXVOCALMORPHER_PHONEMEA,
+ EAXVOCALMORPHER_PHONEMEACOARSETUNING,
+ EAXVOCALMORPHER_PHONEMEB,
+ EAXVOCALMORPHER_PHONEMEBCOARSETUNING,
+ EAXVOCALMORPHER_WAVEFORM,
+ EAXVOCALMORPHER_RATE,
+}; // EAXVOCALMORPHER_PROPERTY
+
+enum :
+ unsigned long
+{
+ A,
+ E,
+ I,
+ O,
+ U,
+ AA,
+ AE,
+ AH,
+ AO,
+ EH,
+ ER,
+ IH,
+ IY,
+ UH,
+ UW,
+ B,
+ D,
+ F,
+ G,
+ J,
+ K,
+ L,
+ M,
+ N,
+ P,
+ R,
+ S,
+ T,
+ V,
+ Z,
+};
+
+enum :
+ unsigned long
+{
+ EAX_VOCALMORPHER_SINUSOID,
+ EAX_VOCALMORPHER_TRIANGLE,
+ EAX_VOCALMORPHER_SAWTOOTH
+};
+
+// Use this structure for EAXVOCALMORPHER_ALLPARAMETERS
+struct EAXVOCALMORPHERPROPERTIES
+{
+ unsigned long ulPhonemeA; // see enum above
+ long lPhonemeACoarseTuning; // (semitones)
+ unsigned long ulPhonemeB; // see enum above
+ long lPhonemeBCoarseTuning; // (semitones)
+ unsigned long ulWaveform; // Waveform selector - see enum above
+ float flRate; // (Hz)
+}; // EAXVOCALMORPHERPROPERTIES
+
+
+constexpr auto EAXVOCALMORPHER_MINPHONEMEA = 0UL;
+constexpr auto EAXVOCALMORPHER_MAXPHONEMEA = 29UL;
+constexpr auto EAXVOCALMORPHER_DEFAULTPHONEMEA = EAXVOCALMORPHER_MINPHONEMEA;
+
+constexpr auto EAXVOCALMORPHER_MINPHONEMEACOARSETUNING = -24L;
+constexpr auto EAXVOCALMORPHER_MAXPHONEMEACOARSETUNING = 24L;
+constexpr auto EAXVOCALMORPHER_DEFAULTPHONEMEACOARSETUNING = 0L;
+
+constexpr auto EAXVOCALMORPHER_MINPHONEMEB = 0UL;
+constexpr auto EAXVOCALMORPHER_MAXPHONEMEB = 29UL;
+constexpr auto EAXVOCALMORPHER_DEFAULTPHONEMEB = 10UL;
+
+constexpr auto EAXVOCALMORPHER_MINPHONEMEBCOARSETUNING = -24L;
+constexpr auto EAXVOCALMORPHER_MAXPHONEMEBCOARSETUNING = 24L;
+constexpr auto EAXVOCALMORPHER_DEFAULTPHONEMEBCOARSETUNING = 0L;
+
+constexpr auto EAXVOCALMORPHER_MINWAVEFORM = 0UL;
+constexpr auto EAXVOCALMORPHER_MAXWAVEFORM = 2UL;
+constexpr auto EAXVOCALMORPHER_DEFAULTWAVEFORM = EAXVOCALMORPHER_MINWAVEFORM;
+
+constexpr auto EAXVOCALMORPHER_MINRATE = 0.0F;
+constexpr auto EAXVOCALMORPHER_MAXRATE = 10.0F;
+constexpr auto EAXVOCALMORPHER_DEFAULTRATE = 1.41F;
+
+
+// Pitch Shifter Effect
+
+extern "C" const GUID EAX_PITCHSHIFTER_EFFECT;
+
+enum EAXPITCHSHIFTER_PROPERTY :
+ unsigned int
+{
+ EAXPITCHSHIFTER_NONE,
+ EAXPITCHSHIFTER_ALLPARAMETERS,
+ EAXPITCHSHIFTER_COARSETUNE,
+ EAXPITCHSHIFTER_FINETUNE,
+}; // EAXPITCHSHIFTER_PROPERTY
+
+struct EAXPITCHSHIFTERPROPERTIES
+{
+ long lCoarseTune; // Amount of pitch shift (semitones)
+ long lFineTune; // Amount of pitch shift (cents)
+}; // EAXPITCHSHIFTERPROPERTIES
+
+
+constexpr auto EAXPITCHSHIFTER_MINCOARSETUNE = -12L;
+constexpr auto EAXPITCHSHIFTER_MAXCOARSETUNE = 12L;
+constexpr auto EAXPITCHSHIFTER_DEFAULTCOARSETUNE = 12L;
+
+constexpr auto EAXPITCHSHIFTER_MINFINETUNE = -50L;
+constexpr auto EAXPITCHSHIFTER_MAXFINETUNE = 50L;
+constexpr auto EAXPITCHSHIFTER_DEFAULTFINETUNE = 0L;
+
+
+// Ring Modulator Effect
+
+extern "C" const GUID EAX_RINGMODULATOR_EFFECT;
+
+enum EAXRINGMODULATOR_PROPERTY :
+ unsigned int
+{
+ EAXRINGMODULATOR_NONE,
+ EAXRINGMODULATOR_ALLPARAMETERS,
+ EAXRINGMODULATOR_FREQUENCY,
+ EAXRINGMODULATOR_HIGHPASSCUTOFF,
+ EAXRINGMODULATOR_WAVEFORM,
+}; // EAXRINGMODULATOR_PROPERTY
+
+enum :
+ unsigned long
+{
+ EAX_RINGMODULATOR_SINUSOID,
+ EAX_RINGMODULATOR_SAWTOOTH,
+ EAX_RINGMODULATOR_SQUARE,
+};
+
+// Use this structure for EAXRINGMODULATOR_ALLPARAMETERS
+struct EAXRINGMODULATORPROPERTIES
+{
+ float flFrequency; // Frequency of modulation (Hz)
+ float flHighPassCutOff; // Cut-off frequency of high-pass filter (Hz)
+ unsigned long ulWaveform; // Waveform selector - see enum above
+}; // EAXRINGMODULATORPROPERTIES
+
+
+constexpr auto EAXRINGMODULATOR_MINFREQUENCY = 0.0F;
+constexpr auto EAXRINGMODULATOR_MAXFREQUENCY = 8000.0F;
+constexpr auto EAXRINGMODULATOR_DEFAULTFREQUENCY = 440.0F;
+
+constexpr auto EAXRINGMODULATOR_MINHIGHPASSCUTOFF = 0.0F;
+constexpr auto EAXRINGMODULATOR_MAXHIGHPASSCUTOFF = 24000.0F;
+constexpr auto EAXRINGMODULATOR_DEFAULTHIGHPASSCUTOFF = 800.0F;
+
+constexpr auto EAXRINGMODULATOR_MINWAVEFORM = 0UL;
+constexpr auto EAXRINGMODULATOR_MAXWAVEFORM = 2UL;
+constexpr auto EAXRINGMODULATOR_DEFAULTWAVEFORM = EAXRINGMODULATOR_MINWAVEFORM;
+
+
+using LPEAXSET = ALenum(AL_APIENTRY*)(
+ const GUID* property_set_id,
+ ALuint property_id,
+ ALuint property_source_id,
+ ALvoid* property_buffer,
+ ALuint property_size);
+
+using LPEAXGET = ALenum(AL_APIENTRY*)(
+ const GUID* property_set_id,
+ ALuint property_id,
+ ALuint property_source_id,
+ ALvoid* property_buffer,
+ ALuint property_size);
+
+
+#endif // !EAX_API_INCLUDED
diff --git a/al/eax_eax_call.cpp b/al/eax_eax_call.cpp
new file mode 100644
index 00000000..e6967ded
--- /dev/null
+++ b/al/eax_eax_call.cpp
@@ -0,0 +1,370 @@
+#include "al/eax_eax_call.h"
+
+#include "al/eax_exception.h"
+
+
+namespace
+{
+
+
+class EaxEaxCallException :
+ public EaxException
+{
+public:
+ explicit EaxEaxCallException(
+ const char* message)
+ :
+ EaxException{"EAX_EAX_CALL", message}
+ {
+ }
+}; // EaxEaxCallException
+
+
+} // namespace
+
+
+EaxEaxCall::EaxEaxCall(
+ bool is_get,
+ const GUID* property_set_id,
+ ALuint property_id,
+ ALuint property_source_id,
+ ALvoid* property_buffer,
+ ALuint property_size)
+{
+ if (!property_set_id)
+ {
+ fail("Null property set ID.");
+ }
+
+ is_get_ = is_get;
+
+ constexpr auto deferred_flag = 0x80000000U;
+ is_deferred_ = ((property_id & 0x80000000U) != 0);
+
+ version_ = 0;
+ fx_slot_index_.reset();
+ property_set_id_ = EaxEaxCallPropertySetId::none;
+
+ property_set_guid_ = *property_set_id;
+ property_id_ = property_id & (~deferred_flag);
+ property_source_id_ = property_source_id;
+ property_buffer_ = property_buffer;
+ property_size_ = property_size;
+
+ if (false)
+ {
+ }
+ else if (property_set_guid_ == EAXPROPERTYID_EAX40_Context)
+ {
+ version_ = 4;
+ property_set_id_ = EaxEaxCallPropertySetId::context;
+ }
+ else if (property_set_guid_ == EAXPROPERTYID_EAX50_Context)
+ {
+ version_ = 5;
+ property_set_id_ = EaxEaxCallPropertySetId::context;
+ }
+ else if (property_set_guid_ == DSPROPSETID_EAX20_ListenerProperties)
+ {
+ version_ = 2;
+ fx_slot_index_ = 0;
+ property_set_id_ = EaxEaxCallPropertySetId::fx_slot_effect;
+ property_id_ = convert_eax_v2_0_listener_property_id(property_id_);
+ }
+ else if (property_set_guid_ == DSPROPSETID_EAX30_ListenerProperties)
+ {
+ version_ = 3;
+ fx_slot_index_ = 0;
+ property_set_id_ = EaxEaxCallPropertySetId::fx_slot_effect;
+ }
+ else if (property_set_guid_ == EAXPROPERTYID_EAX40_FXSlot0)
+ {
+ version_ = 4;
+ fx_slot_index_ = 0;
+ property_set_id_ = EaxEaxCallPropertySetId::fx_slot;
+ }
+ else if (property_set_guid_ == EAXPROPERTYID_EAX50_FXSlot0)
+ {
+ version_ = 5;
+ fx_slot_index_ = 0;
+ property_set_id_ = EaxEaxCallPropertySetId::fx_slot;
+ }
+ else if (property_set_guid_ == EAXPROPERTYID_EAX40_FXSlot1)
+ {
+ version_ = 4;
+ fx_slot_index_ = 1;
+ property_set_id_ = EaxEaxCallPropertySetId::fx_slot;
+ }
+ else if (property_set_guid_ == EAXPROPERTYID_EAX50_FXSlot1)
+ {
+ version_ = 5;
+ fx_slot_index_ = 1;
+ property_set_id_ = EaxEaxCallPropertySetId::fx_slot;
+ }
+ else if (property_set_guid_ == EAXPROPERTYID_EAX40_FXSlot2)
+ {
+ version_ = 4;
+ fx_slot_index_ = 2;
+ property_set_id_ = EaxEaxCallPropertySetId::fx_slot;
+ }
+ else if (property_set_guid_ == EAXPROPERTYID_EAX50_FXSlot2)
+ {
+ version_ = 5;
+ fx_slot_index_ = 2;
+ property_set_id_ = EaxEaxCallPropertySetId::fx_slot;
+ }
+ else if (property_set_guid_ == EAXPROPERTYID_EAX40_FXSlot3)
+ {
+ version_ = 4;
+ fx_slot_index_ = 3;
+ property_set_id_ = EaxEaxCallPropertySetId::fx_slot;
+ }
+ else if (property_set_guid_ == EAXPROPERTYID_EAX50_FXSlot3)
+ {
+ version_ = 5;
+ fx_slot_index_ = 3;
+ property_set_id_ = EaxEaxCallPropertySetId::fx_slot;
+ }
+ else if (property_set_guid_ == DSPROPSETID_EAX20_BufferProperties)
+ {
+ version_ = 2;
+ property_set_id_ = EaxEaxCallPropertySetId::source;
+ property_id_ = convert_eax_v2_0_buffer_property_id(property_id_);
+ }
+ else if (property_set_guid_ == DSPROPSETID_EAX30_BufferProperties)
+ {
+ version_ = 3;
+ property_set_id_ = EaxEaxCallPropertySetId::source;
+ }
+ else if (property_set_guid_ == EAXPROPERTYID_EAX40_Source)
+ {
+ version_ = 4;
+ property_set_id_ = EaxEaxCallPropertySetId::source;
+ }
+ else if (property_set_guid_ == EAXPROPERTYID_EAX50_Source)
+ {
+ version_ = 5;
+ property_set_id_ = EaxEaxCallPropertySetId::source;
+ }
+ else
+ {
+ fail("Unsupported property set id.");
+ }
+
+ if (version_ < 2 || version_ > 5)
+ {
+ fail("EAX version out of range.");
+ }
+
+ if (is_deferred_)
+ {
+ if (is_get_)
+ {
+ fail("Getting deferred properties not supported.");
+ }
+ }
+ else
+ {
+ if (property_set_id_ != EaxEaxCallPropertySetId::fx_slot &&
+ property_id_ != 0)
+ {
+ if (!property_buffer)
+ {
+ fail("Null property buffer.");
+ }
+
+ if (property_size == 0)
+ {
+ fail("Empty property.");
+ }
+ }
+ }
+
+ if (property_set_id_ == EaxEaxCallPropertySetId::source &&
+ property_source_id_ == 0)
+ {
+ fail("Null AL source id.");
+ }
+
+ if (property_set_id_ == EaxEaxCallPropertySetId::fx_slot)
+ {
+ if (property_id_ < EAXFXSLOT_NONE)
+ {
+ property_set_id_ = EaxEaxCallPropertySetId::fx_slot_effect;
+ }
+ }
+}
+
+bool EaxEaxCall::is_get() const noexcept
+{
+ return is_get_;
+}
+
+bool EaxEaxCall::is_deferred() const noexcept
+{
+ return is_deferred_;
+}
+
+int EaxEaxCall::get_version() const noexcept
+{
+ return version_;
+}
+
+EaxEaxCallPropertySetId EaxEaxCall::get_property_set_id() const noexcept
+{
+ return property_set_id_;
+}
+
+ALuint EaxEaxCall::get_property_id() const noexcept
+{
+ return property_id_;
+}
+
+ALuint EaxEaxCall::get_property_al_name() const noexcept
+{
+ return property_source_id_;
+}
+
+EaxFxSlotIndex EaxEaxCall::get_fx_slot_index() const noexcept
+{
+ return fx_slot_index_;
+}
+
+[[noreturn]]
+void EaxEaxCall::fail(
+ const char* message)
+{
+ throw EaxEaxCallException{message};
+}
+
+ALuint EaxEaxCall::convert_eax_v2_0_listener_property_id(
+ ALuint property_id)
+{
+ switch (property_id)
+ {
+ case DSPROPERTY_EAX20LISTENER_NONE:
+ return EAXREVERB_NONE;
+
+ case DSPROPERTY_EAX20LISTENER_ALLPARAMETERS:
+ return EAXREVERB_ALLPARAMETERS;
+
+ case DSPROPERTY_EAX20LISTENER_ROOM:
+ return EAXREVERB_ROOM;
+
+ case DSPROPERTY_EAX20LISTENER_ROOMHF:
+ return EAXREVERB_ROOMHF;
+
+ case DSPROPERTY_EAX20LISTENER_ROOMROLLOFFFACTOR:
+ return EAXREVERB_ROOMROLLOFFFACTOR;
+
+ case DSPROPERTY_EAX20LISTENER_DECAYTIME:
+ return EAXREVERB_DECAYTIME;
+
+ case DSPROPERTY_EAX20LISTENER_DECAYHFRATIO:
+ return EAXREVERB_DECAYHFRATIO;
+
+ case DSPROPERTY_EAX20LISTENER_REFLECTIONS:
+ return EAXREVERB_REFLECTIONS;
+
+ case DSPROPERTY_EAX20LISTENER_REFLECTIONSDELAY:
+ return EAXREVERB_REFLECTIONSDELAY;
+
+ case DSPROPERTY_EAX20LISTENER_REVERB:
+ return EAXREVERB_REVERB;
+
+ case DSPROPERTY_EAX20LISTENER_REVERBDELAY:
+ return EAXREVERB_REVERBDELAY;
+
+ case DSPROPERTY_EAX20LISTENER_ENVIRONMENT:
+ return EAXREVERB_ENVIRONMENT;
+
+ case DSPROPERTY_EAX20LISTENER_ENVIRONMENTSIZE:
+ return EAXREVERB_ENVIRONMENTSIZE;
+
+ case DSPROPERTY_EAX20LISTENER_ENVIRONMENTDIFFUSION:
+ return EAXREVERB_ENVIRONMENTDIFFUSION;
+
+ case DSPROPERTY_EAX20LISTENER_AIRABSORPTIONHF:
+ return EAXREVERB_AIRABSORPTIONHF;
+
+ case DSPROPERTY_EAX20LISTENER_FLAGS:
+ return EAXREVERB_FLAGS;
+
+ default:
+ fail("Unsupported EAX 2.0 listener property id.");
+ }
+}
+
+ALuint EaxEaxCall::convert_eax_v2_0_buffer_property_id(
+ ALuint property_id)
+{
+ switch (property_id)
+ {
+ case DSPROPERTY_EAX20BUFFER_NONE:
+ return EAXSOURCE_NONE;
+
+ case DSPROPERTY_EAX20BUFFER_ALLPARAMETERS:
+ return EAXSOURCE_ALLPARAMETERS;
+
+ case DSPROPERTY_EAX20BUFFER_DIRECT:
+ return EAXSOURCE_DIRECT;
+
+ case DSPROPERTY_EAX20BUFFER_DIRECTHF:
+ return EAXSOURCE_DIRECTHF;
+
+ case DSPROPERTY_EAX20BUFFER_ROOM:
+ return EAXSOURCE_ROOM;
+
+ case DSPROPERTY_EAX20BUFFER_ROOMHF:
+ return EAXSOURCE_ROOMHF;
+
+ case DSPROPERTY_EAX20BUFFER_ROOMROLLOFFFACTOR:
+ return EAXSOURCE_ROOMROLLOFFFACTOR;
+
+ case DSPROPERTY_EAX20BUFFER_OBSTRUCTION:
+ return EAXSOURCE_OBSTRUCTION;
+
+ case DSPROPERTY_EAX20BUFFER_OBSTRUCTIONLFRATIO:
+ return EAXSOURCE_OBSTRUCTIONLFRATIO;
+
+ case DSPROPERTY_EAX20BUFFER_OCCLUSION:
+ return EAXSOURCE_OCCLUSION;
+
+ case DSPROPERTY_EAX20BUFFER_OCCLUSIONLFRATIO:
+ return EAXSOURCE_OCCLUSIONLFRATIO;
+
+ case DSPROPERTY_EAX20BUFFER_OCCLUSIONROOMRATIO:
+ return EAXSOURCE_OCCLUSIONROOMRATIO;
+
+ case DSPROPERTY_EAX20BUFFER_OUTSIDEVOLUMEHF:
+ return EAXSOURCE_OUTSIDEVOLUMEHF;
+
+ case DSPROPERTY_EAX20BUFFER_AIRABSORPTIONFACTOR:
+ return EAXSOURCE_AIRABSORPTIONFACTOR;
+
+ case DSPROPERTY_EAX20BUFFER_FLAGS:
+ return EAXSOURCE_FLAGS;
+
+ default:
+ fail("Unsupported EAX 2.0 buffer property id.");
+ }
+}
+
+
+EaxEaxCall create_eax_call(
+ bool is_get,
+ const GUID* property_set_id,
+ ALuint property_id,
+ ALuint property_source_id,
+ ALvoid* property_buffer,
+ ALuint property_size)
+{
+ return EaxEaxCall{
+ is_get,
+ property_set_id,
+ property_id,
+ property_source_id,
+ property_buffer,
+ property_size
+ };
+}
diff --git a/al/eax_eax_call.h b/al/eax_eax_call.h
new file mode 100644
index 00000000..ec632b7d
--- /dev/null
+++ b/al/eax_eax_call.h
@@ -0,0 +1,128 @@
+#ifndef EAX_EAX_CALL_INCLUDED
+#define EAX_EAX_CALL_INCLUDED
+
+
+#include "AL/al.h"
+
+#include "alspan.h"
+
+#include "eax_api.h"
+#include "eax_fx_slot_index.h"
+
+
+enum class EaxEaxCallPropertySetId
+{
+ none,
+
+ context,
+ fx_slot,
+ source,
+ fx_slot_effect,
+}; // EaxEaxCallPropertySetId
+
+
+class EaxEaxCall
+{
+public:
+ EaxEaxCall(
+ bool is_get,
+ const GUID* property_set_id,
+ ALuint property_id,
+ ALuint property_source_id,
+ ALvoid* property_buffer,
+ ALuint property_size);
+
+
+ bool is_get() const noexcept;
+
+ bool is_deferred() const noexcept;
+
+ int get_version() const noexcept;
+
+ EaxEaxCallPropertySetId get_property_set_id() const noexcept;
+
+ ALuint get_property_id() const noexcept;
+
+ ALuint get_property_al_name() const noexcept;
+
+ EaxFxSlotIndex get_fx_slot_index() const noexcept;
+
+
+ template<
+ typename TException,
+ typename TValue
+ >
+ TValue& get_value() const
+ {
+ if (property_size_ < static_cast<ALuint>(sizeof(TValue)))
+ {
+ throw TException{"Property buffer too small."};
+ }
+
+ return *static_cast<TValue*>(property_buffer_);
+ }
+
+ template<
+ typename TException,
+ typename TValue
+ >
+ al::span<TValue> get_values() const
+ {
+ if (property_size_ < static_cast<ALuint>(sizeof(TValue)))
+ {
+ throw TException{"Property buffer too small."};
+ }
+
+ const auto count = property_size_ / sizeof(TValue);
+
+ return al::span<TValue>{static_cast<TValue*>(property_buffer_), count};
+ }
+
+ template<
+ typename TException,
+ typename TValue
+ >
+ void set_value(
+ const TValue& value) const
+ {
+ get_value<TException, TValue>() = value;
+ }
+
+
+private:
+ bool is_get_;
+ bool is_deferred_;
+ int version_;
+ EaxFxSlotIndex fx_slot_index_;
+ EaxEaxCallPropertySetId property_set_id_;
+
+ GUID property_set_guid_;
+ ALuint property_id_;
+ ALuint property_source_id_;
+ ALvoid* property_buffer_;
+ ALuint property_size_;
+
+
+ [[noreturn]]
+ static void fail(
+ const char* message);
+
+
+ static ALuint convert_eax_v2_0_listener_property_id(
+ ALuint property_id);
+
+ static ALuint convert_eax_v2_0_buffer_property_id(
+ ALuint property_id);
+}; // EaxEaxCall
+
+
+EaxEaxCall create_eax_call(
+ bool is_get,
+ const GUID* property_set_id,
+ ALuint property_id,
+ ALuint property_source_id,
+ ALvoid* property_buffer,
+ ALuint property_size);
+
+
+#endif // !EAX_EAX_CALL_INCLUDED
diff --git a/al/eax_effect.cpp b/al/eax_effect.cpp
new file mode 100644
index 00000000..72af94fc
--- /dev/null
+++ b/al/eax_effect.cpp
@@ -0,0 +1 @@
+#include "eax_effect.h"
diff --git a/al/eax_effect.h b/al/eax_effect.h
new file mode 100644
index 00000000..23dbb73e
--- /dev/null
+++ b/al/eax_effect.h
@@ -0,0 +1,28 @@
+#ifndef EAX_EFFECT_INCLUDED
+#define EAX_EFFECT_INCLUDED
+
+
+#include <memory>
+
+#include "eax_eax_call.h"
+
+
+class EaxEffect
+{
+public:
+ EaxEffect() = default;
+
+ virtual ~EaxEffect() = default;
+
+
+ // Returns "true" if any immediated property was changed.
+ // [[nodiscard]]
+ virtual bool dispatch(
+ const EaxEaxCall& eax_call) = 0;
+}; // EaxEffect
+
+
+using EaxEffectUPtr = std::unique_ptr<EaxEffect>;
+
+
+#endif // !EAX_EFFECT_INCLUDED
diff --git a/al/eax_exception.cpp b/al/eax_exception.cpp
new file mode 100644
index 00000000..e3635793
--- /dev/null
+++ b/al/eax_exception.cpp
@@ -0,0 +1,61 @@
+#include "eax_exception.h"
+
+#include <cassert>
+
+#include <string>
+
+
+EaxException::EaxException(
+ const char* context,
+ const char* message)
+ :
+ std::runtime_error{make_message(context, message)}
+{
+}
+
+std::string EaxException::make_message(
+ const char* context,
+ const char* message)
+{
+ const auto context_size = (context ? std::string::traits_type::length(context) : 0);
+ const auto has_contex = (context_size > 0);
+
+ const auto message_size = (message ? std::string::traits_type::length(message) : 0);
+ const auto has_message = (message_size > 0);
+
+ if (!has_contex && !has_message)
+ {
+ return std::string{};
+ }
+
+ constexpr auto left_prefix = "[";
+ const auto left_prefix_size = std::string::traits_type::length(left_prefix);
+
+ constexpr auto right_prefix = "] ";
+ const auto right_prefix_size = std::string::traits_type::length(right_prefix);
+
+ const auto what_size =
+ (
+ has_contex ?
+ left_prefix_size + context_size + right_prefix_size :
+ 0) +
+ message_size +
+ 1;
+
+ auto what = std::string{};
+ what.reserve(what_size);
+
+ if (has_contex)
+ {
+ what.append(left_prefix, left_prefix_size);
+ what.append(context, context_size);
+ what.append(right_prefix, right_prefix_size);
+ }
+
+ if (has_message)
+ {
+ what.append(message, message_size);
+ }
+
+ return what;
+}
diff --git a/al/eax_exception.h b/al/eax_exception.h
new file mode 100644
index 00000000..9a7acf71
--- /dev/null
+++ b/al/eax_exception.h
@@ -0,0 +1,25 @@
+#ifndef EAX_EXCEPTION_INCLUDED
+#define EAX_EXCEPTION_INCLUDED
+
+
+#include <stdexcept>
+#include <string>
+
+
+class EaxException :
+ public std::runtime_error
+{
+public:
+ EaxException(
+ const char* context,
+ const char* message);
+
+
+private:
+ static std::string make_message(
+ const char* context,
+ const char* message);
+}; // EaxException
+
+
+#endif // !EAX_EXCEPTION_INCLUDED
diff --git a/al/eax_fx_slot_index.cpp b/al/eax_fx_slot_index.cpp
new file mode 100644
index 00000000..dffaef47
--- /dev/null
+++ b/al/eax_fx_slot_index.cpp
@@ -0,0 +1,164 @@
+#include "eax_fx_slot_index.h"
+
+#include "eax_exception.h"
+
+
+namespace
+{
+
+
+class EaxFxSlotIndexException :
+ public EaxException
+{
+public:
+ explicit EaxFxSlotIndexException(
+ const char* message)
+ :
+ EaxException{"EAX_FX_SLOT_INDEX", message}
+ {
+ }
+}; // EaxFxSlotIndexException
+
+
+} // namespace
+
+
+EaxFxSlotIndex::EaxFxSlotIndex(
+ EaxFxSlotIndexValue index)
+{
+ set(index);
+}
+
+EaxFxSlotIndex::EaxFxSlotIndex(
+ const EaxFxSlotIndex& rhs) noexcept
+ :
+ has_value_{rhs.has_value_},
+ value_{rhs.value_}
+{
+}
+
+void EaxFxSlotIndex::operator=(
+ EaxFxSlotIndexValue index)
+{
+ set(index);
+}
+
+void EaxFxSlotIndex::operator=(
+ const GUID& guid)
+{
+ set(guid);
+}
+
+void EaxFxSlotIndex::operator=(
+ const EaxFxSlotIndex& rhs) noexcept
+{
+ has_value_ = rhs.has_value_;
+ value_ = rhs.value_;
+}
+
+bool EaxFxSlotIndex::has_value() const noexcept
+{
+ return has_value_;
+}
+
+EaxFxSlotIndexValue EaxFxSlotIndex::get() const
+{
+ if (!has_value_)
+ {
+ throw EaxFxSlotIndexException{"No value."};
+ }
+
+ return value_;
+}
+
+void EaxFxSlotIndex::reset() noexcept
+{
+ has_value_ = false;
+}
+
+void EaxFxSlotIndex::set(
+ EaxFxSlotIndexValue index)
+{
+ if (index >= static_cast<EaxFxSlotIndexValue>(EAX_MAX_FXSLOTS))
+ {
+ fail("Index out of range.");
+ }
+
+ has_value_ = true;
+ value_ = index;
+}
+
+void EaxFxSlotIndex::set(
+ const GUID& guid)
+{
+ if (false)
+ {
+ }
+ else if (guid == EAX_NULL_GUID)
+ {
+ has_value_ = false;
+ }
+ else if (guid == EAXPROPERTYID_EAX40_FXSlot0 || guid == EAXPROPERTYID_EAX50_FXSlot0)
+ {
+ has_value_ = true;
+ value_ = 0;
+ }
+ else if (guid == EAXPROPERTYID_EAX40_FXSlot1 || guid == EAXPROPERTYID_EAX50_FXSlot1)
+ {
+ has_value_ = true;
+ value_ = 1;
+ }
+ else if (guid == EAXPROPERTYID_EAX40_FXSlot2 || guid == EAXPROPERTYID_EAX50_FXSlot2)
+ {
+ has_value_ = true;
+ value_ = 2;
+ }
+ else if (guid == EAXPROPERTYID_EAX40_FXSlot3 || guid == EAXPROPERTYID_EAX50_FXSlot3)
+ {
+ has_value_ = true;
+ value_ = 3;
+ }
+ else
+ {
+ fail("Unsupported GUID.");
+ }
+}
+
+EaxFxSlotIndex::operator EaxFxSlotIndexValue() const
+{
+ return get();
+}
+
+[[noreturn]]
+void EaxFxSlotIndex::fail(
+ const char* message)
+{
+ throw EaxFxSlotIndexException{message};
+}
+
+
+bool operator==(
+ const EaxFxSlotIndex& lhs,
+ const EaxFxSlotIndex& rhs) noexcept
+{
+ if (lhs.has_value() != rhs.has_value())
+ {
+ return false;
+ }
+
+ if (lhs.has_value())
+ {
+ return lhs.get() == rhs.get();
+ }
+ else
+ {
+ return true;
+ }
+}
+
+bool operator!=(
+ const EaxFxSlotIndex& lhs,
+ const EaxFxSlotIndex& rhs) noexcept
+{
+ return !(lhs == rhs);
+}
diff --git a/al/eax_fx_slot_index.h b/al/eax_fx_slot_index.h
new file mode 100644
index 00000000..95ff9313
--- /dev/null
+++ b/al/eax_fx_slot_index.h
@@ -0,0 +1,69 @@
+#ifndef EAX_FX_SLOT_INDEX_INCLUDED
+#define EAX_FX_SLOT_INDEX_INCLUDED
+
+
+#include <cstddef>
+
+#include "eax_api.h"
+
+
+using EaxFxSlotIndexValue = std::size_t;
+
+
+class EaxFxSlotIndex
+{
+public:
+ EaxFxSlotIndex() noexcept = default;
+
+ EaxFxSlotIndex(
+ EaxFxSlotIndexValue index);
+
+ EaxFxSlotIndex(
+ const EaxFxSlotIndex& rhs) noexcept;
+
+ void operator=(
+ EaxFxSlotIndexValue index);
+
+ void operator=(
+ const GUID& guid);
+
+ void operator=(
+ const EaxFxSlotIndex& rhs) noexcept;
+
+
+ bool has_value() const noexcept;
+
+ EaxFxSlotIndexValue get() const;
+
+ void reset() noexcept;
+
+ void set(
+ EaxFxSlotIndexValue index);
+
+ void set(
+ const GUID& guid);
+
+ operator EaxFxSlotIndexValue() const;
+
+
+private:
+ [[noreturn]]
+ static void fail(
+ const char* message);
+
+
+ bool has_value_{};
+ EaxFxSlotIndexValue value_{};
+}; // EaxFxSlotIndex
+
+
+bool operator==(
+ const EaxFxSlotIndex& lhs,
+ const EaxFxSlotIndex& rhs) noexcept;
+
+bool operator!=(
+ const EaxFxSlotIndex& lhs,
+ const EaxFxSlotIndex& rhs) noexcept;
+
+
+#endif // !EAX_FX_SLOT_INDEX_INCLUDED
diff --git a/al/eax_fx_slots.cpp b/al/eax_fx_slots.cpp
new file mode 100644
index 00000000..41b18f77
--- /dev/null
+++ b/al/eax_fx_slots.cpp
@@ -0,0 +1,81 @@
+#include "eax_fx_slots.h"
+
+#include <array>
+
+#include "eax_exception.h"
+
+#include "eax_api.h"
+
+
+namespace
+{
+
+
+class EaxFxSlotsException :
+ public EaxException
+{
+public:
+ explicit EaxFxSlotsException(
+ const char* message)
+ :
+ EaxException{"EAX_FX_SLOTS", message}
+ {
+ }
+}; // EaxFxSlotsException
+
+
+} // namespace
+
+
+void EaxFxSlots::initialize(
+ ALCcontext& al_context)
+{
+ initialize_fx_slots(al_context);
+}
+
+void EaxFxSlots::uninitialize() noexcept
+{
+ for (auto& fx_slot : fx_slots_)
+ {
+ fx_slot->eax_uninitialize();
+ fx_slot = nullptr;
+ }
+}
+
+const ALeffectslot& EaxFxSlots::get(
+ EaxFxSlotIndex index) const
+{
+ if (!index.has_value())
+ {
+ fail("Empty index.");
+ }
+
+ return *fx_slots_[index.get()];
+}
+
+ALeffectslot& EaxFxSlots::get(
+ EaxFxSlotIndex index)
+{
+ return const_cast<ALeffectslot&>(const_cast<const EaxFxSlots*>(this)->get(index));
+}
+
+
+[[noreturn]]
+void EaxFxSlots::fail(
+ const char* message)
+{
+ throw EaxFxSlotsException{message};
+}
+
+void EaxFxSlots::initialize_fx_slots(
+ ALCcontext& al_context)
+{
+ auto fx_slot_index = EaxFxSlotIndexValue{};
+
+ for (auto& fx_slot : fx_slots_)
+ {
+ fx_slot = eax_create_al_effect_slot(al_context);
+ fx_slot->eax_initialize(al_context, fx_slot_index);
+ fx_slot_index += 1;
+ }
+}
diff --git a/al/eax_fx_slots.h b/al/eax_fx_slots.h
new file mode 100644
index 00000000..6abe61b6
--- /dev/null
+++ b/al/eax_fx_slots.h
@@ -0,0 +1,46 @@
+#ifndef EAX_FX_SLOTS_INCLUDED
+#define EAX_FX_SLOTS_INCLUDED
+
+
+#include <array>
+
+#include "al/auxeffectslot.h"
+
+#include "eax_api.h"
+
+#include "eax_fx_slot_index.h"
+
+
+class EaxFxSlots
+{
+public:
+ void initialize(
+ ALCcontext& al_context);
+
+ void uninitialize() noexcept;
+
+
+ const ALeffectslot& get(
+ EaxFxSlotIndex index) const;
+
+ ALeffectslot& get(
+ EaxFxSlotIndex index);
+
+
+private:
+ using Items = std::array<EaxAlEffectSlotUPtr, EAX_MAX_FXSLOTS>;
+
+
+ Items fx_slots_{};
+
+
+ [[noreturn]]
+ static void fail(
+ const char* message);
+
+ void initialize_fx_slots(
+ ALCcontext& al_context);
+}; // EaxFxSlots
+
+
+#endif // !EAX_FX_SLOTS_INCLUDED
diff --git a/al/eax_globals.cpp b/al/eax_globals.cpp
new file mode 100644
index 00000000..e2f4681e
--- /dev/null
+++ b/al/eax_globals.cpp
@@ -0,0 +1,18 @@
+#include "eax_globals.h"
+
+
+bool eax_g_is_enabled = true;
+
+
+const char* eax_v2_0_ext_name = "EAX2.0";
+const char* eax_v3_0_ext_name = "EAX3.0";
+const char* eax_v4_0_ext_name = "EAX4.0";
+const char* eax_v5_0_ext_name = "EAX5.0";
+
+const char* eax_x_ram_ext_name = "EAX-RAM";
+
+const char* eax_eax_set_func_name = "EAXSet";
+const char* eax_eax_get_func_name = "EAXGet";
+
+const char* eax_eax_set_buffer_mode_func_name = "EAXSetBufferMode";
+const char* eax_eax_get_buffer_mode_func_name = "EAXGetBufferMode";
diff --git a/al/eax_globals.h b/al/eax_globals.h
new file mode 100644
index 00000000..dd2434ed
--- /dev/null
+++ b/al/eax_globals.h
@@ -0,0 +1,22 @@
+#ifndef EAX_GLOBALS_INCLUDED
+#define EAX_GLOBALS_INCLUDED
+
+
+extern bool eax_g_is_enabled;
+
+
+extern const char* eax_v2_0_ext_name;
+extern const char* eax_v3_0_ext_name;
+extern const char* eax_v4_0_ext_name;
+extern const char* eax_v5_0_ext_name;
+
+extern const char* eax_x_ram_ext_name;
+
+extern const char* eax_eax_set_func_name;
+extern const char* eax_eax_get_func_name;
+
+extern const char* eax_eax_set_buffer_mode_func_name;
+extern const char* eax_eax_get_buffer_mode_func_name;
+
+
+#endif // !EAX_GLOBALS_INCLUDED
diff --git a/al/eax_utils.cpp b/al/eax_utils.cpp
new file mode 100644
index 00000000..9a8f04f1
--- /dev/null
+++ b/al/eax_utils.cpp
@@ -0,0 +1,35 @@
+#include "eax_utils.h"
+
+#include <cassert>
+
+#include <exception>
+
+#include "core/logging.h"
+
+
+void eax_log_exception(
+ const char* message) noexcept
+{
+ const auto exception_ptr = std::current_exception();
+
+ assert(exception_ptr);
+
+ if (message)
+ {
+ ERR("%s\n", message);
+ }
+
+ try
+ {
+ std::rethrow_exception(exception_ptr);
+ }
+ catch (const std::exception& ex)
+ {
+ const auto ex_message = ex.what();
+ ERR("%s\n", ex_message);
+ }
+ catch (...)
+ {
+ ERR("%s\n", "Generic exception.");
+ }
+}
diff --git a/al/eax_utils.h b/al/eax_utils.h
new file mode 100644
index 00000000..68e35fcd
--- /dev/null
+++ b/al/eax_utils.h
@@ -0,0 +1,164 @@
+#ifndef EAX_UTILS_INCLUDED
+#define EAX_UTILS_INCLUDED
+
+
+#include <cstdint>
+
+#include <algorithm>
+#include <string>
+#include <type_traits>
+
+
+struct ALCcontext;
+
+
+struct EaxAlLowPassParam
+{
+ float gain;
+ float gain_hf;
+}; // EaxAlLowPassParam
+
+
+// Required to call macro `DO_UPDATEPROPS`.
+class EaxAlContextWrapper
+{
+public:
+ explicit EaxAlContextWrapper(
+ ALCcontext& al_context) noexcept
+ :
+ al_context_{&al_context}
+ {
+ }
+
+ constexpr ALCcontext* get() noexcept
+ {
+ return al_context_;
+ }
+
+ constexpr ALCcontext* operator->() noexcept
+ {
+ return al_context_;
+ }
+
+
+private:
+ ALCcontext* al_context_;
+}; // EaxAlContextWrapper
+
+
+void eax_log_exception(
+ const char* message = nullptr) noexcept;
+
+
+template<
+ typename TException,
+ typename TValue
+>
+void eax_validate_range(
+ const char* value_name,
+ const TValue& value,
+ const TValue& min_value,
+ const TValue& max_value)
+{
+ if (value >= min_value && value <= max_value)
+ {
+ return;
+ }
+
+ const auto message =
+ std::string{value_name} +
+ " out of range (value: " +
+ std::to_string(value) + "; min: " +
+ std::to_string(min_value) + "; max: " +
+ std::to_string(max_value) + ").";
+
+ throw TException{message.c_str()};
+}
+
+
+namespace detail
+{
+
+
+template<
+ typename T
+>
+struct EaxIsBitFieldStruct
+{
+private:
+ using yes = std::true_type;
+ using no = std::false_type;
+
+ template<
+ typename U
+ >
+ static auto test(int) -> decltype(std::declval<typename U::EaxIsBitFieldStruct>(), yes{});
+
+ template<
+ typename
+ >
+ static no test(...);
+
+
+public:
+ static constexpr auto value = std::is_same<decltype(test<T>(0)), yes>::value;
+}; // EaxIsBitFieldStruct
+
+
+template<
+ typename T,
+ typename TValue
+>
+inline bool eax_bit_fields_are_equal(
+ const T& lhs,
+ const T& rhs) noexcept
+{
+ static_assert(sizeof(T) == sizeof(TValue), "Invalid type size.");
+
+ return reinterpret_cast<const TValue&>(lhs) == reinterpret_cast<const TValue&>(rhs);
+}
+
+
+} // namespace detail
+
+
+template<
+ typename T,
+ std::enable_if_t<detail::EaxIsBitFieldStruct<T>::value, int> = 0
+>
+inline bool operator==(
+ const T& lhs,
+ const T& rhs) noexcept
+{
+ using Value = std::conditional_t<
+ sizeof(T) == 1,
+ std::uint8_t,
+ std::conditional_t<
+ sizeof(T) == 2,
+ std::uint16_t,
+ std::conditional_t<
+ sizeof(T) == 4,
+ std::uint32_t,
+ void
+ >
+ >
+ >;
+
+ static_assert(!std::is_same<Value, void>::value, "Unsupported type.");
+
+ return detail::eax_bit_fields_are_equal<T, Value>(lhs, rhs);
+}
+
+template<
+ typename T,
+ std::enable_if_t<detail::EaxIsBitFieldStruct<T>::value, int> = 0
+>
+inline bool operator!=(
+ const T& lhs,
+ const T& rhs) noexcept
+{
+ return !(lhs == rhs);
+}
+
+
+#endif // !EAX_UTILS_INCLUDED
diff --git a/al/eax_x_ram.cpp b/al/eax_x_ram.cpp
new file mode 100644
index 00000000..d11a03ab
--- /dev/null
+++ b/al/eax_x_ram.cpp
@@ -0,0 +1 @@
+#include "eax_x_ram.h"
diff --git a/al/eax_x_ram.h b/al/eax_x_ram.h
new file mode 100644
index 00000000..438b9916
--- /dev/null
+++ b/al/eax_x_ram.h
@@ -0,0 +1,38 @@
+#ifndef EAX_X_RAM_INCLUDED
+#define EAX_X_RAM_INCLUDED
+
+
+#include "AL/al.h"
+
+
+constexpr auto eax_x_ram_min_size = ALsizei{};
+constexpr auto eax_x_ram_max_size = ALsizei{64 * 1'024 * 1'024};
+
+
+constexpr auto AL_EAX_RAM_SIZE = ALenum{0x202201};
+constexpr auto AL_EAX_RAM_FREE = ALenum{0x202202};
+
+constexpr auto AL_STORAGE_AUTOMATIC = ALenum{0x202203};
+constexpr auto AL_STORAGE_HARDWARE = ALenum{0x202204};
+constexpr auto AL_STORAGE_ACCESSIBLE = ALenum{0x202205};
+
+
+constexpr auto AL_EAX_RAM_SIZE_NAME = "AL_EAX_RAM_SIZE";
+constexpr auto AL_EAX_RAM_FREE_NAME = "AL_EAX_RAM_FREE";
+
+constexpr auto AL_STORAGE_AUTOMATIC_NAME = "AL_STORAGE_AUTOMATIC";
+constexpr auto AL_STORAGE_HARDWARE_NAME = "AL_STORAGE_HARDWARE";
+constexpr auto AL_STORAGE_ACCESSIBLE_NAME = "AL_STORAGE_ACCESSIBLE";
+
+
+ALboolean AL_APIENTRY EAXSetBufferMode(
+ ALsizei n,
+ const ALuint* buffers,
+ ALint value);
+
+ALenum AL_APIENTRY EAXGetBufferMode(
+ ALuint buffer,
+ ALint* pReserved);
+
+
+#endif // !EAX_X_RAM_INCLUDED
diff --git a/al/effect.cpp b/al/effect.cpp
index 217cc1c2..79cd7fab 100644
--- a/al/effect.cpp
+++ b/al/effect.cpp
@@ -51,6 +51,11 @@
#include "opthelpers.h"
#include "vector.h"
+#if ALSOFT_EAX
+#include <cassert>
+
+#include "eax_exception.h"
+#endif // ALSOFT_EAX
const EffectList gEffectList[16]{
{ "eaxreverb", EAXREVERB_EFFECT, AL_EFFECT_EAXREVERB },
@@ -745,3 +750,141 @@ void LoadReverbPreset(const char *name, ALeffect *effect)
WARN("Reverb preset '%s' not found\n", name);
}
+
+#if ALSOFT_EAX
+namespace
+{
+
+class EaxAlEffectException :
+ public EaxException
+{
+public:
+ explicit EaxAlEffectException(
+ const char* message)
+ :
+ EaxException{"[EAX_AL_EFFECT]", message}
+ {
+ }
+}; // EaxAlEffectException
+
+
+} // namespace
+
+
+void ALeffect::eax_initialize()
+{
+ eax_effect = nullptr;
+ eax_effect = eax_create_eax_effect(type, Props);
+}
+
+void ALeffect::eax_al_set_effect(
+ ALenum al_effect_type)
+{
+ if (al_effect_type != AL_EFFECT_NULL)
+ {
+ auto has_effect = false;
+
+ for (const auto &effect_item : gEffectList)
+ {
+ if (al_effect_type == effect_item.val && !DisabledEffects[effect_item.type])
+ {
+ has_effect = true;
+ break;
+ }
+ }
+
+ if (!has_effect)
+ {
+ eax_fail("Effect not available.");
+ }
+ }
+
+ InitEffectParams(this, al_effect_type);
+}
+
+[[noreturn]]
+void ALeffect::eax_fail(
+ const char* message)
+{
+ throw EaxAlEffectException{message};
+}
+
+EaxAlEffectDeleter::EaxAlEffectDeleter(
+ ALCcontext& context) noexcept
+ :
+ context_{&context}
+{
+}
+
+void EaxAlEffectDeleter::operator()(
+ ALeffect* effect) const
+{
+ assert(effect);
+
+ eax_al_delete_effect(*context_, *effect);
+}
+
+EaxAlEffectUPtr eax_create_al_effect(
+ ALCcontext& context,
+ ALenum effect_type)
+{
+#define EAX_PREFIX "[EAX_MAKE_EFFECT] "
+
+ auto& device = *context.mALDevice;
+ std::lock_guard<std::mutex> effect_lock{device.EffectLock};
+
+ // Allocate.
+ //
+ if (!EnsureEffects(&device, 1))
+ {
+ ERR(EAX_PREFIX "%s\n", "Failed to ensure.");
+ return nullptr;
+ }
+
+ auto effect = EaxAlEffectUPtr{AllocEffect(&device), EaxAlEffectDeleter{context}};
+
+ if (!effect)
+ {
+ ERR(EAX_PREFIX "%s\n", "Failed to allocate.");
+ return nullptr;
+ }
+
+ // Set the type.
+ //
+ auto is_supported = (effect_type == AL_EFFECT_NULL);
+
+ if (!is_supported)
+ {
+ for (const auto& effect_item : gEffectList)
+ {
+ if(effect_type == effect_item.val && !DisabledEffects[effect_item.type])
+ {
+ is_supported = true;
+ break;
+ }
+ }
+ }
+
+ if (!is_supported)
+ {
+ ERR(EAX_PREFIX "Effect type 0x%04x not supported.\n", effect_type);
+ return nullptr;
+ }
+
+ InitEffectParams(effect.get(), effect_type);
+
+ return effect;
+
+#undef EAX_PREFIX
+}
+
+void eax_al_delete_effect(
+ ALCcontext& context,
+ ALeffect& effect)
+{
+ auto& device = *context.mALDevice;
+ std::lock_guard<std::mutex> effect_lock{device.EffectLock};
+
+ FreeEffect(&device, &effect);
+}
+#endif // ALSOFT_EAX
diff --git a/al/effect.h b/al/effect.h
index 10a692c9..5b5e4b03 100644
--- a/al/effect.h
+++ b/al/effect.h
@@ -7,6 +7,12 @@
#include "al/effects/effects.h"
#include "alc/effects/base.h"
+#if ALSOFT_EAX
+#include <memory>
+
+#include "eax_effect.h"
+#endif // ALSOFT_EAX
+
enum {
EAXREVERB_EFFECT = 0,
@@ -51,10 +57,57 @@ struct ALeffect {
ALuint id{0u};
DISABLE_ALLOC()
+
+
+#if ALSOFT_EAX
+public:
+ EaxEffectUPtr eax_effect{};
+
+
+ void eax_initialize();
+
+ void eax_al_set_effect(
+ ALenum al_effect_type);
+
+
+private:
+ [[noreturn]]
+ static void eax_fail(
+ const char* message);
+#endif // ALSOFT_EAX
};
void InitEffect(ALeffect *effect);
void LoadReverbPreset(const char *name, ALeffect *effect);
+#if ALSOFT_EAX
+class EaxAlEffectDeleter
+{
+public:
+ EaxAlEffectDeleter() noexcept = default;
+
+ EaxAlEffectDeleter(
+ ALCcontext& context) noexcept;
+
+ void operator()(
+ ALeffect* effect) const;
+
+
+private:
+ ALCcontext* context_{};
+}; // EaxAlEffectDeleter
+
+using EaxAlEffectUPtr = std::unique_ptr<ALeffect, EaxAlEffectDeleter>;
+
+
+EaxAlEffectUPtr eax_create_al_effect(
+ ALCcontext& context,
+ ALenum effect_type);
+
+void eax_al_delete_effect(
+ ALCcontext& context,
+ ALeffect& effect);
+#endif // ALSOFT_EAX
+
#endif
diff --git a/al/effects/autowah.cpp b/al/effects/autowah.cpp
index 9abef1f7..bdd1bc09 100644
--- a/al/effects/autowah.cpp
+++ b/al/effects/autowah.cpp
@@ -11,6 +11,14 @@
#include "alc/effects/base.h"
#include "effects.h"
+#if ALSOFT_EAX
+#include "alnumeric.h"
+
+#include "al/eax_exception.h"
+#include "al/eax_utils.h"
+#endif // ALSOFT_EAX
+
+
namespace {
void Autowah_setParamf(EffectProps *props, ALenum param, float val)
@@ -107,3 +115,466 @@ EffectProps genDefaultProps() noexcept
DEFINE_ALEFFECT_VTABLE(Autowah);
const EffectProps AutowahEffectProps{genDefaultProps()};
+
+#if ALSOFT_EAX
+namespace
+{
+
+
+using EaxAutoWahEffectDirtyFlagsValue = std::uint_least8_t;
+
+struct EaxAutoWahEffectDirtyFlags
+{
+ using EaxIsBitFieldStruct = bool;
+
+ EaxAutoWahEffectDirtyFlagsValue flAttackTime : 1;
+ EaxAutoWahEffectDirtyFlagsValue flReleaseTime : 1;
+ EaxAutoWahEffectDirtyFlagsValue lResonance : 1;
+ EaxAutoWahEffectDirtyFlagsValue lPeakLevel : 1;
+}; // EaxAutoWahEffectDirtyFlags
+
+
+class EaxAutoWahEffect final :
+ public EaxEffect
+{
+public:
+ EaxAutoWahEffect(
+ EffectProps& al_effect_props);
+
+
+ // [[nodiscard]]
+ bool dispatch(
+ const EaxEaxCall& eax_call) override;
+
+
+private:
+ EffectProps& al_effect_props_;
+
+ EAXAUTOWAHPROPERTIES eax_{};
+ EAXAUTOWAHPROPERTIES eax_d_{};
+ EaxAutoWahEffectDirtyFlags eax_dirty_flags_{};
+
+
+ void set_eax_defaults();
+
+
+ void set_efx_attack_time();
+
+ void set_efx_release_time();
+
+ void set_efx_resonance();
+
+ void set_efx_peak_gain();
+
+ void set_efx_defaults();
+
+
+ // [[nodiscard]]
+ bool get(
+ const EaxEaxCall& eax_call);
+
+
+ void validate_attack_time(
+ float flAttackTime);
+
+ void validate_release_time(
+ float flReleaseTime);
+
+ void validate_resonance(
+ long lResonance);
+
+ void validate_peak_level(
+ long lPeakLevel);
+
+ void validate_all(
+ const EAXAUTOWAHPROPERTIES& eax_all);
+
+
+ void defer_attack_time(
+ float flAttackTime);
+
+ void defer_release_time(
+ float flReleaseTime);
+
+ void defer_resonance(
+ long lResonance);
+
+ void defer_peak_level(
+ long lPeakLevel);
+
+ void defer_all(
+ const EAXAUTOWAHPROPERTIES& eax_all);
+
+
+ void defer_attack_time(
+ const EaxEaxCall& eax_call);
+
+ void defer_release_time(
+ const EaxEaxCall& eax_call);
+
+ void defer_resonance(
+ const EaxEaxCall& eax_call);
+
+ void defer_peak_level(
+ const EaxEaxCall& eax_call);
+
+ void defer_all(
+ const EaxEaxCall& eax_call);
+
+ // [[nodiscard]]
+ bool apply_deferred();
+
+ // [[nodiscard]]
+ bool set(
+ const EaxEaxCall& eax_call);
+}; // EaxAutoWahEffect
+
+
+class EaxAutoWahEffectException :
+ public EaxException
+{
+public:
+ explicit EaxAutoWahEffectException(
+ const char* message)
+ :
+ EaxException{"EAX_AUTO_WAH_EFFECT", message}
+ {
+ }
+}; // EaxAutoWahEffectException
+
+
+EaxAutoWahEffect::EaxAutoWahEffect(
+ EffectProps& al_effect_props)
+ :
+ al_effect_props_{al_effect_props}
+{
+ set_eax_defaults();
+ set_efx_defaults();
+}
+
+// [[nodiscard]]
+bool EaxAutoWahEffect::dispatch(
+ const EaxEaxCall& eax_call)
+{
+ return eax_call.is_get() ? get(eax_call) : set(eax_call);
+}
+
+void EaxAutoWahEffect::set_eax_defaults()
+{
+ eax_.flAttackTime = EAXAUTOWAH_DEFAULTATTACKTIME;
+ eax_.flReleaseTime = EAXAUTOWAH_DEFAULTRELEASETIME;
+ eax_.lResonance = EAXAUTOWAH_DEFAULTRESONANCE;
+ eax_.lPeakLevel = EAXAUTOWAH_DEFAULTPEAKLEVEL;
+
+ eax_d_ = eax_;
+}
+
+void EaxAutoWahEffect::set_efx_attack_time()
+{
+ const auto attack_time = clamp(
+ eax_.flAttackTime,
+ AL_AUTOWAH_MIN_ATTACK_TIME,
+ AL_AUTOWAH_MAX_ATTACK_TIME);
+
+ al_effect_props_.Autowah.AttackTime = attack_time;
+}
+
+void EaxAutoWahEffect::set_efx_release_time()
+{
+ const auto release_time = clamp(
+ eax_.flReleaseTime,
+ AL_AUTOWAH_MIN_RELEASE_TIME,
+ AL_AUTOWAH_MAX_RELEASE_TIME);
+
+ al_effect_props_.Autowah.ReleaseTime = release_time;
+}
+
+void EaxAutoWahEffect::set_efx_resonance()
+{
+ const auto resonance = clamp(
+ level_mb_to_gain(static_cast<float>(eax_.lResonance)),
+ AL_AUTOWAH_MIN_RESONANCE,
+ AL_AUTOWAH_MAX_RESONANCE);
+
+ al_effect_props_.Autowah.Resonance = resonance;
+}
+
+void EaxAutoWahEffect::set_efx_peak_gain()
+{
+ const auto peak_gain = clamp(
+ level_mb_to_gain(static_cast<float>(eax_.lPeakLevel)),
+ AL_AUTOWAH_MIN_PEAK_GAIN,
+ AL_AUTOWAH_MAX_PEAK_GAIN);
+
+ al_effect_props_.Autowah.PeakGain = peak_gain;
+}
+
+void EaxAutoWahEffect::set_efx_defaults()
+{
+ set_efx_attack_time();
+ set_efx_release_time();
+ set_efx_resonance();
+ set_efx_peak_gain();
+}
+
+bool EaxAutoWahEffect::get(
+ const EaxEaxCall& eax_call)
+{
+ switch (eax_call.get_property_id())
+ {
+ case EAXAUTOWAH_NONE:
+ break;
+
+ case EAXAUTOWAH_ALLPARAMETERS:
+ eax_call.set_value<EaxAutoWahEffectException>(eax_);
+ break;
+
+ case EAXAUTOWAH_ATTACKTIME:
+ eax_call.set_value<EaxAutoWahEffectException>(eax_.flAttackTime);
+ break;
+
+ case EAXAUTOWAH_RELEASETIME:
+ eax_call.set_value<EaxAutoWahEffectException>(eax_.flReleaseTime);
+ break;
+
+ case EAXAUTOWAH_RESONANCE:
+ eax_call.set_value<EaxAutoWahEffectException>(eax_.lResonance);
+ break;
+
+ case EAXAUTOWAH_PEAKLEVEL:
+ eax_call.set_value<EaxAutoWahEffectException>(eax_.lPeakLevel);
+ break;
+
+ default:
+ throw EaxAutoWahEffectException{"Unsupported property id."};
+ }
+
+ return false;
+}
+
+void EaxAutoWahEffect::validate_attack_time(
+ float flAttackTime)
+{
+ eax_validate_range<EaxAutoWahEffectException>(
+ "Attack Time",
+ flAttackTime,
+ EAXAUTOWAH_MINATTACKTIME,
+ EAXAUTOWAH_MAXATTACKTIME);
+}
+
+void EaxAutoWahEffect::validate_release_time(
+ float flReleaseTime)
+{
+ eax_validate_range<EaxAutoWahEffectException>(
+ "Release Time",
+ flReleaseTime,
+ EAXAUTOWAH_MINRELEASETIME,
+ EAXAUTOWAH_MAXRELEASETIME);
+}
+
+void EaxAutoWahEffect::validate_resonance(
+ long lResonance)
+{
+ eax_validate_range<EaxAutoWahEffectException>(
+ "Resonance",
+ lResonance,
+ EAXAUTOWAH_MINRESONANCE,
+ EAXAUTOWAH_MAXRESONANCE);
+}
+
+void EaxAutoWahEffect::validate_peak_level(
+ long lPeakLevel)
+{
+ eax_validate_range<EaxAutoWahEffectException>(
+ "Peak Level",
+ lPeakLevel,
+ EAXAUTOWAH_MINPEAKLEVEL,
+ EAXAUTOWAH_MAXPEAKLEVEL);
+}
+
+void EaxAutoWahEffect::validate_all(
+ const EAXAUTOWAHPROPERTIES& eax_all)
+{
+ validate_attack_time(eax_all.flAttackTime);
+ validate_release_time(eax_all.flReleaseTime);
+ validate_resonance(eax_all.lResonance);
+ validate_peak_level(eax_all.lPeakLevel);
+}
+
+void EaxAutoWahEffect::defer_attack_time(
+ float flAttackTime)
+{
+ eax_d_.flAttackTime = flAttackTime;
+ eax_dirty_flags_.flAttackTime = (eax_.flAttackTime != eax_d_.flAttackTime);
+}
+
+void EaxAutoWahEffect::defer_release_time(
+ float flReleaseTime)
+{
+ eax_d_.flReleaseTime = flReleaseTime;
+ eax_dirty_flags_.flReleaseTime = (eax_.flReleaseTime != eax_d_.flReleaseTime);
+}
+
+void EaxAutoWahEffect::defer_resonance(
+ long lResonance)
+{
+ eax_d_.lResonance = lResonance;
+ eax_dirty_flags_.lResonance = (eax_.lResonance != eax_d_.lResonance);
+}
+
+void EaxAutoWahEffect::defer_peak_level(
+ long lPeakLevel)
+{
+ eax_d_.lPeakLevel = lPeakLevel;
+ eax_dirty_flags_.lPeakLevel = (eax_.lPeakLevel != eax_d_.lPeakLevel);
+}
+
+void EaxAutoWahEffect::defer_all(
+ const EAXAUTOWAHPROPERTIES& eax_all)
+{
+ validate_all(eax_all);
+
+ defer_attack_time(eax_all.flAttackTime);
+ defer_release_time(eax_all.flReleaseTime);
+ defer_resonance(eax_all.lResonance);
+ defer_peak_level(eax_all.lPeakLevel);
+}
+
+void EaxAutoWahEffect::defer_attack_time(
+ const EaxEaxCall& eax_call)
+{
+ const auto& attack_time =
+ eax_call.get_value<EaxAutoWahEffectException, const decltype(EAXAUTOWAHPROPERTIES::flAttackTime)>();
+
+ validate_attack_time(attack_time);
+ defer_attack_time(attack_time);
+}
+
+void EaxAutoWahEffect::defer_release_time(
+ const EaxEaxCall& eax_call)
+{
+ const auto& release_time =
+ eax_call.get_value<EaxAutoWahEffectException, const decltype(EAXAUTOWAHPROPERTIES::flReleaseTime)>();
+
+ validate_release_time(release_time);
+ defer_release_time(release_time);
+}
+
+void EaxAutoWahEffect::defer_resonance(
+ const EaxEaxCall& eax_call)
+{
+ const auto& resonance =
+ eax_call.get_value<EaxAutoWahEffectException, const decltype(EAXAUTOWAHPROPERTIES::lResonance)>();
+
+ validate_resonance(resonance);
+ defer_resonance(resonance);
+}
+
+void EaxAutoWahEffect::defer_peak_level(
+ const EaxEaxCall& eax_call)
+{
+ const auto& peak_level =
+ eax_call.get_value<EaxAutoWahEffectException, const decltype(EAXAUTOWAHPROPERTIES::lPeakLevel)>();
+
+ validate_peak_level(peak_level);
+ defer_peak_level(peak_level);
+}
+
+void EaxAutoWahEffect::defer_all(
+ const EaxEaxCall& eax_call)
+{
+ const auto& all =
+ eax_call.get_value<EaxAutoWahEffectException, const EAXAUTOWAHPROPERTIES>();
+
+ validate_all(all);
+ defer_all(all);
+}
+
+// [[nodiscard]]
+bool EaxAutoWahEffect::apply_deferred()
+{
+ if (eax_dirty_flags_ == EaxAutoWahEffectDirtyFlags{})
+ {
+ return false;
+ }
+
+ eax_ = eax_d_;
+
+ if (eax_dirty_flags_.flAttackTime)
+ {
+ set_efx_attack_time();
+ }
+
+ if (eax_dirty_flags_.flReleaseTime)
+ {
+ set_efx_release_time();
+ }
+
+ if (eax_dirty_flags_.lResonance)
+ {
+ set_efx_resonance();
+ }
+
+ if (eax_dirty_flags_.lPeakLevel)
+ {
+ set_efx_peak_gain();
+ }
+
+ eax_dirty_flags_ = EaxAutoWahEffectDirtyFlags{};
+
+ return true;
+}
+
+// [[nodiscard]]
+bool EaxAutoWahEffect::set(
+ const EaxEaxCall& eax_call)
+{
+ switch (eax_call.get_property_id())
+ {
+ case EAXAUTOWAH_NONE:
+ break;
+
+ case EAXAUTOWAH_ALLPARAMETERS:
+ defer_all(eax_call);
+ break;
+
+ case EAXAUTOWAH_ATTACKTIME:
+ defer_attack_time(eax_call);
+ break;
+
+ case EAXAUTOWAH_RELEASETIME:
+ defer_release_time(eax_call);
+ break;
+
+ case EAXAUTOWAH_RESONANCE:
+ defer_resonance(eax_call);
+ break;
+
+ case EAXAUTOWAH_PEAKLEVEL:
+ defer_peak_level(eax_call);
+ break;
+
+ default:
+ throw EaxAutoWahEffectException{"Unsupported property id."};
+ }
+
+ if (!eax_call.is_deferred())
+ {
+ return apply_deferred();
+ }
+
+ return false;
+}
+
+
+} // namespace
+
+
+EaxEffectUPtr eax_create_eax_auto_wah_effect(
+ EffectProps& al_effect_props)
+{
+ return std::make_unique<::EaxAutoWahEffect>(al_effect_props);
+}
+
+
+#endif // ALSOFT_EAX
diff --git a/al/effects/chorus.cpp b/al/effects/chorus.cpp
index 2466d97e..ed994fbb 100644
--- a/al/effects/chorus.cpp
+++ b/al/effects/chorus.cpp
@@ -11,6 +11,15 @@
#include "core/logging.h"
#include "effects.h"
+#if ALSOFT_EAX
+#include <cassert>
+
+#include "alnumeric.h"
+
+#include "al/eax_exception.h"
+#include "al/eax_utils.h"
+#endif // ALSOFT_EAX
+
namespace {
@@ -279,3 +288,1234 @@ const EffectProps ChorusEffectProps{genDefaultChorusProps()};
DEFINE_ALEFFECT_VTABLE(Flanger);
const EffectProps FlangerEffectProps{genDefaultFlangerProps()};
+
+
+#if ALSOFT_EAX
+namespace
+{
+
+
+void eax_set_efx_waveform(
+ ALenum waveform,
+ EffectProps& al_effect_props)
+{
+ const auto efx_waveform = WaveformFromEnum(waveform);
+ assert(efx_waveform.has_value());
+ al_effect_props.Chorus.Waveform = *efx_waveform;
+}
+
+void eax_set_efx_phase(
+ ALint phase,
+ EffectProps& al_effect_props)
+{
+ al_effect_props.Chorus.Phase = phase;
+}
+
+void eax_set_efx_rate(
+ ALfloat rate,
+ EffectProps& al_effect_props)
+{
+ al_effect_props.Chorus.Rate = rate;
+}
+
+void eax_set_efx_depth(
+ ALfloat depth,
+ EffectProps& al_effect_props)
+{
+ al_effect_props.Chorus.Depth = depth;
+}
+
+void eax_set_efx_feedback(
+ ALfloat feedback,
+ EffectProps& al_effect_props)
+{
+ al_effect_props.Chorus.Feedback = feedback;
+}
+
+void eax_set_efx_delay(
+ ALfloat delay,
+ EffectProps& al_effect_props)
+{
+ al_effect_props.Chorus.Delay = delay;
+}
+
+
+using EaxChorusEffectDirtyFlagsValue = std::uint_least8_t;
+
+struct EaxChorusEffectDirtyFlags
+{
+ using EaxIsBitFieldStruct = bool;
+
+ EaxChorusEffectDirtyFlagsValue ulWaveform : 1;
+ EaxChorusEffectDirtyFlagsValue lPhase : 1;
+ EaxChorusEffectDirtyFlagsValue flRate : 1;
+ EaxChorusEffectDirtyFlagsValue flDepth : 1;
+ EaxChorusEffectDirtyFlagsValue flFeedback : 1;
+ EaxChorusEffectDirtyFlagsValue flDelay : 1;
+}; // EaxChorusEffectDirtyFlags
+
+
+class EaxChorusEffect final :
+ public EaxEffect
+{
+public:
+ EaxChorusEffect(
+ EffectProps& al_effect_props);
+
+
+ // [[nodiscard]]
+ bool dispatch(
+ const EaxEaxCall& eax_call) override;
+
+
+private:
+ EffectProps& al_effect_props_;
+ EAXCHORUSPROPERTIES eax_{};
+ EAXCHORUSPROPERTIES eax_d_{};
+ EaxChorusEffectDirtyFlags eax_dirty_flags_{};
+
+
+ void set_eax_defaults() noexcept;
+
+
+ void set_efx_waveform();
+
+ void set_efx_phase();
+
+ void set_efx_rate();
+
+ void set_efx_depth();
+
+ void set_efx_feedback();
+
+ void set_efx_delay();
+
+ void set_efx_defaults();
+
+
+ // [[nodiscard]]
+ bool get(
+ const EaxEaxCall& eax_call);
+
+
+ void validate_waveform(
+ unsigned long ulWaveform);
+
+ void validate_phase(
+ long lPhase);
+
+ void validate_rate(
+ float flRate);
+
+ void validate_depth(
+ float flDepth);
+
+ void validate_feedback(
+ float flFeedback);
+
+ void validate_delay(
+ float flDelay);
+
+ void validate_all(
+ const EAXCHORUSPROPERTIES& eax_all);
+
+
+ void defer_waveform(
+ unsigned long ulWaveform);
+
+ void defer_phase(
+ long lPhase);
+
+ void defer_rate(
+ float flRate);
+
+ void defer_depth(
+ float flDepth);
+
+ void defer_feedback(
+ float flFeedback);
+
+ void defer_delay(
+ float flDelay);
+
+ void defer_all(
+ const EAXCHORUSPROPERTIES& eax_all);
+
+
+ void defer_waveform(
+ const EaxEaxCall& eax_call);
+
+ void defer_phase(
+ const EaxEaxCall& eax_call);
+
+ void defer_rate(
+ const EaxEaxCall& eax_call);
+
+ void defer_depth(
+ const EaxEaxCall& eax_call);
+
+ void defer_feedback(
+ const EaxEaxCall& eax_call);
+
+ void defer_delay(
+ const EaxEaxCall& eax_call);
+
+ void defer_all(
+ const EaxEaxCall& eax_call);
+
+
+ // [[nodiscard]]
+ bool apply_deferred();
+
+ // [[nodiscard]]
+ bool set(
+ const EaxEaxCall& eax_call);
+}; // EaxChorusEffect
+
+
+class EaxChorusEffectException :
+ public EaxException
+{
+public:
+ explicit EaxChorusEffectException(
+ const char* message)
+ :
+ EaxException{"EAX_CHORUS_EFFECT", message}
+ {
+ }
+}; // EaxChorusEffectException
+
+
+EaxChorusEffect::EaxChorusEffect(
+ EffectProps& al_effect_props)
+ :
+ al_effect_props_{al_effect_props}
+{
+ set_eax_defaults();
+ set_efx_defaults();
+}
+
+// [[nodiscard]]
+bool EaxChorusEffect::dispatch(
+ const EaxEaxCall& eax_call)
+{
+ return eax_call.is_get() ? get(eax_call) : set(eax_call);
+}
+
+void EaxChorusEffect::set_eax_defaults() noexcept
+{
+ eax_.ulWaveform = EAXCHORUS_DEFAULTWAVEFORM;
+ eax_.lPhase = EAXCHORUS_DEFAULTPHASE;
+ eax_.flRate = EAXCHORUS_DEFAULTRATE;
+ eax_.flDepth = EAXCHORUS_DEFAULTDEPTH;
+ eax_.flFeedback = EAXCHORUS_DEFAULTFEEDBACK;
+ eax_.flDelay = EAXCHORUS_DEFAULTDELAY;
+
+ eax_d_ = eax_;
+}
+
+void EaxChorusEffect::set_efx_waveform()
+{
+ const auto waveform = clamp(
+ static_cast<ALint>(eax_.ulWaveform),
+ AL_CHORUS_MIN_WAVEFORM,
+ AL_CHORUS_MAX_WAVEFORM);
+
+ eax_set_efx_waveform(waveform, al_effect_props_);
+}
+
+void EaxChorusEffect::set_efx_phase()
+{
+ const auto phase = clamp(
+ static_cast<ALint>(eax_.lPhase),
+ AL_CHORUS_MIN_PHASE,
+ AL_CHORUS_MAX_PHASE);
+
+ eax_set_efx_phase(phase, al_effect_props_);
+}
+
+void EaxChorusEffect::set_efx_rate()
+{
+ const auto rate = clamp(
+ eax_.flRate,
+ AL_CHORUS_MIN_RATE,
+ AL_CHORUS_MAX_RATE);
+
+ eax_set_efx_rate(rate, al_effect_props_);
+}
+
+void EaxChorusEffect::set_efx_depth()
+{
+ const auto depth = clamp(
+ eax_.flDepth,
+ AL_CHORUS_MIN_DEPTH,
+ AL_CHORUS_MAX_DEPTH);
+
+ eax_set_efx_depth(depth, al_effect_props_);
+}
+
+void EaxChorusEffect::set_efx_feedback()
+{
+ const auto feedback = clamp(
+ eax_.flFeedback,
+ AL_CHORUS_MIN_FEEDBACK,
+ AL_CHORUS_MAX_FEEDBACK);
+
+ eax_set_efx_feedback(feedback, al_effect_props_);
+}
+
+void EaxChorusEffect::set_efx_delay()
+{
+ const auto delay = clamp(
+ eax_.flDelay,
+ AL_CHORUS_MIN_DELAY,
+ AL_CHORUS_MAX_DELAY);
+
+ eax_set_efx_delay(delay, al_effect_props_);
+}
+
+void EaxChorusEffect::set_efx_defaults()
+{
+ set_efx_waveform();
+ set_efx_phase();
+ set_efx_rate();
+ set_efx_depth();
+ set_efx_feedback();
+ set_efx_delay();
+}
+
+bool EaxChorusEffect::get(
+ const EaxEaxCall& eax_call)
+{
+ switch (eax_call.get_property_id())
+ {
+ case EAXCHORUS_NONE:
+ break;
+
+ case EAXCHORUS_ALLPARAMETERS:
+ eax_call.set_value<EaxChorusEffectException>(eax_);
+ break;
+
+ case EAXCHORUS_WAVEFORM:
+ eax_call.set_value<EaxChorusEffectException>(eax_.ulWaveform);
+ break;
+
+ case EAXCHORUS_PHASE:
+ eax_call.set_value<EaxChorusEffectException>(eax_.lPhase);
+ break;
+
+ case EAXCHORUS_RATE:
+ eax_call.set_value<EaxChorusEffectException>(eax_.flRate);
+ break;
+
+ case EAXCHORUS_DEPTH:
+ eax_call.set_value<EaxChorusEffectException>(eax_.flDepth);
+ break;
+
+ case EAXCHORUS_FEEDBACK:
+ eax_call.set_value<EaxChorusEffectException>(eax_.flFeedback);
+ break;
+
+ case EAXCHORUS_DELAY:
+ eax_call.set_value<EaxChorusEffectException>(eax_.flDelay);
+ break;
+
+ default:
+ throw EaxChorusEffectException{"Unsupported property id."};
+ }
+
+ return false;
+}
+
+void EaxChorusEffect::validate_waveform(
+ unsigned long ulWaveform)
+{
+ eax_validate_range<EaxChorusEffectException>(
+ "Waveform",
+ ulWaveform,
+ EAXCHORUS_MINWAVEFORM,
+ EAXCHORUS_MAXWAVEFORM);
+}
+
+void EaxChorusEffect::validate_phase(
+ long lPhase)
+{
+ eax_validate_range<EaxChorusEffectException>(
+ "Phase",
+ lPhase,
+ EAXCHORUS_MINPHASE,
+ EAXCHORUS_MAXPHASE);
+}
+
+void EaxChorusEffect::validate_rate(
+ float flRate)
+{
+ eax_validate_range<EaxChorusEffectException>(
+ "Rate",
+ flRate,
+ EAXCHORUS_MINRATE,
+ EAXCHORUS_MAXRATE);
+}
+
+void EaxChorusEffect::validate_depth(
+ float flDepth)
+{
+ eax_validate_range<EaxChorusEffectException>(
+ "Depth",
+ flDepth,
+ EAXCHORUS_MINDEPTH,
+ EAXCHORUS_MAXDEPTH);
+}
+
+void EaxChorusEffect::validate_feedback(
+ float flFeedback)
+{
+ eax_validate_range<EaxChorusEffectException>(
+ "Feedback",
+ flFeedback,
+ EAXCHORUS_MINFEEDBACK,
+ EAXCHORUS_MAXFEEDBACK);
+}
+
+void EaxChorusEffect::validate_delay(
+ float flDelay)
+{
+ eax_validate_range<EaxChorusEffectException>(
+ "Delay",
+ flDelay,
+ EAXCHORUS_MINDELAY,
+ EAXCHORUS_MAXDELAY);
+}
+
+void EaxChorusEffect::validate_all(
+ const EAXCHORUSPROPERTIES& eax_all)
+{
+ validate_waveform(eax_all.ulWaveform);
+ validate_phase(eax_all.lPhase);
+ validate_rate(eax_all.flRate);
+ validate_depth(eax_all.flDepth);
+ validate_feedback(eax_all.flFeedback);
+ validate_delay(eax_all.flDelay);
+}
+
+void EaxChorusEffect::defer_waveform(
+ unsigned long ulWaveform)
+{
+ eax_d_.ulWaveform = ulWaveform;
+ eax_dirty_flags_.ulWaveform = (eax_.ulWaveform != eax_d_.ulWaveform);
+}
+
+void EaxChorusEffect::defer_phase(
+ long lPhase)
+{
+ eax_d_.lPhase = lPhase;
+ eax_dirty_flags_.lPhase = (eax_.lPhase != eax_d_.lPhase);
+}
+
+void EaxChorusEffect::defer_rate(
+ float flRate)
+{
+ eax_d_.flRate = flRate;
+ eax_dirty_flags_.flRate = (eax_.flRate != eax_d_.flRate);
+}
+
+void EaxChorusEffect::defer_depth(
+ float flDepth)
+{
+ eax_d_.flDepth = flDepth;
+ eax_dirty_flags_.flDepth = (eax_.flDepth != eax_d_.flDepth);
+}
+
+void EaxChorusEffect::defer_feedback(
+ float flFeedback)
+{
+ eax_d_.flFeedback = flFeedback;
+ eax_dirty_flags_.flFeedback = (eax_.flFeedback != eax_d_.flFeedback);
+}
+
+void EaxChorusEffect::defer_delay(
+ float flDelay)
+{
+ eax_d_.flDelay = flDelay;
+ eax_dirty_flags_.flDelay = (eax_.flDelay != eax_d_.flDelay);
+}
+
+void EaxChorusEffect::defer_all(
+ const EAXCHORUSPROPERTIES& eax_all)
+{
+ defer_waveform(eax_all.ulWaveform);
+ defer_phase(eax_all.lPhase);
+ defer_rate(eax_all.flRate);
+ defer_depth(eax_all.flDepth);
+ defer_feedback(eax_all.flFeedback);
+ defer_delay(eax_all.flDelay);
+}
+
+void EaxChorusEffect::defer_waveform(
+ const EaxEaxCall& eax_call)
+{
+ const auto& waveform =
+ eax_call.get_value<EaxChorusEffectException, const decltype(EAXCHORUSPROPERTIES::ulWaveform)>();
+
+ validate_waveform(waveform);
+ defer_waveform(waveform);
+}
+
+void EaxChorusEffect::defer_phase(
+ const EaxEaxCall& eax_call)
+{
+ const auto& phase =
+ eax_call.get_value<EaxChorusEffectException, const decltype(EAXCHORUSPROPERTIES::lPhase)>();
+
+ validate_phase(phase);
+ defer_phase(phase);
+}
+
+void EaxChorusEffect::defer_rate(
+ const EaxEaxCall& eax_call)
+{
+ const auto& rate =
+ eax_call.get_value<EaxChorusEffectException, const decltype(EAXCHORUSPROPERTIES::flRate)>();
+
+ validate_rate(rate);
+ defer_rate(rate);
+}
+
+void EaxChorusEffect::defer_depth(
+ const EaxEaxCall& eax_call)
+{
+ const auto& depth =
+ eax_call.get_value<EaxChorusEffectException, const decltype(EAXCHORUSPROPERTIES::flDepth)>();
+
+ validate_depth(depth);
+ defer_depth(depth);
+}
+
+void EaxChorusEffect::defer_feedback(
+ const EaxEaxCall& eax_call)
+{
+ const auto& feedback =
+ eax_call.get_value<EaxChorusEffectException, const decltype(EAXCHORUSPROPERTIES::flFeedback)>();
+
+ validate_feedback(feedback);
+ defer_feedback(feedback);
+}
+
+void EaxChorusEffect::defer_delay(
+ const EaxEaxCall& eax_call)
+{
+ const auto& delay =
+ eax_call.get_value<EaxChorusEffectException, const decltype(EAXCHORUSPROPERTIES::flDelay)>();
+
+ validate_delay(delay);
+ defer_delay(delay);
+}
+
+void EaxChorusEffect::defer_all(
+ const EaxEaxCall& eax_call)
+{
+ const auto& all =
+ eax_call.get_value<EaxChorusEffectException, const EAXCHORUSPROPERTIES>();
+
+ validate_all(all);
+ defer_all(all);
+}
+
+// [[nodiscard]]
+bool EaxChorusEffect::apply_deferred()
+{
+ if (eax_dirty_flags_ == EaxChorusEffectDirtyFlags{})
+ {
+ return false;
+ }
+
+ eax_ = eax_d_;
+
+ if (eax_dirty_flags_.ulWaveform)
+ {
+ set_efx_waveform();
+ }
+
+ if (eax_dirty_flags_.lPhase)
+ {
+ set_efx_phase();
+ }
+
+ if (eax_dirty_flags_.flRate)
+ {
+ set_efx_rate();
+ }
+
+ if (eax_dirty_flags_.flDepth)
+ {
+ set_efx_depth();
+ }
+
+ if (eax_dirty_flags_.flFeedback)
+ {
+ set_efx_feedback();
+ }
+
+ if (eax_dirty_flags_.flDelay)
+ {
+ set_efx_delay();
+ }
+
+ eax_dirty_flags_ = EaxChorusEffectDirtyFlags{};
+
+ return true;
+}
+
+// [[nodiscard]]
+bool EaxChorusEffect::set(
+ const EaxEaxCall& eax_call)
+{
+ switch (eax_call.get_property_id())
+ {
+ case EAXCHORUS_NONE:
+ break;
+
+ case EAXCHORUS_ALLPARAMETERS:
+ defer_all(eax_call);
+ break;
+
+ case EAXCHORUS_WAVEFORM:
+ defer_waveform(eax_call);
+ break;
+
+ case EAXCHORUS_PHASE:
+ defer_phase(eax_call);
+ break;
+
+ case EAXCHORUS_RATE:
+ defer_rate(eax_call);
+ break;
+
+ case EAXCHORUS_DEPTH:
+ defer_depth(eax_call);
+ break;
+
+ case EAXCHORUS_FEEDBACK:
+ defer_feedback(eax_call);
+ break;
+
+ case EAXCHORUS_DELAY:
+ defer_delay(eax_call);
+ break;
+
+ default:
+ throw EaxChorusEffectException{"Unsupported property id."};
+ }
+
+ if (!eax_call.is_deferred())
+ {
+ return apply_deferred();
+ }
+
+ return false;
+}
+
+
+} // namespace
+
+
+EaxEffectUPtr eax_create_eax_chorus_effect(
+ EffectProps& al_effect_props)
+{
+ return std::make_unique<::EaxChorusEffect>(al_effect_props);
+}
+
+
+namespace
+{
+
+
+using EaxFlangerEffectDirtyFlagsValue = std::uint_least8_t;
+
+struct EaxFlangerEffectDirtyFlags
+{
+ using EaxIsBitFieldStruct = bool;
+
+ EaxFlangerEffectDirtyFlagsValue ulWaveform : 1;
+ EaxFlangerEffectDirtyFlagsValue lPhase : 1;
+ EaxFlangerEffectDirtyFlagsValue flRate : 1;
+ EaxFlangerEffectDirtyFlagsValue flDepth : 1;
+ EaxFlangerEffectDirtyFlagsValue flFeedback : 1;
+ EaxFlangerEffectDirtyFlagsValue flDelay : 1;
+}; // EaxFlangerEffectDirtyFlags
+
+
+class EaxFlangerEffect final :
+ public EaxEffect
+{
+public:
+ EaxFlangerEffect(
+ EffectProps& al_effect_props);
+
+
+ // [[nodiscard]]
+ bool dispatch(
+ const EaxEaxCall& eax_call) override;
+
+
+private:
+ EffectProps& al_effect_props_;
+
+ EAXFLANGERPROPERTIES eax_{};
+ EAXFLANGERPROPERTIES eax_d_{};
+ EaxFlangerEffectDirtyFlags eax_dirty_flags_{};
+
+
+ void set_eax_defaults();
+
+
+ void set_efx_waveform();
+
+ void set_efx_phase();
+
+ void set_efx_rate();
+
+ void set_efx_depth();
+
+ void set_efx_feedback();
+
+ void set_efx_delay();
+
+ void set_efx_defaults();
+
+
+ // [[nodiscard]]
+ bool get(
+ const EaxEaxCall& eax_call);
+
+
+ void validate_waveform(
+ unsigned long ulWaveform);
+
+ void validate_phase(
+ long lPhase);
+
+ void validate_rate(
+ float flRate);
+
+ void validate_depth(
+ float flDepth);
+
+ void validate_feedback(
+ float flFeedback);
+
+ void validate_delay(
+ float flDelay);
+
+ void validate_all(
+ const EAXFLANGERPROPERTIES& all);
+
+
+ void defer_waveform(
+ unsigned long ulWaveform);
+
+ void defer_phase(
+ long lPhase);
+
+ void defer_rate(
+ float flRate);
+
+ void defer_depth(
+ float flDepth);
+
+ void defer_feedback(
+ float flFeedback);
+
+ void defer_delay(
+ float flDelay);
+
+ void defer_all(
+ const EAXFLANGERPROPERTIES& all);
+
+
+ void defer_waveform(
+ const EaxEaxCall& eax_call);
+
+ void defer_phase(
+ const EaxEaxCall& eax_call);
+
+ void defer_rate(
+ const EaxEaxCall& eax_call);
+
+ void defer_depth(
+ const EaxEaxCall& eax_call);
+
+ void defer_feedback(
+ const EaxEaxCall& eax_call);
+
+ void defer_delay(
+ const EaxEaxCall& eax_call);
+
+ void defer_all(
+ const EaxEaxCall& eax_call);
+
+
+ // [[nodiscard]]
+ bool apply_deferred();
+
+ // [[nodiscard]]
+ bool set(
+ const EaxEaxCall& eax_call);
+}; // EaxFlangerEffect
+
+
+class EaxFlangerEffectException :
+ public EaxException
+{
+public:
+ explicit EaxFlangerEffectException(
+ const char* message)
+ :
+ EaxException{"EAX_FLANGER_EFFECT", message}
+ {
+ }
+}; // EaxFlangerEffectException
+
+
+EaxFlangerEffect::EaxFlangerEffect(
+ EffectProps& al_effect_props)
+ :
+ al_effect_props_{al_effect_props}
+{
+ set_eax_defaults();
+ set_efx_defaults();
+}
+
+// [[nodiscard]]
+bool EaxFlangerEffect::dispatch(
+ const EaxEaxCall& eax_call)
+{
+ return eax_call.is_get() ? get(eax_call) : set(eax_call);
+}
+
+void EaxFlangerEffect::set_eax_defaults()
+{
+ eax_.ulWaveform = EAXFLANGER_DEFAULTWAVEFORM;
+ eax_.lPhase = EAXFLANGER_DEFAULTPHASE;
+ eax_.flRate = EAXFLANGER_DEFAULTRATE;
+ eax_.flDepth = EAXFLANGER_DEFAULTDEPTH;
+ eax_.flFeedback = EAXFLANGER_DEFAULTFEEDBACK;
+ eax_.flDelay = EAXFLANGER_DEFAULTDELAY;
+
+ eax_d_ = eax_;
+}
+
+void EaxFlangerEffect::set_efx_waveform()
+{
+ const auto waveform = clamp(
+ static_cast<ALint>(eax_.ulWaveform),
+ AL_FLANGER_MIN_WAVEFORM,
+ AL_FLANGER_MAX_WAVEFORM);
+
+ eax_set_efx_waveform(waveform, al_effect_props_);
+}
+
+void EaxFlangerEffect::set_efx_phase()
+{
+ const auto phase = clamp(
+ static_cast<ALint>(eax_.lPhase),
+ AL_FLANGER_MIN_PHASE,
+ AL_FLANGER_MAX_PHASE);
+
+ eax_set_efx_phase(phase, al_effect_props_);
+}
+
+void EaxFlangerEffect::set_efx_rate()
+{
+ const auto rate = clamp(
+ eax_.flRate,
+ AL_FLANGER_MIN_RATE,
+ AL_FLANGER_MAX_RATE);
+
+ eax_set_efx_rate(rate, al_effect_props_);
+}
+
+void EaxFlangerEffect::set_efx_depth()
+{
+ const auto depth = clamp(
+ eax_.flDepth,
+ AL_FLANGER_MIN_DEPTH,
+ AL_FLANGER_MAX_DEPTH);
+
+ eax_set_efx_depth(depth, al_effect_props_);
+}
+
+void EaxFlangerEffect::set_efx_feedback()
+{
+ const auto feedback = clamp(
+ eax_.flFeedback,
+ AL_FLANGER_MIN_FEEDBACK,
+ AL_FLANGER_MAX_FEEDBACK);
+
+ eax_set_efx_feedback(feedback, al_effect_props_);
+}
+
+void EaxFlangerEffect::set_efx_delay()
+{
+ const auto delay = clamp(
+ eax_.flDelay,
+ AL_FLANGER_MIN_DELAY,
+ AL_FLANGER_MAX_DELAY);
+
+ eax_set_efx_delay(delay, al_effect_props_);
+}
+
+void EaxFlangerEffect::set_efx_defaults()
+{
+ set_efx_waveform();
+ set_efx_phase();
+ set_efx_rate();
+ set_efx_depth();
+ set_efx_feedback();
+ set_efx_delay();
+}
+
+// [[nodiscard]]
+bool EaxFlangerEffect::get(
+ const EaxEaxCall& eax_call)
+{
+ switch (eax_call.get_property_id())
+ {
+ case EAXFLANGER_NONE:
+ break;
+
+ case EAXFLANGER_ALLPARAMETERS:
+ eax_call.set_value<EaxFlangerEffectException>(eax_);
+ break;
+
+ case EAXFLANGER_WAVEFORM:
+ eax_call.set_value<EaxFlangerEffectException>(eax_.ulWaveform);
+ break;
+
+ case EAXFLANGER_PHASE:
+ eax_call.set_value<EaxFlangerEffectException>(eax_.lPhase);
+ break;
+
+ case EAXFLANGER_RATE:
+ eax_call.set_value<EaxFlangerEffectException>(eax_.flRate);
+ break;
+
+ case EAXFLANGER_DEPTH:
+ eax_call.set_value<EaxFlangerEffectException>(eax_.flDepth);
+ break;
+
+ case EAXFLANGER_FEEDBACK:
+ eax_call.set_value<EaxFlangerEffectException>(eax_.flFeedback);
+ break;
+
+ case EAXFLANGER_DELAY:
+ eax_call.set_value<EaxFlangerEffectException>(eax_.flDelay);
+ break;
+
+ default:
+ throw EaxFlangerEffectException{"Unsupported property id."};
+ }
+
+ return false;
+}
+
+void EaxFlangerEffect::validate_waveform(
+ unsigned long ulWaveform)
+{
+ eax_validate_range<EaxFlangerEffectException>(
+ "Waveform",
+ ulWaveform,
+ EAXFLANGER_MINWAVEFORM,
+ EAXFLANGER_MAXWAVEFORM);
+}
+
+void EaxFlangerEffect::validate_phase(
+ long lPhase)
+{
+ eax_validate_range<EaxFlangerEffectException>(
+ "Phase",
+ lPhase,
+ EAXFLANGER_MINPHASE,
+ EAXFLANGER_MAXPHASE);
+}
+
+void EaxFlangerEffect::validate_rate(
+ float flRate)
+{
+ eax_validate_range<EaxFlangerEffectException>(
+ "Rate",
+ flRate,
+ EAXFLANGER_MINRATE,
+ EAXFLANGER_MAXRATE);
+}
+
+void EaxFlangerEffect::validate_depth(
+ float flDepth)
+{
+ eax_validate_range<EaxFlangerEffectException>(
+ "Depth",
+ flDepth,
+ EAXFLANGER_MINDEPTH,
+ EAXFLANGER_MAXDEPTH);
+}
+
+void EaxFlangerEffect::validate_feedback(
+ float flFeedback)
+{
+ eax_validate_range<EaxFlangerEffectException>(
+ "Feedback",
+ flFeedback,
+ EAXFLANGER_MINFEEDBACK,
+ EAXFLANGER_MAXFEEDBACK);
+}
+
+void EaxFlangerEffect::validate_delay(
+ float flDelay)
+{
+ eax_validate_range<EaxFlangerEffectException>(
+ "Delay",
+ flDelay,
+ EAXFLANGER_MINDELAY,
+ EAXFLANGER_MAXDELAY);
+}
+
+void EaxFlangerEffect::validate_all(
+ const EAXFLANGERPROPERTIES& all)
+{
+ validate_waveform(all.ulWaveform);
+ validate_phase(all.lPhase);
+ validate_rate(all.flRate);
+ validate_depth(all.flDepth);
+ validate_feedback(all.flDelay);
+ validate_delay(all.flDelay);
+}
+
+void EaxFlangerEffect::defer_waveform(
+ unsigned long ulWaveform)
+{
+ eax_d_.ulWaveform = ulWaveform;
+ eax_dirty_flags_.ulWaveform = (eax_.ulWaveform != eax_d_.ulWaveform);
+}
+
+void EaxFlangerEffect::defer_phase(
+ long lPhase)
+{
+ eax_d_.lPhase = lPhase;
+ eax_dirty_flags_.lPhase = (eax_.lPhase != eax_d_.lPhase);
+}
+
+void EaxFlangerEffect::defer_rate(
+ float flRate)
+{
+ eax_d_.flRate = flRate;
+ eax_dirty_flags_.flRate = (eax_.flRate != eax_d_.flRate);
+}
+
+void EaxFlangerEffect::defer_depth(
+ float flDepth)
+{
+ eax_d_.flDepth = flDepth;
+ eax_dirty_flags_.flDepth = (eax_.flDepth != eax_d_.flDepth);
+}
+
+void EaxFlangerEffect::defer_feedback(
+ float flFeedback)
+{
+ eax_d_.flFeedback = flFeedback;
+ eax_dirty_flags_.flFeedback = (eax_.flFeedback != eax_d_.flFeedback);
+}
+
+void EaxFlangerEffect::defer_delay(
+ float flDelay)
+{
+ eax_d_.flDelay = flDelay;
+ eax_dirty_flags_.flDelay = (eax_.flDelay != eax_d_.flDelay);
+}
+
+void EaxFlangerEffect::defer_all(
+ const EAXFLANGERPROPERTIES& all)
+{
+ defer_waveform(all.ulWaveform);
+ defer_phase(all.lPhase);
+ defer_rate(all.flRate);
+ defer_depth(all.flDepth);
+ defer_feedback(all.flDelay);
+ defer_delay(all.flDelay);
+}
+
+void EaxFlangerEffect::defer_waveform(
+ const EaxEaxCall& eax_call)
+{
+ const auto& waveform =
+ eax_call.get_value<EaxFlangerEffectException, const decltype(EAXFLANGERPROPERTIES::ulWaveform)>();
+
+ validate_waveform(waveform);
+ defer_waveform(waveform);
+}
+
+void EaxFlangerEffect::defer_phase(
+ const EaxEaxCall& eax_call)
+{
+ const auto& phase =
+ eax_call.get_value<EaxFlangerEffectException, const decltype(EAXFLANGERPROPERTIES::lPhase)>();
+
+ validate_phase(phase);
+ defer_phase(phase);
+}
+
+void EaxFlangerEffect::defer_rate(
+ const EaxEaxCall& eax_call)
+{
+ const auto& rate =
+ eax_call.get_value<EaxFlangerEffectException, const decltype(EAXFLANGERPROPERTIES::flRate)>();
+
+ validate_rate(rate);
+ defer_rate(rate);
+}
+
+void EaxFlangerEffect::defer_depth(
+ const EaxEaxCall& eax_call)
+{
+ const auto& depth =
+ eax_call.get_value<EaxFlangerEffectException, const decltype(EAXFLANGERPROPERTIES::flDepth)>();
+
+ validate_depth(depth);
+ defer_depth(depth);
+}
+
+void EaxFlangerEffect::defer_feedback(
+ const EaxEaxCall& eax_call)
+{
+ const auto& feedback =
+ eax_call.get_value<EaxFlangerEffectException, const decltype(EAXFLANGERPROPERTIES::flFeedback)>();
+
+ validate_feedback(feedback);
+ defer_feedback(feedback);
+}
+
+void EaxFlangerEffect::defer_delay(
+ const EaxEaxCall& eax_call)
+{
+ const auto& delay =
+ eax_call.get_value<EaxFlangerEffectException, const decltype(EAXFLANGERPROPERTIES::flDelay)>();
+
+ validate_delay(delay);
+ defer_delay(delay);
+}
+
+void EaxFlangerEffect::defer_all(
+ const EaxEaxCall& eax_call)
+{
+ const auto& all =
+ eax_call.get_value<EaxFlangerEffectException, const EAXFLANGERPROPERTIES>();
+
+ validate_all(all);
+ defer_all(all);
+}
+
+// [[nodiscard]]
+bool EaxFlangerEffect::apply_deferred()
+{
+ if (eax_dirty_flags_ == EaxFlangerEffectDirtyFlags{})
+ {
+ return false;
+ }
+
+ eax_ = eax_d_;
+
+ if (eax_dirty_flags_.ulWaveform)
+ {
+ set_efx_waveform();
+ }
+
+ if (eax_dirty_flags_.lPhase)
+ {
+ set_efx_phase();
+ }
+
+ if (eax_dirty_flags_.flRate)
+ {
+ set_efx_rate();
+ }
+
+ if (eax_dirty_flags_.flDepth)
+ {
+ set_efx_depth();
+ }
+
+ if (eax_dirty_flags_.flFeedback)
+ {
+ set_efx_feedback();
+ }
+
+ if (eax_dirty_flags_.flDelay)
+ {
+ set_efx_delay();
+ }
+
+ eax_dirty_flags_ = EaxFlangerEffectDirtyFlags{};
+
+ return true;
+}
+
+// [[nodiscard]]
+bool EaxFlangerEffect::set(
+ const EaxEaxCall& eax_call)
+{
+ switch (eax_call.get_property_id())
+ {
+ case EAXFLANGER_NONE:
+ break;
+
+ case EAXFLANGER_ALLPARAMETERS:
+ defer_all(eax_call);
+ break;
+
+ case EAXFLANGER_WAVEFORM:
+ defer_waveform(eax_call);
+ break;
+
+ case EAXFLANGER_PHASE:
+ defer_phase(eax_call);
+ break;
+
+ case EAXFLANGER_RATE:
+ defer_rate(eax_call);
+ break;
+
+ case EAXFLANGER_DEPTH:
+ defer_depth(eax_call);
+ break;
+
+ case EAXFLANGER_FEEDBACK:
+ defer_feedback(eax_call);
+ break;
+
+ case EAXFLANGER_DELAY:
+ defer_delay(eax_call);
+ break;
+
+ default:
+ throw EaxFlangerEffectException{"Unsupported property id."};
+ }
+
+ if (!eax_call.is_deferred())
+ {
+ return apply_deferred();
+ }
+
+ return false;
+}
+
+
+} // namespace
+
+
+EaxEffectUPtr eax_create_eax_flanger_effect(
+ EffectProps& al_effect_props)
+{
+ return std::make_unique<EaxFlangerEffect>(al_effect_props);
+}
+
+
+#endif // ALSOFT_EAX
diff --git a/al/effects/compressor.cpp b/al/effects/compressor.cpp
index f5db2a6f..868c5c1b 100644
--- a/al/effects/compressor.cpp
+++ b/al/effects/compressor.cpp
@@ -7,6 +7,13 @@
#include "alc/effects/base.h"
#include "effects.h"
+#if ALSOFT_EAX
+#include "alnumeric.h"
+
+#include "al/eax_exception.h"
+#include "al/eax_utils.h"
+#endif // ALSOFT_EAX
+
namespace {
@@ -70,3 +77,268 @@ EffectProps genDefaultProps() noexcept
DEFINE_ALEFFECT_VTABLE(Compressor);
const EffectProps CompressorEffectProps{genDefaultProps()};
+
+#if ALSOFT_EAX
+namespace
+{
+
+
+using EaxCompressorEffectDirtyFlagsValue = std::uint_least8_t;
+
+struct EaxCompressorEffectDirtyFlags
+{
+ using EaxIsBitFieldStruct = bool;
+
+ EaxCompressorEffectDirtyFlagsValue ulOnOff : 1;
+}; // EaxCompressorEffectDirtyFlags
+
+
+class EaxCompressorEffect final :
+ public EaxEffect
+{
+public:
+ EaxCompressorEffect(
+ EffectProps& al_effect_props);
+
+
+ // [[nodiscard]]
+ bool dispatch(
+ const EaxEaxCall& eax_call) override;
+
+
+private:
+ EffectProps& al_effect_props_;
+
+ EAXAGCCOMPRESSORPROPERTIES eax_{};
+ EAXAGCCOMPRESSORPROPERTIES eax_d_{};
+ EaxCompressorEffectDirtyFlags eax_dirty_flags_{};
+
+
+ void set_eax_defaults();
+
+
+ void set_efx_on_off();
+
+ void set_efx_defaults();
+
+
+ // [[nodiscard]]
+ bool get(
+ const EaxEaxCall& eax_call);
+
+
+ void validate_on_off(
+ unsigned long ulOnOff);
+
+ void validate_all(
+ const EAXAGCCOMPRESSORPROPERTIES& eax_all);
+
+
+ void defer_on_off(
+ unsigned long ulOnOff);
+
+ void defer_all(
+ const EAXAGCCOMPRESSORPROPERTIES& eax_all);
+
+
+ void defer_on_off(
+ const EaxEaxCall& eax_call);
+
+ void defer_all(
+ const EaxEaxCall& eax_call);
+
+
+ // [[nodiscard]]
+ bool apply_deferred();
+
+ // [[nodiscard]]
+ bool set(
+ const EaxEaxCall& eax_call);
+}; // EaxCompressorEffect
+
+
+class EaxCompressorEffectException :
+ public EaxException
+{
+public:
+ explicit EaxCompressorEffectException(
+ const char* message)
+ :
+ EaxException{"EAX_COMPRESSOR_EFFECT", message}
+ {
+ }
+}; // EaxCompressorEffectException
+
+
+EaxCompressorEffect::EaxCompressorEffect(
+ EffectProps& al_effect_props)
+ :
+ al_effect_props_{al_effect_props}
+{
+ set_eax_defaults();
+ set_efx_defaults();
+}
+
+// [[nodiscard]]
+bool EaxCompressorEffect::dispatch(
+ const EaxEaxCall& eax_call)
+{
+ return eax_call.is_get() ? get(eax_call) : set(eax_call);
+}
+
+void EaxCompressorEffect::set_eax_defaults()
+{
+ eax_.ulOnOff = EAXAGCCOMPRESSOR_DEFAULTONOFF;
+
+ eax_d_ = eax_;
+}
+
+void EaxCompressorEffect::set_efx_on_off()
+{
+ const auto on_off = clamp(
+ static_cast<ALint>(eax_.ulOnOff),
+ AL_COMPRESSOR_MIN_ONOFF,
+ AL_COMPRESSOR_MAX_ONOFF);
+
+ al_effect_props_.Compressor.OnOff = (on_off != AL_FALSE);
+}
+
+void EaxCompressorEffect::set_efx_defaults()
+{
+ set_efx_on_off();
+}
+
+// [[nodiscard]]
+bool EaxCompressorEffect::get(
+ const EaxEaxCall& eax_call)
+{
+ switch (eax_call.get_property_id())
+ {
+ case EAXAGCCOMPRESSOR_NONE:
+ break;
+
+ case EAXAGCCOMPRESSOR_ALLPARAMETERS:
+ eax_call.set_value<EaxCompressorEffectException>(eax_);
+ break;
+
+ case EAXAGCCOMPRESSOR_ONOFF:
+ eax_call.set_value<EaxCompressorEffectException>(eax_.ulOnOff);
+ break;
+
+ default:
+ throw EaxCompressorEffectException{"Unsupported property id."};
+ }
+
+ return false;
+}
+
+void EaxCompressorEffect::validate_on_off(
+ unsigned long ulOnOff)
+{
+ eax_validate_range<EaxCompressorEffectException>(
+ "On-Off",
+ ulOnOff,
+ EAXAGCCOMPRESSOR_MINONOFF,
+ EAXAGCCOMPRESSOR_MAXONOFF);
+}
+
+void EaxCompressorEffect::validate_all(
+ const EAXAGCCOMPRESSORPROPERTIES& eax_all)
+{
+ validate_on_off(eax_all.ulOnOff);
+}
+
+void EaxCompressorEffect::defer_on_off(
+ unsigned long ulOnOff)
+{
+ eax_d_.ulOnOff = ulOnOff;
+ eax_dirty_flags_.ulOnOff = (eax_.ulOnOff != eax_d_.ulOnOff);
+}
+
+void EaxCompressorEffect::defer_all(
+ const EAXAGCCOMPRESSORPROPERTIES& eax_all)
+{
+ defer_on_off(eax_all.ulOnOff);
+}
+
+void EaxCompressorEffect::defer_on_off(
+ const EaxEaxCall& eax_call)
+{
+ const auto& on_off =
+ eax_call.get_value<EaxCompressorEffectException, const decltype(EAXAGCCOMPRESSORPROPERTIES::ulOnOff)>();
+
+ validate_on_off(on_off);
+ defer_on_off(on_off);
+}
+
+void EaxCompressorEffect::defer_all(
+ const EaxEaxCall& eax_call)
+{
+ const auto& all =
+ eax_call.get_value<EaxCompressorEffectException, const EAXAGCCOMPRESSORPROPERTIES>();
+
+ validate_all(all);
+ defer_all(all);
+}
+
+// [[nodiscard]]
+bool EaxCompressorEffect::apply_deferred()
+{
+ if (eax_dirty_flags_ == EaxCompressorEffectDirtyFlags{})
+ {
+ return false;
+ }
+
+ eax_ = eax_d_;
+
+ if (eax_dirty_flags_.ulOnOff)
+ {
+ set_efx_on_off();
+ }
+
+ eax_dirty_flags_ = EaxCompressorEffectDirtyFlags{};
+
+ return true;
+}
+
+// [[nodiscard]]
+bool EaxCompressorEffect::set(
+ const EaxEaxCall& eax_call)
+{
+ switch (eax_call.get_property_id())
+ {
+ case EAXAGCCOMPRESSOR_NONE:
+ break;
+
+ case EAXAGCCOMPRESSOR_ALLPARAMETERS:
+ defer_all(eax_call);
+ break;
+
+ case EAXAGCCOMPRESSOR_ONOFF:
+ defer_on_off(eax_call);
+ break;
+
+ default:
+ throw EaxCompressorEffectException{"Unsupported property id."};
+ }
+
+ if (!eax_call.is_deferred())
+ {
+ return apply_deferred();
+ }
+
+ return false;
+}
+
+
+} // namespace
+
+
+EaxEffectUPtr eax_create_eax_compressor_effect(
+ EffectProps& al_effect_props)
+{
+ return std::make_unique<EaxCompressorEffect>(al_effect_props);
+}
+
+
+#endif // ALSOFT_EAX
diff --git a/al/effects/distortion.cpp b/al/effects/distortion.cpp
index f5d64a9a..062cdc54 100644
--- a/al/effects/distortion.cpp
+++ b/al/effects/distortion.cpp
@@ -7,6 +7,13 @@
#include "alc/effects/base.h"
#include "effects.h"
+#if ALSOFT_EAX
+#include "alnumeric.h"
+
+#include "al/eax_exception.h"
+#include "al/eax_utils.h"
+#endif // ALSOFT_EAX
+
namespace {
@@ -112,3 +119,532 @@ EffectProps genDefaultProps() noexcept
DEFINE_ALEFFECT_VTABLE(Distortion);
const EffectProps DistortionEffectProps{genDefaultProps()};
+
+#if ALSOFT_EAX
+namespace
+{
+
+
+using EaxDistortionEffectDirtyFlagsValue = std::uint_least8_t;
+
+struct EaxDistortionEffectDirtyFlags
+{
+ using EaxIsBitFieldStruct = bool;
+
+ EaxDistortionEffectDirtyFlagsValue flEdge : 1;
+ EaxDistortionEffectDirtyFlagsValue lGain : 1;
+ EaxDistortionEffectDirtyFlagsValue flLowPassCutOff : 1;
+ EaxDistortionEffectDirtyFlagsValue flEQCenter : 1;
+ EaxDistortionEffectDirtyFlagsValue flEQBandwidth : 1;
+}; // EaxDistortionEffectDirtyFlags
+
+
+class EaxDistortionEffect final :
+ public EaxEffect
+{
+public:
+ EaxDistortionEffect(
+ EffectProps& al_effect_props);
+
+
+ // [[nodiscard]]
+ bool dispatch(
+ const EaxEaxCall& eax_call) override;
+
+
+private:
+ EffectProps& al_effect_props_;
+
+ EAXDISTORTIONPROPERTIES eax_{};
+ EAXDISTORTIONPROPERTIES eax_d_{};
+ EaxDistortionEffectDirtyFlags eax_dirty_flags_{};
+
+
+ void set_eax_defaults();
+
+
+ void set_efx_edge();
+
+ void set_efx_gain();
+
+ void set_efx_lowpass_cutoff();
+
+ void set_efx_eq_center();
+
+ void set_efx_eq_bandwidth();
+
+ void set_efx_defaults();
+
+
+ // [[nodiscard]]
+ bool get(
+ const EaxEaxCall& eax_call);
+
+
+ void validate_edge(
+ float flEdge);
+
+ void validate_gain(
+ long lGain);
+
+ void validate_lowpass_cutoff(
+ float flLowPassCutOff);
+
+ void validate_eq_center(
+ float flEQCenter);
+
+ void validate_eq_bandwidth(
+ float flEQBandwidth);
+
+ void validate_all(
+ const EAXDISTORTIONPROPERTIES& eax_all);
+
+
+ void defer_edge(
+ float flEdge);
+
+ void defer_gain(
+ long lGain);
+
+ void defer_low_pass_cutoff(
+ float flLowPassCutOff);
+
+ void defer_eq_center(
+ float flEQCenter);
+
+ void defer_eq_bandwidth(
+ float flEQBandwidth);
+
+ void defer_all(
+ const EAXDISTORTIONPROPERTIES& eax_all);
+
+
+ void defer_edge(
+ const EaxEaxCall& eax_call);
+
+ void defer_gain(
+ const EaxEaxCall& eax_call);
+
+ void defer_low_pass_cutoff(
+ const EaxEaxCall& eax_call);
+
+ void defer_eq_center(
+ const EaxEaxCall& eax_call);
+
+ void defer_eq_bandwidth(
+ const EaxEaxCall& eax_call);
+
+ void defer_all(
+ const EaxEaxCall& eax_call);
+
+
+ // [[nodiscard]]
+ bool apply_deferred();
+
+ // [[nodiscard]]
+ bool set(
+ const EaxEaxCall& eax_call);
+}; // EaxDistortionEffect
+
+
+class EaxDistortionEffectException :
+ public EaxException
+{
+public:
+ explicit EaxDistortionEffectException(
+ const char* message)
+ :
+ EaxException{"EAX_DISTORTION_EFFECT", message}
+ {
+ }
+}; // EaxDistortionEffectException
+
+
+EaxDistortionEffect::EaxDistortionEffect(
+ EffectProps& al_effect_props)
+ :
+ al_effect_props_{al_effect_props}
+{
+ set_eax_defaults();
+ set_efx_defaults();
+}
+
+// [[nodiscard]]
+bool EaxDistortionEffect::dispatch(
+ const EaxEaxCall& eax_call)
+{
+ return eax_call.is_get() ? get(eax_call) : set(eax_call);
+}
+
+void EaxDistortionEffect::set_eax_defaults()
+{
+ eax_.flEdge = EAXDISTORTION_DEFAULTEDGE;
+ eax_.lGain = EAXDISTORTION_DEFAULTGAIN;
+ eax_.flLowPassCutOff = EAXDISTORTION_DEFAULTLOWPASSCUTOFF;
+ eax_.flEQCenter = EAXDISTORTION_DEFAULTEQCENTER;
+ eax_.flEQBandwidth = EAXDISTORTION_DEFAULTEQBANDWIDTH;
+
+ eax_d_ = eax_;
+}
+
+void EaxDistortionEffect::set_efx_edge()
+{
+ const auto edge = clamp(
+ eax_.flEdge,
+ AL_DISTORTION_MIN_EDGE,
+ AL_DISTORTION_MAX_EDGE);
+
+ al_effect_props_.Distortion.Edge = edge;
+}
+
+void EaxDistortionEffect::set_efx_gain()
+{
+ const auto gain = clamp(
+ level_mb_to_gain(static_cast<float>(eax_.lGain)),
+ AL_DISTORTION_MIN_GAIN,
+ AL_DISTORTION_MAX_GAIN);
+
+ al_effect_props_.Distortion.Gain = gain;
+}
+
+void EaxDistortionEffect::set_efx_lowpass_cutoff()
+{
+ const auto lowpass_cutoff = clamp(
+ eax_.flLowPassCutOff,
+ AL_DISTORTION_MIN_LOWPASS_CUTOFF,
+ AL_DISTORTION_MAX_LOWPASS_CUTOFF);
+
+ al_effect_props_.Distortion.LowpassCutoff = lowpass_cutoff;
+}
+
+void EaxDistortionEffect::set_efx_eq_center()
+{
+ const auto eq_center = clamp(
+ eax_.flEQCenter,
+ AL_DISTORTION_MIN_EQCENTER,
+ AL_DISTORTION_MAX_EQCENTER);
+
+ al_effect_props_.Distortion.EQCenter = eq_center;
+}
+
+void EaxDistortionEffect::set_efx_eq_bandwidth()
+{
+ const auto eq_bandwidth = clamp(
+ eax_.flEdge,
+ AL_DISTORTION_MIN_EQBANDWIDTH,
+ AL_DISTORTION_MAX_EQBANDWIDTH);
+
+ al_effect_props_.Distortion.EQBandwidth = eq_bandwidth;
+}
+
+void EaxDistortionEffect::set_efx_defaults()
+{
+ set_efx_edge();
+ set_efx_gain();
+ set_efx_lowpass_cutoff();
+ set_efx_eq_center();
+ set_efx_eq_bandwidth();
+}
+
+// [[nodiscard]]
+bool EaxDistortionEffect::get(
+ const EaxEaxCall& eax_call)
+{
+ switch (eax_call.get_property_id())
+ {
+ case EAXDISTORTION_NONE:
+ break;
+
+ case EAXDISTORTION_ALLPARAMETERS:
+ eax_call.set_value<EaxDistortionEffectException>(eax_);
+ break;
+
+ case EAXDISTORTION_EDGE:
+ eax_call.set_value<EaxDistortionEffectException>(eax_.flEdge);
+ break;
+
+ case EAXDISTORTION_GAIN:
+ eax_call.set_value<EaxDistortionEffectException>(eax_.lGain);
+ break;
+
+ case EAXDISTORTION_LOWPASSCUTOFF:
+ eax_call.set_value<EaxDistortionEffectException>(eax_.flLowPassCutOff);
+ break;
+
+ case EAXDISTORTION_EQCENTER:
+ eax_call.set_value<EaxDistortionEffectException>(eax_.flEQCenter);
+ break;
+
+ case EAXDISTORTION_EQBANDWIDTH:
+ eax_call.set_value<EaxDistortionEffectException>(eax_.flEQBandwidth);
+ break;
+
+ default:
+ throw EaxDistortionEffectException{"Unsupported property id."};
+ }
+
+ return false;
+}
+
+void EaxDistortionEffect::validate_edge(
+ float flEdge)
+{
+ eax_validate_range<EaxDistortionEffectException>(
+ "Edge",
+ flEdge,
+ EAXDISTORTION_MINEDGE,
+ EAXDISTORTION_MAXEDGE);
+}
+
+void EaxDistortionEffect::validate_gain(
+ long lGain)
+{
+ eax_validate_range<EaxDistortionEffectException>(
+ "Gain",
+ lGain,
+ EAXDISTORTION_MINGAIN,
+ EAXDISTORTION_MAXGAIN);
+}
+
+void EaxDistortionEffect::validate_lowpass_cutoff(
+ float flLowPassCutOff)
+{
+ eax_validate_range<EaxDistortionEffectException>(
+ "Low-pass Cut-off",
+ flLowPassCutOff,
+ EAXDISTORTION_MINLOWPASSCUTOFF,
+ EAXDISTORTION_MAXLOWPASSCUTOFF);
+}
+
+void EaxDistortionEffect::validate_eq_center(
+ float flEQCenter)
+{
+ eax_validate_range<EaxDistortionEffectException>(
+ "EQ Center",
+ flEQCenter,
+ EAXDISTORTION_MINEQCENTER,
+ EAXDISTORTION_MAXEQCENTER);
+}
+
+void EaxDistortionEffect::validate_eq_bandwidth(
+ float flEQBandwidth)
+{
+ eax_validate_range<EaxDistortionEffectException>(
+ "EQ Bandwidth",
+ flEQBandwidth,
+ EAXDISTORTION_MINEQBANDWIDTH,
+ EAXDISTORTION_MAXEQBANDWIDTH);
+}
+
+void EaxDistortionEffect::validate_all(
+ const EAXDISTORTIONPROPERTIES& eax_all)
+{
+ validate_edge(eax_all.flEdge);
+ validate_gain(eax_all.lGain);
+ validate_lowpass_cutoff(eax_all.flLowPassCutOff);
+ validate_eq_center(eax_all.flEQCenter);
+ validate_eq_bandwidth(eax_all.flEQBandwidth);
+}
+
+void EaxDistortionEffect::defer_edge(
+ float flEdge)
+{
+ eax_d_.flEdge = flEdge;
+ eax_dirty_flags_.flEdge = (eax_.flEdge != eax_d_.flEdge);
+}
+
+void EaxDistortionEffect::defer_gain(
+ long lGain)
+{
+ eax_d_.lGain = lGain;
+ eax_dirty_flags_.lGain = (eax_.lGain != eax_d_.lGain);
+}
+
+void EaxDistortionEffect::defer_low_pass_cutoff(
+ float flLowPassCutOff)
+{
+ eax_d_.flLowPassCutOff = flLowPassCutOff;
+ eax_dirty_flags_.flLowPassCutOff = (eax_.flLowPassCutOff != eax_d_.flLowPassCutOff);
+}
+
+void EaxDistortionEffect::defer_eq_center(
+ float flEQCenter)
+{
+ eax_d_.flEQCenter = flEQCenter;
+ eax_dirty_flags_.flEQCenter = (eax_.flEQCenter != eax_d_.flEQCenter);
+}
+
+void EaxDistortionEffect::defer_eq_bandwidth(
+ float flEQBandwidth)
+{
+ eax_d_.flEQBandwidth = flEQBandwidth;
+ eax_dirty_flags_.flEQBandwidth = (eax_.flEQBandwidth != eax_d_.flEQBandwidth);
+}
+
+void EaxDistortionEffect::defer_all(
+ const EAXDISTORTIONPROPERTIES& eax_all)
+{
+ defer_edge(eax_all.flEdge);
+ defer_gain(eax_all.lGain);
+ defer_low_pass_cutoff(eax_all.flLowPassCutOff);
+ defer_eq_center(eax_all.flEQCenter);
+ defer_eq_bandwidth(eax_all.flEQBandwidth);
+}
+
+void EaxDistortionEffect::defer_edge(
+ const EaxEaxCall& eax_call)
+{
+ const auto& edge =
+ eax_call.get_value<EaxDistortionEffectException, const decltype(EAXDISTORTIONPROPERTIES::flEdge)>();
+
+ validate_edge(edge);
+ defer_edge(edge);
+}
+
+void EaxDistortionEffect::defer_gain(
+ const EaxEaxCall& eax_call)
+{
+ const auto& gain =
+ eax_call.get_value<EaxDistortionEffectException, const decltype(EAXDISTORTIONPROPERTIES::lGain)>();
+
+ validate_gain(gain);
+ defer_gain(gain);
+}
+
+void EaxDistortionEffect::defer_low_pass_cutoff(
+ const EaxEaxCall& eax_call)
+{
+ const auto& lowpass_cutoff =
+ eax_call.get_value<EaxDistortionEffectException, const decltype(EAXDISTORTIONPROPERTIES::flLowPassCutOff)>();
+
+ validate_lowpass_cutoff(lowpass_cutoff);
+ defer_low_pass_cutoff(lowpass_cutoff);
+}
+
+void EaxDistortionEffect::defer_eq_center(
+ const EaxEaxCall& eax_call)
+{
+ const auto& eq_center =
+ eax_call.get_value<EaxDistortionEffectException, const decltype(EAXDISTORTIONPROPERTIES::flEQCenter)>();
+
+ validate_eq_center(eq_center);
+ defer_eq_center(eq_center);
+}
+
+void EaxDistortionEffect::defer_eq_bandwidth(
+ const EaxEaxCall& eax_call)
+{
+ const auto& eq_bandwidth =
+ eax_call.get_value<EaxDistortionEffectException, const decltype(EAXDISTORTIONPROPERTIES::flEQBandwidth)>();
+
+ validate_eq_bandwidth(eq_bandwidth);
+ defer_eq_bandwidth(eq_bandwidth);
+}
+
+void EaxDistortionEffect::defer_all(
+ const EaxEaxCall& eax_call)
+{
+ const auto& all =
+ eax_call.get_value<EaxDistortionEffectException, const EAXDISTORTIONPROPERTIES>();
+
+ validate_all(all);
+ defer_all(all);
+}
+
+// [[nodiscard]]
+bool EaxDistortionEffect::apply_deferred()
+{
+ if (eax_dirty_flags_ == EaxDistortionEffectDirtyFlags{})
+ {
+ return false;
+ }
+
+ eax_ = eax_d_;
+
+ if (eax_dirty_flags_.flEdge)
+ {
+ set_efx_edge();
+ }
+
+ if (eax_dirty_flags_.lGain)
+ {
+ set_efx_gain();
+ }
+
+ if (eax_dirty_flags_.flLowPassCutOff)
+ {
+ set_efx_lowpass_cutoff();
+ }
+
+ if (eax_dirty_flags_.flEQCenter)
+ {
+ set_efx_eq_center();
+ }
+
+ if (eax_dirty_flags_.flEQBandwidth)
+ {
+ set_efx_eq_bandwidth();
+ }
+
+ eax_dirty_flags_ = EaxDistortionEffectDirtyFlags{};
+
+ return true;
+}
+
+// [[nodiscard]]
+bool EaxDistortionEffect::set(
+ const EaxEaxCall& eax_call)
+{
+ switch (eax_call.get_property_id())
+ {
+ case EAXDISTORTION_NONE:
+ break;
+
+ case EAXDISTORTION_ALLPARAMETERS:
+ defer_all(eax_call);
+ break;
+
+ case EAXDISTORTION_EDGE:
+ defer_edge(eax_call);
+ break;
+
+ case EAXDISTORTION_GAIN:
+ defer_gain(eax_call);
+ break;
+
+ case EAXDISTORTION_LOWPASSCUTOFF:
+ defer_low_pass_cutoff(eax_call);
+ break;
+
+ case EAXDISTORTION_EQCENTER:
+ defer_eq_center(eax_call);
+ break;
+
+ case EAXDISTORTION_EQBANDWIDTH:
+ defer_eq_bandwidth(eax_call);
+ break;
+
+ default:
+ throw EaxDistortionEffectException{"Unsupported property id."};
+ }
+
+ if (!eax_call.is_deferred())
+ {
+ return apply_deferred();
+ }
+
+ return false;
+}
+
+
+} // namespace
+
+
+EaxEffectUPtr eax_create_eax_distortion_effect(
+ EffectProps& al_effect_props)
+{
+ return std::make_unique<EaxDistortionEffect>(al_effect_props);
+}
+
+
+#endif // ALSOFT_EAX
diff --git a/al/effects/echo.cpp b/al/effects/echo.cpp
index 65f691c6..5ceb161d 100644
--- a/al/effects/echo.cpp
+++ b/al/effects/echo.cpp
@@ -7,6 +7,13 @@
#include "alc/effects/base.h"
#include "effects.h"
+#if ALSOFT_EAX
+#include "alnumeric.h"
+
+#include "al/eax_exception.h"
+#include "al/eax_utils.h"
+#endif // ALSOFT_EAX
+
namespace {
@@ -109,3 +116,530 @@ EffectProps genDefaultProps() noexcept
DEFINE_ALEFFECT_VTABLE(Echo);
const EffectProps EchoEffectProps{genDefaultProps()};
+
+#if ALSOFT_EAX
+namespace
+{
+
+
+using EaxEchoEffectDirtyFlagsValue = std::uint_least8_t;
+
+struct EaxEchoEffectDirtyFlags
+{
+ using EaxIsBitFieldStruct = bool;
+
+ EaxEchoEffectDirtyFlagsValue flDelay : 1;
+ EaxEchoEffectDirtyFlagsValue flLRDelay : 1;
+ EaxEchoEffectDirtyFlagsValue flDamping : 1;
+ EaxEchoEffectDirtyFlagsValue flFeedback : 1;
+ EaxEchoEffectDirtyFlagsValue flSpread : 1;
+}; // EaxEchoEffectDirtyFlags
+
+
+class EaxEchoEffect final :
+ public EaxEffect
+{
+public:
+ EaxEchoEffect(
+ EffectProps& al_effect_props);
+
+
+ // [[nodiscard]]
+ bool dispatch(
+ const EaxEaxCall& eax_call) override;
+
+
+private:
+ EffectProps& al_effect_props_;
+
+ EAXECHOPROPERTIES eax_{};
+ EAXECHOPROPERTIES eax_d_{};
+ EaxEchoEffectDirtyFlags eax_dirty_flags_{};
+
+
+ void set_eax_defaults();
+
+
+ void set_efx_delay();
+
+ void set_efx_lr_delay();
+
+ void set_efx_damping();
+
+ void set_efx_feedback();
+
+ void set_efx_spread();
+
+ void set_efx_defaults();
+
+
+ // [[nodiscard]]
+ bool get(
+ const EaxEaxCall& eax_call);
+
+
+ void validate_delay(
+ float flDelay);
+
+ void validate_lr_delay(
+ float flLRDelay);
+
+ void validate_damping(
+ float flDamping);
+
+ void validate_feedback(
+ float flFeedback);
+
+ void validate_spread(
+ float flSpread);
+
+ void validate_all(
+ const EAXECHOPROPERTIES& all);
+
+
+ void defer_delay(
+ float flDelay);
+
+ void defer_lr_delay(
+ float flLRDelay);
+
+ void defer_damping(
+ float flDamping);
+
+ void defer_feedback(
+ float flFeedback);
+
+ void defer_spread(
+ float flSpread);
+
+ void defer_all(
+ const EAXECHOPROPERTIES& all);
+
+
+ void defer_delay(
+ const EaxEaxCall& eax_call);
+
+ void defer_lr_delay(
+ const EaxEaxCall& eax_call);
+
+ void defer_damping(
+ const EaxEaxCall& eax_call);
+
+ void defer_feedback(
+ const EaxEaxCall& eax_call);
+
+ void defer_spread(
+ const EaxEaxCall& eax_call);
+
+ void defer_all(
+ const EaxEaxCall& eax_call);
+
+
+ bool apply_deferred();
+
+ bool set(
+ const EaxEaxCall& eax_call);
+}; // EaxEchoEffect
+
+
+class EaxEchoEffectException :
+ public EaxException
+{
+public:
+ explicit EaxEchoEffectException(
+ const char* message)
+ :
+ EaxException{"EAX_ECHO_EFFECT", message}
+ {
+ }
+}; // EaxEchoEffectException
+
+
+EaxEchoEffect::EaxEchoEffect(
+ EffectProps& al_effect_props)
+ :
+ al_effect_props_{al_effect_props}
+{
+ set_eax_defaults();
+ set_efx_defaults();
+}
+
+// [[nodiscard]]
+bool EaxEchoEffect::dispatch(
+ const EaxEaxCall& eax_call)
+{
+ return eax_call.is_get() ? get(eax_call) : set(eax_call);
+}
+
+void EaxEchoEffect::set_eax_defaults()
+{
+ eax_.flDelay = EAXECHO_DEFAULTDELAY;
+ eax_.flLRDelay = EAXECHO_DEFAULTLRDELAY;
+ eax_.flDamping = EAXECHO_DEFAULTDAMPING;
+ eax_.flFeedback = EAXECHO_DEFAULTFEEDBACK;
+ eax_.flSpread = EAXECHO_DEFAULTSPREAD;
+
+ eax_d_ = eax_;
+}
+
+void EaxEchoEffect::set_efx_delay()
+{
+ const auto delay = clamp(
+ eax_.flDelay,
+ AL_ECHO_MIN_DELAY,
+ AL_ECHO_MAX_DELAY);
+
+ al_effect_props_.Echo.Delay = delay;
+}
+
+void EaxEchoEffect::set_efx_lr_delay()
+{
+ const auto lr_delay = clamp(
+ eax_.flLRDelay,
+ AL_ECHO_MIN_LRDELAY,
+ AL_ECHO_MAX_LRDELAY);
+
+ al_effect_props_.Echo.LRDelay = lr_delay;
+}
+
+void EaxEchoEffect::set_efx_damping()
+{
+ const auto damping = clamp(
+ eax_.flDamping,
+ AL_ECHO_MIN_DAMPING,
+ AL_ECHO_MAX_DAMPING);
+
+ al_effect_props_.Echo.Damping = damping;
+}
+
+void EaxEchoEffect::set_efx_feedback()
+{
+ const auto feedback = clamp(
+ eax_.flFeedback,
+ AL_ECHO_MIN_FEEDBACK,
+ AL_ECHO_MAX_FEEDBACK);
+
+ al_effect_props_.Echo.Feedback = feedback;
+}
+
+void EaxEchoEffect::set_efx_spread()
+{
+ const auto spread = clamp(
+ eax_.flSpread,
+ AL_ECHO_MIN_SPREAD,
+ AL_ECHO_MAX_SPREAD);
+
+ al_effect_props_.Echo.Spread = spread;
+}
+
+void EaxEchoEffect::set_efx_defaults()
+{
+ set_efx_delay();
+ set_efx_lr_delay();
+ set_efx_damping();
+ set_efx_feedback();
+ set_efx_spread();
+}
+
+// [[nodiscard]]
+bool EaxEchoEffect::get(
+ const EaxEaxCall& eax_call)
+{
+ switch (eax_call.get_property_id())
+ {
+ case EAXECHO_NONE:
+ break;
+
+ case EAXECHO_ALLPARAMETERS:
+ eax_call.set_value<EaxEchoEffectException>(eax_);
+ break;
+
+ case EAXECHO_DELAY:
+ eax_call.set_value<EaxEchoEffectException>(eax_.flDelay);
+ break;
+
+ case EAXECHO_LRDELAY:
+ eax_call.set_value<EaxEchoEffectException>(eax_.flLRDelay);
+ break;
+
+ case EAXECHO_DAMPING:
+ eax_call.set_value<EaxEchoEffectException>(eax_.flDamping);
+ break;
+
+ case EAXECHO_FEEDBACK:
+ eax_call.set_value<EaxEchoEffectException>(eax_.flFeedback);
+ break;
+
+ case EAXECHO_SPREAD:
+ eax_call.set_value<EaxEchoEffectException>(eax_.flSpread);
+ break;
+
+ default:
+ throw EaxEchoEffectException{"Unsupported property id."};
+ }
+
+ return false;
+}
+
+void EaxEchoEffect::validate_delay(
+ float flDelay)
+{
+ eax_validate_range<EaxEchoEffectException>(
+ "Delay",
+ flDelay,
+ EAXECHO_MINDELAY,
+ EAXECHO_MAXDELAY);
+}
+
+void EaxEchoEffect::validate_lr_delay(
+ float flLRDelay)
+{
+ eax_validate_range<EaxEchoEffectException>(
+ "LR Delay",
+ flLRDelay,
+ EAXECHO_MINLRDELAY,
+ EAXECHO_MAXLRDELAY);
+}
+
+void EaxEchoEffect::validate_damping(
+ float flDamping)
+{
+ eax_validate_range<EaxEchoEffectException>(
+ "Damping",
+ flDamping,
+ EAXECHO_MINDAMPING,
+ EAXECHO_MAXDAMPING);
+}
+
+void EaxEchoEffect::validate_feedback(
+ float flFeedback)
+{
+ eax_validate_range<EaxEchoEffectException>(
+ "Feedback",
+ flFeedback,
+ EAXECHO_MINFEEDBACK,
+ EAXECHO_MAXFEEDBACK);
+}
+
+void EaxEchoEffect::validate_spread(
+ float flSpread)
+{
+ eax_validate_range<EaxEchoEffectException>(
+ "Spread",
+ flSpread,
+ EAXECHO_MINSPREAD,
+ EAXECHO_MAXSPREAD);
+}
+
+void EaxEchoEffect::validate_all(
+ const EAXECHOPROPERTIES& all)
+{
+ validate_delay(all.flDelay);
+ validate_lr_delay(all.flLRDelay);
+ validate_damping(all.flDamping);
+ validate_feedback(all.flFeedback);
+ validate_spread(all.flSpread);
+}
+
+void EaxEchoEffect::defer_delay(
+ float flDelay)
+{
+ eax_d_.flDelay = flDelay;
+ eax_dirty_flags_.flDelay = (eax_.flDelay != eax_d_.flDelay);
+}
+
+void EaxEchoEffect::defer_lr_delay(
+ float flLRDelay)
+{
+ eax_d_.flLRDelay = flLRDelay;
+ eax_dirty_flags_.flLRDelay = (eax_.flLRDelay != eax_d_.flLRDelay);
+}
+
+void EaxEchoEffect::defer_damping(
+ float flDamping)
+{
+ eax_d_.flDamping = flDamping;
+ eax_dirty_flags_.flDamping = (eax_.flDamping != eax_d_.flDamping);
+}
+
+void EaxEchoEffect::defer_feedback(
+ float flFeedback)
+{
+ eax_d_.flFeedback = flFeedback;
+ eax_dirty_flags_.flFeedback = (eax_.flFeedback != eax_d_.flFeedback);
+}
+
+void EaxEchoEffect::defer_spread(
+ float flSpread)
+{
+ eax_d_.flSpread = flSpread;
+ eax_dirty_flags_.flSpread = (eax_.flSpread != eax_d_.flSpread);
+}
+
+void EaxEchoEffect::defer_all(
+ const EAXECHOPROPERTIES& all)
+{
+ defer_delay(all.flDelay);
+ defer_lr_delay(all.flLRDelay);
+ defer_damping(all.flDamping);
+ defer_feedback(all.flFeedback);
+ defer_spread(all.flSpread);
+}
+
+void EaxEchoEffect::defer_delay(
+ const EaxEaxCall& eax_call)
+{
+ const auto& delay =
+ eax_call.get_value<EaxEchoEffectException, const decltype(EAXECHOPROPERTIES::flDelay)>();
+
+ validate_delay(delay);
+ defer_delay(delay);
+}
+
+void EaxEchoEffect::defer_lr_delay(
+ const EaxEaxCall& eax_call)
+{
+ const auto& lr_delay =
+ eax_call.get_value<EaxEchoEffectException, const decltype(EAXECHOPROPERTIES::flLRDelay)>();
+
+ validate_lr_delay(lr_delay);
+ defer_lr_delay(lr_delay);
+}
+
+void EaxEchoEffect::defer_damping(
+ const EaxEaxCall& eax_call)
+{
+ const auto& damping =
+ eax_call.get_value<EaxEchoEffectException, const decltype(EAXECHOPROPERTIES::flDamping)>();
+
+ validate_damping(damping);
+ defer_damping(damping);
+}
+
+void EaxEchoEffect::defer_feedback(
+ const EaxEaxCall& eax_call)
+{
+ const auto& feedback =
+ eax_call.get_value<EaxEchoEffectException, const decltype(EAXECHOPROPERTIES::flFeedback)>();
+
+ validate_feedback(feedback);
+ defer_feedback(feedback);
+}
+
+void EaxEchoEffect::defer_spread(
+ const EaxEaxCall& eax_call)
+{
+ const auto& spread =
+ eax_call.get_value<EaxEchoEffectException, const decltype(EAXECHOPROPERTIES::flSpread)>();
+
+ validate_spread(spread);
+ defer_spread(spread);
+}
+
+void EaxEchoEffect::defer_all(
+ const EaxEaxCall& eax_call)
+{
+ const auto& all =
+ eax_call.get_value<EaxEchoEffectException, const EAXECHOPROPERTIES>();
+
+ validate_all(all);
+ defer_all(all);
+}
+
+// [[nodiscard]]
+bool EaxEchoEffect::apply_deferred()
+{
+ if (eax_dirty_flags_ == EaxEchoEffectDirtyFlags{})
+ {
+ return false;
+ }
+
+ eax_ = eax_d_;
+
+ if (eax_dirty_flags_.flDelay)
+ {
+ set_efx_delay();
+ }
+
+ if (eax_dirty_flags_.flLRDelay)
+ {
+ set_efx_lr_delay();
+ }
+
+ if (eax_dirty_flags_.flDamping)
+ {
+ set_efx_damping();
+ }
+
+ if (eax_dirty_flags_.flFeedback)
+ {
+ set_efx_feedback();
+ }
+
+ if (eax_dirty_flags_.flSpread)
+ {
+ set_efx_spread();
+ }
+
+ eax_dirty_flags_ = EaxEchoEffectDirtyFlags{};
+
+ return true;
+}
+
+// [[nodiscard]]
+bool EaxEchoEffect::set(
+ const EaxEaxCall& eax_call)
+{
+ switch (eax_call.get_property_id())
+ {
+ case EAXECHO_NONE:
+ break;
+
+ case EAXECHO_ALLPARAMETERS:
+ defer_all(eax_call);
+ break;
+
+ case EAXECHO_DELAY:
+ defer_delay(eax_call);
+ break;
+
+ case EAXECHO_LRDELAY:
+ defer_lr_delay(eax_call);
+ break;
+
+ case EAXECHO_DAMPING:
+ defer_damping(eax_call);
+ break;
+
+ case EAXECHO_FEEDBACK:
+ defer_feedback(eax_call);
+ break;
+
+ case EAXECHO_SPREAD:
+ defer_spread(eax_call);
+ break;
+
+ default:
+ throw EaxEchoEffectException{"Unsupported property id."};
+ }
+
+ if (!eax_call.is_deferred())
+ {
+ return apply_deferred();
+ }
+
+ return false;
+}
+
+
+} // namespace
+
+
+EaxEffectUPtr eax_create_eax_echo_effect(
+ EffectProps& al_effect_props)
+{
+ return std::make_unique<EaxEchoEffect>(al_effect_props);
+}
+
+
+#endif // ALSOFT_EAX
diff --git a/al/effects/effects.cpp b/al/effects/effects.cpp
new file mode 100644
index 00000000..55abfdc5
--- /dev/null
+++ b/al/effects/effects.cpp
@@ -0,0 +1,106 @@
+#if ALSOFT_EAX
+
+
+#include <cassert>
+
+#include "AL/efx.h"
+
+#include "effects.h"
+
+
+EaxEffectUPtr eax_create_eax_null_effect();
+
+EaxEffectUPtr eax_create_eax_chorus_effect(
+ EffectProps& al_effect_props);
+
+EaxEffectUPtr eax_create_eax_distortion_effect(
+ EffectProps& al_effect_props);
+
+EaxEffectUPtr eax_create_eax_echo_effect(
+ EffectProps& al_effect_props);
+
+EaxEffectUPtr eax_create_eax_flanger_effect(
+ EffectProps& al_effect_props);
+
+EaxEffectUPtr eax_create_eax_frequency_shifter_effect(
+ EffectProps& al_effect_props);
+
+EaxEffectUPtr eax_create_eax_vocal_morpher_effect(
+ EffectProps& al_effect_props);
+
+EaxEffectUPtr eax_create_eax_pitch_shifter_effect(
+ EffectProps& al_effect_props);
+
+EaxEffectUPtr eax_create_eax_ring_modulator_effect(
+ EffectProps& al_effect_props);
+
+EaxEffectUPtr eax_create_eax_auto_wah_effect(
+ EffectProps& al_effect_props);
+
+EaxEffectUPtr eax_create_eax_compressor_effect(
+ EffectProps& al_effect_props);
+
+EaxEffectUPtr eax_create_eax_equalizer_effect(
+ EffectProps& al_effect_props);
+
+EaxEffectUPtr eax_create_eax_reverb_effect(
+ EffectProps& al_effect_props);
+
+
+EaxEffectUPtr eax_create_eax_effect(
+ ALenum al_effect_type,
+ EffectProps& al_effect_props)
+{
+#define EAX_PREFIX "[EAX_MAKE_EAX_EFFECT] "
+
+ switch (al_effect_type)
+ {
+ case AL_EFFECT_NULL:
+ return eax_create_eax_null_effect();
+
+ case AL_EFFECT_CHORUS:
+ return eax_create_eax_chorus_effect(al_effect_props);
+
+ case AL_EFFECT_DISTORTION:
+ return eax_create_eax_distortion_effect(al_effect_props);
+
+ case AL_EFFECT_ECHO:
+ return eax_create_eax_echo_effect(al_effect_props);
+
+ case AL_EFFECT_FLANGER:
+ return eax_create_eax_flanger_effect(al_effect_props);
+
+ case AL_EFFECT_FREQUENCY_SHIFTER:
+ return eax_create_eax_frequency_shifter_effect(al_effect_props);
+
+ case AL_EFFECT_VOCAL_MORPHER:
+ return eax_create_eax_vocal_morpher_effect(al_effect_props);
+
+ case AL_EFFECT_PITCH_SHIFTER:
+ return eax_create_eax_pitch_shifter_effect(al_effect_props);
+
+ case AL_EFFECT_RING_MODULATOR:
+ return eax_create_eax_ring_modulator_effect(al_effect_props);
+
+ case AL_EFFECT_AUTOWAH:
+ return eax_create_eax_auto_wah_effect(al_effect_props);
+
+ case AL_EFFECT_COMPRESSOR:
+ return eax_create_eax_compressor_effect(al_effect_props);
+
+ case AL_EFFECT_EQUALIZER:
+ return eax_create_eax_equalizer_effect(al_effect_props);
+
+ case AL_EFFECT_EAXREVERB:
+ return eax_create_eax_reverb_effect(al_effect_props);
+
+ default:
+ assert(false && "Unsupported AL effect type.");
+ return nullptr;
+ }
+
+#undef EAX_PREFIX
+}
+
+
+#endif // ALSOFT_EAX
diff --git a/al/effects/effects.h b/al/effects/effects.h
index 30b4bd75..6813beaa 100644
--- a/al/effects/effects.h
+++ b/al/effects/effects.h
@@ -5,6 +5,10 @@
#include "core/except.h"
+#if ALSOFT_EAX
+#include "al/eax_effect.h"
+#endif // ALSOFT_EAX
+
union EffectProps;
@@ -80,4 +84,11 @@ extern const EffectVtable VmorpherEffectVtable;
extern const EffectVtable DedicatedEffectVtable;
extern const EffectVtable ConvolutionEffectVtable;
+
+#if ALSOFT_EAX
+EaxEffectUPtr eax_create_eax_effect(
+ ALenum al_effect_type,
+ EffectProps& al_effect_props);
+#endif // ALSOFT_EAX
+
#endif /* AL_EFFECTS_EFFECTS_H */
diff --git a/al/effects/equalizer.cpp b/al/effects/equalizer.cpp
index 3c039688..c052db3e 100644
--- a/al/effects/equalizer.cpp
+++ b/al/effects/equalizer.cpp
@@ -7,6 +7,13 @@
#include "alc/effects/base.h"
#include "effects.h"
+#if ALSOFT_EAX
+#include "alnumeric.h"
+
+#include "al/eax_exception.h"
+#include "al/eax_utils.h"
+#endif // ALSOFT_EAX
+
namespace {
@@ -167,3 +174,862 @@ EffectProps genDefaultProps() noexcept
DEFINE_ALEFFECT_VTABLE(Equalizer);
const EffectProps EqualizerEffectProps{genDefaultProps()};
+
+#if ALSOFT_EAX
+namespace
+{
+
+
+using EaxEqualizerEffectDirtyFlagsValue = std::uint_least16_t;
+
+struct EaxEqualizerEffectDirtyFlags
+{
+ using EaxIsBitFieldStruct = bool;
+
+ EaxEqualizerEffectDirtyFlagsValue lLowGain : 1;
+ EaxEqualizerEffectDirtyFlagsValue flLowCutOff : 1;
+ EaxEqualizerEffectDirtyFlagsValue lMid1Gain : 1;
+ EaxEqualizerEffectDirtyFlagsValue flMid1Center : 1;
+ EaxEqualizerEffectDirtyFlagsValue flMid1Width : 1;
+ EaxEqualizerEffectDirtyFlagsValue lMid2Gain : 1;
+ EaxEqualizerEffectDirtyFlagsValue flMid2Center : 1;
+ EaxEqualizerEffectDirtyFlagsValue flMid2Width : 1;
+ EaxEqualizerEffectDirtyFlagsValue lHighGain : 1;
+ EaxEqualizerEffectDirtyFlagsValue flHighCutOff : 1;
+}; // EaxEqualizerEffectDirtyFlags
+
+
+class EaxEqualizerEffect final :
+ public EaxEffect
+{
+public:
+ EaxEqualizerEffect(
+ EffectProps& al_effect_props);
+
+
+ // [[nodiscard]]
+ bool dispatch(
+ const EaxEaxCall& eax_call) override;
+
+
+private:
+ EffectProps& al_effect_props_;
+
+ EAXEQUALIZERPROPERTIES eax_{};
+ EAXEQUALIZERPROPERTIES eax_d_{};
+ EaxEqualizerEffectDirtyFlags eax_dirty_flags_{};
+
+
+ void set_eax_defaults();
+
+
+ void set_efx_low_gain();
+
+ void set_efx_low_cutoff();
+
+ void set_efx_mid1_gain();
+
+ void set_efx_mid1_center();
+
+ void set_efx_mid1_width();
+
+ void set_efx_mid2_gain();
+
+ void set_efx_mid2_center();
+
+ void set_efx_mid2_width();
+
+ void set_efx_high_gain();
+
+ void set_efx_high_cutoff();
+
+ void set_efx_defaults();
+
+
+ // [[nodiscard]]
+ bool get(
+ const EaxEaxCall& eax_call);
+
+
+ void validate_low_gain(
+ long lLowGain);
+
+ void validate_low_cutoff(
+ float flLowCutOff);
+
+ void validate_mid1_gain(
+ long lMid1Gain);
+
+ void validate_mid1_center(
+ float flMid1Center);
+
+ void validate_mid1_width(
+ float flMid1Width);
+
+ void validate_mid2_gain(
+ long lMid2Gain);
+
+ void validate_mid2_center(
+ float flMid2Center);
+
+ void validate_mid2_width(
+ float flMid2Width);
+
+ void validate_high_gain(
+ long lHighGain);
+
+ void validate_high_cutoff(
+ float flHighCutOff);
+
+ void validate_all(
+ const EAXEQUALIZERPROPERTIES& all);
+
+
+ void defer_low_gain(
+ long lLowGain);
+
+ void defer_low_cutoff(
+ float flLowCutOff);
+
+ void defer_mid1_gain(
+ long lMid1Gain);
+
+ void defer_mid1_center(
+ float flMid1Center);
+
+ void defer_mid1_width(
+ float flMid1Width);
+
+ void defer_mid2_gain(
+ long lMid2Gain);
+
+ void defer_mid2_center(
+ float flMid2Center);
+
+ void defer_mid2_width(
+ float flMid2Width);
+
+ void defer_high_gain(
+ long lHighGain);
+
+ void defer_high_cutoff(
+ float flHighCutOff);
+
+ void defer_all(
+ const EAXEQUALIZERPROPERTIES& all);
+
+
+ void defer_low_gain(
+ const EaxEaxCall& eax_call);
+
+ void defer_low_cutoff(
+ const EaxEaxCall& eax_call);
+
+ void defer_mid1_gain(
+ const EaxEaxCall& eax_call);
+
+ void defer_mid1_center(
+ const EaxEaxCall& eax_call);
+
+ void defer_mid1_width(
+ const EaxEaxCall& eax_call);
+
+ void defer_mid2_gain(
+ const EaxEaxCall& eax_call);
+
+ void defer_mid2_center(
+ const EaxEaxCall& eax_call);
+
+ void defer_mid2_width(
+ const EaxEaxCall& eax_call);
+
+ void defer_high_gain(
+ const EaxEaxCall& eax_call);
+
+ void defer_high_cutoff(
+ const EaxEaxCall& eax_call);
+
+ void defer_all(
+ const EaxEaxCall& eax_call);
+
+
+ // [[nodiscard]]
+ bool apply_deferred();
+
+ // [[nodiscard]]
+ bool set(
+ const EaxEaxCall& eax_call);
+}; // EaxEqualizerEffect
+
+
+class EaxEqualizerEffectException :
+ public EaxException
+{
+public:
+ explicit EaxEqualizerEffectException(
+ const char* message)
+ :
+ EaxException{"EAX_EQUALIZER_EFFECT", message}
+ {
+ }
+}; // EaxEqualizerEffectException
+
+
+EaxEqualizerEffect::EaxEqualizerEffect(
+ EffectProps& al_effect_props)
+ :
+ al_effect_props_{al_effect_props}
+{
+ set_eax_defaults();
+ set_efx_defaults();
+}
+
+// [[nodiscard]]
+bool EaxEqualizerEffect::dispatch(
+ const EaxEaxCall& eax_call)
+{
+ return eax_call.is_get() ? get(eax_call) : set(eax_call);
+}
+
+void EaxEqualizerEffect::set_eax_defaults()
+{
+ eax_.lLowGain = EAXEQUALIZER_DEFAULTLOWGAIN;
+ eax_.flLowCutOff = EAXEQUALIZER_DEFAULTLOWCUTOFF;
+ eax_.lMid1Gain = EAXEQUALIZER_DEFAULTMID1GAIN;
+ eax_.flMid1Center = EAXEQUALIZER_DEFAULTMID1CENTER;
+ eax_.flMid1Width = EAXEQUALIZER_DEFAULTMID1WIDTH;
+ eax_.lMid2Gain = EAXEQUALIZER_DEFAULTMID2GAIN;
+ eax_.flMid2Center = EAXEQUALIZER_DEFAULTMID2CENTER;
+ eax_.flMid2Width = EAXEQUALIZER_DEFAULTMID2WIDTH;
+ eax_.lHighGain = EAXEQUALIZER_DEFAULTHIGHGAIN;
+ eax_.flHighCutOff = EAXEQUALIZER_DEFAULTHIGHCUTOFF;
+
+ eax_d_ = eax_;
+}
+
+void EaxEqualizerEffect::set_efx_low_gain()
+{
+ const auto low_gain = clamp(
+ level_mb_to_gain(static_cast<float>(eax_.lLowGain)),
+ AL_EQUALIZER_MIN_LOW_GAIN,
+ AL_EQUALIZER_MAX_LOW_GAIN);
+
+ al_effect_props_.Equalizer.LowGain = low_gain;
+}
+
+void EaxEqualizerEffect::set_efx_low_cutoff()
+{
+ const auto low_cutoff = clamp(
+ eax_.flLowCutOff,
+ AL_EQUALIZER_MIN_LOW_CUTOFF,
+ AL_EQUALIZER_MAX_LOW_CUTOFF);
+
+ al_effect_props_.Equalizer.LowCutoff = low_cutoff;
+}
+
+void EaxEqualizerEffect::set_efx_mid1_gain()
+{
+ const auto mid1_gain = clamp(
+ level_mb_to_gain(static_cast<float>(eax_.lMid1Gain)),
+ AL_EQUALIZER_MIN_MID1_GAIN,
+ AL_EQUALIZER_MAX_MID1_GAIN);
+
+ al_effect_props_.Equalizer.Mid1Gain = mid1_gain;
+}
+
+void EaxEqualizerEffect::set_efx_mid1_center()
+{
+ const auto mid1_center = clamp(
+ eax_.flMid1Center,
+ AL_EQUALIZER_MIN_MID1_CENTER,
+ AL_EQUALIZER_MAX_MID1_CENTER);
+
+ al_effect_props_.Equalizer.Mid1Center = mid1_center;
+}
+
+void EaxEqualizerEffect::set_efx_mid1_width()
+{
+ const auto mid1_width = clamp(
+ eax_.flMid1Width,
+ AL_EQUALIZER_MIN_MID1_WIDTH,
+ AL_EQUALIZER_MAX_MID1_WIDTH);
+
+ al_effect_props_.Equalizer.Mid1Width = mid1_width;
+}
+
+void EaxEqualizerEffect::set_efx_mid2_gain()
+{
+ const auto mid2_gain = clamp(
+ level_mb_to_gain(static_cast<float>(eax_.lMid2Gain)),
+ AL_EQUALIZER_MIN_MID2_GAIN,
+ AL_EQUALIZER_MAX_MID2_GAIN);
+
+ al_effect_props_.Equalizer.Mid2Gain = mid2_gain;
+}
+
+void EaxEqualizerEffect::set_efx_mid2_center()
+{
+ const auto mid2_center = clamp(
+ eax_.flMid2Center,
+ AL_EQUALIZER_MIN_MID2_CENTER,
+ AL_EQUALIZER_MAX_MID2_CENTER);
+
+ al_effect_props_.Equalizer.Mid2Center = mid2_center;
+}
+
+void EaxEqualizerEffect::set_efx_mid2_width()
+{
+ const auto mid2_width = clamp(
+ eax_.flMid2Width,
+ AL_EQUALIZER_MIN_MID2_WIDTH,
+ AL_EQUALIZER_MAX_MID2_WIDTH);
+
+ al_effect_props_.Equalizer.Mid2Width = mid2_width;
+}
+
+void EaxEqualizerEffect::set_efx_high_gain()
+{
+ const auto high_gain = clamp(
+ level_mb_to_gain(static_cast<float>(eax_.lHighGain)),
+ AL_EQUALIZER_MIN_HIGH_GAIN,
+ AL_EQUALIZER_MAX_HIGH_GAIN);
+
+ al_effect_props_.Equalizer.HighGain = high_gain;
+}
+
+void EaxEqualizerEffect::set_efx_high_cutoff()
+{
+ const auto high_cutoff = clamp(
+ eax_.flHighCutOff,
+ AL_EQUALIZER_MIN_HIGH_CUTOFF,
+ AL_EQUALIZER_MAX_HIGH_CUTOFF);
+
+ al_effect_props_.Equalizer.HighCutoff = high_cutoff;
+}
+
+void EaxEqualizerEffect::set_efx_defaults()
+{
+ set_efx_low_gain();
+ set_efx_low_cutoff();
+ set_efx_mid1_gain();
+ set_efx_mid1_center();
+ set_efx_mid1_width();
+ set_efx_mid2_gain();
+ set_efx_mid2_center();
+ set_efx_mid2_width();
+ set_efx_high_gain();
+ set_efx_high_cutoff();
+}
+
+// [[nodiscard]]
+bool EaxEqualizerEffect::get(
+ const EaxEaxCall& eax_call)
+{
+ switch (eax_call.get_property_id())
+ {
+ case EAXEQUALIZER_NONE:
+ break;
+
+ case EAXEQUALIZER_ALLPARAMETERS:
+ eax_call.set_value<EaxEqualizerEffectException>(eax_);
+ break;
+
+ case EAXEQUALIZER_LOWGAIN:
+ eax_call.set_value<EaxEqualizerEffectException>(eax_.lLowGain);
+ break;
+
+ case EAXEQUALIZER_LOWCUTOFF:
+ eax_call.set_value<EaxEqualizerEffectException>(eax_.flLowCutOff);
+ break;
+
+ case EAXEQUALIZER_MID1GAIN:
+ eax_call.set_value<EaxEqualizerEffectException>(eax_.lMid1Gain);
+ break;
+
+ case EAXEQUALIZER_MID1CENTER:
+ eax_call.set_value<EaxEqualizerEffectException>(eax_.flMid1Center);
+ break;
+
+ case EAXEQUALIZER_MID1WIDTH:
+ eax_call.set_value<EaxEqualizerEffectException>(eax_.flMid1Width);
+ break;
+
+ case EAXEQUALIZER_MID2GAIN:
+ eax_call.set_value<EaxEqualizerEffectException>(eax_.lMid2Gain);
+ break;
+
+ case EAXEQUALIZER_MID2CENTER:
+ eax_call.set_value<EaxEqualizerEffectException>(eax_.flMid2Center);
+ break;
+
+ case EAXEQUALIZER_MID2WIDTH:
+ eax_call.set_value<EaxEqualizerEffectException>(eax_.flMid2Width);
+ break;
+
+ case EAXEQUALIZER_HIGHGAIN:
+ eax_call.set_value<EaxEqualizerEffectException>(eax_.lHighGain);
+ break;
+
+ case EAXEQUALIZER_HIGHCUTOFF:
+ eax_call.set_value<EaxEqualizerEffectException>(eax_.flHighCutOff);
+ break;
+
+ default:
+ throw EaxEqualizerEffectException{"Unsupported property id."};
+ }
+
+ return false;
+}
+
+void EaxEqualizerEffect::validate_low_gain(
+ long lLowGain)
+{
+ eax_validate_range<EaxEqualizerEffectException>(
+ "Low Gain",
+ lLowGain,
+ EAXEQUALIZER_MINLOWGAIN,
+ EAXEQUALIZER_MAXLOWGAIN);
+}
+
+void EaxEqualizerEffect::validate_low_cutoff(
+ float flLowCutOff)
+{
+ eax_validate_range<EaxEqualizerEffectException>(
+ "Low Cutoff",
+ flLowCutOff,
+ EAXEQUALIZER_MINLOWCUTOFF,
+ EAXEQUALIZER_MAXLOWCUTOFF);
+}
+
+void EaxEqualizerEffect::validate_mid1_gain(
+ long lMid1Gain)
+{
+ eax_validate_range<EaxEqualizerEffectException>(
+ "Mid1 Gain",
+ lMid1Gain,
+ EAXEQUALIZER_MINMID1GAIN,
+ EAXEQUALIZER_MAXMID1GAIN);
+}
+
+void EaxEqualizerEffect::validate_mid1_center(
+ float flMid1Center)
+{
+ eax_validate_range<EaxEqualizerEffectException>(
+ "Mid1 Center",
+ flMid1Center,
+ EAXEQUALIZER_MINMID1CENTER,
+ EAXEQUALIZER_MAXMID1CENTER);
+}
+
+void EaxEqualizerEffect::validate_mid1_width(
+ float flMid1Width)
+{
+ eax_validate_range<EaxEqualizerEffectException>(
+ "Mid1 Width",
+ flMid1Width,
+ EAXEQUALIZER_MINMID1WIDTH,
+ EAXEQUALIZER_MAXMID1WIDTH);
+}
+
+void EaxEqualizerEffect::validate_mid2_gain(
+ long lMid2Gain)
+{
+ eax_validate_range<EaxEqualizerEffectException>(
+ "Mid2 Gain",
+ lMid2Gain,
+ EAXEQUALIZER_MINMID2GAIN,
+ EAXEQUALIZER_MAXMID2GAIN);
+}
+
+void EaxEqualizerEffect::validate_mid2_center(
+ float flMid2Center)
+{
+ eax_validate_range<EaxEqualizerEffectException>(
+ "Mid2 Center",
+ flMid2Center,
+ EAXEQUALIZER_MINMID2CENTER,
+ EAXEQUALIZER_MAXMID2CENTER);
+}
+
+void EaxEqualizerEffect::validate_mid2_width(
+ float flMid2Width)
+{
+ eax_validate_range<EaxEqualizerEffectException>(
+ "Mid2 Width",
+ flMid2Width,
+ EAXEQUALIZER_MINMID2WIDTH,
+ EAXEQUALIZER_MAXMID2WIDTH);
+}
+
+void EaxEqualizerEffect::validate_high_gain(
+ long lHighGain)
+{
+ eax_validate_range<EaxEqualizerEffectException>(
+ "High Gain",
+ lHighGain,
+ EAXEQUALIZER_MINHIGHGAIN,
+ EAXEQUALIZER_MAXHIGHGAIN);
+}
+
+void EaxEqualizerEffect::validate_high_cutoff(
+ float flHighCutOff)
+{
+ eax_validate_range<EaxEqualizerEffectException>(
+ "High Cutoff",
+ flHighCutOff,
+ EAXEQUALIZER_MINHIGHCUTOFF,
+ EAXEQUALIZER_MAXHIGHCUTOFF);
+}
+
+void EaxEqualizerEffect::validate_all(
+ const EAXEQUALIZERPROPERTIES& all)
+{
+ validate_low_gain(all.lLowGain);
+ validate_low_cutoff(all.flLowCutOff);
+ validate_mid1_gain(all.lMid1Gain);
+ validate_mid1_center(all.flMid1Center);
+ validate_mid1_width(all.flMid1Width);
+ validate_mid2_gain(all.lMid2Gain);
+ validate_mid2_center(all.flMid2Center);
+ validate_mid2_width(all.flMid2Width);
+ validate_high_gain(all.lHighGain);
+ validate_high_cutoff(all.flHighCutOff);
+}
+
+void EaxEqualizerEffect::defer_low_gain(
+ long lLowGain)
+{
+ eax_d_.lLowGain = lLowGain;
+ eax_dirty_flags_.lLowGain = (eax_.lLowGain != eax_d_.lLowGain);
+}
+
+void EaxEqualizerEffect::defer_low_cutoff(
+ float flLowCutOff)
+{
+ eax_d_.flLowCutOff = flLowCutOff;
+ eax_dirty_flags_.flLowCutOff = (eax_.flLowCutOff != eax_d_.flLowCutOff);
+}
+
+void EaxEqualizerEffect::defer_mid1_gain(
+ long lMid1Gain)
+{
+ eax_d_.lMid1Gain = lMid1Gain;
+ eax_dirty_flags_.lMid1Gain = (eax_.lMid1Gain != eax_d_.lMid1Gain);
+}
+
+void EaxEqualizerEffect::defer_mid1_center(
+ float flMid1Center)
+{
+ eax_d_.flMid1Center = flMid1Center;
+ eax_dirty_flags_.flMid1Center = (eax_.flMid1Center != eax_d_.flMid1Center);
+}
+
+void EaxEqualizerEffect::defer_mid1_width(
+ float flMid1Width)
+{
+ eax_d_.flMid1Width = flMid1Width;
+ eax_dirty_flags_.flMid1Width = (eax_.flMid1Width != eax_d_.flMid1Width);
+}
+
+void EaxEqualizerEffect::defer_mid2_gain(
+ long lMid2Gain)
+{
+ eax_d_.lMid2Gain = lMid2Gain;
+ eax_dirty_flags_.lMid2Gain = (eax_.lMid2Gain != eax_d_.lMid2Gain);
+}
+
+void EaxEqualizerEffect::defer_mid2_center(
+ float flMid2Center)
+{
+ eax_d_.flMid2Center = flMid2Center;
+ eax_dirty_flags_.flMid2Center = (eax_.flMid2Center != eax_d_.flMid2Center);
+}
+
+void EaxEqualizerEffect::defer_mid2_width(
+ float flMid2Width)
+{
+ eax_d_.flMid2Width = flMid2Width;
+ eax_dirty_flags_.flMid2Width = (eax_.flMid2Width != eax_d_.flMid2Width);
+}
+
+void EaxEqualizerEffect::defer_high_gain(
+ long lHighGain)
+{
+ eax_d_.lHighGain = lHighGain;
+ eax_dirty_flags_.lHighGain = (eax_.lHighGain != eax_d_.lHighGain);
+}
+
+void EaxEqualizerEffect::defer_high_cutoff(
+ float flHighCutOff)
+{
+ eax_d_.flHighCutOff = flHighCutOff;
+ eax_dirty_flags_.flHighCutOff = (eax_.flHighCutOff != eax_d_.flHighCutOff);
+}
+
+void EaxEqualizerEffect::defer_all(
+ const EAXEQUALIZERPROPERTIES& all)
+{
+ defer_low_gain(all.lLowGain);
+ defer_low_cutoff(all.flLowCutOff);
+ defer_mid1_gain(all.lMid1Gain);
+ defer_mid1_center(all.flMid1Center);
+ defer_mid1_width(all.flMid1Width);
+ defer_mid2_gain(all.lMid2Gain);
+ defer_mid2_center(all.flMid2Center);
+ defer_mid2_width(all.flMid2Width);
+ defer_high_gain(all.lHighGain);
+ defer_high_cutoff(all.flHighCutOff);
+}
+
+void EaxEqualizerEffect::defer_low_gain(
+ const EaxEaxCall& eax_call)
+{
+ const auto& low_gain =
+ eax_call.get_value<EaxEqualizerEffectException, const decltype(EAXEQUALIZERPROPERTIES::lLowGain)>();
+
+ validate_low_gain(low_gain);
+ defer_low_gain(low_gain);
+}
+
+void EaxEqualizerEffect::defer_low_cutoff(
+ const EaxEaxCall& eax_call)
+{
+ const auto& low_cutoff =
+ eax_call.get_value<EaxEqualizerEffectException, const decltype(EAXEQUALIZERPROPERTIES::flLowCutOff)>();
+
+ validate_low_cutoff(low_cutoff);
+ defer_low_cutoff(low_cutoff);
+}
+
+void EaxEqualizerEffect::defer_mid1_gain(
+ const EaxEaxCall& eax_call)
+{
+ const auto& mid1_gain =
+ eax_call.get_value<EaxEqualizerEffectException, const decltype(EAXEQUALIZERPROPERTIES::lMid1Gain)>();
+
+ validate_mid1_gain(mid1_gain);
+ defer_mid1_gain(mid1_gain);
+}
+
+void EaxEqualizerEffect::defer_mid1_center(
+ const EaxEaxCall& eax_call)
+{
+ const auto& mid1_center =
+ eax_call.get_value<EaxEqualizerEffectException, const decltype(EAXEQUALIZERPROPERTIES::flMid1Center)>();
+
+ validate_mid1_center(mid1_center);
+ defer_mid1_center(mid1_center);
+}
+
+void EaxEqualizerEffect::defer_mid1_width(
+ const EaxEaxCall& eax_call)
+{
+ const auto& mid1_width =
+ eax_call.get_value<EaxEqualizerEffectException, const decltype(EAXEQUALIZERPROPERTIES::flMid1Width)>();
+
+ validate_mid1_width(mid1_width);
+ defer_mid1_width(mid1_width);
+}
+
+void EaxEqualizerEffect::defer_mid2_gain(
+ const EaxEaxCall& eax_call)
+{
+ const auto& mid2_gain =
+ eax_call.get_value<EaxEqualizerEffectException, const decltype(EAXEQUALIZERPROPERTIES::lMid2Gain)>();
+
+ validate_mid2_gain(mid2_gain);
+ defer_mid2_gain(mid2_gain);
+}
+
+void EaxEqualizerEffect::defer_mid2_center(
+ const EaxEaxCall& eax_call)
+{
+ const auto& mid2_center =
+ eax_call.get_value<EaxEqualizerEffectException, const decltype(EAXEQUALIZERPROPERTIES::flMid2Center)>();
+
+ validate_mid2_center(mid2_center);
+ defer_mid2_center(mid2_center);
+}
+
+void EaxEqualizerEffect::defer_mid2_width(
+ const EaxEaxCall& eax_call)
+{
+ const auto& mid2_width =
+ eax_call.get_value<EaxEqualizerEffectException, const decltype(EAXEQUALIZERPROPERTIES::flMid2Width)>();
+
+ validate_mid2_width(mid2_width);
+ defer_mid2_width(mid2_width);
+}
+
+void EaxEqualizerEffect::defer_high_gain(
+ const EaxEaxCall& eax_call)
+{
+ const auto& high_gain =
+ eax_call.get_value<EaxEqualizerEffectException, const decltype(EAXEQUALIZERPROPERTIES::lHighGain)>();
+
+ validate_high_gain(high_gain);
+ defer_high_gain(high_gain);
+}
+
+void EaxEqualizerEffect::defer_high_cutoff(
+ const EaxEaxCall& eax_call)
+{
+ const auto& high_cutoff =
+ eax_call.get_value<EaxEqualizerEffectException, const decltype(EAXEQUALIZERPROPERTIES::flHighCutOff)>();
+
+ validate_high_cutoff(high_cutoff);
+ defer_high_cutoff(high_cutoff);
+}
+
+void EaxEqualizerEffect::defer_all(
+ const EaxEaxCall& eax_call)
+{
+ const auto& all =
+ eax_call.get_value<EaxEqualizerEffectException, const EAXEQUALIZERPROPERTIES>();
+
+ validate_all(all);
+ defer_all(all);
+}
+
+// [[nodiscard]]
+bool EaxEqualizerEffect::apply_deferred()
+{
+ if (eax_dirty_flags_ == EaxEqualizerEffectDirtyFlags{})
+ {
+ return false;
+ }
+
+ eax_ = eax_d_;
+
+ if (eax_dirty_flags_.lLowGain)
+ {
+ set_efx_low_gain();
+ }
+
+ if (eax_dirty_flags_.flLowCutOff)
+ {
+ set_efx_low_cutoff();
+ }
+
+ if (eax_dirty_flags_.lMid1Gain)
+ {
+ set_efx_mid1_gain();
+ }
+
+ if (eax_dirty_flags_.flMid1Center)
+ {
+ set_efx_mid1_center();
+ }
+
+ if (eax_dirty_flags_.flMid1Width)
+ {
+ set_efx_mid1_width();
+ }
+
+ if (eax_dirty_flags_.lMid2Gain)
+ {
+ set_efx_mid2_gain();
+ }
+
+ if (eax_dirty_flags_.flMid2Center)
+ {
+ set_efx_mid2_center();
+ }
+
+ if (eax_dirty_flags_.flMid2Width)
+ {
+ set_efx_mid2_width();
+ }
+
+ if (eax_dirty_flags_.lHighGain)
+ {
+ set_efx_high_gain();
+ }
+
+ if (eax_dirty_flags_.flHighCutOff)
+ {
+ set_efx_high_cutoff();
+ }
+
+ eax_dirty_flags_ = EaxEqualizerEffectDirtyFlags{};
+
+ return true;
+}
+
+// [[nodiscard]]
+bool EaxEqualizerEffect::set(
+ const EaxEaxCall& eax_call)
+{
+ switch (eax_call.get_property_id())
+ {
+ case EAXEQUALIZER_NONE:
+ break;
+
+ case EAXEQUALIZER_ALLPARAMETERS:
+ defer_all(eax_call);
+ break;
+
+ case EAXEQUALIZER_LOWGAIN:
+ defer_low_gain(eax_call);
+ break;
+
+ case EAXEQUALIZER_LOWCUTOFF:
+ defer_low_cutoff(eax_call);
+ break;
+
+ case EAXEQUALIZER_MID1GAIN:
+ defer_mid1_gain(eax_call);
+ break;
+
+ case EAXEQUALIZER_MID1CENTER:
+ defer_mid1_center(eax_call);
+ break;
+
+ case EAXEQUALIZER_MID1WIDTH:
+ defer_mid1_width(eax_call);
+ break;
+
+ case EAXEQUALIZER_MID2GAIN:
+ defer_mid2_gain(eax_call);
+ break;
+
+ case EAXEQUALIZER_MID2CENTER:
+ defer_mid2_center(eax_call);
+ break;
+
+ case EAXEQUALIZER_MID2WIDTH:
+ defer_mid2_width(eax_call);
+ break;
+
+ case EAXEQUALIZER_HIGHGAIN:
+ defer_high_gain(eax_call);
+ break;
+
+ case EAXEQUALIZER_HIGHCUTOFF:
+ defer_high_cutoff(eax_call);
+ break;
+
+ default:
+ throw EaxEqualizerEffectException{"Unsupported property id."};
+ }
+
+ if (!eax_call.is_deferred())
+ {
+ return apply_deferred();
+ }
+
+ return false;
+}
+
+
+} // namespace
+
+
+EaxEffectUPtr eax_create_eax_equalizer_effect(
+ EffectProps& al_effect_props)
+{
+ return std::make_unique<EaxEqualizerEffect>(al_effect_props);
+}
+
+
+#endif // ALSOFT_EAX
diff --git a/al/effects/fshifter.cpp b/al/effects/fshifter.cpp
index 4aa860a2..aa4ddadb 100644
--- a/al/effects/fshifter.cpp
+++ b/al/effects/fshifter.cpp
@@ -10,6 +10,15 @@
#include "aloptional.h"
#include "effects.h"
+#if ALSOFT_EAX
+#include <cassert>
+
+#include "alnumeric.h"
+
+#include "al/eax_exception.h"
+#include "al/eax_utils.h"
+#endif // ALSOFT_EAX
+
namespace {
@@ -128,3 +137,408 @@ EffectProps genDefaultProps() noexcept
DEFINE_ALEFFECT_VTABLE(Fshifter);
const EffectProps FshifterEffectProps{genDefaultProps()};
+
+#if ALSOFT_EAX
+namespace
+{
+
+
+using EaxFrequencyShifterEffectDirtyFlagsValue = std::uint_least8_t;
+
+struct EaxFrequencyShifterEffectDirtyFlags
+{
+ using EaxIsBitFieldStruct = bool;
+
+ EaxFrequencyShifterEffectDirtyFlagsValue flFrequency : 1;
+ EaxFrequencyShifterEffectDirtyFlagsValue ulLeftDirection : 1;
+ EaxFrequencyShifterEffectDirtyFlagsValue ulRightDirection : 1;
+}; // EaxFrequencyShifterEffectDirtyFlags
+
+
+class EaxFrequencyShifterEffect final :
+ public EaxEffect
+{
+public:
+ EaxFrequencyShifterEffect(
+ EffectProps& al_effect_props);
+
+
+ // [[nodiscard]]
+ bool dispatch(
+ const EaxEaxCall& eax_call) override;
+
+
+private:
+ EffectProps& al_effect_props_;
+
+ EAXFREQUENCYSHIFTERPROPERTIES eax_{};
+ EAXFREQUENCYSHIFTERPROPERTIES eax_d_{};
+ EaxFrequencyShifterEffectDirtyFlags eax_dirty_flags_{};
+
+
+ void set_eax_defaults();
+
+
+ void set_efx_frequency();
+
+ void set_efx_left_direction();
+
+ void set_efx_right_direction();
+
+ void set_efx_defaults();
+
+
+ // [[nodiscard]]
+ bool get(
+ const EaxEaxCall& eax_call);
+
+
+ void validate_frequency(
+ float flFrequency);
+
+ void validate_left_direction(
+ unsigned long ulLeftDirection);
+
+ void validate_right_direction(
+ unsigned long ulRightDirection);
+
+ void validate_all(
+ const EAXFREQUENCYSHIFTERPROPERTIES& all);
+
+
+ void defer_frequency(
+ float flFrequency);
+
+ void defer_left_direction(
+ unsigned long ulLeftDirection);
+
+ void defer_right_direction(
+ unsigned long ulRightDirection);
+
+ void defer_all(
+ const EAXFREQUENCYSHIFTERPROPERTIES& all);
+
+
+ void defer_frequency(
+ const EaxEaxCall& eax_call);
+
+ void defer_left_direction(
+ const EaxEaxCall& eax_call);
+
+ void defer_right_direction(
+ const EaxEaxCall& eax_call);
+
+ void defer_all(
+ const EaxEaxCall& eax_call);
+
+
+ // [[nodiscard]]
+ bool apply_deferred();
+
+ // [[nodiscard]]
+ bool set(
+ const EaxEaxCall& eax_call);
+}; // EaxFrequencyShifterEffect
+
+
+class EaxFrequencyShifterEffectException :
+ public EaxException
+{
+public:
+ explicit EaxFrequencyShifterEffectException(
+ const char* message)
+ :
+ EaxException{"EAX_FREQUENCY_SHIFTER_EFFECT", message}
+ {
+ }
+}; // EaxFrequencyShifterEffectException
+
+
+EaxFrequencyShifterEffect::EaxFrequencyShifterEffect(
+ EffectProps& al_effect_props)
+ :
+ al_effect_props_{al_effect_props}
+{
+ set_eax_defaults();
+ set_efx_defaults();
+}
+
+// [[nodiscard]]
+bool EaxFrequencyShifterEffect::dispatch(
+ const EaxEaxCall& eax_call)
+{
+ return eax_call.is_get() ? get(eax_call) : set(eax_call);
+}
+
+void EaxFrequencyShifterEffect::set_eax_defaults()
+{
+ eax_.flFrequency = EAXFREQUENCYSHIFTER_DEFAULTFREQUENCY;
+ eax_.ulLeftDirection = EAXFREQUENCYSHIFTER_DEFAULTLEFTDIRECTION;
+ eax_.ulRightDirection = EAXFREQUENCYSHIFTER_DEFAULTRIGHTDIRECTION;
+
+ eax_d_ = eax_;
+}
+
+void EaxFrequencyShifterEffect::set_efx_frequency()
+{
+ const auto frequency = clamp(
+ eax_.flFrequency,
+ AL_FREQUENCY_SHIFTER_MIN_FREQUENCY,
+ AL_FREQUENCY_SHIFTER_MAX_FREQUENCY);
+
+ al_effect_props_.Fshifter.Frequency = frequency;
+}
+
+void EaxFrequencyShifterEffect::set_efx_left_direction()
+{
+ const auto left_direction = clamp(
+ static_cast<ALint>(eax_.ulLeftDirection),
+ AL_FREQUENCY_SHIFTER_MIN_LEFT_DIRECTION,
+ AL_FREQUENCY_SHIFTER_MAX_LEFT_DIRECTION);
+
+ const auto efx_left_direction = DirectionFromEmum(left_direction);
+ assert(efx_left_direction.has_value());
+ al_effect_props_.Fshifter.LeftDirection = *efx_left_direction;
+}
+
+void EaxFrequencyShifterEffect::set_efx_right_direction()
+{
+ const auto right_direction = clamp(
+ static_cast<ALint>(eax_.ulRightDirection),
+ AL_FREQUENCY_SHIFTER_MIN_RIGHT_DIRECTION,
+ AL_FREQUENCY_SHIFTER_MAX_RIGHT_DIRECTION);
+
+ const auto efx_right_direction = DirectionFromEmum(right_direction);
+ assert(efx_right_direction.has_value());
+ al_effect_props_.Fshifter.RightDirection = *efx_right_direction;
+}
+
+void EaxFrequencyShifterEffect::set_efx_defaults()
+{
+ set_efx_frequency();
+ set_efx_left_direction();
+ set_efx_right_direction();
+}
+
+// [[nodiscard]]
+bool EaxFrequencyShifterEffect::get(
+ const EaxEaxCall& eax_call)
+{
+ switch (eax_call.get_property_id())
+ {
+ case EAXFREQUENCYSHIFTER_NONE:
+ break;
+
+ case EAXFREQUENCYSHIFTER_ALLPARAMETERS:
+ eax_call.set_value<EaxFrequencyShifterEffectException>(eax_);
+ break;
+
+ case EAXFREQUENCYSHIFTER_FREQUENCY:
+ eax_call.set_value<EaxFrequencyShifterEffectException>(eax_.flFrequency);
+ break;
+
+ case EAXFREQUENCYSHIFTER_LEFTDIRECTION:
+ eax_call.set_value<EaxFrequencyShifterEffectException>(eax_.ulLeftDirection);
+ break;
+
+ case EAXFREQUENCYSHIFTER_RIGHTDIRECTION:
+ eax_call.set_value<EaxFrequencyShifterEffectException>(eax_.ulRightDirection);
+ break;
+
+ default:
+ throw EaxFrequencyShifterEffectException{"Unsupported property id."};
+ }
+
+ return false;
+}
+
+void EaxFrequencyShifterEffect::validate_frequency(
+ float flFrequency)
+{
+ eax_validate_range<EaxFrequencyShifterEffectException>(
+ "Frequency",
+ flFrequency,
+ EAXFREQUENCYSHIFTER_MINFREQUENCY,
+ EAXFREQUENCYSHIFTER_MAXFREQUENCY);
+}
+
+void EaxFrequencyShifterEffect::validate_left_direction(
+ unsigned long ulLeftDirection)
+{
+ eax_validate_range<EaxFrequencyShifterEffectException>(
+ "Left Direction",
+ ulLeftDirection,
+ EAXFREQUENCYSHIFTER_MINLEFTDIRECTION,
+ EAXFREQUENCYSHIFTER_MAXLEFTDIRECTION);
+}
+
+void EaxFrequencyShifterEffect::validate_right_direction(
+ unsigned long ulRightDirection)
+{
+ eax_validate_range<EaxFrequencyShifterEffectException>(
+ "Right Direction",
+ ulRightDirection,
+ EAXFREQUENCYSHIFTER_MINRIGHTDIRECTION,
+ EAXFREQUENCYSHIFTER_MAXRIGHTDIRECTION);
+}
+
+void EaxFrequencyShifterEffect::validate_all(
+ const EAXFREQUENCYSHIFTERPROPERTIES& all)
+{
+ validate_frequency(all.flFrequency);
+ validate_left_direction(all.ulLeftDirection);
+ validate_right_direction(all.ulRightDirection);
+}
+
+void EaxFrequencyShifterEffect::defer_frequency(
+ float flFrequency)
+{
+ eax_d_.flFrequency = flFrequency;
+ eax_dirty_flags_.flFrequency = (eax_.flFrequency != eax_d_.flFrequency);
+}
+
+void EaxFrequencyShifterEffect::defer_left_direction(
+ unsigned long ulLeftDirection)
+{
+ eax_d_.ulLeftDirection = ulLeftDirection;
+ eax_dirty_flags_.ulLeftDirection = (eax_.ulLeftDirection != eax_d_.ulLeftDirection);
+}
+
+void EaxFrequencyShifterEffect::defer_right_direction(
+ unsigned long ulRightDirection)
+{
+ eax_d_.ulRightDirection = ulRightDirection;
+ eax_dirty_flags_.ulRightDirection = (eax_.ulRightDirection != eax_d_.ulRightDirection);
+}
+
+void EaxFrequencyShifterEffect::defer_all(
+ const EAXFREQUENCYSHIFTERPROPERTIES& all)
+{
+ defer_frequency(all.flFrequency);
+ defer_left_direction(all.ulLeftDirection);
+ defer_right_direction(all.ulRightDirection);
+}
+
+void EaxFrequencyShifterEffect::defer_frequency(
+ const EaxEaxCall& eax_call)
+{
+ const auto& frequency =
+ eax_call.get_value<
+ EaxFrequencyShifterEffectException, const decltype(EAXFREQUENCYSHIFTERPROPERTIES::flFrequency)>();
+
+ validate_frequency(frequency);
+ defer_frequency(frequency);
+}
+
+void EaxFrequencyShifterEffect::defer_left_direction(
+ const EaxEaxCall& eax_call)
+{
+ const auto& left_direction =
+ eax_call.get_value<
+ EaxFrequencyShifterEffectException, const decltype(EAXFREQUENCYSHIFTERPROPERTIES::ulLeftDirection)>();
+
+ validate_left_direction(left_direction);
+ defer_left_direction(left_direction);
+}
+
+void EaxFrequencyShifterEffect::defer_right_direction(
+ const EaxEaxCall& eax_call)
+{
+ const auto& right_direction =
+ eax_call.get_value<
+ EaxFrequencyShifterEffectException, const decltype(EAXFREQUENCYSHIFTERPROPERTIES::ulRightDirection)>();
+
+ validate_right_direction(right_direction);
+ defer_right_direction(right_direction);
+}
+
+void EaxFrequencyShifterEffect::defer_all(
+ const EaxEaxCall& eax_call)
+{
+ const auto& all =
+ eax_call.get_value<
+ EaxFrequencyShifterEffectException, const EAXFREQUENCYSHIFTERPROPERTIES>();
+
+ validate_all(all);
+ defer_all(all);
+}
+
+// [[nodiscard]]
+bool EaxFrequencyShifterEffect::apply_deferred()
+{
+ if (eax_dirty_flags_ == EaxFrequencyShifterEffectDirtyFlags{})
+ {
+ return false;
+ }
+
+ eax_ = eax_d_;
+
+ if (eax_dirty_flags_.flFrequency)
+ {
+ set_efx_frequency();
+ }
+
+ if (eax_dirty_flags_.ulLeftDirection)
+ {
+ set_efx_left_direction();
+ }
+
+ if (eax_dirty_flags_.ulRightDirection)
+ {
+ set_efx_right_direction();
+ }
+
+ eax_dirty_flags_ = EaxFrequencyShifterEffectDirtyFlags{};
+
+ return true;
+}
+
+// [[nodiscard]]
+bool EaxFrequencyShifterEffect::set(
+ const EaxEaxCall& eax_call)
+{
+ switch (eax_call.get_property_id())
+ {
+ case EAXFREQUENCYSHIFTER_NONE:
+ break;
+
+ case EAXFREQUENCYSHIFTER_ALLPARAMETERS:
+ defer_all(eax_call);
+ break;
+
+ case EAXFREQUENCYSHIFTER_FREQUENCY:
+ defer_frequency(eax_call);
+ break;
+
+ case EAXFREQUENCYSHIFTER_LEFTDIRECTION:
+ defer_left_direction(eax_call);
+ break;
+
+ case EAXFREQUENCYSHIFTER_RIGHTDIRECTION:
+ defer_right_direction(eax_call);
+ break;
+
+ default:
+ throw EaxFrequencyShifterEffectException{"Unsupported property id."};
+ }
+
+ if (!eax_call.is_deferred())
+ {
+ return apply_deferred();
+ }
+
+ return false;
+}
+
+
+} // namespace
+
+
+EaxEffectUPtr eax_create_eax_frequency_shifter_effect(
+ EffectProps& al_effect_props)
+{
+ return std::make_unique<EaxFrequencyShifterEffect>(al_effect_props);
+}
+
+
+#endif // ALSOFT_EAX
diff --git a/al/effects/modulator.cpp b/al/effects/modulator.cpp
index d8a1bed6..6a30dc09 100644
--- a/al/effects/modulator.cpp
+++ b/al/effects/modulator.cpp
@@ -10,6 +10,15 @@
#include "aloptional.h"
#include "effects.h"
+#if ALSOFT_EAX
+#include <cassert>
+
+#include "alnumeric.h"
+
+#include "al/eax_exception.h"
+#include "al/eax_utils.h"
+#endif // ALSOFT_EAX
+
namespace {
@@ -134,3 +143,405 @@ EffectProps genDefaultProps() noexcept
DEFINE_ALEFFECT_VTABLE(Modulator);
const EffectProps ModulatorEffectProps{genDefaultProps()};
+
+#if ALSOFT_EAX
+namespace
+{
+
+
+using EaxRingModulatorEffectDirtyFlagsValue = std::uint_least8_t;
+
+struct EaxRingModulatorEffectDirtyFlags
+{
+ using EaxIsBitFieldStruct = bool;
+
+ EaxRingModulatorEffectDirtyFlagsValue flFrequency : 1;
+ EaxRingModulatorEffectDirtyFlagsValue flHighPassCutOff : 1;
+ EaxRingModulatorEffectDirtyFlagsValue ulWaveform : 1;
+}; // EaxPitchShifterEffectDirtyFlags
+
+
+class EaxRingModulatorEffect final :
+ public EaxEffect
+{
+public:
+ EaxRingModulatorEffect(
+ EffectProps& al_effect_props);
+
+
+ // [[nodiscard]]
+ bool dispatch(
+ const EaxEaxCall& eax_call) override;
+
+
+private:
+ EffectProps& al_effect_props_;
+
+ EAXRINGMODULATORPROPERTIES eax_{};
+ EAXRINGMODULATORPROPERTIES eax_d_{};
+ EaxRingModulatorEffectDirtyFlags eax_dirty_flags_{};
+
+
+ void set_eax_defaults();
+
+
+ void set_efx_frequency();
+
+ void set_efx_high_pass_cutoff();
+
+ void set_efx_waveform();
+
+ void set_efx_defaults();
+
+
+ // [[nodiscard]]
+ bool get(
+ const EaxEaxCall& eax_call);
+
+
+ void validate_frequency(
+ float flFrequency);
+
+ void validate_high_pass_cutoff(
+ float flHighPassCutOff);
+
+ void validate_waveform(
+ unsigned long ulWaveform);
+
+ void validate_all(
+ const EAXRINGMODULATORPROPERTIES& all);
+
+
+ void defer_frequency(
+ float flFrequency);
+
+ void defer_high_pass_cutoff(
+ float flHighPassCutOff);
+
+ void defer_waveform(
+ unsigned long ulWaveform);
+
+ void defer_all(
+ const EAXRINGMODULATORPROPERTIES& all);
+
+
+ void defer_frequency(
+ const EaxEaxCall& eax_call);
+
+ void defer_high_pass_cutoff(
+ const EaxEaxCall& eax_call);
+
+ void defer_waveform(
+ const EaxEaxCall& eax_call);
+
+ void defer_all(
+ const EaxEaxCall& eax_call);
+
+
+ // [[nodiscard]]
+ bool apply_deferred();
+
+ // [[nodiscard]]
+ bool set(
+ const EaxEaxCall& eax_call);
+}; // EaxRingModulatorEffect
+
+
+class EaxRingModulatorEffectException :
+ public EaxException
+{
+public:
+ explicit EaxRingModulatorEffectException(
+ const char* message)
+ :
+ EaxException{"EAX_RING_MODULATOR_EFFECT", message}
+ {
+ }
+}; // EaxRingModulatorEffectException
+
+
+EaxRingModulatorEffect::EaxRingModulatorEffect(
+ EffectProps& al_effect_props)
+ :
+ al_effect_props_{al_effect_props}
+{
+ set_eax_defaults();
+ set_efx_defaults();
+}
+
+// [[nodiscard]]
+bool EaxRingModulatorEffect::dispatch(
+ const EaxEaxCall& eax_call)
+{
+ return eax_call.is_get() ? get(eax_call) : set(eax_call);
+}
+
+void EaxRingModulatorEffect::set_eax_defaults()
+{
+ eax_.flFrequency = EAXRINGMODULATOR_DEFAULTFREQUENCY;
+ eax_.flHighPassCutOff = EAXRINGMODULATOR_DEFAULTHIGHPASSCUTOFF;
+ eax_.ulWaveform = EAXRINGMODULATOR_DEFAULTWAVEFORM;
+
+ eax_d_ = eax_;
+}
+
+void EaxRingModulatorEffect::set_efx_frequency()
+{
+ const auto frequency = clamp(
+ eax_.flFrequency,
+ AL_RING_MODULATOR_MIN_FREQUENCY,
+ AL_RING_MODULATOR_MAX_FREQUENCY);
+
+ al_effect_props_.Modulator.Frequency = frequency;
+}
+
+void EaxRingModulatorEffect::set_efx_high_pass_cutoff()
+{
+ const auto high_pass_cutoff = clamp(
+ eax_.flHighPassCutOff,
+ AL_RING_MODULATOR_MIN_HIGHPASS_CUTOFF,
+ AL_RING_MODULATOR_MAX_HIGHPASS_CUTOFF);
+
+ al_effect_props_.Modulator.HighPassCutoff = high_pass_cutoff;
+}
+
+void EaxRingModulatorEffect::set_efx_waveform()
+{
+ const auto waveform = clamp(
+ static_cast<ALint>(eax_.ulWaveform),
+ AL_RING_MODULATOR_MIN_WAVEFORM,
+ AL_RING_MODULATOR_MAX_WAVEFORM);
+
+ const auto efx_waveform = WaveformFromEmum(waveform);
+ assert(efx_waveform.has_value());
+ al_effect_props_.Modulator.Waveform = *efx_waveform;
+}
+
+void EaxRingModulatorEffect::set_efx_defaults()
+{
+ set_efx_frequency();
+ set_efx_high_pass_cutoff();
+ set_efx_waveform();
+}
+
+// [[nodiscard]]
+bool EaxRingModulatorEffect::get(
+ const EaxEaxCall& eax_call)
+{
+ switch (eax_call.get_property_id())
+ {
+ case EAXRINGMODULATOR_NONE:
+ break;
+
+ case EAXRINGMODULATOR_ALLPARAMETERS:
+ eax_call.set_value<EaxRingModulatorEffectException>(eax_);
+ break;
+
+ case EAXRINGMODULATOR_FREQUENCY:
+ eax_call.set_value<EaxRingModulatorEffectException>(eax_.flFrequency);
+ break;
+
+ case EAXRINGMODULATOR_HIGHPASSCUTOFF:
+ eax_call.set_value<EaxRingModulatorEffectException>(eax_.flHighPassCutOff);
+ break;
+
+ case EAXRINGMODULATOR_WAVEFORM:
+ eax_call.set_value<EaxRingModulatorEffectException>(eax_.ulWaveform);
+ break;
+
+ default:
+ throw EaxRingModulatorEffectException{"Unsupported property id."};
+ }
+
+ return false;
+}
+
+void EaxRingModulatorEffect::validate_frequency(
+ float flFrequency)
+{
+ eax_validate_range<EaxRingModulatorEffectException>(
+ "Frequency",
+ flFrequency,
+ EAXRINGMODULATOR_MINFREQUENCY,
+ EAXRINGMODULATOR_MAXFREQUENCY);
+}
+
+void EaxRingModulatorEffect::validate_high_pass_cutoff(
+ float flHighPassCutOff)
+{
+ eax_validate_range<EaxRingModulatorEffectException>(
+ "High-Pass Cutoff",
+ flHighPassCutOff,
+ EAXRINGMODULATOR_MINHIGHPASSCUTOFF,
+ EAXRINGMODULATOR_MAXHIGHPASSCUTOFF);
+}
+
+void EaxRingModulatorEffect::validate_waveform(
+ unsigned long ulWaveform)
+{
+ eax_validate_range<EaxRingModulatorEffectException>(
+ "Waveform",
+ ulWaveform,
+ EAXRINGMODULATOR_MINWAVEFORM,
+ EAXRINGMODULATOR_MAXWAVEFORM);
+}
+
+void EaxRingModulatorEffect::validate_all(
+ const EAXRINGMODULATORPROPERTIES& all)
+{
+ validate_frequency(all.flFrequency);
+ validate_high_pass_cutoff(all.flHighPassCutOff);
+ validate_waveform(all.ulWaveform);
+}
+
+void EaxRingModulatorEffect::defer_frequency(
+ float flFrequency)
+{
+ eax_d_.flFrequency = flFrequency;
+ eax_dirty_flags_.flFrequency = (eax_.flFrequency != eax_d_.flFrequency);
+}
+
+void EaxRingModulatorEffect::defer_high_pass_cutoff(
+ float flHighPassCutOff)
+{
+ eax_d_.flHighPassCutOff = flHighPassCutOff;
+ eax_dirty_flags_.flHighPassCutOff = (eax_.flHighPassCutOff != eax_d_.flHighPassCutOff);
+}
+
+void EaxRingModulatorEffect::defer_waveform(
+ unsigned long ulWaveform)
+{
+ eax_d_.ulWaveform = ulWaveform;
+ eax_dirty_flags_.ulWaveform = (eax_.ulWaveform != eax_d_.ulWaveform);
+}
+
+void EaxRingModulatorEffect::defer_all(
+ const EAXRINGMODULATORPROPERTIES& all)
+{
+ defer_frequency(all.flFrequency);
+ defer_high_pass_cutoff(all.flHighPassCutOff);
+ defer_waveform(all.ulWaveform);
+}
+
+void EaxRingModulatorEffect::defer_frequency(
+ const EaxEaxCall& eax_call)
+{
+ const auto& frequency =
+ eax_call.get_value<
+ EaxRingModulatorEffectException, const decltype(EAXRINGMODULATORPROPERTIES::flFrequency)>();
+
+ validate_frequency(frequency);
+ defer_frequency(frequency);
+}
+
+void EaxRingModulatorEffect::defer_high_pass_cutoff(
+ const EaxEaxCall& eax_call)
+{
+ const auto& high_pass_cutoff =
+ eax_call.get_value<
+ EaxRingModulatorEffectException, const decltype(EAXRINGMODULATORPROPERTIES::flHighPassCutOff)>();
+
+ validate_high_pass_cutoff(high_pass_cutoff);
+ defer_high_pass_cutoff(high_pass_cutoff);
+}
+
+void EaxRingModulatorEffect::defer_waveform(
+ const EaxEaxCall& eax_call)
+{
+ const auto& waveform =
+ eax_call.get_value<
+ EaxRingModulatorEffectException, const decltype(EAXRINGMODULATORPROPERTIES::ulWaveform)>();
+
+ validate_waveform(waveform);
+ defer_waveform(waveform);
+}
+
+void EaxRingModulatorEffect::defer_all(
+ const EaxEaxCall& eax_call)
+{
+ const auto& all =
+ eax_call.get_value<EaxRingModulatorEffectException, const EAXRINGMODULATORPROPERTIES>();
+
+ validate_all(all);
+ defer_all(all);
+}
+
+// [[nodiscard]]
+bool EaxRingModulatorEffect::apply_deferred()
+{
+ if (eax_dirty_flags_ == EaxRingModulatorEffectDirtyFlags{})
+ {
+ return false;
+ }
+
+ eax_ = eax_d_;
+
+ if (eax_dirty_flags_.flFrequency)
+ {
+ set_efx_frequency();
+ }
+
+ if (eax_dirty_flags_.flHighPassCutOff)
+ {
+ set_efx_high_pass_cutoff();
+ }
+
+ if (eax_dirty_flags_.ulWaveform)
+ {
+ set_efx_waveform();
+ }
+
+ eax_dirty_flags_ = EaxRingModulatorEffectDirtyFlags{};
+
+ return true;
+}
+
+// [[nodiscard]]
+bool EaxRingModulatorEffect::set(
+ const EaxEaxCall& eax_call)
+{
+ switch (eax_call.get_property_id())
+ {
+ case EAXRINGMODULATOR_NONE:
+ break;
+
+ case EAXRINGMODULATOR_ALLPARAMETERS:
+ defer_all(eax_call);
+ break;
+
+ case EAXRINGMODULATOR_FREQUENCY:
+ defer_frequency(eax_call);
+ break;
+
+ case EAXRINGMODULATOR_HIGHPASSCUTOFF:
+ defer_high_pass_cutoff(eax_call);
+ break;
+
+ case EAXRINGMODULATOR_WAVEFORM:
+ defer_waveform(eax_call);
+ break;
+
+ default:
+ throw EaxRingModulatorEffectException{"Unsupported property id."};
+ }
+
+ if (!eax_call.is_deferred())
+ {
+ return apply_deferred();
+ }
+
+ return false;
+}
+
+
+} // namespace
+
+
+EaxEffectUPtr eax_create_eax_ring_modulator_effect(
+ EffectProps& al_effect_props)
+{
+ return std::make_unique<EaxRingModulatorEffect>(al_effect_props);
+}
+
+
+#endif // ALSOFT_EAX
diff --git a/al/effects/null.cpp b/al/effects/null.cpp
index 516446db..44595208 100644
--- a/al/effects/null.cpp
+++ b/al/effects/null.cpp
@@ -7,6 +7,10 @@
#include "alc/effects/base.h"
#include "effects.h"
+#if ALSOFT_EAX
+#include "al/eax_exception.h"
+#endif // ALSOFT_EAX
+
namespace {
@@ -91,3 +95,56 @@ EffectProps genDefaultProps() noexcept
DEFINE_ALEFFECT_VTABLE(Null);
const EffectProps NullEffectProps{genDefaultProps()};
+
+
+#if ALSOFT_EAX
+namespace
+{
+
+
+class EaxNullEffect final :
+ public EaxEffect
+{
+public:
+ // [[nodiscard]]
+ bool dispatch(
+ const EaxEaxCall& eax_call) override;
+}; // EaxNullEffect
+
+
+class EaxNullEffectException :
+ public EaxException
+{
+public:
+ explicit EaxNullEffectException(
+ const char* message)
+ :
+ EaxException{"EAX_NULL_EFFECT", message}
+ {
+ }
+}; // EaxNullEffectException
+
+
+// [[nodiscard]]
+bool EaxNullEffect::dispatch(
+ const EaxEaxCall& eax_call)
+{
+ if (eax_call.get_property_id() != 0)
+ {
+ throw EaxNullEffectException{"Unsupported property id."};
+ }
+
+ return false;
+}
+
+
+} // namespace
+
+
+EaxEffectUPtr eax_create_eax_null_effect()
+{
+ return std::make_unique<EaxNullEffect>();
+}
+
+
+#endif // ALSOFT_EAX
diff --git a/al/effects/pshifter.cpp b/al/effects/pshifter.cpp
index 56059a3c..03f9a139 100644
--- a/al/effects/pshifter.cpp
+++ b/al/effects/pshifter.cpp
@@ -7,6 +7,13 @@
#include "alc/effects/base.h"
#include "effects.h"
+#if ALSOFT_EAX
+#include "alnumeric.h"
+
+#include "al/eax_exception.h"
+#include "al/eax_utils.h"
+#endif // ALSOFT_EAX
+
namespace {
@@ -82,3 +89,334 @@ EffectProps genDefaultProps() noexcept
DEFINE_ALEFFECT_VTABLE(Pshifter);
const EffectProps PshifterEffectProps{genDefaultProps()};
+
+#if ALSOFT_EAX
+namespace
+{
+
+
+using EaxPitchShifterEffectDirtyFlagsValue = std::uint_least8_t;
+
+struct EaxPitchShifterEffectDirtyFlags
+{
+ using EaxIsBitFieldStruct = bool;
+
+ EaxPitchShifterEffectDirtyFlagsValue lCoarseTune : 1;
+ EaxPitchShifterEffectDirtyFlagsValue lFineTune : 1;
+}; // EaxPitchShifterEffectDirtyFlags
+
+
+class EaxPitchShifterEffect final :
+ public EaxEffect
+{
+public:
+ EaxPitchShifterEffect(
+ EffectProps& al_effect_props);
+
+
+ // [[nodiscard]]
+ bool dispatch(
+ const EaxEaxCall& eax_call) override;
+
+
+private:
+ EffectProps& al_effect_props_;
+
+ EAXPITCHSHIFTERPROPERTIES eax_{};
+ EAXPITCHSHIFTERPROPERTIES eax_d_{};
+ EaxPitchShifterEffectDirtyFlags eax_dirty_flags_{};
+
+
+ void set_eax_defaults();
+
+
+ void set_efx_coarse_tune();
+
+ void set_efx_fine_tune();
+
+ void set_efx_defaults();
+
+
+ // [[nodiscard]]
+ bool get(
+ const EaxEaxCall& eax_call);
+
+
+ void validate_coarse_tune(
+ long lCoarseTune);
+
+ void validate_fine_tune(
+ long lFineTune);
+
+ void validate_all(
+ const EAXPITCHSHIFTERPROPERTIES& all);
+
+
+ void defer_coarse_tune(
+ long lCoarseTune);
+
+ void defer_fine_tune(
+ long lFineTune);
+
+ void defer_all(
+ const EAXPITCHSHIFTERPROPERTIES& all);
+
+
+ void defer_coarse_tune(
+ const EaxEaxCall& eax_call);
+
+ void defer_fine_tune(
+ const EaxEaxCall& eax_call);
+
+ void defer_all(
+ const EaxEaxCall& eax_call);
+
+
+ // [[nodiscard]]
+ bool apply_deferred();
+
+ // [[nodiscard]]
+ bool set(
+ const EaxEaxCall& eax_call);
+}; // EaxPitchShifterEffect
+
+
+class EaxPitchShifterEffectException :
+ public EaxException
+{
+public:
+ explicit EaxPitchShifterEffectException(
+ const char* message)
+ :
+ EaxException{"EAX_PITCH_SHIFTER_EFFECT", message}
+ {
+ }
+}; // EaxPitchShifterEffectException
+
+
+EaxPitchShifterEffect::EaxPitchShifterEffect(
+ EffectProps& al_effect_props)
+ :
+ al_effect_props_{al_effect_props}
+{
+ set_eax_defaults();
+ set_efx_defaults();
+}
+
+// [[nodiscard]]
+bool EaxPitchShifterEffect::dispatch(
+ const EaxEaxCall& eax_call)
+{
+ return eax_call.is_get() ? get(eax_call) : set(eax_call);
+}
+
+void EaxPitchShifterEffect::set_eax_defaults()
+{
+ eax_.lCoarseTune = EAXPITCHSHIFTER_DEFAULTCOARSETUNE;
+ eax_.lFineTune = EAXPITCHSHIFTER_DEFAULTFINETUNE;
+
+ eax_d_ = eax_;
+}
+
+void EaxPitchShifterEffect::set_efx_coarse_tune()
+{
+ const auto coarse_tune = clamp(
+ static_cast<ALint>(eax_.lCoarseTune),
+ AL_PITCH_SHIFTER_MIN_COARSE_TUNE,
+ AL_PITCH_SHIFTER_MAX_COARSE_TUNE);
+
+ al_effect_props_.Pshifter.CoarseTune = coarse_tune;
+}
+
+void EaxPitchShifterEffect::set_efx_fine_tune()
+{
+ const auto fine_tune = clamp(
+ static_cast<ALint>(eax_.lFineTune),
+ AL_PITCH_SHIFTER_MIN_FINE_TUNE,
+ AL_PITCH_SHIFTER_MAX_FINE_TUNE);
+
+ al_effect_props_.Pshifter.FineTune = fine_tune;
+}
+
+void EaxPitchShifterEffect::set_efx_defaults()
+{
+ set_efx_coarse_tune();
+ set_efx_fine_tune();
+}
+
+// [[nodiscard]]
+bool EaxPitchShifterEffect::get(
+ const EaxEaxCall& eax_call)
+{
+ switch (eax_call.get_property_id())
+ {
+ case EAXPITCHSHIFTER_NONE:
+ break;
+
+ case EAXPITCHSHIFTER_ALLPARAMETERS:
+ eax_call.set_value<EaxPitchShifterEffectException>(eax_);
+ break;
+
+ case EAXPITCHSHIFTER_COARSETUNE:
+ eax_call.set_value<EaxPitchShifterEffectException>(eax_.lCoarseTune);
+ break;
+
+ case EAXPITCHSHIFTER_FINETUNE:
+ eax_call.set_value<EaxPitchShifterEffectException>(eax_.lFineTune);
+ break;
+
+ default:
+ throw EaxPitchShifterEffectException{"Unsupported property id."};
+ }
+
+ return false;
+}
+
+void EaxPitchShifterEffect::validate_coarse_tune(
+ long lCoarseTune)
+{
+ eax_validate_range<EaxPitchShifterEffectException>(
+ "Coarse Tune",
+ lCoarseTune,
+ EAXPITCHSHIFTER_MINCOARSETUNE,
+ EAXPITCHSHIFTER_MAXCOARSETUNE);
+}
+
+void EaxPitchShifterEffect::validate_fine_tune(
+ long lFineTune)
+{
+ eax_validate_range<EaxPitchShifterEffectException>(
+ "Fine Tune",
+ lFineTune,
+ EAXPITCHSHIFTER_MINFINETUNE,
+ EAXPITCHSHIFTER_MAXFINETUNE);
+}
+
+void EaxPitchShifterEffect::validate_all(
+ const EAXPITCHSHIFTERPROPERTIES& all)
+{
+ validate_coarse_tune(all.lCoarseTune);
+ validate_fine_tune(all.lFineTune);
+}
+
+void EaxPitchShifterEffect::defer_coarse_tune(
+ long lCoarseTune)
+{
+ eax_d_.lCoarseTune = lCoarseTune;
+ eax_dirty_flags_.lCoarseTune = (eax_.lCoarseTune != eax_d_.lCoarseTune);
+}
+
+void EaxPitchShifterEffect::defer_fine_tune(
+ long lFineTune)
+{
+ eax_d_.lFineTune = lFineTune;
+ eax_dirty_flags_.lFineTune = (eax_.lFineTune != eax_d_.lFineTune);
+}
+
+void EaxPitchShifterEffect::defer_all(
+ const EAXPITCHSHIFTERPROPERTIES& all)
+{
+ defer_coarse_tune(all.lCoarseTune);
+ defer_fine_tune(all.lFineTune);
+}
+
+void EaxPitchShifterEffect::defer_coarse_tune(
+ const EaxEaxCall& eax_call)
+{
+ const auto& coarse_tune =
+ eax_call.get_value<EaxPitchShifterEffectException, const decltype(EAXPITCHSHIFTERPROPERTIES::lCoarseTune)>();
+
+ validate_coarse_tune(coarse_tune);
+ defer_coarse_tune(coarse_tune);
+}
+
+void EaxPitchShifterEffect::defer_fine_tune(
+ const EaxEaxCall& eax_call)
+{
+ const auto& fine_tune =
+ eax_call.get_value<EaxPitchShifterEffectException, const decltype(EAXPITCHSHIFTERPROPERTIES::lFineTune)>();
+
+ validate_fine_tune(fine_tune);
+ defer_fine_tune(fine_tune);
+}
+
+void EaxPitchShifterEffect::defer_all(
+ const EaxEaxCall& eax_call)
+{
+ const auto& all =
+ eax_call.get_value<EaxPitchShifterEffectException, const EAXPITCHSHIFTERPROPERTIES>();
+
+ validate_all(all);
+ defer_all(all);
+}
+
+// [[nodiscard]]
+bool EaxPitchShifterEffect::apply_deferred()
+{
+ if (eax_dirty_flags_ == EaxPitchShifterEffectDirtyFlags{})
+ {
+ return false;
+ }
+
+ eax_ = eax_d_;
+
+ if (eax_dirty_flags_.lCoarseTune)
+ {
+ set_efx_coarse_tune();
+ }
+
+ if (eax_dirty_flags_.lFineTune)
+ {
+ set_efx_fine_tune();
+ }
+
+ eax_dirty_flags_ = EaxPitchShifterEffectDirtyFlags{};
+
+ return true;
+}
+
+// [[nodiscard]]
+bool EaxPitchShifterEffect::set(
+ const EaxEaxCall& eax_call)
+{
+ switch (eax_call.get_property_id())
+ {
+ case EAXPITCHSHIFTER_NONE:
+ break;
+
+ case EAXPITCHSHIFTER_ALLPARAMETERS:
+ defer_all(eax_call);
+ break;
+
+ case EAXPITCHSHIFTER_COARSETUNE:
+ defer_coarse_tune(eax_call);
+ break;
+
+ case EAXPITCHSHIFTER_FINETUNE:
+ defer_fine_tune(eax_call);
+ break;
+
+ default:
+ throw EaxPitchShifterEffectException{"Unsupported property id."};
+ }
+
+ if (!eax_call.is_deferred())
+ {
+ return apply_deferred();
+ }
+
+ return false;
+}
+
+
+} // namespace
+
+
+EaxEffectUPtr eax_create_eax_pitch_shifter_effect(
+ EffectProps& al_effect_props)
+{
+ return std::make_unique<EaxPitchShifterEffect>(al_effect_props);
+}
+
+
+#endif // ALSOFT_EAX
diff --git a/al/effects/reverb.cpp b/al/effects/reverb.cpp
index 3f234b93..8012450d 100644
--- a/al/effects/reverb.cpp
+++ b/al/effects/reverb.cpp
@@ -9,6 +9,15 @@
#include "alc/effects/base.h"
#include "effects.h"
+#if ALSOFT_EAX
+#include <tuple>
+
+#include "alnumeric.h"
+
+#include "al/eax_exception.h"
+#include "al/eax_utils.h"
+#endif // ALSOFT_EAX
+
namespace {
@@ -554,3 +563,1916 @@ const EffectProps ReverbEffectProps{genDefaultProps()};
DEFINE_ALEFFECT_VTABLE(StdReverb);
const EffectProps StdReverbEffectProps{genDefaultStdProps()};
+
+#if ALSOFT_EAX
+namespace
+{
+
+
+using EaxReverbEffectDirtyFlagsValue = std::uint_least32_t;
+
+struct EaxReverbEffectDirtyFlags
+{
+ using EaxIsBitFieldStruct = bool;
+
+ EaxReverbEffectDirtyFlagsValue ulEnvironment : 1;
+ EaxReverbEffectDirtyFlagsValue flEnvironmentSize : 1;
+ EaxReverbEffectDirtyFlagsValue flEnvironmentDiffusion : 1;
+ EaxReverbEffectDirtyFlagsValue lRoom : 1;
+ EaxReverbEffectDirtyFlagsValue lRoomHF : 1;
+ EaxReverbEffectDirtyFlagsValue lRoomLF : 1;
+ EaxReverbEffectDirtyFlagsValue flDecayTime : 1;
+ EaxReverbEffectDirtyFlagsValue flDecayHFRatio : 1;
+ EaxReverbEffectDirtyFlagsValue flDecayLFRatio : 1;
+ EaxReverbEffectDirtyFlagsValue lReflections : 1;
+ EaxReverbEffectDirtyFlagsValue flReflectionsDelay : 1;
+ EaxReverbEffectDirtyFlagsValue vReflectionsPan : 1;
+ EaxReverbEffectDirtyFlagsValue lReverb : 1;
+ EaxReverbEffectDirtyFlagsValue flReverbDelay : 1;
+ EaxReverbEffectDirtyFlagsValue vReverbPan : 1;
+ EaxReverbEffectDirtyFlagsValue flEchoTime : 1;
+ EaxReverbEffectDirtyFlagsValue flEchoDepth : 1;
+ EaxReverbEffectDirtyFlagsValue flModulationTime : 1;
+ EaxReverbEffectDirtyFlagsValue flModulationDepth : 1;
+ EaxReverbEffectDirtyFlagsValue flAirAbsorptionHF : 1;
+ EaxReverbEffectDirtyFlagsValue flHFReference : 1;
+ EaxReverbEffectDirtyFlagsValue flLFReference : 1;
+ EaxReverbEffectDirtyFlagsValue flRoomRolloffFactor : 1;
+ EaxReverbEffectDirtyFlagsValue ulFlags : 1;
+}; // EaxReverbEffectDirtyFlags
+
+
+class EaxReverbEffect final :
+ public EaxEffect
+{
+public:
+ EaxReverbEffect(
+ EffectProps& al_effect_props);
+
+
+ // [[nodiscard]]
+ bool dispatch(
+ const EaxEaxCall& eax_call) override;
+
+
+private:
+ EffectProps& al_effect_props_;
+
+ EAXREVERBPROPERTIES eax_{};
+ EAXREVERBPROPERTIES eax_d_{};
+ EaxReverbEffectDirtyFlags eax_dirty_flags_{};
+
+
+ void set_eax_defaults();
+
+
+ void set_efx_density();
+
+ void set_efx_diffusion();
+
+ void set_efx_gain();
+
+ void set_efx_gain_hf();
+
+ void set_efx_gain_lf();
+
+ void set_efx_decay_time();
+
+ void set_efx_decay_hf_ratio();
+
+ void set_efx_decay_lf_ratio();
+
+ void set_efx_reflections_gain();
+
+ void set_efx_reflections_delay();
+
+ void set_efx_reflections_pan();
+
+ void set_efx_late_reverb_gain();
+
+ void set_efx_late_reverb_delay();
+
+ void set_efx_late_reverb_pan();
+
+ void set_efx_echo_time();
+
+ void set_efx_echo_depth();
+
+ void set_efx_modulation_time();
+
+ void set_efx_modulation_depth();
+
+ void set_efx_air_absorption_gain_hf();
+
+ void set_efx_hf_reference();
+
+ void set_efx_lf_reference();
+
+ void set_efx_room_rolloff_factor();
+
+ void set_efx_flags();
+
+ void set_efx_defaults();
+
+
+ void get_all(
+ const EaxEaxCall& eax_call) const;
+
+ // [[nodiscard]]
+ bool get(
+ const EaxEaxCall& eax_call) const;
+
+
+ void validate_environment(
+ unsigned long ulEnvironment,
+ int version,
+ bool is_standalone);
+
+ void validate_environment_size(
+ float flEnvironmentSize);
+
+ void validate_environment_diffusion(
+ float flEnvironmentDiffusion);
+
+ void validate_room(
+ long lRoom);
+
+ void validate_room_hf(
+ long lRoomHF);
+
+ void validate_room_lf(
+ long lRoomLF);
+
+ void validate_decay_time(
+ float flDecayTime);
+
+ void validate_decay_hf_ratio(
+ float flDecayHFRatio);
+
+ void validate_decay_lf_ratio(
+ float flDecayLFRatio);
+
+ void validate_reflections(
+ long lReflections);
+
+ void validate_reflections_delay(
+ float flReflectionsDelay);
+
+ void validate_reflections_pan(
+ const EAXVECTOR& vReflectionsPan);
+
+ void validate_reverb(
+ long lReverb);
+
+ void validate_reverb_delay(
+ float flReverbDelay);
+
+ void validate_reverb_pan(
+ const EAXVECTOR& vReverbPan);
+
+ void validate_echo_time(
+ float flEchoTime);
+
+ void validate_echo_depth(
+ float flEchoDepth);
+
+ void validate_modulation_time(
+ float flModulationTime);
+
+ void validate_modulation_depth(
+ float flModulationDepth);
+
+ void validate_air_absorbtion_hf(
+ float air_absorbtion_hf);
+
+ void validate_hf_reference(
+ float flHFReference);
+
+ void validate_lf_reference(
+ float flLFReference);
+
+ void validate_room_rolloff_factor(
+ float flRoomRolloffFactor);
+
+ void validate_flags(
+ unsigned long ulFlags);
+
+ void validate_all(
+ const EAX20LISTENERPROPERTIES& all,
+ int version);
+
+ void validate_all(
+ const EAXREVERBPROPERTIES& all,
+ int version);
+
+
+ void defer_environment(
+ unsigned long ulEnvironment);
+
+ void defer_environment_size(
+ float flEnvironmentSize);
+
+ void defer_environment_diffusion(
+ float flEnvironmentDiffusion);
+
+ void defer_room(
+ long lRoom);
+
+ void defer_room_hf(
+ long lRoomHF);
+
+ void defer_room_lf(
+ long lRoomLF);
+
+ void defer_decay_time(
+ float flDecayTime);
+
+ void defer_decay_hf_ratio(
+ float flDecayHFRatio);
+
+ void defer_decay_lf_ratio(
+ float flDecayLFRatio);
+
+ void defer_reflections(
+ long lReflections);
+
+ void defer_reflections_delay(
+ float flReflectionsDelay);
+
+ void defer_reflections_pan(
+ const EAXVECTOR& vReflectionsPan);
+
+ void defer_reverb(
+ long lReverb);
+
+ void defer_reverb_delay(
+ float flReverbDelay);
+
+ void defer_reverb_pan(
+ const EAXVECTOR& vReverbPan);
+
+ void defer_echo_time(
+ float flEchoTime);
+
+ void defer_echo_depth(
+ float flEchoDepth);
+
+ void defer_modulation_time(
+ float flModulationTime);
+
+ void defer_modulation_depth(
+ float flModulationDepth);
+
+ void defer_air_absorbtion_hf(
+ float flAirAbsorptionHF);
+
+ void defer_hf_reference(
+ float flHFReference);
+
+ void defer_lf_reference(
+ float flLFReference);
+
+ void defer_room_rolloff_factor(
+ float flRoomRolloffFactor);
+
+ void defer_flags(
+ unsigned long ulFlags);
+
+ void defer_all(
+ const EAX20LISTENERPROPERTIES& all);
+
+ void defer_all(
+ const EAXREVERBPROPERTIES& all);
+
+
+ void defer_environment(
+ const EaxEaxCall& eax_call);
+
+ void defer_environment_size(
+ const EaxEaxCall& eax_call);
+
+ void defer_environment_diffusion(
+ const EaxEaxCall& eax_call);
+
+ void defer_room(
+ const EaxEaxCall& eax_call);
+
+ void defer_room_hf(
+ const EaxEaxCall& eax_call);
+
+ void defer_room_lf(
+ const EaxEaxCall& eax_call);
+
+ void defer_decay_time(
+ const EaxEaxCall& eax_call);
+
+ void defer_decay_hf_ratio(
+ const EaxEaxCall& eax_call);
+
+ void defer_decay_lf_ratio(
+ const EaxEaxCall& eax_call);
+
+ void defer_reflections(
+ const EaxEaxCall& eax_call);
+
+ void defer_reflections_delay(
+ const EaxEaxCall& eax_call);
+
+ void defer_reflections_pan(
+ const EaxEaxCall& eax_call);
+
+ void defer_reverb(
+ const EaxEaxCall& eax_call);
+
+ void defer_reverb_delay(
+ const EaxEaxCall& eax_call);
+
+ void defer_reverb_pan(
+ const EaxEaxCall& eax_call);
+
+ void defer_echo_time(
+ const EaxEaxCall& eax_call);
+
+ void defer_echo_depth(
+ const EaxEaxCall& eax_call);
+
+ void defer_modulation_time(
+ const EaxEaxCall& eax_call);
+
+ void defer_modulation_depth(
+ const EaxEaxCall& eax_call);
+
+ void defer_air_absorbtion_hf(
+ const EaxEaxCall& eax_call);
+
+ void defer_hf_reference(
+ const EaxEaxCall& eax_call);
+
+ void defer_lf_reference(
+ const EaxEaxCall& eax_call);
+
+ void defer_room_rolloff_factor(
+ const EaxEaxCall& eax_call);
+
+ void defer_flags(
+ const EaxEaxCall& eax_call);
+
+ void defer_all(
+ const EaxEaxCall& eax_call);
+
+
+ // [[nodiscard]]
+ bool apply_deferred();
+
+ // [[nodiscard]]
+ bool set(
+ const EaxEaxCall& eax_call);
+}; // EaxReverbEffect
+
+
+class EaxReverbEffectException :
+ public EaxException
+{
+public:
+ explicit EaxReverbEffectException(
+ const char* message)
+ :
+ EaxException{"EAX_REVERB_EFFECT", message}
+ {
+ }
+}; // EaxReverbEffectException
+
+
+EaxReverbEffect::EaxReverbEffect(
+ EffectProps& al_effect_props)
+ :
+ al_effect_props_{al_effect_props}
+{
+ set_eax_defaults();
+ set_efx_defaults();
+}
+
+// [[nodiscard]]
+bool EaxReverbEffect::dispatch(
+ const EaxEaxCall& eax_call)
+{
+ return eax_call.is_get() ? get(eax_call) : set(eax_call);
+}
+
+void EaxReverbEffect::set_eax_defaults()
+{
+ eax_ = EAXREVERB_PRESETS[EAX_ENVIRONMENT_GENERIC];
+
+ eax_d_ = eax_;
+}
+
+void EaxReverbEffect::set_efx_density()
+{
+ const auto eax_environment_size = eax_.flEnvironmentSize;
+
+ const auto efx_density = clamp(
+ (eax_environment_size * eax_environment_size * eax_environment_size) / 16.0F,
+ AL_EAXREVERB_MIN_DENSITY,
+ AL_EAXREVERB_MAX_DENSITY);
+
+ al_effect_props_.Reverb.Density = efx_density;
+}
+
+void EaxReverbEffect::set_efx_diffusion()
+{
+ const auto efx_diffusion = clamp(
+ eax_.flEnvironmentDiffusion,
+ AL_EAXREVERB_MIN_DIFFUSION,
+ AL_EAXREVERB_MAX_DIFFUSION);
+
+ al_effect_props_.Reverb.Diffusion = efx_diffusion;
+}
+
+void EaxReverbEffect::set_efx_gain()
+{
+ const auto efx_gain = clamp(
+ level_mb_to_gain(static_cast<float>(eax_.lRoom)),
+ AL_EAXREVERB_MIN_GAIN,
+ AL_EAXREVERB_MAX_GAIN);
+
+ al_effect_props_.Reverb.Gain = efx_gain;
+}
+
+void EaxReverbEffect::set_efx_gain_hf()
+{
+ const auto efx_gain_hf = clamp(
+ level_mb_to_gain(static_cast<float>(eax_.lRoomHF)),
+ AL_EAXREVERB_MIN_GAINHF,
+ AL_EAXREVERB_MAX_GAINHF);
+
+ al_effect_props_.Reverb.GainHF = efx_gain_hf;
+}
+
+void EaxReverbEffect::set_efx_gain_lf()
+{
+ const auto efx_gain_lf = clamp(
+ level_mb_to_gain(static_cast<float>(eax_.lRoomLF)),
+ AL_EAXREVERB_MIN_GAINLF,
+ AL_EAXREVERB_MAX_GAINLF);
+
+ al_effect_props_.Reverb.GainLF = efx_gain_lf;
+}
+
+void EaxReverbEffect::set_efx_decay_time()
+{
+ const auto efx_decay_time = clamp(
+ eax_.flDecayTime,
+ AL_EAXREVERB_MIN_DECAY_TIME,
+ AL_EAXREVERB_MAX_DECAY_TIME);
+
+ al_effect_props_.Reverb.DecayTime = efx_decay_time;
+}
+
+void EaxReverbEffect::set_efx_decay_hf_ratio()
+{
+ const auto efx_decay_hf_ratio = clamp(
+ eax_.flDecayHFRatio,
+ AL_EAXREVERB_MIN_DECAY_HFRATIO,
+ AL_EAXREVERB_MAX_DECAY_HFRATIO);
+
+ al_effect_props_.Reverb.DecayHFRatio = efx_decay_hf_ratio;
+}
+
+void EaxReverbEffect::set_efx_decay_lf_ratio()
+{
+ const auto efx_decay_lf_ratio = clamp(
+ eax_.flDecayLFRatio,
+ AL_EAXREVERB_MIN_DECAY_LFRATIO,
+ AL_EAXREVERB_MAX_DECAY_LFRATIO);
+
+ al_effect_props_.Reverb.DecayLFRatio = efx_decay_lf_ratio;
+}
+
+void EaxReverbEffect::set_efx_reflections_gain()
+{
+ const auto efx_reflections_gain = clamp(
+ level_mb_to_gain(static_cast<float>(eax_.lReflections)),
+ AL_EAXREVERB_MIN_REFLECTIONS_GAIN,
+ AL_EAXREVERB_MAX_REFLECTIONS_GAIN);
+
+ al_effect_props_.Reverb.ReflectionsGain = efx_reflections_gain;
+}
+
+void EaxReverbEffect::set_efx_reflections_delay()
+{
+ const auto efx_reflections_delay = clamp(
+ eax_.flReflectionsDelay,
+ AL_EAXREVERB_MIN_REFLECTIONS_DELAY,
+ AL_EAXREVERB_MAX_REFLECTIONS_DELAY);
+
+ al_effect_props_.Reverb.ReflectionsDelay = efx_reflections_delay;
+}
+
+void EaxReverbEffect::set_efx_reflections_pan()
+{
+ al_effect_props_.Reverb.ReflectionsPan[0] = eax_.vReflectionsPan.x;
+ al_effect_props_.Reverb.ReflectionsPan[1] = eax_.vReflectionsPan.y;
+ al_effect_props_.Reverb.ReflectionsPan[2] = eax_.vReflectionsPan.z;
+}
+
+void EaxReverbEffect::set_efx_late_reverb_gain()
+{
+ const auto efx_late_reverb_gain = clamp(
+ level_mb_to_gain(static_cast<float>(eax_.lReverb)),
+ AL_EAXREVERB_MIN_LATE_REVERB_GAIN,
+ AL_EAXREVERB_MAX_LATE_REVERB_GAIN);
+
+ al_effect_props_.Reverb.LateReverbGain = efx_late_reverb_gain;
+}
+
+void EaxReverbEffect::set_efx_late_reverb_delay()
+{
+ const auto efx_late_reverb_delay = clamp(
+ eax_.flReverbDelay,
+ AL_EAXREVERB_MIN_LATE_REVERB_DELAY,
+ AL_EAXREVERB_MAX_LATE_REVERB_DELAY);
+
+ al_effect_props_.Reverb.LateReverbDelay = efx_late_reverb_delay;
+}
+
+void EaxReverbEffect::set_efx_late_reverb_pan()
+{
+ al_effect_props_.Reverb.LateReverbPan[0] = eax_.vReverbPan.x;
+ al_effect_props_.Reverb.LateReverbPan[1] = eax_.vReverbPan.y;
+ al_effect_props_.Reverb.LateReverbPan[2] = eax_.vReverbPan.z;
+}
+
+void EaxReverbEffect::set_efx_echo_time()
+{
+ const auto efx_echo_time = clamp(
+ eax_.flEchoTime,
+ AL_EAXREVERB_MIN_ECHO_TIME,
+ AL_EAXREVERB_MAX_ECHO_TIME);
+
+ al_effect_props_.Reverb.EchoTime = efx_echo_time;
+}
+
+void EaxReverbEffect::set_efx_echo_depth()
+{
+ const auto efx_echo_depth = clamp(
+ eax_.flEchoDepth,
+ AL_EAXREVERB_MIN_ECHO_DEPTH,
+ AL_EAXREVERB_MAX_ECHO_DEPTH);
+
+ al_effect_props_.Reverb.EchoDepth = efx_echo_depth;
+}
+
+void EaxReverbEffect::set_efx_modulation_time()
+{
+ const auto efx_modulation_time = clamp(
+ eax_.flModulationTime,
+ AL_EAXREVERB_MIN_MODULATION_TIME,
+ AL_EAXREVERB_MAX_MODULATION_TIME);
+
+ al_effect_props_.Reverb.ModulationTime = efx_modulation_time;
+}
+
+void EaxReverbEffect::set_efx_modulation_depth()
+{
+ const auto efx_modulation_depth = clamp(
+ eax_.flModulationDepth,
+ AL_EAXREVERB_MIN_MODULATION_DEPTH,
+ AL_EAXREVERB_MAX_MODULATION_DEPTH);
+
+ al_effect_props_.Reverb.ModulationDepth = efx_modulation_depth;
+}
+
+void EaxReverbEffect::set_efx_air_absorption_gain_hf()
+{
+ const auto efx_air_absorption_hf = clamp(
+ level_mb_to_gain(eax_.flAirAbsorptionHF),
+ AL_EAXREVERB_MIN_AIR_ABSORPTION_GAINHF,
+ AL_EAXREVERB_MAX_AIR_ABSORPTION_GAINHF);
+
+ al_effect_props_.Reverb.AirAbsorptionGainHF = efx_air_absorption_hf;
+}
+
+void EaxReverbEffect::set_efx_hf_reference()
+{
+ const auto efx_hf_reference = clamp(
+ eax_.flHFReference,
+ AL_EAXREVERB_MIN_HFREFERENCE,
+ AL_EAXREVERB_MAX_HFREFERENCE);
+
+ al_effect_props_.Reverb.HFReference = efx_hf_reference;
+}
+
+void EaxReverbEffect::set_efx_lf_reference()
+{
+ const auto efx_lf_reference = clamp(
+ eax_.flLFReference,
+ AL_EAXREVERB_MIN_LFREFERENCE,
+ AL_EAXREVERB_MAX_LFREFERENCE);
+
+ al_effect_props_.Reverb.LFReference = efx_lf_reference;
+}
+
+void EaxReverbEffect::set_efx_room_rolloff_factor()
+{
+ const auto efx_room_rolloff_factor = clamp(
+ eax_.flRoomRolloffFactor,
+ AL_EAXREVERB_MIN_ROOM_ROLLOFF_FACTOR,
+ AL_EAXREVERB_MAX_ROOM_ROLLOFF_FACTOR);
+
+ al_effect_props_.Reverb.RoomRolloffFactor = efx_room_rolloff_factor;
+}
+
+void EaxReverbEffect::set_efx_flags()
+{
+ al_effect_props_.Reverb.DecayHFLimit = ((eax_.ulFlags & EAXREVERBFLAGS_DECAYHFLIMIT) != 0);
+}
+
+void EaxReverbEffect::set_efx_defaults()
+{
+ set_efx_density();
+ set_efx_diffusion();
+ set_efx_gain();
+ set_efx_gain_hf();
+ set_efx_gain_lf();
+ set_efx_decay_time();
+ set_efx_decay_hf_ratio();
+ set_efx_decay_lf_ratio();
+ set_efx_reflections_gain();
+ set_efx_reflections_delay();
+ set_efx_reflections_pan();
+ set_efx_late_reverb_gain();
+ set_efx_late_reverb_delay();
+ set_efx_late_reverb_pan();
+ set_efx_echo_time();
+ set_efx_echo_depth();
+ set_efx_modulation_time();
+ set_efx_modulation_depth();
+ set_efx_air_absorption_gain_hf();
+ set_efx_hf_reference();
+ set_efx_lf_reference();
+ set_efx_room_rolloff_factor();
+ set_efx_flags();
+}
+
+void EaxReverbEffect::get_all(
+ const EaxEaxCall& eax_call) const
+{
+ if (eax_call.get_version() == 2)
+ {
+ auto& eax_reverb = eax_call.get_value<EaxReverbEffectException, EAX20LISTENERPROPERTIES>();
+ eax_reverb.lRoom = eax_.lRoom;
+ eax_reverb.lRoomHF = eax_.lRoomHF;
+ eax_reverb.flRoomRolloffFactor = eax_.flRoomRolloffFactor;
+ eax_reverb.flDecayTime = eax_.flDecayTime;
+ eax_reverb.flDecayHFRatio = eax_.flDecayHFRatio;
+ eax_reverb.lReflections = eax_.lReflections;
+ eax_reverb.flReflectionsDelay = eax_.flReflectionsDelay;
+ eax_reverb.lReverb = eax_.lReverb;
+ eax_reverb.flReverbDelay = eax_.flReverbDelay;
+ eax_reverb.dwEnvironment = eax_.ulEnvironment;
+ eax_reverb.flEnvironmentSize = eax_.flEnvironmentSize;
+ eax_reverb.flEnvironmentDiffusion = eax_.flEnvironmentDiffusion;
+ eax_reverb.flAirAbsorptionHF = eax_.flAirAbsorptionHF;
+ eax_reverb.dwFlags = eax_.ulFlags;
+ }
+ else
+ {
+ eax_call.set_value<EaxReverbEffectException>(eax_);
+ }
+}
+
+// [[nodiscard]]
+bool EaxReverbEffect::get(
+ const EaxEaxCall& eax_call) const
+{
+ switch (eax_call.get_property_id())
+ {
+ case EAXREVERB_NONE:
+ break;
+
+ case EAXREVERB_ALLPARAMETERS:
+ get_all(eax_call);
+ break;
+
+ case EAXREVERB_ENVIRONMENT:
+ eax_call.set_value<EaxReverbEffectException>(eax_.ulEnvironment);
+ break;
+
+ case EAXREVERB_ENVIRONMENTSIZE:
+ eax_call.set_value<EaxReverbEffectException>(eax_.flEnvironmentSize);
+ break;
+
+ case EAXREVERB_ENVIRONMENTDIFFUSION:
+ eax_call.set_value<EaxReverbEffectException>(eax_.flEnvironmentDiffusion);
+ break;
+
+ case EAXREVERB_ROOM:
+ eax_call.set_value<EaxReverbEffectException>(eax_.lRoom);
+ break;
+
+ case EAXREVERB_ROOMHF:
+ eax_call.set_value<EaxReverbEffectException>(eax_.lRoomHF);
+ break;
+
+ case EAXREVERB_ROOMLF:
+ eax_call.set_value<EaxReverbEffectException>(eax_.lRoomLF);
+ break;
+
+ case EAXREVERB_DECAYTIME:
+ eax_call.set_value<EaxReverbEffectException>(eax_.flDecayTime);
+ break;
+
+ case EAXREVERB_DECAYHFRATIO:
+ eax_call.set_value<EaxReverbEffectException>(eax_.flDecayHFRatio);
+ break;
+
+ case EAXREVERB_DECAYLFRATIO:
+ eax_call.set_value<EaxReverbEffectException>(eax_.flDecayLFRatio);
+ break;
+
+ case EAXREVERB_REFLECTIONS:
+ eax_call.set_value<EaxReverbEffectException>(eax_.lReflections);
+ break;
+
+ case EAXREVERB_REFLECTIONSDELAY:
+ eax_call.set_value<EaxReverbEffectException>(eax_.flReflectionsDelay);
+ break;
+
+ case EAXREVERB_REFLECTIONSPAN:
+ eax_call.set_value<EaxReverbEffectException>(eax_.vReflectionsPan);
+ break;
+
+ case EAXREVERB_REVERB:
+ eax_call.set_value<EaxReverbEffectException>(eax_.lReverb);
+ break;
+
+ case EAXREVERB_REVERBDELAY:
+ eax_call.set_value<EaxReverbEffectException>(eax_.flReverbDelay);
+ break;
+
+ case EAXREVERB_REVERBPAN:
+ eax_call.set_value<EaxReverbEffectException>(eax_.vReverbPan);
+ break;
+
+ case EAXREVERB_ECHOTIME:
+ eax_call.set_value<EaxReverbEffectException>(eax_.flEchoTime);
+ break;
+
+ case EAXREVERB_ECHODEPTH:
+ eax_call.set_value<EaxReverbEffectException>(eax_.flEchoDepth);
+ break;
+
+ case EAXREVERB_MODULATIONTIME:
+ eax_call.set_value<EaxReverbEffectException>(eax_.flModulationTime);
+ break;
+
+ case EAXREVERB_MODULATIONDEPTH:
+ eax_call.set_value<EaxReverbEffectException>(eax_.flModulationDepth);
+ break;
+
+ case EAXREVERB_AIRABSORPTIONHF:
+ eax_call.set_value<EaxReverbEffectException>(eax_.flAirAbsorptionHF);
+ break;
+
+ case EAXREVERB_HFREFERENCE:
+ eax_call.set_value<EaxReverbEffectException>(eax_.flHFReference);
+ break;
+
+ case EAXREVERB_LFREFERENCE:
+ eax_call.set_value<EaxReverbEffectException>(eax_.flLFReference);
+ break;
+
+ case EAXREVERB_ROOMROLLOFFFACTOR:
+ eax_call.set_value<EaxReverbEffectException>(eax_.flRoomRolloffFactor);
+ break;
+
+ case EAXREVERB_FLAGS:
+ eax_call.set_value<EaxReverbEffectException>(eax_.ulFlags);
+ break;
+
+ default:
+ throw EaxReverbEffectException{"Unsupported property id."};
+ }
+
+ return false;
+}
+
+void EaxReverbEffect::validate_environment(
+ unsigned long ulEnvironment,
+ int version,
+ bool is_standalone)
+{
+ eax_validate_range<EaxReverbEffectException>(
+ "Environment",
+ ulEnvironment,
+ EAXREVERB_MINENVIRONMENT,
+ (version == 2 || is_standalone) ? EAX20REVERB_MAXENVIRONMENT : EAX30REVERB_MAXENVIRONMENT);
+}
+
+void EaxReverbEffect::validate_environment_size(
+ float flEnvironmentSize)
+{
+ eax_validate_range<EaxReverbEffectException>(
+ "Environment Size",
+ flEnvironmentSize,
+ EAXREVERB_MINENVIRONMENTSIZE,
+ EAXREVERB_MAXENVIRONMENTSIZE);
+}
+
+void EaxReverbEffect::validate_environment_diffusion(
+ float flEnvironmentDiffusion)
+{
+ eax_validate_range<EaxReverbEffectException>(
+ "Environment Diffusion",
+ flEnvironmentDiffusion,
+ EAXREVERB_MINENVIRONMENTDIFFUSION,
+ EAXREVERB_MAXENVIRONMENTDIFFUSION);
+}
+
+void EaxReverbEffect::validate_room(
+ long lRoom)
+{
+ eax_validate_range<EaxReverbEffectException>(
+ "Room",
+ lRoom,
+ EAXREVERB_MINROOM,
+ EAXREVERB_MAXROOM);
+}
+
+void EaxReverbEffect::validate_room_hf(
+ long lRoomHF)
+{
+ eax_validate_range<EaxReverbEffectException>(
+ "Room HF",
+ lRoomHF,
+ EAXREVERB_MINROOMHF,
+ EAXREVERB_MAXROOMHF);
+}
+
+void EaxReverbEffect::validate_room_lf(
+ long lRoomLF)
+{
+ eax_validate_range<EaxReverbEffectException>(
+ "Room LF",
+ lRoomLF,
+ EAXREVERB_MINROOMLF,
+ EAXREVERB_MAXROOMLF);
+}
+
+void EaxReverbEffect::validate_decay_time(
+ float flDecayTime)
+{
+ eax_validate_range<EaxReverbEffectException>(
+ "Decay Time",
+ flDecayTime,
+ EAXREVERB_MINDECAYTIME,
+ EAXREVERB_MAXDECAYTIME);
+}
+
+void EaxReverbEffect::validate_decay_hf_ratio(
+ float flDecayHFRatio)
+{
+ eax_validate_range<EaxReverbEffectException>(
+ "Decay HF Ratio",
+ flDecayHFRatio,
+ EAXREVERB_MINDECAYHFRATIO,
+ EAXREVERB_MAXDECAYHFRATIO);
+}
+
+void EaxReverbEffect::validate_decay_lf_ratio(
+ float flDecayLFRatio)
+{
+ eax_validate_range<EaxReverbEffectException>(
+ "Decay LF Ratio",
+ flDecayLFRatio,
+ EAXREVERB_MINDECAYLFRATIO,
+ EAXREVERB_MAXDECAYLFRATIO);
+}
+
+void EaxReverbEffect::validate_reflections(
+ long lReflections)
+{
+ eax_validate_range<EaxReverbEffectException>(
+ "Reflections",
+ lReflections,
+ EAXREVERB_MINREFLECTIONS,
+ EAXREVERB_MAXREFLECTIONS);
+}
+
+void EaxReverbEffect::validate_reflections_delay(
+ float flReflectionsDelay)
+{
+ eax_validate_range<EaxReverbEffectException>(
+ "Reflections Delay",
+ flReflectionsDelay,
+ EAXREVERB_MINREFLECTIONSDELAY,
+ EAXREVERB_MAXREFLECTIONSDELAY);
+}
+
+void EaxReverbEffect::validate_reflections_pan(
+ const EAXVECTOR& vReflectionsPan)
+{
+ std::ignore = vReflectionsPan;
+}
+
+void EaxReverbEffect::validate_reverb(
+ long lReverb)
+{
+ eax_validate_range<EaxReverbEffectException>(
+ "Reverb",
+ lReverb,
+ EAXREVERB_MINREVERB,
+ EAXREVERB_MAXREVERB);
+}
+
+void EaxReverbEffect::validate_reverb_delay(
+ float flReverbDelay)
+{
+ eax_validate_range<EaxReverbEffectException>(
+ "Reverb Delay",
+ flReverbDelay,
+ EAXREVERB_MINREVERBDELAY,
+ EAXREVERB_MAXREVERBDELAY);
+}
+
+void EaxReverbEffect::validate_reverb_pan(
+ const EAXVECTOR& vReverbPan)
+{
+ std::ignore = vReverbPan;
+}
+
+void EaxReverbEffect::validate_echo_time(
+ float flEchoTime)
+{
+ eax_validate_range<EaxReverbEffectException>(
+ "Echo Time",
+ flEchoTime,
+ EAXREVERB_MINECHOTIME,
+ EAXREVERB_MAXECHOTIME);
+}
+
+void EaxReverbEffect::validate_echo_depth(
+ float flEchoDepth)
+{
+ eax_validate_range<EaxReverbEffectException>(
+ "Echo Depth",
+ flEchoDepth,
+ EAXREVERB_MINECHODEPTH,
+ EAXREVERB_MAXECHODEPTH);
+}
+
+void EaxReverbEffect::validate_modulation_time(
+ float flModulationTime)
+{
+ eax_validate_range<EaxReverbEffectException>(
+ "Modulation Time",
+ flModulationTime,
+ EAXREVERB_MINMODULATIONTIME,
+ EAXREVERB_MAXMODULATIONTIME);
+}
+
+void EaxReverbEffect::validate_modulation_depth(
+ float flModulationDepth)
+{
+ eax_validate_range<EaxReverbEffectException>(
+ "Modulation Depth",
+ flModulationDepth,
+ EAXREVERB_MINMODULATIONDEPTH,
+ EAXREVERB_MAXMODULATIONDEPTH);
+}
+
+void EaxReverbEffect::validate_air_absorbtion_hf(
+ float air_absorbtion_hf)
+{
+ eax_validate_range<EaxReverbEffectException>(
+ "Air Absorbtion HF",
+ air_absorbtion_hf,
+ EAXREVERB_MINAIRABSORPTIONHF,
+ EAXREVERB_MAXAIRABSORPTIONHF);
+}
+
+void EaxReverbEffect::validate_hf_reference(
+ float flHFReference)
+{
+ eax_validate_range<EaxReverbEffectException>(
+ "HF Reference",
+ flHFReference,
+ EAXREVERB_MINHFREFERENCE,
+ EAXREVERB_MAXHFREFERENCE);
+}
+
+void EaxReverbEffect::validate_lf_reference(
+ float flLFReference)
+{
+ eax_validate_range<EaxReverbEffectException>(
+ "LF Reference",
+ flLFReference,
+ EAXREVERB_MINLFREFERENCE,
+ EAXREVERB_MAXLFREFERENCE);
+}
+
+void EaxReverbEffect::validate_room_rolloff_factor(
+ float flRoomRolloffFactor)
+{
+ eax_validate_range<EaxReverbEffectException>(
+ "Room Rolloff Factor",
+ flRoomRolloffFactor,
+ EAXREVERB_MINROOMROLLOFFFACTOR,
+ EAXREVERB_MAXROOMROLLOFFFACTOR);
+}
+
+void EaxReverbEffect::validate_flags(
+ unsigned long ulFlags)
+{
+ eax_validate_range<EaxReverbEffectException>(
+ "Flags",
+ ulFlags,
+ 0UL,
+ ~EAXREVERBFLAGS_RESERVED);
+}
+
+void EaxReverbEffect::validate_all(
+ const EAX20LISTENERPROPERTIES& listener,
+ int version)
+{
+ validate_room(listener.lRoom);
+ validate_room_hf(listener.lRoomHF);
+ validate_room_rolloff_factor(listener.flRoomRolloffFactor);
+ validate_decay_time(listener.flDecayTime);
+ validate_decay_hf_ratio(listener.flDecayHFRatio);
+ validate_reflections(listener.lReflections);
+ validate_reflections_delay(listener.flReflectionsDelay);
+ validate_reverb(listener.lReverb);
+ validate_reverb_delay(listener.flReverbDelay);
+ validate_environment(listener.dwEnvironment, version, false);
+ validate_environment_size(listener.flEnvironmentSize);
+ validate_environment_diffusion(listener.flEnvironmentDiffusion);
+ validate_air_absorbtion_hf(listener.flAirAbsorptionHF);
+ validate_flags(listener.dwFlags);
+}
+
+void EaxReverbEffect::validate_all(
+ const EAXREVERBPROPERTIES& lReverb,
+ int version)
+{
+ validate_environment(lReverb.ulEnvironment, version, false);
+ validate_environment_size(lReverb.flEnvironmentSize);
+ validate_environment_diffusion(lReverb.flEnvironmentDiffusion);
+ validate_room(lReverb.lRoom);
+ validate_room_hf(lReverb.lRoomHF);
+ validate_room_lf(lReverb.lRoomLF);
+ validate_decay_time(lReverb.flDecayTime);
+ validate_decay_hf_ratio(lReverb.flDecayHFRatio);
+ validate_decay_lf_ratio(lReverb.flDecayLFRatio);
+ validate_reflections(lReverb.lReflections);
+ validate_reflections_delay(lReverb.flReflectionsDelay);
+ validate_reverb(lReverb.lReverb);
+ validate_reverb_delay(lReverb.flReverbDelay);
+ validate_echo_time(lReverb.flEchoTime);
+ validate_echo_depth(lReverb.flEchoDepth);
+ validate_modulation_time(lReverb.flModulationTime);
+ validate_modulation_depth(lReverb.flModulationDepth);
+ validate_air_absorbtion_hf(lReverb.flAirAbsorptionHF);
+ validate_hf_reference(lReverb.flHFReference);
+ validate_lf_reference(lReverb.flLFReference);
+ validate_room_rolloff_factor(lReverb.flRoomRolloffFactor);
+ validate_flags(lReverb.ulFlags);
+}
+
+void EaxReverbEffect::defer_environment(
+ unsigned long ulEnvironment)
+{
+ eax_d_.ulEnvironment = ulEnvironment;
+ eax_dirty_flags_.ulEnvironment = (eax_.ulEnvironment != eax_d_.ulEnvironment);
+}
+
+void EaxReverbEffect::defer_environment_size(
+ float flEnvironmentSize)
+{
+ eax_d_.flEnvironmentSize = flEnvironmentSize;
+ eax_dirty_flags_.flEnvironmentSize = (eax_.flEnvironmentSize != eax_d_.flEnvironmentSize);
+}
+
+void EaxReverbEffect::defer_environment_diffusion(
+ float flEnvironmentDiffusion)
+{
+ eax_d_.flEnvironmentDiffusion = flEnvironmentDiffusion;
+ eax_dirty_flags_.flEnvironmentDiffusion = (eax_.flEnvironmentDiffusion != eax_d_.flEnvironmentDiffusion);
+}
+
+void EaxReverbEffect::defer_room(
+ long lRoom)
+{
+ eax_d_.lRoom = lRoom;
+ eax_dirty_flags_.lRoom = (eax_.lRoom != eax_d_.lRoom);
+}
+
+void EaxReverbEffect::defer_room_hf(
+ long lRoomHF)
+{
+ eax_d_.lRoomHF = lRoomHF;
+ eax_dirty_flags_.lRoomHF = (eax_.lRoomHF != eax_d_.lRoomHF);
+}
+
+void EaxReverbEffect::defer_room_lf(
+ long lRoomLF)
+{
+ eax_d_.lRoomLF = lRoomLF;
+ eax_dirty_flags_.lRoomLF = (eax_.lRoomLF != eax_d_.lRoomLF);
+}
+
+void EaxReverbEffect::defer_decay_time(
+ float flDecayTime)
+{
+ eax_d_.flDecayTime = flDecayTime;
+ eax_dirty_flags_.flDecayTime = (eax_.flDecayTime != eax_d_.flDecayTime);
+}
+
+void EaxReverbEffect::defer_decay_hf_ratio(
+ float flDecayHFRatio)
+{
+ eax_d_.flDecayHFRatio = flDecayHFRatio;
+ eax_dirty_flags_.flDecayHFRatio = (eax_.flDecayHFRatio != eax_d_.flDecayHFRatio);
+}
+
+void EaxReverbEffect::defer_decay_lf_ratio(
+ float flDecayLFRatio)
+{
+ eax_d_.flDecayLFRatio = flDecayLFRatio;
+ eax_dirty_flags_.flDecayLFRatio = (eax_.flDecayLFRatio != eax_d_.flDecayLFRatio);
+}
+
+void EaxReverbEffect::defer_reflections(
+ long lReflections)
+{
+ eax_d_.lReflections = lReflections;
+ eax_dirty_flags_.lReflections = (eax_.lReflections != eax_d_.lReflections);
+}
+
+void EaxReverbEffect::defer_reflections_delay(
+ float flReflectionsDelay)
+{
+ eax_d_.flReflectionsDelay = flReflectionsDelay;
+ eax_dirty_flags_.flReflectionsDelay = (eax_.flReflectionsDelay != eax_d_.flReflectionsDelay);
+}
+
+void EaxReverbEffect::defer_reflections_pan(
+ const EAXVECTOR& vReflectionsPan)
+{
+ eax_d_.vReflectionsPan = vReflectionsPan;
+ eax_dirty_flags_.vReflectionsPan = (eax_.vReflectionsPan != eax_d_.vReflectionsPan);
+}
+
+void EaxReverbEffect::defer_reverb(
+ long lReverb)
+{
+ eax_d_.lReverb = lReverb;
+ eax_dirty_flags_.lReverb = (eax_.lReverb != eax_d_.lReverb);
+}
+
+void EaxReverbEffect::defer_reverb_delay(
+ float flReverbDelay)
+{
+ eax_d_.flReverbDelay = flReverbDelay;
+ eax_dirty_flags_.flReverbDelay = (eax_.flReverbDelay != eax_d_.flReverbDelay);
+}
+
+void EaxReverbEffect::defer_reverb_pan(
+ const EAXVECTOR& vReverbPan)
+{
+ eax_d_.vReverbPan = vReverbPan;
+ eax_dirty_flags_.vReverbPan = (eax_.vReverbPan != eax_d_.vReverbPan);
+}
+
+void EaxReverbEffect::defer_echo_time(
+ float flEchoTime)
+{
+ eax_d_.flEchoTime = flEchoTime;
+ eax_dirty_flags_.flEchoTime = (eax_.flEchoTime != eax_d_.flEchoTime);
+}
+
+void EaxReverbEffect::defer_echo_depth(
+ float flEchoDepth)
+{
+ eax_d_.flEchoDepth = flEchoDepth;
+ eax_dirty_flags_.flEchoDepth = (eax_.flEchoDepth != eax_d_.flEchoDepth);
+}
+
+void EaxReverbEffect::defer_modulation_time(
+ float flModulationTime)
+{
+ eax_d_.flModulationTime = flModulationTime;
+ eax_dirty_flags_.flModulationTime = (eax_.flModulationTime != eax_d_.flModulationTime);
+}
+
+void EaxReverbEffect::defer_modulation_depth(
+ float flModulationDepth)
+{
+ eax_d_.flModulationDepth = flModulationDepth;
+ eax_dirty_flags_.flModulationDepth = (eax_.flModulationDepth != eax_d_.flModulationDepth);
+}
+
+void EaxReverbEffect::defer_air_absorbtion_hf(
+ float flAirAbsorptionHF)
+{
+ eax_d_.flAirAbsorptionHF = flAirAbsorptionHF;
+ eax_dirty_flags_.flAirAbsorptionHF = (eax_.flAirAbsorptionHF != eax_d_.flAirAbsorptionHF);
+}
+
+void EaxReverbEffect::defer_hf_reference(
+ float flHFReference)
+{
+ eax_d_.flHFReference = flHFReference;
+ eax_dirty_flags_.flHFReference = (eax_.flHFReference != eax_d_.flHFReference);
+}
+
+void EaxReverbEffect::defer_lf_reference(
+ float flLFReference)
+{
+ eax_d_.flLFReference = flLFReference;
+ eax_dirty_flags_.flLFReference = (eax_.flLFReference != eax_d_.flLFReference);
+}
+
+void EaxReverbEffect::defer_room_rolloff_factor(
+ float flRoomRolloffFactor)
+{
+ eax_d_.flRoomRolloffFactor = flRoomRolloffFactor;
+ eax_dirty_flags_.flRoomRolloffFactor = (eax_.flRoomRolloffFactor != eax_d_.flRoomRolloffFactor);
+}
+
+void EaxReverbEffect::defer_flags(
+ unsigned long ulFlags)
+{
+ eax_d_.ulFlags = ulFlags;
+ eax_dirty_flags_.ulFlags = (eax_.ulFlags != eax_d_.ulFlags);
+}
+
+void EaxReverbEffect::defer_all(
+ const EAX20LISTENERPROPERTIES& listener)
+{
+ defer_room(listener.lRoom);
+ defer_room_hf(listener.lRoomHF);
+ defer_room_rolloff_factor(listener.flRoomRolloffFactor);
+ defer_decay_time(listener.flDecayTime);
+ defer_decay_hf_ratio(listener.flDecayHFRatio);
+ defer_reflections(listener.lReflections);
+ defer_reflections_delay(listener.flReflectionsDelay);
+ defer_reverb(listener.lReverb);
+ defer_reverb_delay(listener.flReverbDelay);
+ defer_environment(listener.dwEnvironment);
+ defer_environment_size(listener.flEnvironmentSize);
+ defer_environment_diffusion(listener.flEnvironmentDiffusion);
+ defer_air_absorbtion_hf(listener.flAirAbsorptionHF);
+ defer_flags(listener.dwFlags);
+}
+
+void EaxReverbEffect::defer_all(
+ const EAXREVERBPROPERTIES& lReverb)
+{
+ defer_environment(lReverb.ulEnvironment);
+ defer_environment_size(lReverb.flEnvironmentSize);
+ defer_environment_diffusion(lReverb.flEnvironmentDiffusion);
+ defer_room(lReverb.lRoom);
+ defer_room_hf(lReverb.lRoomHF);
+ defer_room_lf(lReverb.lRoomLF);
+ defer_decay_time(lReverb.flDecayTime);
+ defer_decay_hf_ratio(lReverb.flDecayHFRatio);
+ defer_decay_lf_ratio(lReverb.flDecayLFRatio);
+ defer_reflections(lReverb.lReflections);
+ defer_reflections_delay(lReverb.flReflectionsDelay);
+ defer_reflections_pan(lReverb.vReflectionsPan);
+ defer_reverb(lReverb.lReverb);
+ defer_reverb_delay(lReverb.flReverbDelay);
+ defer_reverb_pan(lReverb.vReverbPan);
+ defer_echo_time(lReverb.flEchoTime);
+ defer_echo_depth(lReverb.flEchoDepth);
+ defer_modulation_time(lReverb.flModulationTime);
+ defer_modulation_depth(lReverb.flModulationDepth);
+ defer_air_absorbtion_hf(lReverb.flAirAbsorptionHF);
+ defer_hf_reference(lReverb.flHFReference);
+ defer_lf_reference(lReverb.flLFReference);
+ defer_room_rolloff_factor(lReverb.flRoomRolloffFactor);
+ defer_flags(lReverb.ulFlags);
+}
+
+void EaxReverbEffect::defer_environment(
+ const EaxEaxCall& eax_call)
+{
+ const auto& ulEnvironment =
+ eax_call.get_value<EaxReverbEffectException, const decltype(EAXREVERBPROPERTIES::ulEnvironment)>();
+
+ validate_environment(ulEnvironment, eax_call.get_version(), true);
+
+ if (eax_d_.ulEnvironment == ulEnvironment)
+ {
+ return;
+ }
+
+ const auto& reverb_preset = EAXREVERB_PRESETS[ulEnvironment];
+
+ defer_all(reverb_preset);
+}
+
+void EaxReverbEffect::defer_environment_size(
+ const EaxEaxCall& eax_call)
+{
+ const auto& flEnvironmentSize =
+ eax_call.get_value<EaxReverbEffectException, const decltype(EAXREVERBPROPERTIES::flEnvironmentSize)>();
+
+ validate_environment_size(flEnvironmentSize);
+
+ if (eax_d_.flEnvironmentSize == flEnvironmentSize)
+ {
+ return;
+ }
+
+ const auto scale = flEnvironmentSize / eax_d_.flEnvironmentSize;
+
+ defer_environment_size(flEnvironmentSize);
+
+ if ((eax_d_.ulFlags & EAXREVERBFLAGS_DECAYTIMESCALE) != 0)
+ {
+ const auto flDecayTime = clamp(
+ scale * eax_d_.flDecayTime,
+ EAXREVERB_MINDECAYTIME,
+ EAXREVERB_MAXDECAYTIME);
+
+ defer_decay_time(flDecayTime);
+ }
+
+ if ((eax_d_.ulFlags & EAXREVERBFLAGS_REFLECTIONSSCALE) != 0)
+ {
+ if ((eax_d_.ulFlags & EAXREVERBFLAGS_REFLECTIONSDELAYSCALE) != 0)
+ {
+ const auto lReflections = clamp(
+ eax_d_.lReflections - static_cast<long>(gain_to_level_mb(scale)),
+ EAXREVERB_MINREFLECTIONS,
+ EAXREVERB_MAXREFLECTIONS);
+
+ defer_reflections(lReflections);
+ }
+ }
+
+ if ((eax_d_.ulFlags & EAXREVERBFLAGS_REFLECTIONSDELAYSCALE) != 0)
+ {
+ const auto flReflectionsDelay = clamp(
+ eax_d_.flReflectionsDelay * scale,
+ EAXREVERB_MINREFLECTIONSDELAY,
+ EAXREVERB_MAXREFLECTIONSDELAY);
+
+ defer_reflections_delay(flReflectionsDelay);
+ }
+
+ if ((eax_d_.ulFlags & EAXREVERBFLAGS_REVERBSCALE) != 0)
+ {
+ const auto log_scalar = ((eax_d_.ulFlags & EAXREVERBFLAGS_DECAYTIMESCALE) != 0) ? 2'000.0F : 3'000.0F;
+
+ const auto lReverb = clamp(
+ eax_d_.lReverb - static_cast<long>(std::log10(scale) * log_scalar),
+ EAXREVERB_MINREVERB,
+ EAXREVERB_MAXREVERB);
+
+ defer_reverb(lReverb);
+ }
+
+ if ((eax_d_.ulFlags & EAXREVERBFLAGS_REVERBDELAYSCALE) != 0)
+ {
+ const auto flReverbDelay = clamp(
+ scale * eax_d_.flReverbDelay,
+ EAXREVERB_MINREVERBDELAY,
+ EAXREVERB_MAXREVERBDELAY);
+
+ defer_reverb_delay(flReverbDelay);
+ }
+
+ if ((eax_d_.ulFlags & EAXREVERBFLAGS_ECHOTIMESCALE) != 0)
+ {
+ const auto flEchoTime = clamp(
+ eax_d_.flEchoTime * scale,
+ EAXREVERB_MINECHOTIME,
+ EAXREVERB_MAXECHOTIME);
+
+ defer_echo_time(flEchoTime);
+ }
+
+ if ((eax_d_.ulFlags & EAXREVERBFLAGS_MODULATIONTIMESCALE) != 0)
+ {
+ const auto flModulationTime = clamp(
+ scale * eax_d_.flModulationTime,
+ EAXREVERB_MINMODULATIONTIME,
+ EAXREVERB_MAXMODULATIONTIME);
+
+ defer_modulation_time(flModulationTime);
+ }
+}
+
+void EaxReverbEffect::defer_environment_diffusion(
+ const EaxEaxCall& eax_call)
+{
+ const auto& flEnvironmentDiffusion =
+ eax_call.get_value<EaxReverbEffectException, const decltype(EAXREVERBPROPERTIES::flEnvironmentDiffusion)>();
+
+ validate_environment_diffusion(flEnvironmentDiffusion);
+ defer_environment_diffusion(flEnvironmentDiffusion);
+}
+
+void EaxReverbEffect::defer_room(
+ const EaxEaxCall& eax_call)
+{
+ const auto& lRoom =
+ eax_call.get_value<EaxReverbEffectException, const decltype(EAXREVERBPROPERTIES::lRoom)>();
+
+ validate_room(lRoom);
+ defer_room(lRoom);
+}
+
+void EaxReverbEffect::defer_room_hf(
+ const EaxEaxCall& eax_call)
+{
+ const auto& lRoomHF =
+ eax_call.get_value<EaxReverbEffectException, const decltype(EAXREVERBPROPERTIES::lRoomHF)>();
+
+ validate_room_hf(lRoomHF);
+ defer_room_hf(lRoomHF);
+}
+
+void EaxReverbEffect::defer_room_lf(
+ const EaxEaxCall& eax_call)
+{
+ const auto& lRoomLF =
+ eax_call.get_value<EaxReverbEffectException, const decltype(EAXREVERBPROPERTIES::lRoomLF)>();
+
+ validate_room_lf(lRoomLF);
+ defer_room_lf(lRoomLF);
+}
+
+void EaxReverbEffect::defer_decay_time(
+ const EaxEaxCall& eax_call)
+{
+ const auto& flDecayTime =
+ eax_call.get_value<EaxReverbEffectException, const decltype(EAXREVERBPROPERTIES::flDecayTime)>();
+
+ validate_decay_time(flDecayTime);
+ defer_decay_time(flDecayTime);
+}
+
+void EaxReverbEffect::defer_decay_hf_ratio(
+ const EaxEaxCall& eax_call)
+{
+ const auto& flDecayHFRatio =
+ eax_call.get_value<EaxReverbEffectException, const decltype(EAXREVERBPROPERTIES::flDecayHFRatio)>();
+
+ validate_decay_hf_ratio(flDecayHFRatio);
+ defer_decay_hf_ratio(flDecayHFRatio);
+}
+
+void EaxReverbEffect::defer_decay_lf_ratio(
+ const EaxEaxCall& eax_call)
+{
+ const auto& flDecayLFRatio =
+ eax_call.get_value<EaxReverbEffectException, const decltype(EAXREVERBPROPERTIES::flDecayLFRatio)>();
+
+ validate_decay_lf_ratio(flDecayLFRatio);
+ defer_decay_lf_ratio(flDecayLFRatio);
+}
+
+void EaxReverbEffect::defer_reflections(
+ const EaxEaxCall& eax_call)
+{
+ const auto& lReflections =
+ eax_call.get_value<EaxReverbEffectException, const decltype(EAXREVERBPROPERTIES::lReflections)>();
+
+ validate_reflections(lReflections);
+ defer_reflections(lReflections);
+}
+
+void EaxReverbEffect::defer_reflections_delay(
+ const EaxEaxCall& eax_call)
+{
+ const auto& flReflectionsDelay =
+ eax_call.get_value<EaxReverbEffectException, const decltype(EAXREVERBPROPERTIES::flReflectionsDelay)>();
+
+ validate_reflections_delay(flReflectionsDelay);
+ defer_reflections_delay(flReflectionsDelay);
+}
+
+void EaxReverbEffect::defer_reflections_pan(
+ const EaxEaxCall& eax_call)
+{
+ const auto& vReflectionsPan =
+ eax_call.get_value<EaxReverbEffectException, const decltype(EAXREVERBPROPERTIES::vReflectionsPan)>();
+
+ validate_reflections_pan(vReflectionsPan);
+ defer_reflections_pan(vReflectionsPan);
+}
+
+void EaxReverbEffect::defer_reverb(
+ const EaxEaxCall& eax_call)
+{
+ const auto& lReverb =
+ eax_call.get_value<EaxReverbEffectException, const decltype(EAXREVERBPROPERTIES::lReverb)>();
+
+ validate_reverb(lReverb);
+ defer_reverb(lReverb);
+}
+
+void EaxReverbEffect::defer_reverb_delay(
+ const EaxEaxCall& eax_call)
+{
+ const auto& flReverbDelay =
+ eax_call.get_value<EaxReverbEffectException, const decltype(EAXREVERBPROPERTIES::flReverbDelay)>();
+
+ validate_reverb_delay(flReverbDelay);
+ defer_reverb_delay(flReverbDelay);
+}
+
+void EaxReverbEffect::defer_reverb_pan(
+ const EaxEaxCall& eax_call)
+{
+ const auto& vReverbPan =
+ eax_call.get_value<EaxReverbEffectException, const decltype(EAXREVERBPROPERTIES::vReverbPan)>();
+
+ validate_reverb_pan(vReverbPan);
+ defer_reverb_pan(vReverbPan);
+}
+
+void EaxReverbEffect::defer_echo_time(
+ const EaxEaxCall& eax_call)
+{
+ const auto& flEchoTime =
+ eax_call.get_value<EaxReverbEffectException, const decltype(EAXREVERBPROPERTIES::flEchoTime)>();
+
+ validate_echo_time(flEchoTime);
+ defer_echo_time(flEchoTime);
+}
+
+void EaxReverbEffect::defer_echo_depth(
+ const EaxEaxCall& eax_call)
+{
+ const auto& flEchoDepth =
+ eax_call.get_value<EaxReverbEffectException, const decltype(EAXREVERBPROPERTIES::flEchoDepth)>();
+
+ validate_echo_depth(flEchoDepth);
+ defer_echo_depth(flEchoDepth);
+}
+
+void EaxReverbEffect::defer_modulation_time(
+ const EaxEaxCall& eax_call)
+{
+ const auto& flModulationTime =
+ eax_call.get_value<EaxReverbEffectException, const decltype(EAXREVERBPROPERTIES::flModulationTime)>();
+
+ validate_modulation_time(flModulationTime);
+ defer_modulation_time(flModulationTime);
+}
+
+void EaxReverbEffect::defer_modulation_depth(
+ const EaxEaxCall& eax_call)
+{
+ const auto& flModulationDepth =
+ eax_call.get_value<EaxReverbEffectException, const decltype(EAXREVERBPROPERTIES::flModulationDepth)>();
+
+ validate_modulation_depth(flModulationDepth);
+ defer_modulation_depth(flModulationDepth);
+}
+
+void EaxReverbEffect::defer_air_absorbtion_hf(
+ const EaxEaxCall& eax_call)
+{
+ const auto& air_absorbtion_hf =
+ eax_call.get_value<EaxReverbEffectException, const decltype(EAXREVERBPROPERTIES::flAirAbsorptionHF)>();
+
+ validate_air_absorbtion_hf(air_absorbtion_hf);
+ defer_air_absorbtion_hf(air_absorbtion_hf);
+}
+
+void EaxReverbEffect::defer_hf_reference(
+ const EaxEaxCall& eax_call)
+{
+ const auto& flHFReference =
+ eax_call.get_value<EaxReverbEffectException, const decltype(EAXREVERBPROPERTIES::flHFReference)>();
+
+ validate_hf_reference(flHFReference);
+ defer_hf_reference(flHFReference);
+}
+
+void EaxReverbEffect::defer_lf_reference(
+ const EaxEaxCall& eax_call)
+{
+ const auto& flLFReference =
+ eax_call.get_value<EaxReverbEffectException, const decltype(EAXREVERBPROPERTIES::flLFReference)>();
+
+ validate_lf_reference(flLFReference);
+ defer_lf_reference(flLFReference);
+}
+
+void EaxReverbEffect::defer_room_rolloff_factor(
+ const EaxEaxCall& eax_call)
+{
+ const auto& flRoomRolloffFactor =
+ eax_call.get_value<EaxReverbEffectException, const decltype(EAXREVERBPROPERTIES::flRoomRolloffFactor)>();
+
+ validate_room_rolloff_factor(flRoomRolloffFactor);
+ defer_room_rolloff_factor(flRoomRolloffFactor);
+}
+
+void EaxReverbEffect::defer_flags(
+ const EaxEaxCall& eax_call)
+{
+ const auto& ulFlags =
+ eax_call.get_value<EaxReverbEffectException, const decltype(EAXREVERBPROPERTIES::ulFlags)>();
+
+ validate_flags(ulFlags);
+ defer_flags(ulFlags);
+}
+
+void EaxReverbEffect::defer_all(
+ const EaxEaxCall& eax_call)
+{
+ const auto eax_version = eax_call.get_version();
+
+ if (eax_version == 2)
+ {
+ const auto& listener =
+ eax_call.get_value<EaxReverbEffectException, const EAX20LISTENERPROPERTIES>();
+
+ validate_all(listener, eax_version);
+ defer_all(listener);
+ }
+ else
+ {
+ const auto& reverb_all =
+ eax_call.get_value<EaxReverbEffectException, const EAXREVERBPROPERTIES>();
+
+ validate_all(reverb_all, eax_version);
+ defer_all(reverb_all);
+ }
+}
+
+// [[nodiscard]]
+bool EaxReverbEffect::apply_deferred()
+{
+ if (eax_dirty_flags_ == EaxReverbEffectDirtyFlags{})
+ {
+ return false;
+ }
+
+ eax_ = eax_d_;
+
+ if (eax_dirty_flags_.ulEnvironment)
+ {
+ }
+
+ if (eax_dirty_flags_.flEnvironmentSize)
+ {
+ set_efx_density();
+ }
+
+ if (eax_dirty_flags_.flEnvironmentDiffusion)
+ {
+ set_efx_diffusion();
+ }
+
+ if (eax_dirty_flags_.lRoom)
+ {
+ set_efx_gain();
+ }
+
+ if (eax_dirty_flags_.lRoomHF)
+ {
+ set_efx_gain_hf();
+ }
+
+ if (eax_dirty_flags_.lRoomLF)
+ {
+ set_efx_gain_lf();
+ }
+
+ if (eax_dirty_flags_.flDecayTime)
+ {
+ set_efx_decay_time();
+ }
+
+ if (eax_dirty_flags_.flDecayHFRatio)
+ {
+ set_efx_decay_hf_ratio();
+ }
+
+ if (eax_dirty_flags_.flDecayLFRatio)
+ {
+ set_efx_decay_lf_ratio();
+ }
+
+ if (eax_dirty_flags_.lReflections)
+ {
+ set_efx_reflections_gain();
+ }
+
+ if (eax_dirty_flags_.flReflectionsDelay)
+ {
+ set_efx_reflections_delay();
+ }
+
+ if (eax_dirty_flags_.vReflectionsPan)
+ {
+ set_efx_reflections_pan();
+ }
+
+ if (eax_dirty_flags_.lReverb)
+ {
+ set_efx_late_reverb_gain();
+ }
+
+ if (eax_dirty_flags_.flReverbDelay)
+ {
+ set_efx_late_reverb_delay();
+ }
+
+ if (eax_dirty_flags_.vReverbPan)
+ {
+ set_efx_late_reverb_pan();
+ }
+
+ if (eax_dirty_flags_.flEchoTime)
+ {
+ set_efx_echo_time();
+ }
+
+ if (eax_dirty_flags_.flEchoDepth)
+ {
+ set_efx_echo_depth();
+ }
+
+ if (eax_dirty_flags_.flModulationTime)
+ {
+ set_efx_modulation_time();
+ }
+
+ if (eax_dirty_flags_.flModulationDepth)
+ {
+ set_efx_modulation_depth();
+ }
+
+ if (eax_dirty_flags_.flAirAbsorptionHF)
+ {
+ set_efx_air_absorption_gain_hf();
+ }
+
+ if (eax_dirty_flags_.flHFReference)
+ {
+ set_efx_hf_reference();
+ }
+
+ if (eax_dirty_flags_.flLFReference)
+ {
+ set_efx_lf_reference();
+ }
+
+ if (eax_dirty_flags_.flRoomRolloffFactor)
+ {
+ set_efx_room_rolloff_factor();
+ }
+
+ if (eax_dirty_flags_.ulFlags)
+ {
+ set_efx_flags();
+ }
+
+ eax_dirty_flags_ = EaxReverbEffectDirtyFlags{};
+
+ return true;
+}
+
+// [[nodiscard]]
+bool EaxReverbEffect::set(
+ const EaxEaxCall& eax_call)
+{
+ switch (eax_call.get_property_id())
+ {
+ case EAXREVERB_NONE:
+ break;
+
+ case EAXREVERB_ALLPARAMETERS:
+ defer_all(eax_call);
+ break;
+
+ case EAXREVERB_ENVIRONMENT:
+ defer_environment(eax_call);
+ break;
+
+ case EAXREVERB_ENVIRONMENTSIZE:
+ defer_environment_size(eax_call);
+ break;
+
+ case EAXREVERB_ENVIRONMENTDIFFUSION:
+ defer_environment_diffusion(eax_call);
+ break;
+
+ case EAXREVERB_ROOM:
+ defer_room(eax_call);
+ break;
+
+ case EAXREVERB_ROOMHF:
+ defer_room_hf(eax_call);
+ break;
+
+ case EAXREVERB_ROOMLF:
+ defer_room_lf(eax_call);
+ break;
+
+ case EAXREVERB_DECAYTIME:
+ defer_decay_time(eax_call);
+ break;
+
+ case EAXREVERB_DECAYHFRATIO:
+ defer_decay_hf_ratio(eax_call);
+ break;
+
+ case EAXREVERB_DECAYLFRATIO:
+ defer_decay_lf_ratio(eax_call);
+ break;
+
+ case EAXREVERB_REFLECTIONS:
+ defer_reflections(eax_call);
+ break;
+
+ case EAXREVERB_REFLECTIONSDELAY:
+ defer_reflections_delay(eax_call);
+ break;
+
+ case EAXREVERB_REFLECTIONSPAN:
+ defer_reflections_pan(eax_call);
+ break;
+
+ case EAXREVERB_REVERB:
+ defer_reverb(eax_call);
+ break;
+
+ case EAXREVERB_REVERBDELAY:
+ defer_reverb_delay(eax_call);
+ break;
+
+ case EAXREVERB_REVERBPAN:
+ defer_reverb_pan(eax_call);
+ break;
+
+ case EAXREVERB_ECHOTIME:
+ defer_echo_time(eax_call);
+ break;
+
+ case EAXREVERB_ECHODEPTH:
+ defer_echo_depth(eax_call);
+ break;
+
+ case EAXREVERB_MODULATIONTIME:
+ defer_modulation_time(eax_call);
+ break;
+
+ case EAXREVERB_MODULATIONDEPTH:
+ defer_modulation_depth(eax_call);
+ break;
+
+ case EAXREVERB_AIRABSORPTIONHF:
+ defer_air_absorbtion_hf(eax_call);
+ break;
+
+ case EAXREVERB_HFREFERENCE:
+ defer_hf_reference(eax_call);
+ break;
+
+ case EAXREVERB_LFREFERENCE:
+ defer_lf_reference(eax_call);
+ break;
+
+ case EAXREVERB_ROOMROLLOFFFACTOR:
+ defer_room_rolloff_factor(eax_call);
+ break;
+
+ case EAXREVERB_FLAGS:
+ defer_flags(eax_call);
+ break;
+
+ default:
+ throw EaxReverbEffectException{"Unsupported property id."};
+ }
+
+ if (!eax_call.is_deferred())
+ {
+ return apply_deferred();
+ }
+
+ return false;
+}
+
+
+} // namespace
+
+
+EaxEffectUPtr eax_create_eax_reverb_effect(
+ EffectProps& al_effect_props)
+{
+ return std::make_unique<EaxReverbEffect>(al_effect_props);
+}
+
+
+#endif // ALSOFT_EAX
diff --git a/al/effects/vmorpher.cpp b/al/effects/vmorpher.cpp
index 1b4710ff..2a9e0702 100644
--- a/al/effects/vmorpher.cpp
+++ b/al/effects/vmorpher.cpp
@@ -10,6 +10,15 @@
#include "aloptional.h"
#include "effects.h"
+#if ALSOFT_EAX
+#include <cassert>
+
+#include "alnumeric.h"
+
+#include "al/eax_exception.h"
+#include "al/eax_utils.h"
+#endif // ALSOFT_EAX
+
namespace {
@@ -247,3 +256,618 @@ EffectProps genDefaultProps() noexcept
DEFINE_ALEFFECT_VTABLE(Vmorpher);
const EffectProps VmorpherEffectProps{genDefaultProps()};
+
+#if ALSOFT_EAX
+namespace
+{
+
+
+using EaxVocalMorpherEffectDirtyFlagsValue = std::uint_least8_t;
+
+struct EaxVocalMorpherEffectDirtyFlags
+{
+ using EaxIsBitFieldStruct = bool;
+
+ EaxVocalMorpherEffectDirtyFlagsValue ulPhonemeA : 1;
+ EaxVocalMorpherEffectDirtyFlagsValue lPhonemeACoarseTuning : 1;
+ EaxVocalMorpherEffectDirtyFlagsValue ulPhonemeB : 1;
+ EaxVocalMorpherEffectDirtyFlagsValue lPhonemeBCoarseTuning : 1;
+ EaxVocalMorpherEffectDirtyFlagsValue ulWaveform : 1;
+ EaxVocalMorpherEffectDirtyFlagsValue flRate : 1;
+}; // EaxPitchShifterEffectDirtyFlags
+
+
+class EaxVocalMorpherEffect final :
+ public EaxEffect
+{
+public:
+ EaxVocalMorpherEffect(
+ EffectProps& al_effect_props);
+
+
+ // [[nodiscard]]
+ bool dispatch(
+ const EaxEaxCall& eax_call) override;
+
+
+private:
+ EffectProps& al_effect_props_;
+
+ EAXVOCALMORPHERPROPERTIES eax_{};
+ EAXVOCALMORPHERPROPERTIES eax_d_{};
+ EaxVocalMorpherEffectDirtyFlags eax_dirty_flags_{};
+
+
+ void set_eax_defaults();
+
+
+ void set_efx_phoneme_a();
+
+ void set_efx_phoneme_a_coarse_tuning();
+
+ void set_efx_phoneme_b();
+
+ void set_efx_phoneme_b_coarse_tuning();
+
+ void set_efx_waveform();
+
+ void set_efx_rate();
+
+ void set_efx_defaults();
+
+
+ // [[nodiscard]]
+ bool get(
+ const EaxEaxCall& eax_call);
+
+
+ void validate_phoneme_a(
+ unsigned long ulPhonemeA);
+
+ void validate_phoneme_a_coarse_tuning(
+ long lPhonemeACoarseTuning);
+
+ void validate_phoneme_b(
+ unsigned long ulPhonemeB);
+
+ void validate_phoneme_b_coarse_tuning(
+ long lPhonemeBCoarseTuning);
+
+ void validate_waveform(
+ unsigned long ulWaveform);
+
+ void validate_rate(
+ float flRate);
+
+ void validate_all(
+ const EAXVOCALMORPHERPROPERTIES& all);
+
+
+ void defer_phoneme_a(
+ unsigned long ulPhonemeA);
+
+ void defer_phoneme_a_coarse_tuning(
+ long lPhonemeACoarseTuning);
+
+ void defer_phoneme_b(
+ unsigned long ulPhonemeB);
+
+ void defer_phoneme_b_coarse_tuning(
+ long lPhonemeBCoarseTuning);
+
+ void defer_waveform(
+ unsigned long ulWaveform);
+
+ void defer_rate(
+ float flRate);
+
+ void defer_all(
+ const EAXVOCALMORPHERPROPERTIES& all);
+
+
+ void defer_phoneme_a(
+ const EaxEaxCall& eax_call);
+
+ void defer_phoneme_a_coarse_tuning(
+ const EaxEaxCall& eax_call);
+
+ void defer_phoneme_b(
+ const EaxEaxCall& eax_call);
+
+ void defer_phoneme_b_coarse_tuning(
+ const EaxEaxCall& eax_call);
+
+ void defer_waveform(
+ const EaxEaxCall& eax_call);
+
+ void defer_rate(
+ const EaxEaxCall& eax_call);
+
+ void defer_all(
+ const EaxEaxCall& eax_call);
+
+
+ // [[nodiscard]]
+ bool apply_deferred();
+
+ // [[nodiscard]]
+ bool set(
+ const EaxEaxCall& eax_call);
+}; // EaxVocalMorpherEffect
+
+
+class EaxVocalMorpherEffectException :
+ public EaxException
+{
+public:
+ explicit EaxVocalMorpherEffectException(
+ const char* message)
+ :
+ EaxException{"EAX_VOCAL_MORPHER_EFFECT", message}
+ {
+ }
+}; // EaxVocalMorpherEffectException
+
+
+EaxVocalMorpherEffect::EaxVocalMorpherEffect(
+ EffectProps& al_effect_props)
+ :
+ al_effect_props_{al_effect_props}
+{
+ set_eax_defaults();
+ set_efx_defaults();
+}
+
+// [[nodiscard]]
+bool EaxVocalMorpherEffect::dispatch(
+ const EaxEaxCall& eax_call)
+{
+ return eax_call.is_get() ? get(eax_call) : set(eax_call);
+}
+
+void EaxVocalMorpherEffect::set_eax_defaults()
+{
+ eax_.ulPhonemeA = EAXVOCALMORPHER_DEFAULTPHONEMEA;
+ eax_.lPhonemeACoarseTuning = EAXVOCALMORPHER_DEFAULTPHONEMEACOARSETUNING;
+ eax_.ulPhonemeB = EAXVOCALMORPHER_DEFAULTPHONEMEB;
+ eax_.lPhonemeBCoarseTuning = EAXVOCALMORPHER_DEFAULTPHONEMEBCOARSETUNING;
+ eax_.ulWaveform = EAXVOCALMORPHER_DEFAULTWAVEFORM;
+ eax_.flRate = EAXVOCALMORPHER_DEFAULTRATE;
+
+ eax_d_ = eax_;
+}
+
+void EaxVocalMorpherEffect::set_efx_phoneme_a()
+{
+ const auto phoneme_a = clamp(
+ static_cast<ALint>(eax_.ulPhonemeA),
+ AL_VOCAL_MORPHER_MIN_PHONEMEA,
+ AL_VOCAL_MORPHER_MAX_PHONEMEA);
+
+ const auto efx_phoneme_a = PhenomeFromEnum(phoneme_a);
+ assert(efx_phoneme_a.has_value());
+ al_effect_props_.Vmorpher.PhonemeA = *efx_phoneme_a;
+}
+
+void EaxVocalMorpherEffect::set_efx_phoneme_a_coarse_tuning()
+{
+ const auto phoneme_a_coarse_tuning = clamp(
+ static_cast<ALint>(eax_.lPhonemeACoarseTuning),
+ AL_VOCAL_MORPHER_MIN_PHONEMEA_COARSE_TUNING,
+ AL_VOCAL_MORPHER_MAX_PHONEMEA_COARSE_TUNING);
+
+ al_effect_props_.Vmorpher.PhonemeACoarseTuning = phoneme_a_coarse_tuning;
+}
+
+void EaxVocalMorpherEffect::set_efx_phoneme_b()
+{
+ const auto phoneme_b = clamp(
+ static_cast<ALint>(eax_.ulPhonemeB),
+ AL_VOCAL_MORPHER_MIN_PHONEMEB,
+ AL_VOCAL_MORPHER_MAX_PHONEMEB);
+
+ const auto efx_phoneme_b = PhenomeFromEnum(phoneme_b);
+ assert(efx_phoneme_b.has_value());
+ al_effect_props_.Vmorpher.PhonemeB = *efx_phoneme_b;
+}
+
+void EaxVocalMorpherEffect::set_efx_phoneme_b_coarse_tuning()
+{
+ const auto phoneme_b_coarse_tuning = clamp(
+ static_cast<ALint>(eax_.lPhonemeBCoarseTuning),
+ AL_VOCAL_MORPHER_MIN_PHONEMEB_COARSE_TUNING,
+ AL_VOCAL_MORPHER_MAX_PHONEMEB_COARSE_TUNING);
+
+ al_effect_props_.Vmorpher.PhonemeBCoarseTuning = phoneme_b_coarse_tuning;
+}
+
+void EaxVocalMorpherEffect::set_efx_waveform()
+{
+ const auto waveform = clamp(
+ static_cast<ALint>(eax_.ulWaveform),
+ AL_VOCAL_MORPHER_MIN_WAVEFORM,
+ AL_VOCAL_MORPHER_MAX_WAVEFORM);
+
+ const auto wfx_waveform = WaveformFromEmum(waveform);
+ assert(wfx_waveform.has_value());
+ al_effect_props_.Vmorpher.Waveform = *wfx_waveform;
+}
+
+void EaxVocalMorpherEffect::set_efx_rate()
+{
+ const auto rate = clamp(
+ eax_.flRate,
+ AL_VOCAL_MORPHER_MIN_RATE,
+ AL_VOCAL_MORPHER_MAX_RATE);
+
+ al_effect_props_.Vmorpher.Rate = rate;
+}
+
+void EaxVocalMorpherEffect::set_efx_defaults()
+{
+ set_efx_phoneme_a();
+ set_efx_phoneme_a_coarse_tuning();
+ set_efx_phoneme_b();
+ set_efx_phoneme_b_coarse_tuning();
+ set_efx_waveform();
+ set_efx_rate();
+}
+
+// [[nodiscard]]
+bool EaxVocalMorpherEffect::get(
+ const EaxEaxCall& eax_call)
+{
+ switch (eax_call.get_property_id())
+ {
+ case EAXVOCALMORPHER_NONE:
+ break;
+
+ case EAXVOCALMORPHER_ALLPARAMETERS:
+ eax_call.set_value<EaxVocalMorpherEffectException>(eax_);
+ break;
+
+ case EAXVOCALMORPHER_PHONEMEA:
+ eax_call.set_value<EaxVocalMorpherEffectException>(eax_.ulPhonemeA);
+ break;
+
+ case EAXVOCALMORPHER_PHONEMEACOARSETUNING:
+ eax_call.set_value<EaxVocalMorpherEffectException>(eax_.lPhonemeACoarseTuning);
+ break;
+
+ case EAXVOCALMORPHER_PHONEMEB:
+ eax_call.set_value<EaxVocalMorpherEffectException>(eax_.ulPhonemeB);
+ break;
+
+ case EAXVOCALMORPHER_PHONEMEBCOARSETUNING:
+ eax_call.set_value<EaxVocalMorpherEffectException>(eax_.lPhonemeBCoarseTuning);
+ break;
+
+ case EAXVOCALMORPHER_WAVEFORM:
+ eax_call.set_value<EaxVocalMorpherEffectException>(eax_.ulWaveform);
+ break;
+
+ case EAXVOCALMORPHER_RATE:
+ eax_call.set_value<EaxVocalMorpherEffectException>(eax_.flRate);
+ break;
+
+ default:
+ throw EaxVocalMorpherEffectException{"Unsupported property id."};
+ }
+
+ return false;
+}
+
+void EaxVocalMorpherEffect::validate_phoneme_a(
+ unsigned long ulPhonemeA)
+{
+ eax_validate_range<EaxVocalMorpherEffectException>(
+ "Phoneme A",
+ ulPhonemeA,
+ EAXVOCALMORPHER_MINPHONEMEA,
+ EAXVOCALMORPHER_MAXPHONEMEA);
+}
+
+void EaxVocalMorpherEffect::validate_phoneme_a_coarse_tuning(
+ long lPhonemeACoarseTuning)
+{
+ eax_validate_range<EaxVocalMorpherEffectException>(
+ "Phoneme A Coarse Tuning",
+ lPhonemeACoarseTuning,
+ EAXVOCALMORPHER_MINPHONEMEACOARSETUNING,
+ EAXVOCALMORPHER_MAXPHONEMEACOARSETUNING);
+}
+
+void EaxVocalMorpherEffect::validate_phoneme_b(
+ unsigned long ulPhonemeB)
+{
+ eax_validate_range<EaxVocalMorpherEffectException>(
+ "Phoneme B",
+ ulPhonemeB,
+ EAXVOCALMORPHER_MINPHONEMEB,
+ EAXVOCALMORPHER_MAXPHONEMEB);
+}
+
+void EaxVocalMorpherEffect::validate_phoneme_b_coarse_tuning(
+ long lPhonemeBCoarseTuning)
+{
+ eax_validate_range<EaxVocalMorpherEffectException>(
+ "Phoneme B Coarse Tuning",
+ lPhonemeBCoarseTuning,
+ EAXVOCALMORPHER_MINPHONEMEBCOARSETUNING,
+ EAXVOCALMORPHER_MAXPHONEMEBCOARSETUNING);
+}
+
+void EaxVocalMorpherEffect::validate_waveform(
+ unsigned long ulWaveform)
+{
+ eax_validate_range<EaxVocalMorpherEffectException>(
+ "Waveform",
+ ulWaveform,
+ EAXVOCALMORPHER_MINWAVEFORM,
+ EAXVOCALMORPHER_MAXWAVEFORM);
+}
+
+void EaxVocalMorpherEffect::validate_rate(
+ float flRate)
+{
+ eax_validate_range<EaxVocalMorpherEffectException>(
+ "Rate",
+ flRate,
+ EAXVOCALMORPHER_MINRATE,
+ EAXVOCALMORPHER_MAXRATE);
+}
+
+void EaxVocalMorpherEffect::validate_all(
+ const EAXVOCALMORPHERPROPERTIES& all)
+{
+ validate_phoneme_a(all.ulPhonemeA);
+ validate_phoneme_a_coarse_tuning(all.lPhonemeACoarseTuning);
+ validate_phoneme_b(all.ulPhonemeB);
+ validate_phoneme_b_coarse_tuning(all.lPhonemeBCoarseTuning);
+ validate_waveform(all.ulWaveform);
+ validate_rate(all.flRate);
+}
+
+void EaxVocalMorpherEffect::defer_phoneme_a(
+ unsigned long ulPhonemeA)
+{
+ eax_d_.ulPhonemeA = ulPhonemeA;
+ eax_dirty_flags_.ulPhonemeA = (eax_.ulPhonemeA != eax_d_.ulPhonemeA);
+}
+
+void EaxVocalMorpherEffect::defer_phoneme_a_coarse_tuning(
+ long lPhonemeACoarseTuning)
+{
+ eax_d_.lPhonemeACoarseTuning = lPhonemeACoarseTuning;
+ eax_dirty_flags_.lPhonemeACoarseTuning = (eax_.lPhonemeACoarseTuning != eax_d_.lPhonemeACoarseTuning);
+}
+
+void EaxVocalMorpherEffect::defer_phoneme_b(
+ unsigned long ulPhonemeB)
+{
+ eax_d_.ulPhonemeB = ulPhonemeB;
+ eax_dirty_flags_.ulPhonemeB = (eax_.ulPhonemeB != eax_d_.ulPhonemeB);
+}
+
+void EaxVocalMorpherEffect::defer_phoneme_b_coarse_tuning(
+ long lPhonemeBCoarseTuning)
+{
+ eax_d_.lPhonemeBCoarseTuning = lPhonemeBCoarseTuning;
+ eax_dirty_flags_.lPhonemeBCoarseTuning = (eax_.lPhonemeBCoarseTuning != eax_d_.lPhonemeBCoarseTuning);
+}
+
+void EaxVocalMorpherEffect::defer_waveform(
+ unsigned long ulWaveform)
+{
+ eax_d_.ulWaveform = ulWaveform;
+ eax_dirty_flags_.ulWaveform = (eax_.ulWaveform != eax_d_.ulWaveform);
+}
+
+void EaxVocalMorpherEffect::defer_rate(
+ float flRate)
+{
+ eax_d_.flRate = flRate;
+ eax_dirty_flags_.flRate = (eax_.flRate != eax_d_.flRate);
+}
+
+void EaxVocalMorpherEffect::defer_all(
+ const EAXVOCALMORPHERPROPERTIES& all)
+{
+ defer_phoneme_a(all.ulPhonemeA);
+ defer_phoneme_a_coarse_tuning(all.lPhonemeACoarseTuning);
+ defer_phoneme_b(all.ulPhonemeB);
+ defer_phoneme_b_coarse_tuning(all.lPhonemeBCoarseTuning);
+ defer_waveform(all.ulWaveform);
+ defer_rate(all.flRate);
+}
+
+void EaxVocalMorpherEffect::defer_phoneme_a(
+ const EaxEaxCall& eax_call)
+{
+ const auto& phoneme_a = eax_call.get_value<
+ EaxVocalMorpherEffectException,
+ const decltype(EAXVOCALMORPHERPROPERTIES::ulPhonemeA)
+ >();
+
+ validate_phoneme_a(phoneme_a);
+ defer_phoneme_a(phoneme_a);
+}
+
+void EaxVocalMorpherEffect::defer_phoneme_a_coarse_tuning(
+ const EaxEaxCall& eax_call)
+{
+ const auto& phoneme_a_coarse_tuning = eax_call.get_value<
+ EaxVocalMorpherEffectException,
+ const decltype(EAXVOCALMORPHERPROPERTIES::lPhonemeACoarseTuning)
+ >();
+
+ validate_phoneme_a_coarse_tuning(phoneme_a_coarse_tuning);
+ defer_phoneme_a_coarse_tuning(phoneme_a_coarse_tuning);
+}
+
+void EaxVocalMorpherEffect::defer_phoneme_b(
+ const EaxEaxCall& eax_call)
+{
+ const auto& phoneme_b = eax_call.get_value<
+ EaxVocalMorpherEffectException,
+ const decltype(EAXVOCALMORPHERPROPERTIES::ulPhonemeB)
+ >();
+
+ validate_phoneme_b(phoneme_b);
+ defer_phoneme_b(phoneme_b);
+}
+
+void EaxVocalMorpherEffect::defer_phoneme_b_coarse_tuning(
+ const EaxEaxCall& eax_call)
+{
+ const auto& phoneme_b_coarse_tuning = eax_call.get_value<
+ EaxVocalMorpherEffectException,
+ const decltype(EAXVOCALMORPHERPROPERTIES::lPhonemeBCoarseTuning)
+ >();
+
+ validate_phoneme_b_coarse_tuning(phoneme_b_coarse_tuning);
+ defer_phoneme_b_coarse_tuning(phoneme_b_coarse_tuning);
+}
+
+void EaxVocalMorpherEffect::defer_waveform(
+ const EaxEaxCall& eax_call)
+{
+ const auto& waveform = eax_call.get_value<
+ EaxVocalMorpherEffectException,
+ const decltype(EAXVOCALMORPHERPROPERTIES::ulWaveform)
+ >();
+
+ validate_waveform(waveform);
+ defer_waveform(waveform);
+}
+
+void EaxVocalMorpherEffect::defer_rate(
+ const EaxEaxCall& eax_call)
+{
+ const auto& rate = eax_call.get_value<
+ EaxVocalMorpherEffectException,
+ const decltype(EAXVOCALMORPHERPROPERTIES::flRate)
+ >();
+
+ validate_rate(rate);
+ defer_rate(rate);
+}
+
+void EaxVocalMorpherEffect::defer_all(
+ const EaxEaxCall& eax_call)
+{
+ const auto& all = eax_call.get_value<
+ EaxVocalMorpherEffectException,
+ const EAXVOCALMORPHERPROPERTIES
+ >();
+
+ validate_all(all);
+ defer_all(all);
+}
+
+// [[nodiscard]]
+bool EaxVocalMorpherEffect::apply_deferred()
+{
+ if (eax_dirty_flags_ == EaxVocalMorpherEffectDirtyFlags{})
+ {
+ return false;
+ }
+
+ eax_ = eax_d_;
+
+ if (eax_dirty_flags_.ulPhonemeA)
+ {
+ set_efx_phoneme_a();
+ }
+
+ if (eax_dirty_flags_.lPhonemeACoarseTuning)
+ {
+ set_efx_phoneme_a_coarse_tuning();
+ }
+
+ if (eax_dirty_flags_.ulPhonemeB)
+ {
+ set_efx_phoneme_b();
+ }
+
+ if (eax_dirty_flags_.lPhonemeBCoarseTuning)
+ {
+ set_efx_phoneme_b_coarse_tuning();
+ }
+
+ if (eax_dirty_flags_.ulWaveform)
+ {
+ set_efx_waveform();
+ }
+
+ if (eax_dirty_flags_.flRate)
+ {
+ set_efx_rate();
+ }
+
+ eax_dirty_flags_ = EaxVocalMorpherEffectDirtyFlags{};
+
+ return true;
+}
+
+// [[nodiscard]]
+bool EaxVocalMorpherEffect::set(
+ const EaxEaxCall& eax_call)
+{
+ switch (eax_call.get_property_id())
+ {
+ case EAXVOCALMORPHER_NONE:
+ break;
+
+ case EAXVOCALMORPHER_ALLPARAMETERS:
+ defer_all(eax_call);
+ break;
+
+ case EAXVOCALMORPHER_PHONEMEA:
+ defer_phoneme_a(eax_call);
+ break;
+
+ case EAXVOCALMORPHER_PHONEMEACOARSETUNING:
+ defer_phoneme_a_coarse_tuning(eax_call);
+ break;
+
+ case EAXVOCALMORPHER_PHONEMEB:
+ defer_phoneme_b(eax_call);
+ break;
+
+ case EAXVOCALMORPHER_PHONEMEBCOARSETUNING:
+ defer_phoneme_b_coarse_tuning(eax_call);
+ break;
+
+ case EAXVOCALMORPHER_WAVEFORM:
+ defer_waveform(eax_call);
+ break;
+
+ case EAXVOCALMORPHER_RATE:
+ defer_rate(eax_call);
+ break;
+
+ default:
+ throw EaxVocalMorpherEffectException{"Unsupported property id."};
+ }
+
+ if (!eax_call.is_deferred())
+ {
+ return apply_deferred();
+ }
+
+ return false;
+}
+
+
+} // namespace
+
+
+EaxEffectUPtr eax_create_eax_vocal_morpher_effect(
+ EffectProps& al_effect_props)
+{
+ return std::make_unique<EaxVocalMorpherEffect>(al_effect_props);
+}
+
+
+#endif // ALSOFT_EAX
diff --git a/al/extension.cpp b/al/extension.cpp
index 5dda2a86..373f87a9 100644
--- a/al/extension.cpp
+++ b/al/extension.cpp
@@ -32,6 +32,10 @@
#include "core/except.h"
#include "opthelpers.h"
+#if ALSOFT_EAX
+#include "eax_globals.h"
+#include "eax_x_ram.h"
+#endif // ALSOFT_EAX
AL_API ALboolean AL_APIENTRY alIsExtensionPresent(const ALchar *extName)
START_API_FUNC
@@ -43,6 +47,22 @@ START_API_FUNC
SETERR_RETURN(context, AL_INVALID_VALUE, AL_FALSE, "NULL pointer");
size_t len{strlen(extName)};
+#if ALSOFT_EAX
+ if (al::strncasecmp(eax_v2_0_ext_name, extName, len) == 0 ||
+ al::strncasecmp(eax_v3_0_ext_name, extName, len) == 0 ||
+ al::strncasecmp(eax_v4_0_ext_name, extName, len) == 0 ||
+ al::strncasecmp(eax_v5_0_ext_name, extName, len) == 0)
+ {
+ const auto is_present = eax_g_is_enabled && context->eax_is_capable();
+ return is_present ? AL_TRUE : AL_FALSE;
+ }
+
+ if (al::strncasecmp(eax_x_ram_ext_name, extName, len) == 0)
+ {
+ const auto is_present = eax_g_is_enabled;
+ return is_present ? AL_TRUE : AL_FALSE;
+ }
+#endif // ALSOFT_EAX
const char *ptr{context->mExtensionList};
while(ptr && *ptr)
{
@@ -66,6 +86,75 @@ AL_API ALvoid* AL_APIENTRY alGetProcAddress(const ALchar *funcName)
START_API_FUNC
{
if(!funcName) return nullptr;
+#if ALSOFT_EAX
+ if (al::strcasecmp(funcName, eax_eax_set_func_name) == 0)
+ {
+ if (!eax_g_is_enabled)
+ {
+ return nullptr;
+ }
+
+ ContextRef context{GetContextRef()};
+
+ if (!context || !context->eax_is_capable())
+ {
+ return nullptr;
+ }
+
+ return reinterpret_cast<ALvoid*>(EAXSet);
+ }
+
+ if (al::strcasecmp(funcName, eax_eax_get_func_name) == 0)
+ {
+ if (!eax_g_is_enabled)
+ {
+ return nullptr;
+ }
+
+ ContextRef context{GetContextRef()};
+
+ if (!context || !context->eax_is_capable())
+ {
+ return nullptr;
+ }
+
+ return reinterpret_cast<ALvoid*>(EAXGet);
+ }
+
+ if (al::strcasecmp(funcName, eax_eax_set_buffer_mode_func_name) == 0)
+ {
+ if (!eax_g_is_enabled)
+ {
+ return nullptr;
+ }
+
+ ContextRef context{GetContextRef()};
+
+ if (!context)
+ {
+ return nullptr;
+ }
+
+ return reinterpret_cast<ALvoid*>(EAXSetBufferMode);
+ }
+
+ if (al::strcasecmp(funcName, eax_eax_get_buffer_mode_func_name) == 0)
+ {
+ if (!eax_g_is_enabled)
+ {
+ return nullptr;
+ }
+
+ ContextRef context{GetContextRef()};
+
+ if (!context)
+ {
+ return nullptr;
+ }
+
+ return reinterpret_cast<ALvoid*>(EAXGetBufferMode);
+ }
+#endif // ALSOFT_EAX
return alcGetProcAddress(nullptr, funcName);
}
END_API_FUNC
@@ -74,6 +163,34 @@ AL_API ALenum AL_APIENTRY alGetEnumValue(const ALchar *enumName)
START_API_FUNC
{
if(!enumName) return static_cast<ALenum>(0);
+#if ALSOFT_EAX
+ if (eax_g_is_enabled)
+ {
+ struct Descriptor
+ {
+ const char* name;
+ ALenum value;
+ }; // Descriptor
+
+ constexpr Descriptor descriptors[] =
+ {
+ Descriptor{AL_EAX_RAM_SIZE_NAME, AL_EAX_RAM_SIZE},
+ Descriptor{AL_EAX_RAM_FREE_NAME, AL_EAX_RAM_FREE},
+
+ Descriptor{AL_STORAGE_AUTOMATIC_NAME, AL_STORAGE_AUTOMATIC},
+ Descriptor{AL_STORAGE_HARDWARE_NAME, AL_STORAGE_HARDWARE},
+ Descriptor{AL_STORAGE_ACCESSIBLE_NAME, AL_STORAGE_ACCESSIBLE},
+ }; // descriptors
+
+ for (const auto& descriptor : descriptors)
+ {
+ if (strcmp(descriptor.name, enumName) == 0)
+ {
+ return descriptor.value;
+ }
+ }
+ }
+#endif // ALSOFT_EAX
return alcGetEnumValue(nullptr, enumName);
}
END_API_FUNC
diff --git a/al/filter.cpp b/al/filter.cpp
index 73067fd9..563fb3ef 100644
--- a/al/filter.cpp
+++ b/al/filter.cpp
@@ -45,6 +45,10 @@
#include "opthelpers.h"
#include "vector.h"
+#if ALSOFT_EAX
+#include "core/logging.h"
+#endif // ALSOFT_EAX
+
namespace {
@@ -717,3 +721,80 @@ FilterSubList::~FilterSubList()
al_free(Filters);
Filters = nullptr;
}
+
+
+#if ALSOFT_EAX
+EaxAlFilterDeleter::EaxAlFilterDeleter(
+ ALCcontext& context)
+ :
+ context_{&context}
+{
+}
+
+void EaxAlFilterDeleter::operator()(
+ ALfilter* filter)
+{
+ eax_delete_low_pass_filter(*context_, *filter);
+}
+
+EaxAlFilterUPtr eax_create_al_low_pass_filter(
+ ALCcontext& context)
+{
+#define EAX_PREFIX "[EAX_MAKE_LOW_PASS_FILTER] "
+
+ auto& device = *context.mALDevice;
+ std::lock_guard<std::mutex> filter_lock{device.FilterLock};
+
+ if (!EnsureFilters(&device, 1))
+ {
+ ERR(EAX_PREFIX "%s\n", "Failed to ensure.");
+ return nullptr;
+ }
+
+ auto filter = EaxAlFilterUPtr{AllocFilter(&device), EaxAlFilterDeleter{context}};
+
+ if (!filter)
+ {
+ ERR(EAX_PREFIX "%s\n", "Failed to allocate.");
+ return nullptr;
+ }
+
+ InitFilterParams(filter.get(), AL_FILTER_LOWPASS);
+
+ return filter;
+
+#undef EAX_PREFIX
+}
+
+void eax_delete_low_pass_filter(
+ ALCcontext& context,
+ ALfilter& filter)
+{
+ auto& device = *context.mALDevice;
+ std::lock_guard<std::mutex> filter_lock{device.FilterLock};
+
+ FreeFilter(&device, &filter);
+}
+
+void ALfilter::eax_set_low_pass_params(
+ ALCcontext& context,
+ const EaxAlLowPassParam& param)
+{
+#define EAX_PREFIX "[EAX_SET_LOW_PASS_FILTER_PARAMS] "
+
+ auto& device = *context.mALDevice;
+ std::lock_guard<std::mutex> filter_lock{device.FilterLock};
+
+ try
+ {
+ setParamf(AL_LOWPASS_GAIN, param.gain);
+ setParamf(AL_LOWPASS_GAINHF, param.gain_hf);
+ }
+ catch (const filter_exception &e)
+ {
+ ERR(EAX_PREFIX "%s\n", e.what());
+ }
+
+#undef EAX_PREFIX
+}
+#endif // ALSOFT_EAX
diff --git a/al/filter.h b/al/filter.h
index 123e64b0..222a6917 100644
--- a/al/filter.h
+++ b/al/filter.h
@@ -1,12 +1,18 @@
#ifndef AL_FILTER_H
#define AL_FILTER_H
+
#include "AL/al.h"
#include "AL/alc.h"
#include "AL/alext.h"
#include "almalloc.h"
+#if ALSOFT_EAX
+#include <memory>
+
+#include "eax_utils.h"
+#endif // ALSOFT_EAX
#define LOWPASSFREQREF 5000.0f
#define HIGHPASSFREQREF 250.0f
@@ -47,6 +53,40 @@ struct ALfilter {
void getParamfv(ALenum param, float *values) const { vtab->getParamfv(this, param, values); }
DISABLE_ALLOC()
+
+#if ALSOFT_EAX
+public:
+ void eax_set_low_pass_params(
+ ALCcontext& context,
+ const EaxAlLowPassParam& param);
+#endif // ALSOFT_EAX
};
+#if ALSOFT_EAX
+class EaxAlFilterDeleter
+{
+public:
+ EaxAlFilterDeleter() noexcept = default;
+
+ EaxAlFilterDeleter(
+ ALCcontext& context);
+
+
+ void operator()(
+ ALfilter* filter);
+
+private:
+ ALCcontext* context_{};
+}; // EaxAlFilterDeleter
+
+using EaxAlFilterUPtr = std::unique_ptr<ALfilter, EaxAlFilterDeleter>;
+
+EaxAlFilterUPtr eax_create_al_low_pass_filter(
+ ALCcontext& context);
+
+void eax_delete_low_pass_filter(
+ ALCcontext& context,
+ ALfilter& filter);
+#endif // ALSOFT_EAX
+
#endif
diff --git a/al/listener.cpp b/al/listener.cpp
index 0997ff95..1909c644 100644
--- a/al/listener.cpp
+++ b/al/listener.cpp
@@ -450,3 +450,25 @@ void UpdateListenerProps(ALCcontext *context)
AtomicReplaceHead(context->mFreeListenerProps, props);
}
}
+
+#if ALSOFT_EAX
+// `alListenerf(AL_METERS_PER_UNIT, value)`
+void eax_set_al_listener_meters_per_unit(
+ ALCcontext& al_context,
+ ALfloat meters_per_unit)
+{
+ auto context = EaxAlContextWrapper{al_context};
+
+ auto& listener = context->mListener;
+
+ if (meters_per_unit < AL_MIN_METERS_PER_UNIT ||
+ meters_per_unit > AL_MAX_METERS_PER_UNIT)
+ {
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Listener meters per unit out of range");
+ }
+
+ listener.mMetersPerUnit = meters_per_unit;
+
+ DO_UPDATEPROPS();
+}
+#endif // ALSOFT_EAX
diff --git a/al/listener.h b/al/listener.h
index f3332763..05cd3abf 100644
--- a/al/listener.h
+++ b/al/listener.h
@@ -29,4 +29,11 @@ struct ALlistener {
void UpdateListenerProps(ALCcontext *context);
+#if ALSOFT_EAX
+// `alListenerf(AL_METERS_PER_UNIT, value)`
+void eax_set_al_listener_meters_per_unit(
+ ALCcontext& al_context,
+ ALfloat meters_per_unit);
+#endif // ALSOFT_EAX
+
#endif
diff --git a/al/source.cpp b/al/source.cpp
index f7b07338..3f7fa540 100644
--- a/al/source.cpp
+++ b/al/source.cpp
@@ -72,6 +72,10 @@
#include "ringbuffer.h"
#include "threads.h"
+#if ALSOFT_EAX
+#include "eax_exception.h"
+#include "eax_globals.h"
+#endif // ALSOFT_EAX
namespace {
@@ -2414,16 +2418,52 @@ START_API_FUNC
{
ALsource *source{AllocSource(context.get())};
sources[0] = source->id;
+
+#if ALSOFT_EAX
+ if (context->has_eax())
+ {
+ std::unique_lock<std::mutex> prop_lock{context->mPropLock};
+ context->eax_initialize_source(*source);
+ }
+#endif // ALSOFT_EAX
}
else
{
+#if ALSOFT_EAX
+ auto eax_sources = al::vector<ALsource*>{};
+
+ if (context->has_eax())
+ {
+ eax_sources.reserve(static_cast<ALuint>(n));
+ }
+#endif // ALSOFT_EAX
+
al::vector<ALuint> ids;
ids.reserve(static_cast<ALuint>(n));
do {
ALsource *source{AllocSource(context.get())};
ids.emplace_back(source->id);
+
+#if ALSOFT_EAX
+ if (context->has_eax())
+ {
+ eax_sources.emplace_back(source);
+ }
+#endif // ALSOFT_EAX
} while(--n);
std::copy(ids.cbegin(), ids.cend(), sources);
+
+#if ALSOFT_EAX
+ if (context->has_eax())
+ {
+ std::unique_lock<std::mutex> prop_lock{context->mPropLock};
+
+ for (auto& eax_source : eax_sources)
+ {
+ context->eax_initialize_source(*eax_source);
+ }
+ }
+#endif // ALSOFT_EAX
}
}
END_API_FUNC
@@ -3555,6 +3595,10 @@ ALsource::ALsource()
ALsource::~ALsource()
{
+#if ALSOFT_EAX
+ eax_uninitialize();
+#endif // ALSOFT_EAX
+
for(auto &item : mQueue)
{
if(ALbuffer *buffer{item.mBuffer})
@@ -3597,3 +3641,2447 @@ SourceSubList::~SourceSubList()
al_free(Sources);
Sources = nullptr;
}
+
+
+#if ALSOFT_EAX
+class EaxSourceException :
+ public EaxException
+{
+public:
+ explicit EaxSourceException(
+ const char* message)
+ :
+ EaxException{"EAX_SOURCE", message}
+ {
+ }
+}; // EaxSourceException
+
+
+class EaxSourceActiveFxSlotsException :
+ public EaxException
+{
+public:
+ explicit EaxSourceActiveFxSlotsException(
+ const char* message)
+ :
+ EaxException{"EAX_SOURCE_ACTIVE_FX_SLOTS", message}
+ {
+ }
+}; // EaxSourceActiveFxSlotsException
+
+
+class EaxSourceSendException :
+ public EaxException
+{
+public:
+ explicit EaxSourceSendException(
+ const char* message)
+ :
+ EaxException{"EAX_SOURCE_SEND", message}
+ {
+ }
+}; // EaxSourceSendException
+
+
+void ALsource::eax_initialize(
+ const EaxSourceInitParam& param) noexcept
+{
+ eax_validate_init_param(param);
+ eax_copy_init_param(param);
+ eax_set_defaults();
+ eax_initialize_fx_slots();
+
+ eax_d_ = eax_;
+}
+
+void ALsource::eax_uninitialize()
+{
+ if (!eax_al_context_)
+ {
+ return;
+ }
+
+ if (eax_al_context_->has_eax())
+ {
+ for (auto i = 0; i < EAX_MAX_FXSLOTS; ++i)
+ {
+ eax_al_source_3i(
+ AL_AUXILIARY_SEND_FILTER,
+ AL_EFFECTSLOT_NULL,
+ static_cast<ALint>(i),
+ AL_FILTER_NULL
+ );
+ }
+ }
+
+ eax_al_context_ = nullptr;
+}
+
+void ALsource::eax_dispatch(
+ const EaxEaxCall& eax_call)
+{
+ if (eax_call.is_get())
+ {
+ eax_get(eax_call);
+ }
+ else
+ {
+ eax_set(eax_call);
+ }
+}
+
+void ALsource::eax_update_filters()
+{
+ eax_update_filters_internal();
+}
+
+void ALsource::eax_update(
+ EaxContextSharedDirtyFlags dirty_flags)
+{
+ if (dirty_flags.primary_fx_slot_id)
+ {
+ if (eax_uses_primary_id_)
+ {
+ eax_update_primary_fx_slot_id();
+ }
+ }
+
+ if (dirty_flags.air_absorption_hf)
+ {
+ eax_set_air_absorption_factor();
+ }
+}
+
+ALsource* ALsource::eax_lookup_source(
+ ALCcontext& al_context,
+ ALuint source_id) noexcept
+{
+ return LookupSource(&al_context, source_id);
+}
+
+[[noreturn]]
+void ALsource::eax_fail(
+ const char* message)
+{
+ throw EaxSourceException{message};
+}
+
+void ALsource::eax_validate_init_param(
+ const EaxSourceInitParam& param)
+{
+ if (!param.al_context)
+ {
+ eax_fail("Null context.");
+ }
+
+ if (!param.al_filter)
+ {
+ eax_fail("Null filter.");
+ }
+}
+
+void ALsource::eax_copy_init_param(
+ const EaxSourceInitParam& param)
+{
+ eax_al_context_ = param.al_context;
+ eax_al_filter_ = param.al_filter;
+}
+
+void ALsource::eax_set_source_defaults()
+{
+ eax_.source.lDirect = EAXSOURCE_DEFAULTDIRECT;
+ eax_.source.lDirectHF = EAXSOURCE_DEFAULTDIRECTHF;
+ eax_.source.lRoom = EAXSOURCE_DEFAULTROOM;
+ eax_.source.lRoomHF = EAXSOURCE_DEFAULTROOMHF;
+ eax_.source.lObstruction = EAXSOURCE_DEFAULTOBSTRUCTION;
+ eax_.source.flObstructionLFRatio = EAXSOURCE_DEFAULTOBSTRUCTIONLFRATIO;
+ eax_.source.lOcclusion = EAXSOURCE_DEFAULTOCCLUSION;
+ eax_.source.flOcclusionLFRatio = EAXSOURCE_DEFAULTOCCLUSIONLFRATIO;
+ eax_.source.flOcclusionRoomRatio = EAXSOURCE_DEFAULTOCCLUSIONROOMRATIO;
+ eax_.source.flOcclusionDirectRatio = EAXSOURCE_DEFAULTOCCLUSIONDIRECTRATIO;
+ eax_.source.lExclusion = EAXSOURCE_DEFAULTEXCLUSION;
+ eax_.source.flExclusionLFRatio = EAXSOURCE_DEFAULTEXCLUSIONLFRATIO;
+ eax_.source.lOutsideVolumeHF = EAXSOURCE_DEFAULTOUTSIDEVOLUMEHF;
+ eax_.source.flDopplerFactor = EAXSOURCE_DEFAULTDOPPLERFACTOR;
+ eax_.source.flRolloffFactor = EAXSOURCE_DEFAULTROLLOFFFACTOR;
+ eax_.source.flRoomRolloffFactor = EAXSOURCE_DEFAULTROOMROLLOFFFACTOR;
+ eax_.source.flAirAbsorptionFactor = EAXSOURCE_DEFAULTAIRABSORPTIONFACTOR;
+ eax_.source.ulFlags = EAXSOURCE_DEFAULTFLAGS;
+}
+
+void ALsource::eax_set_active_fx_slots_defaults()
+{
+ eax_.active_fx_slots = EAX50SOURCE_3DDEFAULTACTIVEFXSLOTID;
+}
+
+void ALsource::eax_set_send_defaults(
+ EAXSOURCEALLSENDPROPERTIES& eax_send)
+{
+ eax_send.guidReceivingFXSlotID = EAX_NULL_GUID;
+ eax_send.lSend = EAXSOURCE_DEFAULTSEND;
+ eax_send.lSendHF = EAXSOURCE_DEFAULTSENDHF;
+ eax_send.lOcclusion = EAXSOURCE_DEFAULTOCCLUSION;
+ eax_send.flOcclusionLFRatio = EAXSOURCE_DEFAULTOCCLUSIONLFRATIO;
+ eax_send.flOcclusionRoomRatio = EAXSOURCE_DEFAULTOCCLUSIONROOMRATIO;
+ eax_send.flOcclusionDirectRatio = EAXSOURCE_DEFAULTOCCLUSIONDIRECTRATIO;
+ eax_send.lExclusion = EAXSOURCE_DEFAULTEXCLUSION;
+ eax_send.flExclusionLFRatio = EAXSOURCE_DEFAULTEXCLUSIONLFRATIO;
+}
+
+void ALsource::eax_set_sends_defaults()
+{
+ for (auto& eax_send : eax_.sends)
+ {
+ eax_set_send_defaults(eax_send);
+ }
+}
+
+void ALsource::eax_set_speaker_levels_defaults()
+{
+ std::fill(eax_.speaker_levels.begin(), eax_.speaker_levels.end(), EAXSOURCE_DEFAULTSPEAKERLEVEL);
+}
+
+void ALsource::eax_set_defaults()
+{
+ eax_set_source_defaults();
+ eax_set_active_fx_slots_defaults();
+ eax_set_sends_defaults();
+ eax_set_speaker_levels_defaults();
+}
+
+float ALsource::eax_calculate_dst_occlusion_mb(
+ long src_occlusion_mb,
+ float path_ratio,
+ float lf_ratio) noexcept
+{
+ const auto ratio_1 = path_ratio + lf_ratio - 1.0F;
+ const auto ratio_2 = path_ratio * lf_ratio;
+ const auto ratio = (ratio_2 > ratio_1) ? ratio_2 : ratio_1;
+ const auto dst_occlustion_mb = static_cast<float>(src_occlusion_mb) * ratio;
+
+ return dst_occlustion_mb;
+}
+
+EaxAlLowPassParam ALsource::eax_create_direct_filter_param() const noexcept
+{
+ auto gain_mb =
+ static_cast<float>(eax_.source.lDirect) +
+
+ (static_cast<float>(eax_.source.lObstruction) * eax_.source.flObstructionLFRatio) +
+
+ eax_calculate_dst_occlusion_mb(
+ eax_.source.lOcclusion,
+ eax_.source.flOcclusionDirectRatio,
+ eax_.source.flOcclusionLFRatio);
+
+ auto gain_hf_mb =
+ static_cast<float>(eax_.source.lDirectHF) +
+
+ static_cast<float>(eax_.source.lObstruction) +
+
+ (static_cast<float>(eax_.source.lOcclusion) * eax_.source.flOcclusionDirectRatio);
+
+ for (auto i = std::size_t{}; i < EAX_MAX_FXSLOTS; ++i)
+ {
+ if (eax_active_fx_slots_[i])
+ {
+ const auto& send = eax_.sends[i];
+
+ gain_mb += eax_calculate_dst_occlusion_mb(
+ send.lOcclusion,
+ send.flOcclusionDirectRatio,
+ send.flOcclusionLFRatio);
+
+ gain_hf_mb += static_cast<float>(send.lOcclusion) * send.flOcclusionDirectRatio;
+ }
+ }
+
+ const auto max_gain = eax_al_context_->eax_get_max_filter_gain();
+
+ const auto al_low_pass_param = EaxAlLowPassParam
+ {
+ clamp(level_mb_to_gain(gain_mb), 0.0F, max_gain),
+ clamp(level_mb_to_gain(gain_hf_mb), 0.0F, max_gain)
+ };
+
+ return al_low_pass_param;
+}
+
+EaxAlLowPassParam ALsource::eax_create_room_filter_param(
+ const ALeffectslot& fx_slot,
+ const EAXSOURCEALLSENDPROPERTIES& send) const noexcept
+{
+ const auto& fx_slot_eax = fx_slot.eax_get_eax_fx_slot();
+
+ const auto gain_mb =
+ static_cast<float>(
+ eax_.source.lRoom +
+ send.lSend) +
+
+ eax_calculate_dst_occlusion_mb(
+ eax_.source.lOcclusion,
+ eax_.source.flOcclusionRoomRatio,
+ eax_.source.flOcclusionLFRatio
+ ) +
+
+ eax_calculate_dst_occlusion_mb(
+ send.lOcclusion,
+ send.flOcclusionRoomRatio,
+ send.flOcclusionLFRatio
+ ) +
+
+ (static_cast<float>(eax_.source.lExclusion) * eax_.source.flExclusionLFRatio) +
+ (static_cast<float>(send.lExclusion) * send.flExclusionLFRatio) +
+
+ 0.0F;
+
+ const auto gain_hf_mb =
+ static_cast<float>(
+ eax_.source.lRoomHF +
+ send.lSendHF) +
+
+ (static_cast<float>(fx_slot_eax.lOcclusion + eax_.source.lOcclusion) * eax_.source.flOcclusionRoomRatio) +
+ (static_cast<float>(send.lOcclusion) * send.flOcclusionRoomRatio) +
+
+ static_cast<float>(
+ eax_.source.lExclusion +
+ send.lExclusion) +
+
+ 0.0F;
+
+ const auto max_gain = eax_al_context_->eax_get_max_filter_gain();
+
+ const auto al_low_pass_param = EaxAlLowPassParam
+ {
+ clamp(level_mb_to_gain(gain_mb), 0.0F, max_gain),
+ clamp(level_mb_to_gain(gain_hf_mb), 0.0F, max_gain)
+ };
+
+ return al_low_pass_param;
+}
+
+void ALsource::eax_set_al_filter_parameters(
+ const EaxAlLowPassParam& al_low_pass_param) const noexcept
+{
+ eax_al_filter_->eax_set_low_pass_params(*eax_al_context_, al_low_pass_param);
+}
+
+void ALsource::eax_set_fx_slots()
+{
+ eax_uses_primary_id_ = false;
+ eax_has_active_fx_slots_ = false;
+
+ for (auto i = 0; i < EAX_MAX_FXSLOTS; ++i)
+ {
+ const auto& eax_active_fx_slot_id = eax_.active_fx_slots.guidActiveFXSlots[i];
+
+ auto fx_slot_index = EaxFxSlotIndex{};
+
+ if (eax_active_fx_slot_id == EAX_PrimaryFXSlotID)
+ {
+ eax_uses_primary_id_ = true;
+ fx_slot_index = eax_al_context_->eax_get_primary_fx_slot_index();
+ }
+ else
+ {
+ fx_slot_index = eax_active_fx_slot_id;
+ }
+
+ if (fx_slot_index.has_value())
+ {
+ eax_has_active_fx_slots_ = true;
+ eax_active_fx_slots_[fx_slot_index] = true;
+ }
+ }
+
+ for (auto i = 0; i < EAX_MAX_FXSLOTS; ++i)
+ {
+ if (!eax_active_fx_slots_[static_cast<std::size_t>(i)])
+ {
+ eax_al_source_3i(
+ AL_AUXILIARY_SEND_FILTER,
+ AL_EFFECTSLOT_NULL,
+ i,
+ AL_FILTER_NULL);
+ }
+ }
+}
+
+void ALsource::eax_initialize_fx_slots()
+{
+ eax_set_fx_slots();
+ eax_update_filters_internal();
+}
+
+void ALsource::eax_update_direct_filter_internal()
+{
+ const auto& direct_param = eax_create_direct_filter_param();
+ eax_set_al_filter_parameters(direct_param);
+
+ eax_al_source_i(
+ AL_DIRECT_FILTER,
+ static_cast<ALint>(eax_al_filter_->id));
+}
+
+void ALsource::eax_update_room_filters_internal()
+{
+ if (!eax_has_active_fx_slots_)
+ {
+ return;
+ }
+
+ for (auto i = 0; i < EAX_MAX_FXSLOTS; ++i)
+ {
+ if (eax_active_fx_slots_[static_cast<std::size_t>(i)])
+ {
+ const auto& fx_slot = eax_al_context_->eax_get_fx_slot(static_cast<std::size_t>(i));
+ const auto& send = eax_.sends[static_cast<std::size_t>(i)];
+ const auto& room_param = eax_create_room_filter_param(fx_slot, send);
+
+ eax_set_al_filter_parameters(room_param);
+
+ eax_al_source_3i(
+ AL_AUXILIARY_SEND_FILTER,
+ static_cast<ALint>(fx_slot.id),
+ i,
+ static_cast<ALint>(eax_al_filter_->id)
+ );
+ }
+ }
+}
+
+void ALsource::eax_update_filters_internal()
+{
+ eax_update_direct_filter_internal();
+ eax_update_room_filters_internal();
+}
+
+void ALsource::eax_update_primary_fx_slot_id()
+{
+ const auto& previous_primary_fx_slot_index = eax_al_context_->eax_get_previous_primary_fx_slot_index();
+ const auto& primary_fx_slot_index = eax_al_context_->eax_get_primary_fx_slot_index();
+
+ if (previous_primary_fx_slot_index == primary_fx_slot_index)
+ {
+ return;
+ }
+
+ if (previous_primary_fx_slot_index.has_value())
+ {
+ const auto fx_slot_index = previous_primary_fx_slot_index.get();
+ eax_active_fx_slots_[fx_slot_index] = false;
+
+ eax_al_source_3i(
+ AL_AUXILIARY_SEND_FILTER,
+ AL_EFFECTSLOT_NULL,
+ static_cast<ALint>(fx_slot_index),
+ static_cast<ALint>(AL_FILTER_NULL));
+ }
+
+ if (primary_fx_slot_index.has_value())
+ {
+ const auto fx_slot_index = primary_fx_slot_index.get();
+ eax_active_fx_slots_[fx_slot_index] = true;
+
+ const auto& fx_slot = eax_al_context_->eax_get_fx_slot(fx_slot_index);
+ const auto& send = eax_.sends[fx_slot_index];
+ const auto& room_param = eax_create_room_filter_param(fx_slot, send);
+
+ eax_set_al_filter_parameters(room_param);
+
+ eax_al_source_3i(
+ AL_AUXILIARY_SEND_FILTER,
+ static_cast<ALint>(fx_slot.id),
+ static_cast<ALint>(fx_slot_index),
+ static_cast<ALint>(eax_al_filter_->id));
+ }
+
+ eax_has_active_fx_slots_ = std::any_of(
+ eax_active_fx_slots_.cbegin(),
+ eax_active_fx_slots_.cend(),
+ [](const auto& item)
+ {
+ return item;
+ }
+ );
+}
+
+void ALsource::eax_defer_active_fx_slots(
+ const EaxEaxCall& eax_call)
+{
+ const auto active_fx_slots_span =
+ eax_call.get_values<EaxSourceActiveFxSlotsException, const GUID>();
+
+ const auto fx_slot_count = active_fx_slots_span.size();
+
+ if (fx_slot_count <= 0 || fx_slot_count > EAX_MAX_FXSLOTS)
+ {
+ throw EaxSourceActiveFxSlotsException{"Count out of range."};
+ }
+
+ for (auto i = std::size_t{}; i < fx_slot_count; ++i)
+ {
+ const auto& fx_slot_guid = active_fx_slots_span[i];
+
+ if (fx_slot_guid != EAX_NULL_GUID &&
+ fx_slot_guid != EAX_PrimaryFXSlotID &&
+ fx_slot_guid != EAXPROPERTYID_EAX40_FXSlot0 &&
+ fx_slot_guid != EAXPROPERTYID_EAX50_FXSlot0 &&
+ fx_slot_guid != EAXPROPERTYID_EAX40_FXSlot1 &&
+ fx_slot_guid != EAXPROPERTYID_EAX50_FXSlot1 &&
+ fx_slot_guid != EAXPROPERTYID_EAX40_FXSlot2 &&
+ fx_slot_guid != EAXPROPERTYID_EAX50_FXSlot2 &&
+ fx_slot_guid != EAXPROPERTYID_EAX40_FXSlot3 &&
+ fx_slot_guid != EAXPROPERTYID_EAX50_FXSlot3)
+ {
+ throw EaxSourceActiveFxSlotsException{"Unsupported GUID."};
+ }
+ }
+
+ for (auto i = std::size_t{}; i < fx_slot_count; ++i)
+ {
+ eax_d_.active_fx_slots.guidActiveFXSlots[i] = active_fx_slots_span[i];
+ }
+
+ for (auto i = fx_slot_count; i < EAX_MAX_FXSLOTS; ++i)
+ {
+ eax_d_.active_fx_slots.guidActiveFXSlots[i] = EAX_NULL_GUID;
+ }
+
+ eax_are_active_fx_slots_dirty_ = (eax_d_.active_fx_slots != eax_.active_fx_slots);
+}
+
+
+const char* ALsource::eax_get_exclusion_name() noexcept
+{
+ return "Exclusion";
+}
+
+const char* ALsource::eax_get_exclusion_lf_ratio_name() noexcept
+{
+ return "Exclusion LF Ratio";
+}
+
+const char* ALsource::eax_get_occlusion_name() noexcept
+{
+ return "Occlusion";
+}
+
+const char* ALsource::eax_get_occlusion_lf_ratio_name() noexcept
+{
+ return "Occlusion LF Ratio";
+}
+
+const char* ALsource::eax_get_occlusion_direct_ratio_name() noexcept
+{
+ return "Occlusion Direct Ratio";
+}
+
+const char* ALsource::eax_get_occlusion_room_ratio_name() noexcept
+{
+ return "Occlusion Room Ratio";
+}
+
+
+void ALsource::eax_validate_send_receiving_fx_slot_guid(
+ const GUID& guidReceivingFXSlotID)
+{
+ if (guidReceivingFXSlotID != EAXPROPERTYID_EAX40_FXSlot0 &&
+ guidReceivingFXSlotID != EAXPROPERTYID_EAX50_FXSlot0 &&
+ guidReceivingFXSlotID != EAXPROPERTYID_EAX40_FXSlot1 &&
+ guidReceivingFXSlotID != EAXPROPERTYID_EAX50_FXSlot1 &&
+ guidReceivingFXSlotID != EAXPROPERTYID_EAX40_FXSlot2 &&
+ guidReceivingFXSlotID != EAXPROPERTYID_EAX50_FXSlot2 &&
+ guidReceivingFXSlotID != EAXPROPERTYID_EAX40_FXSlot3 &&
+ guidReceivingFXSlotID != EAXPROPERTYID_EAX50_FXSlot3)
+ {
+ throw EaxSourceSendException{"Unsupported receiving FX slot GUID."};
+ }
+}
+
+void ALsource::eax_validate_send_send(
+ long lSend)
+{
+ eax_validate_range<EaxSourceSendException>(
+ "Send",
+ lSend,
+ EAXSOURCE_MINSEND,
+ EAXSOURCE_MAXSEND);
+}
+
+void ALsource::eax_validate_send_send_hf(
+ long lSendHF)
+{
+ eax_validate_range<EaxSourceSendException>(
+ "Send HF",
+ lSendHF,
+ EAXSOURCE_MINSENDHF,
+ EAXSOURCE_MAXSENDHF);
+}
+
+void ALsource::eax_validate_send_occlusion(
+ long lOcclusion)
+{
+ eax_validate_range<EaxSourceSendException>(
+ eax_get_occlusion_name(),
+ lOcclusion,
+ EAXSOURCE_MINOCCLUSION,
+ EAXSOURCE_MAXOCCLUSION);
+}
+
+void ALsource::eax_validate_send_occlusion_lf_ratio(
+ float flOcclusionLFRatio)
+{
+ eax_validate_range<EaxSourceSendException>(
+ eax_get_occlusion_lf_ratio_name(),
+ flOcclusionLFRatio,
+ EAXSOURCE_MINOCCLUSIONLFRATIO,
+ EAXSOURCE_MAXOCCLUSIONLFRATIO);
+}
+
+void ALsource::eax_validate_send_occlusion_room_ratio(
+ float flOcclusionRoomRatio)
+{
+ eax_validate_range<EaxSourceSendException>(
+ eax_get_occlusion_room_ratio_name(),
+ flOcclusionRoomRatio,
+ EAXSOURCE_MINOCCLUSIONROOMRATIO,
+ EAXSOURCE_MAXOCCLUSIONROOMRATIO);
+}
+
+void ALsource::eax_validate_send_occlusion_direct_ratio(
+ float flOcclusionDirectRatio)
+{
+ eax_validate_range<EaxSourceSendException>(
+ eax_get_occlusion_direct_ratio_name(),
+ flOcclusionDirectRatio,
+ EAXSOURCE_MINOCCLUSIONDIRECTRATIO,
+ EAXSOURCE_MAXOCCLUSIONDIRECTRATIO);
+}
+
+void ALsource::eax_validate_send_exclusion(
+ long lExclusion)
+{
+ eax_validate_range<EaxSourceSendException>(
+ eax_get_exclusion_name(),
+ lExclusion,
+ EAXSOURCE_MINEXCLUSION,
+ EAXSOURCE_MAXEXCLUSION);
+}
+
+void ALsource::eax_validate_send_exclusion_lf_ratio(
+ float flExclusionLFRatio)
+{
+ eax_validate_range<EaxSourceSendException>(
+ eax_get_exclusion_lf_ratio_name(),
+ flExclusionLFRatio,
+ EAXSOURCE_MINEXCLUSIONLFRATIO,
+ EAXSOURCE_MAXEXCLUSIONLFRATIO);
+}
+
+void ALsource::eax_validate_send(
+ const EAXSOURCESENDPROPERTIES& all)
+{
+ eax_validate_send_receiving_fx_slot_guid(all.guidReceivingFXSlotID);
+ eax_validate_send_send(all.lSend);
+ eax_validate_send_send_hf(all.lSendHF);
+}
+
+void ALsource::eax_validate_send_exclusion_all(
+ const EAXSOURCEEXCLUSIONSENDPROPERTIES& all)
+{
+ eax_validate_send_receiving_fx_slot_guid(all.guidReceivingFXSlotID);
+ eax_validate_send_exclusion(all.lExclusion);
+ eax_validate_send_exclusion_lf_ratio(all.flExclusionLFRatio);
+}
+
+void ALsource::eax_validate_send_occlusion_all(
+ const EAXSOURCEOCCLUSIONSENDPROPERTIES& all)
+{
+ eax_validate_send_receiving_fx_slot_guid(all.guidReceivingFXSlotID);
+ eax_validate_send_occlusion(all.lOcclusion);
+ eax_validate_send_occlusion_lf_ratio(all.flOcclusionLFRatio);
+ eax_validate_send_occlusion_room_ratio(all.flOcclusionRoomRatio);
+ eax_validate_send_occlusion_direct_ratio(all.flOcclusionDirectRatio);
+}
+
+void ALsource::eax_validate_send_all(
+ const EAXSOURCEALLSENDPROPERTIES& all)
+{
+ eax_validate_send_receiving_fx_slot_guid(all.guidReceivingFXSlotID);
+ eax_validate_send_send(all.lSend);
+ eax_validate_send_send_hf(all.lSendHF);
+ eax_validate_send_occlusion(all.lOcclusion);
+ eax_validate_send_occlusion_lf_ratio(all.flOcclusionLFRatio);
+ eax_validate_send_occlusion_room_ratio(all.flOcclusionRoomRatio);
+ eax_validate_send_occlusion_direct_ratio(all.flOcclusionDirectRatio);
+ eax_validate_send_exclusion(all.lExclusion);
+ eax_validate_send_exclusion_lf_ratio(all.flExclusionLFRatio);
+}
+
+EaxFxSlotIndexValue ALsource::eax_get_send_index(
+ const GUID& send_guid)
+{
+ if (false)
+ {
+ }
+ else if (send_guid == EAXPROPERTYID_EAX40_FXSlot0 || send_guid == EAXPROPERTYID_EAX50_FXSlot0)
+ {
+ return 0;
+ }
+ else if (send_guid == EAXPROPERTYID_EAX40_FXSlot1 || send_guid == EAXPROPERTYID_EAX50_FXSlot1)
+ {
+ return 1;
+ }
+ else if (send_guid == EAXPROPERTYID_EAX40_FXSlot2 || send_guid == EAXPROPERTYID_EAX50_FXSlot2)
+ {
+ return 2;
+ }
+ else if (send_guid == EAXPROPERTYID_EAX40_FXSlot3 || send_guid == EAXPROPERTYID_EAX50_FXSlot3)
+ {
+ return 3;
+ }
+ else
+ {
+ throw EaxSourceSendException{"Unsupported receiving FX slot GUID."};
+ }
+}
+
+void ALsource::eax_defer_send_send(
+ long lSend,
+ EaxFxSlotIndexValue index)
+{
+ eax_d_.sends[index].lSend = lSend;
+
+ eax_sends_dirty_flags_.sends[index].lSend =
+ (eax_.sends[index].lSend != eax_d_.sends[index].lSend);
+}
+
+void ALsource::eax_defer_send_send_hf(
+ long lSendHF,
+ EaxFxSlotIndexValue index)
+{
+ eax_d_.sends[index].lSendHF = lSendHF;
+
+ eax_sends_dirty_flags_.sends[index].lSendHF =
+ (eax_.sends[index].lSendHF != eax_d_.sends[index].lSendHF);
+}
+
+void ALsource::eax_defer_send_occlusion(
+ long lOcclusion,
+ EaxFxSlotIndexValue index)
+{
+ eax_d_.sends[index].lOcclusion = lOcclusion;
+
+ eax_sends_dirty_flags_.sends[index].lOcclusion =
+ (eax_.sends[index].lOcclusion != eax_d_.sends[index].lOcclusion);
+}
+
+void ALsource::eax_defer_send_occlusion_lf_ratio(
+ float flOcclusionLFRatio,
+ EaxFxSlotIndexValue index)
+{
+ eax_d_.sends[index].flOcclusionLFRatio = flOcclusionLFRatio;
+
+ eax_sends_dirty_flags_.sends[index].flOcclusionLFRatio =
+ (eax_.sends[index].flOcclusionLFRatio != eax_d_.sends[index].flOcclusionLFRatio);
+}
+
+void ALsource::eax_defer_send_occlusion_room_ratio(
+ float flOcclusionRoomRatio,
+ EaxFxSlotIndexValue index)
+{
+ eax_d_.sends[index].flOcclusionRoomRatio = flOcclusionRoomRatio;
+
+ eax_sends_dirty_flags_.sends[index].flOcclusionRoomRatio =
+ (eax_.sends[index].flOcclusionRoomRatio != eax_d_.sends[index].flOcclusionRoomRatio);
+}
+
+void ALsource::eax_defer_send_occlusion_direct_ratio(
+ float flOcclusionDirectRatio,
+ EaxFxSlotIndexValue index)
+{
+ eax_d_.sends[index].flOcclusionDirectRatio = flOcclusionDirectRatio;
+
+ eax_sends_dirty_flags_.sends[index].flOcclusionDirectRatio =
+ (eax_.sends[index].flOcclusionDirectRatio != eax_d_.sends[index].flOcclusionDirectRatio);
+}
+
+void ALsource::eax_defer_send_exclusion(
+ long lExclusion,
+ EaxFxSlotIndexValue index)
+{
+ eax_d_.sends[index].lExclusion = lExclusion;
+
+ eax_sends_dirty_flags_.sends[index].lExclusion =
+ (eax_.sends[index].lExclusion != eax_d_.sends[index].lExclusion);
+}
+
+void ALsource::eax_defer_send_exclusion_lf_ratio(
+ float flExclusionLFRatio,
+ EaxFxSlotIndexValue index)
+{
+ eax_d_.sends[index].flExclusionLFRatio = flExclusionLFRatio;
+
+ eax_sends_dirty_flags_.sends[index].flExclusionLFRatio =
+ (eax_.sends[index].flExclusionLFRatio != eax_d_.sends[index].flExclusionLFRatio);
+}
+
+void ALsource::eax_defer_send(
+ const EAXSOURCESENDPROPERTIES& all,
+ EaxFxSlotIndexValue index)
+{
+ eax_defer_send_send(all.lSend, index);
+ eax_defer_send_send_hf(all.lSendHF, index);
+}
+
+void ALsource::eax_defer_send_exclusion_all(
+ const EAXSOURCEEXCLUSIONSENDPROPERTIES& all,
+ EaxFxSlotIndexValue index)
+{
+ eax_defer_send_exclusion(all.lExclusion, index);
+ eax_defer_send_exclusion_lf_ratio(all.flExclusionLFRatio, index);
+}
+
+void ALsource::eax_defer_send_occlusion_all(
+ const EAXSOURCEOCCLUSIONSENDPROPERTIES& all,
+ EaxFxSlotIndexValue index)
+{
+ eax_defer_send_occlusion(all.lOcclusion, index);
+ eax_defer_send_occlusion_lf_ratio(all.flOcclusionLFRatio, index);
+ eax_defer_send_occlusion_room_ratio(all.flOcclusionRoomRatio, index);
+ eax_defer_send_occlusion_direct_ratio(all.flOcclusionDirectRatio, index);
+}
+
+void ALsource::eax_defer_send_all(
+ const EAXSOURCEALLSENDPROPERTIES& all,
+ EaxFxSlotIndexValue index)
+{
+ eax_defer_send_send(all.lSend, index);
+ eax_defer_send_send_hf(all.lSendHF, index);
+ eax_defer_send_occlusion(all.lOcclusion, index);
+ eax_defer_send_occlusion_lf_ratio(all.flOcclusionLFRatio, index);
+ eax_defer_send_occlusion_room_ratio(all.flOcclusionRoomRatio, index);
+ eax_defer_send_occlusion_direct_ratio(all.flOcclusionDirectRatio, index);
+ eax_defer_send_exclusion(all.lExclusion, index);
+ eax_defer_send_exclusion_lf_ratio(all.flExclusionLFRatio, index);
+}
+
+void ALsource::eax_defer_send(
+ const EaxEaxCall& eax_call)
+{
+ const auto eax_all_span =
+ eax_call.get_values<EaxSourceException, const EAXSOURCESENDPROPERTIES>();
+
+ const auto count = eax_all_span.size();
+
+ if (count <= 0 || count > EAX_MAX_FXSLOTS)
+ {
+ throw EaxSourceSendException{"Send count out of range."};
+ }
+
+ for (auto i = std::size_t{}; i < count; ++i)
+ {
+ const auto& all = eax_all_span[i];
+ eax_validate_send(all);
+ }
+
+ for (auto i = std::size_t{}; i < count; ++i)
+ {
+ const auto& all = eax_all_span[i];
+ const auto send_index = eax_get_send_index(all.guidReceivingFXSlotID);
+ eax_defer_send(all, send_index);
+ }
+}
+
+void ALsource::eax_defer_send_exclusion_all(
+ const EaxEaxCall& eax_call)
+{
+ const auto eax_all_span =
+ eax_call.get_values<EaxSourceException, const EAXSOURCEEXCLUSIONSENDPROPERTIES>();
+
+ const auto count = eax_all_span.size();
+
+ if (count <= 0 || count > EAX_MAX_FXSLOTS)
+ {
+ throw EaxSourceSendException{"Send exclusion all count out of range."};
+ }
+
+ for (auto i = std::size_t{}; i < count; ++i)
+ {
+ const auto& all = eax_all_span[i];
+ eax_validate_send_exclusion_all(all);
+ }
+
+ for (auto i = std::size_t{}; i < count; ++i)
+ {
+ const auto& all = eax_all_span[i];
+ const auto send_index = eax_get_send_index(all.guidReceivingFXSlotID);
+ eax_defer_send_exclusion_all(all, send_index);
+ }
+}
+
+void ALsource::eax_defer_send_occlusion_all(
+ const EaxEaxCall& eax_call)
+{
+ const auto eax_all_span =
+ eax_call.get_values<EaxSourceException, const EAXSOURCEOCCLUSIONSENDPROPERTIES>();
+
+ const auto count = eax_all_span.size();
+
+ if (count <= 0 || count > EAX_MAX_FXSLOTS)
+ {
+ throw EaxSourceSendException{"Send occlusion all count out of range."};
+ }
+
+ for (auto i = std::size_t{}; i < count; ++i)
+ {
+ const auto& all = eax_all_span[i];
+ eax_validate_send_occlusion_all(all);
+ }
+
+ for (auto i = std::size_t{}; i < count; ++i)
+ {
+ const auto& all = eax_all_span[i];
+ const auto send_index = eax_get_send_index(all.guidReceivingFXSlotID);
+ eax_defer_send_occlusion_all(all, send_index);
+ }
+}
+
+void ALsource::eax_defer_send_all(
+ const EaxEaxCall& eax_call)
+{
+ const auto eax_all_span =
+ eax_call.get_values<EaxSourceException, const EAXSOURCEALLSENDPROPERTIES>();
+
+ const auto count = eax_all_span.size();
+
+ if (count <= 0 || count > EAX_MAX_FXSLOTS)
+ {
+ throw EaxSourceSendException{"Send all count out of range."};
+ }
+
+ for (auto i = std::size_t{}; i < count; ++i)
+ {
+ const auto& all = eax_all_span[i];
+ eax_validate_send_all(all);
+ }
+
+ for (auto i = std::size_t{}; i < count; ++i)
+ {
+ const auto& all = eax_all_span[i];
+ const auto send_index = eax_get_send_index(all.guidReceivingFXSlotID);
+ eax_defer_send_all(all, send_index);
+ }
+}
+
+
+void ALsource::eax_validate_source_direct(
+ long direct)
+{
+ eax_validate_range<EaxSourceException>(
+ "Direct",
+ direct,
+ EAXSOURCE_MINDIRECT,
+ EAXSOURCE_MAXDIRECT);
+}
+
+void ALsource::eax_validate_source_direct_hf(
+ long direct_hf)
+{
+ eax_validate_range<EaxSourceException>(
+ "Direct HF",
+ direct_hf,
+ EAXSOURCE_MINDIRECTHF,
+ EAXSOURCE_MAXDIRECTHF);
+}
+
+void ALsource::eax_validate_source_room(
+ long room)
+{
+ eax_validate_range<EaxSourceException>(
+ "Room",
+ room,
+ EAXSOURCE_MINROOM,
+ EAXSOURCE_MAXROOM);
+}
+
+void ALsource::eax_validate_source_room_hf(
+ long room_hf)
+{
+ eax_validate_range<EaxSourceException>(
+ "Room HF",
+ room_hf,
+ EAXSOURCE_MINROOMHF,
+ EAXSOURCE_MAXROOMHF);
+}
+
+void ALsource::eax_validate_source_obstruction(
+ long obstruction)
+{
+ eax_validate_range<EaxSourceException>(
+ "Obstruction",
+ obstruction,
+ EAXSOURCE_MINOBSTRUCTION,
+ EAXSOURCE_MAXOBSTRUCTION);
+}
+
+void ALsource::eax_validate_source_obstruction_lf_ratio(
+ float obstruction_lf_ratio)
+{
+ eax_validate_range<EaxSourceException>(
+ "Obstruction LF Ratio",
+ obstruction_lf_ratio,
+ EAXSOURCE_MINOBSTRUCTIONLFRATIO,
+ EAXSOURCE_MAXOBSTRUCTIONLFRATIO);
+}
+
+void ALsource::eax_validate_source_occlusion(
+ long occlusion)
+{
+ eax_validate_range<EaxSourceException>(
+ eax_get_occlusion_name(),
+ occlusion,
+ EAXSOURCE_MINOCCLUSION,
+ EAXSOURCE_MAXOCCLUSION);
+}
+
+void ALsource::eax_validate_source_occlusion_lf_ratio(
+ float occlusion_lf_ratio)
+{
+ eax_validate_range<EaxSourceException>(
+ eax_get_occlusion_lf_ratio_name(),
+ occlusion_lf_ratio,
+ EAXSOURCE_MINOCCLUSIONLFRATIO,
+ EAXSOURCE_MAXOCCLUSIONLFRATIO);
+}
+
+void ALsource::eax_validate_source_occlusion_room_ratio(
+ float occlusion_room_ratio)
+{
+ eax_validate_range<EaxSourceException>(
+ eax_get_occlusion_room_ratio_name(),
+ occlusion_room_ratio,
+ EAXSOURCE_MINOCCLUSIONROOMRATIO,
+ EAXSOURCE_MAXOCCLUSIONROOMRATIO);
+}
+
+void ALsource::eax_validate_source_occlusion_direct_ratio(
+ float occlusion_direct_ratio)
+{
+ eax_validate_range<EaxSourceException>(
+ eax_get_occlusion_direct_ratio_name(),
+ occlusion_direct_ratio,
+ EAXSOURCE_MINOCCLUSIONDIRECTRATIO,
+ EAXSOURCE_MAXOCCLUSIONDIRECTRATIO);
+}
+
+void ALsource::eax_validate_source_exclusion(
+ long exclusion)
+{
+ eax_validate_range<EaxSourceException>(
+ eax_get_exclusion_name(),
+ exclusion,
+ EAXSOURCE_MINEXCLUSION,
+ EAXSOURCE_MAXEXCLUSION);
+}
+
+void ALsource::eax_validate_source_exclusion_lf_ratio(
+ float exclusion_lf_ratio)
+{
+ eax_validate_range<EaxSourceException>(
+ eax_get_exclusion_lf_ratio_name(),
+ exclusion_lf_ratio,
+ EAXSOURCE_MINEXCLUSIONLFRATIO,
+ EAXSOURCE_MAXEXCLUSIONLFRATIO);
+}
+
+void ALsource::eax_validate_source_outside_volume_hf(
+ long outside_volume_hf)
+{
+ eax_validate_range<EaxSourceException>(
+ "Outside Volume HF",
+ outside_volume_hf,
+ EAXSOURCE_MINOUTSIDEVOLUMEHF,
+ EAXSOURCE_MAXOUTSIDEVOLUMEHF);
+}
+
+void ALsource::eax_validate_source_doppler_factor(
+ float doppler_factor)
+{
+ eax_validate_range<EaxSourceException>(
+ "Doppler Factor",
+ doppler_factor,
+ EAXSOURCE_MINDOPPLERFACTOR,
+ EAXSOURCE_MAXDOPPLERFACTOR);
+}
+
+void ALsource::eax_validate_source_rolloff_factor(
+ float rolloff_factor)
+{
+ eax_validate_range<EaxSourceException>(
+ "Rolloff Factor",
+ rolloff_factor,
+ EAXSOURCE_MINROLLOFFFACTOR,
+ EAXSOURCE_MAXROLLOFFFACTOR);
+}
+
+void ALsource::eax_validate_source_room_rolloff_factor(
+ float room_rolloff_factor)
+{
+ eax_validate_range<EaxSourceException>(
+ "Room Rolloff Factor",
+ room_rolloff_factor,
+ EAXSOURCE_MINROOMROLLOFFFACTOR,
+ EAXSOURCE_MAXROOMROLLOFFFACTOR);
+}
+
+void ALsource::eax_validate_source_air_absorption_factor(
+ float air_absorption_factor)
+{
+ eax_validate_range<EaxSourceException>(
+ "Air Absorption Factor",
+ air_absorption_factor,
+ EAXSOURCE_MINAIRABSORPTIONFACTOR,
+ EAXSOURCE_MAXAIRABSORPTIONFACTOR);
+}
+
+void ALsource::eax_validate_source_flags(
+ unsigned long flags,
+ int eax_version)
+{
+ eax_validate_range<EaxSourceException>(
+ "Flags",
+ flags,
+ 0UL,
+ ~((eax_version == 5) ? EAX50SOURCEFLAGS_RESERVED : EAX20SOURCEFLAGS_RESERVED));
+}
+
+void ALsource::eax_validate_source_macro_fx_factor(
+ float macro_fx_factor)
+{
+ eax_validate_range<EaxSourceException>(
+ "Macro FX Factor",
+ macro_fx_factor,
+ EAXSOURCE_MINMACROFXFACTOR,
+ EAXSOURCE_MAXMACROFXFACTOR);
+}
+
+void ALsource::eax_validate_source_2d_all(
+ const EAXSOURCE2DPROPERTIES& all,
+ int eax_version)
+{
+ eax_validate_source_direct(all.lDirect);
+ eax_validate_source_direct_hf(all.lDirectHF);
+ eax_validate_source_room(all.lRoom);
+ eax_validate_source_room_hf(all.lRoomHF);
+ eax_validate_source_flags(all.ulFlags, eax_version);
+}
+
+void ALsource::eax_validate_source_obstruction_all(
+ const EAXOBSTRUCTIONPROPERTIES& all)
+{
+ eax_validate_source_obstruction(all.lObstruction);
+ eax_validate_source_obstruction_lf_ratio(all.flObstructionLFRatio);
+}
+
+void ALsource::eax_validate_source_exclusion_all(
+ const EAXEXCLUSIONPROPERTIES& all)
+{
+ eax_validate_source_exclusion(all.lExclusion);
+ eax_validate_source_exclusion_lf_ratio(all.flExclusionLFRatio);
+}
+
+void ALsource::eax_validate_source_occlusion_all(
+ const EAXOCCLUSIONPROPERTIES& all)
+{
+ eax_validate_source_occlusion(all.lOcclusion);
+ eax_validate_source_occlusion_lf_ratio(all.flOcclusionLFRatio);
+ eax_validate_source_occlusion_room_ratio(all.flOcclusionRoomRatio);
+ eax_validate_source_occlusion_direct_ratio(all.flOcclusionDirectRatio);
+}
+
+void ALsource::eax_validate_source_all(
+ const EAX20BUFFERPROPERTIES& all,
+ int eax_version)
+{
+ eax_validate_source_direct(all.lDirect);
+ eax_validate_source_direct_hf(all.lDirectHF);
+ eax_validate_source_room(all.lRoom);
+ eax_validate_source_room_hf(all.lRoomHF);
+ eax_validate_source_obstruction(all.lObstruction);
+ eax_validate_source_obstruction_lf_ratio(all.flObstructionLFRatio);
+ eax_validate_source_occlusion(all.lOcclusion);
+ eax_validate_source_occlusion_lf_ratio(all.flOcclusionLFRatio);
+ eax_validate_source_occlusion_room_ratio(all.flOcclusionRoomRatio);
+ eax_validate_source_outside_volume_hf(all.lOutsideVolumeHF);
+ eax_validate_source_room_rolloff_factor(all.flRoomRolloffFactor);
+ eax_validate_source_air_absorption_factor(all.flAirAbsorptionFactor);
+ eax_validate_source_flags(all.dwFlags, eax_version);
+}
+
+void ALsource::eax_validate_source_all(
+ const EAX30SOURCEPROPERTIES& all,
+ int eax_version)
+{
+ eax_validate_source_direct(all.lDirect);
+ eax_validate_source_direct_hf(all.lDirectHF);
+ eax_validate_source_room(all.lRoom);
+ eax_validate_source_room_hf(all.lRoomHF);
+ eax_validate_source_obstruction(all.lObstruction);
+ eax_validate_source_obstruction_lf_ratio(all.flObstructionLFRatio);
+ eax_validate_source_occlusion(all.lOcclusion);
+ eax_validate_source_occlusion_lf_ratio(all.flOcclusionLFRatio);
+ eax_validate_source_occlusion_room_ratio(all.flOcclusionRoomRatio);
+ eax_validate_source_occlusion_direct_ratio(all.flOcclusionDirectRatio);
+ eax_validate_source_exclusion(all.lExclusion);
+ eax_validate_source_exclusion_lf_ratio(all.flExclusionLFRatio);
+ eax_validate_source_outside_volume_hf(all.lOutsideVolumeHF);
+ eax_validate_source_doppler_factor(all.flDopplerFactor);
+ eax_validate_source_rolloff_factor(all.flRolloffFactor);
+ eax_validate_source_room_rolloff_factor(all.flRoomRolloffFactor);
+ eax_validate_source_air_absorption_factor(all.flAirAbsorptionFactor);
+ eax_validate_source_flags(all.ulFlags, eax_version);
+}
+
+void ALsource::eax_validate_source_all(
+ const EAX50SOURCEPROPERTIES& all,
+ int eax_version)
+{
+ eax_validate_source_all(static_cast<EAX30SOURCEPROPERTIES>(all), eax_version);
+ eax_validate_source_macro_fx_factor(all.flMacroFXFactor);
+}
+
+void ALsource::eax_validate_source_speaker_id(
+ long speaker_id)
+{
+ eax_validate_range<EaxSourceException>(
+ "Speaker Id",
+ speaker_id,
+ static_cast<long>(EAXSPEAKER_FRONT_LEFT),
+ static_cast<long>(EAXSPEAKER_LOW_FREQUENCY));
+}
+
+void ALsource::eax_validate_source_speaker_level(
+ long speaker_level)
+{
+ eax_validate_range<EaxSourceException>(
+ "Speaker Level",
+ speaker_level,
+ EAXSOURCE_MINSPEAKERLEVEL,
+ EAXSOURCE_MAXSPEAKERLEVEL);
+}
+
+void ALsource::eax_validate_source_speaker_level_all(
+ const EAXSPEAKERLEVELPROPERTIES& all)
+{
+ eax_validate_source_speaker_id(all.lSpeakerID);
+ eax_validate_source_speaker_level(all.lLevel);
+}
+
+void ALsource::eax_defer_source_direct(
+ long lDirect)
+{
+ eax_d_.source.lDirect = lDirect;
+ eax_source_dirty_filter_flags_.lDirect = (eax_.source.lDirect != eax_d_.source.lDirect);
+}
+
+void ALsource::eax_defer_source_direct_hf(
+ long lDirectHF)
+{
+ eax_d_.source.lDirectHF = lDirectHF;
+ eax_source_dirty_filter_flags_.lDirectHF = (eax_.source.lDirectHF != eax_d_.source.lDirectHF);
+}
+
+void ALsource::eax_defer_source_room(
+ long lRoom)
+{
+ eax_d_.source.lRoom = lRoom;
+ eax_source_dirty_filter_flags_.lRoom = (eax_.source.lRoom != eax_d_.source.lRoom);
+}
+
+void ALsource::eax_defer_source_room_hf(
+ long lRoomHF)
+{
+ eax_d_.source.lRoomHF = lRoomHF;
+ eax_source_dirty_filter_flags_.lRoomHF = (eax_.source.lRoomHF != eax_d_.source.lRoomHF);
+}
+
+void ALsource::eax_defer_source_obstruction(
+ long lObstruction)
+{
+ eax_d_.source.lObstruction = lObstruction;
+ eax_source_dirty_filter_flags_.lObstruction = (eax_.source.lObstruction != eax_d_.source.lObstruction);
+}
+
+void ALsource::eax_defer_source_obstruction_lf_ratio(
+ float flObstructionLFRatio)
+{
+ eax_d_.source.flObstructionLFRatio = flObstructionLFRatio;
+ eax_source_dirty_filter_flags_.flObstructionLFRatio = (eax_.source.flObstructionLFRatio != eax_d_.source.flObstructionLFRatio);
+}
+
+void ALsource::eax_defer_source_occlusion(
+ long lOcclusion)
+{
+ eax_d_.source.lOcclusion = lOcclusion;
+ eax_source_dirty_filter_flags_.lOcclusion = (eax_.source.lOcclusion != eax_d_.source.lOcclusion);
+}
+
+void ALsource::eax_defer_source_occlusion_lf_ratio(
+ float flOcclusionLFRatio)
+{
+ eax_d_.source.flOcclusionLFRatio = flOcclusionLFRatio;
+ eax_source_dirty_filter_flags_.flOcclusionLFRatio = (eax_.source.flOcclusionLFRatio != eax_d_.source.flOcclusionLFRatio);
+}
+
+void ALsource::eax_defer_source_occlusion_room_ratio(
+ float flOcclusionRoomRatio)
+{
+ eax_d_.source.flOcclusionRoomRatio = flOcclusionRoomRatio;
+ eax_source_dirty_filter_flags_.flOcclusionRoomRatio = (eax_.source.flOcclusionRoomRatio != eax_d_.source.flOcclusionRoomRatio);
+}
+
+void ALsource::eax_defer_source_occlusion_direct_ratio(
+ float flOcclusionDirectRatio)
+{
+ eax_d_.source.flOcclusionDirectRatio = flOcclusionDirectRatio;
+ eax_source_dirty_filter_flags_.flOcclusionDirectRatio = (eax_.source.flOcclusionDirectRatio != eax_d_.source.flOcclusionDirectRatio);
+}
+
+void ALsource::eax_defer_source_exclusion(
+ long lExclusion)
+{
+ eax_d_.source.lExclusion = lExclusion;
+ eax_source_dirty_filter_flags_.lExclusion = (eax_.source.lExclusion != eax_d_.source.lExclusion);
+}
+
+void ALsource::eax_defer_source_exclusion_lf_ratio(
+ float flExclusionLFRatio)
+{
+ eax_d_.source.flExclusionLFRatio = flExclusionLFRatio;
+ eax_source_dirty_filter_flags_.flExclusionLFRatio = (eax_.source.flExclusionLFRatio != eax_d_.source.flExclusionLFRatio);
+}
+
+void ALsource::eax_defer_source_outside_volume_hf(
+ long lOutsideVolumeHF)
+{
+ eax_d_.source.lOutsideVolumeHF = lOutsideVolumeHF;
+ eax_source_dirty_misc_flags_.lOutsideVolumeHF = (eax_.source.lOutsideVolumeHF != eax_d_.source.lOutsideVolumeHF);
+}
+
+void ALsource::eax_defer_source_doppler_factor(
+ float flDopplerFactor)
+{
+ eax_d_.source.flDopplerFactor = flDopplerFactor;
+ eax_source_dirty_misc_flags_.flDopplerFactor = (eax_.source.flDopplerFactor != eax_d_.source.flDopplerFactor);
+}
+
+void ALsource::eax_defer_source_rolloff_factor(
+ float flRolloffFactor)
+{
+ eax_d_.source.flRolloffFactor = flRolloffFactor;
+ eax_source_dirty_misc_flags_.flRolloffFactor = (eax_.source.flRolloffFactor != eax_d_.source.flRolloffFactor);
+}
+
+void ALsource::eax_defer_source_room_rolloff_factor(
+ float flRoomRolloffFactor)
+{
+ eax_d_.source.flRoomRolloffFactor = flRoomRolloffFactor;
+ eax_source_dirty_misc_flags_.flRoomRolloffFactor = (eax_.source.flRoomRolloffFactor != eax_d_.source.flRoomRolloffFactor);
+}
+
+void ALsource::eax_defer_source_air_absorption_factor(
+ float flAirAbsorptionFactor)
+{
+ eax_d_.source.flAirAbsorptionFactor = flAirAbsorptionFactor;
+ eax_source_dirty_misc_flags_.flAirAbsorptionFactor = (eax_.source.flAirAbsorptionFactor != eax_d_.source.flAirAbsorptionFactor);
+}
+
+void ALsource::eax_defer_source_flags(
+ unsigned long ulFlags)
+{
+ eax_d_.source.ulFlags = ulFlags;
+ eax_source_dirty_misc_flags_.ulFlags = (eax_.source.ulFlags != eax_d_.source.ulFlags);
+}
+
+void ALsource::eax_defer_source_macro_fx_factor(
+ float flMacroFXFactor)
+{
+ eax_d_.source.flMacroFXFactor = flMacroFXFactor;
+ eax_source_dirty_misc_flags_.flMacroFXFactor = (eax_.source.flMacroFXFactor != eax_d_.source.flMacroFXFactor);
+}
+
+void ALsource::eax_defer_source_2d_all(
+ const EAXSOURCE2DPROPERTIES& all)
+{
+ eax_defer_source_direct(all.lDirect);
+ eax_defer_source_direct_hf(all.lDirectHF);
+ eax_defer_source_room(all.lRoom);
+ eax_defer_source_room_hf(all.lRoomHF);
+ eax_defer_source_flags(all.ulFlags);
+}
+
+void ALsource::eax_defer_source_obstruction_all(
+ const EAXOBSTRUCTIONPROPERTIES& all)
+{
+ eax_defer_source_obstruction(all.lObstruction);
+ eax_defer_source_obstruction_lf_ratio(all.flObstructionLFRatio);
+}
+
+void ALsource::eax_defer_source_exclusion_all(
+ const EAXEXCLUSIONPROPERTIES& all)
+{
+ eax_defer_source_exclusion(all.lExclusion);
+ eax_defer_source_exclusion_lf_ratio(all.flExclusionLFRatio);
+}
+
+void ALsource::eax_defer_source_occlusion_all(
+ const EAXOCCLUSIONPROPERTIES& all)
+{
+ eax_defer_source_occlusion(all.lOcclusion);
+ eax_defer_source_occlusion_lf_ratio(all.flOcclusionLFRatio);
+ eax_defer_source_occlusion_room_ratio(all.flOcclusionRoomRatio);
+ eax_defer_source_occlusion_direct_ratio(all.flOcclusionDirectRatio);
+}
+
+void ALsource::eax_defer_source_all(
+ const EAX20BUFFERPROPERTIES& all)
+{
+ eax_defer_source_direct(all.lDirect);
+ eax_defer_source_direct_hf(all.lDirectHF);
+ eax_defer_source_room(all.lRoom);
+ eax_defer_source_room_hf(all.lRoomHF);
+ eax_defer_source_obstruction(all.lObstruction);
+ eax_defer_source_obstruction_lf_ratio(all.flObstructionLFRatio);
+ eax_defer_source_occlusion(all.lOcclusion);
+ eax_defer_source_occlusion_lf_ratio(all.flOcclusionLFRatio);
+ eax_defer_source_occlusion_room_ratio(all.flOcclusionRoomRatio);
+ eax_defer_source_outside_volume_hf(all.lOutsideVolumeHF);
+ eax_defer_source_room_rolloff_factor(all.flRoomRolloffFactor);
+ eax_defer_source_air_absorption_factor(all.flAirAbsorptionFactor);
+ eax_defer_source_flags(all.dwFlags);
+}
+
+void ALsource::eax_defer_source_all(
+ const EAX30SOURCEPROPERTIES& all)
+{
+ eax_defer_source_direct(all.lDirect);
+ eax_defer_source_direct_hf(all.lDirectHF);
+ eax_defer_source_room(all.lRoom);
+ eax_defer_source_room_hf(all.lRoomHF);
+ eax_defer_source_obstruction(all.lObstruction);
+ eax_defer_source_obstruction_lf_ratio(all.flObstructionLFRatio);
+ eax_defer_source_occlusion(all.lOcclusion);
+ eax_defer_source_occlusion_lf_ratio(all.flOcclusionLFRatio);
+ eax_defer_source_occlusion_room_ratio(all.flOcclusionRoomRatio);
+ eax_defer_source_occlusion_direct_ratio(all.flOcclusionDirectRatio);
+ eax_defer_source_exclusion(all.lExclusion);
+ eax_defer_source_exclusion_lf_ratio(all.flExclusionLFRatio);
+ eax_defer_source_outside_volume_hf(all.lOutsideVolumeHF);
+ eax_defer_source_doppler_factor(all.flDopplerFactor);
+ eax_defer_source_rolloff_factor(all.flRolloffFactor);
+ eax_defer_source_room_rolloff_factor(all.flRoomRolloffFactor);
+ eax_defer_source_air_absorption_factor(all.flAirAbsorptionFactor);
+ eax_defer_source_flags(all.ulFlags);
+}
+
+void ALsource::eax_defer_source_all(
+ const EAX50SOURCEPROPERTIES& all)
+{
+ eax_defer_source_all(static_cast<const EAX30SOURCEPROPERTIES&>(all));
+ eax_defer_source_macro_fx_factor(all.flMacroFXFactor);
+}
+
+void ALsource::eax_defer_source_speaker_level_all(
+ const EAXSPEAKERLEVELPROPERTIES& all)
+{
+ const auto speaker_index = static_cast<std::size_t>(all.lSpeakerID - 1);
+ auto& speaker_level_d = eax_d_.speaker_levels[speaker_index];
+ const auto& speaker_level = eax_.speaker_levels[speaker_index];
+
+ if (speaker_level != speaker_level_d)
+ {
+ eax_source_dirty_misc_flags_.speaker_levels = true;
+ }
+}
+
+void ALsource::eax_defer_source_direct(
+ const EaxEaxCall& eax_call)
+{
+ const auto direct =
+ eax_call.get_value<EaxSourceException, const decltype(EAX30SOURCEPROPERTIES::lDirect)>();
+
+ eax_validate_source_direct(direct);
+ eax_defer_source_direct(direct);
+}
+
+void ALsource::eax_defer_source_direct_hf(
+ const EaxEaxCall& eax_call)
+{
+ const auto direct_hf =
+ eax_call.get_value<EaxSourceException, const decltype(EAX30SOURCEPROPERTIES::lDirectHF)>();
+
+ eax_validate_source_direct_hf(direct_hf);
+ eax_defer_source_direct_hf(direct_hf);
+}
+
+void ALsource::eax_defer_source_room(
+ const EaxEaxCall& eax_call)
+{
+ const auto room =
+ eax_call.get_value<EaxSourceException, const decltype(EAX30SOURCEPROPERTIES::lRoom)>();
+
+ eax_validate_source_room(room);
+ eax_defer_source_room(room);
+}
+
+void ALsource::eax_defer_source_room_hf(
+ const EaxEaxCall& eax_call)
+{
+ const auto room_hf =
+ eax_call.get_value<EaxSourceException, const decltype(EAX30SOURCEPROPERTIES::lRoomHF)>();
+
+ eax_validate_source_room_hf(room_hf);
+ eax_defer_source_room_hf(room_hf);
+}
+
+void ALsource::eax_defer_source_obstruction(
+ const EaxEaxCall& eax_call)
+{
+ const auto obstruction =
+ eax_call.get_value<EaxSourceException, const decltype(EAX30SOURCEPROPERTIES::lObstruction)>();
+
+ eax_validate_source_obstruction(obstruction);
+ eax_defer_source_obstruction(obstruction);
+}
+
+void ALsource::eax_defer_source_obstruction_lf_ratio(
+ const EaxEaxCall& eax_call)
+{
+ const auto obstruction_lf_ratio =
+ eax_call.get_value<EaxSourceException, const decltype(EAX30SOURCEPROPERTIES::flObstructionLFRatio)>();
+
+ eax_validate_source_obstruction_lf_ratio(obstruction_lf_ratio);
+ eax_defer_source_obstruction_lf_ratio(obstruction_lf_ratio);
+}
+
+void ALsource::eax_defer_source_occlusion(
+ const EaxEaxCall& eax_call)
+{
+ const auto occlusion =
+ eax_call.get_value<EaxSourceException, const decltype(EAX30SOURCEPROPERTIES::lOcclusion)>();
+
+ eax_validate_source_occlusion(occlusion);
+ eax_defer_source_occlusion(occlusion);
+}
+
+void ALsource::eax_defer_source_occlusion_lf_ratio(
+ const EaxEaxCall& eax_call)
+{
+ const auto occlusion_lf_ratio =
+ eax_call.get_value<EaxSourceException, const decltype(EAX30SOURCEPROPERTIES::flOcclusionLFRatio)>();
+
+ eax_validate_source_occlusion_lf_ratio(occlusion_lf_ratio);
+ eax_defer_source_occlusion_lf_ratio(occlusion_lf_ratio);
+}
+
+void ALsource::eax_defer_source_occlusion_room_ratio(
+ const EaxEaxCall& eax_call)
+{
+ const auto occlusion_room_ratio =
+ eax_call.get_value<EaxSourceException, const decltype(EAX30SOURCEPROPERTIES::flOcclusionRoomRatio)>();
+
+ eax_validate_source_occlusion_room_ratio(occlusion_room_ratio);
+ eax_defer_source_occlusion_room_ratio(occlusion_room_ratio);
+}
+
+void ALsource::eax_defer_source_occlusion_direct_ratio(
+ const EaxEaxCall& eax_call)
+{
+ const auto occlusion_direct_ratio =
+ eax_call.get_value<EaxSourceException, const decltype(EAX30SOURCEPROPERTIES::flOcclusionDirectRatio)>();
+
+ eax_validate_source_occlusion_direct_ratio(occlusion_direct_ratio);
+ eax_defer_source_occlusion_direct_ratio(occlusion_direct_ratio);
+}
+
+void ALsource::eax_defer_source_exclusion(
+ const EaxEaxCall& eax_call)
+{
+ const auto exclusion =
+ eax_call.get_value<EaxSourceException, const decltype(EAX30SOURCEPROPERTIES::lExclusion)>();
+
+ eax_validate_source_exclusion(exclusion);
+ eax_defer_source_exclusion(exclusion);
+}
+
+void ALsource::eax_defer_source_exclusion_lf_ratio(
+ const EaxEaxCall& eax_call)
+{
+ const auto exclusion_lf_ratio =
+ eax_call.get_value<EaxSourceException, const decltype(EAX30SOURCEPROPERTIES::flExclusionLFRatio)>();
+
+ eax_validate_source_exclusion_lf_ratio(exclusion_lf_ratio);
+ eax_defer_source_exclusion_lf_ratio(exclusion_lf_ratio);
+}
+
+void ALsource::eax_defer_source_outside_volume_hf(
+ const EaxEaxCall& eax_call)
+{
+ const auto outside_volume_hf =
+ eax_call.get_value<EaxSourceException, const decltype(EAX30SOURCEPROPERTIES::lOutsideVolumeHF)>();
+
+ eax_validate_source_outside_volume_hf(outside_volume_hf);
+ eax_defer_source_outside_volume_hf(outside_volume_hf);
+}
+
+void ALsource::eax_defer_source_doppler_factor(
+ const EaxEaxCall& eax_call)
+{
+ const auto doppler_factor =
+ eax_call.get_value<EaxSourceException, const decltype(EAX30SOURCEPROPERTIES::flDopplerFactor)>();
+
+ eax_validate_source_doppler_factor(doppler_factor);
+ eax_defer_source_doppler_factor(doppler_factor);
+}
+
+void ALsource::eax_defer_source_rolloff_factor(
+ const EaxEaxCall& eax_call)
+{
+ const auto rolloff_factor =
+ eax_call.get_value<EaxSourceException, const decltype(EAX30SOURCEPROPERTIES::flRolloffFactor)>();
+
+ eax_validate_source_rolloff_factor(rolloff_factor);
+ eax_defer_source_rolloff_factor(rolloff_factor);
+}
+
+void ALsource::eax_defer_source_room_rolloff_factor(
+ const EaxEaxCall& eax_call)
+{
+ const auto room_rolloff_factor =
+ eax_call.get_value<EaxSourceException, const decltype(EAX30SOURCEPROPERTIES::flRoomRolloffFactor)>();
+
+ eax_validate_source_room_rolloff_factor(room_rolloff_factor);
+ eax_defer_source_room_rolloff_factor(room_rolloff_factor);
+}
+
+void ALsource::eax_defer_source_air_absorption_factor(
+ const EaxEaxCall& eax_call)
+{
+ const auto air_absorption_factor =
+ eax_call.get_value<EaxSourceException, const decltype(EAX30SOURCEPROPERTIES::flAirAbsorptionFactor)>();
+
+ eax_validate_source_air_absorption_factor(air_absorption_factor);
+ eax_defer_source_air_absorption_factor(air_absorption_factor);
+}
+
+void ALsource::eax_defer_source_flags(
+ const EaxEaxCall& eax_call)
+{
+ const auto flags =
+ eax_call.get_value<EaxSourceException, const decltype(EAX30SOURCEPROPERTIES::ulFlags)>();
+
+ eax_validate_source_flags(flags, eax_call.get_version());
+ eax_defer_source_flags(flags);
+}
+
+void ALsource::eax_defer_source_macro_fx_factor(
+ const EaxEaxCall& eax_call)
+{
+ const auto macro_fx_factor =
+ eax_call.get_value<EaxSourceException, const decltype(EAX50SOURCEPROPERTIES::flMacroFXFactor)>();
+
+ eax_validate_source_macro_fx_factor(macro_fx_factor);
+ eax_defer_source_macro_fx_factor(macro_fx_factor);
+}
+
+void ALsource::eax_defer_source_2d_all(
+ const EaxEaxCall& eax_call)
+{
+ const auto all = eax_call.get_value<EaxSourceException, const EAXSOURCE2DPROPERTIES>();
+
+ eax_validate_source_2d_all(all, eax_call.get_version());
+ eax_defer_source_2d_all(all);
+}
+
+void ALsource::eax_defer_source_obstruction_all(
+ const EaxEaxCall& eax_call)
+{
+ const auto all = eax_call.get_value<EaxSourceException, const EAXOBSTRUCTIONPROPERTIES>();
+
+ eax_validate_source_obstruction_all(all);
+ eax_defer_source_obstruction_all(all);
+}
+
+void ALsource::eax_defer_source_exclusion_all(
+ const EaxEaxCall& eax_call)
+{
+ const auto all = eax_call.get_value<EaxSourceException, const EAXEXCLUSIONPROPERTIES>();
+
+ eax_validate_source_exclusion_all(all);
+ eax_defer_source_exclusion_all(all);
+}
+
+void ALsource::eax_defer_source_occlusion_all(
+ const EaxEaxCall& eax_call)
+{
+ const auto all = eax_call.get_value<EaxSourceException, const EAXOCCLUSIONPROPERTIES>();
+
+ eax_validate_source_occlusion_all(all);
+ eax_defer_source_occlusion_all(all);
+}
+
+void ALsource::eax_defer_source_all(
+ const EaxEaxCall& eax_call)
+{
+ const auto eax_version = eax_call.get_version();
+
+ if (eax_version == 2)
+ {
+ const auto all = eax_call.get_value<EaxSourceException, const EAX20BUFFERPROPERTIES>();
+
+ eax_validate_source_all(all, eax_version);
+ eax_defer_source_all(all);
+ }
+ else if (eax_version < 5)
+ {
+ const auto all = eax_call.get_value<EaxSourceException, const EAX30SOURCEPROPERTIES>();
+
+ eax_validate_source_all(all, eax_version);
+ eax_defer_source_all(all);
+ }
+ else
+ {
+ const auto all = eax_call.get_value<EaxSourceException, const EAX50SOURCEPROPERTIES>();
+
+ eax_validate_source_all(all, eax_version);
+ eax_defer_source_all(all);
+ }
+}
+
+void ALsource::eax_defer_source_speaker_level_all(
+ const EaxEaxCall& eax_call)
+{
+ const auto speaker_level_properties = eax_call.get_value<EaxSourceException, const EAXSPEAKERLEVELPROPERTIES>();
+
+ eax_validate_source_speaker_level_all(speaker_level_properties);
+ eax_defer_source_speaker_level_all(speaker_level_properties);
+}
+
+void ALsource::eax_set_outside_volume_hf()
+{
+ const auto efx_gain_hf = clamp(
+ level_mb_to_gain(static_cast<float>(eax_.source.lOutsideVolumeHF)),
+ AL_MIN_CONE_OUTER_GAINHF,
+ AL_MAX_CONE_OUTER_GAINHF
+ );
+
+ eax_al_source_f(
+ AL_CONE_OUTER_GAINHF,
+ efx_gain_hf
+ );
+}
+
+void ALsource::eax_set_doppler_factor()
+{
+ eax_al_source_f(
+ AL_DOPPLER_FACTOR,
+ eax_.source.flDopplerFactor
+ );
+}
+
+void ALsource::eax_set_rolloff_factor()
+{
+ eax_al_source_f(
+ AL_ROLLOFF_FACTOR,
+ eax_.source.flRolloffFactor
+ );
+}
+
+void ALsource::eax_set_room_rolloff_factor()
+{
+ eax_al_source_f(
+ AL_ROOM_ROLLOFF_FACTOR,
+ eax_.source.flRoomRolloffFactor
+ );
+}
+
+void ALsource::eax_set_air_absorption_factor()
+{
+ const auto air_absorption_factor =
+ eax_al_context_->eax_get_air_absorption_factor() * eax_.source.flAirAbsorptionFactor;
+
+ eax_al_source_f(
+ AL_AIR_ABSORPTION_FACTOR,
+ air_absorption_factor
+ );
+}
+
+void ALsource::eax_set_direct_hf_auto_flag()
+{
+ const auto is_enable = (eax_.source.ulFlags & EAXSOURCEFLAGS_DIRECTHFAUTO) != 0;
+ const auto al_value = static_cast<ALint>(is_enable ? AL_TRUE : AL_FALSE);
+
+ eax_al_source_i(
+ AL_DIRECT_FILTER_GAINHF_AUTO,
+ al_value
+ );
+}
+
+void ALsource::eax_set_room_auto_flag()
+{
+ const auto is_enable = (eax_.source.ulFlags & EAXSOURCEFLAGS_ROOMAUTO) != 0;
+ const auto al_value = static_cast<ALint>(is_enable ? AL_TRUE : AL_FALSE);
+
+ eax_al_source_i(
+ AL_AUXILIARY_SEND_FILTER_GAIN_AUTO,
+ al_value
+ );
+}
+
+void ALsource::eax_set_room_hf_auto_flag()
+{
+ const auto is_enable = (eax_.source.ulFlags & EAXSOURCEFLAGS_ROOMHFAUTO) != 0;
+ const auto al_value = static_cast<ALint>(is_enable ? AL_TRUE : AL_FALSE);
+
+ eax_al_source_i(
+ AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO,
+ al_value
+ );
+}
+
+void ALsource::eax_set_flags()
+{
+ eax_set_direct_hf_auto_flag();
+ eax_set_room_auto_flag();
+ eax_set_room_hf_auto_flag();
+ eax_set_speaker_levels();
+}
+
+void ALsource::eax_set_macro_fx_factor()
+{
+ // TODO
+}
+
+void ALsource::eax_set_speaker_levels()
+{
+ // TODO
+}
+
+void ALsource::eax_apply_deferred()
+{
+ if (!eax_are_active_fx_slots_dirty_ &&
+ eax_sends_dirty_flags_ == EaxSourceSendsDirtyFlags{} &&
+ eax_source_dirty_filter_flags_ == EaxSourceSourceFilterDirtyFlags{} &&
+ eax_source_dirty_misc_flags_ == EaxSourceSourceMiscDirtyFlags{})
+ {
+ return;
+ }
+
+ eax_ = eax_d_;
+
+ if (eax_are_active_fx_slots_dirty_)
+ {
+ eax_are_active_fx_slots_dirty_ = false;
+ eax_set_fx_slots();
+ eax_update_filters_internal();
+ }
+ else if (eax_has_active_fx_slots_)
+ {
+ if (eax_source_dirty_filter_flags_ != EaxSourceSourceFilterDirtyFlags{})
+ {
+ eax_update_filters_internal();
+ }
+ else if (eax_sends_dirty_flags_ != EaxSourceSendsDirtyFlags{})
+ {
+ for (auto i = std::size_t{}; i < EAX_MAX_FXSLOTS; ++i)
+ {
+ if (eax_active_fx_slots_[i])
+ {
+ if (eax_sends_dirty_flags_.sends[i] != EaxSourceSendDirtyFlags{})
+ {
+ eax_update_filters_internal();
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ if (eax_source_dirty_misc_flags_ != EaxSourceSourceMiscDirtyFlags{})
+ {
+ if (eax_source_dirty_misc_flags_.lOutsideVolumeHF)
+ {
+ eax_set_outside_volume_hf();
+ }
+
+ if (eax_source_dirty_misc_flags_.flDopplerFactor)
+ {
+ eax_set_doppler_factor();
+ }
+
+ if (eax_source_dirty_misc_flags_.flRolloffFactor)
+ {
+ eax_set_rolloff_factor();
+ }
+
+ if (eax_source_dirty_misc_flags_.flRoomRolloffFactor)
+ {
+ eax_set_room_rolloff_factor();
+ }
+
+ if (eax_source_dirty_misc_flags_.flAirAbsorptionFactor)
+ {
+ eax_set_air_absorption_factor();
+ }
+
+ if (eax_source_dirty_misc_flags_.ulFlags)
+ {
+ eax_set_flags();
+ }
+
+ if (eax_source_dirty_misc_flags_.flMacroFXFactor)
+ {
+ eax_set_macro_fx_factor();
+ }
+
+ eax_source_dirty_misc_flags_ = EaxSourceSourceMiscDirtyFlags{};
+ }
+
+ eax_sends_dirty_flags_ = EaxSourceSendsDirtyFlags{};
+ eax_source_dirty_filter_flags_ = EaxSourceSourceFilterDirtyFlags{};
+}
+
+void ALsource::eax_set(
+ const EaxEaxCall& eax_call)
+{
+ switch (eax_call.get_property_id())
+ {
+ case EAXSOURCE_NONE:
+ break;
+
+ case EAXSOURCE_ALLPARAMETERS:
+ eax_defer_source_all(eax_call);
+ break;
+
+ case EAXSOURCE_OBSTRUCTIONPARAMETERS:
+ eax_defer_source_obstruction_all(eax_call);
+ break;
+
+ case EAXSOURCE_OCCLUSIONPARAMETERS:
+ eax_defer_source_occlusion_all(eax_call);
+ break;
+
+ case EAXSOURCE_EXCLUSIONPARAMETERS:
+ eax_defer_source_exclusion_all(eax_call);
+ break;
+
+ case EAXSOURCE_DIRECT:
+ eax_defer_source_direct(eax_call);
+ break;
+
+ case EAXSOURCE_DIRECTHF:
+ eax_defer_source_direct_hf(eax_call);
+ break;
+
+ case EAXSOURCE_ROOM:
+ eax_defer_source_room(eax_call);
+ break;
+
+ case EAXSOURCE_ROOMHF:
+ eax_defer_source_room_hf(eax_call);
+ break;
+
+ case EAXSOURCE_OBSTRUCTION:
+ eax_defer_source_obstruction(eax_call);
+ break;
+
+ case EAXSOURCE_OBSTRUCTIONLFRATIO:
+ eax_defer_source_obstruction_lf_ratio(eax_call);
+ break;
+
+ case EAXSOURCE_OCCLUSION:
+ eax_defer_source_occlusion(eax_call);
+ break;
+
+ case EAXSOURCE_OCCLUSIONLFRATIO:
+ eax_defer_source_occlusion_lf_ratio(eax_call);
+ break;
+
+ case EAXSOURCE_OCCLUSIONROOMRATIO:
+ eax_defer_source_occlusion_room_ratio(eax_call);
+ break;
+
+ case EAXSOURCE_OCCLUSIONDIRECTRATIO:
+ eax_defer_source_occlusion_direct_ratio(eax_call);
+ break;
+
+ case EAXSOURCE_EXCLUSION:
+ eax_defer_source_exclusion(eax_call);
+ break;
+
+ case EAXSOURCE_EXCLUSIONLFRATIO:
+ eax_defer_source_exclusion_lf_ratio(eax_call);
+ break;
+
+ case EAXSOURCE_OUTSIDEVOLUMEHF:
+ eax_defer_source_outside_volume_hf(eax_call);
+ break;
+
+ case EAXSOURCE_DOPPLERFACTOR:
+ eax_defer_source_doppler_factor(eax_call);
+ break;
+
+ case EAXSOURCE_ROLLOFFFACTOR:
+ eax_defer_source_rolloff_factor(eax_call);
+ break;
+
+ case EAXSOURCE_ROOMROLLOFFFACTOR:
+ eax_defer_source_room_rolloff_factor(eax_call);
+ break;
+
+ case EAXSOURCE_AIRABSORPTIONFACTOR:
+ eax_defer_source_air_absorption_factor(eax_call);
+ break;
+
+ case EAXSOURCE_FLAGS:
+ eax_defer_source_flags(eax_call);
+ break;
+
+ case EAXSOURCE_SENDPARAMETERS:
+ eax_defer_send(eax_call);
+ break;
+
+ case EAXSOURCE_ALLSENDPARAMETERS:
+ eax_defer_send_all(eax_call);
+ break;
+
+ case EAXSOURCE_OCCLUSIONSENDPARAMETERS:
+ eax_defer_send_occlusion_all(eax_call);
+ break;
+
+ case EAXSOURCE_EXCLUSIONSENDPARAMETERS:
+ eax_defer_send_exclusion_all(eax_call);
+ break;
+
+ case EAXSOURCE_ACTIVEFXSLOTID:
+ eax_defer_active_fx_slots(eax_call);
+ break;
+
+ case EAXSOURCE_MACROFXFACTOR:
+ eax_defer_source_macro_fx_factor(eax_call);
+ break;
+
+ case EAXSOURCE_SPEAKERLEVELS:
+ eax_defer_source_speaker_level_all(eax_call);
+ break;
+
+ case EAXSOURCE_ALL2DPARAMETERS:
+ eax_defer_source_2d_all(eax_call);
+ break;
+
+ default:
+ eax_fail("Unsupported property id.");
+ }
+
+ if (!eax_call.is_deferred())
+ {
+ eax_apply_deferred();
+ }
+}
+
+const GUID& ALsource::eax_get_send_fx_slot_guid(
+ int eax_version,
+ EaxFxSlotIndexValue fx_slot_index)
+{
+ switch (eax_version)
+ {
+ case 4:
+ switch (fx_slot_index)
+ {
+ case 0:
+ return EAXPROPERTYID_EAX40_FXSlot0;
+
+ case 1:
+ return EAXPROPERTYID_EAX40_FXSlot1;
+
+ case 2:
+ return EAXPROPERTYID_EAX40_FXSlot2;
+
+ case 3:
+ return EAXPROPERTYID_EAX40_FXSlot3;
+
+ default:
+ eax_fail("FX slot index out of range.");
+ }
+
+ case 5:
+ switch (fx_slot_index)
+ {
+ case 0:
+ return EAXPROPERTYID_EAX50_FXSlot0;
+
+ case 1:
+ return EAXPROPERTYID_EAX50_FXSlot1;
+
+ case 2:
+ return EAXPROPERTYID_EAX50_FXSlot2;
+
+ case 3:
+ return EAXPROPERTYID_EAX50_FXSlot3;
+
+ default:
+ eax_fail("FX slot index out of range.");
+ }
+
+ default:
+ eax_fail("Unsupported EAX version.");
+ }
+}
+
+void ALsource::eax_copy_send(
+ const EAXSOURCEALLSENDPROPERTIES& src_send,
+ EAXSOURCESENDPROPERTIES& dst_send)
+{
+ dst_send.lSend = src_send.lSend;
+ dst_send.lSendHF = src_send.lSendHF;
+}
+
+void ALsource::eax_copy_send(
+ const EAXSOURCEALLSENDPROPERTIES& src_send,
+ EAXSOURCEALLSENDPROPERTIES& dst_send)
+{
+ dst_send = src_send;
+}
+
+void ALsource::eax_copy_send(
+ const EAXSOURCEALLSENDPROPERTIES& src_send,
+ EAXSOURCEOCCLUSIONSENDPROPERTIES& dst_send)
+{
+ dst_send.lOcclusion = src_send.lOcclusion;
+ dst_send.flOcclusionLFRatio = src_send.flOcclusionLFRatio;
+ dst_send.flOcclusionRoomRatio = src_send.flOcclusionRoomRatio;
+ dst_send.flOcclusionDirectRatio = src_send.flOcclusionDirectRatio;
+}
+
+void ALsource::eax_copy_send(
+ const EAXSOURCEALLSENDPROPERTIES& src_send,
+ EAXSOURCEEXCLUSIONSENDPROPERTIES& dst_send)
+{
+ dst_send.lExclusion = src_send.lExclusion;
+ dst_send.flExclusionLFRatio = src_send.flExclusionLFRatio;
+}
+
+void ALsource::eax_api_get_source_all_v2(
+ const EaxEaxCall& eax_call)
+{
+ auto eax_2_all = EAX20BUFFERPROPERTIES{};
+ eax_2_all.lDirect = eax_.source.lDirect;
+ eax_2_all.lDirectHF = eax_.source.lDirectHF;
+ eax_2_all.lRoom = eax_.source.lRoom;
+ eax_2_all.lRoomHF = eax_.source.lRoomHF;
+ eax_2_all.flRoomRolloffFactor = eax_.source.flRoomRolloffFactor;
+ eax_2_all.lObstruction = eax_.source.lObstruction;
+ eax_2_all.flObstructionLFRatio = eax_.source.flObstructionLFRatio;
+ eax_2_all.lOcclusion = eax_.source.lOcclusion;
+ eax_2_all.flOcclusionLFRatio = eax_.source.flOcclusionLFRatio;
+ eax_2_all.flOcclusionRoomRatio = eax_.source.flOcclusionRoomRatio;
+ eax_2_all.lOutsideVolumeHF = eax_.source.lOutsideVolumeHF;
+ eax_2_all.flAirAbsorptionFactor = eax_.source.flAirAbsorptionFactor;
+ eax_2_all.dwFlags = eax_.source.ulFlags;
+
+ eax_call.set_value<EaxSourceException>(eax_2_all);
+}
+
+void ALsource::eax_api_get_source_all_v3(
+ const EaxEaxCall& eax_call)
+{
+ eax_call.set_value<EaxSourceException>(static_cast<const EAX30SOURCEPROPERTIES&>(eax_.source));
+}
+
+void ALsource::eax_api_get_source_all_v5(
+ const EaxEaxCall& eax_call)
+{
+ eax_call.set_value<EaxSourceException>(eax_.source);
+}
+
+void ALsource::eax_api_get_source_all(
+ const EaxEaxCall& eax_call)
+{
+ switch (eax_call.get_version())
+ {
+ case 2:
+ eax_api_get_source_all_v2(eax_call);
+ break;
+
+ case 3:
+ case 4:
+ eax_api_get_source_all_v3(eax_call);
+ break;
+
+ case 5:
+ eax_api_get_source_all_v5(eax_call);
+ break;
+
+ default:
+ eax_fail("Unsupported EAX version.");
+ }
+}
+
+void ALsource::eax_api_get_source_all_obstruction(
+ const EaxEaxCall& eax_call)
+{
+ static_assert(
+ offsetof(EAXOBSTRUCTIONPROPERTIES, flObstructionLFRatio) - offsetof(EAXOBSTRUCTIONPROPERTIES, lObstruction) ==
+ offsetof(EAX30SOURCEPROPERTIES, flObstructionLFRatio) - offsetof(EAX30SOURCEPROPERTIES, lObstruction),
+ "Type size."
+ );
+
+ const auto eax_obstruction_all = *reinterpret_cast<const EAXOBSTRUCTIONPROPERTIES*>(&eax_.source.lObstruction);
+
+ eax_call.set_value<EaxSourceException>(eax_obstruction_all);
+}
+
+void ALsource::eax_api_get_source_all_occlusion(
+ const EaxEaxCall& eax_call)
+{
+ static_assert(
+ offsetof(EAXOCCLUSIONPROPERTIES, flOcclusionLFRatio) - offsetof(EAXOCCLUSIONPROPERTIES, lOcclusion) ==
+ offsetof(EAX30SOURCEPROPERTIES, flOcclusionLFRatio) - offsetof(EAX30SOURCEPROPERTIES, lOcclusion) &&
+
+ offsetof(EAXOCCLUSIONPROPERTIES, flOcclusionRoomRatio) - offsetof(EAXOCCLUSIONPROPERTIES, lOcclusion) ==
+ offsetof(EAX30SOURCEPROPERTIES, flOcclusionRoomRatio) - offsetof(EAX30SOURCEPROPERTIES, lOcclusion) &&
+
+ offsetof(EAXOCCLUSIONPROPERTIES, flOcclusionDirectRatio) - offsetof(EAXOCCLUSIONPROPERTIES, lOcclusion) ==
+ offsetof(EAX30SOURCEPROPERTIES, flOcclusionDirectRatio) - offsetof(EAX30SOURCEPROPERTIES, lOcclusion),
+
+ "Type size."
+ );
+
+ const auto eax_occlusion_all = *reinterpret_cast<const EAXOCCLUSIONPROPERTIES*>(&eax_.source.lOcclusion);
+
+ eax_call.set_value<EaxSourceException>(eax_occlusion_all);
+}
+
+void ALsource::eax_api_get_source_all_exclusion(
+ const EaxEaxCall& eax_call)
+{
+ static_assert(
+ offsetof(EAXEXCLUSIONPROPERTIES, flExclusionLFRatio) - offsetof(EAXEXCLUSIONPROPERTIES, lExclusion) ==
+ offsetof(EAX30SOURCEPROPERTIES, flExclusionLFRatio) - offsetof(EAX30SOURCEPROPERTIES, lExclusion),
+
+ "Type size."
+ );
+
+ const auto eax_exclusion_all = *reinterpret_cast<const EAXEXCLUSIONPROPERTIES*>(&eax_.source.lExclusion);
+
+ eax_call.set_value<EaxSourceException>(eax_exclusion_all);
+}
+
+void ALsource::eax_api_get_source_active_fx_slot_id(
+ const EaxEaxCall& eax_call)
+{
+ switch (eax_call.get_version())
+ {
+ case 4:
+ {
+ const auto& active_fx_slots = reinterpret_cast<const EAX40ACTIVEFXSLOTS&>(eax_.active_fx_slots);
+ eax_call.set_value<EaxSourceException>(active_fx_slots);
+ }
+ break;
+
+ case 5:
+ {
+ const auto& active_fx_slots = reinterpret_cast<const EAX50ACTIVEFXSLOTS&>(eax_.active_fx_slots);
+ eax_call.set_value<EaxSourceException>(active_fx_slots);
+ }
+ break;
+
+ default:
+ eax_fail("Unsupported EAX version.");
+ }
+}
+
+void ALsource::eax_api_get_source_all_2d(
+ const EaxEaxCall& eax_call)
+{
+ auto eax_2d_all = EAXSOURCE2DPROPERTIES{};
+ eax_2d_all.lDirect = eax_.source.lDirect;
+ eax_2d_all.lDirectHF = eax_.source.lDirectHF;
+ eax_2d_all.lRoom = eax_.source.lRoom;
+ eax_2d_all.lRoomHF = eax_.source.lRoomHF;
+ eax_2d_all.ulFlags = eax_.source.ulFlags;
+
+ eax_call.set_value<EaxSourceException>(eax_2d_all);
+}
+
+void ALsource::eax_api_get_source_speaker_level_all(
+ const EaxEaxCall& eax_call)
+{
+ auto& all = eax_call.get_value<EaxSourceException, EAXSPEAKERLEVELPROPERTIES>();
+
+ eax_validate_source_speaker_id(all.lSpeakerID);
+ const auto speaker_index = static_cast<std::size_t>(all.lSpeakerID - 1);
+ all.lLevel = eax_.speaker_levels[speaker_index];
+}
+
+void ALsource::eax_get(
+ const EaxEaxCall& eax_call)
+{
+ switch (eax_call.get_property_id())
+ {
+ case EAXSOURCE_NONE:
+ break;
+
+ case EAXSOURCE_ALLPARAMETERS:
+ eax_api_get_source_all(eax_call);
+ break;
+
+ case EAXSOURCE_OBSTRUCTIONPARAMETERS:
+ eax_api_get_source_all_obstruction(eax_call);
+ break;
+
+ case EAXSOURCE_OCCLUSIONPARAMETERS:
+ eax_api_get_source_all_occlusion(eax_call);
+ break;
+
+ case EAXSOURCE_EXCLUSIONPARAMETERS:
+ eax_api_get_source_all_exclusion(eax_call);
+ break;
+
+ case EAXSOURCE_DIRECT:
+ eax_call.set_value<EaxSourceException>(eax_.source.lDirect);
+ break;
+
+ case EAXSOURCE_DIRECTHF:
+ eax_call.set_value<EaxSourceException>(eax_.source.lDirectHF);
+ break;
+
+ case EAXSOURCE_ROOM:
+ eax_call.set_value<EaxSourceException>(eax_.source.lRoom);
+ break;
+
+ case EAXSOURCE_ROOMHF:
+ eax_call.set_value<EaxSourceException>(eax_.source.lRoomHF);
+ break;
+
+ case EAXSOURCE_OBSTRUCTION:
+ eax_call.set_value<EaxSourceException>(eax_.source.lObstruction);
+ break;
+
+ case EAXSOURCE_OBSTRUCTIONLFRATIO:
+ eax_call.set_value<EaxSourceException>(eax_.source.flObstructionLFRatio);
+ break;
+
+ case EAXSOURCE_OCCLUSION:
+ eax_call.set_value<EaxSourceException>(eax_.source.lOcclusion);
+ break;
+
+ case EAXSOURCE_OCCLUSIONLFRATIO:
+ eax_call.set_value<EaxSourceException>(eax_.source.flOcclusionLFRatio);
+ break;
+
+ case EAXSOURCE_OCCLUSIONROOMRATIO:
+ eax_call.set_value<EaxSourceException>(eax_.source.flOcclusionRoomRatio);
+ break;
+
+ case EAXSOURCE_OCCLUSIONDIRECTRATIO:
+ eax_call.set_value<EaxSourceException>(eax_.source.flOcclusionDirectRatio);
+ break;
+
+ case EAXSOURCE_EXCLUSION:
+ eax_call.set_value<EaxSourceException>(eax_.source.lExclusion);
+ break;
+
+ case EAXSOURCE_EXCLUSIONLFRATIO:
+ eax_call.set_value<EaxSourceException>(eax_.source.flExclusionLFRatio);
+ break;
+
+ case EAXSOURCE_OUTSIDEVOLUMEHF:
+ eax_call.set_value<EaxSourceException>(eax_.source.lOutsideVolumeHF);
+ break;
+
+ case EAXSOURCE_DOPPLERFACTOR:
+ eax_call.set_value<EaxSourceException>(eax_.source.flDopplerFactor);
+ break;
+
+ case EAXSOURCE_ROLLOFFFACTOR:
+ eax_call.set_value<EaxSourceException>(eax_.source.flRolloffFactor);
+ break;
+
+ case EAXSOURCE_ROOMROLLOFFFACTOR:
+ eax_call.set_value<EaxSourceException>(eax_.source.flRoomRolloffFactor);
+ break;
+
+ case EAXSOURCE_AIRABSORPTIONFACTOR:
+ eax_call.set_value<EaxSourceException>(eax_.source.flAirAbsorptionFactor);
+ break;
+
+ case EAXSOURCE_FLAGS:
+ eax_call.set_value<EaxSourceException>(eax_.source.ulFlags);
+ break;
+
+ case EAXSOURCE_SENDPARAMETERS:
+ eax_api_get_send_properties<EaxSourceException, EAXSOURCESENDPROPERTIES>(eax_call);
+ break;
+
+ case EAXSOURCE_ALLSENDPARAMETERS:
+ eax_api_get_send_properties<EaxSourceException, EAXSOURCEALLSENDPROPERTIES>(eax_call);
+ break;
+
+ case EAXSOURCE_OCCLUSIONSENDPARAMETERS:
+ eax_api_get_send_properties<EaxSourceException, EAXSOURCEOCCLUSIONSENDPROPERTIES>(eax_call);
+ break;
+
+ case EAXSOURCE_EXCLUSIONSENDPARAMETERS:
+ eax_api_get_send_properties<EaxSourceException, EAXSOURCEEXCLUSIONSENDPROPERTIES>(eax_call);
+ break;
+
+ case EAXSOURCE_ACTIVEFXSLOTID:
+ eax_api_get_source_active_fx_slot_id(eax_call);
+ break;
+
+ case EAXSOURCE_MACROFXFACTOR:
+ eax_call.set_value<EaxSourceException>(eax_.source.flMacroFXFactor);
+ break;
+
+ case EAXSOURCE_SPEAKERLEVELS:
+ eax_api_get_source_speaker_level_all(eax_call);
+ break;
+
+ case EAXSOURCE_ALL2DPARAMETERS:
+ eax_api_get_source_all_2d(eax_call);
+ break;
+
+ default:
+ eax_fail("Unsupported property id.");
+ }
+}
+
+void ALsource::eax_al_source_i(
+ ALenum param,
+ ALint value)
+{
+ SetSourceiv(this, eax_al_context_, static_cast<SourceProp>(param), {&value, 1});
+}
+
+void ALsource::eax_al_source_f(
+ ALenum param,
+ ALfloat value)
+{
+ SetSourcefv(this, eax_al_context_, static_cast<SourceProp>(param), {&value, 1});
+}
+
+void ALsource::eax_al_source_3i(
+ ALenum param,
+ ALint value1,
+ ALint value2,
+ ALint value3)
+{
+ const ALint values[3] = {value1, value2, value3};
+ SetSourceiv(this, eax_al_context_, static_cast<SourceProp>(param), values);
+}
+
+
+#endif // ALSOFT_EAX
diff --git a/al/source.h b/al/source.h
index 25ece822..41cd6187 100644
--- a/al/source.h
+++ b/al/source.h
@@ -21,6 +21,12 @@
#include "core/voice.h"
#include "vector.h"
+#if ALSOFT_EAX
+#include "eax_eax_call.h"
+#include "eax_fx_slot_index.h"
+#include "eax_utils.h"
+#endif // ALSOFT_EAX
+
struct ALbuffer;
struct ALeffectslot;
@@ -41,6 +47,77 @@ struct ALbufferQueueItem : public VoiceBufferItem {
};
+#if ALSOFT_EAX
+struct EaxSourceInitParam
+{
+ ALCcontext* al_context{};
+ ALfilter* al_filter{};
+}; // EaxSourceInitParam
+
+
+using EaxSourceSourceFilterDirtyFlagsValue = std::uint_least16_t;
+
+struct EaxSourceSourceFilterDirtyFlags
+{
+ using EaxIsBitFieldStruct = bool;
+
+ EaxSourceSourceFilterDirtyFlagsValue lDirect : 1;
+ EaxSourceSourceFilterDirtyFlagsValue lDirectHF : 1;
+ EaxSourceSourceFilterDirtyFlagsValue lRoom : 1;
+ EaxSourceSourceFilterDirtyFlagsValue lRoomHF : 1;
+ EaxSourceSourceFilterDirtyFlagsValue lObstruction : 1;
+ EaxSourceSourceFilterDirtyFlagsValue flObstructionLFRatio : 1;
+ EaxSourceSourceFilterDirtyFlagsValue lOcclusion : 1;
+ EaxSourceSourceFilterDirtyFlagsValue flOcclusionLFRatio : 1;
+ EaxSourceSourceFilterDirtyFlagsValue flOcclusionRoomRatio : 1;
+ EaxSourceSourceFilterDirtyFlagsValue flOcclusionDirectRatio : 1;
+ EaxSourceSourceFilterDirtyFlagsValue lExclusion : 1;
+ EaxSourceSourceFilterDirtyFlagsValue flExclusionLFRatio : 1;
+}; // EaxSourceSourceFilterDirtyFlags
+
+
+using EaxSourceSourceMiscDirtyFlagsValue = std::uint_least8_t;
+
+struct EaxSourceSourceMiscDirtyFlags
+{
+ using EaxIsBitFieldStruct = bool;
+
+ EaxSourceSourceMiscDirtyFlagsValue lOutsideVolumeHF : 1;
+ EaxSourceSourceMiscDirtyFlagsValue flDopplerFactor : 1;
+ EaxSourceSourceMiscDirtyFlagsValue flRolloffFactor : 1;
+ EaxSourceSourceMiscDirtyFlagsValue flRoomRolloffFactor : 1;
+ EaxSourceSourceMiscDirtyFlagsValue flAirAbsorptionFactor : 1;
+ EaxSourceSourceMiscDirtyFlagsValue ulFlags : 1;
+ EaxSourceSourceMiscDirtyFlagsValue flMacroFXFactor : 1;
+ EaxSourceSourceMiscDirtyFlagsValue speaker_levels : 1;
+}; // EaxSourceSourceMiscDirtyFlags
+
+
+using EaxSourceSendDirtyFlagsValue = std::uint_least8_t;
+
+struct EaxSourceSendDirtyFlags
+{
+ using EaxIsBitFieldStruct = bool;
+
+ EaxSourceSendDirtyFlagsValue lSend : 1;
+ EaxSourceSendDirtyFlagsValue lSendHF : 1;
+ EaxSourceSendDirtyFlagsValue lOcclusion : 1;
+ EaxSourceSendDirtyFlagsValue flOcclusionLFRatio : 1;
+ EaxSourceSendDirtyFlagsValue flOcclusionRoomRatio : 1;
+ EaxSourceSendDirtyFlagsValue flOcclusionDirectRatio : 1;
+ EaxSourceSendDirtyFlagsValue lExclusion : 1;
+ EaxSourceSendDirtyFlagsValue flExclusionLFRatio : 1;
+}; // EaxSourceSendDirtyFlags
+
+
+struct EaxSourceSendsDirtyFlags
+{
+ using EaxIsBitFieldStruct = bool;
+
+ EaxSourceSendDirtyFlags sends[EAX_MAX_FXSLOTS];
+}; // EaxSourceSendsDirtyFlags
+#endif // ALSOFT_EAX
+
struct ALsource {
/** Source properties. */
float Pitch{1.0f};
@@ -135,6 +212,618 @@ struct ALsource {
ALsource& operator=(const ALsource&) = delete;
DISABLE_ALLOC()
+
+#if ALSOFT_EAX
+public:
+ void eax_initialize(
+ const EaxSourceInitParam& param) noexcept;
+
+ void eax_uninitialize();
+
+
+ void eax_dispatch(
+ const EaxEaxCall& eax_call);
+
+
+ void eax_update_filters();
+
+ void eax_update(
+ EaxContextSharedDirtyFlags dirty_flags);
+
+
+ static ALsource* eax_lookup_source(
+ ALCcontext& al_context,
+ ALuint source_id) noexcept;
+
+
+private:
+ static constexpr auto eax_max_speakers = 9;
+
+
+ using EaxActiveFxSlots = std::array<bool, EAX_MAX_FXSLOTS>;
+ using EaxSpeakerLevels = std::array<long, eax_max_speakers>;
+
+
+ struct Eax
+ {
+ using Sends = std::array<EAXSOURCEALLSENDPROPERTIES, EAX_MAX_FXSLOTS>;
+
+ EAX50ACTIVEFXSLOTS active_fx_slots{};
+ EAX50SOURCEPROPERTIES source{};
+ Sends sends{};
+ EaxSpeakerLevels speaker_levels{};
+ }; // Eax
+
+
+ bool eax_uses_primary_id_{};
+ bool eax_has_active_fx_slots_{};
+ bool eax_are_active_fx_slots_dirty_{};
+
+ ALCcontext* eax_al_context_{};
+ ALfilter* eax_al_filter_{};
+
+ Eax eax_{};
+ Eax eax_d_{};
+ EaxActiveFxSlots eax_active_fx_slots_{};
+
+ EaxSourceSendsDirtyFlags eax_sends_dirty_flags_{};
+ EaxSourceSourceFilterDirtyFlags eax_source_dirty_filter_flags_{};
+ EaxSourceSourceMiscDirtyFlags eax_source_dirty_misc_flags_{};
+
+
+ [[noreturn]]
+ static void eax_fail(
+ const char* message);
+
+
+ static void eax_validate_init_param(
+ const EaxSourceInitParam& param);
+
+ void eax_copy_init_param(
+ const EaxSourceInitParam& param);
+
+
+ void eax_set_source_defaults();
+
+ void eax_set_active_fx_slots_defaults();
+
+ void eax_set_send_defaults(
+ EAXSOURCEALLSENDPROPERTIES& eax_send);
+
+ void eax_set_sends_defaults();
+
+ void eax_set_speaker_levels_defaults();
+
+ void eax_set_defaults();
+
+
+ static float eax_calculate_dst_occlusion_mb(
+ long src_occlusion_mb,
+ float path_ratio,
+ float lf_ratio) noexcept;
+
+ EaxAlLowPassParam eax_create_direct_filter_param() const noexcept;
+
+ EaxAlLowPassParam eax_create_room_filter_param(
+ const ALeffectslot& fx_slot,
+ const EAXSOURCEALLSENDPROPERTIES& send) const noexcept;
+
+ void eax_set_al_filter_parameters(
+ const EaxAlLowPassParam& al_low_pass_param) const noexcept;
+
+ void eax_set_fx_slots();
+
+ void eax_initialize_fx_slots();
+
+ void eax_update_direct_filter_internal();
+
+ void eax_update_room_filters_internal();
+
+ void eax_update_filters_internal();
+
+ void eax_update_primary_fx_slot_id();
+
+
+ void eax_defer_active_fx_slots(
+ const EaxEaxCall& eax_call);
+
+
+ static const char* eax_get_exclusion_name() noexcept;
+
+ static const char* eax_get_exclusion_lf_ratio_name() noexcept;
+
+
+ static const char* eax_get_occlusion_name() noexcept;
+
+ static const char* eax_get_occlusion_lf_ratio_name() noexcept;
+
+ static const char* eax_get_occlusion_direct_ratio_name() noexcept;
+
+ static const char* eax_get_occlusion_room_ratio_name() noexcept;
+
+
+ static void eax_validate_send_receiving_fx_slot_guid(
+ const GUID& guidReceivingFXSlotID);
+
+ static void eax_validate_send_send(
+ long lSend);
+
+ static void eax_validate_send_send_hf(
+ long lSendHF);
+
+ static void eax_validate_send_occlusion(
+ long lOcclusion);
+
+ static void eax_validate_send_occlusion_lf_ratio(
+ float flOcclusionLFRatio);
+
+ static void eax_validate_send_occlusion_room_ratio(
+ float flOcclusionRoomRatio);
+
+ static void eax_validate_send_occlusion_direct_ratio(
+ float flOcclusionDirectRatio);
+
+ static void eax_validate_send_exclusion(
+ long lExclusion);
+
+ static void eax_validate_send_exclusion_lf_ratio(
+ float flExclusionLFRatio);
+
+ static void eax_validate_send(
+ const EAXSOURCESENDPROPERTIES& all);
+
+ static void eax_validate_send_exclusion_all(
+ const EAXSOURCEEXCLUSIONSENDPROPERTIES& all);
+
+ static void eax_validate_send_occlusion_all(
+ const EAXSOURCEOCCLUSIONSENDPROPERTIES& all);
+
+ static void eax_validate_send_all(
+ const EAXSOURCEALLSENDPROPERTIES& all);
+
+
+ static EaxFxSlotIndexValue eax_get_send_index(
+ const GUID& send_guid);
+
+
+ void eax_defer_send_send(
+ long lSend,
+ EaxFxSlotIndexValue index);
+
+ void eax_defer_send_send_hf(
+ long lSendHF,
+ EaxFxSlotIndexValue index);
+
+ void eax_defer_send_occlusion(
+ long lOcclusion,
+ EaxFxSlotIndexValue index);
+
+ void eax_defer_send_occlusion_lf_ratio(
+ float flOcclusionLFRatio,
+ EaxFxSlotIndexValue index);
+
+ void eax_defer_send_occlusion_room_ratio(
+ float flOcclusionRoomRatio,
+ EaxFxSlotIndexValue index);
+
+ void eax_defer_send_occlusion_direct_ratio(
+ float flOcclusionDirectRatio,
+ EaxFxSlotIndexValue index);
+
+ void eax_defer_send_exclusion(
+ long lExclusion,
+ EaxFxSlotIndexValue index);
+
+ void eax_defer_send_exclusion_lf_ratio(
+ float flExclusionLFRatio,
+ EaxFxSlotIndexValue index);
+
+ void eax_defer_send(
+ const EAXSOURCESENDPROPERTIES& all,
+ EaxFxSlotIndexValue index);
+
+ void eax_defer_send_exclusion_all(
+ const EAXSOURCEEXCLUSIONSENDPROPERTIES& all,
+ EaxFxSlotIndexValue index);
+
+ void eax_defer_send_occlusion_all(
+ const EAXSOURCEOCCLUSIONSENDPROPERTIES& all,
+ EaxFxSlotIndexValue index);
+
+ void eax_defer_send_all(
+ const EAXSOURCEALLSENDPROPERTIES& all,
+ EaxFxSlotIndexValue index);
+
+
+ void eax_defer_send(
+ const EaxEaxCall& eax_call);
+
+ void eax_defer_send_exclusion_all(
+ const EaxEaxCall& eax_call);
+
+ void eax_defer_send_occlusion_all(
+ const EaxEaxCall& eax_call);
+
+ void eax_defer_send_all(
+ const EaxEaxCall& eax_call);
+
+
+ static void eax_validate_source_direct(
+ long direct);
+
+ static void eax_validate_source_direct_hf(
+ long direct_hf);
+
+ static void eax_validate_source_room(
+ long room);
+
+ static void eax_validate_source_room_hf(
+ long room_hf);
+
+ static void eax_validate_source_obstruction(
+ long obstruction);
+
+ static void eax_validate_source_obstruction_lf_ratio(
+ float obstruction_lf_ratio);
+
+ static void eax_validate_source_occlusion(
+ long occlusion);
+
+ static void eax_validate_source_occlusion_lf_ratio(
+ float occlusion_lf_ratio);
+
+ static void eax_validate_source_occlusion_room_ratio(
+ float occlusion_room_ratio);
+
+ static void eax_validate_source_occlusion_direct_ratio(
+ float occlusion_direct_ratio);
+
+ static void eax_validate_source_exclusion(
+ long exclusion);
+
+ static void eax_validate_source_exclusion_lf_ratio(
+ float exclusion_lf_ratio);
+
+ static void eax_validate_source_outside_volume_hf(
+ long outside_volume_hf);
+
+ static void eax_validate_source_doppler_factor(
+ float doppler_factor);
+
+ static void eax_validate_source_rolloff_factor(
+ float rolloff_factor);
+
+ static void eax_validate_source_room_rolloff_factor(
+ float room_rolloff_factor);
+
+ static void eax_validate_source_air_absorption_factor(
+ float air_absorption_factor);
+
+ static void eax_validate_source_flags(
+ unsigned long flags,
+ int eax_version);
+
+ static void eax_validate_source_macro_fx_factor(
+ float macro_fx_factor);
+
+ static void eax_validate_source_2d_all(
+ const EAXSOURCE2DPROPERTIES& all,
+ int eax_version);
+
+ static void eax_validate_source_obstruction_all(
+ const EAXOBSTRUCTIONPROPERTIES& all);
+
+ static void eax_validate_source_exclusion_all(
+ const EAXEXCLUSIONPROPERTIES& all);
+
+ static void eax_validate_source_occlusion_all(
+ const EAXOCCLUSIONPROPERTIES& all);
+
+ static void eax_validate_source_all(
+ const EAX20BUFFERPROPERTIES& all,
+ int eax_version);
+
+ static void eax_validate_source_all(
+ const EAX30SOURCEPROPERTIES& all,
+ int eax_version);
+
+ static void eax_validate_source_all(
+ const EAX50SOURCEPROPERTIES& all,
+ int eax_version);
+
+ static void eax_validate_source_speaker_id(
+ long speaker_id);
+
+ static void eax_validate_source_speaker_level(
+ long speaker_level);
+
+ static void eax_validate_source_speaker_level_all(
+ const EAXSPEAKERLEVELPROPERTIES& all);
+
+
+ void eax_defer_source_direct(
+ long lDirect);
+
+ void eax_defer_source_direct_hf(
+ long lDirectHF);
+
+ void eax_defer_source_room(
+ long lRoom);
+
+ void eax_defer_source_room_hf(
+ long lRoomHF);
+
+ void eax_defer_source_obstruction(
+ long lObstruction);
+
+ void eax_defer_source_obstruction_lf_ratio(
+ float flObstructionLFRatio);
+
+ void eax_defer_source_occlusion(
+ long lOcclusion);
+
+ void eax_defer_source_occlusion_lf_ratio(
+ float flOcclusionLFRatio);
+
+ void eax_defer_source_occlusion_room_ratio(
+ float flOcclusionRoomRatio);
+
+ void eax_defer_source_occlusion_direct_ratio(
+ float flOcclusionDirectRatio);
+
+ void eax_defer_source_exclusion(
+ long lExclusion);
+
+ void eax_defer_source_exclusion_lf_ratio(
+ float flExclusionLFRatio);
+
+ void eax_defer_source_outside_volume_hf(
+ long lOutsideVolumeHF);
+
+ void eax_defer_source_doppler_factor(
+ float flDopplerFactor);
+
+ void eax_defer_source_rolloff_factor(
+ float flRolloffFactor);
+
+ void eax_defer_source_room_rolloff_factor(
+ float flRoomRolloffFactor);
+
+ void eax_defer_source_air_absorption_factor(
+ float flAirAbsorptionFactor);
+
+ void eax_defer_source_flags(
+ unsigned long ulFlags);
+
+ void eax_defer_source_macro_fx_factor(
+ float flMacroFXFactor);
+
+ void eax_defer_source_2d_all(
+ const EAXSOURCE2DPROPERTIES& all);
+
+ void eax_defer_source_obstruction_all(
+ const EAXOBSTRUCTIONPROPERTIES& all);
+
+ void eax_defer_source_exclusion_all(
+ const EAXEXCLUSIONPROPERTIES& all);
+
+ void eax_defer_source_occlusion_all(
+ const EAXOCCLUSIONPROPERTIES& all);
+
+ void eax_defer_source_all(
+ const EAX20BUFFERPROPERTIES& all);
+
+ void eax_defer_source_all(
+ const EAX30SOURCEPROPERTIES& all);
+
+ void eax_defer_source_all(
+ const EAX50SOURCEPROPERTIES& all);
+
+ void eax_defer_source_speaker_level_all(
+ const EAXSPEAKERLEVELPROPERTIES& all);
+
+
+ void eax_defer_source_direct(
+ const EaxEaxCall& eax_call);
+
+ void eax_defer_source_direct_hf(
+ const EaxEaxCall& eax_call);
+
+ void eax_defer_source_room(
+ const EaxEaxCall& eax_call);
+
+ void eax_defer_source_room_hf(
+ const EaxEaxCall& eax_call);
+
+ void eax_defer_source_obstruction(
+ const EaxEaxCall& eax_call);
+
+ void eax_defer_source_obstruction_lf_ratio(
+ const EaxEaxCall& eax_call);
+
+ void eax_defer_source_occlusion(
+ const EaxEaxCall& eax_call);
+
+ void eax_defer_source_occlusion_lf_ratio(
+ const EaxEaxCall& eax_call);
+
+ void eax_defer_source_occlusion_room_ratio(
+ const EaxEaxCall& eax_call);
+
+ void eax_defer_source_occlusion_direct_ratio(
+ const EaxEaxCall& eax_call);
+
+ void eax_defer_source_exclusion(
+ const EaxEaxCall& eax_call);
+
+ void eax_defer_source_exclusion_lf_ratio(
+ const EaxEaxCall& eax_call);
+
+ void eax_defer_source_outside_volume_hf(
+ const EaxEaxCall& eax_call);
+
+ void eax_defer_source_doppler_factor(
+ const EaxEaxCall& eax_call);
+
+ void eax_defer_source_rolloff_factor(
+ const EaxEaxCall& eax_call);
+
+ void eax_defer_source_room_rolloff_factor(
+ const EaxEaxCall& eax_call);
+
+ void eax_defer_source_air_absorption_factor(
+ const EaxEaxCall& eax_call);
+
+ void eax_defer_source_flags(
+ const EaxEaxCall& eax_call);
+
+ void eax_defer_source_macro_fx_factor(
+ const EaxEaxCall& eax_call);
+
+ void eax_defer_source_2d_all(
+ const EaxEaxCall& eax_call);
+
+ void eax_defer_source_obstruction_all(
+ const EaxEaxCall& eax_call);
+
+ void eax_defer_source_exclusion_all(
+ const EaxEaxCall& eax_call);
+
+ void eax_defer_source_occlusion_all(
+ const EaxEaxCall& eax_call);
+
+ void eax_defer_source_all(
+ const EaxEaxCall& eax_call);
+
+ void eax_defer_source_speaker_level_all(
+ const EaxEaxCall& eax_call);
+
+
+ void eax_set_outside_volume_hf();
+
+ void eax_set_doppler_factor();
+
+ void eax_set_rolloff_factor();
+
+ void eax_set_room_rolloff_factor();
+
+ void eax_set_air_absorption_factor();
+
+
+ void eax_set_direct_hf_auto_flag();
+
+ void eax_set_room_auto_flag();
+
+ void eax_set_room_hf_auto_flag();
+
+ void eax_set_flags();
+
+
+ void eax_set_macro_fx_factor();
+
+ void eax_set_speaker_levels();
+
+
+ void eax_apply_deferred();
+
+ void eax_set(
+ const EaxEaxCall& eax_call);
+
+
+ static const GUID& eax_get_send_fx_slot_guid(
+ int eax_version,
+ EaxFxSlotIndexValue fx_slot_index);
+
+ static void eax_copy_send(
+ const EAXSOURCEALLSENDPROPERTIES& src_send,
+ EAXSOURCESENDPROPERTIES& dst_send);
+
+ static void eax_copy_send(
+ const EAXSOURCEALLSENDPROPERTIES& src_send,
+ EAXSOURCEALLSENDPROPERTIES& dst_send);
+
+ static void eax_copy_send(
+ const EAXSOURCEALLSENDPROPERTIES& src_send,
+ EAXSOURCEOCCLUSIONSENDPROPERTIES& dst_send);
+
+ static void eax_copy_send(
+ const EAXSOURCEALLSENDPROPERTIES& src_send,
+ EAXSOURCEEXCLUSIONSENDPROPERTIES& dst_send);
+
+ template<
+ typename TException,
+ typename TSrcSend
+ >
+ void eax_api_get_send_properties(
+ const EaxEaxCall& eax_call) const
+ {
+ const auto eax_version = eax_call.get_version();
+ const auto dst_sends = eax_call.get_values<TException, TSrcSend>();
+ const auto send_count = dst_sends.size();
+
+ for (auto fx_slot_index = EaxFxSlotIndexValue{}; fx_slot_index < send_count; ++fx_slot_index)
+ {
+ auto& dst_send = dst_sends[fx_slot_index];
+ const auto& src_send = eax_.sends[fx_slot_index];
+
+ eax_copy_send(src_send, dst_send);
+
+ dst_send.guidReceivingFXSlotID = eax_get_send_fx_slot_guid(eax_version, fx_slot_index);
+ }
+ }
+
+
+ void eax_api_get_source_all_v2(
+ const EaxEaxCall& eax_call);
+
+ void eax_api_get_source_all_v3(
+ const EaxEaxCall& eax_call);
+
+ void eax_api_get_source_all_v5(
+ const EaxEaxCall& eax_call);
+
+ void eax_api_get_source_all(
+ const EaxEaxCall& eax_call);
+
+ void eax_api_get_source_all_obstruction(
+ const EaxEaxCall& eax_call);
+
+ void eax_api_get_source_all_occlusion(
+ const EaxEaxCall& eax_call);
+
+ void eax_api_get_source_all_exclusion(
+ const EaxEaxCall& eax_call);
+
+ void eax_api_get_source_active_fx_slot_id(
+ const EaxEaxCall& eax_call);
+
+ void eax_api_get_source_all_2d(
+ const EaxEaxCall& eax_call);
+
+ void eax_api_get_source_speaker_level_all(
+ const EaxEaxCall& eax_call);
+
+ void eax_get(
+ const EaxEaxCall& eax_call);
+
+
+ // `alSourcei`
+ void eax_al_source_i(
+ ALenum param,
+ ALint value);
+
+ // `alSourcef`
+ void eax_al_source_f(
+ ALenum param,
+ ALfloat value);
+
+ // `alSource3i`
+ void eax_al_source_3i(
+ ALenum param,
+ ALint value1,
+ ALint value2,
+ ALint value3);
+#endif // ALSOFT_EAX
};
void UpdateAllSourceProps(ALCcontext *context);
diff --git a/al/state.cpp b/al/state.cpp
index 10c7bc74..0ec0e280 100644
--- a/al/state.cpp
+++ b/al/state.cpp
@@ -46,6 +46,13 @@
#include "opthelpers.h"
#include "strutils.h"
+#if ALSOFT_EAX
+#include "alc/device.h"
+
+#include "eax_globals.h"
+#include "eax_x_ram.h"
+#endif // ALSOFT_EAX
+
namespace {
@@ -427,6 +434,41 @@ START_API_FUNC
value = static_cast<int>(ResamplerDefault);
break;
+#if ALSOFT_EAX
+
+#define EAX_ERROR "[alGetInteger] EAX not enabled."
+
+ case AL_EAX_RAM_SIZE:
+ if (eax_g_is_enabled)
+ {
+ value = eax_x_ram_max_size;
+ }
+ else
+ {
+ context->setError(AL_INVALID_VALUE, EAX_ERROR);
+ }
+
+ break;
+
+ case AL_EAX_RAM_FREE:
+ if (eax_g_is_enabled)
+ {
+ auto device = context->mALDevice.get();
+ std::lock_guard<std::mutex> device_lock{device->BufferLock};
+
+ value = device->eax_x_ram_free_size;
+ }
+ else
+ {
+ context->setError(AL_INVALID_VALUE, EAX_ERROR);
+ }
+
+ break;
+
+#undef EAX_ERROR
+
+#endif // ALSOFT_EAX
+
default:
context->setError(AL_INVALID_VALUE, "Invalid integer property 0x%04x", pname);
}
diff --git a/alc/alc.cpp b/alc/alc.cpp
index 04dbcab3..b9f322a3 100644
--- a/alc/alc.cpp
+++ b/alc/alc.cpp
@@ -155,6 +155,11 @@
#include "backends/wave.h"
#endif
+#if ALSOFT_EAX
+#include "al/eax_globals.h"
+#include "al/eax_x_ram.h"
+#endif // ALSOFT_EAX
+
FILE *gLogFile{stderr};
#ifdef _DEBUG
@@ -875,6 +880,14 @@ constexpr struct {
DECL(AL_SUPER_STEREO_WIDTH_SOFT),
DECL(AL_STOP_SOURCES_ON_DISCONNECT_SOFT),
+
+#if ALSOFT_EAX
+ DECL(AL_EAX_RAM_SIZE),
+ DECL(AL_EAX_RAM_FREE),
+ DECL(AL_STORAGE_AUTOMATIC),
+ DECL(AL_STORAGE_HARDWARE),
+ DECL(AL_STORAGE_ACCESSIBLE),
+#endif // ALSOFT_EAX
};
#undef DECL
@@ -1244,6 +1257,28 @@ void alc_initconfig(void)
auto defrevopt = al::getenv("ALSOFT_DEFAULT_REVERB");
if(defrevopt || (defrevopt=ConfigValueStr(nullptr, nullptr, "default-reverb")))
LoadReverbPreset(defrevopt->c_str(), &ALCcontext::sDefaultEffect);
+
+#if ALSOFT_EAX
+ {
+ constexpr auto eax_block_name = "eax";
+
+ const auto eax_enable_opt = ConfigValueBool(nullptr, eax_block_name, "enable");
+
+ if (eax_enable_opt)
+ {
+ eax_g_is_enabled = *eax_enable_opt;
+
+ if (!eax_g_is_enabled)
+ {
+ TRACE("%s\n", "EAX disabled by a configuration.");
+ }
+ }
+ else
+ {
+ eax_g_is_enabled = true;
+ }
+ }
+#endif // ALSOFT_EAX
}
#define DO_INITCONFIG() std::call_once(alc_config_once, [](){alc_initconfig();})
@@ -2929,6 +2964,7 @@ START_API_FUNC
return enm.value;
}
}
+
return 0;
}
END_API_FUNC
@@ -3038,6 +3074,7 @@ START_API_FUNC
alcSetError(nullptr, ALC_INVALID_CONTEXT);
return;
}
+
/* Hold a reference to this context so it remains valid until the ListLock
* is released.
*/
@@ -3186,6 +3223,13 @@ START_API_FUNC
device->AuxiliaryEffectSlotMax = 64;
device->NumAuxSends = DEFAULT_SENDS;
+#if ALSOFT_EAX
+ if (eax_g_is_enabled)
+ {
+ device->NumAuxSends = EAX_MAX_FXSLOTS;
+ }
+#endif // ALSOFT_EAX
+
try {
auto backend = PlaybackFactory->createBackend(device.get(), BackendType::Playback);
std::lock_guard<std::recursive_mutex> _{ListLock};
diff --git a/alc/context.cpp b/alc/context.cpp
index c2d3e351..d5fd94b2 100644
--- a/alc/context.cpp
+++ b/alc/context.cpp
@@ -29,6 +29,14 @@
#include "ringbuffer.h"
#include "vecmat.h"
+#if ALSOFT_EAX
+#include <cassert>
+#include <cstring>
+
+#include "alstring.h"
+#include "al/eax_exception.h"
+#include "al/eax_globals.h"
+#endif // ALSOFT_EAX
namespace {
@@ -121,6 +129,10 @@ ALCcontext::~ALCcontext()
mSourceList.clear();
mNumSources = 0;
+#if ALSOFT_EAX
+ eax_uninitialize();
+#endif // ALSOFT_EAX
+
mDefaultSlot = nullptr;
count = std::accumulate(mEffectSlotList.cbegin(), mEffectSlotList.cend(), size_t{0u},
[](size_t cur, const EffectSlotSubList &sublist) noexcept -> size_t
@@ -160,6 +172,9 @@ void ALCcontext::init()
mExtensionList = alExtList;
+#if ALSOFT_EAX
+ eax_initialize_extensions();
+#endif // ALSOFT_EAX
mParams.Position = alu::Vector{0.0f, 0.0f, 0.0f, 1.0f};
mParams.Matrix = alu::Matrix::Identity();
@@ -258,3 +273,1216 @@ void ALCcontext::processUpdates()
mHoldUpdates.store(false, std::memory_order_release);
}
}
+
+#if ALSOFT_EAX
+namespace
+{
+
+
+class ContextException :
+ public EaxException
+{
+public:
+ explicit ContextException(
+ const char* message)
+ :
+ EaxException{"EAX_CONTEXT", message}
+ {
+ }
+}; // ContextException
+
+
+} // namespace
+
+
+ALCcontext::SourceListIterator::SourceListIterator(
+ SourceList& sources,
+ SourceListIteratorBeginTag) noexcept
+ :
+ sub_list_iterator_{sources.begin()},
+ sub_list_end_iterator_{sources.end()},
+ sub_list_item_index_{}
+{
+ // Search for first non-free item.
+ //
+ while (true)
+ {
+ if (sub_list_iterator_ == sub_list_end_iterator_)
+ {
+ // End of list.
+
+ sub_list_item_index_ = 0;
+ return;
+ }
+
+ if ((~sub_list_iterator_->FreeMask) == 0_u64)
+ {
+ // All sub-list's items are free.
+
+ ++sub_list_iterator_;
+ sub_list_item_index_ = 0;
+ continue;
+ }
+
+ if (sub_list_item_index_ >= 64_u64)
+ {
+ // Sub-list item's index beyond the last one.
+
+ ++sub_list_iterator_;
+ sub_list_item_index_ = 0;
+ continue;
+ }
+
+ if ((sub_list_iterator_->FreeMask & (1_u64 << sub_list_item_index_)) == 0_u64)
+ {
+ // Found non-free item.
+
+ break;
+ }
+
+ sub_list_item_index_ += 1;
+ }
+}
+
+ALCcontext::SourceListIterator::SourceListIterator(
+ SourceList& sources,
+ SourceListIteratorEndTag) noexcept
+ :
+ sub_list_iterator_{sources.end()},
+ sub_list_end_iterator_{sources.end()},
+ sub_list_item_index_{}
+{
+}
+
+ALCcontext::SourceListIterator::SourceListIterator(
+ const SourceListIterator& rhs)
+ :
+ sub_list_iterator_{rhs.sub_list_iterator_},
+ sub_list_end_iterator_{rhs.sub_list_end_iterator_},
+ sub_list_item_index_{rhs.sub_list_item_index_}
+{
+}
+
+ALCcontext::SourceListIterator& ALCcontext::SourceListIterator::operator++()
+{
+ while (true)
+ {
+ if (sub_list_iterator_ == sub_list_end_iterator_)
+ {
+ // End of list.
+
+ sub_list_item_index_ = 0;
+ break;
+ }
+
+ if ((~sub_list_iterator_->FreeMask) == 0_u64)
+ {
+ // All sub-list's items are free.
+
+ ++sub_list_iterator_;
+ sub_list_item_index_ = 0;
+ continue;
+ }
+
+ sub_list_item_index_ += 1;
+
+ if (sub_list_item_index_ >= 64_u64)
+ {
+ // Sub-list item's index beyond the last one.
+
+ ++sub_list_iterator_;
+ sub_list_item_index_ = 0;
+ continue;
+ }
+
+ if ((sub_list_iterator_->FreeMask & (1_u64 << sub_list_item_index_)) == 0_u64)
+ {
+ // Found non-free item.
+
+ break;
+ }
+ }
+
+ return *this;
+}
+
+ALsource& ALCcontext::SourceListIterator::operator*() noexcept
+{
+ assert(sub_list_iterator_ != sub_list_end_iterator_);
+ return (*sub_list_iterator_).Sources[sub_list_item_index_];
+}
+
+bool ALCcontext::SourceListIterator::operator==(
+ const SourceListIterator& rhs) const noexcept
+{
+ return
+ sub_list_iterator_ == rhs.sub_list_iterator_ &&
+ sub_list_end_iterator_ == rhs.sub_list_end_iterator_ &&
+ sub_list_item_index_ == rhs.sub_list_item_index_;
+}
+
+bool ALCcontext::SourceListIterator::operator!=(
+ const SourceListIterator& rhs) const noexcept
+{
+ return !((*this) == rhs);
+}
+
+
+ALCcontext::SourceListEnumerator::SourceListEnumerator(
+ ALCcontext::SourceList& sources) noexcept
+ :
+ sources_{sources}
+{
+}
+
+ALCcontext::SourceListIterator ALCcontext::SourceListEnumerator::begin() noexcept
+{
+ return SourceListIterator{sources_, SourceListIteratorBeginTag{}};
+}
+
+ALCcontext::SourceListIterator ALCcontext::SourceListEnumerator::end() noexcept
+{
+ return SourceListIterator{sources_, SourceListIteratorEndTag{}};
+}
+
+
+bool ALCcontext::has_eax() const noexcept
+{
+ return eax_is_initialized_;
+}
+
+bool ALCcontext::eax_is_capable() const noexcept
+{
+ return
+ eax_has_enough_aux_sends() &&
+ eax_has_eax_reverb_effect();
+}
+
+void ALCcontext::eax_uninitialize() noexcept
+{
+ if (!eax_is_initialized_)
+ {
+ return;
+ }
+
+ eax_is_initialized_ = true;
+ eax_is_tried_ = false;
+
+ eax_fx_slots_.uninitialize();
+ eax_al_filter_ = nullptr;
+}
+
+void ALCcontext::eax_initialize_source(
+ ALsource& al_source) noexcept
+try
+{
+ auto param = EaxSourceInitParam{};
+ param.al_context = this;
+ param.al_filter = eax_al_filter_.get();
+
+ al_source.eax_initialize(param);
+}
+catch (...)
+{
+ eax_log_exception("Failed to initialize a source.");
+}
+
+ALenum ALCcontext::eax_eax_set(
+ const GUID* property_set_id,
+ ALuint property_id,
+ ALuint property_source_id,
+ ALvoid* property_value,
+ ALuint property_value_size)
+{
+ eax_initialize();
+
+ const auto eax_call = create_eax_call(
+ false,
+ property_set_id,
+ property_id,
+ property_source_id,
+ property_value,
+ property_value_size
+ );
+
+ switch (eax_call.get_property_set_id())
+ {
+ case EaxEaxCallPropertySetId::context:
+ eax_set(eax_call);
+ break;
+
+ case EaxEaxCallPropertySetId::fx_slot:
+ case EaxEaxCallPropertySetId::fx_slot_effect:
+ eax_dispatch_fx_slot(eax_call);
+ break;
+
+ case EaxEaxCallPropertySetId::source:
+ eax_dispatch_source(eax_call);
+ break;
+
+ default:
+ eax_fail("Unsupported property set id.");
+ }
+
+ return AL_NO_ERROR;
+}
+
+ALenum ALCcontext::eax_eax_get(
+ const GUID* property_set_id,
+ ALuint property_id,
+ ALuint property_source_id,
+ ALvoid* property_value,
+ ALuint property_value_size)
+{
+ eax_initialize();
+
+ const auto eax_call = create_eax_call(
+ true,
+ property_set_id,
+ property_id,
+ property_source_id,
+ property_value,
+ property_value_size
+ );
+
+ switch (eax_call.get_property_set_id())
+ {
+ case EaxEaxCallPropertySetId::context:
+ eax_get(eax_call);
+ break;
+
+ case EaxEaxCallPropertySetId::fx_slot:
+ case EaxEaxCallPropertySetId::fx_slot_effect:
+ eax_dispatch_fx_slot(eax_call);
+ break;
+
+ case EaxEaxCallPropertySetId::source:
+ eax_dispatch_source(eax_call);
+ break;
+
+ default:
+ eax_fail("Unsupported property set id.");
+ }
+
+ return AL_NO_ERROR;
+}
+
+void ALCcontext::eax_update_filters()
+{
+ for (auto& source : SourceListEnumerator{mSourceList})
+ {
+ source.eax_update_filters();
+ }
+}
+
+void ALCcontext::eax_set_last_error() noexcept
+{
+ eax_last_error_ = EAXERR_INVALID_OPERATION;
+}
+
+float ALCcontext::eax_get_max_filter_gain() const noexcept
+{
+ return eax_max_filter_gain_;
+}
+
+float ALCcontext::eax_get_air_absorption_factor() const noexcept
+{
+ return eax_air_absorption_factor_;
+}
+
+EaxFxSlotIndex ALCcontext::eax_get_previous_primary_fx_slot_index() const noexcept
+{
+ return eax_previous_primary_fx_slot_index_;
+}
+
+EaxFxSlotIndex ALCcontext::eax_get_primary_fx_slot_index() const noexcept
+{
+ return eax_primary_fx_slot_index_;
+}
+
+const ALeffectslot& ALCcontext::eax_get_fx_slot(
+ EaxFxSlotIndexValue fx_slot_index) const
+{
+ return eax_fx_slots_.get(fx_slot_index);
+}
+
+ALeffectslot& ALCcontext::eax_get_fx_slot(
+ EaxFxSlotIndexValue fx_slot_index)
+{
+ return eax_fx_slots_.get(fx_slot_index);
+}
+
+[[noreturn]]
+void ALCcontext::eax_fail(
+ const char* message)
+{
+ throw ContextException{message};
+}
+
+void ALCcontext::eax_initialize_extensions()
+{
+ if (!eax_g_is_enabled)
+ {
+ return;
+ }
+
+ const auto string_max_capacity =
+ std::strlen(mExtensionList) + 1 +
+ std::strlen(eax_v2_0_ext_name) + 1 +
+ std::strlen(eax_v3_0_ext_name) + 1 +
+ std::strlen(eax_v4_0_ext_name) + 1 +
+ std::strlen(eax_v5_0_ext_name) + 1 +
+ std::strlen(eax_x_ram_ext_name) + 1 +
+ 0;
+
+ eax_extension_list_.reserve(string_max_capacity);
+
+ if (eax_is_capable())
+ {
+ eax_extension_list_ += eax_v2_0_ext_name;
+ eax_extension_list_ += ' ';
+
+ eax_extension_list_ += eax_v3_0_ext_name;
+ eax_extension_list_ += ' ';
+
+ eax_extension_list_ += eax_v4_0_ext_name;
+ eax_extension_list_ += ' ';
+
+ eax_extension_list_ += eax_v5_0_ext_name;
+ eax_extension_list_ += ' ';
+ }
+
+ eax_extension_list_ += eax_x_ram_ext_name;
+ eax_extension_list_ += ' ';
+
+ eax_extension_list_ += mExtensionList;
+ mExtensionList = eax_extension_list_.c_str();
+}
+
+void ALCcontext::eax_initialize()
+{
+ if (eax_is_initialized_)
+ {
+ return;
+ }
+
+ if (eax_is_tried_)
+ {
+ eax_fail("No EAX.");
+ }
+
+ eax_is_tried_ = true;
+
+ if (!eax_g_is_enabled)
+ {
+ eax_fail("EAX disabled by a configuration.");
+ }
+
+ eax_ensure_compatibility();
+ eax_initialize_filter_gain();
+ eax_initialize_filter();
+ eax_set_defaults();
+ eax_set_air_absorbtion_hf();
+ eax_initialize_fx_slots();
+ eax_initialize_sources();
+
+ eax_is_initialized_ = true;
+}
+
+bool ALCcontext::eax_has_no_default_effect_slot() const noexcept
+{
+ return mDefaultSlot == nullptr;
+}
+
+void ALCcontext::eax_ensure_no_default_effect_slot() const
+{
+ if (!eax_has_no_default_effect_slot())
+ {
+ eax_fail("There is a default effect slot in the context.");
+ }
+}
+
+bool ALCcontext::eax_has_enough_aux_sends() const noexcept
+{
+ return mALDevice->NumAuxSends >= EAX_MAX_FXSLOTS;
+}
+
+void ALCcontext::eax_ensure_enough_aux_sends() const
+{
+ if (!eax_has_enough_aux_sends())
+ {
+ eax_fail("Not enough aux sends.");
+ }
+}
+
+bool ALCcontext::eax_has_eax_reverb_effect() const noexcept
+{
+ return !DisabledEffects[EAXREVERB_EFFECT];
+}
+
+void ALCcontext::eax_ensure_eax_reverb_effect() const
+{
+ if (!eax_has_eax_reverb_effect())
+ {
+ eax_fail("Disabled EAX Reverb Effect.");
+ }
+}
+
+void ALCcontext::eax_ensure_compatibility()
+{
+ eax_ensure_enough_aux_sends();
+ eax_ensure_eax_reverb_effect();
+}
+
+void ALCcontext::eax_initialize_filter_gain()
+{
+ eax_max_filter_gain_ = level_mb_to_gain(GainMixMax / mGainBoost);
+}
+
+void ALCcontext::eax_set_last_error_defaults() noexcept
+{
+ eax_last_error_ = EAX_OK;
+}
+
+void ALCcontext::eax_set_speaker_config_defaults() noexcept
+{
+ eax_speaker_config_ = HEADPHONES;
+}
+
+void ALCcontext::eax_set_session_defaults() noexcept
+{
+ eax_session_.ulEAXVersion = EAXCONTEXT_MINEAXSESSION;
+ eax_session_.ulMaxActiveSends = EAXCONTEXT_DEFAULTMAXACTIVESENDS;
+}
+
+void ALCcontext::eax_set_context_defaults() noexcept
+{
+ eax_.context.guidPrimaryFXSlotID = EAXCONTEXT_DEFAULTPRIMARYFXSLOTID;
+ eax_.context.flDistanceFactor = EAXCONTEXT_DEFAULTDISTANCEFACTOR;
+ eax_.context.flAirAbsorptionHF = EAXCONTEXT_DEFAULTAIRABSORPTIONHF;
+ eax_.context.flHFReference = EAXCONTEXT_DEFAULTHFREFERENCE;
+}
+
+void ALCcontext::eax_set_defaults() noexcept
+{
+ eax_set_last_error_defaults();
+ eax_set_speaker_config_defaults();
+ eax_set_session_defaults();
+ eax_set_context_defaults();
+
+ eax_d_ = eax_;
+}
+
+void ALCcontext::eax_initialize_filter()
+{
+ eax_al_filter_ = eax_create_al_low_pass_filter(*this);
+
+ if (!eax_al_filter_)
+ {
+ eax_fail("Failed to make a low-pass filter.");
+ }
+}
+
+void ALCcontext::eax_dispatch_fx_slot(
+ const EaxEaxCall& eax_call)
+{
+ auto& fx_slot = eax_get_fx_slot(eax_call.get_fx_slot_index());
+
+ if (fx_slot.eax_dispatch(eax_call))
+ {
+ std::lock_guard<std::mutex> source_lock{mSourceLock};
+
+ eax_update_filters();
+ }
+}
+
+void ALCcontext::eax_dispatch_source(
+ const EaxEaxCall& eax_call)
+{
+ const auto source_id = eax_call.get_property_al_name();
+
+ std::lock_guard<std::mutex> source_lock{mSourceLock};
+
+ const auto source = ALsource::eax_lookup_source(*this, source_id);
+
+ if (!source)
+ {
+ eax_fail("Source not found.");
+ }
+
+ source->eax_dispatch(eax_call);
+}
+
+void ALCcontext::eax_get_primary_fx_slot_id(
+ const EaxEaxCall& eax_call)
+{
+ eax_call.set_value<ContextException>(eax_.context.guidPrimaryFXSlotID);
+}
+
+void ALCcontext::eax_get_distance_factor(
+ const EaxEaxCall& eax_call)
+{
+ eax_call.set_value<ContextException>(eax_.context.flDistanceFactor);
+}
+
+void ALCcontext::eax_get_air_absorption_hf(
+ const EaxEaxCall& eax_call)
+{
+ eax_call.set_value<ContextException>(eax_.context.flAirAbsorptionHF);
+}
+
+void ALCcontext::eax_get_hf_reference(
+ const EaxEaxCall& eax_call)
+{
+ eax_call.set_value<ContextException>(eax_.context.flHFReference);
+}
+
+void ALCcontext::eax_get_last_error(
+ const EaxEaxCall& eax_call)
+{
+ const auto eax_last_error = eax_last_error_;
+ eax_last_error_ = EAX_OK;
+ eax_call.set_value<ContextException>(eax_last_error);
+}
+
+void ALCcontext::eax_get_speaker_config(
+ const EaxEaxCall& eax_call)
+{
+ eax_call.set_value<ContextException>(eax_speaker_config_);
+}
+
+void ALCcontext::eax_get_session(
+ const EaxEaxCall& eax_call)
+{
+ eax_call.set_value<ContextException>(eax_session_);
+}
+
+void ALCcontext::eax_get_macro_fx_factor(
+ const EaxEaxCall& eax_call)
+{
+ eax_call.set_value<ContextException>(eax_.context.flMacroFXFactor);
+}
+
+void ALCcontext::eax_get_context_all(
+ const EaxEaxCall& eax_call)
+{
+ switch (eax_call.get_version())
+ {
+ case 4:
+ eax_call.set_value<ContextException>(static_cast<const EAX40CONTEXTPROPERTIES&>(eax_.context));
+ break;
+
+ case 5:
+ eax_call.set_value<ContextException>(static_cast<const EAX50CONTEXTPROPERTIES&>(eax_.context));
+ break;
+
+ default:
+ eax_fail("Unsupported EAX version.");
+ }
+}
+
+void ALCcontext::eax_get(
+ const EaxEaxCall& eax_call)
+{
+ switch (eax_call.get_property_id())
+ {
+ case EAXCONTEXT_NONE:
+ break;
+
+ case EAXCONTEXT_ALLPARAMETERS:
+ eax_get_context_all(eax_call);
+ break;
+
+ case EAXCONTEXT_PRIMARYFXSLOTID:
+ eax_get_primary_fx_slot_id(eax_call);
+ break;
+
+ case EAXCONTEXT_DISTANCEFACTOR:
+ eax_get_distance_factor(eax_call);
+ break;
+
+ case EAXCONTEXT_AIRABSORPTIONHF:
+ eax_get_air_absorption_hf(eax_call);
+ break;
+
+ case EAXCONTEXT_HFREFERENCE:
+ eax_get_hf_reference(eax_call);
+ break;
+
+ case EAXCONTEXT_LASTERROR:
+ eax_get_last_error(eax_call);
+ break;
+
+ case EAXCONTEXT_SPEAKERCONFIG:
+ eax_get_speaker_config(eax_call);
+ break;
+
+ case EAXCONTEXT_EAXSESSION:
+ eax_get_session(eax_call);
+ break;
+
+ case EAXCONTEXT_MACROFXFACTOR:
+ eax_get_macro_fx_factor(eax_call);
+ break;
+
+ default:
+ eax_fail("Unsupported property id.");
+ }
+}
+
+void ALCcontext::eax_set_primary_fx_slot_id()
+{
+ eax_previous_primary_fx_slot_index_ = eax_primary_fx_slot_index_;
+ eax_primary_fx_slot_index_ = eax_.context.guidPrimaryFXSlotID;
+}
+
+void ALCcontext::eax_set_distance_factor()
+{
+ eax_set_al_listener_meters_per_unit(*this, eax_.context.flDistanceFactor);
+}
+
+void ALCcontext::eax_set_air_absorbtion_hf()
+{
+ eax_air_absorption_factor_ = eax_.context.flAirAbsorptionHF / EAXCONTEXT_DEFAULTAIRABSORPTIONHF;
+}
+
+void ALCcontext::eax_set_hf_reference()
+{
+ // TODO
+}
+
+void ALCcontext::eax_set_macro_fx_factor()
+{
+ // TODO
+}
+
+void ALCcontext::eax_set_context()
+{
+ eax_set_primary_fx_slot_id();
+ eax_set_distance_factor();
+ eax_set_air_absorbtion_hf();
+ eax_set_hf_reference();
+}
+
+void ALCcontext::eax_initialize_fx_slots()
+{
+ eax_fx_slots_.initialize(*this);
+ eax_previous_primary_fx_slot_index_ = eax_.context.guidPrimaryFXSlotID;
+ eax_primary_fx_slot_index_ = eax_.context.guidPrimaryFXSlotID;
+}
+
+void ALCcontext::eax_initialize_sources()
+{
+ std::unique_lock<std::mutex> source_lock{mSourceLock};
+
+ for (auto& source : SourceListEnumerator{mSourceList})
+ {
+ eax_initialize_source(source);
+ }
+}
+
+void ALCcontext::eax_update_sources()
+{
+ std::unique_lock<std::mutex> source_lock{mSourceLock};
+
+ for (auto& source : SourceListEnumerator{mSourceList})
+ {
+ source.eax_update(eax_context_shared_dirty_flags_);
+ }
+}
+
+void ALCcontext::eax_validate_primary_fx_slot_id(
+ const GUID& primary_fx_slot_id)
+{
+ if (primary_fx_slot_id != EAX_NULL_GUID &&
+ primary_fx_slot_id != EAXPROPERTYID_EAX40_FXSlot0 &&
+ primary_fx_slot_id != EAXPROPERTYID_EAX50_FXSlot0 &&
+ primary_fx_slot_id != EAXPROPERTYID_EAX40_FXSlot1 &&
+ primary_fx_slot_id != EAXPROPERTYID_EAX50_FXSlot1 &&
+ primary_fx_slot_id != EAXPROPERTYID_EAX40_FXSlot2 &&
+ primary_fx_slot_id != EAXPROPERTYID_EAX50_FXSlot2 &&
+ primary_fx_slot_id != EAXPROPERTYID_EAX40_FXSlot3 &&
+ primary_fx_slot_id != EAXPROPERTYID_EAX50_FXSlot3)
+ {
+ eax_fail("Unsupported primary FX slot id.");
+ }
+}
+
+void ALCcontext::eax_validate_distance_factor(
+ float distance_factor)
+{
+ eax_validate_range<ContextException>(
+ "Distance Factor",
+ distance_factor,
+ EAXCONTEXT_MINDISTANCEFACTOR,
+ EAXCONTEXT_MAXDISTANCEFACTOR);
+}
+
+void ALCcontext::eax_validate_air_absorption_hf(
+ float air_absorption_hf)
+{
+ eax_validate_range<ContextException>(
+ "Air Absorption HF",
+ air_absorption_hf,
+ EAXCONTEXT_MINAIRABSORPTIONHF,
+ EAXCONTEXT_MAXAIRABSORPTIONHF);
+}
+
+void ALCcontext::eax_validate_hf_reference(
+ float hf_reference)
+{
+ eax_validate_range<ContextException>(
+ "HF Reference",
+ hf_reference,
+ EAXCONTEXT_MINHFREFERENCE,
+ EAXCONTEXT_MAXHFREFERENCE);
+}
+
+void ALCcontext::eax_validate_speaker_config(
+ unsigned long speaker_config)
+{
+ switch (speaker_config)
+ {
+ case HEADPHONES:
+ case SPEAKERS_2:
+ case SPEAKERS_4:
+ case SPEAKERS_5:
+ case SPEAKERS_6:
+ case SPEAKERS_7:
+ break;
+
+ default:
+ eax_fail("Unsupported speaker configuration.");
+ }
+}
+
+void ALCcontext::eax_validate_session_eax_version(
+ unsigned long eax_version)
+{
+ switch (eax_version)
+ {
+ case EAX_40:
+ case EAX_50:
+ break;
+
+ default:
+ eax_fail("Unsupported session EAX version.");
+ }
+}
+
+void ALCcontext::eax_validate_session_max_active_sends(
+ unsigned long max_active_sends)
+{
+ eax_validate_range<ContextException>(
+ "Max Active Sends",
+ max_active_sends,
+ EAXCONTEXT_MINMAXACTIVESENDS,
+ EAXCONTEXT_MAXMAXACTIVESENDS);
+}
+
+void ALCcontext::eax_validate_session(
+ const EAXSESSIONPROPERTIES& eax_session)
+{
+ eax_validate_session_eax_version(eax_session.ulEAXVersion);
+ eax_validate_session_max_active_sends(eax_session.ulMaxActiveSends);
+}
+
+void ALCcontext::eax_validate_macro_fx_factor(
+ float macro_fx_factor)
+{
+ eax_validate_range<ContextException>(
+ "Macro FX Factor",
+ macro_fx_factor,
+ EAXCONTEXT_MINMACROFXFACTOR,
+ EAXCONTEXT_MAXMACROFXFACTOR);
+}
+
+void ALCcontext::eax_validate_context_all(
+ const EAX40CONTEXTPROPERTIES& context_all)
+{
+ eax_validate_primary_fx_slot_id(context_all.guidPrimaryFXSlotID);
+ eax_validate_distance_factor(context_all.flDistanceFactor);
+ eax_validate_air_absorption_hf(context_all.flAirAbsorptionHF);
+ eax_validate_hf_reference(context_all.flHFReference);
+}
+
+void ALCcontext::eax_validate_context_all(
+ const EAX50CONTEXTPROPERTIES& context_all)
+{
+ eax_validate_context_all(static_cast<const EAX40CONTEXTPROPERTIES>(context_all));
+ eax_validate_macro_fx_factor(context_all.flMacroFXFactor);
+}
+
+void ALCcontext::eax_defer_primary_fx_slot_id(
+ const GUID& primary_fx_slot_id)
+{
+ eax_d_.context.guidPrimaryFXSlotID = primary_fx_slot_id;
+
+ eax_context_dirty_flags_.guidPrimaryFXSlotID =
+ (eax_.context.guidPrimaryFXSlotID != eax_d_.context.guidPrimaryFXSlotID);
+}
+
+void ALCcontext::eax_defer_distance_factor(
+ float distance_factor)
+{
+ eax_d_.context.flDistanceFactor = distance_factor;
+
+ eax_context_dirty_flags_.flDistanceFactor =
+ (eax_.context.flDistanceFactor != eax_d_.context.flDistanceFactor);
+}
+
+void ALCcontext::eax_defer_air_absorption_hf(
+ float air_absorption_hf)
+{
+ eax_d_.context.flAirAbsorptionHF = air_absorption_hf;
+
+ eax_context_dirty_flags_.flAirAbsorptionHF =
+ (eax_.context.flAirAbsorptionHF != eax_d_.context.flAirAbsorptionHF);
+}
+
+void ALCcontext::eax_defer_hf_reference(
+ float hf_reference)
+{
+ eax_d_.context.flHFReference = hf_reference;
+
+ eax_context_dirty_flags_.flHFReference =
+ (eax_.context.flHFReference != eax_d_.context.flHFReference);
+}
+
+void ALCcontext::eax_defer_macro_fx_factor(
+ float macro_fx_factor)
+{
+ eax_d_.context.flMacroFXFactor = macro_fx_factor;
+
+ eax_context_dirty_flags_.flMacroFXFactor =
+ (eax_.context.flMacroFXFactor != eax_d_.context.flMacroFXFactor);
+}
+
+void ALCcontext::eax_defer_context_all(
+ const EAX40CONTEXTPROPERTIES& context_all)
+{
+ eax_defer_primary_fx_slot_id(context_all.guidPrimaryFXSlotID);
+ eax_defer_distance_factor(context_all.flDistanceFactor);
+ eax_defer_air_absorption_hf(context_all.flAirAbsorptionHF);
+ eax_defer_hf_reference(context_all.flHFReference);
+}
+
+void ALCcontext::eax_defer_context_all(
+ const EAX50CONTEXTPROPERTIES& context_all)
+{
+ eax_defer_context_all(static_cast<const EAX40CONTEXTPROPERTIES&>(context_all));
+ eax_defer_macro_fx_factor(context_all.flMacroFXFactor);
+}
+
+void ALCcontext::eax_defer_context_all(
+ const EaxEaxCall& eax_call)
+{
+ switch (eax_call.get_version())
+ {
+ case 4:
+ {
+ const auto& context_all =
+ eax_call.get_value<ContextException, EAX40CONTEXTPROPERTIES>();
+
+ eax_validate_context_all(context_all);
+ }
+
+ break;
+
+ case 5:
+ {
+ const auto& context_all =
+ eax_call.get_value<ContextException, EAX50CONTEXTPROPERTIES>();
+
+ eax_validate_context_all(context_all);
+ }
+
+ break;
+
+ default:
+ eax_fail("Unsupported EAX version.");
+ }
+}
+
+void ALCcontext::eax_defer_primary_fx_slot_id(
+ const EaxEaxCall& eax_call)
+{
+ const auto& primary_fx_slot_id =
+ eax_call.get_value<ContextException, const decltype(EAX50CONTEXTPROPERTIES::guidPrimaryFXSlotID)>();
+
+ eax_validate_primary_fx_slot_id(primary_fx_slot_id);
+ eax_defer_primary_fx_slot_id(primary_fx_slot_id);
+}
+
+void ALCcontext::eax_defer_distance_factor(
+ const EaxEaxCall& eax_call)
+{
+ const auto& distance_factor =
+ eax_call.get_value<ContextException, const decltype(EAX50CONTEXTPROPERTIES::flDistanceFactor)>();
+
+ eax_validate_distance_factor(distance_factor);
+ eax_defer_distance_factor(distance_factor);
+}
+
+void ALCcontext::eax_defer_air_absorption_hf(
+ const EaxEaxCall& eax_call)
+{
+ const auto& air_absorption_hf =
+ eax_call.get_value<ContextException, const decltype(EAX50CONTEXTPROPERTIES::flAirAbsorptionHF)>();
+
+ eax_validate_air_absorption_hf(air_absorption_hf);
+ eax_defer_air_absorption_hf(air_absorption_hf);
+}
+
+void ALCcontext::eax_defer_hf_reference(
+ const EaxEaxCall& eax_call)
+{
+ const auto& hf_reference =
+ eax_call.get_value<ContextException, const decltype(EAX50CONTEXTPROPERTIES::flHFReference)>();
+
+ eax_validate_hf_reference(hf_reference);
+ eax_defer_hf_reference(hf_reference);
+}
+
+void ALCcontext::eax_set_speaker_config(
+ const EaxEaxCall& eax_call)
+{
+ const auto speaker_config =
+ eax_call.get_value<ContextException, const unsigned long>();
+
+ eax_validate_speaker_config(speaker_config);
+
+ eax_speaker_config_ = speaker_config;
+}
+
+void ALCcontext::eax_set_session(
+ const EaxEaxCall& eax_call)
+{
+ const auto& eax_session =
+ eax_call.get_value<ContextException, const EAXSESSIONPROPERTIES>();
+
+ eax_validate_session(eax_session);
+
+ eax_session_ = eax_session;
+}
+
+void ALCcontext::eax_defer_macro_fx_factor(
+ const EaxEaxCall& eax_call)
+{
+ const auto& macro_fx_factor =
+ eax_call.get_value<ContextException, const decltype(EAX50CONTEXTPROPERTIES::flMacroFXFactor)>();
+
+ eax_validate_macro_fx_factor(macro_fx_factor);
+ eax_defer_macro_fx_factor(macro_fx_factor);
+}
+
+void ALCcontext::eax_set(
+ const EaxEaxCall& eax_call)
+{
+ switch (eax_call.get_property_id())
+ {
+ case EAXCONTEXT_NONE:
+ break;
+
+ case EAXCONTEXT_ALLPARAMETERS:
+ eax_defer_context_all(eax_call);
+ break;
+
+ case EAXCONTEXT_PRIMARYFXSLOTID:
+ eax_defer_primary_fx_slot_id(eax_call);
+ break;
+
+ case EAXCONTEXT_DISTANCEFACTOR:
+ eax_defer_distance_factor(eax_call);
+ break;
+
+ case EAXCONTEXT_AIRABSORPTIONHF:
+ eax_defer_air_absorption_hf(eax_call);
+ break;
+
+ case EAXCONTEXT_HFREFERENCE:
+ eax_defer_hf_reference(eax_call);
+ break;
+
+ case EAXCONTEXT_LASTERROR:
+ eax_fail("Setting last error not supported.");
+
+ case EAXCONTEXT_SPEAKERCONFIG:
+ eax_set_speaker_config(eax_call);
+ break;
+
+ case EAXCONTEXT_EAXSESSION:
+ eax_set_session(eax_call);
+ break;
+
+ case EAXCONTEXT_MACROFXFACTOR:
+ eax_defer_macro_fx_factor(eax_call);
+ break;
+
+ default:
+ eax_fail("Unsupported property id.");
+ }
+
+ if (!eax_call.is_deferred())
+ {
+ eax_apply_deferred();
+ }
+}
+
+void ALCcontext::eax_apply_deferred()
+{
+ if (eax_context_dirty_flags_ == ContextDirtyFlags{})
+ {
+ return;
+ }
+
+ eax_ = eax_d_;
+
+ if (eax_context_dirty_flags_.guidPrimaryFXSlotID)
+ {
+ eax_context_shared_dirty_flags_.primary_fx_slot_id = true;
+ eax_set_primary_fx_slot_id();
+ }
+
+ if (eax_context_dirty_flags_.flDistanceFactor)
+ {
+ eax_set_distance_factor();
+ }
+
+ if (eax_context_dirty_flags_.flAirAbsorptionHF)
+ {
+ eax_context_shared_dirty_flags_.air_absorption_hf = true;
+ eax_set_air_absorbtion_hf();
+ }
+
+ if (eax_context_dirty_flags_.flHFReference)
+ {
+ eax_set_hf_reference();
+ }
+
+ if (eax_context_dirty_flags_.flMacroFXFactor)
+ {
+ eax_set_macro_fx_factor();
+ }
+
+ if (eax_context_shared_dirty_flags_ != EaxContextSharedDirtyFlags{})
+ {
+ eax_update_sources();
+ }
+
+ eax_context_shared_dirty_flags_ = EaxContextSharedDirtyFlags{};
+ eax_context_dirty_flags_ = ContextDirtyFlags{};
+}
+
+
+namespace
+{
+
+
+class EaxSetException :
+ public EaxException
+{
+public:
+ explicit EaxSetException(
+ const char* message)
+ :
+ EaxException{"EAX_SET", message}
+ {
+ }
+}; // EaxSetException
+
+
+[[noreturn]]
+void eax_fail_set(
+ const char* message)
+{
+ throw EaxSetException{message};
+}
+
+
+class EaxGetException :
+ public EaxException
+{
+public:
+ explicit EaxGetException(
+ const char* message)
+ :
+ EaxException{"EAX_GET", message}
+ {
+ }
+}; // EaxGetException
+
+
+[[noreturn]]
+void eax_fail_get(
+ const char* message)
+{
+ throw EaxGetException{message};
+}
+
+
+} // namespace
+
+
+ALenum AL_APIENTRY EAXSet(
+ const GUID* property_set_id,
+ ALuint property_id,
+ ALuint property_source_id,
+ ALvoid* property_value,
+ ALuint property_value_size) noexcept
+try
+{
+ auto context = GetContextRef();
+
+ if (!context)
+ {
+ eax_fail_set("No current context.");
+ }
+
+ std::lock_guard<std::mutex> prop_lock{context->mPropLock};
+
+ return context->eax_eax_set(
+ property_set_id,
+ property_id,
+ property_source_id,
+ property_value,
+ property_value_size
+ );
+}
+catch (...)
+{
+ eax_log_exception(__func__);
+ return AL_INVALID_OPERATION;
+}
+
+ALenum AL_APIENTRY EAXGet(
+ const GUID* property_set_id,
+ ALuint property_id,
+ ALuint property_source_id,
+ ALvoid* property_value,
+ ALuint property_value_size) noexcept
+try
+{
+ auto context = GetContextRef();
+
+ if (!context)
+ {
+ eax_fail_get("No current context.");
+ }
+
+ std::lock_guard<std::mutex> prop_lock{context->mPropLock};
+
+ return context->eax_eax_get(
+ property_set_id,
+ property_id,
+ property_source_id,
+ property_value,
+ property_value_size
+ );
+}
+catch (...)
+{
+ eax_log_exception(__func__);
+ return AL_INVALID_OPERATION;
+}
+#endif // ALSOFT_EAX
diff --git a/alc/context.h b/alc/context.h
index 87754235..b34807b8 100644
--- a/alc/context.h
+++ b/alc/context.h
@@ -19,6 +19,49 @@
#include "intrusive_ptr.h"
#include "vector.h"
+#if ALSOFT_EAX
+#include "al/filter.h"
+
+#include "al/eax_eax_call.h"
+#include "al/eax_fx_slot_index.h"
+#include "al/eax_fx_slots.h"
+#include "al/eax_utils.h"
+#endif // ALSOFT_EAX
+
+
+#if ALSOFT_EAX
+using EaxContextSharedDirtyFlagsValue = std::uint_least8_t;
+
+struct EaxContextSharedDirtyFlags
+{
+ using EaxIsBitFieldStruct = bool;
+
+ EaxContextSharedDirtyFlagsValue primary_fx_slot_id : 1;
+ EaxContextSharedDirtyFlagsValue air_absorption_hf : 1;
+}; // EaxContextSharedDirtyFlags
+
+
+using ContextDirtyFlagsValue = std::uint_least8_t;
+
+struct ContextDirtyFlags
+{
+ using EaxIsBitFieldStruct = bool;
+
+ ContextDirtyFlagsValue guidPrimaryFXSlotID : 1;
+ ContextDirtyFlagsValue flDistanceFactor : 1;
+ ContextDirtyFlagsValue flAirAbsorptionHF : 1;
+ ContextDirtyFlagsValue flHFReference : 1;
+ ContextDirtyFlagsValue flMacroFXFactor : 1;
+}; // ContextDirtyFlags
+
+
+struct EaxAlIsExtensionPresentResult
+{
+ ALboolean is_present;
+ bool is_return;
+}; // EaxAlIsExtensionPresentResult
+#endif // ALSOFT_EAX
+
struct ALeffect;
struct ALeffectslot;
struct ALsource;
@@ -162,6 +205,332 @@ public:
static ALeffect sDefaultEffect;
DEF_NEWDEL(ALCcontext)
+
+#if ALSOFT_EAX
+public:
+ bool has_eax() const noexcept;
+
+ bool eax_is_capable() const noexcept;
+
+
+ void eax_uninitialize() noexcept;
+
+ void eax_initialize_source(
+ ALsource& al_source) noexcept;
+
+
+ ALenum eax_eax_set(
+ const GUID* property_set_id,
+ ALuint property_id,
+ ALuint property_source_id,
+ ALvoid* property_value,
+ ALuint property_value_size);
+
+ ALenum eax_eax_get(
+ const GUID* property_set_id,
+ ALuint property_id,
+ ALuint property_source_id,
+ ALvoid* property_value,
+ ALuint property_value_size);
+
+
+ void eax_update_filters();
+
+
+ void eax_set_last_error() noexcept;
+
+
+ float eax_get_max_filter_gain() const noexcept;
+
+ float eax_get_air_absorption_factor() const noexcept;
+
+ EaxFxSlotIndex eax_get_previous_primary_fx_slot_index() const noexcept;
+
+ EaxFxSlotIndex eax_get_primary_fx_slot_index() const noexcept;
+
+ const ALeffectslot& eax_get_fx_slot(
+ EaxFxSlotIndexValue fx_slot_index) const;
+
+ ALeffectslot& eax_get_fx_slot(
+ EaxFxSlotIndexValue fx_slot_index);
+
+
+private:
+ using SourceList = al::vector<SourceSubList>;
+
+
+ struct SourceListIteratorBeginTag{};
+ struct SourceListIteratorEndTag{};
+
+ class SourceListIterator
+ {
+ public:
+ SourceListIterator(
+ SourceList& sources,
+ SourceListIteratorBeginTag) noexcept;
+
+ SourceListIterator(
+ SourceList& sources,
+ SourceListIteratorEndTag) noexcept;
+
+ SourceListIterator(
+ const SourceListIterator& rhs);
+
+ SourceListIterator& operator=(
+ const SourceListIterator& rhs) = delete;
+
+ SourceListIterator& operator++();
+
+ ALsource& operator*() noexcept;
+
+ bool operator==(
+ const SourceListIterator& rhs) const noexcept;
+
+ bool operator!=(
+ const SourceListIterator& rhs) const noexcept;
+
+
+ private:
+ SourceList::iterator sub_list_iterator_;
+ SourceList::iterator sub_list_end_iterator_;
+ std::uint64_t sub_list_item_index_;
+ }; // SourceListIterator
+
+ class SourceListEnumerator
+ {
+ public:
+ explicit SourceListEnumerator(
+ SourceList& sources) noexcept;
+
+ SourceListEnumerator(
+ const SourceListEnumerator& rhs) = delete;
+
+ SourceListEnumerator& operator=(
+ const SourceListEnumerator& rhs) = delete;
+
+ SourceListIterator begin() noexcept;
+
+ SourceListIterator end() noexcept;
+
+
+ private:
+ SourceList& sources_;
+ }; // SourceListEnumerator
+
+
+ struct Eax
+ {
+ EAX50CONTEXTPROPERTIES context{};
+ }; // Eax
+
+
+ bool eax_is_initialized_{};
+ bool eax_is_tried_{};
+
+ long eax_last_error_{};
+ unsigned long eax_speaker_config_{};
+
+ float eax_max_filter_gain_{};
+ float eax_air_absorption_factor_{};
+ EaxFxSlotIndex eax_previous_primary_fx_slot_index_{};
+ EaxFxSlotIndex eax_primary_fx_slot_index_{};
+ EaxFxSlots eax_fx_slots_{};
+
+ EaxContextSharedDirtyFlags eax_context_shared_dirty_flags_{};
+
+ EaxAlFilterUPtr eax_al_filter_{};
+ Eax eax_{};
+ Eax eax_d_{};
+ EAXSESSIONPROPERTIES eax_session_{};
+
+ ContextDirtyFlags eax_context_dirty_flags_{};
+
+ std::string eax_extension_list_{};
+
+
+ [[noreturn]]
+ static void eax_fail(
+ const char* message);
+
+
+ void eax_initialize_extensions();
+
+ void eax_initialize();
+
+
+ bool eax_has_no_default_effect_slot() const noexcept;
+
+ void eax_ensure_no_default_effect_slot() const;
+
+ bool eax_has_enough_aux_sends() const noexcept;
+
+ void eax_ensure_enough_aux_sends() const;
+
+ bool eax_has_eax_reverb_effect() const noexcept;
+
+ void eax_ensure_eax_reverb_effect() const;
+
+ void eax_ensure_compatibility();
+
+
+ void eax_initialize_filter_gain();
+
+ void eax_set_last_error_defaults() noexcept;
+
+ void eax_set_speaker_config_defaults() noexcept;
+
+ void eax_set_session_defaults() noexcept;
+
+ void eax_set_context_defaults() noexcept;
+
+ void eax_set_defaults() noexcept;
+
+ void eax_initialize_filter();
+
+ void eax_initialize_sources();
+
+
+ void eax_dispatch_fx_slot(
+ const EaxEaxCall& eax_call);
+
+ void eax_dispatch_source(
+ const EaxEaxCall& eax_call);
+
+
+ void eax_get_primary_fx_slot_id(
+ const EaxEaxCall& eax_call);
+
+ void eax_get_distance_factor(
+ const EaxEaxCall& eax_call);
+
+ void eax_get_air_absorption_hf(
+ const EaxEaxCall& eax_call);
+
+ void eax_get_hf_reference(
+ const EaxEaxCall& eax_call);
+
+ void eax_get_last_error(
+ const EaxEaxCall& eax_call);
+
+ void eax_get_speaker_config(
+ const EaxEaxCall& eax_call);
+
+ void eax_get_session(
+ const EaxEaxCall& eax_call);
+
+ void eax_get_macro_fx_factor(
+ const EaxEaxCall& eax_call);
+
+ void eax_get_context_all(
+ const EaxEaxCall& eax_call);
+
+ void eax_get(
+ const EaxEaxCall& eax_call);
+
+
+ void eax_set_primary_fx_slot_id();
+
+ void eax_set_distance_factor();
+
+ void eax_set_air_absorbtion_hf();
+
+ void eax_set_hf_reference();
+
+ void eax_set_macro_fx_factor();
+
+ void eax_set_context();
+
+ void eax_initialize_fx_slots();
+
+
+ void eax_update_sources();
+
+
+ void eax_validate_primary_fx_slot_id(
+ const GUID& primary_fx_slot_id);
+
+ void eax_validate_distance_factor(
+ float distance_factor);
+
+ void eax_validate_air_absorption_hf(
+ float air_absorption_hf);
+
+ void eax_validate_hf_reference(
+ float hf_reference);
+
+ void eax_validate_speaker_config(
+ unsigned long speaker_config);
+
+ void eax_validate_session_eax_version(
+ unsigned long eax_version);
+
+ void eax_validate_session_max_active_sends(
+ unsigned long max_active_sends);
+
+ void eax_validate_session(
+ const EAXSESSIONPROPERTIES& eax_session);
+
+ void eax_validate_macro_fx_factor(
+ float macro_fx_factor);
+
+ void eax_validate_context_all(
+ const EAX40CONTEXTPROPERTIES& context_all);
+
+ void eax_validate_context_all(
+ const EAX50CONTEXTPROPERTIES& context_all);
+
+
+ void eax_defer_primary_fx_slot_id(
+ const GUID& primary_fx_slot_id);
+
+ void eax_defer_distance_factor(
+ float distance_factor);
+
+ void eax_defer_air_absorption_hf(
+ float air_absorption_hf);
+
+ void eax_defer_hf_reference(
+ float hf_reference);
+
+ void eax_defer_macro_fx_factor(
+ float macro_fx_factor);
+
+ void eax_defer_context_all(
+ const EAX40CONTEXTPROPERTIES& context_all);
+
+ void eax_defer_context_all(
+ const EAX50CONTEXTPROPERTIES& context_all);
+
+
+ void eax_defer_context_all(
+ const EaxEaxCall& eax_call);
+
+ void eax_defer_primary_fx_slot_id(
+ const EaxEaxCall& eax_call);
+
+ void eax_defer_distance_factor(
+ const EaxEaxCall& eax_call);
+
+ void eax_defer_air_absorption_hf(
+ const EaxEaxCall& eax_call);
+
+ void eax_defer_hf_reference(
+ const EaxEaxCall& eax_call);
+
+ void eax_set_speaker_config(
+ const EaxEaxCall& eax_call);
+
+ void eax_set_session(
+ const EaxEaxCall& eax_call);
+
+ void eax_defer_macro_fx_factor(
+ const EaxEaxCall& eax_call);
+
+ void eax_set(
+ const EaxEaxCall& eax_call);
+
+ void eax_apply_deferred();
+#endif // ALSOFT_EAX
};
#define SETERR_RETURN(ctx, err, retval, ...) do { \
@@ -179,4 +548,21 @@ void UpdateContextProps(ALCcontext *context);
extern bool TrapALError;
+
+#if ALSOFT_EAX
+ALenum AL_APIENTRY EAXSet(
+ const GUID* property_set_id,
+ ALuint property_id,
+ ALuint property_source_id,
+ ALvoid* property_value,
+ ALuint property_value_size) noexcept;
+
+ALenum AL_APIENTRY EAXGet(
+ const GUID* property_set_id,
+ ALuint property_id,
+ ALuint property_source_id,
+ ALvoid* property_value,
+ ALuint property_value_size) noexcept;
+#endif // ALSOFT_EAX
+
#endif /* ALC_CONTEXT_H */
diff --git a/alc/device.h b/alc/device.h
index 4798d422..e4eb76ea 100644
--- a/alc/device.h
+++ b/alc/device.h
@@ -18,6 +18,10 @@
#include "intrusive_ptr.h"
#include "vector.h"
+#if ALSOFT_EAX
+#include "al/eax_x_ram.h"
+#endif // ALSOFT_EAX
+
struct ALbuffer;
struct ALeffect;
struct ALfilter;
@@ -106,6 +110,10 @@ struct ALCdevice : public al::intrusive_ref<ALCdevice>, DeviceBase {
std::mutex FilterLock;
al::vector<FilterSubList> FilterList;
+#if ALSOFT_EAX
+ ALsizei eax_x_ram_free_size{eax_x_ram_max_size};
+#endif // ALSOFT_EAX
+
ALCdevice(DeviceType type);
~ALCdevice();
diff --git a/alsoftrc.sample b/alsoftrc.sample
index e2a45579..ab0345d9 100644
--- a/alsoftrc.sample
+++ b/alsoftrc.sample
@@ -578,3 +578,11 @@
# Creates AMB format files using first-order ambisonics instead of a standard
# single- or multi-channel .wav file.
#bformat = false
+
+##
+## EAX extensions stuff
+##
+[eax]
+##enable:
+# Sets whether to enable EAX extensions or not.
+#enable = true
diff --git a/common/alnumeric.h b/common/alnumeric.h
index c16f3e62..d72ba1e3 100644
--- a/common/alnumeric.h
+++ b/common/alnumeric.h
@@ -1,6 +1,10 @@
#ifndef AL_NUMERIC_H
#define AL_NUMERIC_H
+#if ALSOFT_EAX
+#include <cmath>
+#endif // ALSOFT_EAX
+
#include <cstddef>
#include <cstdint>
#ifdef HAVE_INTRIN_H
@@ -271,4 +275,45 @@ inline float fast_roundf(float f) noexcept
#endif
}
+#if ALSOFT_EAX
+template<
+ typename T
+>
+inline constexpr const T& clamp(
+ const T& value,
+ const T& min_value,
+ const T& max_value) noexcept
+{
+ return value < min_value ? min_value : (value > max_value ? max_value : value);
+}
+
+// Converts level (mB) to gain.
+inline float level_mb_to_gain(
+ float x)
+{
+ if (x <= -10'000.0F)
+ {
+ return 0.0F;
+ }
+ else
+ {
+ return std::pow(10.0F, x / 2'000.0F);
+ }
+}
+
+// Converts gain to level (mB).
+inline float gain_to_level_mb(
+ float x)
+{
+ if (x <= 0.0F)
+ {
+ return -10'000.0F;
+ }
+ else
+ {
+ return std::log10(x * 2'000.0F);
+ }
+}
+#endif // ALSOFT_EAX
+
#endif /* AL_NUMERIC_H */