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.
8 *
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.
13 *
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
19 */
20
21 #include "config.h"
22
23 #include "alu.h"
24
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>
42
43 #include "AL/al.h"
44 #include "AL/alc.h"
45 #include "AL/efx.h"
46
47 #include "alcmain.h"
48 #include "alcontext.h"
49 #include "almalloc.h"
50 #include "alnumeric.h"
51 #include "alspan.h"
52 #include "alstring.h"
53 #include "async_event.h"
54 #include "atomic.h"
55 #include "bformatdec.h"
56 #include "core/ambidefs.h"
57 #include "core/bs2b.h"
58 #include "core/bsinc_tables.h"
59 #include "core/cpu_caps.h"
60 #include "core/devformat.h"
61 #include "core/filters/biquad.h"
62 #include "core/filters/nfc.h"
63 #include "core/filters/splitter.h"
64 #include "core/fpu_ctrl.h"
65 #include "core/mastering.h"
66 #include "core/mixer/defs.h"
67 #include "core/uhjfilter.h"
68 #include "effects/base.h"
69 #include "effectslot.h"
70 #include "front_stablizer.h"
71 #include "hrtf.h"
72 #include "inprogext.h"
73 #include "math_defs.h"
74 #include "opthelpers.h"
75 #include "ringbuffer.h"
76 #include "strutils.h"
77 #include "threads.h"
78 #include "vecmat.h"
79 #include "voice.h"
80 #include "voice_change.h"
81
82 struct CTag;
83 #ifdef HAVE_SSE
84 struct SSETag;
85 #endif
86 #ifdef HAVE_SSE2
87 struct SSE2Tag;
88 #endif
89 #ifdef HAVE_SSE4_1
90 struct SSE4Tag;
91 #endif
92 #ifdef HAVE_NEON
93 struct NEONTag;
94 #endif
95 struct CopyTag;
96 struct PointTag;
97 struct LerpTag;
98 struct CubicTag;
99 struct BSincTag;
100 struct FastBSincTag;
101
102
103 static_assert(MaxResamplerPadding >= BSincPointsMax, "MaxResamplerPadding is too small");
104 static_assert(!(MaxResamplerPadding&1), "MaxResamplerPadding is not a multiple of two");
105
106
107 namespace {
108
109 constexpr uint MaxPitch{10};
110
111 static_assert((BufferLineSize-1)/MaxPitch > 0, "MaxPitch is too large for BufferLineSize!");
112 static_assert((INT_MAX>>MixerFracBits)/MaxPitch > BufferLineSize,
113 "MaxPitch and/or BufferLineSize are too large for MixerFracBits!");
114
115 using namespace std::placeholders;
116
InitConeScale()117 float InitConeScale()
118 {
119 float ret{1.0f};
120 if(auto optval = al::getenv("__ALSOFT_HALF_ANGLE_CONES"))
121 {
122 if(al::strcasecmp(optval->c_str(), "true") == 0
123 || strtol(optval->c_str(), nullptr, 0) == 1)
124 ret *= 0.5f;
125 }
126 return ret;
127 }
128
InitZScale()129 float InitZScale()
130 {
131 float ret{1.0f};
132 if(auto optval = al::getenv("__ALSOFT_REVERSE_Z"))
133 {
134 if(al::strcasecmp(optval->c_str(), "true") == 0
135 || strtol(optval->c_str(), nullptr, 0) == 1)
136 ret *= -1.0f;
137 }
138 return ret;
139 }
140
141 } // namespace
142
143 /* Cone scalar */
144 const float ConeScale{InitConeScale()};
145
146 /* Localized Z scalar for mono sources */
147 const float ZScale{InitZScale()};
148
149 namespace {
150
151 struct ChanMap {
152 Channel channel;
153 float angle;
154 float elevation;
155 };
156
157 using HrtfDirectMixerFunc = void(*)(FloatBufferLine &LeftOut, FloatBufferLine &RightOut,
158 const al::span<const FloatBufferLine> InSamples, float2 *AccumSamples,
159 float *TempBuf, HrtfChannelState *ChanState, const size_t IrSize, const size_t BufferSize);
160
161 HrtfDirectMixerFunc MixDirectHrtf{MixDirectHrtf_<CTag>};
162
SelectHrtfMixer(void)163 inline HrtfDirectMixerFunc SelectHrtfMixer(void)
164 {
165 #ifdef HAVE_NEON
166 if((CPUCapFlags&CPU_CAP_NEON))
167 return MixDirectHrtf_<NEONTag>;
168 #endif
169 #ifdef HAVE_SSE
170 if((CPUCapFlags&CPU_CAP_SSE))
171 return MixDirectHrtf_<SSETag>;
172 #endif
173
174 return MixDirectHrtf_<CTag>;
175 }
176
177
BsincPrepare(const uint increment,BsincState * state,const BSincTable * table)178 inline void BsincPrepare(const uint increment, BsincState *state, const BSincTable *table)
179 {
180 size_t si{BSincScaleCount - 1};
181 float sf{0.0f};
182
183 if(increment > MixerFracOne)
184 {
185 sf = MixerFracOne / static_cast<float>(increment);
186 sf = maxf(0.0f, (BSincScaleCount-1) * (sf-table->scaleBase) * table->scaleRange);
187 si = float2uint(sf);
188 /* The interpolation factor is fit to this diagonally-symmetric curve
189 * to reduce the transition ripple caused by interpolating different
190 * scales of the sinc function.
191 */
192 sf = 1.0f - std::cos(std::asin(sf - static_cast<float>(si)));
193 }
194
195 state->sf = sf;
196 state->m = table->m[si];
197 state->l = (state->m/2) - 1;
198 state->filter = table->Tab + table->filterOffset[si];
199 }
200
SelectResampler(Resampler resampler,uint increment)201 inline ResamplerFunc SelectResampler(Resampler resampler, uint increment)
202 {
203 switch(resampler)
204 {
205 case Resampler::Point:
206 return Resample_<PointTag,CTag>;
207 case Resampler::Linear:
208 #ifdef HAVE_NEON
209 if((CPUCapFlags&CPU_CAP_NEON))
210 return Resample_<LerpTag,NEONTag>;
211 #endif
212 #ifdef HAVE_SSE4_1
213 if((CPUCapFlags&CPU_CAP_SSE4_1))
214 return Resample_<LerpTag,SSE4Tag>;
215 #endif
216 #ifdef HAVE_SSE2
217 if((CPUCapFlags&CPU_CAP_SSE2))
218 return Resample_<LerpTag,SSE2Tag>;
219 #endif
220 return Resample_<LerpTag,CTag>;
221 case Resampler::Cubic:
222 return Resample_<CubicTag,CTag>;
223 case Resampler::BSinc12:
224 case Resampler::BSinc24:
225 if(increment <= MixerFracOne)
226 {
227 /* fall-through */
228 case Resampler::FastBSinc12:
229 case Resampler::FastBSinc24:
230 #ifdef HAVE_NEON
231 if((CPUCapFlags&CPU_CAP_NEON))
232 return Resample_<FastBSincTag,NEONTag>;
233 #endif
234 #ifdef HAVE_SSE
235 if((CPUCapFlags&CPU_CAP_SSE))
236 return Resample_<FastBSincTag,SSETag>;
237 #endif
238 return Resample_<FastBSincTag,CTag>;
239 }
240 #ifdef HAVE_NEON
241 if((CPUCapFlags&CPU_CAP_NEON))
242 return Resample_<BSincTag,NEONTag>;
243 #endif
244 #ifdef HAVE_SSE
245 if((CPUCapFlags&CPU_CAP_SSE))
246 return Resample_<BSincTag,SSETag>;
247 #endif
248 return Resample_<BSincTag,CTag>;
249 }
250
251 return Resample_<PointTag,CTag>;
252 }
253
254 } // namespace
255
aluInit(void)256 void aluInit(void)
257 {
258 MixDirectHrtf = SelectHrtfMixer();
259 }
260
261
PrepareResampler(Resampler resampler,uint increment,InterpState * state)262 ResamplerFunc PrepareResampler(Resampler resampler, uint increment, InterpState *state)
263 {
264 switch(resampler)
265 {
266 case Resampler::Point:
267 case Resampler::Linear:
268 case Resampler::Cubic:
269 break;
270 case Resampler::FastBSinc12:
271 case Resampler::BSinc12:
272 BsincPrepare(increment, &state->bsinc, &bsinc12);
273 break;
274 case Resampler::FastBSinc24:
275 case Resampler::BSinc24:
276 BsincPrepare(increment, &state->bsinc, &bsinc24);
277 break;
278 }
279 return SelectResampler(resampler, increment);
280 }
281
282
ProcessHrtf(const size_t SamplesToDo)283 void ALCdevice::ProcessHrtf(const size_t SamplesToDo)
284 {
285 /* HRTF is stereo output only. */
286 const uint lidx{RealOut.ChannelIndex[FrontLeft]};
287 const uint ridx{RealOut.ChannelIndex[FrontRight]};
288
289 MixDirectHrtf(RealOut.Buffer[lidx], RealOut.Buffer[ridx], Dry.Buffer, HrtfAccumData,
290 mHrtfState->mTemp.data(), mHrtfState->mChannels.data(), mHrtfState->mIrSize, SamplesToDo);
291 }
292
ProcessAmbiDec(const size_t SamplesToDo)293 void ALCdevice::ProcessAmbiDec(const size_t SamplesToDo)
294 {
295 AmbiDecoder->process(RealOut.Buffer, Dry.Buffer.data(), SamplesToDo);
296 }
297
ProcessAmbiDecStablized(const size_t SamplesToDo)298 void ALCdevice::ProcessAmbiDecStablized(const size_t SamplesToDo)
299 {
300 /* Decode with front image stablization. */
301 const uint lidx{RealOut.ChannelIndex[FrontLeft]};
302 const uint ridx{RealOut.ChannelIndex[FrontRight]};
303 const uint cidx{RealOut.ChannelIndex[FrontCenter]};
304
305 AmbiDecoder->processStablize(RealOut.Buffer, Dry.Buffer.data(), lidx, ridx, cidx,
306 SamplesToDo);
307 }
308
ProcessUhj(const size_t SamplesToDo)309 void ALCdevice::ProcessUhj(const size_t SamplesToDo)
310 {
311 /* UHJ is stereo output only. */
312 const uint lidx{RealOut.ChannelIndex[FrontLeft]};
313 const uint ridx{RealOut.ChannelIndex[FrontRight]};
314
315 /* Encode to stereo-compatible 2-channel UHJ output. */
316 Uhj_Encoder->encode(RealOut.Buffer[lidx], RealOut.Buffer[ridx], Dry.Buffer.data(),
317 SamplesToDo);
318 }
319
ProcessBs2b(const size_t SamplesToDo)320 void ALCdevice::ProcessBs2b(const size_t SamplesToDo)
321 {
322 /* First, decode the ambisonic mix to the "real" output. */
323 AmbiDecoder->process(RealOut.Buffer, Dry.Buffer.data(), SamplesToDo);
324
325 /* BS2B is stereo output only. */
326 const uint lidx{RealOut.ChannelIndex[FrontLeft]};
327 const uint ridx{RealOut.ChannelIndex[FrontRight]};
328
329 /* Now apply the BS2B binaural/crossfeed filter. */
330 bs2b_cross_feed(Bs2b.get(), RealOut.Buffer[lidx].data(), RealOut.Buffer[ridx].data(),
331 SamplesToDo);
332 }
333
334
335 namespace {
336
337 /* This RNG method was created based on the math found in opusdec. It's quick,
338 * and starting with a seed value of 22222, is suitable for generating
339 * whitenoise.
340 */
dither_rng(uint * seed)341 inline uint dither_rng(uint *seed) noexcept
342 {
343 *seed = (*seed * 96314165) + 907633515;
344 return *seed;
345 }
346
347
GetAmbiScales(AmbiScaling scaletype)348 inline auto& GetAmbiScales(AmbiScaling scaletype) noexcept
349 {
350 if(scaletype == AmbiScaling::FuMa) return AmbiScale::FromFuMa();
351 if(scaletype == AmbiScaling::SN3D) return AmbiScale::FromSN3D();
352 return AmbiScale::FromN3D();
353 }
354
GetAmbiLayout(AmbiLayout layouttype)355 inline auto& GetAmbiLayout(AmbiLayout layouttype) noexcept
356 {
357 if(layouttype == AmbiLayout::FuMa) return AmbiIndex::FromFuMa();
358 return AmbiIndex::FromACN();
359 }
360
GetAmbi2DLayout(AmbiLayout layouttype)361 inline auto& GetAmbi2DLayout(AmbiLayout layouttype) noexcept
362 {
363 if(layouttype == AmbiLayout::FuMa) return AmbiIndex::FromFuMa2D();
364 return AmbiIndex::FromACN2D();
365 }
366
367
CalcContextParams(ALCcontext * ctx)368 bool CalcContextParams(ALCcontext *ctx)
369 {
370 ContextProps *props{ctx->mParams.ContextUpdate.exchange(nullptr, std::memory_order_acq_rel)};
371 if(!props) return false;
372
373 ctx->mParams.DopplerFactor = props->DopplerFactor;
374 ctx->mParams.SpeedOfSound = props->SpeedOfSound * props->DopplerVelocity;
375
376 ctx->mParams.SourceDistanceModel = props->SourceDistanceModel;
377 ctx->mParams.mDistanceModel = props->mDistanceModel;
378
379 AtomicReplaceHead(ctx->mFreeContextProps, props);
380 return true;
381 }
382
CalcListenerParams(ALCcontext * ctx)383 bool CalcListenerParams(ALCcontext *ctx)
384 {
385 ListenerProps *props{ctx->mParams.ListenerUpdate.exchange(nullptr,
386 std::memory_order_acq_rel)};
387 if(!props) return false;
388
389 /* AT then UP */
390 alu::Vector N{props->OrientAt[0], props->OrientAt[1], props->OrientAt[2], 0.0f};
391 N.normalize();
392 alu::Vector V{props->OrientUp[0], props->OrientUp[1], props->OrientUp[2], 0.0f};
393 V.normalize();
394 /* Build and normalize right-vector */
395 alu::Vector U{N.cross_product(V)};
396 U.normalize();
397
398 const alu::MatrixR<double> rot{
399 U[0], V[0], -N[0], 0.0,
400 U[1], V[1], -N[1], 0.0,
401 U[2], V[2], -N[2], 0.0,
402 0.0, 0.0, 0.0, 1.0};
403 const alu::VectorR<double> pos{props->Position[0],props->Position[1],props->Position[2],1.0};
404 const alu::VectorR<double> vel{props->Velocity[0],props->Velocity[1],props->Velocity[2],0.0};
405 const alu::Vector P{alu::cast_to<float>(rot * pos)};
406
407 ctx->mParams.Matrix = alu::Matrix{
408 U[0], V[0], -N[0], 0.0f,
409 U[1], V[1], -N[1], 0.0f,
410 U[2], V[2], -N[2], 0.0f,
411 -P[0], -P[1], -P[2], 1.0f};
412 ctx->mParams.Velocity = alu::cast_to<float>(rot * vel);
413
414 ctx->mParams.Gain = props->Gain * ctx->mGainBoost;
415 ctx->mParams.MetersPerUnit = props->MetersPerUnit;
416
417 AtomicReplaceHead(ctx->mFreeListenerProps, props);
418 return true;
419 }
420
CalcEffectSlotParams(EffectSlot * slot,EffectSlot ** sorted_slots,ALCcontext * context)421 bool CalcEffectSlotParams(EffectSlot *slot, EffectSlot **sorted_slots, ALCcontext *context)
422 {
423 EffectSlotProps *props{slot->Update.exchange(nullptr, std::memory_order_acq_rel)};
424 if(!props) return false;
425
426 /* If the effect slot target changed, clear the first sorted entry to force
427 * a re-sort.
428 */
429 if(slot->Target != props->Target)
430 *sorted_slots = nullptr;
431 slot->Gain = props->Gain;
432 slot->AuxSendAuto = props->AuxSendAuto;
433 slot->Target = props->Target;
434 slot->EffectType = props->Type;
435 slot->mEffectProps = props->Props;
436 if(props->Type == EffectSlotType::Reverb || props->Type == EffectSlotType::EAXReverb)
437 {
438 slot->RoomRolloff = props->Props.Reverb.RoomRolloffFactor;
439 slot->DecayTime = props->Props.Reverb.DecayTime;
440 slot->DecayLFRatio = props->Props.Reverb.DecayLFRatio;
441 slot->DecayHFRatio = props->Props.Reverb.DecayHFRatio;
442 slot->DecayHFLimit = props->Props.Reverb.DecayHFLimit;
443 slot->AirAbsorptionGainHF = props->Props.Reverb.AirAbsorptionGainHF;
444 }
445 else
446 {
447 slot->RoomRolloff = 0.0f;
448 slot->DecayTime = 0.0f;
449 slot->DecayLFRatio = 0.0f;
450 slot->DecayHFRatio = 0.0f;
451 slot->DecayHFLimit = false;
452 slot->AirAbsorptionGainHF = 1.0f;
453 }
454
455 EffectState *state{props->State.release()};
456 EffectState *oldstate{slot->mEffectState};
457 slot->mEffectState = state;
458
459 /* Only release the old state if it won't get deleted, since we can't be
460 * deleting/freeing anything in the mixer.
461 */
462 if(!oldstate->releaseIfNoDelete())
463 {
464 /* Otherwise, if it would be deleted send it off with a release event. */
465 RingBuffer *ring{context->mAsyncEvents.get()};
466 auto evt_vec = ring->getWriteVector();
467 if LIKELY(evt_vec.first.len > 0)
468 {
469 AsyncEvent *evt{::new(evt_vec.first.buf) AsyncEvent{EventType_ReleaseEffectState}};
470 evt->u.mEffectState = oldstate;
471 ring->writeAdvance(1);
472 }
473 else
474 {
475 /* If writing the event failed, the queue was probably full. Store
476 * the old state in the property object where it can eventually be
477 * cleaned up sometime later (not ideal, but better than blocking
478 * or leaking).
479 */
480 props->State.reset(oldstate);
481 }
482 }
483
484 AtomicReplaceHead(context->mFreeEffectslotProps, props);
485
486 EffectTarget output;
487 if(EffectSlot *target{slot->Target})
488 output = EffectTarget{&target->Wet, nullptr};
489 else
490 {
491 ALCdevice *device{context->mDevice.get()};
492 output = EffectTarget{&device->Dry, &device->RealOut};
493 }
494 state->update(context, slot, &slot->mEffectProps, output);
495 return true;
496 }
497
498
499 /* Scales the given azimuth toward the side (+/- pi/2 radians) for positions in
500 * front.
501 */
ScaleAzimuthFront(float azimuth,float scale)502 inline float ScaleAzimuthFront(float azimuth, float scale)
503 {
504 const float abs_azi{std::fabs(azimuth)};
505 if(!(abs_azi >= al::MathDefs<float>::Pi()*0.5f))
506 return std::copysign(minf(abs_azi*scale, al::MathDefs<float>::Pi()*0.5f), azimuth);
507 return azimuth;
508 }
509
510 /* Wraps the given value in radians to stay between [-pi,+pi] */
WrapRadians(float r)511 inline float WrapRadians(float r)
512 {
513 constexpr float Pi{al::MathDefs<float>::Pi()};
514 constexpr float Pi2{al::MathDefs<float>::Tau()};
515 if(r > Pi) return std::fmod(Pi+r, Pi2) - Pi;
516 if(r < -Pi) return Pi - std::fmod(Pi-r, Pi2);
517 return r;
518 }
519
520 /* Begin ambisonic rotation helpers.
521 *
522 * Rotating first-order B-Format just needs a straight-forward X/Y/Z rotation
523 * matrix. Higher orders, however, are more complicated. The method implemented
524 * here is a recursive algorithm (the rotation for first-order is used to help
525 * generate the second-order rotation, which helps generate the third-order
526 * rotation, etc).
527 *
528 * Adapted from
529 * <https://github.com/polarch/Spherical-Harmonic-Transform/blob/master/getSHrotMtx.m>,
530 * provided under the BSD 3-Clause license.
531 *
532 * Copyright (c) 2015, Archontis Politis
533 * Copyright (c) 2019, Christopher Robinson
534 *
535 * The u, v, and w coefficients used for generating higher-order rotations are
536 * precomputed since they're constant. The second-order coefficients are
537 * followed by the third-order coefficients, etc.
538 */
539 struct RotatorCoeffs {
540 float u, v, w;
541
542 template<size_t N0, size_t N1>
ConcatArrays__anon2eaeabf90311::RotatorCoeffs543 static std::array<RotatorCoeffs,N0+N1> ConcatArrays(const std::array<RotatorCoeffs,N0> &lhs,
544 const std::array<RotatorCoeffs,N1> &rhs)
545 {
546 std::array<RotatorCoeffs,N0+N1> ret;
547 auto iter = std::copy(lhs.cbegin(), lhs.cend(), ret.begin());
548 std::copy(rhs.cbegin(), rhs.cend(), iter);
549 return ret;
550 }
551
552 template<int l, int num_elems=l*2+1>
GenCoeffs__anon2eaeabf90311::RotatorCoeffs553 static std::array<RotatorCoeffs,num_elems*num_elems> GenCoeffs()
554 {
555 std::array<RotatorCoeffs,num_elems*num_elems> ret{};
556 auto coeffs = ret.begin();
557
558 for(int m{-l};m <= l;++m)
559 {
560 for(int n{-l};n <= l;++n)
561 {
562 // compute u,v,w terms of Eq.8.1 (Table I)
563 const bool d{m == 0}; // the delta function d_m0
564 const float denom{static_cast<float>((std::abs(n) == l) ?
565 (2*l) * (2*l - 1) : (l*l - n*n))};
566
567 const int abs_m{std::abs(m)};
568 coeffs->u = std::sqrt(static_cast<float>(l*l - m*m)/denom);
569 coeffs->v = std::sqrt(static_cast<float>(l+abs_m-1) * static_cast<float>(l+abs_m) /
570 denom) * (1.0f+d) * (1.0f - 2.0f*d) * 0.5f;
571 coeffs->w = std::sqrt(static_cast<float>(l-abs_m-1) * static_cast<float>(l-abs_m) /
572 denom) * (1.0f-d) * -0.5f;
573 ++coeffs;
574 }
575 }
576
577 return ret;
578 }
579 };
580 const auto RotatorCoeffArray = RotatorCoeffs::ConcatArrays(RotatorCoeffs::GenCoeffs<2>(),
581 RotatorCoeffs::GenCoeffs<3>());
582
583 /**
584 * Given the matrix, pre-filled with the (zeroth- and) first-order rotation
585 * coefficients, this fills in the coefficients for the higher orders up to and
586 * including the given order. The matrix is in ACN layout.
587 */
AmbiRotator(std::array<std::array<float,MaxAmbiChannels>,MaxAmbiChannels> & matrix,const int order)588 void AmbiRotator(std::array<std::array<float,MaxAmbiChannels>,MaxAmbiChannels> &matrix,
589 const int order)
590 {
591 /* Don't do anything for < 2nd order. */
592 if(order < 2) return;
593
594 auto P = [](const int i, const int l, const int a, const int n, const size_t last_band,
595 const std::array<std::array<float,MaxAmbiChannels>,MaxAmbiChannels> &R)
596 {
597 const float ri1{ R[static_cast<uint>(i+2)][ 1+2]};
598 const float rim1{R[static_cast<uint>(i+2)][-1+2]};
599 const float ri0{ R[static_cast<uint>(i+2)][ 0+2]};
600
601 auto vec = R[static_cast<uint>(a+l-1) + last_band].cbegin() + last_band;
602 if(n == -l)
603 return ri1*vec[0] + rim1*vec[static_cast<uint>(l-1)*size_t{2}];
604 if(n == l)
605 return ri1*vec[static_cast<uint>(l-1)*size_t{2}] - rim1*vec[0];
606 return ri0*vec[static_cast<uint>(n+l-1)];
607 };
608
609 auto U = [P](const int l, const int m, const int n, const size_t last_band,
610 const std::array<std::array<float,MaxAmbiChannels>,MaxAmbiChannels> &R)
611 {
612 return P(0, l, m, n, last_band, R);
613 };
614 auto V = [P](const int l, const int m, const int n, const size_t last_band,
615 const std::array<std::array<float,MaxAmbiChannels>,MaxAmbiChannels> &R)
616 {
617 if(m > 0)
618 {
619 const bool d{m == 1};
620 const float p0{P( 1, l, m-1, n, last_band, R)};
621 const float p1{P(-1, l, -m+1, n, last_band, R)};
622 return d ? p0*std::sqrt(2.0f) : (p0 - p1);
623 }
624 const bool d{m == -1};
625 const float p0{P( 1, l, m+1, n, last_band, R)};
626 const float p1{P(-1, l, -m-1, n, last_band, R)};
627 return d ? p1*std::sqrt(2.0f) : (p0 + p1);
628 };
629 auto W = [P](const int l, const int m, const int n, const size_t last_band,
630 const std::array<std::array<float,MaxAmbiChannels>,MaxAmbiChannels> &R)
631 {
632 assert(m != 0);
633 if(m > 0)
634 {
635 const float p0{P( 1, l, m+1, n, last_band, R)};
636 const float p1{P(-1, l, -m-1, n, last_band, R)};
637 return p0 + p1;
638 }
639 const float p0{P( 1, l, m-1, n, last_band, R)};
640 const float p1{P(-1, l, -m+1, n, last_band, R)};
641 return p0 - p1;
642 };
643
644 // compute rotation matrix of each subsequent band recursively
645 auto coeffs = RotatorCoeffArray.cbegin();
646 size_t band_idx{4}, last_band{1};
647 for(int l{2};l <= order;++l)
648 {
649 size_t y{band_idx};
650 for(int m{-l};m <= l;++m,++y)
651 {
652 size_t x{band_idx};
653 for(int n{-l};n <= l;++n,++x)
654 {
655 float r{0.0f};
656
657 // computes Eq.8.1
658 const float u{coeffs->u};
659 if(u != 0.0f) r += u * U(l, m, n, last_band, matrix);
660 const float v{coeffs->v};
661 if(v != 0.0f) r += v * V(l, m, n, last_band, matrix);
662 const float w{coeffs->w};
663 if(w != 0.0f) r += w * W(l, m, n, last_band, matrix);
664
665 matrix[y][x] = r;
666 ++coeffs;
667 }
668 }
669 last_band = band_idx;
670 band_idx += static_cast<uint>(l)*size_t{2} + 1;
671 }
672 }
673 /* End ambisonic rotation helpers. */
674
675
676 struct GainTriplet { float Base, HF, LF; };
677
CalcPanningAndFilters(Voice * voice,const float xpos,const float ypos,const float zpos,const float Distance,const float Spread,const GainTriplet & DryGain,const al::span<const GainTriplet,MAX_SENDS> WetGain,EffectSlot * (& SendSlots)[MAX_SENDS],const VoiceProps * props,const ContextParams & Context,const ALCdevice * Device)678 void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, const float zpos,
679 const float Distance, const float Spread, const GainTriplet &DryGain,
680 const al::span<const GainTriplet,MAX_SENDS> WetGain, EffectSlot *(&SendSlots)[MAX_SENDS],
681 const VoiceProps *props, const ContextParams &Context, const ALCdevice *Device)
682 {
683 static const ChanMap MonoMap[1]{
684 { FrontCenter, 0.0f, 0.0f }
685 }, RearMap[2]{
686 { BackLeft, Deg2Rad(-150.0f), Deg2Rad(0.0f) },
687 { BackRight, Deg2Rad( 150.0f), Deg2Rad(0.0f) }
688 }, QuadMap[4]{
689 { FrontLeft, Deg2Rad( -45.0f), Deg2Rad(0.0f) },
690 { FrontRight, Deg2Rad( 45.0f), Deg2Rad(0.0f) },
691 { BackLeft, Deg2Rad(-135.0f), Deg2Rad(0.0f) },
692 { BackRight, Deg2Rad( 135.0f), Deg2Rad(0.0f) }
693 }, X51Map[6]{
694 { FrontLeft, Deg2Rad( -30.0f), Deg2Rad(0.0f) },
695 { FrontRight, Deg2Rad( 30.0f), Deg2Rad(0.0f) },
696 { FrontCenter, Deg2Rad( 0.0f), Deg2Rad(0.0f) },
697 { LFE, 0.0f, 0.0f },
698 { SideLeft, Deg2Rad(-110.0f), Deg2Rad(0.0f) },
699 { SideRight, Deg2Rad( 110.0f), Deg2Rad(0.0f) }
700 }, X61Map[7]{
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 { BackCenter, Deg2Rad(180.0f), Deg2Rad(0.0f) },
706 { SideLeft, Deg2Rad(-90.0f), Deg2Rad(0.0f) },
707 { SideRight, Deg2Rad( 90.0f), Deg2Rad(0.0f) }
708 }, X71Map[8]{
709 { FrontLeft, Deg2Rad( -30.0f), Deg2Rad(0.0f) },
710 { FrontRight, Deg2Rad( 30.0f), Deg2Rad(0.0f) },
711 { FrontCenter, Deg2Rad( 0.0f), Deg2Rad(0.0f) },
712 { LFE, 0.0f, 0.0f },
713 { BackLeft, Deg2Rad(-150.0f), Deg2Rad(0.0f) },
714 { BackRight, Deg2Rad( 150.0f), Deg2Rad(0.0f) },
715 { SideLeft, Deg2Rad( -90.0f), Deg2Rad(0.0f) },
716 { SideRight, Deg2Rad( 90.0f), Deg2Rad(0.0f) }
717 };
718
719 ChanMap StereoMap[2]{
720 { FrontLeft, Deg2Rad(-30.0f), Deg2Rad(0.0f) },
721 { FrontRight, Deg2Rad( 30.0f), Deg2Rad(0.0f) }
722 };
723
724 const auto Frequency = static_cast<float>(Device->Frequency);
725 const uint NumSends{Device->NumAuxSends};
726
727 const size_t num_channels{voice->mChans.size()};
728 ASSUME(num_channels > 0);
729
730 for(auto &chandata : voice->mChans)
731 {
732 chandata.mDryParams.Hrtf.Target = HrtfFilter{};
733 chandata.mDryParams.Gains.Target.fill(0.0f);
734 std::for_each(chandata.mWetParams.begin(), chandata.mWetParams.begin()+NumSends,
735 [](SendParams ¶ms) -> void { params.Gains.Target.fill(0.0f); });
736 }
737
738 DirectMode DirectChannels{props->DirectChannels};
739 const ChanMap *chans{nullptr};
740 float downmix_gain{1.0f};
741 switch(voice->mFmtChannels)
742 {
743 case FmtMono:
744 chans = MonoMap;
745 /* Mono buffers are never played direct. */
746 DirectChannels = DirectMode::Off;
747 break;
748
749 case FmtStereo:
750 if(DirectChannels == DirectMode::Off)
751 {
752 /* Convert counter-clockwise to clock-wise, and wrap between
753 * [-pi,+pi].
754 */
755 StereoMap[0].angle = WrapRadians(-props->StereoPan[0]);
756 StereoMap[1].angle = WrapRadians(-props->StereoPan[1]);
757 }
758
759 chans = StereoMap;
760 downmix_gain = 1.0f / 2.0f;
761 break;
762
763 case FmtRear:
764 chans = RearMap;
765 downmix_gain = 1.0f / 2.0f;
766 break;
767
768 case FmtQuad:
769 chans = QuadMap;
770 downmix_gain = 1.0f / 4.0f;
771 break;
772
773 case FmtX51:
774 chans = X51Map;
775 /* NOTE: Excludes LFE. */
776 downmix_gain = 1.0f / 5.0f;
777 break;
778
779 case FmtX61:
780 chans = X61Map;
781 /* NOTE: Excludes LFE. */
782 downmix_gain = 1.0f / 6.0f;
783 break;
784
785 case FmtX71:
786 chans = X71Map;
787 /* NOTE: Excludes LFE. */
788 downmix_gain = 1.0f / 7.0f;
789 break;
790
791 case FmtBFormat2D:
792 case FmtBFormat3D:
793 DirectChannels = DirectMode::Off;
794 break;
795 }
796
797 voice->mFlags &= ~(VoiceHasHrtf | VoiceHasNfc);
798 if(voice->mFmtChannels == FmtBFormat2D || voice->mFmtChannels == FmtBFormat3D)
799 {
800 /* Special handling for B-Format sources. */
801
802 if(Device->AvgSpeakerDist > 0.0f)
803 {
804 if(!(Distance > std::numeric_limits<float>::epsilon()))
805 {
806 /* NOTE: The NFCtrlFilters were created with a w0 of 0, which
807 * is what we want for FOA input. The first channel may have
808 * been previously re-adjusted if panned, so reset it.
809 */
810 voice->mChans[0].mDryParams.NFCtrlFilter.adjust(0.0f);
811 }
812 else
813 {
814 /* Clamp the distance for really close sources, to prevent
815 * excessive bass.
816 */
817 const float mdist{maxf(Distance, Device->AvgSpeakerDist/4.0f)};
818 const float w0{SpeedOfSoundMetersPerSec / (mdist * Frequency)};
819
820 /* Only need to adjust the first channel of a B-Format source. */
821 voice->mChans[0].mDryParams.NFCtrlFilter.adjust(w0);
822 }
823
824 voice->mFlags |= VoiceHasNfc;
825 }
826
827 /* Panning a B-Format sound toward some direction is easy. Just pan the
828 * first (W) channel as a normal mono sound. The angular spread is used
829 * as a directional scalar to blend between full coverage and full
830 * panning.
831 */
832 const float coverage{!(Distance > std::numeric_limits<float>::epsilon()) ? 1.0f :
833 (Spread * (1.0f/al::MathDefs<float>::Tau()))};
834
835 auto calc_coeffs = [xpos,ypos,zpos](RenderMode mode)
836 {
837 if(mode != RenderMode::Pairwise)
838 return CalcDirectionCoeffs({xpos, ypos, zpos}, 0.0f);
839
840 /* Clamp Y, in case rounding errors caused it to end up outside
841 * of -1...+1.
842 */
843 const float ev{std::asin(clampf(ypos, -1.0f, 1.0f))};
844 /* Negate Z for right-handed coords with -Z in front. */
845 const float az{std::atan2(xpos, -zpos)};
846
847 /* A scalar of 1.5 for plain stereo results in +/-60 degrees
848 * being moved to +/-90 degrees for direct right and left
849 * speaker responses.
850 */
851 return CalcAngleCoeffs(ScaleAzimuthFront(az, 1.5f), ev, 0.0f);
852 };
853 auto coeffs = calc_coeffs(Device->mRenderMode);
854 std::transform(coeffs.begin()+1, coeffs.end(), coeffs.begin()+1,
855 std::bind(std::multiplies<float>{}, _1, 1.0f-coverage));
856
857 /* NOTE: W needs to be scaled according to channel scaling. */
858 auto&& scales = GetAmbiScales(voice->mAmbiScaling);
859 ComputePanGains(&Device->Dry, coeffs.data(), DryGain.Base*scales[0],
860 voice->mChans[0].mDryParams.Gains.Target);
861 for(uint i{0};i < NumSends;i++)
862 {
863 if(const EffectSlot *Slot{SendSlots[i]})
864 ComputePanGains(&Slot->Wet, coeffs.data(), WetGain[i].Base*scales[0],
865 voice->mChans[0].mWetParams[i].Gains.Target);
866 }
867
868 if(coverage > 0.0f)
869 {
870 /* Local B-Format sources have their XYZ channels rotated according
871 * to the orientation.
872 */
873 /* AT then UP */
874 alu::Vector N{props->OrientAt[0], props->OrientAt[1], props->OrientAt[2], 0.0f};
875 N.normalize();
876 alu::Vector V{props->OrientUp[0], props->OrientUp[1], props->OrientUp[2], 0.0f};
877 V.normalize();
878 if(!props->HeadRelative)
879 {
880 N = Context.Matrix * N;
881 V = Context.Matrix * V;
882 }
883 /* Build and normalize right-vector */
884 alu::Vector U{N.cross_product(V)};
885 U.normalize();
886
887 /* Build a rotation matrix. Manually fill the zeroth- and first-
888 * order elements, then construct the rotation for the higher
889 * orders.
890 */
891 std::array<std::array<float,MaxAmbiChannels>,MaxAmbiChannels> shrot{};
892 shrot[0][0] = 1.0f;
893 shrot[1][1] = U[0]; shrot[1][2] = -V[0]; shrot[1][3] = -N[0];
894 shrot[2][1] = -U[1]; shrot[2][2] = V[1]; shrot[2][3] = N[1];
895 shrot[3][1] = U[2]; shrot[3][2] = -V[2]; shrot[3][3] = -N[2];
896 AmbiRotator(shrot, static_cast<int>(minu(voice->mAmbiOrder, Device->mAmbiOrder)));
897
898 /* Convert the rotation matrix for input ordering and scaling, and
899 * whether input is 2D or 3D.
900 */
901 const uint8_t *index_map{(voice->mFmtChannels == FmtBFormat2D) ?
902 GetAmbi2DLayout(voice->mAmbiLayout).data() :
903 GetAmbiLayout(voice->mAmbiLayout).data()};
904
905 static const uint8_t ChansPerOrder[MaxAmbiOrder+1]{1, 3, 5, 7,};
906 static const uint8_t OrderOffset[MaxAmbiOrder+1]{0, 1, 4, 9,};
907 for(size_t c{1};c < num_channels;c++)
908 {
909 const size_t acn{index_map[c]};
910 const size_t order{AmbiIndex::OrderFromChannel()[acn]};
911 const size_t tocopy{ChansPerOrder[order]};
912 const size_t offset{OrderOffset[order]};
913 const float scale{scales[acn] * coverage};
914 auto in = shrot.cbegin() + offset;
915
916 coeffs = std::array<float,MaxAmbiChannels>{};
917 for(size_t x{0};x < tocopy;++x)
918 coeffs[offset+x] = in[x][acn] * scale;
919
920 ComputePanGains(&Device->Dry, coeffs.data(), DryGain.Base,
921 voice->mChans[c].mDryParams.Gains.Target);
922
923 for(uint i{0};i < NumSends;i++)
924 {
925 if(const EffectSlot *Slot{SendSlots[i]})
926 ComputePanGains(&Slot->Wet, coeffs.data(), WetGain[i].Base,
927 voice->mChans[c].mWetParams[i].Gains.Target);
928 }
929 }
930 }
931 }
932 else if(DirectChannels != DirectMode::Off && Device->FmtChans != DevFmtAmbi3D)
933 {
934 /* Direct source channels always play local. Skip the virtual channels
935 * and write inputs to the matching real outputs.
936 */
937 voice->mDirect.Buffer = Device->RealOut.Buffer;
938
939 for(size_t c{0};c < num_channels;c++)
940 {
941 uint idx{GetChannelIdxByName(Device->RealOut, chans[c].channel)};
942 if(idx != INVALID_CHANNEL_INDEX)
943 voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain.Base;
944 else if(DirectChannels == DirectMode::RemixMismatch)
945 {
946 auto match_channel = [chans,c](const InputRemixMap &map) noexcept -> bool
947 { return chans[c].channel == map.channel; };
948 auto remap = std::find_if(Device->RealOut.RemixMap.cbegin(),
949 Device->RealOut.RemixMap.cend(), match_channel);
950 if(remap != Device->RealOut.RemixMap.cend())
951 for(const auto &target : remap->targets)
952 {
953 idx = GetChannelIdxByName(Device->RealOut, target.channel);
954 if(idx != INVALID_CHANNEL_INDEX)
955 voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain.Base *
956 target.mix;
957 }
958 }
959 }
960
961 /* Auxiliary sends still use normal channel panning since they mix to
962 * B-Format, which can't channel-match.
963 */
964 for(size_t c{0};c < num_channels;c++)
965 {
966 const auto coeffs = CalcAngleCoeffs(chans[c].angle, chans[c].elevation, 0.0f);
967
968 for(uint i{0};i < NumSends;i++)
969 {
970 if(const EffectSlot *Slot{SendSlots[i]})
971 ComputePanGains(&Slot->Wet, coeffs.data(), WetGain[i].Base,
972 voice->mChans[c].mWetParams[i].Gains.Target);
973 }
974 }
975 }
976 else if(Device->mRenderMode == RenderMode::Hrtf)
977 {
978 /* Full HRTF rendering. Skip the virtual channels and render to the
979 * real outputs.
980 */
981 voice->mDirect.Buffer = Device->RealOut.Buffer;
982
983 if(Distance > std::numeric_limits<float>::epsilon())
984 {
985 const float ev{std::asin(clampf(ypos, -1.0f, 1.0f))};
986 const float az{std::atan2(xpos, -zpos)};
987
988 /* Get the HRIR coefficients and delays just once, for the given
989 * source direction.
990 */
991 GetHrtfCoeffs(Device->mHrtf.get(), ev, az, Distance, Spread,
992 voice->mChans[0].mDryParams.Hrtf.Target.Coeffs,
993 voice->mChans[0].mDryParams.Hrtf.Target.Delay);
994 voice->mChans[0].mDryParams.Hrtf.Target.Gain = DryGain.Base * downmix_gain;
995
996 /* Remaining channels use the same results as the first. */
997 for(size_t c{1};c < num_channels;c++)
998 {
999 /* Skip LFE */
1000 if(chans[c].channel == LFE) continue;
1001 voice->mChans[c].mDryParams.Hrtf.Target = voice->mChans[0].mDryParams.Hrtf.Target;
1002 }
1003
1004 /* Calculate the directional coefficients once, which apply to all
1005 * input channels of the source sends.
1006 */
1007 const auto coeffs = CalcDirectionCoeffs({xpos, ypos, zpos}, Spread);
1008
1009 for(size_t c{0};c < num_channels;c++)
1010 {
1011 /* Skip LFE */
1012 if(chans[c].channel == LFE)
1013 continue;
1014 for(uint i{0};i < NumSends;i++)
1015 {
1016 if(const EffectSlot *Slot{SendSlots[i]})
1017 ComputePanGains(&Slot->Wet, coeffs.data(), WetGain[i].Base * downmix_gain,
1018 voice->mChans[c].mWetParams[i].Gains.Target);
1019 }
1020 }
1021 }
1022 else
1023 {
1024 /* Local sources on HRTF play with each channel panned to its
1025 * relative location around the listener, providing "virtual
1026 * speaker" responses.
1027 */
1028 for(size_t c{0};c < num_channels;c++)
1029 {
1030 /* Skip LFE */
1031 if(chans[c].channel == LFE)
1032 continue;
1033
1034 /* Get the HRIR coefficients and delays for this channel
1035 * position.
1036 */
1037 GetHrtfCoeffs(Device->mHrtf.get(), chans[c].elevation, chans[c].angle,
1038 std::numeric_limits<float>::infinity(), Spread,
1039 voice->mChans[c].mDryParams.Hrtf.Target.Coeffs,
1040 voice->mChans[c].mDryParams.Hrtf.Target.Delay);
1041 voice->mChans[c].mDryParams.Hrtf.Target.Gain = DryGain.Base;
1042
1043 /* Normal panning for auxiliary sends. */
1044 const auto coeffs = CalcAngleCoeffs(chans[c].angle, chans[c].elevation, Spread);
1045
1046 for(uint i{0};i < NumSends;i++)
1047 {
1048 if(const EffectSlot *Slot{SendSlots[i]})
1049 ComputePanGains(&Slot->Wet, coeffs.data(), WetGain[i].Base,
1050 voice->mChans[c].mWetParams[i].Gains.Target);
1051 }
1052 }
1053 }
1054
1055 voice->mFlags |= VoiceHasHrtf;
1056 }
1057 else
1058 {
1059 /* Non-HRTF rendering. Use normal panning to the output. */
1060
1061 if(Distance > std::numeric_limits<float>::epsilon())
1062 {
1063 /* Calculate NFC filter coefficient if needed. */
1064 if(Device->AvgSpeakerDist > 0.0f)
1065 {
1066 /* Clamp the distance for really close sources, to prevent
1067 * excessive bass.
1068 */
1069 const float mdist{maxf(Distance, Device->AvgSpeakerDist/4.0f)};
1070 const float w0{SpeedOfSoundMetersPerSec / (mdist * Frequency)};
1071
1072 /* Adjust NFC filters. */
1073 for(size_t c{0};c < num_channels;c++)
1074 voice->mChans[c].mDryParams.NFCtrlFilter.adjust(w0);
1075
1076 voice->mFlags |= VoiceHasNfc;
1077 }
1078
1079 /* Calculate the directional coefficients once, which apply to all
1080 * input channels.
1081 */
1082 auto calc_coeffs = [xpos,ypos,zpos,Spread](RenderMode mode)
1083 {
1084 if(mode != RenderMode::Pairwise)
1085 return CalcDirectionCoeffs({xpos, ypos, zpos}, Spread);
1086 const float ev{std::asin(clampf(ypos, -1.0f, 1.0f))};
1087 const float az{std::atan2(xpos, -zpos)};
1088 return CalcAngleCoeffs(ScaleAzimuthFront(az, 1.5f), ev, Spread);
1089 };
1090 const auto coeffs = calc_coeffs(Device->mRenderMode);
1091
1092 for(size_t c{0};c < num_channels;c++)
1093 {
1094 /* Special-case LFE */
1095 if(chans[c].channel == LFE)
1096 {
1097 if(Device->Dry.Buffer.data() == Device->RealOut.Buffer.data())
1098 {
1099 const uint idx{GetChannelIdxByName(Device->RealOut, chans[c].channel)};
1100 if(idx != INVALID_CHANNEL_INDEX)
1101 voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain.Base;
1102 }
1103 continue;
1104 }
1105
1106 ComputePanGains(&Device->Dry, coeffs.data(), DryGain.Base * downmix_gain,
1107 voice->mChans[c].mDryParams.Gains.Target);
1108 for(uint i{0};i < NumSends;i++)
1109 {
1110 if(const EffectSlot *Slot{SendSlots[i]})
1111 ComputePanGains(&Slot->Wet, coeffs.data(), WetGain[i].Base * downmix_gain,
1112 voice->mChans[c].mWetParams[i].Gains.Target);
1113 }
1114 }
1115 }
1116 else
1117 {
1118 if(Device->AvgSpeakerDist > 0.0f)
1119 {
1120 /* If the source distance is 0, simulate a plane-wave by using
1121 * infinite distance, which results in a w0 of 0.
1122 */
1123 constexpr float w0{0.0f};
1124 for(size_t c{0};c < num_channels;c++)
1125 voice->mChans[c].mDryParams.NFCtrlFilter.adjust(w0);
1126
1127 voice->mFlags |= VoiceHasNfc;
1128 }
1129
1130 for(size_t c{0};c < num_channels;c++)
1131 {
1132 /* Special-case LFE */
1133 if(chans[c].channel == LFE)
1134 {
1135 if(Device->Dry.Buffer.data() == Device->RealOut.Buffer.data())
1136 {
1137 const uint idx{GetChannelIdxByName(Device->RealOut, chans[c].channel)};
1138 if(idx != INVALID_CHANNEL_INDEX)
1139 voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain.Base;
1140 }
1141 continue;
1142 }
1143
1144 const auto coeffs = CalcAngleCoeffs((Device->mRenderMode == RenderMode::Pairwise)
1145 ? ScaleAzimuthFront(chans[c].angle, 3.0f) : chans[c].angle,
1146 chans[c].elevation, Spread);
1147
1148 ComputePanGains(&Device->Dry, coeffs.data(), DryGain.Base,
1149 voice->mChans[c].mDryParams.Gains.Target);
1150 for(uint i{0};i < NumSends;i++)
1151 {
1152 if(const EffectSlot *Slot{SendSlots[i]})
1153 ComputePanGains(&Slot->Wet, coeffs.data(), WetGain[i].Base,
1154 voice->mChans[c].mWetParams[i].Gains.Target);
1155 }
1156 }
1157 }
1158 }
1159
1160 {
1161 const float hfNorm{props->Direct.HFReference / Frequency};
1162 const float lfNorm{props->Direct.LFReference / Frequency};
1163
1164 voice->mDirect.FilterType = AF_None;
1165 if(DryGain.HF != 1.0f) voice->mDirect.FilterType |= AF_LowPass;
1166 if(DryGain.LF != 1.0f) voice->mDirect.FilterType |= AF_HighPass;
1167
1168 auto &lowpass = voice->mChans[0].mDryParams.LowPass;
1169 auto &highpass = voice->mChans[0].mDryParams.HighPass;
1170 lowpass.setParamsFromSlope(BiquadType::HighShelf, hfNorm, DryGain.HF, 1.0f);
1171 highpass.setParamsFromSlope(BiquadType::LowShelf, lfNorm, DryGain.LF, 1.0f);
1172 for(size_t c{1};c < num_channels;c++)
1173 {
1174 voice->mChans[c].mDryParams.LowPass.copyParamsFrom(lowpass);
1175 voice->mChans[c].mDryParams.HighPass.copyParamsFrom(highpass);
1176 }
1177 }
1178 for(uint i{0};i < NumSends;i++)
1179 {
1180 const float hfNorm{props->Send[i].HFReference / Frequency};
1181 const float lfNorm{props->Send[i].LFReference / Frequency};
1182
1183 voice->mSend[i].FilterType = AF_None;
1184 if(WetGain[i].HF != 1.0f) voice->mSend[i].FilterType |= AF_LowPass;
1185 if(WetGain[i].LF != 1.0f) voice->mSend[i].FilterType |= AF_HighPass;
1186
1187 auto &lowpass = voice->mChans[0].mWetParams[i].LowPass;
1188 auto &highpass = voice->mChans[0].mWetParams[i].HighPass;
1189 lowpass.setParamsFromSlope(BiquadType::HighShelf, hfNorm, WetGain[i].HF, 1.0f);
1190 highpass.setParamsFromSlope(BiquadType::LowShelf, lfNorm, WetGain[i].LF, 1.0f);
1191 for(size_t c{1};c < num_channels;c++)
1192 {
1193 voice->mChans[c].mWetParams[i].LowPass.copyParamsFrom(lowpass);
1194 voice->mChans[c].mWetParams[i].HighPass.copyParamsFrom(highpass);
1195 }
1196 }
1197 }
1198
CalcNonAttnSourceParams(Voice * voice,const VoiceProps * props,const ALCcontext * context)1199 void CalcNonAttnSourceParams(Voice *voice, const VoiceProps *props, const ALCcontext *context)
1200 {
1201 const ALCdevice *Device{context->mDevice.get()};
1202 EffectSlot *SendSlots[MAX_SENDS];
1203
1204 voice->mDirect.Buffer = Device->Dry.Buffer;
1205 for(uint i{0};i < Device->NumAuxSends;i++)
1206 {
1207 SendSlots[i] = props->Send[i].Slot;
1208 if(!SendSlots[i] || SendSlots[i]->EffectType == EffectSlotType::None)
1209 {
1210 SendSlots[i] = nullptr;
1211 voice->mSend[i].Buffer = {};
1212 }
1213 else
1214 voice->mSend[i].Buffer = SendSlots[i]->Wet.Buffer;
1215 }
1216
1217 /* Calculate the stepping value */
1218 const auto Pitch = static_cast<float>(voice->mFrequency) /
1219 static_cast<float>(Device->Frequency) * props->Pitch;
1220 if(Pitch > float{MaxPitch})
1221 voice->mStep = MaxPitch<<MixerFracBits;
1222 else
1223 voice->mStep = maxu(fastf2u(Pitch * MixerFracOne), 1);
1224 voice->mResampler = PrepareResampler(props->mResampler, voice->mStep, &voice->mResampleState);
1225
1226 /* Calculate gains */
1227 GainTriplet DryGain;
1228 DryGain.Base = minf(clampf(props->Gain, props->MinGain, props->MaxGain) * props->Direct.Gain *
1229 context->mParams.Gain, GainMixMax);
1230 DryGain.HF = props->Direct.GainHF;
1231 DryGain.LF = props->Direct.GainLF;
1232 GainTriplet WetGain[MAX_SENDS];
1233 for(uint i{0};i < Device->NumAuxSends;i++)
1234 {
1235 WetGain[i].Base = minf(clampf(props->Gain, props->MinGain, props->MaxGain) *
1236 props->Send[i].Gain * context->mParams.Gain, GainMixMax);
1237 WetGain[i].HF = props->Send[i].GainHF;
1238 WetGain[i].LF = props->Send[i].GainLF;
1239 }
1240
1241 CalcPanningAndFilters(voice, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, DryGain, WetGain, SendSlots, props,
1242 context->mParams, Device);
1243 }
1244
CalcAttnSourceParams(Voice * voice,const VoiceProps * props,const ALCcontext * context)1245 void CalcAttnSourceParams(Voice *voice, const VoiceProps *props, const ALCcontext *context)
1246 {
1247 const ALCdevice *Device{context->mDevice.get()};
1248 const uint NumSends{Device->NumAuxSends};
1249
1250 /* Set mixing buffers and get send parameters. */
1251 voice->mDirect.Buffer = Device->Dry.Buffer;
1252 EffectSlot *SendSlots[MAX_SENDS];
1253 float RoomRolloff[MAX_SENDS];
1254 GainTriplet DecayDistance[MAX_SENDS];
1255 for(uint i{0};i < NumSends;i++)
1256 {
1257 SendSlots[i] = props->Send[i].Slot;
1258 if(!SendSlots[i] || SendSlots[i]->EffectType == EffectSlotType::None)
1259 {
1260 SendSlots[i] = nullptr;
1261 RoomRolloff[i] = 0.0f;
1262 DecayDistance[i].Base = 0.0f;
1263 DecayDistance[i].LF = 0.0f;
1264 DecayDistance[i].HF = 0.0f;
1265 }
1266 else if(SendSlots[i]->AuxSendAuto)
1267 {
1268 RoomRolloff[i] = SendSlots[i]->RoomRolloff + props->RoomRolloffFactor;
1269 /* Calculate the distances to where this effect's decay reaches
1270 * -60dB.
1271 */
1272 DecayDistance[i].Base = SendSlots[i]->DecayTime * SpeedOfSoundMetersPerSec;
1273 DecayDistance[i].LF = DecayDistance[i].Base * SendSlots[i]->DecayLFRatio;
1274 DecayDistance[i].HF = DecayDistance[i].Base * SendSlots[i]->DecayHFRatio;
1275 if(SendSlots[i]->DecayHFLimit)
1276 {
1277 const float airAbsorption{SendSlots[i]->AirAbsorptionGainHF};
1278 if(airAbsorption < 1.0f)
1279 {
1280 /* Calculate the distance to where this effect's air
1281 * absorption reaches -60dB, and limit the effect's HF
1282 * decay distance (so it doesn't take any longer to decay
1283 * than the air would allow).
1284 */
1285 constexpr float log10_decaygain{-3.0f/*std::log10(ReverbDecayGain)*/};
1286 const float absorb_dist{log10_decaygain / std::log10(airAbsorption)};
1287 DecayDistance[i].HF = minf(absorb_dist, DecayDistance[i].HF);
1288 }
1289 }
1290 }
1291 else
1292 {
1293 /* If the slot's auxiliary send auto is off, the data sent to the
1294 * effect slot is the same as the dry path, sans filter effects */
1295 RoomRolloff[i] = props->RolloffFactor;
1296 DecayDistance[i].Base = 0.0f;
1297 DecayDistance[i].LF = 0.0f;
1298 DecayDistance[i].HF = 0.0f;
1299 }
1300
1301 if(!SendSlots[i])
1302 voice->mSend[i].Buffer = {};
1303 else
1304 voice->mSend[i].Buffer = SendSlots[i]->Wet.Buffer;
1305 }
1306
1307 /* Transform source to listener space (convert to head relative) */
1308 alu::Vector Position{props->Position[0], props->Position[1], props->Position[2], 1.0f};
1309 alu::Vector Velocity{props->Velocity[0], props->Velocity[1], props->Velocity[2], 0.0f};
1310 alu::Vector Direction{props->Direction[0], props->Direction[1], props->Direction[2], 0.0f};
1311 if(!props->HeadRelative)
1312 {
1313 /* Transform source vectors */
1314 Position = context->mParams.Matrix * Position;
1315 Velocity = context->mParams.Matrix * Velocity;
1316 Direction = context->mParams.Matrix * Direction;
1317 }
1318 else
1319 {
1320 /* Offset the source velocity to be relative of the listener velocity */
1321 Velocity += context->mParams.Velocity;
1322 }
1323
1324 const bool directional{Direction.normalize() > 0.0f};
1325 alu::Vector ToSource{Position[0], Position[1], Position[2], 0.0f};
1326 const float Distance{ToSource.normalize(props->RefDistance / 1024.0f)};
1327
1328 /* Initial source gain */
1329 GainTriplet DryGain{props->Gain, 1.0f, 1.0f};
1330 GainTriplet WetGain[MAX_SENDS];
1331 for(uint i{0};i < NumSends;i++)
1332 WetGain[i] = DryGain;
1333
1334 /* Calculate distance attenuation */
1335 float ClampedDist{Distance};
1336
1337 switch(context->mParams.SourceDistanceModel ? props->mDistanceModel
1338 : context->mParams.mDistanceModel)
1339 {
1340 case DistanceModel::InverseClamped:
1341 ClampedDist = clampf(ClampedDist, props->RefDistance, props->MaxDistance);
1342 if(props->MaxDistance < props->RefDistance) break;
1343 /*fall-through*/
1344 case DistanceModel::Inverse:
1345 if(!(props->RefDistance > 0.0f))
1346 ClampedDist = props->RefDistance;
1347 else
1348 {
1349 float dist{lerp(props->RefDistance, ClampedDist, props->RolloffFactor)};
1350 if(dist > 0.0f) DryGain.Base *= props->RefDistance / dist;
1351 for(uint i{0};i < NumSends;i++)
1352 {
1353 dist = lerp(props->RefDistance, ClampedDist, RoomRolloff[i]);
1354 if(dist > 0.0f) WetGain[i].Base *= props->RefDistance / dist;
1355 }
1356 }
1357 break;
1358
1359 case DistanceModel::LinearClamped:
1360 ClampedDist = clampf(ClampedDist, props->RefDistance, props->MaxDistance);
1361 if(props->MaxDistance < props->RefDistance) break;
1362 /*fall-through*/
1363 case DistanceModel::Linear:
1364 if(!(props->MaxDistance != props->RefDistance))
1365 ClampedDist = props->RefDistance;
1366 else
1367 {
1368 float attn{props->RolloffFactor * (ClampedDist-props->RefDistance) /
1369 (props->MaxDistance-props->RefDistance)};
1370 DryGain.Base *= maxf(1.0f - attn, 0.0f);
1371 for(uint i{0};i < NumSends;i++)
1372 {
1373 attn = RoomRolloff[i] * (ClampedDist-props->RefDistance) /
1374 (props->MaxDistance-props->RefDistance);
1375 WetGain[i].Base *= maxf(1.0f - attn, 0.0f);
1376 }
1377 }
1378 break;
1379
1380 case DistanceModel::ExponentClamped:
1381 ClampedDist = clampf(ClampedDist, props->RefDistance, props->MaxDistance);
1382 if(props->MaxDistance < props->RefDistance) break;
1383 /*fall-through*/
1384 case DistanceModel::Exponent:
1385 if(!(ClampedDist > 0.0f && props->RefDistance > 0.0f))
1386 ClampedDist = props->RefDistance;
1387 else
1388 {
1389 const float dist_ratio{ClampedDist/props->RefDistance};
1390 DryGain.Base *= std::pow(dist_ratio, -props->RolloffFactor);
1391 for(uint i{0};i < NumSends;i++)
1392 WetGain[i].Base *= std::pow(dist_ratio, -RoomRolloff[i]);
1393 }
1394 break;
1395
1396 case DistanceModel::Disable:
1397 ClampedDist = props->RefDistance;
1398 break;
1399 }
1400
1401 /* Calculate directional soundcones */
1402 if(directional && props->InnerAngle < 360.0f)
1403 {
1404 const float Angle{Rad2Deg(std::acos(Direction.dot_product(ToSource)) * ConeScale * -2.0f)};
1405
1406 float ConeGain, ConeHF;
1407 if(!(Angle > props->InnerAngle))
1408 {
1409 ConeGain = 1.0f;
1410 ConeHF = 1.0f;
1411 }
1412 else if(Angle < props->OuterAngle)
1413 {
1414 const float scale{(Angle-props->InnerAngle) / (props->OuterAngle-props->InnerAngle)};
1415 ConeGain = lerp(1.0f, props->OuterGain, scale);
1416 ConeHF = lerp(1.0f, props->OuterGainHF, scale);
1417 }
1418 else
1419 {
1420 ConeGain = props->OuterGain;
1421 ConeHF = props->OuterGainHF;
1422 }
1423
1424 DryGain.Base *= ConeGain;
1425 if(props->DryGainHFAuto)
1426 DryGain.HF *= ConeHF;
1427 if(props->WetGainAuto)
1428 std::for_each(std::begin(WetGain), std::begin(WetGain)+NumSends,
1429 [ConeGain](GainTriplet &gain) noexcept -> void { gain.Base *= ConeGain; });
1430 if(props->WetGainHFAuto)
1431 std::for_each(std::begin(WetGain), std::begin(WetGain)+NumSends,
1432 [ConeHF](GainTriplet &gain) noexcept -> void { gain.HF *= ConeHF; });
1433 }
1434
1435 /* Apply gain and frequency filters */
1436 DryGain.Base = minf(clampf(DryGain.Base, props->MinGain, props->MaxGain) * props->Direct.Gain *
1437 context->mParams.Gain, GainMixMax);
1438 DryGain.HF *= props->Direct.GainHF;
1439 DryGain.LF *= props->Direct.GainLF;
1440 for(uint i{0};i < NumSends;i++)
1441 {
1442 WetGain[i].Base = minf(clampf(WetGain[i].Base, props->MinGain, props->MaxGain) *
1443 props->Send[i].Gain * context->mParams.Gain, GainMixMax);
1444 WetGain[i].HF *= props->Send[i].GainHF;
1445 WetGain[i].LF *= props->Send[i].GainLF;
1446 }
1447
1448 /* Distance-based air absorption and initial send decay. */
1449 if(ClampedDist > props->RefDistance && props->RolloffFactor > 0.0f)
1450 {
1451 const float meters_base{(ClampedDist-props->RefDistance) * props->RolloffFactor *
1452 context->mParams.MetersPerUnit};
1453 if(props->AirAbsorptionFactor > 0.0f)
1454 {
1455 const float hfattn{std::pow(AirAbsorbGainHF, meters_base*props->AirAbsorptionFactor)};
1456 DryGain.HF *= hfattn;
1457 std::for_each(std::begin(WetGain), std::begin(WetGain)+NumSends,
1458 [hfattn](GainTriplet &gain) noexcept -> void { gain.HF *= hfattn; });
1459 }
1460
1461 if(props->WetGainAuto)
1462 {
1463 /* Apply a decay-time transformation to the wet path, based on the
1464 * source distance in meters. The initial decay of the reverb
1465 * effect is calculated and applied to the wet path.
1466 */
1467 for(uint i{0};i < NumSends;i++)
1468 {
1469 if(!(DecayDistance[i].Base > 0.0f))
1470 continue;
1471
1472 const float gain{std::pow(ReverbDecayGain, meters_base/DecayDistance[i].Base)};
1473 WetGain[i].Base *= gain;
1474 /* Yes, the wet path's air absorption is applied with
1475 * WetGainAuto on, rather than WetGainHFAuto.
1476 */
1477 if(gain > 0.0f)
1478 {
1479 float gainhf{std::pow(ReverbDecayGain, meters_base/DecayDistance[i].HF)};
1480 WetGain[i].HF *= minf(gainhf / gain, 1.0f);
1481 float gainlf{std::pow(ReverbDecayGain, meters_base/DecayDistance[i].LF)};
1482 WetGain[i].LF *= minf(gainlf / gain, 1.0f);
1483 }
1484 }
1485 }
1486 }
1487
1488
1489 /* Initial source pitch */
1490 float Pitch{props->Pitch};
1491
1492 /* Calculate velocity-based doppler effect */
1493 float DopplerFactor{props->DopplerFactor * context->mParams.DopplerFactor};
1494 if(DopplerFactor > 0.0f)
1495 {
1496 const alu::Vector &lvelocity = context->mParams.Velocity;
1497 float vss{Velocity.dot_product(ToSource) * -DopplerFactor};
1498 float vls{lvelocity.dot_product(ToSource) * -DopplerFactor};
1499
1500 const float SpeedOfSound{context->mParams.SpeedOfSound};
1501 if(!(vls < SpeedOfSound))
1502 {
1503 /* Listener moving away from the source at the speed of sound.
1504 * Sound waves can't catch it.
1505 */
1506 Pitch = 0.0f;
1507 }
1508 else if(!(vss < SpeedOfSound))
1509 {
1510 /* Source moving toward the listener at the speed of sound. Sound
1511 * waves bunch up to extreme frequencies.
1512 */
1513 Pitch = std::numeric_limits<float>::infinity();
1514 }
1515 else
1516 {
1517 /* Source and listener movement is nominal. Calculate the proper
1518 * doppler shift.
1519 */
1520 Pitch *= (SpeedOfSound-vls) / (SpeedOfSound-vss);
1521 }
1522 }
1523
1524 /* Adjust pitch based on the buffer and output frequencies, and calculate
1525 * fixed-point stepping value.
1526 */
1527 Pitch *= static_cast<float>(voice->mFrequency) / static_cast<float>(Device->Frequency);
1528 if(Pitch > float{MaxPitch})
1529 voice->mStep = MaxPitch<<MixerFracBits;
1530 else
1531 voice->mStep = maxu(fastf2u(Pitch * MixerFracOne), 1);
1532 voice->mResampler = PrepareResampler(props->mResampler, voice->mStep, &voice->mResampleState);
1533
1534 float spread{0.0f};
1535 if(props->Radius > Distance)
1536 spread = al::MathDefs<float>::Tau() - Distance/props->Radius*al::MathDefs<float>::Pi();
1537 else if(Distance > 0.0f)
1538 spread = std::asin(props->Radius/Distance) * 2.0f;
1539
1540 CalcPanningAndFilters(voice, ToSource[0], ToSource[1], ToSource[2]*ZScale,
1541 Distance*context->mParams.MetersPerUnit, spread, DryGain, WetGain, SendSlots, props,
1542 context->mParams, Device);
1543 }
1544
CalcSourceParams(Voice * voice,ALCcontext * context,bool force)1545 void CalcSourceParams(Voice *voice, ALCcontext *context, bool force)
1546 {
1547 VoicePropsItem *props{voice->mUpdate.exchange(nullptr, std::memory_order_acq_rel)};
1548 if(!props && !force) return;
1549
1550 if(props)
1551 {
1552 voice->mProps = *props;
1553
1554 AtomicReplaceHead(context->mFreeVoiceProps, props);
1555 }
1556
1557 if((voice->mProps.DirectChannels != DirectMode::Off && voice->mFmtChannels != FmtMono
1558 && voice->mFmtChannels != FmtBFormat2D && voice->mFmtChannels != FmtBFormat3D)
1559 || voice->mProps.mSpatializeMode==SpatializeMode::Off
1560 || (voice->mProps.mSpatializeMode==SpatializeMode::Auto && voice->mFmtChannels != FmtMono))
1561 CalcNonAttnSourceParams(voice, &voice->mProps, context);
1562 else
1563 CalcAttnSourceParams(voice, &voice->mProps, context);
1564 }
1565
1566
SendSourceStateEvent(ALCcontext * context,uint id,VChangeState state)1567 void SendSourceStateEvent(ALCcontext *context, uint id, VChangeState state)
1568 {
1569 RingBuffer *ring{context->mAsyncEvents.get()};
1570 auto evt_vec = ring->getWriteVector();
1571 if(evt_vec.first.len < 1) return;
1572
1573 AsyncEvent *evt{::new(evt_vec.first.buf) AsyncEvent{EventType_SourceStateChange}};
1574 evt->u.srcstate.id = id;
1575 evt->u.srcstate.state = state;
1576
1577 ring->writeAdvance(1);
1578 }
1579
ProcessVoiceChanges(ALCcontext * ctx)1580 void ProcessVoiceChanges(ALCcontext *ctx)
1581 {
1582 VoiceChange *cur{ctx->mCurrentVoiceChange.load(std::memory_order_acquire)};
1583 VoiceChange *next{cur->mNext.load(std::memory_order_acquire)};
1584 if(!next) return;
1585
1586 const uint enabledevt{ctx->mEnabledEvts.load(std::memory_order_acquire)};
1587 do {
1588 cur = next;
1589
1590 bool sendevt{false};
1591 if(cur->mState == VChangeState::Reset || cur->mState == VChangeState::Stop)
1592 {
1593 if(Voice *voice{cur->mVoice})
1594 {
1595 voice->mCurrentBuffer.store(nullptr, std::memory_order_relaxed);
1596 voice->mLoopBuffer.store(nullptr, std::memory_order_relaxed);
1597 /* A source ID indicates the voice was playing or paused, which
1598 * gets a reset/stop event.
1599 */
1600 sendevt = voice->mSourceID.exchange(0u, std::memory_order_relaxed) != 0u;
1601 Voice::State oldvstate{Voice::Playing};
1602 voice->mPlayState.compare_exchange_strong(oldvstate, Voice::Stopping,
1603 std::memory_order_relaxed, std::memory_order_acquire);
1604 voice->mPendingChange.store(false, std::memory_order_release);
1605 }
1606 /* Reset state change events are always sent, even if the voice is
1607 * already stopped or even if there is no voice.
1608 */
1609 sendevt |= (cur->mState == VChangeState::Reset);
1610 }
1611 else if(cur->mState == VChangeState::Pause)
1612 {
1613 Voice *voice{cur->mVoice};
1614 Voice::State oldvstate{Voice::Playing};
1615 sendevt = voice->mPlayState.compare_exchange_strong(oldvstate, Voice::Stopping,
1616 std::memory_order_release, std::memory_order_acquire);
1617 }
1618 else if(cur->mState == VChangeState::Play)
1619 {
1620 /* NOTE: When playing a voice, sending a source state change event
1621 * depends if there's an old voice to stop and if that stop is
1622 * successful. If there is no old voice, a playing event is always
1623 * sent. If there is an old voice, an event is sent only if the
1624 * voice is already stopped.
1625 */
1626 if(Voice *oldvoice{cur->mOldVoice})
1627 {
1628 oldvoice->mCurrentBuffer.store(nullptr, std::memory_order_relaxed);
1629 oldvoice->mLoopBuffer.store(nullptr, std::memory_order_relaxed);
1630 oldvoice->mSourceID.store(0u, std::memory_order_relaxed);
1631 Voice::State oldvstate{Voice::Playing};
1632 sendevt = !oldvoice->mPlayState.compare_exchange_strong(oldvstate, Voice::Stopping,
1633 std::memory_order_relaxed, std::memory_order_acquire);
1634 oldvoice->mPendingChange.store(false, std::memory_order_release);
1635 }
1636 else
1637 sendevt = true;
1638
1639 Voice *voice{cur->mVoice};
1640 voice->mPlayState.store(Voice::Playing, std::memory_order_release);
1641 }
1642 else if(cur->mState == VChangeState::Restart)
1643 {
1644 /* Restarting a voice never sends a source change event. */
1645 Voice *oldvoice{cur->mOldVoice};
1646 oldvoice->mCurrentBuffer.store(nullptr, std::memory_order_relaxed);
1647 oldvoice->mLoopBuffer.store(nullptr, std::memory_order_relaxed);
1648 /* If there's no sourceID, the old voice finished so don't start
1649 * the new one at its new offset.
1650 */
1651 if(oldvoice->mSourceID.exchange(0u, std::memory_order_relaxed) != 0u)
1652 {
1653 /* Otherwise, set the voice to stopping if it's not already (it
1654 * might already be, if paused), and play the new voice as
1655 * appropriate.
1656 */
1657 Voice::State oldvstate{Voice::Playing};
1658 oldvoice->mPlayState.compare_exchange_strong(oldvstate, Voice::Stopping,
1659 std::memory_order_relaxed, std::memory_order_acquire);
1660
1661 Voice *voice{cur->mVoice};
1662 voice->mPlayState.store((oldvstate == Voice::Playing) ? Voice::Playing
1663 : Voice::Stopped, std::memory_order_release);
1664 }
1665 oldvoice->mPendingChange.store(false, std::memory_order_release);
1666 }
1667 if(sendevt && (enabledevt&EventType_SourceStateChange))
1668 SendSourceStateEvent(ctx, cur->mSourceID, cur->mState);
1669
1670 next = cur->mNext.load(std::memory_order_acquire);
1671 } while(next);
1672 ctx->mCurrentVoiceChange.store(cur, std::memory_order_release);
1673 }
1674
ProcessParamUpdates(ALCcontext * ctx,const EffectSlotArray & slots,const al::span<Voice * > voices)1675 void ProcessParamUpdates(ALCcontext *ctx, const EffectSlotArray &slots,
1676 const al::span<Voice*> voices)
1677 {
1678 ProcessVoiceChanges(ctx);
1679
1680 IncrementRef(ctx->mUpdateCount);
1681 if LIKELY(!ctx->mHoldUpdates.load(std::memory_order_acquire))
1682 {
1683 bool force{CalcContextParams(ctx)};
1684 force |= CalcListenerParams(ctx);
1685 auto sorted_slots = const_cast<EffectSlot**>(slots.data() + slots.size());
1686 for(EffectSlot *slot : slots)
1687 force |= CalcEffectSlotParams(slot, sorted_slots, ctx);
1688
1689 for(Voice *voice : voices)
1690 {
1691 /* Only update voices that have a source. */
1692 if(voice->mSourceID.load(std::memory_order_relaxed) != 0)
1693 CalcSourceParams(voice, ctx, force);
1694 }
1695 }
1696 IncrementRef(ctx->mUpdateCount);
1697 }
1698
ProcessContexts(ALCdevice * device,const uint SamplesToDo)1699 void ProcessContexts(ALCdevice *device, const uint SamplesToDo)
1700 {
1701 ASSUME(SamplesToDo > 0);
1702
1703 for(ALCcontext *ctx : *device->mContexts.load(std::memory_order_acquire))
1704 {
1705 const EffectSlotArray &auxslots = *ctx->mActiveAuxSlots.load(std::memory_order_acquire);
1706 const al::span<Voice*> voices{ctx->getVoicesSpanAcquired()};
1707
1708 /* Process pending propery updates for objects on the context. */
1709 ProcessParamUpdates(ctx, auxslots, voices);
1710
1711 /* Clear auxiliary effect slot mixing buffers. */
1712 for(EffectSlot *slot : auxslots)
1713 {
1714 for(auto &buffer : slot->Wet.Buffer)
1715 buffer.fill(0.0f);
1716 }
1717
1718 /* Process voices that have a playing source. */
1719 for(Voice *voice : voices)
1720 {
1721 const Voice::State vstate{voice->mPlayState.load(std::memory_order_acquire)};
1722 if(vstate != Voice::Stopped && vstate != Voice::Pending)
1723 voice->mix(vstate, ctx, SamplesToDo);
1724 }
1725
1726 /* Process effects. */
1727 if(const size_t num_slots{auxslots.size()})
1728 {
1729 auto slots = auxslots.data();
1730 auto slots_end = slots + num_slots;
1731
1732 /* Sort the slots into extra storage, so that effect slots come
1733 * before their effect slot target (or their targets' target).
1734 */
1735 const al::span<EffectSlot*> sorted_slots{const_cast<EffectSlot**>(slots_end),
1736 num_slots};
1737 /* Skip sorting if it has already been done. */
1738 if(!sorted_slots[0])
1739 {
1740 /* First, copy the slots to the sorted list, then partition the
1741 * sorted list so that all slots without a target slot go to
1742 * the end.
1743 */
1744 std::copy(slots, slots_end, sorted_slots.begin());
1745 auto split_point = std::partition(sorted_slots.begin(), sorted_slots.end(),
1746 [](const EffectSlot *slot) noexcept -> bool
1747 { return slot->Target != nullptr; });
1748 /* There must be at least one slot without a slot target. */
1749 assert(split_point != sorted_slots.end());
1750
1751 /* Simple case: no more than 1 slot has a target slot. Either
1752 * all slots go right to the output, or the remaining one must
1753 * target an already-partitioned slot.
1754 */
1755 if(split_point - sorted_slots.begin() > 1)
1756 {
1757 /* At least two slots target other slots. Starting from the
1758 * back of the sorted list, continue partitioning the front
1759 * of the list given each target until all targets are
1760 * accounted for. This ensures all slots without a target
1761 * go last, all slots directly targeting those last slots
1762 * go second-to-last, all slots directly targeting those
1763 * second-last slots go third-to-last, etc.
1764 */
1765 auto next_target = sorted_slots.end();
1766 do {
1767 /* This shouldn't happen, but if there's unsorted slots
1768 * left that don't target any sorted slots, they can't
1769 * contribute to the output, so leave them.
1770 */
1771 if UNLIKELY(next_target == split_point)
1772 break;
1773
1774 --next_target;
1775 split_point = std::partition(sorted_slots.begin(), split_point,
1776 [next_target](const EffectSlot *slot) noexcept -> bool
1777 { return slot->Target != *next_target; });
1778 } while(split_point - sorted_slots.begin() > 1);
1779 }
1780 }
1781
1782 for(const EffectSlot *slot : sorted_slots)
1783 {
1784 EffectState *state{slot->mEffectState};
1785 state->process(SamplesToDo, slot->Wet.Buffer, state->mOutTarget);
1786 }
1787 }
1788
1789 /* Signal the event handler if there are any events to read. */
1790 RingBuffer *ring{ctx->mAsyncEvents.get()};
1791 if(ring->readSpace() > 0)
1792 ctx->mEventSem.post();
1793 }
1794 }
1795
1796
ApplyDistanceComp(const al::span<FloatBufferLine> Samples,const size_t SamplesToDo,const DistanceComp::ChanData * distcomp)1797 void ApplyDistanceComp(const al::span<FloatBufferLine> Samples, const size_t SamplesToDo,
1798 const DistanceComp::ChanData *distcomp)
1799 {
1800 ASSUME(SamplesToDo > 0);
1801
1802 for(auto &chanbuffer : Samples)
1803 {
1804 const float gain{distcomp->Gain};
1805 const size_t base{distcomp->Length};
1806 float *distbuf{al::assume_aligned<16>(distcomp->Buffer)};
1807 ++distcomp;
1808
1809 if(base < 1)
1810 continue;
1811
1812 float *inout{al::assume_aligned<16>(chanbuffer.data())};
1813 auto inout_end = inout + SamplesToDo;
1814 if LIKELY(SamplesToDo >= base)
1815 {
1816 auto delay_end = std::rotate(inout, inout_end - base, inout_end);
1817 std::swap_ranges(inout, delay_end, distbuf);
1818 }
1819 else
1820 {
1821 auto delay_start = std::swap_ranges(inout, inout_end, distbuf);
1822 std::rotate(distbuf, delay_start, distbuf + base);
1823 }
1824 std::transform(inout, inout_end, inout, std::bind(std::multiplies<float>{}, _1, gain));
1825 }
1826 }
1827
ApplyDither(const al::span<FloatBufferLine> Samples,uint * dither_seed,const float quant_scale,const size_t SamplesToDo)1828 void ApplyDither(const al::span<FloatBufferLine> Samples, uint *dither_seed,
1829 const float quant_scale, const size_t SamplesToDo)
1830 {
1831 ASSUME(SamplesToDo > 0);
1832
1833 /* Dithering. Generate whitenoise (uniform distribution of random values
1834 * between -1 and +1) and add it to the sample values, after scaling up to
1835 * the desired quantization depth amd before rounding.
1836 */
1837 const float invscale{1.0f / quant_scale};
1838 uint seed{*dither_seed};
1839 auto dither_sample = [&seed,invscale,quant_scale](const float sample) noexcept -> float
1840 {
1841 float val{sample * quant_scale};
1842 uint rng0{dither_rng(&seed)};
1843 uint rng1{dither_rng(&seed)};
1844 val += static_cast<float>(rng0*(1.0/UINT_MAX) - rng1*(1.0/UINT_MAX));
1845 return fast_roundf(val) * invscale;
1846 };
1847 for(FloatBufferLine &inout : Samples)
1848 std::transform(inout.begin(), inout.begin()+SamplesToDo, inout.begin(), dither_sample);
1849 *dither_seed = seed;
1850 }
1851
1852
1853 /* Base template left undefined. Should be marked =delete, but Clang 3.8.1
1854 * chokes on that given the inline specializations.
1855 */
1856 template<typename T>
1857 inline T SampleConv(float) noexcept;
1858
SampleConv(float val)1859 template<> inline float SampleConv(float val) noexcept
1860 { return val; }
SampleConv(float val)1861 template<> inline int32_t SampleConv(float val) noexcept
1862 {
1863 /* Floats have a 23-bit mantissa, plus an implied 1 bit and a sign bit.
1864 * This means a normalized float has at most 25 bits of signed precision.
1865 * When scaling and clamping for a signed 32-bit integer, these following
1866 * values are the best a float can give.
1867 */
1868 return fastf2i(clampf(val*2147483648.0f, -2147483648.0f, 2147483520.0f));
1869 }
SampleConv(float val)1870 template<> inline int16_t SampleConv(float val) noexcept
1871 { return static_cast<int16_t>(fastf2i(clampf(val*32768.0f, -32768.0f, 32767.0f))); }
SampleConv(float val)1872 template<> inline int8_t SampleConv(float val) noexcept
1873 { return static_cast<int8_t>(fastf2i(clampf(val*128.0f, -128.0f, 127.0f))); }
1874
1875 /* Define unsigned output variations. */
SampleConv(float val)1876 template<> inline uint32_t SampleConv(float val) noexcept
1877 { return static_cast<uint32_t>(SampleConv<int32_t>(val)) + 2147483648u; }
SampleConv(float val)1878 template<> inline uint16_t SampleConv(float val) noexcept
1879 { return static_cast<uint16_t>(SampleConv<int16_t>(val) + 32768); }
SampleConv(float val)1880 template<> inline uint8_t SampleConv(float val) noexcept
1881 { return static_cast<uint8_t>(SampleConv<int8_t>(val) + 128); }
1882
1883 template<DevFmtType T>
Write(const al::span<const FloatBufferLine> InBuffer,void * OutBuffer,const size_t Offset,const size_t SamplesToDo,const size_t FrameStep)1884 void Write(const al::span<const FloatBufferLine> InBuffer, void *OutBuffer, const size_t Offset,
1885 const size_t SamplesToDo, const size_t FrameStep)
1886 {
1887 ASSUME(FrameStep > 0);
1888 ASSUME(SamplesToDo > 0);
1889
1890 DevFmtType_t<T> *outbase = static_cast<DevFmtType_t<T>*>(OutBuffer) + Offset*FrameStep;
1891 for(const FloatBufferLine &inbuf : InBuffer)
1892 {
1893 DevFmtType_t<T> *out{outbase++};
1894 auto conv_sample = [FrameStep,&out](const float s) noexcept -> void
1895 {
1896 *out = SampleConv<DevFmtType_t<T>>(s);
1897 out += FrameStep;
1898 };
1899 std::for_each(inbuf.begin(), inbuf.begin()+SamplesToDo, conv_sample);
1900 }
1901 }
1902
1903 } // namespace
1904
renderSamples(void * outBuffer,const uint numSamples,const size_t frameStep)1905 void ALCdevice::renderSamples(void *outBuffer, const uint numSamples, const size_t frameStep)
1906 {
1907 FPUCtl mixer_mode{};
1908 for(uint written{0u};written < numSamples;)
1909 {
1910 const uint samplesToDo{minu(numSamples-written, BufferLineSize)};
1911
1912 /* Clear main mixing buffers. */
1913 for(FloatBufferLine &buffer : MixBuffer)
1914 buffer.fill(0.0f);
1915
1916 /* Increment the mix count at the start (lsb should now be 1). */
1917 IncrementRef(MixCount);
1918
1919 /* Process and mix each context's sources and effects. */
1920 ProcessContexts(this, samplesToDo);
1921
1922 /* Increment the clock time. Every second's worth of samples is
1923 * converted and added to clock base so that large sample counts don't
1924 * overflow during conversion. This also guarantees a stable
1925 * conversion.
1926 */
1927 SamplesDone += samplesToDo;
1928 ClockBase += std::chrono::seconds{SamplesDone / Frequency};
1929 SamplesDone %= Frequency;
1930
1931 /* Increment the mix count at the end (lsb should now be 0). */
1932 IncrementRef(MixCount);
1933
1934 /* Apply any needed post-process for finalizing the Dry mix to the
1935 * RealOut (Ambisonic decode, UHJ encode, etc).
1936 */
1937 postProcess(samplesToDo);
1938
1939 /* Apply compression, limiting sample amplitude if needed or desired. */
1940 if(Limiter) Limiter->process(samplesToDo, RealOut.Buffer.data());
1941
1942 /* Apply delays and attenuation for mismatched speaker distances. */
1943 if(ChannelDelays)
1944 ApplyDistanceComp(RealOut.Buffer, samplesToDo, ChannelDelays->mChannels.data());
1945
1946 /* Apply dithering. The compressor should have left enough headroom for
1947 * the dither noise to not saturate.
1948 */
1949 if(DitherDepth > 0.0f)
1950 ApplyDither(RealOut.Buffer, &DitherSeed, DitherDepth, samplesToDo);
1951
1952 if LIKELY(outBuffer)
1953 {
1954 /* Finally, interleave and convert samples, writing to the device's
1955 * output buffer.
1956 */
1957 switch(FmtType)
1958 {
1959 #define HANDLE_WRITE(T) case T: \
1960 Write<T>(RealOut.Buffer, outBuffer, written, 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
1969 }
1970 }
1971
1972 written += samplesToDo;
1973 }
1974 }
1975
handleDisconnect(const char * msg,...)1976 void ALCdevice::handleDisconnect(const char *msg, ...)
1977 {
1978 if(!Connected.exchange(false, std::memory_order_acq_rel))
1979 return;
1980
1981 AsyncEvent evt{EventType_Disconnected};
1982
1983 va_list args;
1984 va_start(args, msg);
1985 int msglen{vsnprintf(evt.u.disconnect.msg, sizeof(evt.u.disconnect.msg), msg, args)};
1986 va_end(args);
1987
1988 if(msglen < 0 || static_cast<size_t>(msglen) >= sizeof(evt.u.disconnect.msg))
1989 evt.u.disconnect.msg[sizeof(evt.u.disconnect.msg)-1] = 0;
1990
1991 IncrementRef(MixCount);
1992 for(ALCcontext *ctx : *mContexts.load())
1993 {
1994 const uint enabledevt{ctx->mEnabledEvts.load(std::memory_order_acquire)};
1995 if((enabledevt&EventType_Disconnected))
1996 {
1997 RingBuffer *ring{ctx->mAsyncEvents.get()};
1998 auto evt_data = ring->getWriteVector().first;
1999 if(evt_data.len > 0)
2000 {
2001 ::new(evt_data.buf) AsyncEvent{evt};
2002 ring->writeAdvance(1);
2003 ctx->mEventSem.post();
2004 }
2005 }
2006
2007 auto voicelist = ctx->getVoicesSpanAcquired();
2008 auto stop_voice = [](Voice *voice) -> void
2009 {
2010 voice->mCurrentBuffer.store(nullptr, std::memory_order_relaxed);
2011 voice->mLoopBuffer.store(nullptr, std::memory_order_relaxed);
2012 voice->mSourceID.store(0u, std::memory_order_relaxed);
2013 voice->mPlayState.store(Voice::Stopped, std::memory_order_release);
2014 };
2015 std::for_each(voicelist.begin(), voicelist.end(), stop_voice);
2016 }
2017 IncrementRef(MixCount);
2018 }
2019