]> git.tdb.fi Git - ext/openal.git/blob - al/source.cpp
Import OpenAL Soft 1.23.1 sources
[ext/openal.git] / al / source.cpp
1 /**
2  * OpenAL cross platform audio library
3  * Copyright (C) 1999-2007 by authors.
4  * This library is free software; you can redistribute it and/or
5  *  modify it under the terms of the GNU Library General Public
6  *  License as published by the Free Software Foundation; either
7  *  version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  *  Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  *  License along with this library; if not, write to the
16  *  Free Software Foundation, Inc.,
17  *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18  * Or go to http://www.gnu.org/copyleft/lgpl.html
19  */
20
21 #include "config.h"
22
23 #include "source.h"
24
25 #include <algorithm>
26 #include <array>
27 #include <atomic>
28 #include <cassert>
29 #include <chrono>
30 #include <climits>
31 #include <cmath>
32 #include <cstdint>
33 #include <functional>
34 #include <inttypes.h>
35 #include <iterator>
36 #include <limits>
37 #include <memory>
38 #include <mutex>
39 #include <new>
40 #include <numeric>
41 #include <stdexcept>
42 #include <thread>
43 #include <utility>
44
45 #include "AL/al.h"
46 #include "AL/alc.h"
47 #include "AL/alext.h"
48 #include "AL/efx.h"
49
50 #include "albit.h"
51 #include "alc/alu.h"
52 #include "alc/backends/base.h"
53 #include "alc/context.h"
54 #include "alc/device.h"
55 #include "alc/inprogext.h"
56 #include "almalloc.h"
57 #include "alnumeric.h"
58 #include "aloptional.h"
59 #include "alspan.h"
60 #include "atomic.h"
61 #include "auxeffectslot.h"
62 #include "buffer.h"
63 #include "core/ambidefs.h"
64 #include "core/bformatdec.h"
65 #include "core/except.h"
66 #include "core/filters/nfc.h"
67 #include "core/filters/splitter.h"
68 #include "core/logging.h"
69 #include "core/voice_change.h"
70 #include "event.h"
71 #include "filter.h"
72 #include "opthelpers.h"
73 #include "ringbuffer.h"
74 #include "threads.h"
75
76 #ifdef ALSOFT_EAX
77 #include <cassert>
78 #endif // ALSOFT_EAX
79
80 bool sBufferSubDataCompat{false};
81
82 namespace {
83
84 using namespace std::placeholders;
85 using std::chrono::nanoseconds;
86
87 Voice *GetSourceVoice(ALsource *source, ALCcontext *context)
88 {
89     auto voicelist = context->getVoicesSpan();
90     ALuint idx{source->VoiceIdx};
91     if(idx < voicelist.size())
92     {
93         ALuint sid{source->id};
94         Voice *voice = voicelist[idx];
95         if(voice->mSourceID.load(std::memory_order_acquire) == sid)
96             return voice;
97     }
98     source->VoiceIdx = INVALID_VOICE_IDX;
99     return nullptr;
100 }
101
102
103 void UpdateSourceProps(const ALsource *source, Voice *voice, ALCcontext *context)
104 {
105     /* Get an unused property container, or allocate a new one as needed. */
106     VoicePropsItem *props{context->mFreeVoiceProps.load(std::memory_order_acquire)};
107     if(!props)
108     {
109         context->allocVoiceProps();
110         props = context->mFreeVoiceProps.load(std::memory_order_acquire);
111     }
112     VoicePropsItem *next;
113     do {
114         next = props->next.load(std::memory_order_relaxed);
115     } while(context->mFreeVoiceProps.compare_exchange_weak(props, next,
116         std::memory_order_acq_rel, std::memory_order_acquire) == false);
117
118     props->Pitch = source->Pitch;
119     props->Gain = source->Gain;
120     props->OuterGain = source->OuterGain;
121     props->MinGain = source->MinGain;
122     props->MaxGain = source->MaxGain;
123     props->InnerAngle = source->InnerAngle;
124     props->OuterAngle = source->OuterAngle;
125     props->RefDistance = source->RefDistance;
126     props->MaxDistance = source->MaxDistance;
127     props->RolloffFactor = source->RolloffFactor
128 #ifdef ALSOFT_EAX
129         + source->RolloffFactor2
130 #endif
131     ;
132     props->Position = source->Position;
133     props->Velocity = source->Velocity;
134     props->Direction = source->Direction;
135     props->OrientAt = source->OrientAt;
136     props->OrientUp = source->OrientUp;
137     props->HeadRelative = source->HeadRelative;
138     props->mDistanceModel = source->mDistanceModel;
139     props->mResampler = source->mResampler;
140     props->DirectChannels = source->DirectChannels;
141     props->mSpatializeMode = source->mSpatialize;
142
143     props->DryGainHFAuto = source->DryGainHFAuto;
144     props->WetGainAuto = source->WetGainAuto;
145     props->WetGainHFAuto = source->WetGainHFAuto;
146     props->OuterGainHF = source->OuterGainHF;
147
148     props->AirAbsorptionFactor = source->AirAbsorptionFactor;
149     props->RoomRolloffFactor = source->RoomRolloffFactor;
150     props->DopplerFactor = source->DopplerFactor;
151
152     props->StereoPan = source->StereoPan;
153
154     props->Radius = source->Radius;
155     props->EnhWidth = source->EnhWidth;
156
157     props->Direct.Gain = source->Direct.Gain;
158     props->Direct.GainHF = source->Direct.GainHF;
159     props->Direct.HFReference = source->Direct.HFReference;
160     props->Direct.GainLF = source->Direct.GainLF;
161     props->Direct.LFReference = source->Direct.LFReference;
162
163     auto copy_send = [](const ALsource::SendData &srcsend) noexcept -> VoiceProps::SendData
164     {
165         VoiceProps::SendData ret{};
166         ret.Slot = srcsend.Slot ? srcsend.Slot->mSlot : nullptr;
167         ret.Gain = srcsend.Gain;
168         ret.GainHF = srcsend.GainHF;
169         ret.HFReference = srcsend.HFReference;
170         ret.GainLF = srcsend.GainLF;
171         ret.LFReference = srcsend.LFReference;
172         return ret;
173     };
174     std::transform(source->Send.cbegin(), source->Send.cend(), props->Send, copy_send);
175     if(!props->Send[0].Slot && context->mDefaultSlot)
176         props->Send[0].Slot = context->mDefaultSlot->mSlot;
177
178     /* Set the new container for updating internal parameters. */
179     props = voice->mUpdate.exchange(props, std::memory_order_acq_rel);
180     if(props)
181     {
182         /* If there was an unused update container, put it back in the
183          * freelist.
184          */
185         AtomicReplaceHead(context->mFreeVoiceProps, props);
186     }
187 }
188
189 /* GetSourceSampleOffset
190  *
191  * Gets the current read offset for the given Source, in 32.32 fixed-point
192  * samples. The offset is relative to the start of the queue (not the start of
193  * the current buffer).
194  */
195 int64_t GetSourceSampleOffset(ALsource *Source, ALCcontext *context, nanoseconds *clocktime)
196 {
197     ALCdevice *device{context->mALDevice.get()};
198     const VoiceBufferItem *Current{};
199     int64_t readPos{};
200     uint refcount{};
201     Voice *voice{};
202
203     do {
204         refcount = device->waitForMix();
205         *clocktime = GetDeviceClockTime(device);
206         voice = GetSourceVoice(Source, context);
207         if(voice)
208         {
209             Current = voice->mCurrentBuffer.load(std::memory_order_relaxed);
210
211             readPos  = int64_t{voice->mPosition.load(std::memory_order_relaxed)} << MixerFracBits;
212             readPos += voice->mPositionFrac.load(std::memory_order_relaxed);
213         }
214         std::atomic_thread_fence(std::memory_order_acquire);
215     } while(refcount != device->MixCount.load(std::memory_order_relaxed));
216
217     if(!voice)
218         return 0;
219
220     for(auto &item : Source->mQueue)
221     {
222         if(&item == Current) break;
223         readPos += int64_t{item.mSampleLen} << MixerFracBits;
224     }
225     if(readPos > std::numeric_limits<int64_t>::max() >> (32-MixerFracBits))
226         return std::numeric_limits<int64_t>::max();
227     return readPos << (32-MixerFracBits);
228 }
229
230 /* GetSourceSecOffset
231  *
232  * Gets the current read offset for the given Source, in seconds. The offset is
233  * relative to the start of the queue (not the start of the current buffer).
234  */
235 double GetSourceSecOffset(ALsource *Source, ALCcontext *context, nanoseconds *clocktime)
236 {
237     ALCdevice *device{context->mALDevice.get()};
238     const VoiceBufferItem *Current{};
239     int64_t readPos{};
240     uint refcount{};
241     Voice *voice{};
242
243     do {
244         refcount = device->waitForMix();
245         *clocktime = GetDeviceClockTime(device);
246         voice = GetSourceVoice(Source, context);
247         if(voice)
248         {
249             Current = voice->mCurrentBuffer.load(std::memory_order_relaxed);
250
251             readPos  = int64_t{voice->mPosition.load(std::memory_order_relaxed)} << MixerFracBits;
252             readPos += voice->mPositionFrac.load(std::memory_order_relaxed);
253         }
254         std::atomic_thread_fence(std::memory_order_acquire);
255     } while(refcount != device->MixCount.load(std::memory_order_relaxed));
256
257     if(!voice)
258         return 0.0f;
259
260     const ALbuffer *BufferFmt{nullptr};
261     auto BufferList = Source->mQueue.cbegin();
262     while(BufferList != Source->mQueue.cend() && al::to_address(BufferList) != Current)
263     {
264         if(!BufferFmt) BufferFmt = BufferList->mBuffer;
265         readPos += int64_t{BufferList->mSampleLen} << MixerFracBits;
266         ++BufferList;
267     }
268     while(BufferList != Source->mQueue.cend() && !BufferFmt)
269     {
270         BufferFmt = BufferList->mBuffer;
271         ++BufferList;
272     }
273     ASSUME(BufferFmt != nullptr);
274
275     return static_cast<double>(readPos) / double{MixerFracOne} / BufferFmt->mSampleRate;
276 }
277
278 /* GetSourceOffset
279  *
280  * Gets the current read offset for the given Source, in the appropriate format
281  * (Bytes, Samples or Seconds). The offset is relative to the start of the
282  * queue (not the start of the current buffer).
283  */
284 double GetSourceOffset(ALsource *Source, ALenum name, ALCcontext *context)
285 {
286     ALCdevice *device{context->mALDevice.get()};
287     const VoiceBufferItem *Current{};
288     int64_t readPos{};
289     uint readPosFrac{};
290     uint refcount;
291     Voice *voice;
292
293     do {
294         refcount = device->waitForMix();
295         voice = GetSourceVoice(Source, context);
296         if(voice)
297         {
298             Current = voice->mCurrentBuffer.load(std::memory_order_relaxed);
299
300             readPos = voice->mPosition.load(std::memory_order_relaxed);
301             readPosFrac = voice->mPositionFrac.load(std::memory_order_relaxed);
302         }
303         std::atomic_thread_fence(std::memory_order_acquire);
304     } while(refcount != device->MixCount.load(std::memory_order_relaxed));
305
306     if(!voice)
307         return 0.0;
308
309     const ALbuffer *BufferFmt{nullptr};
310     auto BufferList = Source->mQueue.cbegin();
311     while(BufferList != Source->mQueue.cend() && al::to_address(BufferList) != Current)
312     {
313         if(!BufferFmt) BufferFmt = BufferList->mBuffer;
314         readPos += BufferList->mSampleLen;
315         ++BufferList;
316     }
317     while(BufferList != Source->mQueue.cend() && !BufferFmt)
318     {
319         BufferFmt = BufferList->mBuffer;
320         ++BufferList;
321     }
322     ASSUME(BufferFmt != nullptr);
323
324     double offset{};
325     switch(name)
326     {
327     case AL_SEC_OFFSET:
328         offset  = static_cast<double>(readPos) + readPosFrac/double{MixerFracOne};
329         offset /= BufferFmt->mSampleRate;
330         break;
331
332     case AL_SAMPLE_OFFSET:
333         offset = static_cast<double>(readPos) + readPosFrac/double{MixerFracOne};
334         break;
335
336     case AL_BYTE_OFFSET:
337         const ALuint BlockSamples{BufferFmt->mBlockAlign};
338         const ALuint BlockSize{BufferFmt->blockSizeFromFmt()};
339
340         /* Round down to the block boundary. */
341         offset = static_cast<double>(readPos / BlockSamples) * BlockSize;
342         break;
343     }
344     return offset;
345 }
346
347 /* GetSourceLength
348  *
349  * Gets the length of the given Source's buffer queue, in the appropriate
350  * format (Bytes, Samples or Seconds).
351  */
352 double GetSourceLength(const ALsource *source, ALenum name)
353 {
354     uint64_t length{0};
355     const ALbuffer *BufferFmt{nullptr};
356     for(auto &listitem : source->mQueue)
357     {
358         if(!BufferFmt)
359             BufferFmt = listitem.mBuffer;
360         length += listitem.mSampleLen;
361     }
362     if(length == 0)
363         return 0.0;
364
365     ASSUME(BufferFmt != nullptr);
366     switch(name)
367     {
368     case AL_SEC_LENGTH_SOFT:
369         return static_cast<double>(length) / BufferFmt->mSampleRate;
370
371     case AL_SAMPLE_LENGTH_SOFT:
372         return static_cast<double>(length);
373
374     case AL_BYTE_LENGTH_SOFT:
375         const ALuint BlockSamples{BufferFmt->mBlockAlign};
376         const ALuint BlockSize{BufferFmt->blockSizeFromFmt()};
377
378         /* Round down to the block boundary. */
379         return static_cast<double>(length / BlockSamples) * BlockSize;
380     }
381     return 0.0;
382 }
383
384
385 struct VoicePos {
386     int pos;
387     uint frac;
388     ALbufferQueueItem *bufferitem;
389 };
390
391 /**
392  * GetSampleOffset
393  *
394  * Retrieves the voice position, fixed-point fraction, and bufferlist item
395  * using the givem offset type and offset. If the offset is out of range,
396  * returns an empty optional.
397  */
398 al::optional<VoicePos> GetSampleOffset(al::deque<ALbufferQueueItem> &BufferList, ALenum OffsetType,
399     double Offset)
400 {
401     /* Find the first valid Buffer in the Queue */
402     const ALbuffer *BufferFmt{nullptr};
403     for(auto &item : BufferList)
404     {
405         BufferFmt = item.mBuffer;
406         if(BufferFmt) break;
407     }
408     if(!BufferFmt) UNLIKELY
409         return al::nullopt;
410
411     /* Get sample frame offset */
412     int64_t offset{};
413     uint frac{};
414     double dbloff, dblfrac;
415     switch(OffsetType)
416     {
417     case AL_SEC_OFFSET:
418         dblfrac = std::modf(Offset*BufferFmt->mSampleRate, &dbloff);
419         if(dblfrac < 0.0)
420         {
421             /* If there's a negative fraction, reduce the offset to "floor" it,
422              * and convert the fraction to a percentage to the next value (e.g.
423              * -2.75 -> -3 + 0.25).
424              */
425             dbloff -= 1.0;
426             dblfrac += 1.0;
427         }
428         offset = static_cast<int64_t>(dbloff);
429         frac = static_cast<uint>(mind(dblfrac*MixerFracOne, MixerFracOne-1.0));
430         break;
431
432     case AL_SAMPLE_OFFSET:
433         dblfrac = std::modf(Offset, &dbloff);
434         if(dblfrac < 0.0)
435         {
436             dbloff -= 1.0;
437             dblfrac += 1.0;
438         }
439         offset = static_cast<int64_t>(dbloff);
440         frac = static_cast<uint>(mind(dblfrac*MixerFracOne, MixerFracOne-1.0));
441         break;
442
443     case AL_BYTE_OFFSET:
444         /* Determine the ByteOffset (and ensure it is block aligned) */
445         Offset = std::floor(Offset / BufferFmt->blockSizeFromFmt());
446         offset = static_cast<int64_t>(Offset) * BufferFmt->mBlockAlign;
447         frac = 0;
448         break;
449     }
450
451     /* Find the bufferlist item this offset belongs to. */
452     if(offset < 0)
453     {
454         if(offset < std::numeric_limits<int>::min())
455             return al::nullopt;
456         return VoicePos{static_cast<int>(offset), frac, &BufferList.front()};
457     }
458
459     if(BufferFmt->mCallback)
460         return al::nullopt;
461
462     int64_t totalBufferLen{0};
463     for(auto &item : BufferList)
464     {
465         if(totalBufferLen > offset)
466             break;
467         if(item.mSampleLen > offset-totalBufferLen)
468         {
469             /* Offset is in this buffer */
470             return VoicePos{static_cast<int>(offset-totalBufferLen), frac, &item};
471         }
472         totalBufferLen += item.mSampleLen;
473     }
474
475     /* Offset is out of range of the queue */
476     return al::nullopt;
477 }
478
479
480 void InitVoice(Voice *voice, ALsource *source, ALbufferQueueItem *BufferList, ALCcontext *context,
481     ALCdevice *device)
482 {
483     voice->mLoopBuffer.store(source->Looping ? &source->mQueue.front() : nullptr,
484         std::memory_order_relaxed);
485
486     ALbuffer *buffer{BufferList->mBuffer};
487     voice->mFrequency = buffer->mSampleRate;
488     voice->mFmtChannels =
489         (buffer->mChannels == FmtStereo && source->mStereoMode == SourceStereo::Enhanced) ?
490         FmtSuperStereo : buffer->mChannels;
491     voice->mFmtType = buffer->mType;
492     voice->mFrameStep = buffer->channelsFromFmt();
493     voice->mBytesPerBlock = buffer->blockSizeFromFmt();
494     voice->mSamplesPerBlock = buffer->mBlockAlign;
495     voice->mAmbiLayout = IsUHJ(voice->mFmtChannels) ? AmbiLayout::FuMa : buffer->mAmbiLayout;
496     voice->mAmbiScaling = IsUHJ(voice->mFmtChannels) ? AmbiScaling::UHJ : buffer->mAmbiScaling;
497     voice->mAmbiOrder = (voice->mFmtChannels == FmtSuperStereo) ? 1 : buffer->mAmbiOrder;
498
499     if(buffer->mCallback) voice->mFlags.set(VoiceIsCallback);
500     else if(source->SourceType == AL_STATIC) voice->mFlags.set(VoiceIsStatic);
501     voice->mNumCallbackBlocks = 0;
502     voice->mCallbackBlockBase = 0;
503
504     voice->prepare(device);
505
506     source->mPropsDirty = false;
507     UpdateSourceProps(source, voice, context);
508
509     voice->mSourceID.store(source->id, std::memory_order_release);
510 }
511
512
513 VoiceChange *GetVoiceChanger(ALCcontext *ctx)
514 {
515     VoiceChange *vchg{ctx->mVoiceChangeTail};
516     if(vchg == ctx->mCurrentVoiceChange.load(std::memory_order_acquire)) UNLIKELY
517     {
518         ctx->allocVoiceChanges();
519         vchg = ctx->mVoiceChangeTail;
520     }
521
522     ctx->mVoiceChangeTail = vchg->mNext.exchange(nullptr, std::memory_order_relaxed);
523
524     return vchg;
525 }
526
527 void SendVoiceChanges(ALCcontext *ctx, VoiceChange *tail)
528 {
529     ALCdevice *device{ctx->mALDevice.get()};
530
531     VoiceChange *oldhead{ctx->mCurrentVoiceChange.load(std::memory_order_acquire)};
532     while(VoiceChange *next{oldhead->mNext.load(std::memory_order_relaxed)})
533         oldhead = next;
534     oldhead->mNext.store(tail, std::memory_order_release);
535
536     const bool connected{device->Connected.load(std::memory_order_acquire)};
537     device->waitForMix();
538     if(!connected) UNLIKELY
539     {
540         if(ctx->mStopVoicesOnDisconnect.load(std::memory_order_acquire))
541         {
542             /* If the device is disconnected and voices are stopped, just
543              * ignore all pending changes.
544              */
545             VoiceChange *cur{ctx->mCurrentVoiceChange.load(std::memory_order_acquire)};
546             while(VoiceChange *next{cur->mNext.load(std::memory_order_acquire)})
547             {
548                 cur = next;
549                 if(Voice *voice{cur->mVoice})
550                     voice->mSourceID.store(0, std::memory_order_relaxed);
551             }
552             ctx->mCurrentVoiceChange.store(cur, std::memory_order_release);
553         }
554     }
555 }
556
557
558 bool SetVoiceOffset(Voice *oldvoice, const VoicePos &vpos, ALsource *source, ALCcontext *context,
559     ALCdevice *device)
560 {
561     /* First, get a free voice to start at the new offset. */
562     auto voicelist = context->getVoicesSpan();
563     Voice *newvoice{};
564     ALuint vidx{0};
565     for(Voice *voice : voicelist)
566     {
567         if(voice->mPlayState.load(std::memory_order_acquire) == Voice::Stopped
568             && voice->mSourceID.load(std::memory_order_relaxed) == 0u
569             && voice->mPendingChange.load(std::memory_order_relaxed) == false)
570         {
571             newvoice = voice;
572             break;
573         }
574         ++vidx;
575     }
576     if(!newvoice) UNLIKELY
577     {
578         auto &allvoices = *context->mVoices.load(std::memory_order_relaxed);
579         if(allvoices.size() == voicelist.size())
580             context->allocVoices(1);
581         context->mActiveVoiceCount.fetch_add(1, std::memory_order_release);
582         voicelist = context->getVoicesSpan();
583
584         vidx = 0;
585         for(Voice *voice : voicelist)
586         {
587             if(voice->mPlayState.load(std::memory_order_acquire) == Voice::Stopped
588                 && voice->mSourceID.load(std::memory_order_relaxed) == 0u
589                 && voice->mPendingChange.load(std::memory_order_relaxed) == false)
590             {
591                 newvoice = voice;
592                 break;
593             }
594             ++vidx;
595         }
596         ASSUME(newvoice != nullptr);
597     }
598
599     /* Initialize the new voice and set its starting offset.
600      * TODO: It might be better to have the VoiceChange processing copy the old
601      * voice's mixing parameters (and pending update) insead of initializing it
602      * all here. This would just need to set the minimum properties to link the
603      * voice to the source and its position-dependent properties (including the
604      * fading flag).
605      */
606     newvoice->mPlayState.store(Voice::Pending, std::memory_order_relaxed);
607     newvoice->mPosition.store(vpos.pos, std::memory_order_relaxed);
608     newvoice->mPositionFrac.store(vpos.frac, std::memory_order_relaxed);
609     newvoice->mCurrentBuffer.store(vpos.bufferitem, std::memory_order_relaxed);
610     newvoice->mStartTime = oldvoice->mStartTime;
611     newvoice->mFlags.reset();
612     if(vpos.pos > 0 || (vpos.pos == 0 && vpos.frac > 0)
613         || vpos.bufferitem != &source->mQueue.front())
614         newvoice->mFlags.set(VoiceIsFading);
615     InitVoice(newvoice, source, vpos.bufferitem, context, device);
616     source->VoiceIdx = vidx;
617
618     /* Set the old voice as having a pending change, and send it off with the
619      * new one with a new offset voice change.
620      */
621     oldvoice->mPendingChange.store(true, std::memory_order_relaxed);
622
623     VoiceChange *vchg{GetVoiceChanger(context)};
624     vchg->mOldVoice = oldvoice;
625     vchg->mVoice = newvoice;
626     vchg->mSourceID = source->id;
627     vchg->mState = VChangeState::Restart;
628     SendVoiceChanges(context, vchg);
629
630     /* If the old voice still has a sourceID, it's still active and the change-
631      * over will work on the next update.
632      */
633     if(oldvoice->mSourceID.load(std::memory_order_acquire) != 0u) LIKELY
634         return true;
635
636     /* Otherwise, if the new voice's state is not pending, the change-over
637      * already happened.
638      */
639     if(newvoice->mPlayState.load(std::memory_order_acquire) != Voice::Pending)
640         return true;
641
642     /* Otherwise, wait for any current mix to finish and check one last time. */
643     device->waitForMix();
644     if(newvoice->mPlayState.load(std::memory_order_acquire) != Voice::Pending)
645         return true;
646     /* The change-over failed because the old voice stopped before the new
647      * voice could start at the new offset. Let go of the new voice and have
648      * the caller store the source offset since it's stopped.
649      */
650     newvoice->mCurrentBuffer.store(nullptr, std::memory_order_relaxed);
651     newvoice->mLoopBuffer.store(nullptr, std::memory_order_relaxed);
652     newvoice->mSourceID.store(0u, std::memory_order_relaxed);
653     newvoice->mPlayState.store(Voice::Stopped, std::memory_order_relaxed);
654     return false;
655 }
656
657
658 /**
659  * Returns if the last known state for the source was playing or paused. Does
660  * not sync with the mixer voice.
661  */
662 inline bool IsPlayingOrPaused(ALsource *source)
663 { return source->state == AL_PLAYING || source->state == AL_PAUSED; }
664
665 /**
666  * Returns an updated source state using the matching voice's status (or lack
667  * thereof).
668  */
669 inline ALenum GetSourceState(ALsource *source, Voice *voice)
670 {
671     if(!voice && source->state == AL_PLAYING)
672         source->state = AL_STOPPED;
673     return source->state;
674 }
675
676
677 bool EnsureSources(ALCcontext *context, size_t needed)
678 {
679     size_t count{std::accumulate(context->mSourceList.cbegin(), context->mSourceList.cend(),
680         size_t{0},
681         [](size_t cur, const SourceSubList &sublist) noexcept -> size_t
682         { return cur + static_cast<ALuint>(al::popcount(sublist.FreeMask)); })};
683
684     while(needed > count)
685     {
686         if(context->mSourceList.size() >= 1<<25) UNLIKELY
687             return false;
688
689         context->mSourceList.emplace_back();
690         auto sublist = context->mSourceList.end() - 1;
691         sublist->FreeMask = ~0_u64;
692         sublist->Sources = static_cast<ALsource*>(al_calloc(alignof(ALsource), sizeof(ALsource)*64));
693         if(!sublist->Sources) UNLIKELY
694         {
695             context->mSourceList.pop_back();
696             return false;
697         }
698         count += 64;
699     }
700     return true;
701 }
702
703 ALsource *AllocSource(ALCcontext *context)
704 {
705     auto sublist = std::find_if(context->mSourceList.begin(), context->mSourceList.end(),
706         [](const SourceSubList &entry) noexcept -> bool
707         { return entry.FreeMask != 0; });
708     auto lidx = static_cast<ALuint>(std::distance(context->mSourceList.begin(), sublist));
709     auto slidx = static_cast<ALuint>(al::countr_zero(sublist->FreeMask));
710     ASSUME(slidx < 64);
711
712     ALsource *source{al::construct_at(sublist->Sources + slidx)};
713
714     /* Add 1 to avoid source ID 0. */
715     source->id = ((lidx<<6) | slidx) + 1;
716
717     context->mNumSources += 1;
718     sublist->FreeMask &= ~(1_u64 << slidx);
719
720     return source;
721 }
722
723 void FreeSource(ALCcontext *context, ALsource *source)
724 {
725     const ALuint id{source->id - 1};
726     const size_t lidx{id >> 6};
727     const ALuint slidx{id & 0x3f};
728
729     if(Voice *voice{GetSourceVoice(source, context)})
730     {
731         VoiceChange *vchg{GetVoiceChanger(context)};
732
733         voice->mPendingChange.store(true, std::memory_order_relaxed);
734         vchg->mVoice = voice;
735         vchg->mSourceID = source->id;
736         vchg->mState = VChangeState::Stop;
737
738         SendVoiceChanges(context, vchg);
739     }
740
741     al::destroy_at(source);
742
743     context->mSourceList[lidx].FreeMask |= 1_u64 << slidx;
744     context->mNumSources--;
745 }
746
747
748 inline ALsource *LookupSource(ALCcontext *context, ALuint id) noexcept
749 {
750     const size_t lidx{(id-1) >> 6};
751     const ALuint slidx{(id-1) & 0x3f};
752
753     if(lidx >= context->mSourceList.size()) UNLIKELY
754         return nullptr;
755     SourceSubList &sublist{context->mSourceList[lidx]};
756     if(sublist.FreeMask & (1_u64 << slidx)) UNLIKELY
757         return nullptr;
758     return sublist.Sources + slidx;
759 }
760
761 inline ALbuffer *LookupBuffer(ALCdevice *device, ALuint id) noexcept
762 {
763     const size_t lidx{(id-1) >> 6};
764     const ALuint slidx{(id-1) & 0x3f};
765
766     if(lidx >= device->BufferList.size()) UNLIKELY
767         return nullptr;
768     BufferSubList &sublist = device->BufferList[lidx];
769     if(sublist.FreeMask & (1_u64 << slidx)) UNLIKELY
770         return nullptr;
771     return sublist.Buffers + slidx;
772 }
773
774 inline ALfilter *LookupFilter(ALCdevice *device, ALuint id) noexcept
775 {
776     const size_t lidx{(id-1) >> 6};
777     const ALuint slidx{(id-1) & 0x3f};
778
779     if(lidx >= device->FilterList.size()) UNLIKELY
780         return nullptr;
781     FilterSubList &sublist = device->FilterList[lidx];
782     if(sublist.FreeMask & (1_u64 << slidx)) UNLIKELY
783         return nullptr;
784     return sublist.Filters + slidx;
785 }
786
787 inline ALeffectslot *LookupEffectSlot(ALCcontext *context, ALuint id) noexcept
788 {
789     const size_t lidx{(id-1) >> 6};
790     const ALuint slidx{(id-1) & 0x3f};
791
792     if(lidx >= context->mEffectSlotList.size()) UNLIKELY
793         return nullptr;
794     EffectSlotSubList &sublist{context->mEffectSlotList[lidx]};
795     if(sublist.FreeMask & (1_u64 << slidx)) UNLIKELY
796         return nullptr;
797     return sublist.EffectSlots + slidx;
798 }
799
800
801 al::optional<SourceStereo> StereoModeFromEnum(ALenum mode)
802 {
803     switch(mode)
804     {
805     case AL_NORMAL_SOFT: return SourceStereo::Normal;
806     case AL_SUPER_STEREO_SOFT: return SourceStereo::Enhanced;
807     }
808     WARN("Unsupported stereo mode: 0x%04x\n", mode);
809     return al::nullopt;
810 }
811 ALenum EnumFromStereoMode(SourceStereo mode)
812 {
813     switch(mode)
814     {
815     case SourceStereo::Normal: return AL_NORMAL_SOFT;
816     case SourceStereo::Enhanced: return AL_SUPER_STEREO_SOFT;
817     }
818     throw std::runtime_error{"Invalid SourceStereo: "+std::to_string(int(mode))};
819 }
820
821 al::optional<SpatializeMode> SpatializeModeFromEnum(ALenum mode)
822 {
823     switch(mode)
824     {
825     case AL_FALSE: return SpatializeMode::Off;
826     case AL_TRUE: return SpatializeMode::On;
827     case AL_AUTO_SOFT: return SpatializeMode::Auto;
828     }
829     WARN("Unsupported spatialize mode: 0x%04x\n", mode);
830     return al::nullopt;
831 }
832 ALenum EnumFromSpatializeMode(SpatializeMode mode)
833 {
834     switch(mode)
835     {
836     case SpatializeMode::Off: return AL_FALSE;
837     case SpatializeMode::On: return AL_TRUE;
838     case SpatializeMode::Auto: return AL_AUTO_SOFT;
839     }
840     throw std::runtime_error{"Invalid SpatializeMode: "+std::to_string(int(mode))};
841 }
842
843 al::optional<DirectMode> DirectModeFromEnum(ALenum mode)
844 {
845     switch(mode)
846     {
847     case AL_FALSE: return DirectMode::Off;
848     case AL_DROP_UNMATCHED_SOFT: return DirectMode::DropMismatch;
849     case AL_REMIX_UNMATCHED_SOFT: return DirectMode::RemixMismatch;
850     }
851     WARN("Unsupported direct mode: 0x%04x\n", mode);
852     return al::nullopt;
853 }
854 ALenum EnumFromDirectMode(DirectMode mode)
855 {
856     switch(mode)
857     {
858     case DirectMode::Off: return AL_FALSE;
859     case DirectMode::DropMismatch: return AL_DROP_UNMATCHED_SOFT;
860     case DirectMode::RemixMismatch: return AL_REMIX_UNMATCHED_SOFT;
861     }
862     throw std::runtime_error{"Invalid DirectMode: "+std::to_string(int(mode))};
863 }
864
865 al::optional<DistanceModel> DistanceModelFromALenum(ALenum model)
866 {
867     switch(model)
868     {
869     case AL_NONE: return DistanceModel::Disable;
870     case AL_INVERSE_DISTANCE: return DistanceModel::Inverse;
871     case AL_INVERSE_DISTANCE_CLAMPED: return DistanceModel::InverseClamped;
872     case AL_LINEAR_DISTANCE: return DistanceModel::Linear;
873     case AL_LINEAR_DISTANCE_CLAMPED: return DistanceModel::LinearClamped;
874     case AL_EXPONENT_DISTANCE: return DistanceModel::Exponent;
875     case AL_EXPONENT_DISTANCE_CLAMPED: return DistanceModel::ExponentClamped;
876     }
877     return al::nullopt;
878 }
879 ALenum ALenumFromDistanceModel(DistanceModel model)
880 {
881     switch(model)
882     {
883     case DistanceModel::Disable: return AL_NONE;
884     case DistanceModel::Inverse: return AL_INVERSE_DISTANCE;
885     case DistanceModel::InverseClamped: return AL_INVERSE_DISTANCE_CLAMPED;
886     case DistanceModel::Linear: return AL_LINEAR_DISTANCE;
887     case DistanceModel::LinearClamped: return AL_LINEAR_DISTANCE_CLAMPED;
888     case DistanceModel::Exponent: return AL_EXPONENT_DISTANCE;
889     case DistanceModel::ExponentClamped: return AL_EXPONENT_DISTANCE_CLAMPED;
890     }
891     throw std::runtime_error{"Unexpected distance model "+std::to_string(static_cast<int>(model))};
892 }
893
894 enum SourceProp : ALenum {
895     srcPitch = AL_PITCH,
896     srcGain = AL_GAIN,
897     srcMinGain = AL_MIN_GAIN,
898     srcMaxGain = AL_MAX_GAIN,
899     srcMaxDistance = AL_MAX_DISTANCE,
900     srcRolloffFactor = AL_ROLLOFF_FACTOR,
901     srcDopplerFactor = AL_DOPPLER_FACTOR,
902     srcConeOuterGain = AL_CONE_OUTER_GAIN,
903     srcSecOffset = AL_SEC_OFFSET,
904     srcSampleOffset = AL_SAMPLE_OFFSET,
905     srcByteOffset = AL_BYTE_OFFSET,
906     srcConeInnerAngle = AL_CONE_INNER_ANGLE,
907     srcConeOuterAngle = AL_CONE_OUTER_ANGLE,
908     srcRefDistance = AL_REFERENCE_DISTANCE,
909
910     srcPosition = AL_POSITION,
911     srcVelocity = AL_VELOCITY,
912     srcDirection = AL_DIRECTION,
913
914     srcSourceRelative = AL_SOURCE_RELATIVE,
915     srcLooping = AL_LOOPING,
916     srcBuffer = AL_BUFFER,
917     srcSourceState = AL_SOURCE_STATE,
918     srcBuffersQueued = AL_BUFFERS_QUEUED,
919     srcBuffersProcessed = AL_BUFFERS_PROCESSED,
920     srcSourceType = AL_SOURCE_TYPE,
921
922     /* ALC_EXT_EFX */
923     srcConeOuterGainHF = AL_CONE_OUTER_GAINHF,
924     srcAirAbsorptionFactor = AL_AIR_ABSORPTION_FACTOR,
925     srcRoomRolloffFactor =  AL_ROOM_ROLLOFF_FACTOR,
926     srcDirectFilterGainHFAuto = AL_DIRECT_FILTER_GAINHF_AUTO,
927     srcAuxSendFilterGainAuto = AL_AUXILIARY_SEND_FILTER_GAIN_AUTO,
928     srcAuxSendFilterGainHFAuto = AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO,
929     srcDirectFilter = AL_DIRECT_FILTER,
930     srcAuxSendFilter = AL_AUXILIARY_SEND_FILTER,
931
932     /* AL_SOFT_direct_channels */
933     srcDirectChannelsSOFT = AL_DIRECT_CHANNELS_SOFT,
934
935     /* AL_EXT_source_distance_model */
936     srcDistanceModel = AL_DISTANCE_MODEL,
937
938     /* AL_SOFT_source_latency */
939     srcSampleOffsetLatencySOFT = AL_SAMPLE_OFFSET_LATENCY_SOFT,
940     srcSecOffsetLatencySOFT = AL_SEC_OFFSET_LATENCY_SOFT,
941
942     /* AL_EXT_STEREO_ANGLES */
943     srcAngles = AL_STEREO_ANGLES,
944
945     /* AL_EXT_SOURCE_RADIUS */
946     srcRadius = AL_SOURCE_RADIUS,
947
948     /* AL_EXT_BFORMAT */
949     srcOrientation = AL_ORIENTATION,
950
951     /* AL_SOFT_source_length */
952     srcByteLength = AL_BYTE_LENGTH_SOFT,
953     srcSampleLength = AL_SAMPLE_LENGTH_SOFT,
954     srcSecLength = AL_SEC_LENGTH_SOFT,
955
956     /* AL_SOFT_source_resampler */
957     srcResampler = AL_SOURCE_RESAMPLER_SOFT,
958
959     /* AL_SOFT_source_spatialize */
960     srcSpatialize = AL_SOURCE_SPATIALIZE_SOFT,
961
962     /* ALC_SOFT_device_clock */
963     srcSampleOffsetClockSOFT = AL_SAMPLE_OFFSET_CLOCK_SOFT,
964     srcSecOffsetClockSOFT = AL_SEC_OFFSET_CLOCK_SOFT,
965
966     /* AL_SOFT_UHJ */
967     srcStereoMode = AL_STEREO_MODE_SOFT,
968     srcSuperStereoWidth = AL_SUPER_STEREO_WIDTH_SOFT,
969
970     /* AL_SOFT_buffer_sub_data */
971     srcByteRWOffsetsSOFT = AL_BYTE_RW_OFFSETS_SOFT,
972     srcSampleRWOffsetsSOFT = AL_SAMPLE_RW_OFFSETS_SOFT,
973 };
974
975
976 constexpr size_t MaxValues{6u};
977
978 constexpr ALuint IntValsByProp(ALenum prop)
979 {
980     switch(static_cast<SourceProp>(prop))
981     {
982     case AL_SOURCE_STATE:
983     case AL_SOURCE_TYPE:
984     case AL_BUFFERS_QUEUED:
985     case AL_BUFFERS_PROCESSED:
986     case AL_BYTE_LENGTH_SOFT:
987     case AL_SAMPLE_LENGTH_SOFT:
988     case AL_SOURCE_RELATIVE:
989     case AL_LOOPING:
990     case AL_BUFFER:
991     case AL_SAMPLE_OFFSET:
992     case AL_BYTE_OFFSET:
993     case AL_DIRECT_FILTER:
994     case AL_DIRECT_FILTER_GAINHF_AUTO:
995     case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO:
996     case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO:
997     case AL_DIRECT_CHANNELS_SOFT:
998     case AL_DISTANCE_MODEL:
999     case AL_SOURCE_RESAMPLER_SOFT:
1000     case AL_SOURCE_SPATIALIZE_SOFT:
1001     case AL_STEREO_MODE_SOFT:
1002         return 1;
1003
1004     case AL_SOURCE_RADIUS: /*AL_BYTE_RW_OFFSETS_SOFT:*/
1005         if(sBufferSubDataCompat)
1006             return 2;
1007         /*fall-through*/
1008     case AL_CONE_INNER_ANGLE:
1009     case AL_CONE_OUTER_ANGLE:
1010     case AL_PITCH:
1011     case AL_GAIN:
1012     case AL_MIN_GAIN:
1013     case AL_MAX_GAIN:
1014     case AL_REFERENCE_DISTANCE:
1015     case AL_ROLLOFF_FACTOR:
1016     case AL_CONE_OUTER_GAIN:
1017     case AL_MAX_DISTANCE:
1018     case AL_SEC_OFFSET:
1019     case AL_DOPPLER_FACTOR:
1020     case AL_CONE_OUTER_GAINHF:
1021     case AL_AIR_ABSORPTION_FACTOR:
1022     case AL_ROOM_ROLLOFF_FACTOR:
1023     case AL_SEC_LENGTH_SOFT:
1024     case AL_SUPER_STEREO_WIDTH_SOFT:
1025         return 1; /* 1x float */
1026
1027     case AL_SAMPLE_RW_OFFSETS_SOFT:
1028         if(sBufferSubDataCompat)
1029             return 2;
1030         break;
1031
1032     case AL_AUXILIARY_SEND_FILTER:
1033         return 3;
1034
1035     case AL_POSITION:
1036     case AL_VELOCITY:
1037     case AL_DIRECTION:
1038         return 3; /* 3x float */
1039
1040     case AL_ORIENTATION:
1041         return 6; /* 6x float */
1042
1043     case AL_SAMPLE_OFFSET_LATENCY_SOFT:
1044     case AL_SAMPLE_OFFSET_CLOCK_SOFT:
1045     case AL_STEREO_ANGLES:
1046         break; /* i64 only */
1047     case AL_SEC_OFFSET_LATENCY_SOFT:
1048     case AL_SEC_OFFSET_CLOCK_SOFT:
1049         break; /* double only */
1050     }
1051
1052     return 0;
1053 }
1054
1055 constexpr ALuint Int64ValsByProp(ALenum prop)
1056 {
1057     switch(static_cast<SourceProp>(prop))
1058     {
1059     case AL_SOURCE_STATE:
1060     case AL_SOURCE_TYPE:
1061     case AL_BUFFERS_QUEUED:
1062     case AL_BUFFERS_PROCESSED:
1063     case AL_BYTE_LENGTH_SOFT:
1064     case AL_SAMPLE_LENGTH_SOFT:
1065     case AL_SOURCE_RELATIVE:
1066     case AL_LOOPING:
1067     case AL_BUFFER:
1068     case AL_SAMPLE_OFFSET:
1069     case AL_BYTE_OFFSET:
1070     case AL_DIRECT_FILTER:
1071     case AL_DIRECT_FILTER_GAINHF_AUTO:
1072     case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO:
1073     case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO:
1074     case AL_DIRECT_CHANNELS_SOFT:
1075     case AL_DISTANCE_MODEL:
1076     case AL_SOURCE_RESAMPLER_SOFT:
1077     case AL_SOURCE_SPATIALIZE_SOFT:
1078     case AL_STEREO_MODE_SOFT:
1079         return 1;
1080
1081     case AL_SOURCE_RADIUS: /*AL_BYTE_RW_OFFSETS_SOFT:*/
1082         if(sBufferSubDataCompat)
1083             return 2;
1084         /*fall-through*/
1085     case AL_CONE_INNER_ANGLE:
1086     case AL_CONE_OUTER_ANGLE:
1087     case AL_PITCH:
1088     case AL_GAIN:
1089     case AL_MIN_GAIN:
1090     case AL_MAX_GAIN:
1091     case AL_REFERENCE_DISTANCE:
1092     case AL_ROLLOFF_FACTOR:
1093     case AL_CONE_OUTER_GAIN:
1094     case AL_MAX_DISTANCE:
1095     case AL_SEC_OFFSET:
1096     case AL_DOPPLER_FACTOR:
1097     case AL_CONE_OUTER_GAINHF:
1098     case AL_AIR_ABSORPTION_FACTOR:
1099     case AL_ROOM_ROLLOFF_FACTOR:
1100     case AL_SEC_LENGTH_SOFT:
1101     case AL_SUPER_STEREO_WIDTH_SOFT:
1102         return 1; /* 1x float */
1103
1104     case AL_SAMPLE_RW_OFFSETS_SOFT:
1105         if(sBufferSubDataCompat)
1106             return 2;
1107         break;
1108
1109     case AL_SAMPLE_OFFSET_LATENCY_SOFT:
1110     case AL_SAMPLE_OFFSET_CLOCK_SOFT:
1111     case AL_STEREO_ANGLES:
1112         return 2;
1113
1114     case AL_AUXILIARY_SEND_FILTER:
1115         return 3;
1116
1117     case AL_POSITION:
1118     case AL_VELOCITY:
1119     case AL_DIRECTION:
1120         return 3; /* 3x float */
1121
1122     case AL_ORIENTATION:
1123         return 6; /* 6x float */
1124
1125     case AL_SEC_OFFSET_LATENCY_SOFT:
1126     case AL_SEC_OFFSET_CLOCK_SOFT:
1127         break; /* double only */
1128     }
1129
1130     return 0;
1131 }
1132
1133 constexpr ALuint FloatValsByProp(ALenum prop)
1134 {
1135     switch(static_cast<SourceProp>(prop))
1136     {
1137     case AL_PITCH:
1138     case AL_GAIN:
1139     case AL_MIN_GAIN:
1140     case AL_MAX_GAIN:
1141     case AL_MAX_DISTANCE:
1142     case AL_ROLLOFF_FACTOR:
1143     case AL_DOPPLER_FACTOR:
1144     case AL_CONE_OUTER_GAIN:
1145     case AL_SEC_OFFSET:
1146     case AL_SAMPLE_OFFSET:
1147     case AL_BYTE_OFFSET:
1148     case AL_CONE_INNER_ANGLE:
1149     case AL_CONE_OUTER_ANGLE:
1150     case AL_REFERENCE_DISTANCE:
1151     case AL_CONE_OUTER_GAINHF:
1152     case AL_AIR_ABSORPTION_FACTOR:
1153     case AL_ROOM_ROLLOFF_FACTOR:
1154     case AL_DIRECT_FILTER_GAINHF_AUTO:
1155     case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO:
1156     case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO:
1157     case AL_DIRECT_CHANNELS_SOFT:
1158     case AL_DISTANCE_MODEL:
1159     case AL_SOURCE_RELATIVE:
1160     case AL_LOOPING:
1161     case AL_SOURCE_STATE:
1162     case AL_BUFFERS_QUEUED:
1163     case AL_BUFFERS_PROCESSED:
1164     case AL_SOURCE_TYPE:
1165     case AL_SOURCE_RESAMPLER_SOFT:
1166     case AL_SOURCE_SPATIALIZE_SOFT:
1167     case AL_BYTE_LENGTH_SOFT:
1168     case AL_SAMPLE_LENGTH_SOFT:
1169     case AL_SEC_LENGTH_SOFT:
1170     case AL_STEREO_MODE_SOFT:
1171     case AL_SUPER_STEREO_WIDTH_SOFT:
1172         return 1;
1173
1174     case AL_SOURCE_RADIUS: /*AL_BYTE_RW_OFFSETS_SOFT:*/
1175         if(!sBufferSubDataCompat)
1176             return 1;
1177         /*fall-through*/
1178     case AL_SAMPLE_RW_OFFSETS_SOFT:
1179         break;
1180
1181     case AL_STEREO_ANGLES:
1182         return 2;
1183
1184     case AL_POSITION:
1185     case AL_VELOCITY:
1186     case AL_DIRECTION:
1187         return 3;
1188
1189     case AL_ORIENTATION:
1190         return 6;
1191
1192     case AL_SEC_OFFSET_LATENCY_SOFT:
1193     case AL_SEC_OFFSET_CLOCK_SOFT:
1194         break; /* Double only */
1195
1196     case AL_BUFFER:
1197     case AL_DIRECT_FILTER:
1198     case AL_AUXILIARY_SEND_FILTER:
1199         break; /* i/i64 only */
1200     case AL_SAMPLE_OFFSET_LATENCY_SOFT:
1201     case AL_SAMPLE_OFFSET_CLOCK_SOFT:
1202         break; /* i64 only */
1203     }
1204     return 0;
1205 }
1206 constexpr ALuint DoubleValsByProp(ALenum prop)
1207 {
1208     switch(static_cast<SourceProp>(prop))
1209     {
1210     case AL_PITCH:
1211     case AL_GAIN:
1212     case AL_MIN_GAIN:
1213     case AL_MAX_GAIN:
1214     case AL_MAX_DISTANCE:
1215     case AL_ROLLOFF_FACTOR:
1216     case AL_DOPPLER_FACTOR:
1217     case AL_CONE_OUTER_GAIN:
1218     case AL_SEC_OFFSET:
1219     case AL_SAMPLE_OFFSET:
1220     case AL_BYTE_OFFSET:
1221     case AL_CONE_INNER_ANGLE:
1222     case AL_CONE_OUTER_ANGLE:
1223     case AL_REFERENCE_DISTANCE:
1224     case AL_CONE_OUTER_GAINHF:
1225     case AL_AIR_ABSORPTION_FACTOR:
1226     case AL_ROOM_ROLLOFF_FACTOR:
1227     case AL_DIRECT_FILTER_GAINHF_AUTO:
1228     case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO:
1229     case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO:
1230     case AL_DIRECT_CHANNELS_SOFT:
1231     case AL_DISTANCE_MODEL:
1232     case AL_SOURCE_RELATIVE:
1233     case AL_LOOPING:
1234     case AL_SOURCE_STATE:
1235     case AL_BUFFERS_QUEUED:
1236     case AL_BUFFERS_PROCESSED:
1237     case AL_SOURCE_TYPE:
1238     case AL_SOURCE_RESAMPLER_SOFT:
1239     case AL_SOURCE_SPATIALIZE_SOFT:
1240     case AL_BYTE_LENGTH_SOFT:
1241     case AL_SAMPLE_LENGTH_SOFT:
1242     case AL_SEC_LENGTH_SOFT:
1243     case AL_STEREO_MODE_SOFT:
1244     case AL_SUPER_STEREO_WIDTH_SOFT:
1245         return 1;
1246
1247     case AL_SOURCE_RADIUS: /*AL_BYTE_RW_OFFSETS_SOFT:*/
1248         if(!sBufferSubDataCompat)
1249             return 1;
1250         /*fall-through*/
1251     case AL_SAMPLE_RW_OFFSETS_SOFT:
1252         break;
1253
1254     case AL_SEC_OFFSET_LATENCY_SOFT:
1255     case AL_SEC_OFFSET_CLOCK_SOFT:
1256     case AL_STEREO_ANGLES:
1257         return 2;
1258
1259     case AL_POSITION:
1260     case AL_VELOCITY:
1261     case AL_DIRECTION:
1262         return 3;
1263
1264     case AL_ORIENTATION:
1265         return 6;
1266
1267     case AL_BUFFER:
1268     case AL_DIRECT_FILTER:
1269     case AL_AUXILIARY_SEND_FILTER:
1270         break; /* i/i64 only */
1271     case AL_SAMPLE_OFFSET_LATENCY_SOFT:
1272     case AL_SAMPLE_OFFSET_CLOCK_SOFT:
1273         break; /* i64 only */
1274     }
1275     return 0;
1276 }
1277
1278
1279 void SetSourcefv(ALsource *const Source, ALCcontext *const Context, const SourceProp prop, const al::span<const float> values);
1280 void SetSourceiv(ALsource *const Source, ALCcontext *const Context, const SourceProp prop, const al::span<const int> values);
1281 void SetSourcei64v(ALsource *const Source, ALCcontext *const Context, const SourceProp prop, const al::span<const int64_t> values);
1282
1283 struct check_exception : std::exception {
1284 };
1285 struct check_size_exception final : check_exception {
1286     const char *what() const noexcept override
1287     { return "check_size_exception"; }
1288 };
1289 struct check_value_exception final : check_exception {
1290     const char *what() const noexcept override
1291     { return "check_value_exception"; }
1292 };
1293
1294
1295 void UpdateSourceProps(ALsource *source, ALCcontext *context)
1296 {
1297     if(!context->mDeferUpdates)
1298     {
1299         if(Voice *voice{GetSourceVoice(source, context)})
1300         {
1301             UpdateSourceProps(source, voice, context);
1302             return;
1303         }
1304     }
1305     source->mPropsDirty = true;
1306 }
1307 #ifdef ALSOFT_EAX
1308 void CommitAndUpdateSourceProps(ALsource *source, ALCcontext *context)
1309 {
1310     if(!context->mDeferUpdates)
1311     {
1312         if(context->hasEax())
1313             source->eaxCommit();
1314         if(Voice *voice{GetSourceVoice(source, context)})
1315         {
1316             UpdateSourceProps(source, voice, context);
1317             return;
1318         }
1319     }
1320     source->mPropsDirty = true;
1321 }
1322
1323 #else
1324
1325 inline void CommitAndUpdateSourceProps(ALsource *source, ALCcontext *context)
1326 { UpdateSourceProps(source, context); }
1327 #endif
1328
1329
1330 /**
1331  * Returns a pair of lambdas to check the following setters and getters.
1332  *
1333  * The first lambda checks the size of the span is valid for its given size,
1334  * setting the proper context error and throwing a check_size_exception if it
1335  * fails.
1336  *
1337  * The second lambda tests the validity of the value check, setting the proper
1338  * context error and throwing a check_value_exception if it failed.
1339  */
1340 template<typename T, size_t N>
1341 auto GetCheckers(ALCcontext *const Context, const SourceProp prop, const al::span<T,N> values)
1342 {
1343     return std::make_pair(
1344         [=](size_t expect) -> void
1345         {
1346             if(values.size() == expect) LIKELY return;
1347             Context->setError(AL_INVALID_ENUM, "Property 0x%04x expects %zu value(s), got %zu",
1348                 prop, expect, values.size());
1349             throw check_size_exception{};
1350         },
1351         [Context](bool passed) -> void
1352         {
1353             if(passed) LIKELY return;
1354             Context->setError(AL_INVALID_VALUE, "Value out of range");
1355             throw check_value_exception{};
1356         }
1357     );
1358 }
1359
1360 void SetSourcefv(ALsource *const Source, ALCcontext *const Context, const SourceProp prop,
1361     const al::span<const float> values)
1362 try {
1363     /* Structured bindings would be nice (C++17). */
1364     auto Checkers = GetCheckers(Context, prop, values);
1365     auto &CheckSize = Checkers.first;
1366     auto &CheckValue = Checkers.second;
1367     int ival;
1368
1369     switch(prop)
1370     {
1371     case AL_SEC_LENGTH_SOFT:
1372     case AL_SEC_OFFSET_LATENCY_SOFT:
1373     case AL_SEC_OFFSET_CLOCK_SOFT:
1374         /* Query only */
1375         return Context->setError(AL_INVALID_OPERATION,
1376             "Setting read-only source property 0x%04x", prop);
1377
1378     case AL_PITCH:
1379         CheckSize(1);
1380         CheckValue(values[0] >= 0.0f);
1381
1382         Source->Pitch = values[0];
1383         return UpdateSourceProps(Source, Context);
1384
1385     case AL_CONE_INNER_ANGLE:
1386         CheckSize(1);
1387         CheckValue(values[0] >= 0.0f && values[0] <= 360.0f);
1388
1389         Source->InnerAngle = values[0];
1390         return CommitAndUpdateSourceProps(Source, Context);
1391
1392     case AL_CONE_OUTER_ANGLE:
1393         CheckSize(1);
1394         CheckValue(values[0] >= 0.0f && values[0] <= 360.0f);
1395
1396         Source->OuterAngle = values[0];
1397         return CommitAndUpdateSourceProps(Source, Context);
1398
1399     case AL_GAIN:
1400         CheckSize(1);
1401         CheckValue(values[0] >= 0.0f);
1402
1403         Source->Gain = values[0];
1404         return UpdateSourceProps(Source, Context);
1405
1406     case AL_MAX_DISTANCE:
1407         CheckSize(1);
1408         CheckValue(values[0] >= 0.0f);
1409
1410         Source->MaxDistance = values[0];
1411         return CommitAndUpdateSourceProps(Source, Context);
1412
1413     case AL_ROLLOFF_FACTOR:
1414         CheckSize(1);
1415         CheckValue(values[0] >= 0.0f);
1416
1417         Source->RolloffFactor = values[0];
1418         return CommitAndUpdateSourceProps(Source, Context);
1419
1420     case AL_REFERENCE_DISTANCE:
1421         CheckSize(1);
1422         CheckValue(values[0] >= 0.0f);
1423
1424         Source->RefDistance = values[0];
1425         return CommitAndUpdateSourceProps(Source, Context);
1426
1427     case AL_MIN_GAIN:
1428         CheckSize(1);
1429         CheckValue(values[0] >= 0.0f);
1430
1431         Source->MinGain = values[0];
1432         return UpdateSourceProps(Source, Context);
1433
1434     case AL_MAX_GAIN:
1435         CheckSize(1);
1436         CheckValue(values[0] >= 0.0f);
1437
1438         Source->MaxGain = values[0];
1439         return UpdateSourceProps(Source, Context);
1440
1441     case AL_CONE_OUTER_GAIN:
1442         CheckSize(1);
1443         CheckValue(values[0] >= 0.0f && values[0] <= 1.0f);
1444
1445         Source->OuterGain = values[0];
1446         return UpdateSourceProps(Source, Context);
1447
1448     case AL_CONE_OUTER_GAINHF:
1449         CheckSize(1);
1450         CheckValue(values[0] >= 0.0f && values[0] <= 1.0f);
1451
1452         Source->OuterGainHF = values[0];
1453         return UpdateSourceProps(Source, Context);
1454
1455     case AL_AIR_ABSORPTION_FACTOR:
1456         CheckSize(1);
1457         CheckValue(values[0] >= 0.0f && values[0] <= 10.0f);
1458
1459         Source->AirAbsorptionFactor = values[0];
1460         return UpdateSourceProps(Source, Context);
1461
1462     case AL_ROOM_ROLLOFF_FACTOR:
1463         CheckSize(1);
1464         CheckValue(values[0] >= 0.0f && values[0] <= 10.0f);
1465
1466         Source->RoomRolloffFactor = values[0];
1467         return UpdateSourceProps(Source, Context);
1468
1469     case AL_DOPPLER_FACTOR:
1470         CheckSize(1);
1471         CheckValue(values[0] >= 0.0f && values[0] <= 1.0f);
1472
1473         Source->DopplerFactor = values[0];
1474         return UpdateSourceProps(Source, Context);
1475
1476     case AL_SEC_OFFSET:
1477     case AL_SAMPLE_OFFSET:
1478     case AL_BYTE_OFFSET:
1479         CheckSize(1);
1480         CheckValue(std::isfinite(values[0]));
1481
1482         if(Voice *voice{GetSourceVoice(Source, Context)})
1483         {
1484             auto vpos = GetSampleOffset(Source->mQueue, prop, values[0]);
1485             if(!vpos) return Context->setError(AL_INVALID_VALUE, "Invalid offset");
1486
1487             if(SetVoiceOffset(voice, *vpos, Source, Context, Context->mALDevice.get()))
1488                 return;
1489         }
1490         Source->OffsetType = prop;
1491         Source->Offset = values[0];
1492         return;
1493
1494     case AL_SAMPLE_RW_OFFSETS_SOFT:
1495         break;
1496
1497     case AL_SOURCE_RADIUS: /*AL_BYTE_RW_OFFSETS_SOFT:*/
1498         if(sBufferSubDataCompat)
1499             break;
1500         CheckSize(1);
1501         CheckValue(values[0] >= 0.0f && std::isfinite(values[0]));
1502
1503         Source->Radius = values[0];
1504         return UpdateSourceProps(Source, Context);
1505
1506     case AL_SUPER_STEREO_WIDTH_SOFT:
1507         CheckSize(1);
1508         CheckValue(values[0] >= 0.0f && values[0] <= 1.0f);
1509
1510         Source->EnhWidth = values[0];
1511         return UpdateSourceProps(Source, Context);
1512
1513     case AL_STEREO_ANGLES:
1514         CheckSize(2);
1515         CheckValue(std::isfinite(values[0]) && std::isfinite(values[1]));
1516
1517         Source->StereoPan[0] = values[0];
1518         Source->StereoPan[1] = values[1];
1519         return UpdateSourceProps(Source, Context);
1520
1521
1522     case AL_POSITION:
1523         CheckSize(3);
1524         CheckValue(std::isfinite(values[0]) && std::isfinite(values[1]) && std::isfinite(values[2]));
1525
1526         Source->Position[0] = values[0];
1527         Source->Position[1] = values[1];
1528         Source->Position[2] = values[2];
1529         return CommitAndUpdateSourceProps(Source, Context);
1530
1531     case AL_VELOCITY:
1532         CheckSize(3);
1533         CheckValue(std::isfinite(values[0]) && std::isfinite(values[1]) && std::isfinite(values[2]));
1534
1535         Source->Velocity[0] = values[0];
1536         Source->Velocity[1] = values[1];
1537         Source->Velocity[2] = values[2];
1538         return CommitAndUpdateSourceProps(Source, Context);
1539
1540     case AL_DIRECTION:
1541         CheckSize(3);
1542         CheckValue(std::isfinite(values[0]) && std::isfinite(values[1]) && std::isfinite(values[2]));
1543
1544         Source->Direction[0] = values[0];
1545         Source->Direction[1] = values[1];
1546         Source->Direction[2] = values[2];
1547         return CommitAndUpdateSourceProps(Source, Context);
1548
1549     case AL_ORIENTATION:
1550         CheckSize(6);
1551         CheckValue(std::isfinite(values[0]) && std::isfinite(values[1]) && std::isfinite(values[2])
1552             && std::isfinite(values[3]) && std::isfinite(values[4]) && std::isfinite(values[5]));
1553
1554         Source->OrientAt[0] = values[0];
1555         Source->OrientAt[1] = values[1];
1556         Source->OrientAt[2] = values[2];
1557         Source->OrientUp[0] = values[3];
1558         Source->OrientUp[1] = values[4];
1559         Source->OrientUp[2] = values[5];
1560         return UpdateSourceProps(Source, Context);
1561
1562
1563     case AL_SOURCE_RELATIVE:
1564     case AL_LOOPING:
1565     case AL_SOURCE_STATE:
1566     case AL_SOURCE_TYPE:
1567     case AL_DISTANCE_MODEL:
1568     case AL_DIRECT_FILTER_GAINHF_AUTO:
1569     case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO:
1570     case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO:
1571     case AL_DIRECT_CHANNELS_SOFT:
1572     case AL_SOURCE_RESAMPLER_SOFT:
1573     case AL_SOURCE_SPATIALIZE_SOFT:
1574     case AL_BYTE_LENGTH_SOFT:
1575     case AL_SAMPLE_LENGTH_SOFT:
1576     case AL_STEREO_MODE_SOFT:
1577         CheckSize(1);
1578         ival = static_cast<int>(values[0]);
1579         return SetSourceiv(Source, Context, prop, {&ival, 1u});
1580
1581     case AL_BUFFERS_QUEUED:
1582     case AL_BUFFERS_PROCESSED:
1583         CheckSize(1);
1584         ival = static_cast<int>(static_cast<ALuint>(values[0]));
1585         return SetSourceiv(Source, Context, prop, {&ival, 1u});
1586
1587     case AL_BUFFER:
1588     case AL_DIRECT_FILTER:
1589     case AL_AUXILIARY_SEND_FILTER:
1590     case AL_SAMPLE_OFFSET_LATENCY_SOFT:
1591     case AL_SAMPLE_OFFSET_CLOCK_SOFT:
1592         break;
1593     }
1594
1595     ERR("Unexpected property: 0x%04x\n", prop);
1596     Context->setError(AL_INVALID_ENUM, "Invalid source float property 0x%04x", prop);
1597 }
1598 catch(check_exception&) {
1599 }
1600
1601 void SetSourceiv(ALsource *const Source, ALCcontext *const Context, const SourceProp prop,
1602     const al::span<const int> values)
1603 try {
1604     auto Checkers = GetCheckers(Context, prop, values);
1605     auto &CheckSize = Checkers.first;
1606     auto &CheckValue = Checkers.second;
1607     ALCdevice *device{Context->mALDevice.get()};
1608     ALeffectslot *slot{nullptr};
1609     al::deque<ALbufferQueueItem> oldlist;
1610     std::unique_lock<std::mutex> slotlock;
1611     float fvals[6];
1612
1613     switch(prop)
1614     {
1615     case AL_SOURCE_STATE:
1616     case AL_SOURCE_TYPE:
1617     case AL_BUFFERS_QUEUED:
1618     case AL_BUFFERS_PROCESSED:
1619     case AL_BYTE_LENGTH_SOFT:
1620     case AL_SAMPLE_LENGTH_SOFT:
1621         /* Query only */
1622         return Context->setError(AL_INVALID_OPERATION,
1623             "Setting read-only source property 0x%04x", prop);
1624
1625     case AL_SOURCE_RELATIVE:
1626         CheckSize(1);
1627         CheckValue(values[0] == AL_FALSE || values[0] == AL_TRUE);
1628
1629         Source->HeadRelative = values[0] != AL_FALSE;
1630         return CommitAndUpdateSourceProps(Source, Context);
1631
1632     case AL_LOOPING:
1633         CheckSize(1);
1634         CheckValue(values[0] == AL_FALSE || values[0] == AL_TRUE);
1635
1636         Source->Looping = values[0] != AL_FALSE;
1637         if(Voice *voice{GetSourceVoice(Source, Context)})
1638         {
1639             if(Source->Looping)
1640                 voice->mLoopBuffer.store(&Source->mQueue.front(), std::memory_order_release);
1641             else
1642                 voice->mLoopBuffer.store(nullptr, std::memory_order_release);
1643
1644             /* If the source is playing, wait for the current mix to finish to
1645              * ensure it isn't currently looping back or reaching the end.
1646              */
1647             device->waitForMix();
1648         }
1649         return;
1650
1651     case AL_BUFFER:
1652         CheckSize(1);
1653         {
1654             const ALenum state{GetSourceState(Source, GetSourceVoice(Source, Context))};
1655             if(state == AL_PLAYING || state == AL_PAUSED)
1656                 return Context->setError(AL_INVALID_OPERATION,
1657                     "Setting buffer on playing or paused source %u", Source->id);
1658         }
1659         if(values[0])
1660         {
1661             std::lock_guard<std::mutex> _{device->BufferLock};
1662             ALbuffer *buffer{LookupBuffer(device, static_cast<ALuint>(values[0]))};
1663             if(!buffer)
1664                 return Context->setError(AL_INVALID_VALUE, "Invalid buffer ID %u",
1665                     static_cast<ALuint>(values[0]));
1666             if(buffer->MappedAccess && !(buffer->MappedAccess&AL_MAP_PERSISTENT_BIT_SOFT))
1667                 return Context->setError(AL_INVALID_OPERATION,
1668                     "Setting non-persistently mapped buffer %u", buffer->id);
1669             if(buffer->mCallback && ReadRef(buffer->ref) != 0)
1670                 return Context->setError(AL_INVALID_OPERATION,
1671                     "Setting already-set callback buffer %u", buffer->id);
1672
1673             /* Add the selected buffer to a one-item queue */
1674             al::deque<ALbufferQueueItem> newlist;
1675             newlist.emplace_back();
1676             newlist.back().mCallback = buffer->mCallback;
1677             newlist.back().mUserData = buffer->mUserData;
1678             newlist.back().mBlockAlign = buffer->mBlockAlign;
1679             newlist.back().mSampleLen = buffer->mSampleLen;
1680             newlist.back().mLoopStart = buffer->mLoopStart;
1681             newlist.back().mLoopEnd = buffer->mLoopEnd;
1682             newlist.back().mSamples = buffer->mData.data();
1683             newlist.back().mBuffer = buffer;
1684             IncrementRef(buffer->ref);
1685
1686             /* Source is now Static */
1687             Source->SourceType = AL_STATIC;
1688             Source->mQueue.swap(oldlist);
1689             Source->mQueue.swap(newlist);
1690         }
1691         else
1692         {
1693             /* Source is now Undetermined */
1694             Source->SourceType = AL_UNDETERMINED;
1695             Source->mQueue.swap(oldlist);
1696         }
1697
1698         /* Delete all elements in the previous queue */
1699         for(auto &item : oldlist)
1700         {
1701             if(ALbuffer *buffer{item.mBuffer})
1702                 DecrementRef(buffer->ref);
1703         }
1704         return;
1705
1706     case AL_SEC_OFFSET:
1707     case AL_SAMPLE_OFFSET:
1708     case AL_BYTE_OFFSET:
1709         CheckSize(1);
1710
1711         if(Voice *voice{GetSourceVoice(Source, Context)})
1712         {
1713             auto vpos = GetSampleOffset(Source->mQueue, prop, values[0]);
1714             if(!vpos) return Context->setError(AL_INVALID_VALUE, "Invalid source offset");
1715
1716             if(SetVoiceOffset(voice, *vpos, Source, Context, device))
1717                 return;
1718         }
1719         Source->OffsetType = prop;
1720         Source->Offset = values[0];
1721         return;
1722
1723     case AL_DIRECT_FILTER:
1724         CheckSize(1);
1725         if(values[0])
1726         {
1727             std::lock_guard<std::mutex> _{device->FilterLock};
1728             ALfilter *filter{LookupFilter(device, static_cast<ALuint>(values[0]))};
1729             if(!filter)
1730                 return Context->setError(AL_INVALID_VALUE, "Invalid filter ID %u",
1731                     static_cast<ALuint>(values[0]));
1732             Source->Direct.Gain = filter->Gain;
1733             Source->Direct.GainHF = filter->GainHF;
1734             Source->Direct.HFReference = filter->HFReference;
1735             Source->Direct.GainLF = filter->GainLF;
1736             Source->Direct.LFReference = filter->LFReference;
1737         }
1738         else
1739         {
1740             Source->Direct.Gain = 1.0f;
1741             Source->Direct.GainHF = 1.0f;
1742             Source->Direct.HFReference = LOWPASSFREQREF;
1743             Source->Direct.GainLF = 1.0f;
1744             Source->Direct.LFReference = HIGHPASSFREQREF;
1745         }
1746         return UpdateSourceProps(Source, Context);
1747
1748     case AL_DIRECT_FILTER_GAINHF_AUTO:
1749         CheckSize(1);
1750         CheckValue(values[0] == AL_FALSE || values[0] == AL_TRUE);
1751
1752         Source->DryGainHFAuto = values[0] != AL_FALSE;
1753         return UpdateSourceProps(Source, Context);
1754
1755     case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO:
1756         CheckSize(1);
1757         CheckValue(values[0] == AL_FALSE || values[0] == AL_TRUE);
1758
1759         Source->WetGainAuto = values[0] != AL_FALSE;
1760         return UpdateSourceProps(Source, Context);
1761
1762     case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO:
1763         CheckSize(1);
1764         CheckValue(values[0] == AL_FALSE || values[0] == AL_TRUE);
1765
1766         Source->WetGainHFAuto = values[0] != AL_FALSE;
1767         return UpdateSourceProps(Source, Context);
1768
1769     case AL_DIRECT_CHANNELS_SOFT:
1770         CheckSize(1);
1771         if(auto mode = DirectModeFromEnum(values[0]))
1772         {
1773             Source->DirectChannels = *mode;
1774             return UpdateSourceProps(Source, Context);
1775         }
1776         Context->setError(AL_INVALID_VALUE, "Unsupported AL_DIRECT_CHANNELS_SOFT: 0x%04x\n",
1777             values[0]);
1778         return;
1779
1780     case AL_DISTANCE_MODEL:
1781         CheckSize(1);
1782         if(auto model = DistanceModelFromALenum(values[0]))
1783         {
1784             Source->mDistanceModel = *model;
1785             if(Context->mSourceDistanceModel)
1786                 UpdateSourceProps(Source, Context);
1787             return;
1788         }
1789         Context->setError(AL_INVALID_VALUE, "Distance model out of range: 0x%04x", values[0]);
1790         return;
1791
1792     case AL_SOURCE_RESAMPLER_SOFT:
1793         CheckSize(1);
1794         CheckValue(values[0] >= 0 && values[0] <= static_cast<int>(Resampler::Max));
1795
1796         Source->mResampler = static_cast<Resampler>(values[0]);
1797         return UpdateSourceProps(Source, Context);
1798
1799     case AL_SOURCE_SPATIALIZE_SOFT:
1800         CheckSize(1);
1801         if(auto mode = SpatializeModeFromEnum(values[0]))
1802         {
1803             Source->mSpatialize = *mode;
1804             return UpdateSourceProps(Source, Context);
1805         }
1806         Context->setError(AL_INVALID_VALUE, "Unsupported AL_SOURCE_SPATIALIZE_SOFT: 0x%04x\n",
1807             values[0]);
1808         return;
1809
1810     case AL_STEREO_MODE_SOFT:
1811         CheckSize(1);
1812         {
1813             const ALenum state{GetSourceState(Source, GetSourceVoice(Source, Context))};
1814             if(state == AL_PLAYING || state == AL_PAUSED)
1815                 return Context->setError(AL_INVALID_OPERATION,
1816                     "Modifying stereo mode on playing or paused source %u", Source->id);
1817         }
1818         if(auto mode = StereoModeFromEnum(values[0]))
1819         {
1820             Source->mStereoMode = *mode;
1821             return;
1822         }
1823         Context->setError(AL_INVALID_VALUE, "Unsupported AL_STEREO_MODE_SOFT: 0x%04x\n",
1824             values[0]);
1825         return;
1826
1827     case AL_AUXILIARY_SEND_FILTER:
1828         CheckSize(3);
1829         slotlock = std::unique_lock<std::mutex>{Context->mEffectSlotLock};
1830         if(values[0] && (slot=LookupEffectSlot(Context, static_cast<ALuint>(values[0]))) == nullptr)
1831             return Context->setError(AL_INVALID_VALUE, "Invalid effect ID %u", values[0]);
1832         if(static_cast<ALuint>(values[1]) >= device->NumAuxSends)
1833             return Context->setError(AL_INVALID_VALUE, "Invalid send %u", values[1]);
1834
1835         if(values[2])
1836         {
1837             std::lock_guard<std::mutex> _{device->FilterLock};
1838             ALfilter *filter{LookupFilter(device, static_cast<ALuint>(values[2]))};
1839             if(!filter)
1840                 return Context->setError(AL_INVALID_VALUE, "Invalid filter ID %u", values[2]);
1841
1842             auto &send = Source->Send[static_cast<ALuint>(values[1])];
1843             send.Gain = filter->Gain;
1844             send.GainHF = filter->GainHF;
1845             send.HFReference = filter->HFReference;
1846             send.GainLF = filter->GainLF;
1847             send.LFReference = filter->LFReference;
1848         }
1849         else
1850         {
1851             /* Disable filter */
1852             auto &send = Source->Send[static_cast<ALuint>(values[1])];
1853             send.Gain = 1.0f;
1854             send.GainHF = 1.0f;
1855             send.HFReference = LOWPASSFREQREF;
1856             send.GainLF = 1.0f;
1857             send.LFReference = HIGHPASSFREQREF;
1858         }
1859
1860         /* We must force an update if the current auxiliary slot is valid and
1861          * about to be changed on an active source, in case the old slot is
1862          * about to be deleted.
1863          */
1864         if(Source->Send[static_cast<ALuint>(values[1])].Slot
1865             && slot != Source->Send[static_cast<ALuint>(values[1])].Slot
1866             && IsPlayingOrPaused(Source))
1867         {
1868             /* Add refcount on the new slot, and release the previous slot */
1869             if(slot) IncrementRef(slot->ref);
1870             if(auto *oldslot = Source->Send[static_cast<ALuint>(values[1])].Slot)
1871                 DecrementRef(oldslot->ref);
1872             Source->Send[static_cast<ALuint>(values[1])].Slot = slot;
1873
1874             Voice *voice{GetSourceVoice(Source, Context)};
1875             if(voice) UpdateSourceProps(Source, voice, Context);
1876             else Source->mPropsDirty = true;
1877         }
1878         else
1879         {
1880             if(slot) IncrementRef(slot->ref);
1881             if(auto *oldslot = Source->Send[static_cast<ALuint>(values[1])].Slot)
1882                 DecrementRef(oldslot->ref);
1883             Source->Send[static_cast<ALuint>(values[1])].Slot = slot;
1884             UpdateSourceProps(Source, Context);
1885         }
1886         return;
1887
1888
1889     case AL_SAMPLE_RW_OFFSETS_SOFT:
1890         if(sBufferSubDataCompat)
1891             /* Query only */
1892             return Context->setError(AL_INVALID_OPERATION,
1893                 "Setting read-only source property 0x%04x", prop);
1894         break;
1895
1896     case AL_SOURCE_RADIUS: /*AL_BYTE_RW_OFFSETS_SOFT:*/
1897         if(sBufferSubDataCompat)
1898             return Context->setError(AL_INVALID_OPERATION,
1899                 "Setting read-only source property 0x%04x", prop);
1900         /*fall-through*/
1901
1902     /* 1x float */
1903     case AL_CONE_INNER_ANGLE:
1904     case AL_CONE_OUTER_ANGLE:
1905     case AL_PITCH:
1906     case AL_GAIN:
1907     case AL_MIN_GAIN:
1908     case AL_MAX_GAIN:
1909     case AL_REFERENCE_DISTANCE:
1910     case AL_ROLLOFF_FACTOR:
1911     case AL_CONE_OUTER_GAIN:
1912     case AL_MAX_DISTANCE:
1913     case AL_DOPPLER_FACTOR:
1914     case AL_CONE_OUTER_GAINHF:
1915     case AL_AIR_ABSORPTION_FACTOR:
1916     case AL_ROOM_ROLLOFF_FACTOR:
1917     case AL_SEC_LENGTH_SOFT:
1918     case AL_SUPER_STEREO_WIDTH_SOFT:
1919         CheckSize(1);
1920         fvals[0] = static_cast<float>(values[0]);
1921         return SetSourcefv(Source, Context, prop, {fvals, 1u});
1922
1923     /* 3x float */
1924     case AL_POSITION:
1925     case AL_VELOCITY:
1926     case AL_DIRECTION:
1927         CheckSize(3);
1928         fvals[0] = static_cast<float>(values[0]);
1929         fvals[1] = static_cast<float>(values[1]);
1930         fvals[2] = static_cast<float>(values[2]);
1931         return SetSourcefv(Source, Context, prop, {fvals, 3u});
1932
1933     /* 6x float */
1934     case AL_ORIENTATION:
1935         CheckSize(6);
1936         fvals[0] = static_cast<float>(values[0]);
1937         fvals[1] = static_cast<float>(values[1]);
1938         fvals[2] = static_cast<float>(values[2]);
1939         fvals[3] = static_cast<float>(values[3]);
1940         fvals[4] = static_cast<float>(values[4]);
1941         fvals[5] = static_cast<float>(values[5]);
1942         return SetSourcefv(Source, Context, prop, {fvals, 6u});
1943
1944     case AL_SAMPLE_OFFSET_LATENCY_SOFT:
1945     case AL_SEC_OFFSET_LATENCY_SOFT:
1946     case AL_SEC_OFFSET_CLOCK_SOFT:
1947     case AL_SAMPLE_OFFSET_CLOCK_SOFT:
1948     case AL_STEREO_ANGLES:
1949         break;
1950     }
1951
1952     ERR("Unexpected property: 0x%04x\n", prop);
1953     Context->setError(AL_INVALID_ENUM, "Invalid source integer property 0x%04x", prop);
1954 }
1955 catch(check_exception&) {
1956 }
1957
1958 void SetSourcei64v(ALsource *const Source, ALCcontext *const Context, const SourceProp prop,
1959     const al::span<const int64_t> values)
1960 try {
1961     auto Checkers = GetCheckers(Context, prop, values);
1962     auto &CheckSize = Checkers.first;
1963     auto &CheckValue = Checkers.second;
1964     float fvals[MaxValues];
1965     int   ivals[MaxValues];
1966
1967     switch(prop)
1968     {
1969     case AL_SOURCE_TYPE:
1970     case AL_BUFFERS_QUEUED:
1971     case AL_BUFFERS_PROCESSED:
1972     case AL_SOURCE_STATE:
1973     case AL_BYTE_LENGTH_SOFT:
1974     case AL_SAMPLE_LENGTH_SOFT:
1975     case AL_SAMPLE_OFFSET_LATENCY_SOFT:
1976     case AL_SAMPLE_OFFSET_CLOCK_SOFT:
1977         /* Query only */
1978         return Context->setError(AL_INVALID_OPERATION,
1979             "Setting read-only source property 0x%04x", prop);
1980
1981     /* 1x int */
1982     case AL_SOURCE_RELATIVE:
1983     case AL_LOOPING:
1984     case AL_SEC_OFFSET:
1985     case AL_SAMPLE_OFFSET:
1986     case AL_BYTE_OFFSET:
1987     case AL_DIRECT_FILTER_GAINHF_AUTO:
1988     case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO:
1989     case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO:
1990     case AL_DIRECT_CHANNELS_SOFT:
1991     case AL_DISTANCE_MODEL:
1992     case AL_SOURCE_RESAMPLER_SOFT:
1993     case AL_SOURCE_SPATIALIZE_SOFT:
1994     case AL_STEREO_MODE_SOFT:
1995         CheckSize(1);
1996         CheckValue(values[0] <= INT_MAX && values[0] >= INT_MIN);
1997
1998         ivals[0] = static_cast<int>(values[0]);
1999         return SetSourceiv(Source, Context, prop, {ivals, 1u});
2000
2001     /* 1x uint */
2002     case AL_BUFFER:
2003     case AL_DIRECT_FILTER:
2004         CheckSize(1);
2005         CheckValue(values[0] <= UINT_MAX && values[0] >= 0);
2006
2007         ivals[0] = static_cast<int>(values[0]);
2008         return SetSourceiv(Source, Context, prop, {ivals, 1u});
2009
2010     /* 3x uint */
2011     case AL_AUXILIARY_SEND_FILTER:
2012         CheckSize(3);
2013         CheckValue(values[0] <= UINT_MAX && values[0] >= 0 && values[1] <= UINT_MAX
2014             && values[1] >= 0 && values[2] <= UINT_MAX && values[2] >= 0);
2015
2016         ivals[0] = static_cast<int>(values[0]);
2017         ivals[1] = static_cast<int>(values[1]);
2018         ivals[2] = static_cast<int>(values[2]);
2019         return SetSourceiv(Source, Context, prop, {ivals, 3u});
2020
2021     case AL_SAMPLE_RW_OFFSETS_SOFT:
2022         if(sBufferSubDataCompat)
2023         {
2024             /* Query only */
2025             return Context->setError(AL_INVALID_OPERATION,
2026                 "Setting read-only source property 0x%04x", prop);
2027         }
2028         break;
2029
2030     case AL_SOURCE_RADIUS: /*AL_BYTE_RW_OFFSETS_SOFT:*/
2031         if(sBufferSubDataCompat)
2032             return Context->setError(AL_INVALID_OPERATION,
2033                 "Setting read-only source property 0x%04x", prop);
2034         /*fall-through*/
2035
2036     /* 1x float */
2037     case AL_CONE_INNER_ANGLE:
2038     case AL_CONE_OUTER_ANGLE:
2039     case AL_PITCH:
2040     case AL_GAIN:
2041     case AL_MIN_GAIN:
2042     case AL_MAX_GAIN:
2043     case AL_REFERENCE_DISTANCE:
2044     case AL_ROLLOFF_FACTOR:
2045     case AL_CONE_OUTER_GAIN:
2046     case AL_MAX_DISTANCE:
2047     case AL_DOPPLER_FACTOR:
2048     case AL_CONE_OUTER_GAINHF:
2049     case AL_AIR_ABSORPTION_FACTOR:
2050     case AL_ROOM_ROLLOFF_FACTOR:
2051     case AL_SEC_LENGTH_SOFT:
2052     case AL_SUPER_STEREO_WIDTH_SOFT:
2053         CheckSize(1);
2054         fvals[0] = static_cast<float>(values[0]);
2055         return SetSourcefv(Source, Context, prop, {fvals, 1u});
2056
2057     /* 3x float */
2058     case AL_POSITION:
2059     case AL_VELOCITY:
2060     case AL_DIRECTION:
2061         CheckSize(3);
2062         fvals[0] = static_cast<float>(values[0]);
2063         fvals[1] = static_cast<float>(values[1]);
2064         fvals[2] = static_cast<float>(values[2]);
2065         return SetSourcefv(Source, Context, prop, {fvals, 3u});
2066
2067     /* 6x float */
2068     case AL_ORIENTATION:
2069         CheckSize(6);
2070         fvals[0] = static_cast<float>(values[0]);
2071         fvals[1] = static_cast<float>(values[1]);
2072         fvals[2] = static_cast<float>(values[2]);
2073         fvals[3] = static_cast<float>(values[3]);
2074         fvals[4] = static_cast<float>(values[4]);
2075         fvals[5] = static_cast<float>(values[5]);
2076         return SetSourcefv(Source, Context, prop, {fvals, 6u});
2077
2078     case AL_SEC_OFFSET_LATENCY_SOFT:
2079     case AL_SEC_OFFSET_CLOCK_SOFT:
2080     case AL_STEREO_ANGLES:
2081         break;
2082     }
2083
2084     ERR("Unexpected property: 0x%04x\n", prop);
2085     Context->setError(AL_INVALID_ENUM, "Invalid source integer64 property 0x%04x", prop);
2086 }
2087 catch(check_exception&) {
2088 }
2089
2090
2091 template<typename T, size_t N>
2092 auto GetSizeChecker(ALCcontext *const Context, const SourceProp prop, const al::span<T,N> values)
2093 {
2094     return [=](size_t expect) -> void
2095     {
2096         if(values.size() == expect) LIKELY return;
2097         Context->setError(AL_INVALID_ENUM, "Property 0x%04x expects %zu value(s), got %zu",
2098             prop, expect, values.size());
2099         throw check_size_exception{};
2100     };
2101 }
2102
2103 bool GetSourcedv(ALsource *const Source, ALCcontext *const Context, const SourceProp prop, const al::span<double> values);
2104 bool GetSourceiv(ALsource *const Source, ALCcontext *const Context, const SourceProp prop, const al::span<int> values);
2105 bool GetSourcei64v(ALsource *const Source, ALCcontext *const Context, const SourceProp prop, const al::span<int64_t> values);
2106
2107 bool GetSourcedv(ALsource *const Source, ALCcontext *const Context, const SourceProp prop,
2108     const al::span<double> values)
2109 try {
2110     auto CheckSize = GetSizeChecker(Context, prop, values);
2111     ALCdevice *device{Context->mALDevice.get()};
2112     ClockLatency clocktime;
2113     nanoseconds srcclock;
2114     int ivals[MaxValues];
2115     bool err;
2116
2117     switch(prop)
2118     {
2119     case AL_GAIN:
2120         CheckSize(1);
2121         values[0] = Source->Gain;
2122         return true;
2123
2124     case AL_PITCH:
2125         CheckSize(1);
2126         values[0] = Source->Pitch;
2127         return true;
2128
2129     case AL_MAX_DISTANCE:
2130         CheckSize(1);
2131         values[0] = Source->MaxDistance;
2132         return true;
2133
2134     case AL_ROLLOFF_FACTOR:
2135         CheckSize(1);
2136         values[0] = Source->RolloffFactor;
2137         return true;
2138
2139     case AL_REFERENCE_DISTANCE:
2140         CheckSize(1);
2141         values[0] = Source->RefDistance;
2142         return true;
2143
2144     case AL_CONE_INNER_ANGLE:
2145         CheckSize(1);
2146         values[0] = Source->InnerAngle;
2147         return true;
2148
2149     case AL_CONE_OUTER_ANGLE:
2150         CheckSize(1);
2151         values[0] = Source->OuterAngle;
2152         return true;
2153
2154     case AL_MIN_GAIN:
2155         CheckSize(1);
2156         values[0] = Source->MinGain;
2157         return true;
2158
2159     case AL_MAX_GAIN:
2160         CheckSize(1);
2161         values[0] = Source->MaxGain;
2162         return true;
2163
2164     case AL_CONE_OUTER_GAIN:
2165         CheckSize(1);
2166         values[0] = Source->OuterGain;
2167         return true;
2168
2169     case AL_SEC_OFFSET:
2170     case AL_SAMPLE_OFFSET:
2171     case AL_BYTE_OFFSET:
2172         CheckSize(1);
2173         values[0] = GetSourceOffset(Source, prop, Context);
2174         return true;
2175
2176     case AL_CONE_OUTER_GAINHF:
2177         CheckSize(1);
2178         values[0] = Source->OuterGainHF;
2179         return true;
2180
2181     case AL_AIR_ABSORPTION_FACTOR:
2182         CheckSize(1);
2183         values[0] = Source->AirAbsorptionFactor;
2184         return true;
2185
2186     case AL_ROOM_ROLLOFF_FACTOR:
2187         CheckSize(1);
2188         values[0] = Source->RoomRolloffFactor;
2189         return true;
2190
2191     case AL_DOPPLER_FACTOR:
2192         CheckSize(1);
2193         values[0] = Source->DopplerFactor;
2194         return true;
2195
2196     case AL_SAMPLE_RW_OFFSETS_SOFT:
2197         break;
2198     case AL_SOURCE_RADIUS: /*AL_BYTE_RW_OFFSETS_SOFT:*/
2199         if(sBufferSubDataCompat)
2200             break;
2201
2202         CheckSize(1);
2203         values[0] = Source->Radius;
2204         return true;
2205
2206     case AL_SUPER_STEREO_WIDTH_SOFT:
2207         CheckSize(1);
2208         values[0] = Source->EnhWidth;
2209         return true;
2210
2211     case AL_BYTE_LENGTH_SOFT:
2212     case AL_SAMPLE_LENGTH_SOFT:
2213     case AL_SEC_LENGTH_SOFT:
2214         CheckSize(1);
2215         values[0] = GetSourceLength(Source, prop);
2216         return true;
2217
2218     case AL_STEREO_ANGLES:
2219         CheckSize(2);
2220         values[0] = Source->StereoPan[0];
2221         values[1] = Source->StereoPan[1];
2222         return true;
2223
2224     case AL_SEC_OFFSET_LATENCY_SOFT:
2225         CheckSize(2);
2226         /* Get the source offset with the clock time first. Then get the clock
2227          * time with the device latency. Order is important.
2228          */
2229         values[0] = GetSourceSecOffset(Source, Context, &srcclock);
2230         {
2231             std::lock_guard<std::mutex> _{device->StateLock};
2232             clocktime = GetClockLatency(device, device->Backend.get());
2233         }
2234         if(srcclock == clocktime.ClockTime)
2235             values[1] = static_cast<double>(clocktime.Latency.count()) / 1000000000.0;
2236         else
2237         {
2238             /* If the clock time incremented, reduce the latency by that much
2239              * since it's that much closer to the source offset it got earlier.
2240              */
2241             const nanoseconds diff{clocktime.ClockTime - srcclock};
2242             const nanoseconds latency{clocktime.Latency - std::min(clocktime.Latency, diff)};
2243             values[1] = static_cast<double>(latency.count()) / 1000000000.0;
2244         }
2245         return true;
2246
2247     case AL_SEC_OFFSET_CLOCK_SOFT:
2248         CheckSize(2);
2249         values[0] = GetSourceSecOffset(Source, Context, &srcclock);
2250         values[1] = static_cast<double>(srcclock.count()) / 1000000000.0;
2251         return true;
2252
2253     case AL_POSITION:
2254         CheckSize(3);
2255         values[0] = Source->Position[0];
2256         values[1] = Source->Position[1];
2257         values[2] = Source->Position[2];
2258         return true;
2259
2260     case AL_VELOCITY:
2261         CheckSize(3);
2262         values[0] = Source->Velocity[0];
2263         values[1] = Source->Velocity[1];
2264         values[2] = Source->Velocity[2];
2265         return true;
2266
2267     case AL_DIRECTION:
2268         CheckSize(3);
2269         values[0] = Source->Direction[0];
2270         values[1] = Source->Direction[1];
2271         values[2] = Source->Direction[2];
2272         return true;
2273
2274     case AL_ORIENTATION:
2275         CheckSize(6);
2276         values[0] = Source->OrientAt[0];
2277         values[1] = Source->OrientAt[1];
2278         values[2] = Source->OrientAt[2];
2279         values[3] = Source->OrientUp[0];
2280         values[4] = Source->OrientUp[1];
2281         values[5] = Source->OrientUp[2];
2282         return true;
2283
2284     /* 1x int */
2285     case AL_SOURCE_RELATIVE:
2286     case AL_LOOPING:
2287     case AL_SOURCE_STATE:
2288     case AL_BUFFERS_QUEUED:
2289     case AL_BUFFERS_PROCESSED:
2290     case AL_SOURCE_TYPE:
2291     case AL_DIRECT_FILTER_GAINHF_AUTO:
2292     case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO:
2293     case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO:
2294     case AL_DIRECT_CHANNELS_SOFT:
2295     case AL_DISTANCE_MODEL:
2296     case AL_SOURCE_RESAMPLER_SOFT:
2297     case AL_SOURCE_SPATIALIZE_SOFT:
2298     case AL_STEREO_MODE_SOFT:
2299         CheckSize(1);
2300         if((err=GetSourceiv(Source, Context, prop, {ivals, 1u})) != false)
2301             values[0] = static_cast<double>(ivals[0]);
2302         return err;
2303
2304     case AL_BUFFER:
2305     case AL_DIRECT_FILTER:
2306     case AL_AUXILIARY_SEND_FILTER:
2307     case AL_SAMPLE_OFFSET_LATENCY_SOFT:
2308     case AL_SAMPLE_OFFSET_CLOCK_SOFT:
2309         break;
2310     }
2311
2312     ERR("Unexpected property: 0x%04x\n", prop);
2313     Context->setError(AL_INVALID_ENUM, "Invalid source double property 0x%04x", prop);
2314     return false;
2315 }
2316 catch(check_exception&) {
2317     return false;
2318 }
2319
2320 bool GetSourceiv(ALsource *const Source, ALCcontext *const Context, const SourceProp prop,
2321     const al::span<int> values)
2322 try {
2323     auto CheckSize = GetSizeChecker(Context, prop, values);
2324     double dvals[MaxValues];
2325     bool err;
2326
2327     switch(prop)
2328     {
2329     case AL_SOURCE_RELATIVE:
2330         CheckSize(1);
2331         values[0] = Source->HeadRelative;
2332         return true;
2333
2334     case AL_LOOPING:
2335         CheckSize(1);
2336         values[0] = Source->Looping;
2337         return true;
2338
2339     case AL_BUFFER:
2340         CheckSize(1);
2341         {
2342             ALbufferQueueItem *BufferList{};
2343             /* HACK: This query should technically only return the buffer set
2344              * on a static source. However, some apps had used it to detect
2345              * when a streaming source changed buffers, so report the current
2346              * buffer's ID when playing.
2347              */
2348             if(Source->SourceType == AL_STATIC || Source->state == AL_INITIAL)
2349             {
2350                 if(!Source->mQueue.empty())
2351                     BufferList = &Source->mQueue.front();
2352             }
2353             else if(Voice *voice{GetSourceVoice(Source, Context)})
2354             {
2355                 VoiceBufferItem *Current{voice->mCurrentBuffer.load(std::memory_order_relaxed)};
2356                 BufferList = static_cast<ALbufferQueueItem*>(Current);
2357             }
2358             ALbuffer *buffer{BufferList ? BufferList->mBuffer : nullptr};
2359             values[0] = buffer ? static_cast<int>(buffer->id) : 0;
2360         }
2361         return true;
2362
2363     case AL_SOURCE_STATE:
2364         CheckSize(1);
2365         values[0] = GetSourceState(Source, GetSourceVoice(Source, Context));
2366         return true;
2367
2368     case AL_BUFFERS_QUEUED:
2369         CheckSize(1);
2370         values[0] = static_cast<int>(Source->mQueue.size());
2371         return true;
2372
2373     case AL_BUFFERS_PROCESSED:
2374         CheckSize(1);
2375         if(Source->Looping || Source->SourceType != AL_STREAMING)
2376         {
2377             /* Buffers on a looping source are in a perpetual state of PENDING,
2378              * so don't report any as PROCESSED
2379              */
2380             values[0] = 0;
2381         }
2382         else
2383         {
2384             int played{0};
2385             if(Source->state != AL_INITIAL)
2386             {
2387                 const VoiceBufferItem *Current{nullptr};
2388                 if(Voice *voice{GetSourceVoice(Source, Context)})
2389                     Current = voice->mCurrentBuffer.load(std::memory_order_relaxed);
2390                 for(auto &item : Source->mQueue)
2391                 {
2392                     if(&item == Current)
2393                         break;
2394                     ++played;
2395                 }
2396             }
2397             values[0] = played;
2398         }
2399         return true;
2400
2401     case AL_SOURCE_TYPE:
2402         CheckSize(1);
2403         values[0] = Source->SourceType;
2404         return true;
2405
2406     case AL_DIRECT_FILTER_GAINHF_AUTO:
2407         CheckSize(1);
2408         values[0] = Source->DryGainHFAuto;
2409         return true;
2410
2411     case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO:
2412         CheckSize(1);
2413         values[0] = Source->WetGainAuto;
2414         return true;
2415
2416     case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO:
2417         CheckSize(1);
2418         values[0] = Source->WetGainHFAuto;
2419         return true;
2420
2421     case AL_DIRECT_CHANNELS_SOFT:
2422         CheckSize(1);
2423         values[0] = EnumFromDirectMode(Source->DirectChannels);
2424         return true;
2425
2426     case AL_DISTANCE_MODEL:
2427         CheckSize(1);
2428         values[0] = ALenumFromDistanceModel(Source->mDistanceModel);
2429         return true;
2430
2431     case AL_BYTE_LENGTH_SOFT:
2432     case AL_SAMPLE_LENGTH_SOFT:
2433     case AL_SEC_LENGTH_SOFT:
2434         CheckSize(1);
2435         values[0] = static_cast<int>(mind(GetSourceLength(Source, prop),
2436             std::numeric_limits<int>::max()));
2437         return true;
2438
2439     case AL_SOURCE_RESAMPLER_SOFT:
2440         CheckSize(1);
2441         values[0] = static_cast<int>(Source->mResampler);
2442         return true;
2443
2444     case AL_SOURCE_SPATIALIZE_SOFT:
2445         CheckSize(1);
2446         values[0] = EnumFromSpatializeMode(Source->mSpatialize);
2447         return true;
2448
2449     case AL_STEREO_MODE_SOFT:
2450         CheckSize(1);
2451         values[0] = EnumFromStereoMode(Source->mStereoMode);
2452         return true;
2453
2454     case AL_SAMPLE_RW_OFFSETS_SOFT:
2455         if(sBufferSubDataCompat)
2456         {
2457             CheckSize(2);
2458             const auto offset = GetSourceOffset(Source, AL_SAMPLE_OFFSET, Context);
2459             /* FIXME: values[1] should be ahead of values[0] by the device
2460              * update time. It needs to clamp or wrap the length of the buffer
2461              * queue.
2462              */
2463             values[0] = static_cast<int>(mind(offset, std::numeric_limits<int>::max()));
2464             values[1] = values[0];
2465             return true;
2466         }
2467         break;
2468     case AL_SOURCE_RADIUS: /*AL_BYTE_RW_OFFSETS_SOFT:*/
2469         if(sBufferSubDataCompat)
2470         {
2471             CheckSize(2);
2472             const auto offset = GetSourceOffset(Source, AL_BYTE_OFFSET, Context);
2473             /* FIXME: values[1] should be ahead of values[0] by the device
2474              * update time. It needs to clamp or wrap the length of the buffer
2475              * queue.
2476              */
2477             values[0] = static_cast<int>(mind(offset, std::numeric_limits<int>::max()));
2478             values[1] = values[0];
2479             return true;
2480         }
2481         /*fall-through*/
2482
2483     /* 1x float/double */
2484     case AL_CONE_INNER_ANGLE:
2485     case AL_CONE_OUTER_ANGLE:
2486     case AL_PITCH:
2487     case AL_GAIN:
2488     case AL_MIN_GAIN:
2489     case AL_MAX_GAIN:
2490     case AL_REFERENCE_DISTANCE:
2491     case AL_ROLLOFF_FACTOR:
2492     case AL_CONE_OUTER_GAIN:
2493     case AL_MAX_DISTANCE:
2494     case AL_SEC_OFFSET:
2495     case AL_SAMPLE_OFFSET:
2496     case AL_BYTE_OFFSET:
2497     case AL_DOPPLER_FACTOR:
2498     case AL_AIR_ABSORPTION_FACTOR:
2499     case AL_ROOM_ROLLOFF_FACTOR:
2500     case AL_CONE_OUTER_GAINHF:
2501     case AL_SUPER_STEREO_WIDTH_SOFT:
2502         CheckSize(1);
2503         if((err=GetSourcedv(Source, Context, prop, {dvals, 1u})) != false)
2504             values[0] = static_cast<int>(dvals[0]);
2505         return err;
2506
2507     /* 3x float/double */
2508     case AL_POSITION:
2509     case AL_VELOCITY:
2510     case AL_DIRECTION:
2511         CheckSize(3);
2512         if((err=GetSourcedv(Source, Context, prop, {dvals, 3u})) != false)
2513         {
2514             values[0] = static_cast<int>(dvals[0]);
2515             values[1] = static_cast<int>(dvals[1]);
2516             values[2] = static_cast<int>(dvals[2]);
2517         }
2518         return err;
2519
2520     /* 6x float/double */
2521     case AL_ORIENTATION:
2522         CheckSize(6);
2523         if((err=GetSourcedv(Source, Context, prop, {dvals, 6u})) != false)
2524         {
2525             values[0] = static_cast<int>(dvals[0]);
2526             values[1] = static_cast<int>(dvals[1]);
2527             values[2] = static_cast<int>(dvals[2]);
2528             values[3] = static_cast<int>(dvals[3]);
2529             values[4] = static_cast<int>(dvals[4]);
2530             values[5] = static_cast<int>(dvals[5]);
2531         }
2532         return err;
2533
2534     case AL_SAMPLE_OFFSET_LATENCY_SOFT:
2535     case AL_SAMPLE_OFFSET_CLOCK_SOFT:
2536         break; /* i64 only */
2537     case AL_SEC_OFFSET_LATENCY_SOFT:
2538     case AL_SEC_OFFSET_CLOCK_SOFT:
2539         break; /* Double only */
2540     case AL_STEREO_ANGLES:
2541         break; /* Float/double only */
2542
2543     case AL_DIRECT_FILTER:
2544     case AL_AUXILIARY_SEND_FILTER:
2545         break; /* ??? */
2546     }
2547
2548     ERR("Unexpected property: 0x%04x\n", prop);
2549     Context->setError(AL_INVALID_ENUM, "Invalid source integer property 0x%04x", prop);
2550     return false;
2551 }
2552 catch(check_exception&) {
2553     return false;
2554 }
2555
2556 bool GetSourcei64v(ALsource *const Source, ALCcontext *const Context, const SourceProp prop,
2557     const al::span<int64_t> values)
2558 try {
2559     auto CheckSize = GetSizeChecker(Context, prop, values);
2560     ALCdevice *device{Context->mALDevice.get()};
2561     ClockLatency clocktime;
2562     nanoseconds srcclock;
2563     double dvals[MaxValues];
2564     int ivals[MaxValues];
2565     bool err;
2566
2567     switch(prop)
2568     {
2569     case AL_BYTE_LENGTH_SOFT:
2570     case AL_SAMPLE_LENGTH_SOFT:
2571     case AL_SEC_LENGTH_SOFT:
2572         CheckSize(1);
2573         values[0] = static_cast<int64_t>(GetSourceLength(Source, prop));
2574         return true;
2575
2576     case AL_SAMPLE_OFFSET_LATENCY_SOFT:
2577         CheckSize(2);
2578         /* Get the source offset with the clock time first. Then get the clock
2579          * time with the device latency. Order is important.
2580          */
2581         values[0] = GetSourceSampleOffset(Source, Context, &srcclock);
2582         {
2583             std::lock_guard<std::mutex> _{device->StateLock};
2584             clocktime = GetClockLatency(device, device->Backend.get());
2585         }
2586         if(srcclock == clocktime.ClockTime)
2587             values[1] = clocktime.Latency.count();
2588         else
2589         {
2590             /* If the clock time incremented, reduce the latency by that much
2591              * since it's that much closer to the source offset it got earlier.
2592              */
2593             const nanoseconds diff{clocktime.ClockTime - srcclock};
2594             values[1] = nanoseconds{clocktime.Latency - std::min(clocktime.Latency, diff)}.count();
2595         }
2596         return true;
2597
2598     case AL_SAMPLE_OFFSET_CLOCK_SOFT:
2599         CheckSize(2);
2600         values[0] = GetSourceSampleOffset(Source, Context, &srcclock);
2601         values[1] = srcclock.count();
2602         return true;
2603
2604     case AL_SAMPLE_RW_OFFSETS_SOFT:
2605         if(sBufferSubDataCompat)
2606         {
2607             CheckSize(2);
2608             /* FIXME: values[1] should be ahead of values[0] by the device
2609              * update time. It needs to clamp or wrap the length of the buffer
2610              * queue.
2611              */
2612             values[0] = static_cast<int64_t>(GetSourceOffset(Source, AL_SAMPLE_OFFSET, Context));
2613             values[1] = values[0];
2614             return true;
2615         }
2616         break;
2617     case AL_SOURCE_RADIUS: /*AL_BYTE_RW_OFFSETS_SOFT:*/
2618         if(sBufferSubDataCompat)
2619         {
2620             CheckSize(2);
2621             /* FIXME: values[1] should be ahead of values[0] by the device
2622              * update time. It needs to clamp or wrap the length of the buffer
2623              * queue.
2624              */
2625             values[0] = static_cast<int64_t>(GetSourceOffset(Source, AL_BYTE_OFFSET, Context));
2626             values[1] = values[0];
2627             return true;
2628         }
2629         /*fall-through*/
2630
2631     /* 1x float/double */
2632     case AL_CONE_INNER_ANGLE:
2633     case AL_CONE_OUTER_ANGLE:
2634     case AL_PITCH:
2635     case AL_GAIN:
2636     case AL_MIN_GAIN:
2637     case AL_MAX_GAIN:
2638     case AL_REFERENCE_DISTANCE:
2639     case AL_ROLLOFF_FACTOR:
2640     case AL_CONE_OUTER_GAIN:
2641     case AL_MAX_DISTANCE:
2642     case AL_SEC_OFFSET:
2643     case AL_SAMPLE_OFFSET:
2644     case AL_BYTE_OFFSET:
2645     case AL_DOPPLER_FACTOR:
2646     case AL_AIR_ABSORPTION_FACTOR:
2647     case AL_ROOM_ROLLOFF_FACTOR:
2648     case AL_CONE_OUTER_GAINHF:
2649     case AL_SUPER_STEREO_WIDTH_SOFT:
2650         CheckSize(1);
2651         if((err=GetSourcedv(Source, Context, prop, {dvals, 1u})) != false)
2652             values[0] = static_cast<int64_t>(dvals[0]);
2653         return err;
2654
2655     /* 3x float/double */
2656     case AL_POSITION:
2657     case AL_VELOCITY:
2658     case AL_DIRECTION:
2659         CheckSize(3);
2660         if((err=GetSourcedv(Source, Context, prop, {dvals, 3u})) != false)
2661         {
2662             values[0] = static_cast<int64_t>(dvals[0]);
2663             values[1] = static_cast<int64_t>(dvals[1]);
2664             values[2] = static_cast<int64_t>(dvals[2]);
2665         }
2666         return err;
2667
2668     /* 6x float/double */
2669     case AL_ORIENTATION:
2670         CheckSize(6);
2671         if((err=GetSourcedv(Source, Context, prop, {dvals, 6u})) != false)
2672         {
2673             values[0] = static_cast<int64_t>(dvals[0]);
2674             values[1] = static_cast<int64_t>(dvals[1]);
2675             values[2] = static_cast<int64_t>(dvals[2]);
2676             values[3] = static_cast<int64_t>(dvals[3]);
2677             values[4] = static_cast<int64_t>(dvals[4]);
2678             values[5] = static_cast<int64_t>(dvals[5]);
2679         }
2680         return err;
2681
2682     /* 1x int */
2683     case AL_SOURCE_RELATIVE:
2684     case AL_LOOPING:
2685     case AL_SOURCE_STATE:
2686     case AL_BUFFERS_QUEUED:
2687     case AL_BUFFERS_PROCESSED:
2688     case AL_SOURCE_TYPE:
2689     case AL_DIRECT_FILTER_GAINHF_AUTO:
2690     case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO:
2691     case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO:
2692     case AL_DIRECT_CHANNELS_SOFT:
2693     case AL_DISTANCE_MODEL:
2694     case AL_SOURCE_RESAMPLER_SOFT:
2695     case AL_SOURCE_SPATIALIZE_SOFT:
2696     case AL_STEREO_MODE_SOFT:
2697         CheckSize(1);
2698         if((err=GetSourceiv(Source, Context, prop, {ivals, 1u})) != false)
2699             values[0] = ivals[0];
2700         return err;
2701
2702     /* 1x uint */
2703     case AL_BUFFER:
2704     case AL_DIRECT_FILTER:
2705         CheckSize(1);
2706         if((err=GetSourceiv(Source, Context, prop, {ivals, 1u})) != false)
2707             values[0] = static_cast<ALuint>(ivals[0]);
2708         return err;
2709
2710     /* 3x uint */
2711     case AL_AUXILIARY_SEND_FILTER:
2712         CheckSize(3);
2713         if((err=GetSourceiv(Source, Context, prop, {ivals, 3u})) != false)
2714         {
2715             values[0] = static_cast<ALuint>(ivals[0]);
2716             values[1] = static_cast<ALuint>(ivals[1]);
2717             values[2] = static_cast<ALuint>(ivals[2]);
2718         }
2719         return err;
2720
2721     case AL_SEC_OFFSET_LATENCY_SOFT:
2722     case AL_SEC_OFFSET_CLOCK_SOFT:
2723         break; /* Double only */
2724     case AL_STEREO_ANGLES:
2725         break; /* Float/double only */
2726     }
2727
2728     ERR("Unexpected property: 0x%04x\n", prop);
2729     Context->setError(AL_INVALID_ENUM, "Invalid source integer64 property 0x%04x", prop);
2730     return false;
2731 }
2732 catch(check_exception&) {
2733     return false;
2734 }
2735
2736
2737 void StartSources(ALCcontext *const context, const al::span<ALsource*> srchandles,
2738     const nanoseconds start_time=nanoseconds::min())
2739 {
2740     ALCdevice *device{context->mALDevice.get()};
2741     /* If the device is disconnected, and voices stop on disconnect, go right
2742      * to stopped.
2743      */
2744     if(!device->Connected.load(std::memory_order_acquire)) UNLIKELY
2745     {
2746         if(context->mStopVoicesOnDisconnect.load(std::memory_order_acquire))
2747         {
2748             for(ALsource *source : srchandles)
2749             {
2750                 /* TODO: Send state change event? */
2751                 source->Offset = 0.0;
2752                 source->OffsetType = AL_NONE;
2753                 source->state = AL_STOPPED;
2754             }
2755             return;
2756         }
2757     }
2758
2759     /* Count the number of reusable voices. */
2760     auto voicelist = context->getVoicesSpan();
2761     size_t free_voices{0};
2762     for(const Voice *voice : voicelist)
2763     {
2764         free_voices += (voice->mPlayState.load(std::memory_order_acquire) == Voice::Stopped
2765             && voice->mSourceID.load(std::memory_order_relaxed) == 0u
2766             && voice->mPendingChange.load(std::memory_order_relaxed) == false);
2767         if(free_voices == srchandles.size())
2768             break;
2769     }
2770     if(srchandles.size() != free_voices) UNLIKELY
2771     {
2772         const size_t inc_amount{srchandles.size() - free_voices};
2773         auto &allvoices = *context->mVoices.load(std::memory_order_relaxed);
2774         if(inc_amount > allvoices.size() - voicelist.size())
2775         {
2776             /* Increase the number of voices to handle the request. */
2777             context->allocVoices(inc_amount - (allvoices.size() - voicelist.size()));
2778         }
2779         context->mActiveVoiceCount.fetch_add(inc_amount, std::memory_order_release);
2780         voicelist = context->getVoicesSpan();
2781     }
2782
2783     auto voiceiter = voicelist.begin();
2784     ALuint vidx{0};
2785     VoiceChange *tail{}, *cur{};
2786     for(ALsource *source : srchandles)
2787     {
2788         /* Check that there is a queue containing at least one valid, non zero
2789          * length buffer.
2790          */
2791         auto find_buffer = [](ALbufferQueueItem &entry) noexcept
2792         { return entry.mSampleLen != 0 || entry.mCallback != nullptr; };
2793         auto BufferList = std::find_if(source->mQueue.begin(), source->mQueue.end(), find_buffer);
2794
2795         /* If there's nothing to play, go right to stopped. */
2796         if(BufferList == source->mQueue.end()) UNLIKELY
2797         {
2798             /* NOTE: A source without any playable buffers should not have a
2799              * Voice since it shouldn't be in a playing or paused state. So
2800              * there's no need to look up its voice and clear the source.
2801              */
2802             source->Offset = 0.0;
2803             source->OffsetType = AL_NONE;
2804             source->state = AL_STOPPED;
2805             continue;
2806         }
2807
2808         if(!cur)
2809             cur = tail = GetVoiceChanger(context);
2810         else
2811         {
2812             cur->mNext.store(GetVoiceChanger(context), std::memory_order_relaxed);
2813             cur = cur->mNext.load(std::memory_order_relaxed);
2814         }
2815
2816         Voice *voice{GetSourceVoice(source, context)};
2817         switch(GetSourceState(source, voice))
2818         {
2819         case AL_PAUSED:
2820             /* A source that's paused simply resumes. If there's no voice, it
2821              * was lost from a disconnect, so just start over with a new one.
2822              */
2823             cur->mOldVoice = nullptr;
2824             if(!voice) break;
2825             cur->mVoice = voice;
2826             cur->mSourceID = source->id;
2827             cur->mState = VChangeState::Play;
2828             source->state = AL_PLAYING;
2829 #ifdef ALSOFT_EAX
2830             if(context->hasEax())
2831                 source->eaxCommit();
2832 #endif // ALSOFT_EAX
2833             continue;
2834
2835         case AL_PLAYING:
2836             /* A source that's already playing is restarted from the beginning.
2837              * Stop the current voice and start a new one so it properly cross-
2838              * fades back to the beginning.
2839              */
2840             if(voice)
2841                 voice->mPendingChange.store(true, std::memory_order_relaxed);
2842             cur->mOldVoice = voice;
2843             voice = nullptr;
2844             break;
2845
2846         default:
2847             assert(voice == nullptr);
2848             cur->mOldVoice = nullptr;
2849 #ifdef ALSOFT_EAX
2850             if(context->hasEax())
2851                 source->eaxCommit();
2852 #endif // ALSOFT_EAX
2853             break;
2854         }
2855
2856         /* Find the next unused voice to play this source with. */
2857         for(;voiceiter != voicelist.end();++voiceiter,++vidx)
2858         {
2859             Voice *v{*voiceiter};
2860             if(v->mPlayState.load(std::memory_order_acquire) == Voice::Stopped
2861                 && v->mSourceID.load(std::memory_order_relaxed) == 0u
2862                 && v->mPendingChange.load(std::memory_order_relaxed) == false)
2863             {
2864                 voice = v;
2865                 break;
2866             }
2867         }
2868         ASSUME(voice != nullptr);
2869
2870         voice->mPosition.store(0, std::memory_order_relaxed);
2871         voice->mPositionFrac.store(0, std::memory_order_relaxed);
2872         voice->mCurrentBuffer.store(&source->mQueue.front(), std::memory_order_relaxed);
2873         voice->mStartTime = start_time;
2874         voice->mFlags.reset();
2875         /* A source that's not playing or paused has any offset applied when it
2876          * starts playing.
2877          */
2878         if(const ALenum offsettype{source->OffsetType})
2879         {
2880             const double offset{source->Offset};
2881             source->OffsetType = AL_NONE;
2882             source->Offset = 0.0;
2883             if(auto vpos = GetSampleOffset(source->mQueue, offsettype, offset))
2884             {
2885                 voice->mPosition.store(vpos->pos, std::memory_order_relaxed);
2886                 voice->mPositionFrac.store(vpos->frac, std::memory_order_relaxed);
2887                 voice->mCurrentBuffer.store(vpos->bufferitem, std::memory_order_relaxed);
2888                 if(vpos->pos > 0 || (vpos->pos == 0 && vpos->frac > 0)
2889                     || vpos->bufferitem != &source->mQueue.front())
2890                     voice->mFlags.set(VoiceIsFading);
2891             }
2892         }
2893         InitVoice(voice, source, al::to_address(BufferList), context, device);
2894
2895         source->VoiceIdx = vidx;
2896         source->state = AL_PLAYING;
2897
2898         cur->mVoice = voice;
2899         cur->mSourceID = source->id;
2900         cur->mState = VChangeState::Play;
2901     }
2902     if(tail) LIKELY
2903         SendVoiceChanges(context, tail);
2904 }
2905
2906 } // namespace
2907
2908 AL_API void AL_APIENTRY alGenSources(ALsizei n, ALuint *sources)
2909 START_API_FUNC
2910 {
2911     ContextRef context{GetContextRef()};
2912     if(!context) UNLIKELY return;
2913
2914     if(n < 0) UNLIKELY
2915         context->setError(AL_INVALID_VALUE, "Generating %d sources", n);
2916     if(n <= 0) UNLIKELY return;
2917
2918     std::unique_lock<std::mutex> srclock{context->mSourceLock};
2919     ALCdevice *device{context->mALDevice.get()};
2920     if(static_cast<ALuint>(n) > device->SourcesMax-context->mNumSources)
2921     {
2922         context->setError(AL_OUT_OF_MEMORY, "Exceeding %u source limit (%u + %d)",
2923             device->SourcesMax, context->mNumSources, n);
2924         return;
2925     }
2926     if(!EnsureSources(context.get(), static_cast<ALuint>(n)))
2927     {
2928         context->setError(AL_OUT_OF_MEMORY, "Failed to allocate %d source%s", n, (n==1)?"":"s");
2929         return;
2930     }
2931
2932     if(n == 1)
2933     {
2934         ALsource *source{AllocSource(context.get())};
2935         sources[0] = source->id;
2936
2937 #ifdef ALSOFT_EAX
2938         source->eaxInitialize(context.get());
2939 #endif // ALSOFT_EAX
2940     }
2941     else
2942     {
2943         al::vector<ALuint> ids;
2944         ids.reserve(static_cast<ALuint>(n));
2945         do {
2946             ALsource *source{AllocSource(context.get())};
2947             ids.emplace_back(source->id);
2948
2949 #ifdef ALSOFT_EAX
2950             source->eaxInitialize(context.get());
2951 #endif // ALSOFT_EAX
2952         } while(--n);
2953         std::copy(ids.cbegin(), ids.cend(), sources);
2954     }
2955 }
2956 END_API_FUNC
2957
2958 AL_API void AL_APIENTRY alDeleteSources(ALsizei n, const ALuint *sources)
2959 START_API_FUNC
2960 {
2961     ContextRef context{GetContextRef()};
2962     if(!context) UNLIKELY return;
2963
2964     if(n < 0) UNLIKELY
2965         context->setError(AL_INVALID_VALUE, "Deleting %d sources", n);
2966     if(n <= 0) UNLIKELY return;
2967
2968     std::lock_guard<std::mutex> _{context->mSourceLock};
2969
2970     /* Check that all Sources are valid */
2971     auto validate_source = [&context](const ALuint sid) -> bool
2972     { return LookupSource(context.get(), sid) != nullptr; };
2973
2974     const ALuint *sources_end = sources + n;
2975     auto invsrc = std::find_if_not(sources, sources_end, validate_source);
2976     if(invsrc != sources_end) UNLIKELY
2977         return context->setError(AL_INVALID_NAME, "Invalid source ID %u", *invsrc);
2978
2979     /* All good. Delete source IDs. */
2980     auto delete_source = [&context](const ALuint sid) -> void
2981     {
2982         ALsource *src{LookupSource(context.get(), sid)};
2983         if(src) FreeSource(context.get(), src);
2984     };
2985     std::for_each(sources, sources_end, delete_source);
2986 }
2987 END_API_FUNC
2988
2989 AL_API ALboolean AL_APIENTRY alIsSource(ALuint source)
2990 START_API_FUNC
2991 {
2992     ContextRef context{GetContextRef()};
2993     if(context) LIKELY
2994     {
2995         std::lock_guard<std::mutex> _{context->mSourceLock};
2996         if(LookupSource(context.get(), source) != nullptr)
2997             return AL_TRUE;
2998     }
2999     return AL_FALSE;
3000 }
3001 END_API_FUNC
3002
3003
3004 AL_API void AL_APIENTRY alSourcef(ALuint source, ALenum param, ALfloat value)
3005 START_API_FUNC
3006 {
3007     ContextRef context{GetContextRef()};
3008     if(!context) UNLIKELY return;
3009
3010     std::lock_guard<std::mutex> _{context->mPropLock};
3011     std::lock_guard<std::mutex> __{context->mSourceLock};
3012     ALsource *Source = LookupSource(context.get(), source);
3013     if(!Source) UNLIKELY
3014         context->setError(AL_INVALID_NAME, "Invalid source ID %u", source);
3015     else
3016         SetSourcefv(Source, context.get(), static_cast<SourceProp>(param), {&value, 1u});
3017 }
3018 END_API_FUNC
3019
3020 AL_API void AL_APIENTRY alSource3f(ALuint source, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3)
3021 START_API_FUNC
3022 {
3023     ContextRef context{GetContextRef()};
3024     if(!context) UNLIKELY return;
3025
3026     std::lock_guard<std::mutex> _{context->mPropLock};
3027     std::lock_guard<std::mutex> __{context->mSourceLock};
3028     ALsource *Source = LookupSource(context.get(), source);
3029     if(!Source) UNLIKELY
3030         context->setError(AL_INVALID_NAME, "Invalid source ID %u", source);
3031     else
3032     {
3033         const float fvals[3]{ value1, value2, value3 };
3034         SetSourcefv(Source, context.get(), static_cast<SourceProp>(param), fvals);
3035     }
3036 }
3037 END_API_FUNC
3038
3039 AL_API void AL_APIENTRY alSourcefv(ALuint source, ALenum param, const ALfloat *values)
3040 START_API_FUNC
3041 {
3042     ContextRef context{GetContextRef()};
3043     if(!context) UNLIKELY return;
3044
3045     std::lock_guard<std::mutex> _{context->mPropLock};
3046     std::lock_guard<std::mutex> __{context->mSourceLock};
3047     ALsource *Source = LookupSource(context.get(), source);
3048     if(!Source) UNLIKELY
3049         return context->setError(AL_INVALID_NAME, "Invalid source ID %u", source);
3050     if(!values) UNLIKELY
3051         return context->setError(AL_INVALID_VALUE, "NULL pointer");
3052
3053     const ALuint count{FloatValsByProp(param)};
3054     SetSourcefv(Source, context.get(), static_cast<SourceProp>(param), {values, count});
3055 }
3056 END_API_FUNC
3057
3058
3059 AL_API void AL_APIENTRY alSourcedSOFT(ALuint source, ALenum param, ALdouble value)
3060 START_API_FUNC
3061 {
3062     ContextRef context{GetContextRef()};
3063     if(!context) UNLIKELY return;
3064
3065     std::lock_guard<std::mutex> _{context->mPropLock};
3066     std::lock_guard<std::mutex> __{context->mSourceLock};
3067     ALsource *Source = LookupSource(context.get(), source);
3068     if(!Source) UNLIKELY
3069         context->setError(AL_INVALID_NAME, "Invalid source ID %u", source);
3070     else
3071     {
3072         const float fval[1]{static_cast<float>(value)};
3073         SetSourcefv(Source, context.get(), static_cast<SourceProp>(param), fval);
3074     }
3075 }
3076 END_API_FUNC
3077
3078 AL_API void AL_APIENTRY alSource3dSOFT(ALuint source, ALenum param, ALdouble value1, ALdouble value2, ALdouble value3)
3079 START_API_FUNC
3080 {
3081     ContextRef context{GetContextRef()};
3082     if(!context) UNLIKELY return;
3083
3084     std::lock_guard<std::mutex> _{context->mPropLock};
3085     std::lock_guard<std::mutex> __{context->mSourceLock};
3086     ALsource *Source = LookupSource(context.get(), source);
3087     if(!Source) UNLIKELY
3088         context->setError(AL_INVALID_NAME, "Invalid source ID %u", source);
3089     else
3090     {
3091         const float fvals[3]{static_cast<float>(value1), static_cast<float>(value2),
3092             static_cast<float>(value3)};
3093         SetSourcefv(Source, context.get(), static_cast<SourceProp>(param), fvals);
3094     }
3095 }
3096 END_API_FUNC
3097
3098 AL_API void AL_APIENTRY alSourcedvSOFT(ALuint source, ALenum param, const ALdouble *values)
3099 START_API_FUNC
3100 {
3101     ContextRef context{GetContextRef()};
3102     if(!context) UNLIKELY return;
3103
3104     std::lock_guard<std::mutex> _{context->mPropLock};
3105     std::lock_guard<std::mutex> __{context->mSourceLock};
3106     ALsource *Source = LookupSource(context.get(), source);
3107     if(!Source) UNLIKELY
3108         return context->setError(AL_INVALID_NAME, "Invalid source ID %u", source);
3109     if(!values) UNLIKELY
3110         return context->setError(AL_INVALID_VALUE, "NULL pointer");
3111
3112     const ALuint count{DoubleValsByProp(param)};
3113     float fvals[MaxValues];
3114     std::copy_n(values, count, fvals);
3115     SetSourcefv(Source, context.get(), static_cast<SourceProp>(param), {fvals, count});
3116 }
3117 END_API_FUNC
3118
3119
3120 AL_API void AL_APIENTRY alSourcei(ALuint source, ALenum param, ALint value)
3121 START_API_FUNC
3122 {
3123     ContextRef context{GetContextRef()};
3124     if(!context) UNLIKELY return;
3125
3126     std::lock_guard<std::mutex> _{context->mPropLock};
3127     std::lock_guard<std::mutex> __{context->mSourceLock};
3128     ALsource *Source = LookupSource(context.get(), source);
3129     if(!Source) UNLIKELY
3130         context->setError(AL_INVALID_NAME, "Invalid source ID %u", source);
3131     else
3132         SetSourceiv(Source, context.get(), static_cast<SourceProp>(param), {&value, 1u});
3133 }
3134 END_API_FUNC
3135
3136 AL_API void AL_APIENTRY alSource3i(ALuint source, ALenum param, ALint value1, ALint value2, ALint value3)
3137 START_API_FUNC
3138 {
3139     ContextRef context{GetContextRef()};
3140     if(!context) UNLIKELY return;
3141
3142     std::lock_guard<std::mutex> _{context->mPropLock};
3143     std::lock_guard<std::mutex> __{context->mSourceLock};
3144     ALsource *Source = LookupSource(context.get(), source);
3145     if(!Source) UNLIKELY
3146         context->setError(AL_INVALID_NAME, "Invalid source ID %u", source);
3147     else
3148     {
3149         const int ivals[3]{ value1, value2, value3 };
3150         SetSourceiv(Source, context.get(), static_cast<SourceProp>(param), ivals);
3151     }
3152 }
3153 END_API_FUNC
3154
3155 AL_API void AL_APIENTRY alSourceiv(ALuint source, ALenum param, const ALint *values)
3156 START_API_FUNC
3157 {
3158     ContextRef context{GetContextRef()};
3159     if(!context) UNLIKELY return;
3160
3161     std::lock_guard<std::mutex> _{context->mPropLock};
3162     std::lock_guard<std::mutex> __{context->mSourceLock};
3163     ALsource *Source = LookupSource(context.get(), source);
3164     if(!Source) UNLIKELY
3165         return context->setError(AL_INVALID_NAME, "Invalid source ID %u", source);
3166     if(!values) UNLIKELY
3167         return context->setError(AL_INVALID_VALUE, "NULL pointer");
3168
3169     const ALuint count{IntValsByProp(param)};
3170     SetSourceiv(Source, context.get(), static_cast<SourceProp>(param), {values, count});
3171 }
3172 END_API_FUNC
3173
3174
3175 AL_API void AL_APIENTRY alSourcei64SOFT(ALuint source, ALenum param, ALint64SOFT value)
3176 START_API_FUNC
3177 {
3178     ContextRef context{GetContextRef()};
3179     if(!context) UNLIKELY return;
3180
3181     std::lock_guard<std::mutex> _{context->mPropLock};
3182     std::lock_guard<std::mutex> __{context->mSourceLock};
3183     ALsource *Source{LookupSource(context.get(), source)};
3184     if(!Source) UNLIKELY
3185         context->setError(AL_INVALID_NAME, "Invalid source ID %u", source);
3186     else
3187         SetSourcei64v(Source, context.get(), static_cast<SourceProp>(param), {&value, 1u});
3188 }
3189 END_API_FUNC
3190
3191 AL_API void AL_APIENTRY alSource3i64SOFT(ALuint source, ALenum param, ALint64SOFT value1, ALint64SOFT value2, ALint64SOFT value3)
3192 START_API_FUNC
3193 {
3194     ContextRef context{GetContextRef()};
3195     if(!context) UNLIKELY return;
3196
3197     std::lock_guard<std::mutex> _{context->mPropLock};
3198     std::lock_guard<std::mutex> __{context->mSourceLock};
3199     ALsource *Source{LookupSource(context.get(), source)};
3200     if(!Source) UNLIKELY
3201         context->setError(AL_INVALID_NAME, "Invalid source ID %u", source);
3202     else
3203     {
3204         const int64_t i64vals[3]{ value1, value2, value3 };
3205         SetSourcei64v(Source, context.get(), static_cast<SourceProp>(param), i64vals);
3206     }
3207 }
3208 END_API_FUNC
3209
3210 AL_API void AL_APIENTRY alSourcei64vSOFT(ALuint source, ALenum param, const ALint64SOFT *values)
3211 START_API_FUNC
3212 {
3213     ContextRef context{GetContextRef()};
3214     if(!context) UNLIKELY return;
3215
3216     std::lock_guard<std::mutex> _{context->mPropLock};
3217     std::lock_guard<std::mutex> __{context->mSourceLock};
3218     ALsource *Source{LookupSource(context.get(), source)};
3219     if(!Source) UNLIKELY
3220         return context->setError(AL_INVALID_NAME, "Invalid source ID %u", source);
3221     if(!values) UNLIKELY
3222         return context->setError(AL_INVALID_VALUE, "NULL pointer");
3223
3224     const ALuint count{Int64ValsByProp(param)};
3225     SetSourcei64v(Source, context.get(), static_cast<SourceProp>(param), {values, count});
3226 }
3227 END_API_FUNC
3228
3229
3230 AL_API void AL_APIENTRY alGetSourcef(ALuint source, ALenum param, ALfloat *value)
3231 START_API_FUNC
3232 {
3233     ContextRef context{GetContextRef()};
3234     if(!context) UNLIKELY return;
3235
3236     std::lock_guard<std::mutex> _{context->mSourceLock};
3237     ALsource *Source{LookupSource(context.get(), source)};
3238     if(!Source) UNLIKELY
3239         context->setError(AL_INVALID_NAME, "Invalid source ID %u", source);
3240     else if(!value) UNLIKELY
3241         context->setError(AL_INVALID_VALUE, "NULL pointer");
3242     else
3243     {
3244         double dval[1];
3245         if(GetSourcedv(Source, context.get(), static_cast<SourceProp>(param), dval))
3246             *value = static_cast<float>(dval[0]);
3247     }
3248 }
3249 END_API_FUNC
3250
3251 AL_API void AL_APIENTRY alGetSource3f(ALuint source, ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3)
3252 START_API_FUNC
3253 {
3254     ContextRef context{GetContextRef()};
3255     if(!context) UNLIKELY return;
3256
3257     std::lock_guard<std::mutex> _{context->mSourceLock};
3258     ALsource *Source{LookupSource(context.get(), source)};
3259     if(!Source) UNLIKELY
3260         context->setError(AL_INVALID_NAME, "Invalid source ID %u", source);
3261     else if(!(value1 && value2 && value3)) UNLIKELY
3262         context->setError(AL_INVALID_VALUE, "NULL pointer");
3263     else
3264     {
3265         double dvals[3];
3266         if(GetSourcedv(Source, context.get(), static_cast<SourceProp>(param), dvals))
3267         {
3268             *value1 = static_cast<float>(dvals[0]);
3269             *value2 = static_cast<float>(dvals[1]);
3270             *value3 = static_cast<float>(dvals[2]);
3271         }
3272     }
3273 }
3274 END_API_FUNC
3275
3276 AL_API void AL_APIENTRY alGetSourcefv(ALuint source, ALenum param, ALfloat *values)
3277 START_API_FUNC
3278 {
3279     ContextRef context{GetContextRef()};
3280     if(!context) UNLIKELY return;
3281
3282     std::lock_guard<std::mutex> _{context->mSourceLock};
3283     ALsource *Source{LookupSource(context.get(), source)};
3284     if(!Source) UNLIKELY
3285         return context->setError(AL_INVALID_NAME, "Invalid source ID %u", source);
3286     if(!values) UNLIKELY
3287         return context->setError(AL_INVALID_VALUE, "NULL pointer");
3288
3289     const ALuint count{FloatValsByProp(param)};
3290     double dvals[MaxValues];
3291     if(GetSourcedv(Source, context.get(), static_cast<SourceProp>(param), {dvals, count}))
3292         std::copy_n(dvals, count, values);
3293 }
3294 END_API_FUNC
3295
3296
3297 AL_API void AL_APIENTRY alGetSourcedSOFT(ALuint source, ALenum param, ALdouble *value)
3298 START_API_FUNC
3299 {
3300     ContextRef context{GetContextRef()};
3301     if(!context) UNLIKELY return;
3302
3303     std::lock_guard<std::mutex> _{context->mSourceLock};
3304     ALsource *Source{LookupSource(context.get(), source)};
3305     if(!Source) UNLIKELY
3306         context->setError(AL_INVALID_NAME, "Invalid source ID %u", source);
3307     else if(!value) UNLIKELY
3308         context->setError(AL_INVALID_VALUE, "NULL pointer");
3309     else
3310         GetSourcedv(Source, context.get(), static_cast<SourceProp>(param), {value, 1u});
3311 }
3312 END_API_FUNC
3313
3314 AL_API void AL_APIENTRY alGetSource3dSOFT(ALuint source, ALenum param, ALdouble *value1, ALdouble *value2, ALdouble *value3)
3315 START_API_FUNC
3316 {
3317     ContextRef context{GetContextRef()};
3318     if(!context) UNLIKELY return;
3319
3320     std::lock_guard<std::mutex> _{context->mSourceLock};
3321     ALsource *Source{LookupSource(context.get(), source)};
3322     if(!Source) UNLIKELY
3323         context->setError(AL_INVALID_NAME, "Invalid source ID %u", source);
3324     else if(!(value1 && value2 && value3)) UNLIKELY
3325         context->setError(AL_INVALID_VALUE, "NULL pointer");
3326     else
3327     {
3328         double dvals[3];
3329         if(GetSourcedv(Source, context.get(), static_cast<SourceProp>(param), dvals))
3330         {
3331             *value1 = dvals[0];
3332             *value2 = dvals[1];
3333             *value3 = dvals[2];
3334         }
3335     }
3336 }
3337 END_API_FUNC
3338
3339 AL_API void AL_APIENTRY alGetSourcedvSOFT(ALuint source, ALenum param, ALdouble *values)
3340 START_API_FUNC
3341 {
3342     ContextRef context{GetContextRef()};
3343     if(!context) UNLIKELY return;
3344
3345     std::lock_guard<std::mutex> _{context->mSourceLock};
3346     ALsource *Source{LookupSource(context.get(), source)};
3347     if(!Source) UNLIKELY
3348         return context->setError(AL_INVALID_NAME, "Invalid source ID %u", source);
3349     if(!values) UNLIKELY
3350         return context->setError(AL_INVALID_VALUE, "NULL pointer");
3351
3352     const ALuint count{DoubleValsByProp(param)};
3353     GetSourcedv(Source, context.get(), static_cast<SourceProp>(param), {values, count});
3354 }
3355 END_API_FUNC
3356
3357
3358 AL_API void AL_APIENTRY alGetSourcei(ALuint source, ALenum param, ALint *value)
3359 START_API_FUNC
3360 {
3361     ContextRef context{GetContextRef()};
3362     if(!context) UNLIKELY return;
3363
3364     std::lock_guard<std::mutex> _{context->mSourceLock};
3365     ALsource *Source{LookupSource(context.get(), source)};
3366     if(!Source) UNLIKELY
3367         context->setError(AL_INVALID_NAME, "Invalid source ID %u", source);
3368     else if(!value) UNLIKELY
3369         context->setError(AL_INVALID_VALUE, "NULL pointer");
3370     else
3371         GetSourceiv(Source, context.get(), static_cast<SourceProp>(param), {value, 1u});
3372 }
3373 END_API_FUNC
3374
3375 AL_API void AL_APIENTRY alGetSource3i(ALuint source, ALenum param, ALint *value1, ALint *value2, ALint *value3)
3376 START_API_FUNC
3377 {
3378     ContextRef context{GetContextRef()};
3379     if(!context) UNLIKELY return;
3380
3381     std::lock_guard<std::mutex> _{context->mSourceLock};
3382     ALsource *Source{LookupSource(context.get(), source)};
3383     if(!Source) UNLIKELY
3384         context->setError(AL_INVALID_NAME, "Invalid source ID %u", source);
3385     else if(!(value1 && value2 && value3)) UNLIKELY
3386         context->setError(AL_INVALID_VALUE, "NULL pointer");
3387     else
3388     {
3389         int ivals[3];
3390         if(GetSourceiv(Source, context.get(), static_cast<SourceProp>(param), ivals))
3391         {
3392             *value1 = ivals[0];
3393             *value2 = ivals[1];
3394             *value3 = ivals[2];
3395         }
3396     }
3397 }
3398 END_API_FUNC
3399
3400 AL_API void AL_APIENTRY alGetSourceiv(ALuint source, ALenum param, ALint *values)
3401 START_API_FUNC
3402 {
3403     ContextRef context{GetContextRef()};
3404     if(!context) UNLIKELY return;
3405
3406     std::lock_guard<std::mutex> _{context->mSourceLock};
3407     ALsource *Source{LookupSource(context.get(), source)};
3408     if(!Source) UNLIKELY
3409         return context->setError(AL_INVALID_NAME, "Invalid source ID %u", source);
3410     if(!values) UNLIKELY
3411         return context->setError(AL_INVALID_VALUE, "NULL pointer");
3412
3413     const ALuint count{IntValsByProp(param)};
3414     GetSourceiv(Source, context.get(), static_cast<SourceProp>(param), {values, count});
3415 }
3416 END_API_FUNC
3417
3418
3419 AL_API void AL_APIENTRY alGetSourcei64SOFT(ALuint source, ALenum param, ALint64SOFT *value)
3420 START_API_FUNC
3421 {
3422     ContextRef context{GetContextRef()};
3423     if(!context) UNLIKELY return;
3424
3425     std::lock_guard<std::mutex> _{context->mSourceLock};
3426     ALsource *Source{LookupSource(context.get(), source)};
3427     if(!Source) UNLIKELY
3428         context->setError(AL_INVALID_NAME, "Invalid source ID %u", source);
3429     else if(!value) UNLIKELY
3430         context->setError(AL_INVALID_VALUE, "NULL pointer");
3431     else
3432         GetSourcei64v(Source, context.get(), static_cast<SourceProp>(param), {value, 1u});
3433 }
3434 END_API_FUNC
3435
3436 AL_API void AL_APIENTRY alGetSource3i64SOFT(ALuint source, ALenum param, ALint64SOFT *value1, ALint64SOFT *value2, ALint64SOFT *value3)
3437 START_API_FUNC
3438 {
3439     ContextRef context{GetContextRef()};
3440     if(!context) UNLIKELY return;
3441
3442     std::lock_guard<std::mutex> _{context->mSourceLock};
3443     ALsource *Source{LookupSource(context.get(), source)};
3444     if(!Source) UNLIKELY
3445         context->setError(AL_INVALID_NAME, "Invalid source ID %u", source);
3446     else if(!(value1 && value2 && value3)) UNLIKELY
3447         context->setError(AL_INVALID_VALUE, "NULL pointer");
3448     else
3449     {
3450         int64_t i64vals[3];
3451         if(GetSourcei64v(Source, context.get(), static_cast<SourceProp>(param), i64vals))
3452         {
3453             *value1 = i64vals[0];
3454             *value2 = i64vals[1];
3455             *value3 = i64vals[2];
3456         }
3457     }
3458 }
3459 END_API_FUNC
3460
3461 AL_API void AL_APIENTRY alGetSourcei64vSOFT(ALuint source, ALenum param, ALint64SOFT *values)
3462 START_API_FUNC
3463 {
3464     ContextRef context{GetContextRef()};
3465     if(!context) UNLIKELY return;
3466
3467     std::lock_guard<std::mutex> _{context->mSourceLock};
3468     ALsource *Source{LookupSource(context.get(), source)};
3469     if(!Source) UNLIKELY
3470         return context->setError(AL_INVALID_NAME, "Invalid source ID %u", source);
3471     if(!values) UNLIKELY
3472         return context->setError(AL_INVALID_VALUE, "NULL pointer");
3473
3474     const ALuint count{Int64ValsByProp(param)};
3475     GetSourcei64v(Source, context.get(), static_cast<SourceProp>(param), {values, count});
3476 }
3477 END_API_FUNC
3478
3479
3480 AL_API void AL_APIENTRY alSourcePlay(ALuint source)
3481 START_API_FUNC
3482 {
3483     ContextRef context{GetContextRef()};
3484     if(!context) UNLIKELY return;
3485
3486     std::lock_guard<std::mutex> _{context->mSourceLock};
3487     ALsource *srchandle{LookupSource(context.get(), source)};
3488     if(!srchandle)
3489         return context->setError(AL_INVALID_NAME, "Invalid source ID %u", source);
3490
3491     StartSources(context.get(), {&srchandle, 1});
3492 }
3493 END_API_FUNC
3494
3495 void AL_APIENTRY alSourcePlayAtTimeSOFT(ALuint source, ALint64SOFT start_time)
3496 START_API_FUNC
3497 {
3498     ContextRef context{GetContextRef()};
3499     if(!context) UNLIKELY return;
3500
3501     if(start_time < 0) UNLIKELY
3502         return context->setError(AL_INVALID_VALUE, "Invalid time point %" PRId64, start_time);
3503
3504     std::lock_guard<std::mutex> _{context->mSourceLock};
3505     ALsource *srchandle{LookupSource(context.get(), source)};
3506     if(!srchandle)
3507         return context->setError(AL_INVALID_NAME, "Invalid source ID %u", source);
3508
3509     StartSources(context.get(), {&srchandle, 1}, nanoseconds{start_time});
3510 }
3511 END_API_FUNC
3512
3513 AL_API void AL_APIENTRY alSourcePlayv(ALsizei n, const ALuint *sources)
3514 START_API_FUNC
3515 {
3516     ContextRef context{GetContextRef()};
3517     if(!context) UNLIKELY return;
3518
3519     if(n < 0) UNLIKELY
3520         context->setError(AL_INVALID_VALUE, "Playing %d sources", n);
3521     if(n <= 0) UNLIKELY return;
3522
3523     al::vector<ALsource*> extra_sources;
3524     std::array<ALsource*,8> source_storage;
3525     al::span<ALsource*> srchandles;
3526     if(static_cast<ALuint>(n) <= source_storage.size()) LIKELY
3527         srchandles = {source_storage.data(), static_cast<ALuint>(n)};
3528     else
3529     {
3530         extra_sources.resize(static_cast<ALuint>(n));
3531         srchandles = {extra_sources.data(), extra_sources.size()};
3532     }
3533
3534     std::lock_guard<std::mutex> _{context->mSourceLock};
3535     for(auto &srchdl : srchandles)
3536     {
3537         srchdl = LookupSource(context.get(), *sources);
3538         if(!srchdl) UNLIKELY
3539             return context->setError(AL_INVALID_NAME, "Invalid source ID %u", *sources);
3540         ++sources;
3541     }
3542
3543     StartSources(context.get(), srchandles);
3544 }
3545 END_API_FUNC
3546
3547 void AL_APIENTRY alSourcePlayAtTimevSOFT(ALsizei n, const ALuint *sources, ALint64SOFT start_time)
3548 START_API_FUNC
3549 {
3550     ContextRef context{GetContextRef()};
3551     if(!context) UNLIKELY return;
3552
3553     if(n < 0) UNLIKELY
3554         context->setError(AL_INVALID_VALUE, "Playing %d sources", n);
3555     if(n <= 0) UNLIKELY return;
3556
3557     if(start_time < 0) UNLIKELY
3558         return context->setError(AL_INVALID_VALUE, "Invalid time point %" PRId64, start_time);
3559
3560     al::vector<ALsource*> extra_sources;
3561     std::array<ALsource*,8> source_storage;
3562     al::span<ALsource*> srchandles;
3563     if(static_cast<ALuint>(n) <= source_storage.size()) LIKELY
3564         srchandles = {source_storage.data(), static_cast<ALuint>(n)};
3565     else
3566     {
3567         extra_sources.resize(static_cast<ALuint>(n));
3568         srchandles = {extra_sources.data(), extra_sources.size()};
3569     }
3570
3571     std::lock_guard<std::mutex> _{context->mSourceLock};
3572     for(auto &srchdl : srchandles)
3573     {
3574         srchdl = LookupSource(context.get(), *sources);
3575         if(!srchdl)
3576             return context->setError(AL_INVALID_NAME, "Invalid source ID %u", *sources);
3577         ++sources;
3578     }
3579
3580     StartSources(context.get(), srchandles, nanoseconds{start_time});
3581 }
3582 END_API_FUNC
3583
3584
3585 AL_API void AL_APIENTRY alSourcePause(ALuint source)
3586 START_API_FUNC
3587 { alSourcePausev(1, &source); }
3588 END_API_FUNC
3589
3590 AL_API void AL_APIENTRY alSourcePausev(ALsizei n, const ALuint *sources)
3591 START_API_FUNC
3592 {
3593     ContextRef context{GetContextRef()};
3594     if(!context) UNLIKELY return;
3595
3596     if(n < 0) UNLIKELY
3597         context->setError(AL_INVALID_VALUE, "Pausing %d sources", n);
3598     if(n <= 0) UNLIKELY return;
3599
3600     al::vector<ALsource*> extra_sources;
3601     std::array<ALsource*,8> source_storage;
3602     al::span<ALsource*> srchandles;
3603     if(static_cast<ALuint>(n) <= source_storage.size()) LIKELY
3604         srchandles = {source_storage.data(), static_cast<ALuint>(n)};
3605     else
3606     {
3607         extra_sources.resize(static_cast<ALuint>(n));
3608         srchandles = {extra_sources.data(), extra_sources.size()};
3609     }
3610
3611     std::lock_guard<std::mutex> _{context->mSourceLock};
3612     for(auto &srchdl : srchandles)
3613     {
3614         srchdl = LookupSource(context.get(), *sources);
3615         if(!srchdl)
3616             return context->setError(AL_INVALID_NAME, "Invalid source ID %u", *sources);
3617         ++sources;
3618     }
3619
3620     /* Pausing has to be done in two steps. First, for each source that's
3621      * detected to be playing, chamge the voice (asynchronously) to
3622      * stopping/paused.
3623      */
3624     VoiceChange *tail{}, *cur{};
3625     for(ALsource *source : srchandles)
3626     {
3627         Voice *voice{GetSourceVoice(source, context.get())};
3628         if(GetSourceState(source, voice) == AL_PLAYING)
3629         {
3630             if(!cur)
3631                 cur = tail = GetVoiceChanger(context.get());
3632             else
3633             {
3634                 cur->mNext.store(GetVoiceChanger(context.get()), std::memory_order_relaxed);
3635                 cur = cur->mNext.load(std::memory_order_relaxed);
3636             }
3637             cur->mVoice = voice;
3638             cur->mSourceID = source->id;
3639             cur->mState = VChangeState::Pause;
3640         }
3641     }
3642     if(tail) LIKELY
3643     {
3644         SendVoiceChanges(context.get(), tail);
3645         /* Second, now that the voice changes have been sent, because it's
3646          * possible that the voice stopped after it was detected playing and
3647          * before the voice got paused, recheck that the source is still
3648          * considered playing and set it to paused if so.
3649          */
3650         for(ALsource *source : srchandles)
3651         {
3652             Voice *voice{GetSourceVoice(source, context.get())};
3653             if(GetSourceState(source, voice) == AL_PLAYING)
3654                 source->state = AL_PAUSED;
3655         }
3656     }
3657 }
3658 END_API_FUNC
3659
3660
3661 AL_API void AL_APIENTRY alSourceStop(ALuint source)
3662 START_API_FUNC
3663 { alSourceStopv(1, &source); }
3664 END_API_FUNC
3665
3666 AL_API void AL_APIENTRY alSourceStopv(ALsizei n, const ALuint *sources)
3667 START_API_FUNC
3668 {
3669     ContextRef context{GetContextRef()};
3670     if(!context) UNLIKELY return;
3671
3672     if(n < 0) UNLIKELY
3673         context->setError(AL_INVALID_VALUE, "Stopping %d sources", n);
3674     if(n <= 0) UNLIKELY return;
3675
3676     al::vector<ALsource*> extra_sources;
3677     std::array<ALsource*,8> source_storage;
3678     al::span<ALsource*> srchandles;
3679     if(static_cast<ALuint>(n) <= source_storage.size()) LIKELY
3680         srchandles = {source_storage.data(), static_cast<ALuint>(n)};
3681     else
3682     {
3683         extra_sources.resize(static_cast<ALuint>(n));
3684         srchandles = {extra_sources.data(), extra_sources.size()};
3685     }
3686
3687     std::lock_guard<std::mutex> _{context->mSourceLock};
3688     for(auto &srchdl : srchandles)
3689     {
3690         srchdl = LookupSource(context.get(), *sources);
3691         if(!srchdl)
3692             return context->setError(AL_INVALID_NAME, "Invalid source ID %u", *sources);
3693         ++sources;
3694     }
3695
3696     VoiceChange *tail{}, *cur{};
3697     for(ALsource *source : srchandles)
3698     {
3699         if(Voice *voice{GetSourceVoice(source, context.get())})
3700         {
3701             if(!cur)
3702                 cur = tail = GetVoiceChanger(context.get());
3703             else
3704             {
3705                 cur->mNext.store(GetVoiceChanger(context.get()), std::memory_order_relaxed);
3706                 cur = cur->mNext.load(std::memory_order_relaxed);
3707             }
3708             voice->mPendingChange.store(true, std::memory_order_relaxed);
3709             cur->mVoice = voice;
3710             cur->mSourceID = source->id;
3711             cur->mState = VChangeState::Stop;
3712             source->state = AL_STOPPED;
3713         }
3714         source->Offset = 0.0;
3715         source->OffsetType = AL_NONE;
3716         source->VoiceIdx = INVALID_VOICE_IDX;
3717     }
3718     if(tail) LIKELY
3719         SendVoiceChanges(context.get(), tail);
3720 }
3721 END_API_FUNC
3722
3723
3724 AL_API void AL_APIENTRY alSourceRewind(ALuint source)
3725 START_API_FUNC
3726 { alSourceRewindv(1, &source); }
3727 END_API_FUNC
3728
3729 AL_API void AL_APIENTRY alSourceRewindv(ALsizei n, const ALuint *sources)
3730 START_API_FUNC
3731 {
3732     ContextRef context{GetContextRef()};
3733     if(!context) UNLIKELY return;
3734
3735     if(n < 0) UNLIKELY
3736         context->setError(AL_INVALID_VALUE, "Rewinding %d sources", n);
3737     if(n <= 0) UNLIKELY return;
3738
3739     al::vector<ALsource*> extra_sources;
3740     std::array<ALsource*,8> source_storage;
3741     al::span<ALsource*> srchandles;
3742     if(static_cast<ALuint>(n) <= source_storage.size()) LIKELY
3743         srchandles = {source_storage.data(), static_cast<ALuint>(n)};
3744     else
3745     {
3746         extra_sources.resize(static_cast<ALuint>(n));
3747         srchandles = {extra_sources.data(), extra_sources.size()};
3748     }
3749
3750     std::lock_guard<std::mutex> _{context->mSourceLock};
3751     for(auto &srchdl : srchandles)
3752     {
3753         srchdl = LookupSource(context.get(), *sources);
3754         if(!srchdl)
3755             return context->setError(AL_INVALID_NAME, "Invalid source ID %u", *sources);
3756         ++sources;
3757     }
3758
3759     VoiceChange *tail{}, *cur{};
3760     for(ALsource *source : srchandles)
3761     {
3762         Voice *voice{GetSourceVoice(source, context.get())};
3763         if(source->state != AL_INITIAL)
3764         {
3765             if(!cur)
3766                 cur = tail = GetVoiceChanger(context.get());
3767             else
3768             {
3769                 cur->mNext.store(GetVoiceChanger(context.get()), std::memory_order_relaxed);
3770                 cur = cur->mNext.load(std::memory_order_relaxed);
3771             }
3772             if(voice)
3773                 voice->mPendingChange.store(true, std::memory_order_relaxed);
3774             cur->mVoice = voice;
3775             cur->mSourceID = source->id;
3776             cur->mState = VChangeState::Reset;
3777             source->state = AL_INITIAL;
3778         }
3779         source->Offset = 0.0;
3780         source->OffsetType = AL_NONE;
3781         source->VoiceIdx = INVALID_VOICE_IDX;
3782     }
3783     if(tail) LIKELY
3784         SendVoiceChanges(context.get(), tail);
3785 }
3786 END_API_FUNC
3787
3788
3789 AL_API void AL_APIENTRY alSourceQueueBuffers(ALuint src, ALsizei nb, const ALuint *buffers)
3790 START_API_FUNC
3791 {
3792     ContextRef context{GetContextRef()};
3793     if(!context) UNLIKELY return;
3794
3795     if(nb < 0) UNLIKELY
3796         context->setError(AL_INVALID_VALUE, "Queueing %d buffers", nb);
3797     if(nb <= 0) UNLIKELY return;
3798
3799     std::lock_guard<std::mutex> _{context->mSourceLock};
3800     ALsource *source{LookupSource(context.get(),src)};
3801     if(!source) UNLIKELY
3802         return context->setError(AL_INVALID_NAME, "Invalid source ID %u", src);
3803
3804     /* Can't queue on a Static Source */
3805     if(source->SourceType == AL_STATIC) UNLIKELY
3806         return context->setError(AL_INVALID_OPERATION, "Queueing onto static source %u", src);
3807
3808     /* Check for a valid Buffer, for its frequency and format */
3809     ALCdevice *device{context->mALDevice.get()};
3810     ALbuffer *BufferFmt{nullptr};
3811     for(auto &item : source->mQueue)
3812     {
3813         BufferFmt = item.mBuffer;
3814         if(BufferFmt) break;
3815     }
3816
3817     std::unique_lock<std::mutex> buflock{device->BufferLock};
3818     const size_t NewListStart{source->mQueue.size()};
3819     ALbufferQueueItem *BufferList{nullptr};
3820     for(ALsizei i{0};i < nb;i++)
3821     {
3822         bool fmt_mismatch{false};
3823         ALbuffer *buffer{nullptr};
3824         if(buffers[i] && (buffer=LookupBuffer(device, buffers[i])) == nullptr)
3825         {
3826             context->setError(AL_INVALID_NAME, "Queueing invalid buffer ID %u", buffers[i]);
3827             goto buffer_error;
3828         }
3829         if(buffer)
3830         {
3831             if(buffer->mSampleRate < 1)
3832             {
3833                 context->setError(AL_INVALID_OPERATION, "Queueing buffer %u with no format",
3834                     buffer->id);
3835                 goto buffer_error;
3836             }
3837             if(buffer->mCallback)
3838             {
3839                 context->setError(AL_INVALID_OPERATION, "Queueing callback buffer %u", buffer->id);
3840                 goto buffer_error;
3841             }
3842             if(buffer->MappedAccess != 0 && !(buffer->MappedAccess&AL_MAP_PERSISTENT_BIT_SOFT))
3843             {
3844                 context->setError(AL_INVALID_OPERATION,
3845                     "Queueing non-persistently mapped buffer %u", buffer->id);
3846                 goto buffer_error;
3847             }
3848         }
3849
3850         source->mQueue.emplace_back();
3851         if(!BufferList)
3852             BufferList = &source->mQueue.back();
3853         else
3854         {
3855             auto &item = source->mQueue.back();
3856             BufferList->mNext.store(&item, std::memory_order_relaxed);
3857             BufferList = &item;
3858         }
3859         if(!buffer) continue;
3860         BufferList->mBlockAlign = buffer->mBlockAlign;
3861         BufferList->mSampleLen = buffer->mSampleLen;
3862         BufferList->mLoopEnd = buffer->mSampleLen;
3863         BufferList->mSamples = buffer->mData.data();
3864         BufferList->mBuffer = buffer;
3865         IncrementRef(buffer->ref);
3866
3867         if(BufferFmt == nullptr)
3868             BufferFmt = buffer;
3869         else
3870         {
3871             fmt_mismatch |= BufferFmt->mSampleRate != buffer->mSampleRate;
3872             fmt_mismatch |= BufferFmt->mChannels != buffer->mChannels;
3873             fmt_mismatch |= BufferFmt->mType != buffer->mType;
3874             if(BufferFmt->isBFormat())
3875             {
3876                 fmt_mismatch |= BufferFmt->mAmbiLayout != buffer->mAmbiLayout;
3877                 fmt_mismatch |= BufferFmt->mAmbiScaling != buffer->mAmbiScaling;
3878             }
3879             fmt_mismatch |= BufferFmt->mAmbiOrder != buffer->mAmbiOrder;
3880         }
3881         if(fmt_mismatch) UNLIKELY
3882         {
3883             context->setError(AL_INVALID_OPERATION, "Queueing buffer with mismatched format\n"
3884                 "  Expected: %uhz, %s, %s ; Got: %uhz, %s, %s\n", BufferFmt->mSampleRate,
3885                 NameFromFormat(BufferFmt->mType), NameFromFormat(BufferFmt->mChannels),
3886                 buffer->mSampleRate, NameFromFormat(buffer->mType),
3887                 NameFromFormat(buffer->mChannels));
3888
3889         buffer_error:
3890             /* A buffer failed (invalid ID or format), so unlock and release
3891              * each buffer we had.
3892              */
3893             auto iter = source->mQueue.begin() + ptrdiff_t(NewListStart);
3894             for(;iter != source->mQueue.end();++iter)
3895             {
3896                 if(ALbuffer *buf{iter->mBuffer})
3897                     DecrementRef(buf->ref);
3898             }
3899             source->mQueue.resize(NewListStart);
3900             return;
3901         }
3902     }
3903     /* All buffers good. */
3904     buflock.unlock();
3905
3906     /* Source is now streaming */
3907     source->SourceType = AL_STREAMING;
3908
3909     if(NewListStart != 0)
3910     {
3911         auto iter = source->mQueue.begin() + ptrdiff_t(NewListStart);
3912         (iter-1)->mNext.store(al::to_address(iter), std::memory_order_release);
3913     }
3914 }
3915 END_API_FUNC
3916
3917 AL_API void AL_APIENTRY alSourceUnqueueBuffers(ALuint src, ALsizei nb, ALuint *buffers)
3918 START_API_FUNC
3919 {
3920     ContextRef context{GetContextRef()};
3921     if(!context) UNLIKELY return;
3922
3923     if(nb < 0) UNLIKELY
3924         context->setError(AL_INVALID_VALUE, "Unqueueing %d buffers", nb);
3925     if(nb <= 0) UNLIKELY return;
3926
3927     std::lock_guard<std::mutex> _{context->mSourceLock};
3928     ALsource *source{LookupSource(context.get(),src)};
3929     if(!source) UNLIKELY
3930         return context->setError(AL_INVALID_NAME, "Invalid source ID %u", src);
3931
3932     if(source->SourceType != AL_STREAMING) UNLIKELY
3933         return context->setError(AL_INVALID_VALUE, "Unqueueing from a non-streaming source %u",
3934             src);
3935     if(source->Looping) UNLIKELY
3936         return context->setError(AL_INVALID_VALUE, "Unqueueing from looping source %u", src);
3937
3938     /* Make sure enough buffers have been processed to unqueue. */
3939     uint processed{0u};
3940     if(source->state != AL_INITIAL) LIKELY
3941     {
3942         VoiceBufferItem *Current{nullptr};
3943         if(Voice *voice{GetSourceVoice(source, context.get())})
3944             Current = voice->mCurrentBuffer.load(std::memory_order_relaxed);
3945         for(auto &item : source->mQueue)
3946         {
3947             if(&item == Current)
3948                 break;
3949             ++processed;
3950         }
3951     }
3952     if(processed < static_cast<ALuint>(nb)) UNLIKELY
3953         return context->setError(AL_INVALID_VALUE, "Unqueueing %d buffer%s (only %u processed)",
3954             nb, (nb==1)?"":"s", processed);
3955
3956     do {
3957         auto &head = source->mQueue.front();
3958         if(ALbuffer *buffer{head.mBuffer})
3959         {
3960             *(buffers++) = buffer->id;
3961             DecrementRef(buffer->ref);
3962         }
3963         else
3964             *(buffers++) = 0;
3965         source->mQueue.pop_front();
3966     } while(--nb);
3967 }
3968 END_API_FUNC
3969
3970
3971 AL_API void AL_APIENTRY alSourceQueueBufferLayersSOFT(ALuint, ALsizei, const ALuint*)
3972 START_API_FUNC
3973 {
3974     ContextRef context{GetContextRef()};
3975     if(!context) UNLIKELY return;
3976
3977     context->setError(AL_INVALID_OPERATION, "alSourceQueueBufferLayersSOFT not supported");
3978 }
3979 END_API_FUNC
3980
3981
3982 ALsource::ALsource()
3983 {
3984     Direct.Gain = 1.0f;
3985     Direct.GainHF = 1.0f;
3986     Direct.HFReference = LOWPASSFREQREF;
3987     Direct.GainLF = 1.0f;
3988     Direct.LFReference = HIGHPASSFREQREF;
3989     for(auto &send : Send)
3990     {
3991         send.Slot = nullptr;
3992         send.Gain = 1.0f;
3993         send.GainHF = 1.0f;
3994         send.HFReference = LOWPASSFREQREF;
3995         send.GainLF = 1.0f;
3996         send.LFReference = HIGHPASSFREQREF;
3997     }
3998 }
3999
4000 ALsource::~ALsource()
4001 {
4002     for(auto &item : mQueue)
4003     {
4004         if(ALbuffer *buffer{item.mBuffer})
4005             DecrementRef(buffer->ref);
4006     }
4007
4008     auto clear_send = [](ALsource::SendData &send) -> void
4009     { if(send.Slot) DecrementRef(send.Slot->ref); };
4010     std::for_each(Send.begin(), Send.end(), clear_send);
4011 }
4012
4013 void UpdateAllSourceProps(ALCcontext *context)
4014 {
4015     std::lock_guard<std::mutex> _{context->mSourceLock};
4016     auto voicelist = context->getVoicesSpan();
4017     ALuint vidx{0u};
4018     for(Voice *voice : voicelist)
4019     {
4020         ALuint sid{voice->mSourceID.load(std::memory_order_acquire)};
4021         ALsource *source = sid ? LookupSource(context, sid) : nullptr;
4022         if(source && source->VoiceIdx == vidx)
4023         {
4024             if(std::exchange(source->mPropsDirty, false))
4025                 UpdateSourceProps(source, voice, context);
4026         }
4027         ++vidx;
4028     }
4029 }
4030
4031 SourceSubList::~SourceSubList()
4032 {
4033     uint64_t usemask{~FreeMask};
4034     while(usemask)
4035     {
4036         const int idx{al::countr_zero(usemask)};
4037         usemask &= ~(1_u64 << idx);
4038         al::destroy_at(Sources+idx);
4039     }
4040     FreeMask = ~usemask;
4041     al_free(Sources);
4042     Sources = nullptr;
4043 }
4044
4045
4046 #ifdef ALSOFT_EAX
4047 constexpr const ALsource::EaxFxSlotIds ALsource::eax4_fx_slot_ids;
4048 constexpr const ALsource::EaxFxSlotIds ALsource::eax5_fx_slot_ids;
4049
4050 void ALsource::eaxInitialize(ALCcontext *context) noexcept
4051 {
4052     assert(context != nullptr);
4053     mEaxAlContext = context;
4054
4055     mEaxPrimaryFxSlotId = context->eaxGetPrimaryFxSlotIndex();
4056     eax_set_defaults();
4057
4058     eax1_translate(mEax1.i, mEax);
4059     mEaxVersion = 1;
4060     mEaxChanged = true;
4061 }
4062
4063 void ALsource::eaxDispatch(const EaxCall& call)
4064 {
4065     call.is_get() ? eax_get(call) : eax_set(call);
4066 }
4067
4068 ALsource* ALsource::EaxLookupSource(ALCcontext& al_context, ALuint source_id) noexcept
4069 {
4070     return LookupSource(&al_context, source_id);
4071 }
4072
4073 [[noreturn]] void ALsource::eax_fail(const char* message)
4074 {
4075     throw Exception{message};
4076 }
4077
4078 [[noreturn]] void ALsource::eax_fail_unknown_property_id()
4079 {
4080     eax_fail("Unknown property id.");
4081 }
4082
4083 [[noreturn]] void ALsource::eax_fail_unknown_version()
4084 {
4085     eax_fail("Unknown version.");
4086 }
4087
4088 [[noreturn]] void ALsource::eax_fail_unknown_active_fx_slot_id()
4089 {
4090     eax_fail("Unknown active FX slot ID.");
4091 }
4092
4093 [[noreturn]] void ALsource::eax_fail_unknown_receiving_fx_slot_id()
4094 {
4095     eax_fail("Unknown receiving FX slot ID.");
4096 }
4097
4098 void ALsource::eax_set_sends_defaults(EaxSends& sends, const EaxFxSlotIds& ids) noexcept
4099 {
4100     for (auto i = size_t{}; i < EAX_MAX_FXSLOTS; ++i) {
4101         auto& send = sends[i];
4102         send.guidReceivingFXSlotID = *(ids[i]);
4103         send.lSend = EAXSOURCE_DEFAULTSEND;
4104         send.lSendHF = EAXSOURCE_DEFAULTSENDHF;
4105         send.lOcclusion = EAXSOURCE_DEFAULTOCCLUSION;
4106         send.flOcclusionLFRatio = EAXSOURCE_DEFAULTOCCLUSIONLFRATIO;
4107         send.flOcclusionRoomRatio = EAXSOURCE_DEFAULTOCCLUSIONROOMRATIO;
4108         send.flOcclusionDirectRatio = EAXSOURCE_DEFAULTOCCLUSIONDIRECTRATIO;
4109         send.lExclusion = EAXSOURCE_DEFAULTEXCLUSION;
4110         send.flExclusionLFRatio = EAXSOURCE_DEFAULTEXCLUSIONLFRATIO;
4111     }
4112 }
4113
4114 void ALsource::eax1_set_defaults(Eax1Props& props) noexcept
4115 {
4116     props.fMix = EAX_REVERBMIX_USEDISTANCE;
4117 }
4118
4119 void ALsource::eax1_set_defaults() noexcept
4120 {
4121     eax1_set_defaults(mEax1.i);
4122     mEax1.d = mEax1.i;
4123 }
4124
4125 void ALsource::eax2_set_defaults(Eax2Props& props) noexcept
4126 {
4127     props.lDirect = EAXSOURCE_DEFAULTDIRECT;
4128     props.lDirectHF = EAXSOURCE_DEFAULTDIRECTHF;
4129     props.lRoom = EAXSOURCE_DEFAULTROOM;
4130     props.lRoomHF = EAXSOURCE_DEFAULTROOMHF;
4131     props.flRoomRolloffFactor = EAXSOURCE_DEFAULTROOMROLLOFFFACTOR;
4132     props.lObstruction = EAXSOURCE_DEFAULTOBSTRUCTION;
4133     props.flObstructionLFRatio = EAXSOURCE_DEFAULTOBSTRUCTIONLFRATIO;
4134     props.lOcclusion = EAXSOURCE_DEFAULTOCCLUSION;
4135     props.flOcclusionLFRatio = EAXSOURCE_DEFAULTOCCLUSIONLFRATIO;
4136     props.flOcclusionRoomRatio = EAXSOURCE_DEFAULTOCCLUSIONROOMRATIO;
4137     props.lOutsideVolumeHF = EAXSOURCE_DEFAULTOUTSIDEVOLUMEHF;
4138     props.flAirAbsorptionFactor = EAXSOURCE_DEFAULTAIRABSORPTIONFACTOR;
4139     props.dwFlags = EAXSOURCE_DEFAULTFLAGS;
4140 }
4141
4142 void ALsource::eax2_set_defaults() noexcept
4143 {
4144     eax2_set_defaults(mEax2.i);
4145     mEax2.d = mEax2.i;
4146 }
4147
4148 void ALsource::eax3_set_defaults(Eax3Props& props) noexcept
4149 {
4150     props.lDirect = EAXSOURCE_DEFAULTDIRECT;
4151     props.lDirectHF = EAXSOURCE_DEFAULTDIRECTHF;
4152     props.lRoom = EAXSOURCE_DEFAULTROOM;
4153     props.lRoomHF = EAXSOURCE_DEFAULTROOMHF;
4154     props.lObstruction = EAXSOURCE_DEFAULTOBSTRUCTION;
4155     props.flObstructionLFRatio = EAXSOURCE_DEFAULTOBSTRUCTIONLFRATIO;
4156     props.lOcclusion = EAXSOURCE_DEFAULTOCCLUSION;
4157     props.flOcclusionLFRatio = EAXSOURCE_DEFAULTOCCLUSIONLFRATIO;
4158     props.flOcclusionRoomRatio = EAXSOURCE_DEFAULTOCCLUSIONROOMRATIO;
4159     props.flOcclusionDirectRatio = EAXSOURCE_DEFAULTOCCLUSIONDIRECTRATIO;
4160     props.lExclusion = EAXSOURCE_DEFAULTEXCLUSION;
4161     props.flExclusionLFRatio = EAXSOURCE_DEFAULTEXCLUSIONLFRATIO;
4162     props.lOutsideVolumeHF = EAXSOURCE_DEFAULTOUTSIDEVOLUMEHF;
4163     props.flDopplerFactor = EAXSOURCE_DEFAULTDOPPLERFACTOR;
4164     props.flRolloffFactor = EAXSOURCE_DEFAULTROLLOFFFACTOR;
4165     props.flRoomRolloffFactor = EAXSOURCE_DEFAULTROOMROLLOFFFACTOR;
4166     props.flAirAbsorptionFactor = EAXSOURCE_DEFAULTAIRABSORPTIONFACTOR;
4167     props.ulFlags = EAXSOURCE_DEFAULTFLAGS;
4168 }
4169
4170 void ALsource::eax3_set_defaults() noexcept
4171 {
4172     eax3_set_defaults(mEax3.i);
4173     mEax3.d = mEax3.i;
4174 }
4175
4176 void ALsource::eax4_set_sends_defaults(EaxSends& sends) noexcept
4177 {
4178     eax_set_sends_defaults(sends, eax4_fx_slot_ids);
4179 }
4180
4181 void ALsource::eax4_set_active_fx_slots_defaults(EAX40ACTIVEFXSLOTS& slots) noexcept
4182 {
4183     slots = EAX40SOURCE_DEFAULTACTIVEFXSLOTID;
4184 }
4185
4186 void ALsource::eax4_set_defaults() noexcept
4187 {
4188     eax3_set_defaults(mEax4.i.source);
4189     eax4_set_sends_defaults(mEax4.i.sends);
4190     eax4_set_active_fx_slots_defaults(mEax4.i.active_fx_slots);
4191     mEax4.d = mEax4.i;
4192 }
4193
4194 void ALsource::eax5_set_source_defaults(EAX50SOURCEPROPERTIES& props) noexcept
4195 {
4196     eax3_set_defaults(static_cast<Eax3Props&>(props));
4197     props.flMacroFXFactor = EAXSOURCE_DEFAULTMACROFXFACTOR;
4198 }
4199
4200 void ALsource::eax5_set_sends_defaults(EaxSends& sends) noexcept
4201 {
4202     eax_set_sends_defaults(sends, eax5_fx_slot_ids);
4203 }
4204
4205 void ALsource::eax5_set_active_fx_slots_defaults(EAX50ACTIVEFXSLOTS& slots) noexcept
4206 {
4207     slots = EAX50SOURCE_3DDEFAULTACTIVEFXSLOTID;
4208 }
4209
4210 void ALsource::eax5_set_speaker_levels_defaults(EaxSpeakerLevels& speaker_levels) noexcept
4211 {
4212     for (auto i = size_t{}; i < eax_max_speakers; ++i) {
4213         auto& speaker_level = speaker_levels[i];
4214         speaker_level.lSpeakerID = static_cast<long>(EAXSPEAKER_FRONT_LEFT + i);
4215         speaker_level.lLevel = EAXSOURCE_DEFAULTSPEAKERLEVEL;
4216     }
4217 }
4218
4219 void ALsource::eax5_set_defaults(Eax5Props& props) noexcept
4220 {
4221     eax5_set_source_defaults(props.source);
4222     eax5_set_sends_defaults(props.sends);
4223     eax5_set_active_fx_slots_defaults(props.active_fx_slots);
4224     eax5_set_speaker_levels_defaults(props.speaker_levels);
4225 }
4226
4227 void ALsource::eax5_set_defaults() noexcept
4228 {
4229     eax5_set_defaults(mEax5.i);
4230     mEax5.d = mEax5.i;
4231 }
4232
4233 void ALsource::eax_set_defaults() noexcept
4234 {
4235     eax1_set_defaults();
4236     eax2_set_defaults();
4237     eax3_set_defaults();
4238     eax4_set_defaults();
4239     eax5_set_defaults();
4240 }
4241
4242 void ALsource::eax1_translate(const Eax1Props& src, Eax5Props& dst) noexcept
4243 {
4244     eax5_set_defaults(dst);
4245
4246     if (src.fMix == EAX_REVERBMIX_USEDISTANCE)
4247     {
4248         dst.source.ulFlags |= EAXSOURCEFLAGS_ROOMAUTO;
4249         dst.sends[0].lSend = 0;
4250     }
4251     else
4252     {
4253         dst.source.ulFlags &= ~EAXSOURCEFLAGS_ROOMAUTO;
4254         dst.sends[0].lSend = clamp(static_cast<long>(gain_to_level_mb(src.fMix)),
4255             EAXSOURCE_MINSEND, EAXSOURCE_MAXSEND);
4256     }
4257 }
4258
4259 void ALsource::eax2_translate(const Eax2Props& src, Eax5Props& dst) noexcept
4260 {
4261     // Source.
4262     //
4263     dst.source.lDirect = src.lDirect;
4264     dst.source.lDirectHF = src.lDirectHF;
4265     dst.source.lRoom = src.lRoom;
4266     dst.source.lRoomHF = src.lRoomHF;
4267     dst.source.lObstruction = src.lObstruction;
4268     dst.source.flObstructionLFRatio = src.flObstructionLFRatio;
4269     dst.source.lOcclusion = src.lOcclusion;
4270     dst.source.flOcclusionLFRatio = src.flOcclusionLFRatio;
4271     dst.source.flOcclusionRoomRatio = src.flOcclusionRoomRatio;
4272     dst.source.flOcclusionDirectRatio = EAXSOURCE_DEFAULTOCCLUSIONDIRECTRATIO;
4273     dst.source.lExclusion = EAXSOURCE_DEFAULTEXCLUSION;
4274     dst.source.flExclusionLFRatio = EAXSOURCE_DEFAULTEXCLUSIONLFRATIO;
4275     dst.source.lOutsideVolumeHF = src.lOutsideVolumeHF;
4276     dst.source.flDopplerFactor = EAXSOURCE_DEFAULTDOPPLERFACTOR;
4277     dst.source.flRolloffFactor = EAXSOURCE_DEFAULTROLLOFFFACTOR;
4278     dst.source.flRoomRolloffFactor = src.flRoomRolloffFactor;
4279     dst.source.flAirAbsorptionFactor = src.flAirAbsorptionFactor;
4280     dst.source.ulFlags = src.dwFlags;
4281     dst.source.flMacroFXFactor = EAXSOURCE_DEFAULTMACROFXFACTOR;
4282
4283     // Set everyting else to defaults.
4284     //
4285     eax5_set_sends_defaults(dst.sends);
4286     eax5_set_active_fx_slots_defaults(dst.active_fx_slots);
4287     eax5_set_speaker_levels_defaults(dst.speaker_levels);
4288 }
4289
4290 void ALsource::eax3_translate(const Eax3Props& src, Eax5Props& dst) noexcept
4291 {
4292     // Source.
4293     //
4294     static_cast<Eax3Props&>(dst.source) = src;
4295     dst.source.flMacroFXFactor = EAXSOURCE_DEFAULTMACROFXFACTOR;
4296
4297     // Set everyting else to defaults.
4298     //
4299     eax5_set_sends_defaults(dst.sends);
4300     eax5_set_active_fx_slots_defaults(dst.active_fx_slots);
4301     eax5_set_speaker_levels_defaults(dst.speaker_levels);
4302 }
4303
4304 void ALsource::eax4_translate(const Eax4Props& src, Eax5Props& dst) noexcept
4305 {
4306     // Source.
4307     //
4308     static_cast<Eax3Props&>(dst.source) = src.source;
4309     dst.source.flMacroFXFactor = EAXSOURCE_DEFAULTMACROFXFACTOR;
4310
4311     // Sends.
4312     //
4313     dst.sends = src.sends;
4314
4315     for (auto i = size_t{}; i < EAX_MAX_FXSLOTS; ++i)
4316         dst.sends[i].guidReceivingFXSlotID = *(eax5_fx_slot_ids[i]);
4317
4318     // Active FX slots.
4319     //
4320     for (auto i = 0; i < EAX50_MAX_ACTIVE_FXSLOTS; ++i) {
4321         auto& dst_id = dst.active_fx_slots.guidActiveFXSlots[i];
4322
4323         if (i < EAX40_MAX_ACTIVE_FXSLOTS) {
4324             const auto& src_id = src.active_fx_slots.guidActiveFXSlots[i];
4325
4326             if (src_id == EAX_NULL_GUID)
4327                 dst_id = EAX_NULL_GUID;
4328             else if (src_id == EAX_PrimaryFXSlotID)
4329                 dst_id = EAX_PrimaryFXSlotID;
4330             else if (src_id == EAXPROPERTYID_EAX40_FXSlot0)
4331                 dst_id = EAXPROPERTYID_EAX50_FXSlot0;
4332             else if (src_id == EAXPROPERTYID_EAX40_FXSlot1)
4333                 dst_id = EAXPROPERTYID_EAX50_FXSlot1;
4334             else if (src_id == EAXPROPERTYID_EAX40_FXSlot2)
4335                 dst_id = EAXPROPERTYID_EAX50_FXSlot2;
4336             else if (src_id == EAXPROPERTYID_EAX40_FXSlot3)
4337                 dst_id = EAXPROPERTYID_EAX50_FXSlot3;
4338             else
4339                 assert(false && "Unknown active FX slot ID.");
4340         } else
4341             dst_id = EAX_NULL_GUID;
4342     }
4343
4344     // Speaker levels.
4345     //
4346     eax5_set_speaker_levels_defaults(dst.speaker_levels);
4347 }
4348
4349 float ALsource::eax_calculate_dst_occlusion_mb(
4350     long src_occlusion_mb,
4351     float path_ratio,
4352     float lf_ratio) noexcept
4353 {
4354     const auto ratio_1 = path_ratio + lf_ratio - 1.0F;
4355     const auto ratio_2 = path_ratio * lf_ratio;
4356     const auto ratio = (ratio_2 > ratio_1) ? ratio_2 : ratio_1;
4357     const auto dst_occlustion_mb = static_cast<float>(src_occlusion_mb) * ratio;
4358     return dst_occlustion_mb;
4359 }
4360
4361 EaxAlLowPassParam ALsource::eax_create_direct_filter_param() const noexcept
4362 {
4363     auto gain_mb =
4364         static_cast<float>(mEax.source.lDirect) +
4365         (static_cast<float>(mEax.source.lObstruction) * mEax.source.flObstructionLFRatio) +
4366         eax_calculate_dst_occlusion_mb(
4367             mEax.source.lOcclusion,
4368             mEax.source.flOcclusionDirectRatio,
4369             mEax.source.flOcclusionLFRatio);
4370
4371     const auto has_source_occlusion = (mEax.source.lOcclusion != 0);
4372
4373     auto gain_hf_mb =
4374         static_cast<float>(mEax.source.lDirectHF) +
4375         static_cast<float>(mEax.source.lObstruction);
4376
4377     for (auto i = std::size_t{}; i < EAX_MAX_FXSLOTS; ++i)
4378     {
4379         if(!mEaxActiveFxSlots[i])
4380             continue;
4381
4382         if(has_source_occlusion) {
4383             const auto& fx_slot = mEaxAlContext->eaxGetFxSlot(i);
4384             const auto& fx_slot_eax = fx_slot.eax_get_eax_fx_slot();
4385             const auto is_environmental_fx = ((fx_slot_eax.ulFlags & EAXFXSLOTFLAGS_ENVIRONMENT) != 0);
4386             const auto is_primary = (mEaxPrimaryFxSlotId.value_or(-1) == fx_slot.eax_get_index());
4387             const auto is_listener_environment = (is_environmental_fx && is_primary);
4388
4389             if(is_listener_environment) {
4390                 gain_mb += eax_calculate_dst_occlusion_mb(
4391                     mEax.source.lOcclusion,
4392                     mEax.source.flOcclusionDirectRatio,
4393                     mEax.source.flOcclusionLFRatio);
4394
4395                 gain_hf_mb += static_cast<float>(mEax.source.lOcclusion) * mEax.source.flOcclusionDirectRatio;
4396             }
4397         }
4398
4399         const auto& send = mEax.sends[i];
4400
4401         if(send.lOcclusion != 0) {
4402             gain_mb += eax_calculate_dst_occlusion_mb(
4403                 send.lOcclusion,
4404                 send.flOcclusionDirectRatio,
4405                 send.flOcclusionLFRatio);
4406
4407             gain_hf_mb += static_cast<float>(send.lOcclusion) * send.flOcclusionDirectRatio;
4408         }
4409     }
4410
4411     const auto al_low_pass_param = EaxAlLowPassParam{
4412         level_mb_to_gain(gain_mb),
4413         minf(level_mb_to_gain(gain_hf_mb), 1.0f)};
4414
4415     return al_low_pass_param;
4416 }
4417
4418 EaxAlLowPassParam ALsource::eax_create_room_filter_param(
4419     const ALeffectslot& fx_slot,
4420     const EAXSOURCEALLSENDPROPERTIES& send) const noexcept
4421 {
4422     const auto& fx_slot_eax = fx_slot.eax_get_eax_fx_slot();
4423     const auto is_environmental_fx = ((fx_slot_eax.ulFlags & EAXFXSLOTFLAGS_ENVIRONMENT) != 0);
4424     const auto is_primary = (mEaxPrimaryFxSlotId.value_or(-1) == fx_slot.eax_get_index());
4425     const auto is_listener_environment = (is_environmental_fx && is_primary);
4426
4427     const auto gain_mb =
4428         (static_cast<float>(fx_slot_eax.lOcclusion) * fx_slot_eax.flOcclusionLFRatio) +
4429         static_cast<float>((is_environmental_fx ? mEax.source.lRoom : 0) + send.lSend) +
4430         (is_listener_environment ?
4431             eax_calculate_dst_occlusion_mb(
4432                 mEax.source.lOcclusion,
4433                 mEax.source.flOcclusionRoomRatio,
4434                 mEax.source.flOcclusionLFRatio) :
4435             0.0f) +
4436         eax_calculate_dst_occlusion_mb(
4437             send.lOcclusion,
4438             send.flOcclusionRoomRatio,
4439             send.flOcclusionLFRatio) +
4440         (is_listener_environment ?
4441             (static_cast<float>(mEax.source.lExclusion) * mEax.source.flExclusionLFRatio) :
4442             0.0f) +
4443         (static_cast<float>(send.lExclusion) * send.flExclusionLFRatio);
4444
4445     const auto gain_hf_mb =
4446         static_cast<float>(fx_slot_eax.lOcclusion) +
4447         static_cast<float>((is_environmental_fx ? mEax.source.lRoomHF : 0) + send.lSendHF) +
4448         (is_listener_environment ?
4449             ((static_cast<float>(mEax.source.lOcclusion) * mEax.source.flOcclusionRoomRatio)) :
4450             0.0f) +
4451         (static_cast<float>(send.lOcclusion) * send.flOcclusionRoomRatio) +
4452         (is_listener_environment ?
4453             static_cast<float>(mEax.source.lExclusion + send.lExclusion) :
4454             0.0f);
4455
4456     const auto al_low_pass_param = EaxAlLowPassParam{
4457         level_mb_to_gain(gain_mb),
4458         minf(level_mb_to_gain(gain_hf_mb), 1.0f)};
4459
4460     return al_low_pass_param;
4461 }
4462
4463 void ALsource::eax_update_direct_filter()
4464 {
4465     const auto& direct_param = eax_create_direct_filter_param();
4466     Direct.Gain = direct_param.gain;
4467     Direct.GainHF = direct_param.gain_hf;
4468     Direct.HFReference = LOWPASSFREQREF;
4469     Direct.GainLF = 1.0f;
4470     Direct.LFReference = HIGHPASSFREQREF;
4471     mPropsDirty = true;
4472 }
4473
4474 void ALsource::eax_update_room_filters()
4475 {
4476     for (auto i = size_t{}; i < EAX_MAX_FXSLOTS; ++i) {
4477         if (!mEaxActiveFxSlots[i])
4478             continue;
4479
4480         auto& fx_slot = mEaxAlContext->eaxGetFxSlot(i);
4481         const auto& send = mEax.sends[i];
4482         const auto& room_param = eax_create_room_filter_param(fx_slot, send);
4483         eax_set_al_source_send(&fx_slot, i, room_param);
4484     }
4485 }
4486
4487 void ALsource::eax_set_efx_outer_gain_hf()
4488 {
4489     OuterGainHF = clamp(
4490         level_mb_to_gain(static_cast<float>(mEax.source.lOutsideVolumeHF)),
4491         AL_MIN_CONE_OUTER_GAINHF,
4492         AL_MAX_CONE_OUTER_GAINHF);
4493 }
4494
4495 void ALsource::eax_set_efx_doppler_factor()
4496 {
4497     DopplerFactor = mEax.source.flDopplerFactor;
4498 }
4499
4500 void ALsource::eax_set_efx_rolloff_factor()
4501 {
4502     RolloffFactor2 = mEax.source.flRolloffFactor;
4503 }
4504
4505 void ALsource::eax_set_efx_room_rolloff_factor()
4506 {
4507     RoomRolloffFactor = mEax.source.flRoomRolloffFactor;
4508 }
4509
4510 void ALsource::eax_set_efx_air_absorption_factor()
4511 {
4512     AirAbsorptionFactor = mEax.source.flAirAbsorptionFactor;
4513 }
4514
4515 void ALsource::eax_set_efx_dry_gain_hf_auto()
4516 {
4517     DryGainHFAuto = ((mEax.source.ulFlags & EAXSOURCEFLAGS_DIRECTHFAUTO) != 0);
4518 }
4519
4520 void ALsource::eax_set_efx_wet_gain_auto()
4521 {
4522     WetGainAuto = ((mEax.source.ulFlags & EAXSOURCEFLAGS_ROOMAUTO) != 0);
4523 }
4524
4525 void ALsource::eax_set_efx_wet_gain_hf_auto()
4526 {
4527     WetGainHFAuto = ((mEax.source.ulFlags & EAXSOURCEFLAGS_ROOMHFAUTO) != 0);
4528 }
4529
4530 void ALsource::eax1_set(const EaxCall& call, Eax1Props& props)
4531 {
4532     switch (call.get_property_id()) {
4533         case DSPROPERTY_EAXBUFFER_ALL:
4534             eax_defer<Eax1SourceAllValidator>(call, props);
4535             break;
4536
4537         case DSPROPERTY_EAXBUFFER_REVERBMIX:
4538             eax_defer<Eax1SourceReverbMixValidator>(call, props.fMix);
4539             break;
4540
4541         default:
4542             eax_fail_unknown_property_id();
4543     }
4544 }
4545
4546 void ALsource::eax2_set(const EaxCall& call, Eax2Props& props)
4547 {
4548     switch (call.get_property_id()) {
4549         case DSPROPERTY_EAX20BUFFER_NONE:
4550             break;
4551
4552         case DSPROPERTY_EAX20BUFFER_ALLPARAMETERS:
4553             eax_defer<Eax2SourceAllValidator>(call, props);
4554             break;
4555
4556         case DSPROPERTY_EAX20BUFFER_DIRECT:
4557             eax_defer<Eax2SourceDirectValidator>(call, props.lDirect);
4558             break;
4559
4560         case DSPROPERTY_EAX20BUFFER_DIRECTHF:
4561             eax_defer<Eax2SourceDirectHfValidator>(call, props.lDirectHF);
4562             break;
4563
4564         case DSPROPERTY_EAX20BUFFER_ROOM:
4565             eax_defer<Eax2SourceRoomValidator>(call, props.lRoom);
4566             break;
4567
4568         case DSPROPERTY_EAX20BUFFER_ROOMHF:
4569             eax_defer<Eax2SourceRoomHfValidator>(call, props.lRoomHF);
4570             break;
4571
4572         case DSPROPERTY_EAX20BUFFER_ROOMROLLOFFFACTOR:
4573             eax_defer<Eax2SourceRoomRolloffFactorValidator>(call, props.flRoomRolloffFactor);
4574             break;
4575
4576         case DSPROPERTY_EAX20BUFFER_OBSTRUCTION:
4577             eax_defer<Eax2SourceObstructionValidator>(call, props.lObstruction);
4578             break;
4579
4580         case DSPROPERTY_EAX20BUFFER_OBSTRUCTIONLFRATIO:
4581             eax_defer<Eax2SourceObstructionLfRatioValidator>(call, props.flObstructionLFRatio);
4582             break;
4583
4584         case DSPROPERTY_EAX20BUFFER_OCCLUSION:
4585             eax_defer<Eax2SourceOcclusionValidator>(call, props.lOcclusion);
4586             break;
4587
4588         case DSPROPERTY_EAX20BUFFER_OCCLUSIONLFRATIO:
4589             eax_defer<Eax2SourceOcclusionLfRatioValidator>(call, props.flOcclusionLFRatio);
4590             break;
4591
4592         case DSPROPERTY_EAX20BUFFER_OCCLUSIONROOMRATIO:
4593             eax_defer<Eax2SourceOcclusionRoomRatioValidator>(call, props.flOcclusionRoomRatio);
4594             break;
4595
4596         case DSPROPERTY_EAX20BUFFER_OUTSIDEVOLUMEHF:
4597             eax_defer<Eax2SourceOutsideVolumeHfValidator>(call, props.lOutsideVolumeHF);
4598             break;
4599
4600         case DSPROPERTY_EAX20BUFFER_AIRABSORPTIONFACTOR:
4601             eax_defer<Eax2SourceAirAbsorptionFactorValidator>(call, props.flAirAbsorptionFactor);
4602             break;
4603
4604         case DSPROPERTY_EAX20BUFFER_FLAGS:
4605             eax_defer<Eax2SourceFlagsValidator>(call, props.dwFlags);
4606             break;
4607
4608         default:
4609             eax_fail_unknown_property_id();
4610     }
4611 }
4612
4613 void ALsource::eax3_set(const EaxCall& call, Eax3Props& props)
4614 {
4615     switch (call.get_property_id()) {
4616         case EAXSOURCE_NONE:
4617             break;
4618
4619         case EAXSOURCE_ALLPARAMETERS:
4620             eax_defer<Eax3SourceAllValidator>(call, props);
4621             break;
4622
4623         case EAXSOURCE_OBSTRUCTIONPARAMETERS:
4624             eax_defer_sub<Eax4ObstructionValidator, EAXOBSTRUCTIONPROPERTIES>(call, props.lObstruction);
4625             break;
4626
4627         case EAXSOURCE_OCCLUSIONPARAMETERS:
4628             eax_defer_sub<Eax4OcclusionValidator, EAXOCCLUSIONPROPERTIES>(call, props.lOcclusion);
4629             break;
4630
4631         case EAXSOURCE_EXCLUSIONPARAMETERS:
4632             eax_defer_sub<Eax4ExclusionValidator, EAXEXCLUSIONPROPERTIES>(call, props.lExclusion);
4633             break;
4634
4635         case EAXSOURCE_DIRECT:
4636             eax_defer<Eax2SourceDirectValidator>(call, props.lDirect);
4637             break;
4638
4639         case EAXSOURCE_DIRECTHF:
4640             eax_defer<Eax2SourceDirectHfValidator>(call, props.lDirectHF);
4641             break;
4642
4643         case EAXSOURCE_ROOM:
4644             eax_defer<Eax2SourceRoomValidator>(call, props.lRoom);
4645             break;
4646
4647         case EAXSOURCE_ROOMHF:
4648             eax_defer<Eax2SourceRoomHfValidator>(call, props.lRoomHF);
4649             break;
4650
4651         case EAXSOURCE_OBSTRUCTION:
4652             eax_defer<Eax2SourceObstructionValidator>(call, props.lObstruction);
4653             break;
4654
4655         case EAXSOURCE_OBSTRUCTIONLFRATIO:
4656             eax_defer<Eax2SourceObstructionLfRatioValidator>(call, props.flObstructionLFRatio);
4657             break;
4658
4659         case EAXSOURCE_OCCLUSION:
4660             eax_defer<Eax2SourceOcclusionValidator>(call, props.lOcclusion);
4661             break;
4662
4663         case EAXSOURCE_OCCLUSIONLFRATIO:
4664             eax_defer<Eax2SourceOcclusionLfRatioValidator>(call, props.flOcclusionLFRatio);
4665             break;
4666
4667         case EAXSOURCE_OCCLUSIONROOMRATIO:
4668             eax_defer<Eax2SourceOcclusionRoomRatioValidator>(call, props.flOcclusionRoomRatio);
4669             break;
4670
4671         case EAXSOURCE_OCCLUSIONDIRECTRATIO:
4672             eax_defer<Eax3SourceOcclusionDirectRatioValidator>(call, props.flOcclusionDirectRatio);
4673             break;
4674
4675         case EAXSOURCE_EXCLUSION:
4676             eax_defer<Eax3SourceExclusionValidator>(call, props.lExclusion);
4677             break;
4678
4679         case EAXSOURCE_EXCLUSIONLFRATIO:
4680             eax_defer<Eax3SourceExclusionLfRatioValidator>(call, props.flExclusionLFRatio);
4681             break;
4682
4683         case EAXSOURCE_OUTSIDEVOLUMEHF:
4684             eax_defer<Eax2SourceOutsideVolumeHfValidator>(call, props.lOutsideVolumeHF);
4685             break;
4686
4687         case EAXSOURCE_DOPPLERFACTOR:
4688             eax_defer<Eax3SourceDopplerFactorValidator>(call, props.flDopplerFactor);
4689             break;
4690
4691         case EAXSOURCE_ROLLOFFFACTOR:
4692             eax_defer<Eax3SourceRolloffFactorValidator>(call, props.flRolloffFactor);
4693             break;
4694
4695         case EAXSOURCE_ROOMROLLOFFFACTOR:
4696             eax_defer<Eax2SourceRoomRolloffFactorValidator>(call, props.flRoomRolloffFactor);
4697             break;
4698
4699         case EAXSOURCE_AIRABSORPTIONFACTOR:
4700             eax_defer<Eax2SourceAirAbsorptionFactorValidator>(call, props.flAirAbsorptionFactor);
4701             break;
4702
4703         case EAXSOURCE_FLAGS:
4704             eax_defer<Eax2SourceFlagsValidator>(call, props.ulFlags);
4705             break;
4706
4707         default:
4708             eax_fail_unknown_property_id();
4709     }
4710 }
4711
4712 void ALsource::eax4_set(const EaxCall& call, Eax4Props& props)
4713 {
4714     switch (call.get_property_id()) {
4715         case EAXSOURCE_NONE:
4716         case EAXSOURCE_ALLPARAMETERS:
4717         case EAXSOURCE_OBSTRUCTIONPARAMETERS:
4718         case EAXSOURCE_OCCLUSIONPARAMETERS:
4719         case EAXSOURCE_EXCLUSIONPARAMETERS:
4720         case EAXSOURCE_DIRECT:
4721         case EAXSOURCE_DIRECTHF:
4722         case EAXSOURCE_ROOM:
4723         case EAXSOURCE_ROOMHF:
4724         case EAXSOURCE_OBSTRUCTION:
4725         case EAXSOURCE_OBSTRUCTIONLFRATIO:
4726         case EAXSOURCE_OCCLUSION:
4727         case EAXSOURCE_OCCLUSIONLFRATIO:
4728         case EAXSOURCE_OCCLUSIONROOMRATIO:
4729         case EAXSOURCE_OCCLUSIONDIRECTRATIO:
4730         case EAXSOURCE_EXCLUSION:
4731         case EAXSOURCE_EXCLUSIONLFRATIO:
4732         case EAXSOURCE_OUTSIDEVOLUMEHF:
4733         case EAXSOURCE_DOPPLERFACTOR:
4734         case EAXSOURCE_ROLLOFFFACTOR:
4735         case EAXSOURCE_ROOMROLLOFFFACTOR:
4736         case EAXSOURCE_AIRABSORPTIONFACTOR:
4737         case EAXSOURCE_FLAGS:
4738             eax3_set(call, props.source);
4739             break;
4740
4741         case EAXSOURCE_SENDPARAMETERS:
4742             eax4_defer_sends<Eax4SendValidator, EAXSOURCESENDPROPERTIES>(call, props.sends);
4743             break;
4744
4745         case EAXSOURCE_ALLSENDPARAMETERS:
4746             eax4_defer_sends<Eax4AllSendValidator, EAXSOURCEALLSENDPROPERTIES>(call, props.sends);
4747             break;
4748
4749         case EAXSOURCE_OCCLUSIONSENDPARAMETERS:
4750             eax4_defer_sends<Eax4OcclusionSendValidator, EAXSOURCEOCCLUSIONSENDPROPERTIES>(call, props.sends);
4751             break;
4752
4753         case EAXSOURCE_EXCLUSIONSENDPARAMETERS:
4754             eax4_defer_sends<Eax4ExclusionSendValidator, EAXSOURCEEXCLUSIONSENDPROPERTIES>(call, props.sends);
4755             break;
4756
4757         case EAXSOURCE_ACTIVEFXSLOTID:
4758             eax4_defer_active_fx_slot_id(call, props.active_fx_slots.guidActiveFXSlots);
4759             break;
4760
4761         default:
4762             eax_fail_unknown_property_id();
4763     }
4764 }
4765
4766 void ALsource::eax5_defer_all_2d(const EaxCall& call, EAX50SOURCEPROPERTIES& props)
4767 {
4768     const auto& src_props = call.get_value<Exception, const EAXSOURCE2DPROPERTIES>();
4769     Eax5SourceAll2dValidator{}(src_props);
4770     props.lDirect = src_props.lDirect;
4771     props.lDirectHF = src_props.lDirectHF;
4772     props.lRoom = src_props.lRoom;
4773     props.lRoomHF = src_props.lRoomHF;
4774     props.ulFlags = src_props.ulFlags;
4775 }
4776
4777 void ALsource::eax5_defer_speaker_levels(const EaxCall& call, EaxSpeakerLevels& props)
4778 {
4779     const auto values = call.get_values<const EAXSPEAKERLEVELPROPERTIES>(eax_max_speakers);
4780     std::for_each(values.cbegin(), values.cend(), Eax5SpeakerAllValidator{});
4781
4782     for (const auto& value : values) {
4783         const auto index = static_cast<size_t>(value.lSpeakerID - EAXSPEAKER_FRONT_LEFT);
4784         props[index].lLevel = value.lLevel;
4785     }
4786 }
4787
4788 void ALsource::eax5_set(const EaxCall& call, Eax5Props& props)
4789 {
4790     switch (call.get_property_id()) {
4791         case EAXSOURCE_NONE:
4792             break;
4793
4794         case EAXSOURCE_ALLPARAMETERS:
4795             eax_defer<Eax5SourceAllValidator>(call, props.source);
4796             break;
4797
4798         case EAXSOURCE_OBSTRUCTIONPARAMETERS:
4799         case EAXSOURCE_OCCLUSIONPARAMETERS:
4800         case EAXSOURCE_EXCLUSIONPARAMETERS:
4801         case EAXSOURCE_DIRECT:
4802         case EAXSOURCE_DIRECTHF:
4803         case EAXSOURCE_ROOM:
4804         case EAXSOURCE_ROOMHF:
4805         case EAXSOURCE_OBSTRUCTION:
4806         case EAXSOURCE_OBSTRUCTIONLFRATIO:
4807         case EAXSOURCE_OCCLUSION:
4808         case EAXSOURCE_OCCLUSIONLFRATIO:
4809         case EAXSOURCE_OCCLUSIONROOMRATIO:
4810         case EAXSOURCE_OCCLUSIONDIRECTRATIO:
4811         case EAXSOURCE_EXCLUSION:
4812         case EAXSOURCE_EXCLUSIONLFRATIO:
4813         case EAXSOURCE_OUTSIDEVOLUMEHF:
4814         case EAXSOURCE_DOPPLERFACTOR:
4815         case EAXSOURCE_ROLLOFFFACTOR:
4816         case EAXSOURCE_ROOMROLLOFFFACTOR:
4817         case EAXSOURCE_AIRABSORPTIONFACTOR:
4818         case EAXSOURCE_FLAGS:
4819             eax3_set(call, props.source);
4820             break;
4821
4822         case EAXSOURCE_SENDPARAMETERS:
4823             eax5_defer_sends<Eax5SendValidator, EAXSOURCESENDPROPERTIES>(call, props.sends);
4824             break;
4825
4826         case EAXSOURCE_ALLSENDPARAMETERS:
4827             eax5_defer_sends<Eax5AllSendValidator, EAXSOURCEALLSENDPROPERTIES>(call, props.sends);
4828             break;
4829
4830         case EAXSOURCE_OCCLUSIONSENDPARAMETERS:
4831             eax5_defer_sends<Eax5OcclusionSendValidator, EAXSOURCEOCCLUSIONSENDPROPERTIES>(call, props.sends);
4832             break;
4833
4834         case EAXSOURCE_EXCLUSIONSENDPARAMETERS:
4835             eax5_defer_sends<Eax5ExclusionSendValidator, EAXSOURCEEXCLUSIONSENDPROPERTIES>(call, props.sends);
4836             break;
4837
4838         case EAXSOURCE_ACTIVEFXSLOTID:
4839             eax5_defer_active_fx_slot_id(call, props.active_fx_slots.guidActiveFXSlots);
4840             break;
4841
4842         case EAXSOURCE_MACROFXFACTOR:
4843             eax_defer<Eax5SourceMacroFXFactorValidator>(call, props.source.flMacroFXFactor);
4844             break;
4845
4846         case EAXSOURCE_SPEAKERLEVELS:
4847             eax5_defer_speaker_levels(call, props.speaker_levels);
4848             break;
4849
4850         case EAXSOURCE_ALL2DPARAMETERS:
4851             eax5_defer_all_2d(call, props.source);
4852             break;
4853
4854         default:
4855             eax_fail_unknown_property_id();
4856     }
4857 }
4858
4859 void ALsource::eax_set(const EaxCall& call)
4860 {
4861     const auto eax_version = call.get_version();
4862     switch(eax_version)
4863     {
4864     case 1: eax1_set(call, mEax1.d); break;
4865     case 2: eax2_set(call, mEax2.d); break;
4866     case 3: eax3_set(call, mEax3.d); break;
4867     case 4: eax4_set(call, mEax4.d); break;
4868     case 5: eax5_set(call, mEax5.d); break;
4869     default: eax_fail_unknown_property_id();
4870     }
4871     mEaxChanged = true;
4872     mEaxVersion = eax_version;
4873 }
4874
4875 void ALsource::eax_get_active_fx_slot_id(const EaxCall& call, const GUID* ids, size_t max_count)
4876 {
4877     assert(ids != nullptr);
4878     assert(max_count == EAX40_MAX_ACTIVE_FXSLOTS || max_count == EAX50_MAX_ACTIVE_FXSLOTS);
4879     const auto dst_ids = call.get_values<GUID>(max_count);
4880     const auto count = dst_ids.size();
4881     std::uninitialized_copy_n(ids, count, dst_ids.begin());
4882 }
4883
4884 void ALsource::eax1_get(const EaxCall& call, const Eax1Props& props)
4885 {
4886     switch (call.get_property_id()) {
4887         case DSPROPERTY_EAXBUFFER_ALL:
4888         case DSPROPERTY_EAXBUFFER_REVERBMIX:
4889             call.set_value<Exception>(props.fMix);
4890             break;
4891
4892         default:
4893             eax_fail_unknown_property_id();
4894     }
4895 }
4896
4897 void ALsource::eax2_get(const EaxCall& call, const Eax2Props& props)
4898 {
4899     switch (call.get_property_id()) {
4900         case DSPROPERTY_EAX20BUFFER_NONE:
4901             break;
4902
4903         case DSPROPERTY_EAX20BUFFER_ALLPARAMETERS:
4904             call.set_value<Exception>(props);
4905             break;
4906
4907         case DSPROPERTY_EAX20BUFFER_DIRECT:
4908             call.set_value<Exception>(props.lDirect);
4909             break;
4910
4911         case DSPROPERTY_EAX20BUFFER_DIRECTHF:
4912             call.set_value<Exception>(props.lDirectHF);
4913             break;
4914
4915         case DSPROPERTY_EAX20BUFFER_ROOM:
4916             call.set_value<Exception>(props.lRoom);
4917             break;
4918
4919         case DSPROPERTY_EAX20BUFFER_ROOMHF:
4920             call.set_value<Exception>(props.lRoomHF);
4921             break;
4922
4923         case DSPROPERTY_EAX20BUFFER_ROOMROLLOFFFACTOR:
4924             call.set_value<Exception>(props.flRoomRolloffFactor);
4925             break;
4926
4927         case DSPROPERTY_EAX20BUFFER_OBSTRUCTION:
4928             call.set_value<Exception>(props.lObstruction);
4929             break;
4930
4931         case DSPROPERTY_EAX20BUFFER_OBSTRUCTIONLFRATIO:
4932             call.set_value<Exception>(props.flObstructionLFRatio);
4933             break;
4934
4935         case DSPROPERTY_EAX20BUFFER_OCCLUSION:
4936             call.set_value<Exception>(props.lOcclusion);
4937             break;
4938
4939         case DSPROPERTY_EAX20BUFFER_OCCLUSIONLFRATIO:
4940             call.set_value<Exception>(props.flOcclusionLFRatio);
4941             break;
4942
4943         case DSPROPERTY_EAX20BUFFER_OCCLUSIONROOMRATIO:
4944             call.set_value<Exception>(props.flOcclusionRoomRatio);
4945             break;
4946
4947         case DSPROPERTY_EAX20BUFFER_OUTSIDEVOLUMEHF:
4948             call.set_value<Exception>(props.lOutsideVolumeHF);
4949             break;
4950
4951         case DSPROPERTY_EAX20BUFFER_AIRABSORPTIONFACTOR:
4952             call.set_value<Exception>(props.flAirAbsorptionFactor);
4953             break;
4954
4955         case DSPROPERTY_EAX20BUFFER_FLAGS:
4956             call.set_value<Exception>(props.dwFlags);
4957             break;
4958
4959         default:
4960             eax_fail_unknown_property_id();
4961     }
4962 }
4963
4964 void ALsource::eax3_get_obstruction(const EaxCall& call, const Eax3Props& props)
4965 {
4966     const auto& subprops = reinterpret_cast<const EAXOBSTRUCTIONPROPERTIES&>(props.lObstruction);
4967     call.set_value<Exception>(subprops);
4968 }
4969
4970 void ALsource::eax3_get_occlusion(const EaxCall& call, const Eax3Props& props)
4971 {
4972     const auto& subprops = reinterpret_cast<const EAXOCCLUSIONPROPERTIES&>(props.lOcclusion);
4973     call.set_value<Exception>(subprops);
4974 }
4975
4976 void ALsource::eax3_get_exclusion(const EaxCall& call, const Eax3Props& props)
4977 {
4978     const auto& subprops = reinterpret_cast<const EAXEXCLUSIONPROPERTIES&>(props.lExclusion);
4979     call.set_value<Exception>(subprops);
4980 }
4981
4982 void ALsource::eax3_get(const EaxCall& call, const Eax3Props& props)
4983 {
4984     switch (call.get_property_id()) {
4985         case EAXSOURCE_NONE:
4986             break;
4987
4988         case EAXSOURCE_ALLPARAMETERS:
4989             call.set_value<Exception>(props);
4990             break;
4991
4992         case EAXSOURCE_OBSTRUCTIONPARAMETERS:
4993             eax3_get_obstruction(call, props);
4994             break;
4995
4996         case EAXSOURCE_OCCLUSIONPARAMETERS:
4997             eax3_get_occlusion(call, props);
4998             break;
4999
5000         case EAXSOURCE_EXCLUSIONPARAMETERS:
5001             eax3_get_exclusion(call, props);
5002             break;
5003
5004         case EAXSOURCE_DIRECT:
5005             call.set_value<Exception>(props.lDirect);
5006             break;
5007
5008         case EAXSOURCE_DIRECTHF:
5009             call.set_value<Exception>(props.lDirectHF);
5010             break;
5011
5012         case EAXSOURCE_ROOM:
5013             call.set_value<Exception>(props.lRoom);
5014             break;
5015
5016         case EAXSOURCE_ROOMHF:
5017             call.set_value<Exception>(props.lRoomHF);
5018             break;
5019
5020         case EAXSOURCE_OBSTRUCTION:
5021             call.set_value<Exception>(props.lObstruction);
5022             break;
5023
5024         case EAXSOURCE_OBSTRUCTIONLFRATIO:
5025             call.set_value<Exception>(props.flObstructionLFRatio);
5026             break;
5027
5028         case EAXSOURCE_OCCLUSION:
5029             call.set_value<Exception>(props.lOcclusion);
5030             break;
5031
5032         case EAXSOURCE_OCCLUSIONLFRATIO:
5033             call.set_value<Exception>(props.flOcclusionLFRatio);
5034             break;
5035
5036         case EAXSOURCE_OCCLUSIONROOMRATIO:
5037             call.set_value<Exception>(props.flOcclusionRoomRatio);
5038             break;
5039
5040         case EAXSOURCE_OCCLUSIONDIRECTRATIO:
5041             call.set_value<Exception>(props.flOcclusionDirectRatio);
5042             break;
5043
5044         case EAXSOURCE_EXCLUSION:
5045             call.set_value<Exception>(props.lExclusion);
5046             break;
5047
5048         case EAXSOURCE_EXCLUSIONLFRATIO:
5049             call.set_value<Exception>(props.flExclusionLFRatio);
5050             break;
5051
5052         case EAXSOURCE_OUTSIDEVOLUMEHF:
5053             call.set_value<Exception>(props.lOutsideVolumeHF);
5054             break;
5055
5056         case EAXSOURCE_DOPPLERFACTOR:
5057             call.set_value<Exception>(props.flDopplerFactor);
5058             break;
5059
5060         case EAXSOURCE_ROLLOFFFACTOR:
5061             call.set_value<Exception>(props.flRolloffFactor);
5062             break;
5063
5064         case EAXSOURCE_ROOMROLLOFFFACTOR:
5065             call.set_value<Exception>(props.flRoomRolloffFactor);
5066             break;
5067
5068         case EAXSOURCE_AIRABSORPTIONFACTOR:
5069             call.set_value<Exception>(props.flAirAbsorptionFactor);
5070             break;
5071
5072         case EAXSOURCE_FLAGS:
5073             call.set_value<Exception>(props.ulFlags);
5074             break;
5075
5076         default:
5077             eax_fail_unknown_property_id();
5078     }
5079 }
5080
5081 void ALsource::eax4_get(const EaxCall& call, const Eax4Props& props)
5082 {
5083     switch (call.get_property_id()) {
5084         case EAXSOURCE_NONE:
5085             break;
5086
5087         case EAXSOURCE_ALLPARAMETERS:
5088         case EAXSOURCE_OBSTRUCTIONPARAMETERS:
5089         case EAXSOURCE_OCCLUSIONPARAMETERS:
5090         case EAXSOURCE_EXCLUSIONPARAMETERS:
5091         case EAXSOURCE_DIRECT:
5092         case EAXSOURCE_DIRECTHF:
5093         case EAXSOURCE_ROOM:
5094         case EAXSOURCE_ROOMHF:
5095         case EAXSOURCE_OBSTRUCTION:
5096         case EAXSOURCE_OBSTRUCTIONLFRATIO:
5097         case EAXSOURCE_OCCLUSION:
5098         case EAXSOURCE_OCCLUSIONLFRATIO:
5099         case EAXSOURCE_OCCLUSIONROOMRATIO:
5100         case EAXSOURCE_OCCLUSIONDIRECTRATIO:
5101         case EAXSOURCE_EXCLUSION:
5102         case EAXSOURCE_EXCLUSIONLFRATIO:
5103         case EAXSOURCE_OUTSIDEVOLUMEHF:
5104         case EAXSOURCE_DOPPLERFACTOR:
5105         case EAXSOURCE_ROLLOFFFACTOR:
5106         case EAXSOURCE_ROOMROLLOFFFACTOR:
5107         case EAXSOURCE_AIRABSORPTIONFACTOR:
5108         case EAXSOURCE_FLAGS:
5109             eax3_get(call, props.source);
5110             break;
5111
5112         case EAXSOURCE_SENDPARAMETERS:
5113             eax_get_sends<EAXSOURCESENDPROPERTIES>(call, props.sends);
5114             break;
5115
5116         case EAXSOURCE_ALLSENDPARAMETERS:
5117             eax_get_sends<EAXSOURCEALLSENDPROPERTIES>(call, props.sends);
5118             break;
5119
5120         case EAXSOURCE_OCCLUSIONSENDPARAMETERS:
5121             eax_get_sends<EAXSOURCEOCCLUSIONSENDPROPERTIES>(call, props.sends);
5122             break;
5123
5124         case EAXSOURCE_EXCLUSIONSENDPARAMETERS:
5125             eax_get_sends<EAXSOURCEEXCLUSIONSENDPROPERTIES>(call, props.sends);
5126             break;
5127
5128         case EAXSOURCE_ACTIVEFXSLOTID:
5129             eax_get_active_fx_slot_id(call, props.active_fx_slots.guidActiveFXSlots, EAX40_MAX_ACTIVE_FXSLOTS);
5130             break;
5131
5132         default:
5133             eax_fail_unknown_property_id();
5134     }
5135 }
5136
5137 void ALsource::eax5_get_all_2d(const EaxCall& call, const EAX50SOURCEPROPERTIES& props)
5138 {
5139     auto& subprops = call.get_value<Exception, EAXSOURCE2DPROPERTIES>();
5140     subprops.lDirect = props.lDirect;
5141     subprops.lDirectHF = props.lDirectHF;
5142     subprops.lRoom = props.lRoom;
5143     subprops.lRoomHF = props.lRoomHF;
5144     subprops.ulFlags = props.ulFlags;
5145 }
5146
5147 void ALsource::eax5_get_speaker_levels(const EaxCall& call, const EaxSpeakerLevels& props)
5148 {
5149     const auto subprops = call.get_values<EAXSPEAKERLEVELPROPERTIES>(eax_max_speakers);
5150     std::uninitialized_copy_n(props.cbegin(), subprops.size(), subprops.begin());
5151 }
5152
5153 void ALsource::eax5_get(const EaxCall& call, const Eax5Props& props)
5154 {
5155     switch (call.get_property_id()) {
5156         case EAXSOURCE_NONE:
5157             break;
5158
5159         case EAXSOURCE_ALLPARAMETERS:
5160         case EAXSOURCE_OBSTRUCTIONPARAMETERS:
5161         case EAXSOURCE_OCCLUSIONPARAMETERS:
5162         case EAXSOURCE_EXCLUSIONPARAMETERS:
5163         case EAXSOURCE_DIRECT:
5164         case EAXSOURCE_DIRECTHF:
5165         case EAXSOURCE_ROOM:
5166         case EAXSOURCE_ROOMHF:
5167         case EAXSOURCE_OBSTRUCTION:
5168         case EAXSOURCE_OBSTRUCTIONLFRATIO:
5169         case EAXSOURCE_OCCLUSION:
5170         case EAXSOURCE_OCCLUSIONLFRATIO:
5171         case EAXSOURCE_OCCLUSIONROOMRATIO:
5172         case EAXSOURCE_OCCLUSIONDIRECTRATIO:
5173         case EAXSOURCE_EXCLUSION:
5174         case EAXSOURCE_EXCLUSIONLFRATIO:
5175         case EAXSOURCE_OUTSIDEVOLUMEHF:
5176         case EAXSOURCE_DOPPLERFACTOR:
5177         case EAXSOURCE_ROLLOFFFACTOR:
5178         case EAXSOURCE_ROOMROLLOFFFACTOR:
5179         case EAXSOURCE_AIRABSORPTIONFACTOR:
5180         case EAXSOURCE_FLAGS:
5181             eax3_get(call, props.source);
5182             break;
5183
5184         case EAXSOURCE_SENDPARAMETERS:
5185             eax_get_sends<EAXSOURCESENDPROPERTIES>(call, props.sends);
5186             break;
5187
5188         case EAXSOURCE_ALLSENDPARAMETERS:
5189             eax_get_sends<EAXSOURCEALLSENDPROPERTIES>(call, props.sends);
5190             break;
5191
5192         case EAXSOURCE_OCCLUSIONSENDPARAMETERS:
5193             eax_get_sends<EAXSOURCEOCCLUSIONSENDPROPERTIES>(call, props.sends);
5194             break;
5195
5196         case EAXSOURCE_EXCLUSIONSENDPARAMETERS:
5197             eax_get_sends<EAXSOURCEEXCLUSIONSENDPROPERTIES>(call, props.sends);
5198             break;
5199
5200         case EAXSOURCE_ACTIVEFXSLOTID:
5201             eax_get_active_fx_slot_id(call, props.active_fx_slots.guidActiveFXSlots, EAX50_MAX_ACTIVE_FXSLOTS);
5202             break;
5203
5204         case EAXSOURCE_MACROFXFACTOR:
5205             call.set_value<Exception>(props.source.flMacroFXFactor);
5206             break;
5207
5208         case EAXSOURCE_SPEAKERLEVELS:
5209             call.set_value<Exception>(props.speaker_levels);
5210             break;
5211
5212         case EAXSOURCE_ALL2DPARAMETERS:
5213             eax5_get_all_2d(call, props.source);
5214             break;
5215
5216         default:
5217             eax_fail_unknown_property_id();
5218     }
5219 }
5220
5221 void ALsource::eax_get(const EaxCall& call)
5222 {
5223     switch (call.get_version()) {
5224         case 1: eax1_get(call, mEax1.i); break;
5225         case 2: eax2_get(call, mEax2.i); break;
5226         case 3: eax3_get(call, mEax3.i); break;
5227         case 4: eax4_get(call, mEax4.i); break;
5228         case 5: eax5_get(call, mEax5.i); break;
5229         default: eax_fail_unknown_version();
5230     }
5231 }
5232
5233 void ALsource::eax_set_al_source_send(ALeffectslot *slot, size_t sendidx, const EaxAlLowPassParam &filter)
5234 {
5235     if(sendidx >= EAX_MAX_FXSLOTS)
5236         return;
5237
5238     auto &send = Send[sendidx];
5239     send.Gain = filter.gain;
5240     send.GainHF = filter.gain_hf;
5241     send.HFReference = LOWPASSFREQREF;
5242     send.GainLF = 1.0f;
5243     send.LFReference = HIGHPASSFREQREF;
5244
5245     if(slot != nullptr)
5246         IncrementRef(slot->ref);
5247     if(auto *oldslot = send.Slot)
5248         DecrementRef(oldslot->ref);
5249
5250     send.Slot = slot;
5251     mPropsDirty = true;
5252 }
5253
5254 void ALsource::eax_commit_active_fx_slots()
5255 {
5256     // Clear all slots to an inactive state.
5257     mEaxActiveFxSlots.fill(false);
5258
5259     // Mark the set slots as active.
5260     for(const auto& slot_id : mEax.active_fx_slots.guidActiveFXSlots)
5261     {
5262         if(slot_id == EAX_NULL_GUID)
5263         {
5264         }
5265         else if(slot_id == EAX_PrimaryFXSlotID)
5266         {
5267             // Mark primary FX slot as active.
5268             if(mEaxPrimaryFxSlotId.has_value())
5269                 mEaxActiveFxSlots[*mEaxPrimaryFxSlotId] = true;
5270         }
5271         else if(slot_id == EAXPROPERTYID_EAX50_FXSlot0)
5272             mEaxActiveFxSlots[0] = true;
5273         else if(slot_id == EAXPROPERTYID_EAX50_FXSlot1)
5274             mEaxActiveFxSlots[1] = true;
5275         else if(slot_id == EAXPROPERTYID_EAX50_FXSlot2)
5276             mEaxActiveFxSlots[2] = true;
5277         else if(slot_id == EAXPROPERTYID_EAX50_FXSlot3)
5278             mEaxActiveFxSlots[3] = true;
5279     }
5280
5281     // Deactivate EFX auxiliary effect slots for inactive slots. Active slots
5282     // will be updated with the room filters.
5283     for(auto i = size_t{}; i < EAX_MAX_FXSLOTS; ++i)
5284     {
5285         if(!mEaxActiveFxSlots[i])
5286             eax_set_al_source_send(nullptr, i, EaxAlLowPassParam{1.0f, 1.0f});
5287     }
5288 }
5289
5290 void ALsource::eax_commit_filters()
5291 {
5292     eax_update_direct_filter();
5293     eax_update_room_filters();
5294 }
5295
5296 void ALsource::eaxCommit()
5297 {
5298     const auto primary_fx_slot_id = mEaxAlContext->eaxGetPrimaryFxSlotIndex();
5299     const auto is_primary_fx_slot_id_changed = (mEaxPrimaryFxSlotId != primary_fx_slot_id);
5300
5301     if(!mEaxChanged && !is_primary_fx_slot_id_changed)
5302         return;
5303
5304     mEaxPrimaryFxSlotId = primary_fx_slot_id;
5305     mEaxChanged = false;
5306
5307     switch(mEaxVersion)
5308     {
5309     case 1:
5310         mEax1.i = mEax1.d;
5311         eax1_translate(mEax1.i, mEax);
5312         break;
5313     case 2:
5314         mEax2.i = mEax2.d;
5315         eax2_translate(mEax2.i, mEax);
5316         break;
5317     case 3:
5318         mEax3.i = mEax3.d;
5319         eax3_translate(mEax3.i, mEax);
5320         break;
5321     case 4:
5322         mEax4.i = mEax4.d;
5323         eax4_translate(mEax4.i, mEax);
5324         break;
5325     case 5:
5326         mEax5.i = mEax5.d;
5327         mEax = mEax5.d;
5328         break;
5329     }
5330
5331     eax_set_efx_outer_gain_hf();
5332     eax_set_efx_doppler_factor();
5333     eax_set_efx_rolloff_factor();
5334     eax_set_efx_room_rolloff_factor();
5335     eax_set_efx_air_absorption_factor();
5336     eax_set_efx_dry_gain_hf_auto();
5337     eax_set_efx_wet_gain_auto();
5338     eax_set_efx_wet_gain_hf_auto();
5339
5340     eax_commit_active_fx_slots();
5341     eax_commit_filters();
5342 }
5343
5344 #endif // ALSOFT_EAX