Avoid class templates for the POPCNT64/CTZ64 macros
[openal-soft.git] / alc / alu.cpp
blob978f6d62444b442900b4a156f5904bd8ec48ea30
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 "alu.h"
25 #include <algorithm>
26 #include <array>
27 #include <atomic>
28 #include <cassert>
29 #include <chrono>
30 #include <climits>
31 #include <cmath>
32 #include <cstdarg>
33 #include <cstdio>
34 #include <cstdlib>
35 #include <functional>
36 #include <iterator>
37 #include <limits>
38 #include <memory>
39 #include <new>
40 #include <numeric>
41 #include <utility>
43 #include "AL/al.h"
44 #include "AL/alc.h"
45 #include "AL/efx.h"
47 #include "al/auxeffectslot.h"
48 #include "al/buffer.h"
49 #include "al/effect.h"
50 #include "al/event.h"
51 #include "al/listener.h"
52 #include "alcmain.h"
53 #include "alcontext.h"
54 #include "almalloc.h"
55 #include "alnumeric.h"
56 #include "alspan.h"
57 #include "alstring.h"
58 #include "ambidefs.h"
59 #include "atomic.h"
60 #include "bformatdec.h"
61 #include "bs2b.h"
62 #include "cpu_caps.h"
63 #include "devformat.h"
64 #include "effects/base.h"
65 #include "filters/biquad.h"
66 #include "filters/nfc.h"
67 #include "filters/splitter.h"
68 #include "fpu_ctrl.h"
69 #include "front_stablizer.h"
70 #include "hrtf.h"
71 #include "inprogext.h"
72 #include "mastering.h"
73 #include "math_defs.h"
74 #include "mixer/defs.h"
75 #include "opthelpers.h"
76 #include "ringbuffer.h"
77 #include "strutils.h"
78 #include "threads.h"
79 #include "uhjfilter.h"
80 #include "vecmat.h"
81 #include "voice.h"
83 #include "bsinc_tables.h"
85 struct CTag;
86 #ifdef HAVE_SSE
87 struct SSETag;
88 #endif
89 #ifdef HAVE_SSE2
90 struct SSE2Tag;
91 #endif
92 #ifdef HAVE_SSE4_1
93 struct SSE4Tag;
94 #endif
95 #ifdef HAVE_NEON
96 struct NEONTag;
97 #endif
98 struct CopyTag;
99 struct PointTag;
100 struct LerpTag;
101 struct CubicTag;
102 struct BSincTag;
103 struct FastBSincTag;
106 static_assert(!(MAX_RESAMPLER_PADDING&1) && MAX_RESAMPLER_PADDING >= BSINC_POINTS_MAX,
107 "MAX_RESAMPLER_PADDING is not a multiple of two, or is too small");
110 namespace {
112 using namespace std::placeholders;
114 float InitConeScale()
116 float ret{1.0f};
117 if(auto optval = al::getenv("__ALSOFT_HALF_ANGLE_CONES"))
119 if(al::strcasecmp(optval->c_str(), "true") == 0
120 || strtol(optval->c_str(), nullptr, 0) == 1)
121 ret *= 0.5f;
123 return ret;
126 float InitZScale()
128 float ret{1.0f};
129 if(auto optval = al::getenv("__ALSOFT_REVERSE_Z"))
131 if(al::strcasecmp(optval->c_str(), "true") == 0
132 || strtol(optval->c_str(), nullptr, 0) == 1)
133 ret *= -1.0f;
135 return ret;
138 } // namespace
140 /* Cone scalar */
141 const float ConeScale{InitConeScale()};
143 /* Localized Z scalar for mono sources */
144 const float ZScale{InitZScale()};
146 namespace {
148 struct ChanMap {
149 Channel channel;
150 float angle;
151 float elevation;
154 using HrtfDirectMixerFunc = void(*)(FloatBufferLine &LeftOut, FloatBufferLine &RightOut,
155 const al::span<const FloatBufferLine> InSamples, float2 *AccumSamples, DirectHrtfState *State,
156 const size_t BufferSize);
158 HrtfDirectMixerFunc MixDirectHrtf{MixDirectHrtf_<CTag>};
160 inline HrtfDirectMixerFunc SelectHrtfMixer(void)
162 #ifdef HAVE_NEON
163 if((CPUCapFlags&CPU_CAP_NEON))
164 return MixDirectHrtf_<NEONTag>;
165 #endif
166 #ifdef HAVE_SSE
167 if((CPUCapFlags&CPU_CAP_SSE))
168 return MixDirectHrtf_<SSETag>;
169 #endif
171 return MixDirectHrtf_<CTag>;
175 inline void BsincPrepare(const ALuint increment, BsincState *state, const BSincTable *table)
177 size_t si{BSINC_SCALE_COUNT - 1};
178 float sf{0.0f};
180 if(increment > FRACTIONONE)
182 sf = FRACTIONONE / static_cast<float>(increment);
183 sf = maxf(0.0f, (BSINC_SCALE_COUNT-1) * (sf-table->scaleBase) * table->scaleRange);
184 si = float2uint(sf);
185 /* The interpolation factor is fit to this diagonally-symmetric curve
186 * to reduce the transition ripple caused by interpolating different
187 * scales of the sinc function.
189 sf = 1.0f - std::cos(std::asin(sf - static_cast<float>(si)));
192 state->sf = sf;
193 state->m = table->m[si];
194 state->l = (state->m/2) - 1;
195 state->filter = table->Tab + table->filterOffset[si];
198 inline ResamplerFunc SelectResampler(Resampler resampler, ALuint increment)
200 switch(resampler)
202 case Resampler::Point:
203 return Resample_<PointTag,CTag>;
204 case Resampler::Linear:
205 #ifdef HAVE_NEON
206 if((CPUCapFlags&CPU_CAP_NEON))
207 return Resample_<LerpTag,NEONTag>;
208 #endif
209 #ifdef HAVE_SSE4_1
210 if((CPUCapFlags&CPU_CAP_SSE4_1))
211 return Resample_<LerpTag,SSE4Tag>;
212 #endif
213 #ifdef HAVE_SSE2
214 if((CPUCapFlags&CPU_CAP_SSE2))
215 return Resample_<LerpTag,SSE2Tag>;
216 #endif
217 return Resample_<LerpTag,CTag>;
218 case Resampler::Cubic:
219 return Resample_<CubicTag,CTag>;
220 case Resampler::BSinc12:
221 case Resampler::BSinc24:
222 if(increment <= FRACTIONONE)
224 /* fall-through */
225 case Resampler::FastBSinc12:
226 case Resampler::FastBSinc24:
227 #ifdef HAVE_NEON
228 if((CPUCapFlags&CPU_CAP_NEON))
229 return Resample_<FastBSincTag,NEONTag>;
230 #endif
231 #ifdef HAVE_SSE
232 if((CPUCapFlags&CPU_CAP_SSE))
233 return Resample_<FastBSincTag,SSETag>;
234 #endif
235 return Resample_<FastBSincTag,CTag>;
237 #ifdef HAVE_NEON
238 if((CPUCapFlags&CPU_CAP_NEON))
239 return Resample_<BSincTag,NEONTag>;
240 #endif
241 #ifdef HAVE_SSE
242 if((CPUCapFlags&CPU_CAP_SSE))
243 return Resample_<BSincTag,SSETag>;
244 #endif
245 return Resample_<BSincTag,CTag>;
248 return Resample_<PointTag,CTag>;
251 } // namespace
253 void aluInit(void)
255 MixDirectHrtf = SelectHrtfMixer();
259 ResamplerFunc PrepareResampler(Resampler resampler, ALuint increment, InterpState *state)
261 switch(resampler)
263 case Resampler::Point:
264 case Resampler::Linear:
265 case Resampler::Cubic:
266 break;
267 case Resampler::FastBSinc12:
268 case Resampler::BSinc12:
269 BsincPrepare(increment, &state->bsinc, &bsinc12);
270 break;
271 case Resampler::FastBSinc24:
272 case Resampler::BSinc24:
273 BsincPrepare(increment, &state->bsinc, &bsinc24);
274 break;
276 return SelectResampler(resampler, increment);
280 void ALCdevice::ProcessHrtf(const size_t SamplesToDo)
282 /* HRTF is stereo output only. */
283 const ALuint lidx{RealOut.ChannelIndex[FrontLeft]};
284 const ALuint ridx{RealOut.ChannelIndex[FrontRight]};
286 MixDirectHrtf(RealOut.Buffer[lidx], RealOut.Buffer[ridx], Dry.Buffer, HrtfAccumData,
287 mHrtfState.get(), SamplesToDo);
290 void ALCdevice::ProcessAmbiDec(const size_t SamplesToDo)
292 AmbiDecoder->process(RealOut.Buffer, Dry.Buffer.data(), SamplesToDo);
295 void ALCdevice::ProcessAmbiDecStablized(const size_t SamplesToDo)
297 /* Decode with front image stablization. */
298 const ALuint lidx{RealOut.ChannelIndex[FrontLeft]};
299 const ALuint ridx{RealOut.ChannelIndex[FrontRight]};
300 const ALuint cidx{RealOut.ChannelIndex[FrontCenter]};
302 AmbiDecoder->processStablize(RealOut.Buffer, Dry.Buffer.data(), lidx, ridx, cidx,
303 SamplesToDo);
306 void ALCdevice::ProcessUhj(const size_t SamplesToDo)
308 /* UHJ is stereo output only. */
309 const ALuint lidx{RealOut.ChannelIndex[FrontLeft]};
310 const ALuint ridx{RealOut.ChannelIndex[FrontRight]};
312 /* Encode to stereo-compatible 2-channel UHJ output. */
313 Uhj_Encoder->encode(RealOut.Buffer[lidx], RealOut.Buffer[ridx], Dry.Buffer.data(),
314 SamplesToDo);
317 void ALCdevice::ProcessBs2b(const size_t SamplesToDo)
319 /* First, decode the ambisonic mix to the "real" output. */
320 AmbiDecoder->process(RealOut.Buffer, Dry.Buffer.data(), SamplesToDo);
322 /* BS2B is stereo output only. */
323 const ALuint lidx{RealOut.ChannelIndex[FrontLeft]};
324 const ALuint ridx{RealOut.ChannelIndex[FrontRight]};
326 /* Now apply the BS2B binaural/crossfeed filter. */
327 bs2b_cross_feed(Bs2b.get(), RealOut.Buffer[lidx].data(), RealOut.Buffer[ridx].data(),
328 SamplesToDo);
332 namespace {
334 /* This RNG method was created based on the math found in opusdec. It's quick,
335 * and starting with a seed value of 22222, is suitable for generating
336 * whitenoise.
338 inline ALuint dither_rng(ALuint *seed) noexcept
340 *seed = (*seed * 96314165) + 907633515;
341 return *seed;
345 auto GetAmbiScales(AmbiScaling scaletype) noexcept -> const std::array<float,MAX_AMBI_CHANNELS>&
347 if(scaletype == AmbiScaling::FuMa) return AmbiScale::FromFuMa;
348 if(scaletype == AmbiScaling::SN3D) return AmbiScale::FromSN3D;
349 return AmbiScale::FromN3D;
352 auto GetAmbiLayout(AmbiLayout layouttype) noexcept -> const std::array<uint8_t,MAX_AMBI_CHANNELS>&
354 if(layouttype == AmbiLayout::FuMa) return AmbiIndex::FromFuMa;
355 return AmbiIndex::FromACN;
358 auto GetAmbi2DLayout(AmbiLayout layouttype) noexcept -> const std::array<uint8_t,MAX_AMBI2D_CHANNELS>&
360 if(layouttype == AmbiLayout::FuMa) return AmbiIndex::FromFuMa2D;
361 return AmbiIndex::From2D;
365 inline alu::Vector aluCrossproduct(const alu::Vector &in1, const alu::Vector &in2)
367 return alu::Vector{
368 in1[1]*in2[2] - in1[2]*in2[1],
369 in1[2]*in2[0] - in1[0]*in2[2],
370 in1[0]*in2[1] - in1[1]*in2[0],
371 0.0f
375 inline float aluDotproduct(const alu::Vector &vec1, const alu::Vector &vec2)
377 return vec1[0]*vec2[0] + vec1[1]*vec2[1] + vec1[2]*vec2[2];
381 alu::Vector operator*(const alu::Matrix &mtx, const alu::Vector &vec) noexcept
383 return alu::Vector{
384 vec[0]*mtx[0][0] + vec[1]*mtx[1][0] + vec[2]*mtx[2][0] + vec[3]*mtx[3][0],
385 vec[0]*mtx[0][1] + vec[1]*mtx[1][1] + vec[2]*mtx[2][1] + vec[3]*mtx[3][1],
386 vec[0]*mtx[0][2] + vec[1]*mtx[1][2] + vec[2]*mtx[2][2] + vec[3]*mtx[3][2],
387 vec[0]*mtx[0][3] + vec[1]*mtx[1][3] + vec[2]*mtx[2][3] + vec[3]*mtx[3][3]
392 bool CalcContextParams(ALCcontext *Context)
394 ALcontextProps *props{Context->mUpdate.exchange(nullptr, std::memory_order_acq_rel)};
395 if(!props) return false;
397 ALlistener &Listener = Context->mListener;
398 Listener.Params.DopplerFactor = props->DopplerFactor;
399 Listener.Params.SpeedOfSound = props->SpeedOfSound * props->DopplerVelocity;
401 Listener.Params.SourceDistanceModel = props->SourceDistanceModel;
402 Listener.Params.mDistanceModel = props->mDistanceModel;
404 AtomicReplaceHead(Context->mFreeContextProps, props);
405 return true;
408 bool CalcListenerParams(ALCcontext *Context)
410 ALlistener &Listener = Context->mListener;
412 ALlistenerProps *props{Listener.Params.Update.exchange(nullptr, std::memory_order_acq_rel)};
413 if(!props) return false;
415 /* AT then UP */
416 alu::Vector N{props->OrientAt[0], props->OrientAt[1], props->OrientAt[2], 0.0f};
417 N.normalize();
418 alu::Vector V{props->OrientUp[0], props->OrientUp[1], props->OrientUp[2], 0.0f};
419 V.normalize();
420 /* Build and normalize right-vector */
421 alu::Vector U{aluCrossproduct(N, V)};
422 U.normalize();
424 Listener.Params.Matrix = alu::Matrix{
425 U[0], V[0], -N[0], 0.0f,
426 U[1], V[1], -N[1], 0.0f,
427 U[2], V[2], -N[2], 0.0f,
428 0.0f, 0.0f, 0.0f, 1.0f
431 const alu::Vector P{Listener.Params.Matrix *
432 alu::Vector{props->Position[0], props->Position[1], props->Position[2], 1.0f}};
433 Listener.Params.Matrix.setRow(3, -P[0], -P[1], -P[2], 1.0f);
435 const alu::Vector vel{props->Velocity[0], props->Velocity[1], props->Velocity[2], 0.0f};
436 Listener.Params.Velocity = Listener.Params.Matrix * vel;
438 Listener.Params.Gain = props->Gain * Context->mGainBoost;
439 Listener.Params.MetersPerUnit = props->MetersPerUnit;
441 AtomicReplaceHead(Context->mFreeListenerProps, props);
442 return true;
445 bool CalcEffectSlotParams(ALeffectslot *slot, ALeffectslot **sorted_slots, ALCcontext *context)
447 ALeffectslotProps *props{slot->Params.Update.exchange(nullptr, std::memory_order_acq_rel)};
448 if(!props) return false;
450 /* If the effect slot target changed, clear the first sorted entry to force
451 * a re-sort.
453 if(slot->Params.Target != props->Target)
454 *sorted_slots = nullptr;
455 slot->Params.Gain = props->Gain;
456 slot->Params.AuxSendAuto = props->AuxSendAuto;
457 slot->Params.Target = props->Target;
458 slot->Params.EffectType = props->Type;
459 slot->Params.mEffectProps = props->Props;
460 if(IsReverbEffect(props->Type))
462 slot->Params.RoomRolloff = props->Props.Reverb.RoomRolloffFactor;
463 slot->Params.DecayTime = props->Props.Reverb.DecayTime;
464 slot->Params.DecayLFRatio = props->Props.Reverb.DecayLFRatio;
465 slot->Params.DecayHFRatio = props->Props.Reverb.DecayHFRatio;
466 slot->Params.DecayHFLimit = props->Props.Reverb.DecayHFLimit;
467 slot->Params.AirAbsorptionGainHF = props->Props.Reverb.AirAbsorptionGainHF;
469 else
471 slot->Params.RoomRolloff = 0.0f;
472 slot->Params.DecayTime = 0.0f;
473 slot->Params.DecayLFRatio = 0.0f;
474 slot->Params.DecayHFRatio = 0.0f;
475 slot->Params.DecayHFLimit = false;
476 slot->Params.AirAbsorptionGainHF = 1.0f;
479 EffectState *state{props->State.release()};
480 EffectState *oldstate{slot->Params.mEffectState};
481 slot->Params.mEffectState = state;
483 /* Only release the old state if it won't get deleted, since we can't be
484 * deleting/freeing anything in the mixer.
486 if(!oldstate->releaseIfNoDelete())
488 /* Otherwise, if it would be deleted send it off with a release event. */
489 RingBuffer *ring{context->mAsyncEvents.get()};
490 auto evt_vec = ring->getWriteVector();
491 if LIKELY(evt_vec.first.len > 0)
493 AsyncEvent *evt{::new(evt_vec.first.buf) AsyncEvent{EventType_ReleaseEffectState}};
494 evt->u.mEffectState = oldstate;
495 ring->writeAdvance(1);
497 else
499 /* If writing the event failed, the queue was probably full. Store
500 * the old state in the property object where it can eventually be
501 * cleaned up sometime later (not ideal, but better than blocking
502 * or leaking).
504 props->State.reset(oldstate);
508 EffectBufferBase *buffer{props->Buffer.release()};
509 EffectBufferBase *oldbuffer{slot->Params.mEffectBuffer};
510 slot->Params.mEffectBuffer = buffer;
512 if(oldbuffer && !oldbuffer->releaseIfNoDelete())
514 RingBuffer *ring{context->mAsyncEvents.get()};
515 auto evt_vec = ring->getWriteVector();
516 if LIKELY(evt_vec.first.len > 0)
518 AsyncEvent *evt{::new(evt_vec.first.buf) AsyncEvent{EventType_ReleaseEffectBuffer}};
519 evt->u.mEffectBuffer = oldbuffer;
520 ring->writeAdvance(1);
522 else
523 props->Buffer.reset(oldbuffer);
526 AtomicReplaceHead(context->mFreeEffectslotProps, props);
528 EffectTarget output;
529 if(ALeffectslot *target{slot->Params.Target})
530 output = EffectTarget{&target->Wet, nullptr};
531 else
533 ALCdevice *device{context->mDevice.get()};
534 output = EffectTarget{&device->Dry, &device->RealOut};
536 state->update(context, slot, &slot->Params.mEffectProps, output);
537 return true;
541 /* Scales the given azimuth toward the side (+/- pi/2 radians) for positions in
542 * front.
544 inline float ScaleAzimuthFront(float azimuth, float scale)
546 const float abs_azi{std::fabs(azimuth)};
547 if(!(abs_azi >= al::MathDefs<float>::Pi()*0.5f))
548 return std::copysign(minf(abs_azi*scale, al::MathDefs<float>::Pi()*0.5f), azimuth);
549 return azimuth;
552 /* Wraps the given value in radians to stay between [-pi,+pi] */
553 inline float WrapRadians(float r)
555 constexpr float Pi{al::MathDefs<float>::Pi()};
556 constexpr float Pi2{al::MathDefs<float>::Tau()};
557 if(r > Pi) return std::fmod(Pi+r, Pi2) - Pi;
558 if(r < -Pi) return Pi - std::fmod(Pi-r, Pi2);
559 return r;
562 /* Begin ambisonic rotation helpers.
564 * Rotating first-order B-Format just needs a straight-forward X/Y/Z rotation
565 * matrix. Higher orders, however, are more complicated. The method implemented
566 * here is a recursive algorithm (the rotation for first-order is used to help
567 * generate the second-order rotation, which helps generate the third-order
568 * rotation, etc).
570 * Adapted from
571 * <https://github.com/polarch/Spherical-Harmonic-Transform/blob/master/getSHrotMtx.m>,
572 * provided under the BSD 3-Clause license.
574 * Copyright (c) 2015, Archontis Politis
575 * Copyright (c) 2019, Christopher Robinson
577 * The u, v, and w coefficients used for generating higher-order rotations are
578 * precomputed since they're constant. The second-order coefficients are
579 * followed by the third-order coefficients, etc.
581 struct RotatorCoeffs {
582 float u, v, w;
584 template<size_t N0, size_t N1>
585 static std::array<RotatorCoeffs,N0+N1> ConcatArrays(const std::array<RotatorCoeffs,N0> &lhs,
586 const std::array<RotatorCoeffs,N1> &rhs)
588 std::array<RotatorCoeffs,N0+N1> ret;
589 auto iter = std::copy(lhs.cbegin(), lhs.cend(), ret.begin());
590 std::copy(rhs.cbegin(), rhs.cend(), iter);
591 return ret;
594 template<int l, int num_elems=l*2+1>
595 static std::array<RotatorCoeffs,num_elems*num_elems> GenCoeffs()
597 std::array<RotatorCoeffs,num_elems*num_elems> ret{};
598 auto coeffs = ret.begin();
600 for(int m{-l};m <= l;++m)
602 for(int n{-l};n <= l;++n)
604 // compute u,v,w terms of Eq.8.1 (Table I)
605 const bool d{m == 0}; // the delta function d_m0
606 const float denom{static_cast<float>((std::abs(n) == l) ?
607 (2*l) * (2*l - 1) : (l*l - n*n))};
609 const int abs_m{std::abs(m)};
610 coeffs->u = std::sqrt(static_cast<float>(l*l - m*m)/denom);
611 coeffs->v = std::sqrt(static_cast<float>(l+abs_m-1) * static_cast<float>(l+abs_m) /
612 denom) * (1.0f+d) * (1.0f - 2.0f*d) * 0.5f;
613 coeffs->w = std::sqrt(static_cast<float>(l-abs_m-1) * static_cast<float>(l-abs_m) /
614 denom) * (1.0f-d) * -0.5f;
615 ++coeffs;
619 return ret;
622 const auto RotatorCoeffArray = RotatorCoeffs::ConcatArrays(RotatorCoeffs::GenCoeffs<2>(),
623 RotatorCoeffs::GenCoeffs<3>());
626 * Given the matrix, pre-filled with the (zeroth- and) first-order rotation
627 * coefficients, this fills in the coefficients for the higher orders up to and
628 * including the given order. The matrix is in ACN layout.
630 void AmbiRotator(std::array<std::array<float,MAX_AMBI_CHANNELS>,MAX_AMBI_CHANNELS> &matrix,
631 const int order)
633 /* Don't do anything for < 2nd order. */
634 if(order < 2) return;
636 auto P = [](const int i, const int l, const int a, const int n, const size_t last_band,
637 const std::array<std::array<float,MAX_AMBI_CHANNELS>,MAX_AMBI_CHANNELS> &R)
639 const float ri1{ R[static_cast<ALuint>(i+2)][ 1+2]};
640 const float rim1{R[static_cast<ALuint>(i+2)][-1+2]};
641 const float ri0{ R[static_cast<ALuint>(i+2)][ 0+2]};
643 auto vec = R[static_cast<ALuint>(a+l-1) + last_band].cbegin() + last_band;
644 if(n == -l)
645 return ri1*vec[0] + rim1*vec[static_cast<ALuint>(l-1)*size_t{2}];
646 if(n == l)
647 return ri1*vec[static_cast<ALuint>(l-1)*size_t{2}] - rim1*vec[0];
648 return ri0*vec[static_cast<ALuint>(n+l-1)];
651 auto U = [P](const int l, const int m, const int n, const size_t last_band,
652 const std::array<std::array<float,MAX_AMBI_CHANNELS>,MAX_AMBI_CHANNELS> &R)
654 return P(0, l, m, n, last_band, R);
656 auto V = [P](const int l, const int m, const int n, const size_t last_band,
657 const std::array<std::array<float,MAX_AMBI_CHANNELS>,MAX_AMBI_CHANNELS> &R)
659 if(m > 0)
661 const bool d{m == 1};
662 const float p0{P( 1, l, m-1, n, last_band, R)};
663 const float p1{P(-1, l, -m+1, n, last_band, R)};
664 return d ? p0*std::sqrt(2.0f) : (p0 - p1);
666 const bool d{m == -1};
667 const float p0{P( 1, l, m+1, n, last_band, R)};
668 const float p1{P(-1, l, -m-1, n, last_band, R)};
669 return d ? p1*std::sqrt(2.0f) : (p0 + p1);
671 auto W = [P](const int l, const int m, const int n, const size_t last_band,
672 const std::array<std::array<float,MAX_AMBI_CHANNELS>,MAX_AMBI_CHANNELS> &R)
674 assert(m != 0);
675 if(m > 0)
677 const float p0{P( 1, l, m+1, n, last_band, R)};
678 const float p1{P(-1, l, -m-1, n, last_band, R)};
679 return p0 + p1;
681 const float p0{P( 1, l, m-1, n, last_band, R)};
682 const float p1{P(-1, l, -m+1, n, last_band, R)};
683 return p0 - p1;
686 // compute rotation matrix of each subsequent band recursively
687 auto coeffs = RotatorCoeffArray.cbegin();
688 size_t band_idx{4}, last_band{1};
689 for(int l{2};l <= order;++l)
691 size_t y{band_idx};
692 for(int m{-l};m <= l;++m,++y)
694 size_t x{band_idx};
695 for(int n{-l};n <= l;++n,++x)
697 float r{0.0f};
699 // computes Eq.8.1
700 const float u{coeffs->u};
701 if(u != 0.0f) r += u * U(l, m, n, last_band, matrix);
702 const float v{coeffs->v};
703 if(v != 0.0f) r += v * V(l, m, n, last_band, matrix);
704 const float w{coeffs->w};
705 if(w != 0.0f) r += w * W(l, m, n, last_band, matrix);
707 matrix[y][x] = r;
708 ++coeffs;
711 last_band = band_idx;
712 band_idx += static_cast<ALuint>(l)*size_t{2} + 1;
715 /* End ambisonic rotation helpers. */
718 struct GainTriplet { float Base, HF, LF; };
720 void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, const float zpos,
721 const float Distance, const float Spread, const GainTriplet &DryGain,
722 const al::span<const GainTriplet,MAX_SENDS> WetGain, ALeffectslot *(&SendSlots)[MAX_SENDS],
723 const VoiceProps *props, const ALlistener &Listener, const ALCdevice *Device)
725 static const ChanMap MonoMap[1]{
726 { FrontCenter, 0.0f, 0.0f }
727 }, RearMap[2]{
728 { BackLeft, Deg2Rad(-150.0f), Deg2Rad(0.0f) },
729 { BackRight, Deg2Rad( 150.0f), Deg2Rad(0.0f) }
730 }, QuadMap[4]{
731 { FrontLeft, Deg2Rad( -45.0f), Deg2Rad(0.0f) },
732 { FrontRight, Deg2Rad( 45.0f), Deg2Rad(0.0f) },
733 { BackLeft, Deg2Rad(-135.0f), Deg2Rad(0.0f) },
734 { BackRight, Deg2Rad( 135.0f), Deg2Rad(0.0f) }
735 }, X51Map[6]{
736 { FrontLeft, Deg2Rad( -30.0f), Deg2Rad(0.0f) },
737 { FrontRight, Deg2Rad( 30.0f), Deg2Rad(0.0f) },
738 { FrontCenter, Deg2Rad( 0.0f), Deg2Rad(0.0f) },
739 { LFE, 0.0f, 0.0f },
740 { SideLeft, Deg2Rad(-110.0f), Deg2Rad(0.0f) },
741 { SideRight, Deg2Rad( 110.0f), Deg2Rad(0.0f) }
742 }, X61Map[7]{
743 { FrontLeft, Deg2Rad(-30.0f), Deg2Rad(0.0f) },
744 { FrontRight, Deg2Rad( 30.0f), Deg2Rad(0.0f) },
745 { FrontCenter, Deg2Rad( 0.0f), Deg2Rad(0.0f) },
746 { LFE, 0.0f, 0.0f },
747 { BackCenter, Deg2Rad(180.0f), Deg2Rad(0.0f) },
748 { SideLeft, Deg2Rad(-90.0f), Deg2Rad(0.0f) },
749 { SideRight, Deg2Rad( 90.0f), Deg2Rad(0.0f) }
750 }, X71Map[8]{
751 { FrontLeft, Deg2Rad( -30.0f), Deg2Rad(0.0f) },
752 { FrontRight, Deg2Rad( 30.0f), Deg2Rad(0.0f) },
753 { FrontCenter, Deg2Rad( 0.0f), Deg2Rad(0.0f) },
754 { LFE, 0.0f, 0.0f },
755 { BackLeft, Deg2Rad(-150.0f), Deg2Rad(0.0f) },
756 { BackRight, Deg2Rad( 150.0f), Deg2Rad(0.0f) },
757 { SideLeft, Deg2Rad( -90.0f), Deg2Rad(0.0f) },
758 { SideRight, Deg2Rad( 90.0f), Deg2Rad(0.0f) }
761 ChanMap StereoMap[2]{
762 { FrontLeft, Deg2Rad(-30.0f), Deg2Rad(0.0f) },
763 { FrontRight, Deg2Rad( 30.0f), Deg2Rad(0.0f) }
766 const auto Frequency = static_cast<float>(Device->Frequency);
767 const ALuint NumSends{Device->NumAuxSends};
769 const size_t num_channels{voice->mChans.size()};
770 ASSUME(num_channels > 0);
772 for(auto &chandata : voice->mChans)
774 chandata.mDryParams.Hrtf.Target = HrtfFilter{};
775 chandata.mDryParams.Gains.Target.fill(0.0f);
776 std::for_each(chandata.mWetParams.begin(), chandata.mWetParams.begin()+NumSends,
777 [](SendParams &params) -> void { params.Gains.Target.fill(0.0f); });
780 DirectMode DirectChannels{props->DirectChannels};
781 const ChanMap *chans{nullptr};
782 float downmix_gain{1.0f};
783 switch(voice->mFmtChannels)
785 case FmtMono:
786 chans = MonoMap;
787 /* Mono buffers are never played direct. */
788 DirectChannels = DirectMode::Off;
789 break;
791 case FmtStereo:
792 if(DirectChannels == DirectMode::Off)
794 /* Convert counter-clockwise to clock-wise, and wrap between
795 * [-pi,+pi].
797 StereoMap[0].angle = WrapRadians(-props->StereoPan[0]);
798 StereoMap[1].angle = WrapRadians(-props->StereoPan[1]);
801 chans = StereoMap;
802 downmix_gain = 1.0f / 2.0f;
803 break;
805 case FmtRear:
806 chans = RearMap;
807 downmix_gain = 1.0f / 2.0f;
808 break;
810 case FmtQuad:
811 chans = QuadMap;
812 downmix_gain = 1.0f / 4.0f;
813 break;
815 case FmtX51:
816 chans = X51Map;
817 /* NOTE: Excludes LFE. */
818 downmix_gain = 1.0f / 5.0f;
819 break;
821 case FmtX61:
822 chans = X61Map;
823 /* NOTE: Excludes LFE. */
824 downmix_gain = 1.0f / 6.0f;
825 break;
827 case FmtX71:
828 chans = X71Map;
829 /* NOTE: Excludes LFE. */
830 downmix_gain = 1.0f / 7.0f;
831 break;
833 case FmtBFormat2D:
834 case FmtBFormat3D:
835 DirectChannels = DirectMode::Off;
836 break;
839 voice->mFlags &= ~(VOICE_HAS_HRTF | VOICE_HAS_NFC);
840 if(voice->mFmtChannels == FmtBFormat2D || voice->mFmtChannels == FmtBFormat3D)
842 /* Special handling for B-Format sources. */
844 if(Device->AvgSpeakerDist > 0.0f)
846 if(!(Distance > std::numeric_limits<float>::epsilon()))
848 /* NOTE: The NFCtrlFilters were created with a w0 of 0, which
849 * is what we want for FOA input. The first channel may have
850 * been previously re-adjusted if panned, so reset it.
852 voice->mChans[0].mDryParams.NFCtrlFilter.adjust(0.0f);
854 else
856 /* Clamp the distance for really close sources, to prevent
857 * excessive bass.
859 const float mdist{maxf(Distance, Device->AvgSpeakerDist/4.0f)};
860 const float w0{SPEEDOFSOUNDMETRESPERSEC / (mdist * Frequency)};
862 /* Only need to adjust the first channel of a B-Format source. */
863 voice->mChans[0].mDryParams.NFCtrlFilter.adjust(w0);
866 voice->mFlags |= VOICE_HAS_NFC;
869 /* Panning a B-Format sound toward some direction is easy. Just pan the
870 * first (W) channel as a normal mono sound. The angular spread is used
871 * as a directional scalar to blend between full coverage and full
872 * panning.
874 const float coverage{!(Distance > std::numeric_limits<float>::epsilon()) ? 1.0f :
875 (Spread * (1.0f/al::MathDefs<float>::Tau()))};
877 auto calc_coeffs = [xpos,ypos,zpos](RenderMode mode)
879 if(mode != RenderMode::Pairwise)
880 return CalcDirectionCoeffs({xpos, ypos, zpos}, 0.0f);
882 /* Clamp Y, in case rounding errors caused it to end up outside
883 * of -1...+1.
885 const float ev{std::asin(clampf(ypos, -1.0f, 1.0f))};
886 /* Negate Z for right-handed coords with -Z in front. */
887 const float az{std::atan2(xpos, -zpos)};
889 /* A scalar of 1.5 for plain stereo results in +/-60 degrees
890 * being moved to +/-90 degrees for direct right and left
891 * speaker responses.
893 return CalcAngleCoeffs(ScaleAzimuthFront(az, 1.5f), ev, 0.0f);
895 auto coeffs = calc_coeffs(Device->mRenderMode);
896 std::transform(coeffs.begin()+1, coeffs.end(), coeffs.begin()+1,
897 std::bind(std::multiplies<float>{}, _1, 1.0f-coverage));
899 /* NOTE: W needs to be scaled according to channel scaling. */
900 const auto &scales = GetAmbiScales(voice->mAmbiScaling);
901 ComputePanGains(&Device->Dry, coeffs.data(), DryGain.Base*scales[0],
902 voice->mChans[0].mDryParams.Gains.Target);
903 for(ALuint i{0};i < NumSends;i++)
905 if(const ALeffectslot *Slot{SendSlots[i]})
906 ComputePanGains(&Slot->Wet, coeffs.data(), WetGain[i].Base*scales[0],
907 voice->mChans[0].mWetParams[i].Gains.Target);
910 if(coverage > 0.0f)
912 /* Local B-Format sources have their XYZ channels rotated according
913 * to the orientation.
915 /* AT then UP */
916 alu::Vector N{props->OrientAt[0], props->OrientAt[1], props->OrientAt[2], 0.0f};
917 N.normalize();
918 alu::Vector V{props->OrientUp[0], props->OrientUp[1], props->OrientUp[2], 0.0f};
919 V.normalize();
920 if(!props->HeadRelative)
922 N = Listener.Params.Matrix * N;
923 V = Listener.Params.Matrix * V;
925 /* Build and normalize right-vector */
926 alu::Vector U{aluCrossproduct(N, V)};
927 U.normalize();
929 /* Build a rotation matrix. Manually fill the zeroth- and first-
930 * order elements, then construct the rotation for the higher
931 * orders.
933 std::array<std::array<float,MAX_AMBI_CHANNELS>,MAX_AMBI_CHANNELS> shrot{};
934 shrot[0][0] = 1.0f;
935 shrot[1][1] = U[0]; shrot[1][2] = -V[0]; shrot[1][3] = -N[0];
936 shrot[2][1] = -U[1]; shrot[2][2] = V[1]; shrot[2][3] = N[1];
937 shrot[3][1] = U[2]; shrot[3][2] = -V[2]; shrot[3][3] = -N[2];
938 AmbiRotator(shrot, static_cast<int>(minu(voice->mAmbiOrder, Device->mAmbiOrder)));
940 /* Convert the rotation matrix for input ordering and scaling, and
941 * whether input is 2D or 3D.
943 const uint8_t *index_map{(voice->mFmtChannels == FmtBFormat2D) ?
944 GetAmbi2DLayout(voice->mAmbiLayout).data() :
945 GetAmbiLayout(voice->mAmbiLayout).data()};
947 static const uint8_t ChansPerOrder[MAX_AMBI_ORDER+1]{1, 3, 5, 7,};
948 static const uint8_t OrderOffset[MAX_AMBI_ORDER+1]{0, 1, 4, 9,};
949 for(size_t c{1};c < num_channels;c++)
951 const size_t acn{index_map[c]};
952 const size_t order{AmbiIndex::OrderFromChannel[acn]};
953 const size_t tocopy{ChansPerOrder[order]};
954 const size_t offset{OrderOffset[order]};
955 const float scale{scales[acn] * coverage};
956 auto in = shrot.cbegin() + offset;
958 coeffs = std::array<float,MAX_AMBI_CHANNELS>{};
959 for(size_t x{0};x < tocopy;++x)
960 coeffs[offset+x] = in[x][acn] * scale;
962 ComputePanGains(&Device->Dry, coeffs.data(), DryGain.Base,
963 voice->mChans[c].mDryParams.Gains.Target);
965 for(ALuint i{0};i < NumSends;i++)
967 if(const ALeffectslot *Slot{SendSlots[i]})
968 ComputePanGains(&Slot->Wet, coeffs.data(), WetGain[i].Base,
969 voice->mChans[c].mWetParams[i].Gains.Target);
974 else if(DirectChannels != DirectMode::Off && Device->FmtChans != DevFmtAmbi3D)
976 /* Direct source channels always play local. Skip the virtual channels
977 * and write inputs to the matching real outputs.
979 voice->mDirect.Buffer = Device->RealOut.Buffer;
981 for(size_t c{0};c < num_channels;c++)
983 ALuint idx{GetChannelIdxByName(Device->RealOut, chans[c].channel)};
984 if(idx != INVALID_CHANNEL_INDEX)
985 voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain.Base;
986 else if(DirectChannels == DirectMode::RemixMismatch)
988 auto match_channel = [chans,c](const InputRemixMap &map) noexcept -> bool
989 { return chans[c].channel == map.channel; };
990 auto remap = std::find_if(Device->RealOut.RemixMap.cbegin(),
991 Device->RealOut.RemixMap.cend(), match_channel);
992 if(remap != Device->RealOut.RemixMap.cend())
993 for(const auto &target : remap->targets)
995 idx = GetChannelIdxByName(Device->RealOut, target.channel);
996 if(idx != INVALID_CHANNEL_INDEX)
997 voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain.Base *
998 target.mix;
1003 /* Auxiliary sends still use normal channel panning since they mix to
1004 * B-Format, which can't channel-match.
1006 for(size_t c{0};c < num_channels;c++)
1008 const auto coeffs = CalcAngleCoeffs(chans[c].angle, chans[c].elevation, 0.0f);
1010 for(ALuint i{0};i < NumSends;i++)
1012 if(const ALeffectslot *Slot{SendSlots[i]})
1013 ComputePanGains(&Slot->Wet, coeffs.data(), WetGain[i].Base,
1014 voice->mChans[c].mWetParams[i].Gains.Target);
1018 else if(Device->mRenderMode == RenderMode::Hrtf)
1020 /* Full HRTF rendering. Skip the virtual channels and render to the
1021 * real outputs.
1023 voice->mDirect.Buffer = Device->RealOut.Buffer;
1025 if(Distance > std::numeric_limits<float>::epsilon())
1027 const float ev{std::asin(clampf(ypos, -1.0f, 1.0f))};
1028 const float az{std::atan2(xpos, -zpos)};
1030 /* Get the HRIR coefficients and delays just once, for the given
1031 * source direction.
1033 GetHrtfCoeffs(Device->mHrtf.get(), ev, az, Distance, Spread,
1034 voice->mChans[0].mDryParams.Hrtf.Target.Coeffs,
1035 voice->mChans[0].mDryParams.Hrtf.Target.Delay);
1036 voice->mChans[0].mDryParams.Hrtf.Target.Gain = DryGain.Base * downmix_gain;
1038 /* Remaining channels use the same results as the first. */
1039 for(size_t c{1};c < num_channels;c++)
1041 /* Skip LFE */
1042 if(chans[c].channel == LFE) continue;
1043 voice->mChans[c].mDryParams.Hrtf.Target = voice->mChans[0].mDryParams.Hrtf.Target;
1046 /* Calculate the directional coefficients once, which apply to all
1047 * input channels of the source sends.
1049 const auto coeffs = CalcDirectionCoeffs({xpos, ypos, zpos}, Spread);
1051 for(size_t c{0};c < num_channels;c++)
1053 /* Skip LFE */
1054 if(chans[c].channel == LFE)
1055 continue;
1056 for(ALuint i{0};i < NumSends;i++)
1058 if(const ALeffectslot *Slot{SendSlots[i]})
1059 ComputePanGains(&Slot->Wet, coeffs.data(), WetGain[i].Base * downmix_gain,
1060 voice->mChans[c].mWetParams[i].Gains.Target);
1064 else
1066 /* Local sources on HRTF play with each channel panned to its
1067 * relative location around the listener, providing "virtual
1068 * speaker" responses.
1070 for(size_t c{0};c < num_channels;c++)
1072 /* Skip LFE */
1073 if(chans[c].channel == LFE)
1074 continue;
1076 /* Get the HRIR coefficients and delays for this channel
1077 * position.
1079 GetHrtfCoeffs(Device->mHrtf.get(), chans[c].elevation, chans[c].angle,
1080 std::numeric_limits<float>::infinity(), Spread,
1081 voice->mChans[c].mDryParams.Hrtf.Target.Coeffs,
1082 voice->mChans[c].mDryParams.Hrtf.Target.Delay);
1083 voice->mChans[c].mDryParams.Hrtf.Target.Gain = DryGain.Base;
1085 /* Normal panning for auxiliary sends. */
1086 const auto coeffs = CalcAngleCoeffs(chans[c].angle, chans[c].elevation, Spread);
1088 for(ALuint i{0};i < NumSends;i++)
1090 if(const ALeffectslot *Slot{SendSlots[i]})
1091 ComputePanGains(&Slot->Wet, coeffs.data(), WetGain[i].Base,
1092 voice->mChans[c].mWetParams[i].Gains.Target);
1097 voice->mFlags |= VOICE_HAS_HRTF;
1099 else
1101 /* Non-HRTF rendering. Use normal panning to the output. */
1103 if(Distance > std::numeric_limits<float>::epsilon())
1105 /* Calculate NFC filter coefficient if needed. */
1106 if(Device->AvgSpeakerDist > 0.0f)
1108 /* Clamp the distance for really close sources, to prevent
1109 * excessive bass.
1111 const float mdist{maxf(Distance, Device->AvgSpeakerDist/4.0f)};
1112 const float w0{SPEEDOFSOUNDMETRESPERSEC / (mdist * Frequency)};
1114 /* Adjust NFC filters. */
1115 for(size_t c{0};c < num_channels;c++)
1116 voice->mChans[c].mDryParams.NFCtrlFilter.adjust(w0);
1118 voice->mFlags |= VOICE_HAS_NFC;
1121 /* Calculate the directional coefficients once, which apply to all
1122 * input channels.
1124 auto calc_coeffs = [xpos,ypos,zpos,Spread](RenderMode mode)
1126 if(mode != RenderMode::Pairwise)
1127 return CalcDirectionCoeffs({xpos, ypos, zpos}, Spread);
1128 const float ev{std::asin(clampf(ypos, -1.0f, 1.0f))};
1129 const float az{std::atan2(xpos, -zpos)};
1130 return CalcAngleCoeffs(ScaleAzimuthFront(az, 1.5f), ev, Spread);
1132 const auto coeffs = calc_coeffs(Device->mRenderMode);
1134 for(size_t c{0};c < num_channels;c++)
1136 /* Special-case LFE */
1137 if(chans[c].channel == LFE)
1139 if(Device->Dry.Buffer.data() == Device->RealOut.Buffer.data())
1141 const ALuint idx{GetChannelIdxByName(Device->RealOut, chans[c].channel)};
1142 if(idx != INVALID_CHANNEL_INDEX)
1143 voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain.Base;
1145 continue;
1148 ComputePanGains(&Device->Dry, coeffs.data(), DryGain.Base * downmix_gain,
1149 voice->mChans[c].mDryParams.Gains.Target);
1150 for(ALuint i{0};i < NumSends;i++)
1152 if(const ALeffectslot *Slot{SendSlots[i]})
1153 ComputePanGains(&Slot->Wet, coeffs.data(), WetGain[i].Base * downmix_gain,
1154 voice->mChans[c].mWetParams[i].Gains.Target);
1158 else
1160 if(Device->AvgSpeakerDist > 0.0f)
1162 /* If the source distance is 0, simulate a plane-wave by using
1163 * infinite distance, which results in a w0 of 0.
1165 constexpr float w0{0.0f};
1166 for(size_t c{0};c < num_channels;c++)
1167 voice->mChans[c].mDryParams.NFCtrlFilter.adjust(w0);
1169 voice->mFlags |= VOICE_HAS_NFC;
1172 for(size_t c{0};c < num_channels;c++)
1174 /* Special-case LFE */
1175 if(chans[c].channel == LFE)
1177 if(Device->Dry.Buffer.data() == Device->RealOut.Buffer.data())
1179 const ALuint idx{GetChannelIdxByName(Device->RealOut, chans[c].channel)};
1180 if(idx != INVALID_CHANNEL_INDEX)
1181 voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain.Base;
1183 continue;
1186 const auto coeffs = CalcAngleCoeffs((Device->mRenderMode == RenderMode::Pairwise)
1187 ? ScaleAzimuthFront(chans[c].angle, 3.0f) : chans[c].angle,
1188 chans[c].elevation, Spread);
1190 ComputePanGains(&Device->Dry, coeffs.data(), DryGain.Base,
1191 voice->mChans[c].mDryParams.Gains.Target);
1192 for(ALuint i{0};i < NumSends;i++)
1194 if(const ALeffectslot *Slot{SendSlots[i]})
1195 ComputePanGains(&Slot->Wet, coeffs.data(), WetGain[i].Base,
1196 voice->mChans[c].mWetParams[i].Gains.Target);
1203 const float hfNorm{props->Direct.HFReference / Frequency};
1204 const float lfNorm{props->Direct.LFReference / Frequency};
1206 voice->mDirect.FilterType = AF_None;
1207 if(DryGain.HF != 1.0f) voice->mDirect.FilterType |= AF_LowPass;
1208 if(DryGain.LF != 1.0f) voice->mDirect.FilterType |= AF_HighPass;
1210 auto &lowpass = voice->mChans[0].mDryParams.LowPass;
1211 auto &highpass = voice->mChans[0].mDryParams.HighPass;
1212 lowpass.setParamsFromSlope(BiquadType::HighShelf, hfNorm, DryGain.HF, 1.0f);
1213 highpass.setParamsFromSlope(BiquadType::LowShelf, lfNorm, DryGain.LF, 1.0f);
1214 for(size_t c{1};c < num_channels;c++)
1216 voice->mChans[c].mDryParams.LowPass.copyParamsFrom(lowpass);
1217 voice->mChans[c].mDryParams.HighPass.copyParamsFrom(highpass);
1220 for(ALuint i{0};i < NumSends;i++)
1222 const float hfNorm{props->Send[i].HFReference / Frequency};
1223 const float lfNorm{props->Send[i].LFReference / Frequency};
1225 voice->mSend[i].FilterType = AF_None;
1226 if(WetGain[i].HF != 1.0f) voice->mSend[i].FilterType |= AF_LowPass;
1227 if(WetGain[i].LF != 1.0f) voice->mSend[i].FilterType |= AF_HighPass;
1229 auto &lowpass = voice->mChans[0].mWetParams[i].LowPass;
1230 auto &highpass = voice->mChans[0].mWetParams[i].HighPass;
1231 lowpass.setParamsFromSlope(BiquadType::HighShelf, hfNorm, WetGain[i].HF, 1.0f);
1232 highpass.setParamsFromSlope(BiquadType::LowShelf, lfNorm, WetGain[i].LF, 1.0f);
1233 for(size_t c{1};c < num_channels;c++)
1235 voice->mChans[c].mWetParams[i].LowPass.copyParamsFrom(lowpass);
1236 voice->mChans[c].mWetParams[i].HighPass.copyParamsFrom(highpass);
1241 void CalcNonAttnSourceParams(Voice *voice, const VoiceProps *props, const ALCcontext *ALContext)
1243 const ALCdevice *Device{ALContext->mDevice.get()};
1244 ALeffectslot *SendSlots[MAX_SENDS];
1246 voice->mDirect.Buffer = Device->Dry.Buffer;
1247 for(ALuint i{0};i < Device->NumAuxSends;i++)
1249 SendSlots[i] = props->Send[i].Slot;
1250 if(!SendSlots[i] && i == 0)
1251 SendSlots[i] = ALContext->mDefaultSlot.get();
1252 if(!SendSlots[i] || SendSlots[i]->Params.EffectType == AL_EFFECT_NULL)
1254 SendSlots[i] = nullptr;
1255 voice->mSend[i].Buffer = {};
1257 else
1258 voice->mSend[i].Buffer = SendSlots[i]->Wet.Buffer;
1261 /* Calculate the stepping value */
1262 const auto Pitch = static_cast<float>(voice->mFrequency) /
1263 static_cast<float>(Device->Frequency) * props->Pitch;
1264 if(Pitch > float{MAX_PITCH})
1265 voice->mStep = MAX_PITCH<<FRACTIONBITS;
1266 else
1267 voice->mStep = maxu(fastf2u(Pitch * FRACTIONONE), 1);
1268 voice->mResampler = PrepareResampler(props->mResampler, voice->mStep, &voice->mResampleState);
1270 /* Calculate gains */
1271 const ALlistener &Listener = ALContext->mListener;
1272 GainTriplet DryGain;
1273 DryGain.Base = minf(clampf(props->Gain, props->MinGain, props->MaxGain) * props->Direct.Gain *
1274 Listener.Params.Gain, GAIN_MIX_MAX);
1275 DryGain.HF = props->Direct.GainHF;
1276 DryGain.LF = props->Direct.GainLF;
1277 GainTriplet WetGain[MAX_SENDS];
1278 for(ALuint i{0};i < Device->NumAuxSends;i++)
1280 WetGain[i].Base = minf(clampf(props->Gain, props->MinGain, props->MaxGain) *
1281 props->Send[i].Gain * Listener.Params.Gain, GAIN_MIX_MAX);
1282 WetGain[i].HF = props->Send[i].GainHF;
1283 WetGain[i].LF = props->Send[i].GainLF;
1286 CalcPanningAndFilters(voice, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, DryGain, WetGain, SendSlots, props,
1287 Listener, Device);
1290 void CalcAttnSourceParams(Voice *voice, const VoiceProps *props, const ALCcontext *ALContext)
1292 const ALCdevice *Device{ALContext->mDevice.get()};
1293 const ALuint NumSends{Device->NumAuxSends};
1294 const ALlistener &Listener = ALContext->mListener;
1296 /* Set mixing buffers and get send parameters. */
1297 voice->mDirect.Buffer = Device->Dry.Buffer;
1298 ALeffectslot *SendSlots[MAX_SENDS];
1299 float RoomRolloff[MAX_SENDS];
1300 GainTriplet DecayDistance[MAX_SENDS];
1301 for(ALuint i{0};i < NumSends;i++)
1303 SendSlots[i] = props->Send[i].Slot;
1304 if(!SendSlots[i] && i == 0)
1305 SendSlots[i] = ALContext->mDefaultSlot.get();
1306 if(!SendSlots[i] || SendSlots[i]->Params.EffectType == AL_EFFECT_NULL)
1308 SendSlots[i] = nullptr;
1309 RoomRolloff[i] = 0.0f;
1310 DecayDistance[i].Base = 0.0f;
1311 DecayDistance[i].LF = 0.0f;
1312 DecayDistance[i].HF = 0.0f;
1314 else if(SendSlots[i]->Params.AuxSendAuto)
1316 RoomRolloff[i] = SendSlots[i]->Params.RoomRolloff + props->RoomRolloffFactor;
1317 /* Calculate the distances to where this effect's decay reaches
1318 * -60dB.
1320 DecayDistance[i].Base = SendSlots[i]->Params.DecayTime * SPEEDOFSOUNDMETRESPERSEC;
1321 DecayDistance[i].LF = DecayDistance[i].Base * SendSlots[i]->Params.DecayLFRatio;
1322 DecayDistance[i].HF = DecayDistance[i].Base * SendSlots[i]->Params.DecayHFRatio;
1323 if(SendSlots[i]->Params.DecayHFLimit)
1325 const float airAbsorption{SendSlots[i]->Params.AirAbsorptionGainHF};
1326 if(airAbsorption < 1.0f)
1328 /* Calculate the distance to where this effect's air
1329 * absorption reaches -60dB, and limit the effect's HF
1330 * decay distance (so it doesn't take any longer to decay
1331 * than the air would allow).
1333 constexpr float log10_decaygain{-3.0f/*std::log10(REVERB_DECAY_GAIN)*/};
1334 const float absorb_dist{log10_decaygain / std::log10(airAbsorption)};
1335 DecayDistance[i].HF = minf(absorb_dist, DecayDistance[i].HF);
1339 else
1341 /* If the slot's auxiliary send auto is off, the data sent to the
1342 * effect slot is the same as the dry path, sans filter effects */
1343 RoomRolloff[i] = props->RolloffFactor;
1344 DecayDistance[i].Base = 0.0f;
1345 DecayDistance[i].LF = 0.0f;
1346 DecayDistance[i].HF = 0.0f;
1349 if(!SendSlots[i])
1350 voice->mSend[i].Buffer = {};
1351 else
1352 voice->mSend[i].Buffer = SendSlots[i]->Wet.Buffer;
1355 /* Transform source to listener space (convert to head relative) */
1356 alu::Vector Position{props->Position[0], props->Position[1], props->Position[2], 1.0f};
1357 alu::Vector Velocity{props->Velocity[0], props->Velocity[1], props->Velocity[2], 0.0f};
1358 alu::Vector Direction{props->Direction[0], props->Direction[1], props->Direction[2], 0.0f};
1359 if(props->HeadRelative == AL_FALSE)
1361 /* Transform source vectors */
1362 Position = Listener.Params.Matrix * Position;
1363 Velocity = Listener.Params.Matrix * Velocity;
1364 Direction = Listener.Params.Matrix * Direction;
1366 else
1368 /* Offset the source velocity to be relative of the listener velocity */
1369 Velocity += Listener.Params.Velocity;
1372 const bool directional{Direction.normalize() > 0.0f};
1373 alu::Vector ToSource{Position[0], Position[1], Position[2], 0.0f};
1374 const float Distance{ToSource.normalize()};
1376 /* Initial source gain */
1377 GainTriplet DryGain{props->Gain, 1.0f, 1.0f};
1378 GainTriplet WetGain[MAX_SENDS];
1379 for(ALuint i{0};i < NumSends;i++)
1380 WetGain[i] = DryGain;
1382 /* Calculate distance attenuation */
1383 float ClampedDist{Distance};
1385 switch(Listener.Params.SourceDistanceModel ?
1386 props->mDistanceModel : Listener.Params.mDistanceModel)
1388 case DistanceModel::InverseClamped:
1389 ClampedDist = clampf(ClampedDist, props->RefDistance, props->MaxDistance);
1390 if(props->MaxDistance < props->RefDistance) break;
1391 /*fall-through*/
1392 case DistanceModel::Inverse:
1393 if(!(props->RefDistance > 0.0f))
1394 ClampedDist = props->RefDistance;
1395 else
1397 float dist{lerp(props->RefDistance, ClampedDist, props->RolloffFactor)};
1398 if(dist > 0.0f) DryGain.Base *= props->RefDistance / dist;
1399 for(ALuint i{0};i < NumSends;i++)
1401 dist = lerp(props->RefDistance, ClampedDist, RoomRolloff[i]);
1402 if(dist > 0.0f) WetGain[i].Base *= props->RefDistance / dist;
1405 break;
1407 case DistanceModel::LinearClamped:
1408 ClampedDist = clampf(ClampedDist, props->RefDistance, props->MaxDistance);
1409 if(props->MaxDistance < props->RefDistance) break;
1410 /*fall-through*/
1411 case DistanceModel::Linear:
1412 if(!(props->MaxDistance != props->RefDistance))
1413 ClampedDist = props->RefDistance;
1414 else
1416 float attn{props->RolloffFactor * (ClampedDist-props->RefDistance) /
1417 (props->MaxDistance-props->RefDistance)};
1418 DryGain.Base *= maxf(1.0f - attn, 0.0f);
1419 for(ALuint i{0};i < NumSends;i++)
1421 attn = RoomRolloff[i] * (ClampedDist-props->RefDistance) /
1422 (props->MaxDistance-props->RefDistance);
1423 WetGain[i].Base *= maxf(1.0f - attn, 0.0f);
1426 break;
1428 case DistanceModel::ExponentClamped:
1429 ClampedDist = clampf(ClampedDist, props->RefDistance, props->MaxDistance);
1430 if(props->MaxDistance < props->RefDistance) break;
1431 /*fall-through*/
1432 case DistanceModel::Exponent:
1433 if(!(ClampedDist > 0.0f && props->RefDistance > 0.0f))
1434 ClampedDist = props->RefDistance;
1435 else
1437 const float dist_ratio{ClampedDist/props->RefDistance};
1438 DryGain.Base *= std::pow(dist_ratio, -props->RolloffFactor);
1439 for(ALuint i{0};i < NumSends;i++)
1440 WetGain[i].Base *= std::pow(dist_ratio, -RoomRolloff[i]);
1442 break;
1444 case DistanceModel::Disable:
1445 ClampedDist = props->RefDistance;
1446 break;
1449 /* Calculate directional soundcones */
1450 if(directional && props->InnerAngle < 360.0f)
1452 const float Angle{Rad2Deg(std::acos(-aluDotproduct(Direction, ToSource)) *
1453 ConeScale * 2.0f)};
1455 float ConeGain, ConeHF;
1456 if(!(Angle > props->InnerAngle))
1458 ConeGain = 1.0f;
1459 ConeHF = 1.0f;
1461 else if(Angle < props->OuterAngle)
1463 const float scale{(Angle-props->InnerAngle) / (props->OuterAngle-props->InnerAngle)};
1464 ConeGain = lerp(1.0f, props->OuterGain, scale);
1465 ConeHF = lerp(1.0f, props->OuterGainHF, scale);
1467 else
1469 ConeGain = props->OuterGain;
1470 ConeHF = props->OuterGainHF;
1473 DryGain.Base *= ConeGain;
1474 if(props->DryGainHFAuto)
1475 DryGain.HF *= ConeHF;
1476 if(props->WetGainAuto)
1477 std::for_each(std::begin(WetGain), std::begin(WetGain)+NumSends,
1478 [ConeGain](GainTriplet &gain) noexcept -> void { gain.Base *= ConeGain; });
1479 if(props->WetGainHFAuto)
1480 std::for_each(std::begin(WetGain), std::begin(WetGain)+NumSends,
1481 [ConeHF](GainTriplet &gain) noexcept -> void { gain.HF *= ConeHF; });
1484 /* Apply gain and frequency filters */
1485 DryGain.Base = minf(clampf(DryGain.Base, props->MinGain, props->MaxGain) * props->Direct.Gain *
1486 Listener.Params.Gain, GAIN_MIX_MAX);
1487 DryGain.HF *= props->Direct.GainHF;
1488 DryGain.LF *= props->Direct.GainLF;
1489 for(ALuint i{0};i < NumSends;i++)
1491 WetGain[i].Base = minf(clampf(WetGain[i].Base, props->MinGain, props->MaxGain) *
1492 props->Send[i].Gain * Listener.Params.Gain, GAIN_MIX_MAX);
1493 WetGain[i].HF *= props->Send[i].GainHF;
1494 WetGain[i].LF *= props->Send[i].GainLF;
1497 /* Distance-based air absorption and initial send decay. */
1498 if(ClampedDist > props->RefDistance && props->RolloffFactor > 0.0f)
1500 const float meters_base{(ClampedDist-props->RefDistance) * props->RolloffFactor *
1501 Listener.Params.MetersPerUnit};
1502 if(props->AirAbsorptionFactor > 0.0f)
1504 const float hfattn{std::pow(AIRABSORBGAINHF, meters_base*props->AirAbsorptionFactor)};
1505 DryGain.HF *= hfattn;
1506 std::for_each(std::begin(WetGain), std::begin(WetGain)+NumSends,
1507 [hfattn](GainTriplet &gain) noexcept -> void { gain.HF *= hfattn; });
1510 if(props->WetGainAuto)
1512 /* Apply a decay-time transformation to the wet path, based on the
1513 * source distance in meters. The initial decay of the reverb
1514 * effect is calculated and applied to the wet path.
1516 for(ALuint i{0};i < NumSends;i++)
1518 if(!(DecayDistance[i].Base > 0.0f))
1519 continue;
1521 const float gain{std::pow(REVERB_DECAY_GAIN, meters_base/DecayDistance[i].Base)};
1522 WetGain[i].Base *= gain;
1523 /* Yes, the wet path's air absorption is applied with
1524 * WetGainAuto on, rather than WetGainHFAuto.
1526 if(gain > 0.0f)
1528 float gainhf{std::pow(REVERB_DECAY_GAIN, meters_base/DecayDistance[i].HF)};
1529 WetGain[i].HF *= minf(gainhf / gain, 1.0f);
1530 float gainlf{std::pow(REVERB_DECAY_GAIN, meters_base/DecayDistance[i].LF)};
1531 WetGain[i].LF *= minf(gainlf / gain, 1.0f);
1538 /* Initial source pitch */
1539 float Pitch{props->Pitch};
1541 /* Calculate velocity-based doppler effect */
1542 float DopplerFactor{props->DopplerFactor * Listener.Params.DopplerFactor};
1543 if(DopplerFactor > 0.0f)
1545 const alu::Vector &lvelocity = Listener.Params.Velocity;
1546 float vss{aluDotproduct(Velocity, ToSource) * -DopplerFactor};
1547 float vls{aluDotproduct(lvelocity, ToSource) * -DopplerFactor};
1549 const float SpeedOfSound{Listener.Params.SpeedOfSound};
1550 if(!(vls < SpeedOfSound))
1552 /* Listener moving away from the source at the speed of sound.
1553 * Sound waves can't catch it.
1555 Pitch = 0.0f;
1557 else if(!(vss < SpeedOfSound))
1559 /* Source moving toward the listener at the speed of sound. Sound
1560 * waves bunch up to extreme frequencies.
1562 Pitch = std::numeric_limits<float>::infinity();
1564 else
1566 /* Source and listener movement is nominal. Calculate the proper
1567 * doppler shift.
1569 Pitch *= (SpeedOfSound-vls) / (SpeedOfSound-vss);
1573 /* Adjust pitch based on the buffer and output frequencies, and calculate
1574 * fixed-point stepping value.
1576 Pitch *= static_cast<float>(voice->mFrequency) / static_cast<float>(Device->Frequency);
1577 if(Pitch > float{MAX_PITCH})
1578 voice->mStep = MAX_PITCH<<FRACTIONBITS;
1579 else
1580 voice->mStep = maxu(fastf2u(Pitch * FRACTIONONE), 1);
1581 voice->mResampler = PrepareResampler(props->mResampler, voice->mStep, &voice->mResampleState);
1583 float spread{0.0f};
1584 if(props->Radius > Distance)
1585 spread = al::MathDefs<float>::Tau() - Distance/props->Radius*al::MathDefs<float>::Pi();
1586 else if(Distance > 0.0f)
1587 spread = std::asin(props->Radius/Distance) * 2.0f;
1589 CalcPanningAndFilters(voice, ToSource[0], ToSource[1], ToSource[2]*ZScale,
1590 Distance*Listener.Params.MetersPerUnit, spread, DryGain, WetGain, SendSlots, props,
1591 Listener, Device);
1594 void CalcSourceParams(Voice *voice, ALCcontext *context, bool force)
1596 VoicePropsItem *props{voice->mUpdate.exchange(nullptr, std::memory_order_acq_rel)};
1597 if(!props && !force) return;
1599 if(props)
1601 voice->mProps = *props;
1603 AtomicReplaceHead(context->mFreeVoiceProps, props);
1606 if((voice->mProps.DirectChannels != DirectMode::Off && voice->mFmtChannels != FmtMono
1607 && voice->mFmtChannels != FmtBFormat2D && voice->mFmtChannels != FmtBFormat3D)
1608 || voice->mProps.mSpatializeMode==SpatializeMode::Off
1609 || (voice->mProps.mSpatializeMode==SpatializeMode::Auto && voice->mFmtChannels != FmtMono))
1610 CalcNonAttnSourceParams(voice, &voice->mProps, context);
1611 else
1612 CalcAttnSourceParams(voice, &voice->mProps, context);
1616 void SendSourceStateEvent(ALCcontext *context, ALuint id, ALenum state)
1618 RingBuffer *ring{context->mAsyncEvents.get()};
1619 auto evt_vec = ring->getWriteVector();
1620 if(evt_vec.first.len < 1) return;
1622 AsyncEvent *evt{::new(evt_vec.first.buf) AsyncEvent{EventType_SourceStateChange}};
1623 evt->u.srcstate.id = id;
1624 evt->u.srcstate.state = state;
1626 ring->writeAdvance(1);
1629 void ProcessVoiceChanges(ALCcontext *ctx)
1631 VoiceChange *cur{ctx->mCurrentVoiceChange.load(std::memory_order_acquire)};
1632 VoiceChange *next{cur->mNext.load(std::memory_order_acquire)};
1633 if(!next) return;
1635 const ALbitfieldSOFT enabledevt{ctx->mEnabledEvts.load(std::memory_order_acquire)};
1636 do {
1637 cur = next;
1639 bool sendevt{false};
1640 if(cur->mState == AL_INITIAL || cur->mState == AL_STOPPED)
1642 if(Voice *voice{cur->mVoice})
1644 voice->mCurrentBuffer.store(nullptr, std::memory_order_relaxed);
1645 voice->mLoopBuffer.store(nullptr, std::memory_order_relaxed);
1646 voice->mSourceID.store(0u, std::memory_order_relaxed);
1647 Voice::State oldvstate{Voice::Playing};
1648 sendevt = voice->mPlayState.compare_exchange_strong(oldvstate, Voice::Stopping,
1649 std::memory_order_relaxed, std::memory_order_acquire);
1650 voice->mPendingChange.store(false, std::memory_order_release);
1652 /* AL_INITIAL state change events are always sent, even if the
1653 * voice is already stopped or even if there is no voice.
1655 sendevt |= (cur->mState == AL_INITIAL);
1657 else if(cur->mState == AL_PAUSED)
1659 Voice *voice{cur->mVoice};
1660 Voice::State oldvstate{Voice::Playing};
1661 sendevt = voice->mPlayState.compare_exchange_strong(oldvstate, Voice::Stopping,
1662 std::memory_order_release, std::memory_order_acquire);
1664 else if(cur->mState == AL_PLAYING)
1666 /* NOTE: When playing a voice, sending a source state change event
1667 * depends if there's an old voice to stop and if that stop is
1668 * successful. If there is no old voice, a playing event is always
1669 * sent. If there is an old voice, an event is sent only if the
1670 * voice is already stopped.
1672 if(Voice *oldvoice{cur->mOldVoice})
1674 oldvoice->mCurrentBuffer.store(nullptr, std::memory_order_relaxed);
1675 oldvoice->mLoopBuffer.store(nullptr, std::memory_order_relaxed);
1676 oldvoice->mSourceID.store(0u, std::memory_order_relaxed);
1677 Voice::State oldvstate{Voice::Playing};
1678 sendevt = !oldvoice->mPlayState.compare_exchange_strong(oldvstate, Voice::Stopping,
1679 std::memory_order_relaxed, std::memory_order_acquire);
1680 oldvoice->mPendingChange.store(false, std::memory_order_release);
1682 else
1683 sendevt = true;
1685 Voice *voice{cur->mVoice};
1686 voice->mPlayState.store(Voice::Playing, std::memory_order_release);
1688 else if(cur->mState == AL_SAMPLE_OFFSET)
1690 /* Changing a voice offset never sends a source change event. */
1691 Voice *oldvoice{cur->mOldVoice};
1692 oldvoice->mCurrentBuffer.store(nullptr, std::memory_order_relaxed);
1693 oldvoice->mLoopBuffer.store(nullptr, std::memory_order_relaxed);
1694 /* If there's no sourceID, the old voice finished so don't start
1695 * the new one at its new offset.
1697 if(oldvoice->mSourceID.exchange(0u, std::memory_order_relaxed) != 0u)
1699 /* Otherwise, set the voice to stopping if it's not already (it
1700 * might already be, if paused), and play the new voice as
1701 * appropriate.
1703 Voice::State oldvstate{Voice::Playing};
1704 oldvoice->mPlayState.compare_exchange_strong(oldvstate, Voice::Stopping,
1705 std::memory_order_relaxed, std::memory_order_acquire);
1707 Voice *voice{cur->mVoice};
1708 voice->mPlayState.store((oldvstate == Voice::Playing) ? Voice::Playing
1709 : Voice::Stopped, std::memory_order_release);
1711 oldvoice->mPendingChange.store(false, std::memory_order_release);
1713 if(sendevt && (enabledevt&EventType_SourceStateChange))
1714 SendSourceStateEvent(ctx, cur->mSourceID, cur->mState);
1716 next = cur->mNext.load(std::memory_order_acquire);
1717 } while(next);
1718 ctx->mCurrentVoiceChange.store(cur, std::memory_order_release);
1721 void ProcessParamUpdates(ALCcontext *ctx, const ALeffectslotArray &slots,
1722 const al::span<Voice*> voices)
1724 ProcessVoiceChanges(ctx);
1726 IncrementRef(ctx->mUpdateCount);
1727 if LIKELY(!ctx->mHoldUpdates.load(std::memory_order_acquire))
1729 bool force{CalcContextParams(ctx)};
1730 force |= CalcListenerParams(ctx);
1731 auto sorted_slots = const_cast<ALeffectslot**>(slots.data() + slots.size());
1732 for(ALeffectslot *slot : slots)
1733 force |= CalcEffectSlotParams(slot, sorted_slots, ctx);
1735 for(Voice *voice : voices)
1737 /* Only update voices that have a source. */
1738 if(voice->mSourceID.load(std::memory_order_relaxed) != 0)
1739 CalcSourceParams(voice, ctx, force);
1742 IncrementRef(ctx->mUpdateCount);
1745 void ProcessContexts(ALCdevice *device, const ALuint SamplesToDo)
1747 ASSUME(SamplesToDo > 0);
1749 for(ALCcontext *ctx : *device->mContexts.load(std::memory_order_acquire))
1751 const ALeffectslotArray &auxslots = *ctx->mActiveAuxSlots.load(std::memory_order_acquire);
1752 const al::span<Voice*> voices{ctx->getVoicesSpanAcquired()};
1754 /* Process pending propery updates for objects on the context. */
1755 ProcessParamUpdates(ctx, auxslots, voices);
1757 /* Clear auxiliary effect slot mixing buffers. */
1758 for(ALeffectslot *slot : auxslots)
1760 for(auto &buffer : slot->MixBuffer)
1761 buffer.fill(0.0f);
1764 /* Process voices that have a playing source. */
1765 for(Voice *voice : voices)
1767 const Voice::State vstate{voice->mPlayState.load(std::memory_order_acquire)};
1768 if(vstate != Voice::Stopped && vstate != Voice::Pending)
1769 voice->mix(vstate, ctx, SamplesToDo);
1772 /* Process effects. */
1773 if(const size_t num_slots{auxslots.size()})
1775 auto slots = auxslots.data();
1776 auto slots_end = slots + num_slots;
1778 /* First sort the slots into extra storage, so that effects come
1779 * before their effect target (or their targets' target).
1781 auto sorted_slots = const_cast<ALeffectslot**>(slots_end);
1782 auto sorted_slots_end = sorted_slots;
1783 if(*sorted_slots)
1785 /* Skip sorting if it has already been done. */
1786 sorted_slots_end += num_slots;
1787 goto skip_sorting;
1790 *sorted_slots_end = *slots;
1791 ++sorted_slots_end;
1792 while(++slots != slots_end)
1794 auto in_chain = [](const ALeffectslot *s1, const ALeffectslot *s2) noexcept -> bool
1796 while((s1=s1->Params.Target) != nullptr) {
1797 if(s1 == s2) return true;
1799 return false;
1802 /* If this effect slot targets an effect slot already in the
1803 * list (i.e. slots outputs to something in sorted_slots),
1804 * directly or indirectly, insert it prior to that element.
1806 auto checker = sorted_slots;
1807 do {
1808 if(in_chain(*slots, *checker)) break;
1809 } while(++checker != sorted_slots_end);
1811 checker = std::move_backward(checker, sorted_slots_end, sorted_slots_end+1);
1812 *--checker = *slots;
1813 ++sorted_slots_end;
1816 skip_sorting:
1817 auto process_effect = [SamplesToDo](const ALeffectslot *slot) -> void
1819 EffectState *state{slot->Params.mEffectState};
1820 state->process(SamplesToDo, slot->Wet.Buffer, state->mOutTarget);
1822 std::for_each(sorted_slots, sorted_slots_end, process_effect);
1825 /* Signal the event handler if there are any events to read. */
1826 RingBuffer *ring{ctx->mAsyncEvents.get()};
1827 if(ring->readSpace() > 0)
1828 ctx->mEventSem.post();
1833 void ApplyDistanceComp(const al::span<FloatBufferLine> Samples, const size_t SamplesToDo,
1834 const DistanceComp::DistData *distcomp)
1836 ASSUME(SamplesToDo > 0);
1838 for(auto &chanbuffer : Samples)
1840 const float gain{distcomp->Gain};
1841 const size_t base{distcomp->Length};
1842 float *distbuf{al::assume_aligned<16>(distcomp->Buffer)};
1843 ++distcomp;
1845 if(base < 1)
1846 continue;
1848 float *inout{al::assume_aligned<16>(chanbuffer.data())};
1849 auto inout_end = inout + SamplesToDo;
1850 if LIKELY(SamplesToDo >= base)
1852 auto delay_end = std::rotate(inout, inout_end - base, inout_end);
1853 std::swap_ranges(inout, delay_end, distbuf);
1855 else
1857 auto delay_start = std::swap_ranges(inout, inout_end, distbuf);
1858 std::rotate(distbuf, delay_start, distbuf + base);
1860 std::transform(inout, inout_end, inout, std::bind(std::multiplies<float>{}, _1, gain));
1864 void ApplyDither(const al::span<FloatBufferLine> Samples, ALuint *dither_seed,
1865 const float quant_scale, const size_t SamplesToDo)
1867 ASSUME(SamplesToDo > 0);
1869 /* Dithering. Generate whitenoise (uniform distribution of random values
1870 * between -1 and +1) and add it to the sample values, after scaling up to
1871 * the desired quantization depth amd before rounding.
1873 const float invscale{1.0f / quant_scale};
1874 ALuint seed{*dither_seed};
1875 auto dither_sample = [&seed,invscale,quant_scale](const float sample) noexcept -> float
1877 float val{sample * quant_scale};
1878 ALuint rng0{dither_rng(&seed)};
1879 ALuint rng1{dither_rng(&seed)};
1880 val += static_cast<float>(rng0*(1.0/UINT_MAX) - rng1*(1.0/UINT_MAX));
1881 return fast_roundf(val) * invscale;
1883 for(FloatBufferLine &inout : Samples)
1884 std::transform(inout.begin(), inout.begin()+SamplesToDo, inout.begin(), dither_sample);
1885 *dither_seed = seed;
1889 /* Base template left undefined. Should be marked =delete, but Clang 3.8.1
1890 * chokes on that given the inline specializations.
1892 template<typename T>
1893 inline T SampleConv(float) noexcept;
1895 template<> inline float SampleConv(float val) noexcept
1896 { return val; }
1897 template<> inline int32_t SampleConv(float val) noexcept
1899 /* Floats have a 23-bit mantissa, plus an implied 1 bit and a sign bit.
1900 * This means a normalized float has at most 25 bits of signed precision.
1901 * When scaling and clamping for a signed 32-bit integer, these following
1902 * values are the best a float can give.
1904 return fastf2i(clampf(val*2147483648.0f, -2147483648.0f, 2147483520.0f));
1906 template<> inline int16_t SampleConv(float val) noexcept
1907 { return static_cast<int16_t>(fastf2i(clampf(val*32768.0f, -32768.0f, 32767.0f))); }
1908 template<> inline int8_t SampleConv(float val) noexcept
1909 { return static_cast<int8_t>(fastf2i(clampf(val*128.0f, -128.0f, 127.0f))); }
1911 /* Define unsigned output variations. */
1912 template<> inline uint32_t SampleConv(float val) noexcept
1913 { return static_cast<uint32_t>(SampleConv<int32_t>(val)) + 2147483648u; }
1914 template<> inline uint16_t SampleConv(float val) noexcept
1915 { return static_cast<uint16_t>(SampleConv<int16_t>(val) + 32768); }
1916 template<> inline uint8_t SampleConv(float val) noexcept
1917 { return static_cast<uint8_t>(SampleConv<int8_t>(val) + 128); }
1919 template<DevFmtType T>
1920 void Write(const al::span<const FloatBufferLine> InBuffer, void *OutBuffer, const size_t Offset,
1921 const size_t SamplesToDo, const size_t FrameStep)
1923 using SampleType = typename DevFmtTypeTraits<T>::Type;
1925 ASSUME(FrameStep > 0);
1926 ASSUME(SamplesToDo > 0);
1928 SampleType *outbase = static_cast<SampleType*>(OutBuffer) + Offset*FrameStep;
1929 for(const FloatBufferLine &inbuf : InBuffer)
1931 SampleType *out{outbase++};
1932 auto conv_sample = [FrameStep,&out](const float s) noexcept -> void
1934 *out = SampleConv<SampleType>(s);
1935 out += FrameStep;
1937 std::for_each(inbuf.begin(), inbuf.begin()+SamplesToDo, conv_sample);
1941 } // namespace
1943 void ALCdevice::renderSamples(void *outBuffer, const ALuint numSamples, const size_t frameStep)
1945 FPUCtl mixer_mode{};
1946 for(ALuint written{0u};written < numSamples;)
1948 const ALuint samplesToDo{minu(numSamples-written, BUFFERSIZE)};
1950 /* Clear main mixing buffers. */
1951 for(FloatBufferLine &buffer : MixBuffer)
1952 buffer.fill(0.0f);
1954 /* Increment the mix count at the start (lsb should now be 1). */
1955 IncrementRef(MixCount);
1957 /* Process and mix each context's sources and effects. */
1958 ProcessContexts(this, samplesToDo);
1960 /* Increment the clock time. Every second's worth of samples is
1961 * converted and added to clock base so that large sample counts don't
1962 * overflow during conversion. This also guarantees a stable
1963 * conversion.
1965 SamplesDone += samplesToDo;
1966 ClockBase += std::chrono::seconds{SamplesDone / Frequency};
1967 SamplesDone %= Frequency;
1969 /* Increment the mix count at the end (lsb should now be 0). */
1970 IncrementRef(MixCount);
1972 /* Apply any needed post-process for finalizing the Dry mix to the
1973 * RealOut (Ambisonic decode, UHJ encode, etc).
1975 postProcess(samplesToDo);
1977 /* Apply compression, limiting sample amplitude if needed or desired. */
1978 if(Limiter) Limiter->process(samplesToDo, RealOut.Buffer.data());
1980 /* Apply delays and attenuation for mismatched speaker distances. */
1981 ApplyDistanceComp(RealOut.Buffer, samplesToDo, ChannelDelay.as_span().cbegin());
1983 /* Apply dithering. The compressor should have left enough headroom for
1984 * the dither noise to not saturate.
1986 if(DitherDepth > 0.0f)
1987 ApplyDither(RealOut.Buffer, &DitherSeed, DitherDepth, samplesToDo);
1989 if LIKELY(outBuffer)
1991 /* Finally, interleave and convert samples, writing to the device's
1992 * output buffer.
1994 switch(FmtType)
1996 #define HANDLE_WRITE(T) case T: \
1997 Write<T>(RealOut.Buffer, outBuffer, written, samplesToDo, frameStep); break;
1998 HANDLE_WRITE(DevFmtByte)
1999 HANDLE_WRITE(DevFmtUByte)
2000 HANDLE_WRITE(DevFmtShort)
2001 HANDLE_WRITE(DevFmtUShort)
2002 HANDLE_WRITE(DevFmtInt)
2003 HANDLE_WRITE(DevFmtUInt)
2004 HANDLE_WRITE(DevFmtFloat)
2005 #undef HANDLE_WRITE
2009 written += samplesToDo;
2013 void ALCdevice::handleDisconnect(const char *msg, ...)
2015 if(!Connected.exchange(false, std::memory_order_acq_rel))
2016 return;
2018 AsyncEvent evt{EventType_Disconnected};
2019 evt.u.user.type = AL_EVENT_TYPE_DISCONNECTED_SOFT;
2020 evt.u.user.id = 0;
2021 evt.u.user.param = 0;
2023 va_list args;
2024 va_start(args, msg);
2025 int msglen{vsnprintf(evt.u.user.msg, sizeof(evt.u.user.msg), msg, args)};
2026 va_end(args);
2028 if(msglen < 0 || static_cast<size_t>(msglen) >= sizeof(evt.u.user.msg))
2029 evt.u.user.msg[sizeof(evt.u.user.msg)-1] = 0;
2031 IncrementRef(MixCount);
2032 for(ALCcontext *ctx : *mContexts.load())
2034 const ALbitfieldSOFT enabledevt{ctx->mEnabledEvts.load(std::memory_order_acquire)};
2035 if((enabledevt&EventType_Disconnected))
2037 RingBuffer *ring{ctx->mAsyncEvents.get()};
2038 auto evt_data = ring->getWriteVector().first;
2039 if(evt_data.len > 0)
2041 ::new(evt_data.buf) AsyncEvent{evt};
2042 ring->writeAdvance(1);
2043 ctx->mEventSem.post();
2047 auto voicelist = ctx->getVoicesSpanAcquired();
2048 auto stop_voice = [](Voice *voice) -> void
2050 voice->mCurrentBuffer.store(nullptr, std::memory_order_relaxed);
2051 voice->mLoopBuffer.store(nullptr, std::memory_order_relaxed);
2052 voice->mSourceID.store(0u, std::memory_order_relaxed);
2053 voice->mPlayState.store(Voice::Stopped, std::memory_order_release);
2055 std::for_each(voicelist.begin(), voicelist.end(), stop_voice);
2057 IncrementRef(MixCount);