Don't call log10 with values too close to 0
[openal-soft.git] / al / auxeffectslot.cpp
blob243f8fcbfeea95a3f67c077293c80df4bd7be4ab
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::Reverb: return ReverbStateFactory_getFactory();
83 case EffectSlotType::Chorus: return ChorusStateFactory_getFactory();
84 case EffectSlotType::Autowah: return AutowahStateFactory_getFactory();
85 case EffectSlotType::Compressor: return CompressorStateFactory_getFactory();
86 case EffectSlotType::Convolution: return ConvolutionStateFactory_getFactory();
87 case EffectSlotType::Dedicated: return DedicatedStateFactory_getFactory();
88 case EffectSlotType::Distortion: return DistortionStateFactory_getFactory();
89 case EffectSlotType::Echo: return EchoStateFactory_getFactory();
90 case EffectSlotType::Equalizer: return EqualizerStateFactory_getFactory();
91 case EffectSlotType::Flanger: return ChorusStateFactory_getFactory();
92 case EffectSlotType::FrequencyShifter: return FshifterStateFactory_getFactory();
93 case EffectSlotType::RingModulator: return ModulatorStateFactory_getFactory();
94 case EffectSlotType::PitchShifter: return PshifterStateFactory_getFactory();
95 case EffectSlotType::VocalMorpher: return VmorpherStateFactory_getFactory();
97 return nullptr;
101 auto LookupEffectSlot(ALCcontext *context, ALuint id) noexcept -> ALeffectslot*
103 const size_t lidx{(id-1) >> 6};
104 const ALuint slidx{(id-1) & 0x3f};
106 if(lidx >= context->mEffectSlotList.size()) UNLIKELY
107 return nullptr;
108 EffectSlotSubList &sublist{context->mEffectSlotList[lidx]};
109 if(sublist.FreeMask & (1_u64 << slidx)) UNLIKELY
110 return nullptr;
111 return al::to_address(sublist.EffectSlots->begin() + slidx);
114 inline auto LookupEffect(ALCdevice *device, ALuint id) noexcept -> ALeffect*
116 const size_t lidx{(id-1) >> 6};
117 const ALuint slidx{(id-1) & 0x3f};
119 if(lidx >= device->EffectList.size()) UNLIKELY
120 return nullptr;
121 EffectSubList &sublist = device->EffectList[lidx];
122 if(sublist.FreeMask & (1_u64 << slidx)) UNLIKELY
123 return nullptr;
124 return al::to_address(sublist.Effects->begin() + slidx);
127 inline auto LookupBuffer(ALCdevice *device, ALuint id) noexcept -> ALbuffer*
129 const size_t lidx{(id-1) >> 6};
130 const ALuint slidx{(id-1) & 0x3f};
132 if(lidx >= device->BufferList.size()) UNLIKELY
133 return nullptr;
134 BufferSubList &sublist = device->BufferList[lidx];
135 if(sublist.FreeMask & (1_u64 << slidx)) UNLIKELY
136 return nullptr;
137 return al::to_address(sublist.Buffers->begin() + slidx);
141 void AddActiveEffectSlots(const al::span<ALeffectslot*> auxslots, ALCcontext *context)
143 if(auxslots.empty()) return;
144 EffectSlotArray *curarray{context->mActiveAuxSlots.load(std::memory_order_acquire)};
145 if((curarray->size()>>1) > std::numeric_limits<size_t>::max()-auxslots.size())
146 throw std::runtime_error{"Too many active effect slots"};
148 size_t newcount{(curarray->size()>>1) + auxslots.size()};
149 if(newcount > std::numeric_limits<size_t>::max()>>1)
150 throw std::runtime_error{"Too many active effect slots"};
152 /* Insert the new effect slots into the head of the new array, followed by
153 * the existing ones.
155 auto newarray = EffectSlot::CreatePtrArray(newcount<<1);
156 auto new_end = std::transform(auxslots.begin(), auxslots.end(), newarray->begin(),
157 std::mem_fn(&ALeffectslot::mSlot));
158 new_end = std::copy_n(curarray->begin(), curarray->size()>>1, new_end);
160 /* Remove any duplicates (first instance of each will be kept). */
161 for(auto start=newarray->begin()+1;;)
163 new_end = std::remove(start, new_end, *(start-1));
164 if(start == new_end) break;
165 ++start;
167 newcount = static_cast<size_t>(std::distance(newarray->begin(), new_end));
169 /* Reallocate newarray if the new size ended up smaller from duplicate
170 * removal.
172 if(newcount < newarray->size()>>1) UNLIKELY
174 auto oldarray = std::move(newarray);
175 newarray = EffectSlot::CreatePtrArray(newcount<<1);
176 new_end = std::copy_n(oldarray->begin(), newcount, newarray->begin());
178 std::fill(new_end, newarray->end(), nullptr);
180 auto oldarray = context->mActiveAuxSlots.exchange(std::move(newarray),
181 std::memory_order_acq_rel);
182 std::ignore = context->mDevice->waitForMix();
185 void RemoveActiveEffectSlots(const al::span<ALeffectslot*> auxslots, ALCcontext *context)
187 if(auxslots.empty()) return;
188 EffectSlotArray *curarray{context->mActiveAuxSlots.load(std::memory_order_acquire)};
190 /* Don't shrink the allocated array size since we don't know how many (if
191 * any) of the effect slots to remove are in the array.
193 auto newarray = EffectSlot::CreatePtrArray(curarray->size());
195 auto new_end = std::copy_n(curarray->begin(), curarray->size()>>1, newarray->begin());
196 /* Remove elements from newarray that match any ID in slotids. */
197 for(const ALeffectslot *auxslot : auxslots)
199 auto slot_match = [auxslot](EffectSlot *slot) noexcept -> bool
200 { return (slot == auxslot->mSlot); };
201 new_end = std::remove_if(newarray->begin(), new_end, slot_match);
204 /* Reallocate with the new size. */
205 auto newsize = static_cast<size_t>(std::distance(newarray->begin(), new_end));
206 if(newsize < newarray->size()>>1) LIKELY
208 auto oldarray = std::move(newarray);
209 newarray = EffectSlot::CreatePtrArray(newsize<<1);
210 new_end = std::copy_n(oldarray->begin(), newsize, newarray->begin());
212 std::fill(new_end, newarray->end(), nullptr);
214 auto oldarray = context->mActiveAuxSlots.exchange(std::move(newarray),
215 std::memory_order_acq_rel);
216 std::ignore = context->mDevice->waitForMix();
220 constexpr auto EffectSlotTypeFromEnum(ALenum type) noexcept -> EffectSlotType
222 switch(type)
224 case AL_EFFECT_NULL: return EffectSlotType::None;
225 case AL_EFFECT_REVERB: return EffectSlotType::Reverb;
226 case AL_EFFECT_CHORUS: return EffectSlotType::Chorus;
227 case AL_EFFECT_DISTORTION: return EffectSlotType::Distortion;
228 case AL_EFFECT_ECHO: return EffectSlotType::Echo;
229 case AL_EFFECT_FLANGER: return EffectSlotType::Flanger;
230 case AL_EFFECT_FREQUENCY_SHIFTER: return EffectSlotType::FrequencyShifter;
231 case AL_EFFECT_VOCAL_MORPHER: return EffectSlotType::VocalMorpher;
232 case AL_EFFECT_PITCH_SHIFTER: return EffectSlotType::PitchShifter;
233 case AL_EFFECT_RING_MODULATOR: return EffectSlotType::RingModulator;
234 case AL_EFFECT_AUTOWAH: return EffectSlotType::Autowah;
235 case AL_EFFECT_COMPRESSOR: return EffectSlotType::Compressor;
236 case AL_EFFECT_EQUALIZER: return EffectSlotType::Equalizer;
237 case AL_EFFECT_EAXREVERB: return EffectSlotType::Reverb;
238 case AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT: return EffectSlotType::Dedicated;
239 case AL_EFFECT_DEDICATED_DIALOGUE: return EffectSlotType::Dedicated;
240 case AL_EFFECT_CONVOLUTION_SOFT: return EffectSlotType::Convolution;
242 ERR("Unhandled effect enum: 0x%04x\n", type);
243 return EffectSlotType::None;
246 auto EnsureEffectSlots(ALCcontext *context, size_t needed) noexcept -> bool
247 try {
248 size_t count{std::accumulate(context->mEffectSlotList.cbegin(),
249 context->mEffectSlotList.cend(), 0_uz,
250 [](size_t cur, const EffectSlotSubList &sublist) noexcept -> size_t
251 { return cur + static_cast<ALuint>(al::popcount(sublist.FreeMask)); })};
253 while(needed > count)
255 if(context->mEffectSlotList.size() >= 1<<25) UNLIKELY
256 return false;
258 EffectSlotSubList sublist{};
259 sublist.FreeMask = ~0_u64;
260 sublist.EffectSlots = SubListAllocator{}.allocate(1);
261 context->mEffectSlotList.emplace_back(std::move(sublist));
262 count += std::tuple_size_v<SubListAllocator::value_type>;
264 return true;
266 catch(...) {
267 return false;
270 ALeffectslot *AllocEffectSlot(ALCcontext *context)
272 auto sublist = std::find_if(context->mEffectSlotList.begin(), context->mEffectSlotList.end(),
273 [](const EffectSlotSubList &entry) noexcept -> bool
274 { return entry.FreeMask != 0; });
275 auto lidx = static_cast<ALuint>(std::distance(context->mEffectSlotList.begin(), sublist));
276 auto slidx = static_cast<ALuint>(al::countr_zero(sublist->FreeMask));
277 ASSUME(slidx < 64);
279 ALeffectslot *slot{al::construct_at(al::to_address(sublist->EffectSlots->begin() + slidx),
280 context)};
281 aluInitEffectPanning(slot->mSlot, context);
283 /* Add 1 to avoid ID 0. */
284 slot->id = ((lidx<<6) | slidx) + 1;
286 context->mNumEffectSlots += 1;
287 sublist->FreeMask &= ~(1_u64 << slidx);
289 return slot;
292 void FreeEffectSlot(ALCcontext *context, ALeffectslot *slot)
294 context->mEffectSlotNames.erase(slot->id);
296 const ALuint id{slot->id - 1};
297 const size_t lidx{id >> 6};
298 const ALuint slidx{id & 0x3f};
300 std::destroy_at(slot);
302 context->mEffectSlotList[lidx].FreeMask |= 1_u64 << slidx;
303 context->mNumEffectSlots--;
307 inline void UpdateProps(ALeffectslot *slot, ALCcontext *context)
309 if(!context->mDeferUpdates && slot->mState == SlotState::Playing)
311 slot->updateProps(context);
312 return;
314 slot->mPropsDirty = true;
317 } // namespace
320 AL_API DECL_FUNC2(void, alGenAuxiliaryEffectSlots, ALsizei,n, ALuint*,effectslots)
321 FORCE_ALIGN void AL_APIENTRY alGenAuxiliaryEffectSlotsDirect(ALCcontext *context, ALsizei n,
322 ALuint *effectslots) noexcept
323 try {
324 if(n < 0)
325 throw al::context_error{AL_INVALID_VALUE, "Generating %d effect slots", n};
326 if(n <= 0) UNLIKELY return;
328 std::lock_guard<std::mutex> slotlock{context->mEffectSlotLock};
329 ALCdevice *device{context->mALDevice.get()};
331 const al::span eids{effectslots, static_cast<ALuint>(n)};
332 if(eids.size() > device->AuxiliaryEffectSlotMax-context->mNumEffectSlots)
333 throw al::context_error{AL_OUT_OF_MEMORY, "Exceeding %u effect slot limit (%u + %d)",
334 device->AuxiliaryEffectSlotMax, context->mNumEffectSlots, n};
336 if(!EnsureEffectSlots(context, eids.size()))
337 throw al::context_error{AL_OUT_OF_MEMORY, "Failed to allocate %d effectslot%s", n,
338 (n == 1) ? "" : "s"};
340 std::vector<ALeffectslot*> slots;
341 try {
342 if(eids.size() == 1)
344 /* Special handling for the easy and normal case. */
345 eids[0] = AllocEffectSlot(context)->id;
347 else
349 slots.reserve(eids.size());
350 std::generate_n(std::back_inserter(slots), eids.size(),
351 [context]{ return AllocEffectSlot(context); });
353 std::transform(slots.cbegin(), slots.cend(), eids.begin(),
354 [](ALeffectslot *slot) -> ALuint { return slot->id; });
357 catch(std::exception& e) {
358 ERR("Exception allocating effectslot %zu of %d: %s\n", slots.size()+1, n, e.what());
359 auto delete_effectslot = [context](ALeffectslot *slot) -> void
360 { FreeEffectSlot(context, slot); };
361 std::for_each(slots.begin(), slots.end(), delete_effectslot);
362 throw al::context_error{AL_INVALID_OPERATION, "Exception allocating %d effectslots: %s", n,
363 e.what()};
366 catch(al::context_error& e) {
367 context->setError(e.errorCode(), "%s", e.what());
370 AL_API DECL_FUNC2(void, alDeleteAuxiliaryEffectSlots, ALsizei,n, const ALuint*,effectslots)
371 FORCE_ALIGN void AL_APIENTRY alDeleteAuxiliaryEffectSlotsDirect(ALCcontext *context, ALsizei n,
372 const ALuint *effectslots) noexcept
373 try {
374 if(n < 0) UNLIKELY
375 throw al::context_error{AL_INVALID_VALUE, "Deleting %d effect slots", n};
376 if(n <= 0) UNLIKELY return;
378 std::lock_guard<std::mutex> slotlock{context->mEffectSlotLock};
379 if(n == 1)
381 ALeffectslot *slot{LookupEffectSlot(context, *effectslots)};
382 if(!slot)
383 throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", *effectslots};
384 if(slot->ref.load(std::memory_order_relaxed) != 0)
385 throw al::context_error{AL_INVALID_OPERATION, "Deleting in-use effect slot %u",
386 *effectslots};
388 RemoveActiveEffectSlots({&slot, 1u}, context);
389 FreeEffectSlot(context, slot);
391 else
393 const al::span eids{effectslots, static_cast<ALuint>(n)};
394 std::vector<ALeffectslot*> slots;
395 slots.reserve(eids.size());
397 auto lookupslot = [context](const ALuint eid) -> ALeffectslot*
399 ALeffectslot *slot{LookupEffectSlot(context, eid)};
400 if(!slot)
401 throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", eid};
402 if(slot->ref.load(std::memory_order_relaxed) != 0)
403 throw al::context_error{AL_INVALID_OPERATION, "Deleting in-use effect slot %u",
404 eid};
405 return slot;
407 std::transform(eids.cbegin(), eids.cend(), std::back_inserter(slots), lookupslot);
409 /* All effectslots are valid, remove and delete them */
410 RemoveActiveEffectSlots(slots, context);
412 auto delete_effectslot = [context](const ALuint eid) -> void
414 if(ALeffectslot *slot{LookupEffectSlot(context, eid)})
415 FreeEffectSlot(context, slot);
417 std::for_each(eids.begin(), eids.end(), delete_effectslot);
420 catch(al::context_error& e) {
421 context->setError(e.errorCode(), "%s", e.what());
424 AL_API DECL_FUNC1(ALboolean, alIsAuxiliaryEffectSlot, ALuint,effectslot)
425 FORCE_ALIGN ALboolean AL_APIENTRY alIsAuxiliaryEffectSlotDirect(ALCcontext *context,
426 ALuint effectslot) noexcept
428 std::lock_guard<std::mutex> slotlock{context->mEffectSlotLock};
429 if(LookupEffectSlot(context, effectslot) != nullptr)
430 return AL_TRUE;
431 return AL_FALSE;
435 AL_API void AL_APIENTRY alAuxiliaryEffectSlotPlaySOFT(ALuint) noexcept
437 ContextRef context{GetContextRef()};
438 if(!context) UNLIKELY return;
440 context->setError(AL_INVALID_OPERATION, "alAuxiliaryEffectSlotPlaySOFT not supported");
443 AL_API void AL_APIENTRY alAuxiliaryEffectSlotPlayvSOFT(ALsizei, const ALuint*) noexcept
445 ContextRef context{GetContextRef()};
446 if(!context) UNLIKELY return;
448 context->setError(AL_INVALID_OPERATION, "alAuxiliaryEffectSlotPlayvSOFT not supported");
451 AL_API void AL_APIENTRY alAuxiliaryEffectSlotStopSOFT(ALuint) noexcept
453 ContextRef context{GetContextRef()};
454 if(!context) UNLIKELY return;
456 context->setError(AL_INVALID_OPERATION, "alAuxiliaryEffectSlotStopSOFT not supported");
459 AL_API void AL_APIENTRY alAuxiliaryEffectSlotStopvSOFT(ALsizei, const ALuint*) noexcept
461 ContextRef context{GetContextRef()};
462 if(!context) UNLIKELY return;
464 context->setError(AL_INVALID_OPERATION, "alAuxiliaryEffectSlotStopvSOFT not supported");
468 AL_API DECL_FUNC3(void, alAuxiliaryEffectSloti, ALuint,effectslot, ALenum,param, ALint,value)
469 FORCE_ALIGN void AL_APIENTRY alAuxiliaryEffectSlotiDirect(ALCcontext *context, ALuint effectslot,
470 ALenum param, ALint value) noexcept
471 try {
472 std::lock_guard<std::mutex> proplock{context->mPropLock};
473 std::lock_guard<std::mutex> slotlock{context->mEffectSlotLock};
475 ALeffectslot *slot{LookupEffectSlot(context, effectslot)};
476 if(!slot) UNLIKELY
477 throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", effectslot};
479 ALeffectslot *target{};
480 ALenum err{};
481 switch(param)
483 case AL_EFFECTSLOT_EFFECT:
485 ALCdevice *device{context->mALDevice.get()};
486 std::lock_guard<std::mutex> effectlock{device->EffectLock};
487 ALeffect *effect{value ? LookupEffect(device, static_cast<ALuint>(value)) : nullptr};
488 if(effect)
489 err = slot->initEffect(effect->id, effect->type, effect->Props, context);
490 else
492 if(value != 0)
493 throw al::context_error{AL_INVALID_VALUE, "Invalid effect ID %u", value};
494 err = slot->initEffect(0, AL_EFFECT_NULL, EffectProps{}, context);
497 if(err != AL_NO_ERROR)
498 throw al::context_error{err, "Effect initialization failed"};
500 if(slot->mState == SlotState::Initial) UNLIKELY
502 slot->mPropsDirty = false;
503 slot->updateProps(context);
505 AddActiveEffectSlots({&slot, 1}, context);
506 slot->mState = SlotState::Playing;
507 return;
509 UpdateProps(slot, context);
510 return;
512 case AL_EFFECTSLOT_AUXILIARY_SEND_AUTO:
513 if(!(value == AL_TRUE || value == AL_FALSE))
514 throw al::context_error{AL_INVALID_VALUE,
515 "Effect slot auxiliary send auto out of range"};
516 if(!(slot->AuxSendAuto == !!value)) LIKELY
518 slot->AuxSendAuto = !!value;
519 UpdateProps(slot, context);
521 return;
523 case AL_EFFECTSLOT_TARGET_SOFT:
524 target = LookupEffectSlot(context, static_cast<ALuint>(value));
525 if(value && !target)
526 throw al::context_error{AL_INVALID_VALUE, "Invalid effect slot target ID"};
527 if(slot->Target == target) UNLIKELY
528 return;
529 if(target)
531 ALeffectslot *checker{target};
532 while(checker && checker != slot)
533 checker = checker->Target;
534 if(checker)
535 throw al::context_error{AL_INVALID_OPERATION,
536 "Setting target of effect slot ID %u to %u creates circular chain", slot->id,
537 target->id};
540 if(ALeffectslot *oldtarget{slot->Target})
542 /* We must force an update if there was an existing effect slot
543 * target, in case it's about to be deleted.
545 if(target) IncrementRef(target->ref);
546 DecrementRef(oldtarget->ref);
547 slot->Target = target;
548 slot->updateProps(context);
549 return;
552 if(target) IncrementRef(target->ref);
553 slot->Target = target;
554 UpdateProps(slot, context);
555 return;
557 case AL_BUFFER:
558 if(ALbuffer *buffer{slot->Buffer})
560 if(buffer->id == static_cast<ALuint>(value))
561 return;
563 else if(value == 0)
564 return;
566 if(slot->mState == SlotState::Playing)
568 EffectStateFactory *factory{getFactoryByType(slot->Effect.Type)};
569 assert(factory);
570 al::intrusive_ptr<EffectState> state{factory->create()};
572 ALCdevice *device{context->mALDevice.get()};
573 auto bufferlock = std::unique_lock{device->BufferLock};
574 ALbuffer *buffer{};
575 if(value)
577 buffer = LookupBuffer(device, static_cast<ALuint>(value));
578 if(!buffer)
579 throw al::context_error{AL_INVALID_VALUE, "Invalid buffer ID %u", value};
580 if(buffer->mCallback)
581 throw al::context_error{AL_INVALID_OPERATION,
582 "Callback buffer not valid for effects"};
584 IncrementRef(buffer->ref);
587 /* Stop the effect slot from processing while we switch buffers. */
588 RemoveActiveEffectSlots({&slot, 1}, context);
590 if(ALbuffer *oldbuffer{slot->Buffer})
591 DecrementRef(oldbuffer->ref);
592 slot->Buffer = buffer;
593 bufferlock.unlock();
595 state->mOutTarget = device->Dry.Buffer;
597 FPUCtl mixer_mode{};
598 state->deviceUpdate(device, buffer);
600 slot->Effect.State = std::move(state);
602 slot->mPropsDirty = false;
603 slot->updateProps(context);
604 AddActiveEffectSlots({&slot, 1}, context);
606 else
608 ALCdevice *device{context->mALDevice.get()};
609 auto bufferlock = std::unique_lock{device->BufferLock};
610 ALbuffer *buffer{};
611 if(value)
613 buffer = LookupBuffer(device, static_cast<ALuint>(value));
614 if(!buffer)
615 throw al::context_error{AL_INVALID_VALUE, "Invalid buffer ID %u", value};
616 if(buffer->mCallback)
617 throw al::context_error{AL_INVALID_OPERATION,
618 "Callback buffer not valid for effects"};
620 IncrementRef(buffer->ref);
623 if(ALbuffer *oldbuffer{slot->Buffer})
624 DecrementRef(oldbuffer->ref);
625 slot->Buffer = buffer;
626 bufferlock.unlock();
628 FPUCtl mixer_mode{};
629 auto *state = slot->Effect.State.get();
630 state->deviceUpdate(device, buffer);
631 slot->mPropsDirty = true;
633 return;
635 case AL_EFFECTSLOT_STATE_SOFT:
636 throw al::context_error{AL_INVALID_OPERATION, "AL_EFFECTSLOT_STATE_SOFT is read-only"};
639 throw al::context_error{AL_INVALID_ENUM, "Invalid effect slot integer property 0x%04x", param};
641 catch(al::context_error& e) {
642 context->setError(e.errorCode(), "%s", e.what());
645 AL_API DECL_FUNC3(void, alAuxiliaryEffectSlotiv, ALuint,effectslot, ALenum,param, const ALint*,values)
646 FORCE_ALIGN void AL_APIENTRY alAuxiliaryEffectSlotivDirect(ALCcontext *context, ALuint effectslot,
647 ALenum param, const ALint *values) noexcept
648 try {
649 switch(param)
651 case AL_EFFECTSLOT_EFFECT:
652 case AL_EFFECTSLOT_AUXILIARY_SEND_AUTO:
653 case AL_EFFECTSLOT_TARGET_SOFT:
654 case AL_EFFECTSLOT_STATE_SOFT:
655 case AL_BUFFER:
656 alAuxiliaryEffectSlotiDirect(context, effectslot, param, *values);
657 return;
660 std::lock_guard<std::mutex> slotlock{context->mEffectSlotLock};
661 ALeffectslot *slot{LookupEffectSlot(context, effectslot)};
662 if(!slot)
663 throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", effectslot};
665 switch(param)
668 throw al::context_error{AL_INVALID_ENUM, "Invalid effect slot integer-vector property 0x%04x",
669 param};
671 catch(al::context_error& e) {
672 context->setError(e.errorCode(), "%s", e.what());
675 AL_API DECL_FUNC3(void, alAuxiliaryEffectSlotf, ALuint,effectslot, ALenum,param, ALfloat,value)
676 FORCE_ALIGN void AL_APIENTRY alAuxiliaryEffectSlotfDirect(ALCcontext *context, ALuint effectslot,
677 ALenum param, ALfloat value) noexcept
678 try {
679 std::lock_guard<std::mutex> proplock{context->mPropLock};
680 std::lock_guard<std::mutex> slotlock{context->mEffectSlotLock};
682 ALeffectslot *slot{LookupEffectSlot(context, effectslot)};
683 if(!slot)
684 throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", effectslot};
686 switch(param)
688 case AL_EFFECTSLOT_GAIN:
689 if(!(value >= 0.0f && value <= 1.0f))
690 throw al::context_error{AL_INVALID_VALUE, "Effect slot gain out of range"};
691 if(!(slot->Gain == value)) LIKELY
693 slot->Gain = value;
694 UpdateProps(slot, context);
696 return;
699 throw al::context_error{AL_INVALID_ENUM, "Invalid effect slot float property 0x%04x", param};
701 catch(al::context_error& e) {
702 context->setError(e.errorCode(), "%s", e.what());
705 AL_API DECL_FUNC3(void, alAuxiliaryEffectSlotfv, ALuint,effectslot, ALenum,param, const ALfloat*,values)
706 FORCE_ALIGN void AL_APIENTRY alAuxiliaryEffectSlotfvDirect(ALCcontext *context, ALuint effectslot,
707 ALenum param, const ALfloat *values) noexcept
708 try {
709 switch(param)
711 case AL_EFFECTSLOT_GAIN:
712 alAuxiliaryEffectSlotfDirect(context, effectslot, param, *values);
713 return;
716 std::lock_guard<std::mutex> slotlock{context->mEffectSlotLock};
717 ALeffectslot *slot{LookupEffectSlot(context, effectslot)};
718 if(!slot)
719 throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", effectslot};
721 switch(param)
724 throw al::context_error{AL_INVALID_ENUM, "Invalid effect slot float-vector property 0x%04x",
725 param};
727 catch(al::context_error& e) {
728 context->setError(e.errorCode(), "%s", e.what());
732 AL_API DECL_FUNC3(void, alGetAuxiliaryEffectSloti, ALuint,effectslot, ALenum,param, ALint*,value)
733 FORCE_ALIGN void AL_APIENTRY alGetAuxiliaryEffectSlotiDirect(ALCcontext *context,
734 ALuint effectslot, ALenum param, ALint *value) noexcept
735 try {
736 std::lock_guard<std::mutex> slotlock{context->mEffectSlotLock};
737 ALeffectslot *slot{LookupEffectSlot(context, effectslot)};
738 if(!slot)
739 throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", effectslot};
741 switch(param)
743 case AL_EFFECTSLOT_EFFECT:
744 *value = static_cast<ALint>(slot->EffectId);
745 return;
747 case AL_EFFECTSLOT_AUXILIARY_SEND_AUTO:
748 *value = slot->AuxSendAuto ? AL_TRUE : AL_FALSE;
749 return;
751 case AL_EFFECTSLOT_TARGET_SOFT:
752 if(auto *target = slot->Target)
753 *value = static_cast<ALint>(target->id);
754 else
755 *value = 0;
756 return;
758 case AL_EFFECTSLOT_STATE_SOFT:
759 *value = static_cast<int>(slot->mState);
760 return;
762 case AL_BUFFER:
763 if(auto *buffer = slot->Buffer)
764 *value = static_cast<ALint>(buffer->id);
765 else
766 *value = 0;
767 return;
770 throw al::context_error{AL_INVALID_ENUM, "Invalid effect slot integer property 0x%04x", param};
772 catch(al::context_error& e) {
773 context->setError(e.errorCode(), "%s", e.what());
776 AL_API DECL_FUNC3(void, alGetAuxiliaryEffectSlotiv, ALuint,effectslot, ALenum,param, ALint*,values)
777 FORCE_ALIGN void AL_APIENTRY alGetAuxiliaryEffectSlotivDirect(ALCcontext *context,
778 ALuint effectslot, ALenum param, ALint *values) noexcept
779 try {
780 switch(param)
782 case AL_EFFECTSLOT_EFFECT:
783 case AL_EFFECTSLOT_AUXILIARY_SEND_AUTO:
784 case AL_EFFECTSLOT_TARGET_SOFT:
785 case AL_EFFECTSLOT_STATE_SOFT:
786 case AL_BUFFER:
787 alGetAuxiliaryEffectSlotiDirect(context, effectslot, param, values);
788 return;
791 std::lock_guard<std::mutex> slotlock{context->mEffectSlotLock};
792 ALeffectslot *slot = LookupEffectSlot(context, effectslot);
793 if(!slot)
794 throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", effectslot};
796 switch(param)
799 throw al::context_error{AL_INVALID_ENUM, "Invalid effect slot integer-vector property 0x%04x",
800 param};
802 catch(al::context_error& e) {
803 context->setError(e.errorCode(), "%s", e.what());
806 AL_API DECL_FUNC3(void, alGetAuxiliaryEffectSlotf, ALuint,effectslot, ALenum,param, ALfloat*,value)
807 FORCE_ALIGN void AL_APIENTRY alGetAuxiliaryEffectSlotfDirect(ALCcontext *context,
808 ALuint effectslot, ALenum param, ALfloat *value) noexcept
809 try {
810 std::lock_guard<std::mutex> slotlock{context->mEffectSlotLock};
811 ALeffectslot *slot{LookupEffectSlot(context, effectslot)};
812 if(!slot)
813 throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", effectslot};
815 switch(param)
817 case AL_EFFECTSLOT_GAIN:
818 *value = slot->Gain;
819 return;
822 throw al::context_error{AL_INVALID_ENUM, "Invalid effect slot float property 0x%04x", param};
824 catch(al::context_error& e) {
825 context->setError(e.errorCode(), "%s", e.what());
828 AL_API DECL_FUNC3(void, alGetAuxiliaryEffectSlotfv, ALuint,effectslot, ALenum,param, ALfloat*,values)
829 FORCE_ALIGN void AL_APIENTRY alGetAuxiliaryEffectSlotfvDirect(ALCcontext *context,
830 ALuint effectslot, ALenum param, ALfloat *values) noexcept
831 try {
832 switch(param)
834 case AL_EFFECTSLOT_GAIN:
835 alGetAuxiliaryEffectSlotfDirect(context, effectslot, param, values);
836 return;
839 std::lock_guard<std::mutex> slotlock{context->mEffectSlotLock};
840 ALeffectslot *slot{LookupEffectSlot(context, effectslot)};
841 if(!slot)
842 throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", effectslot};
844 switch(param)
847 throw al::context_error{AL_INVALID_ENUM, "Invalid effect slot float-vector property 0x%04x",
848 param};
850 catch(al::context_error& e) {
851 context->setError(e.errorCode(), "%s", e.what());
855 ALeffectslot::ALeffectslot(ALCcontext *context)
857 EffectStateFactory *factory{getFactoryByType(EffectSlotType::None)};
858 if(!factory) throw std::runtime_error{"Failed to get null effect factory"};
860 al::intrusive_ptr<EffectState> state{factory->create()};
861 Effect.State = state;
863 mSlot = context->getEffectSlot();
864 mSlot->InUse = true;
865 mSlot->mEffectState = std::move(state);
868 ALeffectslot::~ALeffectslot()
870 if(Target)
871 DecrementRef(Target->ref);
872 Target = nullptr;
873 if(Buffer)
874 DecrementRef(Buffer->ref);
875 Buffer = nullptr;
877 if(auto *slot = mSlot->Update.exchange(nullptr, std::memory_order_relaxed))
878 slot->State = nullptr;
880 mSlot->mEffectState = nullptr;
881 mSlot->InUse = false;
884 ALenum ALeffectslot::initEffect(ALuint effectId, ALenum effectType, const EffectProps &effectProps,
885 ALCcontext *context)
887 EffectSlotType newtype{EffectSlotTypeFromEnum(effectType)};
888 if(newtype != Effect.Type)
890 EffectStateFactory *factory{getFactoryByType(newtype)};
891 if(!factory)
893 ERR("Failed to find factory for effect slot type %d\n", static_cast<int>(newtype));
894 return AL_INVALID_ENUM;
896 al::intrusive_ptr<EffectState> state{factory->create()};
898 ALCdevice *device{context->mALDevice.get()};
899 state->mOutTarget = device->Dry.Buffer;
901 FPUCtl mixer_mode{};
902 state->deviceUpdate(device, Buffer);
905 Effect.Type = newtype;
906 Effect.Props = effectProps;
908 Effect.State = std::move(state);
910 else if(newtype != EffectSlotType::None)
911 Effect.Props = effectProps;
912 EffectId = effectId;
914 /* Remove state references from old effect slot property updates. */
915 EffectSlotProps *props{context->mFreeEffectSlotProps.load()};
916 while(props)
918 props->State = nullptr;
919 props = props->next.load(std::memory_order_relaxed);
922 return AL_NO_ERROR;
925 void ALeffectslot::updateProps(ALCcontext *context) const
927 /* Get an unused property container, or allocate a new one as needed. */
928 EffectSlotProps *props{context->mFreeEffectSlotProps.load(std::memory_order_acquire)};
929 if(!props)
931 context->allocEffectSlotProps();
932 props = context->mFreeEffectSlotProps.load(std::memory_order_acquire);
934 EffectSlotProps *next;
935 do {
936 next = props->next.load(std::memory_order_relaxed);
937 } while(!context->mFreeEffectSlotProps.compare_exchange_weak(props, next,
938 std::memory_order_acq_rel, std::memory_order_acquire));
940 /* Copy in current property values. */
941 props->Gain = Gain;
942 props->AuxSendAuto = AuxSendAuto;
943 props->Target = Target ? Target->mSlot : nullptr;
945 props->Type = Effect.Type;
946 props->Props = Effect.Props;
947 props->State = Effect.State;
949 /* Set the new container for updating internal parameters. */
950 props = mSlot->Update.exchange(props, std::memory_order_acq_rel);
951 if(props)
953 /* If there was an unused update container, put it back in the
954 * freelist.
956 props->State = nullptr;
957 AtomicReplaceHead(context->mFreeEffectSlotProps, props);
961 void ALeffectslot::SetName(ALCcontext* context, ALuint id, std::string_view name)
963 std::lock_guard<std::mutex> slotlock{context->mEffectSlotLock};
965 auto slot = LookupEffectSlot(context, id);
966 if(!slot)
967 throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", id};
969 context->mEffectSlotNames.insert_or_assign(id, name);
972 void UpdateAllEffectSlotProps(ALCcontext *context)
974 std::lock_guard<std::mutex> slotlock{context->mEffectSlotLock};
975 for(auto &sublist : context->mEffectSlotList)
977 uint64_t usemask{~sublist.FreeMask};
978 while(usemask)
980 const auto idx = static_cast<uint>(al::countr_zero(usemask));
981 usemask &= ~(1_u64 << idx);
982 auto &slot = (*sublist.EffectSlots)[idx];
984 if(std::exchange(slot.mPropsDirty, false))
985 slot.updateProps(context);
990 EffectSlotSubList::~EffectSlotSubList()
992 if(!EffectSlots)
993 return;
995 uint64_t usemask{~FreeMask};
996 while(usemask)
998 const int idx{al::countr_zero(usemask)};
999 std::destroy_at(al::to_address(EffectSlots->begin() + idx));
1000 usemask &= ~(1_u64 << idx);
1002 FreeMask = ~usemask;
1003 SubListAllocator{}.deallocate(EffectSlots, 1);
1004 EffectSlots = nullptr;
1007 #ifdef ALSOFT_EAX
1008 void ALeffectslot::eax_initialize(ALCcontext& al_context, EaxFxSlotIndexValue index)
1010 if(index >= EAX_MAX_FXSLOTS)
1011 eax_fail("Index out of range.");
1013 eax_al_context_ = &al_context;
1014 eax_fx_slot_index_ = index;
1015 eax_fx_slot_set_defaults();
1017 eax_effect_ = std::make_unique<EaxEffect>();
1018 if(index == 0) eax_effect_->init<EaxReverbCommitter>();
1019 else if(index == 1) eax_effect_->init<EaxChorusCommitter>();
1020 else eax_effect_->init<EaxNullCommitter>();
1023 void ALeffectslot::eax_commit()
1025 if(eax_df_ != EaxDirtyFlags{})
1027 auto df = EaxDirtyFlags{};
1028 switch(eax_version_)
1030 case 1:
1031 case 2:
1032 case 3:
1033 eax5_fx_slot_commit(eax123_, df);
1034 break;
1035 case 4:
1036 eax4_fx_slot_commit(df);
1037 break;
1038 case 5:
1039 eax5_fx_slot_commit(eax5_, df);
1040 break;
1042 eax_df_ = EaxDirtyFlags{};
1044 if((df & eax_volume_dirty_bit) != EaxDirtyFlags{})
1045 eax_fx_slot_set_volume();
1046 if((df & eax_flags_dirty_bit) != EaxDirtyFlags{})
1047 eax_fx_slot_set_flags();
1050 if(eax_effect_->commit(eax_version_))
1051 eax_set_efx_slot_effect(*eax_effect_);
1054 [[noreturn]] void ALeffectslot::eax_fail(const char* message)
1056 throw Exception{message};
1059 [[noreturn]] void ALeffectslot::eax_fail_unknown_effect_id()
1061 eax_fail("Unknown effect ID.");
1064 [[noreturn]] void ALeffectslot::eax_fail_unknown_property_id()
1066 eax_fail("Unknown property ID.");
1069 [[noreturn]] void ALeffectslot::eax_fail_unknown_version()
1071 eax_fail("Unknown version.");
1074 void ALeffectslot::eax4_fx_slot_ensure_unlocked() const
1076 if(eax4_fx_slot_is_legacy())
1077 eax_fail("Locked legacy slot.");
1080 ALenum ALeffectslot::eax_get_efx_effect_type(const GUID& guid)
1082 if(guid == EAX_NULL_GUID)
1083 return AL_EFFECT_NULL;
1084 if(guid == EAX_AUTOWAH_EFFECT)
1085 return AL_EFFECT_AUTOWAH;
1086 if(guid == EAX_CHORUS_EFFECT)
1087 return AL_EFFECT_CHORUS;
1088 if(guid == EAX_AGCCOMPRESSOR_EFFECT)
1089 return AL_EFFECT_COMPRESSOR;
1090 if(guid == EAX_DISTORTION_EFFECT)
1091 return AL_EFFECT_DISTORTION;
1092 if(guid == EAX_REVERB_EFFECT)
1093 return AL_EFFECT_EAXREVERB;
1094 if(guid == EAX_ECHO_EFFECT)
1095 return AL_EFFECT_ECHO;
1096 if(guid == EAX_EQUALIZER_EFFECT)
1097 return AL_EFFECT_EQUALIZER;
1098 if(guid == EAX_FLANGER_EFFECT)
1099 return AL_EFFECT_FLANGER;
1100 if(guid == EAX_FREQUENCYSHIFTER_EFFECT)
1101 return AL_EFFECT_FREQUENCY_SHIFTER;
1102 if(guid == EAX_PITCHSHIFTER_EFFECT)
1103 return AL_EFFECT_PITCH_SHIFTER;
1104 if(guid == EAX_RINGMODULATOR_EFFECT)
1105 return AL_EFFECT_RING_MODULATOR;
1106 if(guid == EAX_VOCALMORPHER_EFFECT)
1107 return AL_EFFECT_VOCAL_MORPHER;
1109 eax_fail_unknown_effect_id();
1112 const GUID& ALeffectslot::eax_get_eax_default_effect_guid() const noexcept
1114 switch(eax_fx_slot_index_)
1116 case 0: return EAX_REVERB_EFFECT;
1117 case 1: return EAX_CHORUS_EFFECT;
1118 default: return EAX_NULL_GUID;
1122 long ALeffectslot::eax_get_eax_default_lock() const noexcept
1124 return eax4_fx_slot_is_legacy() ? EAXFXSLOT_LOCKED : EAXFXSLOT_UNLOCKED;
1127 void ALeffectslot::eax4_fx_slot_set_defaults(Eax4Props& props) noexcept
1129 props.guidLoadEffect = eax_get_eax_default_effect_guid();
1130 props.lVolume = EAXFXSLOT_DEFAULTVOLUME;
1131 props.lLock = eax_get_eax_default_lock();
1132 props.ulFlags = EAX40FXSLOT_DEFAULTFLAGS;
1135 void ALeffectslot::eax5_fx_slot_set_defaults(Eax5Props& props) noexcept
1137 props.guidLoadEffect = eax_get_eax_default_effect_guid();
1138 props.lVolume = EAXFXSLOT_DEFAULTVOLUME;
1139 props.lLock = EAXFXSLOT_UNLOCKED;
1140 props.ulFlags = EAX50FXSLOT_DEFAULTFLAGS;
1141 props.lOcclusion = EAXFXSLOT_DEFAULTOCCLUSION;
1142 props.flOcclusionLFRatio = EAXFXSLOT_DEFAULTOCCLUSIONLFRATIO;
1145 void ALeffectslot::eax_fx_slot_set_defaults()
1147 eax5_fx_slot_set_defaults(eax123_.i);
1148 eax4_fx_slot_set_defaults(eax4_.i);
1149 eax5_fx_slot_set_defaults(eax5_.i);
1150 eax_ = eax5_.i;
1151 eax_df_ = EaxDirtyFlags{};
1154 void ALeffectslot::eax4_fx_slot_get(const EaxCall& call, const Eax4Props& props)
1156 switch(call.get_property_id())
1158 case EAXFXSLOT_ALLPARAMETERS:
1159 call.set_value<Exception>(props);
1160 break;
1161 case EAXFXSLOT_LOADEFFECT:
1162 call.set_value<Exception>(props.guidLoadEffect);
1163 break;
1164 case EAXFXSLOT_VOLUME:
1165 call.set_value<Exception>(props.lVolume);
1166 break;
1167 case EAXFXSLOT_LOCK:
1168 call.set_value<Exception>(props.lLock);
1169 break;
1170 case EAXFXSLOT_FLAGS:
1171 call.set_value<Exception>(props.ulFlags);
1172 break;
1173 default:
1174 eax_fail_unknown_property_id();
1178 void ALeffectslot::eax5_fx_slot_get(const EaxCall& call, const Eax5Props& props)
1180 switch(call.get_property_id())
1182 case EAXFXSLOT_ALLPARAMETERS:
1183 call.set_value<Exception>(props);
1184 break;
1185 case EAXFXSLOT_LOADEFFECT:
1186 call.set_value<Exception>(props.guidLoadEffect);
1187 break;
1188 case EAXFXSLOT_VOLUME:
1189 call.set_value<Exception>(props.lVolume);
1190 break;
1191 case EAXFXSLOT_LOCK:
1192 call.set_value<Exception>(props.lLock);
1193 break;
1194 case EAXFXSLOT_FLAGS:
1195 call.set_value<Exception>(props.ulFlags);
1196 break;
1197 case EAXFXSLOT_OCCLUSION:
1198 call.set_value<Exception>(props.lOcclusion);
1199 break;
1200 case EAXFXSLOT_OCCLUSIONLFRATIO:
1201 call.set_value<Exception>(props.flOcclusionLFRatio);
1202 break;
1203 default:
1204 eax_fail_unknown_property_id();
1208 void ALeffectslot::eax_fx_slot_get(const EaxCall& call) const
1210 switch(call.get_version())
1212 case 4: eax4_fx_slot_get(call, eax4_.i); break;
1213 case 5: eax5_fx_slot_get(call, eax5_.i); break;
1214 default: eax_fail_unknown_version();
1218 bool ALeffectslot::eax_get(const EaxCall& call)
1220 switch(call.get_property_set_id())
1222 case EaxCallPropertySetId::fx_slot:
1223 eax_fx_slot_get(call);
1224 break;
1225 case EaxCallPropertySetId::fx_slot_effect:
1226 eax_effect_->get(call);
1227 break;
1228 default:
1229 eax_fail_unknown_property_id();
1232 return false;
1235 void ALeffectslot::eax_fx_slot_load_effect(int version, ALenum altype)
1237 if(!IsValidEffectType(altype))
1238 altype = AL_EFFECT_NULL;
1239 eax_effect_->set_defaults(version, altype);
1242 void ALeffectslot::eax_fx_slot_set_volume()
1244 const auto volume = std::clamp(eax_.lVolume, EAXFXSLOT_MINVOLUME, EAXFXSLOT_MAXVOLUME);
1245 const auto gain = level_mb_to_gain(static_cast<float>(volume));
1246 eax_set_efx_slot_gain(gain);
1249 void ALeffectslot::eax_fx_slot_set_environment_flag()
1251 eax_set_efx_slot_send_auto((eax_.ulFlags & EAXFXSLOTFLAGS_ENVIRONMENT) != 0u);
1254 void ALeffectslot::eax_fx_slot_set_flags()
1256 eax_fx_slot_set_environment_flag();
1259 void ALeffectslot::eax4_fx_slot_set_all(const EaxCall& call)
1261 eax4_fx_slot_ensure_unlocked();
1262 const auto& src = call.get_value<Exception, const EAX40FXSLOTPROPERTIES>();
1263 Eax4AllValidator{}(src);
1264 auto& dst = eax4_.i;
1265 eax_df_ |= eax_load_effect_dirty_bit; // Always reset the effect.
1266 eax_df_ |= (dst.lVolume != src.lVolume ? eax_volume_dirty_bit : EaxDirtyFlags{});
1267 eax_df_ |= (dst.lLock != src.lLock ? eax_lock_dirty_bit : EaxDirtyFlags{});
1268 eax_df_ |= (dst.ulFlags != src.ulFlags ? eax_flags_dirty_bit : EaxDirtyFlags{});
1269 dst = src;
1272 void ALeffectslot::eax5_fx_slot_set_all(const EaxCall& call)
1274 const auto& src = call.get_value<Exception, const EAX50FXSLOTPROPERTIES>();
1275 Eax5AllValidator{}(src);
1276 auto& dst = eax5_.i;
1277 eax_df_ |= eax_load_effect_dirty_bit; // Always reset the effect.
1278 eax_df_ |= (dst.lVolume != src.lVolume ? eax_volume_dirty_bit : EaxDirtyFlags{});
1279 eax_df_ |= (dst.lLock != src.lLock ? eax_lock_dirty_bit : EaxDirtyFlags{});
1280 eax_df_ |= (dst.ulFlags != src.ulFlags ? eax_flags_dirty_bit : EaxDirtyFlags{});
1281 eax_df_ |= (dst.lOcclusion != src.lOcclusion ? eax_flags_dirty_bit : EaxDirtyFlags{});
1282 eax_df_ |= (dst.flOcclusionLFRatio != src.flOcclusionLFRatio ? eax_flags_dirty_bit : EaxDirtyFlags{});
1283 dst = src;
1286 bool ALeffectslot::eax_fx_slot_should_update_sources() const noexcept
1288 static constexpr auto dirty_bits =
1289 eax_occlusion_dirty_bit |
1290 eax_occlusion_lf_ratio_dirty_bit |
1291 eax_flags_dirty_bit;
1293 return (eax_df_ & dirty_bits) != EaxDirtyFlags{};
1296 // Returns `true` if all sources should be updated, or `false` otherwise.
1297 bool ALeffectslot::eax4_fx_slot_set(const EaxCall& call)
1299 auto& dst = eax4_.i;
1301 switch(call.get_property_id())
1303 case EAXFXSLOT_NONE:
1304 break;
1305 case EAXFXSLOT_ALLPARAMETERS:
1306 eax4_fx_slot_set_all(call);
1307 if((eax_df_ & eax_load_effect_dirty_bit))
1308 eax_fx_slot_load_effect(4, eax_get_efx_effect_type(dst.guidLoadEffect));
1309 break;
1310 case EAXFXSLOT_LOADEFFECT:
1311 eax4_fx_slot_ensure_unlocked();
1312 eax_fx_slot_set_dirty<Eax4GuidLoadEffectValidator, eax_load_effect_dirty_bit>(call, dst.guidLoadEffect, eax_df_);
1313 if((eax_df_ & eax_load_effect_dirty_bit))
1314 eax_fx_slot_load_effect(4, eax_get_efx_effect_type(dst.guidLoadEffect));
1315 break;
1316 case EAXFXSLOT_VOLUME:
1317 eax_fx_slot_set<Eax4VolumeValidator, eax_volume_dirty_bit>(call, dst.lVolume, eax_df_);
1318 break;
1319 case EAXFXSLOT_LOCK:
1320 eax4_fx_slot_ensure_unlocked();
1321 eax_fx_slot_set<Eax4LockValidator, eax_lock_dirty_bit>(call, dst.lLock, eax_df_);
1322 break;
1323 case EAXFXSLOT_FLAGS:
1324 eax_fx_slot_set<Eax4FlagsValidator, eax_flags_dirty_bit>(call, dst.ulFlags, eax_df_);
1325 break;
1326 default:
1327 eax_fail_unknown_property_id();
1330 return eax_fx_slot_should_update_sources();
1333 // Returns `true` if all sources should be updated, or `false` otherwise.
1334 bool ALeffectslot::eax5_fx_slot_set(const EaxCall& call)
1336 auto& dst = eax5_.i;
1338 switch(call.get_property_id())
1340 case EAXFXSLOT_NONE:
1341 break;
1342 case EAXFXSLOT_ALLPARAMETERS:
1343 eax5_fx_slot_set_all(call);
1344 if((eax_df_ & eax_load_effect_dirty_bit))
1345 eax_fx_slot_load_effect(5, eax_get_efx_effect_type(dst.guidLoadEffect));
1346 break;
1347 case EAXFXSLOT_LOADEFFECT:
1348 eax_fx_slot_set_dirty<Eax4GuidLoadEffectValidator, eax_load_effect_dirty_bit>(call, dst.guidLoadEffect, eax_df_);
1349 if((eax_df_ & eax_load_effect_dirty_bit))
1350 eax_fx_slot_load_effect(5, eax_get_efx_effect_type(dst.guidLoadEffect));
1351 break;
1352 case EAXFXSLOT_VOLUME:
1353 eax_fx_slot_set<Eax4VolumeValidator, eax_volume_dirty_bit>(call, dst.lVolume, eax_df_);
1354 break;
1355 case EAXFXSLOT_LOCK:
1356 eax_fx_slot_set<Eax4LockValidator, eax_lock_dirty_bit>(call, dst.lLock, eax_df_);
1357 break;
1358 case EAXFXSLOT_FLAGS:
1359 eax_fx_slot_set<Eax5FlagsValidator, eax_flags_dirty_bit>(call, dst.ulFlags, eax_df_);
1360 break;
1361 case EAXFXSLOT_OCCLUSION:
1362 eax_fx_slot_set<Eax5OcclusionValidator, eax_occlusion_dirty_bit>(call, dst.lOcclusion, eax_df_);
1363 break;
1364 case EAXFXSLOT_OCCLUSIONLFRATIO:
1365 eax_fx_slot_set<Eax5OcclusionLfRatioValidator, eax_occlusion_lf_ratio_dirty_bit>(call, dst.flOcclusionLFRatio, eax_df_);
1366 break;
1367 default:
1368 eax_fail_unknown_property_id();
1371 return eax_fx_slot_should_update_sources();
1374 // Returns `true` if all sources should be updated, or `false` otherwise.
1375 bool ALeffectslot::eax_fx_slot_set(const EaxCall& call)
1377 switch (call.get_version())
1379 case 4: return eax4_fx_slot_set(call);
1380 case 5: return eax5_fx_slot_set(call);
1381 default: eax_fail_unknown_version();
1385 // Returns `true` if all sources should be updated, or `false` otherwise.
1386 bool ALeffectslot::eax_set(const EaxCall& call)
1388 bool ret{false};
1390 switch(call.get_property_set_id())
1392 case EaxCallPropertySetId::fx_slot: ret = eax_fx_slot_set(call); break;
1393 case EaxCallPropertySetId::fx_slot_effect: eax_effect_->set(call); break;
1394 default: eax_fail_unknown_property_id();
1397 const auto version = call.get_version();
1398 if(eax_version_ != version)
1399 eax_df_ = ~EaxDirtyFlags{};
1400 eax_version_ = version;
1402 return ret;
1405 void ALeffectslot::eax4_fx_slot_commit(EaxDirtyFlags& dst_df)
1407 eax_fx_slot_commit_property<eax_load_effect_dirty_bit>(eax4_, dst_df, &EAX40FXSLOTPROPERTIES::guidLoadEffect);
1408 eax_fx_slot_commit_property<eax_volume_dirty_bit>(eax4_, dst_df, &EAX40FXSLOTPROPERTIES::lVolume);
1409 eax_fx_slot_commit_property<eax_lock_dirty_bit>(eax4_, dst_df, &EAX40FXSLOTPROPERTIES::lLock);
1410 eax_fx_slot_commit_property<eax_flags_dirty_bit>(eax4_, dst_df, &EAX40FXSLOTPROPERTIES::ulFlags);
1412 auto& dst_i = eax_;
1414 if(dst_i.lOcclusion != EAXFXSLOT_DEFAULTOCCLUSION) {
1415 dst_df |= eax_occlusion_dirty_bit;
1416 dst_i.lOcclusion = EAXFXSLOT_DEFAULTOCCLUSION;
1419 if(dst_i.flOcclusionLFRatio != EAXFXSLOT_DEFAULTOCCLUSIONLFRATIO) {
1420 dst_df |= eax_occlusion_lf_ratio_dirty_bit;
1421 dst_i.flOcclusionLFRatio = EAXFXSLOT_DEFAULTOCCLUSIONLFRATIO;
1425 void ALeffectslot::eax5_fx_slot_commit(Eax5State& state, EaxDirtyFlags& dst_df)
1427 eax_fx_slot_commit_property<eax_load_effect_dirty_bit>(state, dst_df, &EAX50FXSLOTPROPERTIES::guidLoadEffect);
1428 eax_fx_slot_commit_property<eax_volume_dirty_bit>(state, dst_df, &EAX50FXSLOTPROPERTIES::lVolume);
1429 eax_fx_slot_commit_property<eax_lock_dirty_bit>(state, dst_df, &EAX50FXSLOTPROPERTIES::lLock);
1430 eax_fx_slot_commit_property<eax_flags_dirty_bit>(state, dst_df, &EAX50FXSLOTPROPERTIES::ulFlags);
1431 eax_fx_slot_commit_property<eax_occlusion_dirty_bit>(state, dst_df, &EAX50FXSLOTPROPERTIES::lOcclusion);
1432 eax_fx_slot_commit_property<eax_occlusion_lf_ratio_dirty_bit>(state, dst_df, &EAX50FXSLOTPROPERTIES::flOcclusionLFRatio);
1435 void ALeffectslot::eax_set_efx_slot_effect(EaxEffect &effect)
1437 #define EAX_PREFIX "[EAX_SET_EFFECT_SLOT_EFFECT] "
1439 const auto error = initEffect(0, effect.al_effect_type_, effect.al_effect_props_,
1440 eax_al_context_);
1442 if(error != AL_NO_ERROR) {
1443 ERR(EAX_PREFIX "%s\n", "Failed to initialize an effect.");
1444 return;
1447 if(mState == SlotState::Initial) {
1448 mPropsDirty = false;
1449 updateProps(eax_al_context_);
1450 auto effect_slot_ptr = this;
1451 AddActiveEffectSlots({&effect_slot_ptr, 1}, eax_al_context_);
1452 mState = SlotState::Playing;
1453 return;
1456 mPropsDirty = true;
1458 #undef EAX_PREFIX
1461 void ALeffectslot::eax_set_efx_slot_send_auto(bool is_send_auto)
1463 if(AuxSendAuto == is_send_auto)
1464 return;
1466 AuxSendAuto = is_send_auto;
1467 mPropsDirty = true;
1470 void ALeffectslot::eax_set_efx_slot_gain(ALfloat gain)
1472 #define EAX_PREFIX "[EAX_SET_EFFECT_SLOT_GAIN] "
1474 if(gain == Gain)
1475 return;
1476 if(gain < 0.0f || gain > 1.0f)
1477 ERR(EAX_PREFIX "Gain out of range (%f)\n", gain);
1479 Gain = std::clamp(gain, 0.0f, 1.0f);
1480 mPropsDirty = true;
1482 #undef EAX_PREFIX
1485 void ALeffectslot::EaxDeleter::operator()(ALeffectslot* effect_slot)
1487 eax_delete_al_effect_slot(*effect_slot->eax_al_context_, *effect_slot);
1490 EaxAlEffectSlotUPtr eax_create_al_effect_slot(ALCcontext& context)
1492 #define EAX_PREFIX "[EAX_MAKE_EFFECT_SLOT] "
1494 std::lock_guard<std::mutex> slotlock{context.mEffectSlotLock};
1495 auto& device = *context.mALDevice;
1497 if(context.mNumEffectSlots == device.AuxiliaryEffectSlotMax) {
1498 ERR(EAX_PREFIX "%s\n", "Out of memory.");
1499 return nullptr;
1502 if(!EnsureEffectSlots(&context, 1)) {
1503 ERR(EAX_PREFIX "%s\n", "Failed to ensure.");
1504 return nullptr;
1507 return EaxAlEffectSlotUPtr{AllocEffectSlot(&context)};
1509 #undef EAX_PREFIX
1512 void eax_delete_al_effect_slot(ALCcontext& context, ALeffectslot& effect_slot)
1514 #define EAX_PREFIX "[EAX_DELETE_EFFECT_SLOT] "
1516 std::lock_guard<std::mutex> slotlock{context.mEffectSlotLock};
1518 if(effect_slot.ref.load(std::memory_order_relaxed) != 0)
1520 ERR(EAX_PREFIX "Deleting in-use effect slot %u.\n", effect_slot.id);
1521 return;
1524 auto effect_slot_ptr = &effect_slot;
1525 RemoveActiveEffectSlots({&effect_slot_ptr, 1}, &context);
1526 FreeEffectSlot(&context, &effect_slot);
1528 #undef EAX_PREFIX
1530 #endif // ALSOFT_EAX