Cleanup alu.cpp some
[openal-soft.git] / Alc / alu.cpp
blob37d23c60db8f8332d09df9be53b32d772058a9a8
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 <math.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <ctype.h>
27 #include <assert.h>
29 #include <cmath>
30 #include <algorithm>
32 #include "alMain.h"
33 #include "alcontext.h"
34 #include "alSource.h"
35 #include "alBuffer.h"
36 #include "alListener.h"
37 #include "alAuxEffectSlot.h"
38 #include "alu.h"
39 #include "bs2b.h"
40 #include "hrtf.h"
41 #include "mastering.h"
42 #include "uhjfilter.h"
43 #include "bformatdec.h"
44 #include "ringbuffer.h"
45 #include "filters/splitter.h"
47 #include "mixer/defs.h"
48 #include "fpu_modes.h"
49 #include "cpu_caps.h"
50 #include "bsinc_inc.h"
53 namespace {
55 ALfloat InitConeScale()
57 ALfloat ret{1.0f};
58 const char *str{getenv("__ALSOFT_HALF_ANGLE_CONES")};
59 if(str && (strcasecmp(str, "true") == 0 || strtol(str, nullptr, 0) == 1))
60 ret *= 0.5f;
61 return ret;
64 ALfloat InitZScale()
66 ALfloat ret{1.0f};
67 const char *str{getenv("__ALSOFT_REVERSE_Z")};
68 if(str && (strcasecmp(str, "true") == 0 || strtol(str, nullptr, 0) == 1))
69 ret *= -1.0f;
70 return ret;
73 ALboolean InitReverbSOS()
75 ALboolean ret{AL_FALSE};
76 const char *str{getenv("__ALSOFT_REVERB_IGNORES_SOUND_SPEED")};
77 if(str && (strcasecmp(str, "true") == 0 || strtol(str, nullptr, 0) == 1))
78 ret = AL_TRUE;
79 return ret;
82 } // namespace
84 /* Cone scalar */
85 const ALfloat ConeScale{InitConeScale()};
87 /* Localized Z scalar for mono sources */
88 const ALfloat ZScale{InitZScale()};
90 /* Force default speed of sound for distance-related reverb decay. */
91 const ALboolean OverrideReverbSpeedOfSound{InitReverbSOS()};
94 namespace {
96 void ClearArray(ALfloat (&f)[MAX_OUTPUT_CHANNELS])
98 std::fill(std::begin(f), std::end(f), 0.0f);
101 struct ChanMap {
102 enum Channel channel;
103 ALfloat angle;
104 ALfloat elevation;
107 HrtfDirectMixerFunc MixDirectHrtf = MixDirectHrtf_C;
109 inline HrtfDirectMixerFunc SelectHrtfMixer(void)
111 #ifdef HAVE_NEON
112 if((CPUCapFlags&CPU_CAP_NEON))
113 return MixDirectHrtf_Neon;
114 #endif
115 #ifdef HAVE_SSE
116 if((CPUCapFlags&CPU_CAP_SSE))
117 return MixDirectHrtf_SSE;
118 #endif
120 return MixDirectHrtf_C;
124 void ProcessHrtf(ALCdevice *device, ALsizei SamplesToDo)
126 if(device->AmbiUp)
127 device->AmbiUp->process(device->Dry.Buffer, device->Dry.NumChannels,
128 device->FOAOut.Buffer, SamplesToDo
131 int lidx{GetChannelIdxByName(&device->RealOut, FrontLeft)};
132 int ridx{GetChannelIdxByName(&device->RealOut, FrontRight)};
133 assert(lidx != -1 && ridx != -1);
135 DirectHrtfState *state{device->mHrtfState.get()};
136 for(ALsizei c{0};c < device->Dry.NumChannels;c++)
138 MixDirectHrtf(device->RealOut.Buffer[lidx], device->RealOut.Buffer[ridx],
139 device->Dry.Buffer[c], state->Offset, state->IrSize,
140 state->Chan[c].Coeffs, state->Chan[c].Values, SamplesToDo
143 state->Offset += SamplesToDo;
146 void ProcessAmbiDec(ALCdevice *device, ALsizei SamplesToDo)
148 if(device->Dry.Buffer != device->FOAOut.Buffer)
149 device->AmbiDecoder->upSample(device->Dry.Buffer, device->FOAOut.Buffer,
150 device->FOAOut.NumChannels, SamplesToDo
152 device->AmbiDecoder->process(device->RealOut.Buffer, device->RealOut.NumChannels,
153 device->Dry.Buffer, SamplesToDo
157 void ProcessAmbiUp(ALCdevice *device, ALsizei SamplesToDo)
159 device->AmbiUp->process(device->RealOut.Buffer, device->RealOut.NumChannels,
160 device->FOAOut.Buffer, SamplesToDo
164 void ProcessUhj(ALCdevice *device, ALsizei SamplesToDo)
166 int lidx = GetChannelIdxByName(&device->RealOut, FrontLeft);
167 int ridx = GetChannelIdxByName(&device->RealOut, FrontRight);
168 assert(lidx != -1 && ridx != -1);
170 /* Encode to stereo-compatible 2-channel UHJ output. */
171 EncodeUhj2(device->Uhj_Encoder.get(),
172 device->RealOut.Buffer[lidx], device->RealOut.Buffer[ridx],
173 device->Dry.Buffer, SamplesToDo
177 void ProcessBs2b(ALCdevice *device, ALsizei SamplesToDo)
179 int lidx = GetChannelIdxByName(&device->RealOut, FrontLeft);
180 int ridx = GetChannelIdxByName(&device->RealOut, FrontRight);
181 assert(lidx != -1 && ridx != -1);
183 /* Apply binaural/crossfeed filter */
184 bs2b_cross_feed(device->Bs2b.get(), device->RealOut.Buffer[lidx],
185 device->RealOut.Buffer[ridx], SamplesToDo);
188 } // namespace
190 void aluInit(void)
192 MixDirectHrtf = SelectHrtfMixer();
196 void DeinitVoice(ALvoice *voice) noexcept
198 delete voice->Update.exchange(nullptr, std::memory_order_acq_rel);
199 voice->~ALvoice();
203 void aluSelectPostProcess(ALCdevice *device)
205 if(device->HrtfHandle)
206 device->PostProcess = ProcessHrtf;
207 else if(device->AmbiDecoder)
208 device->PostProcess = ProcessAmbiDec;
209 else if(device->AmbiUp)
210 device->PostProcess = ProcessAmbiUp;
211 else if(device->Uhj_Encoder)
212 device->PostProcess = ProcessUhj;
213 else if(device->Bs2b)
214 device->PostProcess = ProcessBs2b;
215 else
216 device->PostProcess = nullptr;
220 /* Prepares the interpolator for a given rate (determined by increment).
222 * With a bit of work, and a trade of memory for CPU cost, this could be
223 * modified for use with an interpolated increment for buttery-smooth pitch
224 * changes.
226 void BsincPrepare(const ALuint increment, BsincState *state, const BSincTable *table)
228 ALsizei si{BSINC_SCALE_COUNT - 1};
229 ALfloat sf{0.0f};
231 if(increment > FRACTIONONE)
233 sf = (ALfloat)FRACTIONONE / increment;
234 sf = maxf(0.0f, (BSINC_SCALE_COUNT-1) * (sf-table->scaleBase) * table->scaleRange);
235 si = float2int(sf);
236 /* The interpolation factor is fit to this diagonally-symmetric curve
237 * to reduce the transition ripple caused by interpolating different
238 * scales of the sinc function.
240 sf = 1.0f - std::cos(std::asin(sf - si));
243 state->sf = sf;
244 state->m = table->m[si];
245 state->l = (state->m/2) - 1;
246 state->filter = table->Tab + table->filterOffset[si];
250 namespace {
252 /* This RNG method was created based on the math found in opusdec. It's quick,
253 * and starting with a seed value of 22222, is suitable for generating
254 * whitenoise.
256 inline ALuint dither_rng(ALuint *seed) noexcept
258 *seed = (*seed * 96314165) + 907633515;
259 return *seed;
263 inline void aluCrossproduct(const ALfloat *inVector1, const ALfloat *inVector2, ALfloat *outVector)
265 outVector[0] = inVector1[1]*inVector2[2] - inVector1[2]*inVector2[1];
266 outVector[1] = inVector1[2]*inVector2[0] - inVector1[0]*inVector2[2];
267 outVector[2] = inVector1[0]*inVector2[1] - inVector1[1]*inVector2[0];
270 inline ALfloat aluDotproduct(const aluVector *vec1, const aluVector *vec2)
272 return vec1->v[0]*vec2->v[0] + vec1->v[1]*vec2->v[1] + vec1->v[2]*vec2->v[2];
275 ALfloat aluNormalize(ALfloat *vec)
277 const ALfloat length{std::sqrt(vec[0]*vec[0] + vec[1]*vec[1] + vec[2]*vec[2])};
278 if(length > FLT_EPSILON)
280 ALfloat inv_length = 1.0f/length;
281 vec[0] *= inv_length;
282 vec[1] *= inv_length;
283 vec[2] *= inv_length;
284 return length;
286 vec[0] = vec[1] = vec[2] = 0.0f;
287 return 0.0f;
290 void aluMatrixfFloat3(ALfloat *vec, ALfloat w, const aluMatrixf *mtx)
292 const ALfloat v[4]{ vec[0], vec[1], vec[2], w };
294 vec[0] = v[0]*mtx->m[0][0] + v[1]*mtx->m[1][0] + v[2]*mtx->m[2][0] + v[3]*mtx->m[3][0];
295 vec[1] = v[0]*mtx->m[0][1] + v[1]*mtx->m[1][1] + v[2]*mtx->m[2][1] + v[3]*mtx->m[3][1];
296 vec[2] = v[0]*mtx->m[0][2] + v[1]*mtx->m[1][2] + v[2]*mtx->m[2][2] + v[3]*mtx->m[3][2];
299 aluVector aluMatrixfVector(const aluMatrixf *mtx, const aluVector *vec)
301 aluVector v;
302 v.v[0] = vec->v[0]*mtx->m[0][0] + vec->v[1]*mtx->m[1][0] + vec->v[2]*mtx->m[2][0] + vec->v[3]*mtx->m[3][0];
303 v.v[1] = vec->v[0]*mtx->m[0][1] + vec->v[1]*mtx->m[1][1] + vec->v[2]*mtx->m[2][1] + vec->v[3]*mtx->m[3][1];
304 v.v[2] = vec->v[0]*mtx->m[0][2] + vec->v[1]*mtx->m[1][2] + vec->v[2]*mtx->m[2][2] + vec->v[3]*mtx->m[3][2];
305 v.v[3] = vec->v[0]*mtx->m[0][3] + vec->v[1]*mtx->m[1][3] + vec->v[2]*mtx->m[2][3] + vec->v[3]*mtx->m[3][3];
306 return v;
310 void SendSourceStoppedEvent(ALCcontext *context, ALuint id)
312 ALbitfieldSOFT enabledevt{context->EnabledEvts.load(std::memory_order_acquire)};
313 if(!(enabledevt&EventType_SourceStateChange)) return;
315 AsyncEvent evt{EventType_SourceStateChange};
316 evt.u.srcstate.id = id;
317 evt.u.srcstate.state = AL_STOPPED;
319 if(ll_ringbuffer_write(context->AsyncEvents, &evt, 1) == 1)
320 context->EventSem.post();
324 bool CalcContextParams(ALCcontext *Context)
326 ALcontextProps *props{Context->Update.exchange(nullptr, std::memory_order_acq_rel)};
327 if(!props) return false;
329 ALlistener &Listener = Context->Listener;
330 Listener.Params.MetersPerUnit = props->MetersPerUnit;
332 Listener.Params.DopplerFactor = props->DopplerFactor;
333 Listener.Params.SpeedOfSound = props->SpeedOfSound * props->DopplerVelocity;
334 if(!OverrideReverbSpeedOfSound)
335 Listener.Params.ReverbSpeedOfSound = Listener.Params.SpeedOfSound *
336 Listener.Params.MetersPerUnit;
338 Listener.Params.SourceDistanceModel = props->SourceDistanceModel;
339 Listener.Params.mDistanceModel = props->mDistanceModel;
341 AtomicReplaceHead(Context->FreeContextProps, props);
342 return true;
345 bool CalcListenerParams(ALCcontext *Context)
347 ALlistener &Listener = Context->Listener;
349 ALlistenerProps *props{Listener.Update.exchange(nullptr, std::memory_order_acq_rel)};
350 if(!props) return false;
352 /* AT then UP */
353 ALfloat N[3]{ props->Forward[0], props->Forward[1], props->Forward[2] };
354 aluNormalize(N);
355 ALfloat V[3]{ props->Up[0], props->Up[1], props->Up[2] };
356 aluNormalize(V);
357 /* Build and normalize right-vector */
358 ALfloat U[3];
359 aluCrossproduct(N, V, U);
360 aluNormalize(U);
362 aluMatrixfSet(&Listener.Params.Matrix,
363 U[0], V[0], -N[0], 0.0,
364 U[1], V[1], -N[1], 0.0,
365 U[2], V[2], -N[2], 0.0,
366 0.0, 0.0, 0.0, 1.0
369 ALfloat P[3]{ props->Position[0], props->Position[1], props->Position[2] };
370 aluMatrixfFloat3(P, 1.0, &Listener.Params.Matrix);
371 aluMatrixfSetRow(&Listener.Params.Matrix, 3, -P[0], -P[1], -P[2], 1.0f);
373 aluVector vel;
374 aluVectorSet(&vel, props->Velocity[0], props->Velocity[1], props->Velocity[2], 0.0f);
375 Listener.Params.Velocity = aluMatrixfVector(&Listener.Params.Matrix, &vel);
377 Listener.Params.Gain = props->Gain * Context->GainBoost;
379 AtomicReplaceHead(Context->FreeListenerProps, props);
380 return true;
383 bool CalcEffectSlotParams(ALeffectslot *slot, ALCcontext *context, bool force)
385 ALeffectslotProps *props{slot->Update.exchange(nullptr, std::memory_order_acq_rel)};
386 if(!props && !force) return false;
388 EffectState *state;
389 if(!props)
390 state = slot->Params.mEffectState;
391 else
393 slot->Params.Gain = props->Gain;
394 slot->Params.AuxSendAuto = props->AuxSendAuto;
395 slot->Params.EffectType = props->Type;
396 slot->Params.EffectProps = props->Props;
397 if(IsReverbEffect(props->Type))
399 slot->Params.RoomRolloff = props->Props.Reverb.RoomRolloffFactor;
400 slot->Params.DecayTime = props->Props.Reverb.DecayTime;
401 slot->Params.DecayLFRatio = props->Props.Reverb.DecayLFRatio;
402 slot->Params.DecayHFRatio = props->Props.Reverb.DecayHFRatio;
403 slot->Params.DecayHFLimit = props->Props.Reverb.DecayHFLimit;
404 slot->Params.AirAbsorptionGainHF = props->Props.Reverb.AirAbsorptionGainHF;
406 else
408 slot->Params.RoomRolloff = 0.0f;
409 slot->Params.DecayTime = 0.0f;
410 slot->Params.DecayLFRatio = 0.0f;
411 slot->Params.DecayHFRatio = 0.0f;
412 slot->Params.DecayHFLimit = AL_FALSE;
413 slot->Params.AirAbsorptionGainHF = 1.0f;
416 state = props->State;
418 if(state == slot->Params.mEffectState)
420 /* If the effect state is the same as current, we can decrement its
421 * count safely to remove it from the update object (it can't reach
422 * 0 refs since the current params also hold a reference).
424 DecrementRef(&state->mRef);
425 props->State = nullptr;
427 else
429 /* Otherwise, replace it and send off the old one with a release
430 * event.
432 AsyncEvent evt{EventType_ReleaseEffectState};
433 evt.u.mEffectState = slot->Params.mEffectState;
435 slot->Params.mEffectState = state;
436 props->State = NULL;
438 if(LIKELY(ll_ringbuffer_write(context->AsyncEvents, &evt, 1) != 0))
439 context->EventSem.post();
440 else
442 /* If writing the event failed, the queue was probably full.
443 * Store the old state in the property object where it can
444 * eventually be cleaned up sometime later (not ideal, but
445 * better than blocking or leaking).
447 props->State = evt.u.mEffectState;
451 AtomicReplaceHead(context->FreeEffectslotProps, props);
454 state->update(context, slot, &slot->Params.EffectProps);
455 return true;
459 constexpr struct ChanMap MonoMap[1]{
460 { FrontCenter, 0.0f, 0.0f }
461 }, RearMap[2]{
462 { BackLeft, DEG2RAD(-150.0f), DEG2RAD(0.0f) },
463 { BackRight, DEG2RAD( 150.0f), DEG2RAD(0.0f) }
464 }, QuadMap[4]{
465 { FrontLeft, DEG2RAD( -45.0f), DEG2RAD(0.0f) },
466 { FrontRight, DEG2RAD( 45.0f), DEG2RAD(0.0f) },
467 { BackLeft, DEG2RAD(-135.0f), DEG2RAD(0.0f) },
468 { BackRight, DEG2RAD( 135.0f), DEG2RAD(0.0f) }
469 }, X51Map[6]{
470 { FrontLeft, DEG2RAD( -30.0f), DEG2RAD(0.0f) },
471 { FrontRight, DEG2RAD( 30.0f), DEG2RAD(0.0f) },
472 { FrontCenter, DEG2RAD( 0.0f), DEG2RAD(0.0f) },
473 { LFE, 0.0f, 0.0f },
474 { SideLeft, DEG2RAD(-110.0f), DEG2RAD(0.0f) },
475 { SideRight, DEG2RAD( 110.0f), DEG2RAD(0.0f) }
476 }, X61Map[7]{
477 { FrontLeft, DEG2RAD(-30.0f), DEG2RAD(0.0f) },
478 { FrontRight, DEG2RAD( 30.0f), DEG2RAD(0.0f) },
479 { FrontCenter, DEG2RAD( 0.0f), DEG2RAD(0.0f) },
480 { LFE, 0.0f, 0.0f },
481 { BackCenter, DEG2RAD(180.0f), DEG2RAD(0.0f) },
482 { SideLeft, DEG2RAD(-90.0f), DEG2RAD(0.0f) },
483 { SideRight, DEG2RAD( 90.0f), DEG2RAD(0.0f) }
484 }, X71Map[8]{
485 { FrontLeft, DEG2RAD( -30.0f), DEG2RAD(0.0f) },
486 { FrontRight, DEG2RAD( 30.0f), DEG2RAD(0.0f) },
487 { FrontCenter, DEG2RAD( 0.0f), DEG2RAD(0.0f) },
488 { LFE, 0.0f, 0.0f },
489 { BackLeft, DEG2RAD(-150.0f), DEG2RAD(0.0f) },
490 { BackRight, DEG2RAD( 150.0f), DEG2RAD(0.0f) },
491 { SideLeft, DEG2RAD( -90.0f), DEG2RAD(0.0f) },
492 { SideRight, DEG2RAD( 90.0f), DEG2RAD(0.0f) }
495 void CalcPanningAndFilters(ALvoice *voice, const ALfloat Azi, const ALfloat Elev,
496 const ALfloat Distance, const ALfloat Spread,
497 const ALfloat DryGain, const ALfloat DryGainHF,
498 const ALfloat DryGainLF, const ALfloat *WetGain,
499 const ALfloat *WetGainLF, const ALfloat *WetGainHF,
500 ALeffectslot **SendSlots, const ALbuffer *Buffer,
501 const ALvoicePropsBase *props, const ALlistener &Listener,
502 const ALCdevice *Device)
504 ChanMap StereoMap[2]{
505 { FrontLeft, DEG2RAD(-30.0f), DEG2RAD(0.0f) },
506 { FrontRight, DEG2RAD( 30.0f), DEG2RAD(0.0f) }
509 bool DirectChannels{props->DirectChannels != AL_FALSE};
510 const ChanMap *chans{nullptr};
511 ALsizei num_channels{0};
512 bool isbformat{false};
513 ALfloat downmix_gain{1.0f};
514 switch(Buffer->FmtChannels)
516 case FmtMono:
517 chans = MonoMap;
518 num_channels = 1;
519 /* Mono buffers are never played direct. */
520 DirectChannels = false;
521 break;
523 case FmtStereo:
524 /* Convert counter-clockwise to clockwise. */
525 StereoMap[0].angle = -props->StereoPan[0];
526 StereoMap[1].angle = -props->StereoPan[1];
528 chans = StereoMap;
529 num_channels = 2;
530 downmix_gain = 1.0f / 2.0f;
531 break;
533 case FmtRear:
534 chans = RearMap;
535 num_channels = 2;
536 downmix_gain = 1.0f / 2.0f;
537 break;
539 case FmtQuad:
540 chans = QuadMap;
541 num_channels = 4;
542 downmix_gain = 1.0f / 4.0f;
543 break;
545 case FmtX51:
546 chans = X51Map;
547 num_channels = 6;
548 /* NOTE: Excludes LFE. */
549 downmix_gain = 1.0f / 5.0f;
550 break;
552 case FmtX61:
553 chans = X61Map;
554 num_channels = 7;
555 /* NOTE: Excludes LFE. */
556 downmix_gain = 1.0f / 6.0f;
557 break;
559 case FmtX71:
560 chans = X71Map;
561 num_channels = 8;
562 /* NOTE: Excludes LFE. */
563 downmix_gain = 1.0f / 7.0f;
564 break;
566 case FmtBFormat2D:
567 num_channels = 3;
568 isbformat = true;
569 DirectChannels = false;
570 break;
572 case FmtBFormat3D:
573 num_channels = 4;
574 isbformat = true;
575 DirectChannels = false;
576 break;
579 std::for_each(std::begin(voice->Direct.Params), std::begin(voice->Direct.Params)+num_channels,
580 [](DirectParams &params) -> void
582 params.Hrtf.Target = HrtfParams{};
583 ClearArray(params.Gains.Target);
586 const ALsizei NumSends{Device->NumAuxSends};
587 std::for_each(voice->Send+0, voice->Send+NumSends,
588 [num_channels](ALvoice::SendData &send) -> void
590 std::for_each(std::begin(send.Params), std::begin(send.Params)+num_channels,
591 [](SendParams &params) -> void { ClearArray(params.Gains.Target); }
596 voice->Flags &= ~(VOICE_HAS_HRTF | VOICE_HAS_NFC);
597 if(isbformat)
599 /* Special handling for B-Format sources. */
601 if(Distance > FLT_EPSILON)
603 /* Panning a B-Format sound toward some direction is easy. Just pan
604 * the first (W) channel as a normal mono sound and silence the
605 * others.
608 if(Device->AvgSpeakerDist > 0.0f)
610 const ALfloat mdist{Distance * Listener.Params.MetersPerUnit};
611 const ALfloat w1{SPEEDOFSOUNDMETRESPERSEC /
612 (Device->AvgSpeakerDist * (ALfloat)Device->Frequency)};
613 ALfloat w0{SPEEDOFSOUNDMETRESPERSEC /
614 (mdist * (ALfloat)Device->Frequency)};
615 /* Clamp w0 for really close distances, to prevent excessive
616 * bass.
618 w0 = minf(w0, w1*4.0f);
620 /* Only need to adjust the first channel of a B-Format source. */
621 voice->Direct.Params[0].NFCtrlFilter.adjust(w0);
623 std::copy(std::begin(Device->NumChannelsPerOrder),
624 std::end(Device->NumChannelsPerOrder),
625 std::begin(voice->Direct.ChannelsPerOrder));
626 voice->Flags |= VOICE_HAS_NFC;
629 /* A scalar of 1.5 for plain stereo results in +/-60 degrees being
630 * moved to +/-90 degrees for direct right and left speaker
631 * responses.
633 ALfloat coeffs[MAX_AMBI_COEFFS];
634 CalcAngleCoeffs((Device->Render_Mode==StereoPair) ? ScaleAzimuthFront(Azi, 1.5f) : Azi,
635 Elev, Spread, coeffs);
637 /* NOTE: W needs to be scaled by sqrt(2) due to FuMa normalization. */
638 ComputePanGains(&Device->Dry, coeffs, DryGain*SQRTF_2,
639 voice->Direct.Params[0].Gains.Target);
640 for(ALsizei i{0};i < NumSends;i++)
642 if(const ALeffectslot *Slot{SendSlots[i]})
643 ComputePanningGainsBF(Slot->ChanMap, Slot->NumChannels, coeffs,
644 WetGain[i]*SQRTF_2, voice->Send[i].Params[0].Gains.Target
648 else
650 if(Device->AvgSpeakerDist > 0.0f)
652 /* NOTE: The NFCtrlFilters were created with a w0 of 0, which
653 * is what we want for FOA input. The first channel may have
654 * been previously re-adjusted if panned, so reset it.
656 voice->Direct.Params[0].NFCtrlFilter.adjust(0.0f);
658 voice->Direct.ChannelsPerOrder[0] = 1;
659 voice->Direct.ChannelsPerOrder[1] = mini(voice->Direct.Channels-1, 3);
660 std::fill(std::begin(voice->Direct.ChannelsPerOrder)+2,
661 std::end(voice->Direct.ChannelsPerOrder), 0);
662 voice->Flags |= VOICE_HAS_NFC;
665 /* Local B-Format sources have their XYZ channels rotated according
666 * to the orientation.
668 /* AT then UP */
669 ALfloat N[3]{ props->Orientation[0][0], props->Orientation[0][1],
670 props->Orientation[0][2] };
671 aluNormalize(N);
672 ALfloat V[3]{ props->Orientation[1][0], props->Orientation[1][1],
673 props->Orientation[1][2] };
674 aluNormalize(V);
675 if(!props->HeadRelative)
677 const aluMatrixf *lmatrix = &Listener.Params.Matrix;
678 aluMatrixfFloat3(N, 0.0f, lmatrix);
679 aluMatrixfFloat3(V, 0.0f, lmatrix);
681 /* Build and normalize right-vector */
682 ALfloat U[3];
683 aluCrossproduct(N, V, U);
684 aluNormalize(U);
686 /* Build a rotate + conversion matrix (FuMa -> ACN+N3D). NOTE: This
687 * matrix is transposed, for the inputs to align on the rows and
688 * outputs on the columns.
690 aluMatrixf matrix;
691 aluMatrixfSet(&matrix,
692 // ACN0 ACN1 ACN2 ACN3
693 SQRTF_2, 0.0f, 0.0f, 0.0f, // Ambi W
694 0.0f, -N[0]*SQRTF_3, N[1]*SQRTF_3, -N[2]*SQRTF_3, // Ambi X
695 0.0f, U[0]*SQRTF_3, -U[1]*SQRTF_3, U[2]*SQRTF_3, // Ambi Y
696 0.0f, -V[0]*SQRTF_3, V[1]*SQRTF_3, -V[2]*SQRTF_3 // Ambi Z
699 voice->Direct.Buffer = Device->FOAOut.Buffer;
700 voice->Direct.Channels = Device->FOAOut.NumChannels;
701 for(ALsizei c{0};c < num_channels;c++)
702 ComputePanGains(&Device->FOAOut, matrix.m[c], DryGain,
703 voice->Direct.Params[c].Gains.Target);
704 for(ALsizei i{0};i < NumSends;i++)
706 if(const ALeffectslot *Slot{SendSlots[i]})
707 for(ALsizei c{0};c < num_channels;c++)
708 ComputePanningGainsBF(Slot->ChanMap, Slot->NumChannels, matrix.m[c],
709 WetGain[i], voice->Send[i].Params[c].Gains.Target
714 else if(DirectChannels)
716 /* Direct source channels always play local. Skip the virtual channels
717 * and write inputs to the matching real outputs.
719 voice->Direct.Buffer = Device->RealOut.Buffer;
720 voice->Direct.Channels = Device->RealOut.NumChannels;
722 for(ALsizei c{0};c < num_channels;c++)
724 int idx{GetChannelIdxByName(&Device->RealOut, chans[c].channel)};
725 if(idx != -1) voice->Direct.Params[c].Gains.Target[idx] = DryGain;
728 /* Auxiliary sends still use normal channel panning since they mix to
729 * B-Format, which can't channel-match.
731 for(ALsizei c{0};c < num_channels;c++)
733 ALfloat coeffs[MAX_AMBI_COEFFS];
734 CalcAngleCoeffs(chans[c].angle, chans[c].elevation, 0.0f, coeffs);
736 for(ALsizei i{0};i < NumSends;i++)
738 if(const ALeffectslot *Slot{SendSlots[i]})
739 ComputePanningGainsBF(Slot->ChanMap, Slot->NumChannels, coeffs,
740 WetGain[i], voice->Send[i].Params[c].Gains.Target
745 else if(Device->Render_Mode == HrtfRender)
747 /* Full HRTF rendering. Skip the virtual channels and render to the
748 * real outputs.
750 voice->Direct.Buffer = Device->RealOut.Buffer;
751 voice->Direct.Channels = Device->RealOut.NumChannels;
753 if(Distance > FLT_EPSILON)
755 /* Get the HRIR coefficients and delays just once, for the given
756 * source direction.
758 GetHrtfCoeffs(Device->HrtfHandle, Elev, Azi, Spread,
759 voice->Direct.Params[0].Hrtf.Target.Coeffs,
760 voice->Direct.Params[0].Hrtf.Target.Delay);
761 voice->Direct.Params[0].Hrtf.Target.Gain = DryGain * downmix_gain;
763 /* Remaining channels use the same results as the first. */
764 for(ALsizei c{1};c < num_channels;c++)
766 /* Skip LFE */
767 if(chans[c].channel != LFE)
768 voice->Direct.Params[c].Hrtf.Target = voice->Direct.Params[0].Hrtf.Target;
771 /* Calculate the directional coefficients once, which apply to all
772 * input channels of the source sends.
774 ALfloat coeffs[MAX_AMBI_COEFFS];
775 CalcAngleCoeffs(Azi, Elev, Spread, coeffs);
777 for(ALsizei i{0};i < NumSends;i++)
779 if(const ALeffectslot *Slot{SendSlots[i]})
780 for(ALsizei c{0};c < num_channels;c++)
782 /* Skip LFE */
783 if(chans[c].channel != LFE)
784 ComputePanningGainsBF(Slot->ChanMap, Slot->NumChannels, coeffs,
785 WetGain[i]*downmix_gain, voice->Send[i].Params[c].Gains.Target
790 else
792 /* Local sources on HRTF play with each channel panned to its
793 * relative location around the listener, providing "virtual
794 * speaker" responses.
796 for(ALsizei c{0};c < num_channels;c++)
798 /* Skip LFE */
799 if(chans[c].channel == LFE)
800 continue;
802 /* Get the HRIR coefficients and delays for this channel
803 * position.
805 GetHrtfCoeffs(Device->HrtfHandle,
806 chans[c].elevation, chans[c].angle, Spread,
807 voice->Direct.Params[c].Hrtf.Target.Coeffs,
808 voice->Direct.Params[c].Hrtf.Target.Delay
810 voice->Direct.Params[c].Hrtf.Target.Gain = DryGain;
812 /* Normal panning for auxiliary sends. */
813 ALfloat coeffs[MAX_AMBI_COEFFS];
814 CalcAngleCoeffs(chans[c].angle, chans[c].elevation, Spread, coeffs);
816 for(ALsizei i{0};i < NumSends;i++)
818 if(const ALeffectslot *Slot{SendSlots[i]})
819 ComputePanningGainsBF(Slot->ChanMap, Slot->NumChannels, coeffs,
820 WetGain[i], voice->Send[i].Params[c].Gains.Target
826 voice->Flags |= VOICE_HAS_HRTF;
828 else
830 /* Non-HRTF rendering. Use normal panning to the output. */
832 if(Distance > FLT_EPSILON)
834 /* Calculate NFC filter coefficient if needed. */
835 if(Device->AvgSpeakerDist > 0.0f)
837 const ALfloat mdist{Distance * Listener.Params.MetersPerUnit};
838 const ALfloat w1{SPEEDOFSOUNDMETRESPERSEC /
839 (Device->AvgSpeakerDist * (ALfloat)Device->Frequency)};
840 ALfloat w0{SPEEDOFSOUNDMETRESPERSEC /
841 (mdist * (ALfloat)Device->Frequency)};
842 /* Clamp w0 for really close distances, to prevent excessive
843 * bass.
845 w0 = minf(w0, w1*4.0f);
847 /* Adjust NFC filters. */
848 for(ALsizei c{0};c < num_channels;c++)
849 voice->Direct.Params[c].NFCtrlFilter.adjust(w0);
851 std::copy(std::begin(Device->NumChannelsPerOrder),
852 std::end(Device->NumChannelsPerOrder),
853 std::begin(voice->Direct.ChannelsPerOrder));
854 voice->Flags |= VOICE_HAS_NFC;
857 /* Calculate the directional coefficients once, which apply to all
858 * input channels.
860 ALfloat coeffs[MAX_AMBI_COEFFS];
861 CalcAngleCoeffs((Device->Render_Mode==StereoPair) ? ScaleAzimuthFront(Azi, 1.5f) : Azi,
862 Elev, Spread, coeffs);
864 for(ALsizei c{0};c < num_channels;c++)
866 /* Special-case LFE */
867 if(chans[c].channel == LFE)
869 if(Device->Dry.Buffer == Device->RealOut.Buffer)
871 int idx = GetChannelIdxByName(&Device->RealOut, chans[c].channel);
872 if(idx != -1) voice->Direct.Params[c].Gains.Target[idx] = DryGain;
874 continue;
877 ComputePanGains(&Device->Dry, coeffs, DryGain * downmix_gain,
878 voice->Direct.Params[c].Gains.Target);
881 for(ALsizei i{0};i < NumSends;i++)
883 if(const ALeffectslot *Slot{SendSlots[i]})
884 for(ALsizei c{0};c < num_channels;c++)
886 /* Skip LFE */
887 if(chans[c].channel != LFE)
888 ComputePanningGainsBF(Slot->ChanMap, Slot->NumChannels, coeffs,
889 WetGain[i]*downmix_gain, voice->Send[i].Params[c].Gains.Target
894 else
896 if(Device->AvgSpeakerDist > 0.0f)
898 /* If the source distance is 0, set w0 to w1 to act as a pass-
899 * through. We still want to pass the signal through the
900 * filters so they keep an appropriate history, in case the
901 * source moves away from the listener.
903 const ALfloat w0{SPEEDOFSOUNDMETRESPERSEC /
904 (Device->AvgSpeakerDist * (ALfloat)Device->Frequency)};
906 for(ALsizei c{0};c < num_channels;c++)
907 voice->Direct.Params[c].NFCtrlFilter.adjust(w0);
909 std::copy(std::begin(Device->NumChannelsPerOrder),
910 std::end(Device->NumChannelsPerOrder),
911 std::begin(voice->Direct.ChannelsPerOrder));
912 voice->Flags |= VOICE_HAS_NFC;
915 for(ALsizei c{0};c < num_channels;c++)
917 /* Special-case LFE */
918 if(chans[c].channel == LFE)
920 if(Device->Dry.Buffer == Device->RealOut.Buffer)
922 int idx = GetChannelIdxByName(&Device->RealOut, chans[c].channel);
923 if(idx != -1) voice->Direct.Params[c].Gains.Target[idx] = DryGain;
925 continue;
928 ALfloat coeffs[MAX_AMBI_COEFFS];
929 CalcAngleCoeffs(
930 (Device->Render_Mode==StereoPair) ? ScaleAzimuthFront(chans[c].angle, 3.0f)
931 : chans[c].angle,
932 chans[c].elevation, Spread, coeffs
935 ComputePanGains(&Device->Dry, coeffs, DryGain,
936 voice->Direct.Params[c].Gains.Target);
937 for(ALsizei i{0};i < NumSends;i++)
939 if(const ALeffectslot *Slot{SendSlots[i]})
940 ComputePanningGainsBF(Slot->ChanMap, Slot->NumChannels,
941 coeffs, WetGain[i], voice->Send[i].Params[c].Gains.Target
948 const auto Frequency = static_cast<ALfloat>(Device->Frequency);
950 const ALfloat hfScale{props->Direct.HFReference / Frequency};
951 const ALfloat lfScale{props->Direct.LFReference / Frequency};
952 const ALfloat gainHF{maxf(DryGainHF, 0.001f)}; /* Limit -60dB */
953 const ALfloat gainLF{maxf(DryGainLF, 0.001f)};
955 voice->Direct.FilterType = AF_None;
956 if(gainHF != 1.0f) voice->Direct.FilterType |= AF_LowPass;
957 if(gainLF != 1.0f) voice->Direct.FilterType |= AF_HighPass;
958 voice->Direct.Params[0].LowPass.setParams(BiquadType::HighShelf,
959 gainHF, hfScale, calc_rcpQ_from_slope(gainHF, 1.0f)
961 voice->Direct.Params[0].HighPass.setParams(BiquadType::LowShelf,
962 gainLF, lfScale, calc_rcpQ_from_slope(gainLF, 1.0f)
964 for(ALsizei c{1};c < num_channels;c++)
966 voice->Direct.Params[c].LowPass.copyParamsFrom(voice->Direct.Params[0].LowPass);
967 voice->Direct.Params[c].HighPass.copyParamsFrom(voice->Direct.Params[0].HighPass);
970 for(ALsizei i{0};i < NumSends;i++)
972 const ALfloat hfScale{props->Send[i].HFReference / Frequency};
973 const ALfloat lfScale{props->Send[i].LFReference / Frequency};
974 const ALfloat gainHF{maxf(WetGainHF[i], 0.001f)};
975 const ALfloat gainLF{maxf(WetGainLF[i], 0.001f)};
977 voice->Send[i].FilterType = AF_None;
978 if(gainHF != 1.0f) voice->Send[i].FilterType |= AF_LowPass;
979 if(gainLF != 1.0f) voice->Send[i].FilterType |= AF_HighPass;
980 voice->Send[i].Params[0].LowPass.setParams(BiquadType::HighShelf,
981 gainHF, hfScale, calc_rcpQ_from_slope(gainHF, 1.0f)
983 voice->Send[i].Params[0].HighPass.setParams(BiquadType::LowShelf,
984 gainLF, lfScale, calc_rcpQ_from_slope(gainLF, 1.0f)
986 for(ALsizei c{1};c < num_channels;c++)
988 voice->Send[i].Params[c].LowPass.copyParamsFrom(voice->Send[i].Params[0].LowPass);
989 voice->Send[i].Params[c].HighPass.copyParamsFrom(voice->Send[i].Params[0].HighPass);
994 void CalcNonAttnSourceParams(ALvoice *voice, const ALvoicePropsBase *props, const ALbuffer *ALBuffer, const ALCcontext *ALContext)
996 const ALCdevice *Device{ALContext->Device};
997 ALeffectslot *SendSlots[MAX_SENDS];
999 voice->Direct.Buffer = Device->Dry.Buffer;
1000 voice->Direct.Channels = Device->Dry.NumChannels;
1001 for(ALsizei i{0};i < Device->NumAuxSends;i++)
1003 SendSlots[i] = props->Send[i].Slot;
1004 if(!SendSlots[i] && i == 0)
1005 SendSlots[i] = ALContext->DefaultSlot.get();
1006 if(!SendSlots[i] || SendSlots[i]->Params.EffectType == AL_EFFECT_NULL)
1008 SendSlots[i] = NULL;
1009 voice->Send[i].Buffer = NULL;
1010 voice->Send[i].Channels = 0;
1012 else
1014 voice->Send[i].Buffer = SendSlots[i]->WetBuffer;
1015 voice->Send[i].Channels = SendSlots[i]->NumChannels;
1019 /* Calculate the stepping value */
1020 const auto Pitch = static_cast<ALfloat>(ALBuffer->Frequency) /
1021 static_cast<ALfloat>(Device->Frequency) * props->Pitch;
1022 if(Pitch > (ALfloat)MAX_PITCH)
1023 voice->Step = MAX_PITCH<<FRACTIONBITS;
1024 else
1025 voice->Step = maxi(fastf2i(Pitch * FRACTIONONE), 1);
1026 if(props->Resampler == BSinc24Resampler)
1027 BsincPrepare(voice->Step, &voice->ResampleState.bsinc, &bsinc24);
1028 else if(props->Resampler == BSinc12Resampler)
1029 BsincPrepare(voice->Step, &voice->ResampleState.bsinc, &bsinc12);
1030 voice->Resampler = SelectResampler(props->Resampler);
1032 /* Calculate gains */
1033 const ALlistener &Listener = ALContext->Listener;
1034 ALfloat DryGain{clampf(props->Gain, props->MinGain, props->MaxGain)};
1035 DryGain *= props->Direct.Gain * Listener.Params.Gain;
1036 DryGain = minf(DryGain, GAIN_MIX_MAX);
1037 ALfloat DryGainHF{props->Direct.GainHF};
1038 ALfloat DryGainLF{props->Direct.GainLF};
1039 ALfloat WetGain[MAX_SENDS], WetGainHF[MAX_SENDS], WetGainLF[MAX_SENDS];
1040 for(ALsizei i{0};i < Device->NumAuxSends;i++)
1042 WetGain[i] = clampf(props->Gain, props->MinGain, props->MaxGain);
1043 WetGain[i] *= props->Send[i].Gain * Listener.Params.Gain;
1044 WetGain[i] = minf(WetGain[i], GAIN_MIX_MAX);
1045 WetGainHF[i] = props->Send[i].GainHF;
1046 WetGainLF[i] = props->Send[i].GainLF;
1049 CalcPanningAndFilters(voice, 0.0f, 0.0f, 0.0f, 0.0f, DryGain, DryGainHF, DryGainLF, WetGain,
1050 WetGainLF, WetGainHF, SendSlots, ALBuffer, props, Listener, Device);
1053 void CalcAttnSourceParams(ALvoice *voice, const ALvoicePropsBase *props, const ALbuffer *ALBuffer, const ALCcontext *ALContext)
1055 const ALCdevice *Device{ALContext->Device};
1056 const ALsizei NumSends{Device->NumAuxSends};
1057 const ALlistener &Listener = ALContext->Listener;
1059 /* Set mixing buffers and get send parameters. */
1060 voice->Direct.Buffer = Device->Dry.Buffer;
1061 voice->Direct.Channels = Device->Dry.NumChannels;
1062 ALeffectslot *SendSlots[MAX_SENDS];
1063 ALfloat RoomRolloff[MAX_SENDS];
1064 ALfloat DecayDistance[MAX_SENDS];
1065 ALfloat DecayLFDistance[MAX_SENDS];
1066 ALfloat DecayHFDistance[MAX_SENDS];
1067 for(ALsizei i{0};i < NumSends;i++)
1069 SendSlots[i] = props->Send[i].Slot;
1070 if(!SendSlots[i] && i == 0)
1071 SendSlots[i] = ALContext->DefaultSlot.get();
1072 if(!SendSlots[i] || SendSlots[i]->Params.EffectType == AL_EFFECT_NULL)
1074 SendSlots[i] = nullptr;
1075 RoomRolloff[i] = 0.0f;
1076 DecayDistance[i] = 0.0f;
1077 DecayLFDistance[i] = 0.0f;
1078 DecayHFDistance[i] = 0.0f;
1080 else if(SendSlots[i]->Params.AuxSendAuto)
1082 RoomRolloff[i] = SendSlots[i]->Params.RoomRolloff + props->RoomRolloffFactor;
1083 /* Calculate the distances to where this effect's decay reaches
1084 * -60dB.
1086 DecayDistance[i] = SendSlots[i]->Params.DecayTime *
1087 Listener.Params.ReverbSpeedOfSound;
1088 DecayLFDistance[i] = DecayDistance[i] * SendSlots[i]->Params.DecayLFRatio;
1089 DecayHFDistance[i] = DecayDistance[i] * SendSlots[i]->Params.DecayHFRatio;
1090 if(SendSlots[i]->Params.DecayHFLimit)
1092 ALfloat airAbsorption = SendSlots[i]->Params.AirAbsorptionGainHF;
1093 if(airAbsorption < 1.0f)
1095 /* Calculate the distance to where this effect's air
1096 * absorption reaches -60dB, and limit the effect's HF
1097 * decay distance (so it doesn't take any longer to decay
1098 * than the air would allow).
1100 ALfloat absorb_dist = log10f(REVERB_DECAY_GAIN) / log10f(airAbsorption);
1101 DecayHFDistance[i] = minf(absorb_dist, DecayHFDistance[i]);
1105 else
1107 /* If the slot's auxiliary send auto is off, the data sent to the
1108 * effect slot is the same as the dry path, sans filter effects */
1109 RoomRolloff[i] = props->RolloffFactor;
1110 DecayDistance[i] = 0.0f;
1111 DecayLFDistance[i] = 0.0f;
1112 DecayHFDistance[i] = 0.0f;
1115 if(!SendSlots[i])
1117 voice->Send[i].Buffer = nullptr;
1118 voice->Send[i].Channels = 0;
1120 else
1122 voice->Send[i].Buffer = SendSlots[i]->WetBuffer;
1123 voice->Send[i].Channels = SendSlots[i]->NumChannels;
1127 /* Transform source to listener space (convert to head relative) */
1128 aluVector Position, Velocity, Direction;
1129 aluVectorSet(&Position, props->Position[0], props->Position[1], props->Position[2], 1.0f);
1130 aluVectorSet(&Direction, props->Direction[0], props->Direction[1], props->Direction[2], 0.0f);
1131 aluVectorSet(&Velocity, props->Velocity[0], props->Velocity[1], props->Velocity[2], 0.0f);
1132 if(props->HeadRelative == AL_FALSE)
1134 const aluMatrixf *Matrix = &Listener.Params.Matrix;
1135 /* Transform source vectors */
1136 Position = aluMatrixfVector(Matrix, &Position);
1137 Velocity = aluMatrixfVector(Matrix, &Velocity);
1138 Direction = aluMatrixfVector(Matrix, &Direction);
1140 else
1142 const aluVector *lvelocity = &Listener.Params.Velocity;
1143 /* Offset the source velocity to be relative of the listener velocity */
1144 Velocity.v[0] += lvelocity->v[0];
1145 Velocity.v[1] += lvelocity->v[1];
1146 Velocity.v[2] += lvelocity->v[2];
1149 bool directional{aluNormalize(Direction.v) > 0.0f};
1150 aluVector SourceToListener;
1151 SourceToListener.v[0] = -Position.v[0];
1152 SourceToListener.v[1] = -Position.v[1];
1153 SourceToListener.v[2] = -Position.v[2];
1154 SourceToListener.v[3] = 0.0f;
1155 ALfloat Distance{aluNormalize(SourceToListener.v)};
1157 /* Initial source gain */
1158 ALfloat DryGain{props->Gain};
1159 ALfloat DryGainHF{1.0f};
1160 ALfloat DryGainLF{1.0f};
1161 ALfloat WetGain[MAX_SENDS], WetGainHF[MAX_SENDS], WetGainLF[MAX_SENDS];
1162 for(ALsizei i{0};i < NumSends;i++)
1164 WetGain[i] = props->Gain;
1165 WetGainHF[i] = 1.0f;
1166 WetGainLF[i] = 1.0f;
1169 /* Calculate distance attenuation */
1170 ALfloat ClampedDist{Distance};
1172 switch(Listener.Params.SourceDistanceModel ?
1173 props->mDistanceModel : Listener.Params.mDistanceModel)
1175 case DistanceModel::InverseClamped:
1176 ClampedDist = clampf(ClampedDist, props->RefDistance, props->MaxDistance);
1177 if(props->MaxDistance < props->RefDistance) break;
1178 /*fall-through*/
1179 case DistanceModel::Inverse:
1180 if(!(props->RefDistance > 0.0f))
1181 ClampedDist = props->RefDistance;
1182 else
1184 ALfloat dist = lerp(props->RefDistance, ClampedDist, props->RolloffFactor);
1185 if(dist > 0.0f) DryGain *= props->RefDistance / dist;
1186 for(ALsizei i{0};i < NumSends;i++)
1188 dist = lerp(props->RefDistance, ClampedDist, RoomRolloff[i]);
1189 if(dist > 0.0f) WetGain[i] *= props->RefDistance / dist;
1192 break;
1194 case DistanceModel::LinearClamped:
1195 ClampedDist = clampf(ClampedDist, props->RefDistance, props->MaxDistance);
1196 if(props->MaxDistance < props->RefDistance) break;
1197 /*fall-through*/
1198 case DistanceModel::Linear:
1199 if(!(props->MaxDistance != props->RefDistance))
1200 ClampedDist = props->RefDistance;
1201 else
1203 ALfloat attn = props->RolloffFactor * (ClampedDist-props->RefDistance) /
1204 (props->MaxDistance-props->RefDistance);
1205 DryGain *= maxf(1.0f - attn, 0.0f);
1206 for(ALsizei i{0};i < NumSends;i++)
1208 attn = RoomRolloff[i] * (ClampedDist-props->RefDistance) /
1209 (props->MaxDistance-props->RefDistance);
1210 WetGain[i] *= maxf(1.0f - attn, 0.0f);
1213 break;
1215 case DistanceModel::ExponentClamped:
1216 ClampedDist = clampf(ClampedDist, props->RefDistance, props->MaxDistance);
1217 if(props->MaxDistance < props->RefDistance) break;
1218 /*fall-through*/
1219 case DistanceModel::Exponent:
1220 if(!(ClampedDist > 0.0f && props->RefDistance > 0.0f))
1221 ClampedDist = props->RefDistance;
1222 else
1224 DryGain *= std::pow(ClampedDist/props->RefDistance, -props->RolloffFactor);
1225 for(ALsizei i{0};i < NumSends;i++)
1226 WetGain[i] *= std::pow(ClampedDist/props->RefDistance, -RoomRolloff[i]);
1228 break;
1230 case DistanceModel::Disable:
1231 ClampedDist = props->RefDistance;
1232 break;
1235 /* Calculate directional soundcones */
1236 if(directional && props->InnerAngle < 360.0f)
1238 ALfloat Angle{std::acos(aluDotproduct(&Direction, &SourceToListener))};
1239 Angle = RAD2DEG(Angle * ConeScale * 2.0f);
1241 ALfloat ConeVolume, ConeHF;
1242 if(!(Angle > props->InnerAngle))
1244 ConeVolume = 1.0f;
1245 ConeHF = 1.0f;
1247 else if(Angle < props->OuterAngle)
1249 ALfloat scale = ( Angle-props->InnerAngle) /
1250 (props->OuterAngle-props->InnerAngle);
1251 ConeVolume = lerp(1.0f, props->OuterGain, scale);
1252 ConeHF = lerp(1.0f, props->OuterGainHF, scale);
1254 else
1256 ConeVolume = props->OuterGain;
1257 ConeHF = props->OuterGainHF;
1260 DryGain *= ConeVolume;
1261 if(props->DryGainHFAuto)
1262 DryGainHF *= ConeHF;
1263 if(props->WetGainAuto)
1264 std::transform(std::begin(WetGain), std::begin(WetGain)+NumSends, std::begin(WetGain),
1265 [ConeVolume](ALfloat gain) noexcept -> ALfloat { return gain * ConeVolume; }
1267 if(props->WetGainHFAuto)
1268 std::transform(std::begin(WetGainHF), std::begin(WetGainHF)+NumSends,
1269 std::begin(WetGainHF),
1270 [ConeHF](ALfloat gain) noexcept -> ALfloat { return gain * ConeHF; }
1274 /* Apply gain and frequency filters */
1275 DryGain = clampf(DryGain, props->MinGain, props->MaxGain);
1276 DryGain = minf(DryGain*props->Direct.Gain*Listener.Params.Gain, GAIN_MIX_MAX);
1277 DryGainHF *= props->Direct.GainHF;
1278 DryGainLF *= props->Direct.GainLF;
1279 for(ALsizei i{0};i < NumSends;i++)
1281 WetGain[i] = clampf(WetGain[i], props->MinGain, props->MaxGain);
1282 WetGain[i] = minf(WetGain[i]*props->Send[i].Gain*Listener.Params.Gain, GAIN_MIX_MAX);
1283 WetGainHF[i] *= props->Send[i].GainHF;
1284 WetGainLF[i] *= props->Send[i].GainLF;
1287 /* Distance-based air absorption and initial send decay. */
1288 if(ClampedDist > props->RefDistance && props->RolloffFactor > 0.0f)
1290 ALfloat meters_base{(ClampedDist-props->RefDistance) * props->RolloffFactor *
1291 Listener.Params.MetersPerUnit};
1292 if(props->AirAbsorptionFactor > 0.0f)
1294 ALfloat hfattn{std::pow(AIRABSORBGAINHF, meters_base * props->AirAbsorptionFactor)};
1295 DryGainHF *= hfattn;
1296 std::transform(std::begin(WetGainHF), std::begin(WetGainHF)+NumSends,
1297 std::begin(WetGainHF),
1298 [hfattn](ALfloat gain) noexcept -> ALfloat { return gain * hfattn; }
1302 if(props->WetGainAuto)
1304 /* Apply a decay-time transformation to the wet path, based on the
1305 * source distance in meters. The initial decay of the reverb
1306 * effect is calculated and applied to the wet path.
1308 for(ALsizei i{0};i < NumSends;i++)
1310 if(!(DecayDistance[i] > 0.0f))
1311 continue;
1313 const ALfloat gain{std::pow(REVERB_DECAY_GAIN, meters_base/DecayDistance[i])};
1314 WetGain[i] *= gain;
1315 /* Yes, the wet path's air absorption is applied with
1316 * WetGainAuto on, rather than WetGainHFAuto.
1318 if(gain > 0.0f)
1320 ALfloat gainhf{std::pow(REVERB_DECAY_GAIN, meters_base/DecayHFDistance[i])};
1321 WetGainHF[i] *= minf(gainhf / gain, 1.0f);
1322 ALfloat gainlf{std::pow(REVERB_DECAY_GAIN, meters_base/DecayLFDistance[i])};
1323 WetGainLF[i] *= minf(gainlf / gain, 1.0f);
1330 /* Initial source pitch */
1331 ALfloat Pitch{props->Pitch};
1333 /* Calculate velocity-based doppler effect */
1334 ALfloat DopplerFactor{props->DopplerFactor * Listener.Params.DopplerFactor};
1335 if(DopplerFactor > 0.0f)
1337 const aluVector *lvelocity = &Listener.Params.Velocity;
1338 ALfloat vss{aluDotproduct(&Velocity, &SourceToListener) * DopplerFactor};
1339 ALfloat vls{aluDotproduct(lvelocity, &SourceToListener) * DopplerFactor};
1341 const ALfloat SpeedOfSound{Listener.Params.SpeedOfSound};
1342 if(!(vls < SpeedOfSound))
1344 /* Listener moving away from the source at the speed of sound.
1345 * Sound waves can't catch it.
1347 Pitch = 0.0f;
1349 else if(!(vss < SpeedOfSound))
1351 /* Source moving toward the listener at the speed of sound. Sound
1352 * waves bunch up to extreme frequencies.
1354 Pitch = HUGE_VALF;
1356 else
1358 /* Source and listener movement is nominal. Calculate the proper
1359 * doppler shift.
1361 Pitch *= (SpeedOfSound-vls) / (SpeedOfSound-vss);
1365 /* Adjust pitch based on the buffer and output frequencies, and calculate
1366 * fixed-point stepping value.
1368 Pitch *= (ALfloat)ALBuffer->Frequency/(ALfloat)Device->Frequency;
1369 if(Pitch > (ALfloat)MAX_PITCH)
1370 voice->Step = MAX_PITCH<<FRACTIONBITS;
1371 else
1372 voice->Step = maxi(fastf2i(Pitch * FRACTIONONE), 1);
1373 if(props->Resampler == BSinc24Resampler)
1374 BsincPrepare(voice->Step, &voice->ResampleState.bsinc, &bsinc24);
1375 else if(props->Resampler == BSinc12Resampler)
1376 BsincPrepare(voice->Step, &voice->ResampleState.bsinc, &bsinc12);
1377 voice->Resampler = SelectResampler(props->Resampler);
1379 ALfloat ev{0.0f}, az{0.0f};
1380 if(Distance > 0.0f)
1382 /* Clamp Y, in case rounding errors caused it to end up outside of
1383 * -1...+1.
1385 ev = std::asin(clampf(-SourceToListener.v[1], -1.0f, 1.0f));
1386 /* Double negation on Z cancels out; negate once for changing source-
1387 * to-listener to listener-to-source, and again for right-handed coords
1388 * with -Z in front.
1390 az = std::atan2(-SourceToListener.v[0], SourceToListener.v[2]*ZScale);
1393 ALfloat spread{0.0f};
1394 if(props->Radius > Distance)
1395 spread = F_TAU - Distance/props->Radius*F_PI;
1396 else if(Distance > 0.0f)
1397 spread = std::asin(props->Radius/Distance) * 2.0f;
1399 CalcPanningAndFilters(voice, az, ev, Distance, spread, DryGain, DryGainHF, DryGainLF, WetGain,
1400 WetGainLF, WetGainHF, SendSlots, ALBuffer, props, Listener, Device);
1403 void CalcSourceParams(ALvoice *voice, ALCcontext *context, bool force)
1405 ALvoiceProps *props{voice->Update.exchange(nullptr, std::memory_order_acq_rel)};
1406 if(!props && !force) return;
1408 if(props)
1410 voice->Props = *props;
1412 AtomicReplaceHead(context->FreeVoiceProps, props);
1415 ALbufferlistitem *BufferListItem{voice->current_buffer.load(std::memory_order_relaxed)};
1416 while(BufferListItem)
1418 auto buffers_end = BufferListItem->buffers+BufferListItem->num_buffers;
1419 auto buffer = std::find_if(BufferListItem->buffers, buffers_end,
1420 [](const ALbuffer *buffer) noexcept -> bool
1421 { return buffer != nullptr; }
1423 if(LIKELY(buffer != buffers_end))
1425 if(voice->Props.SpatializeMode==SpatializeOn ||
1426 (voice->Props.SpatializeMode==SpatializeAuto && (*buffer)->FmtChannels==FmtMono))
1427 CalcAttnSourceParams(voice, &voice->Props, *buffer, context);
1428 else
1429 CalcNonAttnSourceParams(voice, &voice->Props, *buffer, context);
1430 break;
1432 BufferListItem = BufferListItem->next.load(std::memory_order_acquire);
1437 void ProcessParamUpdates(ALCcontext *ctx, const ALeffectslotArray *slots)
1439 IncrementRef(&ctx->UpdateCount);
1440 if(LIKELY(!ctx->HoldUpdates.load(std::memory_order_acquire)))
1442 bool cforce{CalcContextParams(ctx)};
1443 bool force{CalcListenerParams(ctx) || cforce};
1444 std::for_each(slots->slot, slots->slot+slots->count,
1445 [ctx,cforce,&force](ALeffectslot *slot) -> void
1446 { force |= CalcEffectSlotParams(slot, ctx, cforce); }
1449 std::for_each(ctx->Voices, ctx->Voices+ctx->VoiceCount.load(std::memory_order_acquire),
1450 [ctx,force](ALvoice *voice) -> void
1452 ALuint sid{voice->SourceID.load(std::memory_order_acquire)};
1453 if(sid) CalcSourceParams(voice, ctx, force);
1457 IncrementRef(&ctx->UpdateCount);
1460 void ProcessContext(ALCcontext *ctx, ALsizei SamplesToDo)
1462 const ALeffectslotArray *auxslots{ctx->ActiveAuxSlots.load(std::memory_order_acquire)};
1464 /* Process pending propery updates for objects on the context. */
1465 ProcessParamUpdates(ctx, auxslots);
1467 /* Clear auxiliary effect slot mixing buffers. */
1468 std::for_each(auxslots->slot, auxslots->slot+auxslots->count,
1469 [SamplesToDo](ALeffectslot *slot) -> void
1471 std::for_each(slot->WetBuffer, slot->WetBuffer+slot->NumChannels,
1472 [SamplesToDo](ALfloat *buffer) -> void
1473 { std::fill_n(buffer, SamplesToDo, 0.0f); }
1478 /* Process voices that have a playing source. */
1479 std::for_each(ctx->Voices, ctx->Voices+ctx->VoiceCount.load(std::memory_order_acquire),
1480 [SamplesToDo,ctx](ALvoice *voice) -> void
1482 if(!voice->Playing.load(std::memory_order_acquire)) return;
1483 ALuint sid{voice->SourceID.load(std::memory_order_relaxed)};
1484 if(!sid || voice->Step < 1) return;
1486 if(!MixSource(voice, sid, ctx, SamplesToDo))
1488 voice->SourceID.store(0u, std::memory_order_relaxed);
1489 voice->Playing.store(false, std::memory_order_release);
1490 SendSourceStoppedEvent(ctx, sid);
1495 /* Process effects. */
1496 std::for_each(auxslots->slot, auxslots->slot+auxslots->count,
1497 [SamplesToDo](const ALeffectslot *slot) -> void
1499 EffectState *state{slot->Params.mEffectState};
1500 state->process(SamplesToDo, slot->WetBuffer, state->mOutBuffer,
1501 state->mOutChannels);
1507 void ApplyStablizer(FrontStablizer *Stablizer, ALfloat (*RESTRICT Buffer)[BUFFERSIZE],
1508 int lidx, int ridx, int cidx, ALsizei SamplesToDo, ALsizei NumChannels)
1510 /* Apply an all-pass to all channels, except the front-left and front-
1511 * right, so they maintain the same relative phase.
1513 for(ALsizei i{0};i < NumChannels;i++)
1515 if(i == lidx || i == ridx)
1516 continue;
1517 Stablizer->APFilter[i].process(Buffer[i], SamplesToDo);
1520 ALfloat (*RESTRICT lsplit)[BUFFERSIZE]{Stablizer->LSplit};
1521 ALfloat (*RESTRICT rsplit)[BUFFERSIZE]{Stablizer->RSplit};
1522 Stablizer->LFilter.process(lsplit[1], lsplit[0], Buffer[lidx], SamplesToDo);
1523 Stablizer->RFilter.process(rsplit[1], rsplit[0], Buffer[ridx], SamplesToDo);
1525 for(ALsizei i{0};i < SamplesToDo;i++)
1527 ALfloat lfsum{lsplit[0][i] + rsplit[0][i]};
1528 ALfloat hfsum{lsplit[1][i] + rsplit[1][i]};
1529 ALfloat s{lsplit[0][i] + lsplit[1][i] - rsplit[0][i] - rsplit[1][i]};
1531 /* This pans the separate low- and high-frequency sums between being on
1532 * the center channel and the left/right channels. The low-frequency
1533 * sum is 1/3rd toward center (2/3rds on left/right) and the high-
1534 * frequency sum is 1/4th toward center (3/4ths on left/right). These
1535 * values can be tweaked.
1537 ALfloat m{lfsum*std::cos(1.0f/3.0f * F_PI_2) + hfsum*std::cos(1.0f/4.0f * F_PI_2)};
1538 ALfloat c{lfsum*std::sin(1.0f/3.0f * F_PI_2) + hfsum*std::sin(1.0f/4.0f * F_PI_2)};
1540 /* The generated center channel signal adds to the existing signal,
1541 * while the modified left and right channels replace.
1543 Buffer[lidx][i] = (m + s) * 0.5f;
1544 Buffer[ridx][i] = (m - s) * 0.5f;
1545 Buffer[cidx][i] += c * 0.5f;
1549 void ApplyDistanceComp(ALfloat (*RESTRICT Samples)[BUFFERSIZE], const DistanceComp &distcomp,
1550 ALfloat *RESTRICT Values, ALsizei SamplesToDo, ALsizei numchans)
1552 for(ALsizei c{0};c < numchans;c++)
1554 ALfloat *RESTRICT inout{Samples[c]};
1555 const ALfloat gain{distcomp[c].Gain};
1556 const ALsizei base{distcomp[c].Length};
1557 ALfloat *RESTRICT distbuf{distcomp[c].Buffer};
1559 if(base == 0)
1561 if(gain < 1.0f)
1562 std::for_each(inout, inout+SamplesToDo,
1563 [gain](ALfloat &in) noexcept -> void
1564 { in *= gain; }
1566 continue;
1569 if(LIKELY(SamplesToDo >= base))
1571 auto out = std::copy_n(distbuf, base, Values);
1572 std::copy_n(inout, SamplesToDo-base, out);
1573 std::copy_n(inout+SamplesToDo-base, base, distbuf);
1575 else
1577 std::copy_n(distbuf, SamplesToDo, Values);
1578 auto out = std::copy(distbuf+SamplesToDo, distbuf+base, distbuf);
1579 std::copy_n(inout, SamplesToDo, out);
1581 std::transform<ALfloat*RESTRICT>(Values, Values+SamplesToDo, inout,
1582 [gain](ALfloat in) noexcept -> ALfloat { return in * gain; }
1587 void ApplyDither(ALfloat (*RESTRICT Samples)[BUFFERSIZE], ALuint *dither_seed,
1588 const ALfloat quant_scale, const ALsizei SamplesToDo, const ALsizei numchans)
1590 ASSUME(numchans > 0);
1592 /* Dithering. Generate whitenoise (uniform distribution of random values
1593 * between -1 and +1) and add it to the sample values, after scaling up to
1594 * the desired quantization depth amd before rounding.
1596 const ALfloat invscale{1.0f / quant_scale};
1597 ALuint seed{*dither_seed};
1598 auto dither_channel = [&seed,invscale,quant_scale,SamplesToDo](ALfloat *buffer) -> void
1600 ASSUME(SamplesToDo > 0);
1601 std::transform(buffer, buffer+SamplesToDo, buffer,
1602 [&seed,invscale,quant_scale](ALfloat sample) noexcept -> ALfloat
1604 ALfloat val = sample * quant_scale;
1605 ALuint rng0 = dither_rng(&seed);
1606 ALuint rng1 = dither_rng(&seed);
1607 val += (ALfloat)(rng0*(1.0/UINT_MAX) - rng1*(1.0/UINT_MAX));
1608 return fast_roundf(val) * invscale;
1612 std::for_each(Samples, Samples+numchans, dither_channel);
1613 *dither_seed = seed;
1617 /* Base template left undefined. Should be marked =delete, but Clang 3.8.1
1618 * chokes on that given the inline specializations.
1620 template<typename T>
1621 inline T SampleConv(ALfloat) noexcept;
1623 template<> inline ALfloat SampleConv(ALfloat val) noexcept
1624 { return val; }
1625 template<> inline ALint SampleConv(ALfloat val) noexcept
1627 /* Floats have a 23-bit mantissa. There is an implied 1 bit in the mantissa
1628 * along with the sign bit, giving 25 bits total, so [-16777216, +16777216]
1629 * is the max value a normalized float can be scaled to before losing
1630 * precision.
1632 return fastf2i(clampf(val*16777216.0f, -16777216.0f, 16777215.0f))<<7;
1634 template<> inline ALshort SampleConv(ALfloat val) noexcept
1635 { return fastf2i(clampf(val*32768.0f, -32768.0f, 32767.0f)); }
1636 template<> inline ALbyte SampleConv(ALfloat val) noexcept
1637 { return fastf2i(clampf(val*128.0f, -128.0f, 127.0f)); }
1639 /* Define unsigned output variations. */
1640 template<> inline ALuint SampleConv(ALfloat val) noexcept
1641 { return SampleConv<ALint>(val) + 2147483648u; }
1642 template<> inline ALushort SampleConv(ALfloat val) noexcept
1643 { return SampleConv<ALshort>(val) + 32768; }
1644 template<> inline ALubyte SampleConv(ALfloat val) noexcept
1645 { return SampleConv<ALbyte>(val) + 128; }
1647 template<DevFmtType T>
1648 void Write(const ALfloat (*InBuffer)[BUFFERSIZE], ALvoid *OutBuffer, ALsizei Offset,
1649 ALsizei SamplesToDo, ALsizei numchans)
1651 using SampleType = typename DevFmtTypeTraits<T>::Type;
1653 ASSUME(numchans > 0);
1654 SampleType *outbase = static_cast<SampleType*>(OutBuffer) + Offset*numchans;
1655 auto conv_channel = [&outbase,SamplesToDo,numchans](const ALfloat *inbuf) -> void
1657 ASSUME(SamplesToDo > 0);
1658 SampleType *out{outbase++};
1659 std::for_each<const ALfloat*RESTRICT>(inbuf, inbuf+SamplesToDo,
1660 [numchans,&out](const ALfloat s) noexcept -> void
1662 *out = SampleConv<SampleType>(s);
1663 out += numchans;
1667 std::for_each(InBuffer, InBuffer+numchans, conv_channel);
1670 } // namespace
1672 void aluMixData(ALCdevice *device, ALvoid *OutBuffer, ALsizei NumSamples)
1674 FPUCtl mixer_mode{};
1675 for(ALsizei SamplesDone{0};SamplesDone < NumSamples;)
1677 const ALsizei SamplesToDo{mini(NumSamples-SamplesDone, BUFFERSIZE)};
1679 /* Clear main mixing buffers. */
1680 std::for_each(device->MixBuffer.begin(), device->MixBuffer.end(),
1681 [SamplesToDo](std::array<ALfloat,BUFFERSIZE> &buffer) -> void
1682 { std::fill_n(buffer.begin(), SamplesToDo, 0.0f); }
1685 /* Increment the mix count at the start (lsb should now be 1). */
1686 IncrementRef(&device->MixCount);
1688 /* For each context on this device, process and mix its sources and
1689 * effects.
1691 ALCcontext *ctx{device->ContextList.load(std::memory_order_acquire)};
1692 while(ctx)
1694 ProcessContext(ctx, SamplesToDo);
1696 ctx = ctx->next.load(std::memory_order_relaxed);
1699 /* Increment the clock time. Every second's worth of samples is
1700 * converted and added to clock base so that large sample counts don't
1701 * overflow during conversion. This also guarantees a stable
1702 * conversion.
1704 device->SamplesDone += SamplesToDo;
1705 device->ClockBase += std::chrono::seconds{device->SamplesDone / device->Frequency};
1706 device->SamplesDone %= device->Frequency;
1708 /* Increment the mix count at the end (lsb should now be 0). */
1709 IncrementRef(&device->MixCount);
1711 /* Apply any needed post-process for finalizing the Dry mix to the
1712 * RealOut (Ambisonic decode, UHJ encode, etc).
1714 if(LIKELY(device->PostProcess))
1715 device->PostProcess(device, SamplesToDo);
1717 /* Apply front image stablization for surround sound, if applicable. */
1718 if(device->Stablizer)
1720 const int lidx{GetChannelIdxByName(&device->RealOut, FrontLeft)};
1721 const int ridx{GetChannelIdxByName(&device->RealOut, FrontRight)};
1722 const int cidx{GetChannelIdxByName(&device->RealOut, FrontCenter)};
1723 assert(lidx >= 0 && ridx >= 0 && cidx >= 0);
1725 ApplyStablizer(device->Stablizer.get(), device->RealOut.Buffer, lidx, ridx, cidx,
1726 SamplesToDo, device->RealOut.NumChannels);
1729 /* Apply delays and attenuation for mismatched speaker distances. */
1730 ApplyDistanceComp(device->RealOut.Buffer, device->ChannelDelay, device->TempBuffer[0],
1731 SamplesToDo, device->RealOut.NumChannels);
1733 /* Apply compression, limiting final sample amplitude, if desired. */
1734 if(device->Limiter)
1735 ApplyCompression(device->Limiter.get(), SamplesToDo, device->RealOut.Buffer);
1737 /* Apply dithering. The compressor should have left enough headroom for
1738 * the dither noise to not saturate.
1740 if(device->DitherDepth > 0.0f)
1741 ApplyDither(device->RealOut.Buffer, &device->DitherSeed, device->DitherDepth,
1742 SamplesToDo, device->RealOut.NumChannels);
1744 if(LIKELY(OutBuffer))
1746 ALfloat (*Buffer)[BUFFERSIZE]{device->RealOut.Buffer};
1747 ALsizei Channels{device->RealOut.NumChannels};
1749 /* Finally, interleave and convert samples, writing to the device's
1750 * output buffer.
1752 switch(device->FmtType)
1754 #define HANDLE_WRITE(T) case T: \
1755 Write<T>(Buffer, OutBuffer, SamplesDone, SamplesToDo, Channels); break;
1756 HANDLE_WRITE(DevFmtByte)
1757 HANDLE_WRITE(DevFmtUByte)
1758 HANDLE_WRITE(DevFmtShort)
1759 HANDLE_WRITE(DevFmtUShort)
1760 HANDLE_WRITE(DevFmtInt)
1761 HANDLE_WRITE(DevFmtUInt)
1762 HANDLE_WRITE(DevFmtFloat)
1763 #undef HANDLE_WRITE
1767 SamplesDone += SamplesToDo;
1772 void aluHandleDisconnect(ALCdevice *device, const char *msg, ...)
1774 if(!device->Connected.exchange(AL_FALSE, std::memory_order_acq_rel))
1775 return;
1777 AsyncEvent evt{EventType_Disconnected};
1778 evt.u.user.type = AL_EVENT_TYPE_DISCONNECTED_SOFT;
1779 evt.u.user.id = 0;
1780 evt.u.user.param = 0;
1782 va_list args;
1783 va_start(args, msg);
1784 int msglen{vsnprintf(evt.u.user.msg, sizeof(evt.u.user.msg), msg, args)};
1785 va_end(args);
1787 if(msglen < 0 || (size_t)msglen >= sizeof(evt.u.user.msg))
1788 evt.u.user.msg[sizeof(evt.u.user.msg)-1] = 0;
1790 ALCcontext *ctx{device->ContextList.load()};
1791 while(ctx)
1793 const ALbitfieldSOFT enabledevt{ctx->EnabledEvts.load(std::memory_order_acquire)};
1794 if((enabledevt&EventType_Disconnected) &&
1795 ll_ringbuffer_write(ctx->AsyncEvents, &evt, 1) == 1)
1796 ctx->EventSem.post();
1798 std::for_each(ctx->Voices, ctx->Voices+ctx->VoiceCount.load(std::memory_order_acquire),
1799 [ctx](ALvoice *voice) -> void
1801 if(!voice->Playing.load(std::memory_order_acquire)) return;
1802 ALuint sid{voice->SourceID.load(std::memory_order_relaxed)};
1803 if(!sid) return;
1805 voice->SourceID.store(0u, std::memory_order_relaxed);
1806 voice->Playing.store(false, std::memory_order_release);
1807 /* If the source's voice was playing, it's now effectively
1808 * stopped (the source state will be updated the next time it's
1809 * checked).
1811 SendSourceStoppedEvent(ctx, sid);
1815 ctx = ctx->next.load(std::memory_order_relaxed);