Restructure and clean up alu.cpp a bit
[openal-soft.git] / Alc / alu.cpp
blob71823bf7d9dedc1b88e0781c61e74aed4c44d579
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 size_t i;
67 for(i = 0;i < MAX_OUTPUT_CHANNELS;i++)
68 f[i] = 0.0f;
71 struct ChanMap {
72 enum Channel channel;
73 ALfloat angle;
74 ALfloat elevation;
77 HrtfDirectMixerFunc MixDirectHrtf = MixDirectHrtf_C;
80 inline HrtfDirectMixerFunc SelectHrtfMixer(void)
82 #ifdef HAVE_NEON
83 if((CPUCapFlags&CPU_CAP_NEON))
84 return MixDirectHrtf_Neon;
85 #endif
86 #ifdef HAVE_SSE
87 if((CPUCapFlags&CPU_CAP_SSE))
88 return MixDirectHrtf_SSE;
89 #endif
91 return MixDirectHrtf_C;
94 } // namespace
96 void aluInit(void)
98 MixDirectHrtf = SelectHrtfMixer();
102 void DeinitVoice(ALvoice *voice)
104 al_free(voice->Update.exchange(nullptr));
108 namespace {
110 void ProcessHrtf(ALCdevice *device, ALsizei SamplesToDo)
112 if(device->AmbiUp)
113 ambiup_process(device->AmbiUp.get(),
114 device->Dry.Buffer, device->Dry.NumChannels, device->FOAOut.Buffer,
115 SamplesToDo
118 int lidx{GetChannelIdxByName(&device->RealOut, FrontLeft)};
119 int ridx{GetChannelIdxByName(&device->RealOut, FrontRight)};
120 assert(lidx != -1 && ridx != -1);
122 DirectHrtfState *state{device->mHrtfState.get()};
123 for(ALsizei c{0};c < device->Dry.NumChannels;c++)
125 MixDirectHrtf(device->RealOut.Buffer[lidx], device->RealOut.Buffer[ridx],
126 device->Dry.Buffer[c], state->Offset, state->IrSize,
127 state->Chan[c].Coeffs, state->Chan[c].Values, SamplesToDo
130 state->Offset += SamplesToDo;
133 void ProcessAmbiDec(ALCdevice *device, ALsizei SamplesToDo)
135 if(device->Dry.Buffer != device->FOAOut.Buffer)
136 bformatdec_upSample(device->AmbiDecoder.get(),
137 device->Dry.Buffer, device->FOAOut.Buffer, device->FOAOut.NumChannels,
138 SamplesToDo
140 bformatdec_process(device->AmbiDecoder.get(),
141 device->RealOut.Buffer, device->RealOut.NumChannels, device->Dry.Buffer,
142 SamplesToDo
146 void ProcessAmbiUp(ALCdevice *device, ALsizei SamplesToDo)
148 ambiup_process(device->AmbiUp.get(),
149 device->RealOut.Buffer, device->RealOut.NumChannels, device->FOAOut.Buffer,
150 SamplesToDo
154 void ProcessUhj(ALCdevice *device, ALsizei SamplesToDo)
156 int lidx = GetChannelIdxByName(&device->RealOut, FrontLeft);
157 int ridx = GetChannelIdxByName(&device->RealOut, FrontRight);
158 assert(lidx != -1 && ridx != -1);
160 /* Encode to stereo-compatible 2-channel UHJ output. */
161 EncodeUhj2(device->Uhj_Encoder.get(),
162 device->RealOut.Buffer[lidx], device->RealOut.Buffer[ridx],
163 device->Dry.Buffer, SamplesToDo
167 void ProcessBs2b(ALCdevice *device, ALsizei SamplesToDo)
169 int lidx = GetChannelIdxByName(&device->RealOut, FrontLeft);
170 int ridx = GetChannelIdxByName(&device->RealOut, FrontRight);
171 assert(lidx != -1 && ridx != -1);
173 /* Apply binaural/crossfeed filter */
174 bs2b_cross_feed(device->Bs2b.get(), device->RealOut.Buffer[lidx],
175 device->RealOut.Buffer[ridx], SamplesToDo);
178 } // namespace
180 void aluSelectPostProcess(ALCdevice *device)
182 if(device->HrtfHandle)
183 device->PostProcess = ProcessHrtf;
184 else if(device->AmbiDecoder)
185 device->PostProcess = ProcessAmbiDec;
186 else if(device->AmbiUp)
187 device->PostProcess = ProcessAmbiUp;
188 else if(device->Uhj_Encoder)
189 device->PostProcess = ProcessUhj;
190 else if(device->Bs2b)
191 device->PostProcess = ProcessBs2b;
192 else
193 device->PostProcess = NULL;
197 /* Prepares the interpolator for a given rate (determined by increment).
199 * With a bit of work, and a trade of memory for CPU cost, this could be
200 * modified for use with an interpolated increment for buttery-smooth pitch
201 * changes.
203 void BsincPrepare(const ALuint increment, BsincState *state, const BSincTable *table)
205 ALfloat sf = 0.0f;
206 ALsizei si = BSINC_SCALE_COUNT-1;
208 if(increment > FRACTIONONE)
210 sf = (ALfloat)FRACTIONONE / increment;
211 sf = maxf(0.0f, (BSINC_SCALE_COUNT-1) * (sf-table->scaleBase) * table->scaleRange);
212 si = float2int(sf);
213 /* The interpolation factor is fit to this diagonally-symmetric curve
214 * to reduce the transition ripple caused by interpolating different
215 * scales of the sinc function.
217 sf = 1.0f - cosf(asinf(sf - si));
220 state->sf = sf;
221 state->m = table->m[si];
222 state->l = (state->m/2) - 1;
223 state->filter = table->Tab + table->filterOffset[si];
227 namespace {
229 /* This RNG method was created based on the math found in opusdec. It's quick,
230 * and starting with a seed value of 22222, is suitable for generating
231 * whitenoise.
233 inline ALuint dither_rng(ALuint *seed) noexcept
235 *seed = (*seed * 96314165) + 907633515;
236 return *seed;
240 inline void aluCrossproduct(const ALfloat *inVector1, const ALfloat *inVector2, ALfloat *outVector)
242 outVector[0] = inVector1[1]*inVector2[2] - inVector1[2]*inVector2[1];
243 outVector[1] = inVector1[2]*inVector2[0] - inVector1[0]*inVector2[2];
244 outVector[2] = inVector1[0]*inVector2[1] - inVector1[1]*inVector2[0];
247 inline ALfloat aluDotproduct(const aluVector *vec1, const aluVector *vec2)
249 return vec1->v[0]*vec2->v[0] + vec1->v[1]*vec2->v[1] + vec1->v[2]*vec2->v[2];
252 ALfloat aluNormalize(ALfloat *vec)
254 ALfloat length = sqrtf(vec[0]*vec[0] + vec[1]*vec[1] + vec[2]*vec[2]);
255 if(length > FLT_EPSILON)
257 ALfloat inv_length = 1.0f/length;
258 vec[0] *= inv_length;
259 vec[1] *= inv_length;
260 vec[2] *= inv_length;
261 return length;
263 vec[0] = vec[1] = vec[2] = 0.0f;
264 return 0.0f;
267 void aluMatrixfFloat3(ALfloat *vec, ALfloat w, const aluMatrixf *mtx)
269 ALfloat v[4] = { vec[0], vec[1], vec[2], w };
271 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];
272 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];
273 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];
276 aluVector aluMatrixfVector(const aluMatrixf *mtx, const aluVector *vec)
278 aluVector v;
279 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];
280 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];
281 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];
282 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];
283 return v;
287 void SendSourceStoppedEvent(ALCcontext *context, ALuint id)
289 AsyncEvent evt = ASYNC_EVENT(EventType_SourceStateChange);
290 ALbitfieldSOFT enabledevt;
291 size_t strpos;
292 ALuint scale;
294 enabledevt = context->EnabledEvts.load(std::memory_order_acquire);
295 if(!(enabledevt&EventType_SourceStateChange)) return;
297 evt.u.user.type = AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT;
298 evt.u.user.id = id;
299 evt.u.user.param = AL_STOPPED;
301 /* Normally snprintf would be used, but this is called from the mixer and
302 * that function's not real-time safe, so we have to construct it manually.
304 strcpy(evt.u.user.msg, "Source ID "); strpos = 10;
305 scale = 1000000000;
306 while(scale > 0 && scale > id)
307 scale /= 10;
308 while(scale > 0)
310 evt.u.user.msg[strpos++] = '0' + ((id/scale)%10);
311 scale /= 10;
313 strcpy(evt.u.user.msg+strpos, " state changed to AL_STOPPED");
315 if(ll_ringbuffer_write(context->AsyncEvents, &evt, 1) == 1)
316 alsem_post(&context->EventSem);
320 bool CalcContextParams(ALCcontext *Context)
322 ALlistener &Listener = Context->Listener;
323 struct ALcontextProps *props;
325 props = Context->Update.exchange(nullptr, std::memory_order_acq_rel);
326 if(!props) return false;
328 Listener.Params.MetersPerUnit = props->MetersPerUnit;
330 Listener.Params.DopplerFactor = props->DopplerFactor;
331 Listener.Params.SpeedOfSound = props->SpeedOfSound * props->DopplerVelocity;
332 if(!OverrideReverbSpeedOfSound)
333 Listener.Params.ReverbSpeedOfSound = Listener.Params.SpeedOfSound *
334 Listener.Params.MetersPerUnit;
336 Listener.Params.SourceDistanceModel = props->SourceDistanceModel;
337 Listener.Params.mDistanceModel = props->mDistanceModel;
339 AtomicReplaceHead(Context->FreeContextProps, props);
340 return true;
343 bool CalcListenerParams(ALCcontext *Context)
345 ALlistener &Listener = Context->Listener;
346 ALfloat N[3], V[3], U[3], P[3];
347 struct ALlistenerProps *props;
348 aluVector vel;
350 props = Listener.Update.exchange(nullptr, std::memory_order_acq_rel);
351 if(!props) return false;
353 /* AT then UP */
354 N[0] = props->Forward[0];
355 N[1] = props->Forward[1];
356 N[2] = props->Forward[2];
357 aluNormalize(N);
358 V[0] = props->Up[0];
359 V[1] = props->Up[1];
360 V[2] = props->Up[2];
361 aluNormalize(V);
362 /* Build and normalize right-vector */
363 aluCrossproduct(N, V, U);
364 aluNormalize(U);
366 aluMatrixfSet(&Listener.Params.Matrix,
367 U[0], V[0], -N[0], 0.0,
368 U[1], V[1], -N[1], 0.0,
369 U[2], V[2], -N[2], 0.0,
370 0.0, 0.0, 0.0, 1.0
373 P[0] = props->Position[0];
374 P[1] = props->Position[1];
375 P[2] = props->Position[2];
376 aluMatrixfFloat3(P, 1.0, &Listener.Params.Matrix);
377 aluMatrixfSetRow(&Listener.Params.Matrix, 3, -P[0], -P[1], -P[2], 1.0f);
379 aluVectorSet(&vel, props->Velocity[0], props->Velocity[1], props->Velocity[2], 0.0f);
380 Listener.Params.Velocity = aluMatrixfVector(&Listener.Params.Matrix, &vel);
382 Listener.Params.Gain = props->Gain * Context->GainBoost;
384 AtomicReplaceHead(Context->FreeListenerProps, props);
385 return true;
388 bool CalcEffectSlotParams(ALeffectslot *slot, ALCcontext *context, bool force)
390 struct ALeffectslotProps *props;
391 EffectState *state;
393 props = slot->Update.exchange(nullptr, std::memory_order_acq_rel);
394 if(!props && !force) return false;
396 if(props)
398 slot->Params.Gain = props->Gain;
399 slot->Params.AuxSendAuto = props->AuxSendAuto;
400 slot->Params.EffectType = props->Type;
401 slot->Params.EffectProps = props->Props;
402 if(IsReverbEffect(props->Type))
404 slot->Params.RoomRolloff = props->Props.Reverb.RoomRolloffFactor;
405 slot->Params.DecayTime = props->Props.Reverb.DecayTime;
406 slot->Params.DecayLFRatio = props->Props.Reverb.DecayLFRatio;
407 slot->Params.DecayHFRatio = props->Props.Reverb.DecayHFRatio;
408 slot->Params.DecayHFLimit = props->Props.Reverb.DecayHFLimit;
409 slot->Params.AirAbsorptionGainHF = props->Props.Reverb.AirAbsorptionGainHF;
411 else
413 slot->Params.RoomRolloff = 0.0f;
414 slot->Params.DecayTime = 0.0f;
415 slot->Params.DecayLFRatio = 0.0f;
416 slot->Params.DecayHFRatio = 0.0f;
417 slot->Params.DecayHFLimit = AL_FALSE;
418 slot->Params.AirAbsorptionGainHF = 1.0f;
421 state = props->State;
423 if(state == slot->Params.mEffectState)
425 /* If the effect state is the same as current, we can decrement its
426 * count safely to remove it from the update object (it can't reach
427 * 0 refs since the current params also hold a reference).
429 DecrementRef(&state->mRef);
430 props->State = nullptr;
432 else
434 /* Otherwise, replace it and send off the old one with a release
435 * event.
437 AsyncEvent evt = ASYNC_EVENT(EventType_ReleaseEffectState);
438 evt.u.mEffectState = slot->Params.mEffectState;
440 slot->Params.mEffectState = state;
441 props->State = NULL;
443 if(LIKELY(ll_ringbuffer_write(context->AsyncEvents, &evt, 1) != 0))
444 alsem_post(&context->EventSem);
445 else
447 /* If writing the event failed, the queue was probably full.
448 * Store the old state in the property object where it can
449 * eventually be cleaned up sometime later (not ideal, but
450 * better than blocking or leaking).
452 props->State = evt.u.mEffectState;
456 AtomicReplaceHead(context->FreeEffectslotProps, props);
458 else
459 state = slot->Params.mEffectState;
461 state->update(context, slot, &slot->Params.EffectProps);
462 return true;
466 constexpr struct ChanMap MonoMap[1] = {
467 { FrontCenter, 0.0f, 0.0f }
468 }, RearMap[2] = {
469 { BackLeft, DEG2RAD(-150.0f), DEG2RAD(0.0f) },
470 { BackRight, DEG2RAD( 150.0f), DEG2RAD(0.0f) }
471 }, QuadMap[4] = {
472 { FrontLeft, DEG2RAD( -45.0f), DEG2RAD(0.0f) },
473 { FrontRight, DEG2RAD( 45.0f), DEG2RAD(0.0f) },
474 { BackLeft, DEG2RAD(-135.0f), DEG2RAD(0.0f) },
475 { BackRight, DEG2RAD( 135.0f), DEG2RAD(0.0f) }
476 }, X51Map[6] = {
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 { SideLeft, DEG2RAD(-110.0f), DEG2RAD(0.0f) },
482 { SideRight, DEG2RAD( 110.0f), DEG2RAD(0.0f) }
483 }, X61Map[7] = {
484 { FrontLeft, DEG2RAD(-30.0f), DEG2RAD(0.0f) },
485 { FrontRight, DEG2RAD( 30.0f), DEG2RAD(0.0f) },
486 { FrontCenter, DEG2RAD( 0.0f), DEG2RAD(0.0f) },
487 { LFE, 0.0f, 0.0f },
488 { BackCenter, DEG2RAD(180.0f), DEG2RAD(0.0f) },
489 { SideLeft, DEG2RAD(-90.0f), DEG2RAD(0.0f) },
490 { SideRight, DEG2RAD( 90.0f), DEG2RAD(0.0f) }
491 }, X71Map[8] = {
492 { FrontLeft, DEG2RAD( -30.0f), DEG2RAD(0.0f) },
493 { FrontRight, DEG2RAD( 30.0f), DEG2RAD(0.0f) },
494 { FrontCenter, DEG2RAD( 0.0f), DEG2RAD(0.0f) },
495 { LFE, 0.0f, 0.0f },
496 { BackLeft, DEG2RAD(-150.0f), DEG2RAD(0.0f) },
497 { BackRight, DEG2RAD( 150.0f), DEG2RAD(0.0f) },
498 { SideLeft, DEG2RAD( -90.0f), DEG2RAD(0.0f) },
499 { SideRight, DEG2RAD( 90.0f), DEG2RAD(0.0f) }
502 void CalcPanningAndFilters(ALvoice *voice, const ALfloat Azi, const ALfloat Elev,
503 const ALfloat Distance, const ALfloat Spread,
504 const ALfloat DryGain, const ALfloat DryGainHF,
505 const ALfloat DryGainLF, const ALfloat *WetGain,
506 const ALfloat *WetGainLF, const ALfloat *WetGainHF,
507 ALeffectslot **SendSlots, const ALbuffer *Buffer,
508 const struct ALvoiceProps *props, const ALlistener &Listener,
509 const ALCdevice *Device)
511 struct ChanMap StereoMap[2] = {
512 { FrontLeft, DEG2RAD(-30.0f), DEG2RAD(0.0f) },
513 { FrontRight, DEG2RAD( 30.0f), DEG2RAD(0.0f) }
515 bool DirectChannels = props->DirectChannels;
516 const ALsizei NumSends = Device->NumAuxSends;
517 const ALuint Frequency = Device->Frequency;
518 const struct ChanMap *chans = NULL;
519 ALsizei num_channels = 0;
520 bool isbformat = false;
521 ALfloat downmix_gain = 1.0f;
522 ALsizei c, i;
524 switch(Buffer->FmtChannels)
526 case FmtMono:
527 chans = MonoMap;
528 num_channels = 1;
529 /* Mono buffers are never played direct. */
530 DirectChannels = false;
531 break;
533 case FmtStereo:
534 /* Convert counter-clockwise to clockwise. */
535 StereoMap[0].angle = -props->StereoPan[0];
536 StereoMap[1].angle = -props->StereoPan[1];
538 chans = StereoMap;
539 num_channels = 2;
540 downmix_gain = 1.0f / 2.0f;
541 break;
543 case FmtRear:
544 chans = RearMap;
545 num_channels = 2;
546 downmix_gain = 1.0f / 2.0f;
547 break;
549 case FmtQuad:
550 chans = QuadMap;
551 num_channels = 4;
552 downmix_gain = 1.0f / 4.0f;
553 break;
555 case FmtX51:
556 chans = X51Map;
557 num_channels = 6;
558 /* NOTE: Excludes LFE. */
559 downmix_gain = 1.0f / 5.0f;
560 break;
562 case FmtX61:
563 chans = X61Map;
564 num_channels = 7;
565 /* NOTE: Excludes LFE. */
566 downmix_gain = 1.0f / 6.0f;
567 break;
569 case FmtX71:
570 chans = X71Map;
571 num_channels = 8;
572 /* NOTE: Excludes LFE. */
573 downmix_gain = 1.0f / 7.0f;
574 break;
576 case FmtBFormat2D:
577 num_channels = 3;
578 isbformat = true;
579 DirectChannels = false;
580 break;
582 case FmtBFormat3D:
583 num_channels = 4;
584 isbformat = true;
585 DirectChannels = false;
586 break;
589 for(c = 0;c < num_channels;c++)
591 memset(&voice->Direct.Params[c].Hrtf.Target, 0,
592 sizeof(voice->Direct.Params[c].Hrtf.Target));
593 ClearArray(voice->Direct.Params[c].Gains.Target);
595 for(i = 0;i < NumSends;i++)
597 for(c = 0;c < num_channels;c++)
598 ClearArray(voice->Send[i].Params[c].Gains.Target);
601 voice->Flags &= ~(VOICE_HAS_HRTF | VOICE_HAS_NFC);
602 if(isbformat)
604 /* Special handling for B-Format sources. */
606 if(Distance > FLT_EPSILON)
608 /* Panning a B-Format sound toward some direction is easy. Just pan
609 * the first (W) channel as a normal mono sound and silence the
610 * others.
612 ALfloat coeffs[MAX_AMBI_COEFFS];
614 if(Device->AvgSpeakerDist > 0.0f)
616 ALfloat mdist = Distance * Listener.Params.MetersPerUnit;
617 ALfloat w0 = SPEEDOFSOUNDMETRESPERSEC /
618 (mdist * (ALfloat)Device->Frequency);
619 ALfloat w1 = SPEEDOFSOUNDMETRESPERSEC /
620 (Device->AvgSpeakerDist * (ALfloat)Device->Frequency);
621 /* Clamp w0 for really close distances, to prevent excessive
622 * bass.
624 w0 = minf(w0, w1*4.0f);
626 /* Only need to adjust the first channel of a B-Format source. */
627 NfcFilterAdjust(&voice->Direct.Params[0].NFCtrlFilter, w0);
629 for(i = 0;i < MAX_AMBI_ORDER+1;i++)
630 voice->Direct.ChannelsPerOrder[i] = Device->NumChannelsPerOrder[i];
631 voice->Flags |= VOICE_HAS_NFC;
634 /* A scalar of 1.5 for plain stereo results in +/-60 degrees being
635 * moved to +/-90 degrees for direct right and left speaker
636 * responses.
638 CalcAngleCoeffs((Device->Render_Mode==StereoPair) ? ScaleAzimuthFront(Azi, 1.5f) : Azi,
639 Elev, Spread, coeffs);
641 /* NOTE: W needs to be scaled by sqrt(2) due to FuMa normalization. */
642 ComputePanGains(&Device->Dry, coeffs, DryGain*SQRTF_2,
643 voice->Direct.Params[0].Gains.Target);
644 for(i = 0;i < NumSends;i++)
646 const ALeffectslot *Slot = SendSlots[i];
647 if(Slot)
648 ComputePanningGainsBF(Slot->ChanMap, Slot->NumChannels, coeffs,
649 WetGain[i]*SQRTF_2, voice->Send[i].Params[0].Gains.Target
653 else
655 /* Local B-Format sources have their XYZ channels rotated according
656 * to the orientation.
658 ALfloat N[3], V[3], U[3];
659 aluMatrixf matrix;
661 if(Device->AvgSpeakerDist > 0.0f)
663 /* NOTE: The NFCtrlFilters were created with a w0 of 0, which
664 * is what we want for FOA input. The first channel may have
665 * been previously re-adjusted if panned, so reset it.
667 NfcFilterAdjust(&voice->Direct.Params[0].NFCtrlFilter, 0.0f);
669 voice->Direct.ChannelsPerOrder[0] = 1;
670 voice->Direct.ChannelsPerOrder[1] = mini(voice->Direct.Channels-1, 3);
671 for(i = 2;i < MAX_AMBI_ORDER+1;i++)
672 voice->Direct.ChannelsPerOrder[i] = 0;
673 voice->Flags |= VOICE_HAS_NFC;
676 /* AT then UP */
677 N[0] = props->Orientation[0][0];
678 N[1] = props->Orientation[0][1];
679 N[2] = props->Orientation[0][2];
680 aluNormalize(N);
681 V[0] = props->Orientation[1][0];
682 V[1] = props->Orientation[1][1];
683 V[2] = props->Orientation[1][2];
684 aluNormalize(V);
685 if(!props->HeadRelative)
687 const aluMatrixf *lmatrix = &Listener.Params.Matrix;
688 aluMatrixfFloat3(N, 0.0f, lmatrix);
689 aluMatrixfFloat3(V, 0.0f, lmatrix);
691 /* Build and normalize right-vector */
692 aluCrossproduct(N, V, U);
693 aluNormalize(U);
695 /* Build a rotate + conversion matrix (FuMa -> ACN+N3D). NOTE: This
696 * matrix is transposed, for the inputs to align on the rows and
697 * outputs on the columns.
699 aluMatrixfSet(&matrix,
700 // ACN0 ACN1 ACN2 ACN3
701 SQRTF_2, 0.0f, 0.0f, 0.0f, // Ambi W
702 0.0f, -N[0]*SQRTF_3, N[1]*SQRTF_3, -N[2]*SQRTF_3, // Ambi X
703 0.0f, U[0]*SQRTF_3, -U[1]*SQRTF_3, U[2]*SQRTF_3, // Ambi Y
704 0.0f, -V[0]*SQRTF_3, V[1]*SQRTF_3, -V[2]*SQRTF_3 // Ambi Z
707 voice->Direct.Buffer = Device->FOAOut.Buffer;
708 voice->Direct.Channels = Device->FOAOut.NumChannels;
709 for(c = 0;c < num_channels;c++)
710 ComputePanGains(&Device->FOAOut, matrix.m[c], DryGain,
711 voice->Direct.Params[c].Gains.Target);
712 for(i = 0;i < NumSends;i++)
714 const ALeffectslot *Slot = SendSlots[i];
715 if(Slot)
717 for(c = 0;c < num_channels;c++)
718 ComputePanningGainsBF(Slot->ChanMap, Slot->NumChannels,
719 matrix.m[c], WetGain[i], voice->Send[i].Params[c].Gains.Target
725 else if(DirectChannels)
727 /* Direct source channels always play local. Skip the virtual channels
728 * and write inputs to the matching real outputs.
730 voice->Direct.Buffer = Device->RealOut.Buffer;
731 voice->Direct.Channels = Device->RealOut.NumChannels;
733 for(c = 0;c < num_channels;c++)
735 int idx = GetChannelIdxByName(&Device->RealOut, chans[c].channel);
736 if(idx != -1) voice->Direct.Params[c].Gains.Target[idx] = DryGain;
739 /* Auxiliary sends still use normal channel panning since they mix to
740 * B-Format, which can't channel-match.
742 for(c = 0;c < num_channels;c++)
744 ALfloat coeffs[MAX_AMBI_COEFFS];
745 CalcAngleCoeffs(chans[c].angle, chans[c].elevation, 0.0f, coeffs);
747 for(i = 0;i < NumSends;i++)
749 const ALeffectslot *Slot = SendSlots[i];
750 if(Slot)
751 ComputePanningGainsBF(Slot->ChanMap, Slot->NumChannels,
752 coeffs, WetGain[i], voice->Send[i].Params[c].Gains.Target
757 else if(Device->Render_Mode == HrtfRender)
759 /* Full HRTF rendering. Skip the virtual channels and render to the
760 * real outputs.
762 voice->Direct.Buffer = Device->RealOut.Buffer;
763 voice->Direct.Channels = Device->RealOut.NumChannels;
765 if(Distance > FLT_EPSILON)
767 ALfloat coeffs[MAX_AMBI_COEFFS];
769 /* Get the HRIR coefficients and delays just once, for the given
770 * source direction.
772 GetHrtfCoeffs(Device->HrtfHandle, Elev, Azi, Spread,
773 voice->Direct.Params[0].Hrtf.Target.Coeffs,
774 voice->Direct.Params[0].Hrtf.Target.Delay);
775 voice->Direct.Params[0].Hrtf.Target.Gain = DryGain * downmix_gain;
777 /* Remaining channels use the same results as the first. */
778 for(c = 1;c < num_channels;c++)
780 /* Skip LFE */
781 if(chans[c].channel != LFE)
782 voice->Direct.Params[c].Hrtf.Target = voice->Direct.Params[0].Hrtf.Target;
785 /* Calculate the directional coefficients once, which apply to all
786 * input channels of the source sends.
788 CalcAngleCoeffs(Azi, Elev, Spread, coeffs);
790 for(i = 0;i < NumSends;i++)
792 const ALeffectslot *Slot = SendSlots[i];
793 if(Slot)
794 for(c = 0;c < num_channels;c++)
796 /* Skip LFE */
797 if(chans[c].channel != LFE)
798 ComputePanningGainsBF(Slot->ChanMap,
799 Slot->NumChannels, coeffs, WetGain[i] * downmix_gain,
800 voice->Send[i].Params[c].Gains.Target
805 else
807 /* Local sources on HRTF play with each channel panned to its
808 * relative location around the listener, providing "virtual
809 * speaker" responses.
811 for(c = 0;c < num_channels;c++)
813 ALfloat coeffs[MAX_AMBI_COEFFS];
815 if(chans[c].channel == LFE)
817 /* Skip LFE */
818 continue;
821 /* Get the HRIR coefficients and delays for this channel
822 * position.
824 GetHrtfCoeffs(Device->HrtfHandle,
825 chans[c].elevation, chans[c].angle, Spread,
826 voice->Direct.Params[c].Hrtf.Target.Coeffs,
827 voice->Direct.Params[c].Hrtf.Target.Delay
829 voice->Direct.Params[c].Hrtf.Target.Gain = DryGain;
831 /* Normal panning for auxiliary sends. */
832 CalcAngleCoeffs(chans[c].angle, chans[c].elevation, Spread, coeffs);
834 for(i = 0;i < NumSends;i++)
836 const ALeffectslot *Slot = SendSlots[i];
837 if(Slot)
838 ComputePanningGainsBF(Slot->ChanMap, Slot->NumChannels,
839 coeffs, WetGain[i], voice->Send[i].Params[c].Gains.Target
845 voice->Flags |= VOICE_HAS_HRTF;
847 else
849 /* Non-HRTF rendering. Use normal panning to the output. */
851 if(Distance > FLT_EPSILON)
853 ALfloat coeffs[MAX_AMBI_COEFFS];
854 ALfloat w0 = 0.0f;
856 /* Calculate NFC filter coefficient if needed. */
857 if(Device->AvgSpeakerDist > 0.0f)
859 ALfloat mdist = Distance * Listener.Params.MetersPerUnit;
860 ALfloat w1 = SPEEDOFSOUNDMETRESPERSEC /
861 (Device->AvgSpeakerDist * (ALfloat)Device->Frequency);
862 w0 = SPEEDOFSOUNDMETRESPERSEC /
863 (mdist * (ALfloat)Device->Frequency);
864 /* Clamp w0 for really close distances, to prevent excessive
865 * bass.
867 w0 = minf(w0, w1*4.0f);
869 /* Adjust NFC filters. */
870 for(c = 0;c < num_channels;c++)
871 NfcFilterAdjust(&voice->Direct.Params[c].NFCtrlFilter, w0);
873 for(i = 0;i < MAX_AMBI_ORDER+1;i++)
874 voice->Direct.ChannelsPerOrder[i] = Device->NumChannelsPerOrder[i];
875 voice->Flags |= VOICE_HAS_NFC;
878 /* Calculate the directional coefficients once, which apply to all
879 * input channels.
881 CalcAngleCoeffs((Device->Render_Mode==StereoPair) ? ScaleAzimuthFront(Azi, 1.5f) : Azi,
882 Elev, Spread, coeffs);
884 for(c = 0;c < num_channels;c++)
886 /* Special-case LFE */
887 if(chans[c].channel == LFE)
889 if(Device->Dry.Buffer == Device->RealOut.Buffer)
891 int idx = GetChannelIdxByName(&Device->RealOut, chans[c].channel);
892 if(idx != -1) voice->Direct.Params[c].Gains.Target[idx] = DryGain;
894 continue;
897 ComputePanGains(&Device->Dry, coeffs, DryGain * downmix_gain,
898 voice->Direct.Params[c].Gains.Target);
901 for(i = 0;i < NumSends;i++)
903 const ALeffectslot *Slot = SendSlots[i];
904 if(Slot)
905 for(c = 0;c < num_channels;c++)
907 /* Skip LFE */
908 if(chans[c].channel != LFE)
909 ComputePanningGainsBF(Slot->ChanMap,
910 Slot->NumChannels, coeffs, WetGain[i] * downmix_gain,
911 voice->Send[i].Params[c].Gains.Target
916 else
918 ALfloat w0 = 0.0f;
920 if(Device->AvgSpeakerDist > 0.0f)
922 /* If the source distance is 0, set w0 to w1 to act as a pass-
923 * through. We still want to pass the signal through the
924 * filters so they keep an appropriate history, in case the
925 * source moves away from the listener.
927 w0 = SPEEDOFSOUNDMETRESPERSEC /
928 (Device->AvgSpeakerDist * (ALfloat)Device->Frequency);
930 for(c = 0;c < num_channels;c++)
931 NfcFilterAdjust(&voice->Direct.Params[c].NFCtrlFilter, w0);
933 for(i = 0;i < MAX_AMBI_ORDER+1;i++)
934 voice->Direct.ChannelsPerOrder[i] = Device->NumChannelsPerOrder[i];
935 voice->Flags |= VOICE_HAS_NFC;
938 for(c = 0;c < num_channels;c++)
940 ALfloat coeffs[MAX_AMBI_COEFFS];
942 /* Special-case LFE */
943 if(chans[c].channel == LFE)
945 if(Device->Dry.Buffer == Device->RealOut.Buffer)
947 int idx = GetChannelIdxByName(&Device->RealOut, chans[c].channel);
948 if(idx != -1) voice->Direct.Params[c].Gains.Target[idx] = DryGain;
950 continue;
953 CalcAngleCoeffs(
954 (Device->Render_Mode==StereoPair) ? ScaleAzimuthFront(chans[c].angle, 3.0f)
955 : chans[c].angle,
956 chans[c].elevation, Spread, coeffs
959 ComputePanGains(&Device->Dry, coeffs, DryGain,
960 voice->Direct.Params[c].Gains.Target);
961 for(i = 0;i < NumSends;i++)
963 const ALeffectslot *Slot = SendSlots[i];
964 if(Slot)
965 ComputePanningGainsBF(Slot->ChanMap, Slot->NumChannels,
966 coeffs, WetGain[i], voice->Send[i].Params[c].Gains.Target
974 ALfloat hfScale = props->Direct.HFReference / Frequency;
975 ALfloat lfScale = props->Direct.LFReference / Frequency;
976 ALfloat gainHF = maxf(DryGainHF, 0.001f); /* Limit -60dB */
977 ALfloat gainLF = maxf(DryGainLF, 0.001f);
979 voice->Direct.FilterType = AF_None;
980 if(gainHF != 1.0f) voice->Direct.FilterType |= AF_LowPass;
981 if(gainLF != 1.0f) voice->Direct.FilterType |= AF_HighPass;
982 BiquadFilter_setParams(
983 &voice->Direct.Params[0].LowPass, BiquadType::HighShelf,
984 gainHF, hfScale, calc_rcpQ_from_slope(gainHF, 1.0f)
986 BiquadFilter_setParams(
987 &voice->Direct.Params[0].HighPass, BiquadType::LowShelf,
988 gainLF, lfScale, calc_rcpQ_from_slope(gainLF, 1.0f)
990 for(c = 1;c < num_channels;c++)
992 BiquadFilter_copyParams(&voice->Direct.Params[c].LowPass,
993 &voice->Direct.Params[0].LowPass);
994 BiquadFilter_copyParams(&voice->Direct.Params[c].HighPass,
995 &voice->Direct.Params[0].HighPass);
998 for(i = 0;i < NumSends;i++)
1000 ALfloat hfScale = props->Send[i].HFReference / Frequency;
1001 ALfloat lfScale = props->Send[i].LFReference / Frequency;
1002 ALfloat gainHF = maxf(WetGainHF[i], 0.001f);
1003 ALfloat gainLF = maxf(WetGainLF[i], 0.001f);
1005 voice->Send[i].FilterType = AF_None;
1006 if(gainHF != 1.0f) voice->Send[i].FilterType |= AF_LowPass;
1007 if(gainLF != 1.0f) voice->Send[i].FilterType |= AF_HighPass;
1008 BiquadFilter_setParams(
1009 &voice->Send[i].Params[0].LowPass, BiquadType::HighShelf,
1010 gainHF, hfScale, calc_rcpQ_from_slope(gainHF, 1.0f)
1012 BiquadFilter_setParams(
1013 &voice->Send[i].Params[0].HighPass, BiquadType::LowShelf,
1014 gainLF, lfScale, calc_rcpQ_from_slope(gainLF, 1.0f)
1016 for(c = 1;c < num_channels;c++)
1018 BiquadFilter_copyParams(&voice->Send[i].Params[c].LowPass,
1019 &voice->Send[i].Params[0].LowPass);
1020 BiquadFilter_copyParams(&voice->Send[i].Params[c].HighPass,
1021 &voice->Send[i].Params[0].HighPass);
1026 void CalcNonAttnSourceParams(ALvoice *voice, const struct ALvoiceProps *props, const ALbuffer *ALBuffer, const ALCcontext *ALContext)
1028 const ALCdevice *Device = ALContext->Device;
1029 const ALlistener &Listener = ALContext->Listener;
1030 ALfloat DryGain, DryGainHF, DryGainLF;
1031 ALfloat WetGain[MAX_SENDS];
1032 ALfloat WetGainHF[MAX_SENDS];
1033 ALfloat WetGainLF[MAX_SENDS];
1034 ALeffectslot *SendSlots[MAX_SENDS];
1035 ALfloat Pitch;
1036 ALsizei i;
1038 voice->Direct.Buffer = Device->Dry.Buffer;
1039 voice->Direct.Channels = Device->Dry.NumChannels;
1040 for(i = 0;i < Device->NumAuxSends;i++)
1042 SendSlots[i] = props->Send[i].Slot;
1043 if(!SendSlots[i] && i == 0)
1044 SendSlots[i] = ALContext->DefaultSlot.get();
1045 if(!SendSlots[i] || SendSlots[i]->Params.EffectType == AL_EFFECT_NULL)
1047 SendSlots[i] = NULL;
1048 voice->Send[i].Buffer = NULL;
1049 voice->Send[i].Channels = 0;
1051 else
1053 voice->Send[i].Buffer = SendSlots[i]->WetBuffer;
1054 voice->Send[i].Channels = SendSlots[i]->NumChannels;
1058 /* Calculate the stepping value */
1059 Pitch = (ALfloat)ALBuffer->Frequency/(ALfloat)Device->Frequency * props->Pitch;
1060 if(Pitch > (ALfloat)MAX_PITCH)
1061 voice->Step = MAX_PITCH<<FRACTIONBITS;
1062 else
1063 voice->Step = maxi(fastf2i(Pitch * FRACTIONONE), 1);
1064 if(props->Resampler == BSinc24Resampler)
1065 BsincPrepare(voice->Step, &voice->ResampleState.bsinc, &bsinc24);
1066 else if(props->Resampler == BSinc12Resampler)
1067 BsincPrepare(voice->Step, &voice->ResampleState.bsinc, &bsinc12);
1068 voice->Resampler = SelectResampler(props->Resampler);
1070 /* Calculate gains */
1071 DryGain = clampf(props->Gain, props->MinGain, props->MaxGain);
1072 DryGain *= props->Direct.Gain * Listener.Params.Gain;
1073 DryGain = minf(DryGain, GAIN_MIX_MAX);
1074 DryGainHF = props->Direct.GainHF;
1075 DryGainLF = props->Direct.GainLF;
1076 for(i = 0;i < Device->NumAuxSends;i++)
1078 WetGain[i] = clampf(props->Gain, props->MinGain, props->MaxGain);
1079 WetGain[i] *= props->Send[i].Gain * Listener.Params.Gain;
1080 WetGain[i] = minf(WetGain[i], GAIN_MIX_MAX);
1081 WetGainHF[i] = props->Send[i].GainHF;
1082 WetGainLF[i] = props->Send[i].GainLF;
1085 CalcPanningAndFilters(voice, 0.0f, 0.0f, 0.0f, 0.0f, DryGain, DryGainHF, DryGainLF, WetGain,
1086 WetGainLF, WetGainHF, SendSlots, ALBuffer, props, Listener, Device);
1089 void CalcAttnSourceParams(ALvoice *voice, const struct ALvoiceProps *props, const ALbuffer *ALBuffer, const ALCcontext *ALContext)
1091 const ALCdevice *Device = ALContext->Device;
1092 const ALlistener &Listener = ALContext->Listener;
1093 const ALsizei NumSends = Device->NumAuxSends;
1094 aluVector Position, Velocity, Direction, SourceToListener;
1095 ALfloat Distance, ClampedDist, DopplerFactor;
1096 ALeffectslot *SendSlots[MAX_SENDS];
1097 ALfloat RoomRolloff[MAX_SENDS];
1098 ALfloat DecayDistance[MAX_SENDS];
1099 ALfloat DecayLFDistance[MAX_SENDS];
1100 ALfloat DecayHFDistance[MAX_SENDS];
1101 ALfloat DryGain, DryGainHF, DryGainLF;
1102 ALfloat WetGain[MAX_SENDS];
1103 ALfloat WetGainHF[MAX_SENDS];
1104 ALfloat WetGainLF[MAX_SENDS];
1105 bool directional;
1106 ALfloat ev, az;
1107 ALfloat spread;
1108 ALfloat Pitch;
1109 ALint i;
1111 /* Set mixing buffers and get send parameters. */
1112 voice->Direct.Buffer = Device->Dry.Buffer;
1113 voice->Direct.Channels = Device->Dry.NumChannels;
1114 for(i = 0;i < NumSends;i++)
1116 SendSlots[i] = props->Send[i].Slot;
1117 if(!SendSlots[i] && i == 0)
1118 SendSlots[i] = ALContext->DefaultSlot.get();
1119 if(!SendSlots[i] || SendSlots[i]->Params.EffectType == AL_EFFECT_NULL)
1121 SendSlots[i] = NULL;
1122 RoomRolloff[i] = 0.0f;
1123 DecayDistance[i] = 0.0f;
1124 DecayLFDistance[i] = 0.0f;
1125 DecayHFDistance[i] = 0.0f;
1127 else if(SendSlots[i]->Params.AuxSendAuto)
1129 RoomRolloff[i] = SendSlots[i]->Params.RoomRolloff + props->RoomRolloffFactor;
1130 /* Calculate the distances to where this effect's decay reaches
1131 * -60dB.
1133 DecayDistance[i] = SendSlots[i]->Params.DecayTime *
1134 Listener.Params.ReverbSpeedOfSound;
1135 DecayLFDistance[i] = DecayDistance[i] * SendSlots[i]->Params.DecayLFRatio;
1136 DecayHFDistance[i] = DecayDistance[i] * SendSlots[i]->Params.DecayHFRatio;
1137 if(SendSlots[i]->Params.DecayHFLimit)
1139 ALfloat airAbsorption = SendSlots[i]->Params.AirAbsorptionGainHF;
1140 if(airAbsorption < 1.0f)
1142 /* Calculate the distance to where this effect's air
1143 * absorption reaches -60dB, and limit the effect's HF
1144 * decay distance (so it doesn't take any longer to decay
1145 * than the air would allow).
1147 ALfloat absorb_dist = log10f(REVERB_DECAY_GAIN) / log10f(airAbsorption);
1148 DecayHFDistance[i] = minf(absorb_dist, DecayHFDistance[i]);
1152 else
1154 /* If the slot's auxiliary send auto is off, the data sent to the
1155 * effect slot is the same as the dry path, sans filter effects */
1156 RoomRolloff[i] = props->RolloffFactor;
1157 DecayDistance[i] = 0.0f;
1158 DecayLFDistance[i] = 0.0f;
1159 DecayHFDistance[i] = 0.0f;
1162 if(!SendSlots[i])
1164 voice->Send[i].Buffer = NULL;
1165 voice->Send[i].Channels = 0;
1167 else
1169 voice->Send[i].Buffer = SendSlots[i]->WetBuffer;
1170 voice->Send[i].Channels = SendSlots[i]->NumChannels;
1174 /* Transform source to listener space (convert to head relative) */
1175 aluVectorSet(&Position, props->Position[0], props->Position[1], props->Position[2], 1.0f);
1176 aluVectorSet(&Direction, props->Direction[0], props->Direction[1], props->Direction[2], 0.0f);
1177 aluVectorSet(&Velocity, props->Velocity[0], props->Velocity[1], props->Velocity[2], 0.0f);
1178 if(props->HeadRelative == AL_FALSE)
1180 const aluMatrixf *Matrix = &Listener.Params.Matrix;
1181 /* Transform source vectors */
1182 Position = aluMatrixfVector(Matrix, &Position);
1183 Velocity = aluMatrixfVector(Matrix, &Velocity);
1184 Direction = aluMatrixfVector(Matrix, &Direction);
1186 else
1188 const aluVector *lvelocity = &Listener.Params.Velocity;
1189 /* Offset the source velocity to be relative of the listener velocity */
1190 Velocity.v[0] += lvelocity->v[0];
1191 Velocity.v[1] += lvelocity->v[1];
1192 Velocity.v[2] += lvelocity->v[2];
1195 directional = aluNormalize(Direction.v) > 0.0f;
1196 SourceToListener.v[0] = -Position.v[0];
1197 SourceToListener.v[1] = -Position.v[1];
1198 SourceToListener.v[2] = -Position.v[2];
1199 SourceToListener.v[3] = 0.0f;
1200 Distance = aluNormalize(SourceToListener.v);
1202 /* Initial source gain */
1203 DryGain = props->Gain;
1204 DryGainHF = 1.0f;
1205 DryGainLF = 1.0f;
1206 for(i = 0;i < NumSends;i++)
1208 WetGain[i] = props->Gain;
1209 WetGainHF[i] = 1.0f;
1210 WetGainLF[i] = 1.0f;
1213 /* Calculate distance attenuation */
1214 ClampedDist = Distance;
1216 switch(Listener.Params.SourceDistanceModel ?
1217 props->mDistanceModel : Listener.Params.mDistanceModel)
1219 case DistanceModel::InverseClamped:
1220 ClampedDist = clampf(ClampedDist, props->RefDistance, props->MaxDistance);
1221 if(props->MaxDistance < props->RefDistance)
1222 break;
1223 /*fall-through*/
1224 case DistanceModel::Inverse:
1225 if(!(props->RefDistance > 0.0f))
1226 ClampedDist = props->RefDistance;
1227 else
1229 ALfloat dist = lerp(props->RefDistance, ClampedDist, props->RolloffFactor);
1230 if(dist > 0.0f) DryGain *= props->RefDistance / dist;
1231 for(i = 0;i < NumSends;i++)
1233 dist = lerp(props->RefDistance, ClampedDist, RoomRolloff[i]);
1234 if(dist > 0.0f) WetGain[i] *= props->RefDistance / dist;
1237 break;
1239 case DistanceModel::LinearClamped:
1240 ClampedDist = clampf(ClampedDist, props->RefDistance, props->MaxDistance);
1241 if(props->MaxDistance < props->RefDistance)
1242 break;
1243 /*fall-through*/
1244 case DistanceModel::Linear:
1245 if(!(props->MaxDistance != props->RefDistance))
1246 ClampedDist = props->RefDistance;
1247 else
1249 ALfloat attn = props->RolloffFactor * (ClampedDist-props->RefDistance) /
1250 (props->MaxDistance-props->RefDistance);
1251 DryGain *= maxf(1.0f - attn, 0.0f);
1252 for(i = 0;i < NumSends;i++)
1254 attn = RoomRolloff[i] * (ClampedDist-props->RefDistance) /
1255 (props->MaxDistance-props->RefDistance);
1256 WetGain[i] *= maxf(1.0f - attn, 0.0f);
1259 break;
1261 case DistanceModel::ExponentClamped:
1262 ClampedDist = clampf(ClampedDist, props->RefDistance, props->MaxDistance);
1263 if(props->MaxDistance < props->RefDistance)
1264 break;
1265 /*fall-through*/
1266 case DistanceModel::Exponent:
1267 if(!(ClampedDist > 0.0f && props->RefDistance > 0.0f))
1268 ClampedDist = props->RefDistance;
1269 else
1271 DryGain *= powf(ClampedDist/props->RefDistance, -props->RolloffFactor);
1272 for(i = 0;i < NumSends;i++)
1273 WetGain[i] *= powf(ClampedDist/props->RefDistance, -RoomRolloff[i]);
1275 break;
1277 case DistanceModel::Disable:
1278 ClampedDist = props->RefDistance;
1279 break;
1282 /* Calculate directional soundcones */
1283 if(directional && props->InnerAngle < 360.0f)
1285 ALfloat ConeVolume;
1286 ALfloat ConeHF;
1287 ALfloat Angle;
1289 Angle = acosf(aluDotproduct(&Direction, &SourceToListener));
1290 Angle = RAD2DEG(Angle * ConeScale * 2.0f);
1291 if(!(Angle > props->InnerAngle))
1293 ConeVolume = 1.0f;
1294 ConeHF = 1.0f;
1296 else if(Angle < props->OuterAngle)
1298 ALfloat scale = ( Angle-props->InnerAngle) /
1299 (props->OuterAngle-props->InnerAngle);
1300 ConeVolume = lerp(1.0f, props->OuterGain, scale);
1301 ConeHF = lerp(1.0f, props->OuterGainHF, scale);
1303 else
1305 ConeVolume = props->OuterGain;
1306 ConeHF = props->OuterGainHF;
1309 DryGain *= ConeVolume;
1310 if(props->DryGainHFAuto)
1311 DryGainHF *= ConeHF;
1312 if(props->WetGainAuto)
1314 for(i = 0;i < NumSends;i++)
1315 WetGain[i] *= ConeVolume;
1317 if(props->WetGainHFAuto)
1319 for(i = 0;i < NumSends;i++)
1320 WetGainHF[i] *= ConeHF;
1324 /* Apply gain and frequency filters */
1325 DryGain = clampf(DryGain, props->MinGain, props->MaxGain);
1326 DryGain = minf(DryGain*props->Direct.Gain*Listener.Params.Gain, GAIN_MIX_MAX);
1327 DryGainHF *= props->Direct.GainHF;
1328 DryGainLF *= props->Direct.GainLF;
1329 for(i = 0;i < NumSends;i++)
1331 WetGain[i] = clampf(WetGain[i], props->MinGain, props->MaxGain);
1332 WetGain[i] = minf(WetGain[i]*props->Send[i].Gain*Listener.Params.Gain, GAIN_MIX_MAX);
1333 WetGainHF[i] *= props->Send[i].GainHF;
1334 WetGainLF[i] *= props->Send[i].GainLF;
1337 /* Distance-based air absorption and initial send decay. */
1338 if(ClampedDist > props->RefDistance && props->RolloffFactor > 0.0f)
1340 ALfloat meters_base = (ClampedDist-props->RefDistance) * props->RolloffFactor *
1341 Listener.Params.MetersPerUnit;
1342 if(props->AirAbsorptionFactor > 0.0f)
1344 ALfloat hfattn = powf(AIRABSORBGAINHF, meters_base * props->AirAbsorptionFactor);
1345 DryGainHF *= hfattn;
1346 for(i = 0;i < NumSends;i++)
1347 WetGainHF[i] *= hfattn;
1350 if(props->WetGainAuto)
1352 /* Apply a decay-time transformation to the wet path, based on the
1353 * source distance in meters. The initial decay of the reverb
1354 * effect is calculated and applied to the wet path.
1356 for(i = 0;i < NumSends;i++)
1358 ALfloat gain, gainhf, gainlf;
1360 if(!(DecayDistance[i] > 0.0f))
1361 continue;
1363 gain = powf(REVERB_DECAY_GAIN, meters_base/DecayDistance[i]);
1364 WetGain[i] *= gain;
1365 /* Yes, the wet path's air absorption is applied with
1366 * WetGainAuto on, rather than WetGainHFAuto.
1368 if(gain > 0.0f)
1370 gainhf = powf(REVERB_DECAY_GAIN, meters_base/DecayHFDistance[i]);
1371 WetGainHF[i] *= minf(gainhf / gain, 1.0f);
1372 gainlf = powf(REVERB_DECAY_GAIN, meters_base/DecayLFDistance[i]);
1373 WetGainLF[i] *= minf(gainlf / gain, 1.0f);
1380 /* Initial source pitch */
1381 Pitch = props->Pitch;
1383 /* Calculate velocity-based doppler effect */
1384 DopplerFactor = props->DopplerFactor * Listener.Params.DopplerFactor;
1385 if(DopplerFactor > 0.0f)
1387 const aluVector *lvelocity = &Listener.Params.Velocity;
1388 const ALfloat SpeedOfSound = Listener.Params.SpeedOfSound;
1389 ALfloat vss, vls;
1391 vss = aluDotproduct(&Velocity, &SourceToListener) * DopplerFactor;
1392 vls = aluDotproduct(lvelocity, &SourceToListener) * DopplerFactor;
1394 if(!(vls < SpeedOfSound))
1396 /* Listener moving away from the source at the speed of sound.
1397 * Sound waves can't catch it.
1399 Pitch = 0.0f;
1401 else if(!(vss < SpeedOfSound))
1403 /* Source moving toward the listener at the speed of sound. Sound
1404 * waves bunch up to extreme frequencies.
1406 Pitch = HUGE_VALF;
1408 else
1410 /* Source and listener movement is nominal. Calculate the proper
1411 * doppler shift.
1413 Pitch *= (SpeedOfSound-vls) / (SpeedOfSound-vss);
1417 /* Adjust pitch based on the buffer and output frequencies, and calculate
1418 * fixed-point stepping value.
1420 Pitch *= (ALfloat)ALBuffer->Frequency/(ALfloat)Device->Frequency;
1421 if(Pitch > (ALfloat)MAX_PITCH)
1422 voice->Step = MAX_PITCH<<FRACTIONBITS;
1423 else
1424 voice->Step = maxi(fastf2i(Pitch * FRACTIONONE), 1);
1425 if(props->Resampler == BSinc24Resampler)
1426 BsincPrepare(voice->Step, &voice->ResampleState.bsinc, &bsinc24);
1427 else if(props->Resampler == BSinc12Resampler)
1428 BsincPrepare(voice->Step, &voice->ResampleState.bsinc, &bsinc12);
1429 voice->Resampler = SelectResampler(props->Resampler);
1431 if(Distance > 0.0f)
1433 /* Clamp Y, in case rounding errors caused it to end up outside of
1434 * -1...+1.
1436 ev = asinf(clampf(-SourceToListener.v[1], -1.0f, 1.0f));
1437 /* Double negation on Z cancels out; negate once for changing source-
1438 * to-listener to listener-to-source, and again for right-handed coords
1439 * with -Z in front.
1441 az = atan2f(-SourceToListener.v[0], SourceToListener.v[2]*ZScale);
1443 else
1444 ev = az = 0.0f;
1446 if(props->Radius > Distance)
1447 spread = F_TAU - Distance/props->Radius*F_PI;
1448 else if(Distance > 0.0f)
1449 spread = asinf(props->Radius / Distance) * 2.0f;
1450 else
1451 spread = 0.0f;
1453 CalcPanningAndFilters(voice, az, ev, Distance, spread, DryGain, DryGainHF, DryGainLF, WetGain,
1454 WetGainLF, WetGainHF, SendSlots, ALBuffer, props, Listener, Device);
1457 void CalcSourceParams(ALvoice *voice, ALCcontext *context, bool force)
1459 ALvoiceProps *props{voice->Update.exchange(nullptr, std::memory_order_acq_rel)};
1460 if(!props && !force) return;
1462 if(props)
1464 memcpy(voice->Props, props,
1465 FAM_SIZE(struct ALvoiceProps, Send, context->Device->NumAuxSends)
1468 AtomicReplaceHead(context->FreeVoiceProps, props);
1470 props = voice->Props;
1472 ALbufferlistitem *BufferListItem{voice->current_buffer.load(std::memory_order_relaxed)};
1473 while(BufferListItem)
1475 auto buffers_end = BufferListItem->buffers+BufferListItem->num_buffers;
1476 auto buffer = std::find_if(BufferListItem->buffers, buffers_end,
1477 [](const ALbuffer *buffer) noexcept -> bool
1478 { return buffer != nullptr; }
1480 if(LIKELY(buffer != buffers_end))
1482 if(props->SpatializeMode == SpatializeOn ||
1483 (props->SpatializeMode == SpatializeAuto && (*buffer)->FmtChannels == FmtMono))
1484 CalcAttnSourceParams(voice, props, *buffer, context);
1485 else
1486 CalcNonAttnSourceParams(voice, props, *buffer, context);
1487 break;
1489 BufferListItem = BufferListItem->next.load(std::memory_order_acquire);
1494 void ProcessParamUpdates(ALCcontext *ctx, const ALeffectslotArray *slots)
1496 IncrementRef(&ctx->UpdateCount);
1497 if(LIKELY(!ctx->HoldUpdates.load(std::memory_order_acquire)))
1499 bool cforce = CalcContextParams(ctx);
1500 bool force = CalcListenerParams(ctx) | cforce;
1501 std::for_each(slots->slot, slots->slot+slots->count,
1502 [ctx,cforce,&force](ALeffectslot *slot) -> void
1503 { force |= CalcEffectSlotParams(slot, ctx, cforce); }
1506 std::for_each(ctx->Voices, ctx->Voices+ctx->VoiceCount,
1507 [ctx,force](ALvoice *voice) -> void
1509 ALsource *source{voice->Source.load(std::memory_order_acquire)};
1510 if(source) CalcSourceParams(voice, ctx, force);
1514 IncrementRef(&ctx->UpdateCount);
1517 void ProcessContext(ALCcontext *ctx, ALsizei SamplesToDo)
1519 const ALeffectslotArray *auxslots{ctx->ActiveAuxSlots.load(std::memory_order_acquire)};
1521 /* Process pending propery updates for objects on the context. */
1522 ProcessParamUpdates(ctx, auxslots);
1524 /* Clear auxiliary effect slot mixing buffers. */
1525 std::for_each(auxslots->slot, auxslots->slot+auxslots->count,
1526 [SamplesToDo](ALeffectslot *slot) -> void
1528 std::for_each(slot->WetBuffer, slot->WetBuffer+slot->NumChannels,
1529 [SamplesToDo](ALfloat *buffer) -> void
1530 { std::fill_n(buffer, SamplesToDo, 0.0f); }
1535 /* Process voices that have a playing source. */
1536 std::for_each(ctx->Voices, ctx->Voices+ctx->VoiceCount,
1537 [SamplesToDo,ctx](ALvoice *voice) -> void
1539 ALsource *source{voice->Source.load(std::memory_order_acquire)};
1540 if(!source) return;
1541 if(!voice->Playing.load(std::memory_order_relaxed) || voice->Step < 1)
1542 return;
1544 if(!MixSource(voice, source->id, ctx, SamplesToDo))
1546 voice->Source.store(nullptr, std::memory_order_relaxed);
1547 voice->Playing.store(false, std::memory_order_release);
1548 SendSourceStoppedEvent(ctx, source->id);
1553 /* Process effects. */
1554 std::for_each(auxslots->slot, auxslots->slot+auxslots->count,
1555 [SamplesToDo](const ALeffectslot *slot) -> void
1557 EffectState *state{slot->Params.mEffectState};
1558 state->process(SamplesToDo, slot->WetBuffer, state->mOutBuffer,
1559 state->mOutChannels);
1565 void ApplyStablizer(FrontStablizer *Stablizer, ALfloat (*RESTRICT Buffer)[BUFFERSIZE],
1566 int lidx, int ridx, int cidx, ALsizei SamplesToDo, ALsizei NumChannels)
1568 ALfloat (*RESTRICT lsplit)[BUFFERSIZE] = Stablizer->LSplit;
1569 ALfloat (*RESTRICT rsplit)[BUFFERSIZE] = Stablizer->RSplit;
1570 ALsizei i;
1572 /* Apply an all-pass to all channels, except the front-left and front-
1573 * right, so they maintain the same relative phase.
1575 for(i = 0;i < NumChannels;i++)
1577 if(i == lidx || i == ridx)
1578 continue;
1579 splitterap_process(&Stablizer->APFilter[i], Buffer[i], SamplesToDo);
1582 bandsplit_process(&Stablizer->LFilter, lsplit[1], lsplit[0], Buffer[lidx], SamplesToDo);
1583 bandsplit_process(&Stablizer->RFilter, rsplit[1], rsplit[0], Buffer[ridx], SamplesToDo);
1585 for(i = 0;i < SamplesToDo;i++)
1587 ALfloat lfsum, hfsum;
1588 ALfloat m, s, c;
1590 lfsum = lsplit[0][i] + rsplit[0][i];
1591 hfsum = lsplit[1][i] + rsplit[1][i];
1592 s = lsplit[0][i] + lsplit[1][i] - rsplit[0][i] - rsplit[1][i];
1594 /* This pans the separate low- and high-frequency sums between being on
1595 * the center channel and the left/right channels. The low-frequency
1596 * sum is 1/3rd toward center (2/3rds on left/right) and the high-
1597 * frequency sum is 1/4th toward center (3/4ths on left/right). These
1598 * values can be tweaked.
1600 m = lfsum*cosf(1.0f/3.0f * F_PI_2) + hfsum*cosf(1.0f/4.0f * F_PI_2);
1601 c = lfsum*sinf(1.0f/3.0f * F_PI_2) + hfsum*sinf(1.0f/4.0f * F_PI_2);
1603 /* The generated center channel signal adds to the existing signal,
1604 * while the modified left and right channels replace.
1606 Buffer[lidx][i] = (m + s) * 0.5f;
1607 Buffer[ridx][i] = (m - s) * 0.5f;
1608 Buffer[cidx][i] += c * 0.5f;
1612 void ApplyDistanceComp(ALfloat (*RESTRICT Samples)[BUFFERSIZE], const DistanceComp &distcomp,
1613 ALfloat *RESTRICT Values, ALsizei SamplesToDo, ALsizei numchans)
1615 for(ALsizei c{0};c < numchans;c++)
1617 ALfloat *RESTRICT inout = Samples[c];
1618 const ALfloat gain = distcomp[c].Gain;
1619 const ALsizei base = distcomp[c].Length;
1620 ALfloat *RESTRICT distbuf = distcomp[c].Buffer;
1622 if(base == 0)
1624 if(gain < 1.0f)
1625 std::for_each(inout, inout+SamplesToDo,
1626 [gain](ALfloat &in) noexcept -> void
1627 { in *= gain; }
1629 continue;
1632 if(LIKELY(SamplesToDo >= base))
1634 auto out = std::copy_n(distbuf, base, Values);
1635 std::copy_n(inout, SamplesToDo-base, out);
1636 std::copy_n(inout+SamplesToDo-base, base, distbuf);
1638 else
1640 std::copy_n(distbuf, SamplesToDo, Values);
1641 auto out = std::copy(distbuf+SamplesToDo, distbuf+base, distbuf);
1642 std::copy_n(inout, SamplesToDo, out);
1644 std::transform<ALfloat*RESTRICT>(Values, Values+SamplesToDo, inout,
1645 [gain](ALfloat in) noexcept -> ALfloat
1646 { return in * gain; }
1651 void ApplyDither(ALfloat (*RESTRICT Samples)[BUFFERSIZE], ALuint *dither_seed,
1652 const ALfloat quant_scale, const ALsizei SamplesToDo, const ALsizei numchans)
1654 ASSUME(numchans > 0);
1656 /* Dithering. Generate whitenoise (uniform distribution of random values
1657 * between -1 and +1) and add it to the sample values, after scaling up to
1658 * the desired quantization depth amd before rounding.
1660 const ALfloat invscale = 1.0f / quant_scale;
1661 ALuint seed = *dither_seed;
1662 auto dither_channel = [&seed,invscale,quant_scale,SamplesToDo](ALfloat *buffer) -> void
1664 ASSUME(SamplesToDo > 0);
1665 std::transform(buffer, buffer+SamplesToDo, buffer,
1666 [&seed,invscale,quant_scale](ALfloat sample) noexcept -> ALfloat
1668 ALfloat val = sample * quant_scale;
1669 ALuint rng0 = dither_rng(&seed);
1670 ALuint rng1 = dither_rng(&seed);
1671 val += (ALfloat)(rng0*(1.0/UINT_MAX) - rng1*(1.0/UINT_MAX));
1672 return fast_roundf(val) * invscale;
1676 std::for_each(Samples, Samples+numchans, dither_channel);
1677 *dither_seed = seed;
1681 /* Base template left undefined. Should be marked =delete, but Clang 3.8.1
1682 * chokes on that given the inline specializations.
1684 template<typename T>
1685 inline T SampleConv(ALfloat) noexcept;
1687 template<> inline ALfloat SampleConv(ALfloat val) noexcept
1688 { return val; }
1689 template<> inline ALint SampleConv(ALfloat val) noexcept
1691 /* Floats have a 23-bit mantissa. There is an implied 1 bit in the mantissa
1692 * along with the sign bit, giving 25 bits total, so [-16777216, +16777216]
1693 * is the max value a normalized float can be scaled to before losing
1694 * precision.
1696 return fastf2i(clampf(val*16777216.0f, -16777216.0f, 16777215.0f))<<7;
1698 template<> inline ALshort SampleConv(ALfloat val) noexcept
1699 { return fastf2i(clampf(val*32768.0f, -32768.0f, 32767.0f)); }
1700 template<> inline ALbyte SampleConv(ALfloat val) noexcept
1701 { return fastf2i(clampf(val*128.0f, -128.0f, 127.0f)); }
1703 /* Define unsigned output variations. */
1704 template<> inline ALuint SampleConv(ALfloat val) noexcept
1705 { return SampleConv<ALint>(val) + 2147483648u; }
1706 template<> inline ALushort SampleConv(ALfloat val) noexcept
1707 { return SampleConv<ALshort>(val) + 32768; }
1708 template<> inline ALubyte SampleConv(ALfloat val) noexcept
1709 { return SampleConv<ALbyte>(val) + 128; }
1711 template<DevFmtType T>
1712 void Write(const ALfloat (*RESTRICT InBuffer)[BUFFERSIZE], ALvoid *OutBuffer,
1713 ALsizei Offset, ALsizei SamplesToDo, ALsizei numchans)
1715 using SampleType = typename DevFmtTypeTraits<T>::Type;
1717 ASSUME(numchans > 0);
1718 SampleType *outbase = static_cast<SampleType*>(OutBuffer) + Offset*numchans;
1719 auto conv_channel = [&outbase,SamplesToDo,numchans](const ALfloat *inbuf) -> void
1721 ASSUME(SamplesToDo > 0);
1722 SampleType *out{outbase++};
1723 std::for_each<const ALfloat*RESTRICT>(inbuf, inbuf+SamplesToDo,
1724 [numchans,&out](const ALfloat s) noexcept -> void
1726 *out = SampleConv<SampleType>(s);
1727 out += numchans;
1731 std::for_each(InBuffer, InBuffer+numchans, conv_channel);
1734 } // namespace
1736 void aluMixData(ALCdevice *device, ALvoid *OutBuffer, ALsizei NumSamples)
1738 FPUCtl mixer_mode{};
1739 for(ALsizei SamplesDone{0};SamplesDone < NumSamples;)
1741 const ALsizei SamplesToDo{mini(NumSamples-SamplesDone, BUFFERSIZE)};
1743 /* Clear main mixing buffers. */
1744 std::for_each(device->MixBuffer.begin(), device->MixBuffer.end(),
1745 [SamplesToDo](std::array<ALfloat,BUFFERSIZE> &buffer) -> void
1746 { std::fill_n(buffer.begin(), SamplesToDo, 0.0f); }
1749 /* Increment the mix count at the start (lsb should now be 1). */
1750 IncrementRef(&device->MixCount);
1752 /* For each context on this device, process and mix its sources and
1753 * effects.
1755 ALCcontext *ctx{device->ContextList.load(std::memory_order_acquire)};
1756 while(ctx)
1758 ProcessContext(ctx, SamplesToDo);
1760 ctx = ctx->next.load(std::memory_order_relaxed);
1763 /* Increment the clock time. Every second's worth of samples is
1764 * converted and added to clock base so that large sample counts don't
1765 * overflow during conversion. This also guarantees a stable
1766 * conversion.
1768 device->SamplesDone += SamplesToDo;
1769 device->ClockBase += std::chrono::seconds{device->SamplesDone / device->Frequency};
1770 device->SamplesDone %= device->Frequency;
1772 /* Increment the mix count at the end (lsb should now be 0). */
1773 IncrementRef(&device->MixCount);
1775 /* Apply any needed post-process for finalizing the Dry mix to the
1776 * RealOut (Ambisonic decode, UHJ encode, etc).
1778 if(LIKELY(device->PostProcess))
1779 device->PostProcess(device, SamplesToDo);
1781 /* Apply front image stablization for surround sound, if applicable. */
1782 if(device->Stablizer)
1784 int lidx = GetChannelIdxByName(&device->RealOut, FrontLeft);
1785 int ridx = GetChannelIdxByName(&device->RealOut, FrontRight);
1786 int cidx = GetChannelIdxByName(&device->RealOut, FrontCenter);
1787 assert(lidx >= 0 && ridx >= 0 && cidx >= 0);
1789 ApplyStablizer(device->Stablizer.get(), device->RealOut.Buffer, lidx, ridx, cidx,
1790 SamplesToDo, device->RealOut.NumChannels);
1793 /* Apply delays and attenuation for mismatched speaker distances. */
1794 ApplyDistanceComp(device->RealOut.Buffer, device->ChannelDelay, device->TempBuffer[0],
1795 SamplesToDo, device->RealOut.NumChannels);
1797 /* Apply compression, limiting final sample amplitude, if desired. */
1798 if(device->Limiter)
1799 ApplyCompression(device->Limiter.get(), SamplesToDo, device->RealOut.Buffer);
1801 /* Apply dithering. The compressor should have left enough headroom for
1802 * the dither noise to not saturate.
1804 if(device->DitherDepth > 0.0f)
1805 ApplyDither(device->RealOut.Buffer, &device->DitherSeed, device->DitherDepth,
1806 SamplesToDo, device->RealOut.NumChannels);
1808 if(LIKELY(OutBuffer))
1810 ALfloat (*Buffer)[BUFFERSIZE] = device->RealOut.Buffer;
1811 ALsizei Channels = device->RealOut.NumChannels;
1813 /* Finally, interleave and convert samples, writing to the device's
1814 * output buffer.
1816 switch(device->FmtType)
1818 #define HANDLE_WRITE(T) case T: \
1819 Write<T>(Buffer, OutBuffer, SamplesDone, SamplesToDo, Channels); break;
1820 HANDLE_WRITE(DevFmtByte)
1821 HANDLE_WRITE(DevFmtUByte)
1822 HANDLE_WRITE(DevFmtShort)
1823 HANDLE_WRITE(DevFmtUShort)
1824 HANDLE_WRITE(DevFmtInt)
1825 HANDLE_WRITE(DevFmtUInt)
1826 HANDLE_WRITE(DevFmtFloat)
1827 #undef HANDLE_WRITE
1831 SamplesDone += SamplesToDo;
1836 void aluHandleDisconnect(ALCdevice *device, const char *msg, ...)
1838 if(!device->Connected.exchange(AL_FALSE, std::memory_order_acq_rel))
1839 return;
1841 AsyncEvent evt = ASYNC_EVENT(EventType_Disconnected);
1842 evt.u.user.type = AL_EVENT_TYPE_DISCONNECTED_SOFT;
1843 evt.u.user.id = 0;
1844 evt.u.user.param = 0;
1846 va_list args;
1847 va_start(args, msg);
1848 int msglen{vsnprintf(evt.u.user.msg, sizeof(evt.u.user.msg), msg, args)};
1849 va_end(args);
1851 if(msglen < 0 || (size_t)msglen >= sizeof(evt.u.user.msg))
1852 evt.u.user.msg[sizeof(evt.u.user.msg)-1] = 0;
1854 ALCcontext *ctx{device->ContextList.load()};
1855 while(ctx)
1857 ALbitfieldSOFT enabledevt = ctx->EnabledEvts.load(std::memory_order_acquire);
1858 if((enabledevt&EventType_Disconnected) &&
1859 ll_ringbuffer_write(ctx->AsyncEvents, &evt, 1) == 1)
1860 alsem_post(&ctx->EventSem);
1862 std::for_each(ctx->Voices, ctx->Voices+ctx->VoiceCount,
1863 [ctx](ALvoice *voice) -> void
1865 ALsource *source{voice->Source.exchange(nullptr, std::memory_order_relaxed)};
1866 if(source && voice->Playing.load(std::memory_order_relaxed))
1868 /* If the source's voice was playing, it's now effectively
1869 * stopped (the source state will be updated the next time
1870 * it's checked).
1872 SendSourceStoppedEvent(ctx, source->id);
1874 voice->Playing.store(false, std::memory_order_release);
1878 ctx = ctx->next.load(std::memory_order_relaxed);