Replace a couple more C math calls
[openal-soft.git] / Alc / alu.cpp
blobed5cfe6f7cf11c59792802b68553647f51672c55
1 /**
2 * OpenAL cross platform audio library
3 * Copyright (C) 1999-2007 by authors.
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
14 * You should have received a copy of the GNU Library General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 * Or go to http://www.gnu.org/copyleft/lgpl.html
21 #include "config.h"
23 #include <math.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <ctype.h>
27 #include <assert.h>
29 #include <cmath>
30 #include <limits>
31 #include <numeric>
32 #include <algorithm>
33 #include <functional>
35 #include "alMain.h"
36 #include "alcontext.h"
37 #include "alSource.h"
38 #include "alBuffer.h"
39 #include "alListener.h"
40 #include "alAuxEffectSlot.h"
41 #include "alu.h"
42 #include "bs2b.h"
43 #include "hrtf.h"
44 #include "mastering.h"
45 #include "uhjfilter.h"
46 #include "bformatdec.h"
47 #include "ringbuffer.h"
48 #include "filters/splitter.h"
50 #include "mixer/defs.h"
51 #include "fpu_modes.h"
52 #include "cpu_caps.h"
53 #include "bsinc_inc.h"
56 namespace {
58 using namespace std::placeholders;
60 ALfloat InitConeScale()
62 ALfloat ret{1.0f};
63 const char *str{getenv("__ALSOFT_HALF_ANGLE_CONES")};
64 if(str && (strcasecmp(str, "true") == 0 || strtol(str, nullptr, 0) == 1))
65 ret *= 0.5f;
66 return ret;
69 ALfloat InitZScale()
71 ALfloat ret{1.0f};
72 const char *str{getenv("__ALSOFT_REVERSE_Z")};
73 if(str && (strcasecmp(str, "true") == 0 || strtol(str, nullptr, 0) == 1))
74 ret *= -1.0f;
75 return ret;
78 ALboolean InitReverbSOS()
80 ALboolean ret{AL_FALSE};
81 const char *str{getenv("__ALSOFT_REVERB_IGNORES_SOUND_SPEED")};
82 if(str && (strcasecmp(str, "true") == 0 || strtol(str, nullptr, 0) == 1))
83 ret = AL_TRUE;
84 return ret;
87 } // namespace
89 /* Cone scalar */
90 const ALfloat ConeScale{InitConeScale()};
92 /* Localized Z scalar for mono sources */
93 const ALfloat ZScale{InitZScale()};
95 /* Force default speed of sound for distance-related reverb decay. */
96 const ALboolean OverrideReverbSpeedOfSound{InitReverbSOS()};
99 namespace {
101 void ClearArray(ALfloat (&f)[MAX_OUTPUT_CHANNELS])
103 std::fill(std::begin(f), std::end(f), 0.0f);
106 struct ChanMap {
107 Channel channel;
108 ALfloat angle;
109 ALfloat elevation;
112 HrtfDirectMixerFunc MixDirectHrtf = MixDirectHrtf_C;
114 inline HrtfDirectMixerFunc SelectHrtfMixer(void)
116 #ifdef HAVE_NEON
117 if((CPUCapFlags&CPU_CAP_NEON))
118 return MixDirectHrtf_Neon;
119 #endif
120 #ifdef HAVE_SSE
121 if((CPUCapFlags&CPU_CAP_SSE))
122 return MixDirectHrtf_SSE;
123 #endif
125 return MixDirectHrtf_C;
129 void ProcessHrtf(ALCdevice *device, const ALsizei SamplesToDo)
131 if(AmbiUpsampler *ambiup{device->AmbiUp.get()})
132 ambiup->process(device->Dry.Buffer, device->Dry.NumChannels, device->FOAOut.Buffer,
133 device->FOAOut.NumChannels, SamplesToDo);
135 /* HRTF is stereo output only. */
136 const int lidx{(device->RealOut.ChannelName[0]==FrontLeft) ? 0 : 1};
137 const int ridx{(device->RealOut.ChannelName[0]==FrontLeft) ? 1 : 0};
138 ALfloat *LeftOut{device->RealOut.Buffer[lidx]};
139 ALfloat *RightOut{device->RealOut.Buffer[ridx]};
141 DirectHrtfState *state{device->mHrtfState.get()};
142 MixDirectHrtf(LeftOut, RightOut, device->Dry.Buffer, state, device->Dry.NumChannels,
143 SamplesToDo);
144 state->Offset += SamplesToDo;
147 void ProcessAmbiDec(ALCdevice *device, const ALsizei SamplesToDo)
149 BFormatDec *ambidec{device->AmbiDecoder.get()};
150 if(device->Dry.Buffer != device->FOAOut.Buffer)
151 ambidec->upSample(device->Dry.Buffer, device->Dry.NumChannels, device->FOAOut.Buffer,
152 device->FOAOut.NumChannels, SamplesToDo);
153 ambidec->process(device->RealOut.Buffer, device->RealOut.NumChannels, device->Dry.Buffer,
154 SamplesToDo);
157 void ProcessAmbiUp(ALCdevice *device, const ALsizei SamplesToDo)
159 device->AmbiUp->process(device->RealOut.Buffer, device->RealOut.NumChannels,
160 device->FOAOut.Buffer, device->FOAOut.NumChannels, SamplesToDo);
163 void ProcessUhj(ALCdevice *device, const ALsizei SamplesToDo)
165 /* UHJ is stereo output only. */
166 const int lidx{(device->RealOut.ChannelName[0]==FrontLeft) ? 0 : 1};
167 const int ridx{(device->RealOut.ChannelName[1]==FrontRight) ? 1 : 0};
169 /* Encode to stereo-compatible 2-channel UHJ output. */
170 Uhj2Encoder *uhj2enc{device->Uhj_Encoder.get()};
171 uhj2enc->encode(device->RealOut.Buffer[lidx], device->RealOut.Buffer[ridx],
172 device->Dry.Buffer, SamplesToDo);
175 void ProcessBs2b(ALCdevice *device, const ALsizei SamplesToDo)
177 /* BS2B is stereo output only. */
178 const int lidx{(device->RealOut.ChannelName[0]==FrontLeft) ? 0 : 1};
179 const int ridx{(device->RealOut.ChannelName[1]==FrontRight) ? 1 : 0};
181 /* Apply binaural/crossfeed filter */
182 bs2b_cross_feed(device->Bs2b.get(), device->RealOut.Buffer[lidx],
183 device->RealOut.Buffer[ridx], SamplesToDo);
186 } // namespace
188 void aluInit(void)
190 MixDirectHrtf = SelectHrtfMixer();
194 void DeinitVoice(ALvoice *voice) noexcept
196 delete voice->Update.exchange(nullptr, std::memory_order_acq_rel);
197 voice->~ALvoice();
201 void aluSelectPostProcess(ALCdevice *device)
203 if(device->mHrtf)
204 device->PostProcess = ProcessHrtf;
205 else if(device->AmbiDecoder)
206 device->PostProcess = ProcessAmbiDec;
207 else if(device->AmbiUp)
208 device->PostProcess = ProcessAmbiUp;
209 else if(device->Uhj_Encoder)
210 device->PostProcess = ProcessUhj;
211 else if(device->Bs2b)
212 device->PostProcess = ProcessBs2b;
213 else
214 device->PostProcess = nullptr;
218 /* Prepares the interpolator for a given rate (determined by increment).
220 * With a bit of work, and a trade of memory for CPU cost, this could be
221 * modified for use with an interpolated increment for buttery-smooth pitch
222 * changes.
224 void BsincPrepare(const ALuint increment, BsincState *state, const BSincTable *table)
226 ALsizei si{BSINC_SCALE_COUNT - 1};
227 ALfloat sf{0.0f};
229 if(increment > FRACTIONONE)
231 sf = static_cast<ALfloat>FRACTIONONE / increment;
232 sf = maxf(0.0f, (BSINC_SCALE_COUNT-1) * (sf-table->scaleBase) * table->scaleRange);
233 si = float2int(sf);
234 /* The interpolation factor is fit to this diagonally-symmetric curve
235 * to reduce the transition ripple caused by interpolating different
236 * scales of the sinc function.
238 sf = 1.0f - std::cos(std::asin(sf - si));
241 state->sf = sf;
242 state->m = table->m[si];
243 state->l = (state->m/2) - 1;
244 state->filter = table->Tab + table->filterOffset[si];
248 namespace {
250 /* This RNG method was created based on the math found in opusdec. It's quick,
251 * and starting with a seed value of 22222, is suitable for generating
252 * whitenoise.
254 inline ALuint dither_rng(ALuint *seed) noexcept
256 *seed = (*seed * 96314165) + 907633515;
257 return *seed;
261 inline alu::Vector aluCrossproduct(const alu::Vector &in1, const alu::Vector &in2)
263 return alu::Vector{
264 in1[1]*in2[2] - in1[2]*in2[1],
265 in1[2]*in2[0] - in1[0]*in2[2],
266 in1[0]*in2[1] - in1[1]*in2[0],
267 0.0f
271 inline ALfloat aluDotproduct(const alu::Vector &vec1, const alu::Vector &vec2)
273 return vec1[0]*vec2[0] + vec1[1]*vec2[1] + vec1[2]*vec2[2];
277 alu::Vector operator*(const alu::Matrix &mtx, const alu::Vector &vec) noexcept
279 return alu::Vector{
280 vec[0]*mtx[0][0] + vec[1]*mtx[1][0] + vec[2]*mtx[2][0] + vec[3]*mtx[3][0],
281 vec[0]*mtx[0][1] + vec[1]*mtx[1][1] + vec[2]*mtx[2][1] + vec[3]*mtx[3][1],
282 vec[0]*mtx[0][2] + vec[1]*mtx[1][2] + vec[2]*mtx[2][2] + vec[3]*mtx[3][2],
283 vec[0]*mtx[0][3] + vec[1]*mtx[1][3] + vec[2]*mtx[2][3] + vec[3]*mtx[3][3]
288 void SendSourceStoppedEvent(ALCcontext *context, ALuint id)
290 ALbitfieldSOFT enabledevt{context->EnabledEvts.load(std::memory_order_acquire)};
291 if(!(enabledevt&EventType_SourceStateChange)) return;
293 RingBuffer *ring{context->AsyncEvents.get()};
294 auto evt_vec = ring->getWriteVector();
295 if(evt_vec.first.len < 1) return;
297 AsyncEvent *evt{new (evt_vec.first.buf) AsyncEvent{EventType_SourceStateChange}};
298 evt->u.srcstate.id = id;
299 evt->u.srcstate.state = AL_STOPPED;
301 ring->writeAdvance(1);
302 context->EventSem.post();
306 bool CalcContextParams(ALCcontext *Context)
308 ALcontextProps *props{Context->Update.exchange(nullptr, std::memory_order_acq_rel)};
309 if(!props) return false;
311 ALlistener &Listener = Context->Listener;
312 Listener.Params.MetersPerUnit = props->MetersPerUnit;
314 Listener.Params.DopplerFactor = props->DopplerFactor;
315 Listener.Params.SpeedOfSound = props->SpeedOfSound * props->DopplerVelocity;
316 if(!OverrideReverbSpeedOfSound)
317 Listener.Params.ReverbSpeedOfSound = Listener.Params.SpeedOfSound *
318 Listener.Params.MetersPerUnit;
320 Listener.Params.SourceDistanceModel = props->SourceDistanceModel;
321 Listener.Params.mDistanceModel = props->mDistanceModel;
323 AtomicReplaceHead(Context->FreeContextProps, props);
324 return true;
327 bool CalcListenerParams(ALCcontext *Context)
329 ALlistener &Listener = Context->Listener;
331 ALlistenerProps *props{Listener.Update.exchange(nullptr, std::memory_order_acq_rel)};
332 if(!props) return false;
334 /* AT then UP */
335 alu::Vector N{props->OrientAt[0], props->OrientAt[1], props->OrientAt[2], 0.0f};
336 N.normalize();
337 alu::Vector V{props->OrientUp[0], props->OrientUp[1], props->OrientUp[2], 0.0f};
338 V.normalize();
339 /* Build and normalize right-vector */
340 alu::Vector U{aluCrossproduct(N, V)};
341 U.normalize();
343 Listener.Params.Matrix = alu::Matrix{
344 U[0], V[0], -N[0], 0.0f,
345 U[1], V[1], -N[1], 0.0f,
346 U[2], V[2], -N[2], 0.0f,
347 0.0f, 0.0f, 0.0f, 1.0f
350 const alu::Vector P{Listener.Params.Matrix *
351 alu::Vector{props->Position[0], props->Position[1], props->Position[2], 1.0f}};
352 Listener.Params.Matrix.setRow(3, -P[0], -P[1], -P[2], 1.0f);
354 const alu::Vector vel{props->Velocity[0], props->Velocity[1], props->Velocity[2], 0.0f};
355 Listener.Params.Velocity = Listener.Params.Matrix * vel;
357 Listener.Params.Gain = props->Gain * Context->GainBoost;
359 AtomicReplaceHead(Context->FreeListenerProps, props);
360 return true;
363 bool CalcEffectSlotParams(ALeffectslot *slot, ALCcontext *context, bool force)
365 ALeffectslotProps *props{slot->Update.exchange(nullptr, std::memory_order_acq_rel)};
366 if(!props && !force) return false;
368 EffectState *state;
369 if(!props)
370 state = slot->Params.mEffectState;
371 else
373 slot->Params.Gain = props->Gain;
374 slot->Params.AuxSendAuto = props->AuxSendAuto;
375 slot->Params.Target = props->Target;
376 slot->Params.EffectType = props->Type;
377 slot->Params.EffectProps = props->Props;
378 if(IsReverbEffect(props->Type))
380 slot->Params.RoomRolloff = props->Props.Reverb.RoomRolloffFactor;
381 slot->Params.DecayTime = props->Props.Reverb.DecayTime;
382 slot->Params.DecayLFRatio = props->Props.Reverb.DecayLFRatio;
383 slot->Params.DecayHFRatio = props->Props.Reverb.DecayHFRatio;
384 slot->Params.DecayHFLimit = props->Props.Reverb.DecayHFLimit;
385 slot->Params.AirAbsorptionGainHF = props->Props.Reverb.AirAbsorptionGainHF;
387 else
389 slot->Params.RoomRolloff = 0.0f;
390 slot->Params.DecayTime = 0.0f;
391 slot->Params.DecayLFRatio = 0.0f;
392 slot->Params.DecayHFRatio = 0.0f;
393 slot->Params.DecayHFLimit = AL_FALSE;
394 slot->Params.AirAbsorptionGainHF = 1.0f;
397 state = props->State;
398 props->State = nullptr;
399 EffectState *oldstate{slot->Params.mEffectState};
400 slot->Params.mEffectState = state;
402 /* Manually decrement the old effect state's refcount if it's greater
403 * than 1. We need to be a bit clever here to avoid the refcount
404 * reaching 0 since it can't be deleted in the mixer.
406 ALuint oldval{oldstate->mRef.load(std::memory_order_acquire)};
407 while(oldval > 1 && !oldstate->mRef.compare_exchange_weak(oldval, oldval-1,
408 std::memory_order_acq_rel, std::memory_order_acquire))
410 /* oldval was updated with the current value on failure, so just
411 * try again.
415 if(oldval < 2)
417 /* Otherwise, if it would be deleted, send it off with a release
418 * event.
420 RingBuffer *ring{context->AsyncEvents.get()};
421 auto evt_vec = ring->getWriteVector();
422 if(LIKELY(evt_vec.first.len > 0))
424 AsyncEvent *evt{new (evt_vec.first.buf) AsyncEvent{EventType_ReleaseEffectState}};
425 evt->u.mEffectState = oldstate;
426 ring->writeAdvance(1);
427 context->EventSem.post();
429 else
431 /* If writing the event failed, the queue was probably full.
432 * Store the old state in the property object where it can
433 * eventually be cleaned up sometime later (not ideal, but
434 * better than blocking or leaking).
436 props->State = oldstate;
440 AtomicReplaceHead(context->FreeEffectslotProps, props);
443 MixParams params;
444 EffectTarget output;
445 if(ALeffectslot *target{slot->Params.Target})
447 auto iter = std::copy(std::begin(target->ChanMap), std::end(target->ChanMap),
448 std::begin(params.AmbiMap));
449 std::fill(iter, std::end(params.AmbiMap), BFChannelConfig{});
450 params.Buffer = target->WetBuffer;
451 params.NumChannels = target->NumChannels;
453 output = EffectTarget{&params, &params, nullptr};
455 else
457 ALCdevice *device{context->Device};
458 output = EffectTarget{&device->Dry, &device->FOAOut, &device->RealOut};
460 state->update(context, slot, &slot->Params.EffectProps, output);
461 return true;
465 constexpr ChanMap MonoMap[1]{
466 { FrontCenter, 0.0f, 0.0f }
467 }, RearMap[2]{
468 { BackLeft, Deg2Rad(-150.0f), Deg2Rad(0.0f) },
469 { BackRight, Deg2Rad( 150.0f), Deg2Rad(0.0f) }
470 }, QuadMap[4]{
471 { FrontLeft, Deg2Rad( -45.0f), Deg2Rad(0.0f) },
472 { FrontRight, Deg2Rad( 45.0f), Deg2Rad(0.0f) },
473 { BackLeft, Deg2Rad(-135.0f), Deg2Rad(0.0f) },
474 { BackRight, Deg2Rad( 135.0f), Deg2Rad(0.0f) }
475 }, X51Map[6]{
476 { FrontLeft, Deg2Rad( -30.0f), Deg2Rad(0.0f) },
477 { FrontRight, Deg2Rad( 30.0f), Deg2Rad(0.0f) },
478 { FrontCenter, Deg2Rad( 0.0f), Deg2Rad(0.0f) },
479 { LFE, 0.0f, 0.0f },
480 { SideLeft, Deg2Rad(-110.0f), Deg2Rad(0.0f) },
481 { SideRight, Deg2Rad( 110.0f), Deg2Rad(0.0f) }
482 }, X61Map[7]{
483 { FrontLeft, Deg2Rad(-30.0f), Deg2Rad(0.0f) },
484 { FrontRight, Deg2Rad( 30.0f), Deg2Rad(0.0f) },
485 { FrontCenter, Deg2Rad( 0.0f), Deg2Rad(0.0f) },
486 { LFE, 0.0f, 0.0f },
487 { BackCenter, Deg2Rad(180.0f), Deg2Rad(0.0f) },
488 { SideLeft, Deg2Rad(-90.0f), Deg2Rad(0.0f) },
489 { SideRight, Deg2Rad( 90.0f), Deg2Rad(0.0f) }
490 }, X71Map[8]{
491 { FrontLeft, Deg2Rad( -30.0f), Deg2Rad(0.0f) },
492 { FrontRight, Deg2Rad( 30.0f), Deg2Rad(0.0f) },
493 { FrontCenter, Deg2Rad( 0.0f), Deg2Rad(0.0f) },
494 { LFE, 0.0f, 0.0f },
495 { BackLeft, Deg2Rad(-150.0f), Deg2Rad(0.0f) },
496 { BackRight, Deg2Rad( 150.0f), Deg2Rad(0.0f) },
497 { SideLeft, Deg2Rad( -90.0f), Deg2Rad(0.0f) },
498 { SideRight, Deg2Rad( 90.0f), Deg2Rad(0.0f) }
501 void CalcPanningAndFilters(ALvoice *voice, const ALfloat Azi, const ALfloat Elev,
502 const ALfloat Distance, const ALfloat Spread,
503 const ALfloat DryGain, const ALfloat DryGainHF,
504 const ALfloat DryGainLF, const ALfloat *WetGain,
505 const ALfloat *WetGainLF, const ALfloat *WetGainHF,
506 ALeffectslot **SendSlots, const ALbuffer *Buffer,
507 const ALvoicePropsBase *props, const ALlistener &Listener,
508 const ALCdevice *Device)
510 ChanMap StereoMap[2]{
511 { FrontLeft, Deg2Rad(-30.0f), Deg2Rad(0.0f) },
512 { FrontRight, Deg2Rad( 30.0f), Deg2Rad(0.0f) }
515 bool DirectChannels{props->DirectChannels != AL_FALSE};
516 const ChanMap *chans{nullptr};
517 ALsizei num_channels{0};
518 bool isbformat{false};
519 ALfloat downmix_gain{1.0f};
520 switch(Buffer->mFmtChannels)
522 case FmtMono:
523 chans = MonoMap;
524 num_channels = 1;
525 /* Mono buffers are never played direct. */
526 DirectChannels = false;
527 break;
529 case FmtStereo:
530 /* Convert counter-clockwise to clockwise. */
531 StereoMap[0].angle = -props->StereoPan[0];
532 StereoMap[1].angle = -props->StereoPan[1];
534 chans = StereoMap;
535 num_channels = 2;
536 downmix_gain = 1.0f / 2.0f;
537 break;
539 case FmtRear:
540 chans = RearMap;
541 num_channels = 2;
542 downmix_gain = 1.0f / 2.0f;
543 break;
545 case FmtQuad:
546 chans = QuadMap;
547 num_channels = 4;
548 downmix_gain = 1.0f / 4.0f;
549 break;
551 case FmtX51:
552 chans = X51Map;
553 num_channels = 6;
554 /* NOTE: Excludes LFE. */
555 downmix_gain = 1.0f / 5.0f;
556 break;
558 case FmtX61:
559 chans = X61Map;
560 num_channels = 7;
561 /* NOTE: Excludes LFE. */
562 downmix_gain = 1.0f / 6.0f;
563 break;
565 case FmtX71:
566 chans = X71Map;
567 num_channels = 8;
568 /* NOTE: Excludes LFE. */
569 downmix_gain = 1.0f / 7.0f;
570 break;
572 case FmtBFormat2D:
573 num_channels = 3;
574 isbformat = true;
575 DirectChannels = false;
576 break;
578 case FmtBFormat3D:
579 num_channels = 4;
580 isbformat = true;
581 DirectChannels = false;
582 break;
584 ASSUME(num_channels > 0);
586 std::for_each(std::begin(voice->Direct.Params), std::begin(voice->Direct.Params)+num_channels,
587 [](DirectParams &params) -> void
589 params.Hrtf.Target = HrtfParams{};
590 ClearArray(params.Gains.Target);
593 const ALsizei NumSends{Device->NumAuxSends};
594 ASSUME(NumSends >= 0);
595 std::for_each(voice->Send.begin(), voice->Send.end(),
596 [num_channels](ALvoice::SendData &send) -> void
598 std::for_each(std::begin(send.Params), std::begin(send.Params)+num_channels,
599 [](SendParams &params) -> void { ClearArray(params.Gains.Target); }
604 voice->Flags &= ~(VOICE_HAS_HRTF | VOICE_HAS_NFC);
605 if(isbformat)
607 /* Special handling for B-Format sources. */
609 if(Distance > std::numeric_limits<float>::epsilon())
611 /* Panning a B-Format sound toward some direction is easy. Just pan
612 * the first (W) channel as a normal mono sound and silence the
613 * others.
616 if(Device->AvgSpeakerDist > 0.0f)
618 /* Clamp the distance for really close sources, to prevent
619 * excessive bass.
621 const ALfloat mdist{maxf(Distance*Listener.Params.MetersPerUnit,
622 Device->AvgSpeakerDist/4.0f)};
623 const ALfloat w0{SPEEDOFSOUNDMETRESPERSEC /
624 (mdist * static_cast<ALfloat>(Device->Frequency))};
626 /* Only need to adjust the first channel of a B-Format source. */
627 voice->Direct.Params[0].NFCtrlFilter.adjust(w0);
629 std::copy(std::begin(Device->NumChannelsPerOrder),
630 std::end(Device->NumChannelsPerOrder),
631 std::begin(voice->Direct.ChannelsPerOrder));
632 voice->Flags |= VOICE_HAS_NFC;
635 /* Always render B-Format sources to the FOA output, to ensure
636 * smooth changes if it switches between panned and unpanned.
638 voice->Direct.Buffer = Device->FOAOut.Buffer;
639 voice->Direct.Channels = Device->FOAOut.NumChannels;
641 /* A scalar of 1.5 for plain stereo results in +/-60 degrees being
642 * moved to +/-90 degrees for direct right and left speaker
643 * responses.
645 ALfloat coeffs[MAX_AMBI_COEFFS];
646 CalcAngleCoeffs((Device->mRenderMode==StereoPair) ? ScaleAzimuthFront(Azi, 1.5f) : Azi,
647 Elev, Spread, coeffs);
649 /* NOTE: W needs to be scaled due to FuMa normalization. */
650 const ALfloat &scale0 = AmbiScale::FromFuMa[0];
651 ComputePanGains(&Device->FOAOut, coeffs, DryGain*scale0,
652 voice->Direct.Params[0].Gains.Target);
653 for(ALsizei i{0};i < NumSends;i++)
655 if(const ALeffectslot *Slot{SendSlots[i]})
656 ComputePanningGainsBF(Slot->ChanMap, Slot->NumChannels, coeffs,
657 WetGain[i]*scale0, voice->Send[i].Params[0].Gains.Target);
660 else
662 if(Device->AvgSpeakerDist > 0.0f)
664 /* NOTE: The NFCtrlFilters were created with a w0 of 0, which
665 * is what we want for FOA input. The first channel may have
666 * been previously re-adjusted if panned, so reset it.
668 voice->Direct.Params[0].NFCtrlFilter.adjust(0.0f);
670 voice->Direct.ChannelsPerOrder[0] = 1;
671 voice->Direct.ChannelsPerOrder[1] = mini(voice->Direct.Channels-1, 3);
672 std::fill(std::begin(voice->Direct.ChannelsPerOrder)+2,
673 std::end(voice->Direct.ChannelsPerOrder), 0);
674 voice->Flags |= VOICE_HAS_NFC;
677 /* Local B-Format sources have their XYZ channels rotated according
678 * to the orientation.
680 /* AT then UP */
681 alu::Vector N{props->OrientAt[0], props->OrientAt[1], props->OrientAt[2], 0.0f};
682 N.normalize();
683 alu::Vector V{props->OrientUp[0], props->OrientUp[1], props->OrientUp[2], 0.0f};
684 V.normalize();
685 if(!props->HeadRelative)
687 N = Listener.Params.Matrix * N;
688 V = Listener.Params.Matrix * V;
690 /* Build and normalize right-vector */
691 alu::Vector U{aluCrossproduct(N, V)};
692 U.normalize();
694 /* Build a rotate + conversion matrix (FuMa -> ACN+N3D). NOTE: This
695 * matrix is transposed, for the inputs to align on the rows and
696 * outputs on the columns.
698 const ALfloat &scale0 = AmbiScale::FromFuMa[0];
699 const ALfloat &scale1 = AmbiScale::FromFuMa[1];
700 const ALfloat &scale2 = AmbiScale::FromFuMa[2];
701 const ALfloat &scale3 = AmbiScale::FromFuMa[3];
702 const alu::Matrix matrix{
703 // ACN0 ACN1 ACN2 ACN3
704 scale0, 0.0f, 0.0f, 0.0f, // Ambi W
705 0.0f, -N[0]*scale1, N[1]*scale2, -N[2]*scale3, // Ambi X
706 0.0f, U[0]*scale1, -U[1]*scale2, U[2]*scale3, // Ambi Y
707 0.0f, -V[0]*scale1, V[1]*scale2, -V[2]*scale3 // Ambi Z
710 voice->Direct.Buffer = Device->FOAOut.Buffer;
711 voice->Direct.Channels = Device->FOAOut.NumChannels;
712 for(ALsizei c{0};c < num_channels;c++)
713 ComputePanGains(&Device->FOAOut, matrix[c].data(), DryGain,
714 voice->Direct.Params[c].Gains.Target);
715 for(ALsizei i{0};i < NumSends;i++)
717 if(const ALeffectslot *Slot{SendSlots[i]})
718 for(ALsizei c{0};c < num_channels;c++)
719 ComputePanningGainsBF(Slot->ChanMap, Slot->NumChannels, matrix[c].data(),
720 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(ALsizei 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(ALsizei 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(ALsizei i{0};i < NumSends;i++)
749 if(const ALeffectslot *Slot{SendSlots[i]})
750 ComputePanningGainsBF(Slot->ChanMap, Slot->NumChannels, coeffs,
751 WetGain[i], voice->Send[i].Params[c].Gains.Target
756 else if(Device->mRenderMode == HrtfRender)
758 /* Full HRTF rendering. Skip the virtual channels and render to the
759 * real outputs.
761 voice->Direct.Buffer = Device->RealOut.Buffer;
762 voice->Direct.Channels = Device->RealOut.NumChannels;
764 if(Distance > std::numeric_limits<float>::epsilon())
766 /* Get the HRIR coefficients and delays just once, for the given
767 * source direction.
769 GetHrtfCoeffs(Device->mHrtf, Elev, Azi, Spread,
770 voice->Direct.Params[0].Hrtf.Target.Coeffs,
771 voice->Direct.Params[0].Hrtf.Target.Delay);
772 voice->Direct.Params[0].Hrtf.Target.Gain = DryGain * downmix_gain;
774 /* Remaining channels use the same results as the first. */
775 for(ALsizei c{1};c < num_channels;c++)
777 /* Skip LFE */
778 if(chans[c].channel != LFE)
779 voice->Direct.Params[c].Hrtf.Target = voice->Direct.Params[0].Hrtf.Target;
782 /* Calculate the directional coefficients once, which apply to all
783 * input channels of the source sends.
785 ALfloat coeffs[MAX_AMBI_COEFFS];
786 CalcAngleCoeffs(Azi, Elev, Spread, coeffs);
788 for(ALsizei i{0};i < NumSends;i++)
790 if(const ALeffectslot *Slot{SendSlots[i]})
791 for(ALsizei c{0};c < num_channels;c++)
793 /* Skip LFE */
794 if(chans[c].channel != LFE)
795 ComputePanningGainsBF(Slot->ChanMap, Slot->NumChannels, coeffs,
796 WetGain[i]*downmix_gain, voice->Send[i].Params[c].Gains.Target
801 else
803 /* Local sources on HRTF play with each channel panned to its
804 * relative location around the listener, providing "virtual
805 * speaker" responses.
807 for(ALsizei c{0};c < num_channels;c++)
809 /* Skip LFE */
810 if(chans[c].channel == LFE)
811 continue;
813 /* Get the HRIR coefficients and delays for this channel
814 * position.
816 GetHrtfCoeffs(Device->mHrtf, chans[c].elevation, chans[c].angle, Spread,
817 voice->Direct.Params[c].Hrtf.Target.Coeffs,
818 voice->Direct.Params[c].Hrtf.Target.Delay
820 voice->Direct.Params[c].Hrtf.Target.Gain = DryGain;
822 /* Normal panning for auxiliary sends. */
823 ALfloat coeffs[MAX_AMBI_COEFFS];
824 CalcAngleCoeffs(chans[c].angle, chans[c].elevation, Spread, coeffs);
826 for(ALsizei i{0};i < NumSends;i++)
828 if(const ALeffectslot *Slot{SendSlots[i]})
829 ComputePanningGainsBF(Slot->ChanMap, Slot->NumChannels, coeffs,
830 WetGain[i], voice->Send[i].Params[c].Gains.Target
836 voice->Flags |= VOICE_HAS_HRTF;
838 else
840 /* Non-HRTF rendering. Use normal panning to the output. */
842 if(Distance > std::numeric_limits<float>::epsilon())
844 /* Calculate NFC filter coefficient if needed. */
845 if(Device->AvgSpeakerDist > 0.0f)
847 /* Clamp the distance for really close sources, to prevent
848 * excessive bass.
850 const ALfloat mdist{maxf(Distance*Listener.Params.MetersPerUnit,
851 Device->AvgSpeakerDist/4.0f)};
852 const ALfloat w0{SPEEDOFSOUNDMETRESPERSEC /
853 (mdist * static_cast<ALfloat>(Device->Frequency))};
855 /* Adjust NFC filters. */
856 for(ALsizei c{0};c < num_channels;c++)
857 voice->Direct.Params[c].NFCtrlFilter.adjust(w0);
859 std::copy(std::begin(Device->NumChannelsPerOrder),
860 std::end(Device->NumChannelsPerOrder),
861 std::begin(voice->Direct.ChannelsPerOrder));
862 voice->Flags |= VOICE_HAS_NFC;
865 /* Calculate the directional coefficients once, which apply to all
866 * input channels.
868 ALfloat coeffs[MAX_AMBI_COEFFS];
869 CalcAngleCoeffs((Device->mRenderMode==StereoPair) ? ScaleAzimuthFront(Azi, 1.5f) : Azi,
870 Elev, Spread, coeffs);
872 for(ALsizei c{0};c < num_channels;c++)
874 /* Special-case LFE */
875 if(chans[c].channel == LFE)
877 if(Device->Dry.Buffer == Device->RealOut.Buffer)
879 int idx = GetChannelIdxByName(Device->RealOut, chans[c].channel);
880 if(idx != -1) voice->Direct.Params[c].Gains.Target[idx] = DryGain;
882 continue;
885 ComputePanGains(&Device->Dry, coeffs, DryGain * downmix_gain,
886 voice->Direct.Params[c].Gains.Target);
889 for(ALsizei i{0};i < NumSends;i++)
891 if(const ALeffectslot *Slot{SendSlots[i]})
892 for(ALsizei c{0};c < num_channels;c++)
894 /* Skip LFE */
895 if(chans[c].channel != LFE)
896 ComputePanningGainsBF(Slot->ChanMap, Slot->NumChannels, coeffs,
897 WetGain[i]*downmix_gain, voice->Send[i].Params[c].Gains.Target
902 else
904 if(Device->AvgSpeakerDist > 0.0f)
906 /* If the source distance is 0, set w0 to w1 to act as a pass-
907 * through. We still want to pass the signal through the
908 * filters so they keep an appropriate history, in case the
909 * source moves away from the listener.
911 const ALfloat w0{SPEEDOFSOUNDMETRESPERSEC /
912 (Device->AvgSpeakerDist * static_cast<ALfloat>(Device->Frequency))};
914 for(ALsizei c{0};c < num_channels;c++)
915 voice->Direct.Params[c].NFCtrlFilter.adjust(w0);
917 std::copy(std::begin(Device->NumChannelsPerOrder),
918 std::end(Device->NumChannelsPerOrder),
919 std::begin(voice->Direct.ChannelsPerOrder));
920 voice->Flags |= VOICE_HAS_NFC;
923 for(ALsizei c{0};c < num_channels;c++)
925 /* Special-case LFE */
926 if(chans[c].channel == LFE)
928 if(Device->Dry.Buffer == Device->RealOut.Buffer)
930 int idx = GetChannelIdxByName(Device->RealOut, chans[c].channel);
931 if(idx != -1) voice->Direct.Params[c].Gains.Target[idx] = DryGain;
933 continue;
936 ALfloat coeffs[MAX_AMBI_COEFFS];
937 CalcAngleCoeffs(
938 (Device->mRenderMode==StereoPair) ? ScaleAzimuthFront(chans[c].angle, 3.0f)
939 : chans[c].angle,
940 chans[c].elevation, Spread, coeffs
943 ComputePanGains(&Device->Dry, coeffs, DryGain,
944 voice->Direct.Params[c].Gains.Target);
945 for(ALsizei i{0};i < NumSends;i++)
947 if(const ALeffectslot *Slot{SendSlots[i]})
948 ComputePanningGainsBF(Slot->ChanMap, Slot->NumChannels,
949 coeffs, WetGain[i], voice->Send[i].Params[c].Gains.Target
956 const auto Frequency = static_cast<ALfloat>(Device->Frequency);
958 const ALfloat hfScale{props->Direct.HFReference / Frequency};
959 const ALfloat lfScale{props->Direct.LFReference / Frequency};
960 const ALfloat gainHF{maxf(DryGainHF, 0.001f)}; /* Limit -60dB */
961 const ALfloat gainLF{maxf(DryGainLF, 0.001f)};
963 voice->Direct.FilterType = AF_None;
964 if(gainHF != 1.0f) voice->Direct.FilterType |= AF_LowPass;
965 if(gainLF != 1.0f) voice->Direct.FilterType |= AF_HighPass;
966 voice->Direct.Params[0].LowPass.setParams(BiquadType::HighShelf,
967 gainHF, hfScale, calc_rcpQ_from_slope(gainHF, 1.0f)
969 voice->Direct.Params[0].HighPass.setParams(BiquadType::LowShelf,
970 gainLF, lfScale, calc_rcpQ_from_slope(gainLF, 1.0f)
972 for(ALsizei c{1};c < num_channels;c++)
974 voice->Direct.Params[c].LowPass.copyParamsFrom(voice->Direct.Params[0].LowPass);
975 voice->Direct.Params[c].HighPass.copyParamsFrom(voice->Direct.Params[0].HighPass);
978 for(ALsizei i{0};i < NumSends;i++)
980 const ALfloat hfScale{props->Send[i].HFReference / Frequency};
981 const ALfloat lfScale{props->Send[i].LFReference / Frequency};
982 const ALfloat gainHF{maxf(WetGainHF[i], 0.001f)};
983 const ALfloat gainLF{maxf(WetGainLF[i], 0.001f)};
985 voice->Send[i].FilterType = AF_None;
986 if(gainHF != 1.0f) voice->Send[i].FilterType |= AF_LowPass;
987 if(gainLF != 1.0f) voice->Send[i].FilterType |= AF_HighPass;
988 voice->Send[i].Params[0].LowPass.setParams(BiquadType::HighShelf,
989 gainHF, hfScale, calc_rcpQ_from_slope(gainHF, 1.0f)
991 voice->Send[i].Params[0].HighPass.setParams(BiquadType::LowShelf,
992 gainLF, lfScale, calc_rcpQ_from_slope(gainLF, 1.0f)
994 for(ALsizei c{1};c < num_channels;c++)
996 voice->Send[i].Params[c].LowPass.copyParamsFrom(voice->Send[i].Params[0].LowPass);
997 voice->Send[i].Params[c].HighPass.copyParamsFrom(voice->Send[i].Params[0].HighPass);
1002 void CalcNonAttnSourceParams(ALvoice *voice, const ALvoicePropsBase *props, const ALbuffer *ALBuffer, const ALCcontext *ALContext)
1004 const ALCdevice *Device{ALContext->Device};
1005 ALeffectslot *SendSlots[MAX_SENDS];
1007 voice->Direct.Buffer = Device->Dry.Buffer;
1008 voice->Direct.Channels = Device->Dry.NumChannels;
1009 for(ALsizei i{0};i < Device->NumAuxSends;i++)
1011 SendSlots[i] = props->Send[i].Slot;
1012 if(!SendSlots[i] && i == 0)
1013 SendSlots[i] = ALContext->DefaultSlot.get();
1014 if(!SendSlots[i] || SendSlots[i]->Params.EffectType == AL_EFFECT_NULL)
1016 SendSlots[i] = nullptr;
1017 voice->Send[i].Buffer = nullptr;
1018 voice->Send[i].Channels = 0;
1020 else
1022 voice->Send[i].Buffer = SendSlots[i]->WetBuffer;
1023 voice->Send[i].Channels = SendSlots[i]->NumChannels;
1027 /* Calculate the stepping value */
1028 const auto Pitch = static_cast<ALfloat>(ALBuffer->Frequency) /
1029 static_cast<ALfloat>(Device->Frequency) * props->Pitch;
1030 if(Pitch > static_cast<ALfloat>(MAX_PITCH))
1031 voice->Step = MAX_PITCH<<FRACTIONBITS;
1032 else
1033 voice->Step = maxi(fastf2i(Pitch * FRACTIONONE), 1);
1034 if(props->mResampler == BSinc24Resampler)
1035 BsincPrepare(voice->Step, &voice->ResampleState.bsinc, &bsinc24);
1036 else if(props->mResampler == BSinc12Resampler)
1037 BsincPrepare(voice->Step, &voice->ResampleState.bsinc, &bsinc12);
1038 voice->Resampler = SelectResampler(props->mResampler);
1040 /* Calculate gains */
1041 const ALlistener &Listener = ALContext->Listener;
1042 ALfloat DryGain{clampf(props->Gain, props->MinGain, props->MaxGain)};
1043 DryGain *= props->Direct.Gain * Listener.Params.Gain;
1044 DryGain = minf(DryGain, GAIN_MIX_MAX);
1045 ALfloat DryGainHF{props->Direct.GainHF};
1046 ALfloat DryGainLF{props->Direct.GainLF};
1047 ALfloat WetGain[MAX_SENDS], WetGainHF[MAX_SENDS], WetGainLF[MAX_SENDS];
1048 for(ALsizei i{0};i < Device->NumAuxSends;i++)
1050 WetGain[i] = clampf(props->Gain, props->MinGain, props->MaxGain);
1051 WetGain[i] *= props->Send[i].Gain * Listener.Params.Gain;
1052 WetGain[i] = minf(WetGain[i], GAIN_MIX_MAX);
1053 WetGainHF[i] = props->Send[i].GainHF;
1054 WetGainLF[i] = props->Send[i].GainLF;
1057 CalcPanningAndFilters(voice, 0.0f, 0.0f, 0.0f, 0.0f, DryGain, DryGainHF, DryGainLF, WetGain,
1058 WetGainLF, WetGainHF, SendSlots, ALBuffer, props, Listener, Device);
1061 void CalcAttnSourceParams(ALvoice *voice, const ALvoicePropsBase *props, const ALbuffer *ALBuffer, const ALCcontext *ALContext)
1063 const ALCdevice *Device{ALContext->Device};
1064 const ALsizei NumSends{Device->NumAuxSends};
1065 const ALlistener &Listener = ALContext->Listener;
1067 /* Set mixing buffers and get send parameters. */
1068 voice->Direct.Buffer = Device->Dry.Buffer;
1069 voice->Direct.Channels = Device->Dry.NumChannels;
1070 ALeffectslot *SendSlots[MAX_SENDS];
1071 ALfloat RoomRolloff[MAX_SENDS];
1072 ALfloat DecayDistance[MAX_SENDS];
1073 ALfloat DecayLFDistance[MAX_SENDS];
1074 ALfloat DecayHFDistance[MAX_SENDS];
1075 for(ALsizei i{0};i < NumSends;i++)
1077 SendSlots[i] = props->Send[i].Slot;
1078 if(!SendSlots[i] && i == 0)
1079 SendSlots[i] = ALContext->DefaultSlot.get();
1080 if(!SendSlots[i] || SendSlots[i]->Params.EffectType == AL_EFFECT_NULL)
1082 SendSlots[i] = nullptr;
1083 RoomRolloff[i] = 0.0f;
1084 DecayDistance[i] = 0.0f;
1085 DecayLFDistance[i] = 0.0f;
1086 DecayHFDistance[i] = 0.0f;
1088 else if(SendSlots[i]->Params.AuxSendAuto)
1090 RoomRolloff[i] = SendSlots[i]->Params.RoomRolloff + props->RoomRolloffFactor;
1091 /* Calculate the distances to where this effect's decay reaches
1092 * -60dB.
1094 DecayDistance[i] = SendSlots[i]->Params.DecayTime *
1095 Listener.Params.ReverbSpeedOfSound;
1096 DecayLFDistance[i] = DecayDistance[i] * SendSlots[i]->Params.DecayLFRatio;
1097 DecayHFDistance[i] = DecayDistance[i] * SendSlots[i]->Params.DecayHFRatio;
1098 if(SendSlots[i]->Params.DecayHFLimit)
1100 ALfloat airAbsorption{SendSlots[i]->Params.AirAbsorptionGainHF};
1101 if(airAbsorption < 1.0f)
1103 /* Calculate the distance to where this effect's air
1104 * absorption reaches -60dB, and limit the effect's HF
1105 * decay distance (so it doesn't take any longer to decay
1106 * than the air would allow).
1108 ALfloat absorb_dist{std::log10(REVERB_DECAY_GAIN) / std::log10(airAbsorption)};
1109 DecayHFDistance[i] = minf(absorb_dist, DecayHFDistance[i]);
1113 else
1115 /* If the slot's auxiliary send auto is off, the data sent to the
1116 * effect slot is the same as the dry path, sans filter effects */
1117 RoomRolloff[i] = props->RolloffFactor;
1118 DecayDistance[i] = 0.0f;
1119 DecayLFDistance[i] = 0.0f;
1120 DecayHFDistance[i] = 0.0f;
1123 if(!SendSlots[i])
1125 voice->Send[i].Buffer = nullptr;
1126 voice->Send[i].Channels = 0;
1128 else
1130 voice->Send[i].Buffer = SendSlots[i]->WetBuffer;
1131 voice->Send[i].Channels = SendSlots[i]->NumChannels;
1135 /* Transform source to listener space (convert to head relative) */
1136 alu::Vector Position{props->Position[0], props->Position[1], props->Position[2], 1.0f};
1137 alu::Vector Velocity{props->Velocity[0], props->Velocity[1], props->Velocity[2], 0.0f};
1138 alu::Vector Direction{props->Direction[0], props->Direction[1], props->Direction[2], 0.0f};
1139 if(props->HeadRelative == AL_FALSE)
1141 /* Transform source vectors */
1142 Position = Listener.Params.Matrix * Position;
1143 Velocity = Listener.Params.Matrix * Velocity;
1144 Direction = Listener.Params.Matrix * Direction;
1146 else
1148 /* Offset the source velocity to be relative of the listener velocity */
1149 Velocity += Listener.Params.Velocity;
1152 const bool directional{Direction.normalize() > 0.0f};
1153 alu::Vector SourceToListener{-Position[0], -Position[1], -Position[2], 0.0f};
1154 const ALfloat Distance{SourceToListener.normalize()};
1156 /* Initial source gain */
1157 ALfloat DryGain{props->Gain};
1158 ALfloat DryGainHF{1.0f};
1159 ALfloat DryGainLF{1.0f};
1160 ALfloat WetGain[MAX_SENDS], WetGainHF[MAX_SENDS], WetGainLF[MAX_SENDS];
1161 for(ALsizei i{0};i < NumSends;i++)
1163 WetGain[i] = props->Gain;
1164 WetGainHF[i] = 1.0f;
1165 WetGainLF[i] = 1.0f;
1168 /* Calculate distance attenuation */
1169 ALfloat ClampedDist{Distance};
1171 switch(Listener.Params.SourceDistanceModel ?
1172 props->mDistanceModel : Listener.Params.mDistanceModel)
1174 case DistanceModel::InverseClamped:
1175 ClampedDist = clampf(ClampedDist, props->RefDistance, props->MaxDistance);
1176 if(props->MaxDistance < props->RefDistance) break;
1177 /*fall-through*/
1178 case DistanceModel::Inverse:
1179 if(!(props->RefDistance > 0.0f))
1180 ClampedDist = props->RefDistance;
1181 else
1183 ALfloat dist = lerp(props->RefDistance, ClampedDist, props->RolloffFactor);
1184 if(dist > 0.0f) DryGain *= props->RefDistance / dist;
1185 for(ALsizei i{0};i < NumSends;i++)
1187 dist = lerp(props->RefDistance, ClampedDist, RoomRolloff[i]);
1188 if(dist > 0.0f) WetGain[i] *= props->RefDistance / dist;
1191 break;
1193 case DistanceModel::LinearClamped:
1194 ClampedDist = clampf(ClampedDist, props->RefDistance, props->MaxDistance);
1195 if(props->MaxDistance < props->RefDistance) break;
1196 /*fall-through*/
1197 case DistanceModel::Linear:
1198 if(!(props->MaxDistance != props->RefDistance))
1199 ClampedDist = props->RefDistance;
1200 else
1202 ALfloat attn = props->RolloffFactor * (ClampedDist-props->RefDistance) /
1203 (props->MaxDistance-props->RefDistance);
1204 DryGain *= maxf(1.0f - attn, 0.0f);
1205 for(ALsizei i{0};i < NumSends;i++)
1207 attn = RoomRolloff[i] * (ClampedDist-props->RefDistance) /
1208 (props->MaxDistance-props->RefDistance);
1209 WetGain[i] *= maxf(1.0f - attn, 0.0f);
1212 break;
1214 case DistanceModel::ExponentClamped:
1215 ClampedDist = clampf(ClampedDist, props->RefDistance, props->MaxDistance);
1216 if(props->MaxDistance < props->RefDistance) break;
1217 /*fall-through*/
1218 case DistanceModel::Exponent:
1219 if(!(ClampedDist > 0.0f && props->RefDistance > 0.0f))
1220 ClampedDist = props->RefDistance;
1221 else
1223 DryGain *= std::pow(ClampedDist/props->RefDistance, -props->RolloffFactor);
1224 for(ALsizei i{0};i < NumSends;i++)
1225 WetGain[i] *= std::pow(ClampedDist/props->RefDistance, -RoomRolloff[i]);
1227 break;
1229 case DistanceModel::Disable:
1230 ClampedDist = props->RefDistance;
1231 break;
1234 /* Calculate directional soundcones */
1235 if(directional && props->InnerAngle < 360.0f)
1237 const ALfloat Angle{Rad2Deg(std::acos(aluDotproduct(Direction, SourceToListener)) *
1238 ConeScale * 2.0f)};
1240 ALfloat ConeVolume, ConeHF;
1241 if(!(Angle > props->InnerAngle))
1243 ConeVolume = 1.0f;
1244 ConeHF = 1.0f;
1246 else if(Angle < props->OuterAngle)
1248 ALfloat scale = ( Angle-props->InnerAngle) /
1249 (props->OuterAngle-props->InnerAngle);
1250 ConeVolume = lerp(1.0f, props->OuterGain, scale);
1251 ConeHF = lerp(1.0f, props->OuterGainHF, scale);
1253 else
1255 ConeVolume = props->OuterGain;
1256 ConeHF = props->OuterGainHF;
1259 DryGain *= ConeVolume;
1260 if(props->DryGainHFAuto)
1261 DryGainHF *= ConeHF;
1262 if(props->WetGainAuto)
1263 std::transform(std::begin(WetGain), std::begin(WetGain)+NumSends, std::begin(WetGain),
1264 [ConeVolume](ALfloat gain) noexcept -> ALfloat { return gain * ConeVolume; }
1266 if(props->WetGainHFAuto)
1267 std::transform(std::begin(WetGainHF), std::begin(WetGainHF)+NumSends,
1268 std::begin(WetGainHF),
1269 [ConeHF](ALfloat gain) noexcept -> ALfloat { return gain * ConeHF; }
1273 /* Apply gain and frequency filters */
1274 DryGain = clampf(DryGain, props->MinGain, props->MaxGain);
1275 DryGain = minf(DryGain*props->Direct.Gain*Listener.Params.Gain, GAIN_MIX_MAX);
1276 DryGainHF *= props->Direct.GainHF;
1277 DryGainLF *= props->Direct.GainLF;
1278 for(ALsizei i{0};i < NumSends;i++)
1280 WetGain[i] = clampf(WetGain[i], props->MinGain, props->MaxGain);
1281 WetGain[i] = minf(WetGain[i]*props->Send[i].Gain*Listener.Params.Gain, GAIN_MIX_MAX);
1282 WetGainHF[i] *= props->Send[i].GainHF;
1283 WetGainLF[i] *= props->Send[i].GainLF;
1286 /* Distance-based air absorption and initial send decay. */
1287 if(ClampedDist > props->RefDistance && props->RolloffFactor > 0.0f)
1289 ALfloat meters_base{(ClampedDist-props->RefDistance) * props->RolloffFactor *
1290 Listener.Params.MetersPerUnit};
1291 if(props->AirAbsorptionFactor > 0.0f)
1293 ALfloat hfattn{std::pow(AIRABSORBGAINHF, meters_base * props->AirAbsorptionFactor)};
1294 DryGainHF *= hfattn;
1295 std::transform(std::begin(WetGainHF), std::begin(WetGainHF)+NumSends,
1296 std::begin(WetGainHF),
1297 [hfattn](ALfloat gain) noexcept -> ALfloat { return gain * hfattn; }
1301 if(props->WetGainAuto)
1303 /* Apply a decay-time transformation to the wet path, based on the
1304 * source distance in meters. The initial decay of the reverb
1305 * effect is calculated and applied to the wet path.
1307 for(ALsizei i{0};i < NumSends;i++)
1309 if(!(DecayDistance[i] > 0.0f))
1310 continue;
1312 const ALfloat gain{std::pow(REVERB_DECAY_GAIN, meters_base/DecayDistance[i])};
1313 WetGain[i] *= gain;
1314 /* Yes, the wet path's air absorption is applied with
1315 * WetGainAuto on, rather than WetGainHFAuto.
1317 if(gain > 0.0f)
1319 ALfloat gainhf{std::pow(REVERB_DECAY_GAIN, meters_base/DecayHFDistance[i])};
1320 WetGainHF[i] *= minf(gainhf / gain, 1.0f);
1321 ALfloat gainlf{std::pow(REVERB_DECAY_GAIN, meters_base/DecayLFDistance[i])};
1322 WetGainLF[i] *= minf(gainlf / gain, 1.0f);
1329 /* Initial source pitch */
1330 ALfloat Pitch{props->Pitch};
1332 /* Calculate velocity-based doppler effect */
1333 ALfloat DopplerFactor{props->DopplerFactor * Listener.Params.DopplerFactor};
1334 if(DopplerFactor > 0.0f)
1336 const alu::Vector &lvelocity = Listener.Params.Velocity;
1337 ALfloat vss{aluDotproduct(Velocity, SourceToListener) * DopplerFactor};
1338 ALfloat vls{aluDotproduct(lvelocity, SourceToListener) * DopplerFactor};
1340 const ALfloat SpeedOfSound{Listener.Params.SpeedOfSound};
1341 if(!(vls < SpeedOfSound))
1343 /* Listener moving away from the source at the speed of sound.
1344 * Sound waves can't catch it.
1346 Pitch = 0.0f;
1348 else if(!(vss < SpeedOfSound))
1350 /* Source moving toward the listener at the speed of sound. Sound
1351 * waves bunch up to extreme frequencies.
1353 Pitch = std::numeric_limits<float>::infinity();
1355 else
1357 /* Source and listener movement is nominal. Calculate the proper
1358 * doppler shift.
1360 Pitch *= (SpeedOfSound-vls) / (SpeedOfSound-vss);
1364 /* Adjust pitch based on the buffer and output frequencies, and calculate
1365 * fixed-point stepping value.
1367 Pitch *= static_cast<ALfloat>(ALBuffer->Frequency)/static_cast<ALfloat>(Device->Frequency);
1368 if(Pitch > static_cast<ALfloat>(MAX_PITCH))
1369 voice->Step = MAX_PITCH<<FRACTIONBITS;
1370 else
1371 voice->Step = maxi(fastf2i(Pitch * FRACTIONONE), 1);
1372 if(props->mResampler == BSinc24Resampler)
1373 BsincPrepare(voice->Step, &voice->ResampleState.bsinc, &bsinc24);
1374 else if(props->mResampler == BSinc12Resampler)
1375 BsincPrepare(voice->Step, &voice->ResampleState.bsinc, &bsinc12);
1376 voice->Resampler = SelectResampler(props->mResampler);
1378 ALfloat ev{0.0f}, az{0.0f};
1379 if(Distance > 0.0f)
1381 /* Clamp Y, in case rounding errors caused it to end up outside of
1382 * -1...+1.
1384 ev = std::asin(clampf(-SourceToListener[1], -1.0f, 1.0f));
1385 /* Double negation on Z cancels out; negate once for changing source-
1386 * to-listener to listener-to-source, and again for right-handed coords
1387 * with -Z in front.
1389 az = std::atan2(-SourceToListener[0], SourceToListener[2]*ZScale);
1392 ALfloat spread{0.0f};
1393 if(props->Radius > Distance)
1394 spread = al::MathDefs<float>::Tau() - Distance/props->Radius*al::MathDefs<float>::Pi();
1395 else if(Distance > 0.0f)
1396 spread = std::asin(props->Radius/Distance) * 2.0f;
1398 CalcPanningAndFilters(voice, az, ev, Distance, spread, DryGain, DryGainHF, DryGainLF, WetGain,
1399 WetGainLF, WetGainHF, SendSlots, ALBuffer, props, Listener, Device);
1402 void CalcSourceParams(ALvoice *voice, ALCcontext *context, bool force)
1404 ALvoiceProps *props{voice->Update.exchange(nullptr, std::memory_order_acq_rel)};
1405 if(!props && !force) return;
1407 if(props)
1409 voice->Props = *props;
1411 AtomicReplaceHead(context->FreeVoiceProps, props);
1414 ALbufferlistitem *BufferListItem{voice->current_buffer.load(std::memory_order_relaxed)};
1415 while(BufferListItem)
1417 auto buffers_end = BufferListItem->buffers+BufferListItem->num_buffers;
1418 auto buffer = std::find_if(BufferListItem->buffers, buffers_end,
1419 std::bind(std::not_equal_to<const ALbuffer*>{}, _1, nullptr));
1420 if(LIKELY(buffer != buffers_end))
1422 if(voice->Props.mSpatializeMode==SpatializeOn ||
1423 (voice->Props.mSpatializeMode==SpatializeAuto && (*buffer)->mFmtChannels==FmtMono))
1424 CalcAttnSourceParams(voice, &voice->Props, *buffer, context);
1425 else
1426 CalcNonAttnSourceParams(voice, &voice->Props, *buffer, context);
1427 break;
1429 BufferListItem = BufferListItem->next.load(std::memory_order_acquire);
1434 void ProcessParamUpdates(ALCcontext *ctx, const ALeffectslotArray *slots)
1436 IncrementRef(&ctx->UpdateCount);
1437 if(LIKELY(!ctx->HoldUpdates.load(std::memory_order_acquire)))
1439 bool cforce{CalcContextParams(ctx)};
1440 bool force{CalcListenerParams(ctx) || cforce};
1441 force = std::accumulate(slots->begin(), slots->end(), force,
1442 [ctx,cforce](bool force, ALeffectslot *slot) -> bool
1443 { return CalcEffectSlotParams(slot, ctx, cforce) | force; }
1446 std::for_each(ctx->Voices, ctx->Voices+ctx->VoiceCount.load(std::memory_order_acquire),
1447 [ctx,force](ALvoice *voice) -> void
1449 ALuint sid{voice->SourceID.load(std::memory_order_acquire)};
1450 if(sid) CalcSourceParams(voice, ctx, force);
1454 IncrementRef(&ctx->UpdateCount);
1457 void ProcessContext(ALCcontext *ctx, const ALsizei SamplesToDo)
1459 ASSUME(SamplesToDo > 0);
1461 const ALeffectslotArray *auxslots{ctx->ActiveAuxSlots.load(std::memory_order_acquire)};
1463 /* Process pending propery updates for objects on the context. */
1464 ProcessParamUpdates(ctx, auxslots);
1466 /* Clear auxiliary effect slot mixing buffers. */
1467 std::for_each(auxslots->begin(), auxslots->end(),
1468 [SamplesToDo](ALeffectslot *slot) -> void
1470 std::for_each(slot->WetBuffer, slot->WetBuffer+slot->NumChannels,
1471 [SamplesToDo](ALfloat *buffer) -> void
1472 { std::fill_n(buffer, SamplesToDo, 0.0f); }
1477 /* Process voices that have a playing source. */
1478 std::for_each(ctx->Voices, ctx->Voices+ctx->VoiceCount.load(std::memory_order_acquire),
1479 [SamplesToDo,ctx](ALvoice *voice) -> void
1481 if(!voice->Playing.load(std::memory_order_acquire)) return;
1482 ALuint sid{voice->SourceID.load(std::memory_order_relaxed)};
1483 if(!sid || voice->Step < 1) return;
1485 if(!MixSource(voice, sid, ctx, SamplesToDo))
1487 voice->SourceID.store(0u, std::memory_order_relaxed);
1488 voice->Playing.store(false, std::memory_order_release);
1489 SendSourceStoppedEvent(ctx, sid);
1494 /* Process effects. */
1495 if(auxslots->size() < 1) return;
1496 auto slots = auxslots->data();
1497 auto slots_end = slots + auxslots->size();
1499 /* First sort the slots into scratch storage, so that effects come before
1500 * their effect target (or their targets' target).
1502 auto sorted_slots = const_cast<ALeffectslot**>(slots_end);
1503 auto sorted_slots_end = sorted_slots;
1504 auto in_chain = [](const ALeffectslot *slot1, const ALeffectslot *slot2) noexcept -> bool
1506 while((slot1=slot1->Params.Target) != nullptr) {
1507 if(slot1 == slot2) return true;
1509 return false;
1512 *sorted_slots_end = *slots;
1513 ++sorted_slots_end;
1514 while(++slots != slots_end)
1516 /* If this effect slot targets an effect slot already in the list (i.e.
1517 * slots outputs to something in sorted_slots), directly or indirectly,
1518 * insert it prior to that element.
1520 auto checker = sorted_slots;
1521 do {
1522 if(in_chain(*slots, *checker)) break;
1523 } while(++checker != sorted_slots_end);
1525 checker = std::move_backward(checker, sorted_slots_end, sorted_slots_end+1);
1526 *--checker = *slots;
1527 ++sorted_slots_end;
1530 std::for_each(sorted_slots, sorted_slots_end,
1531 [SamplesToDo](const ALeffectslot *slot) -> void
1533 EffectState *state{slot->Params.mEffectState};
1534 state->process(SamplesToDo, slot->WetBuffer, state->mOutBuffer,
1535 state->mOutChannels);
1541 void ApplyStablizer(FrontStablizer *Stablizer, ALfloat (*RESTRICT Buffer)[BUFFERSIZE],
1542 int lidx, int ridx, int cidx, const ALsizei SamplesToDo,
1543 const ALsizei NumChannels)
1545 ASSUME(SamplesToDo > 0);
1546 ASSUME(NumChannels > 0);
1548 /* Apply an all-pass to all channels, except the front-left and front-
1549 * right, so they maintain the same relative phase.
1551 for(ALsizei i{0};i < NumChannels;i++)
1553 if(i == lidx || i == ridx)
1554 continue;
1555 Stablizer->APFilter[i].process(Buffer[i], SamplesToDo);
1558 ALfloat (&lsplit)[2][BUFFERSIZE] = Stablizer->LSplit;
1559 ALfloat (&rsplit)[2][BUFFERSIZE] = Stablizer->RSplit;
1560 Stablizer->LFilter.process(lsplit[1], lsplit[0], Buffer[lidx], SamplesToDo);
1561 Stablizer->RFilter.process(rsplit[1], rsplit[0], Buffer[ridx], SamplesToDo);
1563 for(ALsizei i{0};i < SamplesToDo;i++)
1565 ALfloat lfsum{lsplit[0][i] + rsplit[0][i]};
1566 ALfloat hfsum{lsplit[1][i] + rsplit[1][i]};
1567 ALfloat s{lsplit[0][i] + lsplit[1][i] - rsplit[0][i] - rsplit[1][i]};
1569 /* This pans the separate low- and high-frequency sums between being on
1570 * the center channel and the left/right channels. The low-frequency
1571 * sum is 1/3rd toward center (2/3rds on left/right) and the high-
1572 * frequency sum is 1/4th toward center (3/4ths on left/right). These
1573 * values can be tweaked.
1575 ALfloat m{lfsum*std::cos(1.0f/3.0f * (al::MathDefs<float>::Pi()*0.5f)) +
1576 hfsum*std::cos(1.0f/4.0f * (al::MathDefs<float>::Pi()*0.5f))};
1577 ALfloat c{lfsum*std::sin(1.0f/3.0f * (al::MathDefs<float>::Pi()*0.5f)) +
1578 hfsum*std::sin(1.0f/4.0f * (al::MathDefs<float>::Pi()*0.5f))};
1580 /* The generated center channel signal adds to the existing signal,
1581 * while the modified left and right channels replace.
1583 Buffer[lidx][i] = (m + s) * 0.5f;
1584 Buffer[ridx][i] = (m - s) * 0.5f;
1585 Buffer[cidx][i] += c * 0.5f;
1589 void ApplyDistanceComp(ALfloat (*Samples)[BUFFERSIZE], const DistanceComp &distcomp,
1590 ALfloat (&Values)[BUFFERSIZE], const ALsizei SamplesToDo, const ALsizei numchans)
1592 ASSUME(SamplesToDo > 0);
1593 ASSUME(numchans > 0);
1595 ALfloat *RESTRICT tempvals{al::assume_aligned<16>(&Values[0])};
1596 for(ALsizei c{0};c < numchans;c++)
1598 ALfloat *RESTRICT inout{al::assume_aligned<16>(Samples[c])};
1599 const ALfloat gain{distcomp[c].Gain};
1600 const ALsizei base{distcomp[c].Length};
1601 ALfloat *RESTRICT distbuf{al::assume_aligned<16>(distcomp[c].Buffer)};
1603 if(base <= 0)
1605 if(gain < 1.0f)
1606 std::transform(inout, inout+SamplesToDo, inout,
1607 [gain](const ALfloat in) noexcept -> ALfloat
1608 { return in * gain; }
1610 continue;
1613 if(LIKELY(SamplesToDo >= base))
1615 auto out = std::copy_n(distbuf, base, tempvals);
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, tempvals);
1622 auto out = std::copy(distbuf+SamplesToDo, distbuf+base, distbuf);
1623 std::copy_n(inout, SamplesToDo, out);
1625 std::transform(tempvals, tempvals+SamplesToDo, inout,
1626 [gain](const ALfloat in) noexcept -> ALfloat { return in * gain; }
1631 void ApplyDither(ALfloat (*Samples)[BUFFERSIZE], ALuint *dither_seed, const ALfloat quant_scale,
1632 const ALsizei SamplesToDo, const ALsizei numchans)
1634 ASSUME(numchans > 0);
1636 /* Dithering. Generate whitenoise (uniform distribution of random values
1637 * between -1 and +1) and add it to the sample values, after scaling up to
1638 * the desired quantization depth amd before rounding.
1640 const ALfloat invscale{1.0f / quant_scale};
1641 ALuint seed{*dither_seed};
1642 auto dither_channel = [&seed,invscale,quant_scale,SamplesToDo](ALfloat *input) -> void
1644 ASSUME(SamplesToDo > 0);
1645 ALfloat *buffer{al::assume_aligned<16>(input)};
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 += static_cast<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 (*InBuffer)[BUFFERSIZE], ALvoid *OutBuffer, ALsizei Offset,
1694 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 const int lidx{GetChannelIdxByName(device->RealOut, FrontLeft)};
1766 const int ridx{GetChannelIdxByName(device->RealOut, FrontRight)};
1767 const 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 compression, limiting sample amplitude if needed or desired. */
1775 if(Compressor *comp{device->Limiter.get()})
1776 comp->process(SamplesToDo, device->RealOut.Buffer);
1778 /* Apply delays and attenuation for mismatched speaker distances. */
1779 ApplyDistanceComp(device->RealOut.Buffer, device->ChannelDelay, device->TempBuffer[0],
1780 SamplesToDo, device->RealOut.NumChannels);
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(false, std::memory_order_acq_rel))
1820 return;
1822 AsyncEvent evt{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 || static_cast<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 const ALbitfieldSOFT enabledevt{ctx->EnabledEvts.load(std::memory_order_acquire)};
1839 if((enabledevt&EventType_Disconnected))
1841 RingBuffer *ring{ctx->AsyncEvents.get()};
1842 auto evt_data = ring->getWriteVector().first;
1843 if(evt_data.len > 0)
1845 new (evt_data.buf) AsyncEvent{evt};
1846 ring->writeAdvance(1);
1847 ctx->EventSem.post();
1851 std::for_each(ctx->Voices, ctx->Voices+ctx->VoiceCount.load(std::memory_order_acquire),
1852 [ctx](ALvoice *voice) -> void
1854 if(!voice->Playing.load(std::memory_order_acquire)) return;
1855 ALuint sid{voice->SourceID.load(std::memory_order_relaxed)};
1856 if(!sid) return;
1858 voice->SourceID.store(0u, std::memory_order_relaxed);
1859 voice->Playing.store(false, std::memory_order_release);
1860 /* If the source's voice was playing, it's now effectively
1861 * stopped (the source state will be updated the next time it's
1862 * checked).
1864 SendSourceStoppedEvent(ctx, sid);
1868 ctx = ctx->next.load(std::memory_order_relaxed);