aboutsummaryrefslogtreecommitdiffstats
path: root/alc/context.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'alc/context.cpp')
-rw-r--r--alc/context.cpp1105
1 files changed, 1105 insertions, 0 deletions
diff --git a/alc/context.cpp b/alc/context.cpp
new file mode 100644
index 00000000..e02c549b
--- /dev/null
+++ b/alc/context.cpp
@@ -0,0 +1,1105 @@
+
+#include "config.h"
+
+#include "context.h"
+
+#include <algorithm>
+#include <functional>
+#include <limits>
+#include <numeric>
+#include <stddef.h>
+#include <stdexcept>
+
+#include "AL/efx.h"
+
+#include "al/auxeffectslot.h"
+#include "al/source.h"
+#include "al/effect.h"
+#include "al/event.h"
+#include "al/listener.h"
+#include "albit.h"
+#include "alc/alu.h"
+#include "core/async_event.h"
+#include "core/device.h"
+#include "core/effectslot.h"
+#include "core/logging.h"
+#include "core/voice.h"
+#include "core/voice_change.h"
+#include "device.h"
+#include "ringbuffer.h"
+#include "vecmat.h"
+
+#ifdef ALSOFT_EAX
+#include <cstring>
+#include "alstring.h"
+#include "al/eax/globals.h"
+#endif // ALSOFT_EAX
+
+namespace {
+
+using namespace std::placeholders;
+
+using voidp = void*;
+
+/* Default context extensions */
+constexpr ALchar alExtList[] =
+ "AL_EXT_ALAW "
+ "AL_EXT_BFORMAT "
+ "AL_EXT_DOUBLE "
+ "AL_EXT_EXPONENT_DISTANCE "
+ "AL_EXT_FLOAT32 "
+ "AL_EXT_IMA4 "
+ "AL_EXT_LINEAR_DISTANCE "
+ "AL_EXT_MCFORMATS "
+ "AL_EXT_MULAW "
+ "AL_EXT_MULAW_BFORMAT "
+ "AL_EXT_MULAW_MCFORMATS "
+ "AL_EXT_OFFSET "
+ "AL_EXT_source_distance_model "
+ "AL_EXT_SOURCE_RADIUS "
+ "AL_EXT_STATIC_BUFFER "
+ "AL_EXT_STEREO_ANGLES "
+ "AL_LOKI_quadriphonic "
+ "AL_SOFT_bformat_ex "
+ "AL_SOFTX_bformat_hoa "
+ "AL_SOFT_block_alignment "
+ "AL_SOFT_buffer_length_query "
+ "AL_SOFT_callback_buffer "
+ "AL_SOFTX_convolution_reverb "
+ "AL_SOFT_deferred_updates "
+ "AL_SOFT_direct_channels "
+ "AL_SOFT_direct_channels_remix "
+ "AL_SOFT_effect_target "
+ "AL_SOFT_events "
+ "AL_SOFT_gain_clamp_ex "
+ "AL_SOFTX_hold_on_disconnect "
+ "AL_SOFT_loop_points "
+ "AL_SOFTX_map_buffer "
+ "AL_SOFT_MSADPCM "
+ "AL_SOFT_source_latency "
+ "AL_SOFT_source_length "
+ "AL_SOFT_source_resampler "
+ "AL_SOFT_source_spatialize "
+ "AL_SOFT_source_start_delay "
+ "AL_SOFT_UHJ "
+ "AL_SOFT_UHJ_ex";
+
+} // namespace
+
+
+std::atomic<bool> ALCcontext::sGlobalContextLock{false};
+std::atomic<ALCcontext*> ALCcontext::sGlobalContext{nullptr};
+
+thread_local ALCcontext *ALCcontext::sLocalContext{nullptr};
+ALCcontext::ThreadCtx::~ThreadCtx()
+{
+ if(ALCcontext *ctx{ALCcontext::sLocalContext})
+ {
+ const bool result{ctx->releaseIfNoDelete()};
+ ERR("Context %p current for thread being destroyed%s!\n", voidp{ctx},
+ result ? "" : ", leak detected");
+ }
+}
+thread_local ALCcontext::ThreadCtx ALCcontext::sThreadContext;
+
+ALeffect ALCcontext::sDefaultEffect;
+
+
+#ifdef __MINGW32__
+ALCcontext *ALCcontext::getThreadContext() noexcept
+{ return sLocalContext; }
+void ALCcontext::setThreadContext(ALCcontext *context) noexcept
+{ sThreadContext.set(context); }
+#endif
+
+ALCcontext::ALCcontext(al::intrusive_ptr<ALCdevice> device)
+ : ContextBase{device.get()}, mALDevice{std::move(device)}
+{
+}
+
+ALCcontext::~ALCcontext()
+{
+ TRACE("Freeing context %p\n", voidp{this});
+
+ size_t count{std::accumulate(mSourceList.cbegin(), mSourceList.cend(), size_t{0u},
+ [](size_t cur, const SourceSubList &sublist) noexcept -> size_t
+ { return cur + static_cast<uint>(al::popcount(~sublist.FreeMask)); })};
+ if(count > 0)
+ WARN("%zu Source%s not deleted\n", count, (count==1)?"":"s");
+ mSourceList.clear();
+ mNumSources = 0;
+
+#ifdef ALSOFT_EAX
+ eaxUninitialize();
+#endif // ALSOFT_EAX
+
+ mDefaultSlot = nullptr;
+ count = std::accumulate(mEffectSlotList.cbegin(), mEffectSlotList.cend(), size_t{0u},
+ [](size_t cur, const EffectSlotSubList &sublist) noexcept -> size_t
+ { return cur + static_cast<uint>(al::popcount(~sublist.FreeMask)); });
+ if(count > 0)
+ WARN("%zu AuxiliaryEffectSlot%s not deleted\n", count, (count==1)?"":"s");
+ mEffectSlotList.clear();
+ mNumEffectSlots = 0;
+}
+
+void ALCcontext::init()
+{
+ if(sDefaultEffect.type != AL_EFFECT_NULL && mDevice->Type == DeviceType::Playback)
+ {
+ mDefaultSlot = std::make_unique<ALeffectslot>(this);
+ aluInitEffectPanning(mDefaultSlot->mSlot, this);
+ }
+
+ EffectSlotArray *auxslots;
+ if(!mDefaultSlot)
+ auxslots = EffectSlot::CreatePtrArray(0);
+ else
+ {
+ auxslots = EffectSlot::CreatePtrArray(1);
+ (*auxslots)[0] = mDefaultSlot->mSlot;
+ mDefaultSlot->mState = SlotState::Playing;
+ }
+ mActiveAuxSlots.store(auxslots, std::memory_order_relaxed);
+
+ allocVoiceChanges();
+ {
+ VoiceChange *cur{mVoiceChangeTail};
+ while(VoiceChange *next{cur->mNext.load(std::memory_order_relaxed)})
+ cur = next;
+ mCurrentVoiceChange.store(cur, std::memory_order_relaxed);
+ }
+
+ mExtensionList = alExtList;
+
+ if(sBufferSubDataCompat)
+ {
+ std::string extlist{mExtensionList};
+
+ const auto pos = extlist.find("AL_EXT_SOURCE_RADIUS ");
+ if(pos != std::string::npos)
+ extlist.replace(pos, 20, "AL_SOFT_buffer_sub_data");
+ else
+ extlist += " AL_SOFT_buffer_sub_data";
+
+ mExtensionListOverride = std::move(extlist);
+ mExtensionList = mExtensionListOverride.c_str();
+ }
+
+#ifdef 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();
+ mParams.Velocity = alu::Vector{};
+ mParams.Gain = mListener.Gain;
+ mParams.MetersPerUnit = mListener.mMetersPerUnit;
+ mParams.AirAbsorptionGainHF = mAirAbsorptionGainHF;
+ mParams.DopplerFactor = mDopplerFactor;
+ mParams.SpeedOfSound = mSpeedOfSound * mDopplerVelocity;
+ mParams.SourceDistanceModel = mSourceDistanceModel;
+ mParams.mDistanceModel = mDistanceModel;
+
+
+ mAsyncEvents = RingBuffer::Create(511, sizeof(AsyncEvent), false);
+ StartEventThrd(this);
+
+
+ allocVoices(256);
+ mActiveVoiceCount.store(64, std::memory_order_relaxed);
+}
+
+bool ALCcontext::deinit()
+{
+ if(sLocalContext == this)
+ {
+ WARN("%p released while current on thread\n", voidp{this});
+ sThreadContext.set(nullptr);
+ dec_ref();
+ }
+
+ ALCcontext *origctx{this};
+ if(sGlobalContext.compare_exchange_strong(origctx, nullptr))
+ {
+ while(sGlobalContextLock.load()) {
+ /* Wait to make sure another thread didn't get the context and is
+ * trying to increment its refcount.
+ */
+ }
+ dec_ref();
+ }
+
+ bool ret{};
+ /* First make sure this context exists in the device's list. */
+ auto *oldarray = mDevice->mContexts.load(std::memory_order_acquire);
+ if(auto toremove = static_cast<size_t>(std::count(oldarray->begin(), oldarray->end(), this)))
+ {
+ using ContextArray = al::FlexArray<ContextBase*>;
+ auto alloc_ctx_array = [](const size_t count) -> ContextArray*
+ {
+ if(count == 0) return &DeviceBase::sEmptyContextArray;
+ return ContextArray::Create(count).release();
+ };
+ auto *newarray = alloc_ctx_array(oldarray->size() - toremove);
+
+ /* Copy the current/old context handles to the new array, excluding the
+ * given context.
+ */
+ std::copy_if(oldarray->begin(), oldarray->end(), newarray->begin(),
+ [this](ContextBase *ctx) { return ctx != this; });
+
+ /* Store the new context array in the device. Wait for any current mix
+ * to finish before deleting the old array.
+ */
+ mDevice->mContexts.store(newarray);
+ if(oldarray != &DeviceBase::sEmptyContextArray)
+ {
+ mDevice->waitForMix();
+ delete oldarray;
+ }
+
+ ret = !newarray->empty();
+ }
+ else
+ ret = !oldarray->empty();
+
+ StopEventThrd(this);
+
+ return ret;
+}
+
+void ALCcontext::applyAllUpdates()
+{
+ /* Tell the mixer to stop applying updates, then wait for any active
+ * updating to finish, before providing updates.
+ */
+ mHoldUpdates.store(true, std::memory_order_release);
+ while((mUpdateCount.load(std::memory_order_acquire)&1) != 0) {
+ /* busy-wait */
+ }
+
+#ifdef ALSOFT_EAX
+ if(mEaxNeedsCommit)
+ eaxCommit();
+#endif
+
+ if(std::exchange(mPropsDirty, false))
+ UpdateContextProps(this);
+ UpdateAllEffectSlotProps(this);
+ UpdateAllSourceProps(this);
+
+ /* Now with all updates declared, let the mixer continue applying them so
+ * they all happen at once.
+ */
+ mHoldUpdates.store(false, std::memory_order_release);
+}
+
+#ifdef ALSOFT_EAX
+namespace {
+
+template<typename F>
+void ForEachSource(ALCcontext *context, F func)
+{
+ for(auto &sublist : context->mSourceList)
+ {
+ uint64_t usemask{~sublist.FreeMask};
+ while(usemask)
+ {
+ const int idx{al::countr_zero(usemask)};
+ usemask &= ~(1_u64 << idx);
+
+ func(sublist.Sources[idx]);
+ }
+ }
+}
+
+} // namespace
+
+
+bool ALCcontext::eaxIsCapable() const noexcept
+{
+ return eax_has_enough_aux_sends();
+}
+
+void ALCcontext::eaxUninitialize() noexcept
+{
+ if(!mEaxIsInitialized)
+ return;
+
+ mEaxIsInitialized = false;
+ mEaxIsTried = false;
+ mEaxFxSlots.uninitialize();
+}
+
+ALenum ALCcontext::eax_eax_set(
+ const GUID* property_set_id,
+ ALuint property_id,
+ ALuint property_source_id,
+ ALvoid* property_value,
+ ALuint property_value_size)
+{
+ const auto call = create_eax_call(
+ EaxCallType::set,
+ property_set_id,
+ property_id,
+ property_source_id,
+ property_value,
+ property_value_size);
+
+ eax_initialize();
+
+ switch(call.get_property_set_id())
+ {
+ case EaxCallPropertySetId::context:
+ eax_set(call);
+ break;
+ case EaxCallPropertySetId::fx_slot:
+ case EaxCallPropertySetId::fx_slot_effect:
+ eax_dispatch_fx_slot(call);
+ break;
+ case EaxCallPropertySetId::source:
+ eax_dispatch_source(call);
+ break;
+ default:
+ eax_fail_unknown_property_set_id();
+ }
+ mEaxNeedsCommit = true;
+
+ if(!call.is_deferred())
+ {
+ eaxCommit();
+ if(!mDeferUpdates)
+ applyAllUpdates();
+ }
+
+ 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)
+{
+ const auto call = create_eax_call(
+ EaxCallType::get,
+ property_set_id,
+ property_id,
+ property_source_id,
+ property_value,
+ property_value_size);
+
+ eax_initialize();
+
+ switch(call.get_property_set_id())
+ {
+ case EaxCallPropertySetId::context:
+ eax_get(call);
+ break;
+ case EaxCallPropertySetId::fx_slot:
+ case EaxCallPropertySetId::fx_slot_effect:
+ eax_dispatch_fx_slot(call);
+ break;
+ case EaxCallPropertySetId::source:
+ eax_dispatch_source(call);
+ break;
+ default:
+ eax_fail_unknown_property_set_id();
+ }
+
+ return AL_NO_ERROR;
+}
+
+void ALCcontext::eaxSetLastError() noexcept
+{
+ mEaxLastError = EAXERR_INVALID_OPERATION;
+}
+
+[[noreturn]] void ALCcontext::eax_fail(const char* message)
+{
+ throw ContextException{message};
+}
+
+[[noreturn]] void ALCcontext::eax_fail_unknown_property_set_id()
+{
+ eax_fail("Unknown property ID.");
+}
+
+[[noreturn]] void ALCcontext::eax_fail_unknown_primary_fx_slot_id()
+{
+ eax_fail("Unknown primary FX Slot ID.");
+}
+
+[[noreturn]] void ALCcontext::eax_fail_unknown_property_id()
+{
+ eax_fail("Unknown property ID.");
+}
+
+[[noreturn]] void ALCcontext::eax_fail_unknown_version()
+{
+ eax_fail("Unknown version.");
+}
+
+void ALCcontext::eax_initialize_extensions()
+{
+ if(!eax_g_is_enabled)
+ return;
+
+ const auto string_max_capacity =
+ std::strlen(mExtensionList) + 1 +
+ std::strlen(eax1_ext_name) + 1 +
+ std::strlen(eax2_ext_name) + 1 +
+ std::strlen(eax3_ext_name) + 1 +
+ std::strlen(eax4_ext_name) + 1 +
+ std::strlen(eax5_ext_name) + 1 +
+ std::strlen(eax_x_ram_ext_name) + 1;
+
+ std::string extlist;
+ extlist.reserve(string_max_capacity);
+
+ if(eaxIsCapable())
+ {
+ extlist += eax1_ext_name;
+ extlist += ' ';
+
+ extlist += eax2_ext_name;
+ extlist += ' ';
+
+ extlist += eax3_ext_name;
+ extlist += ' ';
+
+ extlist += eax4_ext_name;
+ extlist += ' ';
+
+ extlist += eax5_ext_name;
+ extlist += ' ';
+ }
+
+ extlist += eax_x_ram_ext_name;
+ extlist += ' ';
+
+ extlist += mExtensionList;
+
+ mExtensionListOverride = std::move(extlist);
+ mExtensionList = mExtensionListOverride.c_str();
+}
+
+void ALCcontext::eax_initialize()
+{
+ if(mEaxIsInitialized)
+ return;
+
+ if(mEaxIsTried)
+ eax_fail("No EAX.");
+
+ mEaxIsTried = true;
+
+ if(!eax_g_is_enabled)
+ eax_fail("EAX disabled by a configuration.");
+
+ eax_ensure_compatibility();
+ eax_set_defaults();
+ eax_context_commit_air_absorbtion_hf();
+ eax_update_speaker_configuration();
+ eax_initialize_fx_slots();
+
+ mEaxIsInitialized = 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.");
+}
+
+void ALCcontext::eax_ensure_compatibility()
+{
+ eax_ensure_enough_aux_sends();
+}
+
+unsigned long ALCcontext::eax_detect_speaker_configuration() const
+{
+#define EAX_PREFIX "[EAX_DETECT_SPEAKER_CONFIG]"
+
+ switch(mDevice->FmtChans)
+ {
+ case DevFmtMono: return SPEAKERS_2;
+ case DevFmtStereo:
+ /* Pretend 7.1 if using UHJ output, since they both provide full
+ * horizontal surround.
+ */
+ if(mDevice->mUhjEncoder)
+ return SPEAKERS_7;
+ if(mDevice->Flags.test(DirectEar))
+ return HEADPHONES;
+ return SPEAKERS_2;
+ case DevFmtQuad: return SPEAKERS_4;
+ case DevFmtX51: return SPEAKERS_5;
+ case DevFmtX61: return SPEAKERS_6;
+ case DevFmtX71: return SPEAKERS_7;
+ /* 7.1.4 is compatible with 7.1. This could instead be HEADPHONES to
+ * suggest with-height surround sound (like HRTF).
+ */
+ case DevFmtX714: return SPEAKERS_7;
+ /* 3D7.1 is only compatible with 5.1. This could instead be HEADPHONES to
+ * suggest full-sphere surround sound (like HRTF).
+ */
+ case DevFmtX3D71: return SPEAKERS_5;
+ /* This could also be HEADPHONES, since headphones-based HRTF and Ambi3D
+ * provide full-sphere surround sound. Depends if apps are more likely to
+ * consider headphones or 7.1 for surround sound support.
+ */
+ case DevFmtAmbi3D: return SPEAKERS_7;
+ }
+ ERR(EAX_PREFIX "Unexpected device channel format 0x%x.\n", mDevice->FmtChans);
+ return HEADPHONES;
+
+#undef EAX_PREFIX
+}
+
+void ALCcontext::eax_update_speaker_configuration()
+{
+ mEaxSpeakerConfig = eax_detect_speaker_configuration();
+}
+
+void ALCcontext::eax_set_last_error_defaults() noexcept
+{
+ mEaxLastError = EAX_OK;
+}
+
+void ALCcontext::eax_session_set_defaults() noexcept
+{
+ mEaxSession.ulEAXVersion = EAXCONTEXT_DEFAULTEAXSESSION;
+ mEaxSession.ulMaxActiveSends = EAXCONTEXT_DEFAULTMAXACTIVESENDS;
+}
+
+void ALCcontext::eax4_context_set_defaults(Eax4Props& props) noexcept
+{
+ props.guidPrimaryFXSlotID = EAX40CONTEXT_DEFAULTPRIMARYFXSLOTID;
+ props.flDistanceFactor = EAXCONTEXT_DEFAULTDISTANCEFACTOR;
+ props.flAirAbsorptionHF = EAXCONTEXT_DEFAULTAIRABSORPTIONHF;
+ props.flHFReference = EAXCONTEXT_DEFAULTHFREFERENCE;
+}
+
+void ALCcontext::eax4_context_set_defaults(Eax4State& state) noexcept
+{
+ eax4_context_set_defaults(state.i);
+ state.d = state.i;
+}
+
+void ALCcontext::eax5_context_set_defaults(Eax5Props& props) noexcept
+{
+ props.guidPrimaryFXSlotID = EAX50CONTEXT_DEFAULTPRIMARYFXSLOTID;
+ props.flDistanceFactor = EAXCONTEXT_DEFAULTDISTANCEFACTOR;
+ props.flAirAbsorptionHF = EAXCONTEXT_DEFAULTAIRABSORPTIONHF;
+ props.flHFReference = EAXCONTEXT_DEFAULTHFREFERENCE;
+ props.flMacroFXFactor = EAXCONTEXT_DEFAULTMACROFXFACTOR;
+}
+
+void ALCcontext::eax5_context_set_defaults(Eax5State& state) noexcept
+{
+ eax5_context_set_defaults(state.i);
+ state.d = state.i;
+}
+
+void ALCcontext::eax_context_set_defaults()
+{
+ eax5_context_set_defaults(mEax123);
+ eax4_context_set_defaults(mEax4);
+ eax5_context_set_defaults(mEax5);
+ mEax = mEax5.i;
+ mEaxVersion = 5;
+ mEaxDf = EaxDirtyFlags{};
+}
+
+void ALCcontext::eax_set_defaults()
+{
+ eax_set_last_error_defaults();
+ eax_session_set_defaults();
+ eax_context_set_defaults();
+}
+
+void ALCcontext::eax_dispatch_fx_slot(const EaxCall& call)
+{
+ const auto fx_slot_index = call.get_fx_slot_index();
+ if(!fx_slot_index.has_value())
+ eax_fail("Invalid fx slot index.");
+
+ auto& fx_slot = eaxGetFxSlot(*fx_slot_index);
+ if(fx_slot.eax_dispatch(call))
+ {
+ std::lock_guard<std::mutex> source_lock{mSourceLock};
+ ForEachSource(this, std::mem_fn(&ALsource::eaxMarkAsChanged));
+ }
+}
+
+void ALCcontext::eax_dispatch_source(const EaxCall& call)
+{
+ const auto source_id = call.get_property_al_name();
+ std::lock_guard<std::mutex> source_lock{mSourceLock};
+ const auto source = ALsource::EaxLookupSource(*this, source_id);
+
+ if (source == nullptr)
+ eax_fail("Source not found.");
+
+ source->eaxDispatch(call);
+}
+
+void ALCcontext::eax_get_misc(const EaxCall& call)
+{
+ switch(call.get_property_id())
+ {
+ case EAXCONTEXT_NONE:
+ break;
+ case EAXCONTEXT_LASTERROR:
+ call.set_value<ContextException>(mEaxLastError);
+ break;
+ case EAXCONTEXT_SPEAKERCONFIG:
+ call.set_value<ContextException>(mEaxSpeakerConfig);
+ break;
+ case EAXCONTEXT_EAXSESSION:
+ call.set_value<ContextException>(mEaxSession);
+ break;
+ default:
+ eax_fail_unknown_property_id();
+ }
+}
+
+void ALCcontext::eax4_get(const EaxCall& call, const Eax4Props& props)
+{
+ switch(call.get_property_id())
+ {
+ case EAXCONTEXT_ALLPARAMETERS:
+ call.set_value<ContextException>(props);
+ break;
+ case EAXCONTEXT_PRIMARYFXSLOTID:
+ call.set_value<ContextException>(props.guidPrimaryFXSlotID);
+ break;
+ case EAXCONTEXT_DISTANCEFACTOR:
+ call.set_value<ContextException>(props.flDistanceFactor);
+ break;
+ case EAXCONTEXT_AIRABSORPTIONHF:
+ call.set_value<ContextException>(props.flAirAbsorptionHF);
+ break;
+ case EAXCONTEXT_HFREFERENCE:
+ call.set_value<ContextException>(props.flHFReference);
+ break;
+ default:
+ eax_get_misc(call);
+ break;
+ }
+}
+
+void ALCcontext::eax5_get(const EaxCall& call, const Eax5Props& props)
+{
+ switch(call.get_property_id())
+ {
+ case EAXCONTEXT_ALLPARAMETERS:
+ call.set_value<ContextException>(props);
+ break;
+ case EAXCONTEXT_PRIMARYFXSLOTID:
+ call.set_value<ContextException>(props.guidPrimaryFXSlotID);
+ break;
+ case EAXCONTEXT_DISTANCEFACTOR:
+ call.set_value<ContextException>(props.flDistanceFactor);
+ break;
+ case EAXCONTEXT_AIRABSORPTIONHF:
+ call.set_value<ContextException>(props.flAirAbsorptionHF);
+ break;
+ case EAXCONTEXT_HFREFERENCE:
+ call.set_value<ContextException>(props.flHFReference);
+ break;
+ case EAXCONTEXT_MACROFXFACTOR:
+ call.set_value<ContextException>(props.flMacroFXFactor);
+ break;
+ default:
+ eax_get_misc(call);
+ break;
+ }
+}
+
+void ALCcontext::eax_get(const EaxCall& call)
+{
+ switch(call.get_version())
+ {
+ case 4: eax4_get(call, mEax4.i); break;
+ case 5: eax5_get(call, mEax5.i); break;
+ default: eax_fail_unknown_version();
+ }
+}
+
+void ALCcontext::eax_context_commit_primary_fx_slot_id()
+{
+ mEaxPrimaryFxSlotIndex = mEax.guidPrimaryFXSlotID;
+}
+
+void ALCcontext::eax_context_commit_distance_factor()
+{
+ if(mListener.mMetersPerUnit == mEax.flDistanceFactor)
+ return;
+
+ mListener.mMetersPerUnit = mEax.flDistanceFactor;
+ mPropsDirty = true;
+}
+
+void ALCcontext::eax_context_commit_air_absorbtion_hf()
+{
+ const auto new_value = level_mb_to_gain(mEax.flAirAbsorptionHF);
+
+ if(mAirAbsorptionGainHF == new_value)
+ return;
+
+ mAirAbsorptionGainHF = new_value;
+ mPropsDirty = true;
+}
+
+void ALCcontext::eax_context_commit_hf_reference()
+{
+ // TODO
+}
+
+void ALCcontext::eax_context_commit_macro_fx_factor()
+{
+ // TODO
+}
+
+void ALCcontext::eax_initialize_fx_slots()
+{
+ mEaxFxSlots.initialize(*this);
+ mEaxPrimaryFxSlotIndex = mEax.guidPrimaryFXSlotID;
+}
+
+void ALCcontext::eax_update_sources()
+{
+ std::unique_lock<std::mutex> source_lock{mSourceLock};
+ auto update_source = [](ALsource &source)
+ { source.eaxCommit(); };
+ ForEachSource(this, update_source);
+}
+
+void ALCcontext::eax_set_misc(const EaxCall& call)
+{
+ switch(call.get_property_id())
+ {
+ case EAXCONTEXT_NONE:
+ break;
+ case EAXCONTEXT_SPEAKERCONFIG:
+ eax_set<Eax5SpeakerConfigValidator>(call, mEaxSpeakerConfig);
+ break;
+ case EAXCONTEXT_EAXSESSION:
+ eax_set<Eax5SessionAllValidator>(call, mEaxSession);
+ break;
+ default:
+ eax_fail_unknown_property_id();
+ }
+}
+
+void ALCcontext::eax4_defer_all(const EaxCall& call, Eax4State& state)
+{
+ const auto& src = call.get_value<ContextException, const EAX40CONTEXTPROPERTIES>();
+ Eax4AllValidator{}(src);
+ const auto& dst_i = state.i;
+ auto& dst_d = state.d;
+ dst_d = src;
+
+ if(dst_i.guidPrimaryFXSlotID != dst_d.guidPrimaryFXSlotID)
+ mEaxDf |= eax_primary_fx_slot_id_dirty_bit;
+
+ if(dst_i.flDistanceFactor != dst_d.flDistanceFactor)
+ mEaxDf |= eax_distance_factor_dirty_bit;
+
+ if(dst_i.flAirAbsorptionHF != dst_d.flAirAbsorptionHF)
+ mEaxDf |= eax_air_absorption_hf_dirty_bit;
+
+ if(dst_i.flHFReference != dst_d.flHFReference)
+ mEaxDf |= eax_hf_reference_dirty_bit;
+}
+
+void ALCcontext::eax4_defer(const EaxCall& call, Eax4State& state)
+{
+ switch(call.get_property_id())
+ {
+ case EAXCONTEXT_ALLPARAMETERS:
+ eax4_defer_all(call, state);
+ break;
+ case EAXCONTEXT_PRIMARYFXSLOTID:
+ eax_defer<Eax4PrimaryFxSlotIdValidator, eax_primary_fx_slot_id_dirty_bit>(
+ call, state, &EAX40CONTEXTPROPERTIES::guidPrimaryFXSlotID);
+ break;
+ case EAXCONTEXT_DISTANCEFACTOR:
+ eax_defer<Eax4DistanceFactorValidator, eax_distance_factor_dirty_bit>(
+ call, state, &EAX40CONTEXTPROPERTIES::flDistanceFactor);
+ break;
+ case EAXCONTEXT_AIRABSORPTIONHF:
+ eax_defer<Eax4AirAbsorptionHfValidator, eax_air_absorption_hf_dirty_bit>(
+ call, state, &EAX40CONTEXTPROPERTIES::flAirAbsorptionHF);
+ break;
+ case EAXCONTEXT_HFREFERENCE:
+ eax_defer<Eax4HfReferenceValidator, eax_hf_reference_dirty_bit>(
+ call, state, &EAX40CONTEXTPROPERTIES::flHFReference);
+ break;
+ default:
+ eax_set_misc(call);
+ break;
+ }
+}
+
+void ALCcontext::eax5_defer_all(const EaxCall& call, Eax5State& state)
+{
+ const auto& src = call.get_value<ContextException, const EAX50CONTEXTPROPERTIES>();
+ Eax4AllValidator{}(src);
+ const auto& dst_i = state.i;
+ auto& dst_d = state.d;
+ dst_d = src;
+
+ if(dst_i.guidPrimaryFXSlotID != dst_d.guidPrimaryFXSlotID)
+ mEaxDf |= eax_primary_fx_slot_id_dirty_bit;
+
+ if(dst_i.flDistanceFactor != dst_d.flDistanceFactor)
+ mEaxDf |= eax_distance_factor_dirty_bit;
+
+ if(dst_i.flAirAbsorptionHF != dst_d.flAirAbsorptionHF)
+ mEaxDf |= eax_air_absorption_hf_dirty_bit;
+
+ if(dst_i.flHFReference != dst_d.flHFReference)
+ mEaxDf |= eax_hf_reference_dirty_bit;
+
+ if(dst_i.flMacroFXFactor != dst_d.flMacroFXFactor)
+ mEaxDf |= eax_macro_fx_factor_dirty_bit;
+}
+
+void ALCcontext::eax5_defer(const EaxCall& call, Eax5State& state)
+{
+ switch(call.get_property_id())
+ {
+ case EAXCONTEXT_ALLPARAMETERS:
+ eax5_defer_all(call, state);
+ break;
+ case EAXCONTEXT_PRIMARYFXSLOTID:
+ eax_defer<Eax5PrimaryFxSlotIdValidator, eax_primary_fx_slot_id_dirty_bit>(
+ call, state, &EAX50CONTEXTPROPERTIES::guidPrimaryFXSlotID);
+ break;
+ case EAXCONTEXT_DISTANCEFACTOR:
+ eax_defer<Eax4DistanceFactorValidator, eax_distance_factor_dirty_bit>(
+ call, state, &EAX50CONTEXTPROPERTIES::flDistanceFactor);
+ break;
+ case EAXCONTEXT_AIRABSORPTIONHF:
+ eax_defer<Eax4AirAbsorptionHfValidator, eax_air_absorption_hf_dirty_bit>(
+ call, state, &EAX50CONTEXTPROPERTIES::flAirAbsorptionHF);
+ break;
+ case EAXCONTEXT_HFREFERENCE:
+ eax_defer<Eax4HfReferenceValidator, eax_hf_reference_dirty_bit>(
+ call, state, &EAX50CONTEXTPROPERTIES::flHFReference);
+ break;
+ case EAXCONTEXT_MACROFXFACTOR:
+ eax_defer<Eax5MacroFxFactorValidator, eax_macro_fx_factor_dirty_bit>(
+ call, state, &EAX50CONTEXTPROPERTIES::flMacroFXFactor);
+ break;
+ default:
+ eax_set_misc(call);
+ break;
+ }
+}
+
+void ALCcontext::eax_set(const EaxCall& call)
+{
+ const auto version = call.get_version();
+ switch(version)
+ {
+ case 4: eax4_defer(call, mEax4); break;
+ case 5: eax5_defer(call, mEax5); break;
+ default: eax_fail_unknown_version();
+ }
+ if(version != mEaxVersion)
+ mEaxDf = ~EaxDirtyFlags();
+ mEaxVersion = version;
+}
+
+void ALCcontext::eax4_context_commit(Eax4State& state, EaxDirtyFlags& dst_df)
+{
+ if(mEaxDf == EaxDirtyFlags{})
+ return;
+
+ eax_context_commit_property<eax_primary_fx_slot_id_dirty_bit>(
+ state, dst_df, &EAX40CONTEXTPROPERTIES::guidPrimaryFXSlotID);
+ eax_context_commit_property<eax_distance_factor_dirty_bit>(
+ state, dst_df, &EAX40CONTEXTPROPERTIES::flDistanceFactor);
+ eax_context_commit_property<eax_air_absorption_hf_dirty_bit>(
+ state, dst_df, &EAX40CONTEXTPROPERTIES::flAirAbsorptionHF);
+ eax_context_commit_property<eax_hf_reference_dirty_bit>(
+ state, dst_df, &EAX40CONTEXTPROPERTIES::flHFReference);
+
+ mEaxDf = EaxDirtyFlags{};
+}
+
+void ALCcontext::eax5_context_commit(Eax5State& state, EaxDirtyFlags& dst_df)
+{
+ if(mEaxDf == EaxDirtyFlags{})
+ return;
+
+ eax_context_commit_property<eax_primary_fx_slot_id_dirty_bit>(
+ state, dst_df, &EAX50CONTEXTPROPERTIES::guidPrimaryFXSlotID);
+ eax_context_commit_property<eax_distance_factor_dirty_bit>(
+ state, dst_df, &EAX50CONTEXTPROPERTIES::flDistanceFactor);
+ eax_context_commit_property<eax_air_absorption_hf_dirty_bit>(
+ state, dst_df, &EAX50CONTEXTPROPERTIES::flAirAbsorptionHF);
+ eax_context_commit_property<eax_hf_reference_dirty_bit>(
+ state, dst_df, &EAX50CONTEXTPROPERTIES::flHFReference);
+ eax_context_commit_property<eax_macro_fx_factor_dirty_bit>(
+ state, dst_df, &EAX50CONTEXTPROPERTIES::flMacroFXFactor);
+
+ mEaxDf = EaxDirtyFlags{};
+}
+
+void ALCcontext::eax_context_commit()
+{
+ auto dst_df = EaxDirtyFlags{};
+
+ switch(mEaxVersion)
+ {
+ case 1:
+ case 2:
+ case 3:
+ eax5_context_commit(mEax123, dst_df);
+ break;
+ case 4:
+ eax4_context_commit(mEax4, dst_df);
+ break;
+ case 5:
+ eax5_context_commit(mEax5, dst_df);
+ break;
+ }
+
+ if(dst_df == EaxDirtyFlags{})
+ return;
+
+ if((dst_df & eax_primary_fx_slot_id_dirty_bit) != EaxDirtyFlags{})
+ eax_context_commit_primary_fx_slot_id();
+
+ if((dst_df & eax_distance_factor_dirty_bit) != EaxDirtyFlags{})
+ eax_context_commit_distance_factor();
+
+ if((dst_df & eax_air_absorption_hf_dirty_bit) != EaxDirtyFlags{})
+ eax_context_commit_air_absorbtion_hf();
+
+ if((dst_df & eax_hf_reference_dirty_bit) != EaxDirtyFlags{})
+ eax_context_commit_hf_reference();
+
+ if((dst_df & eax_macro_fx_factor_dirty_bit) != EaxDirtyFlags{})
+ eax_context_commit_macro_fx_factor();
+
+ if((dst_df & eax_primary_fx_slot_id_dirty_bit) != EaxDirtyFlags{})
+ eax_update_sources();
+}
+
+void ALCcontext::eaxCommit()
+{
+ mEaxNeedsCommit = false;
+ eax_context_commit();
+ eaxCommitFxSlots();
+ eax_update_sources();
+}
+
+namespace {
+
+class EaxSetException : public EaxException {
+public:
+ explicit EaxSetException(const char* message)
+ : EaxException{"EAX_SET", message}
+ {}
+};
+
+[[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}
+ {}
+};
+
+[[noreturn]] void eax_fail_get(const char* message)
+{
+ throw EaxGetException{message};
+}
+
+} // namespace
+
+
+FORCE_ALIGN 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;
+}
+
+FORCE_ALIGN 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