Remove redundant void argument list in function def
[openal-soft.git] / Alc / alu.cpp
blob42e31b88194798c4ad364871c669a58c076c859e
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 <algorithm>
32 #include <functional>
34 #include "alMain.h"
35 #include "alcontext.h"
36 #include "alSource.h"
37 #include "alBuffer.h"
38 #include "alListener.h"
39 #include "alAuxEffectSlot.h"
40 #include "alu.h"
41 #include "bs2b.h"
42 #include "hrtf.h"
43 #include "mastering.h"
44 #include "uhjfilter.h"
45 #include "bformatdec.h"
46 #include "ringbuffer.h"
47 #include "filters/splitter.h"
49 #include "mixer/defs.h"
50 #include "fpu_modes.h"
51 #include "cpu_caps.h"
52 #include "bsinc_inc.h"
55 namespace {
57 using namespace std::placeholders;
59 ALfloat InitConeScale()
61 ALfloat ret{1.0f};
62 const char *str{getenv("__ALSOFT_HALF_ANGLE_CONES")};
63 if(str && (strcasecmp(str, "true") == 0 || strtol(str, nullptr, 0) == 1))
64 ret *= 0.5f;
65 return ret;
68 ALfloat InitZScale()
70 ALfloat ret{1.0f};
71 const char *str{getenv("__ALSOFT_REVERSE_Z")};
72 if(str && (strcasecmp(str, "true") == 0 || strtol(str, nullptr, 0) == 1))
73 ret *= -1.0f;
74 return ret;
77 ALboolean InitReverbSOS()
79 ALboolean ret{AL_FALSE};
80 const char *str{getenv("__ALSOFT_REVERB_IGNORES_SOUND_SPEED")};
81 if(str && (strcasecmp(str, "true") == 0 || strtol(str, nullptr, 0) == 1))
82 ret = AL_TRUE;
83 return ret;
86 } // namespace
88 /* Cone scalar */
89 const ALfloat ConeScale{InitConeScale()};
91 /* Localized Z scalar for mono sources */
92 const ALfloat ZScale{InitZScale()};
94 /* Force default speed of sound for distance-related reverb decay. */
95 const ALboolean OverrideReverbSpeedOfSound{InitReverbSOS()};
98 namespace {
100 void ClearArray(ALfloat (&f)[MAX_OUTPUT_CHANNELS])
102 std::fill(std::begin(f), std::end(f), 0.0f);
105 struct ChanMap {
106 Channel channel;
107 ALfloat angle;
108 ALfloat elevation;
111 HrtfDirectMixerFunc MixDirectHrtf = MixDirectHrtf_C;
113 inline HrtfDirectMixerFunc SelectHrtfMixer(void)
115 #ifdef HAVE_NEON
116 if((CPUCapFlags&CPU_CAP_NEON))
117 return MixDirectHrtf_Neon;
118 #endif
119 #ifdef HAVE_SSE
120 if((CPUCapFlags&CPU_CAP_SSE))
121 return MixDirectHrtf_SSE;
122 #endif
124 return MixDirectHrtf_C;
128 void ProcessHrtf(ALCdevice *device, ALsizei SamplesToDo)
130 if(AmbiUpsampler *ambiup{device->AmbiUp.get()})
131 ambiup->process(device->Dry.Buffer, device->Dry.NumChannels, device->FOAOut.Buffer,
132 device->FOAOut.NumChannels, SamplesToDo);
134 /* HRTF is stereo output only. */
135 const int lidx{(device->RealOut.ChannelName[0]==FrontLeft) ? 0 : 1};
136 const int ridx{(device->RealOut.ChannelName[0]==FrontLeft) ? 1 : 0};
137 ALfloat *LeftOut{device->RealOut.Buffer[lidx]};
138 ALfloat *RightOut{device->RealOut.Buffer[ridx]};
140 DirectHrtfState *state{device->mHrtfState.get()};
141 MixDirectHrtf(LeftOut, RightOut, device->Dry.Buffer, state, device->Dry.NumChannels,
142 SamplesToDo);
143 state->Offset += SamplesToDo;
146 void ProcessAmbiDec(ALCdevice *device, ALsizei SamplesToDo)
148 BFormatDec *ambidec{device->AmbiDecoder.get()};
149 if(device->Dry.Buffer != device->FOAOut.Buffer)
150 ambidec->upSample(device->Dry.Buffer, device->Dry.NumChannels, device->FOAOut.Buffer,
151 device->FOAOut.NumChannels, SamplesToDo);
152 ambidec->process(device->RealOut.Buffer, device->RealOut.NumChannels, device->Dry.Buffer,
153 SamplesToDo);
156 void ProcessAmbiUp(ALCdevice *device, ALsizei SamplesToDo)
158 device->AmbiUp->process(device->RealOut.Buffer, device->RealOut.NumChannels,
159 device->FOAOut.Buffer, device->FOAOut.NumChannels, SamplesToDo);
162 void ProcessUhj(ALCdevice *device, ALsizei SamplesToDo)
164 /* UHJ is stereo output only. */
165 const int lidx{(device->RealOut.ChannelName[0]==FrontLeft) ? 0 : 1};
166 const int ridx{(device->RealOut.ChannelName[1]==FrontRight) ? 1 : 0};
168 /* Encode to stereo-compatible 2-channel UHJ output. */
169 Uhj2Encoder *uhj2enc{device->Uhj_Encoder.get()};
170 uhj2enc->encode(device->RealOut.Buffer[lidx], device->RealOut.Buffer[ridx],
171 device->Dry.Buffer, SamplesToDo);
174 void ProcessBs2b(ALCdevice *device, ALsizei SamplesToDo)
176 /* BS2B is stereo output only. */
177 const int lidx{(device->RealOut.ChannelName[0]==FrontLeft) ? 0 : 1};
178 const int ridx{(device->RealOut.ChannelName[1]==FrontRight) ? 1 : 0};
180 /* Apply binaural/crossfeed filter */
181 bs2b_cross_feed(device->Bs2b.get(), device->RealOut.Buffer[lidx],
182 device->RealOut.Buffer[ridx], SamplesToDo);
185 } // namespace
187 void aluInit(void)
189 MixDirectHrtf = SelectHrtfMixer();
193 void DeinitVoice(ALvoice *voice) noexcept
195 delete voice->Update.exchange(nullptr, std::memory_order_acq_rel);
196 voice->~ALvoice();
200 void aluSelectPostProcess(ALCdevice *device)
202 if(device->mHrtf)
203 device->PostProcess = ProcessHrtf;
204 else if(device->AmbiDecoder)
205 device->PostProcess = ProcessAmbiDec;
206 else if(device->AmbiUp)
207 device->PostProcess = ProcessAmbiUp;
208 else if(device->Uhj_Encoder)
209 device->PostProcess = ProcessUhj;
210 else if(device->Bs2b)
211 device->PostProcess = ProcessBs2b;
212 else
213 device->PostProcess = nullptr;
217 /* Prepares the interpolator for a given rate (determined by increment).
219 * With a bit of work, and a trade of memory for CPU cost, this could be
220 * modified for use with an interpolated increment for buttery-smooth pitch
221 * changes.
223 void BsincPrepare(const ALuint increment, BsincState *state, const BSincTable *table)
225 ALsizei si{BSINC_SCALE_COUNT - 1};
226 ALfloat sf{0.0f};
228 if(increment > FRACTIONONE)
230 sf = static_cast<ALfloat>FRACTIONONE / increment;
231 sf = maxf(0.0f, (BSINC_SCALE_COUNT-1) * (sf-table->scaleBase) * table->scaleRange);
232 si = float2int(sf);
233 /* The interpolation factor is fit to this diagonally-symmetric curve
234 * to reduce the transition ripple caused by interpolating different
235 * scales of the sinc function.
237 sf = 1.0f - std::cos(std::asin(sf - si));
240 state->sf = sf;
241 state->m = table->m[si];
242 state->l = (state->m/2) - 1;
243 state->filter = table->Tab + table->filterOffset[si];
247 namespace {
249 /* This RNG method was created based on the math found in opusdec. It's quick,
250 * and starting with a seed value of 22222, is suitable for generating
251 * whitenoise.
253 inline ALuint dither_rng(ALuint *seed) noexcept
255 *seed = (*seed * 96314165) + 907633515;
256 return *seed;
260 inline alu::Vector aluCrossproduct(const alu::Vector &in1, const alu::Vector &in2)
262 return alu::Vector{
263 in1[1]*in2[2] - in1[2]*in2[1],
264 in1[2]*in2[0] - in1[0]*in2[2],
265 in1[0]*in2[1] - in1[1]*in2[0],
266 0.0f
270 inline ALfloat aluDotproduct(const alu::Vector &vec1, const alu::Vector &vec2)
272 return vec1[0]*vec2[0] + vec1[1]*vec2[1] + vec1[2]*vec2[2];
276 alu::Vector operator*(const alu::Matrix &mtx, const alu::Vector &vec) noexcept
278 return alu::Vector{
279 vec[0]*mtx[0][0] + vec[1]*mtx[1][0] + vec[2]*mtx[2][0] + vec[3]*mtx[3][0],
280 vec[0]*mtx[0][1] + vec[1]*mtx[1][1] + vec[2]*mtx[2][1] + vec[3]*mtx[3][1],
281 vec[0]*mtx[0][2] + vec[1]*mtx[1][2] + vec[2]*mtx[2][2] + vec[3]*mtx[3][2],
282 vec[0]*mtx[0][3] + vec[1]*mtx[1][3] + vec[2]*mtx[2][3] + vec[3]*mtx[3][3]
287 void SendSourceStoppedEvent(ALCcontext *context, ALuint id)
289 ALbitfieldSOFT enabledevt{context->EnabledEvts.load(std::memory_order_acquire)};
290 if(!(enabledevt&EventType_SourceStateChange)) return;
292 RingBuffer *ring{context->AsyncEvents.get()};
293 auto evt_vec = ring->getWriteVector();
294 if(evt_vec.first.len < 1) return;
296 AsyncEvent *evt{new (evt_vec.first.buf) AsyncEvent{EventType_SourceStateChange}};
297 evt->u.srcstate.id = id;
298 evt->u.srcstate.state = AL_STOPPED;
300 ring->writeAdvance(1);
301 context->EventSem.post();
305 bool CalcContextParams(ALCcontext *Context)
307 ALcontextProps *props{Context->Update.exchange(nullptr, std::memory_order_acq_rel)};
308 if(!props) return false;
310 ALlistener &Listener = Context->Listener;
311 Listener.Params.MetersPerUnit = props->MetersPerUnit;
313 Listener.Params.DopplerFactor = props->DopplerFactor;
314 Listener.Params.SpeedOfSound = props->SpeedOfSound * props->DopplerVelocity;
315 if(!OverrideReverbSpeedOfSound)
316 Listener.Params.ReverbSpeedOfSound = Listener.Params.SpeedOfSound *
317 Listener.Params.MetersPerUnit;
319 Listener.Params.SourceDistanceModel = props->SourceDistanceModel;
320 Listener.Params.mDistanceModel = props->mDistanceModel;
322 AtomicReplaceHead(Context->FreeContextProps, props);
323 return true;
326 bool CalcListenerParams(ALCcontext *Context)
328 ALlistener &Listener = Context->Listener;
330 ALlistenerProps *props{Listener.Update.exchange(nullptr, std::memory_order_acq_rel)};
331 if(!props) return false;
333 /* AT then UP */
334 alu::Vector N{props->OrientAt[0], props->OrientAt[1], props->OrientAt[2], 0.0f};
335 N.normalize();
336 alu::Vector V{props->OrientUp[0], props->OrientUp[1], props->OrientUp[2], 0.0f};
337 V.normalize();
338 /* Build and normalize right-vector */
339 alu::Vector U{aluCrossproduct(N, V)};
340 U.normalize();
342 Listener.Params.Matrix = alu::Matrix{
343 U[0], V[0], -N[0], 0.0f,
344 U[1], V[1], -N[1], 0.0f,
345 U[2], V[2], -N[2], 0.0f,
346 0.0f, 0.0f, 0.0f, 1.0f
349 const alu::Vector P{Listener.Params.Matrix *
350 alu::Vector{props->Position[0], props->Position[1], props->Position[2], 1.0f}};
351 Listener.Params.Matrix.setRow(3, -P[0], -P[1], -P[2], 1.0f);
353 const alu::Vector vel{props->Velocity[0], props->Velocity[1], props->Velocity[2], 0.0f};
354 Listener.Params.Velocity = Listener.Params.Matrix * vel;
356 Listener.Params.Gain = props->Gain * Context->GainBoost;
358 AtomicReplaceHead(Context->FreeListenerProps, props);
359 return true;
362 bool CalcEffectSlotParams(ALeffectslot *slot, ALCcontext *context, bool force)
364 ALeffectslotProps *props{slot->Update.exchange(nullptr, std::memory_order_acq_rel)};
365 if(!props && !force) return false;
367 EffectState *state;
368 if(!props)
369 state = slot->Params.mEffectState;
370 else
372 slot->Params.Gain = props->Gain;
373 slot->Params.AuxSendAuto = props->AuxSendAuto;
374 slot->Params.Target = props->Target;
375 slot->Params.EffectType = props->Type;
376 slot->Params.EffectProps = props->Props;
377 if(IsReverbEffect(props->Type))
379 slot->Params.RoomRolloff = props->Props.Reverb.RoomRolloffFactor;
380 slot->Params.DecayTime = props->Props.Reverb.DecayTime;
381 slot->Params.DecayLFRatio = props->Props.Reverb.DecayLFRatio;
382 slot->Params.DecayHFRatio = props->Props.Reverb.DecayHFRatio;
383 slot->Params.DecayHFLimit = props->Props.Reverb.DecayHFLimit;
384 slot->Params.AirAbsorptionGainHF = props->Props.Reverb.AirAbsorptionGainHF;
386 else
388 slot->Params.RoomRolloff = 0.0f;
389 slot->Params.DecayTime = 0.0f;
390 slot->Params.DecayLFRatio = 0.0f;
391 slot->Params.DecayHFRatio = 0.0f;
392 slot->Params.DecayHFLimit = AL_FALSE;
393 slot->Params.AirAbsorptionGainHF = 1.0f;
396 state = props->State;
397 props->State = nullptr;
398 EffectState *oldstate{slot->Params.mEffectState};
399 slot->Params.mEffectState = state;
401 /* Manually decrement the old effect state's refcount if it's greater
402 * than 1. We need to be a bit clever here to avoid the refcount
403 * reaching 0 since it can't be deleted in the mixer.
405 ALuint oldval{oldstate->mRef.load(std::memory_order_acquire)};
406 while(oldval > 1 && !oldstate->mRef.compare_exchange_weak(oldval, oldval-1,
407 std::memory_order_acq_rel, std::memory_order_acquire))
409 /* oldval was updated with the current value on failure, so just
410 * try again.
414 if(oldval < 2)
416 /* Otherwise, if it would be deleted, send it off with a release
417 * event.
419 RingBuffer *ring{context->AsyncEvents.get()};
420 auto evt_vec = ring->getWriteVector();
421 if(LIKELY(evt_vec.first.len > 0))
423 AsyncEvent *evt{new (evt_vec.first.buf) AsyncEvent{EventType_ReleaseEffectState}};
424 evt->u.mEffectState = oldstate;
425 ring->writeAdvance(1);
426 context->EventSem.post();
428 else
430 /* If writing the event failed, the queue was probably full.
431 * Store the old state in the property object where it can
432 * eventually be cleaned up sometime later (not ideal, but
433 * better than blocking or leaking).
435 props->State = oldstate;
439 AtomicReplaceHead(context->FreeEffectslotProps, props);
442 MixParams params;
443 EffectTarget output;
444 if(ALeffectslot *target{slot->Params.Target})
446 auto iter = std::copy(std::begin(target->ChanMap), std::end(target->ChanMap),
447 std::begin(params.AmbiMap));
448 std::fill(iter, std::end(params.AmbiMap), BFChannelConfig{});
449 params.Buffer = target->WetBuffer;
450 params.NumChannels = target->NumChannels;
452 output = EffectTarget{&params, &params, nullptr};
454 else
456 ALCdevice *device{context->Device};
457 output = EffectTarget{&device->Dry, &device->FOAOut, &device->RealOut};
459 state->update(context, slot, &slot->Params.EffectProps, output);
460 return true;
464 constexpr ChanMap MonoMap[1]{
465 { FrontCenter, 0.0f, 0.0f }
466 }, RearMap[2]{
467 { BackLeft, Deg2Rad(-150.0f), Deg2Rad(0.0f) },
468 { BackRight, Deg2Rad( 150.0f), Deg2Rad(0.0f) }
469 }, QuadMap[4]{
470 { FrontLeft, Deg2Rad( -45.0f), Deg2Rad(0.0f) },
471 { FrontRight, Deg2Rad( 45.0f), Deg2Rad(0.0f) },
472 { BackLeft, Deg2Rad(-135.0f), Deg2Rad(0.0f) },
473 { BackRight, Deg2Rad( 135.0f), Deg2Rad(0.0f) }
474 }, X51Map[6]{
475 { FrontLeft, Deg2Rad( -30.0f), Deg2Rad(0.0f) },
476 { FrontRight, Deg2Rad( 30.0f), Deg2Rad(0.0f) },
477 { FrontCenter, Deg2Rad( 0.0f), Deg2Rad(0.0f) },
478 { LFE, 0.0f, 0.0f },
479 { SideLeft, Deg2Rad(-110.0f), Deg2Rad(0.0f) },
480 { SideRight, Deg2Rad( 110.0f), Deg2Rad(0.0f) }
481 }, X61Map[7]{
482 { FrontLeft, Deg2Rad(-30.0f), Deg2Rad(0.0f) },
483 { FrontRight, Deg2Rad( 30.0f), Deg2Rad(0.0f) },
484 { FrontCenter, Deg2Rad( 0.0f), Deg2Rad(0.0f) },
485 { LFE, 0.0f, 0.0f },
486 { BackCenter, Deg2Rad(180.0f), Deg2Rad(0.0f) },
487 { SideLeft, Deg2Rad(-90.0f), Deg2Rad(0.0f) },
488 { SideRight, Deg2Rad( 90.0f), Deg2Rad(0.0f) }
489 }, X71Map[8]{
490 { FrontLeft, Deg2Rad( -30.0f), Deg2Rad(0.0f) },
491 { FrontRight, Deg2Rad( 30.0f), Deg2Rad(0.0f) },
492 { FrontCenter, Deg2Rad( 0.0f), Deg2Rad(0.0f) },
493 { LFE, 0.0f, 0.0f },
494 { BackLeft, Deg2Rad(-150.0f), Deg2Rad(0.0f) },
495 { BackRight, Deg2Rad( 150.0f), Deg2Rad(0.0f) },
496 { SideLeft, Deg2Rad( -90.0f), Deg2Rad(0.0f) },
497 { SideRight, Deg2Rad( 90.0f), Deg2Rad(0.0f) }
500 void CalcPanningAndFilters(ALvoice *voice, const ALfloat Azi, const ALfloat Elev,
501 const ALfloat Distance, const ALfloat Spread,
502 const ALfloat DryGain, const ALfloat DryGainHF,
503 const ALfloat DryGainLF, const ALfloat *WetGain,
504 const ALfloat *WetGainLF, const ALfloat *WetGainHF,
505 ALeffectslot **SendSlots, const ALbuffer *Buffer,
506 const ALvoicePropsBase *props, const ALlistener &Listener,
507 const ALCdevice *Device)
509 ChanMap StereoMap[2]{
510 { FrontLeft, Deg2Rad(-30.0f), Deg2Rad(0.0f) },
511 { FrontRight, Deg2Rad( 30.0f), Deg2Rad(0.0f) }
514 bool DirectChannels{props->DirectChannels != AL_FALSE};
515 const ChanMap *chans{nullptr};
516 ALsizei num_channels{0};
517 bool isbformat{false};
518 ALfloat downmix_gain{1.0f};
519 switch(Buffer->mFmtChannels)
521 case FmtMono:
522 chans = MonoMap;
523 num_channels = 1;
524 /* Mono buffers are never played direct. */
525 DirectChannels = false;
526 break;
528 case FmtStereo:
529 /* Convert counter-clockwise to clockwise. */
530 StereoMap[0].angle = -props->StereoPan[0];
531 StereoMap[1].angle = -props->StereoPan[1];
533 chans = StereoMap;
534 num_channels = 2;
535 downmix_gain = 1.0f / 2.0f;
536 break;
538 case FmtRear:
539 chans = RearMap;
540 num_channels = 2;
541 downmix_gain = 1.0f / 2.0f;
542 break;
544 case FmtQuad:
545 chans = QuadMap;
546 num_channels = 4;
547 downmix_gain = 1.0f / 4.0f;
548 break;
550 case FmtX51:
551 chans = X51Map;
552 num_channels = 6;
553 /* NOTE: Excludes LFE. */
554 downmix_gain = 1.0f / 5.0f;
555 break;
557 case FmtX61:
558 chans = X61Map;
559 num_channels = 7;
560 /* NOTE: Excludes LFE. */
561 downmix_gain = 1.0f / 6.0f;
562 break;
564 case FmtX71:
565 chans = X71Map;
566 num_channels = 8;
567 /* NOTE: Excludes LFE. */
568 downmix_gain = 1.0f / 7.0f;
569 break;
571 case FmtBFormat2D:
572 num_channels = 3;
573 isbformat = true;
574 DirectChannels = false;
575 break;
577 case FmtBFormat3D:
578 num_channels = 4;
579 isbformat = true;
580 DirectChannels = false;
581 break;
583 ASSUME(num_channels > 0);
585 std::for_each(std::begin(voice->Direct.Params), std::begin(voice->Direct.Params)+num_channels,
586 [](DirectParams &params) -> void
588 params.Hrtf.Target = HrtfParams{};
589 ClearArray(params.Gains.Target);
592 const ALsizei NumSends{Device->NumAuxSends};
593 ASSUME(NumSends >= 0);
594 std::for_each(voice->Send+0, voice->Send+NumSends,
595 [num_channels](ALvoice::SendData &send) -> void
597 std::for_each(std::begin(send.Params), std::begin(send.Params)+num_channels,
598 [](SendParams &params) -> void { ClearArray(params.Gains.Target); }
603 voice->Flags &= ~(VOICE_HAS_HRTF | VOICE_HAS_NFC);
604 if(isbformat)
606 /* Special handling for B-Format sources. */
608 if(Distance > std::numeric_limits<float>::epsilon())
610 /* Panning a B-Format sound toward some direction is easy. Just pan
611 * the first (W) channel as a normal mono sound and silence the
612 * others.
615 if(Device->AvgSpeakerDist > 0.0f)
617 /* Clamp the distance for really close sources, to prevent
618 * excessive bass.
620 const ALfloat mdist{maxf(Distance*Listener.Params.MetersPerUnit,
621 Device->AvgSpeakerDist/4.0f)};
622 const ALfloat w0{SPEEDOFSOUNDMETRESPERSEC /
623 (mdist * static_cast<ALfloat>(Device->Frequency))};
625 /* Only need to adjust the first channel of a B-Format source. */
626 voice->Direct.Params[0].NFCtrlFilter.adjust(w0);
628 std::copy(std::begin(Device->NumChannelsPerOrder),
629 std::end(Device->NumChannelsPerOrder),
630 std::begin(voice->Direct.ChannelsPerOrder));
631 voice->Flags |= VOICE_HAS_NFC;
634 /* Always render B-Format sources to the FOA output, to ensure
635 * smooth changes if it switches between panned and unpanned.
637 voice->Direct.Buffer = Device->FOAOut.Buffer;
638 voice->Direct.Channels = Device->FOAOut.NumChannels;
640 /* A scalar of 1.5 for plain stereo results in +/-60 degrees being
641 * moved to +/-90 degrees for direct right and left speaker
642 * responses.
644 ALfloat coeffs[MAX_AMBI_COEFFS];
645 CalcAngleCoeffs((Device->mRenderMode==StereoPair) ? ScaleAzimuthFront(Azi, 1.5f) : Azi,
646 Elev, Spread, coeffs);
648 /* NOTE: W needs to be scaled due to FuMa normalization. */
649 const ALfloat &scale0 = AmbiScale::FromFuMa[0];
650 ComputePanGains(&Device->FOAOut, coeffs, DryGain*scale0,
651 voice->Direct.Params[0].Gains.Target);
652 for(ALsizei i{0};i < NumSends;i++)
654 if(const ALeffectslot *Slot{SendSlots[i]})
655 ComputePanningGainsBF(Slot->ChanMap, Slot->NumChannels, coeffs,
656 WetGain[i]*scale0, voice->Send[i].Params[0].Gains.Target);
659 else
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 voice->Direct.Params[0].NFCtrlFilter.adjust(0.0f);
669 voice->Direct.ChannelsPerOrder[0] = 1;
670 voice->Direct.ChannelsPerOrder[1] = mini(voice->Direct.Channels-1, 3);
671 std::fill(std::begin(voice->Direct.ChannelsPerOrder)+2,
672 std::end(voice->Direct.ChannelsPerOrder), 0);
673 voice->Flags |= VOICE_HAS_NFC;
676 /* Local B-Format sources have their XYZ channels rotated according
677 * to the orientation.
679 /* AT then UP */
680 alu::Vector N{props->OrientAt[0], props->OrientAt[1], props->OrientAt[2], 0.0f};
681 N.normalize();
682 alu::Vector V{props->OrientUp[0], props->OrientUp[1], props->OrientUp[2], 0.0f};
683 V.normalize();
684 if(!props->HeadRelative)
686 N = Listener.Params.Matrix * N;
687 V = Listener.Params.Matrix * V;
689 /* Build and normalize right-vector */
690 alu::Vector U{aluCrossproduct(N, V)};
691 U.normalize();
693 /* Build a rotate + conversion matrix (FuMa -> ACN+N3D). NOTE: This
694 * matrix is transposed, for the inputs to align on the rows and
695 * outputs on the columns.
697 const ALfloat &scale0 = AmbiScale::FromFuMa[0];
698 const ALfloat &scale1 = AmbiScale::FromFuMa[1];
699 const ALfloat &scale2 = AmbiScale::FromFuMa[2];
700 const ALfloat &scale3 = AmbiScale::FromFuMa[3];
701 const alu::Matrix matrix{
702 // ACN0 ACN1 ACN2 ACN3
703 scale0, 0.0f, 0.0f, 0.0f, // Ambi W
704 0.0f, -N[0]*scale1, N[1]*scale2, -N[2]*scale3, // Ambi X
705 0.0f, U[0]*scale1, -U[1]*scale2, U[2]*scale3, // Ambi Y
706 0.0f, -V[0]*scale1, V[1]*scale2, -V[2]*scale3 // Ambi Z
709 voice->Direct.Buffer = Device->FOAOut.Buffer;
710 voice->Direct.Channels = Device->FOAOut.NumChannels;
711 for(ALsizei c{0};c < num_channels;c++)
712 ComputePanGains(&Device->FOAOut, matrix[c].data(), DryGain,
713 voice->Direct.Params[c].Gains.Target);
714 for(ALsizei i{0};i < NumSends;i++)
716 if(const ALeffectslot *Slot{SendSlots[i]})
717 for(ALsizei c{0};c < num_channels;c++)
718 ComputePanningGainsBF(Slot->ChanMap, Slot->NumChannels, matrix[c].data(),
719 WetGain[i], voice->Send[i].Params[c].Gains.Target
724 else if(DirectChannels)
726 /* Direct source channels always play local. Skip the virtual channels
727 * and write inputs to the matching real outputs.
729 voice->Direct.Buffer = Device->RealOut.Buffer;
730 voice->Direct.Channels = Device->RealOut.NumChannels;
732 for(ALsizei c{0};c < num_channels;c++)
734 int idx{GetChannelIdxByName(Device->RealOut, chans[c].channel)};
735 if(idx != -1) voice->Direct.Params[c].Gains.Target[idx] = DryGain;
738 /* Auxiliary sends still use normal channel panning since they mix to
739 * B-Format, which can't channel-match.
741 for(ALsizei c{0};c < num_channels;c++)
743 ALfloat coeffs[MAX_AMBI_COEFFS];
744 CalcAngleCoeffs(chans[c].angle, chans[c].elevation, 0.0f, coeffs);
746 for(ALsizei i{0};i < NumSends;i++)
748 if(const ALeffectslot *Slot{SendSlots[i]})
749 ComputePanningGainsBF(Slot->ChanMap, Slot->NumChannels, coeffs,
750 WetGain[i], voice->Send[i].Params[c].Gains.Target
755 else if(Device->mRenderMode == HrtfRender)
757 /* Full HRTF rendering. Skip the virtual channels and render to the
758 * real outputs.
760 voice->Direct.Buffer = Device->RealOut.Buffer;
761 voice->Direct.Channels = Device->RealOut.NumChannels;
763 if(Distance > std::numeric_limits<float>::epsilon())
765 /* Get the HRIR coefficients and delays just once, for the given
766 * source direction.
768 GetHrtfCoeffs(Device->mHrtf, Elev, Azi, Spread,
769 voice->Direct.Params[0].Hrtf.Target.Coeffs,
770 voice->Direct.Params[0].Hrtf.Target.Delay);
771 voice->Direct.Params[0].Hrtf.Target.Gain = DryGain * downmix_gain;
773 /* Remaining channels use the same results as the first. */
774 for(ALsizei c{1};c < num_channels;c++)
776 /* Skip LFE */
777 if(chans[c].channel != LFE)
778 voice->Direct.Params[c].Hrtf.Target = voice->Direct.Params[0].Hrtf.Target;
781 /* Calculate the directional coefficients once, which apply to all
782 * input channels of the source sends.
784 ALfloat coeffs[MAX_AMBI_COEFFS];
785 CalcAngleCoeffs(Azi, Elev, Spread, coeffs);
787 for(ALsizei i{0};i < NumSends;i++)
789 if(const ALeffectslot *Slot{SendSlots[i]})
790 for(ALsizei c{0};c < num_channels;c++)
792 /* Skip LFE */
793 if(chans[c].channel != LFE)
794 ComputePanningGainsBF(Slot->ChanMap, Slot->NumChannels, coeffs,
795 WetGain[i]*downmix_gain, voice->Send[i].Params[c].Gains.Target
800 else
802 /* Local sources on HRTF play with each channel panned to its
803 * relative location around the listener, providing "virtual
804 * speaker" responses.
806 for(ALsizei c{0};c < num_channels;c++)
808 /* Skip LFE */
809 if(chans[c].channel == LFE)
810 continue;
812 /* Get the HRIR coefficients and delays for this channel
813 * position.
815 GetHrtfCoeffs(Device->mHrtf, chans[c].elevation, chans[c].angle, Spread,
816 voice->Direct.Params[c].Hrtf.Target.Coeffs,
817 voice->Direct.Params[c].Hrtf.Target.Delay
819 voice->Direct.Params[c].Hrtf.Target.Gain = DryGain;
821 /* Normal panning for auxiliary sends. */
822 ALfloat coeffs[MAX_AMBI_COEFFS];
823 CalcAngleCoeffs(chans[c].angle, chans[c].elevation, Spread, coeffs);
825 for(ALsizei i{0};i < NumSends;i++)
827 if(const ALeffectslot *Slot{SendSlots[i]})
828 ComputePanningGainsBF(Slot->ChanMap, Slot->NumChannels, coeffs,
829 WetGain[i], voice->Send[i].Params[c].Gains.Target
835 voice->Flags |= VOICE_HAS_HRTF;
837 else
839 /* Non-HRTF rendering. Use normal panning to the output. */
841 if(Distance > std::numeric_limits<float>::epsilon())
843 /* Calculate NFC filter coefficient if needed. */
844 if(Device->AvgSpeakerDist > 0.0f)
846 /* Clamp the distance for really close sources, to prevent
847 * excessive bass.
849 const ALfloat mdist{maxf(Distance*Listener.Params.MetersPerUnit,
850 Device->AvgSpeakerDist/4.0f)};
851 const ALfloat w0{SPEEDOFSOUNDMETRESPERSEC /
852 (mdist * static_cast<ALfloat>(Device->Frequency))};
854 /* Adjust NFC filters. */
855 for(ALsizei c{0};c < num_channels;c++)
856 voice->Direct.Params[c].NFCtrlFilter.adjust(w0);
858 std::copy(std::begin(Device->NumChannelsPerOrder),
859 std::end(Device->NumChannelsPerOrder),
860 std::begin(voice->Direct.ChannelsPerOrder));
861 voice->Flags |= VOICE_HAS_NFC;
864 /* Calculate the directional coefficients once, which apply to all
865 * input channels.
867 ALfloat coeffs[MAX_AMBI_COEFFS];
868 CalcAngleCoeffs((Device->mRenderMode==StereoPair) ? ScaleAzimuthFront(Azi, 1.5f) : Azi,
869 Elev, Spread, coeffs);
871 for(ALsizei c{0};c < num_channels;c++)
873 /* Special-case LFE */
874 if(chans[c].channel == LFE)
876 if(Device->Dry.Buffer == Device->RealOut.Buffer)
878 int idx = GetChannelIdxByName(Device->RealOut, chans[c].channel);
879 if(idx != -1) voice->Direct.Params[c].Gains.Target[idx] = DryGain;
881 continue;
884 ComputePanGains(&Device->Dry, coeffs, DryGain * downmix_gain,
885 voice->Direct.Params[c].Gains.Target);
888 for(ALsizei i{0};i < NumSends;i++)
890 if(const ALeffectslot *Slot{SendSlots[i]})
891 for(ALsizei c{0};c < num_channels;c++)
893 /* Skip LFE */
894 if(chans[c].channel != LFE)
895 ComputePanningGainsBF(Slot->ChanMap, Slot->NumChannels, coeffs,
896 WetGain[i]*downmix_gain, voice->Send[i].Params[c].Gains.Target
901 else
903 if(Device->AvgSpeakerDist > 0.0f)
905 /* If the source distance is 0, set w0 to w1 to act as a pass-
906 * through. We still want to pass the signal through the
907 * filters so they keep an appropriate history, in case the
908 * source moves away from the listener.
910 const ALfloat w0{SPEEDOFSOUNDMETRESPERSEC /
911 (Device->AvgSpeakerDist * static_cast<ALfloat>(Device->Frequency))};
913 for(ALsizei c{0};c < num_channels;c++)
914 voice->Direct.Params[c].NFCtrlFilter.adjust(w0);
916 std::copy(std::begin(Device->NumChannelsPerOrder),
917 std::end(Device->NumChannelsPerOrder),
918 std::begin(voice->Direct.ChannelsPerOrder));
919 voice->Flags |= VOICE_HAS_NFC;
922 for(ALsizei c{0};c < num_channels;c++)
924 /* Special-case LFE */
925 if(chans[c].channel == LFE)
927 if(Device->Dry.Buffer == Device->RealOut.Buffer)
929 int idx = GetChannelIdxByName(Device->RealOut, chans[c].channel);
930 if(idx != -1) voice->Direct.Params[c].Gains.Target[idx] = DryGain;
932 continue;
935 ALfloat coeffs[MAX_AMBI_COEFFS];
936 CalcAngleCoeffs(
937 (Device->mRenderMode==StereoPair) ? ScaleAzimuthFront(chans[c].angle, 3.0f)
938 : chans[c].angle,
939 chans[c].elevation, Spread, coeffs
942 ComputePanGains(&Device->Dry, coeffs, DryGain,
943 voice->Direct.Params[c].Gains.Target);
944 for(ALsizei i{0};i < NumSends;i++)
946 if(const ALeffectslot *Slot{SendSlots[i]})
947 ComputePanningGainsBF(Slot->ChanMap, Slot->NumChannels,
948 coeffs, WetGain[i], voice->Send[i].Params[c].Gains.Target
955 const auto Frequency = static_cast<ALfloat>(Device->Frequency);
957 const ALfloat hfScale{props->Direct.HFReference / Frequency};
958 const ALfloat lfScale{props->Direct.LFReference / Frequency};
959 const ALfloat gainHF{maxf(DryGainHF, 0.001f)}; /* Limit -60dB */
960 const ALfloat gainLF{maxf(DryGainLF, 0.001f)};
962 voice->Direct.FilterType = AF_None;
963 if(gainHF != 1.0f) voice->Direct.FilterType |= AF_LowPass;
964 if(gainLF != 1.0f) voice->Direct.FilterType |= AF_HighPass;
965 voice->Direct.Params[0].LowPass.setParams(BiquadType::HighShelf,
966 gainHF, hfScale, calc_rcpQ_from_slope(gainHF, 1.0f)
968 voice->Direct.Params[0].HighPass.setParams(BiquadType::LowShelf,
969 gainLF, lfScale, calc_rcpQ_from_slope(gainLF, 1.0f)
971 for(ALsizei c{1};c < num_channels;c++)
973 voice->Direct.Params[c].LowPass.copyParamsFrom(voice->Direct.Params[0].LowPass);
974 voice->Direct.Params[c].HighPass.copyParamsFrom(voice->Direct.Params[0].HighPass);
977 for(ALsizei i{0};i < NumSends;i++)
979 const ALfloat hfScale{props->Send[i].HFReference / Frequency};
980 const ALfloat lfScale{props->Send[i].LFReference / Frequency};
981 const ALfloat gainHF{maxf(WetGainHF[i], 0.001f)};
982 const ALfloat gainLF{maxf(WetGainLF[i], 0.001f)};
984 voice->Send[i].FilterType = AF_None;
985 if(gainHF != 1.0f) voice->Send[i].FilterType |= AF_LowPass;
986 if(gainLF != 1.0f) voice->Send[i].FilterType |= AF_HighPass;
987 voice->Send[i].Params[0].LowPass.setParams(BiquadType::HighShelf,
988 gainHF, hfScale, calc_rcpQ_from_slope(gainHF, 1.0f)
990 voice->Send[i].Params[0].HighPass.setParams(BiquadType::LowShelf,
991 gainLF, lfScale, calc_rcpQ_from_slope(gainLF, 1.0f)
993 for(ALsizei c{1};c < num_channels;c++)
995 voice->Send[i].Params[c].LowPass.copyParamsFrom(voice->Send[i].Params[0].LowPass);
996 voice->Send[i].Params[c].HighPass.copyParamsFrom(voice->Send[i].Params[0].HighPass);
1001 void CalcNonAttnSourceParams(ALvoice *voice, const ALvoicePropsBase *props, const ALbuffer *ALBuffer, const ALCcontext *ALContext)
1003 const ALCdevice *Device{ALContext->Device};
1004 ALeffectslot *SendSlots[MAX_SENDS];
1006 voice->Direct.Buffer = Device->Dry.Buffer;
1007 voice->Direct.Channels = Device->Dry.NumChannels;
1008 for(ALsizei i{0};i < Device->NumAuxSends;i++)
1010 SendSlots[i] = props->Send[i].Slot;
1011 if(!SendSlots[i] && i == 0)
1012 SendSlots[i] = ALContext->DefaultSlot.get();
1013 if(!SendSlots[i] || SendSlots[i]->Params.EffectType == AL_EFFECT_NULL)
1015 SendSlots[i] = nullptr;
1016 voice->Send[i].Buffer = nullptr;
1017 voice->Send[i].Channels = 0;
1019 else
1021 voice->Send[i].Buffer = SendSlots[i]->WetBuffer;
1022 voice->Send[i].Channels = SendSlots[i]->NumChannels;
1026 /* Calculate the stepping value */
1027 const auto Pitch = static_cast<ALfloat>(ALBuffer->Frequency) /
1028 static_cast<ALfloat>(Device->Frequency) * props->Pitch;
1029 if(Pitch > static_cast<ALfloat>(MAX_PITCH))
1030 voice->Step = MAX_PITCH<<FRACTIONBITS;
1031 else
1032 voice->Step = maxi(fastf2i(Pitch * FRACTIONONE), 1);
1033 if(props->mResampler == BSinc24Resampler)
1034 BsincPrepare(voice->Step, &voice->ResampleState.bsinc, &bsinc24);
1035 else if(props->mResampler == BSinc12Resampler)
1036 BsincPrepare(voice->Step, &voice->ResampleState.bsinc, &bsinc12);
1037 voice->Resampler = SelectResampler(props->mResampler);
1039 /* Calculate gains */
1040 const ALlistener &Listener = ALContext->Listener;
1041 ALfloat DryGain{clampf(props->Gain, props->MinGain, props->MaxGain)};
1042 DryGain *= props->Direct.Gain * Listener.Params.Gain;
1043 DryGain = minf(DryGain, GAIN_MIX_MAX);
1044 ALfloat DryGainHF{props->Direct.GainHF};
1045 ALfloat DryGainLF{props->Direct.GainLF};
1046 ALfloat WetGain[MAX_SENDS], WetGainHF[MAX_SENDS], WetGainLF[MAX_SENDS];
1047 for(ALsizei i{0};i < Device->NumAuxSends;i++)
1049 WetGain[i] = clampf(props->Gain, props->MinGain, props->MaxGain);
1050 WetGain[i] *= props->Send[i].Gain * Listener.Params.Gain;
1051 WetGain[i] = minf(WetGain[i], GAIN_MIX_MAX);
1052 WetGainHF[i] = props->Send[i].GainHF;
1053 WetGainLF[i] = props->Send[i].GainLF;
1056 CalcPanningAndFilters(voice, 0.0f, 0.0f, 0.0f, 0.0f, DryGain, DryGainHF, DryGainLF, WetGain,
1057 WetGainLF, WetGainHF, SendSlots, ALBuffer, props, Listener, Device);
1060 void CalcAttnSourceParams(ALvoice *voice, const ALvoicePropsBase *props, const ALbuffer *ALBuffer, const ALCcontext *ALContext)
1062 const ALCdevice *Device{ALContext->Device};
1063 const ALsizei NumSends{Device->NumAuxSends};
1064 const ALlistener &Listener = ALContext->Listener;
1066 /* Set mixing buffers and get send parameters. */
1067 voice->Direct.Buffer = Device->Dry.Buffer;
1068 voice->Direct.Channels = Device->Dry.NumChannels;
1069 ALeffectslot *SendSlots[MAX_SENDS];
1070 ALfloat RoomRolloff[MAX_SENDS];
1071 ALfloat DecayDistance[MAX_SENDS];
1072 ALfloat DecayLFDistance[MAX_SENDS];
1073 ALfloat DecayHFDistance[MAX_SENDS];
1074 for(ALsizei i{0};i < NumSends;i++)
1076 SendSlots[i] = props->Send[i].Slot;
1077 if(!SendSlots[i] && i == 0)
1078 SendSlots[i] = ALContext->DefaultSlot.get();
1079 if(!SendSlots[i] || SendSlots[i]->Params.EffectType == AL_EFFECT_NULL)
1081 SendSlots[i] = nullptr;
1082 RoomRolloff[i] = 0.0f;
1083 DecayDistance[i] = 0.0f;
1084 DecayLFDistance[i] = 0.0f;
1085 DecayHFDistance[i] = 0.0f;
1087 else if(SendSlots[i]->Params.AuxSendAuto)
1089 RoomRolloff[i] = SendSlots[i]->Params.RoomRolloff + props->RoomRolloffFactor;
1090 /* Calculate the distances to where this effect's decay reaches
1091 * -60dB.
1093 DecayDistance[i] = SendSlots[i]->Params.DecayTime *
1094 Listener.Params.ReverbSpeedOfSound;
1095 DecayLFDistance[i] = DecayDistance[i] * SendSlots[i]->Params.DecayLFRatio;
1096 DecayHFDistance[i] = DecayDistance[i] * SendSlots[i]->Params.DecayHFRatio;
1097 if(SendSlots[i]->Params.DecayHFLimit)
1099 ALfloat airAbsorption = SendSlots[i]->Params.AirAbsorptionGainHF;
1100 if(airAbsorption < 1.0f)
1102 /* Calculate the distance to where this effect's air
1103 * absorption reaches -60dB, and limit the effect's HF
1104 * decay distance (so it doesn't take any longer to decay
1105 * than the air would allow).
1107 ALfloat absorb_dist = log10f(REVERB_DECAY_GAIN) / log10f(airAbsorption);
1108 DecayHFDistance[i] = minf(absorb_dist, DecayHFDistance[i]);
1112 else
1114 /* If the slot's auxiliary send auto is off, the data sent to the
1115 * effect slot is the same as the dry path, sans filter effects */
1116 RoomRolloff[i] = props->RolloffFactor;
1117 DecayDistance[i] = 0.0f;
1118 DecayLFDistance[i] = 0.0f;
1119 DecayHFDistance[i] = 0.0f;
1122 if(!SendSlots[i])
1124 voice->Send[i].Buffer = nullptr;
1125 voice->Send[i].Channels = 0;
1127 else
1129 voice->Send[i].Buffer = SendSlots[i]->WetBuffer;
1130 voice->Send[i].Channels = SendSlots[i]->NumChannels;
1134 /* Transform source to listener space (convert to head relative) */
1135 alu::Vector Position{props->Position[0], props->Position[1], props->Position[2], 1.0f};
1136 alu::Vector Velocity{props->Velocity[0], props->Velocity[1], props->Velocity[2], 0.0f};
1137 alu::Vector Direction{props->Direction[0], props->Direction[1], props->Direction[2], 0.0f};
1138 if(props->HeadRelative == AL_FALSE)
1140 /* Transform source vectors */
1141 Position = Listener.Params.Matrix * Position;
1142 Velocity = Listener.Params.Matrix * Velocity;
1143 Direction = Listener.Params.Matrix * Direction;
1145 else
1147 /* Offset the source velocity to be relative of the listener velocity */
1148 Velocity += Listener.Params.Velocity;
1151 const bool directional{Direction.normalize() > 0.0f};
1152 alu::Vector SourceToListener{-Position[0], -Position[1], -Position[2], 0.0f};
1153 const ALfloat Distance{SourceToListener.normalize()};
1155 /* Initial source gain */
1156 ALfloat DryGain{props->Gain};
1157 ALfloat DryGainHF{1.0f};
1158 ALfloat DryGainLF{1.0f};
1159 ALfloat WetGain[MAX_SENDS], WetGainHF[MAX_SENDS], WetGainLF[MAX_SENDS];
1160 for(ALsizei i{0};i < NumSends;i++)
1162 WetGain[i] = props->Gain;
1163 WetGainHF[i] = 1.0f;
1164 WetGainLF[i] = 1.0f;
1167 /* Calculate distance attenuation */
1168 ALfloat ClampedDist{Distance};
1170 switch(Listener.Params.SourceDistanceModel ?
1171 props->mDistanceModel : Listener.Params.mDistanceModel)
1173 case DistanceModel::InverseClamped:
1174 ClampedDist = clampf(ClampedDist, props->RefDistance, props->MaxDistance);
1175 if(props->MaxDistance < props->RefDistance) break;
1176 /*fall-through*/
1177 case DistanceModel::Inverse:
1178 if(!(props->RefDistance > 0.0f))
1179 ClampedDist = props->RefDistance;
1180 else
1182 ALfloat dist = lerp(props->RefDistance, ClampedDist, props->RolloffFactor);
1183 if(dist > 0.0f) DryGain *= props->RefDistance / dist;
1184 for(ALsizei i{0};i < NumSends;i++)
1186 dist = lerp(props->RefDistance, ClampedDist, RoomRolloff[i]);
1187 if(dist > 0.0f) WetGain[i] *= props->RefDistance / dist;
1190 break;
1192 case DistanceModel::LinearClamped:
1193 ClampedDist = clampf(ClampedDist, props->RefDistance, props->MaxDistance);
1194 if(props->MaxDistance < props->RefDistance) break;
1195 /*fall-through*/
1196 case DistanceModel::Linear:
1197 if(!(props->MaxDistance != props->RefDistance))
1198 ClampedDist = props->RefDistance;
1199 else
1201 ALfloat attn = props->RolloffFactor * (ClampedDist-props->RefDistance) /
1202 (props->MaxDistance-props->RefDistance);
1203 DryGain *= maxf(1.0f - attn, 0.0f);
1204 for(ALsizei i{0};i < NumSends;i++)
1206 attn = RoomRolloff[i] * (ClampedDist-props->RefDistance) /
1207 (props->MaxDistance-props->RefDistance);
1208 WetGain[i] *= maxf(1.0f - attn, 0.0f);
1211 break;
1213 case DistanceModel::ExponentClamped:
1214 ClampedDist = clampf(ClampedDist, props->RefDistance, props->MaxDistance);
1215 if(props->MaxDistance < props->RefDistance) break;
1216 /*fall-through*/
1217 case DistanceModel::Exponent:
1218 if(!(ClampedDist > 0.0f && props->RefDistance > 0.0f))
1219 ClampedDist = props->RefDistance;
1220 else
1222 DryGain *= std::pow(ClampedDist/props->RefDistance, -props->RolloffFactor);
1223 for(ALsizei i{0};i < NumSends;i++)
1224 WetGain[i] *= std::pow(ClampedDist/props->RefDistance, -RoomRolloff[i]);
1226 break;
1228 case DistanceModel::Disable:
1229 ClampedDist = props->RefDistance;
1230 break;
1233 /* Calculate directional soundcones */
1234 if(directional && props->InnerAngle < 360.0f)
1236 const ALfloat Angle{Rad2Deg(std::acos(aluDotproduct(Direction, SourceToListener)) *
1237 ConeScale * 2.0f)};
1239 ALfloat ConeVolume, ConeHF;
1240 if(!(Angle > props->InnerAngle))
1242 ConeVolume = 1.0f;
1243 ConeHF = 1.0f;
1245 else if(Angle < props->OuterAngle)
1247 ALfloat scale = ( Angle-props->InnerAngle) /
1248 (props->OuterAngle-props->InnerAngle);
1249 ConeVolume = lerp(1.0f, props->OuterGain, scale);
1250 ConeHF = lerp(1.0f, props->OuterGainHF, scale);
1252 else
1254 ConeVolume = props->OuterGain;
1255 ConeHF = props->OuterGainHF;
1258 DryGain *= ConeVolume;
1259 if(props->DryGainHFAuto)
1260 DryGainHF *= ConeHF;
1261 if(props->WetGainAuto)
1262 std::transform(std::begin(WetGain), std::begin(WetGain)+NumSends, std::begin(WetGain),
1263 [ConeVolume](ALfloat gain) noexcept -> ALfloat { return gain * ConeVolume; }
1265 if(props->WetGainHFAuto)
1266 std::transform(std::begin(WetGainHF), std::begin(WetGainHF)+NumSends,
1267 std::begin(WetGainHF),
1268 [ConeHF](ALfloat gain) noexcept -> ALfloat { return gain * ConeHF; }
1272 /* Apply gain and frequency filters */
1273 DryGain = clampf(DryGain, props->MinGain, props->MaxGain);
1274 DryGain = minf(DryGain*props->Direct.Gain*Listener.Params.Gain, GAIN_MIX_MAX);
1275 DryGainHF *= props->Direct.GainHF;
1276 DryGainLF *= props->Direct.GainLF;
1277 for(ALsizei i{0};i < NumSends;i++)
1279 WetGain[i] = clampf(WetGain[i], props->MinGain, props->MaxGain);
1280 WetGain[i] = minf(WetGain[i]*props->Send[i].Gain*Listener.Params.Gain, GAIN_MIX_MAX);
1281 WetGainHF[i] *= props->Send[i].GainHF;
1282 WetGainLF[i] *= props->Send[i].GainLF;
1285 /* Distance-based air absorption and initial send decay. */
1286 if(ClampedDist > props->RefDistance && props->RolloffFactor > 0.0f)
1288 ALfloat meters_base{(ClampedDist-props->RefDistance) * props->RolloffFactor *
1289 Listener.Params.MetersPerUnit};
1290 if(props->AirAbsorptionFactor > 0.0f)
1292 ALfloat hfattn{std::pow(AIRABSORBGAINHF, meters_base * props->AirAbsorptionFactor)};
1293 DryGainHF *= hfattn;
1294 std::transform(std::begin(WetGainHF), std::begin(WetGainHF)+NumSends,
1295 std::begin(WetGainHF),
1296 [hfattn](ALfloat gain) noexcept -> ALfloat { return gain * hfattn; }
1300 if(props->WetGainAuto)
1302 /* Apply a decay-time transformation to the wet path, based on the
1303 * source distance in meters. The initial decay of the reverb
1304 * effect is calculated and applied to the wet path.
1306 for(ALsizei i{0};i < NumSends;i++)
1308 if(!(DecayDistance[i] > 0.0f))
1309 continue;
1311 const ALfloat gain{std::pow(REVERB_DECAY_GAIN, meters_base/DecayDistance[i])};
1312 WetGain[i] *= gain;
1313 /* Yes, the wet path's air absorption is applied with
1314 * WetGainAuto on, rather than WetGainHFAuto.
1316 if(gain > 0.0f)
1318 ALfloat gainhf{std::pow(REVERB_DECAY_GAIN, meters_base/DecayHFDistance[i])};
1319 WetGainHF[i] *= minf(gainhf / gain, 1.0f);
1320 ALfloat gainlf{std::pow(REVERB_DECAY_GAIN, meters_base/DecayLFDistance[i])};
1321 WetGainLF[i] *= minf(gainlf / gain, 1.0f);
1328 /* Initial source pitch */
1329 ALfloat Pitch{props->Pitch};
1331 /* Calculate velocity-based doppler effect */
1332 ALfloat DopplerFactor{props->DopplerFactor * Listener.Params.DopplerFactor};
1333 if(DopplerFactor > 0.0f)
1335 const alu::Vector &lvelocity = Listener.Params.Velocity;
1336 ALfloat vss{aluDotproduct(Velocity, SourceToListener) * DopplerFactor};
1337 ALfloat vls{aluDotproduct(lvelocity, SourceToListener) * DopplerFactor};
1339 const ALfloat SpeedOfSound{Listener.Params.SpeedOfSound};
1340 if(!(vls < SpeedOfSound))
1342 /* Listener moving away from the source at the speed of sound.
1343 * Sound waves can't catch it.
1345 Pitch = 0.0f;
1347 else if(!(vss < SpeedOfSound))
1349 /* Source moving toward the listener at the speed of sound. Sound
1350 * waves bunch up to extreme frequencies.
1352 Pitch = std::numeric_limits<float>::infinity();
1354 else
1356 /* Source and listener movement is nominal. Calculate the proper
1357 * doppler shift.
1359 Pitch *= (SpeedOfSound-vls) / (SpeedOfSound-vss);
1363 /* Adjust pitch based on the buffer and output frequencies, and calculate
1364 * fixed-point stepping value.
1366 Pitch *= static_cast<ALfloat>(ALBuffer->Frequency)/static_cast<ALfloat>(Device->Frequency);
1367 if(Pitch > static_cast<ALfloat>(MAX_PITCH))
1368 voice->Step = MAX_PITCH<<FRACTIONBITS;
1369 else
1370 voice->Step = maxi(fastf2i(Pitch * FRACTIONONE), 1);
1371 if(props->mResampler == BSinc24Resampler)
1372 BsincPrepare(voice->Step, &voice->ResampleState.bsinc, &bsinc24);
1373 else if(props->mResampler == BSinc12Resampler)
1374 BsincPrepare(voice->Step, &voice->ResampleState.bsinc, &bsinc12);
1375 voice->Resampler = SelectResampler(props->mResampler);
1377 ALfloat ev{0.0f}, az{0.0f};
1378 if(Distance > 0.0f)
1380 /* Clamp Y, in case rounding errors caused it to end up outside of
1381 * -1...+1.
1383 ev = std::asin(clampf(-SourceToListener[1], -1.0f, 1.0f));
1384 /* Double negation on Z cancels out; negate once for changing source-
1385 * to-listener to listener-to-source, and again for right-handed coords
1386 * with -Z in front.
1388 az = std::atan2(-SourceToListener[0], SourceToListener[2]*ZScale);
1391 ALfloat spread{0.0f};
1392 if(props->Radius > Distance)
1393 spread = al::MathDefs<float>::Tau() - Distance/props->Radius*al::MathDefs<float>::Pi();
1394 else if(Distance > 0.0f)
1395 spread = std::asin(props->Radius/Distance) * 2.0f;
1397 CalcPanningAndFilters(voice, az, ev, Distance, spread, DryGain, DryGainHF, DryGainLF, WetGain,
1398 WetGainLF, WetGainHF, SendSlots, ALBuffer, props, Listener, Device);
1401 void CalcSourceParams(ALvoice *voice, ALCcontext *context, bool force)
1403 ALvoiceProps *props{voice->Update.exchange(nullptr, std::memory_order_acq_rel)};
1404 if(!props && !force) return;
1406 if(props)
1408 voice->Props = *props;
1410 AtomicReplaceHead(context->FreeVoiceProps, props);
1413 ALbufferlistitem *BufferListItem{voice->current_buffer.load(std::memory_order_relaxed)};
1414 while(BufferListItem)
1416 auto buffers_end = BufferListItem->buffers+BufferListItem->num_buffers;
1417 auto buffer = std::find_if(BufferListItem->buffers, buffers_end,
1418 std::bind(std::not_equal_to<const ALbuffer*>{}, _1, nullptr));
1419 if(LIKELY(buffer != buffers_end))
1421 if(voice->Props.mSpatializeMode==SpatializeOn ||
1422 (voice->Props.mSpatializeMode==SpatializeAuto && (*buffer)->mFmtChannels==FmtMono))
1423 CalcAttnSourceParams(voice, &voice->Props, *buffer, context);
1424 else
1425 CalcNonAttnSourceParams(voice, &voice->Props, *buffer, context);
1426 break;
1428 BufferListItem = BufferListItem->next.load(std::memory_order_acquire);
1433 void ProcessParamUpdates(ALCcontext *ctx, const ALeffectslotArray *slots)
1435 IncrementRef(&ctx->UpdateCount);
1436 if(LIKELY(!ctx->HoldUpdates.load(std::memory_order_acquire)))
1438 bool cforce{CalcContextParams(ctx)};
1439 bool force{CalcListenerParams(ctx) || cforce};
1440 std::for_each(slots->slot, slots->slot+slots->count,
1441 [ctx,cforce,&force](ALeffectslot *slot) -> void
1442 { force |= CalcEffectSlotParams(slot, ctx, cforce); }
1445 std::for_each(ctx->Voices, ctx->Voices+ctx->VoiceCount.load(std::memory_order_acquire),
1446 [ctx,force](ALvoice *voice) -> void
1448 ALuint sid{voice->SourceID.load(std::memory_order_acquire)};
1449 if(sid) CalcSourceParams(voice, ctx, force);
1453 IncrementRef(&ctx->UpdateCount);
1456 void ProcessContext(ALCcontext *ctx, const ALsizei SamplesToDo)
1458 ASSUME(SamplesToDo > 0);
1460 const ALeffectslotArray *auxslots{ctx->ActiveAuxSlots.load(std::memory_order_acquire)};
1462 /* Process pending propery updates for objects on the context. */
1463 ProcessParamUpdates(ctx, auxslots);
1465 /* Clear auxiliary effect slot mixing buffers. */
1466 std::for_each(auxslots->slot, auxslots->slot+auxslots->count,
1467 [SamplesToDo](ALeffectslot *slot) -> void
1469 std::for_each(slot->WetBuffer, slot->WetBuffer+slot->NumChannels,
1470 [SamplesToDo](ALfloat *buffer) -> void
1471 { std::fill_n(buffer, SamplesToDo, 0.0f); }
1476 /* Process voices that have a playing source. */
1477 std::for_each(ctx->Voices, ctx->Voices+ctx->VoiceCount.load(std::memory_order_acquire),
1478 [SamplesToDo,ctx](ALvoice *voice) -> void
1480 if(!voice->Playing.load(std::memory_order_acquire)) return;
1481 ALuint sid{voice->SourceID.load(std::memory_order_relaxed)};
1482 if(!sid || voice->Step < 1) return;
1484 if(!MixSource(voice, sid, ctx, SamplesToDo))
1486 voice->SourceID.store(0u, std::memory_order_relaxed);
1487 voice->Playing.store(false, std::memory_order_release);
1488 SendSourceStoppedEvent(ctx, sid);
1493 /* Process effects. */
1494 if(auxslots->count < 1) return;
1495 auto slots = auxslots->slot;
1496 auto slots_end = slots + auxslots->count;
1498 /* First sort the slots into scratch storage, so that effects come before
1499 * their effect target (or their targets' target).
1501 auto sorted_slots = const_cast<ALeffectslot**>(slots_end);
1502 auto sorted_slots_end = sorted_slots;
1503 auto in_chain = [](const ALeffectslot *slot1, const ALeffectslot *slot2) noexcept -> bool
1505 while((slot1=slot1->Params.Target) != nullptr) {
1506 if(slot1 == slot2) return true;
1508 return false;
1511 *sorted_slots_end = *slots;
1512 ++sorted_slots_end;
1513 while(++slots != slots_end)
1515 /* If this effect slot targets an effect slot already in the list (i.e.
1516 * slots outputs to something in sorted_slots), directly or indirectly,
1517 * insert it prior to that element.
1519 auto checker = sorted_slots;
1520 do {
1521 if(in_chain(*slots, *checker)) break;
1522 } while(++checker != sorted_slots_end);
1524 checker = std::move_backward(checker, sorted_slots_end, sorted_slots_end+1);
1525 *--checker = *slots;
1526 ++sorted_slots_end;
1529 std::for_each(sorted_slots, sorted_slots_end,
1530 [SamplesToDo](const ALeffectslot *slot) -> void
1532 EffectState *state{slot->Params.mEffectState};
1533 state->process(SamplesToDo, slot->WetBuffer, state->mOutBuffer,
1534 state->mOutChannels);
1540 void ApplyStablizer(FrontStablizer *Stablizer, ALfloat (*RESTRICT Buffer)[BUFFERSIZE],
1541 int lidx, int ridx, int cidx, const ALsizei SamplesToDo,
1542 const ALsizei NumChannels)
1544 ASSUME(SamplesToDo > 0);
1545 ASSUME(NumChannels > 0);
1547 /* Apply an all-pass to all channels, except the front-left and front-
1548 * right, so they maintain the same relative phase.
1550 for(ALsizei i{0};i < NumChannels;i++)
1552 if(i == lidx || i == ridx)
1553 continue;
1554 Stablizer->APFilter[i].process(Buffer[i], SamplesToDo);
1557 ALfloat (&lsplit)[2][BUFFERSIZE] = Stablizer->LSplit;
1558 ALfloat (&rsplit)[2][BUFFERSIZE] = Stablizer->RSplit;
1559 Stablizer->LFilter.process(lsplit[1], lsplit[0], Buffer[lidx], SamplesToDo);
1560 Stablizer->RFilter.process(rsplit[1], rsplit[0], Buffer[ridx], SamplesToDo);
1562 for(ALsizei i{0};i < SamplesToDo;i++)
1564 ALfloat lfsum{lsplit[0][i] + rsplit[0][i]};
1565 ALfloat hfsum{lsplit[1][i] + rsplit[1][i]};
1566 ALfloat s{lsplit[0][i] + lsplit[1][i] - rsplit[0][i] - rsplit[1][i]};
1568 /* This pans the separate low- and high-frequency sums between being on
1569 * the center channel and the left/right channels. The low-frequency
1570 * sum is 1/3rd toward center (2/3rds on left/right) and the high-
1571 * frequency sum is 1/4th toward center (3/4ths on left/right). These
1572 * values can be tweaked.
1574 ALfloat m{lfsum*std::cos(1.0f/3.0f * (al::MathDefs<float>::Pi()*0.5f)) +
1575 hfsum*std::cos(1.0f/4.0f * (al::MathDefs<float>::Pi()*0.5f))};
1576 ALfloat c{lfsum*std::sin(1.0f/3.0f * (al::MathDefs<float>::Pi()*0.5f)) +
1577 hfsum*std::sin(1.0f/4.0f * (al::MathDefs<float>::Pi()*0.5f))};
1579 /* The generated center channel signal adds to the existing signal,
1580 * while the modified left and right channels replace.
1582 Buffer[lidx][i] = (m + s) * 0.5f;
1583 Buffer[ridx][i] = (m - s) * 0.5f;
1584 Buffer[cidx][i] += c * 0.5f;
1588 void ApplyDistanceComp(ALfloat (*Samples)[BUFFERSIZE], const DistanceComp &distcomp,
1589 ALfloat (&Values)[BUFFERSIZE], const ALsizei SamplesToDo, const ALsizei numchans)
1591 ASSUME(SamplesToDo > 0);
1592 ASSUME(numchans > 0);
1594 ALfloat *RESTRICT tempvals{al::assume_aligned<16>(&Values[0])};
1595 for(ALsizei c{0};c < numchans;c++)
1597 ALfloat *RESTRICT inout{al::assume_aligned<16>(Samples[c])};
1598 const ALfloat gain{distcomp[c].Gain};
1599 const ALsizei base{distcomp[c].Length};
1600 ALfloat *RESTRICT distbuf{al::assume_aligned<16>(distcomp[c].Buffer)};
1602 if(base <= 0)
1604 if(gain < 1.0f)
1605 std::transform(inout, inout+SamplesToDo, inout,
1606 [gain](const ALfloat in) noexcept -> ALfloat
1607 { return in * gain; }
1609 continue;
1612 if(LIKELY(SamplesToDo >= base))
1614 auto out = std::copy_n(distbuf, base, tempvals);
1615 std::copy_n(inout, SamplesToDo-base, out);
1616 std::copy_n(inout+SamplesToDo-base, base, distbuf);
1618 else
1620 std::copy_n(distbuf, SamplesToDo, tempvals);
1621 auto out = std::copy(distbuf+SamplesToDo, distbuf+base, distbuf);
1622 std::copy_n(inout, SamplesToDo, out);
1624 std::transform(tempvals, tempvals+SamplesToDo, inout,
1625 [gain](const ALfloat in) noexcept -> ALfloat { return in * gain; }
1630 void ApplyDither(ALfloat (*Samples)[BUFFERSIZE], ALuint *dither_seed, const ALfloat quant_scale,
1631 const ALsizei SamplesToDo, const ALsizei numchans)
1633 ASSUME(numchans > 0);
1635 /* Dithering. Generate whitenoise (uniform distribution of random values
1636 * between -1 and +1) and add it to the sample values, after scaling up to
1637 * the desired quantization depth amd before rounding.
1639 const ALfloat invscale{1.0f / quant_scale};
1640 ALuint seed{*dither_seed};
1641 auto dither_channel = [&seed,invscale,quant_scale,SamplesToDo](ALfloat *input) -> void
1643 ASSUME(SamplesToDo > 0);
1644 ALfloat *buffer{al::assume_aligned<16>(input)};
1645 std::transform(buffer, buffer+SamplesToDo, buffer,
1646 [&seed,invscale,quant_scale](ALfloat sample) noexcept -> ALfloat
1648 ALfloat val = sample * quant_scale;
1649 ALuint rng0 = dither_rng(&seed);
1650 ALuint rng1 = dither_rng(&seed);
1651 val += static_cast<ALfloat>(rng0*(1.0/UINT_MAX) - rng1*(1.0/UINT_MAX));
1652 return fast_roundf(val) * invscale;
1656 std::for_each(Samples, Samples+numchans, dither_channel);
1657 *dither_seed = seed;
1661 /* Base template left undefined. Should be marked =delete, but Clang 3.8.1
1662 * chokes on that given the inline specializations.
1664 template<typename T>
1665 inline T SampleConv(ALfloat) noexcept;
1667 template<> inline ALfloat SampleConv(ALfloat val) noexcept
1668 { return val; }
1669 template<> inline ALint SampleConv(ALfloat val) noexcept
1671 /* Floats have a 23-bit mantissa. There is an implied 1 bit in the mantissa
1672 * along with the sign bit, giving 25 bits total, so [-16777216, +16777216]
1673 * is the max value a normalized float can be scaled to before losing
1674 * precision.
1676 return fastf2i(clampf(val*16777216.0f, -16777216.0f, 16777215.0f))<<7;
1678 template<> inline ALshort SampleConv(ALfloat val) noexcept
1679 { return fastf2i(clampf(val*32768.0f, -32768.0f, 32767.0f)); }
1680 template<> inline ALbyte SampleConv(ALfloat val) noexcept
1681 { return fastf2i(clampf(val*128.0f, -128.0f, 127.0f)); }
1683 /* Define unsigned output variations. */
1684 template<> inline ALuint SampleConv(ALfloat val) noexcept
1685 { return SampleConv<ALint>(val) + 2147483648u; }
1686 template<> inline ALushort SampleConv(ALfloat val) noexcept
1687 { return SampleConv<ALshort>(val) + 32768; }
1688 template<> inline ALubyte SampleConv(ALfloat val) noexcept
1689 { return SampleConv<ALbyte>(val) + 128; }
1691 template<DevFmtType T>
1692 void Write(const ALfloat (*InBuffer)[BUFFERSIZE], ALvoid *OutBuffer, ALsizei Offset,
1693 ALsizei SamplesToDo, ALsizei numchans)
1695 using SampleType = typename DevFmtTypeTraits<T>::Type;
1697 ASSUME(numchans > 0);
1698 SampleType *outbase = static_cast<SampleType*>(OutBuffer) + Offset*numchans;
1699 auto conv_channel = [&outbase,SamplesToDo,numchans](const ALfloat *inbuf) -> void
1701 ASSUME(SamplesToDo > 0);
1702 SampleType *out{outbase++};
1703 std::for_each<const ALfloat*RESTRICT>(inbuf, inbuf+SamplesToDo,
1704 [numchans,&out](const ALfloat s) noexcept -> void
1706 *out = SampleConv<SampleType>(s);
1707 out += numchans;
1711 std::for_each(InBuffer, InBuffer+numchans, conv_channel);
1714 } // namespace
1716 void aluMixData(ALCdevice *device, ALvoid *OutBuffer, ALsizei NumSamples)
1718 FPUCtl mixer_mode{};
1719 for(ALsizei SamplesDone{0};SamplesDone < NumSamples;)
1721 const ALsizei SamplesToDo{mini(NumSamples-SamplesDone, BUFFERSIZE)};
1723 /* Clear main mixing buffers. */
1724 std::for_each(device->MixBuffer.begin(), device->MixBuffer.end(),
1725 [SamplesToDo](std::array<ALfloat,BUFFERSIZE> &buffer) -> void
1726 { std::fill_n(buffer.begin(), SamplesToDo, 0.0f); }
1729 /* Increment the mix count at the start (lsb should now be 1). */
1730 IncrementRef(&device->MixCount);
1732 /* For each context on this device, process and mix its sources and
1733 * effects.
1735 ALCcontext *ctx{device->ContextList.load(std::memory_order_acquire)};
1736 while(ctx)
1738 ProcessContext(ctx, SamplesToDo);
1740 ctx = ctx->next.load(std::memory_order_relaxed);
1743 /* Increment the clock time. Every second's worth of samples is
1744 * converted and added to clock base so that large sample counts don't
1745 * overflow during conversion. This also guarantees a stable
1746 * conversion.
1748 device->SamplesDone += SamplesToDo;
1749 device->ClockBase += std::chrono::seconds{device->SamplesDone / device->Frequency};
1750 device->SamplesDone %= device->Frequency;
1752 /* Increment the mix count at the end (lsb should now be 0). */
1753 IncrementRef(&device->MixCount);
1755 /* Apply any needed post-process for finalizing the Dry mix to the
1756 * RealOut (Ambisonic decode, UHJ encode, etc).
1758 if(LIKELY(device->PostProcess))
1759 device->PostProcess(device, SamplesToDo);
1761 /* Apply front image stablization for surround sound, if applicable. */
1762 if(device->Stablizer)
1764 const int lidx{GetChannelIdxByName(device->RealOut, FrontLeft)};
1765 const int ridx{GetChannelIdxByName(device->RealOut, FrontRight)};
1766 const int cidx{GetChannelIdxByName(device->RealOut, FrontCenter)};
1767 assert(lidx >= 0 && ridx >= 0 && cidx >= 0);
1769 ApplyStablizer(device->Stablizer.get(), device->RealOut.Buffer, lidx, ridx, cidx,
1770 SamplesToDo, device->RealOut.NumChannels);
1773 /* Apply compression, limiting sample amplitude if needed or desired. */
1774 if(Compressor *comp{device->Limiter.get()})
1775 comp->process(SamplesToDo, device->RealOut.Buffer);
1777 /* Apply delays and attenuation for mismatched speaker distances. */
1778 ApplyDistanceComp(device->RealOut.Buffer, device->ChannelDelay, device->TempBuffer[0],
1779 SamplesToDo, device->RealOut.NumChannels);
1781 /* Apply dithering. The compressor should have left enough headroom for
1782 * the dither noise to not saturate.
1784 if(device->DitherDepth > 0.0f)
1785 ApplyDither(device->RealOut.Buffer, &device->DitherSeed, device->DitherDepth,
1786 SamplesToDo, device->RealOut.NumChannels);
1788 if(LIKELY(OutBuffer))
1790 ALfloat (*Buffer)[BUFFERSIZE]{device->RealOut.Buffer};
1791 ALsizei Channels{device->RealOut.NumChannels};
1793 /* Finally, interleave and convert samples, writing to the device's
1794 * output buffer.
1796 switch(device->FmtType)
1798 #define HANDLE_WRITE(T) case T: \
1799 Write<T>(Buffer, OutBuffer, SamplesDone, SamplesToDo, Channels); break;
1800 HANDLE_WRITE(DevFmtByte)
1801 HANDLE_WRITE(DevFmtUByte)
1802 HANDLE_WRITE(DevFmtShort)
1803 HANDLE_WRITE(DevFmtUShort)
1804 HANDLE_WRITE(DevFmtInt)
1805 HANDLE_WRITE(DevFmtUInt)
1806 HANDLE_WRITE(DevFmtFloat)
1807 #undef HANDLE_WRITE
1811 SamplesDone += SamplesToDo;
1816 void aluHandleDisconnect(ALCdevice *device, const char *msg, ...)
1818 if(!device->Connected.exchange(false, std::memory_order_acq_rel))
1819 return;
1821 AsyncEvent evt{EventType_Disconnected};
1822 evt.u.user.type = AL_EVENT_TYPE_DISCONNECTED_SOFT;
1823 evt.u.user.id = 0;
1824 evt.u.user.param = 0;
1826 va_list args;
1827 va_start(args, msg);
1828 int msglen{vsnprintf(evt.u.user.msg, sizeof(evt.u.user.msg), msg, args)};
1829 va_end(args);
1831 if(msglen < 0 || static_cast<size_t>(msglen) >= sizeof(evt.u.user.msg))
1832 evt.u.user.msg[sizeof(evt.u.user.msg)-1] = 0;
1834 ALCcontext *ctx{device->ContextList.load()};
1835 while(ctx)
1837 const ALbitfieldSOFT enabledevt{ctx->EnabledEvts.load(std::memory_order_acquire)};
1838 if((enabledevt&EventType_Disconnected))
1840 RingBuffer *ring{ctx->AsyncEvents.get()};
1841 auto evt_data = ring->getWriteVector().first;
1842 if(evt_data.len > 0)
1844 new (evt_data.buf) AsyncEvent{evt};
1845 ring->writeAdvance(1);
1846 ctx->EventSem.post();
1850 std::for_each(ctx->Voices, ctx->Voices+ctx->VoiceCount.load(std::memory_order_acquire),
1851 [ctx](ALvoice *voice) -> void
1853 if(!voice->Playing.load(std::memory_order_acquire)) return;
1854 ALuint sid{voice->SourceID.load(std::memory_order_relaxed)};
1855 if(!sid) return;
1857 voice->SourceID.store(0u, std::memory_order_relaxed);
1858 voice->Playing.store(false, std::memory_order_release);
1859 /* If the source's voice was playing, it's now effectively
1860 * stopped (the source state will be updated the next time it's
1861 * checked).
1863 SendSourceStoppedEvent(ctx, sid);
1867 ctx = ctx->next.load(std::memory_order_relaxed);