Allow using a negative offset with callback buffers
[openal-soft.git] / al / source.cpp
blobcba33862e8fac59a42076303c27c539c775a0aeb
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 <cassert>
29 #include <chrono>
30 #include <climits>
31 #include <cmath>
32 #include <cstdint>
33 #include <functional>
34 #include <inttypes.h>
35 #include <iterator>
36 #include <limits>
37 #include <memory>
38 #include <mutex>
39 #include <new>
40 #include <numeric>
41 #include <stdexcept>
42 #include <thread>
43 #include <utility>
45 #include "AL/al.h"
46 #include "AL/alc.h"
47 #include "AL/alext.h"
48 #include "AL/efx.h"
50 #include "albit.h"
51 #include "alc/alu.h"
52 #include "alc/backends/base.h"
53 #include "alc/context.h"
54 #include "alc/device.h"
55 #include "alc/inprogext.h"
56 #include "almalloc.h"
57 #include "alnumeric.h"
58 #include "aloptional.h"
59 #include "alspan.h"
60 #include "atomic.h"
61 #include "auxeffectslot.h"
62 #include "buffer.h"
63 #include "core/ambidefs.h"
64 #include "core/bformatdec.h"
65 #include "core/except.h"
66 #include "core/filters/nfc.h"
67 #include "core/filters/splitter.h"
68 #include "core/logging.h"
69 #include "core/voice_change.h"
70 #include "event.h"
71 #include "filter.h"
72 #include "opthelpers.h"
73 #include "ringbuffer.h"
74 #include "threads.h"
76 #ifdef ALSOFT_EAX
77 #include <cassert>
78 #endif // ALSOFT_EAX
80 bool sBufferSubDataCompat{false};
82 namespace {
84 using namespace std::placeholders;
85 using std::chrono::nanoseconds;
87 Voice *GetSourceVoice(ALsource *source, ALCcontext *context)
89 auto voicelist = context->getVoicesSpan();
90 ALuint idx{source->VoiceIdx};
91 if(idx < voicelist.size())
93 ALuint sid{source->id};
94 Voice *voice = voicelist[idx];
95 if(voice->mSourceID.load(std::memory_order_acquire) == sid)
96 return voice;
98 source->VoiceIdx = INVALID_VOICE_IDX;
99 return nullptr;
103 void UpdateSourceProps(const ALsource *source, Voice *voice, ALCcontext *context)
105 /* Get an unused property container, or allocate a new one as needed. */
106 VoicePropsItem *props{context->mFreeVoiceProps.load(std::memory_order_acquire)};
107 if(!props)
109 context->allocVoiceProps();
110 props = context->mFreeVoiceProps.load(std::memory_order_acquire);
112 VoicePropsItem *next;
113 do {
114 next = props->next.load(std::memory_order_relaxed);
115 } while(context->mFreeVoiceProps.compare_exchange_weak(props, next,
116 std::memory_order_acq_rel, std::memory_order_acquire) == false);
118 props->Pitch = source->Pitch;
119 props->Gain = source->Gain;
120 props->OuterGain = source->OuterGain;
121 props->MinGain = source->MinGain;
122 props->MaxGain = source->MaxGain;
123 props->InnerAngle = source->InnerAngle;
124 props->OuterAngle = source->OuterAngle;
125 props->RefDistance = source->RefDistance;
126 props->MaxDistance = source->MaxDistance;
127 props->RolloffFactor = source->RolloffFactor
128 #ifdef ALSOFT_EAX
129 + source->RolloffFactor2
130 #endif
132 props->Position = source->Position;
133 props->Velocity = source->Velocity;
134 props->Direction = source->Direction;
135 props->OrientAt = source->OrientAt;
136 props->OrientUp = source->OrientUp;
137 props->HeadRelative = source->HeadRelative;
138 props->mDistanceModel = source->mDistanceModel;
139 props->mResampler = source->mResampler;
140 props->DirectChannels = source->DirectChannels;
141 props->mSpatializeMode = source->mSpatialize;
143 props->DryGainHFAuto = source->DryGainHFAuto;
144 props->WetGainAuto = source->WetGainAuto;
145 props->WetGainHFAuto = source->WetGainHFAuto;
146 props->OuterGainHF = source->OuterGainHF;
148 props->AirAbsorptionFactor = source->AirAbsorptionFactor;
149 props->RoomRolloffFactor = source->RoomRolloffFactor;
150 props->DopplerFactor = source->DopplerFactor;
152 props->StereoPan = source->StereoPan;
154 props->Radius = source->Radius;
155 props->EnhWidth = source->EnhWidth;
157 props->Direct.Gain = source->Direct.Gain;
158 props->Direct.GainHF = source->Direct.GainHF;
159 props->Direct.HFReference = source->Direct.HFReference;
160 props->Direct.GainLF = source->Direct.GainLF;
161 props->Direct.LFReference = source->Direct.LFReference;
163 auto copy_send = [](const ALsource::SendData &srcsend) noexcept -> VoiceProps::SendData
165 VoiceProps::SendData ret{};
166 ret.Slot = srcsend.Slot ? srcsend.Slot->mSlot : nullptr;
167 ret.Gain = srcsend.Gain;
168 ret.GainHF = srcsend.GainHF;
169 ret.HFReference = srcsend.HFReference;
170 ret.GainLF = srcsend.GainLF;
171 ret.LFReference = srcsend.LFReference;
172 return ret;
174 std::transform(source->Send.cbegin(), source->Send.cend(), props->Send, copy_send);
175 if(!props->Send[0].Slot && context->mDefaultSlot)
176 props->Send[0].Slot = context->mDefaultSlot->mSlot;
178 /* Set the new container for updating internal parameters. */
179 props = voice->mUpdate.exchange(props, std::memory_order_acq_rel);
180 if(props)
182 /* If there was an unused update container, put it back in the
183 * freelist.
185 AtomicReplaceHead(context->mFreeVoiceProps, props);
189 /* GetSourceSampleOffset
191 * Gets the current read offset for the given Source, in 32.32 fixed-point
192 * samples. The offset is relative to the start of the queue (not the start of
193 * the current buffer).
195 int64_t GetSourceSampleOffset(ALsource *Source, ALCcontext *context, nanoseconds *clocktime)
197 ALCdevice *device{context->mALDevice.get()};
198 const VoiceBufferItem *Current{};
199 int64_t readPos{};
200 uint refcount{};
201 Voice *voice{};
203 do {
204 refcount = device->waitForMix();
205 *clocktime = GetDeviceClockTime(device);
206 voice = GetSourceVoice(Source, context);
207 if(voice)
209 Current = voice->mCurrentBuffer.load(std::memory_order_relaxed);
211 readPos = int64_t{voice->mPosition.load(std::memory_order_relaxed)} << MixerFracBits;
212 readPos += voice->mPositionFrac.load(std::memory_order_relaxed);
214 std::atomic_thread_fence(std::memory_order_acquire);
215 } while(refcount != device->MixCount.load(std::memory_order_relaxed));
217 if(!voice)
218 return 0;
220 for(auto &item : Source->mQueue)
222 if(&item == Current) break;
223 readPos += int64_t{item.mSampleLen} << MixerFracBits;
225 if(readPos > std::numeric_limits<int64_t>::max() >> (32-MixerFracBits))
226 return std::numeric_limits<int64_t>::max();
227 return readPos << (32-MixerFracBits);
230 /* GetSourceSecOffset
232 * Gets the current read offset for the given Source, in seconds. The offset is
233 * relative to the start of the queue (not the start of the current buffer).
235 double GetSourceSecOffset(ALsource *Source, ALCcontext *context, nanoseconds *clocktime)
237 ALCdevice *device{context->mALDevice.get()};
238 const VoiceBufferItem *Current{};
239 int64_t readPos{};
240 uint refcount{};
241 Voice *voice{};
243 do {
244 refcount = device->waitForMix();
245 *clocktime = GetDeviceClockTime(device);
246 voice = GetSourceVoice(Source, context);
247 if(voice)
249 Current = voice->mCurrentBuffer.load(std::memory_order_relaxed);
251 readPos = int64_t{voice->mPosition.load(std::memory_order_relaxed)} << MixerFracBits;
252 readPos += voice->mPositionFrac.load(std::memory_order_relaxed);
254 std::atomic_thread_fence(std::memory_order_acquire);
255 } while(refcount != device->MixCount.load(std::memory_order_relaxed));
257 if(!voice)
258 return 0.0f;
260 const ALbuffer *BufferFmt{nullptr};
261 auto BufferList = Source->mQueue.cbegin();
262 while(BufferList != Source->mQueue.cend() && al::to_address(BufferList) != Current)
264 if(!BufferFmt) BufferFmt = BufferList->mBuffer;
265 readPos += int64_t{BufferList->mSampleLen} << MixerFracBits;
266 ++BufferList;
268 while(BufferList != Source->mQueue.cend() && !BufferFmt)
270 BufferFmt = BufferList->mBuffer;
271 ++BufferList;
273 ASSUME(BufferFmt != nullptr);
275 return static_cast<double>(readPos) / double{MixerFracOne} / BufferFmt->mSampleRate;
278 /* GetSourceOffset
280 * Gets the current read offset for the given Source, in the appropriate format
281 * (Bytes, Samples or Seconds). The offset is relative to the start of the
282 * queue (not the start of the current buffer).
284 double GetSourceOffset(ALsource *Source, ALenum name, ALCcontext *context)
286 ALCdevice *device{context->mALDevice.get()};
287 const VoiceBufferItem *Current{};
288 int64_t readPos{};
289 uint readPosFrac{};
290 uint refcount;
291 Voice *voice;
293 do {
294 refcount = device->waitForMix();
295 voice = GetSourceVoice(Source, context);
296 if(voice)
298 Current = voice->mCurrentBuffer.load(std::memory_order_relaxed);
300 readPos = voice->mPosition.load(std::memory_order_relaxed);
301 readPosFrac = voice->mPositionFrac.load(std::memory_order_relaxed);
303 std::atomic_thread_fence(std::memory_order_acquire);
304 } while(refcount != device->MixCount.load(std::memory_order_relaxed));
306 if(!voice)
307 return 0.0;
309 const ALbuffer *BufferFmt{nullptr};
310 auto BufferList = Source->mQueue.cbegin();
311 while(BufferList != Source->mQueue.cend() && al::to_address(BufferList) != Current)
313 if(!BufferFmt) BufferFmt = BufferList->mBuffer;
314 readPos += BufferList->mSampleLen;
315 ++BufferList;
317 while(BufferList != Source->mQueue.cend() && !BufferFmt)
319 BufferFmt = BufferList->mBuffer;
320 ++BufferList;
322 ASSUME(BufferFmt != nullptr);
324 double offset{};
325 switch(name)
327 case AL_SEC_OFFSET:
328 offset = static_cast<double>(readPos) + readPosFrac/double{MixerFracOne};
329 offset /= BufferFmt->mSampleRate;
330 break;
332 case AL_SAMPLE_OFFSET:
333 offset = static_cast<double>(readPos) + readPosFrac/double{MixerFracOne};
334 break;
336 case AL_BYTE_OFFSET:
337 const ALuint BlockSamples{BufferFmt->mBlockAlign};
338 const ALuint BlockSize{BufferFmt->blockSizeFromFmt()};
340 /* Round down to the block boundary. */
341 offset = static_cast<double>(readPos / BlockSamples) * BlockSize;
342 break;
344 return offset;
347 /* GetSourceLength
349 * Gets the length of the given Source's buffer queue, in the appropriate
350 * format (Bytes, Samples or Seconds).
352 double GetSourceLength(const ALsource *source, ALenum name)
354 uint64_t length{0};
355 const ALbuffer *BufferFmt{nullptr};
356 for(auto &listitem : source->mQueue)
358 if(!BufferFmt)
359 BufferFmt = listitem.mBuffer;
360 length += listitem.mSampleLen;
362 if(length == 0)
363 return 0.0;
365 ASSUME(BufferFmt != nullptr);
366 switch(name)
368 case AL_SEC_LENGTH_SOFT:
369 return static_cast<double>(length) / BufferFmt->mSampleRate;
371 case AL_SAMPLE_LENGTH_SOFT:
372 return static_cast<double>(length);
374 case AL_BYTE_LENGTH_SOFT:
375 const ALuint BlockSamples{BufferFmt->mBlockAlign};
376 const ALuint BlockSize{BufferFmt->blockSizeFromFmt()};
378 /* Round down to the block boundary. */
379 return static_cast<double>(length / BlockSamples) * BlockSize;
381 return 0.0;
385 struct VoicePos {
386 int pos;
387 uint frac;
388 ALbufferQueueItem *bufferitem;
392 * GetSampleOffset
394 * Retrieves the voice position, fixed-point fraction, and bufferlist item
395 * using the givem offset type and offset. If the offset is out of range,
396 * returns an empty optional.
398 al::optional<VoicePos> GetSampleOffset(al::deque<ALbufferQueueItem> &BufferList, ALenum OffsetType,
399 double Offset)
401 /* Find the first valid Buffer in the Queue */
402 const ALbuffer *BufferFmt{nullptr};
403 for(auto &item : BufferList)
405 BufferFmt = item.mBuffer;
406 if(BufferFmt) break;
408 if(!BufferFmt) UNLIKELY
409 return al::nullopt;
411 /* Get sample frame offset */
412 int64_t offset{};
413 uint frac{};
414 double dbloff, dblfrac;
415 switch(OffsetType)
417 case AL_SEC_OFFSET:
418 dblfrac = std::modf(Offset*BufferFmt->mSampleRate, &dbloff);
419 if(dblfrac < 0.0)
421 /* If there's a negative fraction, reduce the offset to "floor" it,
422 * and convert the fraction to a percentage to the next value (e.g.
423 * -2.75 -> -3 + 0.25).
425 dbloff -= 1.0;
426 dblfrac += 1.0;
428 offset = static_cast<int64_t>(dbloff);
429 frac = static_cast<uint>(mind(dblfrac*MixerFracOne, MixerFracOne-1.0));
430 break;
432 case AL_SAMPLE_OFFSET:
433 dblfrac = std::modf(Offset, &dbloff);
434 if(dblfrac < 0.0)
436 dbloff -= 1.0;
437 dblfrac += 1.0;
439 offset = static_cast<int64_t>(dbloff);
440 frac = static_cast<uint>(mind(dblfrac*MixerFracOne, MixerFracOne-1.0));
441 break;
443 case AL_BYTE_OFFSET:
444 /* Determine the ByteOffset (and ensure it is block aligned) */
445 Offset = std::floor(Offset / BufferFmt->blockSizeFromFmt());
446 offset = static_cast<int64_t>(Offset) * BufferFmt->mBlockAlign;
447 frac = 0;
448 break;
451 /* Find the bufferlist item this offset belongs to. */
452 if(offset < 0)
454 if(offset < std::numeric_limits<int>::min())
455 return al::nullopt;
456 return VoicePos{static_cast<int>(offset), frac, &BufferList.front()};
459 if(BufferFmt->mCallback)
460 return al::nullopt;
462 int64_t totalBufferLen{0};
463 for(auto &item : BufferList)
465 if(totalBufferLen > offset)
466 break;
467 if(item.mSampleLen > offset-totalBufferLen)
469 /* Offset is in this buffer */
470 return VoicePos{static_cast<int>(offset-totalBufferLen), frac, &item};
472 totalBufferLen += item.mSampleLen;
475 /* Offset is out of range of the queue */
476 return al::nullopt;
480 void InitVoice(Voice *voice, ALsource *source, ALbufferQueueItem *BufferList, ALCcontext *context,
481 ALCdevice *device)
483 voice->mLoopBuffer.store(source->Looping ? &source->mQueue.front() : nullptr,
484 std::memory_order_relaxed);
486 ALbuffer *buffer{BufferList->mBuffer};
487 voice->mFrequency = buffer->mSampleRate;
488 voice->mFmtChannels =
489 (buffer->mChannels == FmtStereo && source->mStereoMode == SourceStereo::Enhanced) ?
490 FmtSuperStereo : buffer->mChannels;
491 voice->mFmtType = buffer->mType;
492 voice->mFrameStep = buffer->channelsFromFmt();
493 voice->mBytesPerBlock = buffer->blockSizeFromFmt();
494 voice->mSamplesPerBlock = buffer->mBlockAlign;
495 voice->mAmbiLayout = IsUHJ(voice->mFmtChannels) ? AmbiLayout::FuMa : buffer->mAmbiLayout;
496 voice->mAmbiScaling = IsUHJ(voice->mFmtChannels) ? AmbiScaling::UHJ : buffer->mAmbiScaling;
497 voice->mAmbiOrder = (voice->mFmtChannels == FmtSuperStereo) ? 1 : buffer->mAmbiOrder;
499 if(buffer->mCallback) voice->mFlags.set(VoiceIsCallback);
500 else if(source->SourceType == AL_STATIC) voice->mFlags.set(VoiceIsStatic);
501 voice->mNumCallbackBlocks = 0;
502 voice->mCallbackBlockBase = 0;
504 voice->prepare(device);
506 source->mPropsDirty = false;
507 UpdateSourceProps(source, voice, context);
509 voice->mSourceID.store(source->id, std::memory_order_release);
513 VoiceChange *GetVoiceChanger(ALCcontext *ctx)
515 VoiceChange *vchg{ctx->mVoiceChangeTail};
516 if(vchg == ctx->mCurrentVoiceChange.load(std::memory_order_acquire)) UNLIKELY
518 ctx->allocVoiceChanges();
519 vchg = ctx->mVoiceChangeTail;
522 ctx->mVoiceChangeTail = vchg->mNext.exchange(nullptr, std::memory_order_relaxed);
524 return vchg;
527 void SendVoiceChanges(ALCcontext *ctx, VoiceChange *tail)
529 ALCdevice *device{ctx->mALDevice.get()};
531 VoiceChange *oldhead{ctx->mCurrentVoiceChange.load(std::memory_order_acquire)};
532 while(VoiceChange *next{oldhead->mNext.load(std::memory_order_relaxed)})
533 oldhead = next;
534 oldhead->mNext.store(tail, std::memory_order_release);
536 const bool connected{device->Connected.load(std::memory_order_acquire)};
537 device->waitForMix();
538 if(!connected) UNLIKELY
540 if(ctx->mStopVoicesOnDisconnect.load(std::memory_order_acquire))
542 /* If the device is disconnected and voices are stopped, just
543 * ignore all pending changes.
545 VoiceChange *cur{ctx->mCurrentVoiceChange.load(std::memory_order_acquire)};
546 while(VoiceChange *next{cur->mNext.load(std::memory_order_acquire)})
548 cur = next;
549 if(Voice *voice{cur->mVoice})
550 voice->mSourceID.store(0, std::memory_order_relaxed);
552 ctx->mCurrentVoiceChange.store(cur, std::memory_order_release);
558 bool SetVoiceOffset(Voice *oldvoice, const VoicePos &vpos, ALsource *source, ALCcontext *context,
559 ALCdevice *device)
561 /* First, get a free voice to start at the new offset. */
562 auto voicelist = context->getVoicesSpan();
563 Voice *newvoice{};
564 ALuint vidx{0};
565 for(Voice *voice : voicelist)
567 if(voice->mPlayState.load(std::memory_order_acquire) == Voice::Stopped
568 && voice->mSourceID.load(std::memory_order_relaxed) == 0u
569 && voice->mPendingChange.load(std::memory_order_relaxed) == false)
571 newvoice = voice;
572 break;
574 ++vidx;
576 if(!newvoice) UNLIKELY
578 auto &allvoices = *context->mVoices.load(std::memory_order_relaxed);
579 if(allvoices.size() == voicelist.size())
580 context->allocVoices(1);
581 context->mActiveVoiceCount.fetch_add(1, std::memory_order_release);
582 voicelist = context->getVoicesSpan();
584 vidx = 0;
585 for(Voice *voice : voicelist)
587 if(voice->mPlayState.load(std::memory_order_acquire) == Voice::Stopped
588 && voice->mSourceID.load(std::memory_order_relaxed) == 0u
589 && voice->mPendingChange.load(std::memory_order_relaxed) == false)
591 newvoice = voice;
592 break;
594 ++vidx;
596 ASSUME(newvoice != nullptr);
599 /* Initialize the new voice and set its starting offset.
600 * TODO: It might be better to have the VoiceChange processing copy the old
601 * voice's mixing parameters (and pending update) insead of initializing it
602 * all here. This would just need to set the minimum properties to link the
603 * voice to the source and its position-dependent properties (including the
604 * fading flag).
606 newvoice->mPlayState.store(Voice::Pending, std::memory_order_relaxed);
607 newvoice->mPosition.store(vpos.pos, std::memory_order_relaxed);
608 newvoice->mPositionFrac.store(vpos.frac, std::memory_order_relaxed);
609 newvoice->mCurrentBuffer.store(vpos.bufferitem, std::memory_order_relaxed);
610 newvoice->mStartTime = oldvoice->mStartTime;
611 newvoice->mFlags.reset();
612 if(vpos.pos > 0 || (vpos.pos == 0 && vpos.frac > 0)
613 || vpos.bufferitem != &source->mQueue.front())
614 newvoice->mFlags.set(VoiceIsFading);
615 InitVoice(newvoice, source, vpos.bufferitem, context, device);
616 source->VoiceIdx = vidx;
618 /* Set the old voice as having a pending change, and send it off with the
619 * new one with a new offset voice change.
621 oldvoice->mPendingChange.store(true, std::memory_order_relaxed);
623 VoiceChange *vchg{GetVoiceChanger(context)};
624 vchg->mOldVoice = oldvoice;
625 vchg->mVoice = newvoice;
626 vchg->mSourceID = source->id;
627 vchg->mState = VChangeState::Restart;
628 SendVoiceChanges(context, vchg);
630 /* If the old voice still has a sourceID, it's still active and the change-
631 * over will work on the next update.
633 if(oldvoice->mSourceID.load(std::memory_order_acquire) != 0u) LIKELY
634 return true;
636 /* Otherwise, if the new voice's state is not pending, the change-over
637 * already happened.
639 if(newvoice->mPlayState.load(std::memory_order_acquire) != Voice::Pending)
640 return true;
642 /* Otherwise, wait for any current mix to finish and check one last time. */
643 device->waitForMix();
644 if(newvoice->mPlayState.load(std::memory_order_acquire) != Voice::Pending)
645 return true;
646 /* The change-over failed because the old voice stopped before the new
647 * voice could start at the new offset. Let go of the new voice and have
648 * the caller store the source offset since it's stopped.
650 newvoice->mCurrentBuffer.store(nullptr, std::memory_order_relaxed);
651 newvoice->mLoopBuffer.store(nullptr, std::memory_order_relaxed);
652 newvoice->mSourceID.store(0u, std::memory_order_relaxed);
653 newvoice->mPlayState.store(Voice::Stopped, std::memory_order_relaxed);
654 return false;
659 * Returns if the last known state for the source was playing or paused. Does
660 * not sync with the mixer voice.
662 inline bool IsPlayingOrPaused(ALsource *source)
663 { return source->state == AL_PLAYING || source->state == AL_PAUSED; }
666 * Returns an updated source state using the matching voice's status (or lack
667 * thereof).
669 inline ALenum GetSourceState(ALsource *source, Voice *voice)
671 if(!voice && source->state == AL_PLAYING)
672 source->state = AL_STOPPED;
673 return source->state;
677 bool EnsureSources(ALCcontext *context, size_t needed)
679 size_t count{std::accumulate(context->mSourceList.cbegin(), context->mSourceList.cend(),
680 size_t{0},
681 [](size_t cur, const SourceSubList &sublist) noexcept -> size_t
682 { return cur + static_cast<ALuint>(al::popcount(sublist.FreeMask)); })};
684 while(needed > count)
686 if(context->mSourceList.size() >= 1<<25) UNLIKELY
687 return false;
689 context->mSourceList.emplace_back();
690 auto sublist = context->mSourceList.end() - 1;
691 sublist->FreeMask = ~0_u64;
692 sublist->Sources = static_cast<ALsource*>(al_calloc(alignof(ALsource), sizeof(ALsource)*64));
693 if(!sublist->Sources) UNLIKELY
695 context->mSourceList.pop_back();
696 return false;
698 count += 64;
700 return true;
703 ALsource *AllocSource(ALCcontext *context)
705 auto sublist = std::find_if(context->mSourceList.begin(), context->mSourceList.end(),
706 [](const SourceSubList &entry) noexcept -> bool
707 { return entry.FreeMask != 0; });
708 auto lidx = static_cast<ALuint>(std::distance(context->mSourceList.begin(), sublist));
709 auto slidx = static_cast<ALuint>(al::countr_zero(sublist->FreeMask));
710 ASSUME(slidx < 64);
712 ALsource *source{al::construct_at(sublist->Sources + slidx)};
714 /* Add 1 to avoid source ID 0. */
715 source->id = ((lidx<<6) | slidx) + 1;
717 context->mNumSources += 1;
718 sublist->FreeMask &= ~(1_u64 << slidx);
720 return source;
723 void FreeSource(ALCcontext *context, ALsource *source)
725 const ALuint id{source->id - 1};
726 const size_t lidx{id >> 6};
727 const ALuint slidx{id & 0x3f};
729 if(Voice *voice{GetSourceVoice(source, context)})
731 VoiceChange *vchg{GetVoiceChanger(context)};
733 voice->mPendingChange.store(true, std::memory_order_relaxed);
734 vchg->mVoice = voice;
735 vchg->mSourceID = source->id;
736 vchg->mState = VChangeState::Stop;
738 SendVoiceChanges(context, vchg);
741 al::destroy_at(source);
743 context->mSourceList[lidx].FreeMask |= 1_u64 << slidx;
744 context->mNumSources--;
748 inline ALsource *LookupSource(ALCcontext *context, ALuint id) noexcept
750 const size_t lidx{(id-1) >> 6};
751 const ALuint slidx{(id-1) & 0x3f};
753 if(lidx >= context->mSourceList.size()) UNLIKELY
754 return nullptr;
755 SourceSubList &sublist{context->mSourceList[lidx]};
756 if(sublist.FreeMask & (1_u64 << slidx)) UNLIKELY
757 return nullptr;
758 return sublist.Sources + slidx;
761 inline ALbuffer *LookupBuffer(ALCdevice *device, ALuint id) noexcept
763 const size_t lidx{(id-1) >> 6};
764 const ALuint slidx{(id-1) & 0x3f};
766 if(lidx >= device->BufferList.size()) UNLIKELY
767 return nullptr;
768 BufferSubList &sublist = device->BufferList[lidx];
769 if(sublist.FreeMask & (1_u64 << slidx)) UNLIKELY
770 return nullptr;
771 return sublist.Buffers + slidx;
774 inline ALfilter *LookupFilter(ALCdevice *device, ALuint id) noexcept
776 const size_t lidx{(id-1) >> 6};
777 const ALuint slidx{(id-1) & 0x3f};
779 if(lidx >= device->FilterList.size()) UNLIKELY
780 return nullptr;
781 FilterSubList &sublist = device->FilterList[lidx];
782 if(sublist.FreeMask & (1_u64 << slidx)) UNLIKELY
783 return nullptr;
784 return sublist.Filters + slidx;
787 inline ALeffectslot *LookupEffectSlot(ALCcontext *context, ALuint id) noexcept
789 const size_t lidx{(id-1) >> 6};
790 const ALuint slidx{(id-1) & 0x3f};
792 if(lidx >= context->mEffectSlotList.size()) UNLIKELY
793 return nullptr;
794 EffectSlotSubList &sublist{context->mEffectSlotList[lidx]};
795 if(sublist.FreeMask & (1_u64 << slidx)) UNLIKELY
796 return nullptr;
797 return sublist.EffectSlots + slidx;
801 al::optional<SourceStereo> StereoModeFromEnum(ALenum mode)
803 switch(mode)
805 case AL_NORMAL_SOFT: return SourceStereo::Normal;
806 case AL_SUPER_STEREO_SOFT: return SourceStereo::Enhanced;
808 WARN("Unsupported stereo mode: 0x%04x\n", mode);
809 return al::nullopt;
811 ALenum EnumFromStereoMode(SourceStereo mode)
813 switch(mode)
815 case SourceStereo::Normal: return AL_NORMAL_SOFT;
816 case SourceStereo::Enhanced: return AL_SUPER_STEREO_SOFT;
818 throw std::runtime_error{"Invalid SourceStereo: "+std::to_string(int(mode))};
821 al::optional<SpatializeMode> SpatializeModeFromEnum(ALenum mode)
823 switch(mode)
825 case AL_FALSE: return SpatializeMode::Off;
826 case AL_TRUE: return SpatializeMode::On;
827 case AL_AUTO_SOFT: return SpatializeMode::Auto;
829 WARN("Unsupported spatialize mode: 0x%04x\n", mode);
830 return al::nullopt;
832 ALenum EnumFromSpatializeMode(SpatializeMode mode)
834 switch(mode)
836 case SpatializeMode::Off: return AL_FALSE;
837 case SpatializeMode::On: return AL_TRUE;
838 case SpatializeMode::Auto: return AL_AUTO_SOFT;
840 throw std::runtime_error{"Invalid SpatializeMode: "+std::to_string(int(mode))};
843 al::optional<DirectMode> DirectModeFromEnum(ALenum mode)
845 switch(mode)
847 case AL_FALSE: return DirectMode::Off;
848 case AL_DROP_UNMATCHED_SOFT: return DirectMode::DropMismatch;
849 case AL_REMIX_UNMATCHED_SOFT: return DirectMode::RemixMismatch;
851 WARN("Unsupported direct mode: 0x%04x\n", mode);
852 return al::nullopt;
854 ALenum EnumFromDirectMode(DirectMode mode)
856 switch(mode)
858 case DirectMode::Off: return AL_FALSE;
859 case DirectMode::DropMismatch: return AL_DROP_UNMATCHED_SOFT;
860 case DirectMode::RemixMismatch: return AL_REMIX_UNMATCHED_SOFT;
862 throw std::runtime_error{"Invalid DirectMode: "+std::to_string(int(mode))};
865 al::optional<DistanceModel> DistanceModelFromALenum(ALenum model)
867 switch(model)
869 case AL_NONE: return DistanceModel::Disable;
870 case AL_INVERSE_DISTANCE: return DistanceModel::Inverse;
871 case AL_INVERSE_DISTANCE_CLAMPED: return DistanceModel::InverseClamped;
872 case AL_LINEAR_DISTANCE: return DistanceModel::Linear;
873 case AL_LINEAR_DISTANCE_CLAMPED: return DistanceModel::LinearClamped;
874 case AL_EXPONENT_DISTANCE: return DistanceModel::Exponent;
875 case AL_EXPONENT_DISTANCE_CLAMPED: return DistanceModel::ExponentClamped;
877 return al::nullopt;
879 ALenum ALenumFromDistanceModel(DistanceModel model)
881 switch(model)
883 case DistanceModel::Disable: return AL_NONE;
884 case DistanceModel::Inverse: return AL_INVERSE_DISTANCE;
885 case DistanceModel::InverseClamped: return AL_INVERSE_DISTANCE_CLAMPED;
886 case DistanceModel::Linear: return AL_LINEAR_DISTANCE;
887 case DistanceModel::LinearClamped: return AL_LINEAR_DISTANCE_CLAMPED;
888 case DistanceModel::Exponent: return AL_EXPONENT_DISTANCE;
889 case DistanceModel::ExponentClamped: return AL_EXPONENT_DISTANCE_CLAMPED;
891 throw std::runtime_error{"Unexpected distance model "+std::to_string(static_cast<int>(model))};
894 enum SourceProp : ALenum {
895 srcPitch = AL_PITCH,
896 srcGain = AL_GAIN,
897 srcMinGain = AL_MIN_GAIN,
898 srcMaxGain = AL_MAX_GAIN,
899 srcMaxDistance = AL_MAX_DISTANCE,
900 srcRolloffFactor = AL_ROLLOFF_FACTOR,
901 srcDopplerFactor = AL_DOPPLER_FACTOR,
902 srcConeOuterGain = AL_CONE_OUTER_GAIN,
903 srcSecOffset = AL_SEC_OFFSET,
904 srcSampleOffset = AL_SAMPLE_OFFSET,
905 srcByteOffset = AL_BYTE_OFFSET,
906 srcConeInnerAngle = AL_CONE_INNER_ANGLE,
907 srcConeOuterAngle = AL_CONE_OUTER_ANGLE,
908 srcRefDistance = AL_REFERENCE_DISTANCE,
910 srcPosition = AL_POSITION,
911 srcVelocity = AL_VELOCITY,
912 srcDirection = AL_DIRECTION,
914 srcSourceRelative = AL_SOURCE_RELATIVE,
915 srcLooping = AL_LOOPING,
916 srcBuffer = AL_BUFFER,
917 srcSourceState = AL_SOURCE_STATE,
918 srcBuffersQueued = AL_BUFFERS_QUEUED,
919 srcBuffersProcessed = AL_BUFFERS_PROCESSED,
920 srcSourceType = AL_SOURCE_TYPE,
922 /* ALC_EXT_EFX */
923 srcConeOuterGainHF = AL_CONE_OUTER_GAINHF,
924 srcAirAbsorptionFactor = AL_AIR_ABSORPTION_FACTOR,
925 srcRoomRolloffFactor = AL_ROOM_ROLLOFF_FACTOR,
926 srcDirectFilterGainHFAuto = AL_DIRECT_FILTER_GAINHF_AUTO,
927 srcAuxSendFilterGainAuto = AL_AUXILIARY_SEND_FILTER_GAIN_AUTO,
928 srcAuxSendFilterGainHFAuto = AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO,
929 srcDirectFilter = AL_DIRECT_FILTER,
930 srcAuxSendFilter = AL_AUXILIARY_SEND_FILTER,
932 /* AL_SOFT_direct_channels */
933 srcDirectChannelsSOFT = AL_DIRECT_CHANNELS_SOFT,
935 /* AL_EXT_source_distance_model */
936 srcDistanceModel = AL_DISTANCE_MODEL,
938 /* AL_SOFT_source_latency */
939 srcSampleOffsetLatencySOFT = AL_SAMPLE_OFFSET_LATENCY_SOFT,
940 srcSecOffsetLatencySOFT = AL_SEC_OFFSET_LATENCY_SOFT,
942 /* AL_EXT_STEREO_ANGLES */
943 srcAngles = AL_STEREO_ANGLES,
945 /* AL_EXT_SOURCE_RADIUS */
946 srcRadius = AL_SOURCE_RADIUS,
948 /* AL_EXT_BFORMAT */
949 srcOrientation = AL_ORIENTATION,
951 /* AL_SOFT_source_length */
952 srcByteLength = AL_BYTE_LENGTH_SOFT,
953 srcSampleLength = AL_SAMPLE_LENGTH_SOFT,
954 srcSecLength = AL_SEC_LENGTH_SOFT,
956 /* AL_SOFT_source_resampler */
957 srcResampler = AL_SOURCE_RESAMPLER_SOFT,
959 /* AL_SOFT_source_spatialize */
960 srcSpatialize = AL_SOURCE_SPATIALIZE_SOFT,
962 /* ALC_SOFT_device_clock */
963 srcSampleOffsetClockSOFT = AL_SAMPLE_OFFSET_CLOCK_SOFT,
964 srcSecOffsetClockSOFT = AL_SEC_OFFSET_CLOCK_SOFT,
966 /* AL_SOFT_UHJ */
967 srcStereoMode = AL_STEREO_MODE_SOFT,
968 srcSuperStereoWidth = AL_SUPER_STEREO_WIDTH_SOFT,
970 /* AL_SOFT_buffer_sub_data */
971 srcByteRWOffsetsSOFT = AL_BYTE_RW_OFFSETS_SOFT,
972 srcSampleRWOffsetsSOFT = AL_SAMPLE_RW_OFFSETS_SOFT,
976 constexpr size_t MaxValues{6u};
978 constexpr ALuint IntValsByProp(ALenum prop)
980 switch(static_cast<SourceProp>(prop))
982 case AL_SOURCE_STATE:
983 case AL_SOURCE_TYPE:
984 case AL_BUFFERS_QUEUED:
985 case AL_BUFFERS_PROCESSED:
986 case AL_BYTE_LENGTH_SOFT:
987 case AL_SAMPLE_LENGTH_SOFT:
988 case AL_SOURCE_RELATIVE:
989 case AL_LOOPING:
990 case AL_BUFFER:
991 case AL_SAMPLE_OFFSET:
992 case AL_BYTE_OFFSET:
993 case AL_DIRECT_FILTER:
994 case AL_DIRECT_FILTER_GAINHF_AUTO:
995 case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO:
996 case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO:
997 case AL_DIRECT_CHANNELS_SOFT:
998 case AL_DISTANCE_MODEL:
999 case AL_SOURCE_RESAMPLER_SOFT:
1000 case AL_SOURCE_SPATIALIZE_SOFT:
1001 case AL_STEREO_MODE_SOFT:
1002 return 1;
1004 case AL_SOURCE_RADIUS: /*AL_BYTE_RW_OFFSETS_SOFT:*/
1005 if(sBufferSubDataCompat)
1006 return 2;
1007 /*fall-through*/
1008 case AL_CONE_INNER_ANGLE:
1009 case AL_CONE_OUTER_ANGLE:
1010 case AL_PITCH:
1011 case AL_GAIN:
1012 case AL_MIN_GAIN:
1013 case AL_MAX_GAIN:
1014 case AL_REFERENCE_DISTANCE:
1015 case AL_ROLLOFF_FACTOR:
1016 case AL_CONE_OUTER_GAIN:
1017 case AL_MAX_DISTANCE:
1018 case AL_SEC_OFFSET:
1019 case AL_DOPPLER_FACTOR:
1020 case AL_CONE_OUTER_GAINHF:
1021 case AL_AIR_ABSORPTION_FACTOR:
1022 case AL_ROOM_ROLLOFF_FACTOR:
1023 case AL_SEC_LENGTH_SOFT:
1024 case AL_SUPER_STEREO_WIDTH_SOFT:
1025 return 1; /* 1x float */
1027 case AL_SAMPLE_RW_OFFSETS_SOFT:
1028 if(sBufferSubDataCompat)
1029 return 2;
1030 break;
1032 case AL_AUXILIARY_SEND_FILTER:
1033 return 3;
1035 case AL_POSITION:
1036 case AL_VELOCITY:
1037 case AL_DIRECTION:
1038 return 3; /* 3x float */
1040 case AL_ORIENTATION:
1041 return 6; /* 6x float */
1043 case AL_SAMPLE_OFFSET_LATENCY_SOFT:
1044 case AL_SAMPLE_OFFSET_CLOCK_SOFT:
1045 case AL_STEREO_ANGLES:
1046 break; /* i64 only */
1047 case AL_SEC_OFFSET_LATENCY_SOFT:
1048 case AL_SEC_OFFSET_CLOCK_SOFT:
1049 break; /* double only */
1052 return 0;
1055 constexpr ALuint Int64ValsByProp(ALenum prop)
1057 switch(static_cast<SourceProp>(prop))
1059 case AL_SOURCE_STATE:
1060 case AL_SOURCE_TYPE:
1061 case AL_BUFFERS_QUEUED:
1062 case AL_BUFFERS_PROCESSED:
1063 case AL_BYTE_LENGTH_SOFT:
1064 case AL_SAMPLE_LENGTH_SOFT:
1065 case AL_SOURCE_RELATIVE:
1066 case AL_LOOPING:
1067 case AL_BUFFER:
1068 case AL_SAMPLE_OFFSET:
1069 case AL_BYTE_OFFSET:
1070 case AL_DIRECT_FILTER:
1071 case AL_DIRECT_FILTER_GAINHF_AUTO:
1072 case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO:
1073 case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO:
1074 case AL_DIRECT_CHANNELS_SOFT:
1075 case AL_DISTANCE_MODEL:
1076 case AL_SOURCE_RESAMPLER_SOFT:
1077 case AL_SOURCE_SPATIALIZE_SOFT:
1078 case AL_STEREO_MODE_SOFT:
1079 return 1;
1081 case AL_SOURCE_RADIUS: /*AL_BYTE_RW_OFFSETS_SOFT:*/
1082 if(sBufferSubDataCompat)
1083 return 2;
1084 /*fall-through*/
1085 case AL_CONE_INNER_ANGLE:
1086 case AL_CONE_OUTER_ANGLE:
1087 case AL_PITCH:
1088 case AL_GAIN:
1089 case AL_MIN_GAIN:
1090 case AL_MAX_GAIN:
1091 case AL_REFERENCE_DISTANCE:
1092 case AL_ROLLOFF_FACTOR:
1093 case AL_CONE_OUTER_GAIN:
1094 case AL_MAX_DISTANCE:
1095 case AL_SEC_OFFSET:
1096 case AL_DOPPLER_FACTOR:
1097 case AL_CONE_OUTER_GAINHF:
1098 case AL_AIR_ABSORPTION_FACTOR:
1099 case AL_ROOM_ROLLOFF_FACTOR:
1100 case AL_SEC_LENGTH_SOFT:
1101 case AL_SUPER_STEREO_WIDTH_SOFT:
1102 return 1; /* 1x float */
1104 case AL_SAMPLE_RW_OFFSETS_SOFT:
1105 if(sBufferSubDataCompat)
1106 return 2;
1107 break;
1109 case AL_SAMPLE_OFFSET_LATENCY_SOFT:
1110 case AL_SAMPLE_OFFSET_CLOCK_SOFT:
1111 case AL_STEREO_ANGLES:
1112 return 2;
1114 case AL_AUXILIARY_SEND_FILTER:
1115 return 3;
1117 case AL_POSITION:
1118 case AL_VELOCITY:
1119 case AL_DIRECTION:
1120 return 3; /* 3x float */
1122 case AL_ORIENTATION:
1123 return 6; /* 6x float */
1125 case AL_SEC_OFFSET_LATENCY_SOFT:
1126 case AL_SEC_OFFSET_CLOCK_SOFT:
1127 break; /* double only */
1130 return 0;
1133 constexpr ALuint FloatValsByProp(ALenum prop)
1135 switch(static_cast<SourceProp>(prop))
1137 case AL_PITCH:
1138 case AL_GAIN:
1139 case AL_MIN_GAIN:
1140 case AL_MAX_GAIN:
1141 case AL_MAX_DISTANCE:
1142 case AL_ROLLOFF_FACTOR:
1143 case AL_DOPPLER_FACTOR:
1144 case AL_CONE_OUTER_GAIN:
1145 case AL_SEC_OFFSET:
1146 case AL_SAMPLE_OFFSET:
1147 case AL_BYTE_OFFSET:
1148 case AL_CONE_INNER_ANGLE:
1149 case AL_CONE_OUTER_ANGLE:
1150 case AL_REFERENCE_DISTANCE:
1151 case AL_CONE_OUTER_GAINHF:
1152 case AL_AIR_ABSORPTION_FACTOR:
1153 case AL_ROOM_ROLLOFF_FACTOR:
1154 case AL_DIRECT_FILTER_GAINHF_AUTO:
1155 case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO:
1156 case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO:
1157 case AL_DIRECT_CHANNELS_SOFT:
1158 case AL_DISTANCE_MODEL:
1159 case AL_SOURCE_RELATIVE:
1160 case AL_LOOPING:
1161 case AL_SOURCE_STATE:
1162 case AL_BUFFERS_QUEUED:
1163 case AL_BUFFERS_PROCESSED:
1164 case AL_SOURCE_TYPE:
1165 case AL_SOURCE_RESAMPLER_SOFT:
1166 case AL_SOURCE_SPATIALIZE_SOFT:
1167 case AL_BYTE_LENGTH_SOFT:
1168 case AL_SAMPLE_LENGTH_SOFT:
1169 case AL_SEC_LENGTH_SOFT:
1170 case AL_STEREO_MODE_SOFT:
1171 case AL_SUPER_STEREO_WIDTH_SOFT:
1172 return 1;
1174 case AL_SOURCE_RADIUS: /*AL_BYTE_RW_OFFSETS_SOFT:*/
1175 if(!sBufferSubDataCompat)
1176 return 1;
1177 /*fall-through*/
1178 case AL_SAMPLE_RW_OFFSETS_SOFT:
1179 break;
1181 case AL_STEREO_ANGLES:
1182 return 2;
1184 case AL_POSITION:
1185 case AL_VELOCITY:
1186 case AL_DIRECTION:
1187 return 3;
1189 case AL_ORIENTATION:
1190 return 6;
1192 case AL_SEC_OFFSET_LATENCY_SOFT:
1193 case AL_SEC_OFFSET_CLOCK_SOFT:
1194 break; /* Double only */
1196 case AL_BUFFER:
1197 case AL_DIRECT_FILTER:
1198 case AL_AUXILIARY_SEND_FILTER:
1199 break; /* i/i64 only */
1200 case AL_SAMPLE_OFFSET_LATENCY_SOFT:
1201 case AL_SAMPLE_OFFSET_CLOCK_SOFT:
1202 break; /* i64 only */
1204 return 0;
1206 constexpr ALuint DoubleValsByProp(ALenum prop)
1208 switch(static_cast<SourceProp>(prop))
1210 case AL_PITCH:
1211 case AL_GAIN:
1212 case AL_MIN_GAIN:
1213 case AL_MAX_GAIN:
1214 case AL_MAX_DISTANCE:
1215 case AL_ROLLOFF_FACTOR:
1216 case AL_DOPPLER_FACTOR:
1217 case AL_CONE_OUTER_GAIN:
1218 case AL_SEC_OFFSET:
1219 case AL_SAMPLE_OFFSET:
1220 case AL_BYTE_OFFSET:
1221 case AL_CONE_INNER_ANGLE:
1222 case AL_CONE_OUTER_ANGLE:
1223 case AL_REFERENCE_DISTANCE:
1224 case AL_CONE_OUTER_GAINHF:
1225 case AL_AIR_ABSORPTION_FACTOR:
1226 case AL_ROOM_ROLLOFF_FACTOR:
1227 case AL_DIRECT_FILTER_GAINHF_AUTO:
1228 case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO:
1229 case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO:
1230 case AL_DIRECT_CHANNELS_SOFT:
1231 case AL_DISTANCE_MODEL:
1232 case AL_SOURCE_RELATIVE:
1233 case AL_LOOPING:
1234 case AL_SOURCE_STATE:
1235 case AL_BUFFERS_QUEUED:
1236 case AL_BUFFERS_PROCESSED:
1237 case AL_SOURCE_TYPE:
1238 case AL_SOURCE_RESAMPLER_SOFT:
1239 case AL_SOURCE_SPATIALIZE_SOFT:
1240 case AL_BYTE_LENGTH_SOFT:
1241 case AL_SAMPLE_LENGTH_SOFT:
1242 case AL_SEC_LENGTH_SOFT:
1243 case AL_STEREO_MODE_SOFT:
1244 case AL_SUPER_STEREO_WIDTH_SOFT:
1245 return 1;
1247 case AL_SOURCE_RADIUS: /*AL_BYTE_RW_OFFSETS_SOFT:*/
1248 if(!sBufferSubDataCompat)
1249 return 1;
1250 /*fall-through*/
1251 case AL_SAMPLE_RW_OFFSETS_SOFT:
1252 break;
1254 case AL_SEC_OFFSET_LATENCY_SOFT:
1255 case AL_SEC_OFFSET_CLOCK_SOFT:
1256 case AL_STEREO_ANGLES:
1257 return 2;
1259 case AL_POSITION:
1260 case AL_VELOCITY:
1261 case AL_DIRECTION:
1262 return 3;
1264 case AL_ORIENTATION:
1265 return 6;
1267 case AL_BUFFER:
1268 case AL_DIRECT_FILTER:
1269 case AL_AUXILIARY_SEND_FILTER:
1270 break; /* i/i64 only */
1271 case AL_SAMPLE_OFFSET_LATENCY_SOFT:
1272 case AL_SAMPLE_OFFSET_CLOCK_SOFT:
1273 break; /* i64 only */
1275 return 0;
1279 void SetSourcefv(ALsource *const Source, ALCcontext *const Context, const SourceProp prop, const al::span<const float> values);
1280 void SetSourceiv(ALsource *const Source, ALCcontext *const Context, const SourceProp prop, const al::span<const int> values);
1281 void SetSourcei64v(ALsource *const Source, ALCcontext *const Context, const SourceProp prop, const al::span<const int64_t> values);
1283 struct check_exception : std::exception {
1285 struct check_size_exception final : check_exception {
1286 const char *what() const noexcept override
1287 { return "check_size_exception"; }
1289 struct check_value_exception final : check_exception {
1290 const char *what() const noexcept override
1291 { return "check_value_exception"; }
1295 void UpdateSourceProps(ALsource *source, ALCcontext *context)
1297 if(!context->mDeferUpdates)
1299 if(Voice *voice{GetSourceVoice(source, context)})
1301 UpdateSourceProps(source, voice, context);
1302 return;
1305 source->mPropsDirty = true;
1307 #ifdef ALSOFT_EAX
1308 void CommitAndUpdateSourceProps(ALsource *source, ALCcontext *context)
1310 if(!context->mDeferUpdates)
1312 if(context->hasEax())
1313 source->eaxCommit();
1314 if(Voice *voice{GetSourceVoice(source, context)})
1316 UpdateSourceProps(source, voice, context);
1317 return;
1320 source->mPropsDirty = true;
1323 #else
1325 inline void CommitAndUpdateSourceProps(ALsource *source, ALCcontext *context)
1326 { UpdateSourceProps(source, context); }
1327 #endif
1331 * Returns a pair of lambdas to check the following setters and getters.
1333 * The first lambda checks the size of the span is valid for its given size,
1334 * setting the proper context error and throwing a check_size_exception if it
1335 * fails.
1337 * The second lambda tests the validity of the value check, setting the proper
1338 * context error and throwing a check_value_exception if it failed.
1340 template<typename T, size_t N>
1341 auto GetCheckers(ALCcontext *const Context, const SourceProp prop, const al::span<T,N> values)
1343 return std::make_pair(
1344 [=](size_t expect) -> void
1346 if(values.size() == expect) LIKELY return;
1347 Context->setError(AL_INVALID_ENUM, "Property 0x%04x expects %zu value(s), got %zu",
1348 prop, expect, values.size());
1349 throw check_size_exception{};
1351 [Context](bool passed) -> void
1353 if(passed) LIKELY return;
1354 Context->setError(AL_INVALID_VALUE, "Value out of range");
1355 throw check_value_exception{};
1360 void SetSourcefv(ALsource *const Source, ALCcontext *const Context, const SourceProp prop,
1361 const al::span<const float> values)
1362 try {
1363 /* Structured bindings would be nice (C++17). */
1364 auto Checkers = GetCheckers(Context, prop, values);
1365 auto &CheckSize = Checkers.first;
1366 auto &CheckValue = Checkers.second;
1367 int ival;
1369 switch(prop)
1371 case AL_SEC_LENGTH_SOFT:
1372 case AL_SEC_OFFSET_LATENCY_SOFT:
1373 case AL_SEC_OFFSET_CLOCK_SOFT:
1374 /* Query only */
1375 return Context->setError(AL_INVALID_OPERATION,
1376 "Setting read-only source property 0x%04x", prop);
1378 case AL_PITCH:
1379 CheckSize(1);
1380 CheckValue(values[0] >= 0.0f);
1382 Source->Pitch = values[0];
1383 return UpdateSourceProps(Source, Context);
1385 case AL_CONE_INNER_ANGLE:
1386 CheckSize(1);
1387 CheckValue(values[0] >= 0.0f && values[0] <= 360.0f);
1389 Source->InnerAngle = values[0];
1390 return CommitAndUpdateSourceProps(Source, Context);
1392 case AL_CONE_OUTER_ANGLE:
1393 CheckSize(1);
1394 CheckValue(values[0] >= 0.0f && values[0] <= 360.0f);
1396 Source->OuterAngle = values[0];
1397 return CommitAndUpdateSourceProps(Source, Context);
1399 case AL_GAIN:
1400 CheckSize(1);
1401 CheckValue(values[0] >= 0.0f);
1403 Source->Gain = values[0];
1404 return UpdateSourceProps(Source, Context);
1406 case AL_MAX_DISTANCE:
1407 CheckSize(1);
1408 CheckValue(values[0] >= 0.0f);
1410 Source->MaxDistance = values[0];
1411 return CommitAndUpdateSourceProps(Source, Context);
1413 case AL_ROLLOFF_FACTOR:
1414 CheckSize(1);
1415 CheckValue(values[0] >= 0.0f);
1417 Source->RolloffFactor = values[0];
1418 return CommitAndUpdateSourceProps(Source, Context);
1420 case AL_REFERENCE_DISTANCE:
1421 CheckSize(1);
1422 CheckValue(values[0] >= 0.0f);
1424 Source->RefDistance = values[0];
1425 return CommitAndUpdateSourceProps(Source, Context);
1427 case AL_MIN_GAIN:
1428 CheckSize(1);
1429 CheckValue(values[0] >= 0.0f);
1431 Source->MinGain = values[0];
1432 return UpdateSourceProps(Source, Context);
1434 case AL_MAX_GAIN:
1435 CheckSize(1);
1436 CheckValue(values[0] >= 0.0f);
1438 Source->MaxGain = values[0];
1439 return UpdateSourceProps(Source, Context);
1441 case AL_CONE_OUTER_GAIN:
1442 CheckSize(1);
1443 CheckValue(values[0] >= 0.0f && values[0] <= 1.0f);
1445 Source->OuterGain = values[0];
1446 return UpdateSourceProps(Source, Context);
1448 case AL_CONE_OUTER_GAINHF:
1449 CheckSize(1);
1450 CheckValue(values[0] >= 0.0f && values[0] <= 1.0f);
1452 Source->OuterGainHF = values[0];
1453 return UpdateSourceProps(Source, Context);
1455 case AL_AIR_ABSORPTION_FACTOR:
1456 CheckSize(1);
1457 CheckValue(values[0] >= 0.0f && values[0] <= 10.0f);
1459 Source->AirAbsorptionFactor = values[0];
1460 return UpdateSourceProps(Source, Context);
1462 case AL_ROOM_ROLLOFF_FACTOR:
1463 CheckSize(1);
1464 CheckValue(values[0] >= 0.0f && values[0] <= 10.0f);
1466 Source->RoomRolloffFactor = values[0];
1467 return UpdateSourceProps(Source, Context);
1469 case AL_DOPPLER_FACTOR:
1470 CheckSize(1);
1471 CheckValue(values[0] >= 0.0f && values[0] <= 1.0f);
1473 Source->DopplerFactor = values[0];
1474 return UpdateSourceProps(Source, Context);
1476 case AL_SEC_OFFSET:
1477 case AL_SAMPLE_OFFSET:
1478 case AL_BYTE_OFFSET:
1479 CheckSize(1);
1480 CheckValue(std::isfinite(values[0]));
1482 if(Voice *voice{GetSourceVoice(Source, Context)})
1484 auto vpos = GetSampleOffset(Source->mQueue, prop, values[0]);
1485 if(!vpos) return Context->setError(AL_INVALID_VALUE, "Invalid offset");
1487 if(SetVoiceOffset(voice, *vpos, Source, Context, Context->mALDevice.get()))
1488 return;
1490 Source->OffsetType = prop;
1491 Source->Offset = values[0];
1492 return;
1494 case AL_SAMPLE_RW_OFFSETS_SOFT:
1495 break;
1497 case AL_SOURCE_RADIUS: /*AL_BYTE_RW_OFFSETS_SOFT:*/
1498 if(sBufferSubDataCompat)
1499 break;
1500 CheckSize(1);
1501 CheckValue(values[0] >= 0.0f && std::isfinite(values[0]));
1503 Source->Radius = values[0];
1504 return UpdateSourceProps(Source, Context);
1506 case AL_SUPER_STEREO_WIDTH_SOFT:
1507 CheckSize(1);
1508 CheckValue(values[0] >= 0.0f && values[0] <= 1.0f);
1510 Source->EnhWidth = values[0];
1511 return UpdateSourceProps(Source, Context);
1513 case AL_STEREO_ANGLES:
1514 CheckSize(2);
1515 CheckValue(std::isfinite(values[0]) && std::isfinite(values[1]));
1517 Source->StereoPan[0] = values[0];
1518 Source->StereoPan[1] = values[1];
1519 return UpdateSourceProps(Source, Context);
1522 case AL_POSITION:
1523 CheckSize(3);
1524 CheckValue(std::isfinite(values[0]) && std::isfinite(values[1]) && std::isfinite(values[2]));
1526 Source->Position[0] = values[0];
1527 Source->Position[1] = values[1];
1528 Source->Position[2] = values[2];
1529 return CommitAndUpdateSourceProps(Source, Context);
1531 case AL_VELOCITY:
1532 CheckSize(3);
1533 CheckValue(std::isfinite(values[0]) && std::isfinite(values[1]) && std::isfinite(values[2]));
1535 Source->Velocity[0] = values[0];
1536 Source->Velocity[1] = values[1];
1537 Source->Velocity[2] = values[2];
1538 return CommitAndUpdateSourceProps(Source, Context);
1540 case AL_DIRECTION:
1541 CheckSize(3);
1542 CheckValue(std::isfinite(values[0]) && std::isfinite(values[1]) && std::isfinite(values[2]));
1544 Source->Direction[0] = values[0];
1545 Source->Direction[1] = values[1];
1546 Source->Direction[2] = values[2];
1547 return CommitAndUpdateSourceProps(Source, Context);
1549 case AL_ORIENTATION:
1550 CheckSize(6);
1551 CheckValue(std::isfinite(values[0]) && std::isfinite(values[1]) && std::isfinite(values[2])
1552 && std::isfinite(values[3]) && std::isfinite(values[4]) && std::isfinite(values[5]));
1554 Source->OrientAt[0] = values[0];
1555 Source->OrientAt[1] = values[1];
1556 Source->OrientAt[2] = values[2];
1557 Source->OrientUp[0] = values[3];
1558 Source->OrientUp[1] = values[4];
1559 Source->OrientUp[2] = values[5];
1560 return UpdateSourceProps(Source, Context);
1563 case AL_SOURCE_RELATIVE:
1564 case AL_LOOPING:
1565 case AL_SOURCE_STATE:
1566 case AL_SOURCE_TYPE:
1567 case AL_DISTANCE_MODEL:
1568 case AL_DIRECT_FILTER_GAINHF_AUTO:
1569 case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO:
1570 case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO:
1571 case AL_DIRECT_CHANNELS_SOFT:
1572 case AL_SOURCE_RESAMPLER_SOFT:
1573 case AL_SOURCE_SPATIALIZE_SOFT:
1574 case AL_BYTE_LENGTH_SOFT:
1575 case AL_SAMPLE_LENGTH_SOFT:
1576 case AL_STEREO_MODE_SOFT:
1577 CheckSize(1);
1578 ival = static_cast<int>(values[0]);
1579 return SetSourceiv(Source, Context, prop, {&ival, 1u});
1581 case AL_BUFFERS_QUEUED:
1582 case AL_BUFFERS_PROCESSED:
1583 CheckSize(1);
1584 ival = static_cast<int>(static_cast<ALuint>(values[0]));
1585 return SetSourceiv(Source, Context, prop, {&ival, 1u});
1587 case AL_BUFFER:
1588 case AL_DIRECT_FILTER:
1589 case AL_AUXILIARY_SEND_FILTER:
1590 case AL_SAMPLE_OFFSET_LATENCY_SOFT:
1591 case AL_SAMPLE_OFFSET_CLOCK_SOFT:
1592 break;
1595 ERR("Unexpected property: 0x%04x\n", prop);
1596 Context->setError(AL_INVALID_ENUM, "Invalid source float property 0x%04x", prop);
1598 catch(check_exception&) {
1601 void SetSourceiv(ALsource *const Source, ALCcontext *const Context, const SourceProp prop,
1602 const al::span<const int> values)
1603 try {
1604 auto Checkers = GetCheckers(Context, prop, values);
1605 auto &CheckSize = Checkers.first;
1606 auto &CheckValue = Checkers.second;
1607 ALCdevice *device{Context->mALDevice.get()};
1608 ALeffectslot *slot{nullptr};
1609 al::deque<ALbufferQueueItem> oldlist;
1610 std::unique_lock<std::mutex> slotlock;
1611 float fvals[6];
1613 switch(prop)
1615 case AL_SOURCE_STATE:
1616 case AL_SOURCE_TYPE:
1617 case AL_BUFFERS_QUEUED:
1618 case AL_BUFFERS_PROCESSED:
1619 case AL_BYTE_LENGTH_SOFT:
1620 case AL_SAMPLE_LENGTH_SOFT:
1621 /* Query only */
1622 return Context->setError(AL_INVALID_OPERATION,
1623 "Setting read-only source property 0x%04x", prop);
1625 case AL_SOURCE_RELATIVE:
1626 CheckSize(1);
1627 CheckValue(values[0] == AL_FALSE || values[0] == AL_TRUE);
1629 Source->HeadRelative = values[0] != AL_FALSE;
1630 return CommitAndUpdateSourceProps(Source, Context);
1632 case AL_LOOPING:
1633 CheckSize(1);
1634 CheckValue(values[0] == AL_FALSE || values[0] == AL_TRUE);
1636 Source->Looping = values[0] != AL_FALSE;
1637 if(Voice *voice{GetSourceVoice(Source, Context)})
1639 if(Source->Looping)
1640 voice->mLoopBuffer.store(&Source->mQueue.front(), std::memory_order_release);
1641 else
1642 voice->mLoopBuffer.store(nullptr, std::memory_order_release);
1644 /* If the source is playing, wait for the current mix to finish to
1645 * ensure it isn't currently looping back or reaching the end.
1647 device->waitForMix();
1649 return;
1651 case AL_BUFFER:
1652 CheckSize(1);
1654 const ALenum state{GetSourceState(Source, GetSourceVoice(Source, Context))};
1655 if(state == AL_PLAYING || state == AL_PAUSED)
1656 return Context->setError(AL_INVALID_OPERATION,
1657 "Setting buffer on playing or paused source %u", Source->id);
1659 if(values[0])
1661 std::lock_guard<std::mutex> _{device->BufferLock};
1662 ALbuffer *buffer{LookupBuffer(device, static_cast<ALuint>(values[0]))};
1663 if(!buffer)
1664 return Context->setError(AL_INVALID_VALUE, "Invalid buffer ID %u",
1665 static_cast<ALuint>(values[0]));
1666 if(buffer->MappedAccess && !(buffer->MappedAccess&AL_MAP_PERSISTENT_BIT_SOFT))
1667 return Context->setError(AL_INVALID_OPERATION,
1668 "Setting non-persistently mapped buffer %u", buffer->id);
1669 if(buffer->mCallback && ReadRef(buffer->ref) != 0)
1670 return Context->setError(AL_INVALID_OPERATION,
1671 "Setting already-set callback buffer %u", buffer->id);
1673 /* Add the selected buffer to a one-item queue */
1674 al::deque<ALbufferQueueItem> newlist;
1675 newlist.emplace_back();
1676 newlist.back().mCallback = buffer->mCallback;
1677 newlist.back().mUserData = buffer->mUserData;
1678 newlist.back().mBlockAlign = buffer->mBlockAlign;
1679 newlist.back().mSampleLen = buffer->mSampleLen;
1680 newlist.back().mLoopStart = buffer->mLoopStart;
1681 newlist.back().mLoopEnd = buffer->mLoopEnd;
1682 newlist.back().mSamples = buffer->mData.data();
1683 newlist.back().mBuffer = buffer;
1684 IncrementRef(buffer->ref);
1686 /* Source is now Static */
1687 Source->SourceType = AL_STATIC;
1688 Source->mQueue.swap(oldlist);
1689 Source->mQueue.swap(newlist);
1691 else
1693 /* Source is now Undetermined */
1694 Source->SourceType = AL_UNDETERMINED;
1695 Source->mQueue.swap(oldlist);
1698 /* Delete all elements in the previous queue */
1699 for(auto &item : oldlist)
1701 if(ALbuffer *buffer{item.mBuffer})
1702 DecrementRef(buffer->ref);
1704 return;
1706 case AL_SEC_OFFSET:
1707 case AL_SAMPLE_OFFSET:
1708 case AL_BYTE_OFFSET:
1709 CheckSize(1);
1711 if(Voice *voice{GetSourceVoice(Source, Context)})
1713 auto vpos = GetSampleOffset(Source->mQueue, prop, values[0]);
1714 if(!vpos) return Context->setError(AL_INVALID_VALUE, "Invalid source offset");
1716 if(SetVoiceOffset(voice, *vpos, Source, Context, device))
1717 return;
1719 Source->OffsetType = prop;
1720 Source->Offset = values[0];
1721 return;
1723 case AL_DIRECT_FILTER:
1724 CheckSize(1);
1725 if(values[0])
1727 std::lock_guard<std::mutex> _{device->FilterLock};
1728 ALfilter *filter{LookupFilter(device, static_cast<ALuint>(values[0]))};
1729 if(!filter)
1730 return Context->setError(AL_INVALID_VALUE, "Invalid filter ID %u",
1731 static_cast<ALuint>(values[0]));
1732 Source->Direct.Gain = filter->Gain;
1733 Source->Direct.GainHF = filter->GainHF;
1734 Source->Direct.HFReference = filter->HFReference;
1735 Source->Direct.GainLF = filter->GainLF;
1736 Source->Direct.LFReference = filter->LFReference;
1738 else
1740 Source->Direct.Gain = 1.0f;
1741 Source->Direct.GainHF = 1.0f;
1742 Source->Direct.HFReference = LOWPASSFREQREF;
1743 Source->Direct.GainLF = 1.0f;
1744 Source->Direct.LFReference = HIGHPASSFREQREF;
1746 return UpdateSourceProps(Source, Context);
1748 case AL_DIRECT_FILTER_GAINHF_AUTO:
1749 CheckSize(1);
1750 CheckValue(values[0] == AL_FALSE || values[0] == AL_TRUE);
1752 Source->DryGainHFAuto = values[0] != AL_FALSE;
1753 return UpdateSourceProps(Source, Context);
1755 case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO:
1756 CheckSize(1);
1757 CheckValue(values[0] == AL_FALSE || values[0] == AL_TRUE);
1759 Source->WetGainAuto = values[0] != AL_FALSE;
1760 return UpdateSourceProps(Source, Context);
1762 case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO:
1763 CheckSize(1);
1764 CheckValue(values[0] == AL_FALSE || values[0] == AL_TRUE);
1766 Source->WetGainHFAuto = values[0] != AL_FALSE;
1767 return UpdateSourceProps(Source, Context);
1769 case AL_DIRECT_CHANNELS_SOFT:
1770 CheckSize(1);
1771 if(auto mode = DirectModeFromEnum(values[0]))
1773 Source->DirectChannels = *mode;
1774 return UpdateSourceProps(Source, Context);
1776 Context->setError(AL_INVALID_VALUE, "Unsupported AL_DIRECT_CHANNELS_SOFT: 0x%04x\n",
1777 values[0]);
1778 return;
1780 case AL_DISTANCE_MODEL:
1781 CheckSize(1);
1782 if(auto model = DistanceModelFromALenum(values[0]))
1784 Source->mDistanceModel = *model;
1785 if(Context->mSourceDistanceModel)
1786 UpdateSourceProps(Source, Context);
1787 return;
1789 Context->setError(AL_INVALID_VALUE, "Distance model out of range: 0x%04x", values[0]);
1790 return;
1792 case AL_SOURCE_RESAMPLER_SOFT:
1793 CheckSize(1);
1794 CheckValue(values[0] >= 0 && values[0] <= static_cast<int>(Resampler::Max));
1796 Source->mResampler = static_cast<Resampler>(values[0]);
1797 return UpdateSourceProps(Source, Context);
1799 case AL_SOURCE_SPATIALIZE_SOFT:
1800 CheckSize(1);
1801 if(auto mode = SpatializeModeFromEnum(values[0]))
1803 Source->mSpatialize = *mode;
1804 return UpdateSourceProps(Source, Context);
1806 Context->setError(AL_INVALID_VALUE, "Unsupported AL_SOURCE_SPATIALIZE_SOFT: 0x%04x\n",
1807 values[0]);
1808 return;
1810 case AL_STEREO_MODE_SOFT:
1811 CheckSize(1);
1813 const ALenum state{GetSourceState(Source, GetSourceVoice(Source, Context))};
1814 if(state == AL_PLAYING || state == AL_PAUSED)
1815 return Context->setError(AL_INVALID_OPERATION,
1816 "Modifying stereo mode on playing or paused source %u", Source->id);
1818 if(auto mode = StereoModeFromEnum(values[0]))
1820 Source->mStereoMode = *mode;
1821 return;
1823 Context->setError(AL_INVALID_VALUE, "Unsupported AL_STEREO_MODE_SOFT: 0x%04x\n",
1824 values[0]);
1825 return;
1827 case AL_AUXILIARY_SEND_FILTER:
1828 CheckSize(3);
1829 slotlock = std::unique_lock<std::mutex>{Context->mEffectSlotLock};
1830 if(values[0] && (slot=LookupEffectSlot(Context, static_cast<ALuint>(values[0]))) == nullptr)
1831 return Context->setError(AL_INVALID_VALUE, "Invalid effect ID %u", values[0]);
1832 if(static_cast<ALuint>(values[1]) >= device->NumAuxSends)
1833 return Context->setError(AL_INVALID_VALUE, "Invalid send %u", values[1]);
1835 if(values[2])
1837 std::lock_guard<std::mutex> _{device->FilterLock};
1838 ALfilter *filter{LookupFilter(device, static_cast<ALuint>(values[2]))};
1839 if(!filter)
1840 return Context->setError(AL_INVALID_VALUE, "Invalid filter ID %u", values[2]);
1842 auto &send = Source->Send[static_cast<ALuint>(values[1])];
1843 send.Gain = filter->Gain;
1844 send.GainHF = filter->GainHF;
1845 send.HFReference = filter->HFReference;
1846 send.GainLF = filter->GainLF;
1847 send.LFReference = filter->LFReference;
1849 else
1851 /* Disable filter */
1852 auto &send = Source->Send[static_cast<ALuint>(values[1])];
1853 send.Gain = 1.0f;
1854 send.GainHF = 1.0f;
1855 send.HFReference = LOWPASSFREQREF;
1856 send.GainLF = 1.0f;
1857 send.LFReference = HIGHPASSFREQREF;
1860 /* We must force an update if the current auxiliary slot is valid and
1861 * about to be changed on an active source, in case the old slot is
1862 * about to be deleted.
1864 if(Source->Send[static_cast<ALuint>(values[1])].Slot
1865 && slot != Source->Send[static_cast<ALuint>(values[1])].Slot
1866 && IsPlayingOrPaused(Source))
1868 /* Add refcount on the new slot, and release the previous slot */
1869 if(slot) IncrementRef(slot->ref);
1870 if(auto *oldslot = Source->Send[static_cast<ALuint>(values[1])].Slot)
1871 DecrementRef(oldslot->ref);
1872 Source->Send[static_cast<ALuint>(values[1])].Slot = slot;
1874 Voice *voice{GetSourceVoice(Source, Context)};
1875 if(voice) UpdateSourceProps(Source, voice, Context);
1876 else Source->mPropsDirty = true;
1878 else
1880 if(slot) IncrementRef(slot->ref);
1881 if(auto *oldslot = Source->Send[static_cast<ALuint>(values[1])].Slot)
1882 DecrementRef(oldslot->ref);
1883 Source->Send[static_cast<ALuint>(values[1])].Slot = slot;
1884 UpdateSourceProps(Source, Context);
1886 return;
1889 case AL_SAMPLE_RW_OFFSETS_SOFT:
1890 if(sBufferSubDataCompat)
1891 /* Query only */
1892 return Context->setError(AL_INVALID_OPERATION,
1893 "Setting read-only source property 0x%04x", prop);
1894 break;
1896 case AL_SOURCE_RADIUS: /*AL_BYTE_RW_OFFSETS_SOFT:*/
1897 if(sBufferSubDataCompat)
1898 return Context->setError(AL_INVALID_OPERATION,
1899 "Setting read-only source property 0x%04x", prop);
1900 /*fall-through*/
1902 /* 1x float */
1903 case AL_CONE_INNER_ANGLE:
1904 case AL_CONE_OUTER_ANGLE:
1905 case AL_PITCH:
1906 case AL_GAIN:
1907 case AL_MIN_GAIN:
1908 case AL_MAX_GAIN:
1909 case AL_REFERENCE_DISTANCE:
1910 case AL_ROLLOFF_FACTOR:
1911 case AL_CONE_OUTER_GAIN:
1912 case AL_MAX_DISTANCE:
1913 case AL_DOPPLER_FACTOR:
1914 case AL_CONE_OUTER_GAINHF:
1915 case AL_AIR_ABSORPTION_FACTOR:
1916 case AL_ROOM_ROLLOFF_FACTOR:
1917 case AL_SEC_LENGTH_SOFT:
1918 case AL_SUPER_STEREO_WIDTH_SOFT:
1919 CheckSize(1);
1920 fvals[0] = static_cast<float>(values[0]);
1921 return SetSourcefv(Source, Context, prop, {fvals, 1u});
1923 /* 3x float */
1924 case AL_POSITION:
1925 case AL_VELOCITY:
1926 case AL_DIRECTION:
1927 CheckSize(3);
1928 fvals[0] = static_cast<float>(values[0]);
1929 fvals[1] = static_cast<float>(values[1]);
1930 fvals[2] = static_cast<float>(values[2]);
1931 return SetSourcefv(Source, Context, prop, {fvals, 3u});
1933 /* 6x float */
1934 case AL_ORIENTATION:
1935 CheckSize(6);
1936 fvals[0] = static_cast<float>(values[0]);
1937 fvals[1] = static_cast<float>(values[1]);
1938 fvals[2] = static_cast<float>(values[2]);
1939 fvals[3] = static_cast<float>(values[3]);
1940 fvals[4] = static_cast<float>(values[4]);
1941 fvals[5] = static_cast<float>(values[5]);
1942 return SetSourcefv(Source, Context, prop, {fvals, 6u});
1944 case AL_SAMPLE_OFFSET_LATENCY_SOFT:
1945 case AL_SEC_OFFSET_LATENCY_SOFT:
1946 case AL_SEC_OFFSET_CLOCK_SOFT:
1947 case AL_SAMPLE_OFFSET_CLOCK_SOFT:
1948 case AL_STEREO_ANGLES:
1949 break;
1952 ERR("Unexpected property: 0x%04x\n", prop);
1953 Context->setError(AL_INVALID_ENUM, "Invalid source integer property 0x%04x", prop);
1955 catch(check_exception&) {
1958 void SetSourcei64v(ALsource *const Source, ALCcontext *const Context, const SourceProp prop,
1959 const al::span<const int64_t> values)
1960 try {
1961 auto Checkers = GetCheckers(Context, prop, values);
1962 auto &CheckSize = Checkers.first;
1963 auto &CheckValue = Checkers.second;
1964 float fvals[MaxValues];
1965 int ivals[MaxValues];
1967 switch(prop)
1969 case AL_SOURCE_TYPE:
1970 case AL_BUFFERS_QUEUED:
1971 case AL_BUFFERS_PROCESSED:
1972 case AL_SOURCE_STATE:
1973 case AL_BYTE_LENGTH_SOFT:
1974 case AL_SAMPLE_LENGTH_SOFT:
1975 case AL_SAMPLE_OFFSET_LATENCY_SOFT:
1976 case AL_SAMPLE_OFFSET_CLOCK_SOFT:
1977 /* Query only */
1978 return Context->setError(AL_INVALID_OPERATION,
1979 "Setting read-only source property 0x%04x", prop);
1981 /* 1x int */
1982 case AL_SOURCE_RELATIVE:
1983 case AL_LOOPING:
1984 case AL_SEC_OFFSET:
1985 case AL_SAMPLE_OFFSET:
1986 case AL_BYTE_OFFSET:
1987 case AL_DIRECT_FILTER_GAINHF_AUTO:
1988 case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO:
1989 case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO:
1990 case AL_DIRECT_CHANNELS_SOFT:
1991 case AL_DISTANCE_MODEL:
1992 case AL_SOURCE_RESAMPLER_SOFT:
1993 case AL_SOURCE_SPATIALIZE_SOFT:
1994 case AL_STEREO_MODE_SOFT:
1995 CheckSize(1);
1996 CheckValue(values[0] <= INT_MAX && values[0] >= INT_MIN);
1998 ivals[0] = static_cast<int>(values[0]);
1999 return SetSourceiv(Source, Context, prop, {ivals, 1u});
2001 /* 1x uint */
2002 case AL_BUFFER:
2003 case AL_DIRECT_FILTER:
2004 CheckSize(1);
2005 CheckValue(values[0] <= UINT_MAX && values[0] >= 0);
2007 ivals[0] = static_cast<int>(values[0]);
2008 return SetSourceiv(Source, Context, prop, {ivals, 1u});
2010 /* 3x uint */
2011 case AL_AUXILIARY_SEND_FILTER:
2012 CheckSize(3);
2013 CheckValue(values[0] <= UINT_MAX && values[0] >= 0 && values[1] <= UINT_MAX
2014 && values[1] >= 0 && values[2] <= UINT_MAX && values[2] >= 0);
2016 ivals[0] = static_cast<int>(values[0]);
2017 ivals[1] = static_cast<int>(values[1]);
2018 ivals[2] = static_cast<int>(values[2]);
2019 return SetSourceiv(Source, Context, prop, {ivals, 3u});
2021 case AL_SAMPLE_RW_OFFSETS_SOFT:
2022 if(sBufferSubDataCompat)
2024 /* Query only */
2025 return Context->setError(AL_INVALID_OPERATION,
2026 "Setting read-only source property 0x%04x", prop);
2028 break;
2030 case AL_SOURCE_RADIUS: /*AL_BYTE_RW_OFFSETS_SOFT:*/
2031 if(sBufferSubDataCompat)
2032 return Context->setError(AL_INVALID_OPERATION,
2033 "Setting read-only source property 0x%04x", prop);
2034 /*fall-through*/
2036 /* 1x float */
2037 case AL_CONE_INNER_ANGLE:
2038 case AL_CONE_OUTER_ANGLE:
2039 case AL_PITCH:
2040 case AL_GAIN:
2041 case AL_MIN_GAIN:
2042 case AL_MAX_GAIN:
2043 case AL_REFERENCE_DISTANCE:
2044 case AL_ROLLOFF_FACTOR:
2045 case AL_CONE_OUTER_GAIN:
2046 case AL_MAX_DISTANCE:
2047 case AL_DOPPLER_FACTOR:
2048 case AL_CONE_OUTER_GAINHF:
2049 case AL_AIR_ABSORPTION_FACTOR:
2050 case AL_ROOM_ROLLOFF_FACTOR:
2051 case AL_SEC_LENGTH_SOFT:
2052 case AL_SUPER_STEREO_WIDTH_SOFT:
2053 CheckSize(1);
2054 fvals[0] = static_cast<float>(values[0]);
2055 return SetSourcefv(Source, Context, prop, {fvals, 1u});
2057 /* 3x float */
2058 case AL_POSITION:
2059 case AL_VELOCITY:
2060 case AL_DIRECTION:
2061 CheckSize(3);
2062 fvals[0] = static_cast<float>(values[0]);
2063 fvals[1] = static_cast<float>(values[1]);
2064 fvals[2] = static_cast<float>(values[2]);
2065 return SetSourcefv(Source, Context, prop, {fvals, 3u});
2067 /* 6x float */
2068 case AL_ORIENTATION:
2069 CheckSize(6);
2070 fvals[0] = static_cast<float>(values[0]);
2071 fvals[1] = static_cast<float>(values[1]);
2072 fvals[2] = static_cast<float>(values[2]);
2073 fvals[3] = static_cast<float>(values[3]);
2074 fvals[4] = static_cast<float>(values[4]);
2075 fvals[5] = static_cast<float>(values[5]);
2076 return SetSourcefv(Source, Context, prop, {fvals, 6u});
2078 case AL_SEC_OFFSET_LATENCY_SOFT:
2079 case AL_SEC_OFFSET_CLOCK_SOFT:
2080 case AL_STEREO_ANGLES:
2081 break;
2084 ERR("Unexpected property: 0x%04x\n", prop);
2085 Context->setError(AL_INVALID_ENUM, "Invalid source integer64 property 0x%04x", prop);
2087 catch(check_exception&) {
2091 template<typename T, size_t N>
2092 auto GetSizeChecker(ALCcontext *const Context, const SourceProp prop, const al::span<T,N> values)
2094 return [=](size_t expect) -> void
2096 if(values.size() == expect) LIKELY return;
2097 Context->setError(AL_INVALID_ENUM, "Property 0x%04x expects %zu value(s), got %zu",
2098 prop, expect, values.size());
2099 throw check_size_exception{};
2103 bool GetSourcedv(ALsource *const Source, ALCcontext *const Context, const SourceProp prop, const al::span<double> values);
2104 bool GetSourceiv(ALsource *const Source, ALCcontext *const Context, const SourceProp prop, const al::span<int> values);
2105 bool GetSourcei64v(ALsource *const Source, ALCcontext *const Context, const SourceProp prop, const al::span<int64_t> values);
2107 bool GetSourcedv(ALsource *const Source, ALCcontext *const Context, const SourceProp prop,
2108 const al::span<double> values)
2109 try {
2110 auto CheckSize = GetSizeChecker(Context, prop, values);
2111 ALCdevice *device{Context->mALDevice.get()};
2112 ClockLatency clocktime;
2113 nanoseconds srcclock;
2114 int ivals[MaxValues];
2115 bool err;
2117 switch(prop)
2119 case AL_GAIN:
2120 CheckSize(1);
2121 values[0] = Source->Gain;
2122 return true;
2124 case AL_PITCH:
2125 CheckSize(1);
2126 values[0] = Source->Pitch;
2127 return true;
2129 case AL_MAX_DISTANCE:
2130 CheckSize(1);
2131 values[0] = Source->MaxDistance;
2132 return true;
2134 case AL_ROLLOFF_FACTOR:
2135 CheckSize(1);
2136 values[0] = Source->RolloffFactor;
2137 return true;
2139 case AL_REFERENCE_DISTANCE:
2140 CheckSize(1);
2141 values[0] = Source->RefDistance;
2142 return true;
2144 case AL_CONE_INNER_ANGLE:
2145 CheckSize(1);
2146 values[0] = Source->InnerAngle;
2147 return true;
2149 case AL_CONE_OUTER_ANGLE:
2150 CheckSize(1);
2151 values[0] = Source->OuterAngle;
2152 return true;
2154 case AL_MIN_GAIN:
2155 CheckSize(1);
2156 values[0] = Source->MinGain;
2157 return true;
2159 case AL_MAX_GAIN:
2160 CheckSize(1);
2161 values[0] = Source->MaxGain;
2162 return true;
2164 case AL_CONE_OUTER_GAIN:
2165 CheckSize(1);
2166 values[0] = Source->OuterGain;
2167 return true;
2169 case AL_SEC_OFFSET:
2170 case AL_SAMPLE_OFFSET:
2171 case AL_BYTE_OFFSET:
2172 CheckSize(1);
2173 values[0] = GetSourceOffset(Source, prop, Context);
2174 return true;
2176 case AL_CONE_OUTER_GAINHF:
2177 CheckSize(1);
2178 values[0] = Source->OuterGainHF;
2179 return true;
2181 case AL_AIR_ABSORPTION_FACTOR:
2182 CheckSize(1);
2183 values[0] = Source->AirAbsorptionFactor;
2184 return true;
2186 case AL_ROOM_ROLLOFF_FACTOR:
2187 CheckSize(1);
2188 values[0] = Source->RoomRolloffFactor;
2189 return true;
2191 case AL_DOPPLER_FACTOR:
2192 CheckSize(1);
2193 values[0] = Source->DopplerFactor;
2194 return true;
2196 case AL_SAMPLE_RW_OFFSETS_SOFT:
2197 break;
2198 case AL_SOURCE_RADIUS: /*AL_BYTE_RW_OFFSETS_SOFT:*/
2199 if(sBufferSubDataCompat)
2200 break;
2202 CheckSize(1);
2203 values[0] = Source->Radius;
2204 return true;
2206 case AL_SUPER_STEREO_WIDTH_SOFT:
2207 CheckSize(1);
2208 values[0] = Source->EnhWidth;
2209 return true;
2211 case AL_BYTE_LENGTH_SOFT:
2212 case AL_SAMPLE_LENGTH_SOFT:
2213 case AL_SEC_LENGTH_SOFT:
2214 CheckSize(1);
2215 values[0] = GetSourceLength(Source, prop);
2216 return true;
2218 case AL_STEREO_ANGLES:
2219 CheckSize(2);
2220 values[0] = Source->StereoPan[0];
2221 values[1] = Source->StereoPan[1];
2222 return true;
2224 case AL_SEC_OFFSET_LATENCY_SOFT:
2225 CheckSize(2);
2226 /* Get the source offset with the clock time first. Then get the clock
2227 * time with the device latency. Order is important.
2229 values[0] = GetSourceSecOffset(Source, Context, &srcclock);
2231 std::lock_guard<std::mutex> _{device->StateLock};
2232 clocktime = GetClockLatency(device, device->Backend.get());
2234 if(srcclock == clocktime.ClockTime)
2235 values[1] = static_cast<double>(clocktime.Latency.count()) / 1000000000.0;
2236 else
2238 /* If the clock time incremented, reduce the latency by that much
2239 * since it's that much closer to the source offset it got earlier.
2241 const nanoseconds diff{clocktime.ClockTime - srcclock};
2242 const nanoseconds latency{clocktime.Latency - std::min(clocktime.Latency, diff)};
2243 values[1] = static_cast<double>(latency.count()) / 1000000000.0;
2245 return true;
2247 case AL_SEC_OFFSET_CLOCK_SOFT:
2248 CheckSize(2);
2249 values[0] = GetSourceSecOffset(Source, Context, &srcclock);
2250 values[1] = static_cast<double>(srcclock.count()) / 1000000000.0;
2251 return true;
2253 case AL_POSITION:
2254 CheckSize(3);
2255 values[0] = Source->Position[0];
2256 values[1] = Source->Position[1];
2257 values[2] = Source->Position[2];
2258 return true;
2260 case AL_VELOCITY:
2261 CheckSize(3);
2262 values[0] = Source->Velocity[0];
2263 values[1] = Source->Velocity[1];
2264 values[2] = Source->Velocity[2];
2265 return true;
2267 case AL_DIRECTION:
2268 CheckSize(3);
2269 values[0] = Source->Direction[0];
2270 values[1] = Source->Direction[1];
2271 values[2] = Source->Direction[2];
2272 return true;
2274 case AL_ORIENTATION:
2275 CheckSize(6);
2276 values[0] = Source->OrientAt[0];
2277 values[1] = Source->OrientAt[1];
2278 values[2] = Source->OrientAt[2];
2279 values[3] = Source->OrientUp[0];
2280 values[4] = Source->OrientUp[1];
2281 values[5] = Source->OrientUp[2];
2282 return true;
2284 /* 1x int */
2285 case AL_SOURCE_RELATIVE:
2286 case AL_LOOPING:
2287 case AL_SOURCE_STATE:
2288 case AL_BUFFERS_QUEUED:
2289 case AL_BUFFERS_PROCESSED:
2290 case AL_SOURCE_TYPE:
2291 case AL_DIRECT_FILTER_GAINHF_AUTO:
2292 case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO:
2293 case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO:
2294 case AL_DIRECT_CHANNELS_SOFT:
2295 case AL_DISTANCE_MODEL:
2296 case AL_SOURCE_RESAMPLER_SOFT:
2297 case AL_SOURCE_SPATIALIZE_SOFT:
2298 case AL_STEREO_MODE_SOFT:
2299 CheckSize(1);
2300 if((err=GetSourceiv(Source, Context, prop, {ivals, 1u})) != false)
2301 values[0] = static_cast<double>(ivals[0]);
2302 return err;
2304 case AL_BUFFER:
2305 case AL_DIRECT_FILTER:
2306 case AL_AUXILIARY_SEND_FILTER:
2307 case AL_SAMPLE_OFFSET_LATENCY_SOFT:
2308 case AL_SAMPLE_OFFSET_CLOCK_SOFT:
2309 break;
2312 ERR("Unexpected property: 0x%04x\n", prop);
2313 Context->setError(AL_INVALID_ENUM, "Invalid source double property 0x%04x", prop);
2314 return false;
2316 catch(check_exception&) {
2317 return false;
2320 bool GetSourceiv(ALsource *const Source, ALCcontext *const Context, const SourceProp prop,
2321 const al::span<int> values)
2322 try {
2323 auto CheckSize = GetSizeChecker(Context, prop, values);
2324 double dvals[MaxValues];
2325 bool err;
2327 switch(prop)
2329 case AL_SOURCE_RELATIVE:
2330 CheckSize(1);
2331 values[0] = Source->HeadRelative;
2332 return true;
2334 case AL_LOOPING:
2335 CheckSize(1);
2336 values[0] = Source->Looping;
2337 return true;
2339 case AL_BUFFER:
2340 CheckSize(1);
2342 ALbufferQueueItem *BufferList{};
2343 /* HACK: This query should technically only return the buffer set
2344 * on a static source. However, some apps had used it to detect
2345 * when a streaming source changed buffers, so report the current
2346 * buffer's ID when playing.
2348 if(Source->SourceType == AL_STATIC || Source->state == AL_INITIAL)
2350 if(!Source->mQueue.empty())
2351 BufferList = &Source->mQueue.front();
2353 else if(Voice *voice{GetSourceVoice(Source, Context)})
2355 VoiceBufferItem *Current{voice->mCurrentBuffer.load(std::memory_order_relaxed)};
2356 BufferList = static_cast<ALbufferQueueItem*>(Current);
2358 ALbuffer *buffer{BufferList ? BufferList->mBuffer : nullptr};
2359 values[0] = buffer ? static_cast<int>(buffer->id) : 0;
2361 return true;
2363 case AL_SOURCE_STATE:
2364 CheckSize(1);
2365 values[0] = GetSourceState(Source, GetSourceVoice(Source, Context));
2366 return true;
2368 case AL_BUFFERS_QUEUED:
2369 CheckSize(1);
2370 values[0] = static_cast<int>(Source->mQueue.size());
2371 return true;
2373 case AL_BUFFERS_PROCESSED:
2374 CheckSize(1);
2375 if(Source->Looping || Source->SourceType != AL_STREAMING)
2377 /* Buffers on a looping source are in a perpetual state of PENDING,
2378 * so don't report any as PROCESSED
2380 values[0] = 0;
2382 else
2384 int played{0};
2385 if(Source->state != AL_INITIAL)
2387 const VoiceBufferItem *Current{nullptr};
2388 if(Voice *voice{GetSourceVoice(Source, Context)})
2389 Current = voice->mCurrentBuffer.load(std::memory_order_relaxed);
2390 for(auto &item : Source->mQueue)
2392 if(&item == Current)
2393 break;
2394 ++played;
2397 values[0] = played;
2399 return true;
2401 case AL_SOURCE_TYPE:
2402 CheckSize(1);
2403 values[0] = Source->SourceType;
2404 return true;
2406 case AL_DIRECT_FILTER_GAINHF_AUTO:
2407 CheckSize(1);
2408 values[0] = Source->DryGainHFAuto;
2409 return true;
2411 case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO:
2412 CheckSize(1);
2413 values[0] = Source->WetGainAuto;
2414 return true;
2416 case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO:
2417 CheckSize(1);
2418 values[0] = Source->WetGainHFAuto;
2419 return true;
2421 case AL_DIRECT_CHANNELS_SOFT:
2422 CheckSize(1);
2423 values[0] = EnumFromDirectMode(Source->DirectChannels);
2424 return true;
2426 case AL_DISTANCE_MODEL:
2427 CheckSize(1);
2428 values[0] = ALenumFromDistanceModel(Source->mDistanceModel);
2429 return true;
2431 case AL_BYTE_LENGTH_SOFT:
2432 case AL_SAMPLE_LENGTH_SOFT:
2433 case AL_SEC_LENGTH_SOFT:
2434 CheckSize(1);
2435 values[0] = static_cast<int>(mind(GetSourceLength(Source, prop),
2436 std::numeric_limits<int>::max()));
2437 return true;
2439 case AL_SOURCE_RESAMPLER_SOFT:
2440 CheckSize(1);
2441 values[0] = static_cast<int>(Source->mResampler);
2442 return true;
2444 case AL_SOURCE_SPATIALIZE_SOFT:
2445 CheckSize(1);
2446 values[0] = EnumFromSpatializeMode(Source->mSpatialize);
2447 return true;
2449 case AL_STEREO_MODE_SOFT:
2450 CheckSize(1);
2451 values[0] = EnumFromStereoMode(Source->mStereoMode);
2452 return true;
2454 case AL_SAMPLE_RW_OFFSETS_SOFT:
2455 if(sBufferSubDataCompat)
2457 CheckSize(2);
2458 const auto offset = GetSourceOffset(Source, AL_SAMPLE_OFFSET, Context);
2459 /* FIXME: values[1] should be ahead of values[0] by the device
2460 * update time. It needs to clamp or wrap the length of the buffer
2461 * queue.
2463 values[0] = static_cast<int>(mind(offset, std::numeric_limits<int>::max()));
2464 values[1] = values[0];
2465 return true;
2467 break;
2468 case AL_SOURCE_RADIUS: /*AL_BYTE_RW_OFFSETS_SOFT:*/
2469 if(sBufferSubDataCompat)
2471 CheckSize(2);
2472 const auto offset = GetSourceOffset(Source, AL_BYTE_OFFSET, Context);
2473 /* FIXME: values[1] should be ahead of values[0] by the device
2474 * update time. It needs to clamp or wrap the length of the buffer
2475 * queue.
2477 values[0] = static_cast<int>(mind(offset, std::numeric_limits<int>::max()));
2478 values[1] = values[0];
2479 return true;
2481 /*fall-through*/
2483 /* 1x float/double */
2484 case AL_CONE_INNER_ANGLE:
2485 case AL_CONE_OUTER_ANGLE:
2486 case AL_PITCH:
2487 case AL_GAIN:
2488 case AL_MIN_GAIN:
2489 case AL_MAX_GAIN:
2490 case AL_REFERENCE_DISTANCE:
2491 case AL_ROLLOFF_FACTOR:
2492 case AL_CONE_OUTER_GAIN:
2493 case AL_MAX_DISTANCE:
2494 case AL_SEC_OFFSET:
2495 case AL_SAMPLE_OFFSET:
2496 case AL_BYTE_OFFSET:
2497 case AL_DOPPLER_FACTOR:
2498 case AL_AIR_ABSORPTION_FACTOR:
2499 case AL_ROOM_ROLLOFF_FACTOR:
2500 case AL_CONE_OUTER_GAINHF:
2501 case AL_SUPER_STEREO_WIDTH_SOFT:
2502 CheckSize(1);
2503 if((err=GetSourcedv(Source, Context, prop, {dvals, 1u})) != false)
2504 values[0] = static_cast<int>(dvals[0]);
2505 return err;
2507 /* 3x float/double */
2508 case AL_POSITION:
2509 case AL_VELOCITY:
2510 case AL_DIRECTION:
2511 CheckSize(3);
2512 if((err=GetSourcedv(Source, Context, prop, {dvals, 3u})) != false)
2514 values[0] = static_cast<int>(dvals[0]);
2515 values[1] = static_cast<int>(dvals[1]);
2516 values[2] = static_cast<int>(dvals[2]);
2518 return err;
2520 /* 6x float/double */
2521 case AL_ORIENTATION:
2522 CheckSize(6);
2523 if((err=GetSourcedv(Source, Context, prop, {dvals, 6u})) != false)
2525 values[0] = static_cast<int>(dvals[0]);
2526 values[1] = static_cast<int>(dvals[1]);
2527 values[2] = static_cast<int>(dvals[2]);
2528 values[3] = static_cast<int>(dvals[3]);
2529 values[4] = static_cast<int>(dvals[4]);
2530 values[5] = static_cast<int>(dvals[5]);
2532 return err;
2534 case AL_SAMPLE_OFFSET_LATENCY_SOFT:
2535 case AL_SAMPLE_OFFSET_CLOCK_SOFT:
2536 break; /* i64 only */
2537 case AL_SEC_OFFSET_LATENCY_SOFT:
2538 case AL_SEC_OFFSET_CLOCK_SOFT:
2539 break; /* Double only */
2540 case AL_STEREO_ANGLES:
2541 break; /* Float/double only */
2543 case AL_DIRECT_FILTER:
2544 case AL_AUXILIARY_SEND_FILTER:
2545 break; /* ??? */
2548 ERR("Unexpected property: 0x%04x\n", prop);
2549 Context->setError(AL_INVALID_ENUM, "Invalid source integer property 0x%04x", prop);
2550 return false;
2552 catch(check_exception&) {
2553 return false;
2556 bool GetSourcei64v(ALsource *const Source, ALCcontext *const Context, const SourceProp prop,
2557 const al::span<int64_t> values)
2558 try {
2559 auto CheckSize = GetSizeChecker(Context, prop, values);
2560 ALCdevice *device{Context->mALDevice.get()};
2561 ClockLatency clocktime;
2562 nanoseconds srcclock;
2563 double dvals[MaxValues];
2564 int ivals[MaxValues];
2565 bool err;
2567 switch(prop)
2569 case AL_BYTE_LENGTH_SOFT:
2570 case AL_SAMPLE_LENGTH_SOFT:
2571 case AL_SEC_LENGTH_SOFT:
2572 CheckSize(1);
2573 values[0] = static_cast<int64_t>(GetSourceLength(Source, prop));
2574 return true;
2576 case AL_SAMPLE_OFFSET_LATENCY_SOFT:
2577 CheckSize(2);
2578 /* Get the source offset with the clock time first. Then get the clock
2579 * time with the device latency. Order is important.
2581 values[0] = GetSourceSampleOffset(Source, Context, &srcclock);
2583 std::lock_guard<std::mutex> _{device->StateLock};
2584 clocktime = GetClockLatency(device, device->Backend.get());
2586 if(srcclock == clocktime.ClockTime)
2587 values[1] = clocktime.Latency.count();
2588 else
2590 /* If the clock time incremented, reduce the latency by that much
2591 * since it's that much closer to the source offset it got earlier.
2593 const nanoseconds diff{clocktime.ClockTime - srcclock};
2594 values[1] = nanoseconds{clocktime.Latency - std::min(clocktime.Latency, diff)}.count();
2596 return true;
2598 case AL_SAMPLE_OFFSET_CLOCK_SOFT:
2599 CheckSize(2);
2600 values[0] = GetSourceSampleOffset(Source, Context, &srcclock);
2601 values[1] = srcclock.count();
2602 return true;
2604 case AL_SAMPLE_RW_OFFSETS_SOFT:
2605 if(sBufferSubDataCompat)
2607 CheckSize(2);
2608 /* FIXME: values[1] should be ahead of values[0] by the device
2609 * update time. It needs to clamp or wrap the length of the buffer
2610 * queue.
2612 values[0] = static_cast<int64_t>(GetSourceOffset(Source, AL_SAMPLE_OFFSET, Context));
2613 values[1] = values[0];
2614 return true;
2616 break;
2617 case AL_SOURCE_RADIUS: /*AL_BYTE_RW_OFFSETS_SOFT:*/
2618 if(sBufferSubDataCompat)
2620 CheckSize(2);
2621 /* FIXME: values[1] should be ahead of values[0] by the device
2622 * update time. It needs to clamp or wrap the length of the buffer
2623 * queue.
2625 values[0] = static_cast<int64_t>(GetSourceOffset(Source, AL_BYTE_OFFSET, Context));
2626 values[1] = values[0];
2627 return true;
2629 /*fall-through*/
2631 /* 1x float/double */
2632 case AL_CONE_INNER_ANGLE:
2633 case AL_CONE_OUTER_ANGLE:
2634 case AL_PITCH:
2635 case AL_GAIN:
2636 case AL_MIN_GAIN:
2637 case AL_MAX_GAIN:
2638 case AL_REFERENCE_DISTANCE:
2639 case AL_ROLLOFF_FACTOR:
2640 case AL_CONE_OUTER_GAIN:
2641 case AL_MAX_DISTANCE:
2642 case AL_SEC_OFFSET:
2643 case AL_SAMPLE_OFFSET:
2644 case AL_BYTE_OFFSET:
2645 case AL_DOPPLER_FACTOR:
2646 case AL_AIR_ABSORPTION_FACTOR:
2647 case AL_ROOM_ROLLOFF_FACTOR:
2648 case AL_CONE_OUTER_GAINHF:
2649 case AL_SUPER_STEREO_WIDTH_SOFT:
2650 CheckSize(1);
2651 if((err=GetSourcedv(Source, Context, prop, {dvals, 1u})) != false)
2652 values[0] = static_cast<int64_t>(dvals[0]);
2653 return err;
2655 /* 3x float/double */
2656 case AL_POSITION:
2657 case AL_VELOCITY:
2658 case AL_DIRECTION:
2659 CheckSize(3);
2660 if((err=GetSourcedv(Source, Context, prop, {dvals, 3u})) != false)
2662 values[0] = static_cast<int64_t>(dvals[0]);
2663 values[1] = static_cast<int64_t>(dvals[1]);
2664 values[2] = static_cast<int64_t>(dvals[2]);
2666 return err;
2668 /* 6x float/double */
2669 case AL_ORIENTATION:
2670 CheckSize(6);
2671 if((err=GetSourcedv(Source, Context, prop, {dvals, 6u})) != false)
2673 values[0] = static_cast<int64_t>(dvals[0]);
2674 values[1] = static_cast<int64_t>(dvals[1]);
2675 values[2] = static_cast<int64_t>(dvals[2]);
2676 values[3] = static_cast<int64_t>(dvals[3]);
2677 values[4] = static_cast<int64_t>(dvals[4]);
2678 values[5] = static_cast<int64_t>(dvals[5]);
2680 return err;
2682 /* 1x int */
2683 case AL_SOURCE_RELATIVE:
2684 case AL_LOOPING:
2685 case AL_SOURCE_STATE:
2686 case AL_BUFFERS_QUEUED:
2687 case AL_BUFFERS_PROCESSED:
2688 case AL_SOURCE_TYPE:
2689 case AL_DIRECT_FILTER_GAINHF_AUTO:
2690 case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO:
2691 case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO:
2692 case AL_DIRECT_CHANNELS_SOFT:
2693 case AL_DISTANCE_MODEL:
2694 case AL_SOURCE_RESAMPLER_SOFT:
2695 case AL_SOURCE_SPATIALIZE_SOFT:
2696 case AL_STEREO_MODE_SOFT:
2697 CheckSize(1);
2698 if((err=GetSourceiv(Source, Context, prop, {ivals, 1u})) != false)
2699 values[0] = ivals[0];
2700 return err;
2702 /* 1x uint */
2703 case AL_BUFFER:
2704 case AL_DIRECT_FILTER:
2705 CheckSize(1);
2706 if((err=GetSourceiv(Source, Context, prop, {ivals, 1u})) != false)
2707 values[0] = static_cast<ALuint>(ivals[0]);
2708 return err;
2710 /* 3x uint */
2711 case AL_AUXILIARY_SEND_FILTER:
2712 CheckSize(3);
2713 if((err=GetSourceiv(Source, Context, prop, {ivals, 3u})) != false)
2715 values[0] = static_cast<ALuint>(ivals[0]);
2716 values[1] = static_cast<ALuint>(ivals[1]);
2717 values[2] = static_cast<ALuint>(ivals[2]);
2719 return err;
2721 case AL_SEC_OFFSET_LATENCY_SOFT:
2722 case AL_SEC_OFFSET_CLOCK_SOFT:
2723 break; /* Double only */
2724 case AL_STEREO_ANGLES:
2725 break; /* Float/double only */
2728 ERR("Unexpected property: 0x%04x\n", prop);
2729 Context->setError(AL_INVALID_ENUM, "Invalid source integer64 property 0x%04x", prop);
2730 return false;
2732 catch(check_exception&) {
2733 return false;
2737 void StartSources(ALCcontext *const context, const al::span<ALsource*> srchandles,
2738 const nanoseconds start_time=nanoseconds::min())
2740 ALCdevice *device{context->mALDevice.get()};
2741 /* If the device is disconnected, and voices stop on disconnect, go right
2742 * to stopped.
2744 if(!device->Connected.load(std::memory_order_acquire)) UNLIKELY
2746 if(context->mStopVoicesOnDisconnect.load(std::memory_order_acquire))
2748 for(ALsource *source : srchandles)
2750 /* TODO: Send state change event? */
2751 source->Offset = 0.0;
2752 source->OffsetType = AL_NONE;
2753 source->state = AL_STOPPED;
2755 return;
2759 /* Count the number of reusable voices. */
2760 auto voicelist = context->getVoicesSpan();
2761 size_t free_voices{0};
2762 for(const Voice *voice : voicelist)
2764 free_voices += (voice->mPlayState.load(std::memory_order_acquire) == Voice::Stopped
2765 && voice->mSourceID.load(std::memory_order_relaxed) == 0u
2766 && voice->mPendingChange.load(std::memory_order_relaxed) == false);
2767 if(free_voices == srchandles.size())
2768 break;
2770 if(srchandles.size() != free_voices) UNLIKELY
2772 const size_t inc_amount{srchandles.size() - free_voices};
2773 auto &allvoices = *context->mVoices.load(std::memory_order_relaxed);
2774 if(inc_amount > allvoices.size() - voicelist.size())
2776 /* Increase the number of voices to handle the request. */
2777 context->allocVoices(inc_amount - (allvoices.size() - voicelist.size()));
2779 context->mActiveVoiceCount.fetch_add(inc_amount, std::memory_order_release);
2780 voicelist = context->getVoicesSpan();
2783 auto voiceiter = voicelist.begin();
2784 ALuint vidx{0};
2785 VoiceChange *tail{}, *cur{};
2786 for(ALsource *source : srchandles)
2788 /* Check that there is a queue containing at least one valid, non zero
2789 * length buffer.
2791 auto find_buffer = [](ALbufferQueueItem &entry) noexcept
2792 { return entry.mSampleLen != 0 || entry.mCallback != nullptr; };
2793 auto BufferList = std::find_if(source->mQueue.begin(), source->mQueue.end(), find_buffer);
2795 /* If there's nothing to play, go right to stopped. */
2796 if(BufferList == source->mQueue.end()) UNLIKELY
2798 /* NOTE: A source without any playable buffers should not have a
2799 * Voice since it shouldn't be in a playing or paused state. So
2800 * there's no need to look up its voice and clear the source.
2802 source->Offset = 0.0;
2803 source->OffsetType = AL_NONE;
2804 source->state = AL_STOPPED;
2805 continue;
2808 if(!cur)
2809 cur = tail = GetVoiceChanger(context);
2810 else
2812 cur->mNext.store(GetVoiceChanger(context), std::memory_order_relaxed);
2813 cur = cur->mNext.load(std::memory_order_relaxed);
2816 Voice *voice{GetSourceVoice(source, context)};
2817 switch(GetSourceState(source, voice))
2819 case AL_PAUSED:
2820 /* A source that's paused simply resumes. If there's no voice, it
2821 * was lost from a disconnect, so just start over with a new one.
2823 cur->mOldVoice = nullptr;
2824 if(!voice) break;
2825 cur->mVoice = voice;
2826 cur->mSourceID = source->id;
2827 cur->mState = VChangeState::Play;
2828 source->state = AL_PLAYING;
2829 #ifdef ALSOFT_EAX
2830 if(context->hasEax())
2831 source->eaxCommit();
2832 #endif // ALSOFT_EAX
2833 continue;
2835 case AL_PLAYING:
2836 /* A source that's already playing is restarted from the beginning.
2837 * Stop the current voice and start a new one so it properly cross-
2838 * fades back to the beginning.
2840 if(voice)
2841 voice->mPendingChange.store(true, std::memory_order_relaxed);
2842 cur->mOldVoice = voice;
2843 voice = nullptr;
2844 break;
2846 default:
2847 assert(voice == nullptr);
2848 cur->mOldVoice = nullptr;
2849 #ifdef ALSOFT_EAX
2850 if(context->hasEax())
2851 source->eaxCommit();
2852 #endif // ALSOFT_EAX
2853 break;
2856 /* Find the next unused voice to play this source with. */
2857 for(;voiceiter != voicelist.end();++voiceiter,++vidx)
2859 Voice *v{*voiceiter};
2860 if(v->mPlayState.load(std::memory_order_acquire) == Voice::Stopped
2861 && v->mSourceID.load(std::memory_order_relaxed) == 0u
2862 && v->mPendingChange.load(std::memory_order_relaxed) == false)
2864 voice = v;
2865 break;
2868 ASSUME(voice != nullptr);
2870 voice->mPosition.store(0, std::memory_order_relaxed);
2871 voice->mPositionFrac.store(0, std::memory_order_relaxed);
2872 voice->mCurrentBuffer.store(&source->mQueue.front(), std::memory_order_relaxed);
2873 voice->mStartTime = start_time;
2874 voice->mFlags.reset();
2875 /* A source that's not playing or paused has any offset applied when it
2876 * starts playing.
2878 if(const ALenum offsettype{source->OffsetType})
2880 const double offset{source->Offset};
2881 source->OffsetType = AL_NONE;
2882 source->Offset = 0.0;
2883 if(auto vpos = GetSampleOffset(source->mQueue, offsettype, offset))
2885 voice->mPosition.store(vpos->pos, std::memory_order_relaxed);
2886 voice->mPositionFrac.store(vpos->frac, std::memory_order_relaxed);
2887 voice->mCurrentBuffer.store(vpos->bufferitem, std::memory_order_relaxed);
2888 if(vpos->pos > 0 || (vpos->pos == 0 && vpos->frac > 0)
2889 || vpos->bufferitem != &source->mQueue.front())
2890 voice->mFlags.set(VoiceIsFading);
2893 InitVoice(voice, source, al::to_address(BufferList), context, device);
2895 source->VoiceIdx = vidx;
2896 source->state = AL_PLAYING;
2898 cur->mVoice = voice;
2899 cur->mSourceID = source->id;
2900 cur->mState = VChangeState::Play;
2902 if(tail) LIKELY
2903 SendVoiceChanges(context, tail);
2906 } // namespace
2908 AL_API void AL_APIENTRY alGenSources(ALsizei n, ALuint *sources)
2909 START_API_FUNC
2911 ContextRef context{GetContextRef()};
2912 if(!context) UNLIKELY return;
2914 if(n < 0) UNLIKELY
2915 context->setError(AL_INVALID_VALUE, "Generating %d sources", n);
2916 if(n <= 0) UNLIKELY return;
2918 std::unique_lock<std::mutex> srclock{context->mSourceLock};
2919 ALCdevice *device{context->mALDevice.get()};
2920 if(static_cast<ALuint>(n) > device->SourcesMax-context->mNumSources)
2922 context->setError(AL_OUT_OF_MEMORY, "Exceeding %u source limit (%u + %d)",
2923 device->SourcesMax, context->mNumSources, n);
2924 return;
2926 if(!EnsureSources(context.get(), static_cast<ALuint>(n)))
2928 context->setError(AL_OUT_OF_MEMORY, "Failed to allocate %d source%s", n, (n==1)?"":"s");
2929 return;
2932 if(n == 1)
2934 ALsource *source{AllocSource(context.get())};
2935 sources[0] = source->id;
2937 #ifdef ALSOFT_EAX
2938 source->eaxInitialize(context.get());
2939 #endif // ALSOFT_EAX
2941 else
2943 al::vector<ALuint> ids;
2944 ids.reserve(static_cast<ALuint>(n));
2945 do {
2946 ALsource *source{AllocSource(context.get())};
2947 ids.emplace_back(source->id);
2949 #ifdef ALSOFT_EAX
2950 source->eaxInitialize(context.get());
2951 #endif // ALSOFT_EAX
2952 } while(--n);
2953 std::copy(ids.cbegin(), ids.cend(), sources);
2956 END_API_FUNC
2958 AL_API void AL_APIENTRY alDeleteSources(ALsizei n, const ALuint *sources)
2959 START_API_FUNC
2961 ContextRef context{GetContextRef()};
2962 if(!context) UNLIKELY return;
2964 if(n < 0) UNLIKELY
2965 context->setError(AL_INVALID_VALUE, "Deleting %d sources", n);
2966 if(n <= 0) UNLIKELY return;
2968 std::lock_guard<std::mutex> _{context->mSourceLock};
2970 /* Check that all Sources are valid */
2971 auto validate_source = [&context](const ALuint sid) -> bool
2972 { return LookupSource(context.get(), sid) != nullptr; };
2974 const ALuint *sources_end = sources + n;
2975 auto invsrc = std::find_if_not(sources, sources_end, validate_source);
2976 if(invsrc != sources_end) UNLIKELY
2977 return context->setError(AL_INVALID_NAME, "Invalid source ID %u", *invsrc);
2979 /* All good. Delete source IDs. */
2980 auto delete_source = [&context](const ALuint sid) -> void
2982 ALsource *src{LookupSource(context.get(), sid)};
2983 if(src) FreeSource(context.get(), src);
2985 std::for_each(sources, sources_end, delete_source);
2987 END_API_FUNC
2989 AL_API ALboolean AL_APIENTRY alIsSource(ALuint source)
2990 START_API_FUNC
2992 ContextRef context{GetContextRef()};
2993 if(context) LIKELY
2995 std::lock_guard<std::mutex> _{context->mSourceLock};
2996 if(LookupSource(context.get(), source) != nullptr)
2997 return AL_TRUE;
2999 return AL_FALSE;
3001 END_API_FUNC
3004 AL_API void AL_APIENTRY alSourcef(ALuint source, ALenum param, ALfloat value)
3005 START_API_FUNC
3007 ContextRef context{GetContextRef()};
3008 if(!context) UNLIKELY return;
3010 std::lock_guard<std::mutex> _{context->mPropLock};
3011 std::lock_guard<std::mutex> __{context->mSourceLock};
3012 ALsource *Source = LookupSource(context.get(), source);
3013 if(!Source) UNLIKELY
3014 context->setError(AL_INVALID_NAME, "Invalid source ID %u", source);
3015 else
3016 SetSourcefv(Source, context.get(), static_cast<SourceProp>(param), {&value, 1u});
3018 END_API_FUNC
3020 AL_API void AL_APIENTRY alSource3f(ALuint source, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3)
3021 START_API_FUNC
3023 ContextRef context{GetContextRef()};
3024 if(!context) UNLIKELY return;
3026 std::lock_guard<std::mutex> _{context->mPropLock};
3027 std::lock_guard<std::mutex> __{context->mSourceLock};
3028 ALsource *Source = LookupSource(context.get(), source);
3029 if(!Source) UNLIKELY
3030 context->setError(AL_INVALID_NAME, "Invalid source ID %u", source);
3031 else
3033 const float fvals[3]{ value1, value2, value3 };
3034 SetSourcefv(Source, context.get(), static_cast<SourceProp>(param), fvals);
3037 END_API_FUNC
3039 AL_API void AL_APIENTRY alSourcefv(ALuint source, ALenum param, const ALfloat *values)
3040 START_API_FUNC
3042 ContextRef context{GetContextRef()};
3043 if(!context) UNLIKELY return;
3045 std::lock_guard<std::mutex> _{context->mPropLock};
3046 std::lock_guard<std::mutex> __{context->mSourceLock};
3047 ALsource *Source = LookupSource(context.get(), source);
3048 if(!Source) UNLIKELY
3049 return context->setError(AL_INVALID_NAME, "Invalid source ID %u", source);
3050 if(!values) UNLIKELY
3051 return context->setError(AL_INVALID_VALUE, "NULL pointer");
3053 const ALuint count{FloatValsByProp(param)};
3054 SetSourcefv(Source, context.get(), static_cast<SourceProp>(param), {values, count});
3056 END_API_FUNC
3059 AL_API void AL_APIENTRY alSourcedSOFT(ALuint source, ALenum param, ALdouble value)
3060 START_API_FUNC
3062 ContextRef context{GetContextRef()};
3063 if(!context) UNLIKELY return;
3065 std::lock_guard<std::mutex> _{context->mPropLock};
3066 std::lock_guard<std::mutex> __{context->mSourceLock};
3067 ALsource *Source = LookupSource(context.get(), source);
3068 if(!Source) UNLIKELY
3069 context->setError(AL_INVALID_NAME, "Invalid source ID %u", source);
3070 else
3072 const float fval[1]{static_cast<float>(value)};
3073 SetSourcefv(Source, context.get(), static_cast<SourceProp>(param), fval);
3076 END_API_FUNC
3078 AL_API void AL_APIENTRY alSource3dSOFT(ALuint source, ALenum param, ALdouble value1, ALdouble value2, ALdouble value3)
3079 START_API_FUNC
3081 ContextRef context{GetContextRef()};
3082 if(!context) UNLIKELY return;
3084 std::lock_guard<std::mutex> _{context->mPropLock};
3085 std::lock_guard<std::mutex> __{context->mSourceLock};
3086 ALsource *Source = LookupSource(context.get(), source);
3087 if(!Source) UNLIKELY
3088 context->setError(AL_INVALID_NAME, "Invalid source ID %u", source);
3089 else
3091 const float fvals[3]{static_cast<float>(value1), static_cast<float>(value2),
3092 static_cast<float>(value3)};
3093 SetSourcefv(Source, context.get(), static_cast<SourceProp>(param), fvals);
3096 END_API_FUNC
3098 AL_API void AL_APIENTRY alSourcedvSOFT(ALuint source, ALenum param, const ALdouble *values)
3099 START_API_FUNC
3101 ContextRef context{GetContextRef()};
3102 if(!context) UNLIKELY return;
3104 std::lock_guard<std::mutex> _{context->mPropLock};
3105 std::lock_guard<std::mutex> __{context->mSourceLock};
3106 ALsource *Source = LookupSource(context.get(), source);
3107 if(!Source) UNLIKELY
3108 return context->setError(AL_INVALID_NAME, "Invalid source ID %u", source);
3109 if(!values) UNLIKELY
3110 return context->setError(AL_INVALID_VALUE, "NULL pointer");
3112 const ALuint count{DoubleValsByProp(param)};
3113 float fvals[MaxValues];
3114 std::copy_n(values, count, fvals);
3115 SetSourcefv(Source, context.get(), static_cast<SourceProp>(param), {fvals, count});
3117 END_API_FUNC
3120 AL_API void AL_APIENTRY alSourcei(ALuint source, ALenum param, ALint value)
3121 START_API_FUNC
3123 ContextRef context{GetContextRef()};
3124 if(!context) UNLIKELY return;
3126 std::lock_guard<std::mutex> _{context->mPropLock};
3127 std::lock_guard<std::mutex> __{context->mSourceLock};
3128 ALsource *Source = LookupSource(context.get(), source);
3129 if(!Source) UNLIKELY
3130 context->setError(AL_INVALID_NAME, "Invalid source ID %u", source);
3131 else
3132 SetSourceiv(Source, context.get(), static_cast<SourceProp>(param), {&value, 1u});
3134 END_API_FUNC
3136 AL_API void AL_APIENTRY alSource3i(ALuint source, ALenum param, ALint value1, ALint value2, ALint value3)
3137 START_API_FUNC
3139 ContextRef context{GetContextRef()};
3140 if(!context) UNLIKELY return;
3142 std::lock_guard<std::mutex> _{context->mPropLock};
3143 std::lock_guard<std::mutex> __{context->mSourceLock};
3144 ALsource *Source = LookupSource(context.get(), source);
3145 if(!Source) UNLIKELY
3146 context->setError(AL_INVALID_NAME, "Invalid source ID %u", source);
3147 else
3149 const int ivals[3]{ value1, value2, value3 };
3150 SetSourceiv(Source, context.get(), static_cast<SourceProp>(param), ivals);
3153 END_API_FUNC
3155 AL_API void AL_APIENTRY alSourceiv(ALuint source, ALenum param, const ALint *values)
3156 START_API_FUNC
3158 ContextRef context{GetContextRef()};
3159 if(!context) UNLIKELY return;
3161 std::lock_guard<std::mutex> _{context->mPropLock};
3162 std::lock_guard<std::mutex> __{context->mSourceLock};
3163 ALsource *Source = LookupSource(context.get(), source);
3164 if(!Source) UNLIKELY
3165 return context->setError(AL_INVALID_NAME, "Invalid source ID %u", source);
3166 if(!values) UNLIKELY
3167 return context->setError(AL_INVALID_VALUE, "NULL pointer");
3169 const ALuint count{IntValsByProp(param)};
3170 SetSourceiv(Source, context.get(), static_cast<SourceProp>(param), {values, count});
3172 END_API_FUNC
3175 AL_API void AL_APIENTRY alSourcei64SOFT(ALuint source, ALenum param, ALint64SOFT value)
3176 START_API_FUNC
3178 ContextRef context{GetContextRef()};
3179 if(!context) UNLIKELY return;
3181 std::lock_guard<std::mutex> _{context->mPropLock};
3182 std::lock_guard<std::mutex> __{context->mSourceLock};
3183 ALsource *Source{LookupSource(context.get(), source)};
3184 if(!Source) UNLIKELY
3185 context->setError(AL_INVALID_NAME, "Invalid source ID %u", source);
3186 else
3187 SetSourcei64v(Source, context.get(), static_cast<SourceProp>(param), {&value, 1u});
3189 END_API_FUNC
3191 AL_API void AL_APIENTRY alSource3i64SOFT(ALuint source, ALenum param, ALint64SOFT value1, ALint64SOFT value2, ALint64SOFT value3)
3192 START_API_FUNC
3194 ContextRef context{GetContextRef()};
3195 if(!context) UNLIKELY return;
3197 std::lock_guard<std::mutex> _{context->mPropLock};
3198 std::lock_guard<std::mutex> __{context->mSourceLock};
3199 ALsource *Source{LookupSource(context.get(), source)};
3200 if(!Source) UNLIKELY
3201 context->setError(AL_INVALID_NAME, "Invalid source ID %u", source);
3202 else
3204 const int64_t i64vals[3]{ value1, value2, value3 };
3205 SetSourcei64v(Source, context.get(), static_cast<SourceProp>(param), i64vals);
3208 END_API_FUNC
3210 AL_API void AL_APIENTRY alSourcei64vSOFT(ALuint source, ALenum param, const ALint64SOFT *values)
3211 START_API_FUNC
3213 ContextRef context{GetContextRef()};
3214 if(!context) UNLIKELY return;
3216 std::lock_guard<std::mutex> _{context->mPropLock};
3217 std::lock_guard<std::mutex> __{context->mSourceLock};
3218 ALsource *Source{LookupSource(context.get(), source)};
3219 if(!Source) UNLIKELY
3220 return context->setError(AL_INVALID_NAME, "Invalid source ID %u", source);
3221 if(!values) UNLIKELY
3222 return context->setError(AL_INVALID_VALUE, "NULL pointer");
3224 const ALuint count{Int64ValsByProp(param)};
3225 SetSourcei64v(Source, context.get(), static_cast<SourceProp>(param), {values, count});
3227 END_API_FUNC
3230 AL_API void AL_APIENTRY alGetSourcef(ALuint source, ALenum param, ALfloat *value)
3231 START_API_FUNC
3233 ContextRef context{GetContextRef()};
3234 if(!context) UNLIKELY return;
3236 std::lock_guard<std::mutex> _{context->mSourceLock};
3237 ALsource *Source{LookupSource(context.get(), source)};
3238 if(!Source) UNLIKELY
3239 context->setError(AL_INVALID_NAME, "Invalid source ID %u", source);
3240 else if(!value) UNLIKELY
3241 context->setError(AL_INVALID_VALUE, "NULL pointer");
3242 else
3244 double dval[1];
3245 if(GetSourcedv(Source, context.get(), static_cast<SourceProp>(param), dval))
3246 *value = static_cast<float>(dval[0]);
3249 END_API_FUNC
3251 AL_API void AL_APIENTRY alGetSource3f(ALuint source, ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3)
3252 START_API_FUNC
3254 ContextRef context{GetContextRef()};
3255 if(!context) UNLIKELY return;
3257 std::lock_guard<std::mutex> _{context->mSourceLock};
3258 ALsource *Source{LookupSource(context.get(), source)};
3259 if(!Source) UNLIKELY
3260 context->setError(AL_INVALID_NAME, "Invalid source ID %u", source);
3261 else if(!(value1 && value2 && value3)) UNLIKELY
3262 context->setError(AL_INVALID_VALUE, "NULL pointer");
3263 else
3265 double dvals[3];
3266 if(GetSourcedv(Source, context.get(), static_cast<SourceProp>(param), dvals))
3268 *value1 = static_cast<float>(dvals[0]);
3269 *value2 = static_cast<float>(dvals[1]);
3270 *value3 = static_cast<float>(dvals[2]);
3274 END_API_FUNC
3276 AL_API void AL_APIENTRY alGetSourcefv(ALuint source, ALenum param, ALfloat *values)
3277 START_API_FUNC
3279 ContextRef context{GetContextRef()};
3280 if(!context) UNLIKELY return;
3282 std::lock_guard<std::mutex> _{context->mSourceLock};
3283 ALsource *Source{LookupSource(context.get(), source)};
3284 if(!Source) UNLIKELY
3285 return context->setError(AL_INVALID_NAME, "Invalid source ID %u", source);
3286 if(!values) UNLIKELY
3287 return context->setError(AL_INVALID_VALUE, "NULL pointer");
3289 const ALuint count{FloatValsByProp(param)};
3290 double dvals[MaxValues];
3291 if(GetSourcedv(Source, context.get(), static_cast<SourceProp>(param), {dvals, count}))
3292 std::copy_n(dvals, count, values);
3294 END_API_FUNC
3297 AL_API void AL_APIENTRY alGetSourcedSOFT(ALuint source, ALenum param, ALdouble *value)
3298 START_API_FUNC
3300 ContextRef context{GetContextRef()};
3301 if(!context) UNLIKELY return;
3303 std::lock_guard<std::mutex> _{context->mSourceLock};
3304 ALsource *Source{LookupSource(context.get(), source)};
3305 if(!Source) UNLIKELY
3306 context->setError(AL_INVALID_NAME, "Invalid source ID %u", source);
3307 else if(!value) UNLIKELY
3308 context->setError(AL_INVALID_VALUE, "NULL pointer");
3309 else
3310 GetSourcedv(Source, context.get(), static_cast<SourceProp>(param), {value, 1u});
3312 END_API_FUNC
3314 AL_API void AL_APIENTRY alGetSource3dSOFT(ALuint source, ALenum param, ALdouble *value1, ALdouble *value2, ALdouble *value3)
3315 START_API_FUNC
3317 ContextRef context{GetContextRef()};
3318 if(!context) UNLIKELY return;
3320 std::lock_guard<std::mutex> _{context->mSourceLock};
3321 ALsource *Source{LookupSource(context.get(), source)};
3322 if(!Source) UNLIKELY
3323 context->setError(AL_INVALID_NAME, "Invalid source ID %u", source);
3324 else if(!(value1 && value2 && value3)) UNLIKELY
3325 context->setError(AL_INVALID_VALUE, "NULL pointer");
3326 else
3328 double dvals[3];
3329 if(GetSourcedv(Source, context.get(), static_cast<SourceProp>(param), dvals))
3331 *value1 = dvals[0];
3332 *value2 = dvals[1];
3333 *value3 = dvals[2];
3337 END_API_FUNC
3339 AL_API void AL_APIENTRY alGetSourcedvSOFT(ALuint source, ALenum param, ALdouble *values)
3340 START_API_FUNC
3342 ContextRef context{GetContextRef()};
3343 if(!context) UNLIKELY return;
3345 std::lock_guard<std::mutex> _{context->mSourceLock};
3346 ALsource *Source{LookupSource(context.get(), source)};
3347 if(!Source) UNLIKELY
3348 return context->setError(AL_INVALID_NAME, "Invalid source ID %u", source);
3349 if(!values) UNLIKELY
3350 return context->setError(AL_INVALID_VALUE, "NULL pointer");
3352 const ALuint count{DoubleValsByProp(param)};
3353 GetSourcedv(Source, context.get(), static_cast<SourceProp>(param), {values, count});
3355 END_API_FUNC
3358 AL_API void AL_APIENTRY alGetSourcei(ALuint source, ALenum param, ALint *value)
3359 START_API_FUNC
3361 ContextRef context{GetContextRef()};
3362 if(!context) UNLIKELY return;
3364 std::lock_guard<std::mutex> _{context->mSourceLock};
3365 ALsource *Source{LookupSource(context.get(), source)};
3366 if(!Source) UNLIKELY
3367 context->setError(AL_INVALID_NAME, "Invalid source ID %u", source);
3368 else if(!value) UNLIKELY
3369 context->setError(AL_INVALID_VALUE, "NULL pointer");
3370 else
3371 GetSourceiv(Source, context.get(), static_cast<SourceProp>(param), {value, 1u});
3373 END_API_FUNC
3375 AL_API void AL_APIENTRY alGetSource3i(ALuint source, ALenum param, ALint *value1, ALint *value2, ALint *value3)
3376 START_API_FUNC
3378 ContextRef context{GetContextRef()};
3379 if(!context) UNLIKELY return;
3381 std::lock_guard<std::mutex> _{context->mSourceLock};
3382 ALsource *Source{LookupSource(context.get(), source)};
3383 if(!Source) UNLIKELY
3384 context->setError(AL_INVALID_NAME, "Invalid source ID %u", source);
3385 else if(!(value1 && value2 && value3)) UNLIKELY
3386 context->setError(AL_INVALID_VALUE, "NULL pointer");
3387 else
3389 int ivals[3];
3390 if(GetSourceiv(Source, context.get(), static_cast<SourceProp>(param), ivals))
3392 *value1 = ivals[0];
3393 *value2 = ivals[1];
3394 *value3 = ivals[2];
3398 END_API_FUNC
3400 AL_API void AL_APIENTRY alGetSourceiv(ALuint source, ALenum param, ALint *values)
3401 START_API_FUNC
3403 ContextRef context{GetContextRef()};
3404 if(!context) UNLIKELY return;
3406 std::lock_guard<std::mutex> _{context->mSourceLock};
3407 ALsource *Source{LookupSource(context.get(), source)};
3408 if(!Source) UNLIKELY
3409 return context->setError(AL_INVALID_NAME, "Invalid source ID %u", source);
3410 if(!values) UNLIKELY
3411 return context->setError(AL_INVALID_VALUE, "NULL pointer");
3413 const ALuint count{IntValsByProp(param)};
3414 GetSourceiv(Source, context.get(), static_cast<SourceProp>(param), {values, count});
3416 END_API_FUNC
3419 AL_API void AL_APIENTRY alGetSourcei64SOFT(ALuint source, ALenum param, ALint64SOFT *value)
3420 START_API_FUNC
3422 ContextRef context{GetContextRef()};
3423 if(!context) UNLIKELY return;
3425 std::lock_guard<std::mutex> _{context->mSourceLock};
3426 ALsource *Source{LookupSource(context.get(), source)};
3427 if(!Source) UNLIKELY
3428 context->setError(AL_INVALID_NAME, "Invalid source ID %u", source);
3429 else if(!value) UNLIKELY
3430 context->setError(AL_INVALID_VALUE, "NULL pointer");
3431 else
3432 GetSourcei64v(Source, context.get(), static_cast<SourceProp>(param), {value, 1u});
3434 END_API_FUNC
3436 AL_API void AL_APIENTRY alGetSource3i64SOFT(ALuint source, ALenum param, ALint64SOFT *value1, ALint64SOFT *value2, ALint64SOFT *value3)
3437 START_API_FUNC
3439 ContextRef context{GetContextRef()};
3440 if(!context) UNLIKELY return;
3442 std::lock_guard<std::mutex> _{context->mSourceLock};
3443 ALsource *Source{LookupSource(context.get(), source)};
3444 if(!Source) UNLIKELY
3445 context->setError(AL_INVALID_NAME, "Invalid source ID %u", source);
3446 else if(!(value1 && value2 && value3)) UNLIKELY
3447 context->setError(AL_INVALID_VALUE, "NULL pointer");
3448 else
3450 int64_t i64vals[3];
3451 if(GetSourcei64v(Source, context.get(), static_cast<SourceProp>(param), i64vals))
3453 *value1 = i64vals[0];
3454 *value2 = i64vals[1];
3455 *value3 = i64vals[2];
3459 END_API_FUNC
3461 AL_API void AL_APIENTRY alGetSourcei64vSOFT(ALuint source, ALenum param, ALint64SOFT *values)
3462 START_API_FUNC
3464 ContextRef context{GetContextRef()};
3465 if(!context) UNLIKELY return;
3467 std::lock_guard<std::mutex> _{context->mSourceLock};
3468 ALsource *Source{LookupSource(context.get(), source)};
3469 if(!Source) UNLIKELY
3470 return context->setError(AL_INVALID_NAME, "Invalid source ID %u", source);
3471 if(!values) UNLIKELY
3472 return context->setError(AL_INVALID_VALUE, "NULL pointer");
3474 const ALuint count{Int64ValsByProp(param)};
3475 GetSourcei64v(Source, context.get(), static_cast<SourceProp>(param), {values, count});
3477 END_API_FUNC
3480 AL_API void AL_APIENTRY alSourcePlay(ALuint source)
3481 START_API_FUNC
3483 ContextRef context{GetContextRef()};
3484 if(!context) UNLIKELY return;
3486 std::lock_guard<std::mutex> _{context->mSourceLock};
3487 ALsource *srchandle{LookupSource(context.get(), source)};
3488 if(!srchandle)
3489 return context->setError(AL_INVALID_NAME, "Invalid source ID %u", source);
3491 StartSources(context.get(), {&srchandle, 1});
3493 END_API_FUNC
3495 void AL_APIENTRY alSourcePlayAtTimeSOFT(ALuint source, ALint64SOFT start_time)
3496 START_API_FUNC
3498 ContextRef context{GetContextRef()};
3499 if(!context) UNLIKELY return;
3501 if(start_time < 0) UNLIKELY
3502 return context->setError(AL_INVALID_VALUE, "Invalid time point %" PRId64, start_time);
3504 std::lock_guard<std::mutex> _{context->mSourceLock};
3505 ALsource *srchandle{LookupSource(context.get(), source)};
3506 if(!srchandle)
3507 return context->setError(AL_INVALID_NAME, "Invalid source ID %u", source);
3509 StartSources(context.get(), {&srchandle, 1}, nanoseconds{start_time});
3511 END_API_FUNC
3513 AL_API void AL_APIENTRY alSourcePlayv(ALsizei n, const ALuint *sources)
3514 START_API_FUNC
3516 ContextRef context{GetContextRef()};
3517 if(!context) UNLIKELY return;
3519 if(n < 0) UNLIKELY
3520 context->setError(AL_INVALID_VALUE, "Playing %d sources", n);
3521 if(n <= 0) UNLIKELY return;
3523 al::vector<ALsource*> extra_sources;
3524 std::array<ALsource*,8> source_storage;
3525 al::span<ALsource*> srchandles;
3526 if(static_cast<ALuint>(n) <= source_storage.size()) LIKELY
3527 srchandles = {source_storage.data(), static_cast<ALuint>(n)};
3528 else
3530 extra_sources.resize(static_cast<ALuint>(n));
3531 srchandles = {extra_sources.data(), extra_sources.size()};
3534 std::lock_guard<std::mutex> _{context->mSourceLock};
3535 for(auto &srchdl : srchandles)
3537 srchdl = LookupSource(context.get(), *sources);
3538 if(!srchdl) UNLIKELY
3539 return context->setError(AL_INVALID_NAME, "Invalid source ID %u", *sources);
3540 ++sources;
3543 StartSources(context.get(), srchandles);
3545 END_API_FUNC
3547 void AL_APIENTRY alSourcePlayAtTimevSOFT(ALsizei n, const ALuint *sources, ALint64SOFT start_time)
3548 START_API_FUNC
3550 ContextRef context{GetContextRef()};
3551 if(!context) UNLIKELY return;
3553 if(n < 0) UNLIKELY
3554 context->setError(AL_INVALID_VALUE, "Playing %d sources", n);
3555 if(n <= 0) UNLIKELY return;
3557 if(start_time < 0) UNLIKELY
3558 return context->setError(AL_INVALID_VALUE, "Invalid time point %" PRId64, start_time);
3560 al::vector<ALsource*> extra_sources;
3561 std::array<ALsource*,8> source_storage;
3562 al::span<ALsource*> srchandles;
3563 if(static_cast<ALuint>(n) <= source_storage.size()) LIKELY
3564 srchandles = {source_storage.data(), static_cast<ALuint>(n)};
3565 else
3567 extra_sources.resize(static_cast<ALuint>(n));
3568 srchandles = {extra_sources.data(), extra_sources.size()};
3571 std::lock_guard<std::mutex> _{context->mSourceLock};
3572 for(auto &srchdl : srchandles)
3574 srchdl = LookupSource(context.get(), *sources);
3575 if(!srchdl)
3576 return context->setError(AL_INVALID_NAME, "Invalid source ID %u", *sources);
3577 ++sources;
3580 StartSources(context.get(), srchandles, nanoseconds{start_time});
3582 END_API_FUNC
3585 AL_API void AL_APIENTRY alSourcePause(ALuint source)
3586 START_API_FUNC
3587 { alSourcePausev(1, &source); }
3588 END_API_FUNC
3590 AL_API void AL_APIENTRY alSourcePausev(ALsizei n, const ALuint *sources)
3591 START_API_FUNC
3593 ContextRef context{GetContextRef()};
3594 if(!context) UNLIKELY return;
3596 if(n < 0) UNLIKELY
3597 context->setError(AL_INVALID_VALUE, "Pausing %d sources", n);
3598 if(n <= 0) UNLIKELY return;
3600 al::vector<ALsource*> extra_sources;
3601 std::array<ALsource*,8> source_storage;
3602 al::span<ALsource*> srchandles;
3603 if(static_cast<ALuint>(n) <= source_storage.size()) LIKELY
3604 srchandles = {source_storage.data(), static_cast<ALuint>(n)};
3605 else
3607 extra_sources.resize(static_cast<ALuint>(n));
3608 srchandles = {extra_sources.data(), extra_sources.size()};
3611 std::lock_guard<std::mutex> _{context->mSourceLock};
3612 for(auto &srchdl : srchandles)
3614 srchdl = LookupSource(context.get(), *sources);
3615 if(!srchdl)
3616 return context->setError(AL_INVALID_NAME, "Invalid source ID %u", *sources);
3617 ++sources;
3620 /* Pausing has to be done in two steps. First, for each source that's
3621 * detected to be playing, chamge the voice (asynchronously) to
3622 * stopping/paused.
3624 VoiceChange *tail{}, *cur{};
3625 for(ALsource *source : srchandles)
3627 Voice *voice{GetSourceVoice(source, context.get())};
3628 if(GetSourceState(source, voice) == AL_PLAYING)
3630 if(!cur)
3631 cur = tail = GetVoiceChanger(context.get());
3632 else
3634 cur->mNext.store(GetVoiceChanger(context.get()), std::memory_order_relaxed);
3635 cur = cur->mNext.load(std::memory_order_relaxed);
3637 cur->mVoice = voice;
3638 cur->mSourceID = source->id;
3639 cur->mState = VChangeState::Pause;
3642 if(tail) LIKELY
3644 SendVoiceChanges(context.get(), tail);
3645 /* Second, now that the voice changes have been sent, because it's
3646 * possible that the voice stopped after it was detected playing and
3647 * before the voice got paused, recheck that the source is still
3648 * considered playing and set it to paused if so.
3650 for(ALsource *source : srchandles)
3652 Voice *voice{GetSourceVoice(source, context.get())};
3653 if(GetSourceState(source, voice) == AL_PLAYING)
3654 source->state = AL_PAUSED;
3658 END_API_FUNC
3661 AL_API void AL_APIENTRY alSourceStop(ALuint source)
3662 START_API_FUNC
3663 { alSourceStopv(1, &source); }
3664 END_API_FUNC
3666 AL_API void AL_APIENTRY alSourceStopv(ALsizei n, const ALuint *sources)
3667 START_API_FUNC
3669 ContextRef context{GetContextRef()};
3670 if(!context) UNLIKELY return;
3672 if(n < 0) UNLIKELY
3673 context->setError(AL_INVALID_VALUE, "Stopping %d sources", n);
3674 if(n <= 0) UNLIKELY return;
3676 al::vector<ALsource*> extra_sources;
3677 std::array<ALsource*,8> source_storage;
3678 al::span<ALsource*> srchandles;
3679 if(static_cast<ALuint>(n) <= source_storage.size()) LIKELY
3680 srchandles = {source_storage.data(), static_cast<ALuint>(n)};
3681 else
3683 extra_sources.resize(static_cast<ALuint>(n));
3684 srchandles = {extra_sources.data(), extra_sources.size()};
3687 std::lock_guard<std::mutex> _{context->mSourceLock};
3688 for(auto &srchdl : srchandles)
3690 srchdl = LookupSource(context.get(), *sources);
3691 if(!srchdl)
3692 return context->setError(AL_INVALID_NAME, "Invalid source ID %u", *sources);
3693 ++sources;
3696 VoiceChange *tail{}, *cur{};
3697 for(ALsource *source : srchandles)
3699 if(Voice *voice{GetSourceVoice(source, context.get())})
3701 if(!cur)
3702 cur = tail = GetVoiceChanger(context.get());
3703 else
3705 cur->mNext.store(GetVoiceChanger(context.get()), std::memory_order_relaxed);
3706 cur = cur->mNext.load(std::memory_order_relaxed);
3708 voice->mPendingChange.store(true, std::memory_order_relaxed);
3709 cur->mVoice = voice;
3710 cur->mSourceID = source->id;
3711 cur->mState = VChangeState::Stop;
3712 source->state = AL_STOPPED;
3714 source->Offset = 0.0;
3715 source->OffsetType = AL_NONE;
3716 source->VoiceIdx = INVALID_VOICE_IDX;
3718 if(tail) LIKELY
3719 SendVoiceChanges(context.get(), tail);
3721 END_API_FUNC
3724 AL_API void AL_APIENTRY alSourceRewind(ALuint source)
3725 START_API_FUNC
3726 { alSourceRewindv(1, &source); }
3727 END_API_FUNC
3729 AL_API void AL_APIENTRY alSourceRewindv(ALsizei n, const ALuint *sources)
3730 START_API_FUNC
3732 ContextRef context{GetContextRef()};
3733 if(!context) UNLIKELY return;
3735 if(n < 0) UNLIKELY
3736 context->setError(AL_INVALID_VALUE, "Rewinding %d sources", n);
3737 if(n <= 0) UNLIKELY return;
3739 al::vector<ALsource*> extra_sources;
3740 std::array<ALsource*,8> source_storage;
3741 al::span<ALsource*> srchandles;
3742 if(static_cast<ALuint>(n) <= source_storage.size()) LIKELY
3743 srchandles = {source_storage.data(), static_cast<ALuint>(n)};
3744 else
3746 extra_sources.resize(static_cast<ALuint>(n));
3747 srchandles = {extra_sources.data(), extra_sources.size()};
3750 std::lock_guard<std::mutex> _{context->mSourceLock};
3751 for(auto &srchdl : srchandles)
3753 srchdl = LookupSource(context.get(), *sources);
3754 if(!srchdl)
3755 return context->setError(AL_INVALID_NAME, "Invalid source ID %u", *sources);
3756 ++sources;
3759 VoiceChange *tail{}, *cur{};
3760 for(ALsource *source : srchandles)
3762 Voice *voice{GetSourceVoice(source, context.get())};
3763 if(source->state != AL_INITIAL)
3765 if(!cur)
3766 cur = tail = GetVoiceChanger(context.get());
3767 else
3769 cur->mNext.store(GetVoiceChanger(context.get()), std::memory_order_relaxed);
3770 cur = cur->mNext.load(std::memory_order_relaxed);
3772 if(voice)
3773 voice->mPendingChange.store(true, std::memory_order_relaxed);
3774 cur->mVoice = voice;
3775 cur->mSourceID = source->id;
3776 cur->mState = VChangeState::Reset;
3777 source->state = AL_INITIAL;
3779 source->Offset = 0.0;
3780 source->OffsetType = AL_NONE;
3781 source->VoiceIdx = INVALID_VOICE_IDX;
3783 if(tail) LIKELY
3784 SendVoiceChanges(context.get(), tail);
3786 END_API_FUNC
3789 AL_API void AL_APIENTRY alSourceQueueBuffers(ALuint src, ALsizei nb, const ALuint *buffers)
3790 START_API_FUNC
3792 ContextRef context{GetContextRef()};
3793 if(!context) UNLIKELY return;
3795 if(nb < 0) UNLIKELY
3796 context->setError(AL_INVALID_VALUE, "Queueing %d buffers", nb);
3797 if(nb <= 0) UNLIKELY return;
3799 std::lock_guard<std::mutex> _{context->mSourceLock};
3800 ALsource *source{LookupSource(context.get(),src)};
3801 if(!source) UNLIKELY
3802 return context->setError(AL_INVALID_NAME, "Invalid source ID %u", src);
3804 /* Can't queue on a Static Source */
3805 if(source->SourceType == AL_STATIC) UNLIKELY
3806 return context->setError(AL_INVALID_OPERATION, "Queueing onto static source %u", src);
3808 /* Check for a valid Buffer, for its frequency and format */
3809 ALCdevice *device{context->mALDevice.get()};
3810 ALbuffer *BufferFmt{nullptr};
3811 for(auto &item : source->mQueue)
3813 BufferFmt = item.mBuffer;
3814 if(BufferFmt) break;
3817 std::unique_lock<std::mutex> buflock{device->BufferLock};
3818 const size_t NewListStart{source->mQueue.size()};
3819 ALbufferQueueItem *BufferList{nullptr};
3820 for(ALsizei i{0};i < nb;i++)
3822 bool fmt_mismatch{false};
3823 ALbuffer *buffer{nullptr};
3824 if(buffers[i] && (buffer=LookupBuffer(device, buffers[i])) == nullptr)
3826 context->setError(AL_INVALID_NAME, "Queueing invalid buffer ID %u", buffers[i]);
3827 goto buffer_error;
3829 if(buffer)
3831 if(buffer->mSampleRate < 1)
3833 context->setError(AL_INVALID_OPERATION, "Queueing buffer %u with no format",
3834 buffer->id);
3835 goto buffer_error;
3837 if(buffer->mCallback)
3839 context->setError(AL_INVALID_OPERATION, "Queueing callback buffer %u", buffer->id);
3840 goto buffer_error;
3842 if(buffer->MappedAccess != 0 && !(buffer->MappedAccess&AL_MAP_PERSISTENT_BIT_SOFT))
3844 context->setError(AL_INVALID_OPERATION,
3845 "Queueing non-persistently mapped buffer %u", buffer->id);
3846 goto buffer_error;
3850 source->mQueue.emplace_back();
3851 if(!BufferList)
3852 BufferList = &source->mQueue.back();
3853 else
3855 auto &item = source->mQueue.back();
3856 BufferList->mNext.store(&item, std::memory_order_relaxed);
3857 BufferList = &item;
3859 if(!buffer) continue;
3860 BufferList->mBlockAlign = buffer->mBlockAlign;
3861 BufferList->mSampleLen = buffer->mSampleLen;
3862 BufferList->mLoopEnd = buffer->mSampleLen;
3863 BufferList->mSamples = buffer->mData.data();
3864 BufferList->mBuffer = buffer;
3865 IncrementRef(buffer->ref);
3867 if(BufferFmt == nullptr)
3868 BufferFmt = buffer;
3869 else
3871 fmt_mismatch |= BufferFmt->mSampleRate != buffer->mSampleRate;
3872 fmt_mismatch |= BufferFmt->mChannels != buffer->mChannels;
3873 fmt_mismatch |= BufferFmt->mType != buffer->mType;
3874 if(BufferFmt->isBFormat())
3876 fmt_mismatch |= BufferFmt->mAmbiLayout != buffer->mAmbiLayout;
3877 fmt_mismatch |= BufferFmt->mAmbiScaling != buffer->mAmbiScaling;
3879 fmt_mismatch |= BufferFmt->mAmbiOrder != buffer->mAmbiOrder;
3881 if(fmt_mismatch) UNLIKELY
3883 context->setError(AL_INVALID_OPERATION, "Queueing buffer with mismatched format\n"
3884 " Expected: %uhz, %s, %s ; Got: %uhz, %s, %s\n", BufferFmt->mSampleRate,
3885 NameFromFormat(BufferFmt->mType), NameFromFormat(BufferFmt->mChannels),
3886 buffer->mSampleRate, NameFromFormat(buffer->mType),
3887 NameFromFormat(buffer->mChannels));
3889 buffer_error:
3890 /* A buffer failed (invalid ID or format), so unlock and release
3891 * each buffer we had.
3893 auto iter = source->mQueue.begin() + ptrdiff_t(NewListStart);
3894 for(;iter != source->mQueue.end();++iter)
3896 if(ALbuffer *buf{iter->mBuffer})
3897 DecrementRef(buf->ref);
3899 source->mQueue.resize(NewListStart);
3900 return;
3903 /* All buffers good. */
3904 buflock.unlock();
3906 /* Source is now streaming */
3907 source->SourceType = AL_STREAMING;
3909 if(NewListStart != 0)
3911 auto iter = source->mQueue.begin() + ptrdiff_t(NewListStart);
3912 (iter-1)->mNext.store(al::to_address(iter), std::memory_order_release);
3915 END_API_FUNC
3917 AL_API void AL_APIENTRY alSourceUnqueueBuffers(ALuint src, ALsizei nb, ALuint *buffers)
3918 START_API_FUNC
3920 ContextRef context{GetContextRef()};
3921 if(!context) UNLIKELY return;
3923 if(nb < 0) UNLIKELY
3924 context->setError(AL_INVALID_VALUE, "Unqueueing %d buffers", nb);
3925 if(nb <= 0) UNLIKELY return;
3927 std::lock_guard<std::mutex> _{context->mSourceLock};
3928 ALsource *source{LookupSource(context.get(),src)};
3929 if(!source) UNLIKELY
3930 return context->setError(AL_INVALID_NAME, "Invalid source ID %u", src);
3932 if(source->SourceType != AL_STREAMING) UNLIKELY
3933 return context->setError(AL_INVALID_VALUE, "Unqueueing from a non-streaming source %u",
3934 src);
3935 if(source->Looping) UNLIKELY
3936 return context->setError(AL_INVALID_VALUE, "Unqueueing from looping source %u", src);
3938 /* Make sure enough buffers have been processed to unqueue. */
3939 uint processed{0u};
3940 if(source->state != AL_INITIAL) LIKELY
3942 VoiceBufferItem *Current{nullptr};
3943 if(Voice *voice{GetSourceVoice(source, context.get())})
3944 Current = voice->mCurrentBuffer.load(std::memory_order_relaxed);
3945 for(auto &item : source->mQueue)
3947 if(&item == Current)
3948 break;
3949 ++processed;
3952 if(processed < static_cast<ALuint>(nb)) UNLIKELY
3953 return context->setError(AL_INVALID_VALUE, "Unqueueing %d buffer%s (only %u processed)",
3954 nb, (nb==1)?"":"s", processed);
3956 do {
3957 auto &head = source->mQueue.front();
3958 if(ALbuffer *buffer{head.mBuffer})
3960 *(buffers++) = buffer->id;
3961 DecrementRef(buffer->ref);
3963 else
3964 *(buffers++) = 0;
3965 source->mQueue.pop_front();
3966 } while(--nb);
3968 END_API_FUNC
3971 AL_API void AL_APIENTRY alSourceQueueBufferLayersSOFT(ALuint, ALsizei, const ALuint*)
3972 START_API_FUNC
3974 ContextRef context{GetContextRef()};
3975 if(!context) UNLIKELY return;
3977 context->setError(AL_INVALID_OPERATION, "alSourceQueueBufferLayersSOFT not supported");
3979 END_API_FUNC
3982 ALsource::ALsource()
3984 Direct.Gain = 1.0f;
3985 Direct.GainHF = 1.0f;
3986 Direct.HFReference = LOWPASSFREQREF;
3987 Direct.GainLF = 1.0f;
3988 Direct.LFReference = HIGHPASSFREQREF;
3989 for(auto &send : Send)
3991 send.Slot = nullptr;
3992 send.Gain = 1.0f;
3993 send.GainHF = 1.0f;
3994 send.HFReference = LOWPASSFREQREF;
3995 send.GainLF = 1.0f;
3996 send.LFReference = HIGHPASSFREQREF;
4000 ALsource::~ALsource()
4002 for(auto &item : mQueue)
4004 if(ALbuffer *buffer{item.mBuffer})
4005 DecrementRef(buffer->ref);
4008 auto clear_send = [](ALsource::SendData &send) -> void
4009 { if(send.Slot) DecrementRef(send.Slot->ref); };
4010 std::for_each(Send.begin(), Send.end(), clear_send);
4013 void UpdateAllSourceProps(ALCcontext *context)
4015 std::lock_guard<std::mutex> _{context->mSourceLock};
4016 auto voicelist = context->getVoicesSpan();
4017 ALuint vidx{0u};
4018 for(Voice *voice : voicelist)
4020 ALuint sid{voice->mSourceID.load(std::memory_order_acquire)};
4021 ALsource *source = sid ? LookupSource(context, sid) : nullptr;
4022 if(source && source->VoiceIdx == vidx)
4024 if(std::exchange(source->mPropsDirty, false))
4025 UpdateSourceProps(source, voice, context);
4027 ++vidx;
4031 SourceSubList::~SourceSubList()
4033 uint64_t usemask{~FreeMask};
4034 while(usemask)
4036 const int idx{al::countr_zero(usemask)};
4037 usemask &= ~(1_u64 << idx);
4038 al::destroy_at(Sources+idx);
4040 FreeMask = ~usemask;
4041 al_free(Sources);
4042 Sources = nullptr;
4046 #ifdef ALSOFT_EAX
4047 constexpr const ALsource::EaxFxSlotIds ALsource::eax4_fx_slot_ids;
4048 constexpr const ALsource::EaxFxSlotIds ALsource::eax5_fx_slot_ids;
4050 void ALsource::eaxInitialize(ALCcontext *context) noexcept
4052 assert(context != nullptr);
4053 mEaxAlContext = context;
4055 mEaxPrimaryFxSlotId = context->eaxGetPrimaryFxSlotIndex();
4056 eax_set_defaults();
4058 eax1_translate(mEax1.i, mEax);
4059 mEaxVersion = 1;
4060 mEaxChanged = true;
4063 void ALsource::eaxDispatch(const EaxCall& call)
4065 call.is_get() ? eax_get(call) : eax_set(call);
4068 ALsource* ALsource::EaxLookupSource(ALCcontext& al_context, ALuint source_id) noexcept
4070 return LookupSource(&al_context, source_id);
4073 [[noreturn]] void ALsource::eax_fail(const char* message)
4075 throw Exception{message};
4078 [[noreturn]] void ALsource::eax_fail_unknown_property_id()
4080 eax_fail("Unknown property id.");
4083 [[noreturn]] void ALsource::eax_fail_unknown_version()
4085 eax_fail("Unknown version.");
4088 [[noreturn]] void ALsource::eax_fail_unknown_active_fx_slot_id()
4090 eax_fail("Unknown active FX slot ID.");
4093 [[noreturn]] void ALsource::eax_fail_unknown_receiving_fx_slot_id()
4095 eax_fail("Unknown receiving FX slot ID.");
4098 void ALsource::eax_set_sends_defaults(EaxSends& sends, const EaxFxSlotIds& ids) noexcept
4100 for (auto i = size_t{}; i < EAX_MAX_FXSLOTS; ++i) {
4101 auto& send = sends[i];
4102 send.guidReceivingFXSlotID = *(ids[i]);
4103 send.lSend = EAXSOURCE_DEFAULTSEND;
4104 send.lSendHF = EAXSOURCE_DEFAULTSENDHF;
4105 send.lOcclusion = EAXSOURCE_DEFAULTOCCLUSION;
4106 send.flOcclusionLFRatio = EAXSOURCE_DEFAULTOCCLUSIONLFRATIO;
4107 send.flOcclusionRoomRatio = EAXSOURCE_DEFAULTOCCLUSIONROOMRATIO;
4108 send.flOcclusionDirectRatio = EAXSOURCE_DEFAULTOCCLUSIONDIRECTRATIO;
4109 send.lExclusion = EAXSOURCE_DEFAULTEXCLUSION;
4110 send.flExclusionLFRatio = EAXSOURCE_DEFAULTEXCLUSIONLFRATIO;
4114 void ALsource::eax1_set_defaults(Eax1Props& props) noexcept
4116 props.fMix = EAX_REVERBMIX_USEDISTANCE;
4119 void ALsource::eax1_set_defaults() noexcept
4121 eax1_set_defaults(mEax1.i);
4122 mEax1.d = mEax1.i;
4125 void ALsource::eax2_set_defaults(Eax2Props& props) noexcept
4127 props.lDirect = EAXSOURCE_DEFAULTDIRECT;
4128 props.lDirectHF = EAXSOURCE_DEFAULTDIRECTHF;
4129 props.lRoom = EAXSOURCE_DEFAULTROOM;
4130 props.lRoomHF = EAXSOURCE_DEFAULTROOMHF;
4131 props.flRoomRolloffFactor = EAXSOURCE_DEFAULTROOMROLLOFFFACTOR;
4132 props.lObstruction = EAXSOURCE_DEFAULTOBSTRUCTION;
4133 props.flObstructionLFRatio = EAXSOURCE_DEFAULTOBSTRUCTIONLFRATIO;
4134 props.lOcclusion = EAXSOURCE_DEFAULTOCCLUSION;
4135 props.flOcclusionLFRatio = EAXSOURCE_DEFAULTOCCLUSIONLFRATIO;
4136 props.flOcclusionRoomRatio = EAXSOURCE_DEFAULTOCCLUSIONROOMRATIO;
4137 props.lOutsideVolumeHF = EAXSOURCE_DEFAULTOUTSIDEVOLUMEHF;
4138 props.flAirAbsorptionFactor = EAXSOURCE_DEFAULTAIRABSORPTIONFACTOR;
4139 props.dwFlags = EAXSOURCE_DEFAULTFLAGS;
4142 void ALsource::eax2_set_defaults() noexcept
4144 eax2_set_defaults(mEax2.i);
4145 mEax2.d = mEax2.i;
4148 void ALsource::eax3_set_defaults(Eax3Props& props) noexcept
4150 props.lDirect = EAXSOURCE_DEFAULTDIRECT;
4151 props.lDirectHF = EAXSOURCE_DEFAULTDIRECTHF;
4152 props.lRoom = EAXSOURCE_DEFAULTROOM;
4153 props.lRoomHF = EAXSOURCE_DEFAULTROOMHF;
4154 props.lObstruction = EAXSOURCE_DEFAULTOBSTRUCTION;
4155 props.flObstructionLFRatio = EAXSOURCE_DEFAULTOBSTRUCTIONLFRATIO;
4156 props.lOcclusion = EAXSOURCE_DEFAULTOCCLUSION;
4157 props.flOcclusionLFRatio = EAXSOURCE_DEFAULTOCCLUSIONLFRATIO;
4158 props.flOcclusionRoomRatio = EAXSOURCE_DEFAULTOCCLUSIONROOMRATIO;
4159 props.flOcclusionDirectRatio = EAXSOURCE_DEFAULTOCCLUSIONDIRECTRATIO;
4160 props.lExclusion = EAXSOURCE_DEFAULTEXCLUSION;
4161 props.flExclusionLFRatio = EAXSOURCE_DEFAULTEXCLUSIONLFRATIO;
4162 props.lOutsideVolumeHF = EAXSOURCE_DEFAULTOUTSIDEVOLUMEHF;
4163 props.flDopplerFactor = EAXSOURCE_DEFAULTDOPPLERFACTOR;
4164 props.flRolloffFactor = EAXSOURCE_DEFAULTROLLOFFFACTOR;
4165 props.flRoomRolloffFactor = EAXSOURCE_DEFAULTROOMROLLOFFFACTOR;
4166 props.flAirAbsorptionFactor = EAXSOURCE_DEFAULTAIRABSORPTIONFACTOR;
4167 props.ulFlags = EAXSOURCE_DEFAULTFLAGS;
4170 void ALsource::eax3_set_defaults() noexcept
4172 eax3_set_defaults(mEax3.i);
4173 mEax3.d = mEax3.i;
4176 void ALsource::eax4_set_sends_defaults(EaxSends& sends) noexcept
4178 eax_set_sends_defaults(sends, eax4_fx_slot_ids);
4181 void ALsource::eax4_set_active_fx_slots_defaults(EAX40ACTIVEFXSLOTS& slots) noexcept
4183 slots = EAX40SOURCE_DEFAULTACTIVEFXSLOTID;
4186 void ALsource::eax4_set_defaults() noexcept
4188 eax3_set_defaults(mEax4.i.source);
4189 eax4_set_sends_defaults(mEax4.i.sends);
4190 eax4_set_active_fx_slots_defaults(mEax4.i.active_fx_slots);
4191 mEax4.d = mEax4.i;
4194 void ALsource::eax5_set_source_defaults(EAX50SOURCEPROPERTIES& props) noexcept
4196 eax3_set_defaults(static_cast<Eax3Props&>(props));
4197 props.flMacroFXFactor = EAXSOURCE_DEFAULTMACROFXFACTOR;
4200 void ALsource::eax5_set_sends_defaults(EaxSends& sends) noexcept
4202 eax_set_sends_defaults(sends, eax5_fx_slot_ids);
4205 void ALsource::eax5_set_active_fx_slots_defaults(EAX50ACTIVEFXSLOTS& slots) noexcept
4207 slots = EAX50SOURCE_3DDEFAULTACTIVEFXSLOTID;
4210 void ALsource::eax5_set_speaker_levels_defaults(EaxSpeakerLevels& speaker_levels) noexcept
4212 for (auto i = size_t{}; i < eax_max_speakers; ++i) {
4213 auto& speaker_level = speaker_levels[i];
4214 speaker_level.lSpeakerID = static_cast<long>(EAXSPEAKER_FRONT_LEFT + i);
4215 speaker_level.lLevel = EAXSOURCE_DEFAULTSPEAKERLEVEL;
4219 void ALsource::eax5_set_defaults(Eax5Props& props) noexcept
4221 eax5_set_source_defaults(props.source);
4222 eax5_set_sends_defaults(props.sends);
4223 eax5_set_active_fx_slots_defaults(props.active_fx_slots);
4224 eax5_set_speaker_levels_defaults(props.speaker_levels);
4227 void ALsource::eax5_set_defaults() noexcept
4229 eax5_set_defaults(mEax5.i);
4230 mEax5.d = mEax5.i;
4233 void ALsource::eax_set_defaults() noexcept
4235 eax1_set_defaults();
4236 eax2_set_defaults();
4237 eax3_set_defaults();
4238 eax4_set_defaults();
4239 eax5_set_defaults();
4242 void ALsource::eax1_translate(const Eax1Props& src, Eax5Props& dst) noexcept
4244 eax5_set_defaults(dst);
4246 if (src.fMix == EAX_REVERBMIX_USEDISTANCE)
4248 dst.source.ulFlags |= EAXSOURCEFLAGS_ROOMAUTO;
4249 dst.sends[0].lSend = 0;
4251 else
4253 dst.source.ulFlags &= ~EAXSOURCEFLAGS_ROOMAUTO;
4254 dst.sends[0].lSend = clamp(static_cast<long>(gain_to_level_mb(src.fMix)),
4255 EAXSOURCE_MINSEND, EAXSOURCE_MAXSEND);
4259 void ALsource::eax2_translate(const Eax2Props& src, Eax5Props& dst) noexcept
4261 // Source.
4263 dst.source.lDirect = src.lDirect;
4264 dst.source.lDirectHF = src.lDirectHF;
4265 dst.source.lRoom = src.lRoom;
4266 dst.source.lRoomHF = src.lRoomHF;
4267 dst.source.lObstruction = src.lObstruction;
4268 dst.source.flObstructionLFRatio = src.flObstructionLFRatio;
4269 dst.source.lOcclusion = src.lOcclusion;
4270 dst.source.flOcclusionLFRatio = src.flOcclusionLFRatio;
4271 dst.source.flOcclusionRoomRatio = src.flOcclusionRoomRatio;
4272 dst.source.flOcclusionDirectRatio = EAXSOURCE_DEFAULTOCCLUSIONDIRECTRATIO;
4273 dst.source.lExclusion = EAXSOURCE_DEFAULTEXCLUSION;
4274 dst.source.flExclusionLFRatio = EAXSOURCE_DEFAULTEXCLUSIONLFRATIO;
4275 dst.source.lOutsideVolumeHF = src.lOutsideVolumeHF;
4276 dst.source.flDopplerFactor = EAXSOURCE_DEFAULTDOPPLERFACTOR;
4277 dst.source.flRolloffFactor = EAXSOURCE_DEFAULTROLLOFFFACTOR;
4278 dst.source.flRoomRolloffFactor = src.flRoomRolloffFactor;
4279 dst.source.flAirAbsorptionFactor = src.flAirAbsorptionFactor;
4280 dst.source.ulFlags = src.dwFlags;
4281 dst.source.flMacroFXFactor = EAXSOURCE_DEFAULTMACROFXFACTOR;
4283 // Set everyting else to defaults.
4285 eax5_set_sends_defaults(dst.sends);
4286 eax5_set_active_fx_slots_defaults(dst.active_fx_slots);
4287 eax5_set_speaker_levels_defaults(dst.speaker_levels);
4290 void ALsource::eax3_translate(const Eax3Props& src, Eax5Props& dst) noexcept
4292 // Source.
4294 static_cast<Eax3Props&>(dst.source) = src;
4295 dst.source.flMacroFXFactor = EAXSOURCE_DEFAULTMACROFXFACTOR;
4297 // Set everyting else to defaults.
4299 eax5_set_sends_defaults(dst.sends);
4300 eax5_set_active_fx_slots_defaults(dst.active_fx_slots);
4301 eax5_set_speaker_levels_defaults(dst.speaker_levels);
4304 void ALsource::eax4_translate(const Eax4Props& src, Eax5Props& dst) noexcept
4306 // Source.
4308 static_cast<Eax3Props&>(dst.source) = src.source;
4309 dst.source.flMacroFXFactor = EAXSOURCE_DEFAULTMACROFXFACTOR;
4311 // Sends.
4313 dst.sends = src.sends;
4315 for (auto i = size_t{}; i < EAX_MAX_FXSLOTS; ++i)
4316 dst.sends[i].guidReceivingFXSlotID = *(eax5_fx_slot_ids[i]);
4318 // Active FX slots.
4320 for (auto i = 0; i < EAX50_MAX_ACTIVE_FXSLOTS; ++i) {
4321 auto& dst_id = dst.active_fx_slots.guidActiveFXSlots[i];
4323 if (i < EAX40_MAX_ACTIVE_FXSLOTS) {
4324 const auto& src_id = src.active_fx_slots.guidActiveFXSlots[i];
4326 if (src_id == EAX_NULL_GUID)
4327 dst_id = EAX_NULL_GUID;
4328 else if (src_id == EAX_PrimaryFXSlotID)
4329 dst_id = EAX_PrimaryFXSlotID;
4330 else if (src_id == EAXPROPERTYID_EAX40_FXSlot0)
4331 dst_id = EAXPROPERTYID_EAX50_FXSlot0;
4332 else if (src_id == EAXPROPERTYID_EAX40_FXSlot1)
4333 dst_id = EAXPROPERTYID_EAX50_FXSlot1;
4334 else if (src_id == EAXPROPERTYID_EAX40_FXSlot2)
4335 dst_id = EAXPROPERTYID_EAX50_FXSlot2;
4336 else if (src_id == EAXPROPERTYID_EAX40_FXSlot3)
4337 dst_id = EAXPROPERTYID_EAX50_FXSlot3;
4338 else
4339 assert(false && "Unknown active FX slot ID.");
4340 } else
4341 dst_id = EAX_NULL_GUID;
4344 // Speaker levels.
4346 eax5_set_speaker_levels_defaults(dst.speaker_levels);
4349 float ALsource::eax_calculate_dst_occlusion_mb(
4350 long src_occlusion_mb,
4351 float path_ratio,
4352 float lf_ratio) noexcept
4354 const auto ratio_1 = path_ratio + lf_ratio - 1.0F;
4355 const auto ratio_2 = path_ratio * lf_ratio;
4356 const auto ratio = (ratio_2 > ratio_1) ? ratio_2 : ratio_1;
4357 const auto dst_occlustion_mb = static_cast<float>(src_occlusion_mb) * ratio;
4358 return dst_occlustion_mb;
4361 EaxAlLowPassParam ALsource::eax_create_direct_filter_param() const noexcept
4363 auto gain_mb =
4364 static_cast<float>(mEax.source.lDirect) +
4365 (static_cast<float>(mEax.source.lObstruction) * mEax.source.flObstructionLFRatio) +
4366 eax_calculate_dst_occlusion_mb(
4367 mEax.source.lOcclusion,
4368 mEax.source.flOcclusionDirectRatio,
4369 mEax.source.flOcclusionLFRatio);
4371 const auto has_source_occlusion = (mEax.source.lOcclusion != 0);
4373 auto gain_hf_mb =
4374 static_cast<float>(mEax.source.lDirectHF) +
4375 static_cast<float>(mEax.source.lObstruction);
4377 for (auto i = std::size_t{}; i < EAX_MAX_FXSLOTS; ++i)
4379 if(!mEaxActiveFxSlots[i])
4380 continue;
4382 if(has_source_occlusion) {
4383 const auto& fx_slot = mEaxAlContext->eaxGetFxSlot(i);
4384 const auto& fx_slot_eax = fx_slot.eax_get_eax_fx_slot();
4385 const auto is_environmental_fx = ((fx_slot_eax.ulFlags & EAXFXSLOTFLAGS_ENVIRONMENT) != 0);
4386 const auto is_primary = (mEaxPrimaryFxSlotId.value_or(-1) == fx_slot.eax_get_index());
4387 const auto is_listener_environment = (is_environmental_fx && is_primary);
4389 if(is_listener_environment) {
4390 gain_mb += eax_calculate_dst_occlusion_mb(
4391 mEax.source.lOcclusion,
4392 mEax.source.flOcclusionDirectRatio,
4393 mEax.source.flOcclusionLFRatio);
4395 gain_hf_mb += static_cast<float>(mEax.source.lOcclusion) * mEax.source.flOcclusionDirectRatio;
4399 const auto& send = mEax.sends[i];
4401 if(send.lOcclusion != 0) {
4402 gain_mb += eax_calculate_dst_occlusion_mb(
4403 send.lOcclusion,
4404 send.flOcclusionDirectRatio,
4405 send.flOcclusionLFRatio);
4407 gain_hf_mb += static_cast<float>(send.lOcclusion) * send.flOcclusionDirectRatio;
4411 const auto al_low_pass_param = EaxAlLowPassParam{
4412 level_mb_to_gain(gain_mb),
4413 minf(level_mb_to_gain(gain_hf_mb), 1.0f)};
4415 return al_low_pass_param;
4418 EaxAlLowPassParam ALsource::eax_create_room_filter_param(
4419 const ALeffectslot& fx_slot,
4420 const EAXSOURCEALLSENDPROPERTIES& send) const noexcept
4422 const auto& fx_slot_eax = fx_slot.eax_get_eax_fx_slot();
4423 const auto is_environmental_fx = ((fx_slot_eax.ulFlags & EAXFXSLOTFLAGS_ENVIRONMENT) != 0);
4424 const auto is_primary = (mEaxPrimaryFxSlotId.value_or(-1) == fx_slot.eax_get_index());
4425 const auto is_listener_environment = (is_environmental_fx && is_primary);
4427 const auto gain_mb =
4428 (static_cast<float>(fx_slot_eax.lOcclusion) * fx_slot_eax.flOcclusionLFRatio) +
4429 static_cast<float>((is_environmental_fx ? mEax.source.lRoom : 0) + send.lSend) +
4430 (is_listener_environment ?
4431 eax_calculate_dst_occlusion_mb(
4432 mEax.source.lOcclusion,
4433 mEax.source.flOcclusionRoomRatio,
4434 mEax.source.flOcclusionLFRatio) :
4435 0.0f) +
4436 eax_calculate_dst_occlusion_mb(
4437 send.lOcclusion,
4438 send.flOcclusionRoomRatio,
4439 send.flOcclusionLFRatio) +
4440 (is_listener_environment ?
4441 (static_cast<float>(mEax.source.lExclusion) * mEax.source.flExclusionLFRatio) :
4442 0.0f) +
4443 (static_cast<float>(send.lExclusion) * send.flExclusionLFRatio);
4445 const auto gain_hf_mb =
4446 static_cast<float>(fx_slot_eax.lOcclusion) +
4447 static_cast<float>((is_environmental_fx ? mEax.source.lRoomHF : 0) + send.lSendHF) +
4448 (is_listener_environment ?
4449 ((static_cast<float>(mEax.source.lOcclusion) * mEax.source.flOcclusionRoomRatio)) :
4450 0.0f) +
4451 (static_cast<float>(send.lOcclusion) * send.flOcclusionRoomRatio) +
4452 (is_listener_environment ?
4453 static_cast<float>(mEax.source.lExclusion + send.lExclusion) :
4454 0.0f);
4456 const auto al_low_pass_param = EaxAlLowPassParam{
4457 level_mb_to_gain(gain_mb),
4458 minf(level_mb_to_gain(gain_hf_mb), 1.0f)};
4460 return al_low_pass_param;
4463 void ALsource::eax_update_direct_filter()
4465 const auto& direct_param = eax_create_direct_filter_param();
4466 Direct.Gain = direct_param.gain;
4467 Direct.GainHF = direct_param.gain_hf;
4468 Direct.HFReference = LOWPASSFREQREF;
4469 Direct.GainLF = 1.0f;
4470 Direct.LFReference = HIGHPASSFREQREF;
4471 mPropsDirty = true;
4474 void ALsource::eax_update_room_filters()
4476 for (auto i = size_t{}; i < EAX_MAX_FXSLOTS; ++i) {
4477 if (!mEaxActiveFxSlots[i])
4478 continue;
4480 auto& fx_slot = mEaxAlContext->eaxGetFxSlot(i);
4481 const auto& send = mEax.sends[i];
4482 const auto& room_param = eax_create_room_filter_param(fx_slot, send);
4483 eax_set_al_source_send(&fx_slot, i, room_param);
4487 void ALsource::eax_set_efx_outer_gain_hf()
4489 OuterGainHF = clamp(
4490 level_mb_to_gain(static_cast<float>(mEax.source.lOutsideVolumeHF)),
4491 AL_MIN_CONE_OUTER_GAINHF,
4492 AL_MAX_CONE_OUTER_GAINHF);
4495 void ALsource::eax_set_efx_doppler_factor()
4497 DopplerFactor = mEax.source.flDopplerFactor;
4500 void ALsource::eax_set_efx_rolloff_factor()
4502 RolloffFactor2 = mEax.source.flRolloffFactor;
4505 void ALsource::eax_set_efx_room_rolloff_factor()
4507 RoomRolloffFactor = mEax.source.flRoomRolloffFactor;
4510 void ALsource::eax_set_efx_air_absorption_factor()
4512 AirAbsorptionFactor = mEax.source.flAirAbsorptionFactor;
4515 void ALsource::eax_set_efx_dry_gain_hf_auto()
4517 DryGainHFAuto = ((mEax.source.ulFlags & EAXSOURCEFLAGS_DIRECTHFAUTO) != 0);
4520 void ALsource::eax_set_efx_wet_gain_auto()
4522 WetGainAuto = ((mEax.source.ulFlags & EAXSOURCEFLAGS_ROOMAUTO) != 0);
4525 void ALsource::eax_set_efx_wet_gain_hf_auto()
4527 WetGainHFAuto = ((mEax.source.ulFlags & EAXSOURCEFLAGS_ROOMHFAUTO) != 0);
4530 void ALsource::eax1_set(const EaxCall& call, Eax1Props& props)
4532 switch (call.get_property_id()) {
4533 case DSPROPERTY_EAXBUFFER_ALL:
4534 eax_defer<Eax1SourceAllValidator>(call, props);
4535 break;
4537 case DSPROPERTY_EAXBUFFER_REVERBMIX:
4538 eax_defer<Eax1SourceReverbMixValidator>(call, props.fMix);
4539 break;
4541 default:
4542 eax_fail_unknown_property_id();
4546 void ALsource::eax2_set(const EaxCall& call, Eax2Props& props)
4548 switch (call.get_property_id()) {
4549 case DSPROPERTY_EAX20BUFFER_NONE:
4550 break;
4552 case DSPROPERTY_EAX20BUFFER_ALLPARAMETERS:
4553 eax_defer<Eax2SourceAllValidator>(call, props);
4554 break;
4556 case DSPROPERTY_EAX20BUFFER_DIRECT:
4557 eax_defer<Eax2SourceDirectValidator>(call, props.lDirect);
4558 break;
4560 case DSPROPERTY_EAX20BUFFER_DIRECTHF:
4561 eax_defer<Eax2SourceDirectHfValidator>(call, props.lDirectHF);
4562 break;
4564 case DSPROPERTY_EAX20BUFFER_ROOM:
4565 eax_defer<Eax2SourceRoomValidator>(call, props.lRoom);
4566 break;
4568 case DSPROPERTY_EAX20BUFFER_ROOMHF:
4569 eax_defer<Eax2SourceRoomHfValidator>(call, props.lRoomHF);
4570 break;
4572 case DSPROPERTY_EAX20BUFFER_ROOMROLLOFFFACTOR:
4573 eax_defer<Eax2SourceRoomRolloffFactorValidator>(call, props.flRoomRolloffFactor);
4574 break;
4576 case DSPROPERTY_EAX20BUFFER_OBSTRUCTION:
4577 eax_defer<Eax2SourceObstructionValidator>(call, props.lObstruction);
4578 break;
4580 case DSPROPERTY_EAX20BUFFER_OBSTRUCTIONLFRATIO:
4581 eax_defer<Eax2SourceObstructionLfRatioValidator>(call, props.flObstructionLFRatio);
4582 break;
4584 case DSPROPERTY_EAX20BUFFER_OCCLUSION:
4585 eax_defer<Eax2SourceOcclusionValidator>(call, props.lOcclusion);
4586 break;
4588 case DSPROPERTY_EAX20BUFFER_OCCLUSIONLFRATIO:
4589 eax_defer<Eax2SourceOcclusionLfRatioValidator>(call, props.flOcclusionLFRatio);
4590 break;
4592 case DSPROPERTY_EAX20BUFFER_OCCLUSIONROOMRATIO:
4593 eax_defer<Eax2SourceOcclusionRoomRatioValidator>(call, props.flOcclusionRoomRatio);
4594 break;
4596 case DSPROPERTY_EAX20BUFFER_OUTSIDEVOLUMEHF:
4597 eax_defer<Eax2SourceOutsideVolumeHfValidator>(call, props.lOutsideVolumeHF);
4598 break;
4600 case DSPROPERTY_EAX20BUFFER_AIRABSORPTIONFACTOR:
4601 eax_defer<Eax2SourceAirAbsorptionFactorValidator>(call, props.flAirAbsorptionFactor);
4602 break;
4604 case DSPROPERTY_EAX20BUFFER_FLAGS:
4605 eax_defer<Eax2SourceFlagsValidator>(call, props.dwFlags);
4606 break;
4608 default:
4609 eax_fail_unknown_property_id();
4613 void ALsource::eax3_set(const EaxCall& call, Eax3Props& props)
4615 switch (call.get_property_id()) {
4616 case EAXSOURCE_NONE:
4617 break;
4619 case EAXSOURCE_ALLPARAMETERS:
4620 eax_defer<Eax3SourceAllValidator>(call, props);
4621 break;
4623 case EAXSOURCE_OBSTRUCTIONPARAMETERS:
4624 eax_defer_sub<Eax4ObstructionValidator, EAXOBSTRUCTIONPROPERTIES>(call, props.lObstruction);
4625 break;
4627 case EAXSOURCE_OCCLUSIONPARAMETERS:
4628 eax_defer_sub<Eax4OcclusionValidator, EAXOCCLUSIONPROPERTIES>(call, props.lOcclusion);
4629 break;
4631 case EAXSOURCE_EXCLUSIONPARAMETERS:
4632 eax_defer_sub<Eax4ExclusionValidator, EAXEXCLUSIONPROPERTIES>(call, props.lExclusion);
4633 break;
4635 case EAXSOURCE_DIRECT:
4636 eax_defer<Eax2SourceDirectValidator>(call, props.lDirect);
4637 break;
4639 case EAXSOURCE_DIRECTHF:
4640 eax_defer<Eax2SourceDirectHfValidator>(call, props.lDirectHF);
4641 break;
4643 case EAXSOURCE_ROOM:
4644 eax_defer<Eax2SourceRoomValidator>(call, props.lRoom);
4645 break;
4647 case EAXSOURCE_ROOMHF:
4648 eax_defer<Eax2SourceRoomHfValidator>(call, props.lRoomHF);
4649 break;
4651 case EAXSOURCE_OBSTRUCTION:
4652 eax_defer<Eax2SourceObstructionValidator>(call, props.lObstruction);
4653 break;
4655 case EAXSOURCE_OBSTRUCTIONLFRATIO:
4656 eax_defer<Eax2SourceObstructionLfRatioValidator>(call, props.flObstructionLFRatio);
4657 break;
4659 case EAXSOURCE_OCCLUSION:
4660 eax_defer<Eax2SourceOcclusionValidator>(call, props.lOcclusion);
4661 break;
4663 case EAXSOURCE_OCCLUSIONLFRATIO:
4664 eax_defer<Eax2SourceOcclusionLfRatioValidator>(call, props.flOcclusionLFRatio);
4665 break;
4667 case EAXSOURCE_OCCLUSIONROOMRATIO:
4668 eax_defer<Eax2SourceOcclusionRoomRatioValidator>(call, props.flOcclusionRoomRatio);
4669 break;
4671 case EAXSOURCE_OCCLUSIONDIRECTRATIO:
4672 eax_defer<Eax3SourceOcclusionDirectRatioValidator>(call, props.flOcclusionDirectRatio);
4673 break;
4675 case EAXSOURCE_EXCLUSION:
4676 eax_defer<Eax3SourceExclusionValidator>(call, props.lExclusion);
4677 break;
4679 case EAXSOURCE_EXCLUSIONLFRATIO:
4680 eax_defer<Eax3SourceExclusionLfRatioValidator>(call, props.flExclusionLFRatio);
4681 break;
4683 case EAXSOURCE_OUTSIDEVOLUMEHF:
4684 eax_defer<Eax2SourceOutsideVolumeHfValidator>(call, props.lOutsideVolumeHF);
4685 break;
4687 case EAXSOURCE_DOPPLERFACTOR:
4688 eax_defer<Eax3SourceDopplerFactorValidator>(call, props.flDopplerFactor);
4689 break;
4691 case EAXSOURCE_ROLLOFFFACTOR:
4692 eax_defer<Eax3SourceRolloffFactorValidator>(call, props.flRolloffFactor);
4693 break;
4695 case EAXSOURCE_ROOMROLLOFFFACTOR:
4696 eax_defer<Eax2SourceRoomRolloffFactorValidator>(call, props.flRoomRolloffFactor);
4697 break;
4699 case EAXSOURCE_AIRABSORPTIONFACTOR:
4700 eax_defer<Eax2SourceAirAbsorptionFactorValidator>(call, props.flAirAbsorptionFactor);
4701 break;
4703 case EAXSOURCE_FLAGS:
4704 eax_defer<Eax2SourceFlagsValidator>(call, props.ulFlags);
4705 break;
4707 default:
4708 eax_fail_unknown_property_id();
4712 void ALsource::eax4_set(const EaxCall& call, Eax4Props& props)
4714 switch (call.get_property_id()) {
4715 case EAXSOURCE_NONE:
4716 case EAXSOURCE_ALLPARAMETERS:
4717 case EAXSOURCE_OBSTRUCTIONPARAMETERS:
4718 case EAXSOURCE_OCCLUSIONPARAMETERS:
4719 case EAXSOURCE_EXCLUSIONPARAMETERS:
4720 case EAXSOURCE_DIRECT:
4721 case EAXSOURCE_DIRECTHF:
4722 case EAXSOURCE_ROOM:
4723 case EAXSOURCE_ROOMHF:
4724 case EAXSOURCE_OBSTRUCTION:
4725 case EAXSOURCE_OBSTRUCTIONLFRATIO:
4726 case EAXSOURCE_OCCLUSION:
4727 case EAXSOURCE_OCCLUSIONLFRATIO:
4728 case EAXSOURCE_OCCLUSIONROOMRATIO:
4729 case EAXSOURCE_OCCLUSIONDIRECTRATIO:
4730 case EAXSOURCE_EXCLUSION:
4731 case EAXSOURCE_EXCLUSIONLFRATIO:
4732 case EAXSOURCE_OUTSIDEVOLUMEHF:
4733 case EAXSOURCE_DOPPLERFACTOR:
4734 case EAXSOURCE_ROLLOFFFACTOR:
4735 case EAXSOURCE_ROOMROLLOFFFACTOR:
4736 case EAXSOURCE_AIRABSORPTIONFACTOR:
4737 case EAXSOURCE_FLAGS:
4738 eax3_set(call, props.source);
4739 break;
4741 case EAXSOURCE_SENDPARAMETERS:
4742 eax4_defer_sends<Eax4SendValidator, EAXSOURCESENDPROPERTIES>(call, props.sends);
4743 break;
4745 case EAXSOURCE_ALLSENDPARAMETERS:
4746 eax4_defer_sends<Eax4AllSendValidator, EAXSOURCEALLSENDPROPERTIES>(call, props.sends);
4747 break;
4749 case EAXSOURCE_OCCLUSIONSENDPARAMETERS:
4750 eax4_defer_sends<Eax4OcclusionSendValidator, EAXSOURCEOCCLUSIONSENDPROPERTIES>(call, props.sends);
4751 break;
4753 case EAXSOURCE_EXCLUSIONSENDPARAMETERS:
4754 eax4_defer_sends<Eax4ExclusionSendValidator, EAXSOURCEEXCLUSIONSENDPROPERTIES>(call, props.sends);
4755 break;
4757 case EAXSOURCE_ACTIVEFXSLOTID:
4758 eax4_defer_active_fx_slot_id(call, props.active_fx_slots.guidActiveFXSlots);
4759 break;
4761 default:
4762 eax_fail_unknown_property_id();
4766 void ALsource::eax5_defer_all_2d(const EaxCall& call, EAX50SOURCEPROPERTIES& props)
4768 const auto& src_props = call.get_value<Exception, const EAXSOURCE2DPROPERTIES>();
4769 Eax5SourceAll2dValidator{}(src_props);
4770 props.lDirect = src_props.lDirect;
4771 props.lDirectHF = src_props.lDirectHF;
4772 props.lRoom = src_props.lRoom;
4773 props.lRoomHF = src_props.lRoomHF;
4774 props.ulFlags = src_props.ulFlags;
4777 void ALsource::eax5_defer_speaker_levels(const EaxCall& call, EaxSpeakerLevels& props)
4779 const auto values = call.get_values<const EAXSPEAKERLEVELPROPERTIES>(eax_max_speakers);
4780 std::for_each(values.cbegin(), values.cend(), Eax5SpeakerAllValidator{});
4782 for (const auto& value : values) {
4783 const auto index = static_cast<size_t>(value.lSpeakerID - EAXSPEAKER_FRONT_LEFT);
4784 props[index].lLevel = value.lLevel;
4788 void ALsource::eax5_set(const EaxCall& call, Eax5Props& props)
4790 switch (call.get_property_id()) {
4791 case EAXSOURCE_NONE:
4792 break;
4794 case EAXSOURCE_ALLPARAMETERS:
4795 eax_defer<Eax5SourceAllValidator>(call, props.source);
4796 break;
4798 case EAXSOURCE_OBSTRUCTIONPARAMETERS:
4799 case EAXSOURCE_OCCLUSIONPARAMETERS:
4800 case EAXSOURCE_EXCLUSIONPARAMETERS:
4801 case EAXSOURCE_DIRECT:
4802 case EAXSOURCE_DIRECTHF:
4803 case EAXSOURCE_ROOM:
4804 case EAXSOURCE_ROOMHF:
4805 case EAXSOURCE_OBSTRUCTION:
4806 case EAXSOURCE_OBSTRUCTIONLFRATIO:
4807 case EAXSOURCE_OCCLUSION:
4808 case EAXSOURCE_OCCLUSIONLFRATIO:
4809 case EAXSOURCE_OCCLUSIONROOMRATIO:
4810 case EAXSOURCE_OCCLUSIONDIRECTRATIO:
4811 case EAXSOURCE_EXCLUSION:
4812 case EAXSOURCE_EXCLUSIONLFRATIO:
4813 case EAXSOURCE_OUTSIDEVOLUMEHF:
4814 case EAXSOURCE_DOPPLERFACTOR:
4815 case EAXSOURCE_ROLLOFFFACTOR:
4816 case EAXSOURCE_ROOMROLLOFFFACTOR:
4817 case EAXSOURCE_AIRABSORPTIONFACTOR:
4818 case EAXSOURCE_FLAGS:
4819 eax3_set(call, props.source);
4820 break;
4822 case EAXSOURCE_SENDPARAMETERS:
4823 eax5_defer_sends<Eax5SendValidator, EAXSOURCESENDPROPERTIES>(call, props.sends);
4824 break;
4826 case EAXSOURCE_ALLSENDPARAMETERS:
4827 eax5_defer_sends<Eax5AllSendValidator, EAXSOURCEALLSENDPROPERTIES>(call, props.sends);
4828 break;
4830 case EAXSOURCE_OCCLUSIONSENDPARAMETERS:
4831 eax5_defer_sends<Eax5OcclusionSendValidator, EAXSOURCEOCCLUSIONSENDPROPERTIES>(call, props.sends);
4832 break;
4834 case EAXSOURCE_EXCLUSIONSENDPARAMETERS:
4835 eax5_defer_sends<Eax5ExclusionSendValidator, EAXSOURCEEXCLUSIONSENDPROPERTIES>(call, props.sends);
4836 break;
4838 case EAXSOURCE_ACTIVEFXSLOTID:
4839 eax5_defer_active_fx_slot_id(call, props.active_fx_slots.guidActiveFXSlots);
4840 break;
4842 case EAXSOURCE_MACROFXFACTOR:
4843 eax_defer<Eax5SourceMacroFXFactorValidator>(call, props.source.flMacroFXFactor);
4844 break;
4846 case EAXSOURCE_SPEAKERLEVELS:
4847 eax5_defer_speaker_levels(call, props.speaker_levels);
4848 break;
4850 case EAXSOURCE_ALL2DPARAMETERS:
4851 eax5_defer_all_2d(call, props.source);
4852 break;
4854 default:
4855 eax_fail_unknown_property_id();
4859 void ALsource::eax_set(const EaxCall& call)
4861 const auto eax_version = call.get_version();
4862 switch(eax_version)
4864 case 1: eax1_set(call, mEax1.d); break;
4865 case 2: eax2_set(call, mEax2.d); break;
4866 case 3: eax3_set(call, mEax3.d); break;
4867 case 4: eax4_set(call, mEax4.d); break;
4868 case 5: eax5_set(call, mEax5.d); break;
4869 default: eax_fail_unknown_property_id();
4871 mEaxChanged = true;
4872 mEaxVersion = eax_version;
4875 void ALsource::eax_get_active_fx_slot_id(const EaxCall& call, const GUID* ids, size_t max_count)
4877 assert(ids != nullptr);
4878 assert(max_count == EAX40_MAX_ACTIVE_FXSLOTS || max_count == EAX50_MAX_ACTIVE_FXSLOTS);
4879 const auto dst_ids = call.get_values<GUID>(max_count);
4880 const auto count = dst_ids.size();
4881 std::uninitialized_copy_n(ids, count, dst_ids.begin());
4884 void ALsource::eax1_get(const EaxCall& call, const Eax1Props& props)
4886 switch (call.get_property_id()) {
4887 case DSPROPERTY_EAXBUFFER_ALL:
4888 case DSPROPERTY_EAXBUFFER_REVERBMIX:
4889 call.set_value<Exception>(props.fMix);
4890 break;
4892 default:
4893 eax_fail_unknown_property_id();
4897 void ALsource::eax2_get(const EaxCall& call, const Eax2Props& props)
4899 switch (call.get_property_id()) {
4900 case DSPROPERTY_EAX20BUFFER_NONE:
4901 break;
4903 case DSPROPERTY_EAX20BUFFER_ALLPARAMETERS:
4904 call.set_value<Exception>(props);
4905 break;
4907 case DSPROPERTY_EAX20BUFFER_DIRECT:
4908 call.set_value<Exception>(props.lDirect);
4909 break;
4911 case DSPROPERTY_EAX20BUFFER_DIRECTHF:
4912 call.set_value<Exception>(props.lDirectHF);
4913 break;
4915 case DSPROPERTY_EAX20BUFFER_ROOM:
4916 call.set_value<Exception>(props.lRoom);
4917 break;
4919 case DSPROPERTY_EAX20BUFFER_ROOMHF:
4920 call.set_value<Exception>(props.lRoomHF);
4921 break;
4923 case DSPROPERTY_EAX20BUFFER_ROOMROLLOFFFACTOR:
4924 call.set_value<Exception>(props.flRoomRolloffFactor);
4925 break;
4927 case DSPROPERTY_EAX20BUFFER_OBSTRUCTION:
4928 call.set_value<Exception>(props.lObstruction);
4929 break;
4931 case DSPROPERTY_EAX20BUFFER_OBSTRUCTIONLFRATIO:
4932 call.set_value<Exception>(props.flObstructionLFRatio);
4933 break;
4935 case DSPROPERTY_EAX20BUFFER_OCCLUSION:
4936 call.set_value<Exception>(props.lOcclusion);
4937 break;
4939 case DSPROPERTY_EAX20BUFFER_OCCLUSIONLFRATIO:
4940 call.set_value<Exception>(props.flOcclusionLFRatio);
4941 break;
4943 case DSPROPERTY_EAX20BUFFER_OCCLUSIONROOMRATIO:
4944 call.set_value<Exception>(props.flOcclusionRoomRatio);
4945 break;
4947 case DSPROPERTY_EAX20BUFFER_OUTSIDEVOLUMEHF:
4948 call.set_value<Exception>(props.lOutsideVolumeHF);
4949 break;
4951 case DSPROPERTY_EAX20BUFFER_AIRABSORPTIONFACTOR:
4952 call.set_value<Exception>(props.flAirAbsorptionFactor);
4953 break;
4955 case DSPROPERTY_EAX20BUFFER_FLAGS:
4956 call.set_value<Exception>(props.dwFlags);
4957 break;
4959 default:
4960 eax_fail_unknown_property_id();
4964 void ALsource::eax3_get_obstruction(const EaxCall& call, const Eax3Props& props)
4966 const auto& subprops = reinterpret_cast<const EAXOBSTRUCTIONPROPERTIES&>(props.lObstruction);
4967 call.set_value<Exception>(subprops);
4970 void ALsource::eax3_get_occlusion(const EaxCall& call, const Eax3Props& props)
4972 const auto& subprops = reinterpret_cast<const EAXOCCLUSIONPROPERTIES&>(props.lOcclusion);
4973 call.set_value<Exception>(subprops);
4976 void ALsource::eax3_get_exclusion(const EaxCall& call, const Eax3Props& props)
4978 const auto& subprops = reinterpret_cast<const EAXEXCLUSIONPROPERTIES&>(props.lExclusion);
4979 call.set_value<Exception>(subprops);
4982 void ALsource::eax3_get(const EaxCall& call, const Eax3Props& props)
4984 switch (call.get_property_id()) {
4985 case EAXSOURCE_NONE:
4986 break;
4988 case EAXSOURCE_ALLPARAMETERS:
4989 call.set_value<Exception>(props);
4990 break;
4992 case EAXSOURCE_OBSTRUCTIONPARAMETERS:
4993 eax3_get_obstruction(call, props);
4994 break;
4996 case EAXSOURCE_OCCLUSIONPARAMETERS:
4997 eax3_get_occlusion(call, props);
4998 break;
5000 case EAXSOURCE_EXCLUSIONPARAMETERS:
5001 eax3_get_exclusion(call, props);
5002 break;
5004 case EAXSOURCE_DIRECT:
5005 call.set_value<Exception>(props.lDirect);
5006 break;
5008 case EAXSOURCE_DIRECTHF:
5009 call.set_value<Exception>(props.lDirectHF);
5010 break;
5012 case EAXSOURCE_ROOM:
5013 call.set_value<Exception>(props.lRoom);
5014 break;
5016 case EAXSOURCE_ROOMHF:
5017 call.set_value<Exception>(props.lRoomHF);
5018 break;
5020 case EAXSOURCE_OBSTRUCTION:
5021 call.set_value<Exception>(props.lObstruction);
5022 break;
5024 case EAXSOURCE_OBSTRUCTIONLFRATIO:
5025 call.set_value<Exception>(props.flObstructionLFRatio);
5026 break;
5028 case EAXSOURCE_OCCLUSION:
5029 call.set_value<Exception>(props.lOcclusion);
5030 break;
5032 case EAXSOURCE_OCCLUSIONLFRATIO:
5033 call.set_value<Exception>(props.flOcclusionLFRatio);
5034 break;
5036 case EAXSOURCE_OCCLUSIONROOMRATIO:
5037 call.set_value<Exception>(props.flOcclusionRoomRatio);
5038 break;
5040 case EAXSOURCE_OCCLUSIONDIRECTRATIO:
5041 call.set_value<Exception>(props.flOcclusionDirectRatio);
5042 break;
5044 case EAXSOURCE_EXCLUSION:
5045 call.set_value<Exception>(props.lExclusion);
5046 break;
5048 case EAXSOURCE_EXCLUSIONLFRATIO:
5049 call.set_value<Exception>(props.flExclusionLFRatio);
5050 break;
5052 case EAXSOURCE_OUTSIDEVOLUMEHF:
5053 call.set_value<Exception>(props.lOutsideVolumeHF);
5054 break;
5056 case EAXSOURCE_DOPPLERFACTOR:
5057 call.set_value<Exception>(props.flDopplerFactor);
5058 break;
5060 case EAXSOURCE_ROLLOFFFACTOR:
5061 call.set_value<Exception>(props.flRolloffFactor);
5062 break;
5064 case EAXSOURCE_ROOMROLLOFFFACTOR:
5065 call.set_value<Exception>(props.flRoomRolloffFactor);
5066 break;
5068 case EAXSOURCE_AIRABSORPTIONFACTOR:
5069 call.set_value<Exception>(props.flAirAbsorptionFactor);
5070 break;
5072 case EAXSOURCE_FLAGS:
5073 call.set_value<Exception>(props.ulFlags);
5074 break;
5076 default:
5077 eax_fail_unknown_property_id();
5081 void ALsource::eax4_get(const EaxCall& call, const Eax4Props& props)
5083 switch (call.get_property_id()) {
5084 case EAXSOURCE_NONE:
5085 break;
5087 case EAXSOURCE_ALLPARAMETERS:
5088 case EAXSOURCE_OBSTRUCTIONPARAMETERS:
5089 case EAXSOURCE_OCCLUSIONPARAMETERS:
5090 case EAXSOURCE_EXCLUSIONPARAMETERS:
5091 case EAXSOURCE_DIRECT:
5092 case EAXSOURCE_DIRECTHF:
5093 case EAXSOURCE_ROOM:
5094 case EAXSOURCE_ROOMHF:
5095 case EAXSOURCE_OBSTRUCTION:
5096 case EAXSOURCE_OBSTRUCTIONLFRATIO:
5097 case EAXSOURCE_OCCLUSION:
5098 case EAXSOURCE_OCCLUSIONLFRATIO:
5099 case EAXSOURCE_OCCLUSIONROOMRATIO:
5100 case EAXSOURCE_OCCLUSIONDIRECTRATIO:
5101 case EAXSOURCE_EXCLUSION:
5102 case EAXSOURCE_EXCLUSIONLFRATIO:
5103 case EAXSOURCE_OUTSIDEVOLUMEHF:
5104 case EAXSOURCE_DOPPLERFACTOR:
5105 case EAXSOURCE_ROLLOFFFACTOR:
5106 case EAXSOURCE_ROOMROLLOFFFACTOR:
5107 case EAXSOURCE_AIRABSORPTIONFACTOR:
5108 case EAXSOURCE_FLAGS:
5109 eax3_get(call, props.source);
5110 break;
5112 case EAXSOURCE_SENDPARAMETERS:
5113 eax_get_sends<EAXSOURCESENDPROPERTIES>(call, props.sends);
5114 break;
5116 case EAXSOURCE_ALLSENDPARAMETERS:
5117 eax_get_sends<EAXSOURCEALLSENDPROPERTIES>(call, props.sends);
5118 break;
5120 case EAXSOURCE_OCCLUSIONSENDPARAMETERS:
5121 eax_get_sends<EAXSOURCEOCCLUSIONSENDPROPERTIES>(call, props.sends);
5122 break;
5124 case EAXSOURCE_EXCLUSIONSENDPARAMETERS:
5125 eax_get_sends<EAXSOURCEEXCLUSIONSENDPROPERTIES>(call, props.sends);
5126 break;
5128 case EAXSOURCE_ACTIVEFXSLOTID:
5129 eax_get_active_fx_slot_id(call, props.active_fx_slots.guidActiveFXSlots, EAX40_MAX_ACTIVE_FXSLOTS);
5130 break;
5132 default:
5133 eax_fail_unknown_property_id();
5137 void ALsource::eax5_get_all_2d(const EaxCall& call, const EAX50SOURCEPROPERTIES& props)
5139 auto& subprops = call.get_value<Exception, EAXSOURCE2DPROPERTIES>();
5140 subprops.lDirect = props.lDirect;
5141 subprops.lDirectHF = props.lDirectHF;
5142 subprops.lRoom = props.lRoom;
5143 subprops.lRoomHF = props.lRoomHF;
5144 subprops.ulFlags = props.ulFlags;
5147 void ALsource::eax5_get_speaker_levels(const EaxCall& call, const EaxSpeakerLevels& props)
5149 const auto subprops = call.get_values<EAXSPEAKERLEVELPROPERTIES>(eax_max_speakers);
5150 std::uninitialized_copy_n(props.cbegin(), subprops.size(), subprops.begin());
5153 void ALsource::eax5_get(const EaxCall& call, const Eax5Props& props)
5155 switch (call.get_property_id()) {
5156 case EAXSOURCE_NONE:
5157 break;
5159 case EAXSOURCE_ALLPARAMETERS:
5160 case EAXSOURCE_OBSTRUCTIONPARAMETERS:
5161 case EAXSOURCE_OCCLUSIONPARAMETERS:
5162 case EAXSOURCE_EXCLUSIONPARAMETERS:
5163 case EAXSOURCE_DIRECT:
5164 case EAXSOURCE_DIRECTHF:
5165 case EAXSOURCE_ROOM:
5166 case EAXSOURCE_ROOMHF:
5167 case EAXSOURCE_OBSTRUCTION:
5168 case EAXSOURCE_OBSTRUCTIONLFRATIO:
5169 case EAXSOURCE_OCCLUSION:
5170 case EAXSOURCE_OCCLUSIONLFRATIO:
5171 case EAXSOURCE_OCCLUSIONROOMRATIO:
5172 case EAXSOURCE_OCCLUSIONDIRECTRATIO:
5173 case EAXSOURCE_EXCLUSION:
5174 case EAXSOURCE_EXCLUSIONLFRATIO:
5175 case EAXSOURCE_OUTSIDEVOLUMEHF:
5176 case EAXSOURCE_DOPPLERFACTOR:
5177 case EAXSOURCE_ROLLOFFFACTOR:
5178 case EAXSOURCE_ROOMROLLOFFFACTOR:
5179 case EAXSOURCE_AIRABSORPTIONFACTOR:
5180 case EAXSOURCE_FLAGS:
5181 eax3_get(call, props.source);
5182 break;
5184 case EAXSOURCE_SENDPARAMETERS:
5185 eax_get_sends<EAXSOURCESENDPROPERTIES>(call, props.sends);
5186 break;
5188 case EAXSOURCE_ALLSENDPARAMETERS:
5189 eax_get_sends<EAXSOURCEALLSENDPROPERTIES>(call, props.sends);
5190 break;
5192 case EAXSOURCE_OCCLUSIONSENDPARAMETERS:
5193 eax_get_sends<EAXSOURCEOCCLUSIONSENDPROPERTIES>(call, props.sends);
5194 break;
5196 case EAXSOURCE_EXCLUSIONSENDPARAMETERS:
5197 eax_get_sends<EAXSOURCEEXCLUSIONSENDPROPERTIES>(call, props.sends);
5198 break;
5200 case EAXSOURCE_ACTIVEFXSLOTID:
5201 eax_get_active_fx_slot_id(call, props.active_fx_slots.guidActiveFXSlots, EAX50_MAX_ACTIVE_FXSLOTS);
5202 break;
5204 case EAXSOURCE_MACROFXFACTOR:
5205 call.set_value<Exception>(props.source.flMacroFXFactor);
5206 break;
5208 case EAXSOURCE_SPEAKERLEVELS:
5209 call.set_value<Exception>(props.speaker_levels);
5210 break;
5212 case EAXSOURCE_ALL2DPARAMETERS:
5213 eax5_get_all_2d(call, props.source);
5214 break;
5216 default:
5217 eax_fail_unknown_property_id();
5221 void ALsource::eax_get(const EaxCall& call)
5223 switch (call.get_version()) {
5224 case 1: eax1_get(call, mEax1.i); break;
5225 case 2: eax2_get(call, mEax2.i); break;
5226 case 3: eax3_get(call, mEax3.i); break;
5227 case 4: eax4_get(call, mEax4.i); break;
5228 case 5: eax5_get(call, mEax5.i); break;
5229 default: eax_fail_unknown_version();
5233 void ALsource::eax_set_al_source_send(ALeffectslot *slot, size_t sendidx, const EaxAlLowPassParam &filter)
5235 if(sendidx >= EAX_MAX_FXSLOTS)
5236 return;
5238 auto &send = Send[sendidx];
5239 send.Gain = filter.gain;
5240 send.GainHF = filter.gain_hf;
5241 send.HFReference = LOWPASSFREQREF;
5242 send.GainLF = 1.0f;
5243 send.LFReference = HIGHPASSFREQREF;
5245 if(slot != nullptr)
5246 IncrementRef(slot->ref);
5247 if(auto *oldslot = send.Slot)
5248 DecrementRef(oldslot->ref);
5250 send.Slot = slot;
5251 mPropsDirty = true;
5254 void ALsource::eax_commit_active_fx_slots()
5256 // Clear all slots to an inactive state.
5257 mEaxActiveFxSlots.fill(false);
5259 // Mark the set slots as active.
5260 for(const auto& slot_id : mEax.active_fx_slots.guidActiveFXSlots)
5262 if(slot_id == EAX_NULL_GUID)
5265 else if(slot_id == EAX_PrimaryFXSlotID)
5267 // Mark primary FX slot as active.
5268 if(mEaxPrimaryFxSlotId.has_value())
5269 mEaxActiveFxSlots[*mEaxPrimaryFxSlotId] = true;
5271 else if(slot_id == EAXPROPERTYID_EAX50_FXSlot0)
5272 mEaxActiveFxSlots[0] = true;
5273 else if(slot_id == EAXPROPERTYID_EAX50_FXSlot1)
5274 mEaxActiveFxSlots[1] = true;
5275 else if(slot_id == EAXPROPERTYID_EAX50_FXSlot2)
5276 mEaxActiveFxSlots[2] = true;
5277 else if(slot_id == EAXPROPERTYID_EAX50_FXSlot3)
5278 mEaxActiveFxSlots[3] = true;
5281 // Deactivate EFX auxiliary effect slots for inactive slots. Active slots
5282 // will be updated with the room filters.
5283 for(auto i = size_t{}; i < EAX_MAX_FXSLOTS; ++i)
5285 if(!mEaxActiveFxSlots[i])
5286 eax_set_al_source_send(nullptr, i, EaxAlLowPassParam{1.0f, 1.0f});
5290 void ALsource::eax_commit_filters()
5292 eax_update_direct_filter();
5293 eax_update_room_filters();
5296 void ALsource::eaxCommit()
5298 const auto primary_fx_slot_id = mEaxAlContext->eaxGetPrimaryFxSlotIndex();
5299 const auto is_primary_fx_slot_id_changed = (mEaxPrimaryFxSlotId != primary_fx_slot_id);
5301 if(!mEaxChanged && !is_primary_fx_slot_id_changed)
5302 return;
5304 mEaxPrimaryFxSlotId = primary_fx_slot_id;
5305 mEaxChanged = false;
5307 switch(mEaxVersion)
5309 case 1:
5310 mEax1.i = mEax1.d;
5311 eax1_translate(mEax1.i, mEax);
5312 break;
5313 case 2:
5314 mEax2.i = mEax2.d;
5315 eax2_translate(mEax2.i, mEax);
5316 break;
5317 case 3:
5318 mEax3.i = mEax3.d;
5319 eax3_translate(mEax3.i, mEax);
5320 break;
5321 case 4:
5322 mEax4.i = mEax4.d;
5323 eax4_translate(mEax4.i, mEax);
5324 break;
5325 case 5:
5326 mEax5.i = mEax5.d;
5327 mEax = mEax5.d;
5328 break;
5331 eax_set_efx_outer_gain_hf();
5332 eax_set_efx_doppler_factor();
5333 eax_set_efx_rolloff_factor();
5334 eax_set_efx_room_rolloff_factor();
5335 eax_set_efx_air_absorption_factor();
5336 eax_set_efx_dry_gain_hf_auto();
5337 eax_set_efx_wet_gain_auto();
5338 eax_set_efx_wet_gain_hf_auto();
5340 eax_commit_active_fx_slots();
5341 eax_commit_filters();
5344 #endif // ALSOFT_EAX