Move some ambisonic-related macros to a separate header
[openal-soft.git] / Alc / alu.cpp
blob75504af54ef029cdf0f537c980b31a9deb0f8324
1 /**
2 * OpenAL cross platform audio library
3 * Copyright (C) 1999-2007 by authors.
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
14 * You should have received a copy of the GNU Library General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 * Or go to http://www.gnu.org/copyleft/lgpl.html
21 #include "config.h"
23 #include <math.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <ctype.h>
27 #include <assert.h>
29 #include <cmath>
30 #include <algorithm>
32 #include "alMain.h"
33 #include "alcontext.h"
34 #include "alSource.h"
35 #include "alBuffer.h"
36 #include "alListener.h"
37 #include "alAuxEffectSlot.h"
38 #include "alu.h"
39 #include "bs2b.h"
40 #include "hrtf.h"
41 #include "mastering.h"
42 #include "uhjfilter.h"
43 #include "bformatdec.h"
44 #include "ringbuffer.h"
45 #include "filters/splitter.h"
47 #include "mixer/defs.h"
48 #include "fpu_modes.h"
49 #include "cpu_caps.h"
50 #include "bsinc_inc.h"
53 namespace {
55 ALfloat InitConeScale()
57 ALfloat ret{1.0f};
58 const char *str{getenv("__ALSOFT_HALF_ANGLE_CONES")};
59 if(str && (strcasecmp(str, "true") == 0 || strtol(str, nullptr, 0) == 1))
60 ret *= 0.5f;
61 return ret;
64 ALfloat InitZScale()
66 ALfloat ret{1.0f};
67 const char *str{getenv("__ALSOFT_REVERSE_Z")};
68 if(str && (strcasecmp(str, "true") == 0 || strtol(str, nullptr, 0) == 1))
69 ret *= -1.0f;
70 return ret;
73 ALboolean InitReverbSOS()
75 ALboolean ret{AL_FALSE};
76 const char *str{getenv("__ALSOFT_REVERB_IGNORES_SOUND_SPEED")};
77 if(str && (strcasecmp(str, "true") == 0 || strtol(str, nullptr, 0) == 1))
78 ret = AL_TRUE;
79 return ret;
82 } // namespace
84 /* Cone scalar */
85 const ALfloat ConeScale{InitConeScale()};
87 /* Localized Z scalar for mono sources */
88 const ALfloat ZScale{InitZScale()};
90 /* Force default speed of sound for distance-related reverb decay. */
91 const ALboolean OverrideReverbSpeedOfSound{InitReverbSOS()};
94 namespace {
96 void ClearArray(ALfloat (&f)[MAX_OUTPUT_CHANNELS])
98 std::fill(std::begin(f), std::end(f), 0.0f);
101 struct ChanMap {
102 enum Channel channel;
103 ALfloat angle;
104 ALfloat elevation;
107 HrtfDirectMixerFunc MixDirectHrtf = MixDirectHrtf_C;
109 inline HrtfDirectMixerFunc SelectHrtfMixer(void)
111 #ifdef HAVE_NEON
112 if((CPUCapFlags&CPU_CAP_NEON))
113 return MixDirectHrtf_Neon;
114 #endif
115 #ifdef HAVE_SSE
116 if((CPUCapFlags&CPU_CAP_SSE))
117 return MixDirectHrtf_SSE;
118 #endif
120 return MixDirectHrtf_C;
124 void ProcessHrtf(ALCdevice *device, ALsizei SamplesToDo)
126 if(device->AmbiUp)
127 device->AmbiUp->process(device->Dry.Buffer, device->Dry.NumChannels,
128 device->FOAOut.Buffer, SamplesToDo
131 int lidx{GetChannelIdxByName(&device->RealOut, FrontLeft)};
132 int ridx{GetChannelIdxByName(&device->RealOut, FrontRight)};
133 assert(lidx != -1 && ridx != -1);
135 DirectHrtfState *state{device->mHrtfState.get()};
136 for(ALsizei c{0};c < device->Dry.NumChannels;c++)
138 MixDirectHrtf(device->RealOut.Buffer[lidx], device->RealOut.Buffer[ridx],
139 device->Dry.Buffer[c], state->Offset, state->IrSize,
140 state->Chan[c].Coeffs, state->Chan[c].Values, SamplesToDo
143 state->Offset += SamplesToDo;
146 void ProcessAmbiDec(ALCdevice *device, ALsizei SamplesToDo)
148 if(device->Dry.Buffer != device->FOAOut.Buffer)
149 device->AmbiDecoder->upSample(device->Dry.Buffer, device->FOAOut.Buffer,
150 device->FOAOut.NumChannels, SamplesToDo
152 device->AmbiDecoder->process(device->RealOut.Buffer, device->RealOut.NumChannels,
153 device->Dry.Buffer, SamplesToDo
157 void ProcessAmbiUp(ALCdevice *device, ALsizei SamplesToDo)
159 device->AmbiUp->process(device->RealOut.Buffer, device->RealOut.NumChannels,
160 device->FOAOut.Buffer, SamplesToDo
164 void ProcessUhj(ALCdevice *device, ALsizei SamplesToDo)
166 int lidx = GetChannelIdxByName(&device->RealOut, FrontLeft);
167 int ridx = GetChannelIdxByName(&device->RealOut, FrontRight);
168 assert(lidx != -1 && ridx != -1);
170 /* Encode to stereo-compatible 2-channel UHJ output. */
171 EncodeUhj2(device->Uhj_Encoder.get(),
172 device->RealOut.Buffer[lidx], device->RealOut.Buffer[ridx],
173 device->Dry.Buffer, SamplesToDo
177 void ProcessBs2b(ALCdevice *device, ALsizei SamplesToDo)
179 int lidx = GetChannelIdxByName(&device->RealOut, FrontLeft);
180 int ridx = GetChannelIdxByName(&device->RealOut, FrontRight);
181 assert(lidx != -1 && ridx != -1);
183 /* Apply binaural/crossfeed filter */
184 bs2b_cross_feed(device->Bs2b.get(), device->RealOut.Buffer[lidx],
185 device->RealOut.Buffer[ridx], SamplesToDo);
188 } // namespace
190 void aluInit(void)
192 MixDirectHrtf = SelectHrtfMixer();
196 void DeinitVoice(ALvoice *voice) noexcept
198 delete voice->Update.exchange(nullptr, std::memory_order_acq_rel);
199 voice->~ALvoice();
203 void aluSelectPostProcess(ALCdevice *device)
205 if(device->HrtfHandle)
206 device->PostProcess = ProcessHrtf;
207 else if(device->AmbiDecoder)
208 device->PostProcess = ProcessAmbiDec;
209 else if(device->AmbiUp)
210 device->PostProcess = ProcessAmbiUp;
211 else if(device->Uhj_Encoder)
212 device->PostProcess = ProcessUhj;
213 else if(device->Bs2b)
214 device->PostProcess = ProcessBs2b;
215 else
216 device->PostProcess = nullptr;
220 /* Prepares the interpolator for a given rate (determined by increment).
222 * With a bit of work, and a trade of memory for CPU cost, this could be
223 * modified for use with an interpolated increment for buttery-smooth pitch
224 * changes.
226 void BsincPrepare(const ALuint increment, BsincState *state, const BSincTable *table)
228 ALsizei si{BSINC_SCALE_COUNT - 1};
229 ALfloat sf{0.0f};
231 if(increment > FRACTIONONE)
233 sf = (ALfloat)FRACTIONONE / increment;
234 sf = maxf(0.0f, (BSINC_SCALE_COUNT-1) * (sf-table->scaleBase) * table->scaleRange);
235 si = float2int(sf);
236 /* The interpolation factor is fit to this diagonally-symmetric curve
237 * to reduce the transition ripple caused by interpolating different
238 * scales of the sinc function.
240 sf = 1.0f - std::cos(std::asin(sf - si));
243 state->sf = sf;
244 state->m = table->m[si];
245 state->l = (state->m/2) - 1;
246 state->filter = table->Tab + table->filterOffset[si];
250 namespace {
252 /* This RNG method was created based on the math found in opusdec. It's quick,
253 * and starting with a seed value of 22222, is suitable for generating
254 * whitenoise.
256 inline ALuint dither_rng(ALuint *seed) noexcept
258 *seed = (*seed * 96314165) + 907633515;
259 return *seed;
263 inline alu::Vector aluCrossproduct(const alu::Vector &in1, const alu::Vector &in2)
265 return alu::Vector{
266 in1[1]*in2[2] - in1[2]*in2[1],
267 in1[2]*in2[0] - in1[0]*in2[2],
268 in1[0]*in2[1] - in1[1]*in2[0],
269 0.0f
273 inline ALfloat aluDotproduct(const alu::Vector &vec1, const alu::Vector &vec2)
275 return vec1[0]*vec2[0] + vec1[1]*vec2[1] + vec1[2]*vec2[2];
279 alu::Vector operator*(const alu::Matrix &mtx, const alu::Vector &vec) noexcept
281 return alu::Vector{
282 vec[0]*mtx[0][0] + vec[1]*mtx[1][0] + vec[2]*mtx[2][0] + vec[3]*mtx[3][0],
283 vec[0]*mtx[0][1] + vec[1]*mtx[1][1] + vec[2]*mtx[2][1] + vec[3]*mtx[3][1],
284 vec[0]*mtx[0][2] + vec[1]*mtx[1][2] + vec[2]*mtx[2][2] + vec[3]*mtx[3][2],
285 vec[0]*mtx[0][3] + vec[1]*mtx[1][3] + vec[2]*mtx[2][3] + vec[3]*mtx[3][3]
290 void SendSourceStoppedEvent(ALCcontext *context, ALuint id)
292 ALbitfieldSOFT enabledevt{context->EnabledEvts.load(std::memory_order_acquire)};
293 if(!(enabledevt&EventType_SourceStateChange)) return;
295 AsyncEvent evt{EventType_SourceStateChange};
296 evt.u.srcstate.id = id;
297 evt.u.srcstate.state = AL_STOPPED;
299 if(ll_ringbuffer_write(context->AsyncEvents, &evt, 1) == 1)
300 context->EventSem.post();
304 bool CalcContextParams(ALCcontext *Context)
306 ALcontextProps *props{Context->Update.exchange(nullptr, std::memory_order_acq_rel)};
307 if(!props) return false;
309 ALlistener &Listener = Context->Listener;
310 Listener.Params.MetersPerUnit = props->MetersPerUnit;
312 Listener.Params.DopplerFactor = props->DopplerFactor;
313 Listener.Params.SpeedOfSound = props->SpeedOfSound * props->DopplerVelocity;
314 if(!OverrideReverbSpeedOfSound)
315 Listener.Params.ReverbSpeedOfSound = Listener.Params.SpeedOfSound *
316 Listener.Params.MetersPerUnit;
318 Listener.Params.SourceDistanceModel = props->SourceDistanceModel;
319 Listener.Params.mDistanceModel = props->mDistanceModel;
321 AtomicReplaceHead(Context->FreeContextProps, props);
322 return true;
325 bool CalcListenerParams(ALCcontext *Context)
327 ALlistener &Listener = Context->Listener;
329 ALlistenerProps *props{Listener.Update.exchange(nullptr, std::memory_order_acq_rel)};
330 if(!props) return false;
332 /* AT then UP */
333 alu::Vector N{props->Forward[0], props->Forward[1], props->Forward[2], 0.0f};
334 N.normalize();
335 alu::Vector V{props->Up[0], props->Up[1], props->Up[2], 0.0f};
336 V.normalize();
337 /* Build and normalize right-vector */
338 alu::Vector U{aluCrossproduct(N, V)};
339 U.normalize();
341 Listener.Params.Matrix = alu::Matrix{
342 U[0], V[0], -N[0], 0.0f,
343 U[1], V[1], -N[1], 0.0f,
344 U[2], V[2], -N[2], 0.0f,
345 0.0f, 0.0f, 0.0f, 1.0f
348 alu::Vector P{props->Position[0], props->Position[1], props->Position[2], 1.0f};
349 P = Listener.Params.Matrix * P;
350 Listener.Params.Matrix.setRow(3, -P[0], -P[1], -P[2], 1.0f);
352 alu::Vector vel{props->Velocity[0], props->Velocity[1], props->Velocity[2], 0.0f};
353 Listener.Params.Velocity = Listener.Params.Matrix * vel;
355 Listener.Params.Gain = props->Gain * Context->GainBoost;
357 AtomicReplaceHead(Context->FreeListenerProps, props);
358 return true;
361 bool CalcEffectSlotParams(ALeffectslot *slot, ALCcontext *context, bool force)
363 ALeffectslotProps *props{slot->Update.exchange(nullptr, std::memory_order_acq_rel)};
364 if(!props && !force) return false;
366 EffectState *state;
367 if(!props)
368 state = slot->Params.mEffectState;
369 else
371 slot->Params.Gain = props->Gain;
372 slot->Params.AuxSendAuto = props->AuxSendAuto;
373 slot->Params.EffectType = props->Type;
374 slot->Params.EffectProps = props->Props;
375 if(IsReverbEffect(props->Type))
377 slot->Params.RoomRolloff = props->Props.Reverb.RoomRolloffFactor;
378 slot->Params.DecayTime = props->Props.Reverb.DecayTime;
379 slot->Params.DecayLFRatio = props->Props.Reverb.DecayLFRatio;
380 slot->Params.DecayHFRatio = props->Props.Reverb.DecayHFRatio;
381 slot->Params.DecayHFLimit = props->Props.Reverb.DecayHFLimit;
382 slot->Params.AirAbsorptionGainHF = props->Props.Reverb.AirAbsorptionGainHF;
384 else
386 slot->Params.RoomRolloff = 0.0f;
387 slot->Params.DecayTime = 0.0f;
388 slot->Params.DecayLFRatio = 0.0f;
389 slot->Params.DecayHFRatio = 0.0f;
390 slot->Params.DecayHFLimit = AL_FALSE;
391 slot->Params.AirAbsorptionGainHF = 1.0f;
394 state = props->State;
396 if(state == slot->Params.mEffectState)
398 /* If the effect state is the same as current, we can decrement its
399 * count safely to remove it from the update object (it can't reach
400 * 0 refs since the current params also hold a reference).
402 DecrementRef(&state->mRef);
403 props->State = nullptr;
405 else
407 /* Otherwise, replace it and send off the old one with a release
408 * event.
410 AsyncEvent evt{EventType_ReleaseEffectState};
411 evt.u.mEffectState = slot->Params.mEffectState;
413 slot->Params.mEffectState = state;
414 props->State = NULL;
416 if(LIKELY(ll_ringbuffer_write(context->AsyncEvents, &evt, 1) != 0))
417 context->EventSem.post();
418 else
420 /* If writing the event failed, the queue was probably full.
421 * Store the old state in the property object where it can
422 * eventually be cleaned up sometime later (not ideal, but
423 * better than blocking or leaking).
425 props->State = evt.u.mEffectState;
429 AtomicReplaceHead(context->FreeEffectslotProps, props);
432 state->update(context, slot, &slot->Params.EffectProps);
433 return true;
437 constexpr struct ChanMap MonoMap[1]{
438 { FrontCenter, 0.0f, 0.0f }
439 }, RearMap[2]{
440 { BackLeft, DEG2RAD(-150.0f), DEG2RAD(0.0f) },
441 { BackRight, DEG2RAD( 150.0f), DEG2RAD(0.0f) }
442 }, QuadMap[4]{
443 { FrontLeft, DEG2RAD( -45.0f), DEG2RAD(0.0f) },
444 { FrontRight, DEG2RAD( 45.0f), DEG2RAD(0.0f) },
445 { BackLeft, DEG2RAD(-135.0f), DEG2RAD(0.0f) },
446 { BackRight, DEG2RAD( 135.0f), DEG2RAD(0.0f) }
447 }, X51Map[6]{
448 { FrontLeft, DEG2RAD( -30.0f), DEG2RAD(0.0f) },
449 { FrontRight, DEG2RAD( 30.0f), DEG2RAD(0.0f) },
450 { FrontCenter, DEG2RAD( 0.0f), DEG2RAD(0.0f) },
451 { LFE, 0.0f, 0.0f },
452 { SideLeft, DEG2RAD(-110.0f), DEG2RAD(0.0f) },
453 { SideRight, DEG2RAD( 110.0f), DEG2RAD(0.0f) }
454 }, X61Map[7]{
455 { FrontLeft, DEG2RAD(-30.0f), DEG2RAD(0.0f) },
456 { FrontRight, DEG2RAD( 30.0f), DEG2RAD(0.0f) },
457 { FrontCenter, DEG2RAD( 0.0f), DEG2RAD(0.0f) },
458 { LFE, 0.0f, 0.0f },
459 { BackCenter, DEG2RAD(180.0f), DEG2RAD(0.0f) },
460 { SideLeft, DEG2RAD(-90.0f), DEG2RAD(0.0f) },
461 { SideRight, DEG2RAD( 90.0f), DEG2RAD(0.0f) }
462 }, X71Map[8]{
463 { FrontLeft, DEG2RAD( -30.0f), DEG2RAD(0.0f) },
464 { FrontRight, DEG2RAD( 30.0f), DEG2RAD(0.0f) },
465 { FrontCenter, DEG2RAD( 0.0f), DEG2RAD(0.0f) },
466 { LFE, 0.0f, 0.0f },
467 { BackLeft, DEG2RAD(-150.0f), DEG2RAD(0.0f) },
468 { BackRight, DEG2RAD( 150.0f), DEG2RAD(0.0f) },
469 { SideLeft, DEG2RAD( -90.0f), DEG2RAD(0.0f) },
470 { SideRight, DEG2RAD( 90.0f), DEG2RAD(0.0f) }
473 void CalcPanningAndFilters(ALvoice *voice, const ALfloat Azi, const ALfloat Elev,
474 const ALfloat Distance, const ALfloat Spread,
475 const ALfloat DryGain, const ALfloat DryGainHF,
476 const ALfloat DryGainLF, const ALfloat *WetGain,
477 const ALfloat *WetGainLF, const ALfloat *WetGainHF,
478 ALeffectslot **SendSlots, const ALbuffer *Buffer,
479 const ALvoicePropsBase *props, const ALlistener &Listener,
480 const ALCdevice *Device)
482 ChanMap StereoMap[2]{
483 { FrontLeft, DEG2RAD(-30.0f), DEG2RAD(0.0f) },
484 { FrontRight, DEG2RAD( 30.0f), DEG2RAD(0.0f) }
487 bool DirectChannels{props->DirectChannels != AL_FALSE};
488 const ChanMap *chans{nullptr};
489 ALsizei num_channels{0};
490 bool isbformat{false};
491 ALfloat downmix_gain{1.0f};
492 switch(Buffer->FmtChannels)
494 case FmtMono:
495 chans = MonoMap;
496 num_channels = 1;
497 /* Mono buffers are never played direct. */
498 DirectChannels = false;
499 break;
501 case FmtStereo:
502 /* Convert counter-clockwise to clockwise. */
503 StereoMap[0].angle = -props->StereoPan[0];
504 StereoMap[1].angle = -props->StereoPan[1];
506 chans = StereoMap;
507 num_channels = 2;
508 downmix_gain = 1.0f / 2.0f;
509 break;
511 case FmtRear:
512 chans = RearMap;
513 num_channels = 2;
514 downmix_gain = 1.0f / 2.0f;
515 break;
517 case FmtQuad:
518 chans = QuadMap;
519 num_channels = 4;
520 downmix_gain = 1.0f / 4.0f;
521 break;
523 case FmtX51:
524 chans = X51Map;
525 num_channels = 6;
526 /* NOTE: Excludes LFE. */
527 downmix_gain = 1.0f / 5.0f;
528 break;
530 case FmtX61:
531 chans = X61Map;
532 num_channels = 7;
533 /* NOTE: Excludes LFE. */
534 downmix_gain = 1.0f / 6.0f;
535 break;
537 case FmtX71:
538 chans = X71Map;
539 num_channels = 8;
540 /* NOTE: Excludes LFE. */
541 downmix_gain = 1.0f / 7.0f;
542 break;
544 case FmtBFormat2D:
545 num_channels = 3;
546 isbformat = true;
547 DirectChannels = false;
548 break;
550 case FmtBFormat3D:
551 num_channels = 4;
552 isbformat = true;
553 DirectChannels = false;
554 break;
557 std::for_each(std::begin(voice->Direct.Params), std::begin(voice->Direct.Params)+num_channels,
558 [](DirectParams &params) -> void
560 params.Hrtf.Target = HrtfParams{};
561 ClearArray(params.Gains.Target);
564 const ALsizei NumSends{Device->NumAuxSends};
565 std::for_each(voice->Send+0, voice->Send+NumSends,
566 [num_channels](ALvoice::SendData &send) -> void
568 std::for_each(std::begin(send.Params), std::begin(send.Params)+num_channels,
569 [](SendParams &params) -> void { ClearArray(params.Gains.Target); }
574 voice->Flags &= ~(VOICE_HAS_HRTF | VOICE_HAS_NFC);
575 if(isbformat)
577 /* Special handling for B-Format sources. */
579 if(Distance > FLT_EPSILON)
581 /* Panning a B-Format sound toward some direction is easy. Just pan
582 * the first (W) channel as a normal mono sound and silence the
583 * others.
586 if(Device->AvgSpeakerDist > 0.0f)
588 const ALfloat mdist{Distance * Listener.Params.MetersPerUnit};
589 const ALfloat w1{SPEEDOFSOUNDMETRESPERSEC /
590 (Device->AvgSpeakerDist * (ALfloat)Device->Frequency)};
591 ALfloat w0{SPEEDOFSOUNDMETRESPERSEC /
592 (mdist * (ALfloat)Device->Frequency)};
593 /* Clamp w0 for really close distances, to prevent excessive
594 * bass.
596 w0 = minf(w0, w1*4.0f);
598 /* Only need to adjust the first channel of a B-Format source. */
599 voice->Direct.Params[0].NFCtrlFilter.adjust(w0);
601 std::copy(std::begin(Device->NumChannelsPerOrder),
602 std::end(Device->NumChannelsPerOrder),
603 std::begin(voice->Direct.ChannelsPerOrder));
604 voice->Flags |= VOICE_HAS_NFC;
607 /* A scalar of 1.5 for plain stereo results in +/-60 degrees being
608 * moved to +/-90 degrees for direct right and left speaker
609 * responses.
611 ALfloat coeffs[MAX_AMBI_COEFFS];
612 CalcAngleCoeffs((Device->Render_Mode==StereoPair) ? ScaleAzimuthFront(Azi, 1.5f) : Azi,
613 Elev, Spread, coeffs);
615 /* NOTE: W needs to be scaled by sqrt(2) due to FuMa normalization. */
616 ComputePanGains(&Device->Dry, coeffs, DryGain*SQRTF_2,
617 voice->Direct.Params[0].Gains.Target);
618 for(ALsizei i{0};i < NumSends;i++)
620 if(const ALeffectslot *Slot{SendSlots[i]})
621 ComputePanningGainsBF(Slot->ChanMap, Slot->NumChannels, coeffs,
622 WetGain[i]*SQRTF_2, voice->Send[i].Params[0].Gains.Target
626 else
628 if(Device->AvgSpeakerDist > 0.0f)
630 /* NOTE: The NFCtrlFilters were created with a w0 of 0, which
631 * is what we want for FOA input. The first channel may have
632 * been previously re-adjusted if panned, so reset it.
634 voice->Direct.Params[0].NFCtrlFilter.adjust(0.0f);
636 voice->Direct.ChannelsPerOrder[0] = 1;
637 voice->Direct.ChannelsPerOrder[1] = mini(voice->Direct.Channels-1, 3);
638 std::fill(std::begin(voice->Direct.ChannelsPerOrder)+2,
639 std::end(voice->Direct.ChannelsPerOrder), 0);
640 voice->Flags |= VOICE_HAS_NFC;
643 /* Local B-Format sources have their XYZ channels rotated according
644 * to the orientation.
646 /* AT then UP */
647 alu::Vector N{props->Orientation[0][0], props->Orientation[0][1],
648 props->Orientation[0][2], 0.0f};
649 N.normalize();
650 alu::Vector V{props->Orientation[1][0], props->Orientation[1][1],
651 props->Orientation[1][2], 0.0f};
652 V.normalize();
653 if(!props->HeadRelative)
655 N = Listener.Params.Matrix * N;
656 V = Listener.Params.Matrix * V;
658 /* Build and normalize right-vector */
659 alu::Vector U{aluCrossproduct(N, V)};
660 U.normalize();
662 /* Build a rotate + conversion matrix (FuMa -> ACN+N3D). NOTE: This
663 * matrix is transposed, for the inputs to align on the rows and
664 * outputs on the columns.
666 const alu::Matrix matrix{
667 // ACN0 ACN1 ACN2 ACN3
668 SQRTF_2, 0.0f, 0.0f, 0.0f, // Ambi W
669 0.0f, -N[0]*SQRTF_3, N[1]*SQRTF_3, -N[2]*SQRTF_3, // Ambi X
670 0.0f, U[0]*SQRTF_3, -U[1]*SQRTF_3, U[2]*SQRTF_3, // Ambi Y
671 0.0f, -V[0]*SQRTF_3, V[1]*SQRTF_3, -V[2]*SQRTF_3 // Ambi Z
674 voice->Direct.Buffer = Device->FOAOut.Buffer;
675 voice->Direct.Channels = Device->FOAOut.NumChannels;
676 for(ALsizei c{0};c < num_channels;c++)
677 ComputePanGains(&Device->FOAOut, matrix[c].data(), DryGain,
678 voice->Direct.Params[c].Gains.Target);
679 for(ALsizei i{0};i < NumSends;i++)
681 if(const ALeffectslot *Slot{SendSlots[i]})
682 for(ALsizei c{0};c < num_channels;c++)
683 ComputePanningGainsBF(Slot->ChanMap, Slot->NumChannels, matrix[c].data(),
684 WetGain[i], voice->Send[i].Params[c].Gains.Target
689 else if(DirectChannels)
691 /* Direct source channels always play local. Skip the virtual channels
692 * and write inputs to the matching real outputs.
694 voice->Direct.Buffer = Device->RealOut.Buffer;
695 voice->Direct.Channels = Device->RealOut.NumChannels;
697 for(ALsizei c{0};c < num_channels;c++)
699 int idx{GetChannelIdxByName(&Device->RealOut, chans[c].channel)};
700 if(idx != -1) voice->Direct.Params[c].Gains.Target[idx] = DryGain;
703 /* Auxiliary sends still use normal channel panning since they mix to
704 * B-Format, which can't channel-match.
706 for(ALsizei c{0};c < num_channels;c++)
708 ALfloat coeffs[MAX_AMBI_COEFFS];
709 CalcAngleCoeffs(chans[c].angle, chans[c].elevation, 0.0f, coeffs);
711 for(ALsizei i{0};i < NumSends;i++)
713 if(const ALeffectslot *Slot{SendSlots[i]})
714 ComputePanningGainsBF(Slot->ChanMap, Slot->NumChannels, coeffs,
715 WetGain[i], voice->Send[i].Params[c].Gains.Target
720 else if(Device->Render_Mode == HrtfRender)
722 /* Full HRTF rendering. Skip the virtual channels and render to the
723 * real outputs.
725 voice->Direct.Buffer = Device->RealOut.Buffer;
726 voice->Direct.Channels = Device->RealOut.NumChannels;
728 if(Distance > FLT_EPSILON)
730 /* Get the HRIR coefficients and delays just once, for the given
731 * source direction.
733 GetHrtfCoeffs(Device->HrtfHandle, Elev, Azi, Spread,
734 voice->Direct.Params[0].Hrtf.Target.Coeffs,
735 voice->Direct.Params[0].Hrtf.Target.Delay);
736 voice->Direct.Params[0].Hrtf.Target.Gain = DryGain * downmix_gain;
738 /* Remaining channels use the same results as the first. */
739 for(ALsizei c{1};c < num_channels;c++)
741 /* Skip LFE */
742 if(chans[c].channel != LFE)
743 voice->Direct.Params[c].Hrtf.Target = voice->Direct.Params[0].Hrtf.Target;
746 /* Calculate the directional coefficients once, which apply to all
747 * input channels of the source sends.
749 ALfloat coeffs[MAX_AMBI_COEFFS];
750 CalcAngleCoeffs(Azi, Elev, Spread, coeffs);
752 for(ALsizei i{0};i < NumSends;i++)
754 if(const ALeffectslot *Slot{SendSlots[i]})
755 for(ALsizei c{0};c < num_channels;c++)
757 /* Skip LFE */
758 if(chans[c].channel != LFE)
759 ComputePanningGainsBF(Slot->ChanMap, Slot->NumChannels, coeffs,
760 WetGain[i]*downmix_gain, voice->Send[i].Params[c].Gains.Target
765 else
767 /* Local sources on HRTF play with each channel panned to its
768 * relative location around the listener, providing "virtual
769 * speaker" responses.
771 for(ALsizei c{0};c < num_channels;c++)
773 /* Skip LFE */
774 if(chans[c].channel == LFE)
775 continue;
777 /* Get the HRIR coefficients and delays for this channel
778 * position.
780 GetHrtfCoeffs(Device->HrtfHandle,
781 chans[c].elevation, chans[c].angle, Spread,
782 voice->Direct.Params[c].Hrtf.Target.Coeffs,
783 voice->Direct.Params[c].Hrtf.Target.Delay
785 voice->Direct.Params[c].Hrtf.Target.Gain = DryGain;
787 /* Normal panning for auxiliary sends. */
788 ALfloat coeffs[MAX_AMBI_COEFFS];
789 CalcAngleCoeffs(chans[c].angle, chans[c].elevation, Spread, coeffs);
791 for(ALsizei i{0};i < NumSends;i++)
793 if(const ALeffectslot *Slot{SendSlots[i]})
794 ComputePanningGainsBF(Slot->ChanMap, Slot->NumChannels, coeffs,
795 WetGain[i], voice->Send[i].Params[c].Gains.Target
801 voice->Flags |= VOICE_HAS_HRTF;
803 else
805 /* Non-HRTF rendering. Use normal panning to the output. */
807 if(Distance > FLT_EPSILON)
809 /* Calculate NFC filter coefficient if needed. */
810 if(Device->AvgSpeakerDist > 0.0f)
812 const ALfloat mdist{Distance * Listener.Params.MetersPerUnit};
813 const ALfloat w1{SPEEDOFSOUNDMETRESPERSEC /
814 (Device->AvgSpeakerDist * (ALfloat)Device->Frequency)};
815 ALfloat w0{SPEEDOFSOUNDMETRESPERSEC /
816 (mdist * (ALfloat)Device->Frequency)};
817 /* Clamp w0 for really close distances, to prevent excessive
818 * bass.
820 w0 = minf(w0, w1*4.0f);
822 /* Adjust NFC filters. */
823 for(ALsizei c{0};c < num_channels;c++)
824 voice->Direct.Params[c].NFCtrlFilter.adjust(w0);
826 std::copy(std::begin(Device->NumChannelsPerOrder),
827 std::end(Device->NumChannelsPerOrder),
828 std::begin(voice->Direct.ChannelsPerOrder));
829 voice->Flags |= VOICE_HAS_NFC;
832 /* Calculate the directional coefficients once, which apply to all
833 * input channels.
835 ALfloat coeffs[MAX_AMBI_COEFFS];
836 CalcAngleCoeffs((Device->Render_Mode==StereoPair) ? ScaleAzimuthFront(Azi, 1.5f) : Azi,
837 Elev, Spread, coeffs);
839 for(ALsizei c{0};c < num_channels;c++)
841 /* Special-case LFE */
842 if(chans[c].channel == LFE)
844 if(Device->Dry.Buffer == Device->RealOut.Buffer)
846 int idx = GetChannelIdxByName(&Device->RealOut, chans[c].channel);
847 if(idx != -1) voice->Direct.Params[c].Gains.Target[idx] = DryGain;
849 continue;
852 ComputePanGains(&Device->Dry, coeffs, DryGain * downmix_gain,
853 voice->Direct.Params[c].Gains.Target);
856 for(ALsizei i{0};i < NumSends;i++)
858 if(const ALeffectslot *Slot{SendSlots[i]})
859 for(ALsizei c{0};c < num_channels;c++)
861 /* Skip LFE */
862 if(chans[c].channel != LFE)
863 ComputePanningGainsBF(Slot->ChanMap, Slot->NumChannels, coeffs,
864 WetGain[i]*downmix_gain, voice->Send[i].Params[c].Gains.Target
869 else
871 if(Device->AvgSpeakerDist > 0.0f)
873 /* If the source distance is 0, set w0 to w1 to act as a pass-
874 * through. We still want to pass the signal through the
875 * filters so they keep an appropriate history, in case the
876 * source moves away from the listener.
878 const ALfloat w0{SPEEDOFSOUNDMETRESPERSEC /
879 (Device->AvgSpeakerDist * (ALfloat)Device->Frequency)};
881 for(ALsizei c{0};c < num_channels;c++)
882 voice->Direct.Params[c].NFCtrlFilter.adjust(w0);
884 std::copy(std::begin(Device->NumChannelsPerOrder),
885 std::end(Device->NumChannelsPerOrder),
886 std::begin(voice->Direct.ChannelsPerOrder));
887 voice->Flags |= VOICE_HAS_NFC;
890 for(ALsizei c{0};c < num_channels;c++)
892 /* Special-case LFE */
893 if(chans[c].channel == LFE)
895 if(Device->Dry.Buffer == Device->RealOut.Buffer)
897 int idx = GetChannelIdxByName(&Device->RealOut, chans[c].channel);
898 if(idx != -1) voice->Direct.Params[c].Gains.Target[idx] = DryGain;
900 continue;
903 ALfloat coeffs[MAX_AMBI_COEFFS];
904 CalcAngleCoeffs(
905 (Device->Render_Mode==StereoPair) ? ScaleAzimuthFront(chans[c].angle, 3.0f)
906 : chans[c].angle,
907 chans[c].elevation, Spread, coeffs
910 ComputePanGains(&Device->Dry, coeffs, DryGain,
911 voice->Direct.Params[c].Gains.Target);
912 for(ALsizei i{0};i < NumSends;i++)
914 if(const ALeffectslot *Slot{SendSlots[i]})
915 ComputePanningGainsBF(Slot->ChanMap, Slot->NumChannels,
916 coeffs, WetGain[i], voice->Send[i].Params[c].Gains.Target
923 const auto Frequency = static_cast<ALfloat>(Device->Frequency);
925 const ALfloat hfScale{props->Direct.HFReference / Frequency};
926 const ALfloat lfScale{props->Direct.LFReference / Frequency};
927 const ALfloat gainHF{maxf(DryGainHF, 0.001f)}; /* Limit -60dB */
928 const ALfloat gainLF{maxf(DryGainLF, 0.001f)};
930 voice->Direct.FilterType = AF_None;
931 if(gainHF != 1.0f) voice->Direct.FilterType |= AF_LowPass;
932 if(gainLF != 1.0f) voice->Direct.FilterType |= AF_HighPass;
933 voice->Direct.Params[0].LowPass.setParams(BiquadType::HighShelf,
934 gainHF, hfScale, calc_rcpQ_from_slope(gainHF, 1.0f)
936 voice->Direct.Params[0].HighPass.setParams(BiquadType::LowShelf,
937 gainLF, lfScale, calc_rcpQ_from_slope(gainLF, 1.0f)
939 for(ALsizei c{1};c < num_channels;c++)
941 voice->Direct.Params[c].LowPass.copyParamsFrom(voice->Direct.Params[0].LowPass);
942 voice->Direct.Params[c].HighPass.copyParamsFrom(voice->Direct.Params[0].HighPass);
945 for(ALsizei i{0};i < NumSends;i++)
947 const ALfloat hfScale{props->Send[i].HFReference / Frequency};
948 const ALfloat lfScale{props->Send[i].LFReference / Frequency};
949 const ALfloat gainHF{maxf(WetGainHF[i], 0.001f)};
950 const ALfloat gainLF{maxf(WetGainLF[i], 0.001f)};
952 voice->Send[i].FilterType = AF_None;
953 if(gainHF != 1.0f) voice->Send[i].FilterType |= AF_LowPass;
954 if(gainLF != 1.0f) voice->Send[i].FilterType |= AF_HighPass;
955 voice->Send[i].Params[0].LowPass.setParams(BiquadType::HighShelf,
956 gainHF, hfScale, calc_rcpQ_from_slope(gainHF, 1.0f)
958 voice->Send[i].Params[0].HighPass.setParams(BiquadType::LowShelf,
959 gainLF, lfScale, calc_rcpQ_from_slope(gainLF, 1.0f)
961 for(ALsizei c{1};c < num_channels;c++)
963 voice->Send[i].Params[c].LowPass.copyParamsFrom(voice->Send[i].Params[0].LowPass);
964 voice->Send[i].Params[c].HighPass.copyParamsFrom(voice->Send[i].Params[0].HighPass);
969 void CalcNonAttnSourceParams(ALvoice *voice, const ALvoicePropsBase *props, const ALbuffer *ALBuffer, const ALCcontext *ALContext)
971 const ALCdevice *Device{ALContext->Device};
972 ALeffectslot *SendSlots[MAX_SENDS];
974 voice->Direct.Buffer = Device->Dry.Buffer;
975 voice->Direct.Channels = Device->Dry.NumChannels;
976 for(ALsizei i{0};i < Device->NumAuxSends;i++)
978 SendSlots[i] = props->Send[i].Slot;
979 if(!SendSlots[i] && i == 0)
980 SendSlots[i] = ALContext->DefaultSlot.get();
981 if(!SendSlots[i] || SendSlots[i]->Params.EffectType == AL_EFFECT_NULL)
983 SendSlots[i] = NULL;
984 voice->Send[i].Buffer = NULL;
985 voice->Send[i].Channels = 0;
987 else
989 voice->Send[i].Buffer = SendSlots[i]->WetBuffer;
990 voice->Send[i].Channels = SendSlots[i]->NumChannels;
994 /* Calculate the stepping value */
995 const auto Pitch = static_cast<ALfloat>(ALBuffer->Frequency) /
996 static_cast<ALfloat>(Device->Frequency) * props->Pitch;
997 if(Pitch > (ALfloat)MAX_PITCH)
998 voice->Step = MAX_PITCH<<FRACTIONBITS;
999 else
1000 voice->Step = maxi(fastf2i(Pitch * FRACTIONONE), 1);
1001 if(props->Resampler == BSinc24Resampler)
1002 BsincPrepare(voice->Step, &voice->ResampleState.bsinc, &bsinc24);
1003 else if(props->Resampler == BSinc12Resampler)
1004 BsincPrepare(voice->Step, &voice->ResampleState.bsinc, &bsinc12);
1005 voice->Resampler = SelectResampler(props->Resampler);
1007 /* Calculate gains */
1008 const ALlistener &Listener = ALContext->Listener;
1009 ALfloat DryGain{clampf(props->Gain, props->MinGain, props->MaxGain)};
1010 DryGain *= props->Direct.Gain * Listener.Params.Gain;
1011 DryGain = minf(DryGain, GAIN_MIX_MAX);
1012 ALfloat DryGainHF{props->Direct.GainHF};
1013 ALfloat DryGainLF{props->Direct.GainLF};
1014 ALfloat WetGain[MAX_SENDS], WetGainHF[MAX_SENDS], WetGainLF[MAX_SENDS];
1015 for(ALsizei i{0};i < Device->NumAuxSends;i++)
1017 WetGain[i] = clampf(props->Gain, props->MinGain, props->MaxGain);
1018 WetGain[i] *= props->Send[i].Gain * Listener.Params.Gain;
1019 WetGain[i] = minf(WetGain[i], GAIN_MIX_MAX);
1020 WetGainHF[i] = props->Send[i].GainHF;
1021 WetGainLF[i] = props->Send[i].GainLF;
1024 CalcPanningAndFilters(voice, 0.0f, 0.0f, 0.0f, 0.0f, DryGain, DryGainHF, DryGainLF, WetGain,
1025 WetGainLF, WetGainHF, SendSlots, ALBuffer, props, Listener, Device);
1028 void CalcAttnSourceParams(ALvoice *voice, const ALvoicePropsBase *props, const ALbuffer *ALBuffer, const ALCcontext *ALContext)
1030 const ALCdevice *Device{ALContext->Device};
1031 const ALsizei NumSends{Device->NumAuxSends};
1032 const ALlistener &Listener = ALContext->Listener;
1034 /* Set mixing buffers and get send parameters. */
1035 voice->Direct.Buffer = Device->Dry.Buffer;
1036 voice->Direct.Channels = Device->Dry.NumChannels;
1037 ALeffectslot *SendSlots[MAX_SENDS];
1038 ALfloat RoomRolloff[MAX_SENDS];
1039 ALfloat DecayDistance[MAX_SENDS];
1040 ALfloat DecayLFDistance[MAX_SENDS];
1041 ALfloat DecayHFDistance[MAX_SENDS];
1042 for(ALsizei i{0};i < NumSends;i++)
1044 SendSlots[i] = props->Send[i].Slot;
1045 if(!SendSlots[i] && i == 0)
1046 SendSlots[i] = ALContext->DefaultSlot.get();
1047 if(!SendSlots[i] || SendSlots[i]->Params.EffectType == AL_EFFECT_NULL)
1049 SendSlots[i] = nullptr;
1050 RoomRolloff[i] = 0.0f;
1051 DecayDistance[i] = 0.0f;
1052 DecayLFDistance[i] = 0.0f;
1053 DecayHFDistance[i] = 0.0f;
1055 else if(SendSlots[i]->Params.AuxSendAuto)
1057 RoomRolloff[i] = SendSlots[i]->Params.RoomRolloff + props->RoomRolloffFactor;
1058 /* Calculate the distances to where this effect's decay reaches
1059 * -60dB.
1061 DecayDistance[i] = SendSlots[i]->Params.DecayTime *
1062 Listener.Params.ReverbSpeedOfSound;
1063 DecayLFDistance[i] = DecayDistance[i] * SendSlots[i]->Params.DecayLFRatio;
1064 DecayHFDistance[i] = DecayDistance[i] * SendSlots[i]->Params.DecayHFRatio;
1065 if(SendSlots[i]->Params.DecayHFLimit)
1067 ALfloat airAbsorption = SendSlots[i]->Params.AirAbsorptionGainHF;
1068 if(airAbsorption < 1.0f)
1070 /* Calculate the distance to where this effect's air
1071 * absorption reaches -60dB, and limit the effect's HF
1072 * decay distance (so it doesn't take any longer to decay
1073 * than the air would allow).
1075 ALfloat absorb_dist = log10f(REVERB_DECAY_GAIN) / log10f(airAbsorption);
1076 DecayHFDistance[i] = minf(absorb_dist, DecayHFDistance[i]);
1080 else
1082 /* If the slot's auxiliary send auto is off, the data sent to the
1083 * effect slot is the same as the dry path, sans filter effects */
1084 RoomRolloff[i] = props->RolloffFactor;
1085 DecayDistance[i] = 0.0f;
1086 DecayLFDistance[i] = 0.0f;
1087 DecayHFDistance[i] = 0.0f;
1090 if(!SendSlots[i])
1092 voice->Send[i].Buffer = nullptr;
1093 voice->Send[i].Channels = 0;
1095 else
1097 voice->Send[i].Buffer = SendSlots[i]->WetBuffer;
1098 voice->Send[i].Channels = SendSlots[i]->NumChannels;
1102 /* Transform source to listener space (convert to head relative) */
1103 alu::Vector Position{props->Position[0], props->Position[1], props->Position[2], 1.0f};
1104 alu::Vector Velocity{props->Velocity[0], props->Velocity[1], props->Velocity[2], 0.0f};
1105 alu::Vector Direction{props->Direction[0], props->Direction[1], props->Direction[2], 0.0f};
1106 if(props->HeadRelative == AL_FALSE)
1108 /* Transform source vectors */
1109 Position = Listener.Params.Matrix * Position;
1110 Velocity = Listener.Params.Matrix * Velocity;
1111 Direction = Listener.Params.Matrix * Direction;
1113 else
1115 /* Offset the source velocity to be relative of the listener velocity */
1116 Velocity += Listener.Params.Velocity;
1119 const bool directional{Direction.normalize() > 0.0f};
1120 alu::Vector SourceToListener{-Position[0], -Position[1], -Position[2], 0.0f};
1121 const ALfloat Distance{SourceToListener.normalize()};
1123 /* Initial source gain */
1124 ALfloat DryGain{props->Gain};
1125 ALfloat DryGainHF{1.0f};
1126 ALfloat DryGainLF{1.0f};
1127 ALfloat WetGain[MAX_SENDS], WetGainHF[MAX_SENDS], WetGainLF[MAX_SENDS];
1128 for(ALsizei i{0};i < NumSends;i++)
1130 WetGain[i] = props->Gain;
1131 WetGainHF[i] = 1.0f;
1132 WetGainLF[i] = 1.0f;
1135 /* Calculate distance attenuation */
1136 ALfloat ClampedDist{Distance};
1138 switch(Listener.Params.SourceDistanceModel ?
1139 props->mDistanceModel : Listener.Params.mDistanceModel)
1141 case DistanceModel::InverseClamped:
1142 ClampedDist = clampf(ClampedDist, props->RefDistance, props->MaxDistance);
1143 if(props->MaxDistance < props->RefDistance) break;
1144 /*fall-through*/
1145 case DistanceModel::Inverse:
1146 if(!(props->RefDistance > 0.0f))
1147 ClampedDist = props->RefDistance;
1148 else
1150 ALfloat dist = lerp(props->RefDistance, ClampedDist, props->RolloffFactor);
1151 if(dist > 0.0f) DryGain *= props->RefDistance / dist;
1152 for(ALsizei i{0};i < NumSends;i++)
1154 dist = lerp(props->RefDistance, ClampedDist, RoomRolloff[i]);
1155 if(dist > 0.0f) WetGain[i] *= props->RefDistance / dist;
1158 break;
1160 case DistanceModel::LinearClamped:
1161 ClampedDist = clampf(ClampedDist, props->RefDistance, props->MaxDistance);
1162 if(props->MaxDistance < props->RefDistance) break;
1163 /*fall-through*/
1164 case DistanceModel::Linear:
1165 if(!(props->MaxDistance != props->RefDistance))
1166 ClampedDist = props->RefDistance;
1167 else
1169 ALfloat attn = props->RolloffFactor * (ClampedDist-props->RefDistance) /
1170 (props->MaxDistance-props->RefDistance);
1171 DryGain *= maxf(1.0f - attn, 0.0f);
1172 for(ALsizei i{0};i < NumSends;i++)
1174 attn = RoomRolloff[i] * (ClampedDist-props->RefDistance) /
1175 (props->MaxDistance-props->RefDistance);
1176 WetGain[i] *= maxf(1.0f - attn, 0.0f);
1179 break;
1181 case DistanceModel::ExponentClamped:
1182 ClampedDist = clampf(ClampedDist, props->RefDistance, props->MaxDistance);
1183 if(props->MaxDistance < props->RefDistance) break;
1184 /*fall-through*/
1185 case DistanceModel::Exponent:
1186 if(!(ClampedDist > 0.0f && props->RefDistance > 0.0f))
1187 ClampedDist = props->RefDistance;
1188 else
1190 DryGain *= std::pow(ClampedDist/props->RefDistance, -props->RolloffFactor);
1191 for(ALsizei i{0};i < NumSends;i++)
1192 WetGain[i] *= std::pow(ClampedDist/props->RefDistance, -RoomRolloff[i]);
1194 break;
1196 case DistanceModel::Disable:
1197 ClampedDist = props->RefDistance;
1198 break;
1201 /* Calculate directional soundcones */
1202 if(directional && props->InnerAngle < 360.0f)
1204 ALfloat Angle{std::acos(aluDotproduct(Direction, SourceToListener))};
1205 Angle = RAD2DEG(Angle * ConeScale * 2.0f);
1207 ALfloat ConeVolume, ConeHF;
1208 if(!(Angle > props->InnerAngle))
1210 ConeVolume = 1.0f;
1211 ConeHF = 1.0f;
1213 else if(Angle < props->OuterAngle)
1215 ALfloat scale = ( Angle-props->InnerAngle) /
1216 (props->OuterAngle-props->InnerAngle);
1217 ConeVolume = lerp(1.0f, props->OuterGain, scale);
1218 ConeHF = lerp(1.0f, props->OuterGainHF, scale);
1220 else
1222 ConeVolume = props->OuterGain;
1223 ConeHF = props->OuterGainHF;
1226 DryGain *= ConeVolume;
1227 if(props->DryGainHFAuto)
1228 DryGainHF *= ConeHF;
1229 if(props->WetGainAuto)
1230 std::transform(std::begin(WetGain), std::begin(WetGain)+NumSends, std::begin(WetGain),
1231 [ConeVolume](ALfloat gain) noexcept -> ALfloat { return gain * ConeVolume; }
1233 if(props->WetGainHFAuto)
1234 std::transform(std::begin(WetGainHF), std::begin(WetGainHF)+NumSends,
1235 std::begin(WetGainHF),
1236 [ConeHF](ALfloat gain) noexcept -> ALfloat { return gain * ConeHF; }
1240 /* Apply gain and frequency filters */
1241 DryGain = clampf(DryGain, props->MinGain, props->MaxGain);
1242 DryGain = minf(DryGain*props->Direct.Gain*Listener.Params.Gain, GAIN_MIX_MAX);
1243 DryGainHF *= props->Direct.GainHF;
1244 DryGainLF *= props->Direct.GainLF;
1245 for(ALsizei i{0};i < NumSends;i++)
1247 WetGain[i] = clampf(WetGain[i], props->MinGain, props->MaxGain);
1248 WetGain[i] = minf(WetGain[i]*props->Send[i].Gain*Listener.Params.Gain, GAIN_MIX_MAX);
1249 WetGainHF[i] *= props->Send[i].GainHF;
1250 WetGainLF[i] *= props->Send[i].GainLF;
1253 /* Distance-based air absorption and initial send decay. */
1254 if(ClampedDist > props->RefDistance && props->RolloffFactor > 0.0f)
1256 ALfloat meters_base{(ClampedDist-props->RefDistance) * props->RolloffFactor *
1257 Listener.Params.MetersPerUnit};
1258 if(props->AirAbsorptionFactor > 0.0f)
1260 ALfloat hfattn{std::pow(AIRABSORBGAINHF, meters_base * props->AirAbsorptionFactor)};
1261 DryGainHF *= hfattn;
1262 std::transform(std::begin(WetGainHF), std::begin(WetGainHF)+NumSends,
1263 std::begin(WetGainHF),
1264 [hfattn](ALfloat gain) noexcept -> ALfloat { return gain * hfattn; }
1268 if(props->WetGainAuto)
1270 /* Apply a decay-time transformation to the wet path, based on the
1271 * source distance in meters. The initial decay of the reverb
1272 * effect is calculated and applied to the wet path.
1274 for(ALsizei i{0};i < NumSends;i++)
1276 if(!(DecayDistance[i] > 0.0f))
1277 continue;
1279 const ALfloat gain{std::pow(REVERB_DECAY_GAIN, meters_base/DecayDistance[i])};
1280 WetGain[i] *= gain;
1281 /* Yes, the wet path's air absorption is applied with
1282 * WetGainAuto on, rather than WetGainHFAuto.
1284 if(gain > 0.0f)
1286 ALfloat gainhf{std::pow(REVERB_DECAY_GAIN, meters_base/DecayHFDistance[i])};
1287 WetGainHF[i] *= minf(gainhf / gain, 1.0f);
1288 ALfloat gainlf{std::pow(REVERB_DECAY_GAIN, meters_base/DecayLFDistance[i])};
1289 WetGainLF[i] *= minf(gainlf / gain, 1.0f);
1296 /* Initial source pitch */
1297 ALfloat Pitch{props->Pitch};
1299 /* Calculate velocity-based doppler effect */
1300 ALfloat DopplerFactor{props->DopplerFactor * Listener.Params.DopplerFactor};
1301 if(DopplerFactor > 0.0f)
1303 const alu::Vector &lvelocity = Listener.Params.Velocity;
1304 ALfloat vss{aluDotproduct(Velocity, SourceToListener) * DopplerFactor};
1305 ALfloat vls{aluDotproduct(lvelocity, SourceToListener) * DopplerFactor};
1307 const ALfloat SpeedOfSound{Listener.Params.SpeedOfSound};
1308 if(!(vls < SpeedOfSound))
1310 /* Listener moving away from the source at the speed of sound.
1311 * Sound waves can't catch it.
1313 Pitch = 0.0f;
1315 else if(!(vss < SpeedOfSound))
1317 /* Source moving toward the listener at the speed of sound. Sound
1318 * waves bunch up to extreme frequencies.
1320 Pitch = HUGE_VALF;
1322 else
1324 /* Source and listener movement is nominal. Calculate the proper
1325 * doppler shift.
1327 Pitch *= (SpeedOfSound-vls) / (SpeedOfSound-vss);
1331 /* Adjust pitch based on the buffer and output frequencies, and calculate
1332 * fixed-point stepping value.
1334 Pitch *= (ALfloat)ALBuffer->Frequency/(ALfloat)Device->Frequency;
1335 if(Pitch > (ALfloat)MAX_PITCH)
1336 voice->Step = MAX_PITCH<<FRACTIONBITS;
1337 else
1338 voice->Step = maxi(fastf2i(Pitch * FRACTIONONE), 1);
1339 if(props->Resampler == BSinc24Resampler)
1340 BsincPrepare(voice->Step, &voice->ResampleState.bsinc, &bsinc24);
1341 else if(props->Resampler == BSinc12Resampler)
1342 BsincPrepare(voice->Step, &voice->ResampleState.bsinc, &bsinc12);
1343 voice->Resampler = SelectResampler(props->Resampler);
1345 ALfloat ev{0.0f}, az{0.0f};
1346 if(Distance > 0.0f)
1348 /* Clamp Y, in case rounding errors caused it to end up outside of
1349 * -1...+1.
1351 ev = std::asin(clampf(-SourceToListener[1], -1.0f, 1.0f));
1352 /* Double negation on Z cancels out; negate once for changing source-
1353 * to-listener to listener-to-source, and again for right-handed coords
1354 * with -Z in front.
1356 az = std::atan2(-SourceToListener[0], SourceToListener[2]*ZScale);
1359 ALfloat spread{0.0f};
1360 if(props->Radius > Distance)
1361 spread = F_TAU - Distance/props->Radius*F_PI;
1362 else if(Distance > 0.0f)
1363 spread = std::asin(props->Radius/Distance) * 2.0f;
1365 CalcPanningAndFilters(voice, az, ev, Distance, spread, DryGain, DryGainHF, DryGainLF, WetGain,
1366 WetGainLF, WetGainHF, SendSlots, ALBuffer, props, Listener, Device);
1369 void CalcSourceParams(ALvoice *voice, ALCcontext *context, bool force)
1371 ALvoiceProps *props{voice->Update.exchange(nullptr, std::memory_order_acq_rel)};
1372 if(!props && !force) return;
1374 if(props)
1376 voice->Props = *props;
1378 AtomicReplaceHead(context->FreeVoiceProps, props);
1381 ALbufferlistitem *BufferListItem{voice->current_buffer.load(std::memory_order_relaxed)};
1382 while(BufferListItem)
1384 auto buffers_end = BufferListItem->buffers+BufferListItem->num_buffers;
1385 auto buffer = std::find_if(BufferListItem->buffers, buffers_end,
1386 [](const ALbuffer *buffer) noexcept -> bool
1387 { return buffer != nullptr; }
1389 if(LIKELY(buffer != buffers_end))
1391 if(voice->Props.SpatializeMode==SpatializeOn ||
1392 (voice->Props.SpatializeMode==SpatializeAuto && (*buffer)->FmtChannels==FmtMono))
1393 CalcAttnSourceParams(voice, &voice->Props, *buffer, context);
1394 else
1395 CalcNonAttnSourceParams(voice, &voice->Props, *buffer, context);
1396 break;
1398 BufferListItem = BufferListItem->next.load(std::memory_order_acquire);
1403 void ProcessParamUpdates(ALCcontext *ctx, const ALeffectslotArray *slots)
1405 IncrementRef(&ctx->UpdateCount);
1406 if(LIKELY(!ctx->HoldUpdates.load(std::memory_order_acquire)))
1408 bool cforce{CalcContextParams(ctx)};
1409 bool force{CalcListenerParams(ctx) || cforce};
1410 std::for_each(slots->slot, slots->slot+slots->count,
1411 [ctx,cforce,&force](ALeffectslot *slot) -> void
1412 { force |= CalcEffectSlotParams(slot, ctx, cforce); }
1415 std::for_each(ctx->Voices, ctx->Voices+ctx->VoiceCount.load(std::memory_order_acquire),
1416 [ctx,force](ALvoice *voice) -> void
1418 ALuint sid{voice->SourceID.load(std::memory_order_acquire)};
1419 if(sid) CalcSourceParams(voice, ctx, force);
1423 IncrementRef(&ctx->UpdateCount);
1426 void ProcessContext(ALCcontext *ctx, ALsizei SamplesToDo)
1428 const ALeffectslotArray *auxslots{ctx->ActiveAuxSlots.load(std::memory_order_acquire)};
1430 /* Process pending propery updates for objects on the context. */
1431 ProcessParamUpdates(ctx, auxslots);
1433 /* Clear auxiliary effect slot mixing buffers. */
1434 std::for_each(auxslots->slot, auxslots->slot+auxslots->count,
1435 [SamplesToDo](ALeffectslot *slot) -> void
1437 std::for_each(slot->WetBuffer, slot->WetBuffer+slot->NumChannels,
1438 [SamplesToDo](ALfloat *buffer) -> void
1439 { std::fill_n(buffer, SamplesToDo, 0.0f); }
1444 /* Process voices that have a playing source. */
1445 std::for_each(ctx->Voices, ctx->Voices+ctx->VoiceCount.load(std::memory_order_acquire),
1446 [SamplesToDo,ctx](ALvoice *voice) -> void
1448 if(!voice->Playing.load(std::memory_order_acquire)) return;
1449 ALuint sid{voice->SourceID.load(std::memory_order_relaxed)};
1450 if(!sid || voice->Step < 1) return;
1452 if(!MixSource(voice, sid, ctx, SamplesToDo))
1454 voice->SourceID.store(0u, std::memory_order_relaxed);
1455 voice->Playing.store(false, std::memory_order_release);
1456 SendSourceStoppedEvent(ctx, sid);
1461 /* Process effects. */
1462 std::for_each(auxslots->slot, auxslots->slot+auxslots->count,
1463 [SamplesToDo](const ALeffectslot *slot) -> void
1465 EffectState *state{slot->Params.mEffectState};
1466 state->process(SamplesToDo, slot->WetBuffer, state->mOutBuffer,
1467 state->mOutChannels);
1473 void ApplyStablizer(FrontStablizer *Stablizer, ALfloat (*RESTRICT Buffer)[BUFFERSIZE],
1474 int lidx, int ridx, int cidx, ALsizei SamplesToDo, ALsizei NumChannels)
1476 /* Apply an all-pass to all channels, except the front-left and front-
1477 * right, so they maintain the same relative phase.
1479 for(ALsizei i{0};i < NumChannels;i++)
1481 if(i == lidx || i == ridx)
1482 continue;
1483 Stablizer->APFilter[i].process(Buffer[i], SamplesToDo);
1486 ALfloat (*RESTRICT lsplit)[BUFFERSIZE]{Stablizer->LSplit};
1487 ALfloat (*RESTRICT rsplit)[BUFFERSIZE]{Stablizer->RSplit};
1488 Stablizer->LFilter.process(lsplit[1], lsplit[0], Buffer[lidx], SamplesToDo);
1489 Stablizer->RFilter.process(rsplit[1], rsplit[0], Buffer[ridx], SamplesToDo);
1491 for(ALsizei i{0};i < SamplesToDo;i++)
1493 ALfloat lfsum{lsplit[0][i] + rsplit[0][i]};
1494 ALfloat hfsum{lsplit[1][i] + rsplit[1][i]};
1495 ALfloat s{lsplit[0][i] + lsplit[1][i] - rsplit[0][i] - rsplit[1][i]};
1497 /* This pans the separate low- and high-frequency sums between being on
1498 * the center channel and the left/right channels. The low-frequency
1499 * sum is 1/3rd toward center (2/3rds on left/right) and the high-
1500 * frequency sum is 1/4th toward center (3/4ths on left/right). These
1501 * values can be tweaked.
1503 ALfloat m{lfsum*std::cos(1.0f/3.0f * F_PI_2) + hfsum*std::cos(1.0f/4.0f * F_PI_2)};
1504 ALfloat c{lfsum*std::sin(1.0f/3.0f * F_PI_2) + hfsum*std::sin(1.0f/4.0f * F_PI_2)};
1506 /* The generated center channel signal adds to the existing signal,
1507 * while the modified left and right channels replace.
1509 Buffer[lidx][i] = (m + s) * 0.5f;
1510 Buffer[ridx][i] = (m - s) * 0.5f;
1511 Buffer[cidx][i] += c * 0.5f;
1515 void ApplyDistanceComp(ALfloat (*RESTRICT Samples)[BUFFERSIZE], const DistanceComp &distcomp,
1516 ALfloat *RESTRICT Values, ALsizei SamplesToDo, ALsizei numchans)
1518 for(ALsizei c{0};c < numchans;c++)
1520 ALfloat *RESTRICT inout{Samples[c]};
1521 const ALfloat gain{distcomp[c].Gain};
1522 const ALsizei base{distcomp[c].Length};
1523 ALfloat *RESTRICT distbuf{distcomp[c].Buffer};
1525 if(base == 0)
1527 if(gain < 1.0f)
1528 std::for_each(inout, inout+SamplesToDo,
1529 [gain](ALfloat &in) noexcept -> void
1530 { in *= gain; }
1532 continue;
1535 if(LIKELY(SamplesToDo >= base))
1537 auto out = std::copy_n(distbuf, base, Values);
1538 std::copy_n(inout, SamplesToDo-base, out);
1539 std::copy_n(inout+SamplesToDo-base, base, distbuf);
1541 else
1543 std::copy_n(distbuf, SamplesToDo, Values);
1544 auto out = std::copy(distbuf+SamplesToDo, distbuf+base, distbuf);
1545 std::copy_n(inout, SamplesToDo, out);
1547 std::transform<ALfloat*RESTRICT>(Values, Values+SamplesToDo, inout,
1548 [gain](ALfloat in) noexcept -> ALfloat { return in * gain; }
1553 void ApplyDither(ALfloat (*RESTRICT Samples)[BUFFERSIZE], ALuint *dither_seed,
1554 const ALfloat quant_scale, const ALsizei SamplesToDo, const ALsizei numchans)
1556 ASSUME(numchans > 0);
1558 /* Dithering. Generate whitenoise (uniform distribution of random values
1559 * between -1 and +1) and add it to the sample values, after scaling up to
1560 * the desired quantization depth amd before rounding.
1562 const ALfloat invscale{1.0f / quant_scale};
1563 ALuint seed{*dither_seed};
1564 auto dither_channel = [&seed,invscale,quant_scale,SamplesToDo](ALfloat *buffer) -> void
1566 ASSUME(SamplesToDo > 0);
1567 std::transform(buffer, buffer+SamplesToDo, buffer,
1568 [&seed,invscale,quant_scale](ALfloat sample) noexcept -> ALfloat
1570 ALfloat val = sample * quant_scale;
1571 ALuint rng0 = dither_rng(&seed);
1572 ALuint rng1 = dither_rng(&seed);
1573 val += (ALfloat)(rng0*(1.0/UINT_MAX) - rng1*(1.0/UINT_MAX));
1574 return fast_roundf(val) * invscale;
1578 std::for_each(Samples, Samples+numchans, dither_channel);
1579 *dither_seed = seed;
1583 /* Base template left undefined. Should be marked =delete, but Clang 3.8.1
1584 * chokes on that given the inline specializations.
1586 template<typename T>
1587 inline T SampleConv(ALfloat) noexcept;
1589 template<> inline ALfloat SampleConv(ALfloat val) noexcept
1590 { return val; }
1591 template<> inline ALint SampleConv(ALfloat val) noexcept
1593 /* Floats have a 23-bit mantissa. There is an implied 1 bit in the mantissa
1594 * along with the sign bit, giving 25 bits total, so [-16777216, +16777216]
1595 * is the max value a normalized float can be scaled to before losing
1596 * precision.
1598 return fastf2i(clampf(val*16777216.0f, -16777216.0f, 16777215.0f))<<7;
1600 template<> inline ALshort SampleConv(ALfloat val) noexcept
1601 { return fastf2i(clampf(val*32768.0f, -32768.0f, 32767.0f)); }
1602 template<> inline ALbyte SampleConv(ALfloat val) noexcept
1603 { return fastf2i(clampf(val*128.0f, -128.0f, 127.0f)); }
1605 /* Define unsigned output variations. */
1606 template<> inline ALuint SampleConv(ALfloat val) noexcept
1607 { return SampleConv<ALint>(val) + 2147483648u; }
1608 template<> inline ALushort SampleConv(ALfloat val) noexcept
1609 { return SampleConv<ALshort>(val) + 32768; }
1610 template<> inline ALubyte SampleConv(ALfloat val) noexcept
1611 { return SampleConv<ALbyte>(val) + 128; }
1613 template<DevFmtType T>
1614 void Write(const ALfloat (*InBuffer)[BUFFERSIZE], ALvoid *OutBuffer, ALsizei Offset,
1615 ALsizei SamplesToDo, ALsizei numchans)
1617 using SampleType = typename DevFmtTypeTraits<T>::Type;
1619 ASSUME(numchans > 0);
1620 SampleType *outbase = static_cast<SampleType*>(OutBuffer) + Offset*numchans;
1621 auto conv_channel = [&outbase,SamplesToDo,numchans](const ALfloat *inbuf) -> void
1623 ASSUME(SamplesToDo > 0);
1624 SampleType *out{outbase++};
1625 std::for_each<const ALfloat*RESTRICT>(inbuf, inbuf+SamplesToDo,
1626 [numchans,&out](const ALfloat s) noexcept -> void
1628 *out = SampleConv<SampleType>(s);
1629 out += numchans;
1633 std::for_each(InBuffer, InBuffer+numchans, conv_channel);
1636 } // namespace
1638 void aluMixData(ALCdevice *device, ALvoid *OutBuffer, ALsizei NumSamples)
1640 FPUCtl mixer_mode{};
1641 for(ALsizei SamplesDone{0};SamplesDone < NumSamples;)
1643 const ALsizei SamplesToDo{mini(NumSamples-SamplesDone, BUFFERSIZE)};
1645 /* Clear main mixing buffers. */
1646 std::for_each(device->MixBuffer.begin(), device->MixBuffer.end(),
1647 [SamplesToDo](std::array<ALfloat,BUFFERSIZE> &buffer) -> void
1648 { std::fill_n(buffer.begin(), SamplesToDo, 0.0f); }
1651 /* Increment the mix count at the start (lsb should now be 1). */
1652 IncrementRef(&device->MixCount);
1654 /* For each context on this device, process and mix its sources and
1655 * effects.
1657 ALCcontext *ctx{device->ContextList.load(std::memory_order_acquire)};
1658 while(ctx)
1660 ProcessContext(ctx, SamplesToDo);
1662 ctx = ctx->next.load(std::memory_order_relaxed);
1665 /* Increment the clock time. Every second's worth of samples is
1666 * converted and added to clock base so that large sample counts don't
1667 * overflow during conversion. This also guarantees a stable
1668 * conversion.
1670 device->SamplesDone += SamplesToDo;
1671 device->ClockBase += std::chrono::seconds{device->SamplesDone / device->Frequency};
1672 device->SamplesDone %= device->Frequency;
1674 /* Increment the mix count at the end (lsb should now be 0). */
1675 IncrementRef(&device->MixCount);
1677 /* Apply any needed post-process for finalizing the Dry mix to the
1678 * RealOut (Ambisonic decode, UHJ encode, etc).
1680 if(LIKELY(device->PostProcess))
1681 device->PostProcess(device, SamplesToDo);
1683 /* Apply front image stablization for surround sound, if applicable. */
1684 if(device->Stablizer)
1686 const int lidx{GetChannelIdxByName(&device->RealOut, FrontLeft)};
1687 const int ridx{GetChannelIdxByName(&device->RealOut, FrontRight)};
1688 const int cidx{GetChannelIdxByName(&device->RealOut, FrontCenter)};
1689 assert(lidx >= 0 && ridx >= 0 && cidx >= 0);
1691 ApplyStablizer(device->Stablizer.get(), device->RealOut.Buffer, lidx, ridx, cidx,
1692 SamplesToDo, device->RealOut.NumChannels);
1695 /* Apply delays and attenuation for mismatched speaker distances. */
1696 ApplyDistanceComp(device->RealOut.Buffer, device->ChannelDelay, device->TempBuffer[0],
1697 SamplesToDo, device->RealOut.NumChannels);
1699 /* Apply compression, limiting final sample amplitude, if desired. */
1700 if(device->Limiter)
1701 ApplyCompression(device->Limiter.get(), SamplesToDo, device->RealOut.Buffer);
1703 /* Apply dithering. The compressor should have left enough headroom for
1704 * the dither noise to not saturate.
1706 if(device->DitherDepth > 0.0f)
1707 ApplyDither(device->RealOut.Buffer, &device->DitherSeed, device->DitherDepth,
1708 SamplesToDo, device->RealOut.NumChannels);
1710 if(LIKELY(OutBuffer))
1712 ALfloat (*Buffer)[BUFFERSIZE]{device->RealOut.Buffer};
1713 ALsizei Channels{device->RealOut.NumChannels};
1715 /* Finally, interleave and convert samples, writing to the device's
1716 * output buffer.
1718 switch(device->FmtType)
1720 #define HANDLE_WRITE(T) case T: \
1721 Write<T>(Buffer, OutBuffer, SamplesDone, SamplesToDo, Channels); break;
1722 HANDLE_WRITE(DevFmtByte)
1723 HANDLE_WRITE(DevFmtUByte)
1724 HANDLE_WRITE(DevFmtShort)
1725 HANDLE_WRITE(DevFmtUShort)
1726 HANDLE_WRITE(DevFmtInt)
1727 HANDLE_WRITE(DevFmtUInt)
1728 HANDLE_WRITE(DevFmtFloat)
1729 #undef HANDLE_WRITE
1733 SamplesDone += SamplesToDo;
1738 void aluHandleDisconnect(ALCdevice *device, const char *msg, ...)
1740 if(!device->Connected.exchange(AL_FALSE, std::memory_order_acq_rel))
1741 return;
1743 AsyncEvent evt{EventType_Disconnected};
1744 evt.u.user.type = AL_EVENT_TYPE_DISCONNECTED_SOFT;
1745 evt.u.user.id = 0;
1746 evt.u.user.param = 0;
1748 va_list args;
1749 va_start(args, msg);
1750 int msglen{vsnprintf(evt.u.user.msg, sizeof(evt.u.user.msg), msg, args)};
1751 va_end(args);
1753 if(msglen < 0 || (size_t)msglen >= sizeof(evt.u.user.msg))
1754 evt.u.user.msg[sizeof(evt.u.user.msg)-1] = 0;
1756 ALCcontext *ctx{device->ContextList.load()};
1757 while(ctx)
1759 const ALbitfieldSOFT enabledevt{ctx->EnabledEvts.load(std::memory_order_acquire)};
1760 if((enabledevt&EventType_Disconnected) &&
1761 ll_ringbuffer_write(ctx->AsyncEvents, &evt, 1) == 1)
1762 ctx->EventSem.post();
1764 std::for_each(ctx->Voices, ctx->Voices+ctx->VoiceCount.load(std::memory_order_acquire),
1765 [ctx](ALvoice *voice) -> void
1767 if(!voice->Playing.load(std::memory_order_acquire)) return;
1768 ALuint sid{voice->SourceID.load(std::memory_order_relaxed)};
1769 if(!sid) return;
1771 voice->SourceID.store(0u, std::memory_order_relaxed);
1772 voice->Playing.store(false, std::memory_order_release);
1773 /* If the source's voice was playing, it's now effectively
1774 * stopped (the source state will be updated the next time it's
1775 * checked).
1777 SendSourceStoppedEvent(ctx, sid);
1781 ctx = ctx->next.load(std::memory_order_relaxed);