Avoid reference-to-function template parameters
[openal-soft.git] / al / source.cpp
blobfd711b6118b8444ece43192df53c8723860cb349
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.
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.
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
21 #include "config.h"
23 #include "source.h"
25 #include <algorithm>
26 #include <array>
27 #include <atomic>
28 #include <bitset>
29 #include <cassert>
30 #include <chrono>
31 #include <cinttypes>
32 #include <cmath>
33 #include <cstdint>
34 #include <cstdio>
35 #include <exception>
36 #include <iterator>
37 #include <limits>
38 #include <memory>
39 #include <mutex>
40 #include <numeric>
41 #include <optional>
42 #include <ratio>
43 #include <stdexcept>
44 #include <string>
45 #include <tuple>
46 #include <type_traits>
47 #include <unordered_map>
48 #include <utility>
49 #include <vector>
51 #include "AL/al.h"
52 #include "AL/alc.h"
53 #include "AL/alext.h"
54 #include "AL/efx.h"
56 #include "albit.h"
57 #include "alc/backends/base.h"
58 #include "alc/context.h"
59 #include "alc/device.h"
60 #include "alc/inprogext.h"
61 #include "almalloc.h"
62 #include "alnumeric.h"
63 #include "alspan.h"
64 #include "atomic.h"
65 #include "auxeffectslot.h"
66 #include "buffer.h"
67 #include "core/buffer_storage.h"
68 #include "core/logging.h"
69 #include "core/mixer/defs.h"
70 #include "core/voice_change.h"
71 #include "direct_defs.h"
72 #include "error.h"
73 #include "filter.h"
74 #include "flexarray.h"
75 #include "intrusive_ptr.h"
76 #include "opthelpers.h"
78 #ifdef ALSOFT_EAX
79 #include "eax/api.h"
80 #include "eax/call.h"
81 #include "eax/fx_slot_index.h"
82 #include "eax/utils.h"
83 #endif
85 namespace {
87 using SubListAllocator = al::allocator<std::array<ALsource,64>>;
88 using std::chrono::nanoseconds;
89 using seconds_d = std::chrono::duration<double>;
91 Voice *GetSourceVoice(ALsource *source, ALCcontext *context)
93 auto voicelist = context->getVoicesSpan();
94 ALuint idx{source->VoiceIdx};
95 if(idx < voicelist.size())
97 ALuint sid{source->id};
98 Voice *voice = voicelist[idx];
99 if(voice->mSourceID.load(std::memory_order_acquire) == sid)
100 return voice;
102 source->VoiceIdx = InvalidVoiceIndex;
103 return nullptr;
107 void UpdateSourceProps(const ALsource *source, Voice *voice, ALCcontext *context)
109 /* Get an unused property container, or allocate a new one as needed. */
110 VoicePropsItem *props{context->mFreeVoiceProps.load(std::memory_order_acquire)};
111 if(!props)
113 context->allocVoiceProps();
114 props = context->mFreeVoiceProps.load(std::memory_order_acquire);
116 VoicePropsItem *next;
117 do {
118 next = props->next.load(std::memory_order_relaxed);
119 } while(context->mFreeVoiceProps.compare_exchange_weak(props, next,
120 std::memory_order_acq_rel, std::memory_order_acquire) == false);
122 props->Pitch = source->Pitch;
123 props->Gain = source->Gain;
124 props->OuterGain = source->OuterGain;
125 props->MinGain = source->MinGain;
126 props->MaxGain = source->MaxGain;
127 props->InnerAngle = source->InnerAngle;
128 props->OuterAngle = source->OuterAngle;
129 props->RefDistance = source->RefDistance;
130 props->MaxDistance = source->MaxDistance;
131 props->RolloffFactor = source->RolloffFactor
132 #ifdef ALSOFT_EAX
133 + source->RolloffFactor2
134 #endif
136 props->Position = source->Position;
137 props->Velocity = source->Velocity;
138 props->Direction = source->Direction;
139 props->OrientAt = source->OrientAt;
140 props->OrientUp = source->OrientUp;
141 props->HeadRelative = source->HeadRelative;
142 props->mDistanceModel = source->mDistanceModel;
143 props->mResampler = source->mResampler;
144 props->DirectChannels = source->DirectChannels;
145 props->mSpatializeMode = source->mSpatialize;
147 props->DryGainHFAuto = source->DryGainHFAuto;
148 props->WetGainAuto = source->WetGainAuto;
149 props->WetGainHFAuto = source->WetGainHFAuto;
150 props->OuterGainHF = source->OuterGainHF;
152 props->AirAbsorptionFactor = source->AirAbsorptionFactor;
153 props->RoomRolloffFactor = source->RoomRolloffFactor;
154 props->DopplerFactor = source->DopplerFactor;
156 props->StereoPan = source->StereoPan;
158 props->Radius = source->Radius;
159 props->EnhWidth = source->EnhWidth;
160 props->Panning = source->mPanningEnabled ? source->mPan : 0.0f;
162 props->Direct.Gain = source->Direct.Gain;
163 props->Direct.GainHF = source->Direct.GainHF;
164 props->Direct.HFReference = source->Direct.HFReference;
165 props->Direct.GainLF = source->Direct.GainLF;
166 props->Direct.LFReference = source->Direct.LFReference;
168 auto copy_send = [](const ALsource::SendData &srcsend) noexcept -> VoiceProps::SendData
170 VoiceProps::SendData ret{};
171 ret.Slot = srcsend.Slot ? srcsend.Slot->mSlot : nullptr;
172 ret.Gain = srcsend.Gain;
173 ret.GainHF = srcsend.GainHF;
174 ret.HFReference = srcsend.HFReference;
175 ret.GainLF = srcsend.GainLF;
176 ret.LFReference = srcsend.LFReference;
177 return ret;
179 std::transform(source->Send.cbegin(), source->Send.cend(), props->Send.begin(), copy_send);
180 if(!props->Send[0].Slot && context->mDefaultSlot)
181 props->Send[0].Slot = context->mDefaultSlot->mSlot;
183 /* Set the new container for updating internal parameters. */
184 props = voice->mUpdate.exchange(props, std::memory_order_acq_rel);
185 if(props)
187 /* If there was an unused update container, put it back in the
188 * freelist.
190 AtomicReplaceHead(context->mFreeVoiceProps, props);
194 /* GetSourceSampleOffset
196 * Gets the current read offset for the given Source, in 32.32 fixed-point
197 * samples. The offset is relative to the start of the queue (not the start of
198 * the current buffer).
200 int64_t GetSourceSampleOffset(ALsource *Source, ALCcontext *context, nanoseconds *clocktime)
202 ALCdevice *device{context->mALDevice.get()};
203 const VoiceBufferItem *Current{};
204 int64_t readPos{};
205 uint refcount{};
206 Voice *voice{};
208 do {
209 refcount = device->waitForMix();
210 *clocktime = device->getClockTime();
211 voice = GetSourceVoice(Source, context);
212 if(voice)
214 Current = voice->mCurrentBuffer.load(std::memory_order_relaxed);
216 readPos = int64_t{voice->mPosition.load(std::memory_order_relaxed)} << MixerFracBits;
217 readPos += voice->mPositionFrac.load(std::memory_order_relaxed);
219 std::atomic_thread_fence(std::memory_order_acquire);
220 } while(refcount != device->mMixCount.load(std::memory_order_relaxed));
222 if(!voice)
223 return 0;
225 for(auto &item : Source->mQueue)
227 if(&item == Current) break;
228 readPos += int64_t{item.mSampleLen} << MixerFracBits;
230 if(readPos > std::numeric_limits<int64_t>::max() >> (32-MixerFracBits))
231 return std::numeric_limits<int64_t>::max();
232 return readPos << (32-MixerFracBits);
235 /* GetSourceSecOffset
237 * Gets the current read offset for the given Source, in seconds. The offset is
238 * relative to the start of the queue (not the start of the current buffer).
240 double GetSourceSecOffset(ALsource *Source, ALCcontext *context, nanoseconds *clocktime)
242 ALCdevice *device{context->mALDevice.get()};
243 const VoiceBufferItem *Current{};
244 int64_t readPos{};
245 uint refcount{};
246 Voice *voice{};
248 do {
249 refcount = device->waitForMix();
250 *clocktime = device->getClockTime();
251 voice = GetSourceVoice(Source, context);
252 if(voice)
254 Current = voice->mCurrentBuffer.load(std::memory_order_relaxed);
256 readPos = int64_t{voice->mPosition.load(std::memory_order_relaxed)} << MixerFracBits;
257 readPos += voice->mPositionFrac.load(std::memory_order_relaxed);
259 std::atomic_thread_fence(std::memory_order_acquire);
260 } while(refcount != device->mMixCount.load(std::memory_order_relaxed));
262 if(!voice)
263 return 0.0f;
265 const ALbuffer *BufferFmt{nullptr};
266 auto BufferList = Source->mQueue.cbegin();
267 while(BufferList != Source->mQueue.cend() && al::to_address(BufferList) != Current)
269 if(!BufferFmt) BufferFmt = BufferList->mBuffer;
270 readPos += int64_t{BufferList->mSampleLen} << MixerFracBits;
271 ++BufferList;
273 while(BufferList != Source->mQueue.cend() && !BufferFmt)
275 BufferFmt = BufferList->mBuffer;
276 ++BufferList;
278 ASSUME(BufferFmt != nullptr);
280 return static_cast<double>(readPos) / double{MixerFracOne} / BufferFmt->mSampleRate;
283 /* GetSourceOffset
285 * Gets the current read offset for the given Source, in the appropriate format
286 * (Bytes, Samples or Seconds). The offset is relative to the start of the
287 * queue (not the start of the current buffer).
289 template<typename T>
290 NOINLINE T GetSourceOffset(ALsource *Source, ALenum name, ALCcontext *context)
292 ALCdevice *device{context->mALDevice.get()};
293 const VoiceBufferItem *Current{};
294 int64_t readPos{};
295 uint readPosFrac{};
296 uint refcount;
297 Voice *voice;
299 do {
300 refcount = device->waitForMix();
301 voice = GetSourceVoice(Source, context);
302 if(voice)
304 Current = voice->mCurrentBuffer.load(std::memory_order_relaxed);
306 readPos = voice->mPosition.load(std::memory_order_relaxed);
307 readPosFrac = voice->mPositionFrac.load(std::memory_order_relaxed);
309 std::atomic_thread_fence(std::memory_order_acquire);
310 } while(refcount != device->mMixCount.load(std::memory_order_relaxed));
312 if(!voice)
313 return T{0};
315 const ALbuffer *BufferFmt{nullptr};
316 auto BufferList = Source->mQueue.cbegin();
317 while(BufferList != Source->mQueue.cend() && al::to_address(BufferList) != Current)
319 if(!BufferFmt) BufferFmt = BufferList->mBuffer;
320 readPos += BufferList->mSampleLen;
321 ++BufferList;
323 while(BufferList != Source->mQueue.cend() && !BufferFmt)
325 BufferFmt = BufferList->mBuffer;
326 ++BufferList;
328 ASSUME(BufferFmt != nullptr);
330 T offset{};
331 switch(name)
333 case AL_SEC_OFFSET:
334 if constexpr(std::is_floating_point_v<T>)
336 offset = static_cast<T>(readPos) + static_cast<T>(readPosFrac)/T{MixerFracOne};
337 offset /= static_cast<T>(BufferFmt->mSampleRate);
339 else
341 readPos /= BufferFmt->mSampleRate;
342 offset = static_cast<T>(std::clamp<int64_t>(readPos, std::numeric_limits<T>::min(),
343 std::numeric_limits<T>::max()));
345 break;
347 case AL_SAMPLE_OFFSET:
348 if constexpr(std::is_floating_point_v<T>)
349 offset = static_cast<T>(readPos) + static_cast<T>(readPosFrac)/T{MixerFracOne};
350 else
351 offset = static_cast<T>(std::clamp<int64_t>(readPos, std::numeric_limits<T>::min(),
352 std::numeric_limits<T>::max()));
353 break;
355 case AL_BYTE_OFFSET:
356 const ALuint BlockSamples{BufferFmt->mBlockAlign};
357 const ALuint BlockSize{BufferFmt->blockSizeFromFmt()};
358 /* Round down to the block boundary. */
359 readPos = readPos / BlockSamples * BlockSize;
361 if constexpr(std::is_floating_point_v<T>)
362 offset = static_cast<T>(readPos);
363 else
365 if(readPos > std::numeric_limits<T>::max())
366 offset = RoundDown(std::numeric_limits<T>::max(), static_cast<T>(BlockSize));
367 else if(readPos < std::numeric_limits<T>::min())
368 offset = RoundUp(std::numeric_limits<T>::min(), static_cast<T>(BlockSize));
369 else
370 offset = static_cast<T>(readPos);
372 break;
374 return offset;
377 /* GetSourceLength
379 * Gets the length of the given Source's buffer queue, in the appropriate
380 * format (Bytes, Samples or Seconds).
382 template<typename T>
383 NOINLINE T GetSourceLength(const ALsource *source, ALenum name)
385 uint64_t length{0};
386 const ALbuffer *BufferFmt{nullptr};
387 for(auto &listitem : source->mQueue)
389 if(!BufferFmt)
390 BufferFmt = listitem.mBuffer;
391 length += listitem.mSampleLen;
393 if(length == 0)
394 return T{0};
396 ASSUME(BufferFmt != nullptr);
397 switch(name)
399 case AL_SEC_LENGTH_SOFT:
400 if constexpr(std::is_floating_point_v<T>)
401 return static_cast<T>(length) / static_cast<T>(BufferFmt->mSampleRate);
402 else
403 return static_cast<T>(std::min<uint64_t>(length/BufferFmt->mSampleRate,
404 std::numeric_limits<T>::max()));
406 case AL_SAMPLE_LENGTH_SOFT:
407 if constexpr(std::is_floating_point_v<T>)
408 return static_cast<T>(length);
409 else
410 return static_cast<T>(std::min<uint64_t>(length, std::numeric_limits<T>::max()));
412 case AL_BYTE_LENGTH_SOFT:
413 const ALuint BlockSamples{BufferFmt->mBlockAlign};
414 const ALuint BlockSize{BufferFmt->blockSizeFromFmt()};
415 /* Round down to the block boundary. */
416 length = length / BlockSamples * BlockSize;
418 if constexpr(std::is_floating_point_v<T>)
419 return static_cast<T>(length);
420 else
422 if(length > std::numeric_limits<T>::max())
423 return RoundDown(std::numeric_limits<T>::max(), static_cast<T>(BlockSize));
424 return static_cast<T>(length);
427 return T{0};
431 struct VoicePos {
432 int pos;
433 uint frac;
434 ALbufferQueueItem *bufferitem;
438 * GetSampleOffset
440 * Retrieves the voice position, fixed-point fraction, and bufferlist item
441 * using the given offset type and offset. If the offset is out of range,
442 * returns an empty optional.
444 std::optional<VoicePos> GetSampleOffset(std::deque<ALbufferQueueItem> &BufferList,
445 ALenum OffsetType, double Offset)
447 /* Find the first valid Buffer in the Queue */
448 const ALbuffer *BufferFmt{nullptr};
449 for(auto &item : BufferList)
451 BufferFmt = item.mBuffer;
452 if(BufferFmt) break;
454 if(!BufferFmt) UNLIKELY
455 return std::nullopt;
457 /* Get sample frame offset */
458 int64_t offset{};
459 uint frac{};
460 double dbloff, dblfrac;
461 switch(OffsetType)
463 case AL_SEC_OFFSET:
464 dblfrac = std::modf(Offset*BufferFmt->mSampleRate, &dbloff);
465 if(dblfrac < 0.0)
467 /* If there's a negative fraction, reduce the offset to "floor" it,
468 * and convert the fraction to a percentage to the next value (e.g.
469 * -2.75 -> -3 + 0.25).
471 dbloff -= 1.0;
472 dblfrac += 1.0;
474 offset = static_cast<int64_t>(dbloff);
475 frac = static_cast<uint>(std::min(dblfrac*MixerFracOne, MixerFracOne-1.0));
476 break;
478 case AL_SAMPLE_OFFSET:
479 dblfrac = std::modf(Offset, &dbloff);
480 if(dblfrac < 0.0)
482 dbloff -= 1.0;
483 dblfrac += 1.0;
485 offset = static_cast<int64_t>(dbloff);
486 frac = static_cast<uint>(std::min(dblfrac*MixerFracOne, MixerFracOne-1.0));
487 break;
489 case AL_BYTE_OFFSET:
490 /* Determine the ByteOffset (and ensure it is block aligned) */
491 Offset = std::floor(Offset / BufferFmt->blockSizeFromFmt());
492 offset = static_cast<int64_t>(Offset) * BufferFmt->mBlockAlign;
493 frac = 0;
494 break;
497 /* Find the bufferlist item this offset belongs to. */
498 if(offset < 0)
500 if(offset < std::numeric_limits<int>::min())
501 return std::nullopt;
502 return VoicePos{static_cast<int>(offset), frac, &BufferList.front()};
505 if(BufferFmt->mCallback)
506 return std::nullopt;
508 int64_t totalBufferLen{0};
509 for(auto &item : BufferList)
511 if(totalBufferLen > offset)
512 break;
513 if(item.mSampleLen > offset-totalBufferLen)
515 /* Offset is in this buffer */
516 return VoicePos{static_cast<int>(offset-totalBufferLen), frac, &item};
518 totalBufferLen += item.mSampleLen;
521 /* Offset is out of range of the queue */
522 return std::nullopt;
526 void InitVoice(Voice *voice, ALsource *source, ALbufferQueueItem *BufferList, ALCcontext *context,
527 ALCdevice *device)
529 voice->mLoopBuffer.store(source->Looping ? &source->mQueue.front() : nullptr,
530 std::memory_order_relaxed);
532 ALbuffer *buffer{BufferList->mBuffer};
533 voice->mFrequency = buffer->mSampleRate;
534 if(buffer->mChannels == FmtMono && source->mPanningEnabled)
535 voice->mFmtChannels = FmtMonoDup;
536 else if(buffer->mChannels == FmtStereo && source->mStereoMode == SourceStereo::Enhanced)
537 voice->mFmtChannels = FmtSuperStereo;
538 else
539 voice->mFmtChannels = buffer->mChannels;
540 voice->mFmtType = buffer->mType;
541 voice->mFrameStep = buffer->channelsFromFmt();
542 voice->mBytesPerBlock = buffer->blockSizeFromFmt();
543 voice->mSamplesPerBlock = buffer->mBlockAlign;
544 voice->mAmbiLayout = IsUHJ(voice->mFmtChannels) ? AmbiLayout::FuMa : buffer->mAmbiLayout;
545 voice->mAmbiScaling = IsUHJ(voice->mFmtChannels) ? AmbiScaling::UHJ : buffer->mAmbiScaling;
546 voice->mAmbiOrder = (voice->mFmtChannels == FmtSuperStereo) ? 1 : buffer->mAmbiOrder;
548 if(buffer->mCallback) voice->mFlags.set(VoiceIsCallback);
549 else if(source->SourceType == AL_STATIC) voice->mFlags.set(VoiceIsStatic);
550 voice->mNumCallbackBlocks = 0;
551 voice->mCallbackBlockBase = 0;
553 voice->prepare(device);
555 source->mPropsDirty = false;
556 UpdateSourceProps(source, voice, context);
558 voice->mSourceID.store(source->id, std::memory_order_release);
562 VoiceChange *GetVoiceChanger(ALCcontext *ctx)
564 VoiceChange *vchg{ctx->mVoiceChangeTail};
565 if(vchg == ctx->mCurrentVoiceChange.load(std::memory_order_acquire)) UNLIKELY
567 ctx->allocVoiceChanges();
568 vchg = ctx->mVoiceChangeTail;
571 ctx->mVoiceChangeTail = vchg->mNext.exchange(nullptr, std::memory_order_relaxed);
573 return vchg;
576 void SendVoiceChanges(ALCcontext *ctx, VoiceChange *tail)
578 ALCdevice *device{ctx->mALDevice.get()};
580 VoiceChange *oldhead{ctx->mCurrentVoiceChange.load(std::memory_order_acquire)};
581 while(VoiceChange *next{oldhead->mNext.load(std::memory_order_relaxed)})
582 oldhead = next;
583 oldhead->mNext.store(tail, std::memory_order_release);
585 const bool connected{device->Connected.load(std::memory_order_acquire)};
586 std::ignore = device->waitForMix();
587 if(!connected) UNLIKELY
589 if(ctx->mStopVoicesOnDisconnect.load(std::memory_order_acquire))
591 /* If the device is disconnected and voices are stopped, just
592 * ignore all pending changes.
594 VoiceChange *cur{ctx->mCurrentVoiceChange.load(std::memory_order_acquire)};
595 while(VoiceChange *next{cur->mNext.load(std::memory_order_acquire)})
597 cur = next;
598 if(Voice *voice{cur->mVoice})
599 voice->mSourceID.store(0, std::memory_order_relaxed);
601 ctx->mCurrentVoiceChange.store(cur, std::memory_order_release);
607 bool SetVoiceOffset(Voice *oldvoice, const VoicePos &vpos, ALsource *source, ALCcontext *context,
608 ALCdevice *device)
610 /* First, get a free voice to start at the new offset. */
611 auto voicelist = context->getVoicesSpan();
612 Voice *newvoice{};
613 ALuint vidx{0};
614 for(Voice *voice : voicelist)
616 if(voice->mPlayState.load(std::memory_order_acquire) == Voice::Stopped
617 && voice->mSourceID.load(std::memory_order_relaxed) == 0u
618 && voice->mPendingChange.load(std::memory_order_relaxed) == false)
620 newvoice = voice;
621 break;
623 ++vidx;
625 if(!newvoice) UNLIKELY
627 auto &allvoices = *context->mVoices.load(std::memory_order_relaxed);
628 if(allvoices.size() == voicelist.size())
629 context->allocVoices(1);
630 context->mActiveVoiceCount.fetch_add(1, std::memory_order_release);
631 voicelist = context->getVoicesSpan();
633 vidx = 0;
634 for(Voice *voice : voicelist)
636 if(voice->mPlayState.load(std::memory_order_acquire) == Voice::Stopped
637 && voice->mSourceID.load(std::memory_order_relaxed) == 0u
638 && voice->mPendingChange.load(std::memory_order_relaxed) == false)
640 newvoice = voice;
641 break;
643 ++vidx;
645 ASSUME(newvoice != nullptr);
648 /* Initialize the new voice and set its starting offset.
649 * TODO: It might be better to have the VoiceChange processing copy the old
650 * voice's mixing parameters (and pending update) insead of initializing it
651 * all here. This would just need to set the minimum properties to link the
652 * voice to the source and its position-dependent properties (including the
653 * fading flag).
655 newvoice->mPlayState.store(Voice::Pending, std::memory_order_relaxed);
656 newvoice->mPosition.store(vpos.pos, std::memory_order_relaxed);
657 newvoice->mPositionFrac.store(vpos.frac, std::memory_order_relaxed);
658 newvoice->mCurrentBuffer.store(vpos.bufferitem, std::memory_order_relaxed);
659 newvoice->mStartTime = oldvoice->mStartTime;
660 newvoice->mFlags.reset();
661 if(vpos.pos > 0 || (vpos.pos == 0 && vpos.frac > 0)
662 || vpos.bufferitem != &source->mQueue.front())
663 newvoice->mFlags.set(VoiceIsFading);
664 InitVoice(newvoice, source, vpos.bufferitem, context, device);
665 source->VoiceIdx = vidx;
667 /* Set the old voice as having a pending change, and send it off with the
668 * new one with a new offset voice change.
670 oldvoice->mPendingChange.store(true, std::memory_order_relaxed);
672 VoiceChange *vchg{GetVoiceChanger(context)};
673 vchg->mOldVoice = oldvoice;
674 vchg->mVoice = newvoice;
675 vchg->mSourceID = source->id;
676 vchg->mState = VChangeState::Restart;
677 SendVoiceChanges(context, vchg);
679 /* If the old voice still has a sourceID, it's still active and the change-
680 * over will work on the next update.
682 if(oldvoice->mSourceID.load(std::memory_order_acquire) != 0u) LIKELY
683 return true;
685 /* Otherwise, if the new voice's state is not pending, the change-over
686 * already happened.
688 if(newvoice->mPlayState.load(std::memory_order_acquire) != Voice::Pending)
689 return true;
691 /* Otherwise, wait for any current mix to finish and check one last time. */
692 std::ignore = device->waitForMix();
693 if(newvoice->mPlayState.load(std::memory_order_acquire) != Voice::Pending)
694 return true;
695 /* The change-over failed because the old voice stopped before the new
696 * voice could start at the new offset. Let go of the new voice and have
697 * the caller store the source offset since it's stopped.
699 newvoice->mCurrentBuffer.store(nullptr, std::memory_order_relaxed);
700 newvoice->mLoopBuffer.store(nullptr, std::memory_order_relaxed);
701 newvoice->mSourceID.store(0u, std::memory_order_relaxed);
702 newvoice->mPlayState.store(Voice::Stopped, std::memory_order_relaxed);
703 return false;
708 * Returns if the last known state for the source was playing or paused. Does
709 * not sync with the mixer voice.
711 inline bool IsPlayingOrPaused(ALsource *source)
712 { return source->state == AL_PLAYING || source->state == AL_PAUSED; }
715 * Returns an updated source state using the matching voice's status (or lack
716 * thereof).
718 inline ALenum GetSourceState(ALsource *source, Voice *voice)
720 if(!voice && source->state == AL_PLAYING)
721 source->state = AL_STOPPED;
722 return source->state;
726 bool EnsureSources(ALCcontext *context, size_t needed)
728 size_t count{std::accumulate(context->mSourceList.cbegin(), context->mSourceList.cend(), 0_uz,
729 [](size_t cur, const SourceSubList &sublist) noexcept -> size_t
730 { return cur + static_cast<ALuint>(al::popcount(sublist.FreeMask)); })};
732 try {
733 while(needed > count)
735 if(context->mSourceList.size() >= 1<<25) UNLIKELY
736 return false;
738 SourceSubList sublist{};
739 sublist.FreeMask = ~0_u64;
740 sublist.Sources = SubListAllocator{}.allocate(1);
741 context->mSourceList.emplace_back(std::move(sublist));
742 count += std::tuple_size_v<SubListAllocator::value_type>;
745 catch(...) {
746 return false;
748 return true;
751 ALsource *AllocSource(ALCcontext *context) noexcept
753 auto sublist = std::find_if(context->mSourceList.begin(), context->mSourceList.end(),
754 [](const SourceSubList &entry) noexcept -> bool
755 { return entry.FreeMask != 0; });
756 auto lidx = static_cast<ALuint>(std::distance(context->mSourceList.begin(), sublist));
757 auto slidx = static_cast<ALuint>(al::countr_zero(sublist->FreeMask));
758 ASSUME(slidx < 64);
760 ALsource *source{al::construct_at(al::to_address(sublist->Sources->begin() + slidx))};
761 #ifdef ALSOFT_EAX
762 source->eaxInitialize(context);
763 #endif // ALSOFT_EAX
765 /* Add 1 to avoid source ID 0. */
766 source->id = ((lidx<<6) | slidx) + 1;
768 context->mNumSources += 1;
769 sublist->FreeMask &= ~(1_u64 << slidx);
771 return source;
774 void FreeSource(ALCcontext *context, ALsource *source)
776 context->mSourceNames.erase(source->id);
778 const ALuint id{source->id - 1};
779 const size_t lidx{id >> 6};
780 const ALuint slidx{id & 0x3f};
782 if(Voice *voice{GetSourceVoice(source, context)})
784 VoiceChange *vchg{GetVoiceChanger(context)};
786 voice->mPendingChange.store(true, std::memory_order_relaxed);
787 vchg->mVoice = voice;
788 vchg->mSourceID = source->id;
789 vchg->mState = VChangeState::Stop;
791 SendVoiceChanges(context, vchg);
794 std::destroy_at(source);
796 context->mSourceList[lidx].FreeMask |= 1_u64 << slidx;
797 context->mNumSources--;
801 inline ALsource *LookupSource(ALCcontext *context, ALuint id) noexcept
803 const size_t lidx{(id-1) >> 6};
804 const ALuint slidx{(id-1) & 0x3f};
806 if(lidx >= context->mSourceList.size()) UNLIKELY
807 return nullptr;
808 SourceSubList &sublist{context->mSourceList[lidx]};
809 if(sublist.FreeMask & (1_u64 << slidx)) UNLIKELY
810 return nullptr;
811 return al::to_address(sublist.Sources->begin() + slidx);
814 auto LookupBuffer = [](ALCdevice *device, auto id) noexcept -> ALbuffer*
816 const auto lidx{(id-1) >> 6};
817 const auto slidx{(id-1) & 0x3f};
819 if(lidx >= device->BufferList.size()) UNLIKELY
820 return nullptr;
821 BufferSubList &sublist = device->BufferList[static_cast<size_t>(lidx)];
822 if(sublist.FreeMask & (1_u64 << slidx)) UNLIKELY
823 return nullptr;
824 return al::to_address(sublist.Buffers->begin() + static_cast<size_t>(slidx));
827 auto LookupFilter = [](ALCdevice *device, auto id) noexcept -> ALfilter*
829 const auto lidx{(id-1) >> 6};
830 const auto slidx{(id-1) & 0x3f};
832 if(lidx >= device->FilterList.size()) UNLIKELY
833 return nullptr;
834 FilterSubList &sublist = device->FilterList[static_cast<size_t>(lidx)];
835 if(sublist.FreeMask & (1_u64 << slidx)) UNLIKELY
836 return nullptr;
837 return al::to_address(sublist.Filters->begin() + static_cast<size_t>(slidx));
840 auto LookupEffectSlot = [](ALCcontext *context, auto id) noexcept -> ALeffectslot*
842 const auto lidx{(id-1) >> 6};
843 const auto slidx{(id-1) & 0x3f};
845 if(lidx >= context->mEffectSlotList.size()) UNLIKELY
846 return nullptr;
847 EffectSlotSubList &sublist{context->mEffectSlotList[static_cast<size_t>(lidx)]};
848 if(sublist.FreeMask & (1_u64 << slidx)) UNLIKELY
849 return nullptr;
850 return al::to_address(sublist.EffectSlots->begin() + static_cast<size_t>(slidx));
854 auto StereoModeFromEnum = [](auto mode) noexcept -> std::optional<SourceStereo>
856 switch(mode)
858 case AL_NORMAL_SOFT: return SourceStereo::Normal;
859 case AL_SUPER_STEREO_SOFT: return SourceStereo::Enhanced;
861 return std::nullopt;
863 ALenum EnumFromStereoMode(SourceStereo mode)
865 switch(mode)
867 case SourceStereo::Normal: return AL_NORMAL_SOFT;
868 case SourceStereo::Enhanced: return AL_SUPER_STEREO_SOFT;
870 throw std::runtime_error{"Invalid SourceStereo: "+std::to_string(int(mode))};
873 auto SpatializeModeFromEnum = [](auto mode) noexcept -> std::optional<SpatializeMode>
875 switch(mode)
877 case AL_FALSE: return SpatializeMode::Off;
878 case AL_TRUE: return SpatializeMode::On;
879 case AL_AUTO_SOFT: return SpatializeMode::Auto;
881 return std::nullopt;
883 ALenum EnumFromSpatializeMode(SpatializeMode mode)
885 switch(mode)
887 case SpatializeMode::Off: return AL_FALSE;
888 case SpatializeMode::On: return AL_TRUE;
889 case SpatializeMode::Auto: return AL_AUTO_SOFT;
891 throw std::runtime_error{"Invalid SpatializeMode: "+std::to_string(int(mode))};
894 auto DirectModeFromEnum = [](auto mode) noexcept -> std::optional<DirectMode>
896 switch(mode)
898 case AL_FALSE: return DirectMode::Off;
899 case AL_DROP_UNMATCHED_SOFT: return DirectMode::DropMismatch;
900 case AL_REMIX_UNMATCHED_SOFT: return DirectMode::RemixMismatch;
902 return std::nullopt;
904 ALenum EnumFromDirectMode(DirectMode mode)
906 switch(mode)
908 case DirectMode::Off: return AL_FALSE;
909 case DirectMode::DropMismatch: return AL_DROP_UNMATCHED_SOFT;
910 case DirectMode::RemixMismatch: return AL_REMIX_UNMATCHED_SOFT;
912 throw std::runtime_error{"Invalid DirectMode: "+std::to_string(int(mode))};
915 auto DistanceModelFromALenum = [](auto model) noexcept -> std::optional<DistanceModel>
917 switch(model)
919 case AL_NONE: return DistanceModel::Disable;
920 case AL_INVERSE_DISTANCE: return DistanceModel::Inverse;
921 case AL_INVERSE_DISTANCE_CLAMPED: return DistanceModel::InverseClamped;
922 case AL_LINEAR_DISTANCE: return DistanceModel::Linear;
923 case AL_LINEAR_DISTANCE_CLAMPED: return DistanceModel::LinearClamped;
924 case AL_EXPONENT_DISTANCE: return DistanceModel::Exponent;
925 case AL_EXPONENT_DISTANCE_CLAMPED: return DistanceModel::ExponentClamped;
927 return std::nullopt;
929 ALenum ALenumFromDistanceModel(DistanceModel model)
931 switch(model)
933 case DistanceModel::Disable: return AL_NONE;
934 case DistanceModel::Inverse: return AL_INVERSE_DISTANCE;
935 case DistanceModel::InverseClamped: return AL_INVERSE_DISTANCE_CLAMPED;
936 case DistanceModel::Linear: return AL_LINEAR_DISTANCE;
937 case DistanceModel::LinearClamped: return AL_LINEAR_DISTANCE_CLAMPED;
938 case DistanceModel::Exponent: return AL_EXPONENT_DISTANCE;
939 case DistanceModel::ExponentClamped: return AL_EXPONENT_DISTANCE_CLAMPED;
941 throw std::runtime_error{"Unexpected distance model "+std::to_string(static_cast<int>(model))};
944 enum SourceProp : ALenum {
945 srcPitch = AL_PITCH,
946 srcGain = AL_GAIN,
947 srcMinGain = AL_MIN_GAIN,
948 srcMaxGain = AL_MAX_GAIN,
949 srcMaxDistance = AL_MAX_DISTANCE,
950 srcRolloffFactor = AL_ROLLOFF_FACTOR,
951 srcDopplerFactor = AL_DOPPLER_FACTOR,
952 srcConeOuterGain = AL_CONE_OUTER_GAIN,
953 srcSecOffset = AL_SEC_OFFSET,
954 srcSampleOffset = AL_SAMPLE_OFFSET,
955 srcByteOffset = AL_BYTE_OFFSET,
956 srcConeInnerAngle = AL_CONE_INNER_ANGLE,
957 srcConeOuterAngle = AL_CONE_OUTER_ANGLE,
958 srcRefDistance = AL_REFERENCE_DISTANCE,
960 srcPosition = AL_POSITION,
961 srcVelocity = AL_VELOCITY,
962 srcDirection = AL_DIRECTION,
964 srcSourceRelative = AL_SOURCE_RELATIVE,
965 srcLooping = AL_LOOPING,
966 srcBuffer = AL_BUFFER,
967 srcSourceState = AL_SOURCE_STATE,
968 srcBuffersQueued = AL_BUFFERS_QUEUED,
969 srcBuffersProcessed = AL_BUFFERS_PROCESSED,
970 srcSourceType = AL_SOURCE_TYPE,
972 /* ALC_EXT_EFX */
973 srcConeOuterGainHF = AL_CONE_OUTER_GAINHF,
974 srcAirAbsorptionFactor = AL_AIR_ABSORPTION_FACTOR,
975 srcRoomRolloffFactor = AL_ROOM_ROLLOFF_FACTOR,
976 srcDirectFilterGainHFAuto = AL_DIRECT_FILTER_GAINHF_AUTO,
977 srcAuxSendFilterGainAuto = AL_AUXILIARY_SEND_FILTER_GAIN_AUTO,
978 srcAuxSendFilterGainHFAuto = AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO,
979 srcDirectFilter = AL_DIRECT_FILTER,
980 srcAuxSendFilter = AL_AUXILIARY_SEND_FILTER,
982 /* AL_SOFT_direct_channels */
983 srcDirectChannelsSOFT = AL_DIRECT_CHANNELS_SOFT,
985 /* AL_EXT_source_distance_model */
986 srcDistanceModel = AL_DISTANCE_MODEL,
988 /* AL_SOFT_source_latency */
989 srcSampleOffsetLatencySOFT = AL_SAMPLE_OFFSET_LATENCY_SOFT,
990 srcSecOffsetLatencySOFT = AL_SEC_OFFSET_LATENCY_SOFT,
992 /* AL_EXT_STEREO_ANGLES */
993 srcAngles = AL_STEREO_ANGLES,
995 /* AL_EXT_SOURCE_RADIUS */
996 srcRadius = AL_SOURCE_RADIUS,
998 /* AL_EXT_BFORMAT */
999 srcOrientation = AL_ORIENTATION,
1001 /* AL_SOFT_source_length */
1002 srcByteLength = AL_BYTE_LENGTH_SOFT,
1003 srcSampleLength = AL_SAMPLE_LENGTH_SOFT,
1004 srcSecLength = AL_SEC_LENGTH_SOFT,
1006 /* AL_SOFT_source_resampler */
1007 srcResampler = AL_SOURCE_RESAMPLER_SOFT,
1009 /* AL_SOFT_source_spatialize */
1010 srcSpatialize = AL_SOURCE_SPATIALIZE_SOFT,
1012 /* ALC_SOFT_device_clock */
1013 srcSampleOffsetClockSOFT = AL_SAMPLE_OFFSET_CLOCK_SOFT,
1014 srcSecOffsetClockSOFT = AL_SEC_OFFSET_CLOCK_SOFT,
1016 /* AL_SOFT_UHJ */
1017 srcStereoMode = AL_STEREO_MODE_SOFT,
1018 srcSuperStereoWidth = AL_SUPER_STEREO_WIDTH_SOFT,
1020 /* AL_SOFT_buffer_sub_data */
1021 srcByteRWOffsetsSOFT = AL_BYTE_RW_OFFSETS_SOFT,
1022 srcSampleRWOffsetsSOFT = AL_SAMPLE_RW_OFFSETS_SOFT,
1024 /* AL_SOFT_source_panning */
1025 srcPanningEnabledSOFT = AL_PANNING_ENABLED_SOFT,
1026 srcPanSOFT = AL_PAN_SOFT,
1030 constexpr ALuint IntValsByProp(ALenum prop)
1032 switch(static_cast<SourceProp>(prop))
1034 case AL_SOURCE_STATE:
1035 case AL_SOURCE_TYPE:
1036 case AL_BUFFERS_QUEUED:
1037 case AL_BUFFERS_PROCESSED:
1038 case AL_BYTE_LENGTH_SOFT:
1039 case AL_SAMPLE_LENGTH_SOFT:
1040 case AL_SOURCE_RELATIVE:
1041 case AL_LOOPING:
1042 case AL_BUFFER:
1043 case AL_SAMPLE_OFFSET:
1044 case AL_BYTE_OFFSET:
1045 case AL_DIRECT_FILTER:
1046 case AL_DIRECT_FILTER_GAINHF_AUTO:
1047 case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO:
1048 case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO:
1049 case AL_DIRECT_CHANNELS_SOFT:
1050 case AL_DISTANCE_MODEL:
1051 case AL_SOURCE_RESAMPLER_SOFT:
1052 case AL_SOURCE_SPATIALIZE_SOFT:
1053 case AL_STEREO_MODE_SOFT:
1054 case AL_PANNING_ENABLED_SOFT:
1055 case AL_PAN_SOFT:
1056 return 1;
1058 case AL_SOURCE_RADIUS: /*AL_BYTE_RW_OFFSETS_SOFT:*/
1059 if(sBufferSubDataCompat)
1060 return 2;
1061 /*fall-through*/
1062 case AL_CONE_INNER_ANGLE:
1063 case AL_CONE_OUTER_ANGLE:
1064 case AL_PITCH:
1065 case AL_GAIN:
1066 case AL_MIN_GAIN:
1067 case AL_MAX_GAIN:
1068 case AL_REFERENCE_DISTANCE:
1069 case AL_ROLLOFF_FACTOR:
1070 case AL_CONE_OUTER_GAIN:
1071 case AL_MAX_DISTANCE:
1072 case AL_SEC_OFFSET:
1073 case AL_DOPPLER_FACTOR:
1074 case AL_CONE_OUTER_GAINHF:
1075 case AL_AIR_ABSORPTION_FACTOR:
1076 case AL_ROOM_ROLLOFF_FACTOR:
1077 case AL_SEC_LENGTH_SOFT:
1078 case AL_SUPER_STEREO_WIDTH_SOFT:
1079 return 1; /* 1x float */
1081 case AL_SAMPLE_RW_OFFSETS_SOFT:
1082 if(sBufferSubDataCompat)
1083 return 2;
1084 break;
1086 case AL_AUXILIARY_SEND_FILTER:
1087 return 3;
1089 case AL_POSITION:
1090 case AL_VELOCITY:
1091 case AL_DIRECTION:
1092 return 3; /* 3x float */
1094 case AL_ORIENTATION:
1095 return 6; /* 6x float */
1097 case AL_SAMPLE_OFFSET_LATENCY_SOFT:
1098 case AL_SAMPLE_OFFSET_CLOCK_SOFT:
1099 case AL_STEREO_ANGLES:
1100 break; /* i64 only */
1101 case AL_SEC_OFFSET_LATENCY_SOFT:
1102 case AL_SEC_OFFSET_CLOCK_SOFT:
1103 break; /* double only */
1106 return 0;
1109 constexpr ALuint Int64ValsByProp(ALenum prop)
1111 switch(static_cast<SourceProp>(prop))
1113 case AL_SOURCE_STATE:
1114 case AL_SOURCE_TYPE:
1115 case AL_BUFFERS_QUEUED:
1116 case AL_BUFFERS_PROCESSED:
1117 case AL_BYTE_LENGTH_SOFT:
1118 case AL_SAMPLE_LENGTH_SOFT:
1119 case AL_SOURCE_RELATIVE:
1120 case AL_LOOPING:
1121 case AL_BUFFER:
1122 case AL_SAMPLE_OFFSET:
1123 case AL_BYTE_OFFSET:
1124 case AL_DIRECT_FILTER:
1125 case AL_DIRECT_FILTER_GAINHF_AUTO:
1126 case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO:
1127 case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO:
1128 case AL_DIRECT_CHANNELS_SOFT:
1129 case AL_DISTANCE_MODEL:
1130 case AL_SOURCE_RESAMPLER_SOFT:
1131 case AL_SOURCE_SPATIALIZE_SOFT:
1132 case AL_STEREO_MODE_SOFT:
1133 case AL_PANNING_ENABLED_SOFT:
1134 case AL_PAN_SOFT:
1135 return 1;
1137 case AL_SOURCE_RADIUS: /*AL_BYTE_RW_OFFSETS_SOFT:*/
1138 if(sBufferSubDataCompat)
1139 return 2;
1140 /*fall-through*/
1141 case AL_CONE_INNER_ANGLE:
1142 case AL_CONE_OUTER_ANGLE:
1143 case AL_PITCH:
1144 case AL_GAIN:
1145 case AL_MIN_GAIN:
1146 case AL_MAX_GAIN:
1147 case AL_REFERENCE_DISTANCE:
1148 case AL_ROLLOFF_FACTOR:
1149 case AL_CONE_OUTER_GAIN:
1150 case AL_MAX_DISTANCE:
1151 case AL_SEC_OFFSET:
1152 case AL_DOPPLER_FACTOR:
1153 case AL_CONE_OUTER_GAINHF:
1154 case AL_AIR_ABSORPTION_FACTOR:
1155 case AL_ROOM_ROLLOFF_FACTOR:
1156 case AL_SEC_LENGTH_SOFT:
1157 case AL_SUPER_STEREO_WIDTH_SOFT:
1158 return 1; /* 1x float */
1160 case AL_SAMPLE_RW_OFFSETS_SOFT:
1161 if(sBufferSubDataCompat)
1162 return 2;
1163 break;
1165 case AL_SAMPLE_OFFSET_LATENCY_SOFT:
1166 case AL_SAMPLE_OFFSET_CLOCK_SOFT:
1167 case AL_STEREO_ANGLES:
1168 return 2;
1170 case AL_AUXILIARY_SEND_FILTER:
1171 return 3;
1173 case AL_POSITION:
1174 case AL_VELOCITY:
1175 case AL_DIRECTION:
1176 return 3; /* 3x float */
1178 case AL_ORIENTATION:
1179 return 6; /* 6x float */
1181 case AL_SEC_OFFSET_LATENCY_SOFT:
1182 case AL_SEC_OFFSET_CLOCK_SOFT:
1183 break; /* double only */
1186 return 0;
1189 constexpr ALuint FloatValsByProp(ALenum prop)
1191 switch(static_cast<SourceProp>(prop))
1193 case AL_PITCH:
1194 case AL_GAIN:
1195 case AL_MIN_GAIN:
1196 case AL_MAX_GAIN:
1197 case AL_MAX_DISTANCE:
1198 case AL_ROLLOFF_FACTOR:
1199 case AL_DOPPLER_FACTOR:
1200 case AL_CONE_OUTER_GAIN:
1201 case AL_SEC_OFFSET:
1202 case AL_SAMPLE_OFFSET:
1203 case AL_BYTE_OFFSET:
1204 case AL_CONE_INNER_ANGLE:
1205 case AL_CONE_OUTER_ANGLE:
1206 case AL_REFERENCE_DISTANCE:
1207 case AL_CONE_OUTER_GAINHF:
1208 case AL_AIR_ABSORPTION_FACTOR:
1209 case AL_ROOM_ROLLOFF_FACTOR:
1210 case AL_DIRECT_FILTER_GAINHF_AUTO:
1211 case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO:
1212 case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO:
1213 case AL_DIRECT_CHANNELS_SOFT:
1214 case AL_DISTANCE_MODEL:
1215 case AL_SOURCE_RELATIVE:
1216 case AL_LOOPING:
1217 case AL_SOURCE_STATE:
1218 case AL_BUFFERS_QUEUED:
1219 case AL_BUFFERS_PROCESSED:
1220 case AL_SOURCE_TYPE:
1221 case AL_SOURCE_RESAMPLER_SOFT:
1222 case AL_SOURCE_SPATIALIZE_SOFT:
1223 case AL_BYTE_LENGTH_SOFT:
1224 case AL_SAMPLE_LENGTH_SOFT:
1225 case AL_SEC_LENGTH_SOFT:
1226 case AL_STEREO_MODE_SOFT:
1227 case AL_SUPER_STEREO_WIDTH_SOFT:
1228 case AL_PANNING_ENABLED_SOFT:
1229 case AL_PAN_SOFT:
1230 return 1;
1232 case AL_SOURCE_RADIUS: /*AL_BYTE_RW_OFFSETS_SOFT:*/
1233 if(!sBufferSubDataCompat)
1234 return 1;
1235 /*fall-through*/
1236 case AL_SAMPLE_RW_OFFSETS_SOFT:
1237 break;
1239 case AL_STEREO_ANGLES:
1240 return 2;
1242 case AL_POSITION:
1243 case AL_VELOCITY:
1244 case AL_DIRECTION:
1245 return 3;
1247 case AL_ORIENTATION:
1248 return 6;
1250 case AL_SEC_OFFSET_LATENCY_SOFT:
1251 case AL_SEC_OFFSET_CLOCK_SOFT:
1252 break; /* Double only */
1254 case AL_BUFFER:
1255 case AL_DIRECT_FILTER:
1256 case AL_AUXILIARY_SEND_FILTER:
1257 break; /* i/i64 only */
1258 case AL_SAMPLE_OFFSET_LATENCY_SOFT:
1259 case AL_SAMPLE_OFFSET_CLOCK_SOFT:
1260 break; /* i64 only */
1262 return 0;
1264 constexpr ALuint DoubleValsByProp(ALenum prop)
1266 switch(static_cast<SourceProp>(prop))
1268 case AL_PITCH:
1269 case AL_GAIN:
1270 case AL_MIN_GAIN:
1271 case AL_MAX_GAIN:
1272 case AL_MAX_DISTANCE:
1273 case AL_ROLLOFF_FACTOR:
1274 case AL_DOPPLER_FACTOR:
1275 case AL_CONE_OUTER_GAIN:
1276 case AL_SEC_OFFSET:
1277 case AL_SAMPLE_OFFSET:
1278 case AL_BYTE_OFFSET:
1279 case AL_CONE_INNER_ANGLE:
1280 case AL_CONE_OUTER_ANGLE:
1281 case AL_REFERENCE_DISTANCE:
1282 case AL_CONE_OUTER_GAINHF:
1283 case AL_AIR_ABSORPTION_FACTOR:
1284 case AL_ROOM_ROLLOFF_FACTOR:
1285 case AL_DIRECT_FILTER_GAINHF_AUTO:
1286 case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO:
1287 case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO:
1288 case AL_DIRECT_CHANNELS_SOFT:
1289 case AL_DISTANCE_MODEL:
1290 case AL_SOURCE_RELATIVE:
1291 case AL_LOOPING:
1292 case AL_SOURCE_STATE:
1293 case AL_BUFFERS_QUEUED:
1294 case AL_BUFFERS_PROCESSED:
1295 case AL_SOURCE_TYPE:
1296 case AL_SOURCE_RESAMPLER_SOFT:
1297 case AL_SOURCE_SPATIALIZE_SOFT:
1298 case AL_BYTE_LENGTH_SOFT:
1299 case AL_SAMPLE_LENGTH_SOFT:
1300 case AL_SEC_LENGTH_SOFT:
1301 case AL_STEREO_MODE_SOFT:
1302 case AL_SUPER_STEREO_WIDTH_SOFT:
1303 case AL_PANNING_ENABLED_SOFT:
1304 case AL_PAN_SOFT:
1305 return 1;
1307 case AL_SOURCE_RADIUS: /*AL_BYTE_RW_OFFSETS_SOFT:*/
1308 if(!sBufferSubDataCompat)
1309 return 1;
1310 /*fall-through*/
1311 case AL_SAMPLE_RW_OFFSETS_SOFT:
1312 break;
1314 case AL_SEC_OFFSET_LATENCY_SOFT:
1315 case AL_SEC_OFFSET_CLOCK_SOFT:
1316 case AL_STEREO_ANGLES:
1317 return 2;
1319 case AL_POSITION:
1320 case AL_VELOCITY:
1321 case AL_DIRECTION:
1322 return 3;
1324 case AL_ORIENTATION:
1325 return 6;
1327 case AL_BUFFER:
1328 case AL_DIRECT_FILTER:
1329 case AL_AUXILIARY_SEND_FILTER:
1330 break; /* i/i64 only */
1331 case AL_SAMPLE_OFFSET_LATENCY_SOFT:
1332 case AL_SAMPLE_OFFSET_CLOCK_SOFT:
1333 break; /* i64 only */
1335 return 0;
1339 void UpdateSourceProps(ALsource *source, ALCcontext *context)
1341 if(!context->mDeferUpdates)
1343 if(Voice *voice{GetSourceVoice(source, context)})
1345 UpdateSourceProps(source, voice, context);
1346 return;
1349 source->mPropsDirty = true;
1351 #ifdef ALSOFT_EAX
1352 void CommitAndUpdateSourceProps(ALsource *source, ALCcontext *context)
1354 if(!context->mDeferUpdates)
1356 if(context->hasEax())
1357 source->eaxCommit();
1358 if(Voice *voice{GetSourceVoice(source, context)})
1360 UpdateSourceProps(source, voice, context);
1361 return;
1364 source->mPropsDirty = true;
1367 #else
1369 inline void CommitAndUpdateSourceProps(ALsource *source, ALCcontext *context)
1370 { UpdateSourceProps(source, context); }
1371 #endif
1374 template<typename T>
1375 struct PropType { };
1376 template<>
1377 struct PropType<ALint> { static const char *Name() { return "integer"; } };
1378 template<>
1379 struct PropType<ALint64SOFT> { static const char *Name() { return "int64"; } };
1380 template<>
1381 struct PropType<ALfloat> { static const char *Name() { return "float"; } };
1382 template<>
1383 struct PropType<ALdouble> { static const char *Name() { return "double"; } };
1385 struct HexPrinter {
1386 std::array<char,32> mStr{};
1388 template<typename T>
1389 HexPrinter(T value)
1391 using ST = std::make_signed_t<std::remove_cv_t<T>>;
1392 if constexpr(std::is_same_v<ST,int>)
1393 std::snprintf(mStr.data(), mStr.size(), "0x%x", value);
1394 else if constexpr(std::is_same_v<ST,long>)
1395 std::snprintf(mStr.data(), mStr.size(), "0x%lx", value);
1396 else if constexpr(std::is_same_v<ST,long long>)
1397 std::snprintf(mStr.data(), mStr.size(), "0x%llx", value);
1400 [[nodiscard]] auto c_str() const noexcept -> const char* { return mStr.data(); }
1405 * Returns a pair of lambdas to check the following setter.
1407 * The first lambda checks the size of the span is valid for the required size,
1408 * setting the proper context error and throwing a check_size_exception if it
1409 * fails.
1411 * The second lambda tests the validity of the value check, setting the proper
1412 * context error and throwing a check_value_exception if it failed.
1414 template<typename T, size_t N>
1415 auto GetCheckers(const SourceProp prop, const al::span<T,N> values)
1417 return std::make_pair(
1418 [=](size_t expect) -> void
1420 if(values.size() == expect) return;
1421 throw al::context_error{AL_INVALID_ENUM,
1422 "Property 0x%04x expects %zu value(s), got %zu", prop, expect, values.size()};
1424 [](bool passed) -> void
1426 if(passed) return;
1427 throw al::context_error{AL_INVALID_VALUE, "Value out of range"};
1432 template<typename T>
1433 NOINLINE void SetProperty(ALsource *const Source, ALCcontext *const Context, const SourceProp prop,
1434 const al::span<const T> values)
1436 auto [CheckSize, CheckValue] = GetCheckers(prop, values);
1437 ALCdevice *device{Context->mALDevice.get()};
1439 switch(prop)
1441 case AL_SOURCE_STATE:
1442 case AL_SOURCE_TYPE:
1443 case AL_BUFFERS_QUEUED:
1444 case AL_BUFFERS_PROCESSED:
1445 if constexpr(std::is_integral_v<T>)
1447 /* Query only */
1448 throw al::context_error{AL_INVALID_OPERATION,
1449 "Setting read-only source property 0x%04x", prop};
1451 break;
1453 case AL_BYTE_LENGTH_SOFT:
1454 case AL_SAMPLE_LENGTH_SOFT:
1455 case AL_SEC_LENGTH_SOFT:
1456 case AL_SAMPLE_OFFSET_LATENCY_SOFT:
1457 case AL_SEC_OFFSET_LATENCY_SOFT:
1458 case AL_SAMPLE_OFFSET_CLOCK_SOFT:
1459 case AL_SEC_OFFSET_CLOCK_SOFT:
1460 /* Query only */
1461 throw al::context_error{AL_INVALID_OPERATION, "Setting read-only source property 0x%04x",
1462 prop};
1464 case AL_PITCH:
1465 CheckSize(1);
1466 CheckValue(values[0] >= T{0});
1468 Source->Pitch = static_cast<float>(values[0]);
1469 return UpdateSourceProps(Source, Context);
1471 case AL_CONE_INNER_ANGLE:
1472 CheckSize(1);
1473 CheckValue(values[0] >= T{0} && values[0] <= T{360});
1475 Source->InnerAngle = static_cast<float>(values[0]);
1476 return CommitAndUpdateSourceProps(Source, Context);
1478 case AL_CONE_OUTER_ANGLE:
1479 CheckSize(1);
1480 CheckValue(values[0] >= T{0} && values[0] <= T{360});
1482 Source->OuterAngle = static_cast<float>(values[0]);
1483 return CommitAndUpdateSourceProps(Source, Context);
1485 case AL_GAIN:
1486 CheckSize(1);
1487 CheckValue(values[0] >= T{0});
1489 Source->Gain = static_cast<float>(values[0]);
1490 return UpdateSourceProps(Source, Context);
1492 case AL_MAX_DISTANCE:
1493 CheckSize(1);
1494 CheckValue(values[0] >= T{0});
1496 Source->MaxDistance = static_cast<float>(values[0]);
1497 return CommitAndUpdateSourceProps(Source, Context);
1499 case AL_ROLLOFF_FACTOR:
1500 CheckSize(1);
1501 CheckValue(values[0] >= T{0});
1503 Source->RolloffFactor = static_cast<float>(values[0]);
1504 return CommitAndUpdateSourceProps(Source, Context);
1506 case AL_REFERENCE_DISTANCE:
1507 CheckSize(1);
1508 CheckValue(values[0] >= T{0});
1510 Source->RefDistance = static_cast<float>(values[0]);
1511 return CommitAndUpdateSourceProps(Source, Context);
1513 case AL_MIN_GAIN:
1514 CheckSize(1);
1515 CheckValue(values[0] >= T{0});
1517 Source->MinGain = static_cast<float>(values[0]);
1518 return UpdateSourceProps(Source, Context);
1520 case AL_MAX_GAIN:
1521 CheckSize(1);
1522 CheckValue(values[0] >= T{0});
1524 Source->MaxGain = static_cast<float>(values[0]);
1525 return UpdateSourceProps(Source, Context);
1527 case AL_CONE_OUTER_GAIN:
1528 CheckSize(1);
1529 CheckValue(values[0] >= T{0} && values[0] <= T{1});
1531 Source->OuterGain = static_cast<float>(values[0]);
1532 return UpdateSourceProps(Source, Context);
1534 case AL_CONE_OUTER_GAINHF:
1535 CheckSize(1);
1536 CheckValue(values[0] >= T{0} && values[0] <= T{1});
1538 Source->OuterGainHF = static_cast<float>(values[0]);
1539 return UpdateSourceProps(Source, Context);
1541 case AL_AIR_ABSORPTION_FACTOR:
1542 CheckSize(1);
1543 CheckValue(values[0] >= T{0} && values[0] <= T{10});
1545 Source->AirAbsorptionFactor = static_cast<float>(values[0]);
1546 return UpdateSourceProps(Source, Context);
1548 case AL_ROOM_ROLLOFF_FACTOR:
1549 CheckSize(1);
1550 CheckValue(values[0] >= T{0} && values[0] <= T{1});
1552 Source->RoomRolloffFactor = static_cast<float>(values[0]);
1553 return UpdateSourceProps(Source, Context);
1555 case AL_DOPPLER_FACTOR:
1556 CheckSize(1);
1557 CheckValue(values[0] >= T{0} && values[0] <= T{1});
1559 Source->DopplerFactor = static_cast<float>(values[0]);
1560 return UpdateSourceProps(Source, Context);
1563 case AL_SOURCE_RELATIVE:
1564 if constexpr(std::is_integral_v<T>)
1566 CheckSize(1);
1567 CheckValue(values[0] == AL_FALSE || values[0] == AL_TRUE);
1569 Source->HeadRelative = values[0] != AL_FALSE;
1570 return CommitAndUpdateSourceProps(Source, Context);
1572 break;
1574 case AL_LOOPING:
1575 if constexpr(std::is_integral_v<T>)
1577 CheckSize(1);
1578 CheckValue(values[0] == AL_FALSE || values[0] == AL_TRUE);
1580 Source->Looping = values[0] != AL_FALSE;
1581 if(Voice *voice{GetSourceVoice(Source, Context)})
1583 if(Source->Looping)
1584 voice->mLoopBuffer.store(&Source->mQueue.front(), std::memory_order_release);
1585 else
1586 voice->mLoopBuffer.store(nullptr, std::memory_order_release);
1588 /* If the source is playing, wait for the current mix to finish
1589 * to ensure it isn't currently looping back or reaching the
1590 * end.
1592 std::ignore = device->waitForMix();
1594 return;
1596 break;
1598 case AL_BUFFER:
1599 if constexpr(std::is_integral_v<T>)
1601 CheckSize(1);
1602 if(const ALenum state{GetSourceState(Source, GetSourceVoice(Source, Context))};
1603 state == AL_PLAYING || state == AL_PAUSED)
1604 throw al::context_error{AL_INVALID_OPERATION,
1605 "Setting buffer on playing or paused source %u", Source->id};
1607 std::deque<ALbufferQueueItem> oldlist;
1608 if(values[0])
1610 using UT = std::make_unsigned_t<T>;
1611 std::lock_guard<std::mutex> buflock{device->BufferLock};
1612 ALbuffer *buffer{LookupBuffer(device, static_cast<UT>(values[0]))};
1613 if(!buffer)
1614 throw al::context_error{AL_INVALID_VALUE, "Invalid buffer ID %s",
1615 std::to_string(values[0]).c_str()};
1616 if(buffer->MappedAccess && !(buffer->MappedAccess&AL_MAP_PERSISTENT_BIT_SOFT))
1617 throw al::context_error{AL_INVALID_OPERATION,
1618 "Setting non-persistently mapped buffer %u", buffer->id};
1619 if(buffer->mCallback && buffer->ref.load(std::memory_order_relaxed) != 0)
1620 throw al::context_error{AL_INVALID_OPERATION,
1621 "Setting already-set callback buffer %u", buffer->id};
1623 /* Add the selected buffer to a one-item queue */
1624 std::deque<ALbufferQueueItem> newlist;
1625 newlist.emplace_back();
1626 newlist.back().mCallback = buffer->mCallback;
1627 newlist.back().mUserData = buffer->mUserData;
1628 newlist.back().mBlockAlign = buffer->mBlockAlign;
1629 newlist.back().mSampleLen = buffer->mSampleLen;
1630 newlist.back().mLoopStart = buffer->mLoopStart;
1631 newlist.back().mLoopEnd = buffer->mLoopEnd;
1632 newlist.back().mSamples = buffer->mData;
1633 newlist.back().mBuffer = buffer;
1634 IncrementRef(buffer->ref);
1636 /* Source is now Static */
1637 Source->SourceType = AL_STATIC;
1638 Source->mQueue.swap(oldlist);
1639 Source->mQueue.swap(newlist);
1641 else
1643 /* Source is now Undetermined */
1644 Source->SourceType = AL_UNDETERMINED;
1645 Source->mQueue.swap(oldlist);
1648 /* Delete all elements in the previous queue */
1649 for(auto &item : oldlist)
1651 if(ALbuffer *buffer{item.mBuffer})
1652 DecrementRef(buffer->ref);
1654 return;
1656 break;
1659 case AL_SEC_OFFSET:
1660 case AL_SAMPLE_OFFSET:
1661 case AL_BYTE_OFFSET:
1662 CheckSize(1);
1663 if constexpr(std::is_floating_point_v<T>)
1664 CheckValue(std::isfinite(values[0]));
1666 if(Voice *voice{GetSourceVoice(Source, Context)})
1668 auto vpos = GetSampleOffset(Source->mQueue, prop, static_cast<double>(values[0]));
1669 if(!vpos) throw al::context_error{AL_INVALID_VALUE, "Invalid offset"};
1671 if(SetVoiceOffset(voice, *vpos, Source, Context, Context->mALDevice.get()))
1672 return;
1674 Source->OffsetType = prop;
1675 Source->Offset = static_cast<double>(values[0]);
1676 return;
1678 case AL_SAMPLE_RW_OFFSETS_SOFT:
1679 if(sBufferSubDataCompat)
1681 if constexpr(std::is_integral_v<T>)
1683 /* Query only */
1684 throw al::context_error{AL_INVALID_OPERATION,
1685 "Setting read-only source property 0x%04x", prop};
1688 break;
1690 case AL_SOURCE_RADIUS: /*AL_BYTE_RW_OFFSETS_SOFT:*/
1691 if(sBufferSubDataCompat)
1693 if constexpr(std::is_integral_v<T>)
1695 /* Query only */
1696 throw al::context_error{AL_INVALID_OPERATION,
1697 "Setting read-only source property 0x%04x", prop};
1699 break;
1701 CheckSize(1);
1702 if constexpr(std::is_floating_point_v<T>)
1703 CheckValue(values[0] >= T{0} && std::isfinite(static_cast<float>(values[0])));
1704 else
1705 CheckValue(values[0] >= T{0});
1707 Source->Radius = static_cast<float>(values[0]);
1708 return UpdateSourceProps(Source, Context);
1710 case AL_SUPER_STEREO_WIDTH_SOFT:
1711 CheckSize(1);
1712 CheckValue(values[0] >= T{0} && values[0] <= T{1});
1714 Source->EnhWidth = static_cast<float>(values[0]);
1715 return UpdateSourceProps(Source, Context);
1717 case AL_PANNING_ENABLED_SOFT:
1718 CheckSize(1);
1719 if(const ALenum state{GetSourceState(Source, GetSourceVoice(Source, Context))};
1720 state == AL_PLAYING || state == AL_PAUSED)
1721 throw al::context_error{AL_INVALID_OPERATION,
1722 "Modifying panning enabled on playing or paused source %u", Source->id};
1724 CheckValue(values[0] == AL_FALSE || values[0] == AL_TRUE);
1726 Source->mPanningEnabled = values[0] != AL_FALSE;
1727 return UpdateSourceProps(Source, Context);
1729 case AL_PAN_SOFT:
1730 CheckSize(1);
1731 CheckValue(values[0] >= T{-1} && values[0] <= T{1});
1733 Source->mPan = static_cast<float>(values[0]);
1734 return UpdateSourceProps(Source, Context);
1736 case AL_STEREO_ANGLES:
1737 CheckSize(2);
1738 if constexpr(std::is_floating_point_v<T>)
1739 CheckValue(std::isfinite(static_cast<float>(values[0]))
1740 && std::isfinite(static_cast<float>(values[1])));
1742 Source->StereoPan[0] = static_cast<float>(values[0]);
1743 Source->StereoPan[1] = static_cast<float>(values[1]);
1744 return UpdateSourceProps(Source, Context);
1747 case AL_POSITION:
1748 CheckSize(3);
1749 if constexpr(std::is_floating_point_v<T>)
1750 CheckValue(std::isfinite(static_cast<float>(values[0]))
1751 && std::isfinite(static_cast<float>(values[1]))
1752 && std::isfinite(static_cast<float>(values[2])));
1754 Source->Position[0] = static_cast<float>(values[0]);
1755 Source->Position[1] = static_cast<float>(values[1]);
1756 Source->Position[2] = static_cast<float>(values[2]);
1757 return CommitAndUpdateSourceProps(Source, Context);
1759 case AL_VELOCITY:
1760 CheckSize(3);
1761 if constexpr(std::is_floating_point_v<T>)
1762 CheckValue(std::isfinite(static_cast<float>(values[0]))
1763 && std::isfinite(static_cast<float>(values[1]))
1764 && std::isfinite(static_cast<float>(values[2])));
1766 Source->Velocity[0] = static_cast<float>(values[0]);
1767 Source->Velocity[1] = static_cast<float>(values[1]);
1768 Source->Velocity[2] = static_cast<float>(values[2]);
1769 return CommitAndUpdateSourceProps(Source, Context);
1771 case AL_DIRECTION:
1772 CheckSize(3);
1773 if constexpr(std::is_floating_point_v<T>)
1774 CheckValue(std::isfinite(static_cast<float>(values[0]))
1775 && std::isfinite(static_cast<float>(values[1]))
1776 && std::isfinite(static_cast<float>(values[2])));
1778 Source->Direction[0] = static_cast<float>(values[0]);
1779 Source->Direction[1] = static_cast<float>(values[1]);
1780 Source->Direction[2] = static_cast<float>(values[2]);
1781 return CommitAndUpdateSourceProps(Source, Context);
1783 case AL_ORIENTATION:
1784 CheckSize(6);
1785 if constexpr(std::is_floating_point_v<T>)
1786 CheckValue(std::isfinite(static_cast<float>(values[0]))
1787 && std::isfinite(static_cast<float>(values[1]))
1788 && std::isfinite(static_cast<float>(values[2]))
1789 && std::isfinite(static_cast<float>(values[3]))
1790 && std::isfinite(static_cast<float>(values[4]))
1791 && std::isfinite(static_cast<float>(values[5])));
1793 Source->OrientAt[0] = static_cast<float>(values[0]);
1794 Source->OrientAt[1] = static_cast<float>(values[1]);
1795 Source->OrientAt[2] = static_cast<float>(values[2]);
1796 Source->OrientUp[0] = static_cast<float>(values[3]);
1797 Source->OrientUp[1] = static_cast<float>(values[4]);
1798 Source->OrientUp[2] = static_cast<float>(values[5]);
1799 return UpdateSourceProps(Source, Context);
1802 case AL_DIRECT_FILTER:
1803 if constexpr(std::is_integral_v<T>)
1805 CheckSize(1);
1806 const auto filterid = static_cast<std::make_unsigned_t<T>>(values[0]);
1807 if(values[0])
1809 std::lock_guard<std::mutex> filterlock{device->FilterLock};
1810 ALfilter *filter{LookupFilter(device, filterid)};
1811 if(!filter)
1812 throw al::context_error{AL_INVALID_VALUE, "Invalid filter ID %s",
1813 std::to_string(filterid).c_str()};
1814 Source->Direct.Gain = filter->Gain;
1815 Source->Direct.GainHF = filter->GainHF;
1816 Source->Direct.HFReference = filter->HFReference;
1817 Source->Direct.GainLF = filter->GainLF;
1818 Source->Direct.LFReference = filter->LFReference;
1820 else
1822 Source->Direct.Gain = 1.0f;
1823 Source->Direct.GainHF = 1.0f;
1824 Source->Direct.HFReference = LowPassFreqRef;
1825 Source->Direct.GainLF = 1.0f;
1826 Source->Direct.LFReference = HighPassFreqRef;
1828 return UpdateSourceProps(Source, Context);
1830 break;
1832 case AL_DIRECT_FILTER_GAINHF_AUTO:
1833 if constexpr(std::is_integral_v<T>)
1835 CheckSize(1);
1836 CheckValue(values[0] == AL_FALSE || values[0] == AL_TRUE);
1838 Source->DryGainHFAuto = values[0] != AL_FALSE;
1839 return UpdateSourceProps(Source, Context);
1841 break;
1843 case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO:
1844 if constexpr(std::is_integral_v<T>)
1846 CheckSize(1);
1847 CheckValue(values[0] == AL_FALSE || values[0] == AL_TRUE);
1849 Source->WetGainAuto = values[0] != AL_FALSE;
1850 return UpdateSourceProps(Source, Context);
1852 break;
1854 case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO:
1855 if constexpr(std::is_integral_v<T>)
1857 CheckSize(1);
1858 CheckValue(values[0] == AL_FALSE || values[0] == AL_TRUE);
1860 Source->WetGainHFAuto = values[0] != AL_FALSE;
1861 return UpdateSourceProps(Source, Context);
1863 break;
1865 case AL_DIRECT_CHANNELS_SOFT:
1866 if constexpr(std::is_integral_v<T>)
1868 CheckSize(1);
1869 if(auto mode = DirectModeFromEnum(values[0]))
1871 Source->DirectChannels = *mode;
1872 return UpdateSourceProps(Source, Context);
1874 throw al::context_error{AL_INVALID_VALUE, "Invalid direct channels mode: %s\n",
1875 HexPrinter{values[0]}.c_str()};
1877 break;
1879 case AL_DISTANCE_MODEL:
1880 if constexpr(std::is_integral_v<T>)
1882 CheckSize(1);
1883 if(auto model = DistanceModelFromALenum(values[0]))
1885 Source->mDistanceModel = *model;
1886 if(Context->mSourceDistanceModel)
1887 UpdateSourceProps(Source, Context);
1888 return;
1890 throw al::context_error{AL_INVALID_VALUE, "Invalid distance model: %s\n",
1891 HexPrinter{values[0]}.c_str()};
1893 break;
1895 case AL_SOURCE_RESAMPLER_SOFT:
1896 if constexpr(std::is_integral_v<T>)
1898 CheckSize(1);
1899 CheckValue(values[0] >= 0 && values[0] <= static_cast<int>(Resampler::Max));
1901 Source->mResampler = static_cast<Resampler>(values[0]);
1902 return UpdateSourceProps(Source, Context);
1904 break;
1906 case AL_SOURCE_SPATIALIZE_SOFT:
1907 if constexpr(std::is_integral_v<T>)
1909 CheckSize(1);
1910 if(auto mode = SpatializeModeFromEnum(values[0]))
1912 Source->mSpatialize = *mode;
1913 return UpdateSourceProps(Source, Context);
1915 throw al::context_error{AL_INVALID_VALUE, "Invalid source spatialize mode: %s\n",
1916 HexPrinter{values[0]}.c_str()};
1918 break;
1920 case AL_STEREO_MODE_SOFT:
1921 if constexpr(std::is_integral_v<T>)
1923 CheckSize(1);
1924 if(const ALenum state{GetSourceState(Source, GetSourceVoice(Source, Context))};
1925 state == AL_PLAYING || state == AL_PAUSED)
1926 throw al::context_error{AL_INVALID_OPERATION,
1927 "Modifying stereo mode on playing or paused source %u", Source->id};
1929 if(auto mode = StereoModeFromEnum(values[0]))
1931 Source->mStereoMode = *mode;
1932 return;
1934 throw al::context_error{AL_INVALID_VALUE, "Invalid stereo mode: %s\n",
1935 HexPrinter{values[0]}.c_str()};
1937 break;
1939 case AL_AUXILIARY_SEND_FILTER:
1940 if constexpr(std::is_integral_v<T>)
1942 CheckSize(3);
1943 const auto slotid = static_cast<std::make_unsigned_t<T>>(values[0]);
1944 const auto sendidx = static_cast<std::make_unsigned_t<T>>(values[1]);
1945 const auto filterid = static_cast<std::make_unsigned_t<T>>(values[2]);
1947 std::unique_lock slotlock{Context->mEffectSlotLock};
1948 ALeffectslot *slot{};
1949 if(values[0])
1951 slot = LookupEffectSlot(Context, slotid);
1952 if(!slot)
1953 throw al::context_error{AL_INVALID_VALUE, "Invalid effect ID %s",
1954 std::to_string(slotid).c_str()};
1957 if(sendidx >= device->NumAuxSends)
1958 throw al::context_error{AL_INVALID_VALUE, "Invalid send %s",
1959 std::to_string(sendidx).c_str()};
1960 auto &send = Source->Send[static_cast<size_t>(sendidx)];
1962 if(values[2])
1964 std::lock_guard<std::mutex> filterlock{device->FilterLock};
1965 ALfilter *filter{LookupFilter(device, filterid)};
1966 if(!filter)
1967 throw al::context_error{AL_INVALID_VALUE, "Invalid filter ID %s",
1968 std::to_string(filterid).c_str()};
1970 send.Gain = filter->Gain;
1971 send.GainHF = filter->GainHF;
1972 send.HFReference = filter->HFReference;
1973 send.GainLF = filter->GainLF;
1974 send.LFReference = filter->LFReference;
1976 else
1978 /* Disable filter */
1979 send.Gain = 1.0f;
1980 send.GainHF = 1.0f;
1981 send.HFReference = LowPassFreqRef;
1982 send.GainLF = 1.0f;
1983 send.LFReference = HighPassFreqRef;
1986 /* We must force an update if the current auxiliary slot is valid
1987 * and about to be changed on an active source, in case the old
1988 * slot is about to be deleted.
1990 if(send.Slot && slot != send.Slot && IsPlayingOrPaused(Source))
1992 /* Add refcount on the new slot, and release the previous slot */
1993 if(slot) IncrementRef(slot->ref);
1994 if(auto *oldslot = send.Slot)
1995 DecrementRef(oldslot->ref);
1996 send.Slot = slot;
1998 Voice *voice{GetSourceVoice(Source, Context)};
1999 if(voice) UpdateSourceProps(Source, voice, Context);
2000 else Source->mPropsDirty = true;
2002 else
2004 if(slot) IncrementRef(slot->ref);
2005 if(auto *oldslot = send.Slot)
2006 DecrementRef(oldslot->ref);
2007 send.Slot = slot;
2008 UpdateSourceProps(Source, Context);
2010 return;
2012 break;
2015 ERR("Unexpected %s property: 0x%04x\n", PropType<T>::Name(), prop);
2016 throw al::context_error{AL_INVALID_ENUM, "Invalid source %s property 0x%04x",
2017 PropType<T>::Name(), prop};
2021 template<typename T, size_t N>
2022 auto GetSizeChecker(const SourceProp prop, const al::span<T,N> values)
2024 return [=](size_t expect) -> void
2026 if(values.size() == expect) LIKELY return;
2027 throw al::context_error{AL_INVALID_ENUM, "Property 0x%04x expects %zu value(s), got %zu",
2028 prop, expect, values.size()};
2032 template<typename T>
2033 NOINLINE void GetProperty(ALsource *const Source, ALCcontext *const Context, const SourceProp prop,
2034 const al::span<T> values)
2036 using std::chrono::duration_cast;
2037 auto CheckSize = GetSizeChecker(prop, values);
2038 ALCdevice *device{Context->mALDevice.get()};
2040 switch(prop)
2042 case AL_GAIN:
2043 CheckSize(1);
2044 values[0] = static_cast<T>(Source->Gain);
2045 return;
2047 case AL_PITCH:
2048 CheckSize(1);
2049 values[0] = static_cast<T>(Source->Pitch);
2050 return;
2052 case AL_MAX_DISTANCE:
2053 CheckSize(1);
2054 values[0] = static_cast<T>(Source->MaxDistance);
2055 return;
2057 case AL_ROLLOFF_FACTOR:
2058 CheckSize(1);
2059 values[0] = static_cast<T>(Source->RolloffFactor);
2060 return;
2062 case AL_REFERENCE_DISTANCE:
2063 CheckSize(1);
2064 values[0] = static_cast<T>(Source->RefDistance);
2065 return;
2067 case AL_CONE_INNER_ANGLE:
2068 CheckSize(1);
2069 values[0] = static_cast<T>(Source->InnerAngle);
2070 return;
2072 case AL_CONE_OUTER_ANGLE:
2073 CheckSize(1);
2074 values[0] = static_cast<T>(Source->OuterAngle);
2075 return;
2077 case AL_MIN_GAIN:
2078 CheckSize(1);
2079 values[0] = static_cast<T>(Source->MinGain);
2080 return;
2082 case AL_MAX_GAIN:
2083 CheckSize(1);
2084 values[0] = static_cast<T>(Source->MaxGain);
2085 return;
2087 case AL_CONE_OUTER_GAIN:
2088 CheckSize(1);
2089 values[0] = static_cast<T>(Source->OuterGain);
2090 return;
2092 case AL_SEC_OFFSET:
2093 case AL_SAMPLE_OFFSET:
2094 case AL_BYTE_OFFSET:
2095 CheckSize(1);
2096 values[0] = GetSourceOffset<T>(Source, prop, Context);
2097 return;
2099 case AL_CONE_OUTER_GAINHF:
2100 CheckSize(1);
2101 values[0] = static_cast<T>(Source->OuterGainHF);
2102 return;
2104 case AL_AIR_ABSORPTION_FACTOR:
2105 CheckSize(1);
2106 values[0] = static_cast<T>(Source->AirAbsorptionFactor);
2107 return;
2109 case AL_ROOM_ROLLOFF_FACTOR:
2110 CheckSize(1);
2111 values[0] = static_cast<T>(Source->RoomRolloffFactor);
2112 return;
2114 case AL_DOPPLER_FACTOR:
2115 CheckSize(1);
2116 values[0] = static_cast<T>(Source->DopplerFactor);
2117 return;
2119 case AL_SAMPLE_RW_OFFSETS_SOFT:
2120 if constexpr(std::is_integral_v<T>)
2122 if(sBufferSubDataCompat)
2124 CheckSize(2);
2125 values[0] = GetSourceOffset<T>(Source, AL_SAMPLE_OFFSET, Context);
2126 /* FIXME: values[1] should be ahead of values[0] by the device
2127 * update time. It needs to clamp or wrap the length of the
2128 * buffer queue.
2130 values[1] = values[0];
2131 return;
2134 break;
2135 case AL_SOURCE_RADIUS: /*AL_BYTE_RW_OFFSETS_SOFT:*/
2136 if constexpr(std::is_floating_point_v<T>)
2138 if(sBufferSubDataCompat)
2139 break;
2141 CheckSize(1);
2142 values[0] = static_cast<T>(Source->Radius);
2143 return;
2145 else
2147 if(sBufferSubDataCompat)
2149 CheckSize(2);
2150 values[0] = GetSourceOffset<T>(Source, AL_BYTE_OFFSET, Context);
2151 /* FIXME: values[1] should be ahead of values[0] by the device
2152 * update time. It needs to clamp or wrap the length of the
2153 * buffer queue.
2155 values[1] = values[0];
2156 return;
2158 break;
2161 case AL_SUPER_STEREO_WIDTH_SOFT:
2162 CheckSize(1);
2163 values[0] = static_cast<T>(Source->EnhWidth);
2164 return;
2166 case AL_BYTE_LENGTH_SOFT:
2167 case AL_SAMPLE_LENGTH_SOFT:
2168 case AL_SEC_LENGTH_SOFT:
2169 CheckSize(1);
2170 values[0] = GetSourceLength<T>(Source, prop);
2171 return;
2173 case AL_PANNING_ENABLED_SOFT:
2174 CheckSize(1);
2175 values[0] = Source->mPanningEnabled;
2176 return;
2178 case AL_PAN_SOFT:
2179 CheckSize(1);
2180 values[0] = static_cast<T>(Source->mPan);
2181 return;
2183 case AL_STEREO_ANGLES:
2184 if constexpr(std::is_floating_point_v<T>)
2186 CheckSize(2);
2187 values[0] = static_cast<T>(Source->StereoPan[0]);
2188 values[1] = static_cast<T>(Source->StereoPan[1]);
2189 return;
2191 break;
2193 case AL_SAMPLE_OFFSET_LATENCY_SOFT:
2194 if constexpr(std::is_same_v<T,int64_t>)
2196 CheckSize(2);
2197 /* Get the source offset with the clock time first. Then get the
2198 * clock time with the device latency. Order is important.
2200 ClockLatency clocktime{};
2201 nanoseconds srcclock{};
2202 values[0] = GetSourceSampleOffset(Source, Context, &srcclock);
2204 std::lock_guard<std::mutex> statelock{device->StateLock};
2205 clocktime = GetClockLatency(device, device->Backend.get());
2207 if(srcclock == clocktime.ClockTime)
2208 values[1] = nanoseconds{clocktime.Latency}.count();
2209 else
2211 /* If the clock time incremented, reduce the latency by that
2212 * much since it's that much closer to the source offset it got
2213 * earlier.
2215 const auto diff = std::min(clocktime.Latency, clocktime.ClockTime-srcclock);
2216 values[1] = nanoseconds{clocktime.Latency - diff}.count();
2218 return;
2220 break;
2222 case AL_SAMPLE_OFFSET_CLOCK_SOFT:
2223 if constexpr(std::is_same_v<T,int64_t>)
2225 CheckSize(2);
2226 nanoseconds srcclock{};
2227 values[0] = GetSourceSampleOffset(Source, Context, &srcclock);
2228 values[1] = srcclock.count();
2229 return;
2231 break;
2233 case AL_SEC_OFFSET_LATENCY_SOFT:
2234 if constexpr(std::is_same_v<T,double>)
2236 CheckSize(2);
2237 /* Get the source offset with the clock time first. Then get the
2238 * clock time with the device latency. Order is important.
2240 ClockLatency clocktime{};
2241 nanoseconds srcclock{};
2242 values[0] = GetSourceSecOffset(Source, Context, &srcclock);
2244 std::lock_guard<std::mutex> statelock{device->StateLock};
2245 clocktime = GetClockLatency(device, device->Backend.get());
2247 if(srcclock == clocktime.ClockTime)
2248 values[1] = duration_cast<seconds_d>(clocktime.Latency).count();
2249 else
2251 /* If the clock time incremented, reduce the latency by that
2252 * much since it's that much closer to the source offset it got
2253 * earlier.
2255 const auto diff = std::min(clocktime.Latency, clocktime.ClockTime-srcclock);
2256 values[1] = duration_cast<seconds_d>(clocktime.Latency - diff).count();
2258 return;
2260 break;
2262 case AL_SEC_OFFSET_CLOCK_SOFT:
2263 if constexpr(std::is_same_v<T,double>)
2265 CheckSize(2);
2266 nanoseconds srcclock{};
2267 values[0] = GetSourceSecOffset(Source, Context, &srcclock);
2268 values[1] = duration_cast<seconds_d>(srcclock).count();
2269 return;
2271 break;
2273 case AL_POSITION:
2274 CheckSize(3);
2275 values[0] = static_cast<T>(Source->Position[0]);
2276 values[1] = static_cast<T>(Source->Position[1]);
2277 values[2] = static_cast<T>(Source->Position[2]);
2278 return;
2280 case AL_VELOCITY:
2281 CheckSize(3);
2282 values[0] = static_cast<T>(Source->Velocity[0]);
2283 values[1] = static_cast<T>(Source->Velocity[1]);
2284 values[2] = static_cast<T>(Source->Velocity[2]);
2285 return;
2287 case AL_DIRECTION:
2288 CheckSize(3);
2289 values[0] = static_cast<T>(Source->Direction[0]);
2290 values[1] = static_cast<T>(Source->Direction[1]);
2291 values[2] = static_cast<T>(Source->Direction[2]);
2292 return;
2294 case AL_ORIENTATION:
2295 CheckSize(6);
2296 values[0] = static_cast<T>(Source->OrientAt[0]);
2297 values[1] = static_cast<T>(Source->OrientAt[1]);
2298 values[2] = static_cast<T>(Source->OrientAt[2]);
2299 values[3] = static_cast<T>(Source->OrientUp[0]);
2300 values[4] = static_cast<T>(Source->OrientUp[1]);
2301 values[5] = static_cast<T>(Source->OrientUp[2]);
2302 return;
2305 case AL_SOURCE_RELATIVE:
2306 if constexpr(std::is_integral_v<T>)
2308 CheckSize(1);
2309 values[0] = Source->HeadRelative;
2310 return;
2312 break;
2314 case AL_LOOPING:
2315 if constexpr(std::is_integral_v<T>)
2317 CheckSize(1);
2318 values[0] = Source->Looping;
2319 return;
2321 break;
2323 case AL_BUFFER:
2324 if constexpr(std::is_integral_v<T>)
2326 CheckSize(1);
2327 ALbufferQueueItem *BufferList{};
2328 /* HACK: This query should technically only return the buffer set
2329 * on a static source. However, some apps had used it to detect
2330 * when a streaming source changed buffers, so report the current
2331 * buffer's ID when playing.
2333 if(Source->SourceType == AL_STATIC || Source->state == AL_INITIAL)
2335 if(!Source->mQueue.empty())
2336 BufferList = &Source->mQueue.front();
2338 else if(Voice *voice{GetSourceVoice(Source, Context)})
2340 VoiceBufferItem *Current{voice->mCurrentBuffer.load(std::memory_order_relaxed)};
2341 BufferList = static_cast<ALbufferQueueItem*>(Current);
2343 ALbuffer *buffer{BufferList ? BufferList->mBuffer : nullptr};
2344 values[0] = buffer ? static_cast<T>(buffer->id) : T{0};
2345 return;
2347 break;
2349 case AL_SOURCE_STATE:
2350 if constexpr(std::is_integral_v<T>)
2352 CheckSize(1);
2353 values[0] = GetSourceState(Source, GetSourceVoice(Source, Context));
2354 return;
2356 break;
2358 case AL_BUFFERS_QUEUED:
2359 if constexpr(std::is_integral_v<T>)
2361 CheckSize(1);
2362 values[0] = static_cast<T>(Source->mQueue.size());
2363 return;
2365 break;
2367 case AL_BUFFERS_PROCESSED:
2368 if constexpr(std::is_integral_v<T>)
2370 CheckSize(1);
2371 if(Source->Looping || Source->SourceType != AL_STREAMING)
2373 /* Buffers on a looping source are in a perpetual state of
2374 * PENDING, so don't report any as PROCESSED
2376 values[0] = 0;
2378 else
2380 int played{0};
2381 if(Source->state != AL_INITIAL)
2383 const VoiceBufferItem *Current{nullptr};
2384 if(Voice *voice{GetSourceVoice(Source, Context)})
2385 Current = voice->mCurrentBuffer.load(std::memory_order_relaxed);
2386 for(auto &item : Source->mQueue)
2388 if(&item == Current)
2389 break;
2390 ++played;
2393 values[0] = played;
2395 return;
2397 break;
2399 case AL_SOURCE_TYPE:
2400 if constexpr(std::is_integral_v<T>)
2402 CheckSize(1);
2403 values[0] = Source->SourceType;
2404 return;
2406 break;
2408 case AL_DIRECT_FILTER_GAINHF_AUTO:
2409 if constexpr(std::is_integral_v<T>)
2411 CheckSize(1);
2412 values[0] = Source->DryGainHFAuto;
2413 return;
2415 break;
2417 case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO:
2418 if constexpr(std::is_integral_v<T>)
2420 CheckSize(1);
2421 values[0] = Source->WetGainAuto;
2422 return;
2424 break;
2426 case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO:
2427 if constexpr(std::is_integral_v<T>)
2429 CheckSize(1);
2430 values[0] = Source->WetGainHFAuto;
2431 return;
2433 break;
2435 case AL_DIRECT_CHANNELS_SOFT:
2436 if constexpr(std::is_integral_v<T>)
2438 CheckSize(1);
2439 values[0] = EnumFromDirectMode(Source->DirectChannels);
2440 return;
2442 break;
2444 case AL_DISTANCE_MODEL:
2445 if constexpr(std::is_integral_v<T>)
2447 CheckSize(1);
2448 values[0] = ALenumFromDistanceModel(Source->mDistanceModel);
2449 return;
2451 break;
2453 case AL_SOURCE_RESAMPLER_SOFT:
2454 if constexpr(std::is_integral_v<T>)
2456 CheckSize(1);
2457 values[0] = static_cast<T>(Source->mResampler);
2458 return;
2460 break;
2462 case AL_SOURCE_SPATIALIZE_SOFT:
2463 if constexpr(std::is_integral_v<T>)
2465 CheckSize(1);
2466 values[0] = EnumFromSpatializeMode(Source->mSpatialize);
2467 return;
2469 break;
2471 case AL_STEREO_MODE_SOFT:
2472 if constexpr(std::is_integral_v<T>)
2474 CheckSize(1);
2475 values[0] = EnumFromStereoMode(Source->mStereoMode);
2476 return;
2478 break;
2480 case AL_DIRECT_FILTER:
2481 case AL_AUXILIARY_SEND_FILTER:
2482 break;
2485 ERR("Unexpected %s query property: 0x%04x\n", PropType<T>::Name(), prop);
2486 throw al::context_error{AL_INVALID_ENUM, "Invalid source %s query property 0x%04x",
2487 PropType<T>::Name(), prop};
2491 void StartSources(ALCcontext *const context, const al::span<ALsource*> srchandles,
2492 const nanoseconds start_time=nanoseconds::min())
2494 ALCdevice *device{context->mALDevice.get()};
2495 /* If the device is disconnected, and voices stop on disconnect, go right
2496 * to stopped.
2498 if(!device->Connected.load(std::memory_order_acquire)) UNLIKELY
2500 if(context->mStopVoicesOnDisconnect.load(std::memory_order_acquire))
2502 for(ALsource *source : srchandles)
2504 /* TODO: Send state change event? */
2505 source->Offset = 0.0;
2506 source->OffsetType = AL_NONE;
2507 source->state = AL_STOPPED;
2509 return;
2513 /* Count the number of reusable voices. */
2514 auto voicelist = context->getVoicesSpan();
2515 size_t free_voices{0};
2516 for(const Voice *voice : voicelist)
2518 free_voices += (voice->mPlayState.load(std::memory_order_acquire) == Voice::Stopped
2519 && voice->mSourceID.load(std::memory_order_relaxed) == 0u
2520 && voice->mPendingChange.load(std::memory_order_relaxed) == false);
2521 if(free_voices == srchandles.size())
2522 break;
2524 if(srchandles.size() != free_voices) UNLIKELY
2526 const size_t inc_amount{srchandles.size() - free_voices};
2527 auto &allvoices = *context->mVoices.load(std::memory_order_relaxed);
2528 if(inc_amount > allvoices.size() - voicelist.size())
2530 /* Increase the number of voices to handle the request. */
2531 context->allocVoices(inc_amount - (allvoices.size() - voicelist.size()));
2533 context->mActiveVoiceCount.fetch_add(inc_amount, std::memory_order_release);
2534 voicelist = context->getVoicesSpan();
2537 auto voiceiter = voicelist.begin();
2538 ALuint vidx{0};
2539 VoiceChange *tail{}, *cur{};
2540 for(ALsource *source : srchandles)
2542 /* Check that there is a queue containing at least one valid, non zero
2543 * length buffer.
2545 auto find_buffer = [](ALbufferQueueItem &entry) noexcept
2546 { return entry.mSampleLen != 0 || entry.mCallback != nullptr; };
2547 auto BufferList = std::find_if(source->mQueue.begin(), source->mQueue.end(), find_buffer);
2549 /* If there's nothing to play, go right to stopped. */
2550 if(BufferList == source->mQueue.end()) UNLIKELY
2552 /* NOTE: A source without any playable buffers should not have a
2553 * Voice since it shouldn't be in a playing or paused state. So
2554 * there's no need to look up its voice and clear the source.
2556 source->Offset = 0.0;
2557 source->OffsetType = AL_NONE;
2558 source->state = AL_STOPPED;
2559 continue;
2562 if(!cur)
2563 cur = tail = GetVoiceChanger(context);
2564 else
2566 cur->mNext.store(GetVoiceChanger(context), std::memory_order_relaxed);
2567 cur = cur->mNext.load(std::memory_order_relaxed);
2570 Voice *voice{GetSourceVoice(source, context)};
2571 switch(GetSourceState(source, voice))
2573 case AL_PAUSED:
2574 /* A source that's paused simply resumes. If there's no voice, it
2575 * was lost from a disconnect, so just start over with a new one.
2577 cur->mOldVoice = nullptr;
2578 if(!voice) break;
2579 cur->mVoice = voice;
2580 cur->mSourceID = source->id;
2581 cur->mState = VChangeState::Play;
2582 source->state = AL_PLAYING;
2583 #ifdef ALSOFT_EAX
2584 if(context->hasEax())
2585 source->eaxCommit();
2586 #endif // ALSOFT_EAX
2587 continue;
2589 case AL_PLAYING:
2590 /* A source that's already playing is restarted from the beginning.
2591 * Stop the current voice and start a new one so it properly cross-
2592 * fades back to the beginning.
2594 if(voice)
2595 voice->mPendingChange.store(true, std::memory_order_relaxed);
2596 cur->mOldVoice = voice;
2597 voice = nullptr;
2598 break;
2600 default:
2601 assert(voice == nullptr);
2602 cur->mOldVoice = nullptr;
2603 #ifdef ALSOFT_EAX
2604 if(context->hasEax())
2605 source->eaxCommit();
2606 #endif // ALSOFT_EAX
2607 break;
2610 /* Find the next unused voice to play this source with. */
2611 for(;voiceiter != voicelist.end();++voiceiter,++vidx)
2613 Voice *v{*voiceiter};
2614 if(v->mPlayState.load(std::memory_order_acquire) == Voice::Stopped
2615 && v->mSourceID.load(std::memory_order_relaxed) == 0u
2616 && v->mPendingChange.load(std::memory_order_relaxed) == false)
2618 voice = v;
2619 break;
2622 ASSUME(voice != nullptr);
2624 voice->mPosition.store(0, std::memory_order_relaxed);
2625 voice->mPositionFrac.store(0, std::memory_order_relaxed);
2626 voice->mCurrentBuffer.store(&source->mQueue.front(), std::memory_order_relaxed);
2627 voice->mStartTime = start_time;
2628 voice->mFlags.reset();
2629 /* A source that's not playing or paused has any offset applied when it
2630 * starts playing.
2632 if(const ALenum offsettype{source->OffsetType})
2634 const double offset{source->Offset};
2635 source->OffsetType = AL_NONE;
2636 source->Offset = 0.0;
2637 if(auto vpos = GetSampleOffset(source->mQueue, offsettype, offset))
2639 voice->mPosition.store(vpos->pos, std::memory_order_relaxed);
2640 voice->mPositionFrac.store(vpos->frac, std::memory_order_relaxed);
2641 voice->mCurrentBuffer.store(vpos->bufferitem, std::memory_order_relaxed);
2642 if(vpos->pos > 0 || (vpos->pos == 0 && vpos->frac > 0)
2643 || vpos->bufferitem != &source->mQueue.front())
2644 voice->mFlags.set(VoiceIsFading);
2647 InitVoice(voice, source, al::to_address(BufferList), context, device);
2649 source->VoiceIdx = vidx;
2650 source->state = AL_PLAYING;
2652 cur->mVoice = voice;
2653 cur->mSourceID = source->id;
2654 cur->mState = VChangeState::Play;
2656 if(tail) LIKELY
2657 SendVoiceChanges(context, tail);
2660 } // namespace
2662 AL_API DECL_FUNC2(void, alGenSources, ALsizei,n, ALuint*,sources)
2663 FORCE_ALIGN void AL_APIENTRY alGenSourcesDirect(ALCcontext *context, ALsizei n, ALuint *sources) noexcept
2664 try {
2665 if(n < 0)
2666 throw al::context_error{AL_INVALID_VALUE, "Generating %d sources", n};
2667 if(n <= 0) UNLIKELY return;
2669 std::unique_lock<std::mutex> srclock{context->mSourceLock};
2670 ALCdevice *device{context->mALDevice.get()};
2672 const al::span sids{sources, static_cast<ALuint>(n)};
2673 if(sids.size() > device->SourcesMax-context->mNumSources)
2674 throw al::context_error{AL_OUT_OF_MEMORY, "Exceeding %u source limit (%u + %d)",
2675 device->SourcesMax, context->mNumSources, n};
2676 if(!EnsureSources(context, sids.size()))
2677 throw al::context_error{AL_OUT_OF_MEMORY, "Failed to allocate %d source%s", n,
2678 (n == 1) ? "" : "s"};
2680 std::generate(sids.begin(), sids.end(), [context]{ return AllocSource(context)->id; });
2682 catch(al::context_error& e) {
2683 context->setError(e.errorCode(), "%s", e.what());
2686 AL_API DECL_FUNC2(void, alDeleteSources, ALsizei,n, const ALuint*,sources)
2687 FORCE_ALIGN void AL_APIENTRY alDeleteSourcesDirect(ALCcontext *context, ALsizei n,
2688 const ALuint *sources) noexcept
2689 try {
2690 if(n < 0)
2691 throw al::context_error{AL_INVALID_VALUE, "Deleting %d sources", n};
2692 if(n <= 0) UNLIKELY return;
2694 std::lock_guard<std::mutex> srclock{context->mSourceLock};
2696 /* Check that all Sources are valid */
2697 auto validate_source = [context](const ALuint sid) -> bool
2698 { return LookupSource(context, sid) != nullptr; };
2700 const al::span sids{sources, static_cast<ALuint>(n)};
2701 auto invsrc = std::find_if_not(sids.begin(), sids.end(), validate_source);
2702 if(invsrc != sids.end())
2703 throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", *invsrc};
2705 /* All good. Delete source IDs. */
2706 auto delete_source = [&context](const ALuint sid) -> void
2708 if(ALsource *src{LookupSource(context, sid)})
2709 FreeSource(context, src);
2711 std::for_each(sids.begin(), sids.end(), delete_source);
2713 catch(al::context_error& e) {
2714 context->setError(e.errorCode(), "%s", e.what());
2717 AL_API DECL_FUNC1(ALboolean, alIsSource, ALuint,source)
2718 FORCE_ALIGN ALboolean AL_APIENTRY alIsSourceDirect(ALCcontext *context, ALuint source) noexcept
2720 std::lock_guard<std::mutex> srclock{context->mSourceLock};
2721 if(LookupSource(context, source) != nullptr)
2722 return AL_TRUE;
2723 return AL_FALSE;
2727 AL_API DECL_FUNC3(void, alSourcef, ALuint,source, ALenum,param, ALfloat,value)
2728 FORCE_ALIGN void AL_APIENTRY alSourcefDirect(ALCcontext *context, ALuint source, ALenum param,
2729 ALfloat value) noexcept
2730 try {
2731 std::lock_guard<std::mutex> proplock{context->mPropLock};
2732 std::lock_guard<std::mutex> sourcelock{context->mSourceLock};
2733 ALsource *Source{LookupSource(context, source)};
2734 if(!Source)
2735 throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source};
2737 SetProperty<float>(Source, context, static_cast<SourceProp>(param), {&value, 1u});
2739 catch(al::context_error& e) {
2740 context->setError(e.errorCode(), "%s", e.what());
2743 AL_API DECL_FUNC5(void, alSource3f, ALuint,source, ALenum,param, ALfloat,value1, ALfloat,value2, ALfloat,value3)
2744 FORCE_ALIGN void AL_APIENTRY alSource3fDirect(ALCcontext *context, ALuint source, ALenum param,
2745 ALfloat value1, ALfloat value2, ALfloat value3) noexcept
2746 try {
2747 std::lock_guard<std::mutex> proplock{context->mPropLock};
2748 std::lock_guard<std::mutex> sourcelock{context->mSourceLock};
2749 ALsource *Source{LookupSource(context, source)};
2750 if(!Source)
2751 throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source};
2753 const std::array fvals{value1, value2, value3};
2754 SetProperty<float>(Source, context, static_cast<SourceProp>(param), fvals);
2756 catch(al::context_error& e) {
2757 context->setError(e.errorCode(), "%s", e.what());
2760 AL_API DECL_FUNC3(void, alSourcefv, ALuint,source, ALenum,param, const ALfloat*,values)
2761 FORCE_ALIGN void AL_APIENTRY alSourcefvDirect(ALCcontext *context, ALuint source, ALenum param,
2762 const ALfloat *values) noexcept
2763 try {
2764 std::lock_guard<std::mutex> proplock{context->mPropLock};
2765 std::lock_guard<std::mutex> sourcelock{context->mSourceLock};
2766 ALsource *Source{LookupSource(context, source)};
2767 if(!Source)
2768 throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source};
2769 if(!values)
2770 throw al::context_error{AL_INVALID_VALUE, "NULL pointer"};
2772 const ALuint count{FloatValsByProp(param)};
2773 SetProperty(Source, context, static_cast<SourceProp>(param), al::span{values, count});
2775 catch(al::context_error& e) {
2776 context->setError(e.errorCode(), "%s", e.what());
2780 AL_API DECL_FUNCEXT3(void, alSourced,SOFT, ALuint,source, ALenum,param, ALdouble,value)
2781 FORCE_ALIGN void AL_APIENTRY alSourcedDirectSOFT(ALCcontext *context, ALuint source, ALenum param,
2782 ALdouble value) noexcept
2783 try {
2784 std::lock_guard<std::mutex> proplock{context->mPropLock};
2785 std::lock_guard<std::mutex> sourcelock{context->mSourceLock};
2786 ALsource *Source{LookupSource(context, source)};
2787 if(!Source)
2788 throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source};
2790 SetProperty<double>(Source, context, static_cast<SourceProp>(param), {&value, 1});
2792 catch(al::context_error& e) {
2793 context->setError(e.errorCode(), "%s", e.what());
2796 AL_API DECL_FUNCEXT5(void, alSource3d,SOFT, ALuint,source, ALenum,param, ALdouble,value1, ALdouble,value2, ALdouble,value3)
2797 FORCE_ALIGN void AL_APIENTRY alSource3dDirectSOFT(ALCcontext *context, ALuint source, ALenum param,
2798 ALdouble value1, ALdouble value2, ALdouble value3) noexcept
2799 try {
2800 std::lock_guard<std::mutex> proplock{context->mPropLock};
2801 std::lock_guard<std::mutex> sourcelock{context->mSourceLock};
2802 ALsource *Source{LookupSource(context, source)};
2803 if(!Source)
2804 throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source};
2806 const std::array dvals{value1, value2, value3};
2807 SetProperty<double>(Source, context, static_cast<SourceProp>(param), dvals);
2809 catch(al::context_error& e) {
2810 context->setError(e.errorCode(), "%s", e.what());
2813 AL_API DECL_FUNCEXT3(void, alSourcedv,SOFT, ALuint,source, ALenum,param, const ALdouble*,values)
2814 FORCE_ALIGN void AL_APIENTRY alSourcedvDirectSOFT(ALCcontext *context, ALuint source, ALenum param,
2815 const ALdouble *values) noexcept
2816 try {
2817 std::lock_guard<std::mutex> proplock{context->mPropLock};
2818 std::lock_guard<std::mutex> sourcelock{context->mSourceLock};
2819 ALsource *Source{LookupSource(context, source)};
2820 if(!Source)
2821 throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source};
2822 if(!values)
2823 throw al::context_error{AL_INVALID_VALUE, "NULL pointer"};
2825 const ALuint count{DoubleValsByProp(param)};
2826 SetProperty(Source, context, static_cast<SourceProp>(param), al::span{values, count});
2828 catch(al::context_error& e) {
2829 context->setError(e.errorCode(), "%s", e.what());
2833 AL_API DECL_FUNC3(void, alSourcei, ALuint,source, ALenum,param, ALint,value)
2834 FORCE_ALIGN void AL_APIENTRY alSourceiDirect(ALCcontext *context, ALuint source, ALenum param,
2835 ALint value) noexcept
2836 try {
2837 std::lock_guard<std::mutex> proplock{context->mPropLock};
2838 std::lock_guard<std::mutex> sourcelock{context->mSourceLock};
2839 ALsource *Source{LookupSource(context, source)};
2840 if(!Source)
2841 throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source};
2843 SetProperty<int>(Source, context, static_cast<SourceProp>(param), {&value, 1u});
2845 catch(al::context_error& e) {
2846 context->setError(e.errorCode(), "%s", e.what());
2849 AL_API DECL_FUNC5(void, alSource3i, ALuint,buffer, ALenum,param, ALint,value1, ALint,value2, ALint,value3)
2850 FORCE_ALIGN void AL_APIENTRY alSource3iDirect(ALCcontext *context, ALuint source, ALenum param,
2851 ALint value1, ALint value2, ALint value3) noexcept
2852 try {
2853 std::lock_guard<std::mutex> proplock{context->mPropLock};
2854 std::lock_guard<std::mutex> sourcelock{context->mSourceLock};
2855 ALsource *Source{LookupSource(context, source)};
2856 if(!Source)
2857 throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source};
2859 const std::array ivals{value1, value2, value3};
2860 SetProperty<int>(Source, context, static_cast<SourceProp>(param), ivals);
2862 catch(al::context_error& e) {
2863 context->setError(e.errorCode(), "%s", e.what());
2866 AL_API DECL_FUNC3(void, alSourceiv, ALuint,source, ALenum,param, const ALint*,values)
2867 FORCE_ALIGN void AL_APIENTRY alSourceivDirect(ALCcontext *context, ALuint source, ALenum param,
2868 const ALint *values) noexcept
2869 try {
2870 std::lock_guard<std::mutex> proplock{context->mPropLock};
2871 std::lock_guard<std::mutex> sourcelock{context->mSourceLock};
2872 ALsource *Source{LookupSource(context, source)};
2873 if(!Source)
2874 throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source};
2875 if(!values)
2876 throw al::context_error{AL_INVALID_VALUE, "NULL pointer"};
2878 const ALuint count{IntValsByProp(param)};
2879 SetProperty(Source, context, static_cast<SourceProp>(param), al::span{values, count});
2881 catch(al::context_error& e) {
2882 context->setError(e.errorCode(), "%s", e.what());
2886 AL_API DECL_FUNCEXT3(void, alSourcei64,SOFT, ALuint,source, ALenum,param, ALint64SOFT,value)
2887 FORCE_ALIGN void AL_APIENTRY alSourcei64DirectSOFT(ALCcontext *context, ALuint source,
2888 ALenum param, ALint64SOFT value) noexcept
2889 try {
2890 std::lock_guard<std::mutex> proplock{context->mPropLock};
2891 std::lock_guard<std::mutex> sourcelock{context->mSourceLock};
2892 ALsource *Source{LookupSource(context, source)};
2893 if(!Source)
2894 throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source};
2896 SetProperty<int64_t>(Source, context, static_cast<SourceProp>(param), {&value, 1u});
2898 catch(al::context_error& e) {
2899 context->setError(e.errorCode(), "%s", e.what());
2902 AL_API DECL_FUNCEXT5(void, alSource3i64,SOFT, ALuint,source, ALenum,param, ALint64SOFT,value1, ALint64SOFT,value2, ALint64SOFT,value3)
2903 FORCE_ALIGN void AL_APIENTRY alSource3i64DirectSOFT(ALCcontext *context, ALuint source,
2904 ALenum param, ALint64SOFT value1, ALint64SOFT value2, ALint64SOFT value3) noexcept
2905 try {
2906 std::lock_guard<std::mutex> proplock{context->mPropLock};
2907 std::lock_guard<std::mutex> sourcelock{context->mSourceLock};
2908 ALsource *Source{LookupSource(context, source)};
2909 if(!Source)
2910 throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source};
2912 const std::array i64vals{value1, value2, value3};
2913 SetProperty<int64_t>(Source, context, static_cast<SourceProp>(param), i64vals);
2915 catch(al::context_error& e) {
2916 context->setError(e.errorCode(), "%s", e.what());
2919 AL_API DECL_FUNCEXT3(void, alSourcei64v,SOFT, ALuint,source, ALenum,param, const ALint64SOFT*,values)
2920 FORCE_ALIGN void AL_APIENTRY alSourcei64vDirectSOFT(ALCcontext *context, ALuint source,
2921 ALenum param, const ALint64SOFT *values) noexcept
2922 try {
2923 std::lock_guard<std::mutex> proplock{context->mPropLock};
2924 std::lock_guard<std::mutex> sourcelock{context->mSourceLock};
2925 ALsource *Source{LookupSource(context, source)};
2926 if(!Source)
2927 throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source};
2928 if(!values)
2929 throw al::context_error{AL_INVALID_VALUE, "NULL pointer"};
2931 const ALuint count{Int64ValsByProp(param)};
2932 SetProperty(Source, context, static_cast<SourceProp>(param), al::span{values, count});
2934 catch(al::context_error& e) {
2935 context->setError(e.errorCode(), "%s", e.what());
2939 AL_API DECL_FUNC3(void, alGetSourcef, ALuint,source, ALenum,param, ALfloat*,value)
2940 FORCE_ALIGN void AL_APIENTRY alGetSourcefDirect(ALCcontext *context, ALuint source, ALenum param,
2941 ALfloat *value) noexcept
2942 try {
2943 std::lock_guard<std::mutex> sourcelock{context->mSourceLock};
2944 ALsource *Source{LookupSource(context, source)};
2945 if(!Source)
2946 throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source};
2947 if(!value)
2948 throw al::context_error{AL_INVALID_VALUE, "NULL pointer"};
2950 GetProperty(Source, context, static_cast<SourceProp>(param), al::span{value, 1u});
2952 catch(al::context_error& e) {
2953 context->setError(e.errorCode(), "%s", e.what());
2956 AL_API DECL_FUNC5(void, alGetSource3f, ALuint,source, ALenum,param, ALfloat*,value1, ALfloat*,value2, ALfloat*,value3)
2957 FORCE_ALIGN void AL_APIENTRY alGetSource3fDirect(ALCcontext *context, ALuint source, ALenum param,
2958 ALfloat *value1, ALfloat *value2, ALfloat *value3) noexcept
2959 try {
2960 std::lock_guard<std::mutex> sourcelock{context->mSourceLock};
2961 ALsource *Source{LookupSource(context, source)};
2962 if(!Source)
2963 throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source};
2964 if(!(value1 && value2 && value3))
2965 throw al::context_error{AL_INVALID_VALUE, "NULL pointer"};
2967 std::array<float,3> fvals{};
2968 GetProperty<float>(Source, context, static_cast<SourceProp>(param), fvals);
2969 *value1 = fvals[0];
2970 *value2 = fvals[1];
2971 *value3 = fvals[2];
2973 catch(al::context_error& e) {
2974 context->setError(e.errorCode(), "%s", e.what());
2977 AL_API DECL_FUNC3(void, alGetSourcefv, ALuint,source, ALenum,param, ALfloat*,values)
2978 FORCE_ALIGN void AL_APIENTRY alGetSourcefvDirect(ALCcontext *context, ALuint source, ALenum param,
2979 ALfloat *values) noexcept
2980 try {
2981 std::lock_guard<std::mutex> sourcelock{context->mSourceLock};
2982 ALsource *Source{LookupSource(context, source)};
2983 if(!Source)
2984 throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source};
2985 if(!values)
2986 throw al::context_error{AL_INVALID_VALUE, "NULL pointer"};
2988 const ALuint count{FloatValsByProp(param)};
2989 GetProperty(Source, context, static_cast<SourceProp>(param), al::span{values, count});
2991 catch(al::context_error& e) {
2992 context->setError(e.errorCode(), "%s", e.what());
2996 AL_API DECL_FUNCEXT3(void, alGetSourced,SOFT, ALuint,source, ALenum,param, ALdouble*,value)
2997 FORCE_ALIGN void AL_APIENTRY alGetSourcedDirectSOFT(ALCcontext *context, ALuint source,
2998 ALenum param, ALdouble *value) noexcept
2999 try {
3000 std::lock_guard<std::mutex> sourcelock{context->mSourceLock};
3001 ALsource *Source{LookupSource(context, source)};
3002 if(!Source)
3003 throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source};
3004 if(!value)
3005 throw al::context_error{AL_INVALID_VALUE, "NULL pointer"};
3007 GetProperty(Source, context, static_cast<SourceProp>(param), al::span{value, 1u});
3009 catch(al::context_error& e) {
3010 context->setError(e.errorCode(), "%s", e.what());
3013 AL_API DECL_FUNCEXT5(void, alGetSource3d,SOFT, ALuint,source, ALenum,param, ALdouble*,value1, ALdouble*,value2, ALdouble*,value3)
3014 FORCE_ALIGN void AL_APIENTRY alGetSource3dDirectSOFT(ALCcontext *context, ALuint source,
3015 ALenum param, ALdouble *value1, ALdouble *value2, ALdouble *value3) noexcept
3016 try {
3017 std::lock_guard<std::mutex> sourcelock{context->mSourceLock};
3018 ALsource *Source{LookupSource(context, source)};
3019 if(!Source)
3020 throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source};
3021 if(!(value1 && value2 && value3))
3022 throw al::context_error{AL_INVALID_VALUE, "NULL pointer"};
3024 std::array<double,3> dvals{};
3025 GetProperty<double>(Source, context, static_cast<SourceProp>(param), dvals);
3026 *value1 = dvals[0];
3027 *value2 = dvals[1];
3028 *value3 = dvals[2];
3030 catch(al::context_error& e) {
3031 context->setError(e.errorCode(), "%s", e.what());
3034 AL_API DECL_FUNCEXT3(void, alGetSourcedv,SOFT, ALuint,source, ALenum,param, ALdouble*,values)
3035 FORCE_ALIGN void AL_APIENTRY alGetSourcedvDirectSOFT(ALCcontext *context, ALuint source,
3036 ALenum param, ALdouble *values) noexcept
3037 try {
3038 std::lock_guard<std::mutex> sourcelock{context->mSourceLock};
3039 ALsource *Source{LookupSource(context, source)};
3040 if(!Source)
3041 throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source};
3042 if(!values)
3043 throw al::context_error{AL_INVALID_VALUE, "NULL pointer"};
3045 const ALuint count{DoubleValsByProp(param)};
3046 GetProperty(Source, context, static_cast<SourceProp>(param), al::span{values, count});
3048 catch(al::context_error& e) {
3049 context->setError(e.errorCode(), "%s", e.what());
3053 AL_API DECL_FUNC3(void, alGetSourcei, ALuint,source, ALenum,param, ALint*,value)
3054 FORCE_ALIGN void AL_APIENTRY alGetSourceiDirect(ALCcontext *context, ALuint source, ALenum param,
3055 ALint *value) noexcept
3056 try {
3057 std::lock_guard<std::mutex> sourcelock{context->mSourceLock};
3058 ALsource *Source{LookupSource(context, source)};
3059 if(!Source)
3060 throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source};
3061 if(!value)
3062 throw al::context_error{AL_INVALID_VALUE, "NULL pointer"};
3064 GetProperty(Source, context, static_cast<SourceProp>(param), al::span{value, 1u});
3066 catch(al::context_error& e) {
3067 context->setError(e.errorCode(), "%s", e.what());
3070 AL_API DECL_FUNC5(void, alGetSource3i, ALuint,source, ALenum,param, ALint*,value1, ALint*,value2, ALint*,value3)
3071 FORCE_ALIGN void AL_APIENTRY alGetSource3iDirect(ALCcontext *context, ALuint source, ALenum param,
3072 ALint *value1, ALint *value2, ALint *value3) noexcept
3073 try {
3074 std::lock_guard<std::mutex> sourcelock{context->mSourceLock};
3075 ALsource *Source{LookupSource(context, source)};
3076 if(!Source)
3077 throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source};
3078 if(!(value1 && value2 && value3))
3079 throw al::context_error{AL_INVALID_VALUE, "NULL pointer"};
3081 std::array<int,3> ivals{};
3082 GetProperty<int>(Source, context, static_cast<SourceProp>(param), ivals);
3083 *value1 = ivals[0];
3084 *value2 = ivals[1];
3085 *value3 = ivals[2];
3087 catch(al::context_error& e) {
3088 context->setError(e.errorCode(), "%s", e.what());
3091 AL_API DECL_FUNC3(void, alGetSourceiv, ALuint,source, ALenum,param, ALint*,values)
3092 FORCE_ALIGN void AL_APIENTRY alGetSourceivDirect(ALCcontext *context, ALuint source, ALenum param,
3093 ALint *values) noexcept
3094 try {
3095 std::lock_guard<std::mutex> sourcelock{context->mSourceLock};
3096 ALsource *Source{LookupSource(context, source)};
3097 if(!Source)
3098 throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source};
3099 if(!values)
3100 throw al::context_error{AL_INVALID_VALUE, "NULL pointer"};
3102 const ALuint count{IntValsByProp(param)};
3103 GetProperty(Source, context, static_cast<SourceProp>(param), al::span{values, count});
3105 catch(al::context_error& e) {
3106 context->setError(e.errorCode(), "%s", e.what());
3110 AL_API DECL_FUNCEXT3(void, alGetSourcei64,SOFT, ALuint,source, ALenum,param, ALint64SOFT*,value)
3111 FORCE_ALIGN void AL_APIENTRY alGetSourcei64DirectSOFT(ALCcontext *context, ALuint source, ALenum param, ALint64SOFT *value) noexcept
3112 try {
3113 std::lock_guard<std::mutex> sourcelock{context->mSourceLock};
3114 ALsource *Source{LookupSource(context, source)};
3115 if(!Source)
3116 throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source};
3117 if(!value)
3118 throw al::context_error{AL_INVALID_VALUE, "NULL pointer"};
3120 GetProperty(Source, context, static_cast<SourceProp>(param), al::span{value, 1u});
3122 catch(al::context_error& e) {
3123 context->setError(e.errorCode(), "%s", e.what());
3126 AL_API DECL_FUNCEXT5(void, alGetSource3i64,SOFT, ALuint,source, ALenum,param, ALint64SOFT*,value1, ALint64SOFT*,value2, ALint64SOFT*,value3)
3127 FORCE_ALIGN void AL_APIENTRY alGetSource3i64DirectSOFT(ALCcontext *context, ALuint source,
3128 ALenum param, ALint64SOFT *value1, ALint64SOFT *value2, ALint64SOFT *value3) noexcept
3129 try {
3130 std::lock_guard<std::mutex> sourcelock{context->mSourceLock};
3131 ALsource *Source{LookupSource(context, source)};
3132 if(!Source)
3133 throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source};
3134 if(!(value1 && value2 && value3))
3135 throw al::context_error{AL_INVALID_VALUE, "NULL pointer"};
3137 std::array<int64_t,3> i64vals{};
3138 GetProperty<int64_t>(Source, context, static_cast<SourceProp>(param), i64vals);
3139 *value1 = i64vals[0];
3140 *value2 = i64vals[1];
3141 *value3 = i64vals[2];
3143 catch(al::context_error& e) {
3144 context->setError(e.errorCode(), "%s", e.what());
3147 AL_API DECL_FUNCEXT3(void, alGetSourcei64v,SOFT, ALuint,source, ALenum,param, ALint64SOFT*,values)
3148 FORCE_ALIGN void AL_APIENTRY alGetSourcei64vDirectSOFT(ALCcontext *context, ALuint source,
3149 ALenum param, ALint64SOFT *values) noexcept
3150 try {
3151 std::lock_guard<std::mutex> sourcelock{context->mSourceLock};
3152 ALsource *Source{LookupSource(context, source)};
3153 if(!Source)
3154 throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source};
3155 if(!values)
3156 throw al::context_error{AL_INVALID_VALUE, "NULL pointer"};
3158 const ALuint count{Int64ValsByProp(param)};
3159 GetProperty(Source, context, static_cast<SourceProp>(param), al::span{values, count});
3161 catch(al::context_error& e) {
3162 context->setError(e.errorCode(), "%s", e.what());
3166 AL_API DECL_FUNC1(void, alSourcePlay, ALuint,source)
3167 FORCE_ALIGN void AL_APIENTRY alSourcePlayDirect(ALCcontext *context, ALuint source) noexcept
3168 try {
3169 std::lock_guard<std::mutex> sourcelock{context->mSourceLock};
3170 ALsource *Source{LookupSource(context, source)};
3171 if(!Source)
3172 throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source};
3174 StartSources(context, {&Source, 1});
3176 catch(al::context_error& e) {
3177 context->setError(e.errorCode(), "%s", e.what());
3180 FORCE_ALIGN DECL_FUNCEXT2(void, alSourcePlayAtTime,SOFT, ALuint,source, ALint64SOFT,start_time)
3181 FORCE_ALIGN void AL_APIENTRY alSourcePlayAtTimeDirectSOFT(ALCcontext *context, ALuint source,
3182 ALint64SOFT start_time) noexcept
3183 try {
3184 if(start_time < 0)
3185 throw al::context_error{AL_INVALID_VALUE, "Invalid time point %" PRId64, start_time};
3187 std::lock_guard<std::mutex> sourcelock{context->mSourceLock};
3188 ALsource *Source{LookupSource(context, source)};
3189 if(!Source)
3190 throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source};
3192 StartSources(context, {&Source, 1}, nanoseconds{start_time});
3194 catch(al::context_error& e) {
3195 context->setError(e.errorCode(), "%s", e.what());
3198 AL_API DECL_FUNC2(void, alSourcePlayv, ALsizei,n, const ALuint*,sources)
3199 FORCE_ALIGN void AL_APIENTRY alSourcePlayvDirect(ALCcontext *context, ALsizei n,
3200 const ALuint *sources) noexcept
3201 try {
3202 if(n < 0)
3203 throw al::context_error{AL_INVALID_VALUE, "Playing %d sources", n};
3204 if(n <= 0) UNLIKELY return;
3206 al::span sids{sources, static_cast<ALuint>(n)};
3207 std::vector<ALsource*> extra_sources;
3208 std::array<ALsource*,8> source_storage;
3209 al::span<ALsource*> srchandles;
3210 if(sids.size() <= source_storage.size()) LIKELY
3211 srchandles = al::span{source_storage}.first(sids.size());
3212 else
3214 extra_sources.resize(sids.size());
3215 srchandles = extra_sources;
3218 std::lock_guard<std::mutex> sourcelock{context->mSourceLock};
3219 std::transform(sids.cbegin(), sids.cend(), srchandles.begin(),
3220 [context](const ALuint sid) -> ALsource*
3222 if(ALsource *src{LookupSource(context, sid)})
3223 return src;
3224 throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", sid};
3227 StartSources(context, srchandles);
3229 catch(al::context_error& e) {
3230 context->setError(e.errorCode(), "%s", e.what());
3233 FORCE_ALIGN DECL_FUNCEXT3(void, alSourcePlayAtTimev,SOFT, ALsizei,n, const ALuint*,sources, ALint64SOFT,start_time)
3234 FORCE_ALIGN void AL_APIENTRY alSourcePlayAtTimevDirectSOFT(ALCcontext *context, ALsizei n,
3235 const ALuint *sources, ALint64SOFT start_time) noexcept
3236 try {
3237 if(n < 0)
3238 throw al::context_error{AL_INVALID_VALUE, "Playing %d sources", n};
3239 if(n <= 0) UNLIKELY return;
3241 if(start_time < 0)
3242 throw al::context_error{AL_INVALID_VALUE, "Invalid time point %" PRId64, start_time};
3244 al::span sids{sources, static_cast<ALuint>(n)};
3245 std::vector<ALsource*> extra_sources;
3246 std::array<ALsource*,8> source_storage;
3247 al::span<ALsource*> srchandles;
3248 if(sids.size() <= source_storage.size()) LIKELY
3249 srchandles = al::span{source_storage}.first(sids.size());
3250 else
3252 extra_sources.resize(sids.size());
3253 srchandles = extra_sources;
3256 std::lock_guard<std::mutex> sourcelock{context->mSourceLock};
3257 std::transform(sids.cbegin(), sids.cend(), srchandles.begin(),
3258 [context](const ALuint sid) -> ALsource*
3260 if(ALsource *src{LookupSource(context, sid)})
3261 return src;
3262 throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", sid};
3265 StartSources(context, srchandles, nanoseconds{start_time});
3267 catch(al::context_error& e) {
3268 context->setError(e.errorCode(), "%s", e.what());
3272 AL_API DECL_FUNC1(void, alSourcePause, ALuint,source)
3273 FORCE_ALIGN void AL_APIENTRY alSourcePauseDirect(ALCcontext *context, ALuint source) noexcept
3274 { alSourcePausevDirect(context, 1, &source); }
3276 AL_API DECL_FUNC2(void, alSourcePausev, ALsizei,n, const ALuint*,sources)
3277 FORCE_ALIGN void AL_APIENTRY alSourcePausevDirect(ALCcontext *context, ALsizei n,
3278 const ALuint *sources) noexcept
3279 try {
3280 if(n < 0)
3281 throw al::context_error{AL_INVALID_VALUE, "Pausing %d sources", n};
3282 if(n <= 0) UNLIKELY return;
3284 al::span sids{sources, static_cast<ALuint>(n)};
3285 std::vector<ALsource*> extra_sources;
3286 std::array<ALsource*,8> source_storage;
3287 al::span<ALsource*> srchandles;
3288 if(sids.size() <= source_storage.size()) LIKELY
3289 srchandles = al::span{source_storage}.first(sids.size());
3290 else
3292 extra_sources.resize(sids.size());
3293 srchandles = extra_sources;
3296 std::lock_guard<std::mutex> sourcelock{context->mSourceLock};
3297 std::transform(sids.cbegin(), sids.cend(), srchandles.begin(),
3298 [context](const ALuint sid) -> ALsource*
3300 if(ALsource *src{LookupSource(context, sid)})
3301 return src;
3302 throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", sid};
3305 /* Pausing has to be done in two steps. First, for each source that's
3306 * detected to be playing, chamge the voice (asynchronously) to
3307 * stopping/paused.
3309 VoiceChange *tail{}, *cur{};
3310 for(ALsource *source : srchandles)
3312 Voice *voice{GetSourceVoice(source, context)};
3313 if(GetSourceState(source, voice) == AL_PLAYING)
3315 if(!cur)
3316 cur = tail = GetVoiceChanger(context);
3317 else
3319 cur->mNext.store(GetVoiceChanger(context), std::memory_order_relaxed);
3320 cur = cur->mNext.load(std::memory_order_relaxed);
3322 cur->mVoice = voice;
3323 cur->mSourceID = source->id;
3324 cur->mState = VChangeState::Pause;
3327 if(tail) LIKELY
3329 SendVoiceChanges(context, tail);
3330 /* Second, now that the voice changes have been sent, because it's
3331 * possible that the voice stopped after it was detected playing and
3332 * before the voice got paused, recheck that the source is still
3333 * considered playing and set it to paused if so.
3335 for(ALsource *source : srchandles)
3337 Voice *voice{GetSourceVoice(source, context)};
3338 if(GetSourceState(source, voice) == AL_PLAYING)
3339 source->state = AL_PAUSED;
3343 catch(al::context_error& e) {
3344 context->setError(e.errorCode(), "%s", e.what());
3348 AL_API DECL_FUNC1(void, alSourceStop, ALuint,source)
3349 FORCE_ALIGN void AL_APIENTRY alSourceStopDirect(ALCcontext *context, ALuint source) noexcept
3350 { alSourceStopvDirect(context, 1, &source); }
3352 AL_API DECL_FUNC2(void, alSourceStopv, ALsizei,n, const ALuint*,sources)
3353 FORCE_ALIGN void AL_APIENTRY alSourceStopvDirect(ALCcontext *context, ALsizei n,
3354 const ALuint *sources) noexcept
3355 try {
3356 if(n < 0)
3357 throw al::context_error{AL_INVALID_VALUE, "Stopping %d sources", n};
3358 if(n <= 0) UNLIKELY return;
3360 al::span sids{sources, static_cast<ALuint>(n)};
3361 std::vector<ALsource*> extra_sources;
3362 std::array<ALsource*,8> source_storage;
3363 al::span<ALsource*> srchandles;
3364 if(sids.size() <= source_storage.size()) LIKELY
3365 srchandles = al::span{source_storage}.first(sids.size());
3366 else
3368 extra_sources.resize(sids.size());
3369 srchandles = extra_sources;
3372 std::lock_guard<std::mutex> sourcelock{context->mSourceLock};
3373 std::transform(sids.cbegin(), sids.cend(), srchandles.begin(),
3374 [context](const ALuint sid) -> ALsource*
3376 if(ALsource *src{LookupSource(context, sid)})
3377 return src;
3378 throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", sid};
3381 VoiceChange *tail{}, *cur{};
3382 for(ALsource *source : srchandles)
3384 if(Voice *voice{GetSourceVoice(source, context)})
3386 if(!cur)
3387 cur = tail = GetVoiceChanger(context);
3388 else
3390 cur->mNext.store(GetVoiceChanger(context), std::memory_order_relaxed);
3391 cur = cur->mNext.load(std::memory_order_relaxed);
3393 voice->mPendingChange.store(true, std::memory_order_relaxed);
3394 cur->mVoice = voice;
3395 cur->mSourceID = source->id;
3396 cur->mState = VChangeState::Stop;
3397 source->state = AL_STOPPED;
3399 source->Offset = 0.0;
3400 source->OffsetType = AL_NONE;
3401 source->VoiceIdx = InvalidVoiceIndex;
3403 if(tail) LIKELY
3404 SendVoiceChanges(context, tail);
3406 catch(al::context_error& e) {
3407 context->setError(e.errorCode(), "%s", e.what());
3411 AL_API DECL_FUNC1(void, alSourceRewind, ALuint,source)
3412 FORCE_ALIGN void AL_APIENTRY alSourceRewindDirect(ALCcontext *context, ALuint source) noexcept
3413 { alSourceRewindvDirect(context, 1, &source); }
3415 AL_API DECL_FUNC2(void, alSourceRewindv, ALsizei,n, const ALuint*,sources)
3416 FORCE_ALIGN void AL_APIENTRY alSourceRewindvDirect(ALCcontext *context, ALsizei n,
3417 const ALuint *sources) noexcept
3418 try {
3419 if(n < 0)
3420 throw al::context_error{AL_INVALID_VALUE, "Rewinding %d sources", n};
3421 if(n <= 0) UNLIKELY return;
3423 al::span sids{sources, static_cast<ALuint>(n)};
3424 std::vector<ALsource*> extra_sources;
3425 std::array<ALsource*,8> source_storage;
3426 al::span<ALsource*> srchandles;
3427 if(sids.size() <= source_storage.size()) LIKELY
3428 srchandles = al::span{source_storage}.first(sids.size());
3429 else
3431 extra_sources.resize(sids.size());
3432 srchandles = extra_sources;
3435 std::lock_guard<std::mutex> sourcelock{context->mSourceLock};
3436 std::transform(sids.cbegin(), sids.cend(), srchandles.begin(),
3437 [context](const ALuint sid) -> ALsource*
3439 if(ALsource *src{LookupSource(context, sid)})
3440 return src;
3441 throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", sid};
3444 VoiceChange *tail{}, *cur{};
3445 for(ALsource *source : srchandles)
3447 Voice *voice{GetSourceVoice(source, context)};
3448 if(source->state != AL_INITIAL)
3450 if(!cur)
3451 cur = tail = GetVoiceChanger(context);
3452 else
3454 cur->mNext.store(GetVoiceChanger(context), std::memory_order_relaxed);
3455 cur = cur->mNext.load(std::memory_order_relaxed);
3457 if(voice)
3458 voice->mPendingChange.store(true, std::memory_order_relaxed);
3459 cur->mVoice = voice;
3460 cur->mSourceID = source->id;
3461 cur->mState = VChangeState::Reset;
3462 source->state = AL_INITIAL;
3464 source->Offset = 0.0;
3465 source->OffsetType = AL_NONE;
3466 source->VoiceIdx = InvalidVoiceIndex;
3468 if(tail) LIKELY
3469 SendVoiceChanges(context, tail);
3471 catch(al::context_error& e) {
3472 context->setError(e.errorCode(), "%s", e.what());
3476 AL_API DECL_FUNC3(void, alSourceQueueBuffers, ALuint,source, ALsizei,nb, const ALuint*,buffers)
3477 FORCE_ALIGN void AL_APIENTRY alSourceQueueBuffersDirect(ALCcontext *context, ALuint src,
3478 ALsizei nb, const ALuint *buffers) noexcept
3479 try {
3480 if(nb < 0)
3481 throw al::context_error{AL_INVALID_VALUE, "Queueing %d buffers", nb};
3482 if(nb <= 0) UNLIKELY return;
3484 std::lock_guard<std::mutex> sourcelock{context->mSourceLock};
3485 ALsource *source{LookupSource(context,src)};
3486 if(!source)
3487 throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", src};
3489 /* Can't queue on a Static Source */
3490 if(source->SourceType == AL_STATIC)
3491 throw al::context_error{AL_INVALID_OPERATION, "Queueing onto static source %u", src};
3493 /* Check for a valid Buffer, for its frequency and format */
3494 ALCdevice *device{context->mALDevice.get()};
3495 ALbuffer *BufferFmt{nullptr};
3496 for(auto &item : source->mQueue)
3498 BufferFmt = item.mBuffer;
3499 if(BufferFmt) break;
3502 std::unique_lock<std::mutex> buflock{device->BufferLock};
3503 const auto bids = al::span{buffers, static_cast<ALuint>(nb)};
3504 const size_t NewListStart{source->mQueue.size()};
3505 try {
3506 ALbufferQueueItem *BufferList{nullptr};
3507 std::for_each(bids.cbegin(), bids.cend(),
3508 [source,device,&BufferFmt,&BufferList](const ALuint bid)
3510 ALbuffer *buffer{bid ? LookupBuffer(device, bid) : nullptr};
3511 if(bid && !buffer)
3512 throw al::context_error{AL_INVALID_NAME, "Queueing invalid buffer ID %u", bid};
3514 if(buffer)
3516 if(buffer->mSampleRate < 1)
3517 throw al::context_error{AL_INVALID_OPERATION,
3518 "Queueing buffer %u with no format", buffer->id};
3520 if(buffer->mCallback)
3521 throw al::context_error{AL_INVALID_OPERATION, "Queueing callback buffer %u",
3522 buffer->id};
3524 if(buffer->MappedAccess != 0 && !(buffer->MappedAccess&AL_MAP_PERSISTENT_BIT_SOFT))
3525 throw al::context_error{AL_INVALID_OPERATION,
3526 "Queueing non-persistently mapped buffer %u", buffer->id};
3529 source->mQueue.emplace_back();
3530 if(!BufferList)
3531 BufferList = &source->mQueue.back();
3532 else
3534 auto &item = source->mQueue.back();
3535 BufferList->mNext.store(&item, std::memory_order_relaxed);
3536 BufferList = &item;
3538 if(!buffer) return;
3539 BufferList->mBlockAlign = buffer->mBlockAlign;
3540 BufferList->mSampleLen = buffer->mSampleLen;
3541 BufferList->mLoopEnd = buffer->mSampleLen;
3542 BufferList->mSamples = buffer->mData;
3543 BufferList->mBuffer = buffer;
3544 IncrementRef(buffer->ref);
3546 bool fmt_mismatch{false};
3547 if(BufferFmt == nullptr)
3548 BufferFmt = buffer;
3549 else
3551 fmt_mismatch |= BufferFmt->mSampleRate != buffer->mSampleRate;
3552 fmt_mismatch |= BufferFmt->mChannels != buffer->mChannels;
3553 fmt_mismatch |= BufferFmt->mType != buffer->mType;
3554 if(BufferFmt->isBFormat())
3556 fmt_mismatch |= BufferFmt->mAmbiLayout != buffer->mAmbiLayout;
3557 fmt_mismatch |= BufferFmt->mAmbiScaling != buffer->mAmbiScaling;
3559 fmt_mismatch |= BufferFmt->mAmbiOrder != buffer->mAmbiOrder;
3561 if(fmt_mismatch)
3562 throw al::context_error{AL_INVALID_OPERATION,
3563 "Queueing buffer with mismatched format\n"
3564 " Expected: %uhz, %s, %s ; Got: %uhz, %s, %s\n", BufferFmt->mSampleRate,
3565 NameFromFormat(BufferFmt->mType), NameFromFormat(BufferFmt->mChannels),
3566 buffer->mSampleRate, NameFromFormat(buffer->mType),
3567 NameFromFormat(buffer->mChannels)};
3570 catch(...) {
3571 /* A buffer failed (invalid ID or format), or there was some other
3572 * unexpected error, so unlock and release each buffer we had.
3574 auto iter = source->mQueue.begin() + ptrdiff_t(NewListStart);
3575 for(;iter != source->mQueue.end();++iter)
3577 if(ALbuffer *buf{iter->mBuffer})
3578 DecrementRef(buf->ref);
3580 source->mQueue.resize(NewListStart);
3581 throw;
3583 /* All buffers good. */
3584 buflock.unlock();
3586 /* Source is now streaming */
3587 source->SourceType = AL_STREAMING;
3589 if(NewListStart != 0)
3591 auto iter = source->mQueue.begin() + ptrdiff_t(NewListStart);
3592 (iter-1)->mNext.store(al::to_address(iter), std::memory_order_release);
3595 catch(al::context_error& e) {
3596 context->setError(e.errorCode(), "%s", e.what());
3599 AL_API DECL_FUNC3(void, alSourceUnqueueBuffers, ALuint,source, ALsizei,nb, ALuint*,buffers)
3600 FORCE_ALIGN void AL_APIENTRY alSourceUnqueueBuffersDirect(ALCcontext *context, ALuint src,
3601 ALsizei nb, ALuint *buffers) noexcept
3602 try {
3603 if(nb < 0)
3604 throw al::context_error{AL_INVALID_VALUE, "Unqueueing %d buffers", nb};
3605 if(nb <= 0) UNLIKELY return;
3607 std::lock_guard<std::mutex> sourcelock{context->mSourceLock};
3608 ALsource *source{LookupSource(context,src)};
3609 if(!source)
3610 throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", src};
3612 if(source->SourceType != AL_STREAMING)
3613 throw al::context_error{AL_INVALID_VALUE, "Unqueueing from a non-streaming source %u",src};
3614 if(source->Looping)
3615 throw al::context_error{AL_INVALID_VALUE, "Unqueueing from looping source %u", src};
3617 /* Make sure enough buffers have been processed to unqueue. */
3618 const al::span bids{buffers, static_cast<ALuint>(nb)};
3619 size_t processed{0};
3620 if(source->state != AL_INITIAL) LIKELY
3622 VoiceBufferItem *Current{nullptr};
3623 if(Voice *voice{GetSourceVoice(source, context)})
3624 Current = voice->mCurrentBuffer.load(std::memory_order_relaxed);
3625 for(auto &item : source->mQueue)
3627 if(&item == Current)
3628 break;
3629 ++processed;
3632 if(processed < bids.size())
3633 throw al::context_error{AL_INVALID_VALUE, "Unqueueing %d buffer%s (only %zu processed)",
3634 nb, (nb==1)?"":"s", processed};
3636 std::generate(bids.begin(), bids.end(), [source]() noexcept -> ALuint
3638 auto &head = source->mQueue.front();
3639 ALuint bid{0};
3640 if(ALbuffer *buffer{head.mBuffer})
3642 bid = buffer->id;
3643 DecrementRef(buffer->ref);
3645 source->mQueue.pop_front();
3646 return bid;
3649 catch(al::context_error& e) {
3650 context->setError(e.errorCode(), "%s", e.what());
3654 AL_API void AL_APIENTRY alSourceQueueBufferLayersSOFT(ALuint, ALsizei, const ALuint*) noexcept
3656 ContextRef context{GetContextRef()};
3657 if(!context) UNLIKELY return;
3659 context->setError(AL_INVALID_OPERATION, "alSourceQueueBufferLayersSOFT not supported");
3663 ALsource::ALsource() noexcept
3665 Direct.Gain = 1.0f;
3666 Direct.GainHF = 1.0f;
3667 Direct.HFReference = LowPassFreqRef;
3668 Direct.GainLF = 1.0f;
3669 Direct.LFReference = HighPassFreqRef;
3670 for(auto &send : Send)
3672 send.Slot = nullptr;
3673 send.Gain = 1.0f;
3674 send.GainHF = 1.0f;
3675 send.HFReference = LowPassFreqRef;
3676 send.GainLF = 1.0f;
3677 send.LFReference = HighPassFreqRef;
3681 ALsource::~ALsource()
3683 for(auto &item : mQueue)
3685 if(ALbuffer *buffer{item.mBuffer})
3686 DecrementRef(buffer->ref);
3689 auto clear_send = [](ALsource::SendData &send) -> void
3690 { if(send.Slot) DecrementRef(send.Slot->ref); };
3691 std::for_each(Send.begin(), Send.end(), clear_send);
3694 void UpdateAllSourceProps(ALCcontext *context)
3696 std::lock_guard<std::mutex> srclock{context->mSourceLock};
3697 auto voicelist = context->getVoicesSpan();
3698 ALuint vidx{0u};
3699 for(Voice *voice : voicelist)
3701 ALuint sid{voice->mSourceID.load(std::memory_order_acquire)};
3702 ALsource *source{sid ? LookupSource(context, sid) : nullptr};
3703 if(source && source->VoiceIdx == vidx)
3705 if(std::exchange(source->mPropsDirty, false))
3706 UpdateSourceProps(source, voice, context);
3708 ++vidx;
3712 void ALsource::SetName(ALCcontext *context, ALuint id, std::string_view name)
3714 std::lock_guard<std::mutex> srclock{context->mSourceLock};
3716 auto source = LookupSource(context, id);
3717 if(!source)
3718 throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", id};
3720 context->mSourceNames.insert_or_assign(id, name);
3724 SourceSubList::~SourceSubList()
3726 if(!Sources)
3727 return;
3729 uint64_t usemask{~FreeMask};
3730 while(usemask)
3732 const int idx{al::countr_zero(usemask)};
3733 usemask &= ~(1_u64 << idx);
3734 std::destroy_at(al::to_address(Sources->begin() + idx));
3736 FreeMask = ~usemask;
3737 SubListAllocator{}.deallocate(Sources, 1);
3738 Sources = nullptr;
3742 #ifdef ALSOFT_EAX
3743 void ALsource::eaxInitialize(ALCcontext *context) noexcept
3745 assert(context != nullptr);
3746 mEaxAlContext = context;
3748 mEaxPrimaryFxSlotId = context->eaxGetPrimaryFxSlotIndex();
3749 eax_set_defaults();
3751 eax1_translate(mEax1.i, mEax);
3752 mEaxVersion = 1;
3753 mEaxChanged = true;
3756 void ALsource::eaxDispatch(const EaxCall& call)
3758 call.is_get() ? eax_get(call) : eax_set(call);
3761 ALsource* ALsource::EaxLookupSource(ALCcontext& al_context, ALuint source_id) noexcept
3763 return LookupSource(&al_context, source_id);
3766 [[noreturn]] void ALsource::eax_fail(const char* message)
3768 throw Exception{message};
3771 [[noreturn]] void ALsource::eax_fail_unknown_property_id()
3773 eax_fail("Unknown property id.");
3776 [[noreturn]] void ALsource::eax_fail_unknown_version()
3778 eax_fail("Unknown version.");
3781 [[noreturn]] void ALsource::eax_fail_unknown_active_fx_slot_id()
3783 eax_fail("Unknown active FX slot ID.");
3786 [[noreturn]] void ALsource::eax_fail_unknown_receiving_fx_slot_id()
3788 eax_fail("Unknown receiving FX slot ID.");
3791 void ALsource::eax_set_sends_defaults(EaxSends& sends, const EaxFxSlotIds& ids) noexcept
3793 for(size_t i{0};i < EAX_MAX_FXSLOTS;++i)
3795 auto& send = sends[i];
3796 send.guidReceivingFXSlotID = *(ids[i]);
3797 send.lSend = EAXSOURCE_DEFAULTSEND;
3798 send.lSendHF = EAXSOURCE_DEFAULTSENDHF;
3799 send.lOcclusion = EAXSOURCE_DEFAULTOCCLUSION;
3800 send.flOcclusionLFRatio = EAXSOURCE_DEFAULTOCCLUSIONLFRATIO;
3801 send.flOcclusionRoomRatio = EAXSOURCE_DEFAULTOCCLUSIONROOMRATIO;
3802 send.flOcclusionDirectRatio = EAXSOURCE_DEFAULTOCCLUSIONDIRECTRATIO;
3803 send.lExclusion = EAXSOURCE_DEFAULTEXCLUSION;
3804 send.flExclusionLFRatio = EAXSOURCE_DEFAULTEXCLUSIONLFRATIO;
3808 void ALsource::eax1_set_defaults(Eax1Props& props) noexcept
3810 props.fMix = EAX_REVERBMIX_USEDISTANCE;
3813 void ALsource::eax1_set_defaults() noexcept
3815 eax1_set_defaults(mEax1.i);
3816 mEax1.d = mEax1.i;
3819 void ALsource::eax2_set_defaults(Eax2Props& props) noexcept
3821 props.lDirect = EAXSOURCE_DEFAULTDIRECT;
3822 props.lDirectHF = EAXSOURCE_DEFAULTDIRECTHF;
3823 props.lRoom = EAXSOURCE_DEFAULTROOM;
3824 props.lRoomHF = EAXSOURCE_DEFAULTROOMHF;
3825 props.flRoomRolloffFactor = EAXSOURCE_DEFAULTROOMROLLOFFFACTOR;
3826 props.lObstruction = EAXSOURCE_DEFAULTOBSTRUCTION;
3827 props.flObstructionLFRatio = EAXSOURCE_DEFAULTOBSTRUCTIONLFRATIO;
3828 props.lOcclusion = EAXSOURCE_DEFAULTOCCLUSION;
3829 props.flOcclusionLFRatio = EAXSOURCE_DEFAULTOCCLUSIONLFRATIO;
3830 props.flOcclusionRoomRatio = EAXSOURCE_DEFAULTOCCLUSIONROOMRATIO;
3831 props.lOutsideVolumeHF = EAXSOURCE_DEFAULTOUTSIDEVOLUMEHF;
3832 props.flAirAbsorptionFactor = EAXSOURCE_DEFAULTAIRABSORPTIONFACTOR;
3833 props.dwFlags = EAXSOURCE_DEFAULTFLAGS;
3836 void ALsource::eax2_set_defaults() noexcept
3838 eax2_set_defaults(mEax2.i);
3839 mEax2.d = mEax2.i;
3842 void ALsource::eax3_set_defaults(Eax3Props& props) noexcept
3844 props.lDirect = EAXSOURCE_DEFAULTDIRECT;
3845 props.lDirectHF = EAXSOURCE_DEFAULTDIRECTHF;
3846 props.lRoom = EAXSOURCE_DEFAULTROOM;
3847 props.lRoomHF = EAXSOURCE_DEFAULTROOMHF;
3848 props.lObstruction = EAXSOURCE_DEFAULTOBSTRUCTION;
3849 props.flObstructionLFRatio = EAXSOURCE_DEFAULTOBSTRUCTIONLFRATIO;
3850 props.lOcclusion = EAXSOURCE_DEFAULTOCCLUSION;
3851 props.flOcclusionLFRatio = EAXSOURCE_DEFAULTOCCLUSIONLFRATIO;
3852 props.flOcclusionRoomRatio = EAXSOURCE_DEFAULTOCCLUSIONROOMRATIO;
3853 props.flOcclusionDirectRatio = EAXSOURCE_DEFAULTOCCLUSIONDIRECTRATIO;
3854 props.lExclusion = EAXSOURCE_DEFAULTEXCLUSION;
3855 props.flExclusionLFRatio = EAXSOURCE_DEFAULTEXCLUSIONLFRATIO;
3856 props.lOutsideVolumeHF = EAXSOURCE_DEFAULTOUTSIDEVOLUMEHF;
3857 props.flDopplerFactor = EAXSOURCE_DEFAULTDOPPLERFACTOR;
3858 props.flRolloffFactor = EAXSOURCE_DEFAULTROLLOFFFACTOR;
3859 props.flRoomRolloffFactor = EAXSOURCE_DEFAULTROOMROLLOFFFACTOR;
3860 props.flAirAbsorptionFactor = EAXSOURCE_DEFAULTAIRABSORPTIONFACTOR;
3861 props.ulFlags = EAXSOURCE_DEFAULTFLAGS;
3864 void ALsource::eax3_set_defaults() noexcept
3866 eax3_set_defaults(mEax3.i);
3867 mEax3.d = mEax3.i;
3870 void ALsource::eax4_set_sends_defaults(EaxSends& sends) noexcept
3872 eax_set_sends_defaults(sends, eax4_fx_slot_ids);
3875 void ALsource::eax4_set_active_fx_slots_defaults(EAX40ACTIVEFXSLOTS& slots) noexcept
3877 slots = EAX40SOURCE_DEFAULTACTIVEFXSLOTID;
3880 void ALsource::eax4_set_defaults() noexcept
3882 eax3_set_defaults(mEax4.i.source);
3883 eax4_set_sends_defaults(mEax4.i.sends);
3884 eax4_set_active_fx_slots_defaults(mEax4.i.active_fx_slots);
3885 mEax4.d = mEax4.i;
3888 void ALsource::eax5_set_source_defaults(EAX50SOURCEPROPERTIES& props) noexcept
3890 eax3_set_defaults(static_cast<Eax3Props&>(props));
3891 props.flMacroFXFactor = EAXSOURCE_DEFAULTMACROFXFACTOR;
3894 void ALsource::eax5_set_sends_defaults(EaxSends& sends) noexcept
3896 eax_set_sends_defaults(sends, eax5_fx_slot_ids);
3899 void ALsource::eax5_set_active_fx_slots_defaults(EAX50ACTIVEFXSLOTS& slots) noexcept
3901 slots = EAX50SOURCE_3DDEFAULTACTIVEFXSLOTID;
3904 void ALsource::eax5_set_speaker_levels_defaults(EaxSpeakerLevels& speaker_levels) noexcept
3906 for(size_t i{0};i < eax_max_speakers;++i)
3908 auto& speaker_level = speaker_levels[i];
3909 speaker_level.lSpeakerID = static_cast<long>(EAXSPEAKER_FRONT_LEFT + i);
3910 speaker_level.lLevel = EAXSOURCE_DEFAULTSPEAKERLEVEL;
3914 void ALsource::eax5_set_defaults(Eax5Props& props) noexcept
3916 eax5_set_source_defaults(props.source);
3917 eax5_set_sends_defaults(props.sends);
3918 eax5_set_active_fx_slots_defaults(props.active_fx_slots);
3919 eax5_set_speaker_levels_defaults(props.speaker_levels);
3922 void ALsource::eax5_set_defaults() noexcept
3924 eax5_set_defaults(mEax5.i);
3925 mEax5.d = mEax5.i;
3928 void ALsource::eax_set_defaults() noexcept
3930 eax1_set_defaults();
3931 eax2_set_defaults();
3932 eax3_set_defaults();
3933 eax4_set_defaults();
3934 eax5_set_defaults();
3937 void ALsource::eax1_translate(const Eax1Props& src, Eax5Props& dst) noexcept
3939 eax5_set_defaults(dst);
3941 if (src.fMix == EAX_REVERBMIX_USEDISTANCE)
3943 dst.source.ulFlags |= EAXSOURCEFLAGS_ROOMAUTO;
3944 dst.sends[0].lSend = 0;
3946 else
3948 dst.source.ulFlags &= ~EAXSOURCEFLAGS_ROOMAUTO;
3949 dst.sends[0].lSend = std::clamp(static_cast<long>(gain_to_level_mb(src.fMix)),
3950 EAXSOURCE_MINSEND, EAXSOURCE_MAXSEND);
3954 void ALsource::eax2_translate(const Eax2Props& src, Eax5Props& dst) noexcept
3956 // Source.
3958 dst.source.lDirect = src.lDirect;
3959 dst.source.lDirectHF = src.lDirectHF;
3960 dst.source.lRoom = src.lRoom;
3961 dst.source.lRoomHF = src.lRoomHF;
3962 dst.source.lObstruction = src.lObstruction;
3963 dst.source.flObstructionLFRatio = src.flObstructionLFRatio;
3964 dst.source.lOcclusion = src.lOcclusion;
3965 dst.source.flOcclusionLFRatio = src.flOcclusionLFRatio;
3966 dst.source.flOcclusionRoomRatio = src.flOcclusionRoomRatio;
3967 dst.source.flOcclusionDirectRatio = EAXSOURCE_DEFAULTOCCLUSIONDIRECTRATIO;
3968 dst.source.lExclusion = EAXSOURCE_DEFAULTEXCLUSION;
3969 dst.source.flExclusionLFRatio = EAXSOURCE_DEFAULTEXCLUSIONLFRATIO;
3970 dst.source.lOutsideVolumeHF = src.lOutsideVolumeHF;
3971 dst.source.flDopplerFactor = EAXSOURCE_DEFAULTDOPPLERFACTOR;
3972 dst.source.flRolloffFactor = EAXSOURCE_DEFAULTROLLOFFFACTOR;
3973 dst.source.flRoomRolloffFactor = src.flRoomRolloffFactor;
3974 dst.source.flAirAbsorptionFactor = src.flAirAbsorptionFactor;
3975 dst.source.ulFlags = src.dwFlags;
3976 dst.source.flMacroFXFactor = EAXSOURCE_DEFAULTMACROFXFACTOR;
3978 // Set everything else to defaults.
3980 eax5_set_sends_defaults(dst.sends);
3981 eax5_set_active_fx_slots_defaults(dst.active_fx_slots);
3982 eax5_set_speaker_levels_defaults(dst.speaker_levels);
3985 void ALsource::eax3_translate(const Eax3Props& src, Eax5Props& dst) noexcept
3987 // Source.
3989 static_cast<Eax3Props&>(dst.source) = src;
3990 dst.source.flMacroFXFactor = EAXSOURCE_DEFAULTMACROFXFACTOR;
3992 // Set everything else to defaults.
3994 eax5_set_sends_defaults(dst.sends);
3995 eax5_set_active_fx_slots_defaults(dst.active_fx_slots);
3996 eax5_set_speaker_levels_defaults(dst.speaker_levels);
3999 void ALsource::eax4_translate(const Eax4Props& src, Eax5Props& dst) noexcept
4001 // Source.
4003 static_cast<Eax3Props&>(dst.source) = src.source;
4004 dst.source.flMacroFXFactor = EAXSOURCE_DEFAULTMACROFXFACTOR;
4006 // Sends.
4008 dst.sends = src.sends;
4010 for(size_t i{0};i < EAX_MAX_FXSLOTS;++i)
4011 dst.sends[i].guidReceivingFXSlotID = *(eax5_fx_slot_ids[i]);
4013 // Active FX slots.
4015 for(size_t i{0};i < EAX50_MAX_ACTIVE_FXSLOTS;++i)
4017 auto& dst_id = dst.active_fx_slots.guidActiveFXSlots[i];
4019 if(i < EAX40_MAX_ACTIVE_FXSLOTS)
4021 const auto& src_id = src.active_fx_slots.guidActiveFXSlots[i];
4023 if(src_id == EAX_NULL_GUID)
4024 dst_id = EAX_NULL_GUID;
4025 else if(src_id == EAX_PrimaryFXSlotID)
4026 dst_id = EAX_PrimaryFXSlotID;
4027 else if(src_id == EAXPROPERTYID_EAX40_FXSlot0)
4028 dst_id = EAXPROPERTYID_EAX50_FXSlot0;
4029 else if(src_id == EAXPROPERTYID_EAX40_FXSlot1)
4030 dst_id = EAXPROPERTYID_EAX50_FXSlot1;
4031 else if(src_id == EAXPROPERTYID_EAX40_FXSlot2)
4032 dst_id = EAXPROPERTYID_EAX50_FXSlot2;
4033 else if(src_id == EAXPROPERTYID_EAX40_FXSlot3)
4034 dst_id = EAXPROPERTYID_EAX50_FXSlot3;
4035 else
4036 assert(false && "Unknown active FX slot ID.");
4038 else
4039 dst_id = EAX_NULL_GUID;
4042 // Speaker levels.
4044 eax5_set_speaker_levels_defaults(dst.speaker_levels);
4047 float ALsource::eax_calculate_dst_occlusion_mb(
4048 long src_occlusion_mb,
4049 float path_ratio,
4050 float lf_ratio) noexcept
4052 const auto ratio_1 = path_ratio + lf_ratio - 1.0F;
4053 const auto ratio_2 = path_ratio * lf_ratio;
4054 const auto ratio = (ratio_2 > ratio_1) ? ratio_2 : ratio_1;
4055 const auto dst_occlustion_mb = static_cast<float>(src_occlusion_mb) * ratio;
4056 return dst_occlustion_mb;
4059 EaxAlLowPassParam ALsource::eax_create_direct_filter_param() const noexcept
4061 auto gain_mb =
4062 static_cast<float>(mEax.source.lDirect) +
4063 (static_cast<float>(mEax.source.lObstruction) * mEax.source.flObstructionLFRatio) +
4064 eax_calculate_dst_occlusion_mb(
4065 mEax.source.lOcclusion,
4066 mEax.source.flOcclusionDirectRatio,
4067 mEax.source.flOcclusionLFRatio);
4069 const auto has_source_occlusion = (mEax.source.lOcclusion != 0);
4071 auto gain_hf_mb =
4072 static_cast<float>(mEax.source.lDirectHF) +
4073 static_cast<float>(mEax.source.lObstruction);
4075 for(size_t i{0};i < EAX_MAX_FXSLOTS;++i)
4077 if(!mEaxActiveFxSlots[i])
4078 continue;
4080 if(has_source_occlusion)
4082 const auto& fx_slot = mEaxAlContext->eaxGetFxSlot(i);
4083 const auto& fx_slot_eax = fx_slot.eax_get_eax_fx_slot();
4084 const auto is_environmental_fx = ((fx_slot_eax.ulFlags & EAXFXSLOTFLAGS_ENVIRONMENT) != 0);
4085 const auto is_primary = (mEaxPrimaryFxSlotId.value_or(-1) == fx_slot.eax_get_index());
4086 const auto is_listener_environment = (is_environmental_fx && is_primary);
4088 if(is_listener_environment)
4090 gain_mb += eax_calculate_dst_occlusion_mb(
4091 mEax.source.lOcclusion,
4092 mEax.source.flOcclusionDirectRatio,
4093 mEax.source.flOcclusionLFRatio);
4095 gain_hf_mb += static_cast<float>(mEax.source.lOcclusion) * mEax.source.flOcclusionDirectRatio;
4099 const auto& send = mEax.sends[i];
4101 if(send.lOcclusion != 0)
4103 gain_mb += eax_calculate_dst_occlusion_mb(
4104 send.lOcclusion,
4105 send.flOcclusionDirectRatio,
4106 send.flOcclusionLFRatio);
4108 gain_hf_mb += static_cast<float>(send.lOcclusion) * send.flOcclusionDirectRatio;
4112 return EaxAlLowPassParam{
4113 level_mb_to_gain(gain_mb),
4114 std::min(level_mb_to_gain(gain_hf_mb), 1.0f)};
4117 EaxAlLowPassParam ALsource::eax_create_room_filter_param(
4118 const ALeffectslot& fx_slot,
4119 const EAXSOURCEALLSENDPROPERTIES& send) const noexcept
4121 const auto& fx_slot_eax = fx_slot.eax_get_eax_fx_slot();
4122 const auto is_environmental_fx = ((fx_slot_eax.ulFlags & EAXFXSLOTFLAGS_ENVIRONMENT) != 0);
4123 const auto is_primary = (mEaxPrimaryFxSlotId.value_or(-1) == fx_slot.eax_get_index());
4124 const auto is_listener_environment = (is_environmental_fx && is_primary);
4126 const auto gain_mb =
4127 (static_cast<float>(fx_slot_eax.lOcclusion) * fx_slot_eax.flOcclusionLFRatio) +
4128 static_cast<float>((is_environmental_fx ? mEax.source.lRoom : 0) + send.lSend) +
4129 (is_listener_environment ?
4130 eax_calculate_dst_occlusion_mb(
4131 mEax.source.lOcclusion,
4132 mEax.source.flOcclusionRoomRatio,
4133 mEax.source.flOcclusionLFRatio) :
4134 0.0f) +
4135 eax_calculate_dst_occlusion_mb(
4136 send.lOcclusion,
4137 send.flOcclusionRoomRatio,
4138 send.flOcclusionLFRatio) +
4139 (is_listener_environment ?
4140 (static_cast<float>(mEax.source.lExclusion) * mEax.source.flExclusionLFRatio) :
4141 0.0f) +
4142 (static_cast<float>(send.lExclusion) * send.flExclusionLFRatio);
4144 const auto gain_hf_mb =
4145 static_cast<float>(fx_slot_eax.lOcclusion) +
4146 static_cast<float>((is_environmental_fx ? mEax.source.lRoomHF : 0) + send.lSendHF) +
4147 (is_listener_environment ?
4148 ((static_cast<float>(mEax.source.lOcclusion) * mEax.source.flOcclusionRoomRatio)) :
4149 0.0f) +
4150 (static_cast<float>(send.lOcclusion) * send.flOcclusionRoomRatio) +
4151 (is_listener_environment ?
4152 static_cast<float>(mEax.source.lExclusion + send.lExclusion) :
4153 0.0f);
4155 return EaxAlLowPassParam{
4156 level_mb_to_gain(gain_mb),
4157 std::min(level_mb_to_gain(gain_hf_mb), 1.0f)};
4160 void ALsource::eax_update_direct_filter()
4162 const auto& direct_param = eax_create_direct_filter_param();
4163 Direct.Gain = direct_param.gain;
4164 Direct.GainHF = direct_param.gain_hf;
4165 Direct.HFReference = LowPassFreqRef;
4166 Direct.GainLF = 1.0f;
4167 Direct.LFReference = HighPassFreqRef;
4168 mPropsDirty = true;
4171 void ALsource::eax_update_room_filters()
4173 for(size_t i{0};i < EAX_MAX_FXSLOTS;++i)
4175 if(!mEaxActiveFxSlots[i])
4176 continue;
4178 auto& fx_slot = mEaxAlContext->eaxGetFxSlot(i);
4179 const auto& send = mEax.sends[i];
4180 const auto& room_param = eax_create_room_filter_param(fx_slot, send);
4181 eax_set_al_source_send(&fx_slot, i, room_param);
4185 void ALsource::eax_set_efx_outer_gain_hf()
4187 OuterGainHF = std::clamp(
4188 level_mb_to_gain(static_cast<float>(mEax.source.lOutsideVolumeHF)),
4189 AL_MIN_CONE_OUTER_GAINHF,
4190 AL_MAX_CONE_OUTER_GAINHF);
4193 void ALsource::eax_set_efx_doppler_factor()
4195 DopplerFactor = mEax.source.flDopplerFactor;
4198 void ALsource::eax_set_efx_rolloff_factor()
4200 RolloffFactor2 = mEax.source.flRolloffFactor;
4203 void ALsource::eax_set_efx_room_rolloff_factor()
4205 RoomRolloffFactor = mEax.source.flRoomRolloffFactor;
4208 void ALsource::eax_set_efx_air_absorption_factor()
4210 AirAbsorptionFactor = mEax.source.flAirAbsorptionFactor;
4213 void ALsource::eax_set_efx_dry_gain_hf_auto()
4215 DryGainHFAuto = ((mEax.source.ulFlags & EAXSOURCEFLAGS_DIRECTHFAUTO) != 0);
4218 void ALsource::eax_set_efx_wet_gain_auto()
4220 WetGainAuto = ((mEax.source.ulFlags & EAXSOURCEFLAGS_ROOMAUTO) != 0);
4223 void ALsource::eax_set_efx_wet_gain_hf_auto()
4225 WetGainHFAuto = ((mEax.source.ulFlags & EAXSOURCEFLAGS_ROOMHFAUTO) != 0);
4228 void ALsource::eax1_set(const EaxCall& call, Eax1Props& props)
4230 switch (call.get_property_id()) {
4231 case DSPROPERTY_EAXBUFFER_ALL:
4232 eax_defer<Eax1SourceAllValidator>(call, props);
4233 break;
4235 case DSPROPERTY_EAXBUFFER_REVERBMIX:
4236 eax_defer<Eax1SourceReverbMixValidator>(call, props.fMix);
4237 break;
4239 default:
4240 eax_fail_unknown_property_id();
4244 void ALsource::eax2_set(const EaxCall& call, Eax2Props& props)
4246 switch (call.get_property_id()) {
4247 case DSPROPERTY_EAX20BUFFER_NONE:
4248 break;
4250 case DSPROPERTY_EAX20BUFFER_ALLPARAMETERS:
4251 eax_defer<Eax2SourceAllValidator>(call, props);
4252 break;
4254 case DSPROPERTY_EAX20BUFFER_DIRECT:
4255 eax_defer<Eax2SourceDirectValidator>(call, props.lDirect);
4256 break;
4258 case DSPROPERTY_EAX20BUFFER_DIRECTHF:
4259 eax_defer<Eax2SourceDirectHfValidator>(call, props.lDirectHF);
4260 break;
4262 case DSPROPERTY_EAX20BUFFER_ROOM:
4263 eax_defer<Eax2SourceRoomValidator>(call, props.lRoom);
4264 break;
4266 case DSPROPERTY_EAX20BUFFER_ROOMHF:
4267 eax_defer<Eax2SourceRoomHfValidator>(call, props.lRoomHF);
4268 break;
4270 case DSPROPERTY_EAX20BUFFER_ROOMROLLOFFFACTOR:
4271 eax_defer<Eax2SourceRoomRolloffFactorValidator>(call, props.flRoomRolloffFactor);
4272 break;
4274 case DSPROPERTY_EAX20BUFFER_OBSTRUCTION:
4275 eax_defer<Eax2SourceObstructionValidator>(call, props.lObstruction);
4276 break;
4278 case DSPROPERTY_EAX20BUFFER_OBSTRUCTIONLFRATIO:
4279 eax_defer<Eax2SourceObstructionLfRatioValidator>(call, props.flObstructionLFRatio);
4280 break;
4282 case DSPROPERTY_EAX20BUFFER_OCCLUSION:
4283 eax_defer<Eax2SourceOcclusionValidator>(call, props.lOcclusion);
4284 break;
4286 case DSPROPERTY_EAX20BUFFER_OCCLUSIONLFRATIO:
4287 eax_defer<Eax2SourceOcclusionLfRatioValidator>(call, props.flOcclusionLFRatio);
4288 break;
4290 case DSPROPERTY_EAX20BUFFER_OCCLUSIONROOMRATIO:
4291 eax_defer<Eax2SourceOcclusionRoomRatioValidator>(call, props.flOcclusionRoomRatio);
4292 break;
4294 case DSPROPERTY_EAX20BUFFER_OUTSIDEVOLUMEHF:
4295 eax_defer<Eax2SourceOutsideVolumeHfValidator>(call, props.lOutsideVolumeHF);
4296 break;
4298 case DSPROPERTY_EAX20BUFFER_AIRABSORPTIONFACTOR:
4299 eax_defer<Eax2SourceAirAbsorptionFactorValidator>(call, props.flAirAbsorptionFactor);
4300 break;
4302 case DSPROPERTY_EAX20BUFFER_FLAGS:
4303 eax_defer<Eax2SourceFlagsValidator>(call, props.dwFlags);
4304 break;
4306 default:
4307 eax_fail_unknown_property_id();
4311 void ALsource::eax3_set(const EaxCall& call, Eax3Props& props)
4313 switch (call.get_property_id()) {
4314 case EAXSOURCE_NONE:
4315 break;
4317 case EAXSOURCE_ALLPARAMETERS:
4318 eax_defer<Eax3SourceAllValidator>(call, props);
4319 break;
4321 case EAXSOURCE_OBSTRUCTIONPARAMETERS:
4322 eax_defer_sub<Eax4ObstructionValidator, EAXOBSTRUCTIONPROPERTIES>(call, props.lObstruction);
4323 break;
4325 case EAXSOURCE_OCCLUSIONPARAMETERS:
4326 eax_defer_sub<Eax4OcclusionValidator, EAXOCCLUSIONPROPERTIES>(call, props.lOcclusion);
4327 break;
4329 case EAXSOURCE_EXCLUSIONPARAMETERS:
4330 eax_defer_sub<Eax4ExclusionValidator, EAXEXCLUSIONPROPERTIES>(call, props.lExclusion);
4331 break;
4333 case EAXSOURCE_DIRECT:
4334 eax_defer<Eax2SourceDirectValidator>(call, props.lDirect);
4335 break;
4337 case EAXSOURCE_DIRECTHF:
4338 eax_defer<Eax2SourceDirectHfValidator>(call, props.lDirectHF);
4339 break;
4341 case EAXSOURCE_ROOM:
4342 eax_defer<Eax2SourceRoomValidator>(call, props.lRoom);
4343 break;
4345 case EAXSOURCE_ROOMHF:
4346 eax_defer<Eax2SourceRoomHfValidator>(call, props.lRoomHF);
4347 break;
4349 case EAXSOURCE_OBSTRUCTION:
4350 eax_defer<Eax2SourceObstructionValidator>(call, props.lObstruction);
4351 break;
4353 case EAXSOURCE_OBSTRUCTIONLFRATIO:
4354 eax_defer<Eax2SourceObstructionLfRatioValidator>(call, props.flObstructionLFRatio);
4355 break;
4357 case EAXSOURCE_OCCLUSION:
4358 eax_defer<Eax2SourceOcclusionValidator>(call, props.lOcclusion);
4359 break;
4361 case EAXSOURCE_OCCLUSIONLFRATIO:
4362 eax_defer<Eax2SourceOcclusionLfRatioValidator>(call, props.flOcclusionLFRatio);
4363 break;
4365 case EAXSOURCE_OCCLUSIONROOMRATIO:
4366 eax_defer<Eax2SourceOcclusionRoomRatioValidator>(call, props.flOcclusionRoomRatio);
4367 break;
4369 case EAXSOURCE_OCCLUSIONDIRECTRATIO:
4370 eax_defer<Eax3SourceOcclusionDirectRatioValidator>(call, props.flOcclusionDirectRatio);
4371 break;
4373 case EAXSOURCE_EXCLUSION:
4374 eax_defer<Eax3SourceExclusionValidator>(call, props.lExclusion);
4375 break;
4377 case EAXSOURCE_EXCLUSIONLFRATIO:
4378 eax_defer<Eax3SourceExclusionLfRatioValidator>(call, props.flExclusionLFRatio);
4379 break;
4381 case EAXSOURCE_OUTSIDEVOLUMEHF:
4382 eax_defer<Eax2SourceOutsideVolumeHfValidator>(call, props.lOutsideVolumeHF);
4383 break;
4385 case EAXSOURCE_DOPPLERFACTOR:
4386 eax_defer<Eax3SourceDopplerFactorValidator>(call, props.flDopplerFactor);
4387 break;
4389 case EAXSOURCE_ROLLOFFFACTOR:
4390 eax_defer<Eax3SourceRolloffFactorValidator>(call, props.flRolloffFactor);
4391 break;
4393 case EAXSOURCE_ROOMROLLOFFFACTOR:
4394 eax_defer<Eax2SourceRoomRolloffFactorValidator>(call, props.flRoomRolloffFactor);
4395 break;
4397 case EAXSOURCE_AIRABSORPTIONFACTOR:
4398 eax_defer<Eax2SourceAirAbsorptionFactorValidator>(call, props.flAirAbsorptionFactor);
4399 break;
4401 case EAXSOURCE_FLAGS:
4402 eax_defer<Eax2SourceFlagsValidator>(call, props.ulFlags);
4403 break;
4405 default:
4406 eax_fail_unknown_property_id();
4410 void ALsource::eax4_set(const EaxCall& call, Eax4Props& props)
4412 switch (call.get_property_id()) {
4413 case EAXSOURCE_NONE:
4414 case EAXSOURCE_ALLPARAMETERS:
4415 case EAXSOURCE_OBSTRUCTIONPARAMETERS:
4416 case EAXSOURCE_OCCLUSIONPARAMETERS:
4417 case EAXSOURCE_EXCLUSIONPARAMETERS:
4418 case EAXSOURCE_DIRECT:
4419 case EAXSOURCE_DIRECTHF:
4420 case EAXSOURCE_ROOM:
4421 case EAXSOURCE_ROOMHF:
4422 case EAXSOURCE_OBSTRUCTION:
4423 case EAXSOURCE_OBSTRUCTIONLFRATIO:
4424 case EAXSOURCE_OCCLUSION:
4425 case EAXSOURCE_OCCLUSIONLFRATIO:
4426 case EAXSOURCE_OCCLUSIONROOMRATIO:
4427 case EAXSOURCE_OCCLUSIONDIRECTRATIO:
4428 case EAXSOURCE_EXCLUSION:
4429 case EAXSOURCE_EXCLUSIONLFRATIO:
4430 case EAXSOURCE_OUTSIDEVOLUMEHF:
4431 case EAXSOURCE_DOPPLERFACTOR:
4432 case EAXSOURCE_ROLLOFFFACTOR:
4433 case EAXSOURCE_ROOMROLLOFFFACTOR:
4434 case EAXSOURCE_AIRABSORPTIONFACTOR:
4435 case EAXSOURCE_FLAGS:
4436 eax3_set(call, props.source);
4437 break;
4439 case EAXSOURCE_SENDPARAMETERS:
4440 eax4_defer_sends<Eax4SendValidator, EAXSOURCESENDPROPERTIES>(call, props.sends);
4441 break;
4443 case EAXSOURCE_ALLSENDPARAMETERS:
4444 eax4_defer_sends<Eax4AllSendValidator, EAXSOURCEALLSENDPROPERTIES>(call, props.sends);
4445 break;
4447 case EAXSOURCE_OCCLUSIONSENDPARAMETERS:
4448 eax4_defer_sends<Eax4OcclusionSendValidator, EAXSOURCEOCCLUSIONSENDPROPERTIES>(call, props.sends);
4449 break;
4451 case EAXSOURCE_EXCLUSIONSENDPARAMETERS:
4452 eax4_defer_sends<Eax4ExclusionSendValidator, EAXSOURCEEXCLUSIONSENDPROPERTIES>(call, props.sends);
4453 break;
4455 case EAXSOURCE_ACTIVEFXSLOTID:
4456 eax4_defer_active_fx_slot_id(call, al::span{props.active_fx_slots.guidActiveFXSlots});
4457 break;
4459 default:
4460 eax_fail_unknown_property_id();
4464 void ALsource::eax5_defer_all_2d(const EaxCall& call, EAX50SOURCEPROPERTIES& props)
4466 const auto& src_props = call.get_value<Exception, const EAXSOURCE2DPROPERTIES>();
4467 Eax5SourceAll2dValidator{}(src_props);
4468 props.lDirect = src_props.lDirect;
4469 props.lDirectHF = src_props.lDirectHF;
4470 props.lRoom = src_props.lRoom;
4471 props.lRoomHF = src_props.lRoomHF;
4472 props.ulFlags = src_props.ulFlags;
4475 void ALsource::eax5_defer_speaker_levels(const EaxCall& call, EaxSpeakerLevels& props)
4477 const auto values = call.get_values<const EAXSPEAKERLEVELPROPERTIES>(eax_max_speakers);
4478 std::for_each(values.cbegin(), values.cend(), Eax5SpeakerAllValidator{});
4480 for (const auto& value : values) {
4481 const auto index = static_cast<size_t>(value.lSpeakerID - EAXSPEAKER_FRONT_LEFT);
4482 props[index].lLevel = value.lLevel;
4486 void ALsource::eax5_set(const EaxCall& call, Eax5Props& props)
4488 switch (call.get_property_id()) {
4489 case EAXSOURCE_NONE:
4490 break;
4492 case EAXSOURCE_ALLPARAMETERS:
4493 eax_defer<Eax5SourceAllValidator>(call, props.source);
4494 break;
4496 case EAXSOURCE_OBSTRUCTIONPARAMETERS:
4497 case EAXSOURCE_OCCLUSIONPARAMETERS:
4498 case EAXSOURCE_EXCLUSIONPARAMETERS:
4499 case EAXSOURCE_DIRECT:
4500 case EAXSOURCE_DIRECTHF:
4501 case EAXSOURCE_ROOM:
4502 case EAXSOURCE_ROOMHF:
4503 case EAXSOURCE_OBSTRUCTION:
4504 case EAXSOURCE_OBSTRUCTIONLFRATIO:
4505 case EAXSOURCE_OCCLUSION:
4506 case EAXSOURCE_OCCLUSIONLFRATIO:
4507 case EAXSOURCE_OCCLUSIONROOMRATIO:
4508 case EAXSOURCE_OCCLUSIONDIRECTRATIO:
4509 case EAXSOURCE_EXCLUSION:
4510 case EAXSOURCE_EXCLUSIONLFRATIO:
4511 case EAXSOURCE_OUTSIDEVOLUMEHF:
4512 case EAXSOURCE_DOPPLERFACTOR:
4513 case EAXSOURCE_ROLLOFFFACTOR:
4514 case EAXSOURCE_ROOMROLLOFFFACTOR:
4515 case EAXSOURCE_AIRABSORPTIONFACTOR:
4516 case EAXSOURCE_FLAGS:
4517 eax3_set(call, props.source);
4518 break;
4520 case EAXSOURCE_SENDPARAMETERS:
4521 eax5_defer_sends<Eax5SendValidator, EAXSOURCESENDPROPERTIES>(call, props.sends);
4522 break;
4524 case EAXSOURCE_ALLSENDPARAMETERS:
4525 eax5_defer_sends<Eax5AllSendValidator, EAXSOURCEALLSENDPROPERTIES>(call, props.sends);
4526 break;
4528 case EAXSOURCE_OCCLUSIONSENDPARAMETERS:
4529 eax5_defer_sends<Eax5OcclusionSendValidator, EAXSOURCEOCCLUSIONSENDPROPERTIES>(call, props.sends);
4530 break;
4532 case EAXSOURCE_EXCLUSIONSENDPARAMETERS:
4533 eax5_defer_sends<Eax5ExclusionSendValidator, EAXSOURCEEXCLUSIONSENDPROPERTIES>(call, props.sends);
4534 break;
4536 case EAXSOURCE_ACTIVEFXSLOTID:
4537 eax5_defer_active_fx_slot_id(call, al::span{props.active_fx_slots.guidActiveFXSlots});
4538 break;
4540 case EAXSOURCE_MACROFXFACTOR:
4541 eax_defer<Eax5SourceMacroFXFactorValidator>(call, props.source.flMacroFXFactor);
4542 break;
4544 case EAXSOURCE_SPEAKERLEVELS:
4545 eax5_defer_speaker_levels(call, props.speaker_levels);
4546 break;
4548 case EAXSOURCE_ALL2DPARAMETERS:
4549 eax5_defer_all_2d(call, props.source);
4550 break;
4552 default:
4553 eax_fail_unknown_property_id();
4557 void ALsource::eax_set(const EaxCall& call)
4559 const auto eax_version = call.get_version();
4560 switch(eax_version)
4562 case 1: eax1_set(call, mEax1.d); break;
4563 case 2: eax2_set(call, mEax2.d); break;
4564 case 3: eax3_set(call, mEax3.d); break;
4565 case 4: eax4_set(call, mEax4.d); break;
4566 case 5: eax5_set(call, mEax5.d); break;
4567 default: eax_fail_unknown_property_id();
4569 mEaxChanged = true;
4570 mEaxVersion = eax_version;
4573 void ALsource::eax_get_active_fx_slot_id(const EaxCall& call, const al::span<const GUID> src_ids)
4575 assert(src_ids.size()==EAX40_MAX_ACTIVE_FXSLOTS || src_ids.size()==EAX50_MAX_ACTIVE_FXSLOTS);
4576 const auto dst_ids = call.get_values<GUID>(src_ids.size());
4577 std::uninitialized_copy_n(src_ids.begin(), dst_ids.size(), dst_ids.begin());
4580 void ALsource::eax1_get(const EaxCall& call, const Eax1Props& props)
4582 switch (call.get_property_id()) {
4583 case DSPROPERTY_EAXBUFFER_ALL:
4584 case DSPROPERTY_EAXBUFFER_REVERBMIX:
4585 call.set_value<Exception>(props.fMix);
4586 break;
4588 default:
4589 eax_fail_unknown_property_id();
4593 void ALsource::eax2_get(const EaxCall& call, const Eax2Props& props)
4595 switch (call.get_property_id()) {
4596 case DSPROPERTY_EAX20BUFFER_NONE:
4597 break;
4599 case DSPROPERTY_EAX20BUFFER_ALLPARAMETERS:
4600 call.set_value<Exception>(props);
4601 break;
4603 case DSPROPERTY_EAX20BUFFER_DIRECT:
4604 call.set_value<Exception>(props.lDirect);
4605 break;
4607 case DSPROPERTY_EAX20BUFFER_DIRECTHF:
4608 call.set_value<Exception>(props.lDirectHF);
4609 break;
4611 case DSPROPERTY_EAX20BUFFER_ROOM:
4612 call.set_value<Exception>(props.lRoom);
4613 break;
4615 case DSPROPERTY_EAX20BUFFER_ROOMHF:
4616 call.set_value<Exception>(props.lRoomHF);
4617 break;
4619 case DSPROPERTY_EAX20BUFFER_ROOMROLLOFFFACTOR:
4620 call.set_value<Exception>(props.flRoomRolloffFactor);
4621 break;
4623 case DSPROPERTY_EAX20BUFFER_OBSTRUCTION:
4624 call.set_value<Exception>(props.lObstruction);
4625 break;
4627 case DSPROPERTY_EAX20BUFFER_OBSTRUCTIONLFRATIO:
4628 call.set_value<Exception>(props.flObstructionLFRatio);
4629 break;
4631 case DSPROPERTY_EAX20BUFFER_OCCLUSION:
4632 call.set_value<Exception>(props.lOcclusion);
4633 break;
4635 case DSPROPERTY_EAX20BUFFER_OCCLUSIONLFRATIO:
4636 call.set_value<Exception>(props.flOcclusionLFRatio);
4637 break;
4639 case DSPROPERTY_EAX20BUFFER_OCCLUSIONROOMRATIO:
4640 call.set_value<Exception>(props.flOcclusionRoomRatio);
4641 break;
4643 case DSPROPERTY_EAX20BUFFER_OUTSIDEVOLUMEHF:
4644 call.set_value<Exception>(props.lOutsideVolumeHF);
4645 break;
4647 case DSPROPERTY_EAX20BUFFER_AIRABSORPTIONFACTOR:
4648 call.set_value<Exception>(props.flAirAbsorptionFactor);
4649 break;
4651 case DSPROPERTY_EAX20BUFFER_FLAGS:
4652 call.set_value<Exception>(props.dwFlags);
4653 break;
4655 default:
4656 eax_fail_unknown_property_id();
4660 void ALsource::eax3_get_obstruction(const EaxCall& call, const Eax3Props& props)
4662 const auto& subprops = reinterpret_cast<const EAXOBSTRUCTIONPROPERTIES&>(props.lObstruction);
4663 call.set_value<Exception>(subprops);
4666 void ALsource::eax3_get_occlusion(const EaxCall& call, const Eax3Props& props)
4668 const auto& subprops = reinterpret_cast<const EAXOCCLUSIONPROPERTIES&>(props.lOcclusion);
4669 call.set_value<Exception>(subprops);
4672 void ALsource::eax3_get_exclusion(const EaxCall& call, const Eax3Props& props)
4674 const auto& subprops = reinterpret_cast<const EAXEXCLUSIONPROPERTIES&>(props.lExclusion);
4675 call.set_value<Exception>(subprops);
4678 void ALsource::eax3_get(const EaxCall& call, const Eax3Props& props)
4680 switch (call.get_property_id()) {
4681 case EAXSOURCE_NONE:
4682 break;
4684 case EAXSOURCE_ALLPARAMETERS:
4685 call.set_value<Exception>(props);
4686 break;
4688 case EAXSOURCE_OBSTRUCTIONPARAMETERS:
4689 eax3_get_obstruction(call, props);
4690 break;
4692 case EAXSOURCE_OCCLUSIONPARAMETERS:
4693 eax3_get_occlusion(call, props);
4694 break;
4696 case EAXSOURCE_EXCLUSIONPARAMETERS:
4697 eax3_get_exclusion(call, props);
4698 break;
4700 case EAXSOURCE_DIRECT:
4701 call.set_value<Exception>(props.lDirect);
4702 break;
4704 case EAXSOURCE_DIRECTHF:
4705 call.set_value<Exception>(props.lDirectHF);
4706 break;
4708 case EAXSOURCE_ROOM:
4709 call.set_value<Exception>(props.lRoom);
4710 break;
4712 case EAXSOURCE_ROOMHF:
4713 call.set_value<Exception>(props.lRoomHF);
4714 break;
4716 case EAXSOURCE_OBSTRUCTION:
4717 call.set_value<Exception>(props.lObstruction);
4718 break;
4720 case EAXSOURCE_OBSTRUCTIONLFRATIO:
4721 call.set_value<Exception>(props.flObstructionLFRatio);
4722 break;
4724 case EAXSOURCE_OCCLUSION:
4725 call.set_value<Exception>(props.lOcclusion);
4726 break;
4728 case EAXSOURCE_OCCLUSIONLFRATIO:
4729 call.set_value<Exception>(props.flOcclusionLFRatio);
4730 break;
4732 case EAXSOURCE_OCCLUSIONROOMRATIO:
4733 call.set_value<Exception>(props.flOcclusionRoomRatio);
4734 break;
4736 case EAXSOURCE_OCCLUSIONDIRECTRATIO:
4737 call.set_value<Exception>(props.flOcclusionDirectRatio);
4738 break;
4740 case EAXSOURCE_EXCLUSION:
4741 call.set_value<Exception>(props.lExclusion);
4742 break;
4744 case EAXSOURCE_EXCLUSIONLFRATIO:
4745 call.set_value<Exception>(props.flExclusionLFRatio);
4746 break;
4748 case EAXSOURCE_OUTSIDEVOLUMEHF:
4749 call.set_value<Exception>(props.lOutsideVolumeHF);
4750 break;
4752 case EAXSOURCE_DOPPLERFACTOR:
4753 call.set_value<Exception>(props.flDopplerFactor);
4754 break;
4756 case EAXSOURCE_ROLLOFFFACTOR:
4757 call.set_value<Exception>(props.flRolloffFactor);
4758 break;
4760 case EAXSOURCE_ROOMROLLOFFFACTOR:
4761 call.set_value<Exception>(props.flRoomRolloffFactor);
4762 break;
4764 case EAXSOURCE_AIRABSORPTIONFACTOR:
4765 call.set_value<Exception>(props.flAirAbsorptionFactor);
4766 break;
4768 case EAXSOURCE_FLAGS:
4769 call.set_value<Exception>(props.ulFlags);
4770 break;
4772 default:
4773 eax_fail_unknown_property_id();
4777 void ALsource::eax4_get(const EaxCall& call, const Eax4Props& props)
4779 switch (call.get_property_id()) {
4780 case EAXSOURCE_NONE:
4781 break;
4783 case EAXSOURCE_ALLPARAMETERS:
4784 case EAXSOURCE_OBSTRUCTIONPARAMETERS:
4785 case EAXSOURCE_OCCLUSIONPARAMETERS:
4786 case EAXSOURCE_EXCLUSIONPARAMETERS:
4787 case EAXSOURCE_DIRECT:
4788 case EAXSOURCE_DIRECTHF:
4789 case EAXSOURCE_ROOM:
4790 case EAXSOURCE_ROOMHF:
4791 case EAXSOURCE_OBSTRUCTION:
4792 case EAXSOURCE_OBSTRUCTIONLFRATIO:
4793 case EAXSOURCE_OCCLUSION:
4794 case EAXSOURCE_OCCLUSIONLFRATIO:
4795 case EAXSOURCE_OCCLUSIONROOMRATIO:
4796 case EAXSOURCE_OCCLUSIONDIRECTRATIO:
4797 case EAXSOURCE_EXCLUSION:
4798 case EAXSOURCE_EXCLUSIONLFRATIO:
4799 case EAXSOURCE_OUTSIDEVOLUMEHF:
4800 case EAXSOURCE_DOPPLERFACTOR:
4801 case EAXSOURCE_ROLLOFFFACTOR:
4802 case EAXSOURCE_ROOMROLLOFFFACTOR:
4803 case EAXSOURCE_AIRABSORPTIONFACTOR:
4804 case EAXSOURCE_FLAGS:
4805 eax3_get(call, props.source);
4806 break;
4808 case EAXSOURCE_SENDPARAMETERS:
4809 eax_get_sends<EAXSOURCESENDPROPERTIES>(call, props.sends);
4810 break;
4812 case EAXSOURCE_ALLSENDPARAMETERS:
4813 eax_get_sends<EAXSOURCEALLSENDPROPERTIES>(call, props.sends);
4814 break;
4816 case EAXSOURCE_OCCLUSIONSENDPARAMETERS:
4817 eax_get_sends<EAXSOURCEOCCLUSIONSENDPROPERTIES>(call, props.sends);
4818 break;
4820 case EAXSOURCE_EXCLUSIONSENDPARAMETERS:
4821 eax_get_sends<EAXSOURCEEXCLUSIONSENDPROPERTIES>(call, props.sends);
4822 break;
4824 case EAXSOURCE_ACTIVEFXSLOTID:
4825 eax_get_active_fx_slot_id(call, props.active_fx_slots.guidActiveFXSlots);
4826 break;
4828 default:
4829 eax_fail_unknown_property_id();
4833 void ALsource::eax5_get_all_2d(const EaxCall& call, const EAX50SOURCEPROPERTIES& props)
4835 auto& subprops = call.get_value<Exception, EAXSOURCE2DPROPERTIES>();
4836 subprops.lDirect = props.lDirect;
4837 subprops.lDirectHF = props.lDirectHF;
4838 subprops.lRoom = props.lRoom;
4839 subprops.lRoomHF = props.lRoomHF;
4840 subprops.ulFlags = props.ulFlags;
4843 void ALsource::eax5_get_speaker_levels(const EaxCall& call, const EaxSpeakerLevels& props)
4845 const auto subprops = call.get_values<EAXSPEAKERLEVELPROPERTIES>(eax_max_speakers);
4846 std::uninitialized_copy_n(props.cbegin(), subprops.size(), subprops.begin());
4849 void ALsource::eax5_get(const EaxCall& call, const Eax5Props& props)
4851 switch (call.get_property_id()) {
4852 case EAXSOURCE_NONE:
4853 break;
4855 case EAXSOURCE_ALLPARAMETERS:
4856 case EAXSOURCE_OBSTRUCTIONPARAMETERS:
4857 case EAXSOURCE_OCCLUSIONPARAMETERS:
4858 case EAXSOURCE_EXCLUSIONPARAMETERS:
4859 case EAXSOURCE_DIRECT:
4860 case EAXSOURCE_DIRECTHF:
4861 case EAXSOURCE_ROOM:
4862 case EAXSOURCE_ROOMHF:
4863 case EAXSOURCE_OBSTRUCTION:
4864 case EAXSOURCE_OBSTRUCTIONLFRATIO:
4865 case EAXSOURCE_OCCLUSION:
4866 case EAXSOURCE_OCCLUSIONLFRATIO:
4867 case EAXSOURCE_OCCLUSIONROOMRATIO:
4868 case EAXSOURCE_OCCLUSIONDIRECTRATIO:
4869 case EAXSOURCE_EXCLUSION:
4870 case EAXSOURCE_EXCLUSIONLFRATIO:
4871 case EAXSOURCE_OUTSIDEVOLUMEHF:
4872 case EAXSOURCE_DOPPLERFACTOR:
4873 case EAXSOURCE_ROLLOFFFACTOR:
4874 case EAXSOURCE_ROOMROLLOFFFACTOR:
4875 case EAXSOURCE_AIRABSORPTIONFACTOR:
4876 case EAXSOURCE_FLAGS:
4877 eax3_get(call, props.source);
4878 break;
4880 case EAXSOURCE_SENDPARAMETERS:
4881 eax_get_sends<EAXSOURCESENDPROPERTIES>(call, props.sends);
4882 break;
4884 case EAXSOURCE_ALLSENDPARAMETERS:
4885 eax_get_sends<EAXSOURCEALLSENDPROPERTIES>(call, props.sends);
4886 break;
4888 case EAXSOURCE_OCCLUSIONSENDPARAMETERS:
4889 eax_get_sends<EAXSOURCEOCCLUSIONSENDPROPERTIES>(call, props.sends);
4890 break;
4892 case EAXSOURCE_EXCLUSIONSENDPARAMETERS:
4893 eax_get_sends<EAXSOURCEEXCLUSIONSENDPROPERTIES>(call, props.sends);
4894 break;
4896 case EAXSOURCE_ACTIVEFXSLOTID:
4897 eax_get_active_fx_slot_id(call, props.active_fx_slots.guidActiveFXSlots);
4898 break;
4900 case EAXSOURCE_MACROFXFACTOR:
4901 call.set_value<Exception>(props.source.flMacroFXFactor);
4902 break;
4904 case EAXSOURCE_SPEAKERLEVELS:
4905 call.set_value<Exception>(props.speaker_levels);
4906 break;
4908 case EAXSOURCE_ALL2DPARAMETERS:
4909 eax5_get_all_2d(call, props.source);
4910 break;
4912 default:
4913 eax_fail_unknown_property_id();
4917 void ALsource::eax_get(const EaxCall& call)
4919 switch (call.get_version()) {
4920 case 1: eax1_get(call, mEax1.i); break;
4921 case 2: eax2_get(call, mEax2.i); break;
4922 case 3: eax3_get(call, mEax3.i); break;
4923 case 4: eax4_get(call, mEax4.i); break;
4924 case 5: eax5_get(call, mEax5.i); break;
4925 default: eax_fail_unknown_version();
4929 void ALsource::eax_set_al_source_send(ALeffectslot *slot, size_t sendidx, const EaxAlLowPassParam &filter)
4931 if(sendidx >= EAX_MAX_FXSLOTS)
4932 return;
4934 auto &send = Send[sendidx];
4935 send.Gain = filter.gain;
4936 send.GainHF = filter.gain_hf;
4937 send.HFReference = LowPassFreqRef;
4938 send.GainLF = 1.0f;
4939 send.LFReference = HighPassFreqRef;
4941 if(slot != nullptr)
4942 IncrementRef(slot->ref);
4943 if(auto *oldslot = send.Slot)
4944 DecrementRef(oldslot->ref);
4946 send.Slot = slot;
4947 mPropsDirty = true;
4950 void ALsource::eax_commit_active_fx_slots()
4952 // Clear all slots to an inactive state.
4953 mEaxActiveFxSlots.fill(false);
4955 // Mark the set slots as active.
4956 for(const auto& slot_id : mEax.active_fx_slots.guidActiveFXSlots)
4958 if(slot_id == EAX_NULL_GUID)
4961 else if(slot_id == EAX_PrimaryFXSlotID)
4963 // Mark primary FX slot as active.
4964 if(mEaxPrimaryFxSlotId.has_value())
4965 mEaxActiveFxSlots[*mEaxPrimaryFxSlotId] = true;
4967 else if(slot_id == EAXPROPERTYID_EAX50_FXSlot0)
4968 mEaxActiveFxSlots[0] = true;
4969 else if(slot_id == EAXPROPERTYID_EAX50_FXSlot1)
4970 mEaxActiveFxSlots[1] = true;
4971 else if(slot_id == EAXPROPERTYID_EAX50_FXSlot2)
4972 mEaxActiveFxSlots[2] = true;
4973 else if(slot_id == EAXPROPERTYID_EAX50_FXSlot3)
4974 mEaxActiveFxSlots[3] = true;
4977 // Deactivate EFX auxiliary effect slots for inactive slots. Active slots
4978 // will be updated with the room filters.
4979 for(size_t i{0};i < EAX_MAX_FXSLOTS;++i)
4981 if(!mEaxActiveFxSlots[i])
4982 eax_set_al_source_send(nullptr, i, EaxAlLowPassParam{1.0f, 1.0f});
4986 void ALsource::eax_commit_filters()
4988 eax_update_direct_filter();
4989 eax_update_room_filters();
4992 void ALsource::eaxCommit()
4994 const auto primary_fx_slot_id = mEaxAlContext->eaxGetPrimaryFxSlotIndex();
4995 const auto is_primary_fx_slot_id_changed = (mEaxPrimaryFxSlotId != primary_fx_slot_id);
4997 if(!mEaxChanged && !is_primary_fx_slot_id_changed)
4998 return;
5000 mEaxPrimaryFxSlotId = primary_fx_slot_id;
5001 mEaxChanged = false;
5003 switch(mEaxVersion)
5005 case 1:
5006 mEax1.i = mEax1.d;
5007 eax1_translate(mEax1.i, mEax);
5008 break;
5009 case 2:
5010 mEax2.i = mEax2.d;
5011 eax2_translate(mEax2.i, mEax);
5012 break;
5013 case 3:
5014 mEax3.i = mEax3.d;
5015 eax3_translate(mEax3.i, mEax);
5016 break;
5017 case 4:
5018 mEax4.i = mEax4.d;
5019 eax4_translate(mEax4.i, mEax);
5020 break;
5021 case 5:
5022 mEax5.i = mEax5.d;
5023 mEax = mEax5.d;
5024 break;
5027 eax_set_efx_outer_gain_hf();
5028 eax_set_efx_doppler_factor();
5029 eax_set_efx_rolloff_factor();
5030 eax_set_efx_room_rolloff_factor();
5031 eax_set_efx_air_absorption_factor();
5032 eax_set_efx_dry_gain_hf_auto();
5033 eax_set_efx_wet_gain_auto();
5034 eax_set_efx_wet_gain_hf_auto();
5036 eax_commit_active_fx_slots();
5037 eax_commit_filters();
5040 #endif // ALSOFT_EAX