opengl32: Introduce new disabled_extensions_index helper.
[wine.git] / libs / faudio / src / F3DAudio.c
blob8b9db5900ed561377e73459847dcfe673a08f41b
1 /* FAudio - XAudio Reimplementation for FNA
3 * Copyright (c) 2011-2022 Ethan Lee, Luigi Auriemma, and the MonoGame Team
5 * This software is provided 'as-is', without any express or implied warranty.
6 * In no event will the authors be held liable for any damages arising from
7 * the use of this software.
9 * Permission is granted to anyone to use this software for any purpose,
10 * including commercial applications, and to alter it and redistribute it
11 * freely, subject to the following restrictions:
13 * 1. The origin of this software must not be misrepresented; you must not
14 * claim that you wrote the original software. If you use this software in a
15 * product, an acknowledgment in the product documentation would be
16 * appreciated but is not required.
18 * 2. Altered source versions must be plainly marked as such, and must not be
19 * misrepresented as being the original software.
21 * 3. This notice may not be removed or altered from any source distribution.
23 * Ethan "flibitijibibo" Lee <flibitijibibo@flibitijibibo.com>
27 #include "F3DAudio.h"
28 #include "FAudio_internal.h"
30 #include <math.h> /* ONLY USE THIS FOR isnan! */
31 #include <float.h> /* ONLY USE THIS FOR FLT_MIN/FLT_MAX! */
33 /* VS2010 doesn't define isnan (which is C99), so here it is. */
34 #if defined(_MSC_VER) && !defined(isnan)
35 #define isnan(x) _isnan(x)
36 #endif
38 /* UTILITY MACROS */
40 #define PARAM_CHECK_OK 1
41 #define PARAM_CHECK_FAIL (!PARAM_CHECK_OK)
43 #define ARRAY_COUNT(x) (sizeof(x) / sizeof(x[0]))
45 #define LERP(a, x, y) ((1.0f - a) * x + a * y)
47 /* PARAMETER CHECK MACROS */
49 #define PARAM_CHECK(cond, msg) FAudio_assert(cond && msg)
51 #define POINTER_CHECK(p) \
52 PARAM_CHECK(p != NULL, "Pointer " #p " must be != NULL")
54 #define FLOAT_BETWEEN_CHECK(f, a, b) \
55 PARAM_CHECK(f >= a, "Value" #f " is too low"); \
56 PARAM_CHECK(f <= b, "Value" #f " is too big")
59 /* Quote X3DAUDIO docs:
60 * "To be considered orthonormal, a pair of vectors must have a magnitude of
61 * 1 +- 1x10-5 and a dot product of 0 +- 1x10-5."
62 * VECTOR_NORMAL_CHECK verifies that vectors are normal (i.e. have norm 1 +- 1x10-5)
63 * VECTOR_BASE_CHECK verifies that a pair of vectors are orthogonal (i.e. their dot
64 * product is 0 +- 1x10-5)
67 /* TODO: Switch to square length (to save CPU) */
68 #define VECTOR_NORMAL_CHECK(v) \
69 PARAM_CHECK( \
70 FAudio_fabsf(VectorLength(v) - 1.0f) <= 1e-5f, \
71 "Vector " #v " isn't normal" \
74 #define VECTOR_BASE_CHECK(u, v) \
75 PARAM_CHECK( \
76 FAudio_fabsf(VectorDot(u, v)) <= 1e-5f, \
77 "Vector u and v have non-negligible dot product" \
80 /*************************************
81 * F3DAudioInitialize Implementation *
82 *************************************/
84 /* F3DAUDIO_HANDLE Structure */
85 #define SPEAKERMASK(Instance) *((uint32_t*) &Instance[0])
86 #define SPEAKERCOUNT(Instance) *((uint32_t*) &Instance[4])
87 #define SPEAKER_LF_INDEX(Instance) *((uint32_t*) &Instance[8])
88 #define SPEEDOFSOUND(Instance) *((float*) &Instance[12])
89 #define SPEEDOFSOUNDEPSILON(Instance) *((float*) &Instance[16])
91 /* Export for unit tests */
92 F3DAUDIOAPI uint32_t F3DAudioCheckInitParams(
93 uint32_t SpeakerChannelMask,
94 float SpeedOfSound,
95 F3DAUDIO_HANDLE instance
96 ) {
97 const uint32_t kAllowedSpeakerMasks[] =
99 SPEAKER_MONO,
100 SPEAKER_STEREO,
101 SPEAKER_2POINT1,
102 SPEAKER_QUAD,
103 SPEAKER_SURROUND,
104 SPEAKER_4POINT1,
105 SPEAKER_5POINT1,
106 SPEAKER_5POINT1_SURROUND,
107 SPEAKER_7POINT1,
108 SPEAKER_7POINT1_SURROUND,
110 uint8_t speakerMaskIsValid = 0;
111 uint32_t i;
113 POINTER_CHECK(instance);
115 for (i = 0; i < ARRAY_COUNT(kAllowedSpeakerMasks); i += 1)
117 if (SpeakerChannelMask == kAllowedSpeakerMasks[i])
119 speakerMaskIsValid = 1;
120 break;
124 /* The docs don't clearly say it, but the debug dll does check that
125 * we're exactly in one of the allowed speaker configurations.
126 * -Adrien
128 PARAM_CHECK(
129 speakerMaskIsValid == 1,
130 "SpeakerChannelMask is invalid. Needs to be one of"
131 " MONO, STEREO, QUAD, 2POINT1, 4POINT1, 5POINT1, 7POINT1,"
132 " SURROUND, 5POINT1_SURROUND, or 7POINT1_SURROUND."
135 PARAM_CHECK(SpeedOfSound >= FLT_MIN, "SpeedOfSound needs to be >= FLT_MIN");
137 return PARAM_CHECK_OK;
140 void F3DAudioInitialize(
141 uint32_t SpeakerChannelMask,
142 float SpeedOfSound,
143 F3DAUDIO_HANDLE Instance
145 F3DAudioInitialize8(SpeakerChannelMask, SpeedOfSound, Instance);
148 uint32_t F3DAudioInitialize8(
149 uint32_t SpeakerChannelMask,
150 float SpeedOfSound,
151 F3DAUDIO_HANDLE Instance
153 union
155 float f;
156 uint32_t i;
157 } epsilonHack;
158 uint32_t speakerCount = 0;
160 if (!F3DAudioCheckInitParams(SpeakerChannelMask, SpeedOfSound, Instance))
162 return FAUDIO_E_INVALID_CALL;
165 SPEAKERMASK(Instance) = SpeakerChannelMask;
166 SPEEDOFSOUND(Instance) = SpeedOfSound;
168 /* "Convert" raw float to int... */
169 epsilonHack.f = SpeedOfSound;
170 /* ... Subtract epsilon value... */
171 epsilonHack.i -= 1;
172 /* ... Convert back to float. */
173 SPEEDOFSOUNDEPSILON(Instance) = epsilonHack.f;
175 SPEAKER_LF_INDEX(Instance) = 0xFFFFFFFF;
176 if (SpeakerChannelMask & SPEAKER_LOW_FREQUENCY)
178 if (SpeakerChannelMask & SPEAKER_FRONT_CENTER)
180 SPEAKER_LF_INDEX(Instance) = 3;
182 else
184 SPEAKER_LF_INDEX(Instance) = 2;
188 while (SpeakerChannelMask)
190 speakerCount += 1;
191 SpeakerChannelMask &= SpeakerChannelMask - 1;
193 SPEAKERCOUNT(Instance) = speakerCount;
195 return 0;
199 /************************************
200 * F3DAudioCalculate Implementation *
201 ************************************/
203 /* VECTOR UTILITIES */
205 static inline F3DAUDIO_VECTOR Vec(float x, float y, float z)
207 F3DAUDIO_VECTOR res;
208 res.x = x;
209 res.y = y;
210 res.z = z;
211 return res;
214 #define VectorAdd(u, v) Vec(u.x + v.x, u.y + v.y, u.z + v.z)
216 #define VectorSub(u, v) Vec(u.x - v.x, u.y - v.y, u.z - v.z)
218 #define VectorScale(u, s) Vec(u.x * s, u.y * s, u.z * s)
220 #define VectorCross(u, v) Vec( \
221 (u.y * v.z) - (u.z * v.y), \
222 (u.z * v.x) - (u.x * v.z), \
223 (u.x * v.y) - (u.y * v.x) \
226 #define VectorLength(v) FAudio_sqrtf( \
227 (v.x * v.x) + (v.y * v.y) + (v.z * v.z) \
230 #define VectorDot(u, v) ((u.x * v.x) + (u.y * v.y) + (u.z * v.z))
232 /* This structure represent a tuple of vectors that form a left-handed basis.
233 * That is, all vectors are normal, orthogonal to each other, and taken in the
234 * order front, right, top they follow the left-hand rule.
235 * (https://en.wikipedia.org/wiki/Right-hand_rule)
237 typedef struct F3DAUDIO_BASIS
239 F3DAUDIO_VECTOR front;
240 F3DAUDIO_VECTOR right;
241 F3DAUDIO_VECTOR top;
242 } F3DAUDIO_BASIS;
244 /* CHECK UTILITY FUNCTIONS */
246 static inline uint8_t CheckCone(F3DAUDIO_CONE *pCone)
248 if (!pCone)
250 return PARAM_CHECK_OK;
253 FLOAT_BETWEEN_CHECK(pCone->InnerAngle, 0.0f, F3DAUDIO_2PI);
254 FLOAT_BETWEEN_CHECK(pCone->OuterAngle, pCone->InnerAngle, F3DAUDIO_2PI);
256 FLOAT_BETWEEN_CHECK(pCone->InnerVolume, 0.0f, 2.0f);
257 FLOAT_BETWEEN_CHECK(pCone->OuterVolume, 0.0f, 2.0f);
259 FLOAT_BETWEEN_CHECK(pCone->InnerLPF, 0.0f, 1.0f);
260 FLOAT_BETWEEN_CHECK(pCone->OuterLPF, 0.0f, 1.0f);
262 FLOAT_BETWEEN_CHECK(pCone->InnerReverb, 0.0f, 2.0f);
263 FLOAT_BETWEEN_CHECK(pCone->OuterReverb, 0.0f, 2.0f);
265 return PARAM_CHECK_OK;
268 static inline uint8_t CheckCurve(F3DAUDIO_DISTANCE_CURVE *pCurve)
270 F3DAUDIO_DISTANCE_CURVE_POINT *points;
271 uint32_t i;
272 if (!pCurve)
274 return PARAM_CHECK_OK;
277 points = pCurve->pPoints;
278 POINTER_CHECK(points);
279 PARAM_CHECK(pCurve->PointCount >= 2, "Invalid number of points for curve");
281 for (i = 0; i < pCurve->PointCount; i += 1)
283 FLOAT_BETWEEN_CHECK(points[i].Distance, 0.0f, 1.0f);
286 PARAM_CHECK(
287 points[0].Distance == 0.0f,
288 "First point in the curve must be at distance 0.0f"
290 PARAM_CHECK(
291 points[pCurve->PointCount - 1].Distance == 1.0f,
292 "Last point in the curve must be at distance 1.0f"
295 for (i = 0; i < (pCurve->PointCount - 1); i += 1)
297 PARAM_CHECK(
298 points[i].Distance < points[i + 1].Distance,
299 "Curve points must be in strict ascending order"
303 return PARAM_CHECK_OK;
306 /* Export for unit tests */
307 F3DAUDIOAPI uint8_t F3DAudioCheckCalculateParams(
308 const F3DAUDIO_HANDLE Instance,
309 const F3DAUDIO_LISTENER *pListener,
310 const F3DAUDIO_EMITTER *pEmitter,
311 uint32_t Flags,
312 F3DAUDIO_DSP_SETTINGS *pDSPSettings
314 uint32_t i, ChannelCount;
316 POINTER_CHECK(Instance);
317 POINTER_CHECK(pListener);
318 POINTER_CHECK(pEmitter);
319 POINTER_CHECK(pDSPSettings);
321 if (Flags & F3DAUDIO_CALCULATE_MATRIX)
323 POINTER_CHECK(pDSPSettings->pMatrixCoefficients);
325 if (Flags & F3DAUDIO_CALCULATE_ZEROCENTER)
327 const uint32_t isCalculateMatrix = (Flags & F3DAUDIO_CALCULATE_MATRIX);
328 const uint32_t hasCenter = SPEAKERMASK(Instance) & SPEAKER_FRONT_CENTER;
329 PARAM_CHECK(
330 isCalculateMatrix && hasCenter,
331 "F3DAUDIO_CALCULATE_ZEROCENTER is only valid for matrix"
332 " calculations with an output format that has a center channel"
336 if (Flags & F3DAUDIO_CALCULATE_REDIRECT_TO_LFE)
338 const uint32_t isCalculateMatrix = (Flags & F3DAUDIO_CALCULATE_MATRIX);
339 const uint32_t hasLF = SPEAKERMASK(Instance) & SPEAKER_LOW_FREQUENCY;
340 PARAM_CHECK(
341 isCalculateMatrix && hasLF,
342 "F3DAUDIO_CALCULATE_REDIRECT_TO_LFE is only valid for matrix"
343 " calculations with an output format that has a low-frequency"
344 " channel"
348 ChannelCount = SPEAKERCOUNT(Instance);
349 PARAM_CHECK(
350 pDSPSettings->DstChannelCount == ChannelCount,
351 "Invalid channel count, DSP settings and speaker configuration must agree"
353 PARAM_CHECK(
354 pDSPSettings->SrcChannelCount == pEmitter->ChannelCount,
355 "Invalid channel count, DSP settings and emitter must agree"
358 if (pListener->pCone)
360 PARAM_CHECK(
361 CheckCone(pListener->pCone) == PARAM_CHECK_OK,
362 "Invalid listener cone"
365 VECTOR_NORMAL_CHECK(pListener->OrientFront);
366 VECTOR_NORMAL_CHECK(pListener->OrientTop);
367 VECTOR_BASE_CHECK(pListener->OrientFront, pListener->OrientTop);
369 if (pEmitter->pCone)
371 VECTOR_NORMAL_CHECK(pEmitter->OrientFront);
372 PARAM_CHECK(
373 CheckCone(pEmitter->pCone) == PARAM_CHECK_OK,
374 "Invalid emitter cone"
377 else if (Flags & F3DAUDIO_CALCULATE_EMITTER_ANGLE)
379 VECTOR_NORMAL_CHECK(pEmitter->OrientFront);
381 if (pEmitter->ChannelCount > 1)
383 /* Only used for multi-channel emitters */
384 VECTOR_NORMAL_CHECK(pEmitter->OrientFront);
385 VECTOR_NORMAL_CHECK(pEmitter->OrientTop);
386 VECTOR_BASE_CHECK(pEmitter->OrientFront, pEmitter->OrientTop);
388 FLOAT_BETWEEN_CHECK(pEmitter->InnerRadius, 0.0f, FLT_MAX);
389 FLOAT_BETWEEN_CHECK(pEmitter->InnerRadiusAngle, 0.0f, F3DAUDIO_2PI / 4.0f);
390 PARAM_CHECK(
391 pEmitter->ChannelCount > 0,
392 "Invalid channel count for emitter"
394 PARAM_CHECK(
395 pEmitter->ChannelRadius >= 0.0f,
396 "Invalid channel radius for emitter"
398 if (pEmitter->ChannelCount > 1)
400 PARAM_CHECK(
401 pEmitter->pChannelAzimuths != NULL,
402 "Invalid channel azimuths for multi-channel emitter"
404 if (pEmitter->pChannelAzimuths)
406 for (i = 0; i < pEmitter->ChannelCount; i += 1)
408 float currentAzimuth = pEmitter->pChannelAzimuths[i];
409 FLOAT_BETWEEN_CHECK(currentAzimuth, 0.0f, F3DAUDIO_2PI);
410 if (currentAzimuth == F3DAUDIO_2PI)
412 PARAM_CHECK(
413 !(Flags & F3DAUDIO_CALCULATE_REDIRECT_TO_LFE),
414 "F3DAUDIO_CALCULATE_REDIRECT_TO_LFE valid only for"
415 " matrix calculations with emitters that have no LFE"
416 " channel"
422 FLOAT_BETWEEN_CHECK(pEmitter->CurveDistanceScaler, FLT_MIN, FLT_MAX);
423 FLOAT_BETWEEN_CHECK(pEmitter->DopplerScaler, 0.0f, FLT_MAX);
425 PARAM_CHECK(
426 CheckCurve(pEmitter->pVolumeCurve) == PARAM_CHECK_OK,
427 "Invalid Volume curve"
429 PARAM_CHECK(
430 CheckCurve(pEmitter->pLFECurve) == PARAM_CHECK_OK,
431 "Invalid LFE curve"
433 PARAM_CHECK(
434 CheckCurve(pEmitter->pLPFDirectCurve) == PARAM_CHECK_OK,
435 "Invalid LPFDirect curve"
437 PARAM_CHECK(
438 CheckCurve(pEmitter->pLPFReverbCurve) == PARAM_CHECK_OK,
439 "Invalid LPFReverb curve"
441 PARAM_CHECK(
442 CheckCurve(pEmitter->pReverbCurve) == PARAM_CHECK_OK,
443 "Invalid Reverb curve"
446 return PARAM_CHECK_OK;
450 * MATRIX CALCULATION
453 /* This function computes the distance either according to a curve if pCurve
454 * isn't NULL, or according to the inverse distance law 1/d otherwise.
456 static inline float ComputeDistanceAttenuation(
457 float normalizedDistance,
458 F3DAUDIO_DISTANCE_CURVE *pCurve
460 float res;
461 float alpha;
462 uint32_t n_points;
463 size_t i;
464 if (pCurve)
466 F3DAUDIO_DISTANCE_CURVE_POINT* points = pCurve->pPoints;
467 n_points = pCurve->PointCount;
469 /* By definition, the first point in the curve must be 0.0f
470 * -Adrien
473 /* We advance i up until our normalizedDistance lies between the distances of
474 * the i_th and (i-1)_th points, or we reach the last point.
476 for (i = 1; (i < n_points) && (normalizedDistance >= points[i].Distance); i += 1);
477 if (i == n_points)
479 /* We've reached the last point, so we use its value directly.
480 * Quote X3DAUDIO docs:
481 * "If an emitter moves beyond a distance of (CurveDistanceScaler × 1.0f),
482 * the last point on the curve is used to compute the volume output level."
484 res = points[n_points - 1].DSPSetting;
486 else
488 /* We're between two points: the distance attenuation is the linear interpolation of the DSPSetting
489 * values defined by our points, according to the distance.
491 alpha = (points[i].Distance - normalizedDistance) / (points[i].Distance - points[i - 1].Distance);
492 res = LERP(alpha, points[i].DSPSetting, points[i - 1].DSPSetting);
495 else
497 res = 1.0f;
498 if (normalizedDistance >= 1.0f)
500 res /= normalizedDistance;
503 return res;
506 static inline float ComputeConeParameter(
507 float distance,
508 float angle,
509 float innerAngle,
510 float outerAngle,
511 float innerParam,
512 float outerParam
514 /* When computing whether a point lies inside a cone, X3DAUDIO first determines
515 * whether the point is close enough to the apex of the cone.
516 * If it is, the innerParam is used.
517 * The following constant is the one that is used for this distance check;
518 * It is an approximation, found by manual binary search.
519 * TODO: find the exact value of the constant via automated binary search. */
520 #define CONE_NULL_DISTANCE_TOLERANCE 1e-7
522 float halfInnerAngle, halfOuterAngle, alpha;
524 /* Quote X3DAudio.h:
525 * "Set both cone angles to 0 or X3DAUDIO_2PI for omnidirectionality using
526 * only the outer or inner values respectively."
528 if (innerAngle == 0.0f && outerAngle == 0.0f)
530 return outerParam;
532 if (innerAngle == F3DAUDIO_2PI && outerAngle == F3DAUDIO_2PI)
534 return innerParam;
537 /* If we're within the inner angle, or close enough to the apex, we use
538 * the innerParam. */
539 halfInnerAngle = innerAngle / 2.0f;
540 if (distance <= CONE_NULL_DISTANCE_TOLERANCE || angle <= halfInnerAngle)
542 return innerParam;
545 /* If we're between the inner angle and the outer angle, we must use
546 * some interpolation of the innerParam and outerParam according to the
547 * distance between our angle and the inner and outer angles.
549 halfOuterAngle = outerAngle / 2.0f;
550 if (angle <= halfOuterAngle)
552 alpha = (angle - halfInnerAngle) / (halfOuterAngle - halfInnerAngle);
554 /* Sooo... This is awkward. MSDN doesn't say anything, but
555 * X3DAudio.h says that this should be lerped. However in
556 * practice the behaviour of X3DAudio isn't a lerp at all. It's
557 * easy to see with big (InnerAngle / OuterAngle) values. If we
558 * want accurate emulation, we'll need to either find what
559 * formula they use, or use a more advanced interpolation, like
560 * tricubic.
562 * TODO: HIGH_ACCURACY version.
563 * -Adrien
565 return LERP(alpha, innerParam, outerParam);
568 /* Otherwise, we're outside the outer angle, so we just return the outer param. */
569 return outerParam;
572 /* X3DAudio.h declares something like this, but the default (if emitter is NULL)
573 * volume curve is a *computed* inverse law, while on the other hand a curve
574 * leads to a piecewise linear function. So a "default curve" like this is
575 * pointless, not sure what X3DAudio does with it...
576 * -Adrien
578 #if 0
579 static F3DAUDIO_DISTANCE_CURVE_POINT DefaultVolumeCurvePoints[] =
581 { 0.0f, 1.0f },
582 { 1.0f, 0.0f }
584 static F3DAUDIO_DISTANCE_CURVE DefaultVolumeCurve =
586 DefaultVolumeCurvePoints,
587 ARRAY_COUNT(DefaultVolumeCurvePoints)
589 #endif
591 /* Here we declare the azimuths of every speaker for every speaker
592 * configuration, ordered by increasing angle, as well as the index to which
593 * they map in the final matrix for their respective configuration. It had to be
594 * reverse engineered by looking at the data from various X3DAudioCalculate()
595 * matrix results for the various speaker configurations; *in particular*, the
596 * azimuths are different from the ones in F3DAudio.h (and X3DAudio.h) for
597 * SPEAKER_STEREO (which is declared has having front L and R speakers in the
598 * bit mask, but in fact has L and R *side* speakers). LF speakers are
599 * deliberately not included in the SpeakerInfo list, rather, we store the index
600 * into a separate field (with a -1 sentinel value if it has no LF speaker).
601 * -Adrien
603 typedef struct
605 float azimuth;
606 uint32_t matrixIdx;
607 } SpeakerInfo;
609 typedef struct
611 uint32_t configMask;
612 const SpeakerInfo *speakers;
614 /* Not strictly necessary because it can be inferred from the
615 * SpeakerCount field of the F3DAUDIO_HANDLE, but makes code much
616 * cleaner and less error prone
618 uint32_t numNonLFSpeakers;
620 int32_t LFSpeakerIdx;
621 } ConfigInfo;
623 /* It is absolutely necessary that these are stored in increasing, *positive*
624 * azimuth order (i.e. all angles between [0; 2PI]), as we'll do a linear
625 * interval search inside FindSpeakerAzimuths.
626 * -Adrien
629 #define SPEAKER_AZIMUTH_CENTER 0.0f
630 #define SPEAKER_AZIMUTH_FRONT_RIGHT_OF_CENTER (F3DAUDIO_PI * 1.0f / 8.0f)
631 #define SPEAKER_AZIMUTH_FRONT_RIGHT (F3DAUDIO_PI * 1.0f / 4.0f)
632 #define SPEAKER_AZIMUTH_SIDE_RIGHT (F3DAUDIO_PI * 1.0f / 2.0f)
633 #define SPEAKER_AZIMUTH_BACK_RIGHT (F3DAUDIO_PI * 3.0f / 4.0f)
634 #define SPEAKER_AZIMUTH_BACK_CENTER F3DAUDIO_PI
635 #define SPEAKER_AZIMUTH_BACK_LEFT (F3DAUDIO_PI * 5.0f / 4.0f)
636 #define SPEAKER_AZIMUTH_SIDE_LEFT (F3DAUDIO_PI * 3.0f / 2.0f)
637 #define SPEAKER_AZIMUTH_FRONT_LEFT (F3DAUDIO_PI * 7.0f / 4.0f)
638 #define SPEAKER_AZIMUTH_FRONT_LEFT_OF_CENTER (F3DAUDIO_PI * 15.0f / 8.0f)
640 const SpeakerInfo kMonoConfigSpeakers[] =
642 { SPEAKER_AZIMUTH_CENTER, 0 },
644 const SpeakerInfo kStereoConfigSpeakers[] =
646 { SPEAKER_AZIMUTH_SIDE_RIGHT, 1 },
647 { SPEAKER_AZIMUTH_SIDE_LEFT, 0 },
649 const SpeakerInfo k2Point1ConfigSpeakers[] =
651 { SPEAKER_AZIMUTH_SIDE_RIGHT, 1 },
652 { SPEAKER_AZIMUTH_SIDE_LEFT, 0 },
654 const SpeakerInfo kSurroundConfigSpeakers[] =
656 { SPEAKER_AZIMUTH_CENTER, 2 },
657 { SPEAKER_AZIMUTH_FRONT_RIGHT, 1 },
658 { SPEAKER_AZIMUTH_BACK_CENTER, 3 },
659 { SPEAKER_AZIMUTH_FRONT_LEFT, 0 },
661 const SpeakerInfo kQuadConfigSpeakers[] =
663 { SPEAKER_AZIMUTH_FRONT_RIGHT, 1 },
664 { SPEAKER_AZIMUTH_BACK_RIGHT, 3 },
665 { SPEAKER_AZIMUTH_BACK_LEFT, 2 },
666 { SPEAKER_AZIMUTH_FRONT_LEFT, 0 },
668 const SpeakerInfo k4Point1ConfigSpeakers[] =
670 { SPEAKER_AZIMUTH_FRONT_RIGHT, 1 },
671 { SPEAKER_AZIMUTH_BACK_RIGHT, 4 },
672 { SPEAKER_AZIMUTH_BACK_LEFT, 3 },
673 { SPEAKER_AZIMUTH_FRONT_LEFT, 0 },
675 const SpeakerInfo k5Point1ConfigSpeakers[] =
677 { SPEAKER_AZIMUTH_CENTER, 2 },
678 { SPEAKER_AZIMUTH_FRONT_RIGHT, 1 },
679 { SPEAKER_AZIMUTH_BACK_RIGHT, 5 },
680 { SPEAKER_AZIMUTH_BACK_LEFT, 4 },
681 { SPEAKER_AZIMUTH_FRONT_LEFT, 0 },
683 const SpeakerInfo k7Point1ConfigSpeakers[] =
685 { SPEAKER_AZIMUTH_CENTER, 2 },
686 { SPEAKER_AZIMUTH_FRONT_RIGHT_OF_CENTER, 7 },
687 { SPEAKER_AZIMUTH_FRONT_RIGHT, 1 },
688 { SPEAKER_AZIMUTH_BACK_RIGHT, 5 },
689 { SPEAKER_AZIMUTH_BACK_LEFT, 4 },
690 { SPEAKER_AZIMUTH_FRONT_LEFT, 0 },
691 { SPEAKER_AZIMUTH_FRONT_LEFT_OF_CENTER, 6 },
693 const SpeakerInfo k5Point1SurroundConfigSpeakers[] =
695 { SPEAKER_AZIMUTH_CENTER, 2 },
696 { SPEAKER_AZIMUTH_FRONT_RIGHT, 1 },
697 { SPEAKER_AZIMUTH_SIDE_RIGHT, 5 },
698 { SPEAKER_AZIMUTH_SIDE_LEFT, 4 },
699 { SPEAKER_AZIMUTH_FRONT_LEFT, 0 },
701 const SpeakerInfo k7Point1SurroundConfigSpeakers[] =
703 { SPEAKER_AZIMUTH_CENTER, 2 },
704 { SPEAKER_AZIMUTH_FRONT_RIGHT, 1 },
705 { SPEAKER_AZIMUTH_SIDE_RIGHT, 7 },
706 { SPEAKER_AZIMUTH_BACK_RIGHT, 5 },
707 { SPEAKER_AZIMUTH_BACK_LEFT, 4 },
708 { SPEAKER_AZIMUTH_SIDE_LEFT, 6 },
709 { SPEAKER_AZIMUTH_FRONT_LEFT, 0 },
712 /* With that organization, the index of the LF speaker into the matrix array
713 * strangely looks *exactly* like the mystery field in the F3DAUDIO_HANDLE!!
714 * We're keeping a separate field within ConfigInfo because it makes the code
715 * much cleaner, though.
716 * -Adrien
718 const ConfigInfo kSpeakersConfigInfo[] =
720 { SPEAKER_MONO, kMonoConfigSpeakers, ARRAY_COUNT(kMonoConfigSpeakers), -1 },
721 { SPEAKER_STEREO, kStereoConfigSpeakers, ARRAY_COUNT(kStereoConfigSpeakers), -1 },
722 { SPEAKER_2POINT1, k2Point1ConfigSpeakers, ARRAY_COUNT(k2Point1ConfigSpeakers), 2 },
723 { SPEAKER_SURROUND, kSurroundConfigSpeakers, ARRAY_COUNT(kSurroundConfigSpeakers), -1 },
724 { SPEAKER_QUAD, kQuadConfigSpeakers, ARRAY_COUNT(kQuadConfigSpeakers), -1 },
725 { SPEAKER_4POINT1, k4Point1ConfigSpeakers, ARRAY_COUNT(k4Point1ConfigSpeakers), 2 },
726 { SPEAKER_5POINT1, k5Point1ConfigSpeakers, ARRAY_COUNT(k5Point1ConfigSpeakers), 3 },
727 { SPEAKER_7POINT1, k7Point1ConfigSpeakers, ARRAY_COUNT(k7Point1ConfigSpeakers), 3 },
728 { SPEAKER_5POINT1_SURROUND, k5Point1SurroundConfigSpeakers, ARRAY_COUNT(k5Point1SurroundConfigSpeakers), 3 },
729 { SPEAKER_7POINT1_SURROUND, k7Point1SurroundConfigSpeakers, ARRAY_COUNT(k7Point1SurroundConfigSpeakers), 3 },
732 /* A simple linear search is absolutely OK for 10 elements. */
733 static const ConfigInfo* GetConfigInfo(uint32_t speakerConfigMask)
735 uint32_t i;
736 for (i = 0; i < ARRAY_COUNT(kSpeakersConfigInfo); i += 1)
738 if (kSpeakersConfigInfo[i].configMask == speakerConfigMask)
740 return &kSpeakersConfigInfo[i];
744 FAudio_assert(0 && "Config info not found!");
745 return NULL;
748 /* Given a configuration, this function finds the azimuths of the two speakers
749 * between which the emitter lies. All the azimuths here are relative to the
750 * listener's base, since that's where the speakers are defined.
752 static inline void FindSpeakerAzimuths(
753 const ConfigInfo* config,
754 float emitterAzimuth,
755 uint8_t skipCenter,
756 const SpeakerInfo **speakerInfo
758 uint32_t i, nexti = 0;
759 float a0 = 0.0f, a1 = 0.0f;
761 FAudio_assert(config != NULL);
763 /* We want to find, given an azimuth, which speakers are the closest
764 * ones (in terms of angle) to that azimuth.
765 * This is done by iterating through the list of speaker azimuths, as
766 * given to us by the current ConfigInfo (which stores speaker azimuths
767 * in increasing order of azimuth for each possible speaker configuration;
768 * each speaker azimuth is defined to be between 0 and 2PI by construction).
770 for (i = 0; i < config->numNonLFSpeakers; i += 1)
772 /* a0 and a1 are the azimuths of candidate speakers */
773 a0 = config->speakers[i].azimuth;
774 nexti = (i + 1) % config->numNonLFSpeakers;
775 a1 = config->speakers[nexti].azimuth;
777 if (a0 < a1)
779 if (emitterAzimuth >= a0 && emitterAzimuth < a1)
781 break;
784 /* It is possible for a speaker pair to enclose the singulary at 0 == 2PI:
785 * consider for example the quad config, which has a front left speaker
786 * at 7PI/4 and a front right speaker at PI/4. In that case a0 = 7PI/4 and
787 * a1 = PI/4, and the way we know whether our current azimuth lies between
788 * that pair is by checking whether the azimuth is greather than 7PI/4 or
789 * whether it's less than PI/4. (By contract, currentAzimuth is always less
790 * than 2PI.)
792 else
794 if (emitterAzimuth >= a0 || emitterAzimuth < a1)
796 break;
800 FAudio_assert(emitterAzimuth >= a0 || emitterAzimuth < a1);
802 /* skipCenter means that we don't want to use the center speaker.
803 * The easiest way to deal with this is to check whether either of our candidate
804 * speakers are the center, which always has an azimuth of 0.0. If that is the case
805 * we just replace it with either the previous one or the next one.
807 if (skipCenter)
809 if (a0 == 0.0f)
811 if (i == 0)
813 i = config->numNonLFSpeakers - 1;
815 else
817 i -= 1;
820 else if (a1 == 0.0f)
822 nexti += 1;
823 if (nexti >= config->numNonLFSpeakers)
825 nexti -= config->numNonLFSpeakers;
829 speakerInfo[0] = &config->speakers[i];
830 speakerInfo[1] = &config->speakers[nexti];
833 /* Used to store diffusion factors */
834 /* See below for explanation. */
835 #define DIFFUSION_SPEAKERS_ALL 0
836 #define DIFFUSION_SPEAKERS_MATCHING 1
837 #define DIFFUSION_SPEAKERS_OPPOSITE 2
838 typedef float DiffusionSpeakerFactors[3];
840 /* ComputeInnerRadiusDiffusionFactors is a utility function that returns how
841 * energy dissipates to the speakers, given the radial distance between the
842 * emitter and the listener and the (optionally 0) InnerRadius distance. It
843 * returns 3 floats, via the diffusionFactors array, that say how much energy
844 * (after distance attenuation) will need to be distributed between each of the
845 * following cases:
847 * - SPEAKERS_ALL for all (non-LF) speakers, _INCLUDING_ the MATCHING and OPPOSITE.
848 * - SPEAKERS_OPPOSITE corresponds to the two speakers OPPOSITE the emitter.
849 * - SPEAKERS_MATCHING corresponds to the two speakers closest to the emitter.
851 * For a distance below a certain threshold (DISTANCE_EQUAL_ENERGY), all
852 * speakers receive equal energy.
854 * Above that, the amount that all speakers receive decreases linearly as radial
855 * distance increases, up until InnerRadius / 2. (If InnerRadius is null, we use
856 * MINIMUM_INNER_RADIUS.)
858 * At the same time, both opposite and matching speakers start to receive sound
859 * (in addition to the energy they receive from the aforementioned "all
860 * speakers" linear law) according to some unknown as of now law,
861 * that is currently emulated with a LERP. This is true up until InnerRadius.
863 * Above InnerRadius, only the two matching speakers receive sound.
865 * For more detail, see the "Inner Radius and Inner Radius Angle" in the
866 * MSDN docs for the X3DAUDIO_EMITTER structure.
867 * https://msdn.microsoft.com/en-us/library/windows/desktop/microsoft.directx_sdk.x3daudio.x3daudio_emitter(v=vs.85).aspx
869 static inline void ComputeInnerRadiusDiffusionFactors(
870 float radialDistance,
871 float InnerRadius,
872 DiffusionSpeakerFactors diffusionFactors
875 /* Determined experimentally; this is the midpoint value, i.e. the
876 * value at 0.5 for the matching speakers, used for the standard
877 * diffusion curve.
879 * Note: It is SUSPICIOUSLY close to 1/sqrt(2), but I haven't figured out why.
880 * -Adrien
882 #define DIFFUSION_LERP_MIDPOINT_VALUE 0.707107f
884 /* X3DAudio always uses an InnerRadius-like behaviour (i.e. diffusing sound to more than
885 * a pair of speakers) even if InnerRadius is set to 0.0f.
886 * This constant determines the distance at which this behaviour is produced in that case. */
887 /* This constant was determined by manual binary search. TODO: get a more accurate version
888 * via an automated binary search. */
889 #define DIFFUSION_DISTANCE_MINIMUM_INNER_RADIUS 4e-7f
890 float actualInnerRadius = FAudio_max(InnerRadius, DIFFUSION_DISTANCE_MINIMUM_INNER_RADIUS);
891 float normalizedRadialDist;
892 float a, ms, os;
894 normalizedRadialDist = radialDistance / actualInnerRadius;
896 /* X3DAudio does another check for small radial distances before applying any InnerRadius-like
897 * behaviour. This is the constant that determines the threshold: below this distance we simply
898 * diffuse to all speakers equally. */
899 #define DIFFUSION_DISTANCE_EQUAL_ENERGY 1e-7f
900 if (radialDistance <= DIFFUSION_DISTANCE_EQUAL_ENERGY)
902 a = 1.0f;
903 ms = 0.0f;
904 os = 0.0f;
906 else if (normalizedRadialDist <= 0.5f)
908 /* Determined experimentally that this is indeed a linear law,
909 * with 100% confidence.
910 * -Adrien
912 a = 1.0f - 2.0f * normalizedRadialDist;
914 /* Lerping here is an approximation.
915 * TODO: High accuracy version. Having stared at the curves long
916 * enough, I'm pretty sure this is a quadratic, but trying to
917 * polyfit with numpy didn't give nice, round polynomial
918 * coefficients...
919 * -Adrien
921 ms = LERP(2.0f * normalizedRadialDist, 0.0f, DIFFUSION_LERP_MIDPOINT_VALUE);
922 os = 1.0f - a - ms;
924 else if (normalizedRadialDist <= 1.0f)
926 a = 0.0f;
928 /* Similarly, this is a lerp based on the midpoint value; the
929 * real, high-accuracy curve also looks like a quadratic.
930 * -Adrien
932 ms = LERP(2.0f * (normalizedRadialDist - 0.5f), DIFFUSION_LERP_MIDPOINT_VALUE, 1.0f);
933 os = 1.0f - a - ms;
935 else
937 a = 0.0f;
938 ms = 1.0f;
939 os = 0.0f;
941 diffusionFactors[DIFFUSION_SPEAKERS_ALL] = a;
942 diffusionFactors[DIFFUSION_SPEAKERS_MATCHING] = ms;
943 diffusionFactors[DIFFUSION_SPEAKERS_OPPOSITE] = os;
946 /* ComputeEmitterChannelCoefficients handles the coefficients calculation for 1
947 * column of the matrix. It uses ComputeInnerRadiusDiffusionFactors to separate
948 * into three discrete cases; and for each case does the right repartition of
949 * the energy after attenuation to the right speakers, in particular in the
950 * MATCHING and OPPOSITE cases, it gives each of the two speakers found a linear
951 * amount of the energy, according to the angular distance between the emitter
952 * and the speaker azimuth.
954 static inline void ComputeEmitterChannelCoefficients(
955 const ConfigInfo *curConfig,
956 const F3DAUDIO_BASIS *listenerBasis,
957 float innerRadius,
958 F3DAUDIO_VECTOR channelPosition,
959 float attenuation,
960 float LFEattenuation,
961 uint32_t flags,
962 uint32_t currentChannel,
963 uint32_t numSrcChannels,
964 float *pMatrixCoefficients
966 float elevation, radialDistance;
967 F3DAUDIO_VECTOR projTopVec, projPlane;
968 uint8_t skipCenter = (flags & F3DAUDIO_CALCULATE_ZEROCENTER) ? 1 : 0;
969 DiffusionSpeakerFactors diffusionFactors = { 0.0f };
971 float x, y;
972 float emitterAzimuth;
973 float energyPerChannel;
974 float totalEnergy;
975 uint32_t nChannelsToDiffuseTo;
976 uint32_t iS, centerChannelIdx = -1;
977 const SpeakerInfo* infos[2];
978 float a0, a1, val;
979 uint32_t i0, i1;
981 /* We project against the listener basis' top vector to get the elevation of the
982 * current emitter channel position.
984 elevation = VectorDot(listenerBasis->top, channelPosition);
986 /* To obtain the projection in the front-right plane of the listener's basis of the
987 * emitter channel position, we simply remove the projection against the top vector.
988 * The radial distance is then the length of the projected vector.
990 projTopVec = VectorScale(listenerBasis->top, elevation);
991 projPlane = VectorSub(channelPosition, projTopVec);
992 radialDistance = VectorLength(projPlane);
994 ComputeInnerRadiusDiffusionFactors(
995 radialDistance,
996 innerRadius,
997 diffusionFactors
1000 /* See the ComputeInnerRadiusDiffusionFactors comment above for more context. */
1001 /* DIFFUSION_SPEAKERS_ALL corresponds to diffusing part of the sound to all of the
1002 * speakers, equally. The amount of sound is determined by the float value
1003 * diffusionFactors[DIFFUSION_SPEAKERS_ALL]. */
1004 if (diffusionFactors[DIFFUSION_SPEAKERS_ALL] > 0.0f)
1006 nChannelsToDiffuseTo = curConfig->numNonLFSpeakers;
1007 totalEnergy = diffusionFactors[DIFFUSION_SPEAKERS_ALL] * attenuation;
1009 if (skipCenter)
1011 nChannelsToDiffuseTo -= 1;
1012 FAudio_assert(curConfig->speakers[0].azimuth == SPEAKER_AZIMUTH_CENTER);
1013 centerChannelIdx = curConfig->speakers[0].matrixIdx;
1016 energyPerChannel = totalEnergy / nChannelsToDiffuseTo;
1018 for (iS = 0; iS < curConfig->numNonLFSpeakers; iS += 1)
1020 const uint32_t curSpeakerIdx = curConfig->speakers[iS].matrixIdx;
1021 if (skipCenter && curSpeakerIdx == centerChannelIdx)
1023 continue;
1026 pMatrixCoefficients[curSpeakerIdx * numSrcChannels + currentChannel] += energyPerChannel;
1030 /* DIFFUSION_SPEAKERS_MATCHING corresponds to sending part of the sound to the speakers closest
1031 * (in terms of azimuth) to the current position of the emitter. The amount of sound we shoud send
1032 * corresponds here to diffusionFactors[DIFFUSION_SPEAKERS_MATCHING].
1033 * We use the FindSpeakerAzimuths function to find the speakers that match. */
1034 if (diffusionFactors[DIFFUSION_SPEAKERS_MATCHING] > 0.0f)
1036 const float totalEnergy = diffusionFactors[DIFFUSION_SPEAKERS_MATCHING] * attenuation;
1038 x = VectorDot(listenerBasis->front, projPlane);
1039 y = VectorDot(listenerBasis->right, projPlane);
1041 /* Now, a critical point: We shouldn't be sending sound to
1042 * matching speakers when x and y are close to 0. That's the
1043 * contract we get from ComputeInnerRadiusDiffusionFactors,
1044 * which checks that we're not too close to the zero distance.
1045 * This allows the atan2 calculation to give good results.
1048 /* atan2 returns [-PI, PI], but we want [0, 2PI] */
1049 emitterAzimuth = FAudio_atan2f(y, x);
1050 if (emitterAzimuth < 0.0f)
1052 emitterAzimuth += F3DAUDIO_2PI;
1055 FindSpeakerAzimuths(curConfig, emitterAzimuth, skipCenter, infos);
1056 a0 = infos[0]->azimuth;
1057 a1 = infos[1]->azimuth;
1059 /* The following code is necessary to handle the singularity in
1060 * (0 == 2PI). It'll give us a nice, well ordered interval.
1062 if (a0 > a1)
1064 if (emitterAzimuth >= a0)
1066 emitterAzimuth -= F3DAUDIO_2PI;
1068 a0 -= F3DAUDIO_2PI;
1070 FAudio_assert(emitterAzimuth >= a0 && emitterAzimuth <= a1);
1072 val = (emitterAzimuth - a0) / (a1 - a0);
1074 i0 = infos[0]->matrixIdx;
1075 i1 = infos[1]->matrixIdx;
1077 pMatrixCoefficients[i0 * numSrcChannels + currentChannel] += (1.0f - val) * totalEnergy;
1078 pMatrixCoefficients[i1 * numSrcChannels + currentChannel] += ( val) * totalEnergy;
1081 /* DIFFUSION_SPEAKERS_OPPOSITE corresponds to sending part of the sound to the speakers
1082 * _opposite_ the ones that are the closest to the current emitter position.
1083 * To find these, we simply find the ones that are closest to the current emitter's azimuth + PI
1084 * using the FindSpeakerAzimuth function. */
1085 if (diffusionFactors[DIFFUSION_SPEAKERS_OPPOSITE] > 0.0f)
1087 /* This code is similar to the matching speakers code above. */
1088 const float totalEnergy = diffusionFactors[DIFFUSION_SPEAKERS_OPPOSITE] * attenuation;
1090 x = VectorDot(listenerBasis->front, projPlane);
1091 y = VectorDot(listenerBasis->right, projPlane);
1093 /* Similarly, we expect atan2 to be well behaved here. */
1094 emitterAzimuth = FAudio_atan2f(y, x);
1096 /* Opposite speakers lie at azimuth + PI */
1097 emitterAzimuth += F3DAUDIO_PI;
1099 /* Normalize to [0; 2PI) range. */
1100 if (emitterAzimuth < 0.0f)
1102 emitterAzimuth += F3DAUDIO_2PI;
1104 else if (emitterAzimuth > F3DAUDIO_2PI)
1106 emitterAzimuth -= F3DAUDIO_2PI;
1109 FindSpeakerAzimuths(curConfig, emitterAzimuth, skipCenter, infos);
1110 a0 = infos[0]->azimuth;
1111 a1 = infos[1]->azimuth;
1113 /* The following code is necessary to handle the singularity in
1114 * (0 == 2PI). It'll give us a nice, well ordered interval.
1116 if (a0 > a1)
1118 if (emitterAzimuth >= a0)
1120 emitterAzimuth -= F3DAUDIO_2PI;
1122 a0 -= F3DAUDIO_2PI;
1124 FAudio_assert(emitterAzimuth >= a0 && emitterAzimuth <= a1);
1126 val = (emitterAzimuth - a0) / (a1 - a0);
1128 i0 = infos[0]->matrixIdx;
1129 i1 = infos[1]->matrixIdx;
1131 pMatrixCoefficients[i0 * numSrcChannels + currentChannel] += (1.0f - val) * totalEnergy;
1132 pMatrixCoefficients[i1 * numSrcChannels + currentChannel] += ( val) * totalEnergy;
1135 if (flags & F3DAUDIO_CALCULATE_REDIRECT_TO_LFE)
1137 FAudio_assert(curConfig->LFSpeakerIdx != -1);
1138 pMatrixCoefficients[curConfig->LFSpeakerIdx * numSrcChannels + currentChannel] += LFEattenuation / numSrcChannels;
1142 /* Calculations consist of several orthogonal steps that compose multiplicatively:
1144 * First, we compute the attenuations (volume and LFE) due to distance, which
1145 * may involve an optional volume and/or LFE volume curve.
1147 * Then, we compute those due to optional cones.
1149 * We then compute how much energy is diffuse w.r.t InnerRadius. If InnerRadius
1150 * is 0.0f, this step is computed as if it was InnerRadius was
1151 * NON_NULL_DISTANCE_DISK_RADIUS. The way this works is, we look at the radial
1152 * distance of the current emitter channel to the listener, with regard to the
1153 * listener's top orientation (i.e. this distance is independant of the
1154 * emitter's elevation!). If this distance is less than NULL_DISTANCE_RADIUS,
1155 * energy is diffused equally between all channels. If it's greater than
1156 * InnerRadius (or NON_NULL_DISTANCE_RADIUS, if InnerRadius is 0.0f, as
1157 * mentioned above), the two closest speakers, by azimuth, receive all the
1158 * energy. Between InnerRadius/2.0f and InnerRadius, the energy starts bleeding
1159 * into the opposite speakers. Once we go below InnerRadius/2.0f, the energy
1160 * also starts to bleed into the other (non-opposite) channels, if there are
1161 * any. This computation is handled by the ComputeInnerRadiusDiffusionFactors
1162 * function. (TODO: High-accuracy version of this.)
1164 * Finally, if we're not in the equal diffusion case, we find out the azimuths
1165 * of the two closest speakers (with azimuth being defined with respect to the
1166 * listener's front orientation, in the plane normal to the listener's top
1167 * vector), as well as the azimuths of the two opposite speakers, if necessary,
1168 * and linearly interpolate with respect to the angular distance. In the equal
1169 * diffusion case, each channel receives the same value.
1171 * Note: in the case of multi-channel emitters, the distance attenuation is only
1172 * compted once, but all the azimuths and InnerRadius calculations are done per
1173 * emitter channel.
1175 * TODO: Handle InnerRadiusAngle. But honestly the X3DAudio default behaviour is
1176 * so wacky that I wonder if anybody has ever used it.
1177 * -Adrien
1179 static inline void CalculateMatrix(
1180 uint32_t ChannelMask,
1181 uint32_t Flags,
1182 const F3DAUDIO_LISTENER *pListener,
1183 const F3DAUDIO_EMITTER *pEmitter,
1184 uint32_t SrcChannelCount,
1185 uint32_t DstChannelCount,
1186 F3DAUDIO_VECTOR emitterToListener,
1187 float eToLDistance,
1188 float normalizedDistance,
1189 float* MatrixCoefficients
1191 uint32_t iEC;
1192 float curEmAzimuth;
1193 const ConfigInfo* curConfig = GetConfigInfo(ChannelMask);
1194 float attenuation = ComputeDistanceAttenuation(
1195 normalizedDistance,
1196 pEmitter->pVolumeCurve
1198 /* TODO: this could be skipped if the destination has no LFE */
1199 float LFEattenuation = ComputeDistanceAttenuation(
1200 normalizedDistance,
1201 pEmitter->pLFECurve
1204 F3DAUDIO_VECTOR listenerToEmitter;
1205 F3DAUDIO_VECTOR listenerToEmChannel;
1206 F3DAUDIO_BASIS listenerBasis;
1208 /* Note: For both cone calculations, the angle might be NaN or infinite
1209 * if distance == 0... ComputeConeParameter *does* check for this
1210 * special case. It is necessary that we still go through the
1211 * ComputeConeParameter function, because omnidirectional cones might
1212 * give either InnerVolume or OuterVolume.
1213 * -Adrien
1215 if (pListener->pCone)
1217 /* Negate the dot product because we need listenerToEmitter in
1218 * this case
1219 * -Adrien
1221 const float angle = -FAudio_acosf(
1222 VectorDot(pListener->OrientFront, emitterToListener) /
1223 eToLDistance
1226 const float listenerConeParam = ComputeConeParameter(
1227 eToLDistance,
1228 angle,
1229 pListener->pCone->InnerAngle,
1230 pListener->pCone->OuterAngle,
1231 pListener->pCone->InnerVolume,
1232 pListener->pCone->OuterVolume
1234 attenuation *= listenerConeParam;
1235 LFEattenuation *= listenerConeParam;
1238 /* See note above. */
1239 if (pEmitter->pCone && pEmitter->ChannelCount == 1)
1241 const float angle = FAudio_acosf(
1242 VectorDot(pEmitter->OrientFront, emitterToListener) /
1243 eToLDistance
1246 const float emitterConeParam = ComputeConeParameter(
1247 eToLDistance,
1248 angle,
1249 pEmitter->pCone->InnerAngle,
1250 pEmitter->pCone->OuterAngle,
1251 pEmitter->pCone->InnerVolume,
1252 pEmitter->pCone->OuterVolume
1254 attenuation *= emitterConeParam;
1257 FAudio_zero(MatrixCoefficients, sizeof(float) * SrcChannelCount * DstChannelCount);
1259 /* In the SPEAKER_MONO case, we can skip all energy diffusion calculation. */
1260 if (DstChannelCount == 1)
1262 for (iEC = 0; iEC < pEmitter->ChannelCount; iEC += 1)
1264 curEmAzimuth = 0.0f;
1265 if (pEmitter->pChannelAzimuths)
1267 curEmAzimuth = pEmitter->pChannelAzimuths[iEC];
1270 /* The MONO setup doesn't have an LFE speaker. */
1271 if (curEmAzimuth != F3DAUDIO_2PI)
1273 MatrixCoefficients[iEC] = attenuation;
1277 else
1279 listenerToEmitter = VectorScale(emitterToListener, -1.0f);
1281 /* Remember here that the coordinate system is Left-Handed. */
1282 listenerBasis.front = pListener->OrientFront;
1283 listenerBasis.right = VectorCross(pListener->OrientTop, pListener->OrientFront);
1284 listenerBasis.top = pListener->OrientTop;
1287 /* Handling the mono-channel emitter case separately is easier
1288 * than having it as a separate case of a for-loop; indeed, in
1289 * this case, we need to ignore the non-relevant values from the
1290 * emitter, _even if they're set_.
1292 if (pEmitter->ChannelCount == 1)
1294 listenerToEmChannel = listenerToEmitter;
1296 ComputeEmitterChannelCoefficients(
1297 curConfig,
1298 &listenerBasis,
1299 pEmitter->InnerRadius,
1300 listenerToEmChannel,
1301 attenuation,
1302 LFEattenuation,
1303 Flags,
1304 0 /* currentChannel */,
1305 1 /* numSrcChannels */,
1306 MatrixCoefficients
1309 else /* Multi-channel emitter case. */
1311 const F3DAUDIO_VECTOR emitterRight = VectorCross(pEmitter->OrientTop, pEmitter->OrientFront);
1313 for (iEC = 0; iEC < pEmitter->ChannelCount; iEC += 1)
1315 const float emChAzimuth = pEmitter->pChannelAzimuths[iEC];
1317 /* LFEs are easy enough to deal with; we can
1318 * just do them separately.
1320 if (emChAzimuth == F3DAUDIO_2PI)
1322 MatrixCoefficients[curConfig->LFSpeakerIdx * pEmitter->ChannelCount + iEC] = LFEattenuation;
1324 else
1326 /* First compute the emitter channel
1327 * vector relative to the emitter base...
1329 const F3DAUDIO_VECTOR emitterBaseToChannel = VectorAdd(
1330 VectorScale(pEmitter->OrientFront, pEmitter->ChannelRadius * FAudio_cosf(emChAzimuth)),
1331 VectorScale(emitterRight, pEmitter->ChannelRadius * FAudio_sinf(emChAzimuth))
1333 /* ... then translate. */
1334 listenerToEmChannel = VectorAdd(
1335 listenerToEmitter,
1336 emitterBaseToChannel
1339 ComputeEmitterChannelCoefficients(
1340 curConfig,
1341 &listenerBasis,
1342 pEmitter->InnerRadius,
1343 listenerToEmChannel,
1344 attenuation,
1345 LFEattenuation,
1346 Flags,
1347 iEC,
1348 pEmitter->ChannelCount,
1349 MatrixCoefficients
1358 /* TODO: add post check to validate values
1359 * (sum < 1, all values > 0, no Inf / NaN..
1360 * Sum can be >1 when cone or curve is set to a gain!
1361 * Perhaps under a paranoid check disabled by default.
1366 * OTHER CALCULATIONS
1369 /* DopplerPitchScalar
1370 * Adapted from algorithm published as a part of the webaudio specification:
1371 * https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/specification.html#Spatialization-doppler-shift
1372 * -Chad
1374 static inline void CalculateDoppler(
1375 float SpeedOfSound,
1376 const F3DAUDIO_LISTENER* pListener,
1377 const F3DAUDIO_EMITTER* pEmitter,
1378 F3DAUDIO_VECTOR emitterToListener,
1379 float eToLDistance,
1380 float* listenerVelocityComponent,
1381 float* emitterVelocityComponent,
1382 float* DopplerFactor
1384 float scaledSpeedOfSound;
1385 *DopplerFactor = 1.0f;
1387 /* Project... */
1388 if (eToLDistance != 0.0f)
1390 *listenerVelocityComponent =
1391 VectorDot(emitterToListener, pListener->Velocity) / eToLDistance;
1392 *emitterVelocityComponent =
1393 VectorDot(emitterToListener, pEmitter->Velocity) / eToLDistance;
1395 else
1397 *listenerVelocityComponent = 0.0f;
1398 *emitterVelocityComponent = 0.0f;
1401 if (pEmitter->DopplerScaler > 0.0f)
1403 scaledSpeedOfSound = SpeedOfSound / pEmitter->DopplerScaler;
1405 /* Clamp... */
1406 *listenerVelocityComponent = FAudio_min(
1407 *listenerVelocityComponent,
1408 scaledSpeedOfSound
1410 *emitterVelocityComponent = FAudio_min(
1411 *emitterVelocityComponent,
1412 scaledSpeedOfSound
1415 /* ... then Multiply. */
1416 *DopplerFactor = (
1417 SpeedOfSound - pEmitter->DopplerScaler * *listenerVelocityComponent
1418 ) / (
1419 SpeedOfSound - pEmitter->DopplerScaler * *emitterVelocityComponent
1421 if (isnan(*DopplerFactor)) /* If emitter/listener are at the same pos... */
1423 *DopplerFactor = 1.0f;
1426 /* Limit the pitch shifting to 2 octaves up and 1 octave down */
1427 *DopplerFactor = FAudio_clamp(
1428 *DopplerFactor,
1429 0.5f,
1430 4.0f
1435 void F3DAudioCalculate(
1436 const F3DAUDIO_HANDLE Instance,
1437 const F3DAUDIO_LISTENER *pListener,
1438 const F3DAUDIO_EMITTER *pEmitter,
1439 uint32_t Flags,
1440 F3DAUDIO_DSP_SETTINGS *pDSPSettings
1442 uint32_t i;
1443 F3DAUDIO_VECTOR emitterToListener;
1444 float eToLDistance, normalizedDistance, dp;
1446 #define DEFAULT_POINTS(name, x1, y1, x2, y2) \
1447 static F3DAUDIO_DISTANCE_CURVE_POINT name##Points[2] = \
1449 { x1, y1 }, \
1450 { x2, y2 } \
1451 }; \
1452 static F3DAUDIO_DISTANCE_CURVE name##Default = \
1454 (F3DAUDIO_DISTANCE_CURVE_POINT*) &name##Points[0], 2 \
1456 DEFAULT_POINTS(lpfDirect, 0.0f, 1.0f, 1.0f, 0.75f)
1457 DEFAULT_POINTS(lpfReverb, 0.0f, 0.75f, 1.0f, 0.75f)
1458 DEFAULT_POINTS(reverb, 0.0f, 1.0f, 1.0f, 0.0f)
1459 #undef DEFAULT_POINTS
1461 /* For XACT, this calculates "Distance" */
1462 emitterToListener = VectorSub(pListener->Position, pEmitter->Position);
1463 eToLDistance = VectorLength(emitterToListener);
1464 pDSPSettings->EmitterToListenerDistance = eToLDistance;
1466 F3DAudioCheckCalculateParams(Instance, pListener, pEmitter, Flags, pDSPSettings);
1468 /* This is used by MATRIX, LPF, and REVERB */
1469 normalizedDistance = eToLDistance / pEmitter->CurveDistanceScaler;
1471 if (Flags & F3DAUDIO_CALCULATE_MATRIX)
1473 CalculateMatrix(
1474 SPEAKERMASK(Instance),
1475 Flags,
1476 pListener,
1477 pEmitter,
1478 pDSPSettings->SrcChannelCount,
1479 pDSPSettings->DstChannelCount,
1480 emitterToListener,
1481 eToLDistance,
1482 normalizedDistance,
1483 pDSPSettings->pMatrixCoefficients
1487 if (Flags & F3DAUDIO_CALCULATE_LPF_DIRECT)
1489 pDSPSettings->LPFDirectCoefficient = ComputeDistanceAttenuation(
1490 normalizedDistance,
1491 (pEmitter->pLPFDirectCurve != NULL) ?
1492 pEmitter->pLPFDirectCurve :
1493 &lpfDirectDefault
1497 if (Flags & F3DAUDIO_CALCULATE_LPF_REVERB)
1499 pDSPSettings->LPFReverbCoefficient = ComputeDistanceAttenuation(
1500 normalizedDistance,
1501 (pEmitter->pLPFReverbCurve != NULL) ?
1502 pEmitter->pLPFReverbCurve :
1503 &lpfReverbDefault
1507 if (Flags & F3DAUDIO_CALCULATE_REVERB)
1509 pDSPSettings->ReverbLevel = ComputeDistanceAttenuation(
1510 normalizedDistance,
1511 (pEmitter->pReverbCurve != NULL) ?
1512 pEmitter->pReverbCurve :
1513 &reverbDefault
1517 /* For XACT, this calculates "DopplerPitchScalar" */
1518 if (Flags & F3DAUDIO_CALCULATE_DOPPLER)
1520 CalculateDoppler(
1521 SPEEDOFSOUND(Instance),
1522 pListener,
1523 pEmitter,
1524 emitterToListener,
1525 eToLDistance,
1526 &pDSPSettings->ListenerVelocityComponent,
1527 &pDSPSettings->EmitterVelocityComponent,
1528 &pDSPSettings->DopplerFactor
1532 /* For XACT, this calculates "OrientationAngle" */
1533 if (Flags & F3DAUDIO_CALCULATE_EMITTER_ANGLE)
1535 /* Determined roughly.
1536 * Below that distance, the emitter angle is considered to be PI/2.
1538 #define EMITTER_ANGLE_NULL_DISTANCE 1.2e-7
1539 if (eToLDistance < EMITTER_ANGLE_NULL_DISTANCE)
1541 pDSPSettings->EmitterToListenerAngle = F3DAUDIO_PI / 2.0f;
1543 else
1545 /* Note: pEmitter->OrientFront is normalized. */
1546 dp = VectorDot(emitterToListener, pEmitter->OrientFront) / eToLDistance;
1547 pDSPSettings->EmitterToListenerAngle = FAudio_acosf(dp);
1551 /* Unimplemented Flags */
1552 if ( (Flags & F3DAUDIO_CALCULATE_DELAY) &&
1553 SPEAKERMASK(Instance) == SPEAKER_STEREO )
1555 for (i = 0; i < pDSPSettings->DstChannelCount; i += 1)
1557 pDSPSettings->pDelayTimes[i] = 0.0f;
1559 FAudio_assert(0 && "DELAY not implemented!");
1563 /* vim: set noexpandtab shiftwidth=8 tabstop=8: */