Handle AL effect properties with a variant
[openal-soft.git] / al / auxeffectslot.cpp
blob4f21278d45326120d1ac729a589b04d6efcc180f
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 "auxeffectslot.h"
25 #include <algorithm>
26 #include <cstddef>
27 #include <cstdint>
28 #include <functional>
29 #include <iterator>
30 #include <memory>
31 #include <mutex>
32 #include <numeric>
33 #include <stdexcept>
34 #include <string>
35 #include <tuple>
36 #include <unordered_map>
37 #include <vector>
39 #include "AL/al.h"
40 #include "AL/alc.h"
41 #include "AL/alext.h"
42 #include "AL/efx.h"
44 #include "albit.h"
45 #include "alc/alu.h"
46 #include "alc/context.h"
47 #include "alc/device.h"
48 #include "alc/effects/base.h"
49 #include "alc/inprogext.h"
50 #include "almalloc.h"
51 #include "alnumeric.h"
52 #include "alspan.h"
53 #include "atomic.h"
54 #include "buffer.h"
55 #include "core/buffer_storage.h"
56 #include "core/device.h"
57 #include "core/fpu_ctrl.h"
58 #include "core/logging.h"
59 #include "direct_defs.h"
60 #include "effect.h"
61 #include "error.h"
62 #include "flexarray.h"
63 #include "opthelpers.h"
65 #ifdef ALSOFT_EAX
66 #include "eax/api.h"
67 #include "eax/call.h"
68 #include "eax/effect.h"
69 #include "eax/fx_slot_index.h"
70 #include "eax/utils.h"
71 #endif
73 namespace {
75 using SubListAllocator = al::allocator<std::array<ALeffectslot,64>>;
77 EffectStateFactory *getFactoryByType(EffectSlotType type)
79 switch(type)
81 case EffectSlotType::None: return NullStateFactory_getFactory();
82 case EffectSlotType::EAXReverb: return ReverbStateFactory_getFactory();
83 case EffectSlotType::Reverb: return StdReverbStateFactory_getFactory();
84 case EffectSlotType::Autowah: return AutowahStateFactory_getFactory();
85 case EffectSlotType::Chorus: return ChorusStateFactory_getFactory();
86 case EffectSlotType::Compressor: return CompressorStateFactory_getFactory();
87 case EffectSlotType::Distortion: return DistortionStateFactory_getFactory();
88 case EffectSlotType::Echo: return EchoStateFactory_getFactory();
89 case EffectSlotType::Equalizer: return EqualizerStateFactory_getFactory();
90 case EffectSlotType::Flanger: return ChorusStateFactory_getFactory();
91 case EffectSlotType::FrequencyShifter: return FshifterStateFactory_getFactory();
92 case EffectSlotType::RingModulator: return ModulatorStateFactory_getFactory();
93 case EffectSlotType::PitchShifter: return PshifterStateFactory_getFactory();
94 case EffectSlotType::VocalMorpher: return VmorpherStateFactory_getFactory();
95 case EffectSlotType::DedicatedDialog: return DedicatedStateFactory_getFactory();
96 case EffectSlotType::DedicatedLFE: return DedicatedStateFactory_getFactory();
97 case EffectSlotType::Convolution: return ConvolutionStateFactory_getFactory();
99 return nullptr;
103 auto LookupEffectSlot(ALCcontext *context, ALuint id) noexcept -> ALeffectslot*
105 const size_t lidx{(id-1) >> 6};
106 const ALuint slidx{(id-1) & 0x3f};
108 if(lidx >= context->mEffectSlotList.size()) UNLIKELY
109 return nullptr;
110 EffectSlotSubList &sublist{context->mEffectSlotList[lidx]};
111 if(sublist.FreeMask & (1_u64 << slidx)) UNLIKELY
112 return nullptr;
113 return al::to_address(sublist.EffectSlots->begin() + slidx);
116 inline auto LookupEffect(ALCdevice *device, ALuint id) noexcept -> ALeffect*
118 const size_t lidx{(id-1) >> 6};
119 const ALuint slidx{(id-1) & 0x3f};
121 if(lidx >= device->EffectList.size()) UNLIKELY
122 return nullptr;
123 EffectSubList &sublist = device->EffectList[lidx];
124 if(sublist.FreeMask & (1_u64 << slidx)) UNLIKELY
125 return nullptr;
126 return al::to_address(sublist.Effects->begin() + slidx);
129 inline auto LookupBuffer(ALCdevice *device, ALuint id) noexcept -> ALbuffer*
131 const size_t lidx{(id-1) >> 6};
132 const ALuint slidx{(id-1) & 0x3f};
134 if(lidx >= device->BufferList.size()) UNLIKELY
135 return nullptr;
136 BufferSubList &sublist = device->BufferList[lidx];
137 if(sublist.FreeMask & (1_u64 << slidx)) UNLIKELY
138 return nullptr;
139 return al::to_address(sublist.Buffers->begin() + slidx);
143 void AddActiveEffectSlots(const al::span<ALeffectslot*> auxslots, ALCcontext *context)
145 if(auxslots.empty()) return;
146 EffectSlotArray *curarray{context->mActiveAuxSlots.load(std::memory_order_acquire)};
147 if((curarray->size()>>1) > std::numeric_limits<size_t>::max()-auxslots.size())
148 throw std::runtime_error{"Too many active effect slots"};
150 size_t newcount{(curarray->size()>>1) + auxslots.size()};
151 if(newcount > std::numeric_limits<size_t>::max()>>1)
152 throw std::runtime_error{"Too many active effect slots"};
154 /* Insert the new effect slots into the head of the new array, followed by
155 * the existing ones.
157 auto newarray = EffectSlot::CreatePtrArray(newcount<<1);
158 auto new_end = std::transform(auxslots.begin(), auxslots.end(), newarray->begin(),
159 std::mem_fn(&ALeffectslot::mSlot));
160 new_end = std::copy_n(curarray->begin(), curarray->size()>>1, new_end);
162 /* Remove any duplicates (first instance of each will be kept). */
163 for(auto start=newarray->begin()+1;;)
165 new_end = std::remove(start, new_end, *(start-1));
166 if(start == new_end) break;
167 ++start;
169 newcount = static_cast<size_t>(std::distance(newarray->begin(), new_end));
171 /* Reallocate newarray if the new size ended up smaller from duplicate
172 * removal.
174 if(newcount < newarray->size()>>1) UNLIKELY
176 auto oldarray = std::move(newarray);
177 newarray = EffectSlot::CreatePtrArray(newcount<<1);
178 new_end = std::copy_n(oldarray->begin(), newcount, newarray->begin());
180 std::fill(new_end, newarray->end(), nullptr);
182 auto oldarray = context->mActiveAuxSlots.exchange(std::move(newarray),
183 std::memory_order_acq_rel);
184 std::ignore = context->mDevice->waitForMix();
187 void RemoveActiveEffectSlots(const al::span<ALeffectslot*> auxslots, ALCcontext *context)
189 if(auxslots.empty()) return;
190 EffectSlotArray *curarray{context->mActiveAuxSlots.load(std::memory_order_acquire)};
192 /* Don't shrink the allocated array size since we don't know how many (if
193 * any) of the effect slots to remove are in the array.
195 auto newarray = EffectSlot::CreatePtrArray(curarray->size());
197 auto new_end = std::copy_n(curarray->begin(), curarray->size()>>1, newarray->begin());
198 /* Remove elements from newarray that match any ID in slotids. */
199 for(const ALeffectslot *auxslot : auxslots)
201 auto slot_match = [auxslot](EffectSlot *slot) noexcept -> bool
202 { return (slot == auxslot->mSlot); };
203 new_end = std::remove_if(newarray->begin(), new_end, slot_match);
206 /* Reallocate with the new size. */
207 auto newsize = static_cast<size_t>(std::distance(newarray->begin(), new_end));
208 if(newsize < newarray->size()>>1) LIKELY
210 auto oldarray = std::move(newarray);
211 newarray = EffectSlot::CreatePtrArray(newsize<<1);
212 new_end = std::copy_n(oldarray->begin(), newsize, newarray->begin());
214 std::fill(new_end, newarray->end(), nullptr);
216 auto oldarray = context->mActiveAuxSlots.exchange(std::move(newarray),
217 std::memory_order_acq_rel);
218 std::ignore = context->mDevice->waitForMix();
222 constexpr auto EffectSlotTypeFromEnum(ALenum type) noexcept -> EffectSlotType
224 switch(type)
226 case AL_EFFECT_NULL: return EffectSlotType::None;
227 case AL_EFFECT_REVERB: return EffectSlotType::Reverb;
228 case AL_EFFECT_CHORUS: return EffectSlotType::Chorus;
229 case AL_EFFECT_DISTORTION: return EffectSlotType::Distortion;
230 case AL_EFFECT_ECHO: return EffectSlotType::Echo;
231 case AL_EFFECT_FLANGER: return EffectSlotType::Flanger;
232 case AL_EFFECT_FREQUENCY_SHIFTER: return EffectSlotType::FrequencyShifter;
233 case AL_EFFECT_VOCAL_MORPHER: return EffectSlotType::VocalMorpher;
234 case AL_EFFECT_PITCH_SHIFTER: return EffectSlotType::PitchShifter;
235 case AL_EFFECT_RING_MODULATOR: return EffectSlotType::RingModulator;
236 case AL_EFFECT_AUTOWAH: return EffectSlotType::Autowah;
237 case AL_EFFECT_COMPRESSOR: return EffectSlotType::Compressor;
238 case AL_EFFECT_EQUALIZER: return EffectSlotType::Equalizer;
239 case AL_EFFECT_EAXREVERB: return EffectSlotType::EAXReverb;
240 case AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT: return EffectSlotType::DedicatedLFE;
241 case AL_EFFECT_DEDICATED_DIALOGUE: return EffectSlotType::DedicatedDialog;
242 case AL_EFFECT_CONVOLUTION_SOFT: return EffectSlotType::Convolution;
244 ERR("Unhandled effect enum: 0x%04x\n", type);
245 return EffectSlotType::None;
248 auto EnsureEffectSlots(ALCcontext *context, size_t needed) noexcept -> bool
249 try {
250 size_t count{std::accumulate(context->mEffectSlotList.cbegin(),
251 context->mEffectSlotList.cend(), 0_uz,
252 [](size_t cur, const EffectSlotSubList &sublist) noexcept -> size_t
253 { return cur + static_cast<ALuint>(al::popcount(sublist.FreeMask)); })};
255 while(needed > count)
257 if(context->mEffectSlotList.size() >= 1<<25) UNLIKELY
258 return false;
260 EffectSlotSubList sublist{};
261 sublist.FreeMask = ~0_u64;
262 sublist.EffectSlots = SubListAllocator{}.allocate(1);
263 context->mEffectSlotList.emplace_back(std::move(sublist));
264 count += std::tuple_size_v<SubListAllocator::value_type>;
266 return true;
268 catch(...) {
269 return false;
272 ALeffectslot *AllocEffectSlot(ALCcontext *context)
274 auto sublist = std::find_if(context->mEffectSlotList.begin(), context->mEffectSlotList.end(),
275 [](const EffectSlotSubList &entry) noexcept -> bool
276 { return entry.FreeMask != 0; });
277 auto lidx = static_cast<ALuint>(std::distance(context->mEffectSlotList.begin(), sublist));
278 auto slidx = static_cast<ALuint>(al::countr_zero(sublist->FreeMask));
279 ASSUME(slidx < 64);
281 ALeffectslot *slot{al::construct_at(al::to_address(sublist->EffectSlots->begin() + slidx),
282 context)};
283 aluInitEffectPanning(slot->mSlot, context);
285 /* Add 1 to avoid ID 0. */
286 slot->id = ((lidx<<6) | slidx) + 1;
288 context->mNumEffectSlots += 1;
289 sublist->FreeMask &= ~(1_u64 << slidx);
291 return slot;
294 void FreeEffectSlot(ALCcontext *context, ALeffectslot *slot)
296 context->mEffectSlotNames.erase(slot->id);
298 const ALuint id{slot->id - 1};
299 const size_t lidx{id >> 6};
300 const ALuint slidx{id & 0x3f};
302 std::destroy_at(slot);
304 context->mEffectSlotList[lidx].FreeMask |= 1_u64 << slidx;
305 context->mNumEffectSlots--;
309 inline void UpdateProps(ALeffectslot *slot, ALCcontext *context)
311 if(!context->mDeferUpdates && slot->mState == SlotState::Playing)
313 slot->updateProps(context);
314 return;
316 slot->mPropsDirty = true;
319 } // namespace
322 AL_API DECL_FUNC2(void, alGenAuxiliaryEffectSlots, ALsizei,n, ALuint*,effectslots)
323 FORCE_ALIGN void AL_APIENTRY alGenAuxiliaryEffectSlotsDirect(ALCcontext *context, ALsizei n,
324 ALuint *effectslots) noexcept
325 try {
326 if(n < 0)
327 throw al::context_error{AL_INVALID_VALUE, "Generating %d effect slots", n};
328 if(n <= 0) UNLIKELY return;
330 std::lock_guard<std::mutex> slotlock{context->mEffectSlotLock};
331 ALCdevice *device{context->mALDevice.get()};
333 const al::span eids{effectslots, static_cast<ALuint>(n)};
334 if(eids.size() > device->AuxiliaryEffectSlotMax-context->mNumEffectSlots)
335 throw al::context_error{AL_OUT_OF_MEMORY, "Exceeding %u effect slot limit (%u + %d)",
336 device->AuxiliaryEffectSlotMax, context->mNumEffectSlots, n};
338 if(!EnsureEffectSlots(context, eids.size()))
339 throw al::context_error{AL_OUT_OF_MEMORY, "Failed to allocate %d effectslot%s", n,
340 (n == 1) ? "" : "s"};
342 std::vector<ALeffectslot*> slots;
343 try {
344 if(eids.size() == 1)
346 /* Special handling for the easy and normal case. */
347 eids[0] = AllocEffectSlot(context)->id;
349 else
351 slots.reserve(eids.size());
352 std::generate_n(std::back_inserter(slots), eids.size(),
353 [context]{ return AllocEffectSlot(context); });
355 std::transform(slots.cbegin(), slots.cend(), eids.begin(),
356 [](ALeffectslot *slot) -> ALuint { return slot->id; });
359 catch(std::exception& e) {
360 ERR("Exception allocating effectslot %zu of %d: %s\n", slots.size()+1, n, e.what());
361 auto delete_effectslot = [context](ALeffectslot *slot) -> void
362 { FreeEffectSlot(context, slot); };
363 std::for_each(slots.begin(), slots.end(), delete_effectslot);
364 throw al::context_error{AL_INVALID_OPERATION, "Exception allocating %d effectslots: %s", n,
365 e.what()};
368 catch(al::context_error& e) {
369 context->setError(e.errorCode(), "%s", e.what());
372 AL_API DECL_FUNC2(void, alDeleteAuxiliaryEffectSlots, ALsizei,n, const ALuint*,effectslots)
373 FORCE_ALIGN void AL_APIENTRY alDeleteAuxiliaryEffectSlotsDirect(ALCcontext *context, ALsizei n,
374 const ALuint *effectslots) noexcept
375 try {
376 if(n < 0) UNLIKELY
377 throw al::context_error{AL_INVALID_VALUE, "Deleting %d effect slots", n};
378 if(n <= 0) UNLIKELY return;
380 std::lock_guard<std::mutex> slotlock{context->mEffectSlotLock};
381 if(n == 1)
383 ALeffectslot *slot{LookupEffectSlot(context, *effectslots)};
384 if(!slot)
385 throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", *effectslots};
386 if(slot->ref.load(std::memory_order_relaxed) != 0)
387 throw al::context_error{AL_INVALID_OPERATION, "Deleting in-use effect slot %u",
388 *effectslots};
390 RemoveActiveEffectSlots({&slot, 1u}, context);
391 FreeEffectSlot(context, slot);
393 else
395 const al::span eids{effectslots, static_cast<ALuint>(n)};
396 std::vector<ALeffectslot*> slots;
397 slots.reserve(eids.size());
399 auto lookupslot = [context](const ALuint eid) -> ALeffectslot*
401 ALeffectslot *slot{LookupEffectSlot(context, eid)};
402 if(!slot)
403 throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", eid};
404 if(slot->ref.load(std::memory_order_relaxed) != 0)
405 throw al::context_error{AL_INVALID_OPERATION, "Deleting in-use effect slot %u",
406 eid};
407 return slot;
409 std::transform(eids.cbegin(), eids.cend(), std::back_inserter(slots), lookupslot);
411 /* All effectslots are valid, remove and delete them */
412 RemoveActiveEffectSlots(slots, context);
414 auto delete_effectslot = [context](const ALuint eid) -> void
416 if(ALeffectslot *slot{LookupEffectSlot(context, eid)})
417 FreeEffectSlot(context, slot);
419 std::for_each(eids.begin(), eids.end(), delete_effectslot);
422 catch(al::context_error& e) {
423 context->setError(e.errorCode(), "%s", e.what());
426 AL_API DECL_FUNC1(ALboolean, alIsAuxiliaryEffectSlot, ALuint,effectslot)
427 FORCE_ALIGN ALboolean AL_APIENTRY alIsAuxiliaryEffectSlotDirect(ALCcontext *context,
428 ALuint effectslot) noexcept
430 std::lock_guard<std::mutex> slotlock{context->mEffectSlotLock};
431 if(LookupEffectSlot(context, effectslot) != nullptr)
432 return AL_TRUE;
433 return AL_FALSE;
437 AL_API void AL_APIENTRY alAuxiliaryEffectSlotPlaySOFT(ALuint slotid) noexcept
439 ContextRef context{GetContextRef()};
440 if(!context) UNLIKELY return;
442 std::lock_guard<std::mutex> slotlock{context->mEffectSlotLock};
443 ALeffectslot *slot{LookupEffectSlot(context.get(), slotid)};
444 if(!slot) UNLIKELY
446 context->setError(AL_INVALID_NAME, "Invalid effect slot ID %u", slotid);
447 return;
449 if(slot->mState == SlotState::Playing)
450 return;
452 slot->mPropsDirty = false;
453 slot->updateProps(context.get());
455 AddActiveEffectSlots({&slot, 1}, context.get());
456 slot->mState = SlotState::Playing;
459 AL_API void AL_APIENTRY alAuxiliaryEffectSlotPlayvSOFT(ALsizei n, const ALuint *slotids) noexcept
461 ContextRef context{GetContextRef()};
462 if(!context) UNLIKELY return;
464 try {
465 if(n < 0)
466 throw al::context_error{AL_INVALID_VALUE, "Playing %d effect slots", n};
467 if(n <= 0) UNLIKELY return;
469 auto ids = al::span{slotids, static_cast<ALuint>(n)};
470 auto slots = std::vector<ALeffectslot*>(ids.size());
471 std::lock_guard<std::mutex> slotlock{context->mEffectSlotLock};
473 auto lookupslot = [&context](const ALuint id) -> ALeffectslot*
475 ALeffectslot *slot{LookupEffectSlot(context.get(), id)};
476 if(!slot)
477 throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", id};
479 if(slot->mState != SlotState::Playing)
481 slot->mPropsDirty = false;
482 slot->updateProps(context.get());
484 return slot;
486 std::transform(ids.cbegin(), ids.cend(), slots.begin(), lookupslot);
488 AddActiveEffectSlots(slots, context.get());
489 for(auto slot : slots)
490 slot->mState = SlotState::Playing;
492 catch(al::context_error& e) {
493 context->setError(e.errorCode(), "%s", e.what());
494 return;
498 AL_API void AL_APIENTRY alAuxiliaryEffectSlotStopSOFT(ALuint slotid) noexcept
500 ContextRef context{GetContextRef()};
501 if(!context) UNLIKELY return;
503 std::lock_guard<std::mutex> slotlock{context->mEffectSlotLock};
504 ALeffectslot *slot{LookupEffectSlot(context.get(), slotid)};
505 if(!slot) UNLIKELY
507 context->setError(AL_INVALID_NAME, "Invalid effect slot ID %u", slotid);
508 return;
511 RemoveActiveEffectSlots({&slot, 1}, context.get());
512 slot->mState = SlotState::Stopped;
515 AL_API void AL_APIENTRY alAuxiliaryEffectSlotStopvSOFT(ALsizei n, const ALuint *slotids) noexcept
517 ContextRef context{GetContextRef()};
518 if(!context) UNLIKELY return;
520 try {
521 if(n < 0)
522 throw al::context_error{AL_INVALID_VALUE, "Stopping %d effect slots", n};
523 if(n <= 0) UNLIKELY return;
525 auto ids = al::span{slotids, static_cast<ALuint>(n)};
526 auto slots = std::vector<ALeffectslot*>(ids.size());
527 std::lock_guard<std::mutex> slotlock{context->mEffectSlotLock};
529 auto lookupslot = [&context](const ALuint id) -> ALeffectslot*
531 if(ALeffectslot *slot{LookupEffectSlot(context.get(), id)})
532 return slot;
533 throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", id};
535 std::transform(ids.cbegin(), ids.cend(), slots.begin(), lookupslot);
537 RemoveActiveEffectSlots(slots, context.get());
538 for(auto slot : slots)
539 slot->mState = SlotState::Stopped;
541 catch(al::context_error& e) {
542 context->setError(e.errorCode(), "%s", e.what());
543 return;
548 AL_API DECL_FUNC3(void, alAuxiliaryEffectSloti, ALuint,effectslot, ALenum,param, ALint,value)
549 FORCE_ALIGN void AL_APIENTRY alAuxiliaryEffectSlotiDirect(ALCcontext *context, ALuint effectslot,
550 ALenum param, ALint value) noexcept
551 try {
552 std::lock_guard<std::mutex> proplock{context->mPropLock};
553 std::lock_guard<std::mutex> slotlock{context->mEffectSlotLock};
555 ALeffectslot *slot{LookupEffectSlot(context, effectslot)};
556 if(!slot) UNLIKELY
557 throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", effectslot};
559 ALeffectslot *target{};
560 ALenum err{};
561 switch(param)
563 case AL_EFFECTSLOT_EFFECT:
565 ALCdevice *device{context->mALDevice.get()};
566 std::lock_guard<std::mutex> effectlock{device->EffectLock};
567 ALeffect *effect{value ? LookupEffect(device, static_cast<ALuint>(value)) : nullptr};
568 if(effect)
569 err = slot->initEffect(effect->id, effect->type, effect->Props, context);
570 else
572 if(value != 0)
573 throw al::context_error{AL_INVALID_VALUE, "Invalid effect ID %u", value};
574 err = slot->initEffect(0, AL_EFFECT_NULL, EffectProps{}, context);
577 if(err != AL_NO_ERROR)
578 throw al::context_error{err, "Effect initialization failed"};
580 if(slot->mState == SlotState::Initial) UNLIKELY
582 slot->mPropsDirty = false;
583 slot->updateProps(context);
585 AddActiveEffectSlots({&slot, 1}, context);
586 slot->mState = SlotState::Playing;
587 return;
589 UpdateProps(slot, context);
590 return;
592 case AL_EFFECTSLOT_AUXILIARY_SEND_AUTO:
593 if(!(value == AL_TRUE || value == AL_FALSE))
594 throw al::context_error{AL_INVALID_VALUE,
595 "Effect slot auxiliary send auto out of range"};
596 if(!(slot->AuxSendAuto == !!value)) LIKELY
598 slot->AuxSendAuto = !!value;
599 UpdateProps(slot, context);
601 return;
603 case AL_EFFECTSLOT_TARGET_SOFT:
604 target = LookupEffectSlot(context, static_cast<ALuint>(value));
605 if(value && !target)
606 throw al::context_error{AL_INVALID_VALUE, "Invalid effect slot target ID"};
607 if(slot->Target == target) UNLIKELY
608 return;
609 if(target)
611 ALeffectslot *checker{target};
612 while(checker && checker != slot)
613 checker = checker->Target;
614 if(checker)
615 throw al::context_error{AL_INVALID_OPERATION,
616 "Setting target of effect slot ID %u to %u creates circular chain", slot->id,
617 target->id};
620 if(ALeffectslot *oldtarget{slot->Target})
622 /* We must force an update if there was an existing effect slot
623 * target, in case it's about to be deleted.
625 if(target) IncrementRef(target->ref);
626 DecrementRef(oldtarget->ref);
627 slot->Target = target;
628 slot->updateProps(context);
629 return;
632 if(target) IncrementRef(target->ref);
633 slot->Target = target;
634 UpdateProps(slot, context);
635 return;
637 case AL_BUFFER:
638 if(slot->mState == SlotState::Playing)
639 throw al::context_error{AL_INVALID_OPERATION,
640 "Setting buffer on playing effect slot %u", slot->id};
642 if(ALbuffer *buffer{slot->Buffer})
644 if(buffer->id == static_cast<ALuint>(value)) UNLIKELY
645 return;
647 else if(value == 0) UNLIKELY
648 return;
651 ALCdevice *device{context->mALDevice.get()};
652 std::lock_guard<std::mutex> bufferlock{device->BufferLock};
653 ALbuffer *buffer{};
654 if(value)
656 buffer = LookupBuffer(device, static_cast<ALuint>(value));
657 if(!buffer)
658 throw al::context_error{AL_INVALID_VALUE, "Invalid buffer ID %u", value};
659 if(buffer->mCallback)
660 throw al::context_error{AL_INVALID_OPERATION,
661 "Callback buffer not valid for effects"};
663 IncrementRef(buffer->ref);
666 if(ALbuffer *oldbuffer{slot->Buffer})
667 DecrementRef(oldbuffer->ref);
668 slot->Buffer = buffer;
670 FPUCtl mixer_mode{};
671 auto *state = slot->Effect.State.get();
672 state->deviceUpdate(device, buffer);
674 UpdateProps(slot, context);
675 return;
677 case AL_EFFECTSLOT_STATE_SOFT:
678 throw al::context_error{AL_INVALID_OPERATION, "AL_EFFECTSLOT_STATE_SOFT is read-only"};
681 throw al::context_error{AL_INVALID_ENUM, "Invalid effect slot integer property 0x%04x", param};
683 catch(al::context_error& e) {
684 context->setError(e.errorCode(), "%s", e.what());
687 AL_API DECL_FUNC3(void, alAuxiliaryEffectSlotiv, ALuint,effectslot, ALenum,param, const ALint*,values)
688 FORCE_ALIGN void AL_APIENTRY alAuxiliaryEffectSlotivDirect(ALCcontext *context, ALuint effectslot,
689 ALenum param, const ALint *values) noexcept
690 try {
691 switch(param)
693 case AL_EFFECTSLOT_EFFECT:
694 case AL_EFFECTSLOT_AUXILIARY_SEND_AUTO:
695 case AL_EFFECTSLOT_TARGET_SOFT:
696 case AL_EFFECTSLOT_STATE_SOFT:
697 case AL_BUFFER:
698 alAuxiliaryEffectSlotiDirect(context, effectslot, param, *values);
699 return;
702 std::lock_guard<std::mutex> slotlock{context->mEffectSlotLock};
703 ALeffectslot *slot{LookupEffectSlot(context, effectslot)};
704 if(!slot)
705 throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", effectslot};
707 switch(param)
710 throw al::context_error{AL_INVALID_ENUM, "Invalid effect slot integer-vector property 0x%04x",
711 param};
713 catch(al::context_error& e) {
714 context->setError(e.errorCode(), "%s", e.what());
717 AL_API DECL_FUNC3(void, alAuxiliaryEffectSlotf, ALuint,effectslot, ALenum,param, ALfloat,value)
718 FORCE_ALIGN void AL_APIENTRY alAuxiliaryEffectSlotfDirect(ALCcontext *context, ALuint effectslot,
719 ALenum param, ALfloat value) noexcept
720 try {
721 std::lock_guard<std::mutex> proplock{context->mPropLock};
722 std::lock_guard<std::mutex> slotlock{context->mEffectSlotLock};
724 ALeffectslot *slot{LookupEffectSlot(context, effectslot)};
725 if(!slot)
726 throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", effectslot};
728 switch(param)
730 case AL_EFFECTSLOT_GAIN:
731 if(!(value >= 0.0f && value <= 1.0f))
732 throw al::context_error{AL_INVALID_VALUE, "Effect slot gain out of range"};
733 if(!(slot->Gain == value)) LIKELY
735 slot->Gain = value;
736 UpdateProps(slot, context);
738 return;
741 throw al::context_error{AL_INVALID_ENUM, "Invalid effect slot float property 0x%04x", param};
743 catch(al::context_error& e) {
744 context->setError(e.errorCode(), "%s", e.what());
747 AL_API DECL_FUNC3(void, alAuxiliaryEffectSlotfv, ALuint,effectslot, ALenum,param, const ALfloat*,values)
748 FORCE_ALIGN void AL_APIENTRY alAuxiliaryEffectSlotfvDirect(ALCcontext *context, ALuint effectslot,
749 ALenum param, const ALfloat *values) noexcept
750 try {
751 switch(param)
753 case AL_EFFECTSLOT_GAIN:
754 alAuxiliaryEffectSlotfDirect(context, effectslot, param, *values);
755 return;
758 std::lock_guard<std::mutex> slotlock{context->mEffectSlotLock};
759 ALeffectslot *slot{LookupEffectSlot(context, effectslot)};
760 if(!slot)
761 throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", effectslot};
763 switch(param)
766 throw al::context_error{AL_INVALID_ENUM, "Invalid effect slot float-vector property 0x%04x",
767 param};
769 catch(al::context_error& e) {
770 context->setError(e.errorCode(), "%s", e.what());
774 AL_API DECL_FUNC3(void, alGetAuxiliaryEffectSloti, ALuint,effectslot, ALenum,param, ALint*,value)
775 FORCE_ALIGN void AL_APIENTRY alGetAuxiliaryEffectSlotiDirect(ALCcontext *context,
776 ALuint effectslot, ALenum param, ALint *value) noexcept
777 try {
778 std::lock_guard<std::mutex> slotlock{context->mEffectSlotLock};
779 ALeffectslot *slot{LookupEffectSlot(context, effectslot)};
780 if(!slot)
781 throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", effectslot};
783 switch(param)
785 case AL_EFFECTSLOT_EFFECT:
786 *value = static_cast<ALint>(slot->EffectId);
787 return;
789 case AL_EFFECTSLOT_AUXILIARY_SEND_AUTO:
790 *value = slot->AuxSendAuto ? AL_TRUE : AL_FALSE;
791 return;
793 case AL_EFFECTSLOT_TARGET_SOFT:
794 if(auto *target = slot->Target)
795 *value = static_cast<ALint>(target->id);
796 else
797 *value = 0;
798 return;
800 case AL_EFFECTSLOT_STATE_SOFT:
801 *value = static_cast<int>(slot->mState);
802 return;
804 case AL_BUFFER:
805 if(auto *buffer = slot->Buffer)
806 *value = static_cast<ALint>(buffer->id);
807 else
808 *value = 0;
809 return;
812 throw al::context_error{AL_INVALID_ENUM, "Invalid effect slot integer property 0x%04x", param};
814 catch(al::context_error& e) {
815 context->setError(e.errorCode(), "%s", e.what());
818 AL_API DECL_FUNC3(void, alGetAuxiliaryEffectSlotiv, ALuint,effectslot, ALenum,param, ALint*,values)
819 FORCE_ALIGN void AL_APIENTRY alGetAuxiliaryEffectSlotivDirect(ALCcontext *context,
820 ALuint effectslot, ALenum param, ALint *values) noexcept
821 try {
822 switch(param)
824 case AL_EFFECTSLOT_EFFECT:
825 case AL_EFFECTSLOT_AUXILIARY_SEND_AUTO:
826 case AL_EFFECTSLOT_TARGET_SOFT:
827 case AL_EFFECTSLOT_STATE_SOFT:
828 case AL_BUFFER:
829 alGetAuxiliaryEffectSlotiDirect(context, effectslot, param, values);
830 return;
833 std::lock_guard<std::mutex> slotlock{context->mEffectSlotLock};
834 ALeffectslot *slot = LookupEffectSlot(context, effectslot);
835 if(!slot)
836 throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", effectslot};
838 switch(param)
841 throw al::context_error{AL_INVALID_ENUM, "Invalid effect slot integer-vector property 0x%04x",
842 param};
844 catch(al::context_error& e) {
845 context->setError(e.errorCode(), "%s", e.what());
848 AL_API DECL_FUNC3(void, alGetAuxiliaryEffectSlotf, ALuint,effectslot, ALenum,param, ALfloat*,value)
849 FORCE_ALIGN void AL_APIENTRY alGetAuxiliaryEffectSlotfDirect(ALCcontext *context,
850 ALuint effectslot, ALenum param, ALfloat *value) noexcept
851 try {
852 std::lock_guard<std::mutex> slotlock{context->mEffectSlotLock};
853 ALeffectslot *slot{LookupEffectSlot(context, effectslot)};
854 if(!slot)
855 throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", effectslot};
857 switch(param)
859 case AL_EFFECTSLOT_GAIN:
860 *value = slot->Gain;
861 return;
864 throw al::context_error{AL_INVALID_ENUM, "Invalid effect slot float property 0x%04x", param};
866 catch(al::context_error& e) {
867 context->setError(e.errorCode(), "%s", e.what());
870 AL_API DECL_FUNC3(void, alGetAuxiliaryEffectSlotfv, ALuint,effectslot, ALenum,param, ALfloat*,values)
871 FORCE_ALIGN void AL_APIENTRY alGetAuxiliaryEffectSlotfvDirect(ALCcontext *context,
872 ALuint effectslot, ALenum param, ALfloat *values) noexcept
873 try {
874 switch(param)
876 case AL_EFFECTSLOT_GAIN:
877 alGetAuxiliaryEffectSlotfDirect(context, effectslot, param, values);
878 return;
881 std::lock_guard<std::mutex> slotlock{context->mEffectSlotLock};
882 ALeffectslot *slot{LookupEffectSlot(context, effectslot)};
883 if(!slot)
884 throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", effectslot};
886 switch(param)
889 throw al::context_error{AL_INVALID_ENUM, "Invalid effect slot float-vector property 0x%04x",
890 param};
892 catch(al::context_error& e) {
893 context->setError(e.errorCode(), "%s", e.what());
897 ALeffectslot::ALeffectslot(ALCcontext *context)
899 EffectStateFactory *factory{getFactoryByType(EffectSlotType::None)};
900 if(!factory) throw std::runtime_error{"Failed to get null effect factory"};
902 al::intrusive_ptr<EffectState> state{factory->create()};
903 Effect.State = state;
905 mSlot = context->getEffectSlot();
906 mSlot->InUse = true;
907 mSlot->mEffectState = std::move(state);
910 ALeffectslot::~ALeffectslot()
912 if(Target)
913 DecrementRef(Target->ref);
914 Target = nullptr;
915 if(Buffer)
916 DecrementRef(Buffer->ref);
917 Buffer = nullptr;
919 if(auto *slot = mSlot->Update.exchange(nullptr, std::memory_order_relaxed))
920 slot->State = nullptr;
922 mSlot->mEffectState = nullptr;
923 mSlot->InUse = false;
926 ALenum ALeffectslot::initEffect(ALuint effectId, ALenum effectType, const EffectProps &effectProps,
927 ALCcontext *context)
929 EffectSlotType newtype{EffectSlotTypeFromEnum(effectType)};
930 if(newtype != Effect.Type)
932 EffectStateFactory *factory{getFactoryByType(newtype)};
933 if(!factory)
935 ERR("Failed to find factory for effect slot type %d\n", static_cast<int>(newtype));
936 return AL_INVALID_ENUM;
938 al::intrusive_ptr<EffectState> state{factory->create()};
940 ALCdevice *device{context->mALDevice.get()};
941 std::unique_lock<std::mutex> statelock{device->StateLock};
942 state->mOutTarget = device->Dry.Buffer;
944 FPUCtl mixer_mode{};
945 state->deviceUpdate(device, Buffer);
948 Effect.Type = newtype;
949 Effect.Props = effectProps;
951 Effect.State = std::move(state);
953 else if(newtype != EffectSlotType::None)
954 Effect.Props = effectProps;
955 EffectId = effectId;
957 /* Remove state references from old effect slot property updates. */
958 EffectSlotProps *props{context->mFreeEffectSlotProps.load()};
959 while(props)
961 props->State = nullptr;
962 props = props->next.load(std::memory_order_relaxed);
965 return AL_NO_ERROR;
968 void ALeffectslot::updateProps(ALCcontext *context) const
970 /* Get an unused property container, or allocate a new one as needed. */
971 EffectSlotProps *props{context->mFreeEffectSlotProps.load(std::memory_order_acquire)};
972 if(!props)
974 context->allocEffectSlotProps();
975 props = context->mFreeEffectSlotProps.load(std::memory_order_acquire);
977 EffectSlotProps *next;
978 do {
979 next = props->next.load(std::memory_order_relaxed);
980 } while(!context->mFreeEffectSlotProps.compare_exchange_weak(props, next,
981 std::memory_order_acq_rel, std::memory_order_acquire));
983 /* Copy in current property values. */
984 props->Gain = Gain;
985 props->AuxSendAuto = AuxSendAuto;
986 props->Target = Target ? Target->mSlot : nullptr;
988 props->Type = Effect.Type;
989 props->Props = Effect.Props;
990 props->State = Effect.State;
992 /* Set the new container for updating internal parameters. */
993 props = mSlot->Update.exchange(props, std::memory_order_acq_rel);
994 if(props)
996 /* If there was an unused update container, put it back in the
997 * freelist.
999 props->State = nullptr;
1000 AtomicReplaceHead(context->mFreeEffectSlotProps, props);
1004 void ALeffectslot::SetName(ALCcontext* context, ALuint id, std::string_view name)
1006 std::lock_guard<std::mutex> slotlock{context->mEffectSlotLock};
1008 auto slot = LookupEffectSlot(context, id);
1009 if(!slot)
1010 throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", id};
1012 context->mEffectSlotNames.insert_or_assign(id, name);
1015 void UpdateAllEffectSlotProps(ALCcontext *context)
1017 std::lock_guard<std::mutex> slotlock{context->mEffectSlotLock};
1018 for(auto &sublist : context->mEffectSlotList)
1020 uint64_t usemask{~sublist.FreeMask};
1021 while(usemask)
1023 const auto idx = static_cast<uint>(al::countr_zero(usemask));
1024 usemask &= ~(1_u64 << idx);
1025 auto &slot = (*sublist.EffectSlots)[idx];
1027 if(slot.mState != SlotState::Stopped && std::exchange(slot.mPropsDirty, false))
1028 slot.updateProps(context);
1033 EffectSlotSubList::~EffectSlotSubList()
1035 if(!EffectSlots)
1036 return;
1038 uint64_t usemask{~FreeMask};
1039 while(usemask)
1041 const int idx{al::countr_zero(usemask)};
1042 std::destroy_at(al::to_address(EffectSlots->begin() + idx));
1043 usemask &= ~(1_u64 << idx);
1045 FreeMask = ~usemask;
1046 SubListAllocator{}.deallocate(EffectSlots, 1);
1047 EffectSlots = nullptr;
1050 #ifdef ALSOFT_EAX
1051 void ALeffectslot::eax_initialize(ALCcontext& al_context, EaxFxSlotIndexValue index)
1053 if(index >= EAX_MAX_FXSLOTS)
1054 eax_fail("Index out of range.");
1056 eax_al_context_ = &al_context;
1057 eax_fx_slot_index_ = index;
1058 eax_fx_slot_set_defaults();
1060 eax_effect_ = std::make_unique<EaxEffect>();
1061 if(index == 0) eax_effect_->init<EaxReverbCommitter>();
1062 else if(index == 1) eax_effect_->init<EaxChorusCommitter>();
1063 else eax_effect_->init<EaxNullCommitter>();
1066 void ALeffectslot::eax_commit()
1068 if(eax_df_ != EaxDirtyFlags{})
1070 auto df = EaxDirtyFlags{};
1071 switch(eax_version_)
1073 case 1:
1074 case 2:
1075 case 3:
1076 eax5_fx_slot_commit(eax123_, df);
1077 break;
1078 case 4:
1079 eax4_fx_slot_commit(df);
1080 break;
1081 case 5:
1082 eax5_fx_slot_commit(eax5_, df);
1083 break;
1085 eax_df_ = EaxDirtyFlags{};
1087 if((df & eax_volume_dirty_bit) != EaxDirtyFlags{})
1088 eax_fx_slot_set_volume();
1089 if((df & eax_flags_dirty_bit) != EaxDirtyFlags{})
1090 eax_fx_slot_set_flags();
1093 if(eax_effect_->commit(eax_version_))
1094 eax_set_efx_slot_effect(*eax_effect_);
1097 [[noreturn]] void ALeffectslot::eax_fail(const char* message)
1099 throw Exception{message};
1102 [[noreturn]] void ALeffectslot::eax_fail_unknown_effect_id()
1104 eax_fail("Unknown effect ID.");
1107 [[noreturn]] void ALeffectslot::eax_fail_unknown_property_id()
1109 eax_fail("Unknown property ID.");
1112 [[noreturn]] void ALeffectslot::eax_fail_unknown_version()
1114 eax_fail("Unknown version.");
1117 void ALeffectslot::eax4_fx_slot_ensure_unlocked() const
1119 if(eax4_fx_slot_is_legacy())
1120 eax_fail("Locked legacy slot.");
1123 ALenum ALeffectslot::eax_get_efx_effect_type(const GUID& guid)
1125 if(guid == EAX_NULL_GUID)
1126 return AL_EFFECT_NULL;
1127 if(guid == EAX_AUTOWAH_EFFECT)
1128 return AL_EFFECT_AUTOWAH;
1129 if(guid == EAX_CHORUS_EFFECT)
1130 return AL_EFFECT_CHORUS;
1131 if(guid == EAX_AGCCOMPRESSOR_EFFECT)
1132 return AL_EFFECT_COMPRESSOR;
1133 if(guid == EAX_DISTORTION_EFFECT)
1134 return AL_EFFECT_DISTORTION;
1135 if(guid == EAX_REVERB_EFFECT)
1136 return AL_EFFECT_EAXREVERB;
1137 if(guid == EAX_ECHO_EFFECT)
1138 return AL_EFFECT_ECHO;
1139 if(guid == EAX_EQUALIZER_EFFECT)
1140 return AL_EFFECT_EQUALIZER;
1141 if(guid == EAX_FLANGER_EFFECT)
1142 return AL_EFFECT_FLANGER;
1143 if(guid == EAX_FREQUENCYSHIFTER_EFFECT)
1144 return AL_EFFECT_FREQUENCY_SHIFTER;
1145 if(guid == EAX_PITCHSHIFTER_EFFECT)
1146 return AL_EFFECT_PITCH_SHIFTER;
1147 if(guid == EAX_RINGMODULATOR_EFFECT)
1148 return AL_EFFECT_RING_MODULATOR;
1149 if(guid == EAX_VOCALMORPHER_EFFECT)
1150 return AL_EFFECT_VOCAL_MORPHER;
1152 eax_fail_unknown_effect_id();
1155 const GUID& ALeffectslot::eax_get_eax_default_effect_guid() const noexcept
1157 switch(eax_fx_slot_index_)
1159 case 0: return EAX_REVERB_EFFECT;
1160 case 1: return EAX_CHORUS_EFFECT;
1161 default: return EAX_NULL_GUID;
1165 long ALeffectslot::eax_get_eax_default_lock() const noexcept
1167 return eax4_fx_slot_is_legacy() ? EAXFXSLOT_LOCKED : EAXFXSLOT_UNLOCKED;
1170 void ALeffectslot::eax4_fx_slot_set_defaults(Eax4Props& props) noexcept
1172 props.guidLoadEffect = eax_get_eax_default_effect_guid();
1173 props.lVolume = EAXFXSLOT_DEFAULTVOLUME;
1174 props.lLock = eax_get_eax_default_lock();
1175 props.ulFlags = EAX40FXSLOT_DEFAULTFLAGS;
1178 void ALeffectslot::eax5_fx_slot_set_defaults(Eax5Props& props) noexcept
1180 props.guidLoadEffect = eax_get_eax_default_effect_guid();
1181 props.lVolume = EAXFXSLOT_DEFAULTVOLUME;
1182 props.lLock = EAXFXSLOT_UNLOCKED;
1183 props.ulFlags = EAX50FXSLOT_DEFAULTFLAGS;
1184 props.lOcclusion = EAXFXSLOT_DEFAULTOCCLUSION;
1185 props.flOcclusionLFRatio = EAXFXSLOT_DEFAULTOCCLUSIONLFRATIO;
1188 void ALeffectslot::eax_fx_slot_set_defaults()
1190 eax5_fx_slot_set_defaults(eax123_.i);
1191 eax4_fx_slot_set_defaults(eax4_.i);
1192 eax5_fx_slot_set_defaults(eax5_.i);
1193 eax_ = eax5_.i;
1194 eax_df_ = EaxDirtyFlags{};
1197 void ALeffectslot::eax4_fx_slot_get(const EaxCall& call, const Eax4Props& props)
1199 switch(call.get_property_id())
1201 case EAXFXSLOT_ALLPARAMETERS:
1202 call.set_value<Exception>(props);
1203 break;
1204 case EAXFXSLOT_LOADEFFECT:
1205 call.set_value<Exception>(props.guidLoadEffect);
1206 break;
1207 case EAXFXSLOT_VOLUME:
1208 call.set_value<Exception>(props.lVolume);
1209 break;
1210 case EAXFXSLOT_LOCK:
1211 call.set_value<Exception>(props.lLock);
1212 break;
1213 case EAXFXSLOT_FLAGS:
1214 call.set_value<Exception>(props.ulFlags);
1215 break;
1216 default:
1217 eax_fail_unknown_property_id();
1221 void ALeffectslot::eax5_fx_slot_get(const EaxCall& call, const Eax5Props& props)
1223 switch(call.get_property_id())
1225 case EAXFXSLOT_ALLPARAMETERS:
1226 call.set_value<Exception>(props);
1227 break;
1228 case EAXFXSLOT_LOADEFFECT:
1229 call.set_value<Exception>(props.guidLoadEffect);
1230 break;
1231 case EAXFXSLOT_VOLUME:
1232 call.set_value<Exception>(props.lVolume);
1233 break;
1234 case EAXFXSLOT_LOCK:
1235 call.set_value<Exception>(props.lLock);
1236 break;
1237 case EAXFXSLOT_FLAGS:
1238 call.set_value<Exception>(props.ulFlags);
1239 break;
1240 case EAXFXSLOT_OCCLUSION:
1241 call.set_value<Exception>(props.lOcclusion);
1242 break;
1243 case EAXFXSLOT_OCCLUSIONLFRATIO:
1244 call.set_value<Exception>(props.flOcclusionLFRatio);
1245 break;
1246 default:
1247 eax_fail_unknown_property_id();
1251 void ALeffectslot::eax_fx_slot_get(const EaxCall& call) const
1253 switch(call.get_version())
1255 case 4: eax4_fx_slot_get(call, eax4_.i); break;
1256 case 5: eax5_fx_slot_get(call, eax5_.i); break;
1257 default: eax_fail_unknown_version();
1261 bool ALeffectslot::eax_get(const EaxCall& call)
1263 switch(call.get_property_set_id())
1265 case EaxCallPropertySetId::fx_slot:
1266 eax_fx_slot_get(call);
1267 break;
1268 case EaxCallPropertySetId::fx_slot_effect:
1269 eax_effect_->get(call);
1270 break;
1271 default:
1272 eax_fail_unknown_property_id();
1275 return false;
1278 void ALeffectslot::eax_fx_slot_load_effect(int version, ALenum altype)
1280 if(!IsValidEffectType(altype))
1281 altype = AL_EFFECT_NULL;
1282 eax_effect_->set_defaults(version, altype);
1285 void ALeffectslot::eax_fx_slot_set_volume()
1287 const auto volume = std::clamp(eax_.lVolume, EAXFXSLOT_MINVOLUME, EAXFXSLOT_MAXVOLUME);
1288 const auto gain = level_mb_to_gain(static_cast<float>(volume));
1289 eax_set_efx_slot_gain(gain);
1292 void ALeffectslot::eax_fx_slot_set_environment_flag()
1294 eax_set_efx_slot_send_auto((eax_.ulFlags & EAXFXSLOTFLAGS_ENVIRONMENT) != 0u);
1297 void ALeffectslot::eax_fx_slot_set_flags()
1299 eax_fx_slot_set_environment_flag();
1302 void ALeffectslot::eax4_fx_slot_set_all(const EaxCall& call)
1304 eax4_fx_slot_ensure_unlocked();
1305 const auto& src = call.get_value<Exception, const EAX40FXSLOTPROPERTIES>();
1306 Eax4AllValidator{}(src);
1307 auto& dst = eax4_.i;
1308 eax_df_ |= eax_load_effect_dirty_bit; // Always reset the effect.
1309 eax_df_ |= (dst.lVolume != src.lVolume ? eax_volume_dirty_bit : EaxDirtyFlags{});
1310 eax_df_ |= (dst.lLock != src.lLock ? eax_lock_dirty_bit : EaxDirtyFlags{});
1311 eax_df_ |= (dst.ulFlags != src.ulFlags ? eax_flags_dirty_bit : EaxDirtyFlags{});
1312 dst = src;
1315 void ALeffectslot::eax5_fx_slot_set_all(const EaxCall& call)
1317 const auto& src = call.get_value<Exception, const EAX50FXSLOTPROPERTIES>();
1318 Eax5AllValidator{}(src);
1319 auto& dst = eax5_.i;
1320 eax_df_ |= eax_load_effect_dirty_bit; // Always reset the effect.
1321 eax_df_ |= (dst.lVolume != src.lVolume ? eax_volume_dirty_bit : EaxDirtyFlags{});
1322 eax_df_ |= (dst.lLock != src.lLock ? eax_lock_dirty_bit : EaxDirtyFlags{});
1323 eax_df_ |= (dst.ulFlags != src.ulFlags ? eax_flags_dirty_bit : EaxDirtyFlags{});
1324 eax_df_ |= (dst.lOcclusion != src.lOcclusion ? eax_flags_dirty_bit : EaxDirtyFlags{});
1325 eax_df_ |= (dst.flOcclusionLFRatio != src.flOcclusionLFRatio ? eax_flags_dirty_bit : EaxDirtyFlags{});
1326 dst = src;
1329 bool ALeffectslot::eax_fx_slot_should_update_sources() const noexcept
1331 static constexpr auto dirty_bits =
1332 eax_occlusion_dirty_bit |
1333 eax_occlusion_lf_ratio_dirty_bit |
1334 eax_flags_dirty_bit;
1336 return (eax_df_ & dirty_bits) != EaxDirtyFlags{};
1339 // Returns `true` if all sources should be updated, or `false` otherwise.
1340 bool ALeffectslot::eax4_fx_slot_set(const EaxCall& call)
1342 auto& dst = eax4_.i;
1344 switch(call.get_property_id())
1346 case EAXFXSLOT_NONE:
1347 break;
1348 case EAXFXSLOT_ALLPARAMETERS:
1349 eax4_fx_slot_set_all(call);
1350 if((eax_df_ & eax_load_effect_dirty_bit))
1351 eax_fx_slot_load_effect(4, eax_get_efx_effect_type(dst.guidLoadEffect));
1352 break;
1353 case EAXFXSLOT_LOADEFFECT:
1354 eax4_fx_slot_ensure_unlocked();
1355 eax_fx_slot_set_dirty<Eax4GuidLoadEffectValidator, eax_load_effect_dirty_bit>(call, dst.guidLoadEffect, eax_df_);
1356 if((eax_df_ & eax_load_effect_dirty_bit))
1357 eax_fx_slot_load_effect(4, eax_get_efx_effect_type(dst.guidLoadEffect));
1358 break;
1359 case EAXFXSLOT_VOLUME:
1360 eax_fx_slot_set<Eax4VolumeValidator, eax_volume_dirty_bit>(call, dst.lVolume, eax_df_);
1361 break;
1362 case EAXFXSLOT_LOCK:
1363 eax4_fx_slot_ensure_unlocked();
1364 eax_fx_slot_set<Eax4LockValidator, eax_lock_dirty_bit>(call, dst.lLock, eax_df_);
1365 break;
1366 case EAXFXSLOT_FLAGS:
1367 eax_fx_slot_set<Eax4FlagsValidator, eax_flags_dirty_bit>(call, dst.ulFlags, eax_df_);
1368 break;
1369 default:
1370 eax_fail_unknown_property_id();
1373 return eax_fx_slot_should_update_sources();
1376 // Returns `true` if all sources should be updated, or `false` otherwise.
1377 bool ALeffectslot::eax5_fx_slot_set(const EaxCall& call)
1379 auto& dst = eax5_.i;
1381 switch(call.get_property_id())
1383 case EAXFXSLOT_NONE:
1384 break;
1385 case EAXFXSLOT_ALLPARAMETERS:
1386 eax5_fx_slot_set_all(call);
1387 if((eax_df_ & eax_load_effect_dirty_bit))
1388 eax_fx_slot_load_effect(5, eax_get_efx_effect_type(dst.guidLoadEffect));
1389 break;
1390 case EAXFXSLOT_LOADEFFECT:
1391 eax_fx_slot_set_dirty<Eax4GuidLoadEffectValidator, eax_load_effect_dirty_bit>(call, dst.guidLoadEffect, eax_df_);
1392 if((eax_df_ & eax_load_effect_dirty_bit))
1393 eax_fx_slot_load_effect(5, eax_get_efx_effect_type(dst.guidLoadEffect));
1394 break;
1395 case EAXFXSLOT_VOLUME:
1396 eax_fx_slot_set<Eax4VolumeValidator, eax_volume_dirty_bit>(call, dst.lVolume, eax_df_);
1397 break;
1398 case EAXFXSLOT_LOCK:
1399 eax_fx_slot_set<Eax4LockValidator, eax_lock_dirty_bit>(call, dst.lLock, eax_df_);
1400 break;
1401 case EAXFXSLOT_FLAGS:
1402 eax_fx_slot_set<Eax5FlagsValidator, eax_flags_dirty_bit>(call, dst.ulFlags, eax_df_);
1403 break;
1404 case EAXFXSLOT_OCCLUSION:
1405 eax_fx_slot_set<Eax5OcclusionValidator, eax_occlusion_dirty_bit>(call, dst.lOcclusion, eax_df_);
1406 break;
1407 case EAXFXSLOT_OCCLUSIONLFRATIO:
1408 eax_fx_slot_set<Eax5OcclusionLfRatioValidator, eax_occlusion_lf_ratio_dirty_bit>(call, dst.flOcclusionLFRatio, eax_df_);
1409 break;
1410 default:
1411 eax_fail_unknown_property_id();
1414 return eax_fx_slot_should_update_sources();
1417 // Returns `true` if all sources should be updated, or `false` otherwise.
1418 bool ALeffectslot::eax_fx_slot_set(const EaxCall& call)
1420 switch (call.get_version())
1422 case 4: return eax4_fx_slot_set(call);
1423 case 5: return eax5_fx_slot_set(call);
1424 default: eax_fail_unknown_version();
1428 // Returns `true` if all sources should be updated, or `false` otherwise.
1429 bool ALeffectslot::eax_set(const EaxCall& call)
1431 bool ret{false};
1433 switch(call.get_property_set_id())
1435 case EaxCallPropertySetId::fx_slot: ret = eax_fx_slot_set(call); break;
1436 case EaxCallPropertySetId::fx_slot_effect: eax_effect_->set(call); break;
1437 default: eax_fail_unknown_property_id();
1440 const auto version = call.get_version();
1441 if(eax_version_ != version)
1442 eax_df_ = ~EaxDirtyFlags{};
1443 eax_version_ = version;
1445 return ret;
1448 void ALeffectslot::eax4_fx_slot_commit(EaxDirtyFlags& dst_df)
1450 eax_fx_slot_commit_property<eax_load_effect_dirty_bit>(eax4_, dst_df, &EAX40FXSLOTPROPERTIES::guidLoadEffect);
1451 eax_fx_slot_commit_property<eax_volume_dirty_bit>(eax4_, dst_df, &EAX40FXSLOTPROPERTIES::lVolume);
1452 eax_fx_slot_commit_property<eax_lock_dirty_bit>(eax4_, dst_df, &EAX40FXSLOTPROPERTIES::lLock);
1453 eax_fx_slot_commit_property<eax_flags_dirty_bit>(eax4_, dst_df, &EAX40FXSLOTPROPERTIES::ulFlags);
1455 auto& dst_i = eax_;
1457 if(dst_i.lOcclusion != EAXFXSLOT_DEFAULTOCCLUSION) {
1458 dst_df |= eax_occlusion_dirty_bit;
1459 dst_i.lOcclusion = EAXFXSLOT_DEFAULTOCCLUSION;
1462 if(dst_i.flOcclusionLFRatio != EAXFXSLOT_DEFAULTOCCLUSIONLFRATIO) {
1463 dst_df |= eax_occlusion_lf_ratio_dirty_bit;
1464 dst_i.flOcclusionLFRatio = EAXFXSLOT_DEFAULTOCCLUSIONLFRATIO;
1468 void ALeffectslot::eax5_fx_slot_commit(Eax5State& state, EaxDirtyFlags& dst_df)
1470 eax_fx_slot_commit_property<eax_load_effect_dirty_bit>(state, dst_df, &EAX50FXSLOTPROPERTIES::guidLoadEffect);
1471 eax_fx_slot_commit_property<eax_volume_dirty_bit>(state, dst_df, &EAX50FXSLOTPROPERTIES::lVolume);
1472 eax_fx_slot_commit_property<eax_lock_dirty_bit>(state, dst_df, &EAX50FXSLOTPROPERTIES::lLock);
1473 eax_fx_slot_commit_property<eax_flags_dirty_bit>(state, dst_df, &EAX50FXSLOTPROPERTIES::ulFlags);
1474 eax_fx_slot_commit_property<eax_occlusion_dirty_bit>(state, dst_df, &EAX50FXSLOTPROPERTIES::lOcclusion);
1475 eax_fx_slot_commit_property<eax_occlusion_lf_ratio_dirty_bit>(state, dst_df, &EAX50FXSLOTPROPERTIES::flOcclusionLFRatio);
1478 void ALeffectslot::eax_set_efx_slot_effect(EaxEffect &effect)
1480 #define EAX_PREFIX "[EAX_SET_EFFECT_SLOT_EFFECT] "
1482 const auto error = initEffect(0, effect.al_effect_type_, effect.al_effect_props_,
1483 eax_al_context_);
1485 if(error != AL_NO_ERROR) {
1486 ERR(EAX_PREFIX "%s\n", "Failed to initialize an effect.");
1487 return;
1490 if(mState == SlotState::Initial) {
1491 mPropsDirty = false;
1492 updateProps(eax_al_context_);
1493 auto effect_slot_ptr = this;
1494 AddActiveEffectSlots({&effect_slot_ptr, 1}, eax_al_context_);
1495 mState = SlotState::Playing;
1496 return;
1499 mPropsDirty = true;
1501 #undef EAX_PREFIX
1504 void ALeffectslot::eax_set_efx_slot_send_auto(bool is_send_auto)
1506 if(AuxSendAuto == is_send_auto)
1507 return;
1509 AuxSendAuto = is_send_auto;
1510 mPropsDirty = true;
1513 void ALeffectslot::eax_set_efx_slot_gain(ALfloat gain)
1515 #define EAX_PREFIX "[EAX_SET_EFFECT_SLOT_GAIN] "
1517 if(gain == Gain)
1518 return;
1519 if(gain < 0.0f || gain > 1.0f)
1520 ERR(EAX_PREFIX "Gain out of range (%f)\n", gain);
1522 Gain = std::clamp(gain, 0.0f, 1.0f);
1523 mPropsDirty = true;
1525 #undef EAX_PREFIX
1528 void ALeffectslot::EaxDeleter::operator()(ALeffectslot* effect_slot)
1530 eax_delete_al_effect_slot(*effect_slot->eax_al_context_, *effect_slot);
1533 EaxAlEffectSlotUPtr eax_create_al_effect_slot(ALCcontext& context)
1535 #define EAX_PREFIX "[EAX_MAKE_EFFECT_SLOT] "
1537 std::lock_guard<std::mutex> slotlock{context.mEffectSlotLock};
1538 auto& device = *context.mALDevice;
1540 if(context.mNumEffectSlots == device.AuxiliaryEffectSlotMax) {
1541 ERR(EAX_PREFIX "%s\n", "Out of memory.");
1542 return nullptr;
1545 if(!EnsureEffectSlots(&context, 1)) {
1546 ERR(EAX_PREFIX "%s\n", "Failed to ensure.");
1547 return nullptr;
1550 return EaxAlEffectSlotUPtr{AllocEffectSlot(&context)};
1552 #undef EAX_PREFIX
1555 void eax_delete_al_effect_slot(ALCcontext& context, ALeffectslot& effect_slot)
1557 #define EAX_PREFIX "[EAX_DELETE_EFFECT_SLOT] "
1559 std::lock_guard<std::mutex> slotlock{context.mEffectSlotLock};
1561 if(effect_slot.ref.load(std::memory_order_relaxed) != 0)
1563 ERR(EAX_PREFIX "Deleting in-use effect slot %u.\n", effect_slot.id);
1564 return;
1567 auto effect_slot_ptr = &effect_slot;
1568 RemoveActiveEffectSlots({&effect_slot_ptr, 1}, &context);
1569 FreeEffectSlot(&context, &effect_slot);
1571 #undef EAX_PREFIX
1573 #endif // ALSOFT_EAX