]> git.tdb.fi Git - ext/openal.git/blob - alc/context.cpp
Import OpenAL Soft 1.23.1 sources
[ext/openal.git] / alc / context.cpp
1
2 #include "config.h"
3
4 #include "context.h"
5
6 #include <algorithm>
7 #include <functional>
8 #include <limits>
9 #include <numeric>
10 #include <stddef.h>
11 #include <stdexcept>
12
13 #include "AL/efx.h"
14
15 #include "al/auxeffectslot.h"
16 #include "al/source.h"
17 #include "al/effect.h"
18 #include "al/event.h"
19 #include "al/listener.h"
20 #include "albit.h"
21 #include "alc/alu.h"
22 #include "core/async_event.h"
23 #include "core/device.h"
24 #include "core/effectslot.h"
25 #include "core/logging.h"
26 #include "core/voice.h"
27 #include "core/voice_change.h"
28 #include "device.h"
29 #include "ringbuffer.h"
30 #include "vecmat.h"
31
32 #ifdef ALSOFT_EAX
33 #include <cstring>
34 #include "alstring.h"
35 #include "al/eax/globals.h"
36 #endif // ALSOFT_EAX
37
38 namespace {
39
40 using namespace std::placeholders;
41
42 using voidp = void*;
43
44 /* Default context extensions */
45 constexpr ALchar alExtList[] =
46     "AL_EXT_ALAW "
47     "AL_EXT_BFORMAT "
48     "AL_EXT_DOUBLE "
49     "AL_EXT_EXPONENT_DISTANCE "
50     "AL_EXT_FLOAT32 "
51     "AL_EXT_IMA4 "
52     "AL_EXT_LINEAR_DISTANCE "
53     "AL_EXT_MCFORMATS "
54     "AL_EXT_MULAW "
55     "AL_EXT_MULAW_BFORMAT "
56     "AL_EXT_MULAW_MCFORMATS "
57     "AL_EXT_OFFSET "
58     "AL_EXT_source_distance_model "
59     "AL_EXT_SOURCE_RADIUS "
60     "AL_EXT_STATIC_BUFFER "
61     "AL_EXT_STEREO_ANGLES "
62     "AL_LOKI_quadriphonic "
63     "AL_SOFT_bformat_ex "
64     "AL_SOFTX_bformat_hoa "
65     "AL_SOFT_block_alignment "
66     "AL_SOFT_buffer_length_query "
67     "AL_SOFT_callback_buffer "
68     "AL_SOFTX_convolution_reverb "
69     "AL_SOFT_deferred_updates "
70     "AL_SOFT_direct_channels "
71     "AL_SOFT_direct_channels_remix "
72     "AL_SOFT_effect_target "
73     "AL_SOFT_events "
74     "AL_SOFT_gain_clamp_ex "
75     "AL_SOFTX_hold_on_disconnect "
76     "AL_SOFT_loop_points "
77     "AL_SOFTX_map_buffer "
78     "AL_SOFT_MSADPCM "
79     "AL_SOFT_source_latency "
80     "AL_SOFT_source_length "
81     "AL_SOFT_source_resampler "
82     "AL_SOFT_source_spatialize "
83     "AL_SOFT_source_start_delay "
84     "AL_SOFT_UHJ "
85     "AL_SOFT_UHJ_ex";
86
87 } // namespace
88
89
90 std::atomic<bool> ALCcontext::sGlobalContextLock{false};
91 std::atomic<ALCcontext*> ALCcontext::sGlobalContext{nullptr};
92
93 thread_local ALCcontext *ALCcontext::sLocalContext{nullptr};
94 ALCcontext::ThreadCtx::~ThreadCtx()
95 {
96     if(ALCcontext *ctx{ALCcontext::sLocalContext})
97     {
98         const bool result{ctx->releaseIfNoDelete()};
99         ERR("Context %p current for thread being destroyed%s!\n", voidp{ctx},
100             result ? "" : ", leak detected");
101     }
102 }
103 thread_local ALCcontext::ThreadCtx ALCcontext::sThreadContext;
104
105 ALeffect ALCcontext::sDefaultEffect;
106
107
108 #ifdef __MINGW32__
109 ALCcontext *ALCcontext::getThreadContext() noexcept
110 { return sLocalContext; }
111 void ALCcontext::setThreadContext(ALCcontext *context) noexcept
112 { sThreadContext.set(context); }
113 #endif
114
115 ALCcontext::ALCcontext(al::intrusive_ptr<ALCdevice> device)
116   : ContextBase{device.get()}, mALDevice{std::move(device)}
117 {
118 }
119
120 ALCcontext::~ALCcontext()
121 {
122     TRACE("Freeing context %p\n", voidp{this});
123
124     size_t count{std::accumulate(mSourceList.cbegin(), mSourceList.cend(), size_t{0u},
125         [](size_t cur, const SourceSubList &sublist) noexcept -> size_t
126         { return cur + static_cast<uint>(al::popcount(~sublist.FreeMask)); })};
127     if(count > 0)
128         WARN("%zu Source%s not deleted\n", count, (count==1)?"":"s");
129     mSourceList.clear();
130     mNumSources = 0;
131
132 #ifdef ALSOFT_EAX
133     eaxUninitialize();
134 #endif // ALSOFT_EAX
135
136     mDefaultSlot = nullptr;
137     count = std::accumulate(mEffectSlotList.cbegin(), mEffectSlotList.cend(), size_t{0u},
138         [](size_t cur, const EffectSlotSubList &sublist) noexcept -> size_t
139         { return cur + static_cast<uint>(al::popcount(~sublist.FreeMask)); });
140     if(count > 0)
141         WARN("%zu AuxiliaryEffectSlot%s not deleted\n", count, (count==1)?"":"s");
142     mEffectSlotList.clear();
143     mNumEffectSlots = 0;
144 }
145
146 void ALCcontext::init()
147 {
148     if(sDefaultEffect.type != AL_EFFECT_NULL && mDevice->Type == DeviceType::Playback)
149     {
150         mDefaultSlot = std::make_unique<ALeffectslot>(this);
151         aluInitEffectPanning(mDefaultSlot->mSlot, this);
152     }
153
154     EffectSlotArray *auxslots;
155     if(!mDefaultSlot)
156         auxslots = EffectSlot::CreatePtrArray(0);
157     else
158     {
159         auxslots = EffectSlot::CreatePtrArray(1);
160         (*auxslots)[0] = mDefaultSlot->mSlot;
161         mDefaultSlot->mState = SlotState::Playing;
162     }
163     mActiveAuxSlots.store(auxslots, std::memory_order_relaxed);
164
165     allocVoiceChanges();
166     {
167         VoiceChange *cur{mVoiceChangeTail};
168         while(VoiceChange *next{cur->mNext.load(std::memory_order_relaxed)})
169             cur = next;
170         mCurrentVoiceChange.store(cur, std::memory_order_relaxed);
171     }
172
173     mExtensionList = alExtList;
174
175     if(sBufferSubDataCompat)
176     {
177         std::string extlist{mExtensionList};
178
179         const auto pos = extlist.find("AL_EXT_SOURCE_RADIUS ");
180         if(pos != std::string::npos)
181             extlist.replace(pos, 20, "AL_SOFT_buffer_sub_data");
182         else
183             extlist += " AL_SOFT_buffer_sub_data";
184
185         mExtensionListOverride = std::move(extlist);
186         mExtensionList = mExtensionListOverride.c_str();
187     }
188
189 #ifdef ALSOFT_EAX
190     eax_initialize_extensions();
191 #endif // ALSOFT_EAX
192
193     mParams.Position = alu::Vector{0.0f, 0.0f, 0.0f, 1.0f};
194     mParams.Matrix = alu::Matrix::Identity();
195     mParams.Velocity = alu::Vector{};
196     mParams.Gain = mListener.Gain;
197     mParams.MetersPerUnit = mListener.mMetersPerUnit;
198     mParams.AirAbsorptionGainHF = mAirAbsorptionGainHF;
199     mParams.DopplerFactor = mDopplerFactor;
200     mParams.SpeedOfSound = mSpeedOfSound * mDopplerVelocity;
201     mParams.SourceDistanceModel = mSourceDistanceModel;
202     mParams.mDistanceModel = mDistanceModel;
203
204
205     mAsyncEvents = RingBuffer::Create(511, sizeof(AsyncEvent), false);
206     StartEventThrd(this);
207
208
209     allocVoices(256);
210     mActiveVoiceCount.store(64, std::memory_order_relaxed);
211 }
212
213 bool ALCcontext::deinit()
214 {
215     if(sLocalContext == this)
216     {
217         WARN("%p released while current on thread\n", voidp{this});
218         sThreadContext.set(nullptr);
219         dec_ref();
220     }
221
222     ALCcontext *origctx{this};
223     if(sGlobalContext.compare_exchange_strong(origctx, nullptr))
224     {
225         while(sGlobalContextLock.load()) {
226             /* Wait to make sure another thread didn't get the context and is
227              * trying to increment its refcount.
228              */
229         }
230         dec_ref();
231     }
232
233     bool ret{};
234     /* First make sure this context exists in the device's list. */
235     auto *oldarray = mDevice->mContexts.load(std::memory_order_acquire);
236     if(auto toremove = static_cast<size_t>(std::count(oldarray->begin(), oldarray->end(), this)))
237     {
238         using ContextArray = al::FlexArray<ContextBase*>;
239         auto alloc_ctx_array = [](const size_t count) -> ContextArray*
240         {
241             if(count == 0) return &DeviceBase::sEmptyContextArray;
242             return ContextArray::Create(count).release();
243         };
244         auto *newarray = alloc_ctx_array(oldarray->size() - toremove);
245
246         /* Copy the current/old context handles to the new array, excluding the
247          * given context.
248          */
249         std::copy_if(oldarray->begin(), oldarray->end(), newarray->begin(),
250             [this](ContextBase *ctx) { return ctx != this; });
251
252         /* Store the new context array in the device. Wait for any current mix
253          * to finish before deleting the old array.
254          */
255         mDevice->mContexts.store(newarray);
256         if(oldarray != &DeviceBase::sEmptyContextArray)
257         {
258             mDevice->waitForMix();
259             delete oldarray;
260         }
261
262         ret = !newarray->empty();
263     }
264     else
265         ret = !oldarray->empty();
266
267     StopEventThrd(this);
268
269     return ret;
270 }
271
272 void ALCcontext::applyAllUpdates()
273 {
274     /* Tell the mixer to stop applying updates, then wait for any active
275      * updating to finish, before providing updates.
276      */
277     mHoldUpdates.store(true, std::memory_order_release);
278     while((mUpdateCount.load(std::memory_order_acquire)&1) != 0) {
279         /* busy-wait */
280     }
281
282 #ifdef ALSOFT_EAX
283     if(mEaxNeedsCommit)
284         eaxCommit();
285 #endif
286
287     if(std::exchange(mPropsDirty, false))
288         UpdateContextProps(this);
289     UpdateAllEffectSlotProps(this);
290     UpdateAllSourceProps(this);
291
292     /* Now with all updates declared, let the mixer continue applying them so
293      * they all happen at once.
294      */
295     mHoldUpdates.store(false, std::memory_order_release);
296 }
297
298 #ifdef ALSOFT_EAX
299 namespace {
300
301 template<typename F>
302 void ForEachSource(ALCcontext *context, F func)
303 {
304     for(auto &sublist : context->mSourceList)
305     {
306         uint64_t usemask{~sublist.FreeMask};
307         while(usemask)
308         {
309             const int idx{al::countr_zero(usemask)};
310             usemask &= ~(1_u64 << idx);
311
312             func(sublist.Sources[idx]);
313         }
314     }
315 }
316
317 } // namespace
318
319
320 bool ALCcontext::eaxIsCapable() const noexcept
321 {
322     return eax_has_enough_aux_sends();
323 }
324
325 void ALCcontext::eaxUninitialize() noexcept
326 {
327     if(!mEaxIsInitialized)
328         return;
329
330     mEaxIsInitialized = false;
331     mEaxIsTried = false;
332     mEaxFxSlots.uninitialize();
333 }
334
335 ALenum ALCcontext::eax_eax_set(
336     const GUID* property_set_id,
337     ALuint property_id,
338     ALuint property_source_id,
339     ALvoid* property_value,
340     ALuint property_value_size)
341 {
342     const auto call = create_eax_call(
343         EaxCallType::set,
344         property_set_id,
345         property_id,
346         property_source_id,
347         property_value,
348         property_value_size);
349
350     eax_initialize();
351
352     switch(call.get_property_set_id())
353     {
354     case EaxCallPropertySetId::context:
355         eax_set(call);
356         break;
357     case EaxCallPropertySetId::fx_slot:
358     case EaxCallPropertySetId::fx_slot_effect:
359         eax_dispatch_fx_slot(call);
360         break;
361     case EaxCallPropertySetId::source:
362         eax_dispatch_source(call);
363         break;
364     default:
365         eax_fail_unknown_property_set_id();
366     }
367     mEaxNeedsCommit = true;
368
369     if(!call.is_deferred())
370     {
371         eaxCommit();
372         if(!mDeferUpdates)
373             applyAllUpdates();
374     }
375
376     return AL_NO_ERROR;
377 }
378
379 ALenum ALCcontext::eax_eax_get(
380     const GUID* property_set_id,
381     ALuint property_id,
382     ALuint property_source_id,
383     ALvoid* property_value,
384     ALuint property_value_size)
385 {
386     const auto call = create_eax_call(
387         EaxCallType::get,
388         property_set_id,
389         property_id,
390         property_source_id,
391         property_value,
392         property_value_size);
393
394     eax_initialize();
395
396     switch(call.get_property_set_id())
397     {
398     case EaxCallPropertySetId::context:
399         eax_get(call);
400         break;
401     case EaxCallPropertySetId::fx_slot:
402     case EaxCallPropertySetId::fx_slot_effect:
403         eax_dispatch_fx_slot(call);
404         break;
405     case EaxCallPropertySetId::source:
406         eax_dispatch_source(call);
407         break;
408     default:
409         eax_fail_unknown_property_set_id();
410     }
411
412     return AL_NO_ERROR;
413 }
414
415 void ALCcontext::eaxSetLastError() noexcept
416 {
417     mEaxLastError = EAXERR_INVALID_OPERATION;
418 }
419
420 [[noreturn]] void ALCcontext::eax_fail(const char* message)
421 {
422     throw ContextException{message};
423 }
424
425 [[noreturn]] void ALCcontext::eax_fail_unknown_property_set_id()
426 {
427     eax_fail("Unknown property ID.");
428 }
429
430 [[noreturn]] void ALCcontext::eax_fail_unknown_primary_fx_slot_id()
431 {
432     eax_fail("Unknown primary FX Slot ID.");
433 }
434
435 [[noreturn]] void ALCcontext::eax_fail_unknown_property_id()
436 {
437     eax_fail("Unknown property ID.");
438 }
439
440 [[noreturn]] void ALCcontext::eax_fail_unknown_version()
441 {
442     eax_fail("Unknown version.");
443 }
444
445 void ALCcontext::eax_initialize_extensions()
446 {
447     if(!eax_g_is_enabled)
448         return;
449
450     const auto string_max_capacity =
451         std::strlen(mExtensionList) + 1 +
452         std::strlen(eax1_ext_name) + 1 +
453         std::strlen(eax2_ext_name) + 1 +
454         std::strlen(eax3_ext_name) + 1 +
455         std::strlen(eax4_ext_name) + 1 +
456         std::strlen(eax5_ext_name) + 1 +
457         std::strlen(eax_x_ram_ext_name) + 1;
458
459     std::string extlist;
460     extlist.reserve(string_max_capacity);
461
462     if(eaxIsCapable())
463     {
464         extlist += eax1_ext_name;
465         extlist += ' ';
466
467         extlist += eax2_ext_name;
468         extlist += ' ';
469
470         extlist += eax3_ext_name;
471         extlist += ' ';
472
473         extlist += eax4_ext_name;
474         extlist += ' ';
475
476         extlist += eax5_ext_name;
477         extlist += ' ';
478     }
479
480     extlist += eax_x_ram_ext_name;
481     extlist += ' ';
482
483     extlist += mExtensionList;
484
485     mExtensionListOverride = std::move(extlist);
486     mExtensionList = mExtensionListOverride.c_str();
487 }
488
489 void ALCcontext::eax_initialize()
490 {
491     if(mEaxIsInitialized)
492         return;
493
494     if(mEaxIsTried)
495         eax_fail("No EAX.");
496
497     mEaxIsTried = true;
498
499     if(!eax_g_is_enabled)
500         eax_fail("EAX disabled by a configuration.");
501
502     eax_ensure_compatibility();
503     eax_set_defaults();
504     eax_context_commit_air_absorbtion_hf();
505     eax_update_speaker_configuration();
506     eax_initialize_fx_slots();
507
508     mEaxIsInitialized = true;
509 }
510
511 bool ALCcontext::eax_has_no_default_effect_slot() const noexcept
512 {
513     return mDefaultSlot == nullptr;
514 }
515
516 void ALCcontext::eax_ensure_no_default_effect_slot() const
517 {
518     if(!eax_has_no_default_effect_slot())
519         eax_fail("There is a default effect slot in the context.");
520 }
521
522 bool ALCcontext::eax_has_enough_aux_sends() const noexcept
523 {
524     return mALDevice->NumAuxSends >= EAX_MAX_FXSLOTS;
525 }
526
527 void ALCcontext::eax_ensure_enough_aux_sends() const
528 {
529     if(!eax_has_enough_aux_sends())
530         eax_fail("Not enough aux sends.");
531 }
532
533 void ALCcontext::eax_ensure_compatibility()
534 {
535     eax_ensure_enough_aux_sends();
536 }
537
538 unsigned long ALCcontext::eax_detect_speaker_configuration() const
539 {
540 #define EAX_PREFIX "[EAX_DETECT_SPEAKER_CONFIG]"
541
542     switch(mDevice->FmtChans)
543     {
544     case DevFmtMono: return SPEAKERS_2;
545     case DevFmtStereo:
546         /* Pretend 7.1 if using UHJ output, since they both provide full
547          * horizontal surround.
548          */
549         if(mDevice->mUhjEncoder)
550             return SPEAKERS_7;
551         if(mDevice->Flags.test(DirectEar))
552             return HEADPHONES;
553         return SPEAKERS_2;
554     case DevFmtQuad: return SPEAKERS_4;
555     case DevFmtX51: return SPEAKERS_5;
556     case DevFmtX61: return SPEAKERS_6;
557     case DevFmtX71: return SPEAKERS_7;
558     /* 7.1.4 is compatible with 7.1. This could instead be HEADPHONES to
559      * suggest with-height surround sound (like HRTF).
560      */
561     case DevFmtX714: return SPEAKERS_7;
562     /* 3D7.1 is only compatible with 5.1. This could instead be HEADPHONES to
563      * suggest full-sphere surround sound (like HRTF).
564      */
565     case DevFmtX3D71: return SPEAKERS_5;
566     /* This could also be HEADPHONES, since headphones-based HRTF and Ambi3D
567      * provide full-sphere surround sound. Depends if apps are more likely to
568      * consider headphones or 7.1 for surround sound support.
569      */
570     case DevFmtAmbi3D: return SPEAKERS_7;
571     }
572     ERR(EAX_PREFIX "Unexpected device channel format 0x%x.\n", mDevice->FmtChans);
573     return HEADPHONES;
574
575 #undef EAX_PREFIX
576 }
577
578 void ALCcontext::eax_update_speaker_configuration()
579 {
580     mEaxSpeakerConfig = eax_detect_speaker_configuration();
581 }
582
583 void ALCcontext::eax_set_last_error_defaults() noexcept
584 {
585     mEaxLastError = EAX_OK;
586 }
587
588 void ALCcontext::eax_session_set_defaults() noexcept
589 {
590     mEaxSession.ulEAXVersion = EAXCONTEXT_DEFAULTEAXSESSION;
591     mEaxSession.ulMaxActiveSends = EAXCONTEXT_DEFAULTMAXACTIVESENDS;
592 }
593
594 void ALCcontext::eax4_context_set_defaults(Eax4Props& props) noexcept
595 {
596     props.guidPrimaryFXSlotID = EAX40CONTEXT_DEFAULTPRIMARYFXSLOTID;
597     props.flDistanceFactor = EAXCONTEXT_DEFAULTDISTANCEFACTOR;
598     props.flAirAbsorptionHF = EAXCONTEXT_DEFAULTAIRABSORPTIONHF;
599     props.flHFReference = EAXCONTEXT_DEFAULTHFREFERENCE;
600 }
601
602 void ALCcontext::eax4_context_set_defaults(Eax4State& state) noexcept
603 {
604     eax4_context_set_defaults(state.i);
605     state.d = state.i;
606 }
607
608 void ALCcontext::eax5_context_set_defaults(Eax5Props& props) noexcept
609 {
610     props.guidPrimaryFXSlotID = EAX50CONTEXT_DEFAULTPRIMARYFXSLOTID;
611     props.flDistanceFactor = EAXCONTEXT_DEFAULTDISTANCEFACTOR;
612     props.flAirAbsorptionHF = EAXCONTEXT_DEFAULTAIRABSORPTIONHF;
613     props.flHFReference = EAXCONTEXT_DEFAULTHFREFERENCE;
614     props.flMacroFXFactor = EAXCONTEXT_DEFAULTMACROFXFACTOR;
615 }
616
617 void ALCcontext::eax5_context_set_defaults(Eax5State& state) noexcept
618 {
619     eax5_context_set_defaults(state.i);
620     state.d = state.i;
621 }
622
623 void ALCcontext::eax_context_set_defaults()
624 {
625     eax5_context_set_defaults(mEax123);
626     eax4_context_set_defaults(mEax4);
627     eax5_context_set_defaults(mEax5);
628     mEax = mEax5.i;
629     mEaxVersion = 5;
630     mEaxDf = EaxDirtyFlags{};
631 }
632
633 void ALCcontext::eax_set_defaults()
634 {
635     eax_set_last_error_defaults();
636     eax_session_set_defaults();
637     eax_context_set_defaults();
638 }
639
640 void ALCcontext::eax_dispatch_fx_slot(const EaxCall& call)
641 {
642     const auto fx_slot_index = call.get_fx_slot_index();
643     if(!fx_slot_index.has_value())
644         eax_fail("Invalid fx slot index.");
645
646     auto& fx_slot = eaxGetFxSlot(*fx_slot_index);
647     if(fx_slot.eax_dispatch(call))
648     {
649         std::lock_guard<std::mutex> source_lock{mSourceLock};
650         ForEachSource(this, std::mem_fn(&ALsource::eaxMarkAsChanged));
651     }
652 }
653
654 void ALCcontext::eax_dispatch_source(const EaxCall& call)
655 {
656     const auto source_id = call.get_property_al_name();
657     std::lock_guard<std::mutex> source_lock{mSourceLock};
658     const auto source = ALsource::EaxLookupSource(*this, source_id);
659
660     if (source == nullptr)
661         eax_fail("Source not found.");
662
663     source->eaxDispatch(call);
664 }
665
666 void ALCcontext::eax_get_misc(const EaxCall& call)
667 {
668     switch(call.get_property_id())
669     {
670     case EAXCONTEXT_NONE:
671         break;
672     case EAXCONTEXT_LASTERROR:
673         call.set_value<ContextException>(mEaxLastError);
674         break;
675     case EAXCONTEXT_SPEAKERCONFIG:
676         call.set_value<ContextException>(mEaxSpeakerConfig);
677         break;
678     case EAXCONTEXT_EAXSESSION:
679         call.set_value<ContextException>(mEaxSession);
680         break;
681     default:
682         eax_fail_unknown_property_id();
683     }
684 }
685
686 void ALCcontext::eax4_get(const EaxCall& call, const Eax4Props& props)
687 {
688     switch(call.get_property_id())
689     {
690     case EAXCONTEXT_ALLPARAMETERS:
691         call.set_value<ContextException>(props);
692         break;
693     case EAXCONTEXT_PRIMARYFXSLOTID:
694         call.set_value<ContextException>(props.guidPrimaryFXSlotID);
695         break;
696     case EAXCONTEXT_DISTANCEFACTOR:
697         call.set_value<ContextException>(props.flDistanceFactor);
698         break;
699     case EAXCONTEXT_AIRABSORPTIONHF:
700         call.set_value<ContextException>(props.flAirAbsorptionHF);
701         break;
702     case EAXCONTEXT_HFREFERENCE:
703         call.set_value<ContextException>(props.flHFReference);
704         break;
705     default:
706         eax_get_misc(call);
707         break;
708     }
709 }
710
711 void ALCcontext::eax5_get(const EaxCall& call, const Eax5Props& props)
712 {
713     switch(call.get_property_id())
714     {
715     case EAXCONTEXT_ALLPARAMETERS:
716         call.set_value<ContextException>(props);
717         break;
718     case EAXCONTEXT_PRIMARYFXSLOTID:
719         call.set_value<ContextException>(props.guidPrimaryFXSlotID);
720         break;
721     case EAXCONTEXT_DISTANCEFACTOR:
722         call.set_value<ContextException>(props.flDistanceFactor);
723         break;
724     case EAXCONTEXT_AIRABSORPTIONHF:
725         call.set_value<ContextException>(props.flAirAbsorptionHF);
726         break;
727     case EAXCONTEXT_HFREFERENCE:
728         call.set_value<ContextException>(props.flHFReference);
729         break;
730     case EAXCONTEXT_MACROFXFACTOR:
731         call.set_value<ContextException>(props.flMacroFXFactor);
732         break;
733     default:
734         eax_get_misc(call);
735         break;
736     }
737 }
738
739 void ALCcontext::eax_get(const EaxCall& call)
740 {
741     switch(call.get_version())
742     {
743     case 4: eax4_get(call, mEax4.i); break;
744     case 5: eax5_get(call, mEax5.i); break;
745     default: eax_fail_unknown_version();
746     }
747 }
748
749 void ALCcontext::eax_context_commit_primary_fx_slot_id()
750 {
751     mEaxPrimaryFxSlotIndex = mEax.guidPrimaryFXSlotID;
752 }
753
754 void ALCcontext::eax_context_commit_distance_factor()
755 {
756     if(mListener.mMetersPerUnit == mEax.flDistanceFactor)
757         return;
758
759     mListener.mMetersPerUnit = mEax.flDistanceFactor;
760     mPropsDirty = true;
761 }
762
763 void ALCcontext::eax_context_commit_air_absorbtion_hf()
764 {
765     const auto new_value = level_mb_to_gain(mEax.flAirAbsorptionHF);
766
767     if(mAirAbsorptionGainHF == new_value)
768         return;
769
770     mAirAbsorptionGainHF = new_value;
771     mPropsDirty = true;
772 }
773
774 void ALCcontext::eax_context_commit_hf_reference()
775 {
776     // TODO
777 }
778
779 void ALCcontext::eax_context_commit_macro_fx_factor()
780 {
781     // TODO
782 }
783
784 void ALCcontext::eax_initialize_fx_slots()
785 {
786     mEaxFxSlots.initialize(*this);
787     mEaxPrimaryFxSlotIndex = mEax.guidPrimaryFXSlotID;
788 }
789
790 void ALCcontext::eax_update_sources()
791 {
792     std::unique_lock<std::mutex> source_lock{mSourceLock};
793     auto update_source = [](ALsource &source)
794     { source.eaxCommit(); };
795     ForEachSource(this, update_source);
796 }
797
798 void ALCcontext::eax_set_misc(const EaxCall& call)
799 {
800     switch(call.get_property_id())
801     {
802     case EAXCONTEXT_NONE:
803         break;
804     case EAXCONTEXT_SPEAKERCONFIG:
805         eax_set<Eax5SpeakerConfigValidator>(call, mEaxSpeakerConfig);
806         break;
807     case EAXCONTEXT_EAXSESSION:
808         eax_set<Eax5SessionAllValidator>(call, mEaxSession);
809         break;
810     default:
811         eax_fail_unknown_property_id();
812     }
813 }
814
815 void ALCcontext::eax4_defer_all(const EaxCall& call, Eax4State& state)
816 {
817     const auto& src = call.get_value<ContextException, const EAX40CONTEXTPROPERTIES>();
818     Eax4AllValidator{}(src);
819     const auto& dst_i = state.i;
820     auto& dst_d = state.d;
821     dst_d = src;
822
823     if(dst_i.guidPrimaryFXSlotID != dst_d.guidPrimaryFXSlotID)
824         mEaxDf |= eax_primary_fx_slot_id_dirty_bit;
825
826     if(dst_i.flDistanceFactor != dst_d.flDistanceFactor)
827         mEaxDf |= eax_distance_factor_dirty_bit;
828
829     if(dst_i.flAirAbsorptionHF != dst_d.flAirAbsorptionHF)
830         mEaxDf |= eax_air_absorption_hf_dirty_bit;
831
832     if(dst_i.flHFReference != dst_d.flHFReference)
833         mEaxDf |= eax_hf_reference_dirty_bit;
834 }
835
836 void ALCcontext::eax4_defer(const EaxCall& call, Eax4State& state)
837 {
838     switch(call.get_property_id())
839     {
840     case EAXCONTEXT_ALLPARAMETERS:
841         eax4_defer_all(call, state);
842         break;
843     case EAXCONTEXT_PRIMARYFXSLOTID:
844         eax_defer<Eax4PrimaryFxSlotIdValidator, eax_primary_fx_slot_id_dirty_bit>(
845             call, state, &EAX40CONTEXTPROPERTIES::guidPrimaryFXSlotID);
846         break;
847     case EAXCONTEXT_DISTANCEFACTOR:
848         eax_defer<Eax4DistanceFactorValidator, eax_distance_factor_dirty_bit>(
849             call, state, &EAX40CONTEXTPROPERTIES::flDistanceFactor);
850         break;
851     case EAXCONTEXT_AIRABSORPTIONHF:
852         eax_defer<Eax4AirAbsorptionHfValidator, eax_air_absorption_hf_dirty_bit>(
853             call, state, &EAX40CONTEXTPROPERTIES::flAirAbsorptionHF);
854         break;
855     case EAXCONTEXT_HFREFERENCE:
856         eax_defer<Eax4HfReferenceValidator, eax_hf_reference_dirty_bit>(
857             call, state, &EAX40CONTEXTPROPERTIES::flHFReference);
858         break;
859     default:
860         eax_set_misc(call);
861         break;
862     }
863 }
864
865 void ALCcontext::eax5_defer_all(const EaxCall& call, Eax5State& state)
866 {
867     const auto& src = call.get_value<ContextException, const EAX50CONTEXTPROPERTIES>();
868     Eax4AllValidator{}(src);
869     const auto& dst_i = state.i;
870     auto& dst_d = state.d;
871     dst_d = src;
872
873     if(dst_i.guidPrimaryFXSlotID != dst_d.guidPrimaryFXSlotID)
874         mEaxDf |= eax_primary_fx_slot_id_dirty_bit;
875
876     if(dst_i.flDistanceFactor != dst_d.flDistanceFactor)
877         mEaxDf |= eax_distance_factor_dirty_bit;
878
879     if(dst_i.flAirAbsorptionHF != dst_d.flAirAbsorptionHF)
880         mEaxDf |= eax_air_absorption_hf_dirty_bit;
881
882     if(dst_i.flHFReference != dst_d.flHFReference)
883         mEaxDf |= eax_hf_reference_dirty_bit;
884
885     if(dst_i.flMacroFXFactor != dst_d.flMacroFXFactor)
886         mEaxDf |= eax_macro_fx_factor_dirty_bit;
887 }
888
889 void ALCcontext::eax5_defer(const EaxCall& call, Eax5State& state)
890 {
891     switch(call.get_property_id())
892     {
893     case EAXCONTEXT_ALLPARAMETERS:
894         eax5_defer_all(call, state);
895         break;
896     case EAXCONTEXT_PRIMARYFXSLOTID:
897         eax_defer<Eax5PrimaryFxSlotIdValidator, eax_primary_fx_slot_id_dirty_bit>(
898             call, state, &EAX50CONTEXTPROPERTIES::guidPrimaryFXSlotID);
899         break;
900     case EAXCONTEXT_DISTANCEFACTOR:
901         eax_defer<Eax4DistanceFactorValidator, eax_distance_factor_dirty_bit>(
902             call, state, &EAX50CONTEXTPROPERTIES::flDistanceFactor);
903         break;
904     case EAXCONTEXT_AIRABSORPTIONHF:
905         eax_defer<Eax4AirAbsorptionHfValidator, eax_air_absorption_hf_dirty_bit>(
906             call, state, &EAX50CONTEXTPROPERTIES::flAirAbsorptionHF);
907         break;
908     case EAXCONTEXT_HFREFERENCE:
909         eax_defer<Eax4HfReferenceValidator, eax_hf_reference_dirty_bit>(
910             call, state, &EAX50CONTEXTPROPERTIES::flHFReference);
911         break;
912     case EAXCONTEXT_MACROFXFACTOR:
913         eax_defer<Eax5MacroFxFactorValidator, eax_macro_fx_factor_dirty_bit>(
914             call, state, &EAX50CONTEXTPROPERTIES::flMacroFXFactor);
915         break;
916     default:
917         eax_set_misc(call);
918         break;
919     }
920 }
921
922 void ALCcontext::eax_set(const EaxCall& call)
923 {
924     const auto version = call.get_version();
925     switch(version)
926     {
927     case 4: eax4_defer(call, mEax4); break;
928     case 5: eax5_defer(call, mEax5); break;
929     default: eax_fail_unknown_version();
930     }
931     if(version != mEaxVersion)
932         mEaxDf = ~EaxDirtyFlags();
933     mEaxVersion = version;
934 }
935
936 void ALCcontext::eax4_context_commit(Eax4State& state, EaxDirtyFlags& dst_df)
937 {
938     if(mEaxDf == EaxDirtyFlags{})
939         return;
940
941     eax_context_commit_property<eax_primary_fx_slot_id_dirty_bit>(
942         state, dst_df, &EAX40CONTEXTPROPERTIES::guidPrimaryFXSlotID);
943     eax_context_commit_property<eax_distance_factor_dirty_bit>(
944         state, dst_df, &EAX40CONTEXTPROPERTIES::flDistanceFactor);
945     eax_context_commit_property<eax_air_absorption_hf_dirty_bit>(
946         state, dst_df, &EAX40CONTEXTPROPERTIES::flAirAbsorptionHF);
947     eax_context_commit_property<eax_hf_reference_dirty_bit>(
948         state, dst_df, &EAX40CONTEXTPROPERTIES::flHFReference);
949
950     mEaxDf = EaxDirtyFlags{};
951 }
952
953 void ALCcontext::eax5_context_commit(Eax5State& state, EaxDirtyFlags& dst_df)
954 {
955     if(mEaxDf == EaxDirtyFlags{})
956         return;
957
958     eax_context_commit_property<eax_primary_fx_slot_id_dirty_bit>(
959         state, dst_df, &EAX50CONTEXTPROPERTIES::guidPrimaryFXSlotID);
960     eax_context_commit_property<eax_distance_factor_dirty_bit>(
961         state, dst_df, &EAX50CONTEXTPROPERTIES::flDistanceFactor);
962     eax_context_commit_property<eax_air_absorption_hf_dirty_bit>(
963         state, dst_df, &EAX50CONTEXTPROPERTIES::flAirAbsorptionHF);
964     eax_context_commit_property<eax_hf_reference_dirty_bit>(
965         state, dst_df, &EAX50CONTEXTPROPERTIES::flHFReference);
966     eax_context_commit_property<eax_macro_fx_factor_dirty_bit>(
967         state, dst_df, &EAX50CONTEXTPROPERTIES::flMacroFXFactor);
968
969     mEaxDf = EaxDirtyFlags{};
970 }
971
972 void ALCcontext::eax_context_commit()
973 {
974     auto dst_df = EaxDirtyFlags{};
975
976     switch(mEaxVersion)
977     {
978     case 1:
979     case 2:
980     case 3:
981         eax5_context_commit(mEax123, dst_df);
982         break;
983     case 4:
984         eax4_context_commit(mEax4, dst_df);
985         break;
986     case 5:
987         eax5_context_commit(mEax5, dst_df);
988         break;
989     }
990
991     if(dst_df == EaxDirtyFlags{})
992         return;
993
994     if((dst_df & eax_primary_fx_slot_id_dirty_bit) != EaxDirtyFlags{})
995         eax_context_commit_primary_fx_slot_id();
996
997     if((dst_df & eax_distance_factor_dirty_bit) != EaxDirtyFlags{})
998         eax_context_commit_distance_factor();
999
1000     if((dst_df & eax_air_absorption_hf_dirty_bit) != EaxDirtyFlags{})
1001         eax_context_commit_air_absorbtion_hf();
1002
1003     if((dst_df & eax_hf_reference_dirty_bit) != EaxDirtyFlags{})
1004         eax_context_commit_hf_reference();
1005
1006     if((dst_df & eax_macro_fx_factor_dirty_bit) != EaxDirtyFlags{})
1007         eax_context_commit_macro_fx_factor();
1008
1009     if((dst_df & eax_primary_fx_slot_id_dirty_bit) != EaxDirtyFlags{})
1010         eax_update_sources();
1011 }
1012
1013 void ALCcontext::eaxCommit()
1014 {
1015     mEaxNeedsCommit = false;
1016     eax_context_commit();
1017     eaxCommitFxSlots();
1018     eax_update_sources();
1019 }
1020
1021 namespace {
1022
1023 class EaxSetException : public EaxException {
1024 public:
1025     explicit EaxSetException(const char* message)
1026         : EaxException{"EAX_SET", message}
1027     {}
1028 };
1029
1030 [[noreturn]] void eax_fail_set(const char* message)
1031 {
1032     throw EaxSetException{message};
1033 }
1034
1035 class EaxGetException : public EaxException {
1036 public:
1037     explicit EaxGetException(const char* message)
1038         : EaxException{"EAX_GET", message}
1039     {}
1040 };
1041
1042 [[noreturn]] void eax_fail_get(const char* message)
1043 {
1044     throw EaxGetException{message};
1045 }
1046
1047 } // namespace
1048
1049
1050 FORCE_ALIGN ALenum AL_APIENTRY EAXSet(
1051     const GUID* property_set_id,
1052     ALuint property_id,
1053     ALuint property_source_id,
1054     ALvoid* property_value,
1055     ALuint property_value_size) noexcept
1056 try
1057 {
1058     auto context = GetContextRef();
1059
1060     if(!context)
1061         eax_fail_set("No current context.");
1062
1063     std::lock_guard<std::mutex> prop_lock{context->mPropLock};
1064
1065     return context->eax_eax_set(
1066         property_set_id,
1067         property_id,
1068         property_source_id,
1069         property_value,
1070         property_value_size);
1071 }
1072 catch (...)
1073 {
1074     eax_log_exception(__func__);
1075     return AL_INVALID_OPERATION;
1076 }
1077
1078 FORCE_ALIGN ALenum AL_APIENTRY EAXGet(
1079     const GUID* property_set_id,
1080     ALuint property_id,
1081     ALuint property_source_id,
1082     ALvoid* property_value,
1083     ALuint property_value_size) noexcept
1084 try
1085 {
1086     auto context = GetContextRef();
1087
1088     if(!context)
1089         eax_fail_get("No current context.");
1090
1091     std::lock_guard<std::mutex> prop_lock{context->mPropLock};
1092
1093     return context->eax_eax_get(
1094         property_set_id,
1095         property_id,
1096         property_source_id,
1097         property_value,
1098         property_value_size);
1099 }
1100 catch (...)
1101 {
1102     eax_log_exception(__func__);
1103     return AL_INVALID_OPERATION;
1104 }
1105 #endif // ALSOFT_EAX