Clean up some unnecessary includes
[openal-soft.git] / alc / alu.cpp
blob6d3e55497847179808a966e3940e0bffefaa8062
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 "alu.h"
25 #include <algorithm>
26 #include <array>
27 #include <atomic>
28 #include <chrono>
29 #include <climits>
30 #include <cmath>
31 #include <cstdarg>
32 #include <cstdio>
33 #include <cstdlib>
34 #include <functional>
35 #include <iterator>
36 #include <limits>
37 #include <memory>
38 #include <new>
39 #include <numeric>
40 #include <utility>
42 #include "AL/al.h"
43 #include "AL/alc.h"
44 #include "AL/efx.h"
46 #include "al/auxeffectslot.h"
47 #include "al/buffer.h"
48 #include "al/effect.h"
49 #include "al/event.h"
50 #include "al/listener.h"
51 #include "alcmain.h"
52 #include "alcontext.h"
53 #include "almalloc.h"
54 #include "alnumeric.h"
55 #include "alspan.h"
56 #include "alstring.h"
57 #include "ambidefs.h"
58 #include "atomic.h"
59 #include "bformatdec.h"
60 #include "bs2b.h"
61 #include "cpu_caps.h"
62 #include "devformat.h"
63 #include "effects/base.h"
64 #include "filters/biquad.h"
65 #include "filters/nfc.h"
66 #include "filters/splitter.h"
67 #include "fpu_modes.h"
68 #include "hrtf.h"
69 #include "inprogext.h"
70 #include "mastering.h"
71 #include "math_defs.h"
72 #include "mixer/defs.h"
73 #include "opthelpers.h"
74 #include "ringbuffer.h"
75 #include "strutils.h"
76 #include "threads.h"
77 #include "uhjfilter.h"
78 #include "vecmat.h"
79 #include "voice.h"
81 #include "bsinc_inc.h"
84 static_assert(!(MAX_RESAMPLER_PADDING&1) && MAX_RESAMPLER_PADDING >= bsinc24.m[0],
85 "MAX_RESAMPLER_PADDING is not a multiple of two, or is too small");
88 namespace {
90 using namespace std::placeholders;
92 ALfloat InitConeScale()
94 ALfloat ret{1.0f};
95 if(auto optval = al::getenv("__ALSOFT_HALF_ANGLE_CONES"))
97 if(al::strcasecmp(optval->c_str(), "true") == 0
98 || strtol(optval->c_str(), nullptr, 0) == 1)
99 ret *= 0.5f;
101 return ret;
104 ALfloat InitZScale()
106 ALfloat ret{1.0f};
107 if(auto optval = al::getenv("__ALSOFT_REVERSE_Z"))
109 if(al::strcasecmp(optval->c_str(), "true") == 0
110 || strtol(optval->c_str(), nullptr, 0) == 1)
111 ret *= -1.0f;
113 return ret;
116 } // namespace
118 /* Cone scalar */
119 const ALfloat ConeScale{InitConeScale()};
121 /* Localized Z scalar for mono sources */
122 const ALfloat ZScale{InitZScale()};
124 MixerFunc MixSamples{Mix_<CTag>};
125 RowMixerFunc MixRowSamples{MixRow_<CTag>};
127 namespace {
129 struct ChanMap {
130 Channel channel;
131 ALfloat angle;
132 ALfloat elevation;
135 void ClearArray(ALfloat (&f)[MAX_OUTPUT_CHANNELS])
137 std::fill(std::begin(f), std::end(f), 0.0f);
140 HrtfDirectMixerFunc MixDirectHrtf = MixDirectHrtf_<CTag>;
142 inline MixerFunc SelectMixer()
144 #ifdef HAVE_NEON
145 if((CPUCapFlags&CPU_CAP_NEON))
146 return Mix_<NEONTag>;
147 #endif
148 #ifdef HAVE_SSE
149 if((CPUCapFlags&CPU_CAP_SSE))
150 return Mix_<SSETag>;
151 #endif
152 return Mix_<CTag>;
155 inline RowMixerFunc SelectRowMixer()
157 #ifdef HAVE_NEON
158 if((CPUCapFlags&CPU_CAP_NEON))
159 return MixRow_<NEONTag>;
160 #endif
161 #ifdef HAVE_SSE
162 if((CPUCapFlags&CPU_CAP_SSE))
163 return MixRow_<SSETag>;
164 #endif
165 return MixRow_<CTag>;
168 inline HrtfDirectMixerFunc SelectHrtfMixer(void)
170 #ifdef HAVE_NEON
171 if((CPUCapFlags&CPU_CAP_NEON))
172 return MixDirectHrtf_<NEONTag>;
173 #endif
174 #ifdef HAVE_SSE
175 if((CPUCapFlags&CPU_CAP_SSE))
176 return MixDirectHrtf_<SSETag>;
177 #endif
179 return MixDirectHrtf_<CTag>;
183 inline void BsincPrepare(const ALuint increment, BsincState *state, const BSincTable *table)
185 size_t si{BSINC_SCALE_COUNT - 1};
186 float sf{0.0f};
188 if(increment > FRACTIONONE)
190 sf = FRACTIONONE / static_cast<float>(increment);
191 sf = maxf(0.0f, (BSINC_SCALE_COUNT-1) * (sf-table->scaleBase) * table->scaleRange);
192 si = float2uint(sf);
193 /* The interpolation factor is fit to this diagonally-symmetric curve
194 * to reduce the transition ripple caused by interpolating different
195 * scales of the sinc function.
197 sf = 1.0f - std::cos(std::asin(sf - static_cast<float>(si)));
200 state->sf = sf;
201 state->m = table->m[si];
202 state->l = (state->m/2) - 1;
203 state->filter = table->Tab + table->filterOffset[si];
206 inline ResamplerFunc SelectResampler(Resampler resampler, ALuint increment)
208 switch(resampler)
210 case Resampler::Point:
211 return Resample_<PointTag,CTag>;
212 case Resampler::Linear:
213 #ifdef HAVE_NEON
214 if((CPUCapFlags&CPU_CAP_NEON))
215 return Resample_<LerpTag,NEONTag>;
216 #endif
217 #ifdef HAVE_SSE4_1
218 if((CPUCapFlags&CPU_CAP_SSE4_1))
219 return Resample_<LerpTag,SSE4Tag>;
220 #endif
221 #ifdef HAVE_SSE2
222 if((CPUCapFlags&CPU_CAP_SSE2))
223 return Resample_<LerpTag,SSE2Tag>;
224 #endif
225 return Resample_<LerpTag,CTag>;
226 case Resampler::Cubic:
227 return Resample_<CubicTag,CTag>;
228 case Resampler::BSinc12:
229 case Resampler::BSinc24:
230 if(increment <= FRACTIONONE)
232 /* fall-through */
233 case Resampler::FastBSinc12:
234 case Resampler::FastBSinc24:
235 #ifdef HAVE_NEON
236 if((CPUCapFlags&CPU_CAP_NEON))
237 return Resample_<FastBSincTag,NEONTag>;
238 #endif
239 #ifdef HAVE_SSE
240 if((CPUCapFlags&CPU_CAP_SSE))
241 return Resample_<FastBSincTag,SSETag>;
242 #endif
243 return Resample_<FastBSincTag,CTag>;
245 #ifdef HAVE_NEON
246 if((CPUCapFlags&CPU_CAP_NEON))
247 return Resample_<BSincTag,NEONTag>;
248 #endif
249 #ifdef HAVE_SSE
250 if((CPUCapFlags&CPU_CAP_SSE))
251 return Resample_<BSincTag,SSETag>;
252 #endif
253 return Resample_<BSincTag,CTag>;
256 return Resample_<PointTag,CTag>;
259 } // namespace
261 void aluInit(void)
263 MixSamples = SelectMixer();
264 MixRowSamples = SelectRowMixer();
265 MixDirectHrtf = SelectHrtfMixer();
269 ResamplerFunc PrepareResampler(Resampler resampler, ALuint increment, InterpState *state)
271 switch(resampler)
273 case Resampler::Point:
274 case Resampler::Linear:
275 case Resampler::Cubic:
276 break;
277 case Resampler::FastBSinc12:
278 case Resampler::BSinc12:
279 BsincPrepare(increment, &state->bsinc, &bsinc12);
280 break;
281 case Resampler::FastBSinc24:
282 case Resampler::BSinc24:
283 BsincPrepare(increment, &state->bsinc, &bsinc24);
284 break;
286 return SelectResampler(resampler, increment);
290 void ALCdevice::ProcessHrtf(const size_t SamplesToDo)
292 /* HRTF is stereo output only. */
293 const ALuint lidx{RealOut.ChannelIndex[FrontLeft]};
294 const ALuint ridx{RealOut.ChannelIndex[FrontRight]};
296 MixDirectHrtf(RealOut.Buffer[lidx], RealOut.Buffer[ridx], Dry.Buffer, HrtfAccumData,
297 mHrtfState.get(), SamplesToDo);
300 void ALCdevice::ProcessAmbiDec(const size_t SamplesToDo)
302 AmbiDecoder->process(RealOut.Buffer, Dry.Buffer.data(), SamplesToDo);
305 void ALCdevice::ProcessUhj(const size_t SamplesToDo)
307 /* UHJ is stereo output only. */
308 const ALuint lidx{RealOut.ChannelIndex[FrontLeft]};
309 const ALuint ridx{RealOut.ChannelIndex[FrontRight]};
311 /* Encode to stereo-compatible 2-channel UHJ output. */
312 Uhj_Encoder->encode(RealOut.Buffer[lidx], RealOut.Buffer[ridx], Dry.Buffer.data(),
313 SamplesToDo);
316 void ALCdevice::ProcessBs2b(const size_t SamplesToDo)
318 /* First, decode the ambisonic mix to the "real" output. */
319 AmbiDecoder->process(RealOut.Buffer, Dry.Buffer.data(), SamplesToDo);
321 /* BS2B is stereo output only. */
322 const ALuint lidx{RealOut.ChannelIndex[FrontLeft]};
323 const ALuint ridx{RealOut.ChannelIndex[FrontRight]};
325 /* Now apply the BS2B binaural/crossfeed filter. */
326 bs2b_cross_feed(Bs2b.get(), RealOut.Buffer[lidx].data(), RealOut.Buffer[ridx].data(),
327 SamplesToDo);
331 namespace {
333 /* This RNG method was created based on the math found in opusdec. It's quick,
334 * and starting with a seed value of 22222, is suitable for generating
335 * whitenoise.
337 inline ALuint dither_rng(ALuint *seed) noexcept
339 *seed = (*seed * 96314165) + 907633515;
340 return *seed;
344 inline alu::Vector aluCrossproduct(const alu::Vector &in1, const alu::Vector &in2)
346 return alu::Vector{
347 in1[1]*in2[2] - in1[2]*in2[1],
348 in1[2]*in2[0] - in1[0]*in2[2],
349 in1[0]*in2[1] - in1[1]*in2[0],
350 0.0f
354 inline ALfloat aluDotproduct(const alu::Vector &vec1, const alu::Vector &vec2)
356 return vec1[0]*vec2[0] + vec1[1]*vec2[1] + vec1[2]*vec2[2];
360 alu::Vector operator*(const alu::Matrix &mtx, const alu::Vector &vec) noexcept
362 return alu::Vector{
363 vec[0]*mtx[0][0] + vec[1]*mtx[1][0] + vec[2]*mtx[2][0] + vec[3]*mtx[3][0],
364 vec[0]*mtx[0][1] + vec[1]*mtx[1][1] + vec[2]*mtx[2][1] + vec[3]*mtx[3][1],
365 vec[0]*mtx[0][2] + vec[1]*mtx[1][2] + vec[2]*mtx[2][2] + vec[3]*mtx[3][2],
366 vec[0]*mtx[0][3] + vec[1]*mtx[1][3] + vec[2]*mtx[2][3] + vec[3]*mtx[3][3]
371 bool CalcContextParams(ALCcontext *Context)
373 ALcontextProps *props{Context->mUpdate.exchange(nullptr, std::memory_order_acq_rel)};
374 if(!props) return false;
376 ALlistener &Listener = Context->mListener;
377 Listener.Params.DopplerFactor = props->DopplerFactor;
378 Listener.Params.SpeedOfSound = props->SpeedOfSound * props->DopplerVelocity;
380 Listener.Params.SourceDistanceModel = props->SourceDistanceModel;
381 Listener.Params.mDistanceModel = props->mDistanceModel;
383 AtomicReplaceHead(Context->mFreeContextProps, props);
384 return true;
387 bool CalcListenerParams(ALCcontext *Context)
389 ALlistener &Listener = Context->mListener;
391 ALlistenerProps *props{Listener.Params.Update.exchange(nullptr, std::memory_order_acq_rel)};
392 if(!props) return false;
394 /* AT then UP */
395 alu::Vector N{props->OrientAt[0], props->OrientAt[1], props->OrientAt[2], 0.0f};
396 N.normalize();
397 alu::Vector V{props->OrientUp[0], props->OrientUp[1], props->OrientUp[2], 0.0f};
398 V.normalize();
399 /* Build and normalize right-vector */
400 alu::Vector U{aluCrossproduct(N, V)};
401 U.normalize();
403 Listener.Params.Matrix = alu::Matrix{
404 U[0], V[0], -N[0], 0.0f,
405 U[1], V[1], -N[1], 0.0f,
406 U[2], V[2], -N[2], 0.0f,
407 0.0f, 0.0f, 0.0f, 1.0f
410 const alu::Vector P{Listener.Params.Matrix *
411 alu::Vector{props->Position[0], props->Position[1], props->Position[2], 1.0f}};
412 Listener.Params.Matrix.setRow(3, -P[0], -P[1], -P[2], 1.0f);
414 const alu::Vector vel{props->Velocity[0], props->Velocity[1], props->Velocity[2], 0.0f};
415 Listener.Params.Velocity = Listener.Params.Matrix * vel;
417 Listener.Params.Gain = props->Gain * Context->mGainBoost;
418 Listener.Params.MetersPerUnit = props->MetersPerUnit;
420 AtomicReplaceHead(Context->mFreeListenerProps, props);
421 return true;
424 bool CalcEffectSlotParams(ALeffectslot *slot, ALCcontext *context)
426 ALeffectslotProps *props{slot->Params.Update.exchange(nullptr, std::memory_order_acq_rel)};
427 if(!props) return false;
429 slot->Params.Gain = props->Gain;
430 slot->Params.AuxSendAuto = props->AuxSendAuto;
431 slot->Params.Target = props->Target;
432 slot->Params.EffectType = props->Type;
433 slot->Params.mEffectProps = props->Props;
434 if(IsReverbEffect(props->Type))
436 slot->Params.RoomRolloff = props->Props.Reverb.RoomRolloffFactor;
437 slot->Params.DecayTime = props->Props.Reverb.DecayTime;
438 slot->Params.DecayLFRatio = props->Props.Reverb.DecayLFRatio;
439 slot->Params.DecayHFRatio = props->Props.Reverb.DecayHFRatio;
440 slot->Params.DecayHFLimit = props->Props.Reverb.DecayHFLimit;
441 slot->Params.AirAbsorptionGainHF = props->Props.Reverb.AirAbsorptionGainHF;
443 else
445 slot->Params.RoomRolloff = 0.0f;
446 slot->Params.DecayTime = 0.0f;
447 slot->Params.DecayLFRatio = 0.0f;
448 slot->Params.DecayHFRatio = 0.0f;
449 slot->Params.DecayHFLimit = AL_FALSE;
450 slot->Params.AirAbsorptionGainHF = 1.0f;
453 EffectState *state{props->State};
454 props->State = nullptr;
455 EffectState *oldstate{slot->Params.mEffectState};
456 slot->Params.mEffectState = state;
458 /* Only release the old state if it won't get deleted, since we can't be
459 * deleting/freeing anything in the mixer.
461 if(!oldstate->releaseIfNoDelete())
463 /* Otherwise, if it would be deleted send it off with a release event. */
464 RingBuffer *ring{context->mAsyncEvents.get()};
465 auto evt_vec = ring->getWriteVector();
466 if LIKELY(evt_vec.first.len > 0)
468 AsyncEvent *evt{new (evt_vec.first.buf) AsyncEvent{EventType_ReleaseEffectState}};
469 evt->u.mEffectState = oldstate;
470 ring->writeAdvance(1);
471 context->mEventSem.post();
473 else
475 /* If writing the event failed, the queue was probably full. Store
476 * the old state in the property object where it can eventually be
477 * cleaned up sometime later (not ideal, but better than blocking
478 * or leaking).
480 props->State = oldstate;
484 AtomicReplaceHead(context->mFreeEffectslotProps, props);
486 EffectTarget output;
487 if(ALeffectslot *target{slot->Params.Target})
488 output = EffectTarget{&target->Wet, nullptr};
489 else
491 ALCdevice *device{context->mDevice.get()};
492 output = EffectTarget{&device->Dry, &device->RealOut};
494 state->update(context, slot, &slot->Params.mEffectProps, output);
495 return true;
499 /* Scales the given azimuth toward the side (+/- pi/2 radians) for positions in
500 * front.
502 inline float ScaleAzimuthFront(float azimuth, float scale)
504 const ALfloat abs_azi{std::fabs(azimuth)};
505 if(!(abs_azi >= al::MathDefs<float>::Pi()*0.5f))
506 return std::copysign(minf(abs_azi*scale, al::MathDefs<float>::Pi()*0.5f), azimuth);
507 return azimuth;
510 void CalcPanningAndFilters(ALvoice *voice, const ALfloat xpos, const ALfloat ypos,
511 const ALfloat zpos, const ALfloat Distance, const ALfloat Spread, const ALfloat DryGain,
512 const ALfloat DryGainHF, const ALfloat DryGainLF, const ALfloat (&WetGain)[MAX_SENDS],
513 const ALfloat (&WetGainLF)[MAX_SENDS], const ALfloat (&WetGainHF)[MAX_SENDS],
514 ALeffectslot *(&SendSlots)[MAX_SENDS], const ALvoicePropsBase *props,
515 const ALlistener &Listener, const ALCdevice *Device)
517 static constexpr ChanMap MonoMap[1]{
518 { FrontCenter, 0.0f, 0.0f }
519 }, RearMap[2]{
520 { BackLeft, Deg2Rad(-150.0f), Deg2Rad(0.0f) },
521 { BackRight, Deg2Rad( 150.0f), Deg2Rad(0.0f) }
522 }, QuadMap[4]{
523 { FrontLeft, Deg2Rad( -45.0f), Deg2Rad(0.0f) },
524 { FrontRight, Deg2Rad( 45.0f), Deg2Rad(0.0f) },
525 { BackLeft, Deg2Rad(-135.0f), Deg2Rad(0.0f) },
526 { BackRight, Deg2Rad( 135.0f), Deg2Rad(0.0f) }
527 }, X51Map[6]{
528 { FrontLeft, Deg2Rad( -30.0f), Deg2Rad(0.0f) },
529 { FrontRight, Deg2Rad( 30.0f), Deg2Rad(0.0f) },
530 { FrontCenter, Deg2Rad( 0.0f), Deg2Rad(0.0f) },
531 { LFE, 0.0f, 0.0f },
532 { SideLeft, Deg2Rad(-110.0f), Deg2Rad(0.0f) },
533 { SideRight, Deg2Rad( 110.0f), Deg2Rad(0.0f) }
534 }, X61Map[7]{
535 { FrontLeft, Deg2Rad(-30.0f), Deg2Rad(0.0f) },
536 { FrontRight, Deg2Rad( 30.0f), Deg2Rad(0.0f) },
537 { FrontCenter, Deg2Rad( 0.0f), Deg2Rad(0.0f) },
538 { LFE, 0.0f, 0.0f },
539 { BackCenter, Deg2Rad(180.0f), Deg2Rad(0.0f) },
540 { SideLeft, Deg2Rad(-90.0f), Deg2Rad(0.0f) },
541 { SideRight, Deg2Rad( 90.0f), Deg2Rad(0.0f) }
542 }, X71Map[8]{
543 { FrontLeft, Deg2Rad( -30.0f), Deg2Rad(0.0f) },
544 { FrontRight, Deg2Rad( 30.0f), Deg2Rad(0.0f) },
545 { FrontCenter, Deg2Rad( 0.0f), Deg2Rad(0.0f) },
546 { LFE, 0.0f, 0.0f },
547 { BackLeft, Deg2Rad(-150.0f), Deg2Rad(0.0f) },
548 { BackRight, Deg2Rad( 150.0f), Deg2Rad(0.0f) },
549 { SideLeft, Deg2Rad( -90.0f), Deg2Rad(0.0f) },
550 { SideRight, Deg2Rad( 90.0f), Deg2Rad(0.0f) }
553 ChanMap StereoMap[2]{
554 { FrontLeft, Deg2Rad(-30.0f), Deg2Rad(0.0f) },
555 { FrontRight, Deg2Rad( 30.0f), Deg2Rad(0.0f) }
558 const auto Frequency = static_cast<ALfloat>(Device->Frequency);
559 const ALuint NumSends{Device->NumAuxSends};
561 bool DirectChannels{props->DirectChannels != AL_FALSE};
562 const ChanMap *chans{nullptr};
563 ALuint num_channels{0};
564 bool isbformat{false};
565 ALfloat downmix_gain{1.0f};
566 switch(voice->mFmtChannels)
568 case FmtMono:
569 chans = MonoMap;
570 num_channels = 1;
571 /* Mono buffers are never played direct. */
572 DirectChannels = false;
573 break;
575 case FmtStereo:
576 /* Convert counter-clockwise to clockwise. */
577 StereoMap[0].angle = -props->StereoPan[0];
578 StereoMap[1].angle = -props->StereoPan[1];
580 chans = StereoMap;
581 num_channels = 2;
582 downmix_gain = 1.0f / 2.0f;
583 break;
585 case FmtRear:
586 chans = RearMap;
587 num_channels = 2;
588 downmix_gain = 1.0f / 2.0f;
589 break;
591 case FmtQuad:
592 chans = QuadMap;
593 num_channels = 4;
594 downmix_gain = 1.0f / 4.0f;
595 break;
597 case FmtX51:
598 chans = X51Map;
599 num_channels = 6;
600 /* NOTE: Excludes LFE. */
601 downmix_gain = 1.0f / 5.0f;
602 break;
604 case FmtX61:
605 chans = X61Map;
606 num_channels = 7;
607 /* NOTE: Excludes LFE. */
608 downmix_gain = 1.0f / 6.0f;
609 break;
611 case FmtX71:
612 chans = X71Map;
613 num_channels = 8;
614 /* NOTE: Excludes LFE. */
615 downmix_gain = 1.0f / 7.0f;
616 break;
618 case FmtBFormat2D:
619 num_channels = 3;
620 isbformat = true;
621 DirectChannels = false;
622 break;
624 case FmtBFormat3D:
625 num_channels = 4;
626 isbformat = true;
627 DirectChannels = false;
628 break;
630 ASSUME(num_channels > 0);
632 std::for_each(voice->mChans.begin(), voice->mChans.begin()+num_channels,
633 [NumSends](ALvoice::ChannelData &chandata) -> void
635 chandata.mDryParams.Hrtf.Target = HrtfFilter{};
636 ClearArray(chandata.mDryParams.Gains.Target);
637 std::for_each(chandata.mWetParams.begin(), chandata.mWetParams.begin()+NumSends,
638 [](SendParams &params) -> void { ClearArray(params.Gains.Target); });
641 voice->mFlags &= ~(VOICE_HAS_HRTF | VOICE_HAS_NFC);
642 if(isbformat)
644 /* Special handling for B-Format sources. */
646 if(Distance > std::numeric_limits<float>::epsilon())
648 /* Panning a B-Format sound toward some direction is easy. Just pan
649 * the first (W) channel as a normal mono sound and silence the
650 * others.
653 if(Device->AvgSpeakerDist > 0.0f)
655 /* Clamp the distance for really close sources, to prevent
656 * excessive bass.
658 const ALfloat mdist{maxf(Distance, Device->AvgSpeakerDist/4.0f)};
659 const ALfloat w0{SPEEDOFSOUNDMETRESPERSEC / (mdist * Frequency)};
661 /* Only need to adjust the first channel of a B-Format source. */
662 voice->mChans[0].mDryParams.NFCtrlFilter.adjust(w0);
664 voice->mFlags |= VOICE_HAS_NFC;
667 ALfloat coeffs[MAX_AMBI_CHANNELS];
668 if(Device->mRenderMode != StereoPair)
669 CalcDirectionCoeffs({xpos, ypos, zpos}, Spread, coeffs);
670 else
672 /* Clamp Y, in case rounding errors caused it to end up outside
673 * of -1...+1.
675 const ALfloat ev{std::asin(clampf(ypos, -1.0f, 1.0f))};
676 /* Negate Z for right-handed coords with -Z in front. */
677 const ALfloat az{std::atan2(xpos, -zpos)};
679 /* A scalar of 1.5 for plain stereo results in +/-60 degrees
680 * being moved to +/-90 degrees for direct right and left
681 * speaker responses.
683 CalcAngleCoeffs(ScaleAzimuthFront(az, 1.5f), ev, Spread, coeffs);
686 /* NOTE: W needs to be scaled due to FuMa normalization. */
687 const ALfloat &scale0 = AmbiScale::FromFuMa[0];
688 ComputePanGains(&Device->Dry, coeffs, DryGain*scale0,
689 voice->mChans[0].mDryParams.Gains.Target);
690 for(ALuint i{0};i < NumSends;i++)
692 if(const ALeffectslot *Slot{SendSlots[i]})
693 ComputePanGains(&Slot->Wet, coeffs, WetGain[i]*scale0,
694 voice->mChans[0].mWetParams[i].Gains.Target);
697 else
699 if(Device->AvgSpeakerDist > 0.0f)
701 /* NOTE: The NFCtrlFilters were created with a w0 of 0, which
702 * is what we want for FOA input. The first channel may have
703 * been previously re-adjusted if panned, so reset it.
705 voice->mChans[0].mDryParams.NFCtrlFilter.adjust(0.0f);
707 voice->mFlags |= VOICE_HAS_NFC;
710 /* Local B-Format sources have their XYZ channels rotated according
711 * to the orientation.
713 /* AT then UP */
714 alu::Vector N{props->OrientAt[0], props->OrientAt[1], props->OrientAt[2], 0.0f};
715 N.normalize();
716 alu::Vector V{props->OrientUp[0], props->OrientUp[1], props->OrientUp[2], 0.0f};
717 V.normalize();
718 if(!props->HeadRelative)
720 N = Listener.Params.Matrix * N;
721 V = Listener.Params.Matrix * V;
723 /* Build and normalize right-vector */
724 alu::Vector U{aluCrossproduct(N, V)};
725 U.normalize();
727 /* Build a rotate + conversion matrix (FuMa -> ACN+N3D). NOTE: This
728 * matrix is transposed, for the inputs to align on the rows and
729 * outputs on the columns.
731 const ALfloat &wscale = AmbiScale::FromFuMa[0];
732 const ALfloat &yscale = AmbiScale::FromFuMa[1];
733 const ALfloat &zscale = AmbiScale::FromFuMa[2];
734 const ALfloat &xscale = AmbiScale::FromFuMa[3];
735 const ALfloat matrix[4][MAX_AMBI_CHANNELS]{
736 // ACN0 ACN1 ACN2 ACN3
737 { wscale, 0.0f, 0.0f, 0.0f }, // FuMa W
738 { 0.0f, -N[0]*xscale, N[1]*xscale, -N[2]*xscale }, // FuMa X
739 { 0.0f, U[0]*yscale, -U[1]*yscale, U[2]*yscale }, // FuMa Y
740 { 0.0f, -V[0]*zscale, V[1]*zscale, -V[2]*zscale } // FuMa Z
743 for(ALuint c{0};c < num_channels;c++)
745 ComputePanGains(&Device->Dry, matrix[c], DryGain,
746 voice->mChans[c].mDryParams.Gains.Target);
748 for(ALuint i{0};i < NumSends;i++)
750 if(const ALeffectslot *Slot{SendSlots[i]})
751 ComputePanGains(&Slot->Wet, matrix[c], WetGain[i],
752 voice->mChans[c].mWetParams[i].Gains.Target);
757 else if(DirectChannels)
759 /* Direct source channels always play local. Skip the virtual channels
760 * and write inputs to the matching real outputs.
762 voice->mDirect.Buffer = Device->RealOut.Buffer;
764 for(ALuint c{0};c < num_channels;c++)
766 const ALuint idx{GetChannelIdxByName(Device->RealOut, chans[c].channel)};
767 if(idx != INVALID_CHANNEL_INDEX)
768 voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain;
771 /* Auxiliary sends still use normal channel panning since they mix to
772 * B-Format, which can't channel-match.
774 for(ALuint c{0};c < num_channels;c++)
776 ALfloat coeffs[MAX_AMBI_CHANNELS];
777 CalcAngleCoeffs(chans[c].angle, chans[c].elevation, 0.0f, coeffs);
779 for(ALuint i{0};i < NumSends;i++)
781 if(const ALeffectslot *Slot{SendSlots[i]})
782 ComputePanGains(&Slot->Wet, coeffs, WetGain[i],
783 voice->mChans[c].mWetParams[i].Gains.Target);
787 else if(Device->mRenderMode == HrtfRender)
789 /* Full HRTF rendering. Skip the virtual channels and render to the
790 * real outputs.
792 voice->mDirect.Buffer = Device->RealOut.Buffer;
794 if(Distance > std::numeric_limits<float>::epsilon())
796 const ALfloat ev{std::asin(clampf(ypos, -1.0f, 1.0f))};
797 const ALfloat az{std::atan2(xpos, -zpos)};
799 /* Get the HRIR coefficients and delays just once, for the given
800 * source direction.
802 GetHrtfCoeffs(Device->mHrtf, ev, az, Distance, Spread,
803 voice->mChans[0].mDryParams.Hrtf.Target.Coeffs,
804 voice->mChans[0].mDryParams.Hrtf.Target.Delay);
805 voice->mChans[0].mDryParams.Hrtf.Target.Gain = DryGain * downmix_gain;
807 /* Remaining channels use the same results as the first. */
808 for(ALuint c{1};c < num_channels;c++)
810 /* Skip LFE */
811 if(chans[c].channel == LFE) continue;
812 voice->mChans[c].mDryParams.Hrtf.Target = voice->mChans[0].mDryParams.Hrtf.Target;
815 /* Calculate the directional coefficients once, which apply to all
816 * input channels of the source sends.
818 ALfloat coeffs[MAX_AMBI_CHANNELS];
819 CalcDirectionCoeffs({xpos, ypos, zpos}, Spread, coeffs);
821 for(ALuint c{0};c < num_channels;c++)
823 /* Skip LFE */
824 if(chans[c].channel == LFE)
825 continue;
826 for(ALuint i{0};i < NumSends;i++)
828 if(const ALeffectslot *Slot{SendSlots[i]})
829 ComputePanGains(&Slot->Wet, coeffs, WetGain[i] * downmix_gain,
830 voice->mChans[c].mWetParams[i].Gains.Target);
834 else
836 /* Local sources on HRTF play with each channel panned to its
837 * relative location around the listener, providing "virtual
838 * speaker" responses.
840 for(ALuint c{0};c < num_channels;c++)
842 /* Skip LFE */
843 if(chans[c].channel == LFE)
844 continue;
846 /* Get the HRIR coefficients and delays for this channel
847 * position.
849 GetHrtfCoeffs(Device->mHrtf, chans[c].elevation, chans[c].angle,
850 std::numeric_limits<float>::infinity(), Spread,
851 voice->mChans[c].mDryParams.Hrtf.Target.Coeffs,
852 voice->mChans[c].mDryParams.Hrtf.Target.Delay);
853 voice->mChans[c].mDryParams.Hrtf.Target.Gain = DryGain;
855 /* Normal panning for auxiliary sends. */
856 ALfloat coeffs[MAX_AMBI_CHANNELS];
857 CalcAngleCoeffs(chans[c].angle, chans[c].elevation, Spread, coeffs);
859 for(ALuint i{0};i < NumSends;i++)
861 if(const ALeffectslot *Slot{SendSlots[i]})
862 ComputePanGains(&Slot->Wet, coeffs, WetGain[i],
863 voice->mChans[c].mWetParams[i].Gains.Target);
868 voice->mFlags |= VOICE_HAS_HRTF;
870 else
872 /* Non-HRTF rendering. Use normal panning to the output. */
874 if(Distance > std::numeric_limits<float>::epsilon())
876 /* Calculate NFC filter coefficient if needed. */
877 if(Device->AvgSpeakerDist > 0.0f)
879 /* Clamp the distance for really close sources, to prevent
880 * excessive bass.
882 const ALfloat mdist{maxf(Distance, Device->AvgSpeakerDist/4.0f)};
883 const ALfloat w0{SPEEDOFSOUNDMETRESPERSEC / (mdist * Frequency)};
885 /* Adjust NFC filters. */
886 for(ALuint c{0};c < num_channels;c++)
887 voice->mChans[c].mDryParams.NFCtrlFilter.adjust(w0);
889 voice->mFlags |= VOICE_HAS_NFC;
892 /* Calculate the directional coefficients once, which apply to all
893 * input channels.
895 ALfloat coeffs[MAX_AMBI_CHANNELS];
896 if(Device->mRenderMode != StereoPair)
897 CalcDirectionCoeffs({xpos, ypos, zpos}, Spread, coeffs);
898 else
900 const ALfloat ev{std::asin(clampf(ypos, -1.0f, 1.0f))};
901 const ALfloat az{std::atan2(xpos, -zpos)};
902 CalcAngleCoeffs(ScaleAzimuthFront(az, 1.5f), ev, Spread, coeffs);
905 for(ALuint c{0};c < num_channels;c++)
907 /* Special-case LFE */
908 if(chans[c].channel == LFE)
910 if(Device->Dry.Buffer.data() == Device->RealOut.Buffer.data())
912 const ALuint idx{GetChannelIdxByName(Device->RealOut, chans[c].channel)};
913 if(idx != INVALID_CHANNEL_INDEX)
914 voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain;
916 continue;
919 ComputePanGains(&Device->Dry, coeffs, DryGain * downmix_gain,
920 voice->mChans[c].mDryParams.Gains.Target);
921 for(ALuint i{0};i < NumSends;i++)
923 if(const ALeffectslot *Slot{SendSlots[i]})
924 ComputePanGains(&Slot->Wet, coeffs, WetGain[i] * downmix_gain,
925 voice->mChans[c].mWetParams[i].Gains.Target);
929 else
931 if(Device->AvgSpeakerDist > 0.0f)
933 /* If the source distance is 0, set w0 to w1 to act as a pass-
934 * through. We still want to pass the signal through the
935 * filters so they keep an appropriate history, in case the
936 * source moves away from the listener.
938 const ALfloat w0{SPEEDOFSOUNDMETRESPERSEC / (Device->AvgSpeakerDist * Frequency)};
940 for(ALuint c{0};c < num_channels;c++)
941 voice->mChans[c].mDryParams.NFCtrlFilter.adjust(w0);
943 voice->mFlags |= VOICE_HAS_NFC;
946 for(ALuint c{0};c < num_channels;c++)
948 /* Special-case LFE */
949 if(chans[c].channel == LFE)
951 if(Device->Dry.Buffer.data() == Device->RealOut.Buffer.data())
953 const ALuint idx{GetChannelIdxByName(Device->RealOut, chans[c].channel)};
954 if(idx != INVALID_CHANNEL_INDEX)
955 voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain;
957 continue;
960 ALfloat coeffs[MAX_AMBI_CHANNELS];
961 CalcAngleCoeffs(
962 (Device->mRenderMode==StereoPair) ? ScaleAzimuthFront(chans[c].angle, 3.0f)
963 : chans[c].angle,
964 chans[c].elevation, Spread, coeffs
967 ComputePanGains(&Device->Dry, coeffs, DryGain,
968 voice->mChans[c].mDryParams.Gains.Target);
969 for(ALuint i{0};i < NumSends;i++)
971 if(const ALeffectslot *Slot{SendSlots[i]})
972 ComputePanGains(&Slot->Wet, coeffs, WetGain[i],
973 voice->mChans[c].mWetParams[i].Gains.Target);
980 const ALfloat hfScale{props->Direct.HFReference / Frequency};
981 const ALfloat lfScale{props->Direct.LFReference / Frequency};
982 const ALfloat gainHF{maxf(DryGainHF, 0.001f)}; /* Limit -60dB */
983 const ALfloat gainLF{maxf(DryGainLF, 0.001f)};
985 voice->mDirect.FilterType = AF_None;
986 if(gainHF != 1.0f) voice->mDirect.FilterType |= AF_LowPass;
987 if(gainLF != 1.0f) voice->mDirect.FilterType |= AF_HighPass;
988 auto &lowpass = voice->mChans[0].mDryParams.LowPass;
989 auto &highpass = voice->mChans[0].mDryParams.HighPass;
990 lowpass.setParams(BiquadType::HighShelf, gainHF, hfScale,
991 lowpass.rcpQFromSlope(gainHF, 1.0f));
992 highpass.setParams(BiquadType::LowShelf, gainLF, lfScale,
993 highpass.rcpQFromSlope(gainLF, 1.0f));
994 for(ALuint c{1};c < num_channels;c++)
996 voice->mChans[c].mDryParams.LowPass.copyParamsFrom(lowpass);
997 voice->mChans[c].mDryParams.HighPass.copyParamsFrom(highpass);
1000 for(ALuint i{0};i < NumSends;i++)
1002 const ALfloat hfScale{props->Send[i].HFReference / Frequency};
1003 const ALfloat lfScale{props->Send[i].LFReference / Frequency};
1004 const ALfloat gainHF{maxf(WetGainHF[i], 0.001f)};
1005 const ALfloat gainLF{maxf(WetGainLF[i], 0.001f)};
1007 voice->mSend[i].FilterType = AF_None;
1008 if(gainHF != 1.0f) voice->mSend[i].FilterType |= AF_LowPass;
1009 if(gainLF != 1.0f) voice->mSend[i].FilterType |= AF_HighPass;
1011 auto &lowpass = voice->mChans[0].mWetParams[i].LowPass;
1012 auto &highpass = voice->mChans[0].mWetParams[i].HighPass;
1013 lowpass.setParams(BiquadType::HighShelf, gainHF, hfScale,
1014 lowpass.rcpQFromSlope(gainHF, 1.0f));
1015 highpass.setParams(BiquadType::LowShelf, gainLF, lfScale,
1016 highpass.rcpQFromSlope(gainLF, 1.0f));
1017 for(ALuint c{1};c < num_channels;c++)
1019 voice->mChans[c].mWetParams[i].LowPass.copyParamsFrom(lowpass);
1020 voice->mChans[c].mWetParams[i].HighPass.copyParamsFrom(highpass);
1025 void CalcNonAttnSourceParams(ALvoice *voice, const ALvoicePropsBase *props, const ALCcontext *ALContext)
1027 const ALCdevice *Device{ALContext->mDevice.get()};
1028 ALeffectslot *SendSlots[MAX_SENDS];
1030 voice->mDirect.Buffer = Device->Dry.Buffer;
1031 for(ALuint i{0};i < Device->NumAuxSends;i++)
1033 SendSlots[i] = props->Send[i].Slot;
1034 if(!SendSlots[i] && i == 0)
1035 SendSlots[i] = ALContext->mDefaultSlot.get();
1036 if(!SendSlots[i] || SendSlots[i]->Params.EffectType == AL_EFFECT_NULL)
1038 SendSlots[i] = nullptr;
1039 voice->mSend[i].Buffer = {};
1041 else
1042 voice->mSend[i].Buffer = SendSlots[i]->Wet.Buffer;
1045 /* Calculate the stepping value */
1046 const auto Pitch = static_cast<ALfloat>(voice->mFrequency) /
1047 static_cast<ALfloat>(Device->Frequency) * props->Pitch;
1048 if(Pitch > float{MAX_PITCH})
1049 voice->mStep = MAX_PITCH<<FRACTIONBITS;
1050 else
1051 voice->mStep = maxu(fastf2u(Pitch * FRACTIONONE), 1);
1052 voice->mResampler = PrepareResampler(props->mResampler, voice->mStep, &voice->mResampleState);
1054 /* Calculate gains */
1055 const ALlistener &Listener = ALContext->mListener;
1056 ALfloat DryGain{clampf(props->Gain, props->MinGain, props->MaxGain)};
1057 DryGain *= props->Direct.Gain * Listener.Params.Gain;
1058 DryGain = minf(DryGain, GAIN_MIX_MAX);
1059 ALfloat DryGainHF{props->Direct.GainHF};
1060 ALfloat DryGainLF{props->Direct.GainLF};
1061 ALfloat WetGain[MAX_SENDS], WetGainHF[MAX_SENDS], WetGainLF[MAX_SENDS];
1062 for(ALuint i{0};i < Device->NumAuxSends;i++)
1064 WetGain[i] = clampf(props->Gain, props->MinGain, props->MaxGain);
1065 WetGain[i] *= props->Send[i].Gain * Listener.Params.Gain;
1066 WetGain[i] = minf(WetGain[i], GAIN_MIX_MAX);
1067 WetGainHF[i] = props->Send[i].GainHF;
1068 WetGainLF[i] = props->Send[i].GainLF;
1071 CalcPanningAndFilters(voice, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, DryGain, DryGainHF, DryGainLF,
1072 WetGain, WetGainLF, WetGainHF, SendSlots, props, Listener, Device);
1075 void CalcAttnSourceParams(ALvoice *voice, const ALvoicePropsBase *props, const ALCcontext *ALContext)
1077 const ALCdevice *Device{ALContext->mDevice.get()};
1078 const ALuint NumSends{Device->NumAuxSends};
1079 const ALlistener &Listener = ALContext->mListener;
1081 /* Set mixing buffers and get send parameters. */
1082 voice->mDirect.Buffer = Device->Dry.Buffer;
1083 ALeffectslot *SendSlots[MAX_SENDS];
1084 ALfloat RoomRolloff[MAX_SENDS];
1085 ALfloat DecayDistance[MAX_SENDS];
1086 ALfloat DecayLFDistance[MAX_SENDS];
1087 ALfloat DecayHFDistance[MAX_SENDS];
1088 for(ALuint i{0};i < NumSends;i++)
1090 SendSlots[i] = props->Send[i].Slot;
1091 if(!SendSlots[i] && i == 0)
1092 SendSlots[i] = ALContext->mDefaultSlot.get();
1093 if(!SendSlots[i] || SendSlots[i]->Params.EffectType == AL_EFFECT_NULL)
1095 SendSlots[i] = nullptr;
1096 RoomRolloff[i] = 0.0f;
1097 DecayDistance[i] = 0.0f;
1098 DecayLFDistance[i] = 0.0f;
1099 DecayHFDistance[i] = 0.0f;
1101 else if(SendSlots[i]->Params.AuxSendAuto)
1103 RoomRolloff[i] = SendSlots[i]->Params.RoomRolloff + props->RoomRolloffFactor;
1104 /* Calculate the distances to where this effect's decay reaches
1105 * -60dB.
1107 DecayDistance[i] = SendSlots[i]->Params.DecayTime * SPEEDOFSOUNDMETRESPERSEC;
1108 DecayLFDistance[i] = DecayDistance[i] * SendSlots[i]->Params.DecayLFRatio;
1109 DecayHFDistance[i] = DecayDistance[i] * SendSlots[i]->Params.DecayHFRatio;
1110 if(SendSlots[i]->Params.DecayHFLimit)
1112 ALfloat airAbsorption{SendSlots[i]->Params.AirAbsorptionGainHF};
1113 if(airAbsorption < 1.0f)
1115 /* Calculate the distance to where this effect's air
1116 * absorption reaches -60dB, and limit the effect's HF
1117 * decay distance (so it doesn't take any longer to decay
1118 * than the air would allow).
1120 ALfloat absorb_dist{std::log10(REVERB_DECAY_GAIN) / std::log10(airAbsorption)};
1121 DecayHFDistance[i] = minf(absorb_dist, DecayHFDistance[i]);
1125 else
1127 /* If the slot's auxiliary send auto is off, the data sent to the
1128 * effect slot is the same as the dry path, sans filter effects */
1129 RoomRolloff[i] = props->RolloffFactor;
1130 DecayDistance[i] = 0.0f;
1131 DecayLFDistance[i] = 0.0f;
1132 DecayHFDistance[i] = 0.0f;
1135 if(!SendSlots[i])
1136 voice->mSend[i].Buffer = {};
1137 else
1138 voice->mSend[i].Buffer = SendSlots[i]->Wet.Buffer;
1141 /* Transform source to listener space (convert to head relative) */
1142 alu::Vector Position{props->Position[0], props->Position[1], props->Position[2], 1.0f};
1143 alu::Vector Velocity{props->Velocity[0], props->Velocity[1], props->Velocity[2], 0.0f};
1144 alu::Vector Direction{props->Direction[0], props->Direction[1], props->Direction[2], 0.0f};
1145 if(props->HeadRelative == AL_FALSE)
1147 /* Transform source vectors */
1148 Position = Listener.Params.Matrix * Position;
1149 Velocity = Listener.Params.Matrix * Velocity;
1150 Direction = Listener.Params.Matrix * Direction;
1152 else
1154 /* Offset the source velocity to be relative of the listener velocity */
1155 Velocity += Listener.Params.Velocity;
1158 const bool directional{Direction.normalize() > 0.0f};
1159 alu::Vector ToSource{Position[0], Position[1], Position[2], 0.0f};
1160 const ALfloat Distance{ToSource.normalize()};
1162 /* Initial source gain */
1163 ALfloat DryGain{props->Gain};
1164 ALfloat DryGainHF{1.0f};
1165 ALfloat DryGainLF{1.0f};
1166 ALfloat WetGain[MAX_SENDS], WetGainHF[MAX_SENDS], WetGainLF[MAX_SENDS];
1167 for(ALuint i{0};i < NumSends;i++)
1169 WetGain[i] = props->Gain;
1170 WetGainHF[i] = 1.0f;
1171 WetGainLF[i] = 1.0f;
1174 /* Calculate distance attenuation */
1175 ALfloat ClampedDist{Distance};
1177 switch(Listener.Params.SourceDistanceModel ?
1178 props->mDistanceModel : Listener.Params.mDistanceModel)
1180 case DistanceModel::InverseClamped:
1181 ClampedDist = clampf(ClampedDist, props->RefDistance, props->MaxDistance);
1182 if(props->MaxDistance < props->RefDistance) break;
1183 /*fall-through*/
1184 case DistanceModel::Inverse:
1185 if(!(props->RefDistance > 0.0f))
1186 ClampedDist = props->RefDistance;
1187 else
1189 ALfloat dist = lerp(props->RefDistance, ClampedDist, props->RolloffFactor);
1190 if(dist > 0.0f) DryGain *= props->RefDistance / dist;
1191 for(ALuint i{0};i < NumSends;i++)
1193 dist = lerp(props->RefDistance, ClampedDist, RoomRolloff[i]);
1194 if(dist > 0.0f) WetGain[i] *= props->RefDistance / dist;
1197 break;
1199 case DistanceModel::LinearClamped:
1200 ClampedDist = clampf(ClampedDist, props->RefDistance, props->MaxDistance);
1201 if(props->MaxDistance < props->RefDistance) break;
1202 /*fall-through*/
1203 case DistanceModel::Linear:
1204 if(!(props->MaxDistance != props->RefDistance))
1205 ClampedDist = props->RefDistance;
1206 else
1208 ALfloat attn = props->RolloffFactor * (ClampedDist-props->RefDistance) /
1209 (props->MaxDistance-props->RefDistance);
1210 DryGain *= maxf(1.0f - attn, 0.0f);
1211 for(ALuint i{0};i < NumSends;i++)
1213 attn = RoomRolloff[i] * (ClampedDist-props->RefDistance) /
1214 (props->MaxDistance-props->RefDistance);
1215 WetGain[i] *= maxf(1.0f - attn, 0.0f);
1218 break;
1220 case DistanceModel::ExponentClamped:
1221 ClampedDist = clampf(ClampedDist, props->RefDistance, props->MaxDistance);
1222 if(props->MaxDistance < props->RefDistance) break;
1223 /*fall-through*/
1224 case DistanceModel::Exponent:
1225 if(!(ClampedDist > 0.0f && props->RefDistance > 0.0f))
1226 ClampedDist = props->RefDistance;
1227 else
1229 DryGain *= std::pow(ClampedDist/props->RefDistance, -props->RolloffFactor);
1230 for(ALuint i{0};i < NumSends;i++)
1231 WetGain[i] *= std::pow(ClampedDist/props->RefDistance, -RoomRolloff[i]);
1233 break;
1235 case DistanceModel::Disable:
1236 ClampedDist = props->RefDistance;
1237 break;
1240 /* Calculate directional soundcones */
1241 if(directional && props->InnerAngle < 360.0f)
1243 const ALfloat Angle{Rad2Deg(std::acos(-aluDotproduct(Direction, ToSource)) *
1244 ConeScale * 2.0f)};
1246 ALfloat ConeVolume, ConeHF;
1247 if(!(Angle > props->InnerAngle))
1249 ConeVolume = 1.0f;
1250 ConeHF = 1.0f;
1252 else if(Angle < props->OuterAngle)
1254 ALfloat scale = ( Angle-props->InnerAngle) /
1255 (props->OuterAngle-props->InnerAngle);
1256 ConeVolume = lerp(1.0f, props->OuterGain, scale);
1257 ConeHF = lerp(1.0f, props->OuterGainHF, scale);
1259 else
1261 ConeVolume = props->OuterGain;
1262 ConeHF = props->OuterGainHF;
1265 DryGain *= ConeVolume;
1266 if(props->DryGainHFAuto)
1267 DryGainHF *= ConeHF;
1268 if(props->WetGainAuto)
1269 std::transform(std::begin(WetGain), std::begin(WetGain)+NumSends, std::begin(WetGain),
1270 [ConeVolume](ALfloat gain) noexcept -> ALfloat { return gain * ConeVolume; }
1272 if(props->WetGainHFAuto)
1273 std::transform(std::begin(WetGainHF), std::begin(WetGainHF)+NumSends,
1274 std::begin(WetGainHF),
1275 [ConeHF](ALfloat gain) noexcept -> ALfloat { return gain * ConeHF; }
1279 /* Apply gain and frequency filters */
1280 DryGain = clampf(DryGain, props->MinGain, props->MaxGain);
1281 DryGain = minf(DryGain*props->Direct.Gain*Listener.Params.Gain, GAIN_MIX_MAX);
1282 DryGainHF *= props->Direct.GainHF;
1283 DryGainLF *= props->Direct.GainLF;
1284 for(ALuint i{0};i < NumSends;i++)
1286 WetGain[i] = clampf(WetGain[i], props->MinGain, props->MaxGain);
1287 WetGain[i] = minf(WetGain[i]*props->Send[i].Gain*Listener.Params.Gain, GAIN_MIX_MAX);
1288 WetGainHF[i] *= props->Send[i].GainHF;
1289 WetGainLF[i] *= props->Send[i].GainLF;
1292 /* Distance-based air absorption and initial send decay. */
1293 if(ClampedDist > props->RefDistance && props->RolloffFactor > 0.0f)
1295 ALfloat meters_base{(ClampedDist-props->RefDistance) * props->RolloffFactor *
1296 Listener.Params.MetersPerUnit};
1297 if(props->AirAbsorptionFactor > 0.0f)
1299 ALfloat hfattn{std::pow(AIRABSORBGAINHF, meters_base * props->AirAbsorptionFactor)};
1300 DryGainHF *= hfattn;
1301 std::transform(std::begin(WetGainHF), std::begin(WetGainHF)+NumSends,
1302 std::begin(WetGainHF),
1303 [hfattn](ALfloat gain) noexcept -> ALfloat { return gain * hfattn; }
1307 if(props->WetGainAuto)
1309 /* Apply a decay-time transformation to the wet path, based on the
1310 * source distance in meters. The initial decay of the reverb
1311 * effect is calculated and applied to the wet path.
1313 for(ALuint i{0};i < NumSends;i++)
1315 if(!(DecayDistance[i] > 0.0f))
1316 continue;
1318 const ALfloat gain{std::pow(REVERB_DECAY_GAIN, meters_base/DecayDistance[i])};
1319 WetGain[i] *= gain;
1320 /* Yes, the wet path's air absorption is applied with
1321 * WetGainAuto on, rather than WetGainHFAuto.
1323 if(gain > 0.0f)
1325 ALfloat gainhf{std::pow(REVERB_DECAY_GAIN, meters_base/DecayHFDistance[i])};
1326 WetGainHF[i] *= minf(gainhf / gain, 1.0f);
1327 ALfloat gainlf{std::pow(REVERB_DECAY_GAIN, meters_base/DecayLFDistance[i])};
1328 WetGainLF[i] *= minf(gainlf / gain, 1.0f);
1335 /* Initial source pitch */
1336 ALfloat Pitch{props->Pitch};
1338 /* Calculate velocity-based doppler effect */
1339 ALfloat DopplerFactor{props->DopplerFactor * Listener.Params.DopplerFactor};
1340 if(DopplerFactor > 0.0f)
1342 const alu::Vector &lvelocity = Listener.Params.Velocity;
1343 ALfloat vss{aluDotproduct(Velocity, ToSource) * -DopplerFactor};
1344 ALfloat vls{aluDotproduct(lvelocity, ToSource) * -DopplerFactor};
1346 const ALfloat SpeedOfSound{Listener.Params.SpeedOfSound};
1347 if(!(vls < SpeedOfSound))
1349 /* Listener moving away from the source at the speed of sound.
1350 * Sound waves can't catch it.
1352 Pitch = 0.0f;
1354 else if(!(vss < SpeedOfSound))
1356 /* Source moving toward the listener at the speed of sound. Sound
1357 * waves bunch up to extreme frequencies.
1359 Pitch = std::numeric_limits<float>::infinity();
1361 else
1363 /* Source and listener movement is nominal. Calculate the proper
1364 * doppler shift.
1366 Pitch *= (SpeedOfSound-vls) / (SpeedOfSound-vss);
1370 /* Adjust pitch based on the buffer and output frequencies, and calculate
1371 * fixed-point stepping value.
1373 Pitch *= static_cast<ALfloat>(voice->mFrequency)/static_cast<ALfloat>(Device->Frequency);
1374 if(Pitch > float{MAX_PITCH})
1375 voice->mStep = MAX_PITCH<<FRACTIONBITS;
1376 else
1377 voice->mStep = maxu(fastf2u(Pitch * FRACTIONONE), 1);
1378 voice->mResampler = PrepareResampler(props->mResampler, voice->mStep, &voice->mResampleState);
1380 ALfloat spread{0.0f};
1381 if(props->Radius > Distance)
1382 spread = al::MathDefs<float>::Tau() - Distance/props->Radius*al::MathDefs<float>::Pi();
1383 else if(Distance > 0.0f)
1384 spread = std::asin(props->Radius/Distance) * 2.0f;
1386 CalcPanningAndFilters(voice, ToSource[0], ToSource[1], ToSource[2]*ZScale,
1387 Distance*Listener.Params.MetersPerUnit, spread, DryGain, DryGainHF, DryGainLF, WetGain,
1388 WetGainLF, WetGainHF, SendSlots, props, Listener, Device);
1391 void CalcSourceParams(ALvoice *voice, ALCcontext *context, bool force)
1393 ALvoiceProps *props{voice->mUpdate.exchange(nullptr, std::memory_order_acq_rel)};
1394 if(!props && !force) return;
1396 if(props)
1398 voice->mProps = *props;
1400 AtomicReplaceHead(context->mFreeVoiceProps, props);
1403 if((voice->mProps.mSpatializeMode == SpatializeAuto && voice->mFmtChannels == FmtMono) ||
1404 voice->mProps.mSpatializeMode == SpatializeOn)
1405 CalcAttnSourceParams(voice, &voice->mProps, context);
1406 else
1407 CalcNonAttnSourceParams(voice, &voice->mProps, context);
1411 void ProcessParamUpdates(ALCcontext *ctx, const ALeffectslotArray &slots,
1412 const al::span<ALvoice> voices)
1414 IncrementRef(ctx->mUpdateCount);
1415 if LIKELY(!ctx->mHoldUpdates.load(std::memory_order_acquire))
1417 bool force{CalcContextParams(ctx)};
1418 force |= CalcListenerParams(ctx);
1419 force = std::accumulate(slots.begin(), slots.end(), force,
1420 [ctx](const bool f, ALeffectslot *slot) -> bool
1421 { return CalcEffectSlotParams(slot, ctx) | f; }
1424 auto calc_params = [ctx,force](ALvoice &voice) -> void
1426 if(voice.mSourceID.load(std::memory_order_acquire) != 0)
1427 CalcSourceParams(&voice, ctx, force);
1429 std::for_each(voices.begin(), voices.end(), calc_params);
1431 IncrementRef(ctx->mUpdateCount);
1434 void ProcessContext(ALCcontext *ctx, const ALuint SamplesToDo)
1436 ASSUME(SamplesToDo > 0);
1438 const ALeffectslotArray &auxslots = *ctx->mActiveAuxSlots.load(std::memory_order_acquire);
1439 const al::span<ALvoice> voices{ctx->mVoices.data(), ctx->mVoices.size()};
1441 /* Process pending propery updates for objects on the context. */
1442 ProcessParamUpdates(ctx, auxslots, voices);
1444 /* Clear auxiliary effect slot mixing buffers. */
1445 std::for_each(auxslots.begin(), auxslots.end(),
1446 [SamplesToDo](ALeffectslot *slot) -> void
1448 for(auto &buffer : slot->MixBuffer)
1449 std::fill_n(buffer.begin(), SamplesToDo, 0.0f);
1453 /* Process voices that have a playing source. */
1454 std::for_each(voices.begin(), voices.end(),
1455 [SamplesToDo,ctx](ALvoice &voice) -> void
1457 const ALvoice::State vstate{voice.mPlayState.load(std::memory_order_acquire)};
1458 if(vstate != ALvoice::Stopped) voice.mix(vstate, ctx, SamplesToDo);
1462 /* Process effects. */
1463 if(auxslots.empty()) return;
1464 auto slots = auxslots.data();
1465 auto slots_end = slots + auxslots.size();
1467 /* First sort the slots into scratch storage, so that effects come before
1468 * their effect target (or their targets' target).
1470 auto sorted_slots = const_cast<ALeffectslot**>(slots_end);
1471 auto sorted_slots_end = sorted_slots;
1472 auto in_chain = [](const ALeffectslot *slot1, const ALeffectslot *slot2) noexcept -> bool
1474 while((slot1=slot1->Params.Target) != nullptr) {
1475 if(slot1 == slot2) return true;
1477 return false;
1480 *sorted_slots_end = *slots;
1481 ++sorted_slots_end;
1482 while(++slots != slots_end)
1484 /* If this effect slot targets an effect slot already in the list (i.e.
1485 * slots outputs to something in sorted_slots), directly or indirectly,
1486 * insert it prior to that element.
1488 auto checker = sorted_slots;
1489 do {
1490 if(in_chain(*slots, *checker)) break;
1491 } while(++checker != sorted_slots_end);
1493 checker = std::move_backward(checker, sorted_slots_end, sorted_slots_end+1);
1494 *--checker = *slots;
1495 ++sorted_slots_end;
1498 std::for_each(sorted_slots, sorted_slots_end,
1499 [SamplesToDo](const ALeffectslot *slot) -> void
1501 EffectState *state{slot->Params.mEffectState};
1502 state->process(SamplesToDo, slot->Wet.Buffer, state->mOutTarget);
1508 void ApplyStablizer(FrontStablizer *Stablizer, const al::span<FloatBufferLine> Buffer,
1509 const ALuint lidx, const ALuint ridx, const ALuint cidx, const ALuint SamplesToDo)
1511 ASSUME(SamplesToDo > 0);
1513 /* Apply a delay to all channels, except the front-left and front-right, so
1514 * they maintain correct timing.
1516 const size_t NumChannels{Buffer.size()};
1517 for(size_t i{0u};i < NumChannels;i++)
1519 if(i == lidx || i == ridx)
1520 continue;
1522 auto &DelayBuf = Stablizer->DelayBuf[i];
1523 auto buffer_end = Buffer[i].begin() + SamplesToDo;
1524 if LIKELY(SamplesToDo >= ALuint{FrontStablizer::DelayLength})
1526 auto delay_end = std::rotate(Buffer[i].begin(),
1527 buffer_end - FrontStablizer::DelayLength, buffer_end);
1528 std::swap_ranges(Buffer[i].begin(), delay_end, std::begin(DelayBuf));
1530 else
1532 auto delay_start = std::swap_ranges(Buffer[i].begin(), buffer_end,
1533 std::begin(DelayBuf));
1534 std::rotate(std::begin(DelayBuf), delay_start, std::end(DelayBuf));
1538 ALfloat (&lsplit)[2][BUFFERSIZE] = Stablizer->LSplit;
1539 ALfloat (&rsplit)[2][BUFFERSIZE] = Stablizer->RSplit;
1540 auto &tmpbuf = Stablizer->TempBuf;
1542 /* This applies the band-splitter, preserving phase at the cost of some
1543 * delay. The shorter the delay, the more error seeps into the result.
1545 auto apply_splitter = [&tmpbuf,SamplesToDo](const FloatBufferLine &InBuf,
1546 ALfloat (&DelayBuf)[FrontStablizer::DelayLength], BandSplitter &Filter,
1547 ALfloat (&splitbuf)[2][BUFFERSIZE]) -> void
1549 /* Combine the delayed samples and the input samples into the temp
1550 * buffer, in reverse. Then copy the final samples back into the delay
1551 * buffer for next time. Note that the delay buffer's samples are
1552 * stored backwards here.
1554 auto tmpbuf_end = std::begin(tmpbuf) + SamplesToDo;
1555 std::copy_n(std::begin(DelayBuf), FrontStablizer::DelayLength, tmpbuf_end);
1556 std::reverse_copy(InBuf.begin(), InBuf.begin()+SamplesToDo, std::begin(tmpbuf));
1557 std::copy_n(std::begin(tmpbuf), FrontStablizer::DelayLength, std::begin(DelayBuf));
1559 /* Apply an all-pass on the reversed signal, then reverse the samples
1560 * to get the forward signal with a reversed phase shift.
1562 Filter.applyAllpass(tmpbuf, SamplesToDo+FrontStablizer::DelayLength);
1563 std::reverse(std::begin(tmpbuf), tmpbuf_end+FrontStablizer::DelayLength);
1565 /* Now apply the band-splitter, combining its phase shift with the
1566 * reversed phase shift, restoring the original phase on the split
1567 * signal.
1569 Filter.process(splitbuf[1], splitbuf[0], tmpbuf, SamplesToDo);
1571 apply_splitter(Buffer[lidx], Stablizer->DelayBuf[lidx], Stablizer->LFilter, lsplit);
1572 apply_splitter(Buffer[ridx], Stablizer->DelayBuf[ridx], Stablizer->RFilter, rsplit);
1574 for(ALuint i{0};i < SamplesToDo;i++)
1576 ALfloat lfsum{lsplit[0][i] + rsplit[0][i]};
1577 ALfloat hfsum{lsplit[1][i] + rsplit[1][i]};
1578 ALfloat s{lsplit[0][i] + lsplit[1][i] - rsplit[0][i] - rsplit[1][i]};
1580 /* This pans the separate low- and high-frequency sums between being on
1581 * the center channel and the left/right channels. The low-frequency
1582 * sum is 1/3rd toward center (2/3rds on left/right) and the high-
1583 * frequency sum is 1/4th toward center (3/4ths on left/right). These
1584 * values can be tweaked.
1586 ALfloat m{lfsum*std::cos(1.0f/3.0f * (al::MathDefs<float>::Pi()*0.5f)) +
1587 hfsum*std::cos(1.0f/4.0f * (al::MathDefs<float>::Pi()*0.5f))};
1588 ALfloat c{lfsum*std::sin(1.0f/3.0f * (al::MathDefs<float>::Pi()*0.5f)) +
1589 hfsum*std::sin(1.0f/4.0f * (al::MathDefs<float>::Pi()*0.5f))};
1591 /* The generated center channel signal adds to the existing signal,
1592 * while the modified left and right channels replace.
1594 Buffer[lidx][i] = (m + s) * 0.5f;
1595 Buffer[ridx][i] = (m - s) * 0.5f;
1596 Buffer[cidx][i] += c * 0.5f;
1600 void ApplyDistanceComp(const al::span<FloatBufferLine> Samples, const ALuint SamplesToDo,
1601 const DistanceComp::DistData *distcomp)
1603 ASSUME(SamplesToDo > 0);
1605 for(auto &chanbuffer : Samples)
1607 const ALfloat gain{distcomp->Gain};
1608 const ALuint base{distcomp->Length};
1609 ALfloat *distbuf{al::assume_aligned<16>(distcomp->Buffer)};
1610 ++distcomp;
1612 if(base < 1)
1613 continue;
1615 ALfloat *inout{al::assume_aligned<16>(chanbuffer.data())};
1616 auto inout_end = inout + SamplesToDo;
1617 if LIKELY(SamplesToDo >= base)
1619 auto delay_end = std::rotate(inout, inout_end - base, inout_end);
1620 std::swap_ranges(inout, delay_end, distbuf);
1622 else
1624 auto delay_start = std::swap_ranges(inout, inout_end, distbuf);
1625 std::rotate(distbuf, delay_start, distbuf + base);
1627 std::transform(inout, inout_end, inout, std::bind(std::multiplies<float>{}, _1, gain));
1631 void ApplyDither(const al::span<FloatBufferLine> Samples, ALuint *dither_seed,
1632 const ALfloat quant_scale, const ALuint SamplesToDo)
1634 /* Dithering. Generate whitenoise (uniform distribution of random values
1635 * between -1 and +1) and add it to the sample values, after scaling up to
1636 * the desired quantization depth amd before rounding.
1638 const ALfloat invscale{1.0f / quant_scale};
1639 ALuint seed{*dither_seed};
1640 auto dither_channel = [&seed,invscale,quant_scale,SamplesToDo](FloatBufferLine &input) -> void
1642 ASSUME(SamplesToDo > 0);
1643 auto dither_sample = [&seed,invscale,quant_scale](const ALfloat sample) noexcept -> ALfloat
1645 ALfloat val{sample * quant_scale};
1646 ALuint rng0{dither_rng(&seed)};
1647 ALuint rng1{dither_rng(&seed)};
1648 val += static_cast<ALfloat>(rng0*(1.0/UINT_MAX) - rng1*(1.0/UINT_MAX));
1649 return fast_roundf(val) * invscale;
1651 std::transform(input.begin(), input.begin()+SamplesToDo, input.begin(), dither_sample);
1653 std::for_each(Samples.begin(), Samples.end(), dither_channel);
1654 *dither_seed = seed;
1658 /* Base template left undefined. Should be marked =delete, but Clang 3.8.1
1659 * chokes on that given the inline specializations.
1661 template<typename T>
1662 inline T SampleConv(ALfloat) noexcept;
1664 template<> inline ALfloat SampleConv(ALfloat val) noexcept
1665 { return val; }
1666 template<> inline ALint SampleConv(ALfloat val) noexcept
1668 /* Floats have a 23-bit mantissa, plus an implied 1 bit and a sign bit.
1669 * This means a normalized float has at most 25 bits of signed precision.
1670 * When scaling and clamping for a signed 32-bit integer, these following
1671 * values are the best a float can give.
1673 return fastf2i(clampf(val*2147483648.0f, -2147483648.0f, 2147483520.0f));
1675 template<> inline ALshort SampleConv(ALfloat val) noexcept
1676 { return static_cast<ALshort>(fastf2i(clampf(val*32768.0f, -32768.0f, 32767.0f))); }
1677 template<> inline ALbyte SampleConv(ALfloat val) noexcept
1678 { return static_cast<ALbyte>(fastf2i(clampf(val*128.0f, -128.0f, 127.0f))); }
1680 /* Define unsigned output variations. */
1681 template<> inline ALuint SampleConv(ALfloat val) noexcept
1682 { return static_cast<ALuint>(SampleConv<ALint>(val)) + 2147483648u; }
1683 template<> inline ALushort SampleConv(ALfloat val) noexcept
1684 { return static_cast<ALushort>(SampleConv<ALshort>(val) + 32768); }
1685 template<> inline ALubyte SampleConv(ALfloat val) noexcept
1686 { return static_cast<ALubyte>(SampleConv<ALbyte>(val) + 128); }
1688 template<DevFmtType T>
1689 void Write(const al::span<const FloatBufferLine> InBuffer, ALvoid *OutBuffer, const size_t Offset,
1690 const ALuint SamplesToDo)
1692 using SampleType = typename DevFmtTypeTraits<T>::Type;
1694 const size_t numchans{InBuffer.size()};
1695 ASSUME(numchans > 0);
1697 SampleType *outbase = static_cast<SampleType*>(OutBuffer) + Offset*numchans;
1698 auto conv_channel = [&outbase,SamplesToDo,numchans](const FloatBufferLine &inbuf) -> void
1700 ASSUME(SamplesToDo > 0);
1701 SampleType *out{outbase++};
1702 auto conv_sample = [numchans,&out](const ALfloat s) noexcept -> void
1704 *out = SampleConv<SampleType>(s);
1705 out += numchans;
1707 std::for_each(inbuf.begin(), inbuf.begin()+SamplesToDo, conv_sample);
1709 std::for_each(InBuffer.cbegin(), InBuffer.cend(), conv_channel);
1712 } // namespace
1714 void aluMixData(ALCdevice *device, ALvoid *OutBuffer, const ALuint NumSamples)
1716 FPUCtl mixer_mode{};
1717 for(ALuint SamplesDone{0u};SamplesDone < NumSamples;)
1719 const ALuint SamplesToDo{minu(NumSamples-SamplesDone, BUFFERSIZE)};
1721 /* Clear main mixing buffers. */
1722 std::for_each(device->MixBuffer.begin(), device->MixBuffer.end(),
1723 [SamplesToDo](std::array<ALfloat,BUFFERSIZE> &buffer) -> void
1724 { std::fill_n(buffer.begin(), SamplesToDo, 0.0f); }
1727 /* Increment the mix count at the start (lsb should now be 1). */
1728 IncrementRef(device->MixCount);
1730 /* For each context on this device, process and mix its sources and
1731 * effects.
1733 for(ALCcontext *ctx : *device->mContexts.load(std::memory_order_acquire))
1734 ProcessContext(ctx, SamplesToDo);
1736 /* Increment the clock time. Every second's worth of samples is
1737 * converted and added to clock base so that large sample counts don't
1738 * overflow during conversion. This also guarantees a stable
1739 * conversion.
1741 device->SamplesDone += SamplesToDo;
1742 device->ClockBase += std::chrono::seconds{device->SamplesDone / device->Frequency};
1743 device->SamplesDone %= device->Frequency;
1745 /* Increment the mix count at the end (lsb should now be 0). */
1746 IncrementRef(device->MixCount);
1748 /* Apply any needed post-process for finalizing the Dry mix to the
1749 * RealOut (Ambisonic decode, UHJ encode, etc).
1751 device->postProcess(SamplesToDo);
1753 const al::span<FloatBufferLine> RealOut{device->RealOut.Buffer};
1755 /* Apply front image stablization for surround sound, if applicable. */
1756 if(device->Stablizer)
1758 const ALuint lidx{GetChannelIdxByName(device->RealOut, FrontLeft)};
1759 const ALuint ridx{GetChannelIdxByName(device->RealOut, FrontRight)};
1760 const ALuint cidx{GetChannelIdxByName(device->RealOut, FrontCenter)};
1762 ApplyStablizer(device->Stablizer.get(), RealOut, lidx, ridx, cidx, SamplesToDo);
1765 /* Apply compression, limiting sample amplitude if needed or desired. */
1766 if(Compressor *comp{device->Limiter.get()})
1767 comp->process(SamplesToDo, RealOut.data());
1769 /* Apply delays and attenuation for mismatched speaker distances. */
1770 ApplyDistanceComp(RealOut, SamplesToDo, device->ChannelDelay.as_span().cbegin());
1772 /* Apply dithering. The compressor should have left enough headroom for
1773 * the dither noise to not saturate.
1775 if(device->DitherDepth > 0.0f)
1776 ApplyDither(RealOut, &device->DitherSeed, device->DitherDepth, SamplesToDo);
1778 if LIKELY(OutBuffer)
1780 /* Finally, interleave and convert samples, writing to the device's
1781 * output buffer.
1783 switch(device->FmtType)
1785 #define HANDLE_WRITE(T) case T: \
1786 Write<T>(RealOut, OutBuffer, SamplesDone, SamplesToDo); break;
1787 HANDLE_WRITE(DevFmtByte)
1788 HANDLE_WRITE(DevFmtUByte)
1789 HANDLE_WRITE(DevFmtShort)
1790 HANDLE_WRITE(DevFmtUShort)
1791 HANDLE_WRITE(DevFmtInt)
1792 HANDLE_WRITE(DevFmtUInt)
1793 HANDLE_WRITE(DevFmtFloat)
1794 #undef HANDLE_WRITE
1798 SamplesDone += SamplesToDo;
1803 void aluHandleDisconnect(ALCdevice *device, const char *msg, ...)
1805 if(!device->Connected.exchange(false, std::memory_order_acq_rel))
1806 return;
1808 AsyncEvent evt{EventType_Disconnected};
1809 evt.u.user.type = AL_EVENT_TYPE_DISCONNECTED_SOFT;
1810 evt.u.user.id = 0;
1811 evt.u.user.param = 0;
1813 va_list args;
1814 va_start(args, msg);
1815 int msglen{vsnprintf(evt.u.user.msg, sizeof(evt.u.user.msg), msg, args)};
1816 va_end(args);
1818 if(msglen < 0 || static_cast<size_t>(msglen) >= sizeof(evt.u.user.msg))
1819 evt.u.user.msg[sizeof(evt.u.user.msg)-1] = 0;
1821 IncrementRef(device->MixCount);
1822 for(ALCcontext *ctx : *device->mContexts.load())
1824 const ALbitfieldSOFT enabledevt{ctx->mEnabledEvts.load(std::memory_order_acquire)};
1825 if((enabledevt&EventType_Disconnected))
1827 RingBuffer *ring{ctx->mAsyncEvents.get()};
1828 auto evt_data = ring->getWriteVector().first;
1829 if(evt_data.len > 0)
1831 ::new (evt_data.buf) AsyncEvent{evt};
1832 ring->writeAdvance(1);
1833 ctx->mEventSem.post();
1837 auto stop_voice = [](ALvoice &voice) -> void
1839 voice.mCurrentBuffer.store(nullptr, std::memory_order_relaxed);
1840 voice.mLoopBuffer.store(nullptr, std::memory_order_relaxed);
1841 voice.mSourceID.store(0u, std::memory_order_relaxed);
1842 voice.mPlayState.store(ALvoice::Stopped, std::memory_order_release);
1844 std::for_each(ctx->mVoices.begin(), ctx->mVoices.end(), stop_voice);
1846 IncrementRef(device->MixCount);