diff options
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 */ |