Minor code refactor for the frequency shifter effect
[openal-soft.git] / alc / alu.cpp
blob7b844c582ffd3f62923750cfaeb51a5a611ce95c
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 <cstdarg>
32 #include <cstdio>
33 #include <cstdlib>
34 #include <functional>
35 #include <iterator>
36 #include <limits>
37 #include <memory>
38 #include <new>
39 #include <stdint.h>
40 #include <utility>
42 #include "almalloc.h"
43 #include "alnumbers.h"
44 #include "alnumeric.h"
45 #include "alspan.h"
46 #include "alstring.h"
47 #include "atomic.h"
48 #include "core/ambidefs.h"
49 #include "core/async_event.h"
50 #include "core/bformatdec.h"
51 #include "core/bs2b.h"
52 #include "core/bsinc_defs.h"
53 #include "core/bsinc_tables.h"
54 #include "core/bufferline.h"
55 #include "core/buffer_storage.h"
56 #include "core/context.h"
57 #include "core/cpu_caps.h"
58 #include "core/devformat.h"
59 #include "core/device.h"
60 #include "core/effects/base.h"
61 #include "core/effectslot.h"
62 #include "core/filters/biquad.h"
63 #include "core/filters/nfc.h"
64 #include "core/fpu_ctrl.h"
65 #include "core/hrtf.h"
66 #include "core/mastering.h"
67 #include "core/mixer.h"
68 #include "core/mixer/defs.h"
69 #include "core/mixer/hrtfdefs.h"
70 #include "core/resampler_limits.h"
71 #include "core/uhjfilter.h"
72 #include "core/voice.h"
73 #include "core/voice_change.h"
74 #include "intrusive_ptr.h"
75 #include "opthelpers.h"
76 #include "ringbuffer.h"
77 #include "strutils.h"
78 #include "threads.h"
79 #include "vecmat.h"
80 #include "vector.h"
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 PointTag;
96 struct LerpTag;
97 struct CubicTag;
98 struct BSincTag;
99 struct FastBSincTag;
102 static_assert(!(MaxResamplerPadding&1), "MaxResamplerPadding is not a multiple of two");
105 namespace {
107 using uint = unsigned int;
108 using namespace std::chrono;
110 constexpr uint MaxPitch{10};
112 static_assert((BufferLineSize-1)/MaxPitch > 0, "MaxPitch is too large for BufferLineSize!");
113 static_assert((INT_MAX>>MixerFracBits)/MaxPitch > BufferLineSize,
114 "MaxPitch and/or BufferLineSize are too large for MixerFracBits!");
116 using namespace std::placeholders;
118 float InitConeScale()
120 float ret{1.0f};
121 if(auto optval = al::getenv("__ALSOFT_HALF_ANGLE_CONES"))
123 if(al::strcasecmp(optval->c_str(), "true") == 0
124 || strtol(optval->c_str(), nullptr, 0) == 1)
125 ret *= 0.5f;
127 return ret;
129 /* Cone scalar */
130 const float ConeScale{InitConeScale()};
132 /* Localized scalars for mono sources (initialized in aluInit, after
133 * configuration is loaded).
135 float XScale{1.0f};
136 float YScale{1.0f};
137 float ZScale{1.0f};
139 /* Source distance scale for NFC filters. */
140 float NfcScale{1.0f};
143 struct ChanMap {
144 Channel channel;
145 float angle;
146 float elevation;
149 using HrtfDirectMixerFunc = void(*)(const FloatBufferSpan LeftOut, const FloatBufferSpan RightOut,
150 const al::span<const FloatBufferLine> InSamples, float2 *AccumSamples, float *TempBuf,
151 HrtfChannelState *ChanState, const size_t IrSize, const size_t BufferSize);
153 HrtfDirectMixerFunc MixDirectHrtf{MixDirectHrtf_<CTag>};
155 inline HrtfDirectMixerFunc SelectHrtfMixer(void)
157 #ifdef HAVE_NEON
158 if((CPUCapFlags&CPU_CAP_NEON))
159 return MixDirectHrtf_<NEONTag>;
160 #endif
161 #ifdef HAVE_SSE
162 if((CPUCapFlags&CPU_CAP_SSE))
163 return MixDirectHrtf_<SSETag>;
164 #endif
166 return MixDirectHrtf_<CTag>;
170 inline void BsincPrepare(const uint increment, BsincState *state, const BSincTable *table)
172 size_t si{BSincScaleCount - 1};
173 float sf{0.0f};
175 if(increment > MixerFracOne)
177 sf = MixerFracOne/static_cast<float>(increment) - table->scaleBase;
178 sf = maxf(0.0f, BSincScaleCount*sf*table->scaleRange - 1.0f);
179 si = float2uint(sf);
180 /* The interpolation factor is fit to this diagonally-symmetric curve
181 * to reduce the transition ripple caused by interpolating different
182 * scales of the sinc function.
184 sf = 1.0f - std::cos(std::asin(sf - static_cast<float>(si)));
187 state->sf = sf;
188 state->m = table->m[si];
189 state->l = (state->m/2) - 1;
190 state->filter = table->Tab + table->filterOffset[si];
193 inline ResamplerFunc SelectResampler(Resampler resampler, uint increment)
195 switch(resampler)
197 case Resampler::Point:
198 return Resample_<PointTag,CTag>;
199 case Resampler::Linear:
200 #ifdef HAVE_NEON
201 if((CPUCapFlags&CPU_CAP_NEON))
202 return Resample_<LerpTag,NEONTag>;
203 #endif
204 #ifdef HAVE_SSE4_1
205 if((CPUCapFlags&CPU_CAP_SSE4_1))
206 return Resample_<LerpTag,SSE4Tag>;
207 #endif
208 #ifdef HAVE_SSE2
209 if((CPUCapFlags&CPU_CAP_SSE2))
210 return Resample_<LerpTag,SSE2Tag>;
211 #endif
212 return Resample_<LerpTag,CTag>;
213 case Resampler::Cubic:
214 return Resample_<CubicTag,CTag>;
215 case Resampler::BSinc12:
216 case Resampler::BSinc24:
217 if(increment > MixerFracOne)
219 #ifdef HAVE_NEON
220 if((CPUCapFlags&CPU_CAP_NEON))
221 return Resample_<BSincTag,NEONTag>;
222 #endif
223 #ifdef HAVE_SSE
224 if((CPUCapFlags&CPU_CAP_SSE))
225 return Resample_<BSincTag,SSETag>;
226 #endif
227 return Resample_<BSincTag,CTag>;
229 /* fall-through */
230 case Resampler::FastBSinc12:
231 case Resampler::FastBSinc24:
232 #ifdef HAVE_NEON
233 if((CPUCapFlags&CPU_CAP_NEON))
234 return Resample_<FastBSincTag,NEONTag>;
235 #endif
236 #ifdef HAVE_SSE
237 if((CPUCapFlags&CPU_CAP_SSE))
238 return Resample_<FastBSincTag,SSETag>;
239 #endif
240 return Resample_<FastBSincTag,CTag>;
243 return Resample_<PointTag,CTag>;
246 } // namespace
248 void aluInit(CompatFlagBitset flags, const float nfcscale)
250 MixDirectHrtf = SelectHrtfMixer();
251 XScale = flags.test(CompatFlags::ReverseX) ? -1.0f : 1.0f;
252 YScale = flags.test(CompatFlags::ReverseY) ? -1.0f : 1.0f;
253 ZScale = flags.test(CompatFlags::ReverseZ) ? -1.0f : 1.0f;
255 NfcScale = clampf(nfcscale, 0.0001f, 10000.0f);
259 ResamplerFunc PrepareResampler(Resampler resampler, uint increment, InterpState *state)
261 switch(resampler)
263 case Resampler::Point:
264 case Resampler::Linear:
265 case Resampler::Cubic:
266 break;
267 case Resampler::FastBSinc12:
268 case Resampler::BSinc12:
269 BsincPrepare(increment, &state->bsinc, &bsinc12);
270 break;
271 case Resampler::FastBSinc24:
272 case Resampler::BSinc24:
273 BsincPrepare(increment, &state->bsinc, &bsinc24);
274 break;
276 return SelectResampler(resampler, increment);
280 void DeviceBase::ProcessHrtf(const size_t SamplesToDo)
282 /* HRTF is stereo output only. */
283 const uint lidx{RealOut.ChannelIndex[FrontLeft]};
284 const uint ridx{RealOut.ChannelIndex[FrontRight]};
286 MixDirectHrtf(RealOut.Buffer[lidx], RealOut.Buffer[ridx], Dry.Buffer, HrtfAccumData,
287 mHrtfState->mTemp.data(), mHrtfState->mChannels.data(), mHrtfState->mIrSize, SamplesToDo);
290 void DeviceBase::ProcessAmbiDec(const size_t SamplesToDo)
292 AmbiDecoder->process(RealOut.Buffer, Dry.Buffer.data(), SamplesToDo);
295 void DeviceBase::ProcessAmbiDecStablized(const size_t SamplesToDo)
297 /* Decode with front image stablization. */
298 const uint lidx{RealOut.ChannelIndex[FrontLeft]};
299 const uint ridx{RealOut.ChannelIndex[FrontRight]};
300 const uint cidx{RealOut.ChannelIndex[FrontCenter]};
302 AmbiDecoder->processStablize(RealOut.Buffer, Dry.Buffer.data(), lidx, ridx, cidx,
303 SamplesToDo);
306 void DeviceBase::ProcessUhj(const size_t SamplesToDo)
308 /* UHJ is stereo output only. */
309 const uint lidx{RealOut.ChannelIndex[FrontLeft]};
310 const uint ridx{RealOut.ChannelIndex[FrontRight]};
312 /* Encode to stereo-compatible 2-channel UHJ output. */
313 mUhjEncoder->encode(RealOut.Buffer[lidx].data(), RealOut.Buffer[ridx].data(),
314 {{Dry.Buffer[0].data(), Dry.Buffer[1].data(), Dry.Buffer[2].data()}}, SamplesToDo);
317 void DeviceBase::ProcessBs2b(const size_t SamplesToDo)
319 /* First, decode the ambisonic mix to the "real" output. */
320 AmbiDecoder->process(RealOut.Buffer, Dry.Buffer.data(), SamplesToDo);
322 /* BS2B is stereo output only. */
323 const uint lidx{RealOut.ChannelIndex[FrontLeft]};
324 const uint ridx{RealOut.ChannelIndex[FrontRight]};
326 /* Now apply the BS2B binaural/crossfeed filter. */
327 bs2b_cross_feed(Bs2b.get(), RealOut.Buffer[lidx].data(), RealOut.Buffer[ridx].data(),
328 SamplesToDo);
332 namespace {
334 /* This RNG method was created based on the math found in opusdec. It's quick,
335 * and starting with a seed value of 22222, is suitable for generating
336 * whitenoise.
338 inline uint dither_rng(uint *seed) noexcept
340 *seed = (*seed * 96314165) + 907633515;
341 return *seed;
345 /* Ambisonic upsampler function. It's effectively a matrix multiply. It takes
346 * an 'upsampler' and 'rotator' as the input matrices, resulting in a matrix
347 * that behaves as if the B-Format input was first decoded to a speaker array
348 * at its input order, encoded back into the higher order mix, then finally
349 * rotated.
351 void UpsampleBFormatTransform(size_t coeffs_order,
352 const al::span<const std::array<float,MaxAmbiChannels>> matrix1,
353 const al::span<std::array<float,MaxAmbiChannels>,MaxAmbiChannels> coeffs)
355 auto copy_coeffs = [coeffs]() noexcept
357 std::array<std::array<float,MaxAmbiChannels>,MaxAmbiChannels> res{};
358 for(size_t i{0};i < MaxAmbiChannels;++i)
359 res[i] = coeffs[i];
360 return res;
362 const auto matrix2 = copy_coeffs();
364 const size_t num_chans{AmbiChannelsFromOrder(coeffs_order)};
365 for(size_t i{0};i < matrix1.size();++i)
367 for(size_t j{0};j < num_chans;++j)
369 float sum{0.0};
370 for(size_t k{0};k < num_chans;++k)
371 sum += matrix1[i][k] * matrix2[j][k];
372 coeffs[j][i] = sum;
378 inline auto& GetAmbiScales(AmbiScaling scaletype) noexcept
380 switch(scaletype)
382 case AmbiScaling::FuMa: return AmbiScale::FromFuMa();
383 case AmbiScaling::SN3D: return AmbiScale::FromSN3D();
384 case AmbiScaling::UHJ: return AmbiScale::FromUHJ();
385 case AmbiScaling::N3D: break;
387 return AmbiScale::FromN3D();
390 inline auto& GetAmbiLayout(AmbiLayout layouttype) noexcept
392 if(layouttype == AmbiLayout::FuMa) return AmbiIndex::FromFuMa();
393 return AmbiIndex::FromACN();
396 inline auto& GetAmbi2DLayout(AmbiLayout layouttype) noexcept
398 if(layouttype == AmbiLayout::FuMa) return AmbiIndex::FromFuMa2D();
399 return AmbiIndex::FromACN2D();
403 bool CalcContextParams(ContextBase *ctx)
405 ContextProps *props{ctx->mParams.ContextUpdate.exchange(nullptr, std::memory_order_acq_rel)};
406 if(!props) return false;
408 const alu::Vector pos{props->Position[0], props->Position[1], props->Position[2], 1.0f};
409 ctx->mParams.Position = pos;
411 /* AT then UP */
412 alu::Vector N{props->OrientAt[0], props->OrientAt[1], props->OrientAt[2], 0.0f};
413 N.normalize();
414 alu::Vector V{props->OrientUp[0], props->OrientUp[1], props->OrientUp[2], 0.0f};
415 V.normalize();
416 /* Build and normalize right-vector */
417 alu::Vector U{N.cross_product(V)};
418 U.normalize();
420 const alu::Matrix rot{
421 U[0], V[0], -N[0], 0.0,
422 U[1], V[1], -N[1], 0.0,
423 U[2], V[2], -N[2], 0.0,
424 0.0, 0.0, 0.0, 1.0};
425 const alu::Vector vel{props->Velocity[0], props->Velocity[1], props->Velocity[2], 0.0};
427 ctx->mParams.Matrix = rot;
428 ctx->mParams.Velocity = rot * vel;
430 ctx->mParams.Gain = props->Gain * ctx->mGainBoost;
431 ctx->mParams.MetersPerUnit = props->MetersPerUnit;
432 ctx->mParams.AirAbsorptionGainHF = props->AirAbsorptionGainHF;
434 ctx->mParams.DopplerFactor = props->DopplerFactor;
435 ctx->mParams.SpeedOfSound = props->SpeedOfSound * props->DopplerVelocity;
437 ctx->mParams.SourceDistanceModel = props->SourceDistanceModel;
438 ctx->mParams.mDistanceModel = props->mDistanceModel;
440 AtomicReplaceHead(ctx->mFreeContextProps, props);
441 return true;
444 bool CalcEffectSlotParams(EffectSlot *slot, EffectSlot **sorted_slots, ContextBase *context)
446 EffectSlotProps *props{slot->Update.exchange(nullptr, std::memory_order_acq_rel)};
447 if(!props) return false;
449 /* If the effect slot target changed, clear the first sorted entry to force
450 * a re-sort.
452 if(slot->Target != props->Target)
453 *sorted_slots = nullptr;
454 slot->Gain = props->Gain;
455 slot->AuxSendAuto = props->AuxSendAuto;
456 slot->Target = props->Target;
457 slot->EffectType = props->Type;
458 slot->mEffectProps = props->Props;
459 if(props->Type == EffectSlotType::Reverb || props->Type == EffectSlotType::EAXReverb)
461 slot->RoomRolloff = props->Props.Reverb.RoomRolloffFactor;
462 slot->DecayTime = props->Props.Reverb.DecayTime;
463 slot->DecayLFRatio = props->Props.Reverb.DecayLFRatio;
464 slot->DecayHFRatio = props->Props.Reverb.DecayHFRatio;
465 slot->DecayHFLimit = props->Props.Reverb.DecayHFLimit;
466 slot->AirAbsorptionGainHF = props->Props.Reverb.AirAbsorptionGainHF;
468 else
470 slot->RoomRolloff = 0.0f;
471 slot->DecayTime = 0.0f;
472 slot->DecayLFRatio = 0.0f;
473 slot->DecayHFRatio = 0.0f;
474 slot->DecayHFLimit = false;
475 slot->AirAbsorptionGainHF = 1.0f;
478 EffectState *state{props->State.release()};
479 EffectState *oldstate{slot->mEffectState.release()};
480 slot->mEffectState.reset(state);
482 /* Only release the old state if it won't get deleted, since we can't be
483 * deleting/freeing anything in the mixer.
485 if(!oldstate->releaseIfNoDelete())
487 /* Otherwise, if it would be deleted send it off with a release event. */
488 RingBuffer *ring{context->mAsyncEvents.get()};
489 auto evt_vec = ring->getWriteVector();
490 if(evt_vec.first.len > 0) [[likely]]
492 AsyncEvent *evt{al::construct_at(reinterpret_cast<AsyncEvent*>(evt_vec.first.buf),
493 AsyncEvent::ReleaseEffectState)};
494 evt->u.mEffectState = oldstate;
495 ring->writeAdvance(1);
497 else
499 /* If writing the event failed, the queue was probably full. Store
500 * the old state in the property object where it can eventually be
501 * cleaned up sometime later (not ideal, but better than blocking
502 * or leaking).
504 props->State.reset(oldstate);
508 AtomicReplaceHead(context->mFreeEffectslotProps, props);
510 EffectTarget output;
511 if(EffectSlot *target{slot->Target})
512 output = EffectTarget{&target->Wet, nullptr};
513 else
515 DeviceBase *device{context->mDevice};
516 output = EffectTarget{&device->Dry, &device->RealOut};
518 state->update(context, slot, &slot->mEffectProps, output);
519 return true;
523 /* Scales the given azimuth toward the side (+/- pi/2 radians) for positions in
524 * front.
526 inline float ScaleAzimuthFront(float azimuth, float scale)
528 const float abs_azi{std::fabs(azimuth)};
529 if(!(abs_azi >= al::numbers::pi_v<float>*0.5f))
530 return std::copysign(minf(abs_azi*scale, al::numbers::pi_v<float>*0.5f), azimuth);
531 return azimuth;
534 /* Wraps the given value in radians to stay between [-pi,+pi] */
535 inline float WrapRadians(float r)
537 static constexpr float Pi{al::numbers::pi_v<float>};
538 static constexpr float Pi2{Pi*2.0f};
539 if(r > Pi) return std::fmod(Pi+r, Pi2) - Pi;
540 if(r < -Pi) return Pi - std::fmod(Pi-r, Pi2);
541 return r;
544 /* Begin ambisonic rotation helpers.
546 * Rotating first-order B-Format just needs a straight-forward X/Y/Z rotation
547 * matrix. Higher orders, however, are more complicated. The method implemented
548 * here is a recursive algorithm (the rotation for first-order is used to help
549 * generate the second-order rotation, which helps generate the third-order
550 * rotation, etc).
552 * Adapted from
553 * <https://github.com/polarch/Spherical-Harmonic-Transform/blob/master/getSHrotMtx.m>,
554 * provided under the BSD 3-Clause license.
556 * Copyright (c) 2015, Archontis Politis
557 * Copyright (c) 2019, Christopher Robinson
559 * The u, v, and w coefficients used for generating higher-order rotations are
560 * precomputed since they're constant. The second-order coefficients are
561 * followed by the third-order coefficients, etc.
563 template<size_t L>
564 constexpr size_t CalcRotatorSize()
565 { return (L*2 + 1)*(L*2 + 1) + CalcRotatorSize<L-1>(); }
567 template<> constexpr size_t CalcRotatorSize<0>() = delete;
568 template<> constexpr size_t CalcRotatorSize<1>() = delete;
569 template<> constexpr size_t CalcRotatorSize<2>() { return 5*5; }
571 struct RotatorCoeffs {
572 struct CoeffValues {
573 float u, v, w;
575 std::array<CoeffValues,CalcRotatorSize<MaxAmbiOrder>()> mCoeffs{};
577 RotatorCoeffs()
579 auto coeffs = mCoeffs.begin();
581 for(int l=2;l <= MaxAmbiOrder;++l)
583 for(int m{-l};m <= l;++m)
585 for(int n{-l};n <= l;++n)
587 // compute u,v,w terms of Eq.8.1 (Table I)
588 const bool d{m == 0}; // the delta function d_m0
589 const float denom{static_cast<float>((std::abs(n) == l) ?
590 (2*l) * (2*l - 1) : (l*l - n*n))};
592 const int abs_m{std::abs(m)};
593 coeffs->u = std::sqrt(static_cast<float>(l*l - m*m)/denom);
594 coeffs->v = std::sqrt(static_cast<float>(l+abs_m-1) *
595 static_cast<float>(l+abs_m) / denom) * (1.0f+d) * (1.0f - 2.0f*d) * 0.5f;
596 coeffs->w = std::sqrt(static_cast<float>(l-abs_m-1) *
597 static_cast<float>(l-abs_m) / denom) * (1.0f-d) * -0.5f;
598 ++coeffs;
604 const RotatorCoeffs RotatorCoeffArray{};
607 * Given the matrix, pre-filled with the (zeroth- and) first-order rotation
608 * coefficients, this fills in the coefficients for the higher orders up to and
609 * including the given order. The matrix is in ACN layout.
611 void AmbiRotator(AmbiRotateMatrix &matrix, const int order)
613 /* Don't do anything for < 2nd order. */
614 if(order < 2) return;
616 auto P = [](const int i, const int l, const int a, const int n, const size_t last_band,
617 const AmbiRotateMatrix &R)
619 const float ri1{ R[static_cast<uint>(i+2)][ 1+2]};
620 const float rim1{R[static_cast<uint>(i+2)][-1+2]};
621 const float ri0{ R[static_cast<uint>(i+2)][ 0+2]};
623 auto vec = R[static_cast<uint>(a+l-1) + last_band].cbegin() + last_band;
624 if(n == -l)
625 return ri1*vec[0] + rim1*vec[static_cast<uint>(l-1)*size_t{2}];
626 if(n == l)
627 return ri1*vec[static_cast<uint>(l-1)*size_t{2}] - rim1*vec[0];
628 return ri0*vec[static_cast<uint>(n+l-1)];
631 auto U = [P](const int l, const int m, const int n, const size_t last_band,
632 const AmbiRotateMatrix &R)
634 return P(0, l, m, n, last_band, R);
636 auto V = [P](const int l, const int m, const int n, const size_t last_band,
637 const AmbiRotateMatrix &R)
639 using namespace al::numbers;
640 if(m > 0)
642 const bool d{m == 1};
643 const float p0{P( 1, l, m-1, n, last_band, R)};
644 const float p1{P(-1, l, -m+1, n, last_band, R)};
645 return d ? p0*sqrt2_v<float> : (p0 - p1);
647 const bool d{m == -1};
648 const float p0{P( 1, l, m+1, n, last_band, R)};
649 const float p1{P(-1, l, -m-1, n, last_band, R)};
650 return d ? p1*sqrt2_v<float> : (p0 + p1);
652 auto W = [P](const int l, const int m, const int n, const size_t last_band,
653 const AmbiRotateMatrix &R)
655 assert(m != 0);
656 if(m > 0)
658 const float p0{P( 1, l, m+1, n, last_band, R)};
659 const float p1{P(-1, l, -m-1, n, last_band, R)};
660 return p0 + p1;
662 const float p0{P( 1, l, m-1, n, last_band, R)};
663 const float p1{P(-1, l, -m+1, n, last_band, R)};
664 return p0 - p1;
667 // compute rotation matrix of each subsequent band recursively
668 auto coeffs = RotatorCoeffArray.mCoeffs.cbegin();
669 size_t band_idx{4}, last_band{1};
670 for(int l{2};l <= order;++l)
672 size_t y{band_idx};
673 for(int m{-l};m <= l;++m,++y)
675 size_t x{band_idx};
676 for(int n{-l};n <= l;++n,++x)
678 float r{0.0f};
680 // computes Eq.8.1
681 const float u{coeffs->u};
682 if(u != 0.0f) r += u * U(l, m, n, last_band, matrix);
683 const float v{coeffs->v};
684 if(v != 0.0f) r += v * V(l, m, n, last_band, matrix);
685 const float w{coeffs->w};
686 if(w != 0.0f) r += w * W(l, m, n, last_band, matrix);
688 matrix[y][x] = r;
689 ++coeffs;
692 last_band = band_idx;
693 band_idx += static_cast<uint>(l)*size_t{2} + 1;
696 /* End ambisonic rotation helpers. */
699 constexpr float Deg2Rad(float x) noexcept
700 { return static_cast<float>(al::numbers::pi / 180.0 * x); }
702 struct GainTriplet { float Base, HF, LF; };
704 void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, const float zpos,
705 const float Distance, const float Spread, const GainTriplet &DryGain,
706 const al::span<const GainTriplet,MAX_SENDS> WetGain, EffectSlot *(&SendSlots)[MAX_SENDS],
707 const VoiceProps *props, const ContextParams &Context, DeviceBase *Device)
709 static constexpr ChanMap MonoMap[1]{
710 { FrontCenter, 0.0f, 0.0f }
711 }, RearMap[2]{
712 { BackLeft, Deg2Rad(-150.0f), Deg2Rad(0.0f) },
713 { BackRight, Deg2Rad( 150.0f), Deg2Rad(0.0f) }
714 }, QuadMap[4]{
715 { FrontLeft, Deg2Rad( -45.0f), Deg2Rad(0.0f) },
716 { FrontRight, Deg2Rad( 45.0f), Deg2Rad(0.0f) },
717 { BackLeft, Deg2Rad(-135.0f), Deg2Rad(0.0f) },
718 { BackRight, Deg2Rad( 135.0f), Deg2Rad(0.0f) }
719 }, X51Map[6]{
720 { FrontLeft, Deg2Rad( -30.0f), Deg2Rad(0.0f) },
721 { FrontRight, Deg2Rad( 30.0f), Deg2Rad(0.0f) },
722 { FrontCenter, Deg2Rad( 0.0f), Deg2Rad(0.0f) },
723 { LFE, 0.0f, 0.0f },
724 { SideLeft, Deg2Rad(-110.0f), Deg2Rad(0.0f) },
725 { SideRight, Deg2Rad( 110.0f), Deg2Rad(0.0f) }
726 }, X61Map[7]{
727 { FrontLeft, Deg2Rad(-30.0f), Deg2Rad(0.0f) },
728 { FrontRight, Deg2Rad( 30.0f), Deg2Rad(0.0f) },
729 { FrontCenter, Deg2Rad( 0.0f), Deg2Rad(0.0f) },
730 { LFE, 0.0f, 0.0f },
731 { BackCenter, Deg2Rad(180.0f), Deg2Rad(0.0f) },
732 { SideLeft, Deg2Rad(-90.0f), Deg2Rad(0.0f) },
733 { SideRight, Deg2Rad( 90.0f), Deg2Rad(0.0f) }
734 }, X71Map[8]{
735 { FrontLeft, Deg2Rad( -30.0f), Deg2Rad(0.0f) },
736 { FrontRight, Deg2Rad( 30.0f), Deg2Rad(0.0f) },
737 { FrontCenter, Deg2Rad( 0.0f), Deg2Rad(0.0f) },
738 { LFE, 0.0f, 0.0f },
739 { BackLeft, Deg2Rad(-150.0f), Deg2Rad(0.0f) },
740 { BackRight, Deg2Rad( 150.0f), Deg2Rad(0.0f) },
741 { SideLeft, Deg2Rad( -90.0f), Deg2Rad(0.0f) },
742 { SideRight, Deg2Rad( 90.0f), Deg2Rad(0.0f) }
745 ChanMap StereoMap[2]{
746 { FrontLeft, Deg2Rad(-30.0f), Deg2Rad(0.0f) },
747 { FrontRight, Deg2Rad( 30.0f), Deg2Rad(0.0f) }
750 const auto Frequency = static_cast<float>(Device->Frequency);
751 const uint NumSends{Device->NumAuxSends};
753 const size_t num_channels{voice->mChans.size()};
754 ASSUME(num_channels > 0);
756 for(auto &chandata : voice->mChans)
758 chandata.mDryParams.Hrtf.Target = HrtfFilter{};
759 chandata.mDryParams.Gains.Target.fill(0.0f);
760 std::for_each(chandata.mWetParams.begin(), chandata.mWetParams.begin()+NumSends,
761 [](SendParams &params) -> void { params.Gains.Target.fill(0.0f); });
764 DirectMode DirectChannels{props->DirectChannels};
765 const ChanMap *chans{nullptr};
766 switch(voice->mFmtChannels)
768 case FmtMono:
769 chans = MonoMap;
770 /* Mono buffers are never played direct. */
771 DirectChannels = DirectMode::Off;
772 break;
774 case FmtStereo:
775 if(DirectChannels == DirectMode::Off)
777 /* Convert counter-clockwise to clock-wise, and wrap between
778 * [-pi,+pi].
780 StereoMap[0].angle = WrapRadians(-props->StereoPan[0]);
781 StereoMap[1].angle = WrapRadians(-props->StereoPan[1]);
783 chans = StereoMap;
784 break;
786 case FmtRear: chans = RearMap; break;
787 case FmtQuad: chans = QuadMap; break;
788 case FmtX51: chans = X51Map; break;
789 case FmtX61: chans = X61Map; break;
790 case FmtX71: chans = X71Map; break;
792 case FmtBFormat2D:
793 case FmtBFormat3D:
794 case FmtUHJ2:
795 case FmtUHJ3:
796 case FmtUHJ4:
797 case FmtSuperStereo:
798 DirectChannels = DirectMode::Off;
799 break;
802 voice->mFlags.reset(VoiceHasHrtf).reset(VoiceHasNfc);
803 if(auto *decoder{voice->mDecoder.get()})
804 decoder->mWidthControl = minf(props->EnhWidth, 0.7f);
806 if(IsAmbisonic(voice->mFmtChannels))
808 /* Special handling for B-Format and UHJ sources. */
810 if(Device->AvgSpeakerDist > 0.0f && voice->mFmtChannels != FmtUHJ2
811 && voice->mFmtChannels != FmtSuperStereo)
813 if(!(Distance > std::numeric_limits<float>::epsilon()))
815 /* NOTE: The NFCtrlFilters were created with a w0 of 0, which
816 * is what we want for FOA input. The first channel may have
817 * been previously re-adjusted if panned, so reset it.
819 voice->mChans[0].mDryParams.NFCtrlFilter.adjust(0.0f);
821 else
823 /* Clamp the distance for really close sources, to prevent
824 * excessive bass.
826 const float mdist{maxf(Distance*NfcScale, Device->AvgSpeakerDist/4.0f)};
827 const float w0{SpeedOfSoundMetersPerSec / (mdist * Frequency)};
829 /* Only need to adjust the first channel of a B-Format source. */
830 voice->mChans[0].mDryParams.NFCtrlFilter.adjust(w0);
833 voice->mFlags.set(VoiceHasNfc);
836 /* Panning a B-Format sound toward some direction is easy. Just pan the
837 * first (W) channel as a normal mono sound. The angular spread is used
838 * as a directional scalar to blend between full coverage and full
839 * panning.
841 const float coverage{!(Distance > std::numeric_limits<float>::epsilon()) ? 1.0f :
842 (al::numbers::inv_pi_v<float>/2.0f * Spread)};
844 auto calc_coeffs = [xpos,ypos,zpos](RenderMode mode)
846 if(mode != RenderMode::Pairwise)
847 return CalcDirectionCoeffs({xpos, ypos, zpos});
849 /* Clamp Y, in case rounding errors caused it to end up outside
850 * of -1...+1.
852 const float ev{std::asin(clampf(ypos, -1.0f, 1.0f))};
853 /* Negate Z for right-handed coords with -Z in front. */
854 const float az{std::atan2(xpos, -zpos)};
856 /* A scalar of 1.5 for plain stereo results in +/-60 degrees
857 * being moved to +/-90 degrees for direct right and left
858 * speaker responses.
860 return CalcAngleCoeffs(ScaleAzimuthFront(az, 1.5f), ev, 0.0f);
862 auto&& scales = GetAmbiScales(voice->mAmbiScaling);
863 auto coeffs = calc_coeffs(Device->mRenderMode);
864 /* Scale the panned W signal based on the coverage (full coverage means
865 * no panned signal). Scale the panned W signal according to channel
866 * scaling.
868 std::transform(coeffs.begin(), coeffs.end(), coeffs.begin(),
869 [scale=(1.0f-coverage)*scales[0]](const float c){ return c * scale; });
871 if(!(coverage > 0.0f))
873 ComputePanGains(&Device->Dry, coeffs.data(), DryGain.Base,
874 voice->mChans[0].mDryParams.Gains.Target);
875 for(uint i{0};i < NumSends;i++)
877 if(const EffectSlot *Slot{SendSlots[i]})
878 ComputePanGains(&Slot->Wet, coeffs.data(), WetGain[i].Base*scales[0],
879 voice->mChans[0].mWetParams[i].Gains.Target);
882 else
884 /* Local B-Format sources have their XYZ channels rotated according
885 * to the orientation.
887 /* AT then UP */
888 alu::Vector N{props->OrientAt[0], props->OrientAt[1], props->OrientAt[2], 0.0f};
889 N.normalize();
890 alu::Vector V{props->OrientUp[0], props->OrientUp[1], props->OrientUp[2], 0.0f};
891 V.normalize();
892 if(!props->HeadRelative)
894 N = Context.Matrix * N;
895 V = Context.Matrix * V;
897 /* Build and normalize right-vector */
898 alu::Vector U{N.cross_product(V)};
899 U.normalize();
901 /* Build a rotation matrix. Manually fill the zeroth- and first-
902 * order elements, then construct the rotation for the higher
903 * orders.
905 AmbiRotateMatrix &shrot = Device->mAmbiRotateMatrix;
906 shrot.fill({});
908 shrot[0][0] = 1.0f;
909 shrot[1][1] = U[0]; shrot[1][2] = -V[0]; shrot[1][3] = -N[0];
910 shrot[2][1] = -U[1]; shrot[2][2] = V[1]; shrot[2][3] = N[1];
911 shrot[3][1] = U[2]; shrot[3][2] = -V[2]; shrot[3][3] = -N[2];
912 AmbiRotator(shrot, static_cast<int>(Device->mAmbiOrder));
913 /* If the device is higher order than the voice, "upsample" the
914 * matrix.
916 * NOTE: Starting with second-order, a 2D upsample needs to be
917 * applied with a 2D source and 3D output, even when they're the
918 * same order. This is because higher orders have a height offset
919 * on various channels (i.e. when elevation=0, those height-related
920 * channels should be non-0).
922 if(Device->mAmbiOrder > voice->mAmbiOrder
923 || (Device->mAmbiOrder >= 2 && !Device->m2DMixing
924 && Is2DAmbisonic(voice->mFmtChannels)))
926 if(voice->mAmbiOrder == 1)
928 auto&& upsampler = Is2DAmbisonic(voice->mFmtChannels) ?
929 AmbiScale::FirstOrder2DUp : AmbiScale::FirstOrderUp;
930 UpsampleBFormatTransform(Device->mAmbiOrder, upsampler, shrot);
932 else if(voice->mAmbiOrder == 2)
934 auto&& upsampler = Is2DAmbisonic(voice->mFmtChannels) ?
935 AmbiScale::SecondOrder2DUp : AmbiScale::SecondOrderUp;
936 UpsampleBFormatTransform(Device->mAmbiOrder, upsampler, shrot);
938 else if(voice->mAmbiOrder == 3)
940 auto&& upsampler = Is2DAmbisonic(voice->mFmtChannels) ?
941 AmbiScale::ThirdOrder2DUp : AmbiScale::ThirdOrderUp;
942 UpsampleBFormatTransform(Device->mAmbiOrder, upsampler, shrot);
944 else if(voice->mAmbiOrder == 4)
946 auto&& upsampler = AmbiScale::FourthOrder2DUp;
947 UpsampleBFormatTransform(Device->mAmbiOrder, upsampler, shrot);
951 /* Convert the rotation matrix for input ordering and scaling, and
952 * whether input is 2D or 3D.
954 const uint8_t *index_map{Is2DAmbisonic(voice->mFmtChannels) ?
955 GetAmbi2DLayout(voice->mAmbiLayout).data() :
956 GetAmbiLayout(voice->mAmbiLayout).data()};
958 static const uint8_t OrderOffset[MaxAmbiOrder+1]{0, 1, 4, 9,};
959 for(size_t c{0};c < num_channels;c++)
961 const size_t acn{index_map[c]};
962 const size_t order{AmbiIndex::OrderFromChannel()[acn]};
963 const float scale{scales[acn] * coverage};
965 /* For channel 0, combine the B-Format signal (scaled according
966 * to the coverage amount) with the directional pan. For all
967 * other channels, use just the (scaled) B-Format signal.
969 for(size_t x{OrderOffset[order]};x < MaxAmbiChannels;++x)
970 coeffs[x] += shrot[x][acn] * scale;
972 ComputePanGains(&Device->Dry, coeffs.data(), DryGain.Base,
973 voice->mChans[c].mDryParams.Gains.Target);
975 for(uint i{0};i < NumSends;i++)
977 if(const EffectSlot *Slot{SendSlots[i]})
978 ComputePanGains(&Slot->Wet, coeffs.data(), WetGain[i].Base,
979 voice->mChans[c].mWetParams[i].Gains.Target);
982 coeffs = std::array<float,MaxAmbiChannels>{};
986 else if(DirectChannels != DirectMode::Off && !Device->RealOut.RemixMap.empty())
988 /* Direct source channels always play local. Skip the virtual channels
989 * and write inputs to the matching real outputs.
991 voice->mDirect.Buffer = Device->RealOut.Buffer;
993 for(size_t c{0};c < num_channels;c++)
995 uint idx{Device->channelIdxByName(chans[c].channel)};
996 if(idx != InvalidChannelIndex)
997 voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain.Base;
998 else if(DirectChannels == DirectMode::RemixMismatch)
1000 auto match_channel = [chans,c](const InputRemixMap &map) noexcept -> bool
1001 { return chans[c].channel == map.channel; };
1002 auto remap = std::find_if(Device->RealOut.RemixMap.cbegin(),
1003 Device->RealOut.RemixMap.cend(), match_channel);
1004 if(remap != Device->RealOut.RemixMap.cend())
1006 for(const auto &target : remap->targets)
1008 idx = Device->channelIdxByName(target.channel);
1009 if(idx != InvalidChannelIndex)
1010 voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain.Base *
1011 target.mix;
1017 /* Auxiliary sends still use normal channel panning since they mix to
1018 * B-Format, which can't channel-match.
1020 for(size_t c{0};c < num_channels;c++)
1022 /* Skip LFE */
1023 if(chans[c].channel == LFE)
1024 continue;
1026 const auto coeffs = CalcAngleCoeffs(chans[c].angle, chans[c].elevation, 0.0f);
1028 for(uint i{0};i < NumSends;i++)
1030 if(const EffectSlot *Slot{SendSlots[i]})
1031 ComputePanGains(&Slot->Wet, coeffs.data(), WetGain[i].Base,
1032 voice->mChans[c].mWetParams[i].Gains.Target);
1036 else if(Device->mRenderMode == RenderMode::Hrtf)
1038 /* Full HRTF rendering. Skip the virtual channels and render to the
1039 * real outputs.
1041 voice->mDirect.Buffer = Device->RealOut.Buffer;
1043 if(Distance > std::numeric_limits<float>::epsilon())
1045 const float src_ev{std::asin(clampf(ypos, -1.0f, 1.0f))};
1046 const float src_az{std::atan2(xpos, -zpos)};
1048 if(voice->mFmtChannels == FmtMono)
1050 Device->mHrtf->getCoeffs(src_ev, src_az, Distance*NfcScale, Spread,
1051 voice->mChans[0].mDryParams.Hrtf.Target.Coeffs,
1052 voice->mChans[0].mDryParams.Hrtf.Target.Delay);
1053 voice->mChans[0].mDryParams.Hrtf.Target.Gain = DryGain.Base;
1055 const auto coeffs = CalcAngleCoeffs(src_az, src_ev, Spread);
1056 for(uint i{0};i < NumSends;i++)
1058 if(const EffectSlot *Slot{SendSlots[i]})
1059 ComputePanGains(&Slot->Wet, coeffs.data(), WetGain[i].Base,
1060 voice->mChans[0].mWetParams[i].Gains.Target);
1063 else for(size_t c{0};c < num_channels;c++)
1065 using namespace al::numbers;
1067 /* Skip LFE */
1068 if(chans[c].channel == LFE) continue;
1070 /* Warp the channel position toward the source position as the
1071 * source spread decreases. With no spread, all channels are at
1072 * the source position, at full spread (pi*2), each channel is
1073 * left unchanged.
1075 const float ev{lerpf(src_ev, chans[c].elevation, inv_pi_v<float>/2.0f * Spread)};
1077 float az{chans[c].angle - src_az};
1078 if(az < -pi_v<float>) az += pi_v<float>*2.0f;
1079 else if(az > pi_v<float>) az -= pi_v<float>*2.0f;
1081 az *= inv_pi_v<float>/2.0f * Spread;
1083 az += src_az;
1084 if(az < -pi_v<float>) az += pi_v<float>*2.0f;
1085 else if(az > pi_v<float>) az -= pi_v<float>*2.0f;
1087 Device->mHrtf->getCoeffs(ev, az, Distance*NfcScale, 0.0f,
1088 voice->mChans[c].mDryParams.Hrtf.Target.Coeffs,
1089 voice->mChans[c].mDryParams.Hrtf.Target.Delay);
1090 voice->mChans[c].mDryParams.Hrtf.Target.Gain = DryGain.Base;
1092 const auto coeffs = CalcAngleCoeffs(az, ev, 0.0f);
1093 for(uint i{0};i < NumSends;i++)
1095 if(const EffectSlot *Slot{SendSlots[i]})
1096 ComputePanGains(&Slot->Wet, coeffs.data(), WetGain[i].Base,
1097 voice->mChans[c].mWetParams[i].Gains.Target);
1101 else
1103 /* With no distance, spread is only meaningful for mono sources
1104 * where it can be 0 or full (non-mono sources are always full
1105 * spread here).
1107 const float spread{Spread * (voice->mFmtChannels == FmtMono)};
1109 /* Local sources on HRTF play with each channel panned to its
1110 * relative location around the listener, providing "virtual
1111 * speaker" responses.
1113 for(size_t c{0};c < num_channels;c++)
1115 /* Skip LFE */
1116 if(chans[c].channel == LFE)
1117 continue;
1119 /* Get the HRIR coefficients and delays for this channel
1120 * position.
1122 Device->mHrtf->getCoeffs(chans[c].elevation, chans[c].angle,
1123 std::numeric_limits<float>::infinity(), spread,
1124 voice->mChans[c].mDryParams.Hrtf.Target.Coeffs,
1125 voice->mChans[c].mDryParams.Hrtf.Target.Delay);
1126 voice->mChans[c].mDryParams.Hrtf.Target.Gain = DryGain.Base;
1128 /* Normal panning for auxiliary sends. */
1129 const auto coeffs = CalcAngleCoeffs(chans[c].angle, chans[c].elevation, spread);
1131 for(uint i{0};i < NumSends;i++)
1133 if(const EffectSlot *Slot{SendSlots[i]})
1134 ComputePanGains(&Slot->Wet, coeffs.data(), WetGain[i].Base,
1135 voice->mChans[c].mWetParams[i].Gains.Target);
1140 voice->mFlags.set(VoiceHasHrtf);
1142 else
1144 /* Non-HRTF rendering. Use normal panning to the output. */
1146 if(Distance > std::numeric_limits<float>::epsilon())
1148 /* Calculate NFC filter coefficient if needed. */
1149 if(Device->AvgSpeakerDist > 0.0f)
1151 /* Clamp the distance for really close sources, to prevent
1152 * excessive bass.
1154 const float mdist{maxf(Distance*NfcScale, Device->AvgSpeakerDist/4.0f)};
1155 const float w0{SpeedOfSoundMetersPerSec / (mdist * Frequency)};
1157 /* Adjust NFC filters. */
1158 for(size_t c{0};c < num_channels;c++)
1159 voice->mChans[c].mDryParams.NFCtrlFilter.adjust(w0);
1161 voice->mFlags.set(VoiceHasNfc);
1164 if(voice->mFmtChannels == FmtMono)
1166 auto calc_coeffs = [xpos,ypos,zpos,Spread](RenderMode mode)
1168 if(mode != RenderMode::Pairwise)
1169 return CalcDirectionCoeffs({xpos, ypos, zpos}, Spread);
1170 const float ev{std::asin(clampf(ypos, -1.0f, 1.0f))};
1171 const float az{std::atan2(xpos, -zpos)};
1172 return CalcAngleCoeffs(ScaleAzimuthFront(az, 1.5f), ev, Spread);
1174 const auto coeffs = calc_coeffs(Device->mRenderMode);
1176 ComputePanGains(&Device->Dry, coeffs.data(), DryGain.Base,
1177 voice->mChans[0].mDryParams.Gains.Target);
1178 for(uint i{0};i < NumSends;i++)
1180 if(const EffectSlot *Slot{SendSlots[i]})
1181 ComputePanGains(&Slot->Wet, coeffs.data(), WetGain[i].Base,
1182 voice->mChans[0].mWetParams[i].Gains.Target);
1185 else
1187 using namespace al::numbers;
1189 const float src_ev{std::asin(clampf(ypos, -1.0f, 1.0f))};
1190 const float src_az{std::atan2(xpos, -zpos)};
1192 for(size_t c{0};c < num_channels;c++)
1194 /* Special-case LFE */
1195 if(chans[c].channel == LFE)
1197 if(Device->Dry.Buffer.data() == Device->RealOut.Buffer.data())
1199 const uint idx{Device->channelIdxByName(chans[c].channel)};
1200 if(idx != InvalidChannelIndex)
1201 voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain.Base;
1203 continue;
1206 /* Warp the channel position toward the source position as
1207 * the spread decreases. With no spread, all channels are
1208 * at the source position, at full spread (pi*2), each
1209 * channel position is left unchanged.
1211 const float ev{lerpf(src_ev, chans[c].elevation,
1212 inv_pi_v<float>/2.0f * Spread)};
1214 float az{chans[c].angle - src_az};
1215 if(az < -pi_v<float>) az += pi_v<float>*2.0f;
1216 else if(az > pi_v<float>) az -= pi_v<float>*2.0f;
1218 az *= inv_pi_v<float>/2.0f * Spread;
1220 az += src_az;
1221 if(az < -pi_v<float>) az += pi_v<float>*2.0f;
1222 else if(az > pi_v<float>) az -= pi_v<float>*2.0f;
1224 if(Device->mRenderMode == RenderMode::Pairwise)
1225 az = ScaleAzimuthFront(az, 3.0f);
1226 const auto coeffs = CalcAngleCoeffs(az, ev, 0.0f);
1228 ComputePanGains(&Device->Dry, coeffs.data(), DryGain.Base,
1229 voice->mChans[c].mDryParams.Gains.Target);
1230 for(uint i{0};i < NumSends;i++)
1232 if(const EffectSlot *Slot{SendSlots[i]})
1233 ComputePanGains(&Slot->Wet, coeffs.data(), WetGain[i].Base,
1234 voice->mChans[c].mWetParams[i].Gains.Target);
1239 else
1241 if(Device->AvgSpeakerDist > 0.0f)
1243 /* If the source distance is 0, simulate a plane-wave by using
1244 * infinite distance, which results in a w0 of 0.
1246 static constexpr float w0{0.0f};
1247 for(size_t c{0};c < num_channels;c++)
1248 voice->mChans[c].mDryParams.NFCtrlFilter.adjust(w0);
1250 voice->mFlags.set(VoiceHasNfc);
1253 /* With no distance, spread is only meaningful for mono sources
1254 * where it can be 0 or full (non-mono sources are always full
1255 * spread here).
1257 const float spread{Spread * (voice->mFmtChannels == FmtMono)};
1258 for(size_t c{0};c < num_channels;c++)
1260 /* Special-case LFE */
1261 if(chans[c].channel == LFE)
1263 if(Device->Dry.Buffer.data() == Device->RealOut.Buffer.data())
1265 const uint idx{Device->channelIdxByName(chans[c].channel)};
1266 if(idx != InvalidChannelIndex)
1267 voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain.Base;
1269 continue;
1272 const auto coeffs = CalcAngleCoeffs((Device->mRenderMode == RenderMode::Pairwise)
1273 ? ScaleAzimuthFront(chans[c].angle, 3.0f) : chans[c].angle,
1274 chans[c].elevation, spread);
1276 ComputePanGains(&Device->Dry, coeffs.data(), DryGain.Base,
1277 voice->mChans[c].mDryParams.Gains.Target);
1278 for(uint i{0};i < NumSends;i++)
1280 if(const EffectSlot *Slot{SendSlots[i]})
1281 ComputePanGains(&Slot->Wet, coeffs.data(), WetGain[i].Base,
1282 voice->mChans[c].mWetParams[i].Gains.Target);
1289 const float hfNorm{props->Direct.HFReference / Frequency};
1290 const float lfNorm{props->Direct.LFReference / Frequency};
1292 voice->mDirect.FilterType = AF_None;
1293 if(DryGain.HF != 1.0f) voice->mDirect.FilterType |= AF_LowPass;
1294 if(DryGain.LF != 1.0f) voice->mDirect.FilterType |= AF_HighPass;
1296 auto &lowpass = voice->mChans[0].mDryParams.LowPass;
1297 auto &highpass = voice->mChans[0].mDryParams.HighPass;
1298 lowpass.setParamsFromSlope(BiquadType::HighShelf, hfNorm, DryGain.HF, 1.0f);
1299 highpass.setParamsFromSlope(BiquadType::LowShelf, lfNorm, DryGain.LF, 1.0f);
1300 for(size_t c{1};c < num_channels;c++)
1302 voice->mChans[c].mDryParams.LowPass.copyParamsFrom(lowpass);
1303 voice->mChans[c].mDryParams.HighPass.copyParamsFrom(highpass);
1306 for(uint i{0};i < NumSends;i++)
1308 const float hfNorm{props->Send[i].HFReference / Frequency};
1309 const float lfNorm{props->Send[i].LFReference / Frequency};
1311 voice->mSend[i].FilterType = AF_None;
1312 if(WetGain[i].HF != 1.0f) voice->mSend[i].FilterType |= AF_LowPass;
1313 if(WetGain[i].LF != 1.0f) voice->mSend[i].FilterType |= AF_HighPass;
1315 auto &lowpass = voice->mChans[0].mWetParams[i].LowPass;
1316 auto &highpass = voice->mChans[0].mWetParams[i].HighPass;
1317 lowpass.setParamsFromSlope(BiquadType::HighShelf, hfNorm, WetGain[i].HF, 1.0f);
1318 highpass.setParamsFromSlope(BiquadType::LowShelf, lfNorm, WetGain[i].LF, 1.0f);
1319 for(size_t c{1};c < num_channels;c++)
1321 voice->mChans[c].mWetParams[i].LowPass.copyParamsFrom(lowpass);
1322 voice->mChans[c].mWetParams[i].HighPass.copyParamsFrom(highpass);
1327 void CalcNonAttnSourceParams(Voice *voice, const VoiceProps *props, const ContextBase *context)
1329 DeviceBase *Device{context->mDevice};
1330 EffectSlot *SendSlots[MAX_SENDS];
1332 voice->mDirect.Buffer = Device->Dry.Buffer;
1333 for(uint i{0};i < Device->NumAuxSends;i++)
1335 SendSlots[i] = props->Send[i].Slot;
1336 if(!SendSlots[i] || SendSlots[i]->EffectType == EffectSlotType::None)
1338 SendSlots[i] = nullptr;
1339 voice->mSend[i].Buffer = {};
1341 else
1342 voice->mSend[i].Buffer = SendSlots[i]->Wet.Buffer;
1345 /* Calculate the stepping value */
1346 const auto Pitch = static_cast<float>(voice->mFrequency) /
1347 static_cast<float>(Device->Frequency) * props->Pitch;
1348 if(Pitch > float{MaxPitch})
1349 voice->mStep = MaxPitch<<MixerFracBits;
1350 else
1351 voice->mStep = maxu(fastf2u(Pitch * MixerFracOne), 1);
1352 voice->mResampler = PrepareResampler(props->mResampler, voice->mStep, &voice->mResampleState);
1354 /* Calculate gains */
1355 GainTriplet DryGain;
1356 DryGain.Base = minf(clampf(props->Gain, props->MinGain, props->MaxGain) * props->Direct.Gain *
1357 context->mParams.Gain, GainMixMax);
1358 DryGain.HF = props->Direct.GainHF;
1359 DryGain.LF = props->Direct.GainLF;
1360 GainTriplet WetGain[MAX_SENDS];
1361 for(uint i{0};i < Device->NumAuxSends;i++)
1363 WetGain[i].Base = minf(clampf(props->Gain, props->MinGain, props->MaxGain) *
1364 props->Send[i].Gain * context->mParams.Gain, GainMixMax);
1365 WetGain[i].HF = props->Send[i].GainHF;
1366 WetGain[i].LF = props->Send[i].GainLF;
1369 CalcPanningAndFilters(voice, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, DryGain, WetGain, SendSlots, props,
1370 context->mParams, Device);
1373 void CalcAttnSourceParams(Voice *voice, const VoiceProps *props, const ContextBase *context)
1375 DeviceBase *Device{context->mDevice};
1376 const uint NumSends{Device->NumAuxSends};
1378 /* Set mixing buffers and get send parameters. */
1379 voice->mDirect.Buffer = Device->Dry.Buffer;
1380 EffectSlot *SendSlots[MAX_SENDS];
1381 uint UseDryAttnForRoom{0};
1382 for(uint i{0};i < NumSends;i++)
1384 SendSlots[i] = props->Send[i].Slot;
1385 if(!SendSlots[i] || SendSlots[i]->EffectType == EffectSlotType::None)
1386 SendSlots[i] = nullptr;
1387 else if(!SendSlots[i]->AuxSendAuto)
1389 /* If the slot's auxiliary send auto is off, the data sent to the
1390 * effect slot is the same as the dry path, sans filter effects.
1392 UseDryAttnForRoom |= 1u<<i;
1395 if(!SendSlots[i])
1396 voice->mSend[i].Buffer = {};
1397 else
1398 voice->mSend[i].Buffer = SendSlots[i]->Wet.Buffer;
1401 /* Transform source to listener space (convert to head relative) */
1402 alu::Vector Position{props->Position[0], props->Position[1], props->Position[2], 1.0f};
1403 alu::Vector Velocity{props->Velocity[0], props->Velocity[1], props->Velocity[2], 0.0f};
1404 alu::Vector Direction{props->Direction[0], props->Direction[1], props->Direction[2], 0.0f};
1405 if(!props->HeadRelative)
1407 /* Transform source vectors */
1408 Position = context->mParams.Matrix * (Position - context->mParams.Position);
1409 Velocity = context->mParams.Matrix * Velocity;
1410 Direction = context->mParams.Matrix * Direction;
1412 else
1414 /* Offset the source velocity to be relative of the listener velocity */
1415 Velocity += context->mParams.Velocity;
1418 const bool directional{Direction.normalize() > 0.0f};
1419 alu::Vector ToSource{Position[0], Position[1], Position[2], 0.0f};
1420 const float Distance{ToSource.normalize()};
1422 /* Calculate distance attenuation */
1423 float ClampedDist{Distance};
1424 float DryGainBase{props->Gain};
1425 float WetGainBase{props->Gain};
1427 switch(context->mParams.SourceDistanceModel ? props->mDistanceModel
1428 : context->mParams.mDistanceModel)
1430 case DistanceModel::InverseClamped:
1431 if(props->MaxDistance < props->RefDistance) break;
1432 ClampedDist = clampf(ClampedDist, props->RefDistance, props->MaxDistance);
1433 /*fall-through*/
1434 case DistanceModel::Inverse:
1435 if(props->RefDistance > 0.0f)
1437 float dist{lerpf(props->RefDistance, ClampedDist, props->RolloffFactor)};
1438 if(dist > 0.0f) DryGainBase *= props->RefDistance / dist;
1440 dist = lerpf(props->RefDistance, ClampedDist, props->RoomRolloffFactor);
1441 if(dist > 0.0f) WetGainBase *= props->RefDistance / dist;
1443 break;
1445 case DistanceModel::LinearClamped:
1446 if(props->MaxDistance < props->RefDistance) break;
1447 ClampedDist = clampf(ClampedDist, props->RefDistance, props->MaxDistance);
1448 /*fall-through*/
1449 case DistanceModel::Linear:
1450 if(props->MaxDistance != props->RefDistance)
1452 float attn{(ClampedDist-props->RefDistance) /
1453 (props->MaxDistance-props->RefDistance) * props->RolloffFactor};
1454 DryGainBase *= maxf(1.0f - attn, 0.0f);
1456 attn = (ClampedDist-props->RefDistance) /
1457 (props->MaxDistance-props->RefDistance) * props->RoomRolloffFactor;
1458 WetGainBase *= maxf(1.0f - attn, 0.0f);
1460 break;
1462 case DistanceModel::ExponentClamped:
1463 if(props->MaxDistance < props->RefDistance) break;
1464 ClampedDist = clampf(ClampedDist, props->RefDistance, props->MaxDistance);
1465 /*fall-through*/
1466 case DistanceModel::Exponent:
1467 if(ClampedDist > 0.0f && props->RefDistance > 0.0f)
1469 const float dist_ratio{ClampedDist/props->RefDistance};
1470 DryGainBase *= std::pow(dist_ratio, -props->RolloffFactor);
1471 WetGainBase *= std::pow(dist_ratio, -props->RoomRolloffFactor);
1473 break;
1475 case DistanceModel::Disable:
1476 break;
1479 /* Calculate directional soundcones */
1480 float ConeHF{1.0f}, WetConeHF{1.0f};
1481 if(directional && props->InnerAngle < 360.0f)
1483 static constexpr float Rad2Deg{static_cast<float>(180.0 / al::numbers::pi)};
1484 const float Angle{Rad2Deg*2.0f * std::acos(-Direction.dot_product(ToSource)) * ConeScale};
1486 float ConeGain{1.0f};
1487 if(Angle >= props->OuterAngle)
1489 ConeGain = props->OuterGain;
1490 ConeHF = lerpf(1.0f, props->OuterGainHF, props->DryGainHFAuto);
1492 else if(Angle >= props->InnerAngle)
1494 const float scale{(Angle-props->InnerAngle) / (props->OuterAngle-props->InnerAngle)};
1495 ConeGain = lerpf(1.0f, props->OuterGain, scale);
1496 ConeHF = lerpf(1.0f, props->OuterGainHF, scale * props->DryGainHFAuto);
1499 DryGainBase *= ConeGain;
1500 WetGainBase *= lerpf(1.0f, ConeGain, props->WetGainAuto);
1502 WetConeHF = lerpf(1.0f, ConeHF, props->WetGainHFAuto);
1505 /* Apply gain and frequency filters */
1506 DryGainBase = clampf(DryGainBase, props->MinGain, props->MaxGain) * context->mParams.Gain;
1507 WetGainBase = clampf(WetGainBase, props->MinGain, props->MaxGain) * context->mParams.Gain;
1509 GainTriplet DryGain{};
1510 DryGain.Base = minf(DryGainBase * props->Direct.Gain, GainMixMax);
1511 DryGain.HF = ConeHF * props->Direct.GainHF;
1512 DryGain.LF = props->Direct.GainLF;
1513 GainTriplet WetGain[MAX_SENDS]{};
1514 for(uint i{0};i < NumSends;i++)
1516 /* If this effect slot's Auxiliary Send Auto is off, then use the dry
1517 * path distance and cone attenuation, otherwise use the wet (room)
1518 * path distance and cone attenuation. The send filter is used instead
1519 * of the direct filter, regardless.
1521 const bool use_room{!(UseDryAttnForRoom&(1u<<i))};
1522 const float gain{use_room ? WetGainBase : DryGainBase};
1523 WetGain[i].Base = minf(gain * props->Send[i].Gain, GainMixMax);
1524 WetGain[i].HF = (use_room ? WetConeHF : ConeHF) * props->Send[i].GainHF;
1525 WetGain[i].LF = props->Send[i].GainLF;
1528 /* Distance-based air absorption and initial send decay. */
1529 if(Distance > props->RefDistance) [[likely]]
1531 const float distance_base{(Distance-props->RefDistance) * props->RolloffFactor};
1532 const float distance_meters{distance_base * context->mParams.MetersPerUnit};
1533 const float dryabsorb{distance_meters * props->AirAbsorptionFactor};
1534 if(dryabsorb > std::numeric_limits<float>::epsilon())
1535 DryGain.HF *= std::pow(context->mParams.AirAbsorptionGainHF, dryabsorb);
1537 /* If the source's Auxiliary Send Filter Gain Auto is off, no extra
1538 * adjustment is applied to the send gains.
1540 for(uint i{props->WetGainAuto ? 0u : NumSends};i < NumSends;++i)
1542 if(!SendSlots[i] || !(SendSlots[i]->DecayTime > 0.0f))
1543 continue;
1545 auto calc_attenuation = [](float distance, float refdist, float rolloff) noexcept
1547 const float dist{lerpf(refdist, distance, rolloff)};
1548 if(dist > refdist) return refdist / dist;
1549 return 1.0f;
1552 /* The reverb effect's room rolloff factor always applies to an
1553 * inverse distance rolloff model.
1555 WetGain[i].Base *= calc_attenuation(Distance, props->RefDistance,
1556 SendSlots[i]->RoomRolloff);
1558 if(distance_meters > std::numeric_limits<float>::epsilon())
1559 WetGain[i].HF *= std::pow(SendSlots[i]->AirAbsorptionGainHF, distance_meters);
1561 /* If this effect slot's Auxiliary Send Auto is off, don't apply
1562 * the automatic initial reverb decay (should the reverb's room
1563 * rolloff still apply?).
1565 if(!SendSlots[i]->AuxSendAuto)
1566 continue;
1568 GainTriplet DecayDistance;
1569 /* Calculate the distances to where this effect's decay reaches
1570 * -60dB.
1572 DecayDistance.Base = SendSlots[i]->DecayTime * SpeedOfSoundMetersPerSec;
1573 DecayDistance.LF = DecayDistance.Base * SendSlots[i]->DecayLFRatio;
1574 DecayDistance.HF = DecayDistance.Base * SendSlots[i]->DecayHFRatio;
1575 if(SendSlots[i]->DecayHFLimit)
1577 const float airAbsorption{SendSlots[i]->AirAbsorptionGainHF};
1578 if(airAbsorption < 1.0f)
1580 /* Calculate the distance to where this effect's air
1581 * absorption reaches -60dB, and limit the effect's HF
1582 * decay distance (so it doesn't take any longer to decay
1583 * than the air would allow).
1585 static constexpr float log10_decaygain{-3.0f/*std::log10(ReverbDecayGain)*/};
1586 const float absorb_dist{log10_decaygain / std::log10(airAbsorption)};
1587 DecayDistance.HF = minf(absorb_dist, DecayDistance.HF);
1591 const float baseAttn = calc_attenuation(Distance, props->RefDistance,
1592 props->RolloffFactor);
1594 /* Apply a decay-time transformation to the wet path, based on the
1595 * source distance. The initial decay of the reverb effect is
1596 * calculated and applied to the wet path.
1598 const float fact{distance_base / DecayDistance.Base};
1599 const float gain{std::pow(ReverbDecayGain, fact)*(1.0f-baseAttn) + baseAttn};
1600 WetGain[i].Base *= gain;
1602 if(gain > 0.0f)
1604 const float hffact{distance_base / DecayDistance.HF};
1605 const float gainhf{std::pow(ReverbDecayGain, hffact)*(1.0f-baseAttn) + baseAttn};
1606 WetGain[i].HF *= minf(gainhf/gain, 1.0f);
1607 const float lffact{distance_base / DecayDistance.LF};
1608 const float gainlf{std::pow(ReverbDecayGain, lffact)*(1.0f-baseAttn) + baseAttn};
1609 WetGain[i].LF *= minf(gainlf/gain, 1.0f);
1615 /* Initial source pitch */
1616 float Pitch{props->Pitch};
1618 /* Calculate velocity-based doppler effect */
1619 float DopplerFactor{props->DopplerFactor * context->mParams.DopplerFactor};
1620 if(DopplerFactor > 0.0f)
1622 const alu::Vector &lvelocity = context->mParams.Velocity;
1623 float vss{Velocity.dot_product(ToSource) * -DopplerFactor};
1624 float vls{lvelocity.dot_product(ToSource) * -DopplerFactor};
1626 const float SpeedOfSound{context->mParams.SpeedOfSound};
1627 if(!(vls < SpeedOfSound))
1629 /* Listener moving away from the source at the speed of sound.
1630 * Sound waves can't catch it.
1632 Pitch = 0.0f;
1634 else if(!(vss < SpeedOfSound))
1636 /* Source moving toward the listener at the speed of sound. Sound
1637 * waves bunch up to extreme frequencies.
1639 Pitch = std::numeric_limits<float>::infinity();
1641 else
1643 /* Source and listener movement is nominal. Calculate the proper
1644 * doppler shift.
1646 Pitch *= (SpeedOfSound-vls) / (SpeedOfSound-vss);
1650 /* Adjust pitch based on the buffer and output frequencies, and calculate
1651 * fixed-point stepping value.
1653 Pitch *= static_cast<float>(voice->mFrequency) / static_cast<float>(Device->Frequency);
1654 if(Pitch > float{MaxPitch})
1655 voice->mStep = MaxPitch<<MixerFracBits;
1656 else
1657 voice->mStep = maxu(fastf2u(Pitch * MixerFracOne), 1);
1658 voice->mResampler = PrepareResampler(props->mResampler, voice->mStep, &voice->mResampleState);
1660 float spread{0.0f};
1661 if(props->Radius > Distance)
1662 spread = al::numbers::pi_v<float>*2.0f - Distance/props->Radius*al::numbers::pi_v<float>;
1663 else if(Distance > 0.0f)
1664 spread = std::asin(props->Radius/Distance) * 2.0f;
1666 CalcPanningAndFilters(voice, ToSource[0]*XScale, ToSource[1]*YScale, ToSource[2]*ZScale,
1667 Distance, spread, DryGain, WetGain, SendSlots, props, context->mParams, Device);
1670 void CalcSourceParams(Voice *voice, ContextBase *context, bool force)
1672 VoicePropsItem *props{voice->mUpdate.exchange(nullptr, std::memory_order_acq_rel)};
1673 if(!props && !force) return;
1675 if(props)
1677 voice->mProps = *props;
1679 AtomicReplaceHead(context->mFreeVoiceProps, props);
1682 if((voice->mProps.DirectChannels != DirectMode::Off && voice->mFmtChannels != FmtMono
1683 && !IsAmbisonic(voice->mFmtChannels))
1684 || voice->mProps.mSpatializeMode == SpatializeMode::Off
1685 || (voice->mProps.mSpatializeMode==SpatializeMode::Auto && voice->mFmtChannels != FmtMono))
1686 CalcNonAttnSourceParams(voice, &voice->mProps, context);
1687 else
1688 CalcAttnSourceParams(voice, &voice->mProps, context);
1692 void SendSourceStateEvent(ContextBase *context, uint id, VChangeState state)
1694 RingBuffer *ring{context->mAsyncEvents.get()};
1695 auto evt_vec = ring->getWriteVector();
1696 if(evt_vec.first.len < 1) return;
1698 AsyncEvent *evt{al::construct_at(reinterpret_cast<AsyncEvent*>(evt_vec.first.buf),
1699 AsyncEvent::SourceStateChange)};
1700 evt->u.srcstate.id = id;
1701 switch(state)
1703 case VChangeState::Reset:
1704 evt->u.srcstate.state = AsyncEvent::SrcState::Reset;
1705 break;
1706 case VChangeState::Stop:
1707 evt->u.srcstate.state = AsyncEvent::SrcState::Stop;
1708 break;
1709 case VChangeState::Play:
1710 evt->u.srcstate.state = AsyncEvent::SrcState::Play;
1711 break;
1712 case VChangeState::Pause:
1713 evt->u.srcstate.state = AsyncEvent::SrcState::Pause;
1714 break;
1715 /* Shouldn't happen. */
1716 case VChangeState::Restart:
1717 al::unreachable();
1720 ring->writeAdvance(1);
1723 void ProcessVoiceChanges(ContextBase *ctx)
1725 VoiceChange *cur{ctx->mCurrentVoiceChange.load(std::memory_order_acquire)};
1726 VoiceChange *next{cur->mNext.load(std::memory_order_acquire)};
1727 if(!next) return;
1729 const auto enabledevt = ctx->mEnabledEvts.load(std::memory_order_acquire);
1730 do {
1731 cur = next;
1733 bool sendevt{false};
1734 if(cur->mState == VChangeState::Reset || cur->mState == VChangeState::Stop)
1736 if(Voice *voice{cur->mVoice})
1738 voice->mCurrentBuffer.store(nullptr, std::memory_order_relaxed);
1739 voice->mLoopBuffer.store(nullptr, std::memory_order_relaxed);
1740 /* A source ID indicates the voice was playing or paused, which
1741 * gets a reset/stop event.
1743 sendevt = voice->mSourceID.exchange(0u, std::memory_order_relaxed) != 0u;
1744 Voice::State oldvstate{Voice::Playing};
1745 voice->mPlayState.compare_exchange_strong(oldvstate, Voice::Stopping,
1746 std::memory_order_relaxed, std::memory_order_acquire);
1747 voice->mPendingChange.store(false, std::memory_order_release);
1749 /* Reset state change events are always sent, even if the voice is
1750 * already stopped or even if there is no voice.
1752 sendevt |= (cur->mState == VChangeState::Reset);
1754 else if(cur->mState == VChangeState::Pause)
1756 Voice *voice{cur->mVoice};
1757 Voice::State oldvstate{Voice::Playing};
1758 sendevt = voice->mPlayState.compare_exchange_strong(oldvstate, Voice::Stopping,
1759 std::memory_order_release, std::memory_order_acquire);
1761 else if(cur->mState == VChangeState::Play)
1763 /* NOTE: When playing a voice, sending a source state change event
1764 * depends if there's an old voice to stop and if that stop is
1765 * successful. If there is no old voice, a playing event is always
1766 * sent. If there is an old voice, an event is sent only if the
1767 * voice is already stopped.
1769 if(Voice *oldvoice{cur->mOldVoice})
1771 oldvoice->mCurrentBuffer.store(nullptr, std::memory_order_relaxed);
1772 oldvoice->mLoopBuffer.store(nullptr, std::memory_order_relaxed);
1773 oldvoice->mSourceID.store(0u, std::memory_order_relaxed);
1774 Voice::State oldvstate{Voice::Playing};
1775 sendevt = !oldvoice->mPlayState.compare_exchange_strong(oldvstate, Voice::Stopping,
1776 std::memory_order_relaxed, std::memory_order_acquire);
1777 oldvoice->mPendingChange.store(false, std::memory_order_release);
1779 else
1780 sendevt = true;
1782 Voice *voice{cur->mVoice};
1783 voice->mPlayState.store(Voice::Playing, std::memory_order_release);
1785 else if(cur->mState == VChangeState::Restart)
1787 /* Restarting a voice never sends a source change event. */
1788 Voice *oldvoice{cur->mOldVoice};
1789 oldvoice->mCurrentBuffer.store(nullptr, std::memory_order_relaxed);
1790 oldvoice->mLoopBuffer.store(nullptr, std::memory_order_relaxed);
1791 /* If there's no sourceID, the old voice finished so don't start
1792 * the new one at its new offset.
1794 if(oldvoice->mSourceID.exchange(0u, std::memory_order_relaxed) != 0u)
1796 /* Otherwise, set the voice to stopping if it's not already (it
1797 * might already be, if paused), and play the new voice as
1798 * appropriate.
1800 Voice::State oldvstate{Voice::Playing};
1801 oldvoice->mPlayState.compare_exchange_strong(oldvstate, Voice::Stopping,
1802 std::memory_order_relaxed, std::memory_order_acquire);
1804 Voice *voice{cur->mVoice};
1805 voice->mPlayState.store((oldvstate == Voice::Playing) ? Voice::Playing
1806 : Voice::Stopped, std::memory_order_release);
1808 oldvoice->mPendingChange.store(false, std::memory_order_release);
1810 if(sendevt && enabledevt.test(AsyncEvent::SourceStateChange))
1811 SendSourceStateEvent(ctx, cur->mSourceID, cur->mState);
1813 next = cur->mNext.load(std::memory_order_acquire);
1814 } while(next);
1815 ctx->mCurrentVoiceChange.store(cur, std::memory_order_release);
1818 void ProcessParamUpdates(ContextBase *ctx, const EffectSlotArray &slots,
1819 const al::span<Voice*> voices)
1821 ProcessVoiceChanges(ctx);
1823 IncrementRef(ctx->mUpdateCount);
1824 if(!ctx->mHoldUpdates.load(std::memory_order_acquire)) [[likely]]
1826 bool force{CalcContextParams(ctx)};
1827 auto sorted_slots = const_cast<EffectSlot**>(slots.data() + slots.size());
1828 for(EffectSlot *slot : slots)
1829 force |= CalcEffectSlotParams(slot, sorted_slots, ctx);
1831 for(Voice *voice : voices)
1833 /* Only update voices that have a source. */
1834 if(voice->mSourceID.load(std::memory_order_relaxed) != 0)
1835 CalcSourceParams(voice, ctx, force);
1838 IncrementRef(ctx->mUpdateCount);
1841 void ProcessContexts(DeviceBase *device, const uint SamplesToDo)
1843 ASSUME(SamplesToDo > 0);
1845 const nanoseconds curtime{device->ClockBase +
1846 nanoseconds{seconds{device->SamplesDone}}/device->Frequency};
1848 for(ContextBase *ctx : *device->mContexts.load(std::memory_order_acquire))
1850 const EffectSlotArray &auxslots = *ctx->mActiveAuxSlots.load(std::memory_order_acquire);
1851 const al::span<Voice*> voices{ctx->getVoicesSpanAcquired()};
1853 /* Process pending propery updates for objects on the context. */
1854 ProcessParamUpdates(ctx, auxslots, voices);
1856 /* Clear auxiliary effect slot mixing buffers. */
1857 for(EffectSlot *slot : auxslots)
1859 for(auto &buffer : slot->Wet.Buffer)
1860 buffer.fill(0.0f);
1863 /* Process voices that have a playing source. */
1864 for(Voice *voice : voices)
1866 const Voice::State vstate{voice->mPlayState.load(std::memory_order_acquire)};
1867 if(vstate != Voice::Stopped && vstate != Voice::Pending)
1868 voice->mix(vstate, ctx, curtime, SamplesToDo);
1871 /* Process effects. */
1872 if(const size_t num_slots{auxslots.size()})
1874 auto slots = auxslots.data();
1875 auto slots_end = slots + num_slots;
1877 /* Sort the slots into extra storage, so that effect slots come
1878 * before their effect slot target (or their targets' target).
1880 const al::span<EffectSlot*> sorted_slots{const_cast<EffectSlot**>(slots_end),
1881 num_slots};
1882 /* Skip sorting if it has already been done. */
1883 if(!sorted_slots[0])
1885 /* First, copy the slots to the sorted list, then partition the
1886 * sorted list so that all slots without a target slot go to
1887 * the end.
1889 std::copy(slots, slots_end, sorted_slots.begin());
1890 auto split_point = std::partition(sorted_slots.begin(), sorted_slots.end(),
1891 [](const EffectSlot *slot) noexcept -> bool
1892 { return slot->Target != nullptr; });
1893 /* There must be at least one slot without a slot target. */
1894 assert(split_point != sorted_slots.end());
1896 /* Simple case: no more than 1 slot has a target slot. Either
1897 * all slots go right to the output, or the remaining one must
1898 * target an already-partitioned slot.
1900 if(split_point - sorted_slots.begin() > 1)
1902 /* At least two slots target other slots. Starting from the
1903 * back of the sorted list, continue partitioning the front
1904 * of the list given each target until all targets are
1905 * accounted for. This ensures all slots without a target
1906 * go last, all slots directly targeting those last slots
1907 * go second-to-last, all slots directly targeting those
1908 * second-last slots go third-to-last, etc.
1910 auto next_target = sorted_slots.end();
1911 do {
1912 /* This shouldn't happen, but if there's unsorted slots
1913 * left that don't target any sorted slots, they can't
1914 * contribute to the output, so leave them.
1916 if(next_target == split_point) [[unlikely]]
1917 break;
1919 --next_target;
1920 split_point = std::partition(sorted_slots.begin(), split_point,
1921 [next_target](const EffectSlot *slot) noexcept -> bool
1922 { return slot->Target != *next_target; });
1923 } while(split_point - sorted_slots.begin() > 1);
1927 for(const EffectSlot *slot : sorted_slots)
1929 EffectState *state{slot->mEffectState.get()};
1930 state->process(SamplesToDo, slot->Wet.Buffer, state->mOutTarget);
1934 /* Signal the event handler if there are any events to read. */
1935 RingBuffer *ring{ctx->mAsyncEvents.get()};
1936 if(ring->readSpace() > 0)
1937 ctx->mEventSem.post();
1942 void ApplyDistanceComp(const al::span<FloatBufferLine> Samples, const size_t SamplesToDo,
1943 const DistanceComp::ChanData *distcomp)
1945 ASSUME(SamplesToDo > 0);
1947 for(auto &chanbuffer : Samples)
1949 const float gain{distcomp->Gain};
1950 const size_t base{distcomp->Length};
1951 float *distbuf{al::assume_aligned<16>(distcomp->Buffer)};
1952 ++distcomp;
1954 if(base < 1)
1955 continue;
1957 float *inout{al::assume_aligned<16>(chanbuffer.data())};
1958 auto inout_end = inout + SamplesToDo;
1959 if(SamplesToDo >= base) [[likely]]
1961 auto delay_end = std::rotate(inout, inout_end - base, inout_end);
1962 std::swap_ranges(inout, delay_end, distbuf);
1964 else
1966 auto delay_start = std::swap_ranges(inout, inout_end, distbuf);
1967 std::rotate(distbuf, delay_start, distbuf + base);
1969 std::transform(inout, inout_end, inout, [gain](auto a){ return a * gain; });
1973 void ApplyDither(const al::span<FloatBufferLine> Samples, uint *dither_seed,
1974 const float quant_scale, const size_t SamplesToDo)
1976 ASSUME(SamplesToDo > 0);
1978 /* Dithering. Generate whitenoise (uniform distribution of random values
1979 * between -1 and +1) and add it to the sample values, after scaling up to
1980 * the desired quantization depth amd before rounding.
1982 const float invscale{1.0f / quant_scale};
1983 uint seed{*dither_seed};
1984 auto dither_sample = [&seed,invscale,quant_scale](const float sample) noexcept -> float
1986 float val{sample * quant_scale};
1987 uint rng0{dither_rng(&seed)};
1988 uint rng1{dither_rng(&seed)};
1989 val += static_cast<float>(rng0*(1.0/UINT_MAX) - rng1*(1.0/UINT_MAX));
1990 return fast_roundf(val) * invscale;
1992 for(FloatBufferLine &inout : Samples)
1993 std::transform(inout.begin(), inout.begin()+SamplesToDo, inout.begin(), dither_sample);
1994 *dither_seed = seed;
1998 /* Base template left undefined. Should be marked =delete, but Clang 3.8.1
1999 * chokes on that given the inline specializations.
2001 template<typename T>
2002 inline T SampleConv(float) noexcept;
2004 template<> inline float SampleConv(float val) noexcept
2005 { return val; }
2006 template<> inline int32_t SampleConv(float val) noexcept
2008 /* Floats have a 23-bit mantissa, plus an implied 1 bit and a sign bit.
2009 * This means a normalized float has at most 25 bits of signed precision.
2010 * When scaling and clamping for a signed 32-bit integer, these following
2011 * values are the best a float can give.
2013 return fastf2i(clampf(val*2147483648.0f, -2147483648.0f, 2147483520.0f));
2015 template<> inline int16_t SampleConv(float val) noexcept
2016 { return static_cast<int16_t>(fastf2i(clampf(val*32768.0f, -32768.0f, 32767.0f))); }
2017 template<> inline int8_t SampleConv(float val) noexcept
2018 { return static_cast<int8_t>(fastf2i(clampf(val*128.0f, -128.0f, 127.0f))); }
2020 /* Define unsigned output variations. */
2021 template<> inline uint32_t SampleConv(float val) noexcept
2022 { return static_cast<uint32_t>(SampleConv<int32_t>(val)) + 2147483648u; }
2023 template<> inline uint16_t SampleConv(float val) noexcept
2024 { return static_cast<uint16_t>(SampleConv<int16_t>(val) + 32768); }
2025 template<> inline uint8_t SampleConv(float val) noexcept
2026 { return static_cast<uint8_t>(SampleConv<int8_t>(val) + 128); }
2028 template<DevFmtType T>
2029 void Write(const al::span<const FloatBufferLine> InBuffer, void *OutBuffer, const size_t Offset,
2030 const size_t SamplesToDo, const size_t FrameStep)
2032 ASSUME(FrameStep > 0);
2033 ASSUME(SamplesToDo > 0);
2035 DevFmtType_t<T> *outbase{static_cast<DevFmtType_t<T>*>(OutBuffer) + Offset*FrameStep};
2036 size_t c{0};
2037 for(const FloatBufferLine &inbuf : InBuffer)
2039 DevFmtType_t<T> *out{outbase++};
2040 auto conv_sample = [FrameStep,&out](const float s) noexcept -> void
2042 *out = SampleConv<DevFmtType_t<T>>(s);
2043 out += FrameStep;
2045 std::for_each(inbuf.begin(), inbuf.begin()+SamplesToDo, conv_sample);
2046 ++c;
2048 if(const size_t extra{FrameStep - c})
2050 const auto silence = SampleConv<DevFmtType_t<T>>(0.0f);
2051 for(size_t i{0};i < SamplesToDo;++i)
2053 std::fill_n(outbase, extra, silence);
2054 outbase += FrameStep;
2059 } // namespace
2061 uint DeviceBase::renderSamples(const uint numSamples)
2063 const uint samplesToDo{minu(numSamples, BufferLineSize)};
2065 /* Clear main mixing buffers. */
2066 for(FloatBufferLine &buffer : MixBuffer)
2067 buffer.fill(0.0f);
2069 /* Increment the mix count at the start (lsb should now be 1). */
2070 IncrementRef(MixCount);
2072 /* Process and mix each context's sources and effects. */
2073 ProcessContexts(this, samplesToDo);
2075 /* Increment the clock time. Every second's worth of samples is converted
2076 * and added to clock base so that large sample counts don't overflow
2077 * during conversion. This also guarantees a stable conversion.
2079 SamplesDone += samplesToDo;
2080 ClockBase += std::chrono::seconds{SamplesDone / Frequency};
2081 SamplesDone %= Frequency;
2083 /* Increment the mix count at the end (lsb should now be 0). */
2084 IncrementRef(MixCount);
2086 /* Apply any needed post-process for finalizing the Dry mix to the RealOut
2087 * (Ambisonic decode, UHJ encode, etc).
2089 postProcess(samplesToDo);
2091 /* Apply compression, limiting sample amplitude if needed or desired. */
2092 if(Limiter) Limiter->process(samplesToDo, RealOut.Buffer.data());
2094 /* Apply delays and attenuation for mismatched speaker distances. */
2095 if(ChannelDelays)
2096 ApplyDistanceComp(RealOut.Buffer, samplesToDo, ChannelDelays->mChannels.data());
2098 /* Apply dithering. The compressor should have left enough headroom for the
2099 * dither noise to not saturate.
2101 if(DitherDepth > 0.0f)
2102 ApplyDither(RealOut.Buffer, &DitherSeed, DitherDepth, samplesToDo);
2104 return samplesToDo;
2107 void DeviceBase::renderSamples(const al::span<float*> outBuffers, const uint numSamples)
2109 FPUCtl mixer_mode{};
2110 uint total{0};
2111 while(const uint todo{numSamples - total})
2113 const uint samplesToDo{renderSamples(todo)};
2115 auto *srcbuf = RealOut.Buffer.data();
2116 for(auto *dstbuf : outBuffers)
2118 std::copy_n(srcbuf->data(), samplesToDo, dstbuf + total);
2119 ++srcbuf;
2122 total += samplesToDo;
2126 void DeviceBase::renderSamples(void *outBuffer, const uint numSamples, const size_t frameStep)
2128 FPUCtl mixer_mode{};
2129 uint total{0};
2130 while(const uint todo{numSamples - total})
2132 const uint samplesToDo{renderSamples(todo)};
2134 if(outBuffer) [[likely]]
2136 /* Finally, interleave and convert samples, writing to the device's
2137 * output buffer.
2139 switch(FmtType)
2141 #define HANDLE_WRITE(T) case T: \
2142 Write<T>(RealOut.Buffer, outBuffer, total, samplesToDo, frameStep); break;
2143 HANDLE_WRITE(DevFmtByte)
2144 HANDLE_WRITE(DevFmtUByte)
2145 HANDLE_WRITE(DevFmtShort)
2146 HANDLE_WRITE(DevFmtUShort)
2147 HANDLE_WRITE(DevFmtInt)
2148 HANDLE_WRITE(DevFmtUInt)
2149 HANDLE_WRITE(DevFmtFloat)
2150 #undef HANDLE_WRITE
2154 total += samplesToDo;
2158 void DeviceBase::handleDisconnect(const char *msg, ...)
2160 IncrementRef(MixCount);
2161 if(Connected.exchange(false, std::memory_order_acq_rel))
2163 AsyncEvent evt{AsyncEvent::Disconnected};
2165 va_list args;
2166 va_start(args, msg);
2167 int msglen{vsnprintf(evt.u.disconnect.msg, sizeof(evt.u.disconnect.msg), msg, args)};
2168 va_end(args);
2170 if(msglen < 0 || static_cast<size_t>(msglen) >= sizeof(evt.u.disconnect.msg))
2171 evt.u.disconnect.msg[sizeof(evt.u.disconnect.msg)-1] = 0;
2173 for(ContextBase *ctx : *mContexts.load())
2175 if(ctx->mEnabledEvts.load(std::memory_order_acquire).test(AsyncEvent::Disconnected))
2177 RingBuffer *ring{ctx->mAsyncEvents.get()};
2178 auto evt_data = ring->getWriteVector().first;
2179 if(evt_data.len > 0)
2181 al::construct_at(reinterpret_cast<AsyncEvent*>(evt_data.buf), evt);
2182 ring->writeAdvance(1);
2183 ctx->mEventSem.post();
2187 if(!ctx->mStopVoicesOnDisconnect)
2189 ProcessVoiceChanges(ctx);
2190 continue;
2193 auto voicelist = ctx->getVoicesSpanAcquired();
2194 auto stop_voice = [](Voice *voice) -> void
2196 voice->mCurrentBuffer.store(nullptr, std::memory_order_relaxed);
2197 voice->mLoopBuffer.store(nullptr, std::memory_order_relaxed);
2198 voice->mSourceID.store(0u, std::memory_order_relaxed);
2199 voice->mPlayState.store(Voice::Stopped, std::memory_order_release);
2201 std::for_each(voicelist.begin(), voicelist.end(), stop_voice);
2204 IncrementRef(MixCount);