Use filebuf instead of ifstream in allafplay
[openal-soft.git] / al / source.cpp
blob81a809e69e20013e8e5c7de0f450909be9d0e5d0
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 <iterator>
36 #include <limits>
37 #include <memory>
38 #include <mutex>
39 #include <numeric>
40 #include <optional>
41 #include <stdexcept>
42 #include <string>
43 #include <tuple>
44 #include <type_traits>
45 #include <unordered_map>
46 #include <utility>
47 #include <vector>
49 #include "AL/al.h"
50 #include "AL/alc.h"
51 #include "AL/alext.h"
52 #include "AL/efx.h"
54 #include "albit.h"
55 #include "alc/backends/base.h"
56 #include "alc/context.h"
57 #include "alc/device.h"
58 #include "alc/inprogext.h"
59 #include "almalloc.h"
60 #include "alnumeric.h"
61 #include "alspan.h"
62 #include "atomic.h"
63 #include "auxeffectslot.h"
64 #include "buffer.h"
65 #include "core/buffer_storage.h"
66 #include "core/logging.h"
67 #include "core/mixer/defs.h"
68 #include "core/voice_change.h"
69 #include "direct_defs.h"
70 #include "error.h"
71 #include "filter.h"
72 #include "flexarray.h"
73 #include "intrusive_ptr.h"
74 #include "opthelpers.h"
76 #if ALSOFT_EAX
77 #include "eax/api.h"
78 #include "eax/call.h"
79 #include "eax/fx_slot_index.h"
80 #include "eax/utils.h"
81 #endif
83 namespace {
85 using SubListAllocator = al::allocator<std::array<ALsource,64>>;
86 using std::chrono::nanoseconds;
87 using seconds_d = std::chrono::duration<double>;
88 using source_store_array = std::array<ALsource*,3>;
89 using source_store_vector = std::vector<ALsource*>;
90 using source_store_variant = std::variant<std::monostate,source_store_array,source_store_vector>;
93 Voice *GetSourceVoice(ALsource *source, ALCcontext *context)
95 auto voicelist = context->getVoicesSpan();
96 ALuint idx{source->VoiceIdx};
97 if(idx < voicelist.size())
99 ALuint sid{source->id};
100 Voice *voice = voicelist[idx];
101 if(voice->mSourceID.load(std::memory_order_acquire) == sid)
102 return voice;
104 source->VoiceIdx = InvalidVoiceIndex;
105 return nullptr;
109 void UpdateSourceProps(const ALsource *source, Voice *voice, ALCcontext *context)
111 /* Get an unused property container, or allocate a new one as needed. */
112 VoicePropsItem *props{context->mFreeVoiceProps.load(std::memory_order_acquire)};
113 if(!props)
115 context->allocVoiceProps();
116 props = context->mFreeVoiceProps.load(std::memory_order_acquire);
118 VoicePropsItem *next;
119 do {
120 next = props->next.load(std::memory_order_relaxed);
121 } while(context->mFreeVoiceProps.compare_exchange_weak(props, next,
122 std::memory_order_acq_rel, std::memory_order_acquire) == false);
124 props->Pitch = source->Pitch;
125 props->Gain = source->Gain;
126 props->OuterGain = source->OuterGain;
127 props->MinGain = source->MinGain;
128 props->MaxGain = source->MaxGain;
129 props->InnerAngle = source->InnerAngle;
130 props->OuterAngle = source->OuterAngle;
131 props->RefDistance = source->RefDistance;
132 props->MaxDistance = source->MaxDistance;
133 props->RolloffFactor = source->RolloffFactor
134 #if ALSOFT_EAX
135 + source->RolloffFactor2
136 #endif
138 props->Position = source->Position;
139 props->Velocity = source->Velocity;
140 props->Direction = source->Direction;
141 props->OrientAt = source->OrientAt;
142 props->OrientUp = source->OrientUp;
143 props->HeadRelative = source->HeadRelative;
144 props->mDistanceModel = source->mDistanceModel;
145 props->mResampler = source->mResampler;
146 props->DirectChannels = source->DirectChannels;
147 props->mSpatializeMode = source->mSpatialize;
149 props->DryGainHFAuto = source->DryGainHFAuto;
150 props->WetGainAuto = source->WetGainAuto;
151 props->WetGainHFAuto = source->WetGainHFAuto;
152 props->OuterGainHF = source->OuterGainHF;
154 props->AirAbsorptionFactor = source->AirAbsorptionFactor;
155 props->RoomRolloffFactor = source->RoomRolloffFactor;
156 props->DopplerFactor = source->DopplerFactor;
158 props->StereoPan = source->StereoPan;
160 props->Radius = source->Radius;
161 props->EnhWidth = source->EnhWidth;
162 props->Panning = source->mPanningEnabled ? source->mPan : 0.0f;
164 props->Direct.Gain = source->Direct.Gain;
165 props->Direct.GainHF = source->Direct.GainHF;
166 props->Direct.HFReference = source->Direct.HFReference;
167 props->Direct.GainLF = source->Direct.GainLF;
168 props->Direct.LFReference = source->Direct.LFReference;
170 auto copy_send = [](const ALsource::SendData &srcsend) noexcept -> VoiceProps::SendData
172 VoiceProps::SendData ret{};
173 ret.Slot = srcsend.Slot ? srcsend.Slot->mSlot : nullptr;
174 ret.Gain = srcsend.Gain;
175 ret.GainHF = srcsend.GainHF;
176 ret.HFReference = srcsend.HFReference;
177 ret.GainLF = srcsend.GainLF;
178 ret.LFReference = srcsend.LFReference;
179 return ret;
181 std::transform(source->Send.cbegin(), source->Send.cend(), props->Send.begin(), copy_send);
182 if(!props->Send[0].Slot && context->mDefaultSlot)
183 props->Send[0].Slot = context->mDefaultSlot->mSlot;
185 /* Set the new container for updating internal parameters. */
186 props = voice->mUpdate.exchange(props, std::memory_order_acq_rel);
187 if(props)
189 /* If there was an unused update container, put it back in the
190 * freelist.
192 AtomicReplaceHead(context->mFreeVoiceProps, props);
196 /* GetSourceSampleOffset
198 * Gets the current read offset for the given Source, in 32.32 fixed-point
199 * samples. The offset is relative to the start of the queue (not the start of
200 * the current buffer).
202 int64_t GetSourceSampleOffset(ALsource *Source, ALCcontext *context, nanoseconds *clocktime)
204 auto *device = context->mALDevice.get();
205 const VoiceBufferItem *Current{};
206 int64_t readPos{};
207 uint refcount{};
208 Voice *voice{};
210 do {
211 refcount = device->waitForMix();
212 *clocktime = device->getClockTime();
213 voice = GetSourceVoice(Source, context);
214 if(voice)
216 Current = voice->mCurrentBuffer.load(std::memory_order_relaxed);
218 readPos = int64_t{voice->mPosition.load(std::memory_order_relaxed)} << MixerFracBits;
219 readPos += voice->mPositionFrac.load(std::memory_order_relaxed);
221 std::atomic_thread_fence(std::memory_order_acquire);
222 } while(refcount != device->mMixCount.load(std::memory_order_relaxed));
224 if(!voice)
225 return 0;
227 for(auto &item : Source->mQueue)
229 if(&item == Current) break;
230 readPos += int64_t{item.mSampleLen} << MixerFracBits;
232 if(readPos > std::numeric_limits<int64_t>::max() >> (32-MixerFracBits))
233 return std::numeric_limits<int64_t>::max();
234 return readPos << (32-MixerFracBits);
237 /* GetSourceSecOffset
239 * Gets the current read offset for the given Source, in seconds. The offset is
240 * relative to the start of the queue (not the start of the current buffer).
242 double GetSourceSecOffset(ALsource *Source, ALCcontext *context, nanoseconds *clocktime)
244 auto *device = context->mALDevice.get();
245 const VoiceBufferItem *Current{};
246 int64_t readPos{};
247 uint refcount{};
248 Voice *voice{};
250 do {
251 refcount = device->waitForMix();
252 *clocktime = device->getClockTime();
253 voice = GetSourceVoice(Source, context);
254 if(voice)
256 Current = voice->mCurrentBuffer.load(std::memory_order_relaxed);
258 readPos = int64_t{voice->mPosition.load(std::memory_order_relaxed)} << MixerFracBits;
259 readPos += voice->mPositionFrac.load(std::memory_order_relaxed);
261 std::atomic_thread_fence(std::memory_order_acquire);
262 } while(refcount != device->mMixCount.load(std::memory_order_relaxed));
264 if(!voice)
265 return 0.0f;
267 const ALbuffer *BufferFmt{nullptr};
268 auto BufferList = Source->mQueue.cbegin();
269 while(BufferList != Source->mQueue.cend() && al::to_address(BufferList) != Current)
271 if(!BufferFmt) BufferFmt = BufferList->mBuffer;
272 readPos += int64_t{BufferList->mSampleLen} << MixerFracBits;
273 ++BufferList;
275 while(BufferList != Source->mQueue.cend() && !BufferFmt)
277 BufferFmt = BufferList->mBuffer;
278 ++BufferList;
280 ASSUME(BufferFmt != nullptr);
282 return static_cast<double>(readPos) / double{MixerFracOne} / BufferFmt->mSampleRate;
285 /* GetSourceOffset
287 * Gets the current read offset for the given Source, in the appropriate format
288 * (Bytes, Samples or Seconds). The offset is relative to the start of the
289 * queue (not the start of the current buffer).
291 template<typename T>
292 NOINLINE T GetSourceOffset(ALsource *Source, ALenum name, ALCcontext *context)
294 auto *device = context->mALDevice.get();
295 const VoiceBufferItem *Current{};
296 int64_t readPos{};
297 uint readPosFrac{};
298 uint refcount;
299 Voice *voice;
301 do {
302 refcount = device->waitForMix();
303 voice = GetSourceVoice(Source, context);
304 if(voice)
306 Current = voice->mCurrentBuffer.load(std::memory_order_relaxed);
308 readPos = voice->mPosition.load(std::memory_order_relaxed);
309 readPosFrac = voice->mPositionFrac.load(std::memory_order_relaxed);
311 std::atomic_thread_fence(std::memory_order_acquire);
312 } while(refcount != device->mMixCount.load(std::memory_order_relaxed));
314 if(!voice)
315 return T{0};
317 const ALbuffer *BufferFmt{nullptr};
318 auto BufferList = Source->mQueue.cbegin();
319 while(BufferList != Source->mQueue.cend() && al::to_address(BufferList) != Current)
321 if(!BufferFmt) BufferFmt = BufferList->mBuffer;
322 readPos += BufferList->mSampleLen;
323 ++BufferList;
325 while(BufferList != Source->mQueue.cend() && !BufferFmt)
327 BufferFmt = BufferList->mBuffer;
328 ++BufferList;
330 ASSUME(BufferFmt != nullptr);
332 T offset{};
333 switch(name)
335 case AL_SEC_OFFSET:
336 if constexpr(std::is_floating_point_v<T>)
338 offset = static_cast<T>(readPos) + static_cast<T>(readPosFrac)/T{MixerFracOne};
339 offset /= static_cast<T>(BufferFmt->mSampleRate);
341 else
343 readPos /= BufferFmt->mSampleRate;
344 offset = static_cast<T>(std::clamp<int64_t>(readPos, std::numeric_limits<T>::min(),
345 std::numeric_limits<T>::max()));
347 break;
349 case AL_SAMPLE_OFFSET:
350 if constexpr(std::is_floating_point_v<T>)
351 offset = static_cast<T>(readPos) + static_cast<T>(readPosFrac)/T{MixerFracOne};
352 else
353 offset = static_cast<T>(std::clamp<int64_t>(readPos, std::numeric_limits<T>::min(),
354 std::numeric_limits<T>::max()));
355 break;
357 case AL_BYTE_OFFSET:
358 const ALuint BlockSamples{BufferFmt->mBlockAlign};
359 const ALuint BlockSize{BufferFmt->blockSizeFromFmt()};
360 /* Round down to the block boundary. */
361 readPos = readPos / BlockSamples * BlockSize;
363 if constexpr(std::is_floating_point_v<T>)
364 offset = static_cast<T>(readPos);
365 else
367 if(readPos > std::numeric_limits<T>::max())
368 offset = RoundDown(std::numeric_limits<T>::max(), static_cast<T>(BlockSize));
369 else if(readPos < std::numeric_limits<T>::min())
370 offset = RoundUp(std::numeric_limits<T>::min(), static_cast<T>(BlockSize));
371 else
372 offset = static_cast<T>(readPos);
374 break;
376 return offset;
379 /* GetSourceLength
381 * Gets the length of the given Source's buffer queue, in the appropriate
382 * format (Bytes, Samples or Seconds).
384 template<typename T>
385 NOINLINE T GetSourceLength(const ALsource *source, ALenum name)
387 uint64_t length{0};
388 const ALbuffer *BufferFmt{nullptr};
389 for(auto &listitem : source->mQueue)
391 if(!BufferFmt)
392 BufferFmt = listitem.mBuffer;
393 length += listitem.mSampleLen;
395 if(length == 0)
396 return T{0};
398 ASSUME(BufferFmt != nullptr);
399 switch(name)
401 case AL_SEC_LENGTH_SOFT:
402 if constexpr(std::is_floating_point_v<T>)
403 return static_cast<T>(length) / static_cast<T>(BufferFmt->mSampleRate);
404 else
405 return static_cast<T>(std::min<uint64_t>(length/BufferFmt->mSampleRate,
406 std::numeric_limits<T>::max()));
408 case AL_SAMPLE_LENGTH_SOFT:
409 if constexpr(std::is_floating_point_v<T>)
410 return static_cast<T>(length);
411 else
412 return static_cast<T>(std::min<uint64_t>(length, std::numeric_limits<T>::max()));
414 case AL_BYTE_LENGTH_SOFT:
415 const ALuint BlockSamples{BufferFmt->mBlockAlign};
416 const ALuint BlockSize{BufferFmt->blockSizeFromFmt()};
417 /* Round down to the block boundary. */
418 length = length / BlockSamples * BlockSize;
420 if constexpr(std::is_floating_point_v<T>)
421 return static_cast<T>(length);
422 else
424 if(length > uint64_t{std::numeric_limits<T>::max()})
425 return RoundDown(std::numeric_limits<T>::max(), static_cast<T>(BlockSize));
426 return static_cast<T>(length);
429 return T{0};
433 struct VoicePos {
434 int pos;
435 uint frac;
436 ALbufferQueueItem *bufferitem;
440 * GetSampleOffset
442 * Retrieves the voice position, fixed-point fraction, and bufferlist item
443 * using the given offset type and offset. If the offset is out of range,
444 * returns an empty optional.
446 std::optional<VoicePos> GetSampleOffset(std::deque<ALbufferQueueItem> &BufferList,
447 ALenum OffsetType, double Offset)
449 /* Find the first valid Buffer in the Queue */
450 const ALbuffer *BufferFmt{nullptr};
451 for(auto &item : BufferList)
453 BufferFmt = item.mBuffer;
454 if(BufferFmt) break;
456 if(!BufferFmt) UNLIKELY
457 return std::nullopt;
459 /* Get sample frame offset */
460 int64_t offset{};
461 uint frac{};
462 double dbloff, dblfrac;
463 switch(OffsetType)
465 case AL_SEC_OFFSET:
466 dblfrac = std::modf(Offset*BufferFmt->mSampleRate, &dbloff);
467 if(dblfrac < 0.0)
469 /* If there's a negative fraction, reduce the offset to "floor" it,
470 * and convert the fraction to a percentage to the next value (e.g.
471 * -2.75 -> -3 + 0.25).
473 dbloff -= 1.0;
474 dblfrac += 1.0;
476 offset = static_cast<int64_t>(dbloff);
477 frac = static_cast<uint>(std::min(dblfrac*MixerFracOne, MixerFracOne-1.0));
478 break;
480 case AL_SAMPLE_OFFSET:
481 dblfrac = std::modf(Offset, &dbloff);
482 if(dblfrac < 0.0)
484 dbloff -= 1.0;
485 dblfrac += 1.0;
487 offset = static_cast<int64_t>(dbloff);
488 frac = static_cast<uint>(std::min(dblfrac*MixerFracOne, MixerFracOne-1.0));
489 break;
491 case AL_BYTE_OFFSET:
492 /* Determine the ByteOffset (and ensure it is block aligned) */
493 Offset = std::floor(Offset / BufferFmt->blockSizeFromFmt());
494 offset = static_cast<int64_t>(Offset) * BufferFmt->mBlockAlign;
495 frac = 0;
496 break;
499 /* Find the bufferlist item this offset belongs to. */
500 if(offset < 0)
502 if(offset < std::numeric_limits<int>::min())
503 return std::nullopt;
504 return VoicePos{static_cast<int>(offset), frac, &BufferList.front()};
507 if(BufferFmt->mCallback)
508 return std::nullopt;
510 int64_t totalBufferLen{0};
511 for(auto &item : BufferList)
513 if(totalBufferLen > offset)
514 break;
515 if(item.mSampleLen > offset-totalBufferLen)
517 /* Offset is in this buffer */
518 return VoicePos{static_cast<int>(offset-totalBufferLen), frac, &item};
520 totalBufferLen += item.mSampleLen;
523 /* Offset is out of range of the queue */
524 return std::nullopt;
528 void InitVoice(Voice *voice, ALsource *source, ALbufferQueueItem *BufferList, ALCcontext *context,
529 al::Device *device)
531 voice->mLoopBuffer.store(source->Looping ? &source->mQueue.front() : nullptr,
532 std::memory_order_relaxed);
534 ALbuffer *buffer{BufferList->mBuffer};
535 voice->mFrequency = buffer->mSampleRate;
536 if(buffer->mChannels == FmtMono && source->mPanningEnabled)
537 voice->mFmtChannels = FmtMonoDup;
538 else if(buffer->mChannels == FmtStereo && source->mStereoMode == SourceStereo::Enhanced)
539 voice->mFmtChannels = FmtSuperStereo;
540 else
541 voice->mFmtChannels = buffer->mChannels;
542 voice->mFmtType = buffer->mType;
543 voice->mFrameStep = buffer->channelsFromFmt();
544 voice->mBytesPerBlock = buffer->blockSizeFromFmt();
545 voice->mSamplesPerBlock = buffer->mBlockAlign;
546 voice->mAmbiLayout = IsUHJ(voice->mFmtChannels) ? AmbiLayout::FuMa : buffer->mAmbiLayout;
547 voice->mAmbiScaling = IsUHJ(voice->mFmtChannels) ? AmbiScaling::UHJ : buffer->mAmbiScaling;
548 voice->mAmbiOrder = (voice->mFmtChannels == FmtSuperStereo) ? 1 : buffer->mAmbiOrder;
550 if(buffer->mCallback) voice->mFlags.set(VoiceIsCallback);
551 else if(source->SourceType == AL_STATIC) voice->mFlags.set(VoiceIsStatic);
552 voice->mNumCallbackBlocks = 0;
553 voice->mCallbackBlockBase = 0;
555 voice->prepare(device);
557 source->mPropsDirty = false;
558 UpdateSourceProps(source, voice, context);
560 voice->mSourceID.store(source->id, std::memory_order_release);
564 VoiceChange *GetVoiceChanger(ALCcontext *ctx)
566 VoiceChange *vchg{ctx->mVoiceChangeTail};
567 if(vchg == ctx->mCurrentVoiceChange.load(std::memory_order_acquire)) UNLIKELY
569 ctx->allocVoiceChanges();
570 vchg = ctx->mVoiceChangeTail;
573 ctx->mVoiceChangeTail = vchg->mNext.exchange(nullptr, std::memory_order_relaxed);
575 return vchg;
578 void SendVoiceChanges(ALCcontext *ctx, VoiceChange *tail)
580 auto *device = ctx->mALDevice.get();
582 VoiceChange *oldhead{ctx->mCurrentVoiceChange.load(std::memory_order_acquire)};
583 while(VoiceChange *next{oldhead->mNext.load(std::memory_order_relaxed)})
584 oldhead = next;
585 oldhead->mNext.store(tail, std::memory_order_release);
587 const bool connected{device->Connected.load(std::memory_order_acquire)};
588 std::ignore = device->waitForMix();
589 if(!connected) UNLIKELY
591 if(ctx->mStopVoicesOnDisconnect.load(std::memory_order_acquire))
593 /* If the device is disconnected and voices are stopped, just
594 * ignore all pending changes.
596 VoiceChange *cur{ctx->mCurrentVoiceChange.load(std::memory_order_acquire)};
597 while(VoiceChange *next{cur->mNext.load(std::memory_order_acquire)})
599 cur = next;
600 if(Voice *voice{cur->mVoice})
601 voice->mSourceID.store(0, std::memory_order_relaxed);
603 ctx->mCurrentVoiceChange.store(cur, std::memory_order_release);
609 auto SetVoiceOffset(Voice *oldvoice, const VoicePos &vpos, ALsource *source, ALCcontext *context,
610 al::Device *device) -> bool
612 /* First, get a free voice to start at the new offset. */
613 auto voicelist = context->getVoicesSpan();
614 Voice *newvoice{};
615 ALuint vidx{0};
616 for(Voice *voice : voicelist)
618 if(voice->mPlayState.load(std::memory_order_acquire) == Voice::Stopped
619 && voice->mSourceID.load(std::memory_order_relaxed) == 0u
620 && voice->mPendingChange.load(std::memory_order_relaxed) == false)
622 newvoice = voice;
623 break;
625 ++vidx;
627 if(!newvoice) UNLIKELY
629 auto &allvoices = *context->mVoices.load(std::memory_order_relaxed);
630 if(allvoices.size() == voicelist.size())
631 context->allocVoices(1);
632 context->mActiveVoiceCount.fetch_add(1, std::memory_order_release);
633 voicelist = context->getVoicesSpan();
635 vidx = 0;
636 for(Voice *voice : voicelist)
638 if(voice->mPlayState.load(std::memory_order_acquire) == Voice::Stopped
639 && voice->mSourceID.load(std::memory_order_relaxed) == 0u
640 && voice->mPendingChange.load(std::memory_order_relaxed) == false)
642 newvoice = voice;
643 break;
645 ++vidx;
647 ASSUME(newvoice != nullptr);
650 /* Initialize the new voice and set its starting offset.
651 * TODO: It might be better to have the VoiceChange processing copy the old
652 * voice's mixing parameters (and pending update) insead of initializing it
653 * all here. This would just need to set the minimum properties to link the
654 * voice to the source and its position-dependent properties (including the
655 * fading flag).
657 newvoice->mPlayState.store(Voice::Pending, std::memory_order_relaxed);
658 newvoice->mPosition.store(vpos.pos, std::memory_order_relaxed);
659 newvoice->mPositionFrac.store(vpos.frac, std::memory_order_relaxed);
660 newvoice->mCurrentBuffer.store(vpos.bufferitem, std::memory_order_relaxed);
661 newvoice->mStartTime = oldvoice->mStartTime;
662 newvoice->mFlags.reset();
663 if(vpos.pos > 0 || (vpos.pos == 0 && vpos.frac > 0)
664 || vpos.bufferitem != &source->mQueue.front())
665 newvoice->mFlags.set(VoiceIsFading);
666 InitVoice(newvoice, source, vpos.bufferitem, context, device);
667 source->VoiceIdx = vidx;
669 /* Set the old voice as having a pending change, and send it off with the
670 * new one with a new offset voice change.
672 oldvoice->mPendingChange.store(true, std::memory_order_relaxed);
674 VoiceChange *vchg{GetVoiceChanger(context)};
675 vchg->mOldVoice = oldvoice;
676 vchg->mVoice = newvoice;
677 vchg->mSourceID = source->id;
678 vchg->mState = VChangeState::Restart;
679 SendVoiceChanges(context, vchg);
681 /* If the old voice still has a sourceID, it's still active and the change-
682 * over will work on the next update.
684 if(oldvoice->mSourceID.load(std::memory_order_acquire) != 0u) LIKELY
685 return true;
687 /* Otherwise, if the new voice's state is not pending, the change-over
688 * already happened.
690 if(newvoice->mPlayState.load(std::memory_order_acquire) != Voice::Pending)
691 return true;
693 /* Otherwise, wait for any current mix to finish and check one last time. */
694 std::ignore = device->waitForMix();
695 if(newvoice->mPlayState.load(std::memory_order_acquire) != Voice::Pending)
696 return true;
697 /* The change-over failed because the old voice stopped before the new
698 * voice could start at the new offset. Let go of the new voice and have
699 * the caller store the source offset since it's stopped.
701 newvoice->mCurrentBuffer.store(nullptr, std::memory_order_relaxed);
702 newvoice->mLoopBuffer.store(nullptr, std::memory_order_relaxed);
703 newvoice->mSourceID.store(0u, std::memory_order_relaxed);
704 newvoice->mPlayState.store(Voice::Stopped, std::memory_order_relaxed);
705 return false;
710 * Returns if the last known state for the source was playing or paused. Does
711 * not sync with the mixer voice.
713 inline bool IsPlayingOrPaused(ALsource *source)
714 { return source->state == AL_PLAYING || source->state == AL_PAUSED; }
717 * Returns an updated source state using the matching voice's status (or lack
718 * thereof).
720 inline ALenum GetSourceState(ALsource *source, Voice *voice)
722 if(!voice && source->state == AL_PLAYING)
723 source->state = AL_STOPPED;
724 return source->state;
728 bool EnsureSources(ALCcontext *context, size_t needed)
730 size_t count{std::accumulate(context->mSourceList.cbegin(), context->mSourceList.cend(), 0_uz,
731 [](size_t cur, const SourceSubList &sublist) noexcept -> size_t
732 { return cur + static_cast<ALuint>(al::popcount(sublist.FreeMask)); })};
734 try {
735 while(needed > count)
737 if(context->mSourceList.size() >= 1<<25) UNLIKELY
738 return false;
740 SourceSubList sublist{};
741 sublist.FreeMask = ~0_u64;
742 sublist.Sources = SubListAllocator{}.allocate(1);
743 context->mSourceList.emplace_back(std::move(sublist));
744 count += std::tuple_size_v<SubListAllocator::value_type>;
747 catch(...) {
748 return false;
750 return true;
753 ALsource *AllocSource(ALCcontext *context) noexcept
755 auto sublist = std::find_if(context->mSourceList.begin(), context->mSourceList.end(),
756 [](const SourceSubList &entry) noexcept -> bool
757 { return entry.FreeMask != 0; });
758 auto lidx = static_cast<ALuint>(std::distance(context->mSourceList.begin(), sublist));
759 auto slidx = static_cast<ALuint>(al::countr_zero(sublist->FreeMask));
760 ASSUME(slidx < 64);
762 ALsource *source{al::construct_at(al::to_address(sublist->Sources->begin() + slidx))};
763 #if ALSOFT_EAX
764 source->eaxInitialize(context);
765 #endif // ALSOFT_EAX
767 /* Add 1 to avoid source ID 0. */
768 source->id = ((lidx<<6) | slidx) + 1;
770 context->mNumSources += 1;
771 sublist->FreeMask &= ~(1_u64 << slidx);
773 return source;
776 void FreeSource(ALCcontext *context, ALsource *source)
778 context->mSourceNames.erase(source->id);
780 const ALuint id{source->id - 1};
781 const size_t lidx{id >> 6};
782 const ALuint slidx{id & 0x3f};
784 if(Voice *voice{GetSourceVoice(source, context)})
786 VoiceChange *vchg{GetVoiceChanger(context)};
788 voice->mPendingChange.store(true, std::memory_order_relaxed);
789 vchg->mVoice = voice;
790 vchg->mSourceID = source->id;
791 vchg->mState = VChangeState::Stop;
793 SendVoiceChanges(context, vchg);
796 std::destroy_at(source);
798 context->mSourceList[lidx].FreeMask |= 1_u64 << slidx;
799 context->mNumSources--;
803 inline ALsource *LookupSource(ALCcontext *context, ALuint id) noexcept
805 const size_t lidx{(id-1) >> 6};
806 const ALuint slidx{(id-1) & 0x3f};
808 if(lidx >= context->mSourceList.size()) UNLIKELY
809 return nullptr;
810 SourceSubList &sublist{context->mSourceList[lidx]};
811 if(sublist.FreeMask & (1_u64 << slidx)) UNLIKELY
812 return nullptr;
813 return al::to_address(sublist.Sources->begin() + slidx);
816 auto LookupBuffer = [](al::Device *device, auto id) noexcept -> ALbuffer*
818 const auto lidx{(id-1) >> 6};
819 const auto slidx{(id-1) & 0x3f};
821 if(lidx >= device->BufferList.size()) UNLIKELY
822 return nullptr;
823 BufferSubList &sublist = device->BufferList[static_cast<size_t>(lidx)];
824 if(sublist.FreeMask & (1_u64 << slidx)) UNLIKELY
825 return nullptr;
826 return al::to_address(sublist.Buffers->begin() + static_cast<size_t>(slidx));
829 auto LookupFilter = [](al::Device *device, auto id) noexcept -> ALfilter*
831 const auto lidx{(id-1) >> 6};
832 const auto slidx{(id-1) & 0x3f};
834 if(lidx >= device->FilterList.size()) UNLIKELY
835 return nullptr;
836 FilterSubList &sublist = device->FilterList[static_cast<size_t>(lidx)];
837 if(sublist.FreeMask & (1_u64 << slidx)) UNLIKELY
838 return nullptr;
839 return al::to_address(sublist.Filters->begin() + static_cast<size_t>(slidx));
842 auto LookupEffectSlot = [](ALCcontext *context, auto id) noexcept -> ALeffectslot*
844 const auto lidx{(id-1) >> 6};
845 const auto slidx{(id-1) & 0x3f};
847 if(lidx >= context->mEffectSlotList.size()) UNLIKELY
848 return nullptr;
849 EffectSlotSubList &sublist{context->mEffectSlotList[static_cast<size_t>(lidx)]};
850 if(sublist.FreeMask & (1_u64 << slidx)) UNLIKELY
851 return nullptr;
852 return al::to_address(sublist.EffectSlots->begin() + static_cast<size_t>(slidx));
856 auto StereoModeFromEnum = [](auto mode) noexcept -> std::optional<SourceStereo>
858 switch(mode)
860 case AL_NORMAL_SOFT: return SourceStereo::Normal;
861 case AL_SUPER_STEREO_SOFT: return SourceStereo::Enhanced;
863 return std::nullopt;
865 ALenum EnumFromStereoMode(SourceStereo mode)
867 switch(mode)
869 case SourceStereo::Normal: return AL_NORMAL_SOFT;
870 case SourceStereo::Enhanced: return AL_SUPER_STEREO_SOFT;
872 throw std::runtime_error{"Invalid SourceStereo: "+std::to_string(int(mode))};
875 auto SpatializeModeFromEnum = [](auto mode) noexcept -> std::optional<SpatializeMode>
877 switch(mode)
879 case AL_FALSE: return SpatializeMode::Off;
880 case AL_TRUE: return SpatializeMode::On;
881 case AL_AUTO_SOFT: return SpatializeMode::Auto;
883 return std::nullopt;
885 ALenum EnumFromSpatializeMode(SpatializeMode mode)
887 switch(mode)
889 case SpatializeMode::Off: return AL_FALSE;
890 case SpatializeMode::On: return AL_TRUE;
891 case SpatializeMode::Auto: return AL_AUTO_SOFT;
893 throw std::runtime_error{"Invalid SpatializeMode: "+std::to_string(int(mode))};
896 auto DirectModeFromEnum = [](auto mode) noexcept -> std::optional<DirectMode>
898 switch(mode)
900 case AL_FALSE: return DirectMode::Off;
901 case AL_DROP_UNMATCHED_SOFT: return DirectMode::DropMismatch;
902 case AL_REMIX_UNMATCHED_SOFT: return DirectMode::RemixMismatch;
904 return std::nullopt;
906 ALenum EnumFromDirectMode(DirectMode mode)
908 switch(mode)
910 case DirectMode::Off: return AL_FALSE;
911 case DirectMode::DropMismatch: return AL_DROP_UNMATCHED_SOFT;
912 case DirectMode::RemixMismatch: return AL_REMIX_UNMATCHED_SOFT;
914 throw std::runtime_error{"Invalid DirectMode: "+std::to_string(int(mode))};
917 auto DistanceModelFromALenum = [](auto model) noexcept -> std::optional<DistanceModel>
919 switch(model)
921 case AL_NONE: return DistanceModel::Disable;
922 case AL_INVERSE_DISTANCE: return DistanceModel::Inverse;
923 case AL_INVERSE_DISTANCE_CLAMPED: return DistanceModel::InverseClamped;
924 case AL_LINEAR_DISTANCE: return DistanceModel::Linear;
925 case AL_LINEAR_DISTANCE_CLAMPED: return DistanceModel::LinearClamped;
926 case AL_EXPONENT_DISTANCE: return DistanceModel::Exponent;
927 case AL_EXPONENT_DISTANCE_CLAMPED: return DistanceModel::ExponentClamped;
929 return std::nullopt;
931 ALenum ALenumFromDistanceModel(DistanceModel model)
933 switch(model)
935 case DistanceModel::Disable: return AL_NONE;
936 case DistanceModel::Inverse: return AL_INVERSE_DISTANCE;
937 case DistanceModel::InverseClamped: return AL_INVERSE_DISTANCE_CLAMPED;
938 case DistanceModel::Linear: return AL_LINEAR_DISTANCE;
939 case DistanceModel::LinearClamped: return AL_LINEAR_DISTANCE_CLAMPED;
940 case DistanceModel::Exponent: return AL_EXPONENT_DISTANCE;
941 case DistanceModel::ExponentClamped: return AL_EXPONENT_DISTANCE_CLAMPED;
943 throw std::runtime_error{"Unexpected distance model "+std::to_string(static_cast<int>(model))};
946 enum SourceProp : ALenum {
947 srcPitch = AL_PITCH,
948 srcGain = AL_GAIN,
949 srcMinGain = AL_MIN_GAIN,
950 srcMaxGain = AL_MAX_GAIN,
951 srcMaxDistance = AL_MAX_DISTANCE,
952 srcRolloffFactor = AL_ROLLOFF_FACTOR,
953 srcDopplerFactor = AL_DOPPLER_FACTOR,
954 srcConeOuterGain = AL_CONE_OUTER_GAIN,
955 srcSecOffset = AL_SEC_OFFSET,
956 srcSampleOffset = AL_SAMPLE_OFFSET,
957 srcByteOffset = AL_BYTE_OFFSET,
958 srcConeInnerAngle = AL_CONE_INNER_ANGLE,
959 srcConeOuterAngle = AL_CONE_OUTER_ANGLE,
960 srcRefDistance = AL_REFERENCE_DISTANCE,
962 srcPosition = AL_POSITION,
963 srcVelocity = AL_VELOCITY,
964 srcDirection = AL_DIRECTION,
966 srcSourceRelative = AL_SOURCE_RELATIVE,
967 srcLooping = AL_LOOPING,
968 srcBuffer = AL_BUFFER,
969 srcSourceState = AL_SOURCE_STATE,
970 srcBuffersQueued = AL_BUFFERS_QUEUED,
971 srcBuffersProcessed = AL_BUFFERS_PROCESSED,
972 srcSourceType = AL_SOURCE_TYPE,
974 /* ALC_EXT_EFX */
975 srcConeOuterGainHF = AL_CONE_OUTER_GAINHF,
976 srcAirAbsorptionFactor = AL_AIR_ABSORPTION_FACTOR,
977 srcRoomRolloffFactor = AL_ROOM_ROLLOFF_FACTOR,
978 srcDirectFilterGainHFAuto = AL_DIRECT_FILTER_GAINHF_AUTO,
979 srcAuxSendFilterGainAuto = AL_AUXILIARY_SEND_FILTER_GAIN_AUTO,
980 srcAuxSendFilterGainHFAuto = AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO,
981 srcDirectFilter = AL_DIRECT_FILTER,
982 srcAuxSendFilter = AL_AUXILIARY_SEND_FILTER,
984 /* AL_SOFT_direct_channels */
985 srcDirectChannelsSOFT = AL_DIRECT_CHANNELS_SOFT,
987 /* AL_EXT_source_distance_model */
988 srcDistanceModel = AL_DISTANCE_MODEL,
990 /* AL_SOFT_source_latency */
991 srcSampleOffsetLatencySOFT = AL_SAMPLE_OFFSET_LATENCY_SOFT,
992 srcSecOffsetLatencySOFT = AL_SEC_OFFSET_LATENCY_SOFT,
994 /* AL_EXT_STEREO_ANGLES */
995 srcAngles = AL_STEREO_ANGLES,
997 /* AL_EXT_SOURCE_RADIUS */
998 srcRadius = AL_SOURCE_RADIUS,
1000 /* AL_EXT_BFORMAT */
1001 srcOrientation = AL_ORIENTATION,
1003 /* AL_SOFT_source_length */
1004 srcByteLength = AL_BYTE_LENGTH_SOFT,
1005 srcSampleLength = AL_SAMPLE_LENGTH_SOFT,
1006 srcSecLength = AL_SEC_LENGTH_SOFT,
1008 /* AL_SOFT_source_resampler */
1009 srcResampler = AL_SOURCE_RESAMPLER_SOFT,
1011 /* AL_SOFT_source_spatialize */
1012 srcSpatialize = AL_SOURCE_SPATIALIZE_SOFT,
1014 /* ALC_SOFT_device_clock */
1015 srcSampleOffsetClockSOFT = AL_SAMPLE_OFFSET_CLOCK_SOFT,
1016 srcSecOffsetClockSOFT = AL_SEC_OFFSET_CLOCK_SOFT,
1018 /* AL_SOFT_UHJ */
1019 srcStereoMode = AL_STEREO_MODE_SOFT,
1020 srcSuperStereoWidth = AL_SUPER_STEREO_WIDTH_SOFT,
1022 /* AL_SOFT_buffer_sub_data */
1023 srcByteRWOffsetsSOFT = AL_BYTE_RW_OFFSETS_SOFT,
1024 srcSampleRWOffsetsSOFT = AL_SAMPLE_RW_OFFSETS_SOFT,
1026 /* AL_SOFT_source_panning */
1027 srcPanningEnabledSOFT = AL_PANNING_ENABLED_SOFT,
1028 srcPanSOFT = AL_PAN_SOFT,
1032 constexpr ALuint IntValsByProp(ALenum prop)
1034 switch(static_cast<SourceProp>(prop))
1036 case AL_SOURCE_STATE:
1037 case AL_SOURCE_TYPE:
1038 case AL_BUFFERS_QUEUED:
1039 case AL_BUFFERS_PROCESSED:
1040 case AL_BYTE_LENGTH_SOFT:
1041 case AL_SAMPLE_LENGTH_SOFT:
1042 case AL_SOURCE_RELATIVE:
1043 case AL_LOOPING:
1044 case AL_BUFFER:
1045 case AL_SAMPLE_OFFSET:
1046 case AL_BYTE_OFFSET:
1047 case AL_DIRECT_FILTER:
1048 case AL_DIRECT_FILTER_GAINHF_AUTO:
1049 case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO:
1050 case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO:
1051 case AL_DIRECT_CHANNELS_SOFT:
1052 case AL_DISTANCE_MODEL:
1053 case AL_SOURCE_RESAMPLER_SOFT:
1054 case AL_SOURCE_SPATIALIZE_SOFT:
1055 case AL_STEREO_MODE_SOFT:
1056 case AL_PANNING_ENABLED_SOFT:
1057 case AL_PAN_SOFT:
1058 return 1;
1060 case AL_SOURCE_RADIUS: /*AL_BYTE_RW_OFFSETS_SOFT:*/
1061 if(sBufferSubDataCompat)
1062 return 2;
1063 /*fall-through*/
1064 case AL_CONE_INNER_ANGLE:
1065 case AL_CONE_OUTER_ANGLE:
1066 case AL_PITCH:
1067 case AL_GAIN:
1068 case AL_MIN_GAIN:
1069 case AL_MAX_GAIN:
1070 case AL_REFERENCE_DISTANCE:
1071 case AL_ROLLOFF_FACTOR:
1072 case AL_CONE_OUTER_GAIN:
1073 case AL_MAX_DISTANCE:
1074 case AL_SEC_OFFSET:
1075 case AL_DOPPLER_FACTOR:
1076 case AL_CONE_OUTER_GAINHF:
1077 case AL_AIR_ABSORPTION_FACTOR:
1078 case AL_ROOM_ROLLOFF_FACTOR:
1079 case AL_SEC_LENGTH_SOFT:
1080 case AL_SUPER_STEREO_WIDTH_SOFT:
1081 return 1; /* 1x float */
1083 case AL_SAMPLE_RW_OFFSETS_SOFT:
1084 if(sBufferSubDataCompat)
1085 return 2;
1086 break;
1088 case AL_AUXILIARY_SEND_FILTER:
1089 return 3;
1091 case AL_POSITION:
1092 case AL_VELOCITY:
1093 case AL_DIRECTION:
1094 return 3; /* 3x float */
1096 case AL_ORIENTATION:
1097 return 6; /* 6x float */
1099 case AL_SAMPLE_OFFSET_LATENCY_SOFT:
1100 case AL_SAMPLE_OFFSET_CLOCK_SOFT:
1101 case AL_STEREO_ANGLES:
1102 break; /* i64 only */
1103 case AL_SEC_OFFSET_LATENCY_SOFT:
1104 case AL_SEC_OFFSET_CLOCK_SOFT:
1105 break; /* double only */
1108 return 0;
1111 constexpr ALuint Int64ValsByProp(ALenum prop)
1113 switch(static_cast<SourceProp>(prop))
1115 case AL_SOURCE_STATE:
1116 case AL_SOURCE_TYPE:
1117 case AL_BUFFERS_QUEUED:
1118 case AL_BUFFERS_PROCESSED:
1119 case AL_BYTE_LENGTH_SOFT:
1120 case AL_SAMPLE_LENGTH_SOFT:
1121 case AL_SOURCE_RELATIVE:
1122 case AL_LOOPING:
1123 case AL_BUFFER:
1124 case AL_SAMPLE_OFFSET:
1125 case AL_BYTE_OFFSET:
1126 case AL_DIRECT_FILTER:
1127 case AL_DIRECT_FILTER_GAINHF_AUTO:
1128 case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO:
1129 case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO:
1130 case AL_DIRECT_CHANNELS_SOFT:
1131 case AL_DISTANCE_MODEL:
1132 case AL_SOURCE_RESAMPLER_SOFT:
1133 case AL_SOURCE_SPATIALIZE_SOFT:
1134 case AL_STEREO_MODE_SOFT:
1135 case AL_PANNING_ENABLED_SOFT:
1136 case AL_PAN_SOFT:
1137 return 1;
1139 case AL_SOURCE_RADIUS: /*AL_BYTE_RW_OFFSETS_SOFT:*/
1140 if(sBufferSubDataCompat)
1141 return 2;
1142 /*fall-through*/
1143 case AL_CONE_INNER_ANGLE:
1144 case AL_CONE_OUTER_ANGLE:
1145 case AL_PITCH:
1146 case AL_GAIN:
1147 case AL_MIN_GAIN:
1148 case AL_MAX_GAIN:
1149 case AL_REFERENCE_DISTANCE:
1150 case AL_ROLLOFF_FACTOR:
1151 case AL_CONE_OUTER_GAIN:
1152 case AL_MAX_DISTANCE:
1153 case AL_SEC_OFFSET:
1154 case AL_DOPPLER_FACTOR:
1155 case AL_CONE_OUTER_GAINHF:
1156 case AL_AIR_ABSORPTION_FACTOR:
1157 case AL_ROOM_ROLLOFF_FACTOR:
1158 case AL_SEC_LENGTH_SOFT:
1159 case AL_SUPER_STEREO_WIDTH_SOFT:
1160 return 1; /* 1x float */
1162 case AL_SAMPLE_RW_OFFSETS_SOFT:
1163 if(sBufferSubDataCompat)
1164 return 2;
1165 break;
1167 case AL_SAMPLE_OFFSET_LATENCY_SOFT:
1168 case AL_SAMPLE_OFFSET_CLOCK_SOFT:
1169 case AL_STEREO_ANGLES:
1170 return 2;
1172 case AL_AUXILIARY_SEND_FILTER:
1173 return 3;
1175 case AL_POSITION:
1176 case AL_VELOCITY:
1177 case AL_DIRECTION:
1178 return 3; /* 3x float */
1180 case AL_ORIENTATION:
1181 return 6; /* 6x float */
1183 case AL_SEC_OFFSET_LATENCY_SOFT:
1184 case AL_SEC_OFFSET_CLOCK_SOFT:
1185 break; /* double only */
1188 return 0;
1191 constexpr ALuint FloatValsByProp(ALenum prop)
1193 switch(static_cast<SourceProp>(prop))
1195 case AL_PITCH:
1196 case AL_GAIN:
1197 case AL_MIN_GAIN:
1198 case AL_MAX_GAIN:
1199 case AL_MAX_DISTANCE:
1200 case AL_ROLLOFF_FACTOR:
1201 case AL_DOPPLER_FACTOR:
1202 case AL_CONE_OUTER_GAIN:
1203 case AL_SEC_OFFSET:
1204 case AL_SAMPLE_OFFSET:
1205 case AL_BYTE_OFFSET:
1206 case AL_CONE_INNER_ANGLE:
1207 case AL_CONE_OUTER_ANGLE:
1208 case AL_REFERENCE_DISTANCE:
1209 case AL_CONE_OUTER_GAINHF:
1210 case AL_AIR_ABSORPTION_FACTOR:
1211 case AL_ROOM_ROLLOFF_FACTOR:
1212 case AL_DIRECT_FILTER_GAINHF_AUTO:
1213 case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO:
1214 case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO:
1215 case AL_DIRECT_CHANNELS_SOFT:
1216 case AL_DISTANCE_MODEL:
1217 case AL_SOURCE_RELATIVE:
1218 case AL_LOOPING:
1219 case AL_SOURCE_STATE:
1220 case AL_BUFFERS_QUEUED:
1221 case AL_BUFFERS_PROCESSED:
1222 case AL_SOURCE_TYPE:
1223 case AL_SOURCE_RESAMPLER_SOFT:
1224 case AL_SOURCE_SPATIALIZE_SOFT:
1225 case AL_BYTE_LENGTH_SOFT:
1226 case AL_SAMPLE_LENGTH_SOFT:
1227 case AL_SEC_LENGTH_SOFT:
1228 case AL_STEREO_MODE_SOFT:
1229 case AL_SUPER_STEREO_WIDTH_SOFT:
1230 case AL_PANNING_ENABLED_SOFT:
1231 case AL_PAN_SOFT:
1232 return 1;
1234 case AL_SOURCE_RADIUS: /*AL_BYTE_RW_OFFSETS_SOFT:*/
1235 if(!sBufferSubDataCompat)
1236 return 1;
1237 /*fall-through*/
1238 case AL_SAMPLE_RW_OFFSETS_SOFT:
1239 break;
1241 case AL_STEREO_ANGLES:
1242 return 2;
1244 case AL_POSITION:
1245 case AL_VELOCITY:
1246 case AL_DIRECTION:
1247 return 3;
1249 case AL_ORIENTATION:
1250 return 6;
1252 case AL_SEC_OFFSET_LATENCY_SOFT:
1253 case AL_SEC_OFFSET_CLOCK_SOFT:
1254 break; /* Double only */
1256 case AL_BUFFER:
1257 case AL_DIRECT_FILTER:
1258 case AL_AUXILIARY_SEND_FILTER:
1259 break; /* i/i64 only */
1260 case AL_SAMPLE_OFFSET_LATENCY_SOFT:
1261 case AL_SAMPLE_OFFSET_CLOCK_SOFT:
1262 break; /* i64 only */
1264 return 0;
1266 constexpr ALuint DoubleValsByProp(ALenum prop)
1268 switch(static_cast<SourceProp>(prop))
1270 case AL_PITCH:
1271 case AL_GAIN:
1272 case AL_MIN_GAIN:
1273 case AL_MAX_GAIN:
1274 case AL_MAX_DISTANCE:
1275 case AL_ROLLOFF_FACTOR:
1276 case AL_DOPPLER_FACTOR:
1277 case AL_CONE_OUTER_GAIN:
1278 case AL_SEC_OFFSET:
1279 case AL_SAMPLE_OFFSET:
1280 case AL_BYTE_OFFSET:
1281 case AL_CONE_INNER_ANGLE:
1282 case AL_CONE_OUTER_ANGLE:
1283 case AL_REFERENCE_DISTANCE:
1284 case AL_CONE_OUTER_GAINHF:
1285 case AL_AIR_ABSORPTION_FACTOR:
1286 case AL_ROOM_ROLLOFF_FACTOR:
1287 case AL_DIRECT_FILTER_GAINHF_AUTO:
1288 case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO:
1289 case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO:
1290 case AL_DIRECT_CHANNELS_SOFT:
1291 case AL_DISTANCE_MODEL:
1292 case AL_SOURCE_RELATIVE:
1293 case AL_LOOPING:
1294 case AL_SOURCE_STATE:
1295 case AL_BUFFERS_QUEUED:
1296 case AL_BUFFERS_PROCESSED:
1297 case AL_SOURCE_TYPE:
1298 case AL_SOURCE_RESAMPLER_SOFT:
1299 case AL_SOURCE_SPATIALIZE_SOFT:
1300 case AL_BYTE_LENGTH_SOFT:
1301 case AL_SAMPLE_LENGTH_SOFT:
1302 case AL_SEC_LENGTH_SOFT:
1303 case AL_STEREO_MODE_SOFT:
1304 case AL_SUPER_STEREO_WIDTH_SOFT:
1305 case AL_PANNING_ENABLED_SOFT:
1306 case AL_PAN_SOFT:
1307 return 1;
1309 case AL_SOURCE_RADIUS: /*AL_BYTE_RW_OFFSETS_SOFT:*/
1310 if(!sBufferSubDataCompat)
1311 return 1;
1312 /*fall-through*/
1313 case AL_SAMPLE_RW_OFFSETS_SOFT:
1314 break;
1316 case AL_SEC_OFFSET_LATENCY_SOFT:
1317 case AL_SEC_OFFSET_CLOCK_SOFT:
1318 case AL_STEREO_ANGLES:
1319 return 2;
1321 case AL_POSITION:
1322 case AL_VELOCITY:
1323 case AL_DIRECTION:
1324 return 3;
1326 case AL_ORIENTATION:
1327 return 6;
1329 case AL_BUFFER:
1330 case AL_DIRECT_FILTER:
1331 case AL_AUXILIARY_SEND_FILTER:
1332 break; /* i/i64 only */
1333 case AL_SAMPLE_OFFSET_LATENCY_SOFT:
1334 case AL_SAMPLE_OFFSET_CLOCK_SOFT:
1335 break; /* i64 only */
1337 return 0;
1341 void UpdateSourceProps(ALsource *source, ALCcontext *context)
1343 if(!context->mDeferUpdates)
1345 if(Voice *voice{GetSourceVoice(source, context)})
1347 UpdateSourceProps(source, voice, context);
1348 return;
1351 source->mPropsDirty = true;
1353 #if ALSOFT_EAX
1354 void CommitAndUpdateSourceProps(ALsource *source, ALCcontext *context)
1356 if(!context->mDeferUpdates)
1358 if(context->hasEax())
1359 source->eaxCommit();
1360 if(Voice *voice{GetSourceVoice(source, context)})
1362 UpdateSourceProps(source, voice, context);
1363 return;
1366 source->mPropsDirty = true;
1369 #else
1371 inline void CommitAndUpdateSourceProps(ALsource *source, ALCcontext *context)
1372 { UpdateSourceProps(source, context); }
1373 #endif
1376 template<typename T>
1377 struct PropType { };
1378 template<>
1379 struct PropType<ALint> { static const char *Name() { return "integer"; } };
1380 template<>
1381 struct PropType<ALint64SOFT> { static const char *Name() { return "int64"; } };
1382 template<>
1383 struct PropType<ALfloat> { static const char *Name() { return "float"; } };
1384 template<>
1385 struct PropType<ALdouble> { static const char *Name() { return "double"; } };
1387 struct HexPrinter {
1388 std::array<char,32> mStr{};
1390 template<typename T>
1391 HexPrinter(T value)
1393 using ST = std::make_signed_t<std::remove_cv_t<T>>;
1394 if constexpr(std::is_same_v<ST,int>)
1395 std::snprintf(mStr.data(), mStr.size(), "0x%x", value);
1396 else if constexpr(std::is_same_v<ST,long>)
1397 std::snprintf(mStr.data(), mStr.size(), "0x%lx", value);
1398 else if constexpr(std::is_same_v<ST,long long>)
1399 std::snprintf(mStr.data(), mStr.size(), "0x%llx", value);
1402 [[nodiscard]] auto c_str() const noexcept -> const char* { return mStr.data(); }
1407 * Returns a pair of lambdas to check the following setter.
1409 * The first lambda checks the size of the span is valid for the required size,
1410 * throwing a context error if it fails.
1412 * The second lambda tests the validity of the value check, throwing a context
1413 * error if it failed.
1415 template<typename T, size_t N>
1416 auto GetCheckers(const SourceProp prop, const al::span<T,N> values)
1418 return std::make_pair(
1419 [=](size_t expect) -> void
1421 if(values.size() == expect) return;
1422 throw al::context_error{AL_INVALID_ENUM,
1423 "Property 0x%04x expects %zu value(s), got %zu", prop, expect, values.size()};
1425 [](bool passed) -> void
1427 if(passed) return;
1428 throw al::context_error{AL_INVALID_VALUE, "Value out of range"};
1433 template<typename T>
1434 NOINLINE void SetProperty(ALsource *const Source, ALCcontext *const Context, const SourceProp prop,
1435 const al::span<const T> values)
1437 auto [CheckSize, CheckValue] = GetCheckers(prop, values);
1438 auto *device = Context->mALDevice.get();
1440 switch(prop)
1442 case AL_SOURCE_STATE:
1443 case AL_SOURCE_TYPE:
1444 case AL_BUFFERS_QUEUED:
1445 case AL_BUFFERS_PROCESSED:
1446 if constexpr(std::is_integral_v<T>)
1448 /* Query only */
1449 throw al::context_error{AL_INVALID_OPERATION,
1450 "Setting read-only source property 0x%04x", prop};
1452 break;
1454 case AL_BYTE_LENGTH_SOFT:
1455 case AL_SAMPLE_LENGTH_SOFT:
1456 case AL_SEC_LENGTH_SOFT:
1457 case AL_SAMPLE_OFFSET_LATENCY_SOFT:
1458 case AL_SEC_OFFSET_LATENCY_SOFT:
1459 case AL_SAMPLE_OFFSET_CLOCK_SOFT:
1460 case AL_SEC_OFFSET_CLOCK_SOFT:
1461 /* Query only */
1462 throw al::context_error{AL_INVALID_OPERATION, "Setting read-only source property 0x%04x",
1463 prop};
1465 case AL_PITCH:
1466 CheckSize(1);
1467 if constexpr(std::is_floating_point_v<T>)
1468 CheckValue(values[0] >= T{0} && std::isfinite(static_cast<float>(values[0])));
1469 else
1470 CheckValue(values[0] >= T{0});
1472 Source->Pitch = static_cast<float>(values[0]);
1473 return UpdateSourceProps(Source, Context);
1475 case AL_CONE_INNER_ANGLE:
1476 CheckSize(1);
1477 CheckValue(values[0] >= T{0} && values[0] <= T{360});
1479 Source->InnerAngle = static_cast<float>(values[0]);
1480 return CommitAndUpdateSourceProps(Source, Context);
1482 case AL_CONE_OUTER_ANGLE:
1483 CheckSize(1);
1484 CheckValue(values[0] >= T{0} && values[0] <= T{360});
1486 Source->OuterAngle = static_cast<float>(values[0]);
1487 return CommitAndUpdateSourceProps(Source, Context);
1489 case AL_GAIN:
1490 CheckSize(1);
1491 if constexpr(std::is_floating_point_v<T>)
1492 CheckValue(values[0] >= T{0} && std::isfinite(static_cast<float>(values[0])));
1493 else
1494 CheckValue(values[0] >= T{0});
1496 Source->Gain = static_cast<float>(values[0]);
1497 return UpdateSourceProps(Source, Context);
1499 case AL_MAX_DISTANCE:
1500 CheckSize(1);
1501 if constexpr(std::is_floating_point_v<T>)
1502 CheckValue(values[0] >= T{0} && std::isfinite(static_cast<float>(values[0])));
1503 else
1504 CheckValue(values[0] >= T{0});
1506 Source->MaxDistance = static_cast<float>(values[0]);
1507 return CommitAndUpdateSourceProps(Source, Context);
1509 case AL_ROLLOFF_FACTOR:
1510 CheckSize(1);
1511 if constexpr(std::is_floating_point_v<T>)
1512 CheckValue(values[0] >= T{0} && std::isfinite(static_cast<float>(values[0])));
1513 else
1514 CheckValue(values[0] >= T{0});
1516 Source->RolloffFactor = static_cast<float>(values[0]);
1517 return CommitAndUpdateSourceProps(Source, Context);
1519 case AL_REFERENCE_DISTANCE:
1520 CheckSize(1);
1521 if constexpr(std::is_floating_point_v<T>)
1522 CheckValue(values[0] >= T{0} && std::isfinite(static_cast<float>(values[0])));
1523 else
1524 CheckValue(values[0] >= T{0});
1526 Source->RefDistance = static_cast<float>(values[0]);
1527 return CommitAndUpdateSourceProps(Source, Context);
1529 case AL_MIN_GAIN:
1530 CheckSize(1);
1531 if constexpr(std::is_floating_point_v<T>)
1532 CheckValue(values[0] >= T{0} && std::isfinite(static_cast<float>(values[0])));
1533 else
1534 CheckValue(values[0] >= T{0});
1536 Source->MinGain = static_cast<float>(values[0]);
1537 return UpdateSourceProps(Source, Context);
1539 case AL_MAX_GAIN:
1540 CheckSize(1);
1541 if constexpr(std::is_floating_point_v<T>)
1542 CheckValue(values[0] >= T{0} && std::isfinite(static_cast<float>(values[0])));
1543 else
1544 CheckValue(values[0] >= T{0});
1546 Source->MaxGain = static_cast<float>(values[0]);
1547 return UpdateSourceProps(Source, Context);
1549 case AL_CONE_OUTER_GAIN:
1550 CheckSize(1);
1551 CheckValue(values[0] >= T{0} && values[0] <= T{1});
1553 Source->OuterGain = static_cast<float>(values[0]);
1554 return UpdateSourceProps(Source, Context);
1556 case AL_CONE_OUTER_GAINHF:
1557 CheckSize(1);
1558 CheckValue(values[0] >= T{0} && values[0] <= T{1});
1560 Source->OuterGainHF = static_cast<float>(values[0]);
1561 return UpdateSourceProps(Source, Context);
1563 case AL_AIR_ABSORPTION_FACTOR:
1564 CheckSize(1);
1565 CheckValue(values[0] >= T{0} && values[0] <= T{10});
1567 Source->AirAbsorptionFactor = static_cast<float>(values[0]);
1568 return UpdateSourceProps(Source, Context);
1570 case AL_ROOM_ROLLOFF_FACTOR:
1571 CheckSize(1);
1572 CheckValue(values[0] >= T{0} && values[0] <= T{1});
1574 Source->RoomRolloffFactor = static_cast<float>(values[0]);
1575 return UpdateSourceProps(Source, Context);
1577 case AL_DOPPLER_FACTOR:
1578 CheckSize(1);
1579 CheckValue(values[0] >= T{0} && values[0] <= T{1});
1581 Source->DopplerFactor = static_cast<float>(values[0]);
1582 return UpdateSourceProps(Source, Context);
1585 case AL_SOURCE_RELATIVE:
1586 if constexpr(std::is_integral_v<T>)
1588 CheckSize(1);
1589 CheckValue(values[0] == AL_FALSE || values[0] == AL_TRUE);
1591 Source->HeadRelative = values[0] != AL_FALSE;
1592 return CommitAndUpdateSourceProps(Source, Context);
1594 break;
1596 case AL_LOOPING:
1597 if constexpr(std::is_integral_v<T>)
1599 CheckSize(1);
1600 CheckValue(values[0] == AL_FALSE || values[0] == AL_TRUE);
1602 Source->Looping = values[0] != AL_FALSE;
1603 if(Voice *voice{GetSourceVoice(Source, Context)})
1605 if(Source->Looping)
1606 voice->mLoopBuffer.store(&Source->mQueue.front(), std::memory_order_release);
1607 else
1608 voice->mLoopBuffer.store(nullptr, std::memory_order_release);
1610 /* If the source is playing, wait for the current mix to finish
1611 * to ensure it isn't currently looping back or reaching the
1612 * end.
1614 std::ignore = device->waitForMix();
1616 return;
1618 break;
1620 case AL_BUFFER:
1621 if constexpr(std::is_integral_v<T>)
1623 CheckSize(1);
1624 if(const ALenum state{GetSourceState(Source, GetSourceVoice(Source, Context))};
1625 state == AL_PLAYING || state == AL_PAUSED)
1626 throw al::context_error{AL_INVALID_OPERATION,
1627 "Setting buffer on playing or paused source %u", Source->id};
1629 std::deque<ALbufferQueueItem> oldlist;
1630 if(values[0])
1632 using UT = std::make_unsigned_t<T>;
1633 std::lock_guard<std::mutex> buflock{device->BufferLock};
1634 ALbuffer *buffer{LookupBuffer(device, static_cast<UT>(values[0]))};
1635 if(!buffer)
1636 throw al::context_error{AL_INVALID_VALUE, "Invalid buffer ID %s",
1637 std::to_string(values[0]).c_str()};
1638 if(buffer->MappedAccess && !(buffer->MappedAccess&AL_MAP_PERSISTENT_BIT_SOFT))
1639 throw al::context_error{AL_INVALID_OPERATION,
1640 "Setting non-persistently mapped buffer %u", buffer->id};
1641 if(buffer->mCallback && buffer->ref.load(std::memory_order_relaxed) != 0)
1642 throw al::context_error{AL_INVALID_OPERATION,
1643 "Setting already-set callback buffer %u", buffer->id};
1645 /* Add the selected buffer to a one-item queue */
1646 std::deque<ALbufferQueueItem> newlist;
1647 newlist.emplace_back();
1648 newlist.back().mCallback = buffer->mCallback;
1649 newlist.back().mUserData = buffer->mUserData;
1650 newlist.back().mBlockAlign = buffer->mBlockAlign;
1651 newlist.back().mSampleLen = buffer->mSampleLen;
1652 newlist.back().mLoopStart = buffer->mLoopStart;
1653 newlist.back().mLoopEnd = buffer->mLoopEnd;
1654 newlist.back().mSamples = buffer->mData;
1655 newlist.back().mBuffer = buffer;
1656 IncrementRef(buffer->ref);
1658 /* Source is now Static */
1659 Source->SourceType = AL_STATIC;
1660 Source->mQueue.swap(oldlist);
1661 Source->mQueue.swap(newlist);
1663 else
1665 /* Source is now Undetermined */
1666 Source->SourceType = AL_UNDETERMINED;
1667 Source->mQueue.swap(oldlist);
1670 /* Delete all elements in the previous queue */
1671 for(auto &item : oldlist)
1673 if(ALbuffer *buffer{item.mBuffer})
1674 DecrementRef(buffer->ref);
1676 return;
1678 break;
1681 case AL_SEC_OFFSET:
1682 case AL_SAMPLE_OFFSET:
1683 case AL_BYTE_OFFSET:
1684 CheckSize(1);
1685 if constexpr(std::is_floating_point_v<T>)
1686 CheckValue(std::isfinite(values[0]));
1688 if(Voice *voice{GetSourceVoice(Source, Context)})
1690 auto vpos = GetSampleOffset(Source->mQueue, prop, static_cast<double>(values[0]));
1691 if(!vpos) throw al::context_error{AL_INVALID_VALUE, "Invalid offset"};
1693 if(SetVoiceOffset(voice, *vpos, Source, Context, Context->mALDevice.get()))
1694 return;
1696 Source->OffsetType = prop;
1697 Source->Offset = static_cast<double>(values[0]);
1698 return;
1700 case AL_SAMPLE_RW_OFFSETS_SOFT:
1701 if(sBufferSubDataCompat)
1703 if constexpr(std::is_integral_v<T>)
1705 /* Query only */
1706 throw al::context_error{AL_INVALID_OPERATION,
1707 "Setting read-only source property 0x%04x", prop};
1710 break;
1712 case AL_SOURCE_RADIUS: /*AL_BYTE_RW_OFFSETS_SOFT:*/
1713 if(sBufferSubDataCompat)
1715 if constexpr(std::is_integral_v<T>)
1717 /* Query only */
1718 throw al::context_error{AL_INVALID_OPERATION,
1719 "Setting read-only source property 0x%04x", prop};
1721 break;
1723 CheckSize(1);
1724 if constexpr(std::is_floating_point_v<T>)
1725 CheckValue(values[0] >= T{0} && std::isfinite(static_cast<float>(values[0])));
1726 else
1727 CheckValue(values[0] >= T{0});
1729 Source->Radius = static_cast<float>(values[0]);
1730 return UpdateSourceProps(Source, Context);
1732 case AL_SUPER_STEREO_WIDTH_SOFT:
1733 CheckSize(1);
1734 CheckValue(values[0] >= T{0} && values[0] <= T{1});
1736 Source->EnhWidth = static_cast<float>(values[0]);
1737 return UpdateSourceProps(Source, Context);
1739 case AL_PANNING_ENABLED_SOFT:
1740 CheckSize(1);
1741 if(const ALenum state{GetSourceState(Source, GetSourceVoice(Source, Context))};
1742 state == AL_PLAYING || state == AL_PAUSED)
1743 throw al::context_error{AL_INVALID_OPERATION,
1744 "Modifying panning enabled on playing or paused source %u", Source->id};
1746 CheckValue(values[0] == AL_FALSE || values[0] == AL_TRUE);
1748 Source->mPanningEnabled = values[0] != AL_FALSE;
1749 return UpdateSourceProps(Source, Context);
1751 case AL_PAN_SOFT:
1752 CheckSize(1);
1753 CheckValue(values[0] >= T{-1} && values[0] <= T{1});
1755 Source->mPan = static_cast<float>(values[0]);
1756 return UpdateSourceProps(Source, Context);
1758 case AL_STEREO_ANGLES:
1759 CheckSize(2);
1760 if constexpr(std::is_floating_point_v<T>)
1761 CheckValue(std::isfinite(static_cast<float>(values[0]))
1762 && std::isfinite(static_cast<float>(values[1])));
1764 Source->StereoPan[0] = static_cast<float>(values[0]);
1765 Source->StereoPan[1] = static_cast<float>(values[1]);
1766 return UpdateSourceProps(Source, Context);
1769 case AL_POSITION:
1770 CheckSize(3);
1771 if constexpr(std::is_floating_point_v<T>)
1772 CheckValue(std::isfinite(static_cast<float>(values[0]))
1773 && std::isfinite(static_cast<float>(values[1]))
1774 && std::isfinite(static_cast<float>(values[2])));
1776 Source->Position[0] = static_cast<float>(values[0]);
1777 Source->Position[1] = static_cast<float>(values[1]);
1778 Source->Position[2] = static_cast<float>(values[2]);
1779 return CommitAndUpdateSourceProps(Source, Context);
1781 case AL_VELOCITY:
1782 CheckSize(3);
1783 if constexpr(std::is_floating_point_v<T>)
1784 CheckValue(std::isfinite(static_cast<float>(values[0]))
1785 && std::isfinite(static_cast<float>(values[1]))
1786 && std::isfinite(static_cast<float>(values[2])));
1788 Source->Velocity[0] = static_cast<float>(values[0]);
1789 Source->Velocity[1] = static_cast<float>(values[1]);
1790 Source->Velocity[2] = static_cast<float>(values[2]);
1791 return CommitAndUpdateSourceProps(Source, Context);
1793 case AL_DIRECTION:
1794 CheckSize(3);
1795 if constexpr(std::is_floating_point_v<T>)
1796 CheckValue(std::isfinite(static_cast<float>(values[0]))
1797 && std::isfinite(static_cast<float>(values[1]))
1798 && std::isfinite(static_cast<float>(values[2])));
1800 Source->Direction[0] = static_cast<float>(values[0]);
1801 Source->Direction[1] = static_cast<float>(values[1]);
1802 Source->Direction[2] = static_cast<float>(values[2]);
1803 return CommitAndUpdateSourceProps(Source, Context);
1805 case AL_ORIENTATION:
1806 CheckSize(6);
1807 if constexpr(std::is_floating_point_v<T>)
1808 CheckValue(std::isfinite(static_cast<float>(values[0]))
1809 && std::isfinite(static_cast<float>(values[1]))
1810 && std::isfinite(static_cast<float>(values[2]))
1811 && std::isfinite(static_cast<float>(values[3]))
1812 && std::isfinite(static_cast<float>(values[4]))
1813 && std::isfinite(static_cast<float>(values[5])));
1815 Source->OrientAt[0] = static_cast<float>(values[0]);
1816 Source->OrientAt[1] = static_cast<float>(values[1]);
1817 Source->OrientAt[2] = static_cast<float>(values[2]);
1818 Source->OrientUp[0] = static_cast<float>(values[3]);
1819 Source->OrientUp[1] = static_cast<float>(values[4]);
1820 Source->OrientUp[2] = static_cast<float>(values[5]);
1821 return UpdateSourceProps(Source, Context);
1824 case AL_DIRECT_FILTER:
1825 if constexpr(std::is_integral_v<T>)
1827 CheckSize(1);
1828 const auto filterid = static_cast<std::make_unsigned_t<T>>(values[0]);
1829 if(values[0])
1831 std::lock_guard<std::mutex> filterlock{device->FilterLock};
1832 ALfilter *filter{LookupFilter(device, filterid)};
1833 if(!filter)
1834 throw al::context_error{AL_INVALID_VALUE, "Invalid filter ID %s",
1835 std::to_string(filterid).c_str()};
1836 Source->Direct.Gain = filter->Gain;
1837 Source->Direct.GainHF = filter->GainHF;
1838 Source->Direct.HFReference = filter->HFReference;
1839 Source->Direct.GainLF = filter->GainLF;
1840 Source->Direct.LFReference = filter->LFReference;
1842 else
1844 Source->Direct.Gain = 1.0f;
1845 Source->Direct.GainHF = 1.0f;
1846 Source->Direct.HFReference = LowPassFreqRef;
1847 Source->Direct.GainLF = 1.0f;
1848 Source->Direct.LFReference = HighPassFreqRef;
1850 return UpdateSourceProps(Source, Context);
1852 break;
1854 case AL_DIRECT_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->DryGainHFAuto = values[0] != AL_FALSE;
1861 return UpdateSourceProps(Source, Context);
1863 break;
1865 case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO:
1866 if constexpr(std::is_integral_v<T>)
1868 CheckSize(1);
1869 CheckValue(values[0] == AL_FALSE || values[0] == AL_TRUE);
1871 Source->WetGainAuto = values[0] != AL_FALSE;
1872 return UpdateSourceProps(Source, Context);
1874 break;
1876 case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO:
1877 if constexpr(std::is_integral_v<T>)
1879 CheckSize(1);
1880 CheckValue(values[0] == AL_FALSE || values[0] == AL_TRUE);
1882 Source->WetGainHFAuto = values[0] != AL_FALSE;
1883 return UpdateSourceProps(Source, Context);
1885 break;
1887 case AL_DIRECT_CHANNELS_SOFT:
1888 if constexpr(std::is_integral_v<T>)
1890 CheckSize(1);
1891 if(auto mode = DirectModeFromEnum(values[0]))
1893 Source->DirectChannels = *mode;
1894 return UpdateSourceProps(Source, Context);
1896 throw al::context_error{AL_INVALID_VALUE, "Invalid direct channels mode: %s\n",
1897 HexPrinter{values[0]}.c_str()};
1899 break;
1901 case AL_DISTANCE_MODEL:
1902 if constexpr(std::is_integral_v<T>)
1904 CheckSize(1);
1905 if(auto model = DistanceModelFromALenum(values[0]))
1907 Source->mDistanceModel = *model;
1908 if(Context->mSourceDistanceModel)
1909 UpdateSourceProps(Source, Context);
1910 return;
1912 throw al::context_error{AL_INVALID_VALUE, "Invalid distance model: %s\n",
1913 HexPrinter{values[0]}.c_str()};
1915 break;
1917 case AL_SOURCE_RESAMPLER_SOFT:
1918 if constexpr(std::is_integral_v<T>)
1920 CheckSize(1);
1921 CheckValue(values[0] >= 0 && values[0] <= static_cast<int>(Resampler::Max));
1923 Source->mResampler = static_cast<Resampler>(values[0]);
1924 return UpdateSourceProps(Source, Context);
1926 break;
1928 case AL_SOURCE_SPATIALIZE_SOFT:
1929 if constexpr(std::is_integral_v<T>)
1931 CheckSize(1);
1932 if(auto mode = SpatializeModeFromEnum(values[0]))
1934 Source->mSpatialize = *mode;
1935 return UpdateSourceProps(Source, Context);
1937 throw al::context_error{AL_INVALID_VALUE, "Invalid source spatialize mode: %s\n",
1938 HexPrinter{values[0]}.c_str()};
1940 break;
1942 case AL_STEREO_MODE_SOFT:
1943 if constexpr(std::is_integral_v<T>)
1945 CheckSize(1);
1946 if(const ALenum state{GetSourceState(Source, GetSourceVoice(Source, Context))};
1947 state == AL_PLAYING || state == AL_PAUSED)
1948 throw al::context_error{AL_INVALID_OPERATION,
1949 "Modifying stereo mode on playing or paused source %u", Source->id};
1951 if(auto mode = StereoModeFromEnum(values[0]))
1953 Source->mStereoMode = *mode;
1954 return;
1956 throw al::context_error{AL_INVALID_VALUE, "Invalid stereo mode: %s\n",
1957 HexPrinter{values[0]}.c_str()};
1959 break;
1961 case AL_AUXILIARY_SEND_FILTER:
1962 if constexpr(std::is_integral_v<T>)
1964 CheckSize(3);
1965 const auto slotid = static_cast<std::make_unsigned_t<T>>(values[0]);
1966 const auto sendidx = static_cast<std::make_unsigned_t<T>>(values[1]);
1967 const auto filterid = static_cast<std::make_unsigned_t<T>>(values[2]);
1969 std::unique_lock slotlock{Context->mEffectSlotLock};
1970 ALeffectslot *slot{};
1971 if(values[0])
1973 slot = LookupEffectSlot(Context, slotid);
1974 if(!slot)
1975 throw al::context_error{AL_INVALID_VALUE, "Invalid effect ID %s",
1976 std::to_string(slotid).c_str()};
1979 if(sendidx >= device->NumAuxSends)
1980 throw al::context_error{AL_INVALID_VALUE, "Invalid send %s",
1981 std::to_string(sendidx).c_str()};
1982 auto &send = Source->Send[static_cast<size_t>(sendidx)];
1984 if(values[2])
1986 std::lock_guard<std::mutex> filterlock{device->FilterLock};
1987 ALfilter *filter{LookupFilter(device, filterid)};
1988 if(!filter)
1989 throw al::context_error{AL_INVALID_VALUE, "Invalid filter ID %s",
1990 std::to_string(filterid).c_str()};
1992 send.Gain = filter->Gain;
1993 send.GainHF = filter->GainHF;
1994 send.HFReference = filter->HFReference;
1995 send.GainLF = filter->GainLF;
1996 send.LFReference = filter->LFReference;
1998 else
2000 /* Disable filter */
2001 send.Gain = 1.0f;
2002 send.GainHF = 1.0f;
2003 send.HFReference = LowPassFreqRef;
2004 send.GainLF = 1.0f;
2005 send.LFReference = HighPassFreqRef;
2008 /* We must force an update if the current auxiliary slot is valid
2009 * and about to be changed on an active source, in case the old
2010 * slot is about to be deleted.
2012 if(send.Slot && slot != send.Slot && IsPlayingOrPaused(Source))
2014 /* Add refcount on the new slot, and release the previous slot */
2015 if(slot) IncrementRef(slot->ref);
2016 if(auto *oldslot = send.Slot)
2017 DecrementRef(oldslot->ref);
2018 send.Slot = slot;
2020 Voice *voice{GetSourceVoice(Source, Context)};
2021 if(voice) UpdateSourceProps(Source, voice, Context);
2022 else Source->mPropsDirty = true;
2024 else
2026 if(slot) IncrementRef(slot->ref);
2027 if(auto *oldslot = send.Slot)
2028 DecrementRef(oldslot->ref);
2029 send.Slot = slot;
2030 UpdateSourceProps(Source, Context);
2032 return;
2034 break;
2037 ERR("Unexpected %s property: 0x%04x\n", PropType<T>::Name(), prop);
2038 throw al::context_error{AL_INVALID_ENUM, "Invalid source %s property 0x%04x",
2039 PropType<T>::Name(), prop};
2043 template<typename T, size_t N>
2044 auto GetSizeChecker(const SourceProp prop, const al::span<T,N> values)
2046 return [=](size_t expect) -> void
2048 if(values.size() == expect) LIKELY return;
2049 throw al::context_error{AL_INVALID_ENUM, "Property 0x%04x expects %zu value(s), got %zu",
2050 prop, expect, values.size()};
2054 template<typename T>
2055 NOINLINE void GetProperty(ALsource *const Source, ALCcontext *const Context, const SourceProp prop,
2056 const al::span<T> values)
2058 using std::chrono::duration_cast;
2059 auto CheckSize = GetSizeChecker(prop, values);
2060 auto *device = Context->mALDevice.get();
2062 switch(prop)
2064 case AL_GAIN:
2065 CheckSize(1);
2066 values[0] = static_cast<T>(Source->Gain);
2067 return;
2069 case AL_PITCH:
2070 CheckSize(1);
2071 values[0] = static_cast<T>(Source->Pitch);
2072 return;
2074 case AL_MAX_DISTANCE:
2075 CheckSize(1);
2076 values[0] = static_cast<T>(Source->MaxDistance);
2077 return;
2079 case AL_ROLLOFF_FACTOR:
2080 CheckSize(1);
2081 values[0] = static_cast<T>(Source->RolloffFactor);
2082 return;
2084 case AL_REFERENCE_DISTANCE:
2085 CheckSize(1);
2086 values[0] = static_cast<T>(Source->RefDistance);
2087 return;
2089 case AL_CONE_INNER_ANGLE:
2090 CheckSize(1);
2091 values[0] = static_cast<T>(Source->InnerAngle);
2092 return;
2094 case AL_CONE_OUTER_ANGLE:
2095 CheckSize(1);
2096 values[0] = static_cast<T>(Source->OuterAngle);
2097 return;
2099 case AL_MIN_GAIN:
2100 CheckSize(1);
2101 values[0] = static_cast<T>(Source->MinGain);
2102 return;
2104 case AL_MAX_GAIN:
2105 CheckSize(1);
2106 values[0] = static_cast<T>(Source->MaxGain);
2107 return;
2109 case AL_CONE_OUTER_GAIN:
2110 CheckSize(1);
2111 values[0] = static_cast<T>(Source->OuterGain);
2112 return;
2114 case AL_SEC_OFFSET:
2115 case AL_SAMPLE_OFFSET:
2116 case AL_BYTE_OFFSET:
2117 CheckSize(1);
2118 values[0] = GetSourceOffset<T>(Source, prop, Context);
2119 return;
2121 case AL_CONE_OUTER_GAINHF:
2122 CheckSize(1);
2123 values[0] = static_cast<T>(Source->OuterGainHF);
2124 return;
2126 case AL_AIR_ABSORPTION_FACTOR:
2127 CheckSize(1);
2128 values[0] = static_cast<T>(Source->AirAbsorptionFactor);
2129 return;
2131 case AL_ROOM_ROLLOFF_FACTOR:
2132 CheckSize(1);
2133 values[0] = static_cast<T>(Source->RoomRolloffFactor);
2134 return;
2136 case AL_DOPPLER_FACTOR:
2137 CheckSize(1);
2138 values[0] = static_cast<T>(Source->DopplerFactor);
2139 return;
2141 case AL_SAMPLE_RW_OFFSETS_SOFT:
2142 if constexpr(std::is_integral_v<T>)
2144 if(sBufferSubDataCompat)
2146 CheckSize(2);
2147 values[0] = GetSourceOffset<T>(Source, AL_SAMPLE_OFFSET, Context);
2148 /* FIXME: values[1] should be ahead of values[0] by the device
2149 * update time. It needs to clamp or wrap the length of the
2150 * buffer queue.
2152 values[1] = values[0];
2153 return;
2156 break;
2157 case AL_SOURCE_RADIUS: /*AL_BYTE_RW_OFFSETS_SOFT:*/
2158 if constexpr(std::is_floating_point_v<T>)
2160 if(sBufferSubDataCompat)
2161 break;
2163 CheckSize(1);
2164 values[0] = static_cast<T>(Source->Radius);
2165 return;
2167 else
2169 if(sBufferSubDataCompat)
2171 CheckSize(2);
2172 values[0] = GetSourceOffset<T>(Source, AL_BYTE_OFFSET, Context);
2173 /* FIXME: values[1] should be ahead of values[0] by the device
2174 * update time. It needs to clamp or wrap the length of the
2175 * buffer queue.
2177 values[1] = values[0];
2178 return;
2180 break;
2183 case AL_SUPER_STEREO_WIDTH_SOFT:
2184 CheckSize(1);
2185 values[0] = static_cast<T>(Source->EnhWidth);
2186 return;
2188 case AL_BYTE_LENGTH_SOFT:
2189 case AL_SAMPLE_LENGTH_SOFT:
2190 case AL_SEC_LENGTH_SOFT:
2191 CheckSize(1);
2192 values[0] = GetSourceLength<T>(Source, prop);
2193 return;
2195 case AL_PANNING_ENABLED_SOFT:
2196 CheckSize(1);
2197 values[0] = Source->mPanningEnabled;
2198 return;
2200 case AL_PAN_SOFT:
2201 CheckSize(1);
2202 values[0] = static_cast<T>(Source->mPan);
2203 return;
2205 case AL_STEREO_ANGLES:
2206 if constexpr(std::is_floating_point_v<T>)
2208 CheckSize(2);
2209 values[0] = static_cast<T>(Source->StereoPan[0]);
2210 values[1] = static_cast<T>(Source->StereoPan[1]);
2211 return;
2213 break;
2215 case AL_SAMPLE_OFFSET_LATENCY_SOFT:
2216 if constexpr(std::is_same_v<T,int64_t>)
2218 CheckSize(2);
2219 /* Get the source offset with the clock time first. Then get the
2220 * clock time with the device latency. Order is important.
2222 ClockLatency clocktime{};
2223 nanoseconds srcclock{};
2224 values[0] = GetSourceSampleOffset(Source, Context, &srcclock);
2226 std::lock_guard<std::mutex> statelock{device->StateLock};
2227 clocktime = GetClockLatency(device, device->Backend.get());
2229 if(srcclock == clocktime.ClockTime)
2230 values[1] = nanoseconds{clocktime.Latency}.count();
2231 else
2233 /* If the clock time incremented, reduce the latency by that
2234 * much since it's that much closer to the source offset it got
2235 * earlier.
2237 const auto diff = std::min(clocktime.Latency, clocktime.ClockTime-srcclock);
2238 values[1] = nanoseconds{clocktime.Latency - diff}.count();
2240 return;
2242 break;
2244 case AL_SAMPLE_OFFSET_CLOCK_SOFT:
2245 if constexpr(std::is_same_v<T,int64_t>)
2247 CheckSize(2);
2248 nanoseconds srcclock{};
2249 values[0] = GetSourceSampleOffset(Source, Context, &srcclock);
2250 values[1] = srcclock.count();
2251 return;
2253 break;
2255 case AL_SEC_OFFSET_LATENCY_SOFT:
2256 if constexpr(std::is_same_v<T,double>)
2258 CheckSize(2);
2259 /* Get the source offset with the clock time first. Then get the
2260 * clock time with the device latency. Order is important.
2262 ClockLatency clocktime{};
2263 nanoseconds srcclock{};
2264 values[0] = GetSourceSecOffset(Source, Context, &srcclock);
2266 std::lock_guard<std::mutex> statelock{device->StateLock};
2267 clocktime = GetClockLatency(device, device->Backend.get());
2269 if(srcclock == clocktime.ClockTime)
2270 values[1] = duration_cast<seconds_d>(clocktime.Latency).count();
2271 else
2273 /* If the clock time incremented, reduce the latency by that
2274 * much since it's that much closer to the source offset it got
2275 * earlier.
2277 const auto diff = std::min(clocktime.Latency, clocktime.ClockTime-srcclock);
2278 values[1] = duration_cast<seconds_d>(clocktime.Latency - diff).count();
2280 return;
2282 break;
2284 case AL_SEC_OFFSET_CLOCK_SOFT:
2285 if constexpr(std::is_same_v<T,double>)
2287 CheckSize(2);
2288 nanoseconds srcclock{};
2289 values[0] = GetSourceSecOffset(Source, Context, &srcclock);
2290 values[1] = duration_cast<seconds_d>(srcclock).count();
2291 return;
2293 break;
2295 case AL_POSITION:
2296 CheckSize(3);
2297 values[0] = static_cast<T>(Source->Position[0]);
2298 values[1] = static_cast<T>(Source->Position[1]);
2299 values[2] = static_cast<T>(Source->Position[2]);
2300 return;
2302 case AL_VELOCITY:
2303 CheckSize(3);
2304 values[0] = static_cast<T>(Source->Velocity[0]);
2305 values[1] = static_cast<T>(Source->Velocity[1]);
2306 values[2] = static_cast<T>(Source->Velocity[2]);
2307 return;
2309 case AL_DIRECTION:
2310 CheckSize(3);
2311 values[0] = static_cast<T>(Source->Direction[0]);
2312 values[1] = static_cast<T>(Source->Direction[1]);
2313 values[2] = static_cast<T>(Source->Direction[2]);
2314 return;
2316 case AL_ORIENTATION:
2317 CheckSize(6);
2318 values[0] = static_cast<T>(Source->OrientAt[0]);
2319 values[1] = static_cast<T>(Source->OrientAt[1]);
2320 values[2] = static_cast<T>(Source->OrientAt[2]);
2321 values[3] = static_cast<T>(Source->OrientUp[0]);
2322 values[4] = static_cast<T>(Source->OrientUp[1]);
2323 values[5] = static_cast<T>(Source->OrientUp[2]);
2324 return;
2327 case AL_SOURCE_RELATIVE:
2328 if constexpr(std::is_integral_v<T>)
2330 CheckSize(1);
2331 values[0] = Source->HeadRelative;
2332 return;
2334 break;
2336 case AL_LOOPING:
2337 if constexpr(std::is_integral_v<T>)
2339 CheckSize(1);
2340 values[0] = Source->Looping;
2341 return;
2343 break;
2345 case AL_BUFFER:
2346 if constexpr(std::is_integral_v<T>)
2348 CheckSize(1);
2349 const ALbufferQueueItem *BufferList{};
2350 /* HACK: This query should technically only return the buffer set
2351 * on a static source. However, some apps had used it to detect
2352 * when a streaming source changed buffers, so report the current
2353 * buffer's ID when playing.
2355 if(Source->SourceType == AL_STATIC || Source->state == AL_INITIAL)
2357 if(!Source->mQueue.empty())
2358 BufferList = &Source->mQueue.front();
2360 else if(Voice *voice{GetSourceVoice(Source, Context)})
2362 VoiceBufferItem *Current{voice->mCurrentBuffer.load(std::memory_order_relaxed)};
2363 const auto iter = std::find_if(Source->mQueue.cbegin(), Source->mQueue.cend(),
2364 [Current](const ALbufferQueueItem &item) noexcept -> bool
2365 { return &item == Current; });
2366 BufferList = (iter != Source->mQueue.cend()) ? al::to_address(iter) : nullptr;
2368 ALbuffer *buffer{BufferList ? BufferList->mBuffer : nullptr};
2369 values[0] = buffer ? static_cast<T>(buffer->id) : T{0};
2370 return;
2372 break;
2374 case AL_SOURCE_STATE:
2375 if constexpr(std::is_integral_v<T>)
2377 CheckSize(1);
2378 values[0] = GetSourceState(Source, GetSourceVoice(Source, Context));
2379 return;
2381 break;
2383 case AL_BUFFERS_QUEUED:
2384 if constexpr(std::is_integral_v<T>)
2386 CheckSize(1);
2387 values[0] = static_cast<T>(Source->mQueue.size());
2388 return;
2390 break;
2392 case AL_BUFFERS_PROCESSED:
2393 if constexpr(std::is_integral_v<T>)
2395 CheckSize(1);
2396 if(Source->Looping || Source->SourceType != AL_STREAMING)
2398 /* Buffers on a looping source are in a perpetual state of
2399 * PENDING, so don't report any as PROCESSED
2401 values[0] = 0;
2403 else
2405 int played{0};
2406 if(Source->state != AL_INITIAL)
2408 const VoiceBufferItem *Current{nullptr};
2409 if(Voice *voice{GetSourceVoice(Source, Context)})
2410 Current = voice->mCurrentBuffer.load(std::memory_order_relaxed);
2411 for(auto &item : Source->mQueue)
2413 if(&item == Current)
2414 break;
2415 ++played;
2418 values[0] = played;
2420 return;
2422 break;
2424 case AL_SOURCE_TYPE:
2425 if constexpr(std::is_integral_v<T>)
2427 CheckSize(1);
2428 values[0] = Source->SourceType;
2429 return;
2431 break;
2433 case AL_DIRECT_FILTER_GAINHF_AUTO:
2434 if constexpr(std::is_integral_v<T>)
2436 CheckSize(1);
2437 values[0] = Source->DryGainHFAuto;
2438 return;
2440 break;
2442 case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO:
2443 if constexpr(std::is_integral_v<T>)
2445 CheckSize(1);
2446 values[0] = Source->WetGainAuto;
2447 return;
2449 break;
2451 case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO:
2452 if constexpr(std::is_integral_v<T>)
2454 CheckSize(1);
2455 values[0] = Source->WetGainHFAuto;
2456 return;
2458 break;
2460 case AL_DIRECT_CHANNELS_SOFT:
2461 if constexpr(std::is_integral_v<T>)
2463 CheckSize(1);
2464 values[0] = EnumFromDirectMode(Source->DirectChannels);
2465 return;
2467 break;
2469 case AL_DISTANCE_MODEL:
2470 if constexpr(std::is_integral_v<T>)
2472 CheckSize(1);
2473 values[0] = ALenumFromDistanceModel(Source->mDistanceModel);
2474 return;
2476 break;
2478 case AL_SOURCE_RESAMPLER_SOFT:
2479 if constexpr(std::is_integral_v<T>)
2481 CheckSize(1);
2482 values[0] = static_cast<T>(Source->mResampler);
2483 return;
2485 break;
2487 case AL_SOURCE_SPATIALIZE_SOFT:
2488 if constexpr(std::is_integral_v<T>)
2490 CheckSize(1);
2491 values[0] = EnumFromSpatializeMode(Source->mSpatialize);
2492 return;
2494 break;
2496 case AL_STEREO_MODE_SOFT:
2497 if constexpr(std::is_integral_v<T>)
2499 CheckSize(1);
2500 values[0] = EnumFromStereoMode(Source->mStereoMode);
2501 return;
2503 break;
2505 case AL_DIRECT_FILTER:
2506 case AL_AUXILIARY_SEND_FILTER:
2507 break;
2510 ERR("Unexpected %s query property: 0x%04x\n", PropType<T>::Name(), prop);
2511 throw al::context_error{AL_INVALID_ENUM, "Invalid source %s query property 0x%04x",
2512 PropType<T>::Name(), prop};
2516 void StartSources(ALCcontext *const context, const al::span<ALsource*> srchandles,
2517 const nanoseconds start_time=nanoseconds::min())
2519 auto *device = context->mALDevice.get();
2520 /* If the device is disconnected, and voices stop on disconnect, go right
2521 * to stopped.
2523 if(!device->Connected.load(std::memory_order_acquire)) UNLIKELY
2525 if(context->mStopVoicesOnDisconnect.load(std::memory_order_acquire))
2527 for(ALsource *source : srchandles)
2529 /* TODO: Send state change event? */
2530 source->Offset = 0.0;
2531 source->OffsetType = AL_NONE;
2532 source->state = AL_STOPPED;
2534 return;
2538 /* Count the number of reusable voices. */
2539 auto voicelist = context->getVoicesSpan();
2540 size_t free_voices{0};
2541 for(const Voice *voice : voicelist)
2543 free_voices += (voice->mPlayState.load(std::memory_order_acquire) == Voice::Stopped
2544 && voice->mSourceID.load(std::memory_order_relaxed) == 0u
2545 && voice->mPendingChange.load(std::memory_order_relaxed) == false);
2546 if(free_voices == srchandles.size())
2547 break;
2549 if(srchandles.size() != free_voices) UNLIKELY
2551 const size_t inc_amount{srchandles.size() - free_voices};
2552 auto &allvoices = *context->mVoices.load(std::memory_order_relaxed);
2553 if(inc_amount > allvoices.size() - voicelist.size())
2555 /* Increase the number of voices to handle the request. */
2556 context->allocVoices(inc_amount - (allvoices.size() - voicelist.size()));
2558 context->mActiveVoiceCount.fetch_add(inc_amount, std::memory_order_release);
2559 voicelist = context->getVoicesSpan();
2562 auto voiceiter = voicelist.begin();
2563 ALuint vidx{0};
2564 VoiceChange *tail{}, *cur{};
2565 for(ALsource *source : srchandles)
2567 /* Check that there is a queue containing at least one valid, non zero
2568 * length buffer.
2570 auto find_buffer = [](ALbufferQueueItem &entry) noexcept
2571 { return entry.mSampleLen != 0 || entry.mCallback != nullptr; };
2572 auto BufferList = std::find_if(source->mQueue.begin(), source->mQueue.end(), find_buffer);
2574 /* If there's nothing to play, go right to stopped. */
2575 if(BufferList == source->mQueue.end()) UNLIKELY
2577 /* NOTE: A source without any playable buffers should not have a
2578 * Voice since it shouldn't be in a playing or paused state. So
2579 * there's no need to look up its voice and clear the source.
2581 source->Offset = 0.0;
2582 source->OffsetType = AL_NONE;
2583 source->state = AL_STOPPED;
2584 continue;
2587 if(!cur)
2588 cur = tail = GetVoiceChanger(context);
2589 else
2591 cur->mNext.store(GetVoiceChanger(context), std::memory_order_relaxed);
2592 cur = cur->mNext.load(std::memory_order_relaxed);
2595 Voice *voice{GetSourceVoice(source, context)};
2596 switch(GetSourceState(source, voice))
2598 case AL_PAUSED:
2599 /* A source that's paused simply resumes. If there's no voice, it
2600 * was lost from a disconnect, so just start over with a new one.
2602 cur->mOldVoice = nullptr;
2603 if(!voice) break;
2604 cur->mVoice = voice;
2605 cur->mSourceID = source->id;
2606 cur->mState = VChangeState::Play;
2607 source->state = AL_PLAYING;
2608 #if ALSOFT_EAX
2609 if(context->hasEax())
2610 source->eaxCommit();
2611 #endif // ALSOFT_EAX
2612 continue;
2614 case AL_PLAYING:
2615 /* A source that's already playing is restarted from the beginning.
2616 * Stop the current voice and start a new one so it properly cross-
2617 * fades back to the beginning.
2619 if(voice)
2620 voice->mPendingChange.store(true, std::memory_order_relaxed);
2621 cur->mOldVoice = voice;
2622 voice = nullptr;
2623 break;
2625 default:
2626 assert(voice == nullptr);
2627 cur->mOldVoice = nullptr;
2628 #if ALSOFT_EAX
2629 if(context->hasEax())
2630 source->eaxCommit();
2631 #endif // ALSOFT_EAX
2632 break;
2635 /* Find the next unused voice to play this source with. */
2636 for(;voiceiter != voicelist.end();++voiceiter,++vidx)
2638 Voice *v{*voiceiter};
2639 if(v->mPlayState.load(std::memory_order_acquire) == Voice::Stopped
2640 && v->mSourceID.load(std::memory_order_relaxed) == 0u
2641 && v->mPendingChange.load(std::memory_order_relaxed) == false)
2643 voice = v;
2644 break;
2647 ASSUME(voice != nullptr);
2649 voice->mPosition.store(0, std::memory_order_relaxed);
2650 voice->mPositionFrac.store(0, std::memory_order_relaxed);
2651 voice->mCurrentBuffer.store(&source->mQueue.front(), std::memory_order_relaxed);
2652 voice->mStartTime = start_time;
2653 voice->mFlags.reset();
2654 /* A source that's not playing or paused has any offset applied when it
2655 * starts playing.
2657 if(const ALenum offsettype{source->OffsetType})
2659 const double offset{source->Offset};
2660 source->OffsetType = AL_NONE;
2661 source->Offset = 0.0;
2662 if(auto vpos = GetSampleOffset(source->mQueue, offsettype, offset))
2664 voice->mPosition.store(vpos->pos, std::memory_order_relaxed);
2665 voice->mPositionFrac.store(vpos->frac, std::memory_order_relaxed);
2666 voice->mCurrentBuffer.store(vpos->bufferitem, std::memory_order_relaxed);
2667 if(vpos->pos > 0 || (vpos->pos == 0 && vpos->frac > 0)
2668 || vpos->bufferitem != &source->mQueue.front())
2669 voice->mFlags.set(VoiceIsFading);
2672 InitVoice(voice, source, al::to_address(BufferList), context, device);
2674 source->VoiceIdx = vidx;
2675 source->state = AL_PLAYING;
2677 cur->mVoice = voice;
2678 cur->mSourceID = source->id;
2679 cur->mState = VChangeState::Play;
2681 if(tail) LIKELY
2682 SendVoiceChanges(context, tail);
2685 } // namespace
2687 AL_API DECL_FUNC2(void, alGenSources, ALsizei,n, ALuint*,sources)
2688 FORCE_ALIGN void AL_APIENTRY alGenSourcesDirect(ALCcontext *context, ALsizei n, ALuint *sources) noexcept
2689 try {
2690 if(n < 0)
2691 throw al::context_error{AL_INVALID_VALUE, "Generating %d sources", n};
2692 if(n <= 0) UNLIKELY return;
2694 auto srclock = std::unique_lock{context->mSourceLock};
2695 auto *device = context->mALDevice.get();
2697 const al::span sids{sources, static_cast<ALuint>(n)};
2698 if(context->mNumSources > device->SourcesMax
2699 || sids.size() > device->SourcesMax-context->mNumSources)
2700 throw al::context_error{AL_OUT_OF_MEMORY, "Exceeding %u source limit (%u + %d)",
2701 device->SourcesMax, context->mNumSources, n};
2702 if(!EnsureSources(context, sids.size()))
2703 throw al::context_error{AL_OUT_OF_MEMORY, "Failed to allocate %d source%s", n,
2704 (n == 1) ? "" : "s"};
2706 std::generate(sids.begin(), sids.end(), [context]{ return AllocSource(context)->id; });
2708 catch(al::context_error& e) {
2709 context->setError(e.errorCode(), "%s", e.what());
2712 AL_API DECL_FUNC2(void, alDeleteSources, ALsizei,n, const ALuint*,sources)
2713 FORCE_ALIGN void AL_APIENTRY alDeleteSourcesDirect(ALCcontext *context, ALsizei n,
2714 const ALuint *sources) noexcept
2715 try {
2716 if(n < 0)
2717 throw al::context_error{AL_INVALID_VALUE, "Deleting %d sources", n};
2718 if(n <= 0) UNLIKELY return;
2720 std::lock_guard<std::mutex> srclock{context->mSourceLock};
2722 /* Check that all Sources are valid */
2723 auto validate_source = [context](const ALuint sid) -> bool
2724 { return LookupSource(context, sid) != nullptr; };
2726 const al::span sids{sources, static_cast<ALuint>(n)};
2727 auto invsrc = std::find_if_not(sids.begin(), sids.end(), validate_source);
2728 if(invsrc != sids.end())
2729 throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", *invsrc};
2731 /* All good. Delete source IDs. */
2732 auto delete_source = [&context](const ALuint sid) -> void
2734 if(ALsource *src{LookupSource(context, sid)})
2735 FreeSource(context, src);
2737 std::for_each(sids.begin(), sids.end(), delete_source);
2739 catch(al::context_error& e) {
2740 context->setError(e.errorCode(), "%s", e.what());
2743 AL_API DECL_FUNC1(ALboolean, alIsSource, ALuint,source)
2744 FORCE_ALIGN ALboolean AL_APIENTRY alIsSourceDirect(ALCcontext *context, ALuint source) noexcept
2746 std::lock_guard<std::mutex> srclock{context->mSourceLock};
2747 if(LookupSource(context, source) != nullptr)
2748 return AL_TRUE;
2749 return AL_FALSE;
2753 AL_API DECL_FUNC3(void, alSourcef, ALuint,source, ALenum,param, ALfloat,value)
2754 FORCE_ALIGN void AL_APIENTRY alSourcefDirect(ALCcontext *context, ALuint source, ALenum param,
2755 ALfloat value) noexcept
2756 try {
2757 std::lock_guard<std::mutex> proplock{context->mPropLock};
2758 std::lock_guard<std::mutex> sourcelock{context->mSourceLock};
2759 ALsource *Source{LookupSource(context, source)};
2760 if(!Source)
2761 throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source};
2763 SetProperty<float>(Source, context, static_cast<SourceProp>(param), {&value, 1u});
2765 catch(al::context_error& e) {
2766 context->setError(e.errorCode(), "%s", e.what());
2769 AL_API DECL_FUNC5(void, alSource3f, ALuint,source, ALenum,param, ALfloat,value1, ALfloat,value2, ALfloat,value3)
2770 FORCE_ALIGN void AL_APIENTRY alSource3fDirect(ALCcontext *context, ALuint source, ALenum param,
2771 ALfloat value1, ALfloat value2, ALfloat value3) noexcept
2772 try {
2773 std::lock_guard<std::mutex> proplock{context->mPropLock};
2774 std::lock_guard<std::mutex> sourcelock{context->mSourceLock};
2775 ALsource *Source{LookupSource(context, source)};
2776 if(!Source)
2777 throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source};
2779 const std::array fvals{value1, value2, value3};
2780 SetProperty<float>(Source, context, static_cast<SourceProp>(param), fvals);
2782 catch(al::context_error& e) {
2783 context->setError(e.errorCode(), "%s", e.what());
2786 AL_API DECL_FUNC3(void, alSourcefv, ALuint,source, ALenum,param, const ALfloat*,values)
2787 FORCE_ALIGN void AL_APIENTRY alSourcefvDirect(ALCcontext *context, ALuint source, ALenum param,
2788 const ALfloat *values) noexcept
2789 try {
2790 std::lock_guard<std::mutex> proplock{context->mPropLock};
2791 std::lock_guard<std::mutex> sourcelock{context->mSourceLock};
2792 ALsource *Source{LookupSource(context, source)};
2793 if(!Source)
2794 throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source};
2795 if(!values)
2796 throw al::context_error{AL_INVALID_VALUE, "NULL pointer"};
2798 const ALuint count{FloatValsByProp(param)};
2799 SetProperty(Source, context, static_cast<SourceProp>(param), al::span{values, count});
2801 catch(al::context_error& e) {
2802 context->setError(e.errorCode(), "%s", e.what());
2806 AL_API DECL_FUNCEXT3(void, alSourced,SOFT, ALuint,source, ALenum,param, ALdouble,value)
2807 FORCE_ALIGN void AL_APIENTRY alSourcedDirectSOFT(ALCcontext *context, ALuint source, ALenum param,
2808 ALdouble value) noexcept
2809 try {
2810 std::lock_guard<std::mutex> proplock{context->mPropLock};
2811 std::lock_guard<std::mutex> sourcelock{context->mSourceLock};
2812 ALsource *Source{LookupSource(context, source)};
2813 if(!Source)
2814 throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source};
2816 SetProperty<double>(Source, context, static_cast<SourceProp>(param), {&value, 1});
2818 catch(al::context_error& e) {
2819 context->setError(e.errorCode(), "%s", e.what());
2822 AL_API DECL_FUNCEXT5(void, alSource3d,SOFT, ALuint,source, ALenum,param, ALdouble,value1, ALdouble,value2, ALdouble,value3)
2823 FORCE_ALIGN void AL_APIENTRY alSource3dDirectSOFT(ALCcontext *context, ALuint source, ALenum param,
2824 ALdouble value1, ALdouble value2, ALdouble value3) noexcept
2825 try {
2826 std::lock_guard<std::mutex> proplock{context->mPropLock};
2827 std::lock_guard<std::mutex> sourcelock{context->mSourceLock};
2828 ALsource *Source{LookupSource(context, source)};
2829 if(!Source)
2830 throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source};
2832 const std::array dvals{value1, value2, value3};
2833 SetProperty<double>(Source, context, static_cast<SourceProp>(param), dvals);
2835 catch(al::context_error& e) {
2836 context->setError(e.errorCode(), "%s", e.what());
2839 AL_API DECL_FUNCEXT3(void, alSourcedv,SOFT, ALuint,source, ALenum,param, const ALdouble*,values)
2840 FORCE_ALIGN void AL_APIENTRY alSourcedvDirectSOFT(ALCcontext *context, ALuint source, ALenum param,
2841 const ALdouble *values) noexcept
2842 try {
2843 std::lock_guard<std::mutex> proplock{context->mPropLock};
2844 std::lock_guard<std::mutex> sourcelock{context->mSourceLock};
2845 ALsource *Source{LookupSource(context, source)};
2846 if(!Source)
2847 throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source};
2848 if(!values)
2849 throw al::context_error{AL_INVALID_VALUE, "NULL pointer"};
2851 const ALuint count{DoubleValsByProp(param)};
2852 SetProperty(Source, context, static_cast<SourceProp>(param), al::span{values, count});
2854 catch(al::context_error& e) {
2855 context->setError(e.errorCode(), "%s", e.what());
2859 AL_API DECL_FUNC3(void, alSourcei, ALuint,source, ALenum,param, ALint,value)
2860 FORCE_ALIGN void AL_APIENTRY alSourceiDirect(ALCcontext *context, ALuint source, ALenum param,
2861 ALint value) noexcept
2862 try {
2863 std::lock_guard<std::mutex> proplock{context->mPropLock};
2864 std::lock_guard<std::mutex> sourcelock{context->mSourceLock};
2865 ALsource *Source{LookupSource(context, source)};
2866 if(!Source)
2867 throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source};
2869 SetProperty<int>(Source, context, static_cast<SourceProp>(param), {&value, 1u});
2871 catch(al::context_error& e) {
2872 context->setError(e.errorCode(), "%s", e.what());
2875 AL_API DECL_FUNC5(void, alSource3i, ALuint,buffer, ALenum,param, ALint,value1, ALint,value2, ALint,value3)
2876 FORCE_ALIGN void AL_APIENTRY alSource3iDirect(ALCcontext *context, ALuint source, ALenum param,
2877 ALint value1, ALint value2, ALint value3) noexcept
2878 try {
2879 std::lock_guard<std::mutex> proplock{context->mPropLock};
2880 std::lock_guard<std::mutex> sourcelock{context->mSourceLock};
2881 ALsource *Source{LookupSource(context, source)};
2882 if(!Source)
2883 throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source};
2885 const std::array ivals{value1, value2, value3};
2886 SetProperty<int>(Source, context, static_cast<SourceProp>(param), ivals);
2888 catch(al::context_error& e) {
2889 context->setError(e.errorCode(), "%s", e.what());
2892 AL_API DECL_FUNC3(void, alSourceiv, ALuint,source, ALenum,param, const ALint*,values)
2893 FORCE_ALIGN void AL_APIENTRY alSourceivDirect(ALCcontext *context, ALuint source, ALenum param,
2894 const ALint *values) noexcept
2895 try {
2896 std::lock_guard<std::mutex> proplock{context->mPropLock};
2897 std::lock_guard<std::mutex> sourcelock{context->mSourceLock};
2898 ALsource *Source{LookupSource(context, source)};
2899 if(!Source)
2900 throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source};
2901 if(!values)
2902 throw al::context_error{AL_INVALID_VALUE, "NULL pointer"};
2904 const ALuint count{IntValsByProp(param)};
2905 SetProperty(Source, context, static_cast<SourceProp>(param), al::span{values, count});
2907 catch(al::context_error& e) {
2908 context->setError(e.errorCode(), "%s", e.what());
2912 AL_API DECL_FUNCEXT3(void, alSourcei64,SOFT, ALuint,source, ALenum,param, ALint64SOFT,value)
2913 FORCE_ALIGN void AL_APIENTRY alSourcei64DirectSOFT(ALCcontext *context, ALuint source,
2914 ALenum param, ALint64SOFT value) noexcept
2915 try {
2916 std::lock_guard<std::mutex> proplock{context->mPropLock};
2917 std::lock_guard<std::mutex> sourcelock{context->mSourceLock};
2918 ALsource *Source{LookupSource(context, source)};
2919 if(!Source)
2920 throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source};
2922 SetProperty<int64_t>(Source, context, static_cast<SourceProp>(param), {&value, 1u});
2924 catch(al::context_error& e) {
2925 context->setError(e.errorCode(), "%s", e.what());
2928 AL_API DECL_FUNCEXT5(void, alSource3i64,SOFT, ALuint,source, ALenum,param, ALint64SOFT,value1, ALint64SOFT,value2, ALint64SOFT,value3)
2929 FORCE_ALIGN void AL_APIENTRY alSource3i64DirectSOFT(ALCcontext *context, ALuint source,
2930 ALenum param, ALint64SOFT value1, ALint64SOFT value2, ALint64SOFT value3) noexcept
2931 try {
2932 std::lock_guard<std::mutex> proplock{context->mPropLock};
2933 std::lock_guard<std::mutex> sourcelock{context->mSourceLock};
2934 ALsource *Source{LookupSource(context, source)};
2935 if(!Source)
2936 throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source};
2938 const std::array i64vals{value1, value2, value3};
2939 SetProperty<int64_t>(Source, context, static_cast<SourceProp>(param), i64vals);
2941 catch(al::context_error& e) {
2942 context->setError(e.errorCode(), "%s", e.what());
2945 AL_API DECL_FUNCEXT3(void, alSourcei64v,SOFT, ALuint,source, ALenum,param, const ALint64SOFT*,values)
2946 FORCE_ALIGN void AL_APIENTRY alSourcei64vDirectSOFT(ALCcontext *context, ALuint source,
2947 ALenum param, const ALint64SOFT *values) noexcept
2948 try {
2949 std::lock_guard<std::mutex> proplock{context->mPropLock};
2950 std::lock_guard<std::mutex> sourcelock{context->mSourceLock};
2951 ALsource *Source{LookupSource(context, source)};
2952 if(!Source)
2953 throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source};
2954 if(!values)
2955 throw al::context_error{AL_INVALID_VALUE, "NULL pointer"};
2957 const ALuint count{Int64ValsByProp(param)};
2958 SetProperty(Source, context, static_cast<SourceProp>(param), al::span{values, count});
2960 catch(al::context_error& e) {
2961 context->setError(e.errorCode(), "%s", e.what());
2965 AL_API DECL_FUNC3(void, alGetSourcef, ALuint,source, ALenum,param, ALfloat*,value)
2966 FORCE_ALIGN void AL_APIENTRY alGetSourcefDirect(ALCcontext *context, ALuint source, ALenum param,
2967 ALfloat *value) noexcept
2968 try {
2969 std::lock_guard<std::mutex> sourcelock{context->mSourceLock};
2970 ALsource *Source{LookupSource(context, source)};
2971 if(!Source)
2972 throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source};
2973 if(!value)
2974 throw al::context_error{AL_INVALID_VALUE, "NULL pointer"};
2976 GetProperty(Source, context, static_cast<SourceProp>(param), al::span{value, 1u});
2978 catch(al::context_error& e) {
2979 context->setError(e.errorCode(), "%s", e.what());
2982 AL_API DECL_FUNC5(void, alGetSource3f, ALuint,source, ALenum,param, ALfloat*,value1, ALfloat*,value2, ALfloat*,value3)
2983 FORCE_ALIGN void AL_APIENTRY alGetSource3fDirect(ALCcontext *context, ALuint source, ALenum param,
2984 ALfloat *value1, ALfloat *value2, ALfloat *value3) noexcept
2985 try {
2986 std::lock_guard<std::mutex> sourcelock{context->mSourceLock};
2987 ALsource *Source{LookupSource(context, source)};
2988 if(!Source)
2989 throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source};
2990 if(!(value1 && value2 && value3))
2991 throw al::context_error{AL_INVALID_VALUE, "NULL pointer"};
2993 std::array<float,3> fvals{};
2994 GetProperty<float>(Source, context, static_cast<SourceProp>(param), fvals);
2995 *value1 = fvals[0];
2996 *value2 = fvals[1];
2997 *value3 = fvals[2];
2999 catch(al::context_error& e) {
3000 context->setError(e.errorCode(), "%s", e.what());
3003 AL_API DECL_FUNC3(void, alGetSourcefv, ALuint,source, ALenum,param, ALfloat*,values)
3004 FORCE_ALIGN void AL_APIENTRY alGetSourcefvDirect(ALCcontext *context, ALuint source, ALenum param,
3005 ALfloat *values) noexcept
3006 try {
3007 std::lock_guard<std::mutex> sourcelock{context->mSourceLock};
3008 ALsource *Source{LookupSource(context, source)};
3009 if(!Source)
3010 throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source};
3011 if(!values)
3012 throw al::context_error{AL_INVALID_VALUE, "NULL pointer"};
3014 const ALuint count{FloatValsByProp(param)};
3015 GetProperty(Source, context, static_cast<SourceProp>(param), al::span{values, count});
3017 catch(al::context_error& e) {
3018 context->setError(e.errorCode(), "%s", e.what());
3022 AL_API DECL_FUNCEXT3(void, alGetSourced,SOFT, ALuint,source, ALenum,param, ALdouble*,value)
3023 FORCE_ALIGN void AL_APIENTRY alGetSourcedDirectSOFT(ALCcontext *context, ALuint source,
3024 ALenum param, ALdouble *value) noexcept
3025 try {
3026 std::lock_guard<std::mutex> sourcelock{context->mSourceLock};
3027 ALsource *Source{LookupSource(context, source)};
3028 if(!Source)
3029 throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source};
3030 if(!value)
3031 throw al::context_error{AL_INVALID_VALUE, "NULL pointer"};
3033 GetProperty(Source, context, static_cast<SourceProp>(param), al::span{value, 1u});
3035 catch(al::context_error& e) {
3036 context->setError(e.errorCode(), "%s", e.what());
3039 AL_API DECL_FUNCEXT5(void, alGetSource3d,SOFT, ALuint,source, ALenum,param, ALdouble*,value1, ALdouble*,value2, ALdouble*,value3)
3040 FORCE_ALIGN void AL_APIENTRY alGetSource3dDirectSOFT(ALCcontext *context, ALuint source,
3041 ALenum param, ALdouble *value1, ALdouble *value2, ALdouble *value3) noexcept
3042 try {
3043 std::lock_guard<std::mutex> sourcelock{context->mSourceLock};
3044 ALsource *Source{LookupSource(context, source)};
3045 if(!Source)
3046 throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source};
3047 if(!(value1 && value2 && value3))
3048 throw al::context_error{AL_INVALID_VALUE, "NULL pointer"};
3050 std::array<double,3> dvals{};
3051 GetProperty<double>(Source, context, static_cast<SourceProp>(param), dvals);
3052 *value1 = dvals[0];
3053 *value2 = dvals[1];
3054 *value3 = dvals[2];
3056 catch(al::context_error& e) {
3057 context->setError(e.errorCode(), "%s", e.what());
3060 AL_API DECL_FUNCEXT3(void, alGetSourcedv,SOFT, ALuint,source, ALenum,param, ALdouble*,values)
3061 FORCE_ALIGN void AL_APIENTRY alGetSourcedvDirectSOFT(ALCcontext *context, ALuint source,
3062 ALenum param, ALdouble *values) noexcept
3063 try {
3064 std::lock_guard<std::mutex> sourcelock{context->mSourceLock};
3065 ALsource *Source{LookupSource(context, source)};
3066 if(!Source)
3067 throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source};
3068 if(!values)
3069 throw al::context_error{AL_INVALID_VALUE, "NULL pointer"};
3071 const ALuint count{DoubleValsByProp(param)};
3072 GetProperty(Source, context, static_cast<SourceProp>(param), al::span{values, count});
3074 catch(al::context_error& e) {
3075 context->setError(e.errorCode(), "%s", e.what());
3079 AL_API DECL_FUNC3(void, alGetSourcei, ALuint,source, ALenum,param, ALint*,value)
3080 FORCE_ALIGN void AL_APIENTRY alGetSourceiDirect(ALCcontext *context, ALuint source, ALenum param,
3081 ALint *value) noexcept
3082 try {
3083 std::lock_guard<std::mutex> sourcelock{context->mSourceLock};
3084 ALsource *Source{LookupSource(context, source)};
3085 if(!Source)
3086 throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source};
3087 if(!value)
3088 throw al::context_error{AL_INVALID_VALUE, "NULL pointer"};
3090 GetProperty(Source, context, static_cast<SourceProp>(param), al::span{value, 1u});
3092 catch(al::context_error& e) {
3093 context->setError(e.errorCode(), "%s", e.what());
3096 AL_API DECL_FUNC5(void, alGetSource3i, ALuint,source, ALenum,param, ALint*,value1, ALint*,value2, ALint*,value3)
3097 FORCE_ALIGN void AL_APIENTRY alGetSource3iDirect(ALCcontext *context, ALuint source, ALenum param,
3098 ALint *value1, ALint *value2, ALint *value3) noexcept
3099 try {
3100 std::lock_guard<std::mutex> sourcelock{context->mSourceLock};
3101 ALsource *Source{LookupSource(context, source)};
3102 if(!Source)
3103 throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source};
3104 if(!(value1 && value2 && value3))
3105 throw al::context_error{AL_INVALID_VALUE, "NULL pointer"};
3107 std::array<int,3> ivals{};
3108 GetProperty<int>(Source, context, static_cast<SourceProp>(param), ivals);
3109 *value1 = ivals[0];
3110 *value2 = ivals[1];
3111 *value3 = ivals[2];
3113 catch(al::context_error& e) {
3114 context->setError(e.errorCode(), "%s", e.what());
3117 AL_API DECL_FUNC3(void, alGetSourceiv, ALuint,source, ALenum,param, ALint*,values)
3118 FORCE_ALIGN void AL_APIENTRY alGetSourceivDirect(ALCcontext *context, ALuint source, ALenum param,
3119 ALint *values) noexcept
3120 try {
3121 std::lock_guard<std::mutex> sourcelock{context->mSourceLock};
3122 ALsource *Source{LookupSource(context, source)};
3123 if(!Source)
3124 throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source};
3125 if(!values)
3126 throw al::context_error{AL_INVALID_VALUE, "NULL pointer"};
3128 const ALuint count{IntValsByProp(param)};
3129 GetProperty(Source, context, static_cast<SourceProp>(param), al::span{values, count});
3131 catch(al::context_error& e) {
3132 context->setError(e.errorCode(), "%s", e.what());
3136 AL_API DECL_FUNCEXT3(void, alGetSourcei64,SOFT, ALuint,source, ALenum,param, ALint64SOFT*,value)
3137 FORCE_ALIGN void AL_APIENTRY alGetSourcei64DirectSOFT(ALCcontext *context, ALuint source, ALenum param, ALint64SOFT *value) noexcept
3138 try {
3139 std::lock_guard<std::mutex> sourcelock{context->mSourceLock};
3140 ALsource *Source{LookupSource(context, source)};
3141 if(!Source)
3142 throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source};
3143 if(!value)
3144 throw al::context_error{AL_INVALID_VALUE, "NULL pointer"};
3146 GetProperty(Source, context, static_cast<SourceProp>(param), al::span{value, 1u});
3148 catch(al::context_error& e) {
3149 context->setError(e.errorCode(), "%s", e.what());
3152 AL_API DECL_FUNCEXT5(void, alGetSource3i64,SOFT, ALuint,source, ALenum,param, ALint64SOFT*,value1, ALint64SOFT*,value2, ALint64SOFT*,value3)
3153 FORCE_ALIGN void AL_APIENTRY alGetSource3i64DirectSOFT(ALCcontext *context, ALuint source,
3154 ALenum param, ALint64SOFT *value1, ALint64SOFT *value2, ALint64SOFT *value3) noexcept
3155 try {
3156 std::lock_guard<std::mutex> sourcelock{context->mSourceLock};
3157 ALsource *Source{LookupSource(context, source)};
3158 if(!Source)
3159 throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source};
3160 if(!(value1 && value2 && value3))
3161 throw al::context_error{AL_INVALID_VALUE, "NULL pointer"};
3163 std::array<int64_t,3> i64vals{};
3164 GetProperty<int64_t>(Source, context, static_cast<SourceProp>(param), i64vals);
3165 *value1 = i64vals[0];
3166 *value2 = i64vals[1];
3167 *value3 = i64vals[2];
3169 catch(al::context_error& e) {
3170 context->setError(e.errorCode(), "%s", e.what());
3173 AL_API DECL_FUNCEXT3(void, alGetSourcei64v,SOFT, ALuint,source, ALenum,param, ALint64SOFT*,values)
3174 FORCE_ALIGN void AL_APIENTRY alGetSourcei64vDirectSOFT(ALCcontext *context, ALuint source,
3175 ALenum param, ALint64SOFT *values) noexcept
3176 try {
3177 std::lock_guard<std::mutex> sourcelock{context->mSourceLock};
3178 ALsource *Source{LookupSource(context, source)};
3179 if(!Source)
3180 throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source};
3181 if(!values)
3182 throw al::context_error{AL_INVALID_VALUE, "NULL pointer"};
3184 const ALuint count{Int64ValsByProp(param)};
3185 GetProperty(Source, context, static_cast<SourceProp>(param), al::span{values, count});
3187 catch(al::context_error& e) {
3188 context->setError(e.errorCode(), "%s", e.what());
3192 AL_API DECL_FUNC1(void, alSourcePlay, ALuint,source)
3193 FORCE_ALIGN void AL_APIENTRY alSourcePlayDirect(ALCcontext *context, ALuint source) noexcept
3194 try {
3195 std::lock_guard<std::mutex> sourcelock{context->mSourceLock};
3196 ALsource *Source{LookupSource(context, source)};
3197 if(!Source)
3198 throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source};
3200 StartSources(context, {&Source, 1});
3202 catch(al::context_error& e) {
3203 context->setError(e.errorCode(), "%s", e.what());
3206 FORCE_ALIGN DECL_FUNCEXT2(void, alSourcePlayAtTime,SOFT, ALuint,source, ALint64SOFT,start_time)
3207 FORCE_ALIGN void AL_APIENTRY alSourcePlayAtTimeDirectSOFT(ALCcontext *context, ALuint source,
3208 ALint64SOFT start_time) noexcept
3209 try {
3210 if(start_time < 0)
3211 throw al::context_error{AL_INVALID_VALUE, "Invalid time point %" PRId64, start_time};
3213 std::lock_guard<std::mutex> sourcelock{context->mSourceLock};
3214 ALsource *Source{LookupSource(context, source)};
3215 if(!Source)
3216 throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source};
3218 StartSources(context, {&Source, 1}, nanoseconds{start_time});
3220 catch(al::context_error& e) {
3221 context->setError(e.errorCode(), "%s", e.what());
3224 AL_API DECL_FUNC2(void, alSourcePlayv, ALsizei,n, const ALuint*,sources)
3225 FORCE_ALIGN void AL_APIENTRY alSourcePlayvDirect(ALCcontext *context, ALsizei n,
3226 const ALuint *sources) noexcept
3227 try {
3228 if(n < 0)
3229 throw al::context_error{AL_INVALID_VALUE, "Playing %d sources", n};
3230 if(n <= 0) UNLIKELY return;
3232 al::span sids{sources, static_cast<ALuint>(n)};
3233 source_store_variant source_store;
3234 const auto srchandles = [&source_store](size_t count) -> al::span<ALsource*>
3236 if(count > std::tuple_size_v<source_store_array>)
3237 return al::span{source_store.emplace<source_store_vector>(count)};
3238 return al::span{source_store.emplace<source_store_array>()}.first(count);
3239 }(sids.size());
3241 std::lock_guard<std::mutex> sourcelock{context->mSourceLock};
3242 auto lookup_src = [context](const ALuint sid) -> ALsource*
3244 if(ALsource *src{LookupSource(context, sid)})
3245 return src;
3246 throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", sid};
3248 std::transform(sids.cbegin(), sids.cend(), srchandles.begin(), lookup_src);
3250 StartSources(context, srchandles);
3252 catch(al::context_error& e) {
3253 context->setError(e.errorCode(), "%s", e.what());
3256 FORCE_ALIGN DECL_FUNCEXT3(void, alSourcePlayAtTimev,SOFT, ALsizei,n, const ALuint*,sources, ALint64SOFT,start_time)
3257 FORCE_ALIGN void AL_APIENTRY alSourcePlayAtTimevDirectSOFT(ALCcontext *context, ALsizei n,
3258 const ALuint *sources, ALint64SOFT start_time) noexcept
3259 try {
3260 if(n < 0)
3261 throw al::context_error{AL_INVALID_VALUE, "Playing %d sources", n};
3262 if(n <= 0) UNLIKELY return;
3264 if(start_time < 0)
3265 throw al::context_error{AL_INVALID_VALUE, "Invalid time point %" PRId64, start_time};
3267 al::span sids{sources, static_cast<ALuint>(n)};
3268 source_store_variant source_store;
3269 const auto srchandles = [&source_store](size_t count) -> al::span<ALsource*>
3271 if(count > std::tuple_size_v<source_store_array>)
3272 return al::span{source_store.emplace<source_store_vector>(count)};
3273 return al::span{source_store.emplace<source_store_array>()}.first(count);
3274 }(sids.size());
3276 std::lock_guard<std::mutex> sourcelock{context->mSourceLock};
3277 auto lookup_src = [context](const ALuint sid) -> ALsource*
3279 if(ALsource *src{LookupSource(context, sid)})
3280 return src;
3281 throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", sid};
3283 std::transform(sids.cbegin(), sids.cend(), srchandles.begin(), lookup_src);
3285 StartSources(context, srchandles, nanoseconds{start_time});
3287 catch(al::context_error& e) {
3288 context->setError(e.errorCode(), "%s", e.what());
3292 AL_API DECL_FUNC1(void, alSourcePause, ALuint,source)
3293 FORCE_ALIGN void AL_APIENTRY alSourcePauseDirect(ALCcontext *context, ALuint source) noexcept
3294 { alSourcePausevDirect(context, 1, &source); }
3296 AL_API DECL_FUNC2(void, alSourcePausev, ALsizei,n, const ALuint*,sources)
3297 FORCE_ALIGN void AL_APIENTRY alSourcePausevDirect(ALCcontext *context, ALsizei n,
3298 const ALuint *sources) noexcept
3299 try {
3300 if(n < 0)
3301 throw al::context_error{AL_INVALID_VALUE, "Pausing %d sources", n};
3302 if(n <= 0) UNLIKELY return;
3304 al::span sids{sources, static_cast<ALuint>(n)};
3305 source_store_variant source_store;
3306 const auto srchandles = [&source_store](size_t count) -> al::span<ALsource*>
3308 if(count > std::tuple_size_v<source_store_array>)
3309 return al::span{source_store.emplace<source_store_vector>(count)};
3310 return al::span{source_store.emplace<source_store_array>()}.first(count);
3311 }(sids.size());
3313 std::lock_guard<std::mutex> sourcelock{context->mSourceLock};
3314 auto lookup_src = [context](const ALuint sid) -> ALsource*
3316 if(ALsource *src{LookupSource(context, sid)})
3317 return src;
3318 throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", sid};
3320 std::transform(sids.cbegin(), sids.cend(), srchandles.begin(), lookup_src);
3322 /* Pausing has to be done in two steps. First, for each source that's
3323 * detected to be playing, chamge the voice (asynchronously) to
3324 * stopping/paused.
3326 VoiceChange *tail{}, *cur{};
3327 for(ALsource *source : srchandles)
3329 Voice *voice{GetSourceVoice(source, context)};
3330 if(GetSourceState(source, voice) == AL_PLAYING)
3332 if(!cur)
3333 cur = tail = GetVoiceChanger(context);
3334 else
3336 cur->mNext.store(GetVoiceChanger(context), std::memory_order_relaxed);
3337 cur = cur->mNext.load(std::memory_order_relaxed);
3339 cur->mVoice = voice;
3340 cur->mSourceID = source->id;
3341 cur->mState = VChangeState::Pause;
3344 if(tail) LIKELY
3346 SendVoiceChanges(context, tail);
3347 /* Second, now that the voice changes have been sent, because it's
3348 * possible that the voice stopped after it was detected playing and
3349 * before the voice got paused, recheck that the source is still
3350 * considered playing and set it to paused if so.
3352 for(ALsource *source : srchandles)
3354 Voice *voice{GetSourceVoice(source, context)};
3355 if(GetSourceState(source, voice) == AL_PLAYING)
3356 source->state = AL_PAUSED;
3360 catch(al::context_error& e) {
3361 context->setError(e.errorCode(), "%s", e.what());
3365 AL_API DECL_FUNC1(void, alSourceStop, ALuint,source)
3366 FORCE_ALIGN void AL_APIENTRY alSourceStopDirect(ALCcontext *context, ALuint source) noexcept
3367 { alSourceStopvDirect(context, 1, &source); }
3369 AL_API DECL_FUNC2(void, alSourceStopv, ALsizei,n, const ALuint*,sources)
3370 FORCE_ALIGN void AL_APIENTRY alSourceStopvDirect(ALCcontext *context, ALsizei n,
3371 const ALuint *sources) noexcept
3372 try {
3373 if(n < 0)
3374 throw al::context_error{AL_INVALID_VALUE, "Stopping %d sources", n};
3375 if(n <= 0) UNLIKELY return;
3377 al::span sids{sources, static_cast<ALuint>(n)};
3378 source_store_variant source_store;
3379 const auto srchandles = [&source_store](size_t count) -> al::span<ALsource*>
3381 if(count > std::tuple_size_v<source_store_array>)
3382 return al::span{source_store.emplace<source_store_vector>(count)};
3383 return al::span{source_store.emplace<source_store_array>()}.first(count);
3384 }(sids.size());
3386 std::lock_guard<std::mutex> sourcelock{context->mSourceLock};
3387 auto lookup_src = [context](const ALuint sid) -> ALsource*
3389 if(ALsource *src{LookupSource(context, sid)})
3390 return src;
3391 throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", sid};
3393 std::transform(sids.cbegin(), sids.cend(), srchandles.begin(), lookup_src);
3395 VoiceChange *tail{}, *cur{};
3396 for(ALsource *source : srchandles)
3398 if(Voice *voice{GetSourceVoice(source, context)})
3400 if(!cur)
3401 cur = tail = GetVoiceChanger(context);
3402 else
3404 cur->mNext.store(GetVoiceChanger(context), std::memory_order_relaxed);
3405 cur = cur->mNext.load(std::memory_order_relaxed);
3407 voice->mPendingChange.store(true, std::memory_order_relaxed);
3408 cur->mVoice = voice;
3409 cur->mSourceID = source->id;
3410 cur->mState = VChangeState::Stop;
3411 source->state = AL_STOPPED;
3413 source->Offset = 0.0;
3414 source->OffsetType = AL_NONE;
3415 source->VoiceIdx = InvalidVoiceIndex;
3417 if(tail) LIKELY
3418 SendVoiceChanges(context, tail);
3420 catch(al::context_error& e) {
3421 context->setError(e.errorCode(), "%s", e.what());
3425 AL_API DECL_FUNC1(void, alSourceRewind, ALuint,source)
3426 FORCE_ALIGN void AL_APIENTRY alSourceRewindDirect(ALCcontext *context, ALuint source) noexcept
3427 { alSourceRewindvDirect(context, 1, &source); }
3429 AL_API DECL_FUNC2(void, alSourceRewindv, ALsizei,n, const ALuint*,sources)
3430 FORCE_ALIGN void AL_APIENTRY alSourceRewindvDirect(ALCcontext *context, ALsizei n,
3431 const ALuint *sources) noexcept
3432 try {
3433 if(n < 0)
3434 throw al::context_error{AL_INVALID_VALUE, "Rewinding %d sources", n};
3435 if(n <= 0) UNLIKELY return;
3437 al::span sids{sources, static_cast<ALuint>(n)};
3438 source_store_variant source_store;
3439 const auto srchandles = [&source_store](size_t count) -> al::span<ALsource*>
3441 if(count > std::tuple_size_v<source_store_array>)
3442 return al::span{source_store.emplace<source_store_vector>(count)};
3443 return al::span{source_store.emplace<source_store_array>()}.first(count);
3444 }(sids.size());
3446 std::lock_guard<std::mutex> sourcelock{context->mSourceLock};
3447 auto lookup_src = [context](const ALuint sid) -> ALsource*
3449 if(ALsource *src{LookupSource(context, sid)})
3450 return src;
3451 throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", sid};
3453 std::transform(sids.cbegin(), sids.cend(), srchandles.begin(), lookup_src);
3455 VoiceChange *tail{}, *cur{};
3456 for(ALsource *source : srchandles)
3458 Voice *voice{GetSourceVoice(source, context)};
3459 if(source->state != AL_INITIAL)
3461 if(!cur)
3462 cur = tail = GetVoiceChanger(context);
3463 else
3465 cur->mNext.store(GetVoiceChanger(context), std::memory_order_relaxed);
3466 cur = cur->mNext.load(std::memory_order_relaxed);
3468 if(voice)
3469 voice->mPendingChange.store(true, std::memory_order_relaxed);
3470 cur->mVoice = voice;
3471 cur->mSourceID = source->id;
3472 cur->mState = VChangeState::Reset;
3473 source->state = AL_INITIAL;
3475 source->Offset = 0.0;
3476 source->OffsetType = AL_NONE;
3477 source->VoiceIdx = InvalidVoiceIndex;
3479 if(tail) LIKELY
3480 SendVoiceChanges(context, tail);
3482 catch(al::context_error& e) {
3483 context->setError(e.errorCode(), "%s", e.what());
3487 AL_API DECL_FUNC3(void, alSourceQueueBuffers, ALuint,source, ALsizei,nb, const ALuint*,buffers)
3488 FORCE_ALIGN void AL_APIENTRY alSourceQueueBuffersDirect(ALCcontext *context, ALuint src,
3489 ALsizei nb, const ALuint *buffers) noexcept
3490 try {
3491 if(nb < 0)
3492 throw al::context_error{AL_INVALID_VALUE, "Queueing %d buffers", nb};
3493 if(nb <= 0) UNLIKELY return;
3495 std::lock_guard<std::mutex> sourcelock{context->mSourceLock};
3496 ALsource *source{LookupSource(context,src)};
3497 if(!source)
3498 throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", src};
3500 /* Can't queue on a Static Source */
3501 if(source->SourceType == AL_STATIC)
3502 throw al::context_error{AL_INVALID_OPERATION, "Queueing onto static source %u", src};
3504 /* Check for a valid Buffer, for its frequency and format */
3505 auto *device = context->mALDevice.get();
3506 ALbuffer *BufferFmt{nullptr};
3507 for(auto &item : source->mQueue)
3509 BufferFmt = item.mBuffer;
3510 if(BufferFmt) break;
3513 std::unique_lock<std::mutex> buflock{device->BufferLock};
3514 const auto bids = al::span{buffers, static_cast<ALuint>(nb)};
3515 const size_t NewListStart{source->mQueue.size()};
3516 try {
3517 ALbufferQueueItem *BufferList{nullptr};
3518 std::for_each(bids.cbegin(), bids.cend(),
3519 [source,device,&BufferFmt,&BufferList](const ALuint bid)
3521 ALbuffer *buffer{bid ? LookupBuffer(device, bid) : nullptr};
3522 if(bid && !buffer)
3523 throw al::context_error{AL_INVALID_NAME, "Queueing invalid buffer ID %u", bid};
3525 if(buffer)
3527 if(buffer->mSampleRate < 1)
3528 throw al::context_error{AL_INVALID_OPERATION,
3529 "Queueing buffer %u with no format", buffer->id};
3531 if(buffer->mCallback)
3532 throw al::context_error{AL_INVALID_OPERATION, "Queueing callback buffer %u",
3533 buffer->id};
3535 if(buffer->MappedAccess != 0 && !(buffer->MappedAccess&AL_MAP_PERSISTENT_BIT_SOFT))
3536 throw al::context_error{AL_INVALID_OPERATION,
3537 "Queueing non-persistently mapped buffer %u", buffer->id};
3540 source->mQueue.emplace_back();
3541 if(!BufferList)
3542 BufferList = &source->mQueue.back();
3543 else
3545 auto &item = source->mQueue.back();
3546 BufferList->mNext.store(&item, std::memory_order_relaxed);
3547 BufferList = &item;
3549 if(!buffer) return;
3550 BufferList->mBlockAlign = buffer->mBlockAlign;
3551 BufferList->mSampleLen = buffer->mSampleLen;
3552 BufferList->mLoopEnd = buffer->mSampleLen;
3553 BufferList->mSamples = buffer->mData;
3554 BufferList->mBuffer = buffer;
3555 IncrementRef(buffer->ref);
3557 bool fmt_mismatch{false};
3558 if(BufferFmt == nullptr)
3559 BufferFmt = buffer;
3560 else
3562 fmt_mismatch |= BufferFmt->mSampleRate != buffer->mSampleRate;
3563 fmt_mismatch |= BufferFmt->mChannels != buffer->mChannels;
3564 fmt_mismatch |= BufferFmt->mType != buffer->mType;
3565 if(BufferFmt->isBFormat())
3567 fmt_mismatch |= BufferFmt->mAmbiLayout != buffer->mAmbiLayout;
3568 fmt_mismatch |= BufferFmt->mAmbiScaling != buffer->mAmbiScaling;
3570 fmt_mismatch |= BufferFmt->mAmbiOrder != buffer->mAmbiOrder;
3572 if(fmt_mismatch)
3573 throw al::context_error{AL_INVALID_OPERATION,
3574 "Queueing buffer with mismatched format\n"
3575 " Expected: %uhz, %s, %s ; Got: %uhz, %s, %s\n", BufferFmt->mSampleRate,
3576 NameFromFormat(BufferFmt->mType), NameFromFormat(BufferFmt->mChannels),
3577 buffer->mSampleRate, NameFromFormat(buffer->mType),
3578 NameFromFormat(buffer->mChannels)};
3581 catch(...) {
3582 /* A buffer failed (invalid ID or format), or there was some other
3583 * unexpected error, so unlock and release each buffer we had.
3585 auto iter = source->mQueue.begin() + ptrdiff_t(NewListStart);
3586 for(;iter != source->mQueue.end();++iter)
3588 if(ALbuffer *buf{iter->mBuffer})
3589 DecrementRef(buf->ref);
3591 source->mQueue.resize(NewListStart);
3592 throw;
3594 /* All buffers good. */
3595 buflock.unlock();
3597 /* Source is now streaming */
3598 source->SourceType = AL_STREAMING;
3600 if(NewListStart != 0)
3602 auto iter = source->mQueue.begin() + ptrdiff_t(NewListStart);
3603 (iter-1)->mNext.store(al::to_address(iter), std::memory_order_release);
3606 catch(al::context_error& e) {
3607 context->setError(e.errorCode(), "%s", e.what());
3610 AL_API DECL_FUNC3(void, alSourceUnqueueBuffers, ALuint,source, ALsizei,nb, ALuint*,buffers)
3611 FORCE_ALIGN void AL_APIENTRY alSourceUnqueueBuffersDirect(ALCcontext *context, ALuint src,
3612 ALsizei nb, ALuint *buffers) noexcept
3613 try {
3614 if(nb < 0)
3615 throw al::context_error{AL_INVALID_VALUE, "Unqueueing %d buffers", nb};
3616 if(nb <= 0) UNLIKELY return;
3618 std::lock_guard<std::mutex> sourcelock{context->mSourceLock};
3619 ALsource *source{LookupSource(context,src)};
3620 if(!source)
3621 throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", src};
3623 if(source->SourceType != AL_STREAMING)
3624 throw al::context_error{AL_INVALID_VALUE, "Unqueueing from a non-streaming source %u",src};
3625 if(source->Looping)
3626 throw al::context_error{AL_INVALID_VALUE, "Unqueueing from looping source %u", src};
3628 /* Make sure enough buffers have been processed to unqueue. */
3629 const al::span bids{buffers, static_cast<ALuint>(nb)};
3630 size_t processed{0};
3631 if(source->state != AL_INITIAL) LIKELY
3633 VoiceBufferItem *Current{nullptr};
3634 if(Voice *voice{GetSourceVoice(source, context)})
3635 Current = voice->mCurrentBuffer.load(std::memory_order_relaxed);
3636 for(auto &item : source->mQueue)
3638 if(&item == Current)
3639 break;
3640 ++processed;
3643 if(processed < bids.size())
3644 throw al::context_error{AL_INVALID_VALUE, "Unqueueing %d buffer%s (only %zu processed)",
3645 nb, (nb==1)?"":"s", processed};
3647 std::generate(bids.begin(), bids.end(), [source]() noexcept -> ALuint
3649 auto &head = source->mQueue.front();
3650 ALuint bid{0};
3651 if(ALbuffer *buffer{head.mBuffer})
3653 bid = buffer->id;
3654 DecrementRef(buffer->ref);
3656 source->mQueue.pop_front();
3657 return bid;
3660 catch(al::context_error& e) {
3661 context->setError(e.errorCode(), "%s", e.what());
3665 AL_API void AL_APIENTRY alSourceQueueBufferLayersSOFT(ALuint, ALsizei, const ALuint*) noexcept
3667 ContextRef context{GetContextRef()};
3668 if(!context) UNLIKELY return;
3670 context->setError(AL_INVALID_OPERATION, "alSourceQueueBufferLayersSOFT not supported");
3674 ALsource::ALsource() noexcept
3676 Direct.Gain = 1.0f;
3677 Direct.GainHF = 1.0f;
3678 Direct.HFReference = LowPassFreqRef;
3679 Direct.GainLF = 1.0f;
3680 Direct.LFReference = HighPassFreqRef;
3681 for(auto &send : Send)
3683 send.Slot = nullptr;
3684 send.Gain = 1.0f;
3685 send.GainHF = 1.0f;
3686 send.HFReference = LowPassFreqRef;
3687 send.GainLF = 1.0f;
3688 send.LFReference = HighPassFreqRef;
3692 ALsource::~ALsource()
3694 for(auto &item : mQueue)
3696 if(ALbuffer *buffer{item.mBuffer})
3697 DecrementRef(buffer->ref);
3700 auto clear_send = [](ALsource::SendData &send) -> void
3701 { if(send.Slot) DecrementRef(send.Slot->ref); };
3702 std::for_each(Send.begin(), Send.end(), clear_send);
3705 void UpdateAllSourceProps(ALCcontext *context)
3707 std::lock_guard<std::mutex> srclock{context->mSourceLock};
3708 auto voicelist = context->getVoicesSpan();
3709 ALuint vidx{0u};
3710 for(Voice *voice : voicelist)
3712 ALuint sid{voice->mSourceID.load(std::memory_order_acquire)};
3713 ALsource *source{sid ? LookupSource(context, sid) : nullptr};
3714 if(source && source->VoiceIdx == vidx)
3716 if(std::exchange(source->mPropsDirty, false))
3717 UpdateSourceProps(source, voice, context);
3719 ++vidx;
3723 void ALsource::SetName(ALCcontext *context, ALuint id, std::string_view name)
3725 std::lock_guard<std::mutex> srclock{context->mSourceLock};
3727 auto source = LookupSource(context, id);
3728 if(!source)
3729 throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", id};
3731 context->mSourceNames.insert_or_assign(id, name);
3735 SourceSubList::~SourceSubList()
3737 if(!Sources)
3738 return;
3740 uint64_t usemask{~FreeMask};
3741 while(usemask)
3743 const int idx{al::countr_zero(usemask)};
3744 usemask &= ~(1_u64 << idx);
3745 std::destroy_at(al::to_address(Sources->begin() + idx));
3747 FreeMask = ~usemask;
3748 SubListAllocator{}.deallocate(Sources, 1);
3749 Sources = nullptr;
3753 #if ALSOFT_EAX
3754 void ALsource::eaxInitialize(ALCcontext *context) noexcept
3756 assert(context != nullptr);
3757 mEaxAlContext = context;
3759 mEaxPrimaryFxSlotId = context->eaxGetPrimaryFxSlotIndex();
3760 eax_set_defaults();
3762 eax1_translate(mEax1.i, mEax);
3763 mEaxVersion = 1;
3764 mEaxChanged = true;
3767 void ALsource::eaxDispatch(const EaxCall& call)
3769 call.is_get() ? eax_get(call) : eax_set(call);
3772 ALsource* ALsource::EaxLookupSource(ALCcontext& al_context, ALuint source_id) noexcept
3774 return LookupSource(&al_context, source_id);
3777 [[noreturn]] void ALsource::eax_fail(const char* message)
3779 throw Exception{message};
3782 [[noreturn]] void ALsource::eax_fail_unknown_property_id()
3784 eax_fail("Unknown property id.");
3787 [[noreturn]] void ALsource::eax_fail_unknown_version()
3789 eax_fail("Unknown version.");
3792 [[noreturn]] void ALsource::eax_fail_unknown_active_fx_slot_id()
3794 eax_fail("Unknown active FX slot ID.");
3797 [[noreturn]] void ALsource::eax_fail_unknown_receiving_fx_slot_id()
3799 eax_fail("Unknown receiving FX slot ID.");
3802 void ALsource::eax_set_sends_defaults(EaxSends& sends, const EaxFxSlotIds& ids) noexcept
3804 for(size_t i{0};i < EAX_MAX_FXSLOTS;++i)
3806 auto& send = sends[i];
3807 send.guidReceivingFXSlotID = *(ids[i]);
3808 send.lSend = EAXSOURCE_DEFAULTSEND;
3809 send.lSendHF = EAXSOURCE_DEFAULTSENDHF;
3810 send.lOcclusion = EAXSOURCE_DEFAULTOCCLUSION;
3811 send.flOcclusionLFRatio = EAXSOURCE_DEFAULTOCCLUSIONLFRATIO;
3812 send.flOcclusionRoomRatio = EAXSOURCE_DEFAULTOCCLUSIONROOMRATIO;
3813 send.flOcclusionDirectRatio = EAXSOURCE_DEFAULTOCCLUSIONDIRECTRATIO;
3814 send.lExclusion = EAXSOURCE_DEFAULTEXCLUSION;
3815 send.flExclusionLFRatio = EAXSOURCE_DEFAULTEXCLUSIONLFRATIO;
3819 void ALsource::eax1_set_defaults(Eax1Props& props) noexcept
3821 props.fMix = EAX_REVERBMIX_USEDISTANCE;
3824 void ALsource::eax1_set_defaults() noexcept
3826 eax1_set_defaults(mEax1.i);
3827 mEax1.d = mEax1.i;
3830 void ALsource::eax2_set_defaults(Eax2Props& props) noexcept
3832 props.lDirect = EAXSOURCE_DEFAULTDIRECT;
3833 props.lDirectHF = EAXSOURCE_DEFAULTDIRECTHF;
3834 props.lRoom = EAXSOURCE_DEFAULTROOM;
3835 props.lRoomHF = EAXSOURCE_DEFAULTROOMHF;
3836 props.flRoomRolloffFactor = EAXSOURCE_DEFAULTROOMROLLOFFFACTOR;
3837 props.lObstruction = EAXSOURCE_DEFAULTOBSTRUCTION;
3838 props.flObstructionLFRatio = EAXSOURCE_DEFAULTOBSTRUCTIONLFRATIO;
3839 props.lOcclusion = EAXSOURCE_DEFAULTOCCLUSION;
3840 props.flOcclusionLFRatio = EAXSOURCE_DEFAULTOCCLUSIONLFRATIO;
3841 props.flOcclusionRoomRatio = EAXSOURCE_DEFAULTOCCLUSIONROOMRATIO;
3842 props.lOutsideVolumeHF = EAXSOURCE_DEFAULTOUTSIDEVOLUMEHF;
3843 props.flAirAbsorptionFactor = EAXSOURCE_DEFAULTAIRABSORPTIONFACTOR;
3844 props.dwFlags = EAXSOURCE_DEFAULTFLAGS;
3847 void ALsource::eax2_set_defaults() noexcept
3849 eax2_set_defaults(mEax2.i);
3850 mEax2.d = mEax2.i;
3853 void ALsource::eax3_set_defaults(Eax3Props& props) noexcept
3855 props.lDirect = EAXSOURCE_DEFAULTDIRECT;
3856 props.lDirectHF = EAXSOURCE_DEFAULTDIRECTHF;
3857 props.lRoom = EAXSOURCE_DEFAULTROOM;
3858 props.lRoomHF = EAXSOURCE_DEFAULTROOMHF;
3859 props.lObstruction = EAXSOURCE_DEFAULTOBSTRUCTION;
3860 props.flObstructionLFRatio = EAXSOURCE_DEFAULTOBSTRUCTIONLFRATIO;
3861 props.lOcclusion = EAXSOURCE_DEFAULTOCCLUSION;
3862 props.flOcclusionLFRatio = EAXSOURCE_DEFAULTOCCLUSIONLFRATIO;
3863 props.flOcclusionRoomRatio = EAXSOURCE_DEFAULTOCCLUSIONROOMRATIO;
3864 props.flOcclusionDirectRatio = EAXSOURCE_DEFAULTOCCLUSIONDIRECTRATIO;
3865 props.lExclusion = EAXSOURCE_DEFAULTEXCLUSION;
3866 props.flExclusionLFRatio = EAXSOURCE_DEFAULTEXCLUSIONLFRATIO;
3867 props.lOutsideVolumeHF = EAXSOURCE_DEFAULTOUTSIDEVOLUMEHF;
3868 props.flDopplerFactor = EAXSOURCE_DEFAULTDOPPLERFACTOR;
3869 props.flRolloffFactor = EAXSOURCE_DEFAULTROLLOFFFACTOR;
3870 props.flRoomRolloffFactor = EAXSOURCE_DEFAULTROOMROLLOFFFACTOR;
3871 props.flAirAbsorptionFactor = EAXSOURCE_DEFAULTAIRABSORPTIONFACTOR;
3872 props.ulFlags = EAXSOURCE_DEFAULTFLAGS;
3875 void ALsource::eax3_set_defaults() noexcept
3877 eax3_set_defaults(mEax3.i);
3878 mEax3.d = mEax3.i;
3881 void ALsource::eax4_set_sends_defaults(EaxSends& sends) noexcept
3883 eax_set_sends_defaults(sends, eax4_fx_slot_ids);
3886 void ALsource::eax4_set_active_fx_slots_defaults(EAX40ACTIVEFXSLOTS& slots) noexcept
3888 slots = EAX40SOURCE_DEFAULTACTIVEFXSLOTID;
3891 void ALsource::eax4_set_defaults() noexcept
3893 eax3_set_defaults(mEax4.i.source);
3894 eax4_set_sends_defaults(mEax4.i.sends);
3895 eax4_set_active_fx_slots_defaults(mEax4.i.active_fx_slots);
3896 mEax4.d = mEax4.i;
3899 void ALsource::eax5_set_source_defaults(EAX50SOURCEPROPERTIES& props) noexcept
3901 eax3_set_defaults(static_cast<Eax3Props&>(props));
3902 props.flMacroFXFactor = EAXSOURCE_DEFAULTMACROFXFACTOR;
3905 void ALsource::eax5_set_sends_defaults(EaxSends& sends) noexcept
3907 eax_set_sends_defaults(sends, eax5_fx_slot_ids);
3910 void ALsource::eax5_set_active_fx_slots_defaults(EAX50ACTIVEFXSLOTS& slots) noexcept
3912 slots = EAX50SOURCE_3DDEFAULTACTIVEFXSLOTID;
3915 void ALsource::eax5_set_speaker_levels_defaults(EaxSpeakerLevels& speaker_levels) noexcept
3917 for(size_t i{0};i < eax_max_speakers;++i)
3919 auto& speaker_level = speaker_levels[i];
3920 speaker_level.lSpeakerID = static_cast<long>(EAXSPEAKER_FRONT_LEFT + i);
3921 speaker_level.lLevel = EAXSOURCE_DEFAULTSPEAKERLEVEL;
3925 void ALsource::eax5_set_defaults(Eax5Props& props) noexcept
3927 eax5_set_source_defaults(props.source);
3928 eax5_set_sends_defaults(props.sends);
3929 eax5_set_active_fx_slots_defaults(props.active_fx_slots);
3930 eax5_set_speaker_levels_defaults(props.speaker_levels);
3933 void ALsource::eax5_set_defaults() noexcept
3935 eax5_set_defaults(mEax5.i);
3936 mEax5.d = mEax5.i;
3939 void ALsource::eax_set_defaults() noexcept
3941 eax1_set_defaults();
3942 eax2_set_defaults();
3943 eax3_set_defaults();
3944 eax4_set_defaults();
3945 eax5_set_defaults();
3948 void ALsource::eax1_translate(const Eax1Props& src, Eax5Props& dst) noexcept
3950 eax5_set_defaults(dst);
3952 if (src.fMix == EAX_REVERBMIX_USEDISTANCE)
3954 dst.source.ulFlags |= EAXSOURCEFLAGS_ROOMAUTO;
3955 dst.sends[0].lSend = 0;
3957 else
3959 dst.source.ulFlags &= ~EAXSOURCEFLAGS_ROOMAUTO;
3960 dst.sends[0].lSend = std::clamp(static_cast<long>(gain_to_level_mb(src.fMix)),
3961 EAXSOURCE_MINSEND, EAXSOURCE_MAXSEND);
3965 void ALsource::eax2_translate(const Eax2Props& src, Eax5Props& dst) noexcept
3967 // Source.
3969 dst.source.lDirect = src.lDirect;
3970 dst.source.lDirectHF = src.lDirectHF;
3971 dst.source.lRoom = src.lRoom;
3972 dst.source.lRoomHF = src.lRoomHF;
3973 dst.source.lObstruction = src.lObstruction;
3974 dst.source.flObstructionLFRatio = src.flObstructionLFRatio;
3975 dst.source.lOcclusion = src.lOcclusion;
3976 dst.source.flOcclusionLFRatio = src.flOcclusionLFRatio;
3977 dst.source.flOcclusionRoomRatio = src.flOcclusionRoomRatio;
3978 dst.source.flOcclusionDirectRatio = EAXSOURCE_DEFAULTOCCLUSIONDIRECTRATIO;
3979 dst.source.lExclusion = EAXSOURCE_DEFAULTEXCLUSION;
3980 dst.source.flExclusionLFRatio = EAXSOURCE_DEFAULTEXCLUSIONLFRATIO;
3981 dst.source.lOutsideVolumeHF = src.lOutsideVolumeHF;
3982 dst.source.flDopplerFactor = EAXSOURCE_DEFAULTDOPPLERFACTOR;
3983 dst.source.flRolloffFactor = EAXSOURCE_DEFAULTROLLOFFFACTOR;
3984 dst.source.flRoomRolloffFactor = src.flRoomRolloffFactor;
3985 dst.source.flAirAbsorptionFactor = src.flAirAbsorptionFactor;
3986 dst.source.ulFlags = src.dwFlags;
3987 dst.source.flMacroFXFactor = EAXSOURCE_DEFAULTMACROFXFACTOR;
3989 // Set everything else to defaults.
3991 eax5_set_sends_defaults(dst.sends);
3992 eax5_set_active_fx_slots_defaults(dst.active_fx_slots);
3993 eax5_set_speaker_levels_defaults(dst.speaker_levels);
3996 void ALsource::eax3_translate(const Eax3Props& src, Eax5Props& dst) noexcept
3998 // Source.
4000 static_cast<Eax3Props&>(dst.source) = src;
4001 dst.source.flMacroFXFactor = EAXSOURCE_DEFAULTMACROFXFACTOR;
4003 // Set everything else to defaults.
4005 eax5_set_sends_defaults(dst.sends);
4006 eax5_set_active_fx_slots_defaults(dst.active_fx_slots);
4007 eax5_set_speaker_levels_defaults(dst.speaker_levels);
4010 void ALsource::eax4_translate(const Eax4Props& src, Eax5Props& dst) noexcept
4012 // Source.
4014 static_cast<Eax3Props&>(dst.source) = src.source;
4015 dst.source.flMacroFXFactor = EAXSOURCE_DEFAULTMACROFXFACTOR;
4017 // Sends.
4019 dst.sends = src.sends;
4021 for(size_t i{0};i < EAX_MAX_FXSLOTS;++i)
4022 dst.sends[i].guidReceivingFXSlotID = *(eax5_fx_slot_ids[i]);
4024 // Active FX slots.
4026 auto translate_slotid = [](const GUID &src_id) -> GUID
4028 if(src_id == EAX_NULL_GUID)
4029 return EAX_NULL_GUID;
4030 if(src_id == EAX_PrimaryFXSlotID)
4031 return EAX_PrimaryFXSlotID;
4032 if(src_id == EAXPROPERTYID_EAX40_FXSlot0)
4033 return EAXPROPERTYID_EAX50_FXSlot0;
4034 if(src_id == EAXPROPERTYID_EAX40_FXSlot1)
4035 return EAXPROPERTYID_EAX50_FXSlot1;
4036 if(src_id == EAXPROPERTYID_EAX40_FXSlot2)
4037 return EAXPROPERTYID_EAX50_FXSlot2;
4038 if(src_id == EAXPROPERTYID_EAX40_FXSlot3)
4039 return EAXPROPERTYID_EAX50_FXSlot3;
4041 UNLIKELY
4042 ERR("Unexpected active FX slot ID\n");
4043 return EAX_NULL_GUID;
4045 const auto src_slots = al::span{src.active_fx_slots.guidActiveFXSlots};
4046 const auto dst_slots = al::span{dst.active_fx_slots.guidActiveFXSlots};
4047 auto dstiter = std::transform(src_slots.cbegin(), src_slots.cend(), dst_slots.begin(),
4048 translate_slotid);
4049 std::fill(dstiter, dst_slots.end(), EAX_NULL_GUID);
4051 // Speaker levels.
4053 eax5_set_speaker_levels_defaults(dst.speaker_levels);
4056 float ALsource::eax_calculate_dst_occlusion_mb(
4057 long src_occlusion_mb,
4058 float path_ratio,
4059 float lf_ratio) noexcept
4061 const auto ratio_1 = path_ratio + lf_ratio - 1.0F;
4062 const auto ratio_2 = path_ratio * lf_ratio;
4063 const auto ratio = (ratio_2 > ratio_1) ? ratio_2 : ratio_1;
4064 const auto dst_occlustion_mb = static_cast<float>(src_occlusion_mb) * ratio;
4065 return dst_occlustion_mb;
4068 EaxAlLowPassParam ALsource::eax_create_direct_filter_param() const noexcept
4070 const auto &source = mEax.source;
4072 auto gain_mb = static_cast<float>(source.lObstruction) * source.flObstructionLFRatio;
4073 auto gainhf_mb = static_cast<float>(source.lObstruction);
4075 for(size_t i{0};i < EAX_MAX_FXSLOTS;++i)
4077 if(!mEaxActiveFxSlots[i])
4078 continue;
4080 if(source.lOcclusion != 0)
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());
4087 if(is_environmental_fx && is_primary)
4089 gain_mb += eax_calculate_dst_occlusion_mb(source.lOcclusion,
4090 source.flOcclusionDirectRatio, source.flOcclusionLFRatio);
4092 gainhf_mb += static_cast<float>(source.lOcclusion) * source.flOcclusionDirectRatio;
4096 const auto& send = mEax.sends[i];
4097 if(send.lOcclusion != 0)
4099 gain_mb += eax_calculate_dst_occlusion_mb(send.lOcclusion, send.flOcclusionDirectRatio,
4100 send.flOcclusionLFRatio);
4102 gainhf_mb += static_cast<float>(send.lOcclusion) * send.flOcclusionDirectRatio;
4106 /* gainhf_mb is the absolute mBFS of the filter's high-frequency volume,
4107 * and gain_mb is the absolute mBFS of the filter's low-frequency volume.
4108 * Adjust the HF volume to be relative to the LF volume, to make the
4109 * appropriate main and relative HF filter volumes.
4111 * Also add the Direct and DirectHF properties to the filter, which are
4112 * already the main and relative HF volumes.
4114 gainhf_mb -= gain_mb - static_cast<float>(source.lDirectHF);
4115 gain_mb += static_cast<float>(source.lDirect);
4117 return EaxAlLowPassParam{level_mb_to_gain(gain_mb),
4118 std::min(level_mb_to_gain(gainhf_mb), 1.0f)};
4121 EaxAlLowPassParam ALsource::eax_create_room_filter_param(
4122 const ALeffectslot& fx_slot,
4123 const EAXSOURCEALLSENDPROPERTIES& send) const noexcept
4125 const auto& fx_slot_eax = fx_slot.eax_get_eax_fx_slot();
4126 const auto is_environmental_fx = bool{(fx_slot_eax.ulFlags & EAXFXSLOTFLAGS_ENVIRONMENT) != 0};
4127 const auto is_primary = bool{mEaxPrimaryFxSlotId.value_or(-1) == fx_slot.eax_get_index()};
4129 auto gain_mb = (static_cast<float>(fx_slot_eax.lOcclusion) * fx_slot_eax.flOcclusionLFRatio)
4130 + eax_calculate_dst_occlusion_mb(send.lOcclusion, send.flOcclusionRoomRatio,
4131 send.flOcclusionLFRatio)
4132 + (static_cast<float>(send.lExclusion) * send.flExclusionLFRatio);
4134 auto gainhf_mb = static_cast<float>(fx_slot_eax.lOcclusion)
4135 + (static_cast<float>(send.lOcclusion) * send.flOcclusionRoomRatio);
4137 if(is_environmental_fx && is_primary)
4139 const auto &source = mEax.source;
4141 gain_mb += eax_calculate_dst_occlusion_mb(source.lOcclusion, source.flOcclusionRoomRatio,
4142 source.flOcclusionLFRatio);
4143 gain_mb += static_cast<float>(source.lExclusion) * source.flExclusionLFRatio;
4145 gainhf_mb += static_cast<float>(source.lOcclusion) * source.flOcclusionRoomRatio;
4146 gainhf_mb += static_cast<float>(source.lExclusion + send.lExclusion);
4149 gainhf_mb -= gain_mb - static_cast<float>(send.lSendHF);
4150 gain_mb += static_cast<float>(send.lSend);
4151 if(is_environmental_fx)
4153 const auto &source = mEax.source;
4154 gain_mb += static_cast<float>(source.lRoom);
4155 gainhf_mb += static_cast<float>(source.lRoomHF);
4158 return EaxAlLowPassParam{level_mb_to_gain(gain_mb),
4159 std::min(level_mb_to_gain(gainhf_mb), 1.0f)};
4162 void ALsource::eax_update_direct_filter()
4164 const auto& direct_param = eax_create_direct_filter_param();
4165 Direct.Gain = direct_param.gain;
4166 Direct.GainHF = direct_param.gain_hf;
4167 Direct.HFReference = LowPassFreqRef;
4168 Direct.GainLF = 1.0f;
4169 Direct.LFReference = HighPassFreqRef;
4170 mPropsDirty = true;
4173 void ALsource::eax_update_room_filters()
4175 for(size_t i{0};i < EAX_MAX_FXSLOTS;++i)
4177 if(!mEaxActiveFxSlots[i])
4178 continue;
4180 auto& fx_slot = mEaxAlContext->eaxGetFxSlot(i);
4181 const auto& send = mEax.sends[i];
4182 const auto& room_param = eax_create_room_filter_param(fx_slot, send);
4183 eax_set_al_source_send(&fx_slot, i, room_param);
4187 void ALsource::eax_set_efx_outer_gain_hf()
4189 OuterGainHF = std::clamp(
4190 level_mb_to_gain(static_cast<float>(mEax.source.lOutsideVolumeHF)),
4191 AL_MIN_CONE_OUTER_GAINHF,
4192 AL_MAX_CONE_OUTER_GAINHF);
4195 void ALsource::eax_set_efx_doppler_factor()
4197 DopplerFactor = mEax.source.flDopplerFactor;
4200 void ALsource::eax_set_efx_rolloff_factor()
4202 RolloffFactor2 = mEax.source.flRolloffFactor;
4205 void ALsource::eax_set_efx_room_rolloff_factor()
4207 RoomRolloffFactor = mEax.source.flRoomRolloffFactor;
4210 void ALsource::eax_set_efx_air_absorption_factor()
4212 AirAbsorptionFactor = mEax.source.flAirAbsorptionFactor;
4215 void ALsource::eax_set_efx_dry_gain_hf_auto()
4217 DryGainHFAuto = ((mEax.source.ulFlags & EAXSOURCEFLAGS_DIRECTHFAUTO) != 0);
4220 void ALsource::eax_set_efx_wet_gain_auto()
4222 WetGainAuto = ((mEax.source.ulFlags & EAXSOURCEFLAGS_ROOMAUTO) != 0);
4225 void ALsource::eax_set_efx_wet_gain_hf_auto()
4227 WetGainHFAuto = ((mEax.source.ulFlags & EAXSOURCEFLAGS_ROOMHFAUTO) != 0);
4230 void ALsource::eax1_set(const EaxCall& call, Eax1Props& props)
4232 switch (call.get_property_id()) {
4233 case DSPROPERTY_EAXBUFFER_ALL:
4234 eax_defer<Eax1SourceAllValidator>(call, props);
4235 break;
4237 case DSPROPERTY_EAXBUFFER_REVERBMIX:
4238 eax_defer<Eax1SourceReverbMixValidator>(call, props.fMix);
4239 break;
4241 default:
4242 eax_fail_unknown_property_id();
4246 void ALsource::eax2_set(const EaxCall& call, Eax2Props& props)
4248 switch (call.get_property_id()) {
4249 case DSPROPERTY_EAX20BUFFER_NONE:
4250 break;
4252 case DSPROPERTY_EAX20BUFFER_ALLPARAMETERS:
4253 eax_defer<Eax2SourceAllValidator>(call, props);
4254 break;
4256 case DSPROPERTY_EAX20BUFFER_DIRECT:
4257 eax_defer<Eax2SourceDirectValidator>(call, props.lDirect);
4258 break;
4260 case DSPROPERTY_EAX20BUFFER_DIRECTHF:
4261 eax_defer<Eax2SourceDirectHfValidator>(call, props.lDirectHF);
4262 break;
4264 case DSPROPERTY_EAX20BUFFER_ROOM:
4265 eax_defer<Eax2SourceRoomValidator>(call, props.lRoom);
4266 break;
4268 case DSPROPERTY_EAX20BUFFER_ROOMHF:
4269 eax_defer<Eax2SourceRoomHfValidator>(call, props.lRoomHF);
4270 break;
4272 case DSPROPERTY_EAX20BUFFER_ROOMROLLOFFFACTOR:
4273 eax_defer<Eax2SourceRoomRolloffFactorValidator>(call, props.flRoomRolloffFactor);
4274 break;
4276 case DSPROPERTY_EAX20BUFFER_OBSTRUCTION:
4277 eax_defer<Eax2SourceObstructionValidator>(call, props.lObstruction);
4278 break;
4280 case DSPROPERTY_EAX20BUFFER_OBSTRUCTIONLFRATIO:
4281 eax_defer<Eax2SourceObstructionLfRatioValidator>(call, props.flObstructionLFRatio);
4282 break;
4284 case DSPROPERTY_EAX20BUFFER_OCCLUSION:
4285 eax_defer<Eax2SourceOcclusionValidator>(call, props.lOcclusion);
4286 break;
4288 case DSPROPERTY_EAX20BUFFER_OCCLUSIONLFRATIO:
4289 eax_defer<Eax2SourceOcclusionLfRatioValidator>(call, props.flOcclusionLFRatio);
4290 break;
4292 case DSPROPERTY_EAX20BUFFER_OCCLUSIONROOMRATIO:
4293 eax_defer<Eax2SourceOcclusionRoomRatioValidator>(call, props.flOcclusionRoomRatio);
4294 break;
4296 case DSPROPERTY_EAX20BUFFER_OUTSIDEVOLUMEHF:
4297 eax_defer<Eax2SourceOutsideVolumeHfValidator>(call, props.lOutsideVolumeHF);
4298 break;
4300 case DSPROPERTY_EAX20BUFFER_AIRABSORPTIONFACTOR:
4301 eax_defer<Eax2SourceAirAbsorptionFactorValidator>(call, props.flAirAbsorptionFactor);
4302 break;
4304 case DSPROPERTY_EAX20BUFFER_FLAGS:
4305 eax_defer<Eax2SourceFlagsValidator>(call, props.dwFlags);
4306 break;
4308 default:
4309 eax_fail_unknown_property_id();
4313 void ALsource::eax3_set(const EaxCall& call, Eax3Props& props)
4315 switch (call.get_property_id()) {
4316 case EAXSOURCE_NONE:
4317 break;
4319 case EAXSOURCE_ALLPARAMETERS:
4320 eax_defer<Eax3SourceAllValidator>(call, props);
4321 break;
4323 case EAXSOURCE_OBSTRUCTIONPARAMETERS:
4324 eax_defer_sub<Eax4ObstructionValidator, EAXOBSTRUCTIONPROPERTIES>(call, props.lObstruction);
4325 break;
4327 case EAXSOURCE_OCCLUSIONPARAMETERS:
4328 eax_defer_sub<Eax4OcclusionValidator, EAXOCCLUSIONPROPERTIES>(call, props.lOcclusion);
4329 break;
4331 case EAXSOURCE_EXCLUSIONPARAMETERS:
4332 eax_defer_sub<Eax4ExclusionValidator, EAXEXCLUSIONPROPERTIES>(call, props.lExclusion);
4333 break;
4335 case EAXSOURCE_DIRECT:
4336 eax_defer<Eax2SourceDirectValidator>(call, props.lDirect);
4337 break;
4339 case EAXSOURCE_DIRECTHF:
4340 eax_defer<Eax2SourceDirectHfValidator>(call, props.lDirectHF);
4341 break;
4343 case EAXSOURCE_ROOM:
4344 eax_defer<Eax2SourceRoomValidator>(call, props.lRoom);
4345 break;
4347 case EAXSOURCE_ROOMHF:
4348 eax_defer<Eax2SourceRoomHfValidator>(call, props.lRoomHF);
4349 break;
4351 case EAXSOURCE_OBSTRUCTION:
4352 eax_defer<Eax2SourceObstructionValidator>(call, props.lObstruction);
4353 break;
4355 case EAXSOURCE_OBSTRUCTIONLFRATIO:
4356 eax_defer<Eax2SourceObstructionLfRatioValidator>(call, props.flObstructionLFRatio);
4357 break;
4359 case EAXSOURCE_OCCLUSION:
4360 eax_defer<Eax2SourceOcclusionValidator>(call, props.lOcclusion);
4361 break;
4363 case EAXSOURCE_OCCLUSIONLFRATIO:
4364 eax_defer<Eax2SourceOcclusionLfRatioValidator>(call, props.flOcclusionLFRatio);
4365 break;
4367 case EAXSOURCE_OCCLUSIONROOMRATIO:
4368 eax_defer<Eax2SourceOcclusionRoomRatioValidator>(call, props.flOcclusionRoomRatio);
4369 break;
4371 case EAXSOURCE_OCCLUSIONDIRECTRATIO:
4372 eax_defer<Eax3SourceOcclusionDirectRatioValidator>(call, props.flOcclusionDirectRatio);
4373 break;
4375 case EAXSOURCE_EXCLUSION:
4376 eax_defer<Eax3SourceExclusionValidator>(call, props.lExclusion);
4377 break;
4379 case EAXSOURCE_EXCLUSIONLFRATIO:
4380 eax_defer<Eax3SourceExclusionLfRatioValidator>(call, props.flExclusionLFRatio);
4381 break;
4383 case EAXSOURCE_OUTSIDEVOLUMEHF:
4384 eax_defer<Eax2SourceOutsideVolumeHfValidator>(call, props.lOutsideVolumeHF);
4385 break;
4387 case EAXSOURCE_DOPPLERFACTOR:
4388 eax_defer<Eax3SourceDopplerFactorValidator>(call, props.flDopplerFactor);
4389 break;
4391 case EAXSOURCE_ROLLOFFFACTOR:
4392 eax_defer<Eax3SourceRolloffFactorValidator>(call, props.flRolloffFactor);
4393 break;
4395 case EAXSOURCE_ROOMROLLOFFFACTOR:
4396 eax_defer<Eax2SourceRoomRolloffFactorValidator>(call, props.flRoomRolloffFactor);
4397 break;
4399 case EAXSOURCE_AIRABSORPTIONFACTOR:
4400 eax_defer<Eax2SourceAirAbsorptionFactorValidator>(call, props.flAirAbsorptionFactor);
4401 break;
4403 case EAXSOURCE_FLAGS:
4404 eax_defer<Eax2SourceFlagsValidator>(call, props.ulFlags);
4405 break;
4407 default:
4408 eax_fail_unknown_property_id();
4412 void ALsource::eax4_set(const EaxCall& call, Eax4Props& props)
4414 switch (call.get_property_id()) {
4415 case EAXSOURCE_NONE:
4416 case EAXSOURCE_ALLPARAMETERS:
4417 case EAXSOURCE_OBSTRUCTIONPARAMETERS:
4418 case EAXSOURCE_OCCLUSIONPARAMETERS:
4419 case EAXSOURCE_EXCLUSIONPARAMETERS:
4420 case EAXSOURCE_DIRECT:
4421 case EAXSOURCE_DIRECTHF:
4422 case EAXSOURCE_ROOM:
4423 case EAXSOURCE_ROOMHF:
4424 case EAXSOURCE_OBSTRUCTION:
4425 case EAXSOURCE_OBSTRUCTIONLFRATIO:
4426 case EAXSOURCE_OCCLUSION:
4427 case EAXSOURCE_OCCLUSIONLFRATIO:
4428 case EAXSOURCE_OCCLUSIONROOMRATIO:
4429 case EAXSOURCE_OCCLUSIONDIRECTRATIO:
4430 case EAXSOURCE_EXCLUSION:
4431 case EAXSOURCE_EXCLUSIONLFRATIO:
4432 case EAXSOURCE_OUTSIDEVOLUMEHF:
4433 case EAXSOURCE_DOPPLERFACTOR:
4434 case EAXSOURCE_ROLLOFFFACTOR:
4435 case EAXSOURCE_ROOMROLLOFFFACTOR:
4436 case EAXSOURCE_AIRABSORPTIONFACTOR:
4437 case EAXSOURCE_FLAGS:
4438 eax3_set(call, props.source);
4439 break;
4441 case EAXSOURCE_SENDPARAMETERS:
4442 eax4_defer_sends<Eax4SendValidator, EAXSOURCESENDPROPERTIES>(call, props.sends);
4443 break;
4445 case EAXSOURCE_ALLSENDPARAMETERS:
4446 eax4_defer_sends<Eax4AllSendValidator, EAXSOURCEALLSENDPROPERTIES>(call, props.sends);
4447 break;
4449 case EAXSOURCE_OCCLUSIONSENDPARAMETERS:
4450 eax4_defer_sends<Eax4OcclusionSendValidator, EAXSOURCEOCCLUSIONSENDPROPERTIES>(call, props.sends);
4451 break;
4453 case EAXSOURCE_EXCLUSIONSENDPARAMETERS:
4454 eax4_defer_sends<Eax4ExclusionSendValidator, EAXSOURCEEXCLUSIONSENDPROPERTIES>(call, props.sends);
4455 break;
4457 case EAXSOURCE_ACTIVEFXSLOTID:
4458 eax4_defer_active_fx_slot_id(call, al::span{props.active_fx_slots.guidActiveFXSlots});
4459 break;
4461 default:
4462 eax_fail_unknown_property_id();
4466 void ALsource::eax5_defer_all_2d(const EaxCall& call, EAX50SOURCEPROPERTIES& props)
4468 const auto& src_props = call.get_value<Exception, const EAXSOURCE2DPROPERTIES>();
4469 Eax5SourceAll2dValidator{}(src_props);
4470 props.lDirect = src_props.lDirect;
4471 props.lDirectHF = src_props.lDirectHF;
4472 props.lRoom = src_props.lRoom;
4473 props.lRoomHF = src_props.lRoomHF;
4474 props.ulFlags = src_props.ulFlags;
4477 void ALsource::eax5_defer_speaker_levels(const EaxCall& call, EaxSpeakerLevels& props)
4479 const auto values = call.get_values<const EAXSPEAKERLEVELPROPERTIES>(eax_max_speakers);
4480 std::for_each(values.cbegin(), values.cend(), Eax5SpeakerAllValidator{});
4482 for (const auto& value : values) {
4483 const auto index = static_cast<size_t>(value.lSpeakerID - EAXSPEAKER_FRONT_LEFT);
4484 props[index].lLevel = value.lLevel;
4488 void ALsource::eax5_set(const EaxCall& call, Eax5Props& props)
4490 switch (call.get_property_id()) {
4491 case EAXSOURCE_NONE:
4492 break;
4494 case EAXSOURCE_ALLPARAMETERS:
4495 eax_defer<Eax5SourceAllValidator>(call, props.source);
4496 break;
4498 case EAXSOURCE_OBSTRUCTIONPARAMETERS:
4499 case EAXSOURCE_OCCLUSIONPARAMETERS:
4500 case EAXSOURCE_EXCLUSIONPARAMETERS:
4501 case EAXSOURCE_DIRECT:
4502 case EAXSOURCE_DIRECTHF:
4503 case EAXSOURCE_ROOM:
4504 case EAXSOURCE_ROOMHF:
4505 case EAXSOURCE_OBSTRUCTION:
4506 case EAXSOURCE_OBSTRUCTIONLFRATIO:
4507 case EAXSOURCE_OCCLUSION:
4508 case EAXSOURCE_OCCLUSIONLFRATIO:
4509 case EAXSOURCE_OCCLUSIONROOMRATIO:
4510 case EAXSOURCE_OCCLUSIONDIRECTRATIO:
4511 case EAXSOURCE_EXCLUSION:
4512 case EAXSOURCE_EXCLUSIONLFRATIO:
4513 case EAXSOURCE_OUTSIDEVOLUMEHF:
4514 case EAXSOURCE_DOPPLERFACTOR:
4515 case EAXSOURCE_ROLLOFFFACTOR:
4516 case EAXSOURCE_ROOMROLLOFFFACTOR:
4517 case EAXSOURCE_AIRABSORPTIONFACTOR:
4518 eax3_set(call, props.source);
4519 break;
4521 case EAXSOURCE_FLAGS:
4522 eax_defer<Eax5SourceFlagsValidator>(call, props.source.ulFlags);
4523 break;
4525 case EAXSOURCE_SENDPARAMETERS:
4526 eax5_defer_sends<Eax5SendValidator, EAXSOURCESENDPROPERTIES>(call, props.sends);
4527 break;
4529 case EAXSOURCE_ALLSENDPARAMETERS:
4530 eax5_defer_sends<Eax5AllSendValidator, EAXSOURCEALLSENDPROPERTIES>(call, props.sends);
4531 break;
4533 case EAXSOURCE_OCCLUSIONSENDPARAMETERS:
4534 eax5_defer_sends<Eax5OcclusionSendValidator, EAXSOURCEOCCLUSIONSENDPROPERTIES>(call, props.sends);
4535 break;
4537 case EAXSOURCE_EXCLUSIONSENDPARAMETERS:
4538 eax5_defer_sends<Eax5ExclusionSendValidator, EAXSOURCEEXCLUSIONSENDPROPERTIES>(call, props.sends);
4539 break;
4541 case EAXSOURCE_ACTIVEFXSLOTID:
4542 eax5_defer_active_fx_slot_id(call, al::span{props.active_fx_slots.guidActiveFXSlots});
4543 break;
4545 case EAXSOURCE_MACROFXFACTOR:
4546 eax_defer<Eax5SourceMacroFXFactorValidator>(call, props.source.flMacroFXFactor);
4547 break;
4549 case EAXSOURCE_SPEAKERLEVELS:
4550 eax5_defer_speaker_levels(call, props.speaker_levels);
4551 break;
4553 case EAXSOURCE_ALL2DPARAMETERS:
4554 eax5_defer_all_2d(call, props.source);
4555 break;
4557 default:
4558 eax_fail_unknown_property_id();
4562 void ALsource::eax_set(const EaxCall& call)
4564 const auto eax_version = call.get_version();
4565 switch(eax_version)
4567 case 1: eax1_set(call, mEax1.d); break;
4568 case 2: eax2_set(call, mEax2.d); break;
4569 case 3: eax3_set(call, mEax3.d); break;
4570 case 4: eax4_set(call, mEax4.d); break;
4571 case 5: eax5_set(call, mEax5.d); break;
4572 default: eax_fail_unknown_property_id();
4574 mEaxChanged = true;
4575 mEaxVersion = eax_version;
4578 void ALsource::eax_get_active_fx_slot_id(const EaxCall& call, const al::span<const GUID> src_ids)
4580 assert(src_ids.size()==EAX40_MAX_ACTIVE_FXSLOTS || src_ids.size()==EAX50_MAX_ACTIVE_FXSLOTS);
4581 const auto dst_ids = call.get_values<GUID>(src_ids.size());
4582 std::uninitialized_copy_n(src_ids.begin(), dst_ids.size(), dst_ids.begin());
4585 void ALsource::eax1_get(const EaxCall& call, const Eax1Props& props)
4587 switch (call.get_property_id()) {
4588 case DSPROPERTY_EAXBUFFER_ALL:
4589 case DSPROPERTY_EAXBUFFER_REVERBMIX:
4590 call.set_value<Exception>(props.fMix);
4591 break;
4593 default:
4594 eax_fail_unknown_property_id();
4598 void ALsource::eax2_get(const EaxCall& call, const Eax2Props& props)
4600 switch (call.get_property_id()) {
4601 case DSPROPERTY_EAX20BUFFER_NONE:
4602 break;
4604 case DSPROPERTY_EAX20BUFFER_ALLPARAMETERS:
4605 call.set_value<Exception>(props);
4606 break;
4608 case DSPROPERTY_EAX20BUFFER_DIRECT:
4609 call.set_value<Exception>(props.lDirect);
4610 break;
4612 case DSPROPERTY_EAX20BUFFER_DIRECTHF:
4613 call.set_value<Exception>(props.lDirectHF);
4614 break;
4616 case DSPROPERTY_EAX20BUFFER_ROOM:
4617 call.set_value<Exception>(props.lRoom);
4618 break;
4620 case DSPROPERTY_EAX20BUFFER_ROOMHF:
4621 call.set_value<Exception>(props.lRoomHF);
4622 break;
4624 case DSPROPERTY_EAX20BUFFER_ROOMROLLOFFFACTOR:
4625 call.set_value<Exception>(props.flRoomRolloffFactor);
4626 break;
4628 case DSPROPERTY_EAX20BUFFER_OBSTRUCTION:
4629 call.set_value<Exception>(props.lObstruction);
4630 break;
4632 case DSPROPERTY_EAX20BUFFER_OBSTRUCTIONLFRATIO:
4633 call.set_value<Exception>(props.flObstructionLFRatio);
4634 break;
4636 case DSPROPERTY_EAX20BUFFER_OCCLUSION:
4637 call.set_value<Exception>(props.lOcclusion);
4638 break;
4640 case DSPROPERTY_EAX20BUFFER_OCCLUSIONLFRATIO:
4641 call.set_value<Exception>(props.flOcclusionLFRatio);
4642 break;
4644 case DSPROPERTY_EAX20BUFFER_OCCLUSIONROOMRATIO:
4645 call.set_value<Exception>(props.flOcclusionRoomRatio);
4646 break;
4648 case DSPROPERTY_EAX20BUFFER_OUTSIDEVOLUMEHF:
4649 call.set_value<Exception>(props.lOutsideVolumeHF);
4650 break;
4652 case DSPROPERTY_EAX20BUFFER_AIRABSORPTIONFACTOR:
4653 call.set_value<Exception>(props.flAirAbsorptionFactor);
4654 break;
4656 case DSPROPERTY_EAX20BUFFER_FLAGS:
4657 call.set_value<Exception>(props.dwFlags);
4658 break;
4660 default:
4661 eax_fail_unknown_property_id();
4665 void ALsource::eax3_get_obstruction(const EaxCall& call, const Eax3Props& props)
4667 const auto& subprops = reinterpret_cast<const EAXOBSTRUCTIONPROPERTIES&>(props.lObstruction);
4668 call.set_value<Exception>(subprops);
4671 void ALsource::eax3_get_occlusion(const EaxCall& call, const Eax3Props& props)
4673 const auto& subprops = reinterpret_cast<const EAXOCCLUSIONPROPERTIES&>(props.lOcclusion);
4674 call.set_value<Exception>(subprops);
4677 void ALsource::eax3_get_exclusion(const EaxCall& call, const Eax3Props& props)
4679 const auto& subprops = reinterpret_cast<const EAXEXCLUSIONPROPERTIES&>(props.lExclusion);
4680 call.set_value<Exception>(subprops);
4683 void ALsource::eax3_get(const EaxCall& call, const Eax3Props& props)
4685 switch (call.get_property_id()) {
4686 case EAXSOURCE_NONE:
4687 break;
4689 case EAXSOURCE_ALLPARAMETERS:
4690 call.set_value<Exception>(props);
4691 break;
4693 case EAXSOURCE_OBSTRUCTIONPARAMETERS:
4694 eax3_get_obstruction(call, props);
4695 break;
4697 case EAXSOURCE_OCCLUSIONPARAMETERS:
4698 eax3_get_occlusion(call, props);
4699 break;
4701 case EAXSOURCE_EXCLUSIONPARAMETERS:
4702 eax3_get_exclusion(call, props);
4703 break;
4705 case EAXSOURCE_DIRECT:
4706 call.set_value<Exception>(props.lDirect);
4707 break;
4709 case EAXSOURCE_DIRECTHF:
4710 call.set_value<Exception>(props.lDirectHF);
4711 break;
4713 case EAXSOURCE_ROOM:
4714 call.set_value<Exception>(props.lRoom);
4715 break;
4717 case EAXSOURCE_ROOMHF:
4718 call.set_value<Exception>(props.lRoomHF);
4719 break;
4721 case EAXSOURCE_OBSTRUCTION:
4722 call.set_value<Exception>(props.lObstruction);
4723 break;
4725 case EAXSOURCE_OBSTRUCTIONLFRATIO:
4726 call.set_value<Exception>(props.flObstructionLFRatio);
4727 break;
4729 case EAXSOURCE_OCCLUSION:
4730 call.set_value<Exception>(props.lOcclusion);
4731 break;
4733 case EAXSOURCE_OCCLUSIONLFRATIO:
4734 call.set_value<Exception>(props.flOcclusionLFRatio);
4735 break;
4737 case EAXSOURCE_OCCLUSIONROOMRATIO:
4738 call.set_value<Exception>(props.flOcclusionRoomRatio);
4739 break;
4741 case EAXSOURCE_OCCLUSIONDIRECTRATIO:
4742 call.set_value<Exception>(props.flOcclusionDirectRatio);
4743 break;
4745 case EAXSOURCE_EXCLUSION:
4746 call.set_value<Exception>(props.lExclusion);
4747 break;
4749 case EAXSOURCE_EXCLUSIONLFRATIO:
4750 call.set_value<Exception>(props.flExclusionLFRatio);
4751 break;
4753 case EAXSOURCE_OUTSIDEVOLUMEHF:
4754 call.set_value<Exception>(props.lOutsideVolumeHF);
4755 break;
4757 case EAXSOURCE_DOPPLERFACTOR:
4758 call.set_value<Exception>(props.flDopplerFactor);
4759 break;
4761 case EAXSOURCE_ROLLOFFFACTOR:
4762 call.set_value<Exception>(props.flRolloffFactor);
4763 break;
4765 case EAXSOURCE_ROOMROLLOFFFACTOR:
4766 call.set_value<Exception>(props.flRoomRolloffFactor);
4767 break;
4769 case EAXSOURCE_AIRABSORPTIONFACTOR:
4770 call.set_value<Exception>(props.flAirAbsorptionFactor);
4771 break;
4773 case EAXSOURCE_FLAGS:
4774 call.set_value<Exception>(props.ulFlags);
4775 break;
4777 default:
4778 eax_fail_unknown_property_id();
4782 void ALsource::eax4_get(const EaxCall& call, const Eax4Props& props)
4784 switch (call.get_property_id()) {
4785 case EAXSOURCE_NONE:
4786 break;
4788 case EAXSOURCE_ALLPARAMETERS:
4789 case EAXSOURCE_OBSTRUCTIONPARAMETERS:
4790 case EAXSOURCE_OCCLUSIONPARAMETERS:
4791 case EAXSOURCE_EXCLUSIONPARAMETERS:
4792 case EAXSOURCE_DIRECT:
4793 case EAXSOURCE_DIRECTHF:
4794 case EAXSOURCE_ROOM:
4795 case EAXSOURCE_ROOMHF:
4796 case EAXSOURCE_OBSTRUCTION:
4797 case EAXSOURCE_OBSTRUCTIONLFRATIO:
4798 case EAXSOURCE_OCCLUSION:
4799 case EAXSOURCE_OCCLUSIONLFRATIO:
4800 case EAXSOURCE_OCCLUSIONROOMRATIO:
4801 case EAXSOURCE_OCCLUSIONDIRECTRATIO:
4802 case EAXSOURCE_EXCLUSION:
4803 case EAXSOURCE_EXCLUSIONLFRATIO:
4804 case EAXSOURCE_OUTSIDEVOLUMEHF:
4805 case EAXSOURCE_DOPPLERFACTOR:
4806 case EAXSOURCE_ROLLOFFFACTOR:
4807 case EAXSOURCE_ROOMROLLOFFFACTOR:
4808 case EAXSOURCE_AIRABSORPTIONFACTOR:
4809 case EAXSOURCE_FLAGS:
4810 eax3_get(call, props.source);
4811 break;
4813 case EAXSOURCE_SENDPARAMETERS:
4814 eax_get_sends<EAXSOURCESENDPROPERTIES>(call, props.sends);
4815 break;
4817 case EAXSOURCE_ALLSENDPARAMETERS:
4818 eax_get_sends<EAXSOURCEALLSENDPROPERTIES>(call, props.sends);
4819 break;
4821 case EAXSOURCE_OCCLUSIONSENDPARAMETERS:
4822 eax_get_sends<EAXSOURCEOCCLUSIONSENDPROPERTIES>(call, props.sends);
4823 break;
4825 case EAXSOURCE_EXCLUSIONSENDPARAMETERS:
4826 eax_get_sends<EAXSOURCEEXCLUSIONSENDPROPERTIES>(call, props.sends);
4827 break;
4829 case EAXSOURCE_ACTIVEFXSLOTID:
4830 eax_get_active_fx_slot_id(call, props.active_fx_slots.guidActiveFXSlots);
4831 break;
4833 default:
4834 eax_fail_unknown_property_id();
4838 void ALsource::eax5_get_all_2d(const EaxCall& call, const EAX50SOURCEPROPERTIES& props)
4840 auto& subprops = call.get_value<Exception, EAXSOURCE2DPROPERTIES>();
4841 subprops.lDirect = props.lDirect;
4842 subprops.lDirectHF = props.lDirectHF;
4843 subprops.lRoom = props.lRoom;
4844 subprops.lRoomHF = props.lRoomHF;
4845 subprops.ulFlags = props.ulFlags;
4848 void ALsource::eax5_get_speaker_levels(const EaxCall& call, const EaxSpeakerLevels& props)
4850 const auto subprops = call.get_values<EAXSPEAKERLEVELPROPERTIES>(eax_max_speakers);
4851 std::uninitialized_copy_n(props.cbegin(), subprops.size(), subprops.begin());
4854 void ALsource::eax5_get(const EaxCall& call, const Eax5Props& props)
4856 switch (call.get_property_id()) {
4857 case EAXSOURCE_NONE:
4858 break;
4860 case EAXSOURCE_ALLPARAMETERS:
4861 case EAXSOURCE_OBSTRUCTIONPARAMETERS:
4862 case EAXSOURCE_OCCLUSIONPARAMETERS:
4863 case EAXSOURCE_EXCLUSIONPARAMETERS:
4864 case EAXSOURCE_DIRECT:
4865 case EAXSOURCE_DIRECTHF:
4866 case EAXSOURCE_ROOM:
4867 case EAXSOURCE_ROOMHF:
4868 case EAXSOURCE_OBSTRUCTION:
4869 case EAXSOURCE_OBSTRUCTIONLFRATIO:
4870 case EAXSOURCE_OCCLUSION:
4871 case EAXSOURCE_OCCLUSIONLFRATIO:
4872 case EAXSOURCE_OCCLUSIONROOMRATIO:
4873 case EAXSOURCE_OCCLUSIONDIRECTRATIO:
4874 case EAXSOURCE_EXCLUSION:
4875 case EAXSOURCE_EXCLUSIONLFRATIO:
4876 case EAXSOURCE_OUTSIDEVOLUMEHF:
4877 case EAXSOURCE_DOPPLERFACTOR:
4878 case EAXSOURCE_ROLLOFFFACTOR:
4879 case EAXSOURCE_ROOMROLLOFFFACTOR:
4880 case EAXSOURCE_AIRABSORPTIONFACTOR:
4881 case EAXSOURCE_FLAGS:
4882 eax3_get(call, props.source);
4883 break;
4885 case EAXSOURCE_SENDPARAMETERS:
4886 eax_get_sends<EAXSOURCESENDPROPERTIES>(call, props.sends);
4887 break;
4889 case EAXSOURCE_ALLSENDPARAMETERS:
4890 eax_get_sends<EAXSOURCEALLSENDPROPERTIES>(call, props.sends);
4891 break;
4893 case EAXSOURCE_OCCLUSIONSENDPARAMETERS:
4894 eax_get_sends<EAXSOURCEOCCLUSIONSENDPROPERTIES>(call, props.sends);
4895 break;
4897 case EAXSOURCE_EXCLUSIONSENDPARAMETERS:
4898 eax_get_sends<EAXSOURCEEXCLUSIONSENDPROPERTIES>(call, props.sends);
4899 break;
4901 case EAXSOURCE_ACTIVEFXSLOTID:
4902 eax_get_active_fx_slot_id(call, props.active_fx_slots.guidActiveFXSlots);
4903 break;
4905 case EAXSOURCE_MACROFXFACTOR:
4906 call.set_value<Exception>(props.source.flMacroFXFactor);
4907 break;
4909 case EAXSOURCE_SPEAKERLEVELS:
4910 call.set_value<Exception>(props.speaker_levels);
4911 break;
4913 case EAXSOURCE_ALL2DPARAMETERS:
4914 eax5_get_all_2d(call, props.source);
4915 break;
4917 default:
4918 eax_fail_unknown_property_id();
4922 void ALsource::eax_get(const EaxCall& call)
4924 switch (call.get_version()) {
4925 case 1: eax1_get(call, mEax1.i); break;
4926 case 2: eax2_get(call, mEax2.i); break;
4927 case 3: eax3_get(call, mEax3.i); break;
4928 case 4: eax4_get(call, mEax4.i); break;
4929 case 5: eax5_get(call, mEax5.i); break;
4930 default: eax_fail_unknown_version();
4934 void ALsource::eax_set_al_source_send(ALeffectslot *slot, size_t sendidx, const EaxAlLowPassParam &filter)
4936 if(sendidx >= EAX_MAX_FXSLOTS)
4937 return;
4939 auto &send = Send[sendidx];
4940 send.Gain = filter.gain;
4941 send.GainHF = filter.gain_hf;
4942 send.HFReference = LowPassFreqRef;
4943 send.GainLF = 1.0f;
4944 send.LFReference = HighPassFreqRef;
4946 if(slot != nullptr)
4947 IncrementRef(slot->ref);
4948 if(auto *oldslot = send.Slot)
4949 DecrementRef(oldslot->ref);
4951 send.Slot = slot;
4952 mPropsDirty = true;
4955 void ALsource::eax_commit_active_fx_slots()
4957 // Clear all slots to an inactive state.
4958 mEaxActiveFxSlots.fill(false);
4960 // Mark the set slots as active.
4961 for(const auto& slot_id : mEax.active_fx_slots.guidActiveFXSlots)
4963 if(slot_id == EAX_NULL_GUID)
4966 else if(slot_id == EAX_PrimaryFXSlotID)
4968 // Mark primary FX slot as active.
4969 if(mEaxPrimaryFxSlotId.has_value())
4970 mEaxActiveFxSlots[*mEaxPrimaryFxSlotId] = true;
4972 else if(slot_id == EAXPROPERTYID_EAX50_FXSlot0)
4973 mEaxActiveFxSlots[0] = true;
4974 else if(slot_id == EAXPROPERTYID_EAX50_FXSlot1)
4975 mEaxActiveFxSlots[1] = true;
4976 else if(slot_id == EAXPROPERTYID_EAX50_FXSlot2)
4977 mEaxActiveFxSlots[2] = true;
4978 else if(slot_id == EAXPROPERTYID_EAX50_FXSlot3)
4979 mEaxActiveFxSlots[3] = true;
4982 // Deactivate EFX auxiliary effect slots for inactive slots. Active slots
4983 // will be updated with the room filters.
4984 for(size_t i{0};i < EAX_MAX_FXSLOTS;++i)
4986 if(!mEaxActiveFxSlots[i])
4987 eax_set_al_source_send(nullptr, i, EaxAlLowPassParam{1.0f, 1.0f});
4991 void ALsource::eax_commit_filters()
4993 eax_update_direct_filter();
4994 eax_update_room_filters();
4997 void ALsource::eaxCommit()
4999 const auto primary_fx_slot_id = mEaxAlContext->eaxGetPrimaryFxSlotIndex();
5000 const auto is_primary_fx_slot_id_changed = (mEaxPrimaryFxSlotId != primary_fx_slot_id);
5002 if(!mEaxChanged && !is_primary_fx_slot_id_changed)
5003 return;
5005 mEaxPrimaryFxSlotId = primary_fx_slot_id;
5006 mEaxChanged = false;
5008 switch(mEaxVersion)
5010 case 1:
5011 mEax1.i = mEax1.d;
5012 eax1_translate(mEax1.i, mEax);
5013 break;
5014 case 2:
5015 mEax2.i = mEax2.d;
5016 eax2_translate(mEax2.i, mEax);
5017 break;
5018 case 3:
5019 mEax3.i = mEax3.d;
5020 eax3_translate(mEax3.i, mEax);
5021 break;
5022 case 4:
5023 mEax4.i = mEax4.d;
5024 eax4_translate(mEax4.i, mEax);
5025 break;
5026 case 5:
5027 mEax5.i = mEax5.d;
5028 mEax = mEax5.d;
5029 break;
5032 eax_set_efx_outer_gain_hf();
5033 eax_set_efx_doppler_factor();
5034 eax_set_efx_rolloff_factor();
5035 eax_set_efx_room_rolloff_factor();
5036 eax_set_efx_air_absorption_factor();
5037 eax_set_efx_dry_gain_hf_auto();
5038 eax_set_efx_wet_gain_auto();
5039 eax_set_efx_wet_gain_hf_auto();
5041 eax_commit_active_fx_slots();
5042 eax_commit_filters();
5045 #endif // ALSOFT_EAX