Avoid a few more explicit loops
[openal-soft.git] / Alc / alu.cpp
blobde46633a4b3a813cda202ebff8ca366f78cb51c6
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 <algorithm>
31 #include "alMain.h"
32 #include "alcontext.h"
33 #include "alSource.h"
34 #include "alBuffer.h"
35 #include "alListener.h"
36 #include "alAuxEffectSlot.h"
37 #include "alu.h"
38 #include "bs2b.h"
39 #include "hrtf.h"
40 #include "mastering.h"
41 #include "uhjfilter.h"
42 #include "bformatdec.h"
43 #include "ringbuffer.h"
44 #include "filters/splitter.h"
46 #include "mixer/defs.h"
47 #include "fpu_modes.h"
48 #include "cpu_caps.h"
49 #include "bsinc_inc.h"
52 /* Cone scalar */
53 ALfloat ConeScale = 1.0f;
55 /* Localized Z scalar for mono sources */
56 ALfloat ZScale = 1.0f;
58 /* Force default speed of sound for distance-related reverb decay. */
59 ALboolean OverrideReverbSpeedOfSound = AL_FALSE;
62 namespace {
64 void ClearArray(ALfloat (&f)[MAX_OUTPUT_CHANNELS])
66 std::fill(std::begin(f), std::end(f), 0.0f);
69 struct ChanMap {
70 enum Channel channel;
71 ALfloat angle;
72 ALfloat elevation;
75 HrtfDirectMixerFunc MixDirectHrtf = MixDirectHrtf_C;
78 inline HrtfDirectMixerFunc SelectHrtfMixer(void)
80 #ifdef HAVE_NEON
81 if((CPUCapFlags&CPU_CAP_NEON))
82 return MixDirectHrtf_Neon;
83 #endif
84 #ifdef HAVE_SSE
85 if((CPUCapFlags&CPU_CAP_SSE))
86 return MixDirectHrtf_SSE;
87 #endif
89 return MixDirectHrtf_C;
92 } // namespace
94 void aluInit(void)
96 MixDirectHrtf = SelectHrtfMixer();
100 void DeinitVoice(ALvoice *voice) noexcept
102 delete voice->Update.exchange(nullptr, std::memory_order_acq_rel);
103 voice->~ALvoice();
107 namespace {
109 void ProcessHrtf(ALCdevice *device, ALsizei SamplesToDo)
111 if(device->AmbiUp)
112 ambiup_process(device->AmbiUp.get(),
113 device->Dry.Buffer, device->Dry.NumChannels, device->FOAOut.Buffer,
114 SamplesToDo
117 int lidx{GetChannelIdxByName(&device->RealOut, FrontLeft)};
118 int ridx{GetChannelIdxByName(&device->RealOut, FrontRight)};
119 assert(lidx != -1 && ridx != -1);
121 DirectHrtfState *state{device->mHrtfState.get()};
122 for(ALsizei c{0};c < device->Dry.NumChannels;c++)
124 MixDirectHrtf(device->RealOut.Buffer[lidx], device->RealOut.Buffer[ridx],
125 device->Dry.Buffer[c], state->Offset, state->IrSize,
126 state->Chan[c].Coeffs, state->Chan[c].Values, SamplesToDo
129 state->Offset += SamplesToDo;
132 void ProcessAmbiDec(ALCdevice *device, ALsizei SamplesToDo)
134 if(device->Dry.Buffer != device->FOAOut.Buffer)
135 bformatdec_upSample(device->AmbiDecoder.get(),
136 device->Dry.Buffer, device->FOAOut.Buffer, device->FOAOut.NumChannels,
137 SamplesToDo
139 bformatdec_process(device->AmbiDecoder.get(),
140 device->RealOut.Buffer, device->RealOut.NumChannels, device->Dry.Buffer,
141 SamplesToDo
145 void ProcessAmbiUp(ALCdevice *device, ALsizei SamplesToDo)
147 ambiup_process(device->AmbiUp.get(),
148 device->RealOut.Buffer, device->RealOut.NumChannels, device->FOAOut.Buffer,
149 SamplesToDo
153 void ProcessUhj(ALCdevice *device, ALsizei SamplesToDo)
155 int lidx = GetChannelIdxByName(&device->RealOut, FrontLeft);
156 int ridx = GetChannelIdxByName(&device->RealOut, FrontRight);
157 assert(lidx != -1 && ridx != -1);
159 /* Encode to stereo-compatible 2-channel UHJ output. */
160 EncodeUhj2(device->Uhj_Encoder.get(),
161 device->RealOut.Buffer[lidx], device->RealOut.Buffer[ridx],
162 device->Dry.Buffer, SamplesToDo
166 void ProcessBs2b(ALCdevice *device, ALsizei SamplesToDo)
168 int lidx = GetChannelIdxByName(&device->RealOut, FrontLeft);
169 int ridx = GetChannelIdxByName(&device->RealOut, FrontRight);
170 assert(lidx != -1 && ridx != -1);
172 /* Apply binaural/crossfeed filter */
173 bs2b_cross_feed(device->Bs2b.get(), device->RealOut.Buffer[lidx],
174 device->RealOut.Buffer[ridx], SamplesToDo);
177 } // namespace
179 void aluSelectPostProcess(ALCdevice *device)
181 if(device->HrtfHandle)
182 device->PostProcess = ProcessHrtf;
183 else if(device->AmbiDecoder)
184 device->PostProcess = ProcessAmbiDec;
185 else if(device->AmbiUp)
186 device->PostProcess = ProcessAmbiUp;
187 else if(device->Uhj_Encoder)
188 device->PostProcess = ProcessUhj;
189 else if(device->Bs2b)
190 device->PostProcess = ProcessBs2b;
191 else
192 device->PostProcess = NULL;
196 /* Prepares the interpolator for a given rate (determined by increment).
198 * With a bit of work, and a trade of memory for CPU cost, this could be
199 * modified for use with an interpolated increment for buttery-smooth pitch
200 * changes.
202 void BsincPrepare(const ALuint increment, BsincState *state, const BSincTable *table)
204 ALfloat sf = 0.0f;
205 ALsizei si = BSINC_SCALE_COUNT-1;
207 if(increment > FRACTIONONE)
209 sf = (ALfloat)FRACTIONONE / increment;
210 sf = maxf(0.0f, (BSINC_SCALE_COUNT-1) * (sf-table->scaleBase) * table->scaleRange);
211 si = float2int(sf);
212 /* The interpolation factor is fit to this diagonally-symmetric curve
213 * to reduce the transition ripple caused by interpolating different
214 * scales of the sinc function.
216 sf = 1.0f - cosf(asinf(sf - si));
219 state->sf = sf;
220 state->m = table->m[si];
221 state->l = (state->m/2) - 1;
222 state->filter = table->Tab + table->filterOffset[si];
226 namespace {
228 /* This RNG method was created based on the math found in opusdec. It's quick,
229 * and starting with a seed value of 22222, is suitable for generating
230 * whitenoise.
232 inline ALuint dither_rng(ALuint *seed) noexcept
234 *seed = (*seed * 96314165) + 907633515;
235 return *seed;
239 inline void aluCrossproduct(const ALfloat *inVector1, const ALfloat *inVector2, ALfloat *outVector)
241 outVector[0] = inVector1[1]*inVector2[2] - inVector1[2]*inVector2[1];
242 outVector[1] = inVector1[2]*inVector2[0] - inVector1[0]*inVector2[2];
243 outVector[2] = inVector1[0]*inVector2[1] - inVector1[1]*inVector2[0];
246 inline ALfloat aluDotproduct(const aluVector *vec1, const aluVector *vec2)
248 return vec1->v[0]*vec2->v[0] + vec1->v[1]*vec2->v[1] + vec1->v[2]*vec2->v[2];
251 ALfloat aluNormalize(ALfloat *vec)
253 ALfloat length = sqrtf(vec[0]*vec[0] + vec[1]*vec[1] + vec[2]*vec[2]);
254 if(length > FLT_EPSILON)
256 ALfloat inv_length = 1.0f/length;
257 vec[0] *= inv_length;
258 vec[1] *= inv_length;
259 vec[2] *= inv_length;
260 return length;
262 vec[0] = vec[1] = vec[2] = 0.0f;
263 return 0.0f;
266 void aluMatrixfFloat3(ALfloat *vec, ALfloat w, const aluMatrixf *mtx)
268 ALfloat v[4] = { vec[0], vec[1], vec[2], w };
270 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];
271 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];
272 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];
275 aluVector aluMatrixfVector(const aluMatrixf *mtx, const aluVector *vec)
277 aluVector v;
278 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];
279 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];
280 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];
281 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];
282 return v;
286 void SendSourceStoppedEvent(ALCcontext *context, ALuint id)
288 ALbitfieldSOFT enabledevt{context->EnabledEvts.load(std::memory_order_acquire)};
289 if(!(enabledevt&EventType_SourceStateChange)) return;
291 AsyncEvent evt{ASYNC_EVENT(EventType_SourceStateChange)};
292 evt.u.srcstate.id = id;
293 evt.u.srcstate.state = AL_STOPPED;
295 if(ll_ringbuffer_write(context->AsyncEvents, &evt, 1) == 1)
296 context->EventSem.post();
300 bool CalcContextParams(ALCcontext *Context)
302 ALlistener &Listener = Context->Listener;
303 struct ALcontextProps *props;
305 props = Context->Update.exchange(nullptr, std::memory_order_acq_rel);
306 if(!props) return false;
308 Listener.Params.MetersPerUnit = props->MetersPerUnit;
310 Listener.Params.DopplerFactor = props->DopplerFactor;
311 Listener.Params.SpeedOfSound = props->SpeedOfSound * props->DopplerVelocity;
312 if(!OverrideReverbSpeedOfSound)
313 Listener.Params.ReverbSpeedOfSound = Listener.Params.SpeedOfSound *
314 Listener.Params.MetersPerUnit;
316 Listener.Params.SourceDistanceModel = props->SourceDistanceModel;
317 Listener.Params.mDistanceModel = props->mDistanceModel;
319 AtomicReplaceHead(Context->FreeContextProps, props);
320 return true;
323 bool CalcListenerParams(ALCcontext *Context)
325 ALlistener &Listener = Context->Listener;
326 ALfloat N[3], V[3], U[3], P[3];
327 struct ALlistenerProps *props;
328 aluVector vel;
330 props = Listener.Update.exchange(nullptr, std::memory_order_acq_rel);
331 if(!props) return false;
333 /* AT then UP */
334 N[0] = props->Forward[0];
335 N[1] = props->Forward[1];
336 N[2] = props->Forward[2];
337 aluNormalize(N);
338 V[0] = props->Up[0];
339 V[1] = props->Up[1];
340 V[2] = props->Up[2];
341 aluNormalize(V);
342 /* Build and normalize right-vector */
343 aluCrossproduct(N, V, U);
344 aluNormalize(U);
346 aluMatrixfSet(&Listener.Params.Matrix,
347 U[0], V[0], -N[0], 0.0,
348 U[1], V[1], -N[1], 0.0,
349 U[2], V[2], -N[2], 0.0,
350 0.0, 0.0, 0.0, 1.0
353 P[0] = props->Position[0];
354 P[1] = props->Position[1];
355 P[2] = props->Position[2];
356 aluMatrixfFloat3(P, 1.0, &Listener.Params.Matrix);
357 aluMatrixfSetRow(&Listener.Params.Matrix, 3, -P[0], -P[1], -P[2], 1.0f);
359 aluVectorSet(&vel, props->Velocity[0], props->Velocity[1], props->Velocity[2], 0.0f);
360 Listener.Params.Velocity = aluMatrixfVector(&Listener.Params.Matrix, &vel);
362 Listener.Params.Gain = props->Gain * Context->GainBoost;
364 AtomicReplaceHead(Context->FreeListenerProps, props);
365 return true;
368 bool CalcEffectSlotParams(ALeffectslot *slot, ALCcontext *context, bool force)
370 struct ALeffectslotProps *props;
371 EffectState *state;
373 props = slot->Update.exchange(nullptr, std::memory_order_acq_rel);
374 if(!props && !force) return false;
376 if(props)
378 slot->Params.Gain = props->Gain;
379 slot->Params.AuxSendAuto = props->AuxSendAuto;
380 slot->Params.EffectType = props->Type;
381 slot->Params.EffectProps = props->Props;
382 if(IsReverbEffect(props->Type))
384 slot->Params.RoomRolloff = props->Props.Reverb.RoomRolloffFactor;
385 slot->Params.DecayTime = props->Props.Reverb.DecayTime;
386 slot->Params.DecayLFRatio = props->Props.Reverb.DecayLFRatio;
387 slot->Params.DecayHFRatio = props->Props.Reverb.DecayHFRatio;
388 slot->Params.DecayHFLimit = props->Props.Reverb.DecayHFLimit;
389 slot->Params.AirAbsorptionGainHF = props->Props.Reverb.AirAbsorptionGainHF;
391 else
393 slot->Params.RoomRolloff = 0.0f;
394 slot->Params.DecayTime = 0.0f;
395 slot->Params.DecayLFRatio = 0.0f;
396 slot->Params.DecayHFRatio = 0.0f;
397 slot->Params.DecayHFLimit = AL_FALSE;
398 slot->Params.AirAbsorptionGainHF = 1.0f;
401 state = props->State;
403 if(state == slot->Params.mEffectState)
405 /* If the effect state is the same as current, we can decrement its
406 * count safely to remove it from the update object (it can't reach
407 * 0 refs since the current params also hold a reference).
409 DecrementRef(&state->mRef);
410 props->State = nullptr;
412 else
414 /* Otherwise, replace it and send off the old one with a release
415 * event.
417 AsyncEvent evt = ASYNC_EVENT(EventType_ReleaseEffectState);
418 evt.u.mEffectState = slot->Params.mEffectState;
420 slot->Params.mEffectState = state;
421 props->State = NULL;
423 if(LIKELY(ll_ringbuffer_write(context->AsyncEvents, &evt, 1) != 0))
424 context->EventSem.post();
425 else
427 /* If writing the event failed, the queue was probably full.
428 * Store the old state in the property object where it can
429 * eventually be cleaned up sometime later (not ideal, but
430 * better than blocking or leaking).
432 props->State = evt.u.mEffectState;
436 AtomicReplaceHead(context->FreeEffectslotProps, props);
438 else
439 state = slot->Params.mEffectState;
441 state->update(context, slot, &slot->Params.EffectProps);
442 return true;
446 constexpr struct ChanMap MonoMap[1] = {
447 { FrontCenter, 0.0f, 0.0f }
448 }, RearMap[2] = {
449 { BackLeft, DEG2RAD(-150.0f), DEG2RAD(0.0f) },
450 { BackRight, DEG2RAD( 150.0f), DEG2RAD(0.0f) }
451 }, QuadMap[4] = {
452 { FrontLeft, DEG2RAD( -45.0f), DEG2RAD(0.0f) },
453 { FrontRight, DEG2RAD( 45.0f), DEG2RAD(0.0f) },
454 { BackLeft, DEG2RAD(-135.0f), DEG2RAD(0.0f) },
455 { BackRight, DEG2RAD( 135.0f), DEG2RAD(0.0f) }
456 }, X51Map[6] = {
457 { FrontLeft, DEG2RAD( -30.0f), DEG2RAD(0.0f) },
458 { FrontRight, DEG2RAD( 30.0f), DEG2RAD(0.0f) },
459 { FrontCenter, DEG2RAD( 0.0f), DEG2RAD(0.0f) },
460 { LFE, 0.0f, 0.0f },
461 { SideLeft, DEG2RAD(-110.0f), DEG2RAD(0.0f) },
462 { SideRight, DEG2RAD( 110.0f), DEG2RAD(0.0f) }
463 }, X61Map[7] = {
464 { FrontLeft, DEG2RAD(-30.0f), DEG2RAD(0.0f) },
465 { FrontRight, DEG2RAD( 30.0f), DEG2RAD(0.0f) },
466 { FrontCenter, DEG2RAD( 0.0f), DEG2RAD(0.0f) },
467 { LFE, 0.0f, 0.0f },
468 { BackCenter, DEG2RAD(180.0f), DEG2RAD(0.0f) },
469 { SideLeft, DEG2RAD(-90.0f), DEG2RAD(0.0f) },
470 { SideRight, DEG2RAD( 90.0f), DEG2RAD(0.0f) }
471 }, X71Map[8] = {
472 { FrontLeft, DEG2RAD( -30.0f), DEG2RAD(0.0f) },
473 { FrontRight, DEG2RAD( 30.0f), DEG2RAD(0.0f) },
474 { FrontCenter, DEG2RAD( 0.0f), DEG2RAD(0.0f) },
475 { LFE, 0.0f, 0.0f },
476 { BackLeft, DEG2RAD(-150.0f), DEG2RAD(0.0f) },
477 { BackRight, DEG2RAD( 150.0f), DEG2RAD(0.0f) },
478 { SideLeft, DEG2RAD( -90.0f), DEG2RAD(0.0f) },
479 { SideRight, DEG2RAD( 90.0f), DEG2RAD(0.0f) }
482 void CalcPanningAndFilters(ALvoice *voice, const ALfloat Azi, const ALfloat Elev,
483 const ALfloat Distance, const ALfloat Spread,
484 const ALfloat DryGain, const ALfloat DryGainHF,
485 const ALfloat DryGainLF, const ALfloat *WetGain,
486 const ALfloat *WetGainLF, const ALfloat *WetGainHF,
487 ALeffectslot **SendSlots, const ALbuffer *Buffer,
488 const ALvoicePropsBase *props, const ALlistener &Listener,
489 const ALCdevice *Device)
491 struct ChanMap StereoMap[2] = {
492 { FrontLeft, DEG2RAD(-30.0f), DEG2RAD(0.0f) },
493 { FrontRight, DEG2RAD( 30.0f), DEG2RAD(0.0f) }
495 bool DirectChannels = props->DirectChannels;
496 const ALsizei NumSends = Device->NumAuxSends;
497 const ALuint Frequency = Device->Frequency;
498 const struct ChanMap *chans = NULL;
499 ALsizei num_channels = 0;
500 bool isbformat = false;
501 ALfloat downmix_gain = 1.0f;
502 ALsizei c, i;
504 switch(Buffer->FmtChannels)
506 case FmtMono:
507 chans = MonoMap;
508 num_channels = 1;
509 /* Mono buffers are never played direct. */
510 DirectChannels = false;
511 break;
513 case FmtStereo:
514 /* Convert counter-clockwise to clockwise. */
515 StereoMap[0].angle = -props->StereoPan[0];
516 StereoMap[1].angle = -props->StereoPan[1];
518 chans = StereoMap;
519 num_channels = 2;
520 downmix_gain = 1.0f / 2.0f;
521 break;
523 case FmtRear:
524 chans = RearMap;
525 num_channels = 2;
526 downmix_gain = 1.0f / 2.0f;
527 break;
529 case FmtQuad:
530 chans = QuadMap;
531 num_channels = 4;
532 downmix_gain = 1.0f / 4.0f;
533 break;
535 case FmtX51:
536 chans = X51Map;
537 num_channels = 6;
538 /* NOTE: Excludes LFE. */
539 downmix_gain = 1.0f / 5.0f;
540 break;
542 case FmtX61:
543 chans = X61Map;
544 num_channels = 7;
545 /* NOTE: Excludes LFE. */
546 downmix_gain = 1.0f / 6.0f;
547 break;
549 case FmtX71:
550 chans = X71Map;
551 num_channels = 8;
552 /* NOTE: Excludes LFE. */
553 downmix_gain = 1.0f / 7.0f;
554 break;
556 case FmtBFormat2D:
557 num_channels = 3;
558 isbformat = true;
559 DirectChannels = false;
560 break;
562 case FmtBFormat3D:
563 num_channels = 4;
564 isbformat = true;
565 DirectChannels = false;
566 break;
569 std::for_each(std::begin(voice->Direct.Params), std::begin(voice->Direct.Params)+num_channels,
570 [](DirectParams &params) -> void
572 params.Hrtf.Target = HrtfParams{};
573 ClearArray(params.Gains.Target);
576 std::for_each(voice->Send+0, voice->Send+NumSends,
577 [num_channels](ALvoice::SendData &send) -> void
579 std::for_each(std::begin(send.Params), std::begin(send.Params)+num_channels,
580 [](SendParams &params) -> void { ClearArray(params.Gains.Target); }
585 voice->Flags &= ~(VOICE_HAS_HRTF | VOICE_HAS_NFC);
586 if(isbformat)
588 /* Special handling for B-Format sources. */
590 if(Distance > FLT_EPSILON)
592 /* Panning a B-Format sound toward some direction is easy. Just pan
593 * the first (W) channel as a normal mono sound and silence the
594 * others.
596 ALfloat coeffs[MAX_AMBI_COEFFS];
598 if(Device->AvgSpeakerDist > 0.0f)
600 ALfloat mdist = Distance * Listener.Params.MetersPerUnit;
601 ALfloat w0 = SPEEDOFSOUNDMETRESPERSEC /
602 (mdist * (ALfloat)Device->Frequency);
603 ALfloat w1 = SPEEDOFSOUNDMETRESPERSEC /
604 (Device->AvgSpeakerDist * (ALfloat)Device->Frequency);
605 /* Clamp w0 for really close distances, to prevent excessive
606 * bass.
608 w0 = minf(w0, w1*4.0f);
610 /* Only need to adjust the first channel of a B-Format source. */
611 NfcFilterAdjust(&voice->Direct.Params[0].NFCtrlFilter, w0);
613 std::copy(std::begin(Device->NumChannelsPerOrder),
614 std::end(Device->NumChannelsPerOrder),
615 std::begin(voice->Direct.ChannelsPerOrder));
616 voice->Flags |= VOICE_HAS_NFC;
619 /* A scalar of 1.5 for plain stereo results in +/-60 degrees being
620 * moved to +/-90 degrees for direct right and left speaker
621 * responses.
623 CalcAngleCoeffs((Device->Render_Mode==StereoPair) ? ScaleAzimuthFront(Azi, 1.5f) : Azi,
624 Elev, Spread, coeffs);
626 /* NOTE: W needs to be scaled by sqrt(2) due to FuMa normalization. */
627 ComputePanGains(&Device->Dry, coeffs, DryGain*SQRTF_2,
628 voice->Direct.Params[0].Gains.Target);
629 for(i = 0;i < NumSends;i++)
631 const ALeffectslot *Slot = SendSlots[i];
632 if(Slot)
633 ComputePanningGainsBF(Slot->ChanMap, Slot->NumChannels, coeffs,
634 WetGain[i]*SQRTF_2, voice->Send[i].Params[0].Gains.Target
638 else
640 /* Local B-Format sources have their XYZ channels rotated according
641 * to the orientation.
643 ALfloat N[3], V[3], U[3];
644 aluMatrixf matrix;
646 if(Device->AvgSpeakerDist > 0.0f)
648 /* NOTE: The NFCtrlFilters were created with a w0 of 0, which
649 * is what we want for FOA input. The first channel may have
650 * been previously re-adjusted if panned, so reset it.
652 NfcFilterAdjust(&voice->Direct.Params[0].NFCtrlFilter, 0.0f);
654 voice->Direct.ChannelsPerOrder[0] = 1;
655 voice->Direct.ChannelsPerOrder[1] = mini(voice->Direct.Channels-1, 3);
656 std::fill(std::begin(voice->Direct.ChannelsPerOrder)+2,
657 std::end(voice->Direct.ChannelsPerOrder), 0);
658 voice->Flags |= VOICE_HAS_NFC;
661 /* AT then UP */
662 N[0] = props->Orientation[0][0];
663 N[1] = props->Orientation[0][1];
664 N[2] = props->Orientation[0][2];
665 aluNormalize(N);
666 V[0] = props->Orientation[1][0];
667 V[1] = props->Orientation[1][1];
668 V[2] = props->Orientation[1][2];
669 aluNormalize(V);
670 if(!props->HeadRelative)
672 const aluMatrixf *lmatrix = &Listener.Params.Matrix;
673 aluMatrixfFloat3(N, 0.0f, lmatrix);
674 aluMatrixfFloat3(V, 0.0f, lmatrix);
676 /* Build and normalize right-vector */
677 aluCrossproduct(N, V, U);
678 aluNormalize(U);
680 /* Build a rotate + conversion matrix (FuMa -> ACN+N3D). NOTE: This
681 * matrix is transposed, for the inputs to align on the rows and
682 * outputs on the columns.
684 aluMatrixfSet(&matrix,
685 // ACN0 ACN1 ACN2 ACN3
686 SQRTF_2, 0.0f, 0.0f, 0.0f, // Ambi W
687 0.0f, -N[0]*SQRTF_3, N[1]*SQRTF_3, -N[2]*SQRTF_3, // Ambi X
688 0.0f, U[0]*SQRTF_3, -U[1]*SQRTF_3, U[2]*SQRTF_3, // Ambi Y
689 0.0f, -V[0]*SQRTF_3, V[1]*SQRTF_3, -V[2]*SQRTF_3 // Ambi Z
692 voice->Direct.Buffer = Device->FOAOut.Buffer;
693 voice->Direct.Channels = Device->FOAOut.NumChannels;
694 for(c = 0;c < num_channels;c++)
695 ComputePanGains(&Device->FOAOut, matrix.m[c], DryGain,
696 voice->Direct.Params[c].Gains.Target);
697 for(i = 0;i < NumSends;i++)
699 const ALeffectslot *Slot = SendSlots[i];
700 if(Slot)
702 for(c = 0;c < num_channels;c++)
703 ComputePanningGainsBF(Slot->ChanMap, Slot->NumChannels,
704 matrix.m[c], WetGain[i], voice->Send[i].Params[c].Gains.Target
710 else if(DirectChannels)
712 /* Direct source channels always play local. Skip the virtual channels
713 * and write inputs to the matching real outputs.
715 voice->Direct.Buffer = Device->RealOut.Buffer;
716 voice->Direct.Channels = Device->RealOut.NumChannels;
718 for(c = 0;c < num_channels;c++)
720 int idx = GetChannelIdxByName(&Device->RealOut, chans[c].channel);
721 if(idx != -1) voice->Direct.Params[c].Gains.Target[idx] = DryGain;
724 /* Auxiliary sends still use normal channel panning since they mix to
725 * B-Format, which can't channel-match.
727 for(c = 0;c < num_channels;c++)
729 ALfloat coeffs[MAX_AMBI_COEFFS];
730 CalcAngleCoeffs(chans[c].angle, chans[c].elevation, 0.0f, coeffs);
732 for(i = 0;i < NumSends;i++)
734 const ALeffectslot *Slot = SendSlots[i];
735 if(Slot)
736 ComputePanningGainsBF(Slot->ChanMap, Slot->NumChannels,
737 coeffs, WetGain[i], voice->Send[i].Params[c].Gains.Target
742 else if(Device->Render_Mode == HrtfRender)
744 /* Full HRTF rendering. Skip the virtual channels and render to the
745 * real outputs.
747 voice->Direct.Buffer = Device->RealOut.Buffer;
748 voice->Direct.Channels = Device->RealOut.NumChannels;
750 if(Distance > FLT_EPSILON)
752 ALfloat coeffs[MAX_AMBI_COEFFS];
754 /* Get the HRIR coefficients and delays just once, for the given
755 * source direction.
757 GetHrtfCoeffs(Device->HrtfHandle, Elev, Azi, Spread,
758 voice->Direct.Params[0].Hrtf.Target.Coeffs,
759 voice->Direct.Params[0].Hrtf.Target.Delay);
760 voice->Direct.Params[0].Hrtf.Target.Gain = DryGain * downmix_gain;
762 /* Remaining channels use the same results as the first. */
763 for(c = 1;c < num_channels;c++)
765 /* Skip LFE */
766 if(chans[c].channel != LFE)
767 voice->Direct.Params[c].Hrtf.Target = voice->Direct.Params[0].Hrtf.Target;
770 /* Calculate the directional coefficients once, which apply to all
771 * input channels of the source sends.
773 CalcAngleCoeffs(Azi, Elev, Spread, coeffs);
775 for(i = 0;i < NumSends;i++)
777 const ALeffectslot *Slot = SendSlots[i];
778 if(Slot)
779 for(c = 0;c < num_channels;c++)
781 /* Skip LFE */
782 if(chans[c].channel != LFE)
783 ComputePanningGainsBF(Slot->ChanMap,
784 Slot->NumChannels, coeffs, WetGain[i] * downmix_gain,
785 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(c = 0;c < num_channels;c++)
798 ALfloat coeffs[MAX_AMBI_COEFFS];
800 if(chans[c].channel == LFE)
802 /* Skip LFE */
803 continue;
806 /* Get the HRIR coefficients and delays for this channel
807 * position.
809 GetHrtfCoeffs(Device->HrtfHandle,
810 chans[c].elevation, chans[c].angle, Spread,
811 voice->Direct.Params[c].Hrtf.Target.Coeffs,
812 voice->Direct.Params[c].Hrtf.Target.Delay
814 voice->Direct.Params[c].Hrtf.Target.Gain = DryGain;
816 /* Normal panning for auxiliary sends. */
817 CalcAngleCoeffs(chans[c].angle, chans[c].elevation, Spread, coeffs);
819 for(i = 0;i < NumSends;i++)
821 const ALeffectslot *Slot = SendSlots[i];
822 if(Slot)
823 ComputePanningGainsBF(Slot->ChanMap, Slot->NumChannels,
824 coeffs, WetGain[i], voice->Send[i].Params[c].Gains.Target
830 voice->Flags |= VOICE_HAS_HRTF;
832 else
834 /* Non-HRTF rendering. Use normal panning to the output. */
836 if(Distance > FLT_EPSILON)
838 ALfloat coeffs[MAX_AMBI_COEFFS];
839 ALfloat w0 = 0.0f;
841 /* Calculate NFC filter coefficient if needed. */
842 if(Device->AvgSpeakerDist > 0.0f)
844 ALfloat mdist = Distance * Listener.Params.MetersPerUnit;
845 ALfloat w1 = SPEEDOFSOUNDMETRESPERSEC /
846 (Device->AvgSpeakerDist * (ALfloat)Device->Frequency);
847 w0 = SPEEDOFSOUNDMETRESPERSEC /
848 (mdist * (ALfloat)Device->Frequency);
849 /* Clamp w0 for really close distances, to prevent excessive
850 * bass.
852 w0 = minf(w0, w1*4.0f);
854 /* Adjust NFC filters. */
855 for(c = 0;c < num_channels;c++)
856 NfcFilterAdjust(&voice->Direct.Params[c].NFCtrlFilter, w0);
858 for(i = 0;i < MAX_AMBI_ORDER+1;i++)
859 voice->Direct.ChannelsPerOrder[i] = Device->NumChannelsPerOrder[i];
860 voice->Flags |= VOICE_HAS_NFC;
863 /* Calculate the directional coefficients once, which apply to all
864 * input channels.
866 CalcAngleCoeffs((Device->Render_Mode==StereoPair) ? ScaleAzimuthFront(Azi, 1.5f) : Azi,
867 Elev, Spread, coeffs);
869 for(c = 0;c < num_channels;c++)
871 /* Special-case LFE */
872 if(chans[c].channel == LFE)
874 if(Device->Dry.Buffer == Device->RealOut.Buffer)
876 int idx = GetChannelIdxByName(&Device->RealOut, chans[c].channel);
877 if(idx != -1) voice->Direct.Params[c].Gains.Target[idx] = DryGain;
879 continue;
882 ComputePanGains(&Device->Dry, coeffs, DryGain * downmix_gain,
883 voice->Direct.Params[c].Gains.Target);
886 for(i = 0;i < NumSends;i++)
888 const ALeffectslot *Slot = SendSlots[i];
889 if(Slot)
890 for(c = 0;c < num_channels;c++)
892 /* Skip LFE */
893 if(chans[c].channel != LFE)
894 ComputePanningGainsBF(Slot->ChanMap,
895 Slot->NumChannels, coeffs, WetGain[i] * downmix_gain,
896 voice->Send[i].Params[c].Gains.Target
901 else
903 ALfloat w0 = 0.0f;
905 if(Device->AvgSpeakerDist > 0.0f)
907 /* If the source distance is 0, set w0 to w1 to act as a pass-
908 * through. We still want to pass the signal through the
909 * filters so they keep an appropriate history, in case the
910 * source moves away from the listener.
912 w0 = SPEEDOFSOUNDMETRESPERSEC /
913 (Device->AvgSpeakerDist * (ALfloat)Device->Frequency);
915 for(c = 0;c < num_channels;c++)
916 NfcFilterAdjust(&voice->Direct.Params[c].NFCtrlFilter, w0);
918 for(i = 0;i < MAX_AMBI_ORDER+1;i++)
919 voice->Direct.ChannelsPerOrder[i] = Device->NumChannelsPerOrder[i];
920 voice->Flags |= VOICE_HAS_NFC;
923 for(c = 0;c < num_channels;c++)
925 ALfloat coeffs[MAX_AMBI_COEFFS];
927 /* Special-case LFE */
928 if(chans[c].channel == LFE)
930 if(Device->Dry.Buffer == Device->RealOut.Buffer)
932 int idx = GetChannelIdxByName(&Device->RealOut, chans[c].channel);
933 if(idx != -1) voice->Direct.Params[c].Gains.Target[idx] = DryGain;
935 continue;
938 CalcAngleCoeffs(
939 (Device->Render_Mode==StereoPair) ? ScaleAzimuthFront(chans[c].angle, 3.0f)
940 : chans[c].angle,
941 chans[c].elevation, Spread, coeffs
944 ComputePanGains(&Device->Dry, coeffs, DryGain,
945 voice->Direct.Params[c].Gains.Target);
946 for(i = 0;i < NumSends;i++)
948 const ALeffectslot *Slot = SendSlots[i];
949 if(Slot)
950 ComputePanningGainsBF(Slot->ChanMap, Slot->NumChannels,
951 coeffs, WetGain[i], voice->Send[i].Params[c].Gains.Target
959 ALfloat hfScale = props->Direct.HFReference / Frequency;
960 ALfloat lfScale = props->Direct.LFReference / Frequency;
961 ALfloat gainHF = maxf(DryGainHF, 0.001f); /* Limit -60dB */
962 ALfloat gainLF = maxf(DryGainLF, 0.001f);
964 voice->Direct.FilterType = AF_None;
965 if(gainHF != 1.0f) voice->Direct.FilterType |= AF_LowPass;
966 if(gainLF != 1.0f) voice->Direct.FilterType |= AF_HighPass;
967 BiquadFilter_setParams(
968 &voice->Direct.Params[0].LowPass, BiquadType::HighShelf,
969 gainHF, hfScale, calc_rcpQ_from_slope(gainHF, 1.0f)
971 BiquadFilter_setParams(
972 &voice->Direct.Params[0].HighPass, BiquadType::LowShelf,
973 gainLF, lfScale, calc_rcpQ_from_slope(gainLF, 1.0f)
975 for(c = 1;c < num_channels;c++)
977 BiquadFilter_copyParams(&voice->Direct.Params[c].LowPass,
978 &voice->Direct.Params[0].LowPass);
979 BiquadFilter_copyParams(&voice->Direct.Params[c].HighPass,
980 &voice->Direct.Params[0].HighPass);
983 for(i = 0;i < NumSends;i++)
985 ALfloat hfScale = props->Send[i].HFReference / Frequency;
986 ALfloat lfScale = props->Send[i].LFReference / Frequency;
987 ALfloat gainHF = maxf(WetGainHF[i], 0.001f);
988 ALfloat gainLF = maxf(WetGainLF[i], 0.001f);
990 voice->Send[i].FilterType = AF_None;
991 if(gainHF != 1.0f) voice->Send[i].FilterType |= AF_LowPass;
992 if(gainLF != 1.0f) voice->Send[i].FilterType |= AF_HighPass;
993 BiquadFilter_setParams(
994 &voice->Send[i].Params[0].LowPass, BiquadType::HighShelf,
995 gainHF, hfScale, calc_rcpQ_from_slope(gainHF, 1.0f)
997 BiquadFilter_setParams(
998 &voice->Send[i].Params[0].HighPass, BiquadType::LowShelf,
999 gainLF, lfScale, calc_rcpQ_from_slope(gainLF, 1.0f)
1001 for(c = 1;c < num_channels;c++)
1003 BiquadFilter_copyParams(&voice->Send[i].Params[c].LowPass,
1004 &voice->Send[i].Params[0].LowPass);
1005 BiquadFilter_copyParams(&voice->Send[i].Params[c].HighPass,
1006 &voice->Send[i].Params[0].HighPass);
1011 void CalcNonAttnSourceParams(ALvoice *voice, const ALvoicePropsBase *props, const ALbuffer *ALBuffer, const ALCcontext *ALContext)
1013 const ALCdevice *Device = ALContext->Device;
1014 const ALlistener &Listener = ALContext->Listener;
1015 ALfloat DryGain, DryGainHF, DryGainLF;
1016 ALfloat WetGain[MAX_SENDS];
1017 ALfloat WetGainHF[MAX_SENDS];
1018 ALfloat WetGainLF[MAX_SENDS];
1019 ALeffectslot *SendSlots[MAX_SENDS];
1020 ALfloat Pitch;
1021 ALsizei i;
1023 voice->Direct.Buffer = Device->Dry.Buffer;
1024 voice->Direct.Channels = Device->Dry.NumChannels;
1025 for(i = 0;i < Device->NumAuxSends;i++)
1027 SendSlots[i] = props->Send[i].Slot;
1028 if(!SendSlots[i] && i == 0)
1029 SendSlots[i] = ALContext->DefaultSlot.get();
1030 if(!SendSlots[i] || SendSlots[i]->Params.EffectType == AL_EFFECT_NULL)
1032 SendSlots[i] = NULL;
1033 voice->Send[i].Buffer = NULL;
1034 voice->Send[i].Channels = 0;
1036 else
1038 voice->Send[i].Buffer = SendSlots[i]->WetBuffer;
1039 voice->Send[i].Channels = SendSlots[i]->NumChannels;
1043 /* Calculate the stepping value */
1044 Pitch = (ALfloat)ALBuffer->Frequency/(ALfloat)Device->Frequency * props->Pitch;
1045 if(Pitch > (ALfloat)MAX_PITCH)
1046 voice->Step = MAX_PITCH<<FRACTIONBITS;
1047 else
1048 voice->Step = maxi(fastf2i(Pitch * FRACTIONONE), 1);
1049 if(props->Resampler == BSinc24Resampler)
1050 BsincPrepare(voice->Step, &voice->ResampleState.bsinc, &bsinc24);
1051 else if(props->Resampler == BSinc12Resampler)
1052 BsincPrepare(voice->Step, &voice->ResampleState.bsinc, &bsinc12);
1053 voice->Resampler = SelectResampler(props->Resampler);
1055 /* Calculate gains */
1056 DryGain = clampf(props->Gain, props->MinGain, props->MaxGain);
1057 DryGain *= props->Direct.Gain * Listener.Params.Gain;
1058 DryGain = minf(DryGain, GAIN_MIX_MAX);
1059 DryGainHF = props->Direct.GainHF;
1060 DryGainLF = props->Direct.GainLF;
1061 for(i = 0;i < Device->NumAuxSends;i++)
1063 WetGain[i] = clampf(props->Gain, props->MinGain, props->MaxGain);
1064 WetGain[i] *= props->Send[i].Gain * Listener.Params.Gain;
1065 WetGain[i] = minf(WetGain[i], GAIN_MIX_MAX);
1066 WetGainHF[i] = props->Send[i].GainHF;
1067 WetGainLF[i] = props->Send[i].GainLF;
1070 CalcPanningAndFilters(voice, 0.0f, 0.0f, 0.0f, 0.0f, DryGain, DryGainHF, DryGainLF, WetGain,
1071 WetGainLF, WetGainHF, SendSlots, ALBuffer, props, Listener, Device);
1074 void CalcAttnSourceParams(ALvoice *voice, const ALvoicePropsBase *props, const ALbuffer *ALBuffer, const ALCcontext *ALContext)
1076 const ALCdevice *Device = ALContext->Device;
1077 const ALlistener &Listener = ALContext->Listener;
1078 const ALsizei NumSends = Device->NumAuxSends;
1079 aluVector Position, Velocity, Direction, SourceToListener;
1080 ALfloat Distance, ClampedDist, DopplerFactor;
1081 ALeffectslot *SendSlots[MAX_SENDS];
1082 ALfloat RoomRolloff[MAX_SENDS];
1083 ALfloat DecayDistance[MAX_SENDS];
1084 ALfloat DecayLFDistance[MAX_SENDS];
1085 ALfloat DecayHFDistance[MAX_SENDS];
1086 ALfloat DryGain, DryGainHF, DryGainLF;
1087 ALfloat WetGain[MAX_SENDS];
1088 ALfloat WetGainHF[MAX_SENDS];
1089 ALfloat WetGainLF[MAX_SENDS];
1090 bool directional;
1091 ALfloat ev, az;
1092 ALfloat spread;
1093 ALfloat Pitch;
1094 ALint i;
1096 /* Set mixing buffers and get send parameters. */
1097 voice->Direct.Buffer = Device->Dry.Buffer;
1098 voice->Direct.Channels = Device->Dry.NumChannels;
1099 for(i = 0;i < NumSends;i++)
1101 SendSlots[i] = props->Send[i].Slot;
1102 if(!SendSlots[i] && i == 0)
1103 SendSlots[i] = ALContext->DefaultSlot.get();
1104 if(!SendSlots[i] || SendSlots[i]->Params.EffectType == AL_EFFECT_NULL)
1106 SendSlots[i] = NULL;
1107 RoomRolloff[i] = 0.0f;
1108 DecayDistance[i] = 0.0f;
1109 DecayLFDistance[i] = 0.0f;
1110 DecayHFDistance[i] = 0.0f;
1112 else if(SendSlots[i]->Params.AuxSendAuto)
1114 RoomRolloff[i] = SendSlots[i]->Params.RoomRolloff + props->RoomRolloffFactor;
1115 /* Calculate the distances to where this effect's decay reaches
1116 * -60dB.
1118 DecayDistance[i] = SendSlots[i]->Params.DecayTime *
1119 Listener.Params.ReverbSpeedOfSound;
1120 DecayLFDistance[i] = DecayDistance[i] * SendSlots[i]->Params.DecayLFRatio;
1121 DecayHFDistance[i] = DecayDistance[i] * SendSlots[i]->Params.DecayHFRatio;
1122 if(SendSlots[i]->Params.DecayHFLimit)
1124 ALfloat airAbsorption = SendSlots[i]->Params.AirAbsorptionGainHF;
1125 if(airAbsorption < 1.0f)
1127 /* Calculate the distance to where this effect's air
1128 * absorption reaches -60dB, and limit the effect's HF
1129 * decay distance (so it doesn't take any longer to decay
1130 * than the air would allow).
1132 ALfloat absorb_dist = log10f(REVERB_DECAY_GAIN) / log10f(airAbsorption);
1133 DecayHFDistance[i] = minf(absorb_dist, DecayHFDistance[i]);
1137 else
1139 /* If the slot's auxiliary send auto is off, the data sent to the
1140 * effect slot is the same as the dry path, sans filter effects */
1141 RoomRolloff[i] = props->RolloffFactor;
1142 DecayDistance[i] = 0.0f;
1143 DecayLFDistance[i] = 0.0f;
1144 DecayHFDistance[i] = 0.0f;
1147 if(!SendSlots[i])
1149 voice->Send[i].Buffer = NULL;
1150 voice->Send[i].Channels = 0;
1152 else
1154 voice->Send[i].Buffer = SendSlots[i]->WetBuffer;
1155 voice->Send[i].Channels = SendSlots[i]->NumChannels;
1159 /* Transform source to listener space (convert to head relative) */
1160 aluVectorSet(&Position, props->Position[0], props->Position[1], props->Position[2], 1.0f);
1161 aluVectorSet(&Direction, props->Direction[0], props->Direction[1], props->Direction[2], 0.0f);
1162 aluVectorSet(&Velocity, props->Velocity[0], props->Velocity[1], props->Velocity[2], 0.0f);
1163 if(props->HeadRelative == AL_FALSE)
1165 const aluMatrixf *Matrix = &Listener.Params.Matrix;
1166 /* Transform source vectors */
1167 Position = aluMatrixfVector(Matrix, &Position);
1168 Velocity = aluMatrixfVector(Matrix, &Velocity);
1169 Direction = aluMatrixfVector(Matrix, &Direction);
1171 else
1173 const aluVector *lvelocity = &Listener.Params.Velocity;
1174 /* Offset the source velocity to be relative of the listener velocity */
1175 Velocity.v[0] += lvelocity->v[0];
1176 Velocity.v[1] += lvelocity->v[1];
1177 Velocity.v[2] += lvelocity->v[2];
1180 directional = aluNormalize(Direction.v) > 0.0f;
1181 SourceToListener.v[0] = -Position.v[0];
1182 SourceToListener.v[1] = -Position.v[1];
1183 SourceToListener.v[2] = -Position.v[2];
1184 SourceToListener.v[3] = 0.0f;
1185 Distance = aluNormalize(SourceToListener.v);
1187 /* Initial source gain */
1188 DryGain = props->Gain;
1189 DryGainHF = 1.0f;
1190 DryGainLF = 1.0f;
1191 for(i = 0;i < NumSends;i++)
1193 WetGain[i] = props->Gain;
1194 WetGainHF[i] = 1.0f;
1195 WetGainLF[i] = 1.0f;
1198 /* Calculate distance attenuation */
1199 ClampedDist = Distance;
1201 switch(Listener.Params.SourceDistanceModel ?
1202 props->mDistanceModel : Listener.Params.mDistanceModel)
1204 case DistanceModel::InverseClamped:
1205 ClampedDist = clampf(ClampedDist, props->RefDistance, props->MaxDistance);
1206 if(props->MaxDistance < props->RefDistance)
1207 break;
1208 /*fall-through*/
1209 case DistanceModel::Inverse:
1210 if(!(props->RefDistance > 0.0f))
1211 ClampedDist = props->RefDistance;
1212 else
1214 ALfloat dist = lerp(props->RefDistance, ClampedDist, props->RolloffFactor);
1215 if(dist > 0.0f) DryGain *= props->RefDistance / dist;
1216 for(i = 0;i < NumSends;i++)
1218 dist = lerp(props->RefDistance, ClampedDist, RoomRolloff[i]);
1219 if(dist > 0.0f) WetGain[i] *= props->RefDistance / dist;
1222 break;
1224 case DistanceModel::LinearClamped:
1225 ClampedDist = clampf(ClampedDist, props->RefDistance, props->MaxDistance);
1226 if(props->MaxDistance < props->RefDistance)
1227 break;
1228 /*fall-through*/
1229 case DistanceModel::Linear:
1230 if(!(props->MaxDistance != props->RefDistance))
1231 ClampedDist = props->RefDistance;
1232 else
1234 ALfloat attn = props->RolloffFactor * (ClampedDist-props->RefDistance) /
1235 (props->MaxDistance-props->RefDistance);
1236 DryGain *= maxf(1.0f - attn, 0.0f);
1237 for(i = 0;i < NumSends;i++)
1239 attn = RoomRolloff[i] * (ClampedDist-props->RefDistance) /
1240 (props->MaxDistance-props->RefDistance);
1241 WetGain[i] *= maxf(1.0f - attn, 0.0f);
1244 break;
1246 case DistanceModel::ExponentClamped:
1247 ClampedDist = clampf(ClampedDist, props->RefDistance, props->MaxDistance);
1248 if(props->MaxDistance < props->RefDistance)
1249 break;
1250 /*fall-through*/
1251 case DistanceModel::Exponent:
1252 if(!(ClampedDist > 0.0f && props->RefDistance > 0.0f))
1253 ClampedDist = props->RefDistance;
1254 else
1256 DryGain *= powf(ClampedDist/props->RefDistance, -props->RolloffFactor);
1257 for(i = 0;i < NumSends;i++)
1258 WetGain[i] *= powf(ClampedDist/props->RefDistance, -RoomRolloff[i]);
1260 break;
1262 case DistanceModel::Disable:
1263 ClampedDist = props->RefDistance;
1264 break;
1267 /* Calculate directional soundcones */
1268 if(directional && props->InnerAngle < 360.0f)
1270 ALfloat ConeVolume;
1271 ALfloat ConeHF;
1272 ALfloat Angle;
1274 Angle = acosf(aluDotproduct(&Direction, &SourceToListener));
1275 Angle = RAD2DEG(Angle * ConeScale * 2.0f);
1276 if(!(Angle > props->InnerAngle))
1278 ConeVolume = 1.0f;
1279 ConeHF = 1.0f;
1281 else if(Angle < props->OuterAngle)
1283 ALfloat scale = ( Angle-props->InnerAngle) /
1284 (props->OuterAngle-props->InnerAngle);
1285 ConeVolume = lerp(1.0f, props->OuterGain, scale);
1286 ConeHF = lerp(1.0f, props->OuterGainHF, scale);
1288 else
1290 ConeVolume = props->OuterGain;
1291 ConeHF = props->OuterGainHF;
1294 DryGain *= ConeVolume;
1295 if(props->DryGainHFAuto)
1296 DryGainHF *= ConeHF;
1297 if(props->WetGainAuto)
1299 for(i = 0;i < NumSends;i++)
1300 WetGain[i] *= ConeVolume;
1302 if(props->WetGainHFAuto)
1304 for(i = 0;i < NumSends;i++)
1305 WetGainHF[i] *= ConeHF;
1309 /* Apply gain and frequency filters */
1310 DryGain = clampf(DryGain, props->MinGain, props->MaxGain);
1311 DryGain = minf(DryGain*props->Direct.Gain*Listener.Params.Gain, GAIN_MIX_MAX);
1312 DryGainHF *= props->Direct.GainHF;
1313 DryGainLF *= props->Direct.GainLF;
1314 for(i = 0;i < NumSends;i++)
1316 WetGain[i] = clampf(WetGain[i], props->MinGain, props->MaxGain);
1317 WetGain[i] = minf(WetGain[i]*props->Send[i].Gain*Listener.Params.Gain, GAIN_MIX_MAX);
1318 WetGainHF[i] *= props->Send[i].GainHF;
1319 WetGainLF[i] *= props->Send[i].GainLF;
1322 /* Distance-based air absorption and initial send decay. */
1323 if(ClampedDist > props->RefDistance && props->RolloffFactor > 0.0f)
1325 ALfloat meters_base = (ClampedDist-props->RefDistance) * props->RolloffFactor *
1326 Listener.Params.MetersPerUnit;
1327 if(props->AirAbsorptionFactor > 0.0f)
1329 ALfloat hfattn = powf(AIRABSORBGAINHF, meters_base * props->AirAbsorptionFactor);
1330 DryGainHF *= hfattn;
1331 for(i = 0;i < NumSends;i++)
1332 WetGainHF[i] *= hfattn;
1335 if(props->WetGainAuto)
1337 /* Apply a decay-time transformation to the wet path, based on the
1338 * source distance in meters. The initial decay of the reverb
1339 * effect is calculated and applied to the wet path.
1341 for(i = 0;i < NumSends;i++)
1343 ALfloat gain, gainhf, gainlf;
1345 if(!(DecayDistance[i] > 0.0f))
1346 continue;
1348 gain = powf(REVERB_DECAY_GAIN, meters_base/DecayDistance[i]);
1349 WetGain[i] *= gain;
1350 /* Yes, the wet path's air absorption is applied with
1351 * WetGainAuto on, rather than WetGainHFAuto.
1353 if(gain > 0.0f)
1355 gainhf = powf(REVERB_DECAY_GAIN, meters_base/DecayHFDistance[i]);
1356 WetGainHF[i] *= minf(gainhf / gain, 1.0f);
1357 gainlf = powf(REVERB_DECAY_GAIN, meters_base/DecayLFDistance[i]);
1358 WetGainLF[i] *= minf(gainlf / gain, 1.0f);
1365 /* Initial source pitch */
1366 Pitch = props->Pitch;
1368 /* Calculate velocity-based doppler effect */
1369 DopplerFactor = props->DopplerFactor * Listener.Params.DopplerFactor;
1370 if(DopplerFactor > 0.0f)
1372 const aluVector *lvelocity = &Listener.Params.Velocity;
1373 const ALfloat SpeedOfSound = Listener.Params.SpeedOfSound;
1374 ALfloat vss, vls;
1376 vss = aluDotproduct(&Velocity, &SourceToListener) * DopplerFactor;
1377 vls = aluDotproduct(lvelocity, &SourceToListener) * DopplerFactor;
1379 if(!(vls < SpeedOfSound))
1381 /* Listener moving away from the source at the speed of sound.
1382 * Sound waves can't catch it.
1384 Pitch = 0.0f;
1386 else if(!(vss < SpeedOfSound))
1388 /* Source moving toward the listener at the speed of sound. Sound
1389 * waves bunch up to extreme frequencies.
1391 Pitch = HUGE_VALF;
1393 else
1395 /* Source and listener movement is nominal. Calculate the proper
1396 * doppler shift.
1398 Pitch *= (SpeedOfSound-vls) / (SpeedOfSound-vss);
1402 /* Adjust pitch based on the buffer and output frequencies, and calculate
1403 * fixed-point stepping value.
1405 Pitch *= (ALfloat)ALBuffer->Frequency/(ALfloat)Device->Frequency;
1406 if(Pitch > (ALfloat)MAX_PITCH)
1407 voice->Step = MAX_PITCH<<FRACTIONBITS;
1408 else
1409 voice->Step = maxi(fastf2i(Pitch * FRACTIONONE), 1);
1410 if(props->Resampler == BSinc24Resampler)
1411 BsincPrepare(voice->Step, &voice->ResampleState.bsinc, &bsinc24);
1412 else if(props->Resampler == BSinc12Resampler)
1413 BsincPrepare(voice->Step, &voice->ResampleState.bsinc, &bsinc12);
1414 voice->Resampler = SelectResampler(props->Resampler);
1416 if(Distance > 0.0f)
1418 /* Clamp Y, in case rounding errors caused it to end up outside of
1419 * -1...+1.
1421 ev = asinf(clampf(-SourceToListener.v[1], -1.0f, 1.0f));
1422 /* Double negation on Z cancels out; negate once for changing source-
1423 * to-listener to listener-to-source, and again for right-handed coords
1424 * with -Z in front.
1426 az = atan2f(-SourceToListener.v[0], SourceToListener.v[2]*ZScale);
1428 else
1429 ev = az = 0.0f;
1431 if(props->Radius > Distance)
1432 spread = F_TAU - Distance/props->Radius*F_PI;
1433 else if(Distance > 0.0f)
1434 spread = asinf(props->Radius / Distance) * 2.0f;
1435 else
1436 spread = 0.0f;
1438 CalcPanningAndFilters(voice, az, ev, Distance, spread, DryGain, DryGainHF, DryGainLF, WetGain,
1439 WetGainLF, WetGainHF, SendSlots, ALBuffer, props, Listener, Device);
1442 void CalcSourceParams(ALvoice *voice, ALCcontext *context, bool force)
1444 ALvoiceProps *props{voice->Update.exchange(nullptr, std::memory_order_acq_rel)};
1445 if(!props && !force) return;
1447 if(props)
1449 voice->Props = *props;
1451 AtomicReplaceHead(context->FreeVoiceProps, props);
1454 ALbufferlistitem *BufferListItem{voice->current_buffer.load(std::memory_order_relaxed)};
1455 while(BufferListItem)
1457 auto buffers_end = BufferListItem->buffers+BufferListItem->num_buffers;
1458 auto buffer = std::find_if(BufferListItem->buffers, buffers_end,
1459 [](const ALbuffer *buffer) noexcept -> bool
1460 { return buffer != nullptr; }
1462 if(LIKELY(buffer != buffers_end))
1464 if(voice->Props.SpatializeMode == SpatializeOn ||
1465 (voice->Props.SpatializeMode == SpatializeAuto && (*buffer)->FmtChannels == FmtMono))
1466 CalcAttnSourceParams(voice, &voice->Props, *buffer, context);
1467 else
1468 CalcNonAttnSourceParams(voice, &voice->Props, *buffer, context);
1469 break;
1471 BufferListItem = BufferListItem->next.load(std::memory_order_acquire);
1476 void ProcessParamUpdates(ALCcontext *ctx, const ALeffectslotArray *slots)
1478 IncrementRef(&ctx->UpdateCount);
1479 if(LIKELY(!ctx->HoldUpdates.load(std::memory_order_acquire)))
1481 bool cforce = CalcContextParams(ctx);
1482 bool force = CalcListenerParams(ctx) | cforce;
1483 std::for_each(slots->slot, slots->slot+slots->count,
1484 [ctx,cforce,&force](ALeffectslot *slot) -> void
1485 { force |= CalcEffectSlotParams(slot, ctx, cforce); }
1488 std::for_each(ctx->Voices, ctx->Voices+ctx->VoiceCount.load(std::memory_order_acquire),
1489 [ctx,force](ALvoice *voice) -> void
1491 ALuint sid{voice->SourceID.load(std::memory_order_acquire)};
1492 if(sid) CalcSourceParams(voice, ctx, force);
1496 IncrementRef(&ctx->UpdateCount);
1499 void ProcessContext(ALCcontext *ctx, ALsizei SamplesToDo)
1501 const ALeffectslotArray *auxslots{ctx->ActiveAuxSlots.load(std::memory_order_acquire)};
1503 /* Process pending propery updates for objects on the context. */
1504 ProcessParamUpdates(ctx, auxslots);
1506 /* Clear auxiliary effect slot mixing buffers. */
1507 std::for_each(auxslots->slot, auxslots->slot+auxslots->count,
1508 [SamplesToDo](ALeffectslot *slot) -> void
1510 std::for_each(slot->WetBuffer, slot->WetBuffer+slot->NumChannels,
1511 [SamplesToDo](ALfloat *buffer) -> void
1512 { std::fill_n(buffer, SamplesToDo, 0.0f); }
1517 /* Process voices that have a playing source. */
1518 std::for_each(ctx->Voices, ctx->Voices+ctx->VoiceCount.load(std::memory_order_acquire),
1519 [SamplesToDo,ctx](ALvoice *voice) -> void
1521 if(!voice->Playing.load(std::memory_order_acquire)) return;
1522 ALuint sid{voice->SourceID.load(std::memory_order_relaxed)};
1523 if(!sid || voice->Step < 1) return;
1525 if(!MixSource(voice, sid, ctx, SamplesToDo))
1527 voice->SourceID.store(0u, std::memory_order_relaxed);
1528 voice->Playing.store(false, std::memory_order_release);
1529 SendSourceStoppedEvent(ctx, sid);
1534 /* Process effects. */
1535 std::for_each(auxslots->slot, auxslots->slot+auxslots->count,
1536 [SamplesToDo](const ALeffectslot *slot) -> void
1538 EffectState *state{slot->Params.mEffectState};
1539 state->process(SamplesToDo, slot->WetBuffer, state->mOutBuffer,
1540 state->mOutChannels);
1546 void ApplyStablizer(FrontStablizer *Stablizer, ALfloat (*RESTRICT Buffer)[BUFFERSIZE],
1547 int lidx, int ridx, int cidx, ALsizei SamplesToDo, ALsizei NumChannels)
1549 ALfloat (*RESTRICT lsplit)[BUFFERSIZE] = Stablizer->LSplit;
1550 ALfloat (*RESTRICT rsplit)[BUFFERSIZE] = Stablizer->RSplit;
1551 ALsizei i;
1553 /* Apply an all-pass to all channels, except the front-left and front-
1554 * right, so they maintain the same relative phase.
1556 for(i = 0;i < NumChannels;i++)
1558 if(i == lidx || i == ridx)
1559 continue;
1560 splitterap_process(&Stablizer->APFilter[i], Buffer[i], SamplesToDo);
1563 bandsplit_process(&Stablizer->LFilter, lsplit[1], lsplit[0], Buffer[lidx], SamplesToDo);
1564 bandsplit_process(&Stablizer->RFilter, rsplit[1], rsplit[0], Buffer[ridx], SamplesToDo);
1566 for(i = 0;i < SamplesToDo;i++)
1568 ALfloat lfsum, hfsum;
1569 ALfloat m, s, c;
1571 lfsum = lsplit[0][i] + rsplit[0][i];
1572 hfsum = lsplit[1][i] + rsplit[1][i];
1573 s = lsplit[0][i] + lsplit[1][i] - rsplit[0][i] - rsplit[1][i];
1575 /* This pans the separate low- and high-frequency sums between being on
1576 * the center channel and the left/right channels. The low-frequency
1577 * sum is 1/3rd toward center (2/3rds on left/right) and the high-
1578 * frequency sum is 1/4th toward center (3/4ths on left/right). These
1579 * values can be tweaked.
1581 m = lfsum*cosf(1.0f/3.0f * F_PI_2) + hfsum*cosf(1.0f/4.0f * F_PI_2);
1582 c = lfsum*sinf(1.0f/3.0f * F_PI_2) + hfsum*sinf(1.0f/4.0f * F_PI_2);
1584 /* The generated center channel signal adds to the existing signal,
1585 * while the modified left and right channels replace.
1587 Buffer[lidx][i] = (m + s) * 0.5f;
1588 Buffer[ridx][i] = (m - s) * 0.5f;
1589 Buffer[cidx][i] += c * 0.5f;
1593 void ApplyDistanceComp(ALfloat (*RESTRICT Samples)[BUFFERSIZE], const DistanceComp &distcomp,
1594 ALfloat *RESTRICT Values, ALsizei SamplesToDo, ALsizei numchans)
1596 for(ALsizei c{0};c < numchans;c++)
1598 ALfloat *RESTRICT inout = Samples[c];
1599 const ALfloat gain = distcomp[c].Gain;
1600 const ALsizei base = distcomp[c].Length;
1601 ALfloat *RESTRICT distbuf = distcomp[c].Buffer;
1603 if(base == 0)
1605 if(gain < 1.0f)
1606 std::for_each(inout, inout+SamplesToDo,
1607 [gain](ALfloat &in) noexcept -> void
1608 { in *= gain; }
1610 continue;
1613 if(LIKELY(SamplesToDo >= base))
1615 auto out = std::copy_n(distbuf, base, Values);
1616 std::copy_n(inout, SamplesToDo-base, out);
1617 std::copy_n(inout+SamplesToDo-base, base, distbuf);
1619 else
1621 std::copy_n(distbuf, SamplesToDo, Values);
1622 auto out = std::copy(distbuf+SamplesToDo, distbuf+base, distbuf);
1623 std::copy_n(inout, SamplesToDo, out);
1625 std::transform<ALfloat*RESTRICT>(Values, Values+SamplesToDo, inout,
1626 [gain](ALfloat in) noexcept -> ALfloat
1627 { return in * gain; }
1632 void ApplyDither(ALfloat (*RESTRICT Samples)[BUFFERSIZE], ALuint *dither_seed,
1633 const ALfloat quant_scale, const ALsizei SamplesToDo, const ALsizei numchans)
1635 ASSUME(numchans > 0);
1637 /* Dithering. Generate whitenoise (uniform distribution of random values
1638 * between -1 and +1) and add it to the sample values, after scaling up to
1639 * the desired quantization depth amd before rounding.
1641 const ALfloat invscale = 1.0f / quant_scale;
1642 ALuint seed = *dither_seed;
1643 auto dither_channel = [&seed,invscale,quant_scale,SamplesToDo](ALfloat *buffer) -> void
1645 ASSUME(SamplesToDo > 0);
1646 std::transform(buffer, buffer+SamplesToDo, buffer,
1647 [&seed,invscale,quant_scale](ALfloat sample) noexcept -> ALfloat
1649 ALfloat val = sample * quant_scale;
1650 ALuint rng0 = dither_rng(&seed);
1651 ALuint rng1 = dither_rng(&seed);
1652 val += (ALfloat)(rng0*(1.0/UINT_MAX) - rng1*(1.0/UINT_MAX));
1653 return fast_roundf(val) * invscale;
1657 std::for_each(Samples, Samples+numchans, dither_channel);
1658 *dither_seed = seed;
1662 /* Base template left undefined. Should be marked =delete, but Clang 3.8.1
1663 * chokes on that given the inline specializations.
1665 template<typename T>
1666 inline T SampleConv(ALfloat) noexcept;
1668 template<> inline ALfloat SampleConv(ALfloat val) noexcept
1669 { return val; }
1670 template<> inline ALint SampleConv(ALfloat val) noexcept
1672 /* Floats have a 23-bit mantissa. There is an implied 1 bit in the mantissa
1673 * along with the sign bit, giving 25 bits total, so [-16777216, +16777216]
1674 * is the max value a normalized float can be scaled to before losing
1675 * precision.
1677 return fastf2i(clampf(val*16777216.0f, -16777216.0f, 16777215.0f))<<7;
1679 template<> inline ALshort SampleConv(ALfloat val) noexcept
1680 { return fastf2i(clampf(val*32768.0f, -32768.0f, 32767.0f)); }
1681 template<> inline ALbyte SampleConv(ALfloat val) noexcept
1682 { return fastf2i(clampf(val*128.0f, -128.0f, 127.0f)); }
1684 /* Define unsigned output variations. */
1685 template<> inline ALuint SampleConv(ALfloat val) noexcept
1686 { return SampleConv<ALint>(val) + 2147483648u; }
1687 template<> inline ALushort SampleConv(ALfloat val) noexcept
1688 { return SampleConv<ALshort>(val) + 32768; }
1689 template<> inline ALubyte SampleConv(ALfloat val) noexcept
1690 { return SampleConv<ALbyte>(val) + 128; }
1692 template<DevFmtType T>
1693 void Write(const ALfloat (*RESTRICT InBuffer)[BUFFERSIZE], ALvoid *OutBuffer,
1694 ALsizei Offset, ALsizei SamplesToDo, ALsizei numchans)
1696 using SampleType = typename DevFmtTypeTraits<T>::Type;
1698 ASSUME(numchans > 0);
1699 SampleType *outbase = static_cast<SampleType*>(OutBuffer) + Offset*numchans;
1700 auto conv_channel = [&outbase,SamplesToDo,numchans](const ALfloat *inbuf) -> void
1702 ASSUME(SamplesToDo > 0);
1703 SampleType *out{outbase++};
1704 std::for_each<const ALfloat*RESTRICT>(inbuf, inbuf+SamplesToDo,
1705 [numchans,&out](const ALfloat s) noexcept -> void
1707 *out = SampleConv<SampleType>(s);
1708 out += numchans;
1712 std::for_each(InBuffer, InBuffer+numchans, conv_channel);
1715 } // namespace
1717 void aluMixData(ALCdevice *device, ALvoid *OutBuffer, ALsizei NumSamples)
1719 FPUCtl mixer_mode{};
1720 for(ALsizei SamplesDone{0};SamplesDone < NumSamples;)
1722 const ALsizei SamplesToDo{mini(NumSamples-SamplesDone, BUFFERSIZE)};
1724 /* Clear main mixing buffers. */
1725 std::for_each(device->MixBuffer.begin(), device->MixBuffer.end(),
1726 [SamplesToDo](std::array<ALfloat,BUFFERSIZE> &buffer) -> void
1727 { std::fill_n(buffer.begin(), SamplesToDo, 0.0f); }
1730 /* Increment the mix count at the start (lsb should now be 1). */
1731 IncrementRef(&device->MixCount);
1733 /* For each context on this device, process and mix its sources and
1734 * effects.
1736 ALCcontext *ctx{device->ContextList.load(std::memory_order_acquire)};
1737 while(ctx)
1739 ProcessContext(ctx, SamplesToDo);
1741 ctx = ctx->next.load(std::memory_order_relaxed);
1744 /* Increment the clock time. Every second's worth of samples is
1745 * converted and added to clock base so that large sample counts don't
1746 * overflow during conversion. This also guarantees a stable
1747 * conversion.
1749 device->SamplesDone += SamplesToDo;
1750 device->ClockBase += std::chrono::seconds{device->SamplesDone / device->Frequency};
1751 device->SamplesDone %= device->Frequency;
1753 /* Increment the mix count at the end (lsb should now be 0). */
1754 IncrementRef(&device->MixCount);
1756 /* Apply any needed post-process for finalizing the Dry mix to the
1757 * RealOut (Ambisonic decode, UHJ encode, etc).
1759 if(LIKELY(device->PostProcess))
1760 device->PostProcess(device, SamplesToDo);
1762 /* Apply front image stablization for surround sound, if applicable. */
1763 if(device->Stablizer)
1765 int lidx = GetChannelIdxByName(&device->RealOut, FrontLeft);
1766 int ridx = GetChannelIdxByName(&device->RealOut, FrontRight);
1767 int cidx = GetChannelIdxByName(&device->RealOut, FrontCenter);
1768 assert(lidx >= 0 && ridx >= 0 && cidx >= 0);
1770 ApplyStablizer(device->Stablizer.get(), device->RealOut.Buffer, lidx, ridx, cidx,
1771 SamplesToDo, device->RealOut.NumChannels);
1774 /* Apply delays and attenuation for mismatched speaker distances. */
1775 ApplyDistanceComp(device->RealOut.Buffer, device->ChannelDelay, device->TempBuffer[0],
1776 SamplesToDo, device->RealOut.NumChannels);
1778 /* Apply compression, limiting final sample amplitude, if desired. */
1779 if(device->Limiter)
1780 ApplyCompression(device->Limiter.get(), SamplesToDo, device->RealOut.Buffer);
1782 /* Apply dithering. The compressor should have left enough headroom for
1783 * the dither noise to not saturate.
1785 if(device->DitherDepth > 0.0f)
1786 ApplyDither(device->RealOut.Buffer, &device->DitherSeed, device->DitherDepth,
1787 SamplesToDo, device->RealOut.NumChannels);
1789 if(LIKELY(OutBuffer))
1791 ALfloat (*Buffer)[BUFFERSIZE] = device->RealOut.Buffer;
1792 ALsizei Channels = device->RealOut.NumChannels;
1794 /* Finally, interleave and convert samples, writing to the device's
1795 * output buffer.
1797 switch(device->FmtType)
1799 #define HANDLE_WRITE(T) case T: \
1800 Write<T>(Buffer, OutBuffer, SamplesDone, SamplesToDo, Channels); break;
1801 HANDLE_WRITE(DevFmtByte)
1802 HANDLE_WRITE(DevFmtUByte)
1803 HANDLE_WRITE(DevFmtShort)
1804 HANDLE_WRITE(DevFmtUShort)
1805 HANDLE_WRITE(DevFmtInt)
1806 HANDLE_WRITE(DevFmtUInt)
1807 HANDLE_WRITE(DevFmtFloat)
1808 #undef HANDLE_WRITE
1812 SamplesDone += SamplesToDo;
1817 void aluHandleDisconnect(ALCdevice *device, const char *msg, ...)
1819 if(!device->Connected.exchange(AL_FALSE, std::memory_order_acq_rel))
1820 return;
1822 AsyncEvent evt = ASYNC_EVENT(EventType_Disconnected);
1823 evt.u.user.type = AL_EVENT_TYPE_DISCONNECTED_SOFT;
1824 evt.u.user.id = 0;
1825 evt.u.user.param = 0;
1827 va_list args;
1828 va_start(args, msg);
1829 int msglen{vsnprintf(evt.u.user.msg, sizeof(evt.u.user.msg), msg, args)};
1830 va_end(args);
1832 if(msglen < 0 || (size_t)msglen >= sizeof(evt.u.user.msg))
1833 evt.u.user.msg[sizeof(evt.u.user.msg)-1] = 0;
1835 ALCcontext *ctx{device->ContextList.load()};
1836 while(ctx)
1838 ALbitfieldSOFT enabledevt = ctx->EnabledEvts.load(std::memory_order_acquire);
1839 if((enabledevt&EventType_Disconnected) &&
1840 ll_ringbuffer_write(ctx->AsyncEvents, &evt, 1) == 1)
1841 ctx->EventSem.post();
1843 std::for_each(ctx->Voices, ctx->Voices+ctx->VoiceCount.load(std::memory_order_acquire),
1844 [ctx](ALvoice *voice) -> void
1846 if(!voice->Playing.load(std::memory_order_acquire)) return;
1847 ALuint sid{voice->SourceID.load(std::memory_order_relaxed)};
1848 if(!sid) return;
1850 voice->SourceID.store(0u, std::memory_order_relaxed);
1851 voice->Playing.store(false, std::memory_order_release);
1852 /* If the source's voice was playing, it's now effectively
1853 * stopped (the source state will be updated the next time it's
1854 * checked).
1856 SendSourceStoppedEvent(ctx, sid);
1860 ctx = ctx->next.load(std::memory_order_relaxed);