Use a span for the band-splitter input
[openal-soft.git] / alc / alu.cpp
blobda1d72b99df700d26c71bd21a6a4953344fc3f6b
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_modes.h"
69 #include "hrtf.h"
70 #include "inprogext.h"
71 #include "mastering.h"
72 #include "math_defs.h"
73 #include "mixer/defs.h"
74 #include "opthelpers.h"
75 #include "ringbuffer.h"
76 #include "strutils.h"
77 #include "threads.h"
78 #include "uhjfilter.h"
79 #include "vecmat.h"
80 #include "voice.h"
82 #include "bsinc_inc.h"
85 static_assert(!(MAX_RESAMPLER_PADDING&1) && MAX_RESAMPLER_PADDING >= bsinc24.m[0],
86 "MAX_RESAMPLER_PADDING is not a multiple of two, or is too small");
89 namespace {
91 using namespace std::placeholders;
93 ALfloat InitConeScale()
95 ALfloat ret{1.0f};
96 if(auto optval = al::getenv("__ALSOFT_HALF_ANGLE_CONES"))
98 if(al::strcasecmp(optval->c_str(), "true") == 0
99 || strtol(optval->c_str(), nullptr, 0) == 1)
100 ret *= 0.5f;
102 return ret;
105 ALfloat InitZScale()
107 ALfloat ret{1.0f};
108 if(auto optval = al::getenv("__ALSOFT_REVERSE_Z"))
110 if(al::strcasecmp(optval->c_str(), "true") == 0
111 || strtol(optval->c_str(), nullptr, 0) == 1)
112 ret *= -1.0f;
114 return ret;
117 } // namespace
119 /* Cone scalar */
120 const ALfloat ConeScale{InitConeScale()};
122 /* Localized Z scalar for mono sources */
123 const ALfloat ZScale{InitZScale()};
125 MixerFunc MixSamples{Mix_<CTag>};
126 RowMixerFunc MixRowSamples{MixRow_<CTag>};
128 namespace {
130 struct ChanMap {
131 Channel channel;
132 ALfloat angle;
133 ALfloat elevation;
136 HrtfDirectMixerFunc MixDirectHrtf = MixDirectHrtf_<CTag>;
138 inline MixerFunc SelectMixer()
140 #ifdef HAVE_NEON
141 if((CPUCapFlags&CPU_CAP_NEON))
142 return Mix_<NEONTag>;
143 #endif
144 #ifdef HAVE_SSE
145 if((CPUCapFlags&CPU_CAP_SSE))
146 return Mix_<SSETag>;
147 #endif
148 return Mix_<CTag>;
151 inline RowMixerFunc SelectRowMixer()
153 #ifdef HAVE_NEON
154 if((CPUCapFlags&CPU_CAP_NEON))
155 return MixRow_<NEONTag>;
156 #endif
157 #ifdef HAVE_SSE
158 if((CPUCapFlags&CPU_CAP_SSE))
159 return MixRow_<SSETag>;
160 #endif
161 return MixRow_<CTag>;
164 inline HrtfDirectMixerFunc SelectHrtfMixer(void)
166 #ifdef HAVE_NEON
167 if((CPUCapFlags&CPU_CAP_NEON))
168 return MixDirectHrtf_<NEONTag>;
169 #endif
170 #ifdef HAVE_SSE
171 if((CPUCapFlags&CPU_CAP_SSE))
172 return MixDirectHrtf_<SSETag>;
173 #endif
175 return MixDirectHrtf_<CTag>;
179 inline void BsincPrepare(const ALuint increment, BsincState *state, const BSincTable *table)
181 size_t si{BSINC_SCALE_COUNT - 1};
182 float sf{0.0f};
184 if(increment > FRACTIONONE)
186 sf = FRACTIONONE / static_cast<float>(increment);
187 sf = maxf(0.0f, (BSINC_SCALE_COUNT-1) * (sf-table->scaleBase) * table->scaleRange);
188 si = float2uint(sf);
189 /* The interpolation factor is fit to this diagonally-symmetric curve
190 * to reduce the transition ripple caused by interpolating different
191 * scales of the sinc function.
193 sf = 1.0f - std::cos(std::asin(sf - static_cast<float>(si)));
196 state->sf = sf;
197 state->m = table->m[si];
198 state->l = (state->m/2) - 1;
199 state->filter = table->Tab + table->filterOffset[si];
202 inline ResamplerFunc SelectResampler(Resampler resampler, ALuint increment)
204 switch(resampler)
206 case Resampler::Point:
207 return Resample_<PointTag,CTag>;
208 case Resampler::Linear:
209 #ifdef HAVE_NEON
210 if((CPUCapFlags&CPU_CAP_NEON))
211 return Resample_<LerpTag,NEONTag>;
212 #endif
213 #ifdef HAVE_SSE4_1
214 if((CPUCapFlags&CPU_CAP_SSE4_1))
215 return Resample_<LerpTag,SSE4Tag>;
216 #endif
217 #ifdef HAVE_SSE2
218 if((CPUCapFlags&CPU_CAP_SSE2))
219 return Resample_<LerpTag,SSE2Tag>;
220 #endif
221 return Resample_<LerpTag,CTag>;
222 case Resampler::Cubic:
223 return Resample_<CubicTag,CTag>;
224 case Resampler::BSinc12:
225 case Resampler::BSinc24:
226 if(increment <= FRACTIONONE)
228 /* fall-through */
229 case Resampler::FastBSinc12:
230 case Resampler::FastBSinc24:
231 #ifdef HAVE_NEON
232 if((CPUCapFlags&CPU_CAP_NEON))
233 return Resample_<FastBSincTag,NEONTag>;
234 #endif
235 #ifdef HAVE_SSE
236 if((CPUCapFlags&CPU_CAP_SSE))
237 return Resample_<FastBSincTag,SSETag>;
238 #endif
239 return Resample_<FastBSincTag,CTag>;
241 #ifdef HAVE_NEON
242 if((CPUCapFlags&CPU_CAP_NEON))
243 return Resample_<BSincTag,NEONTag>;
244 #endif
245 #ifdef HAVE_SSE
246 if((CPUCapFlags&CPU_CAP_SSE))
247 return Resample_<BSincTag,SSETag>;
248 #endif
249 return Resample_<BSincTag,CTag>;
252 return Resample_<PointTag,CTag>;
255 } // namespace
257 void aluInit(void)
259 MixSamples = SelectMixer();
260 MixRowSamples = SelectRowMixer();
261 MixDirectHrtf = SelectHrtfMixer();
265 ResamplerFunc PrepareResampler(Resampler resampler, ALuint increment, InterpState *state)
267 switch(resampler)
269 case Resampler::Point:
270 case Resampler::Linear:
271 case Resampler::Cubic:
272 break;
273 case Resampler::FastBSinc12:
274 case Resampler::BSinc12:
275 BsincPrepare(increment, &state->bsinc, &bsinc12);
276 break;
277 case Resampler::FastBSinc24:
278 case Resampler::BSinc24:
279 BsincPrepare(increment, &state->bsinc, &bsinc24);
280 break;
282 return SelectResampler(resampler, increment);
286 void ALCdevice::ProcessHrtf(const size_t SamplesToDo)
288 /* HRTF is stereo output only. */
289 const ALuint lidx{RealOut.ChannelIndex[FrontLeft]};
290 const ALuint ridx{RealOut.ChannelIndex[FrontRight]};
292 MixDirectHrtf(RealOut.Buffer[lidx], RealOut.Buffer[ridx], Dry.Buffer, HrtfAccumData,
293 mHrtfState.get(), SamplesToDo);
296 void ALCdevice::ProcessAmbiDec(const size_t SamplesToDo)
298 AmbiDecoder->process(RealOut.Buffer, Dry.Buffer.data(), SamplesToDo);
301 void ALCdevice::ProcessUhj(const size_t SamplesToDo)
303 /* UHJ is stereo output only. */
304 const ALuint lidx{RealOut.ChannelIndex[FrontLeft]};
305 const ALuint ridx{RealOut.ChannelIndex[FrontRight]};
307 /* Encode to stereo-compatible 2-channel UHJ output. */
308 Uhj_Encoder->encode(RealOut.Buffer[lidx], RealOut.Buffer[ridx], Dry.Buffer.data(),
309 SamplesToDo);
312 void ALCdevice::ProcessBs2b(const size_t SamplesToDo)
314 /* First, decode the ambisonic mix to the "real" output. */
315 AmbiDecoder->process(RealOut.Buffer, Dry.Buffer.data(), SamplesToDo);
317 /* BS2B is stereo output only. */
318 const ALuint lidx{RealOut.ChannelIndex[FrontLeft]};
319 const ALuint ridx{RealOut.ChannelIndex[FrontRight]};
321 /* Now apply the BS2B binaural/crossfeed filter. */
322 bs2b_cross_feed(Bs2b.get(), RealOut.Buffer[lidx].data(), RealOut.Buffer[ridx].data(),
323 SamplesToDo);
327 namespace {
329 /* This RNG method was created based on the math found in opusdec. It's quick,
330 * and starting with a seed value of 22222, is suitable for generating
331 * whitenoise.
333 inline ALuint dither_rng(ALuint *seed) noexcept
335 *seed = (*seed * 96314165) + 907633515;
336 return *seed;
340 auto GetAmbiScales(AmbiNorm scaletype) noexcept -> const std::array<float,MAX_AMBI_CHANNELS>&
342 if(scaletype == AmbiNorm::FuMa) return AmbiScale::FromFuMa;
343 if(scaletype == AmbiNorm::SN3D) return AmbiScale::FromSN3D;
344 return AmbiScale::FromN3D;
347 auto GetAmbiLayout(AmbiLayout layouttype) noexcept -> const std::array<uint8_t,MAX_AMBI_CHANNELS>&
349 if(layouttype == AmbiLayout::FuMa) return AmbiIndex::FromFuMa;
350 return AmbiIndex::FromACN;
353 auto GetAmbi2DLayout(AmbiLayout layouttype) noexcept -> const std::array<uint8_t,MAX_AMBI2D_CHANNELS>&
355 if(layouttype == AmbiLayout::FuMa) return AmbiIndex::FromFuMa2D;
356 return AmbiIndex::From2D;
360 inline alu::Vector aluCrossproduct(const alu::Vector &in1, const alu::Vector &in2)
362 return alu::Vector{
363 in1[1]*in2[2] - in1[2]*in2[1],
364 in1[2]*in2[0] - in1[0]*in2[2],
365 in1[0]*in2[1] - in1[1]*in2[0],
366 0.0f
370 inline ALfloat aluDotproduct(const alu::Vector &vec1, const alu::Vector &vec2)
372 return vec1[0]*vec2[0] + vec1[1]*vec2[1] + vec1[2]*vec2[2];
376 alu::Vector operator*(const alu::Matrix &mtx, const alu::Vector &vec) noexcept
378 return alu::Vector{
379 vec[0]*mtx[0][0] + vec[1]*mtx[1][0] + vec[2]*mtx[2][0] + vec[3]*mtx[3][0],
380 vec[0]*mtx[0][1] + vec[1]*mtx[1][1] + vec[2]*mtx[2][1] + vec[3]*mtx[3][1],
381 vec[0]*mtx[0][2] + vec[1]*mtx[1][2] + vec[2]*mtx[2][2] + vec[3]*mtx[3][2],
382 vec[0]*mtx[0][3] + vec[1]*mtx[1][3] + vec[2]*mtx[2][3] + vec[3]*mtx[3][3]
387 bool CalcContextParams(ALCcontext *Context)
389 ALcontextProps *props{Context->mUpdate.exchange(nullptr, std::memory_order_acq_rel)};
390 if(!props) return false;
392 ALlistener &Listener = Context->mListener;
393 Listener.Params.DopplerFactor = props->DopplerFactor;
394 Listener.Params.SpeedOfSound = props->SpeedOfSound * props->DopplerVelocity;
396 Listener.Params.SourceDistanceModel = props->SourceDistanceModel;
397 Listener.Params.mDistanceModel = props->mDistanceModel;
399 AtomicReplaceHead(Context->mFreeContextProps, props);
400 return true;
403 bool CalcListenerParams(ALCcontext *Context)
405 ALlistener &Listener = Context->mListener;
407 ALlistenerProps *props{Listener.Params.Update.exchange(nullptr, std::memory_order_acq_rel)};
408 if(!props) return false;
410 /* AT then UP */
411 alu::Vector N{props->OrientAt[0], props->OrientAt[1], props->OrientAt[2], 0.0f};
412 N.normalize();
413 alu::Vector V{props->OrientUp[0], props->OrientUp[1], props->OrientUp[2], 0.0f};
414 V.normalize();
415 /* Build and normalize right-vector */
416 alu::Vector U{aluCrossproduct(N, V)};
417 U.normalize();
419 Listener.Params.Matrix = alu::Matrix{
420 U[0], V[0], -N[0], 0.0f,
421 U[1], V[1], -N[1], 0.0f,
422 U[2], V[2], -N[2], 0.0f,
423 0.0f, 0.0f, 0.0f, 1.0f
426 const alu::Vector P{Listener.Params.Matrix *
427 alu::Vector{props->Position[0], props->Position[1], props->Position[2], 1.0f}};
428 Listener.Params.Matrix.setRow(3, -P[0], -P[1], -P[2], 1.0f);
430 const alu::Vector vel{props->Velocity[0], props->Velocity[1], props->Velocity[2], 0.0f};
431 Listener.Params.Velocity = Listener.Params.Matrix * vel;
433 Listener.Params.Gain = props->Gain * Context->mGainBoost;
434 Listener.Params.MetersPerUnit = props->MetersPerUnit;
436 AtomicReplaceHead(Context->mFreeListenerProps, props);
437 return true;
440 bool CalcEffectSlotParams(ALeffectslot *slot, ALCcontext *context)
442 ALeffectslotProps *props{slot->Params.Update.exchange(nullptr, std::memory_order_acq_rel)};
443 if(!props) return false;
445 slot->Params.Gain = props->Gain;
446 slot->Params.AuxSendAuto = props->AuxSendAuto;
447 slot->Params.Target = props->Target;
448 slot->Params.EffectType = props->Type;
449 slot->Params.mEffectProps = props->Props;
450 if(IsReverbEffect(props->Type))
452 slot->Params.RoomRolloff = props->Props.Reverb.RoomRolloffFactor;
453 slot->Params.DecayTime = props->Props.Reverb.DecayTime;
454 slot->Params.DecayLFRatio = props->Props.Reverb.DecayLFRatio;
455 slot->Params.DecayHFRatio = props->Props.Reverb.DecayHFRatio;
456 slot->Params.DecayHFLimit = props->Props.Reverb.DecayHFLimit;
457 slot->Params.AirAbsorptionGainHF = props->Props.Reverb.AirAbsorptionGainHF;
459 else
461 slot->Params.RoomRolloff = 0.0f;
462 slot->Params.DecayTime = 0.0f;
463 slot->Params.DecayLFRatio = 0.0f;
464 slot->Params.DecayHFRatio = 0.0f;
465 slot->Params.DecayHFLimit = AL_FALSE;
466 slot->Params.AirAbsorptionGainHF = 1.0f;
469 EffectState *state{props->State};
470 props->State = nullptr;
471 EffectState *oldstate{slot->Params.mEffectState};
472 slot->Params.mEffectState = state;
474 /* Only release the old state if it won't get deleted, since we can't be
475 * deleting/freeing anything in the mixer.
477 if(!oldstate->releaseIfNoDelete())
479 /* Otherwise, if it would be deleted send it off with a release event. */
480 RingBuffer *ring{context->mAsyncEvents.get()};
481 auto evt_vec = ring->getWriteVector();
482 if LIKELY(evt_vec.first.len > 0)
484 AsyncEvent *evt{new (evt_vec.first.buf) AsyncEvent{EventType_ReleaseEffectState}};
485 evt->u.mEffectState = oldstate;
486 ring->writeAdvance(1);
487 context->mEventSem.post();
489 else
491 /* If writing the event failed, the queue was probably full. Store
492 * the old state in the property object where it can eventually be
493 * cleaned up sometime later (not ideal, but better than blocking
494 * or leaking).
496 props->State = oldstate;
500 AtomicReplaceHead(context->mFreeEffectslotProps, props);
502 EffectTarget output;
503 if(ALeffectslot *target{slot->Params.Target})
504 output = EffectTarget{&target->Wet, nullptr};
505 else
507 ALCdevice *device{context->mDevice.get()};
508 output = EffectTarget{&device->Dry, &device->RealOut};
510 state->update(context, slot, &slot->Params.mEffectProps, output);
511 return true;
515 /* Scales the given azimuth toward the side (+/- pi/2 radians) for positions in
516 * front.
518 inline float ScaleAzimuthFront(float azimuth, float scale)
520 const ALfloat abs_azi{std::fabs(azimuth)};
521 if(!(abs_azi >= al::MathDefs<float>::Pi()*0.5f))
522 return std::copysign(minf(abs_azi*scale, al::MathDefs<float>::Pi()*0.5f), azimuth);
523 return azimuth;
527 /* Begin ambisonic rotation helpers.
529 * Rotating first-order B-Format just needs a straight-forward X/Y/Z rotation
530 * matrix. Higher orders, however, are more complicated. The method implemented
531 * here is a recursive algorithm (the rotation for first-order is used to help
532 * generate the second-order rotation, which helps generate the third-order
533 * rotation, etc).
535 * Adapted from
536 * <https://github.com/polarch/Spherical-Harmonic-Transform/blob/master/getSHrotMtx.m>,
537 * provided under the BSD 3-Clause license.
539 * Copyright (c) 2015, Archontis Politis
540 * Copyright (c) 2019, Christopher Robinson
542 * The u, v, and w coefficients used for generating higher-order rotations are
543 * precomputed since they're constant. The second-order coefficients are
544 * followed by the third-order coefficients, etc.
546 struct RotatorCoeffs {
547 float u, v, w;
549 template<size_t N0, size_t N1>
550 static std::array<RotatorCoeffs,N0+N1> ConcatArrays(const std::array<RotatorCoeffs,N0> &lhs,
551 const std::array<RotatorCoeffs,N1> &rhs)
553 std::array<RotatorCoeffs,N0+N1> ret;
554 auto iter = std::copy(lhs.cbegin(), lhs.cend(), ret.begin());
555 std::copy(rhs.cbegin(), rhs.cend(), iter);
556 return ret;
559 template<int l, int num_elems=l*2+1>
560 static std::array<RotatorCoeffs,num_elems*num_elems> GenCoeffs()
562 std::array<RotatorCoeffs,num_elems*num_elems> ret{};
563 auto coeffs = ret.begin();
565 for(int m{-l};m <= l;++m)
567 for(int n{-l};n <= l;++n)
569 // compute u,v,w terms of Eq.8.1 (Table I)
570 const bool d{m == 0}; // the delta function d_m0
571 const float denom{static_cast<float>((std::abs(n) == l) ?
572 (2*l) * (2*l - 1) : (l*l - n*n))};
574 const int abs_m{std::abs(m)};
575 coeffs->u = std::sqrt(static_cast<float>(l*l - m*m)/denom);
576 coeffs->v = std::sqrt(static_cast<float>(l+abs_m-1) * static_cast<float>(l+abs_m) /
577 denom) * (1.0f+d) * (1.0f - 2.0f*d) * 0.5f;
578 coeffs->w = std::sqrt(static_cast<float>(l-abs_m-1) * static_cast<float>(l-abs_m) /
579 denom) * (1.0f-d) * -0.5f;
580 ++coeffs;
584 return ret;
587 const auto RotatorCoeffArray = RotatorCoeffs::ConcatArrays(RotatorCoeffs::GenCoeffs<2>(),
588 RotatorCoeffs::GenCoeffs<3>());
591 * Given the matrix, pre-filled with the (zeroth- and) first-order rotation
592 * coefficients, this fills in the coefficients for the higher orders up to and
593 * including the given order. The matrix is in ACN layout.
595 void AmbiRotator(std::array<std::array<float,MAX_AMBI_CHANNELS>,MAX_AMBI_CHANNELS> &matrix,
596 const int order)
598 /* Don't do anything for < 2nd order. */
599 if(order < 2) return;
601 auto P = [](const int i, const int l, const int a, const int n, const size_t last_band,
602 const std::array<std::array<float,MAX_AMBI_CHANNELS>,MAX_AMBI_CHANNELS> &R)
604 const float ri1{ R[static_cast<ALuint>(i+2)][ 1+2]};
605 const float rim1{R[static_cast<ALuint>(i+2)][-1+2]};
606 const float ri0{ R[static_cast<ALuint>(i+2)][ 0+2]};
608 auto vec = R[static_cast<ALuint>(a+l-1) + last_band].cbegin() + last_band;
609 if(n == -l)
610 return ri1*vec[0] + rim1*vec[static_cast<ALuint>(l-1)*size_t{2}];
611 if(n == l)
612 return ri1*vec[static_cast<ALuint>(l-1)*size_t{2}] - rim1*vec[0];
613 return ri0*vec[static_cast<ALuint>(n+l-1)];
616 auto U = [P](const int l, const int m, const int n, const size_t last_band,
617 const std::array<std::array<float,MAX_AMBI_CHANNELS>,MAX_AMBI_CHANNELS> &R)
619 return P(0, l, m, n, last_band, R);
621 auto V = [P](const int l, const int m, const int n, const size_t last_band,
622 const std::array<std::array<float,MAX_AMBI_CHANNELS>,MAX_AMBI_CHANNELS> &R)
624 if(m > 0)
626 const bool d{m == 1};
627 const float p0{P( 1, l, m-1, n, last_band, R)};
628 const float p1{P(-1, l, -m+1, n, last_band, R)};
629 return d ? p0*std::sqrt(2.0f) : (p0 - p1);
631 const bool d{m == -1};
632 const float p0{P( 1, l, m+1, n, last_band, R)};
633 const float p1{P(-1, l, -m-1, n, last_band, R)};
634 return d ? p1*std::sqrt(2.0f) : (p0 + p1);
636 auto W = [P](const int l, const int m, const int n, const size_t last_band,
637 const std::array<std::array<float,MAX_AMBI_CHANNELS>,MAX_AMBI_CHANNELS> &R)
639 assert(m != 0);
640 if(m > 0)
642 const float p0{P( 1, l, m+1, n, last_band, R)};
643 const float p1{P(-1, l, -m-1, n, last_band, R)};
644 return p0 + p1;
646 const float p0{P( 1, l, m-1, n, last_band, R)};
647 const float p1{P(-1, l, -m+1, n, last_band, R)};
648 return p0 - p1;
651 // compute rotation matrix of each subsequent band recursively
652 auto coeffs = RotatorCoeffArray.cbegin();
653 size_t band_idx{4}, last_band{1};
654 for(int l{2};l <= order;++l)
656 size_t y{band_idx};
657 for(int m{-l};m <= l;++m,++y)
659 size_t x{band_idx};
660 for(int n{-l};n <= l;++n,++x)
662 float r{0.0f};
664 // computes Eq.8.1
665 const float u{coeffs->u};
666 if(u != 0.0f) r += u * U(l, m, n, last_band, matrix);
667 const float v{coeffs->v};
668 if(v != 0.0f) r += v * V(l, m, n, last_band, matrix);
669 const float w{coeffs->w};
670 if(w != 0.0f) r += w * W(l, m, n, last_band, matrix);
672 matrix[y][x] = r;
673 ++coeffs;
676 last_band = band_idx;
677 band_idx += static_cast<ALuint>(l)*size_t{2} + 1;
680 /* End ambisonic rotation helpers. */
683 struct GainTriplet { float Base, HF, LF; };
685 void CalcPanningAndFilters(ALvoice *voice, const ALfloat xpos, const ALfloat ypos,
686 const ALfloat zpos, const ALfloat Distance, const ALfloat Spread, const GainTriplet &DryGain,
687 const al::span<const GainTriplet,MAX_SENDS> WetGain, ALeffectslot *(&SendSlots)[MAX_SENDS],
688 const ALvoicePropsBase *props, const ALlistener &Listener, const ALCdevice *Device)
690 static const ChanMap MonoMap[1]{
691 { FrontCenter, 0.0f, 0.0f }
692 }, RearMap[2]{
693 { BackLeft, Deg2Rad(-150.0f), Deg2Rad(0.0f) },
694 { BackRight, Deg2Rad( 150.0f), Deg2Rad(0.0f) }
695 }, QuadMap[4]{
696 { FrontLeft, Deg2Rad( -45.0f), Deg2Rad(0.0f) },
697 { FrontRight, Deg2Rad( 45.0f), Deg2Rad(0.0f) },
698 { BackLeft, Deg2Rad(-135.0f), Deg2Rad(0.0f) },
699 { BackRight, Deg2Rad( 135.0f), Deg2Rad(0.0f) }
700 }, X51Map[6]{
701 { FrontLeft, Deg2Rad( -30.0f), Deg2Rad(0.0f) },
702 { FrontRight, Deg2Rad( 30.0f), Deg2Rad(0.0f) },
703 { FrontCenter, Deg2Rad( 0.0f), Deg2Rad(0.0f) },
704 { LFE, 0.0f, 0.0f },
705 { SideLeft, Deg2Rad(-110.0f), Deg2Rad(0.0f) },
706 { SideRight, Deg2Rad( 110.0f), Deg2Rad(0.0f) }
707 }, X61Map[7]{
708 { FrontLeft, Deg2Rad(-30.0f), Deg2Rad(0.0f) },
709 { FrontRight, Deg2Rad( 30.0f), Deg2Rad(0.0f) },
710 { FrontCenter, Deg2Rad( 0.0f), Deg2Rad(0.0f) },
711 { LFE, 0.0f, 0.0f },
712 { BackCenter, Deg2Rad(180.0f), Deg2Rad(0.0f) },
713 { SideLeft, Deg2Rad(-90.0f), Deg2Rad(0.0f) },
714 { SideRight, Deg2Rad( 90.0f), Deg2Rad(0.0f) }
715 }, X71Map[8]{
716 { FrontLeft, Deg2Rad( -30.0f), Deg2Rad(0.0f) },
717 { FrontRight, Deg2Rad( 30.0f), Deg2Rad(0.0f) },
718 { FrontCenter, Deg2Rad( 0.0f), Deg2Rad(0.0f) },
719 { LFE, 0.0f, 0.0f },
720 { BackLeft, Deg2Rad(-150.0f), Deg2Rad(0.0f) },
721 { BackRight, Deg2Rad( 150.0f), Deg2Rad(0.0f) },
722 { SideLeft, Deg2Rad( -90.0f), Deg2Rad(0.0f) },
723 { SideRight, Deg2Rad( 90.0f), Deg2Rad(0.0f) }
726 ChanMap StereoMap[2]{
727 { FrontLeft, Deg2Rad(-30.0f), Deg2Rad(0.0f) },
728 { FrontRight, Deg2Rad( 30.0f), Deg2Rad(0.0f) }
731 const auto Frequency = static_cast<ALfloat>(Device->Frequency);
732 const ALuint NumSends{Device->NumAuxSends};
734 bool DirectChannels{props->DirectChannels != AL_FALSE};
735 const ALuint num_channels{voice->mNumChannels};
736 const ChanMap *chans{nullptr};
737 ALfloat downmix_gain{1.0f};
738 switch(voice->mFmtChannels)
740 case FmtMono:
741 chans = MonoMap;
742 /* Mono buffers are never played direct. */
743 DirectChannels = false;
744 break;
746 case FmtStereo:
747 if(!DirectChannels)
749 /* Convert counter-clockwise to clockwise. */
750 StereoMap[0].angle = -props->StereoPan[0];
751 StereoMap[1].angle = -props->StereoPan[1];
754 chans = StereoMap;
755 downmix_gain = 1.0f / 2.0f;
756 break;
758 case FmtRear:
759 chans = RearMap;
760 downmix_gain = 1.0f / 2.0f;
761 break;
763 case FmtQuad:
764 chans = QuadMap;
765 downmix_gain = 1.0f / 4.0f;
766 break;
768 case FmtX51:
769 chans = X51Map;
770 /* NOTE: Excludes LFE. */
771 downmix_gain = 1.0f / 5.0f;
772 break;
774 case FmtX61:
775 chans = X61Map;
776 /* NOTE: Excludes LFE. */
777 downmix_gain = 1.0f / 6.0f;
778 break;
780 case FmtX71:
781 chans = X71Map;
782 /* NOTE: Excludes LFE. */
783 downmix_gain = 1.0f / 7.0f;
784 break;
786 case FmtBFormat2D:
787 case FmtBFormat3D:
788 DirectChannels = false;
789 break;
791 ASSUME(num_channels > 0);
793 std::for_each(voice->mChans.begin(), voice->mChans.begin()+num_channels,
794 [NumSends](ALvoice::ChannelData &chandata) -> void
796 chandata.mDryParams.Hrtf.Target = HrtfFilter{};
797 chandata.mDryParams.Gains.Target.fill(0.0f);
798 std::for_each(chandata.mWetParams.begin(), chandata.mWetParams.begin()+NumSends,
799 [](SendParams &params) -> void { params.Gains.Target.fill(0.0f); });
802 voice->mFlags &= ~(VOICE_HAS_HRTF | VOICE_HAS_NFC);
803 if(voice->mFmtChannels == FmtBFormat2D || voice->mFmtChannels == FmtBFormat3D)
805 /* Special handling for B-Format sources. */
807 if(Distance > std::numeric_limits<float>::epsilon())
809 /* Panning a B-Format sound toward some direction is easy. Just pan
810 * the first (W) channel as a normal mono sound and silence the
811 * others.
814 if(Device->AvgSpeakerDist > 0.0f)
816 /* Clamp the distance for really close sources, to prevent
817 * excessive bass.
819 const ALfloat mdist{maxf(Distance, Device->AvgSpeakerDist/4.0f)};
820 const ALfloat w0{SPEEDOFSOUNDMETRESPERSEC / (mdist * Frequency)};
822 /* Only need to adjust the first channel of a B-Format source. */
823 voice->mChans[0].mDryParams.NFCtrlFilter.adjust(w0);
825 voice->mFlags |= VOICE_HAS_NFC;
828 ALfloat coeffs[MAX_AMBI_CHANNELS];
829 if(Device->mRenderMode != StereoPair)
830 CalcDirectionCoeffs({xpos, ypos, zpos}, Spread, coeffs);
831 else
833 /* Clamp Y, in case rounding errors caused it to end up outside
834 * of -1...+1.
836 const ALfloat ev{std::asin(clampf(ypos, -1.0f, 1.0f))};
837 /* Negate Z for right-handed coords with -Z in front. */
838 const ALfloat az{std::atan2(xpos, -zpos)};
840 /* A scalar of 1.5 for plain stereo results in +/-60 degrees
841 * being moved to +/-90 degrees for direct right and left
842 * speaker responses.
844 CalcAngleCoeffs(ScaleAzimuthFront(az, 1.5f), ev, Spread, coeffs);
847 /* NOTE: W needs to be scaled according to channel scaling. */
848 const float scale0{GetAmbiScales(voice->mAmbiScaling)[0]};
849 ComputePanGains(&Device->Dry, coeffs, DryGain.Base*scale0,
850 voice->mChans[0].mDryParams.Gains.Target);
851 for(ALuint i{0};i < NumSends;i++)
853 if(const ALeffectslot *Slot{SendSlots[i]})
854 ComputePanGains(&Slot->Wet, coeffs, WetGain[i].Base*scale0,
855 voice->mChans[0].mWetParams[i].Gains.Target);
858 else
860 if(Device->AvgSpeakerDist > 0.0f)
862 /* NOTE: The NFCtrlFilters were created with a w0 of 0, which
863 * is what we want for FOA input. The first channel may have
864 * been previously re-adjusted if panned, so reset it.
866 voice->mChans[0].mDryParams.NFCtrlFilter.adjust(0.0f);
868 voice->mFlags |= VOICE_HAS_NFC;
871 /* Local B-Format sources have their XYZ channels rotated according
872 * to the orientation.
874 /* AT then UP */
875 alu::Vector N{props->OrientAt[0], props->OrientAt[1], props->OrientAt[2], 0.0f};
876 N.normalize();
877 alu::Vector V{props->OrientUp[0], props->OrientUp[1], props->OrientUp[2], 0.0f};
878 V.normalize();
879 if(!props->HeadRelative)
881 N = Listener.Params.Matrix * N;
882 V = Listener.Params.Matrix * V;
884 /* Build and normalize right-vector */
885 alu::Vector U{aluCrossproduct(N, V)};
886 U.normalize();
888 /* Build a rotation matrix. Manually fill the zeroth- and first-
889 * order elements, then construct the rotation for the higher
890 * orders.
892 std::array<std::array<float,MAX_AMBI_CHANNELS>,MAX_AMBI_CHANNELS> shrot{};
893 shrot[0][0] = 1.0f;
894 shrot[1][1] = U[0]; shrot[1][2] = -V[0]; shrot[1][3] = -N[0];
895 shrot[2][1] = -U[1]; shrot[2][2] = V[1]; shrot[2][3] = N[1];
896 shrot[3][1] = U[2]; shrot[3][2] = -V[2]; shrot[3][3] = -N[2];
897 AmbiRotator(shrot, static_cast<int>(minu(voice->mAmbiOrder, Device->mAmbiOrder)));
899 /* Convert the rotation matrix for input ordering and scaling, and
900 * whether input is 2D or 3D.
902 const uint8_t *index_map{(voice->mFmtChannels == FmtBFormat2D) ?
903 GetAmbi2DLayout(voice->mAmbiLayout).data() :
904 GetAmbiLayout(voice->mAmbiLayout).data()};
905 const float *scales{GetAmbiScales(voice->mAmbiScaling).data()};
907 static const uint8_t OrderFromChan[MAX_AMBI_CHANNELS]{
908 0, 1,1,1, 2,2,2,2,2, 3,3,3,3,3,3,3,
910 static const uint8_t ChansPerOrder[MAX_AMBI_ORDER+1]{1, 3, 5, 7,};
911 static const uint8_t OrderOffset[MAX_AMBI_ORDER+1]{0, 1, 4, 9,};
912 for(ALuint c{0};c < num_channels;c++)
914 const size_t acn{index_map[c]};
915 const size_t order{OrderFromChan[acn]};
916 const size_t tocopy{ChansPerOrder[order]};
917 const size_t offset{OrderOffset[order]};
918 const float scale{scales[acn]};
919 auto in = shrot.cbegin() + offset;
921 float coeffs[MAX_AMBI_CHANNELS]{};
922 for(size_t x{0};x < tocopy;++x)
923 coeffs[offset+x] = in[x][acn] * scale;
925 ComputePanGains(&Device->Dry, coeffs, DryGain.Base,
926 voice->mChans[c].mDryParams.Gains.Target);
928 for(ALuint i{0};i < NumSends;i++)
930 if(const ALeffectslot *Slot{SendSlots[i]})
931 ComputePanGains(&Slot->Wet, coeffs, WetGain[i].Base,
932 voice->mChans[c].mWetParams[i].Gains.Target);
937 else if(DirectChannels && Device->FmtChans != DevFmtMono && Device->FmtChans != DevFmtAmbi3D)
939 /* Direct source channels always play local. Skip the virtual channels
940 * and write inputs to the matching real outputs.
942 voice->mDirect.Buffer = Device->RealOut.Buffer;
944 for(ALuint c{0};c < num_channels;c++)
946 ALuint idx{GetChannelIdxByName(Device->RealOut, chans[c].channel)};
947 if(idx != INVALID_CHANNEL_INDEX)
948 voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain.Base;
949 else
951 auto match_channel = [chans,c](const InputRemixMap &map) noexcept -> bool
952 { return chans[c].channel == map.channel; };
953 auto remap = std::find_if(Device->RealOut.RemixMap.cbegin(),
954 Device->RealOut.RemixMap.cend(), match_channel);
955 if(remap != Device->RealOut.RemixMap.cend())
956 for(const auto &target : remap->targets)
958 idx = GetChannelIdxByName(Device->RealOut, target.channel);
959 if(idx != INVALID_CHANNEL_INDEX)
960 voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain.Base *
961 target.mix;
966 /* Auxiliary sends still use normal channel panning since they mix to
967 * B-Format, which can't channel-match.
969 for(ALuint c{0};c < num_channels;c++)
971 ALfloat coeffs[MAX_AMBI_CHANNELS];
972 CalcAngleCoeffs(chans[c].angle, chans[c].elevation, 0.0f, coeffs);
974 for(ALuint i{0};i < NumSends;i++)
976 if(const ALeffectslot *Slot{SendSlots[i]})
977 ComputePanGains(&Slot->Wet, coeffs, WetGain[i].Base,
978 voice->mChans[c].mWetParams[i].Gains.Target);
982 else if(Device->mRenderMode == HrtfRender)
984 /* Full HRTF rendering. Skip the virtual channels and render to the
985 * real outputs.
987 voice->mDirect.Buffer = Device->RealOut.Buffer;
989 if(Distance > std::numeric_limits<float>::epsilon())
991 const ALfloat ev{std::asin(clampf(ypos, -1.0f, 1.0f))};
992 const ALfloat az{std::atan2(xpos, -zpos)};
994 /* Get the HRIR coefficients and delays just once, for the given
995 * source direction.
997 GetHrtfCoeffs(Device->mHrtf, ev, az, Distance, Spread,
998 voice->mChans[0].mDryParams.Hrtf.Target.Coeffs,
999 voice->mChans[0].mDryParams.Hrtf.Target.Delay);
1000 voice->mChans[0].mDryParams.Hrtf.Target.Gain = DryGain.Base * downmix_gain;
1002 /* Remaining channels use the same results as the first. */
1003 for(ALuint c{1};c < num_channels;c++)
1005 /* Skip LFE */
1006 if(chans[c].channel == LFE) continue;
1007 voice->mChans[c].mDryParams.Hrtf.Target = voice->mChans[0].mDryParams.Hrtf.Target;
1010 /* Calculate the directional coefficients once, which apply to all
1011 * input channels of the source sends.
1013 ALfloat coeffs[MAX_AMBI_CHANNELS];
1014 CalcDirectionCoeffs({xpos, ypos, zpos}, Spread, coeffs);
1016 for(ALuint c{0};c < num_channels;c++)
1018 /* Skip LFE */
1019 if(chans[c].channel == LFE)
1020 continue;
1021 for(ALuint i{0};i < NumSends;i++)
1023 if(const ALeffectslot *Slot{SendSlots[i]})
1024 ComputePanGains(&Slot->Wet, coeffs, WetGain[i].Base * downmix_gain,
1025 voice->mChans[c].mWetParams[i].Gains.Target);
1029 else
1031 /* Local sources on HRTF play with each channel panned to its
1032 * relative location around the listener, providing "virtual
1033 * speaker" responses.
1035 for(ALuint c{0};c < num_channels;c++)
1037 /* Skip LFE */
1038 if(chans[c].channel == LFE)
1039 continue;
1041 /* Get the HRIR coefficients and delays for this channel
1042 * position.
1044 GetHrtfCoeffs(Device->mHrtf, chans[c].elevation, chans[c].angle,
1045 std::numeric_limits<float>::infinity(), Spread,
1046 voice->mChans[c].mDryParams.Hrtf.Target.Coeffs,
1047 voice->mChans[c].mDryParams.Hrtf.Target.Delay);
1048 voice->mChans[c].mDryParams.Hrtf.Target.Gain = DryGain.Base;
1050 /* Normal panning for auxiliary sends. */
1051 ALfloat coeffs[MAX_AMBI_CHANNELS];
1052 CalcAngleCoeffs(chans[c].angle, chans[c].elevation, Spread, coeffs);
1054 for(ALuint i{0};i < NumSends;i++)
1056 if(const ALeffectslot *Slot{SendSlots[i]})
1057 ComputePanGains(&Slot->Wet, coeffs, WetGain[i].Base,
1058 voice->mChans[c].mWetParams[i].Gains.Target);
1063 voice->mFlags |= VOICE_HAS_HRTF;
1065 else
1067 /* Non-HRTF rendering. Use normal panning to the output. */
1069 if(Distance > std::numeric_limits<float>::epsilon())
1071 /* Calculate NFC filter coefficient if needed. */
1072 if(Device->AvgSpeakerDist > 0.0f)
1074 /* Clamp the distance for really close sources, to prevent
1075 * excessive bass.
1077 const ALfloat mdist{maxf(Distance, Device->AvgSpeakerDist/4.0f)};
1078 const ALfloat w0{SPEEDOFSOUNDMETRESPERSEC / (mdist * Frequency)};
1080 /* Adjust NFC filters. */
1081 for(ALuint c{0};c < num_channels;c++)
1082 voice->mChans[c].mDryParams.NFCtrlFilter.adjust(w0);
1084 voice->mFlags |= VOICE_HAS_NFC;
1087 /* Calculate the directional coefficients once, which apply to all
1088 * input channels.
1090 ALfloat coeffs[MAX_AMBI_CHANNELS];
1091 if(Device->mRenderMode != StereoPair)
1092 CalcDirectionCoeffs({xpos, ypos, zpos}, Spread, coeffs);
1093 else
1095 const ALfloat ev{std::asin(clampf(ypos, -1.0f, 1.0f))};
1096 const ALfloat az{std::atan2(xpos, -zpos)};
1097 CalcAngleCoeffs(ScaleAzimuthFront(az, 1.5f), ev, Spread, coeffs);
1100 for(ALuint c{0};c < num_channels;c++)
1102 /* Special-case LFE */
1103 if(chans[c].channel == LFE)
1105 if(Device->Dry.Buffer.data() == Device->RealOut.Buffer.data())
1107 const ALuint idx{GetChannelIdxByName(Device->RealOut, chans[c].channel)};
1108 if(idx != INVALID_CHANNEL_INDEX)
1109 voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain.Base;
1111 continue;
1114 ComputePanGains(&Device->Dry, coeffs, DryGain.Base * downmix_gain,
1115 voice->mChans[c].mDryParams.Gains.Target);
1116 for(ALuint i{0};i < NumSends;i++)
1118 if(const ALeffectslot *Slot{SendSlots[i]})
1119 ComputePanGains(&Slot->Wet, coeffs, WetGain[i].Base * downmix_gain,
1120 voice->mChans[c].mWetParams[i].Gains.Target);
1124 else
1126 if(Device->AvgSpeakerDist > 0.0f)
1128 /* If the source distance is 0, set w0 to w1 to act as a pass-
1129 * through. We still want to pass the signal through the
1130 * filters so they keep an appropriate history, in case the
1131 * source moves away from the listener.
1133 const ALfloat w0{SPEEDOFSOUNDMETRESPERSEC / (Device->AvgSpeakerDist * Frequency)};
1135 for(ALuint c{0};c < num_channels;c++)
1136 voice->mChans[c].mDryParams.NFCtrlFilter.adjust(w0);
1138 voice->mFlags |= VOICE_HAS_NFC;
1141 for(ALuint c{0};c < num_channels;c++)
1143 /* Special-case LFE */
1144 if(chans[c].channel == LFE)
1146 if(Device->Dry.Buffer.data() == Device->RealOut.Buffer.data())
1148 const ALuint idx{GetChannelIdxByName(Device->RealOut, chans[c].channel)};
1149 if(idx != INVALID_CHANNEL_INDEX)
1150 voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain.Base;
1152 continue;
1155 ALfloat coeffs[MAX_AMBI_CHANNELS];
1156 CalcAngleCoeffs(
1157 (Device->mRenderMode==StereoPair) ? ScaleAzimuthFront(chans[c].angle, 3.0f)
1158 : chans[c].angle,
1159 chans[c].elevation, Spread, coeffs
1162 ComputePanGains(&Device->Dry, coeffs, DryGain.Base,
1163 voice->mChans[c].mDryParams.Gains.Target);
1164 for(ALuint i{0};i < NumSends;i++)
1166 if(const ALeffectslot *Slot{SendSlots[i]})
1167 ComputePanGains(&Slot->Wet, coeffs, WetGain[i].Base,
1168 voice->mChans[c].mWetParams[i].Gains.Target);
1175 const float hfNorm{props->Direct.HFReference / Frequency};
1176 const float lfNorm{props->Direct.LFReference / Frequency};
1178 voice->mDirect.FilterType = AF_None;
1179 if(DryGain.HF != 1.0f) voice->mDirect.FilterType |= AF_LowPass;
1180 if(DryGain.LF != 1.0f) voice->mDirect.FilterType |= AF_HighPass;
1182 auto &lowpass = voice->mChans[0].mDryParams.LowPass;
1183 auto &highpass = voice->mChans[0].mDryParams.HighPass;
1184 lowpass.setParamsFromSlope(BiquadType::HighShelf, hfNorm, DryGain.HF, 1.0f);
1185 highpass.setParamsFromSlope(BiquadType::LowShelf, lfNorm, DryGain.LF, 1.0f);
1186 for(ALuint c{1};c < num_channels;c++)
1188 voice->mChans[c].mDryParams.LowPass.copyParamsFrom(lowpass);
1189 voice->mChans[c].mDryParams.HighPass.copyParamsFrom(highpass);
1192 for(ALuint i{0};i < NumSends;i++)
1194 const float hfNorm{props->Send[i].HFReference / Frequency};
1195 const float lfNorm{props->Send[i].LFReference / Frequency};
1197 voice->mSend[i].FilterType = AF_None;
1198 if(WetGain[i].HF != 1.0f) voice->mSend[i].FilterType |= AF_LowPass;
1199 if(WetGain[i].LF != 1.0f) voice->mSend[i].FilterType |= AF_HighPass;
1201 auto &lowpass = voice->mChans[0].mWetParams[i].LowPass;
1202 auto &highpass = voice->mChans[0].mWetParams[i].HighPass;
1203 lowpass.setParamsFromSlope(BiquadType::HighShelf, hfNorm, WetGain[i].HF, 1.0f);
1204 highpass.setParamsFromSlope(BiquadType::LowShelf, lfNorm, WetGain[i].LF, 1.0f);
1205 for(ALuint c{1};c < num_channels;c++)
1207 voice->mChans[c].mWetParams[i].LowPass.copyParamsFrom(lowpass);
1208 voice->mChans[c].mWetParams[i].HighPass.copyParamsFrom(highpass);
1213 void CalcNonAttnSourceParams(ALvoice *voice, const ALvoicePropsBase *props, const ALCcontext *ALContext)
1215 const ALCdevice *Device{ALContext->mDevice.get()};
1216 ALeffectslot *SendSlots[MAX_SENDS];
1218 voice->mDirect.Buffer = Device->Dry.Buffer;
1219 for(ALuint i{0};i < Device->NumAuxSends;i++)
1221 SendSlots[i] = props->Send[i].Slot;
1222 if(!SendSlots[i] && i == 0)
1223 SendSlots[i] = ALContext->mDefaultSlot.get();
1224 if(!SendSlots[i] || SendSlots[i]->Params.EffectType == AL_EFFECT_NULL)
1226 SendSlots[i] = nullptr;
1227 voice->mSend[i].Buffer = {};
1229 else
1230 voice->mSend[i].Buffer = SendSlots[i]->Wet.Buffer;
1233 /* Calculate the stepping value */
1234 const auto Pitch = static_cast<ALfloat>(voice->mFrequency) /
1235 static_cast<ALfloat>(Device->Frequency) * props->Pitch;
1236 if(Pitch > float{MAX_PITCH})
1237 voice->mStep = MAX_PITCH<<FRACTIONBITS;
1238 else
1239 voice->mStep = maxu(fastf2u(Pitch * FRACTIONONE), 1);
1240 voice->mResampler = PrepareResampler(props->mResampler, voice->mStep, &voice->mResampleState);
1242 /* Calculate gains */
1243 const ALlistener &Listener = ALContext->mListener;
1244 GainTriplet DryGain;
1245 DryGain.Base = minf(clampf(props->Gain, props->MinGain, props->MaxGain) * props->Direct.Gain *
1246 Listener.Params.Gain, GAIN_MIX_MAX);
1247 DryGain.HF = props->Direct.GainHF;
1248 DryGain.LF = props->Direct.GainLF;
1249 GainTriplet WetGain[MAX_SENDS];
1250 for(ALuint i{0};i < Device->NumAuxSends;i++)
1252 WetGain[i].Base = minf(clampf(props->Gain, props->MinGain, props->MaxGain) *
1253 props->Send[i].Gain * Listener.Params.Gain, GAIN_MIX_MAX);
1254 WetGain[i].HF = props->Send[i].GainHF;
1255 WetGain[i].LF = props->Send[i].GainLF;
1258 CalcPanningAndFilters(voice, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, DryGain, WetGain, SendSlots, props,
1259 Listener, Device);
1262 void CalcAttnSourceParams(ALvoice *voice, const ALvoicePropsBase *props, const ALCcontext *ALContext)
1264 const ALCdevice *Device{ALContext->mDevice.get()};
1265 const ALuint NumSends{Device->NumAuxSends};
1266 const ALlistener &Listener = ALContext->mListener;
1268 /* Set mixing buffers and get send parameters. */
1269 voice->mDirect.Buffer = Device->Dry.Buffer;
1270 ALeffectslot *SendSlots[MAX_SENDS];
1271 ALfloat RoomRolloff[MAX_SENDS];
1272 ALfloat DecayDistance[MAX_SENDS];
1273 ALfloat DecayLFDistance[MAX_SENDS];
1274 ALfloat DecayHFDistance[MAX_SENDS];
1275 for(ALuint i{0};i < NumSends;i++)
1277 SendSlots[i] = props->Send[i].Slot;
1278 if(!SendSlots[i] && i == 0)
1279 SendSlots[i] = ALContext->mDefaultSlot.get();
1280 if(!SendSlots[i] || SendSlots[i]->Params.EffectType == AL_EFFECT_NULL)
1282 SendSlots[i] = nullptr;
1283 RoomRolloff[i] = 0.0f;
1284 DecayDistance[i] = 0.0f;
1285 DecayLFDistance[i] = 0.0f;
1286 DecayHFDistance[i] = 0.0f;
1288 else if(SendSlots[i]->Params.AuxSendAuto)
1290 RoomRolloff[i] = SendSlots[i]->Params.RoomRolloff + props->RoomRolloffFactor;
1291 /* Calculate the distances to where this effect's decay reaches
1292 * -60dB.
1294 DecayDistance[i] = SendSlots[i]->Params.DecayTime * SPEEDOFSOUNDMETRESPERSEC;
1295 DecayLFDistance[i] = DecayDistance[i] * SendSlots[i]->Params.DecayLFRatio;
1296 DecayHFDistance[i] = DecayDistance[i] * SendSlots[i]->Params.DecayHFRatio;
1297 if(SendSlots[i]->Params.DecayHFLimit)
1299 ALfloat airAbsorption{SendSlots[i]->Params.AirAbsorptionGainHF};
1300 if(airAbsorption < 1.0f)
1302 /* Calculate the distance to where this effect's air
1303 * absorption reaches -60dB, and limit the effect's HF
1304 * decay distance (so it doesn't take any longer to decay
1305 * than the air would allow).
1307 ALfloat absorb_dist{std::log10(REVERB_DECAY_GAIN) / std::log10(airAbsorption)};
1308 DecayHFDistance[i] = minf(absorb_dist, DecayHFDistance[i]);
1312 else
1314 /* If the slot's auxiliary send auto is off, the data sent to the
1315 * effect slot is the same as the dry path, sans filter effects */
1316 RoomRolloff[i] = props->RolloffFactor;
1317 DecayDistance[i] = 0.0f;
1318 DecayLFDistance[i] = 0.0f;
1319 DecayHFDistance[i] = 0.0f;
1322 if(!SendSlots[i])
1323 voice->mSend[i].Buffer = {};
1324 else
1325 voice->mSend[i].Buffer = SendSlots[i]->Wet.Buffer;
1328 /* Transform source to listener space (convert to head relative) */
1329 alu::Vector Position{props->Position[0], props->Position[1], props->Position[2], 1.0f};
1330 alu::Vector Velocity{props->Velocity[0], props->Velocity[1], props->Velocity[2], 0.0f};
1331 alu::Vector Direction{props->Direction[0], props->Direction[1], props->Direction[2], 0.0f};
1332 if(props->HeadRelative == AL_FALSE)
1334 /* Transform source vectors */
1335 Position = Listener.Params.Matrix * Position;
1336 Velocity = Listener.Params.Matrix * Velocity;
1337 Direction = Listener.Params.Matrix * Direction;
1339 else
1341 /* Offset the source velocity to be relative of the listener velocity */
1342 Velocity += Listener.Params.Velocity;
1345 const bool directional{Direction.normalize() > 0.0f};
1346 alu::Vector ToSource{Position[0], Position[1], Position[2], 0.0f};
1347 const ALfloat Distance{ToSource.normalize()};
1349 /* Initial source gain */
1350 GainTriplet DryGain{props->Gain, 1.0f, 1.0f};
1351 GainTriplet WetGain[MAX_SENDS];
1352 for(ALuint i{0};i < NumSends;i++)
1353 WetGain[i] = DryGain;
1355 /* Calculate distance attenuation */
1356 float ClampedDist{Distance};
1358 switch(Listener.Params.SourceDistanceModel ?
1359 props->mDistanceModel : Listener.Params.mDistanceModel)
1361 case DistanceModel::InverseClamped:
1362 ClampedDist = clampf(ClampedDist, props->RefDistance, props->MaxDistance);
1363 if(props->MaxDistance < props->RefDistance) break;
1364 /*fall-through*/
1365 case DistanceModel::Inverse:
1366 if(!(props->RefDistance > 0.0f))
1367 ClampedDist = props->RefDistance;
1368 else
1370 float dist{lerp(props->RefDistance, ClampedDist, props->RolloffFactor)};
1371 if(dist > 0.0f) DryGain.Base *= props->RefDistance / dist;
1372 for(ALuint i{0};i < NumSends;i++)
1374 dist = lerp(props->RefDistance, ClampedDist, RoomRolloff[i]);
1375 if(dist > 0.0f) WetGain[i].Base *= props->RefDistance / dist;
1378 break;
1380 case DistanceModel::LinearClamped:
1381 ClampedDist = clampf(ClampedDist, props->RefDistance, props->MaxDistance);
1382 if(props->MaxDistance < props->RefDistance) break;
1383 /*fall-through*/
1384 case DistanceModel::Linear:
1385 if(!(props->MaxDistance != props->RefDistance))
1386 ClampedDist = props->RefDistance;
1387 else
1389 float attn{props->RolloffFactor * (ClampedDist-props->RefDistance) /
1390 (props->MaxDistance-props->RefDistance)};
1391 DryGain.Base *= maxf(1.0f - attn, 0.0f);
1392 for(ALuint i{0};i < NumSends;i++)
1394 attn = RoomRolloff[i] * (ClampedDist-props->RefDistance) /
1395 (props->MaxDistance-props->RefDistance);
1396 WetGain[i].Base *= maxf(1.0f - attn, 0.0f);
1399 break;
1401 case DistanceModel::ExponentClamped:
1402 ClampedDist = clampf(ClampedDist, props->RefDistance, props->MaxDistance);
1403 if(props->MaxDistance < props->RefDistance) break;
1404 /*fall-through*/
1405 case DistanceModel::Exponent:
1406 if(!(ClampedDist > 0.0f && props->RefDistance > 0.0f))
1407 ClampedDist = props->RefDistance;
1408 else
1410 DryGain.Base *= std::pow(ClampedDist/props->RefDistance, -props->RolloffFactor);
1411 for(ALuint i{0};i < NumSends;i++)
1412 WetGain[i].Base *= std::pow(ClampedDist/props->RefDistance, -RoomRolloff[i]);
1414 break;
1416 case DistanceModel::Disable:
1417 ClampedDist = props->RefDistance;
1418 break;
1421 /* Calculate directional soundcones */
1422 if(directional && props->InnerAngle < 360.0f)
1424 const float Angle{Rad2Deg(std::acos(-aluDotproduct(Direction, ToSource)) *
1425 ConeScale * 2.0f)};
1427 float ConeGain, ConeHF;
1428 if(!(Angle > props->InnerAngle))
1430 ConeGain = 1.0f;
1431 ConeHF = 1.0f;
1433 else if(Angle < props->OuterAngle)
1435 const float scale{(Angle-props->InnerAngle) / (props->OuterAngle-props->InnerAngle)};
1436 ConeGain = lerp(1.0f, props->OuterGain, scale);
1437 ConeHF = lerp(1.0f, props->OuterGainHF, scale);
1439 else
1441 ConeGain = props->OuterGain;
1442 ConeHF = props->OuterGainHF;
1445 DryGain.Base *= ConeGain;
1446 if(props->DryGainHFAuto)
1447 DryGain.HF *= ConeHF;
1448 if(props->WetGainAuto)
1449 std::for_each(std::begin(WetGain), std::begin(WetGain)+NumSends,
1450 [ConeGain](GainTriplet &gain) noexcept -> void { gain.Base *= ConeGain; });
1451 if(props->WetGainHFAuto)
1452 std::for_each(std::begin(WetGain), std::begin(WetGain)+NumSends,
1453 [ConeHF](GainTriplet &gain) noexcept -> void { gain.HF *= ConeHF; });
1456 /* Apply gain and frequency filters */
1457 DryGain.Base = minf(clampf(DryGain.Base, props->MinGain, props->MaxGain) * props->Direct.Gain *
1458 Listener.Params.Gain, GAIN_MIX_MAX);
1459 DryGain.HF *= props->Direct.GainHF;
1460 DryGain.LF *= props->Direct.GainLF;
1461 for(ALuint i{0};i < NumSends;i++)
1463 WetGain[i].Base = minf(clampf(WetGain[i].Base, props->MinGain, props->MaxGain) *
1464 props->Send[i].Gain * Listener.Params.Gain, GAIN_MIX_MAX);
1465 WetGain[i].HF *= props->Send[i].GainHF;
1466 WetGain[i].LF *= props->Send[i].GainLF;
1469 /* Distance-based air absorption and initial send decay. */
1470 if(ClampedDist > props->RefDistance && props->RolloffFactor > 0.0f)
1472 const float meters_base{(ClampedDist-props->RefDistance) * props->RolloffFactor *
1473 Listener.Params.MetersPerUnit};
1474 if(props->AirAbsorptionFactor > 0.0f)
1476 const float hfattn{std::pow(AIRABSORBGAINHF, meters_base*props->AirAbsorptionFactor)};
1477 DryGain.HF *= hfattn;
1478 std::for_each(std::begin(WetGain), std::begin(WetGain)+NumSends,
1479 [hfattn](GainTriplet &gain) noexcept -> void { gain.HF *= hfattn; });
1482 if(props->WetGainAuto)
1484 /* Apply a decay-time transformation to the wet path, based on the
1485 * source distance in meters. The initial decay of the reverb
1486 * effect is calculated and applied to the wet path.
1488 for(ALuint i{0};i < NumSends;i++)
1490 if(!(DecayDistance[i] > 0.0f))
1491 continue;
1493 const ALfloat gain{std::pow(REVERB_DECAY_GAIN, meters_base/DecayDistance[i])};
1494 WetGain[i].Base *= gain;
1495 /* Yes, the wet path's air absorption is applied with
1496 * WetGainAuto on, rather than WetGainHFAuto.
1498 if(gain > 0.0f)
1500 ALfloat gainhf{std::pow(REVERB_DECAY_GAIN, meters_base/DecayHFDistance[i])};
1501 WetGain[i].HF *= minf(gainhf / gain, 1.0f);
1502 ALfloat gainlf{std::pow(REVERB_DECAY_GAIN, meters_base/DecayLFDistance[i])};
1503 WetGain[i].LF *= minf(gainlf / gain, 1.0f);
1510 /* Initial source pitch */
1511 ALfloat Pitch{props->Pitch};
1513 /* Calculate velocity-based doppler effect */
1514 ALfloat DopplerFactor{props->DopplerFactor * Listener.Params.DopplerFactor};
1515 if(DopplerFactor > 0.0f)
1517 const alu::Vector &lvelocity = Listener.Params.Velocity;
1518 ALfloat vss{aluDotproduct(Velocity, ToSource) * -DopplerFactor};
1519 ALfloat vls{aluDotproduct(lvelocity, ToSource) * -DopplerFactor};
1521 const ALfloat SpeedOfSound{Listener.Params.SpeedOfSound};
1522 if(!(vls < SpeedOfSound))
1524 /* Listener moving away from the source at the speed of sound.
1525 * Sound waves can't catch it.
1527 Pitch = 0.0f;
1529 else if(!(vss < SpeedOfSound))
1531 /* Source moving toward the listener at the speed of sound. Sound
1532 * waves bunch up to extreme frequencies.
1534 Pitch = std::numeric_limits<float>::infinity();
1536 else
1538 /* Source and listener movement is nominal. Calculate the proper
1539 * doppler shift.
1541 Pitch *= (SpeedOfSound-vls) / (SpeedOfSound-vss);
1545 /* Adjust pitch based on the buffer and output frequencies, and calculate
1546 * fixed-point stepping value.
1548 Pitch *= static_cast<ALfloat>(voice->mFrequency)/static_cast<ALfloat>(Device->Frequency);
1549 if(Pitch > float{MAX_PITCH})
1550 voice->mStep = MAX_PITCH<<FRACTIONBITS;
1551 else
1552 voice->mStep = maxu(fastf2u(Pitch * FRACTIONONE), 1);
1553 voice->mResampler = PrepareResampler(props->mResampler, voice->mStep, &voice->mResampleState);
1555 ALfloat spread{0.0f};
1556 if(props->Radius > Distance)
1557 spread = al::MathDefs<float>::Tau() - Distance/props->Radius*al::MathDefs<float>::Pi();
1558 else if(Distance > 0.0f)
1559 spread = std::asin(props->Radius/Distance) * 2.0f;
1561 CalcPanningAndFilters(voice, ToSource[0], ToSource[1], ToSource[2]*ZScale,
1562 Distance*Listener.Params.MetersPerUnit, spread, DryGain, WetGain, SendSlots, props,
1563 Listener, Device);
1566 void CalcSourceParams(ALvoice *voice, ALCcontext *context, bool force)
1568 ALvoiceProps *props{voice->mUpdate.exchange(nullptr, std::memory_order_acq_rel)};
1569 if(!props && !force) return;
1571 if(props)
1573 voice->mProps = *props;
1575 AtomicReplaceHead(context->mFreeVoiceProps, props);
1578 if(voice->mProps.DirectChannels || voice->mProps.mSpatializeMode == SpatializeOff
1579 || (voice->mProps.mSpatializeMode == SpatializeAuto && voice->mFmtChannels != FmtMono))
1580 CalcNonAttnSourceParams(voice, &voice->mProps, context);
1581 else
1582 CalcAttnSourceParams(voice, &voice->mProps, context);
1586 void ProcessParamUpdates(ALCcontext *ctx, const ALeffectslotArray &slots,
1587 const al::span<ALvoice> voices)
1589 IncrementRef(ctx->mUpdateCount);
1590 if LIKELY(!ctx->mHoldUpdates.load(std::memory_order_acquire))
1592 bool force{CalcContextParams(ctx)};
1593 force |= CalcListenerParams(ctx);
1594 force = std::accumulate(slots.begin(), slots.end(), force,
1595 [ctx](const bool f, ALeffectslot *slot) -> bool
1596 { return CalcEffectSlotParams(slot, ctx) | f; }
1599 auto calc_params = [ctx,force](ALvoice &voice) -> void
1601 if(voice.mSourceID.load(std::memory_order_acquire) != 0)
1602 CalcSourceParams(&voice, ctx, force);
1604 std::for_each(voices.begin(), voices.end(), calc_params);
1606 IncrementRef(ctx->mUpdateCount);
1609 void ProcessContext(ALCcontext *ctx, const ALuint SamplesToDo)
1611 ASSUME(SamplesToDo > 0);
1613 const ALeffectslotArray &auxslots = *ctx->mActiveAuxSlots.load(std::memory_order_acquire);
1614 const al::span<ALvoice> voices{ctx->mVoices.data(), ctx->mVoices.size()};
1616 /* Process pending propery updates for objects on the context. */
1617 ProcessParamUpdates(ctx, auxslots, voices);
1619 /* Clear auxiliary effect slot mixing buffers. */
1620 std::for_each(auxslots.begin(), auxslots.end(),
1621 [SamplesToDo](ALeffectslot *slot) -> void
1623 for(auto &buffer : slot->MixBuffer)
1624 std::fill_n(buffer.begin(), SamplesToDo, 0.0f);
1628 /* Process voices that have a playing source. */
1629 std::for_each(voices.begin(), voices.end(),
1630 [SamplesToDo,ctx](ALvoice &voice) -> void
1632 const ALvoice::State vstate{voice.mPlayState.load(std::memory_order_acquire)};
1633 if(vstate != ALvoice::Stopped) voice.mix(vstate, ctx, SamplesToDo);
1637 /* Process effects. */
1638 if(auxslots.empty()) return;
1639 auto slots = auxslots.data();
1640 auto slots_end = slots + auxslots.size();
1642 /* First sort the slots into scratch storage, so that effects come before
1643 * their effect target (or their targets' target).
1645 auto sorted_slots = const_cast<ALeffectslot**>(slots_end);
1646 auto sorted_slots_end = sorted_slots;
1647 auto in_chain = [](const ALeffectslot *slot1, const ALeffectslot *slot2) noexcept -> bool
1649 while((slot1=slot1->Params.Target) != nullptr) {
1650 if(slot1 == slot2) return true;
1652 return false;
1655 *sorted_slots_end = *slots;
1656 ++sorted_slots_end;
1657 while(++slots != slots_end)
1659 /* If this effect slot targets an effect slot already in the list (i.e.
1660 * slots outputs to something in sorted_slots), directly or indirectly,
1661 * insert it prior to that element.
1663 auto checker = sorted_slots;
1664 do {
1665 if(in_chain(*slots, *checker)) break;
1666 } while(++checker != sorted_slots_end);
1668 checker = std::move_backward(checker, sorted_slots_end, sorted_slots_end+1);
1669 *--checker = *slots;
1670 ++sorted_slots_end;
1673 std::for_each(sorted_slots, sorted_slots_end,
1674 [SamplesToDo](const ALeffectslot *slot) -> void
1676 EffectState *state{slot->Params.mEffectState};
1677 state->process(SamplesToDo, slot->Wet.Buffer, state->mOutTarget);
1683 void ApplyStablizer(FrontStablizer *Stablizer, const al::span<FloatBufferLine> Buffer,
1684 const ALuint lidx, const ALuint ridx, const ALuint cidx, const ALuint SamplesToDo)
1686 ASSUME(SamplesToDo > 0);
1688 /* Apply a delay to all channels, except the front-left and front-right, so
1689 * they maintain correct timing.
1691 const size_t NumChannels{Buffer.size()};
1692 for(size_t i{0u};i < NumChannels;i++)
1694 if(i == lidx || i == ridx)
1695 continue;
1697 auto &DelayBuf = Stablizer->DelayBuf[i];
1698 auto buffer_end = Buffer[i].begin() + SamplesToDo;
1699 if LIKELY(SamplesToDo >= ALuint{FrontStablizer::DelayLength})
1701 auto delay_end = std::rotate(Buffer[i].begin(),
1702 buffer_end - FrontStablizer::DelayLength, buffer_end);
1703 std::swap_ranges(Buffer[i].begin(), delay_end, std::begin(DelayBuf));
1705 else
1707 auto delay_start = std::swap_ranges(Buffer[i].begin(), buffer_end,
1708 std::begin(DelayBuf));
1709 std::rotate(std::begin(DelayBuf), delay_start, std::end(DelayBuf));
1713 ALfloat (&lsplit)[2][BUFFERSIZE] = Stablizer->LSplit;
1714 ALfloat (&rsplit)[2][BUFFERSIZE] = Stablizer->RSplit;
1715 const al::span<float> tmpbuf{Stablizer->TempBuf, SamplesToDo+FrontStablizer::DelayLength};
1717 /* This applies the band-splitter, preserving phase at the cost of some
1718 * delay. The shorter the delay, the more error seeps into the result.
1720 auto apply_splitter = [tmpbuf,SamplesToDo](const FloatBufferLine &InBuf,
1721 const al::span<float,FrontStablizer::DelayLength> DelayBuf, BandSplitter &Filter,
1722 ALfloat (&splitbuf)[2][BUFFERSIZE]) -> void
1724 /* Combine the delayed samples and the input samples into the temp
1725 * buffer, in reverse. Then copy the final samples back into the delay
1726 * buffer for next time. Note that the delay buffer's samples are
1727 * stored backwards here.
1729 std::copy_backward(DelayBuf.cbegin(), DelayBuf.cend(), tmpbuf.end());
1730 std::reverse_copy(InBuf.begin(), InBuf.begin()+SamplesToDo, tmpbuf.begin());
1731 std::copy_n(tmpbuf.cbegin(), DelayBuf.size(), DelayBuf.begin());
1733 /* Apply an all-pass on the reversed signal, then reverse the samples
1734 * to get the forward signal with a reversed phase shift.
1736 Filter.applyAllpass(tmpbuf);
1737 std::reverse(tmpbuf.begin(), tmpbuf.end());
1739 /* Now apply the band-splitter, combining its phase shift with the
1740 * reversed phase shift, restoring the original phase on the split
1741 * signal.
1743 Filter.process(tmpbuf.first(SamplesToDo), splitbuf[1], splitbuf[0]);
1745 apply_splitter(Buffer[lidx], Stablizer->DelayBuf[lidx], Stablizer->LFilter, lsplit);
1746 apply_splitter(Buffer[ridx], Stablizer->DelayBuf[ridx], Stablizer->RFilter, rsplit);
1748 for(ALuint i{0};i < SamplesToDo;i++)
1750 ALfloat lfsum{lsplit[0][i] + rsplit[0][i]};
1751 ALfloat hfsum{lsplit[1][i] + rsplit[1][i]};
1752 ALfloat s{lsplit[0][i] + lsplit[1][i] - rsplit[0][i] - rsplit[1][i]};
1754 /* This pans the separate low- and high-frequency sums between being on
1755 * the center channel and the left/right channels. The low-frequency
1756 * sum is 1/3rd toward center (2/3rds on left/right) and the high-
1757 * frequency sum is 1/4th toward center (3/4ths on left/right). These
1758 * values can be tweaked.
1760 ALfloat m{lfsum*std::cos(1.0f/3.0f * (al::MathDefs<float>::Pi()*0.5f)) +
1761 hfsum*std::cos(1.0f/4.0f * (al::MathDefs<float>::Pi()*0.5f))};
1762 ALfloat c{lfsum*std::sin(1.0f/3.0f * (al::MathDefs<float>::Pi()*0.5f)) +
1763 hfsum*std::sin(1.0f/4.0f * (al::MathDefs<float>::Pi()*0.5f))};
1765 /* The generated center channel signal adds to the existing signal,
1766 * while the modified left and right channels replace.
1768 Buffer[lidx][i] = (m + s) * 0.5f;
1769 Buffer[ridx][i] = (m - s) * 0.5f;
1770 Buffer[cidx][i] += c * 0.5f;
1774 void ApplyDistanceComp(const al::span<FloatBufferLine> Samples, const ALuint SamplesToDo,
1775 const DistanceComp::DistData *distcomp)
1777 ASSUME(SamplesToDo > 0);
1779 for(auto &chanbuffer : Samples)
1781 const ALfloat gain{distcomp->Gain};
1782 const ALuint base{distcomp->Length};
1783 ALfloat *distbuf{al::assume_aligned<16>(distcomp->Buffer)};
1784 ++distcomp;
1786 if(base < 1)
1787 continue;
1789 ALfloat *inout{al::assume_aligned<16>(chanbuffer.data())};
1790 auto inout_end = inout + SamplesToDo;
1791 if LIKELY(SamplesToDo >= base)
1793 auto delay_end = std::rotate(inout, inout_end - base, inout_end);
1794 std::swap_ranges(inout, delay_end, distbuf);
1796 else
1798 auto delay_start = std::swap_ranges(inout, inout_end, distbuf);
1799 std::rotate(distbuf, delay_start, distbuf + base);
1801 std::transform(inout, inout_end, inout, std::bind(std::multiplies<float>{}, _1, gain));
1805 void ApplyDither(const al::span<FloatBufferLine> Samples, ALuint *dither_seed,
1806 const ALfloat quant_scale, const ALuint SamplesToDo)
1808 /* Dithering. Generate whitenoise (uniform distribution of random values
1809 * between -1 and +1) and add it to the sample values, after scaling up to
1810 * the desired quantization depth amd before rounding.
1812 const ALfloat invscale{1.0f / quant_scale};
1813 ALuint seed{*dither_seed};
1814 auto dither_channel = [&seed,invscale,quant_scale,SamplesToDo](FloatBufferLine &input) -> void
1816 ASSUME(SamplesToDo > 0);
1817 auto dither_sample = [&seed,invscale,quant_scale](const ALfloat sample) noexcept -> ALfloat
1819 ALfloat val{sample * quant_scale};
1820 ALuint rng0{dither_rng(&seed)};
1821 ALuint rng1{dither_rng(&seed)};
1822 val += static_cast<ALfloat>(rng0*(1.0/UINT_MAX) - rng1*(1.0/UINT_MAX));
1823 return fast_roundf(val) * invscale;
1825 std::transform(input.begin(), input.begin()+SamplesToDo, input.begin(), dither_sample);
1827 std::for_each(Samples.begin(), Samples.end(), dither_channel);
1828 *dither_seed = seed;
1832 /* Base template left undefined. Should be marked =delete, but Clang 3.8.1
1833 * chokes on that given the inline specializations.
1835 template<typename T>
1836 inline T SampleConv(float) noexcept;
1838 template<> inline float SampleConv(float val) noexcept
1839 { return val; }
1840 template<> inline int32_t SampleConv(float val) noexcept
1842 /* Floats have a 23-bit mantissa, plus an implied 1 bit and a sign bit.
1843 * This means a normalized float has at most 25 bits of signed precision.
1844 * When scaling and clamping for a signed 32-bit integer, these following
1845 * values are the best a float can give.
1847 return fastf2i(clampf(val*2147483648.0f, -2147483648.0f, 2147483520.0f));
1849 template<> inline int16_t SampleConv(float val) noexcept
1850 { return static_cast<int16_t>(fastf2i(clampf(val*32768.0f, -32768.0f, 32767.0f))); }
1851 template<> inline int8_t SampleConv(float val) noexcept
1852 { return static_cast<int8_t>(fastf2i(clampf(val*128.0f, -128.0f, 127.0f))); }
1854 /* Define unsigned output variations. */
1855 template<> inline uint32_t SampleConv(float val) noexcept
1856 { return static_cast<uint32_t>(SampleConv<int32_t>(val)) + 2147483648u; }
1857 template<> inline uint16_t SampleConv(float val) noexcept
1858 { return static_cast<uint16_t>(SampleConv<int16_t>(val) + 32768); }
1859 template<> inline uint8_t SampleConv(float val) noexcept
1860 { return static_cast<uint8_t>(SampleConv<int8_t>(val) + 128); }
1862 template<DevFmtType T>
1863 void Write(const al::span<const FloatBufferLine> InBuffer, void *OutBuffer, const size_t Offset,
1864 const ALuint SamplesToDo, const size_t FrameStep)
1866 using SampleType = typename DevFmtTypeTraits<T>::Type;
1868 ASSUME(FrameStep > 0);
1870 SampleType *outbase = static_cast<SampleType*>(OutBuffer) + Offset*FrameStep;
1871 auto conv_channel = [&outbase,SamplesToDo,FrameStep](const FloatBufferLine &inbuf) -> void
1873 ASSUME(SamplesToDo > 0);
1874 SampleType *out{outbase++};
1875 auto conv_sample = [FrameStep,&out](const float s) noexcept -> void
1877 *out = SampleConv<SampleType>(s);
1878 out += FrameStep;
1880 std::for_each(inbuf.begin(), inbuf.begin()+SamplesToDo, conv_sample);
1882 std::for_each(InBuffer.cbegin(), InBuffer.cend(), conv_channel);
1885 } // namespace
1887 void aluMixData(ALCdevice *device, void *OutBuffer, const ALuint NumSamples,
1888 const size_t FrameStep)
1890 FPUCtl mixer_mode{};
1891 for(ALuint SamplesDone{0u};SamplesDone < NumSamples;)
1893 const ALuint SamplesToDo{minu(NumSamples-SamplesDone, BUFFERSIZE)};
1895 /* Clear main mixing buffers. */
1896 std::for_each(device->MixBuffer.begin(), device->MixBuffer.end(),
1897 [SamplesToDo](std::array<ALfloat,BUFFERSIZE> &buffer) -> void
1898 { std::fill_n(buffer.begin(), SamplesToDo, 0.0f); }
1901 /* Increment the mix count at the start (lsb should now be 1). */
1902 IncrementRef(device->MixCount);
1904 /* For each context on this device, process and mix its sources and
1905 * effects.
1907 for(ALCcontext *ctx : *device->mContexts.load(std::memory_order_acquire))
1908 ProcessContext(ctx, SamplesToDo);
1910 /* Increment the clock time. Every second's worth of samples is
1911 * converted and added to clock base so that large sample counts don't
1912 * overflow during conversion. This also guarantees a stable
1913 * conversion.
1915 device->SamplesDone += SamplesToDo;
1916 device->ClockBase += std::chrono::seconds{device->SamplesDone / device->Frequency};
1917 device->SamplesDone %= device->Frequency;
1919 /* Increment the mix count at the end (lsb should now be 0). */
1920 IncrementRef(device->MixCount);
1922 /* Apply any needed post-process for finalizing the Dry mix to the
1923 * RealOut (Ambisonic decode, UHJ encode, etc).
1925 device->postProcess(SamplesToDo);
1927 const al::span<FloatBufferLine> RealOut{device->RealOut.Buffer};
1929 /* Apply front image stablization for surround sound, if applicable. */
1930 if(FrontStablizer *stablizer{device->Stablizer.get()})
1932 const ALuint lidx{GetChannelIdxByName(device->RealOut, FrontLeft)};
1933 const ALuint ridx{GetChannelIdxByName(device->RealOut, FrontRight)};
1934 const ALuint cidx{GetChannelIdxByName(device->RealOut, FrontCenter)};
1936 ApplyStablizer(stablizer, RealOut, lidx, ridx, cidx, SamplesToDo);
1939 /* Apply compression, limiting sample amplitude if needed or desired. */
1940 if(Compressor *comp{device->Limiter.get()})
1941 comp->process(SamplesToDo, RealOut.data());
1943 /* Apply delays and attenuation for mismatched speaker distances. */
1944 ApplyDistanceComp(RealOut, SamplesToDo, device->ChannelDelay.as_span().cbegin());
1946 /* Apply dithering. The compressor should have left enough headroom for
1947 * the dither noise to not saturate.
1949 if(device->DitherDepth > 0.0f)
1950 ApplyDither(RealOut, &device->DitherSeed, device->DitherDepth, SamplesToDo);
1952 if LIKELY(OutBuffer)
1954 /* Finally, interleave and convert samples, writing to the device's
1955 * output buffer.
1957 switch(device->FmtType)
1959 #define HANDLE_WRITE(T) case T: \
1960 Write<T>(RealOut, OutBuffer, SamplesDone, SamplesToDo, FrameStep); break;
1961 HANDLE_WRITE(DevFmtByte)
1962 HANDLE_WRITE(DevFmtUByte)
1963 HANDLE_WRITE(DevFmtShort)
1964 HANDLE_WRITE(DevFmtUShort)
1965 HANDLE_WRITE(DevFmtInt)
1966 HANDLE_WRITE(DevFmtUInt)
1967 HANDLE_WRITE(DevFmtFloat)
1968 #undef HANDLE_WRITE
1972 SamplesDone += SamplesToDo;
1977 void aluHandleDisconnect(ALCdevice *device, const char *msg, ...)
1979 if(!device->Connected.exchange(false, std::memory_order_acq_rel))
1980 return;
1982 AsyncEvent evt{EventType_Disconnected};
1983 evt.u.user.type = AL_EVENT_TYPE_DISCONNECTED_SOFT;
1984 evt.u.user.id = 0;
1985 evt.u.user.param = 0;
1987 va_list args;
1988 va_start(args, msg);
1989 int msglen{vsnprintf(evt.u.user.msg, sizeof(evt.u.user.msg), msg, args)};
1990 va_end(args);
1992 if(msglen < 0 || static_cast<size_t>(msglen) >= sizeof(evt.u.user.msg))
1993 evt.u.user.msg[sizeof(evt.u.user.msg)-1] = 0;
1995 IncrementRef(device->MixCount);
1996 for(ALCcontext *ctx : *device->mContexts.load())
1998 const ALbitfieldSOFT enabledevt{ctx->mEnabledEvts.load(std::memory_order_acquire)};
1999 if((enabledevt&EventType_Disconnected))
2001 RingBuffer *ring{ctx->mAsyncEvents.get()};
2002 auto evt_data = ring->getWriteVector().first;
2003 if(evt_data.len > 0)
2005 ::new (evt_data.buf) AsyncEvent{evt};
2006 ring->writeAdvance(1);
2007 ctx->mEventSem.post();
2011 auto stop_voice = [](ALvoice &voice) -> void
2013 voice.mCurrentBuffer.store(nullptr, std::memory_order_relaxed);
2014 voice.mLoopBuffer.store(nullptr, std::memory_order_relaxed);
2015 voice.mSourceID.store(0u, std::memory_order_relaxed);
2016 voice.mPlayState.store(ALvoice::Stopped, std::memory_order_release);
2018 std::for_each(ctx->mVoices.begin(), ctx->mVoices.end(), stop_voice);
2020 IncrementRef(device->MixCount);