Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
wine-mirror
GitHub Repository: wine-mirror/wine
Path: blob/master/libs/faudio/src/F3DAudio.c
4389 views
1
/* FAudio - XAudio Reimplementation for FNA
2
*
3
* Copyright (c) 2011-2024 Ethan Lee, Luigi Auriemma, and the MonoGame Team
4
*
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.
8
*
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:
12
*
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.
17
*
18
* 2. Altered source versions must be plainly marked as such, and must not be
19
* misrepresented as being the original software.
20
*
21
* 3. This notice may not be removed or altered from any source distribution.
22
*
23
* Ethan "flibitijibibo" Lee <[email protected]>
24
*
25
*/
26
27
#include "F3DAudio.h"
28
#include "FAudio_internal.h"
29
30
#include <math.h> /* ONLY USE THIS FOR isnan! */
31
#include <float.h> /* ONLY USE THIS FOR FLT_MIN/FLT_MAX! */
32
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
37
38
/* UTILITY MACROS */
39
40
#define PARAM_CHECK_OK 1
41
#define PARAM_CHECK_FAIL (!PARAM_CHECK_OK)
42
43
#define ARRAY_COUNT(x) (sizeof(x) / sizeof(x[0]))
44
45
#define LERP(a, x, y) ((1.0f - a) * x + a * y)
46
47
/* PARAMETER CHECK MACROS */
48
49
#define PARAM_CHECK(cond, msg) FAudio_assert(cond && msg)
50
51
#define POINTER_CHECK(p) \
52
PARAM_CHECK(p != NULL, "Pointer " #p " must be != NULL")
53
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")
57
58
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)
65
*/
66
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" \
72
)
73
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" \
78
)
79
80
/*************************************
81
* F3DAudioInitialize Implementation *
82
*************************************/
83
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])
90
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[] =
98
{
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,
109
};
110
uint8_t speakerMaskIsValid = 0;
111
uint32_t i;
112
113
POINTER_CHECK(instance);
114
115
for (i = 0; i < ARRAY_COUNT(kAllowedSpeakerMasks); i += 1)
116
{
117
if (SpeakerChannelMask == kAllowedSpeakerMasks[i])
118
{
119
speakerMaskIsValid = 1;
120
break;
121
}
122
}
123
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
127
*/
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."
133
);
134
135
PARAM_CHECK(SpeedOfSound >= FLT_MIN, "SpeedOfSound needs to be >= FLT_MIN");
136
137
return PARAM_CHECK_OK;
138
}
139
140
void F3DAudioInitialize(
141
uint32_t SpeakerChannelMask,
142
float SpeedOfSound,
143
F3DAUDIO_HANDLE Instance
144
) {
145
F3DAudioInitialize8(SpeakerChannelMask, SpeedOfSound, Instance);
146
}
147
148
uint32_t F3DAudioInitialize8(
149
uint32_t SpeakerChannelMask,
150
float SpeedOfSound,
151
F3DAUDIO_HANDLE Instance
152
) {
153
union
154
{
155
float f;
156
uint32_t i;
157
} epsilonHack;
158
uint32_t speakerCount = 0;
159
160
if (!F3DAudioCheckInitParams(SpeakerChannelMask, SpeedOfSound, Instance))
161
{
162
return FAUDIO_E_INVALID_CALL;
163
}
164
165
SPEAKERMASK(Instance) = SpeakerChannelMask;
166
SPEEDOFSOUND(Instance) = SpeedOfSound;
167
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;
174
175
SPEAKER_LF_INDEX(Instance) = 0xFFFFFFFF;
176
if (SpeakerChannelMask & SPEAKER_LOW_FREQUENCY)
177
{
178
if (SpeakerChannelMask & SPEAKER_FRONT_CENTER)
179
{
180
SPEAKER_LF_INDEX(Instance) = 3;
181
}
182
else
183
{
184
SPEAKER_LF_INDEX(Instance) = 2;
185
}
186
}
187
188
while (SpeakerChannelMask)
189
{
190
speakerCount += 1;
191
SpeakerChannelMask &= SpeakerChannelMask - 1;
192
}
193
SPEAKERCOUNT(Instance) = speakerCount;
194
195
return 0;
196
}
197
198
199
/************************************
200
* F3DAudioCalculate Implementation *
201
************************************/
202
203
/* VECTOR UTILITIES */
204
205
static inline F3DAUDIO_VECTOR Vec(float x, float y, float z)
206
{
207
F3DAUDIO_VECTOR res;
208
res.x = x;
209
res.y = y;
210
res.z = z;
211
return res;
212
}
213
214
#define VectorAdd(u, v) Vec(u.x + v.x, u.y + v.y, u.z + v.z)
215
216
#define VectorSub(u, v) Vec(u.x - v.x, u.y - v.y, u.z - v.z)
217
218
#define VectorScale(u, s) Vec(u.x * s, u.y * s, u.z * s)
219
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) \
224
)
225
226
#define VectorLength(v) FAudio_sqrtf( \
227
(v.x * v.x) + (v.y * v.y) + (v.z * v.z) \
228
)
229
230
#define VectorDot(u, v) ((u.x * v.x) + (u.y * v.y) + (u.z * v.z))
231
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)
236
*/
237
typedef struct F3DAUDIO_BASIS
238
{
239
F3DAUDIO_VECTOR front;
240
F3DAUDIO_VECTOR right;
241
F3DAUDIO_VECTOR top;
242
} F3DAUDIO_BASIS;
243
244
/* CHECK UTILITY FUNCTIONS */
245
246
static inline uint8_t CheckCone(F3DAUDIO_CONE *pCone)
247
{
248
if (!pCone)
249
{
250
return PARAM_CHECK_OK;
251
}
252
253
FLOAT_BETWEEN_CHECK(pCone->InnerAngle, 0.0f, F3DAUDIO_2PI);
254
FLOAT_BETWEEN_CHECK(pCone->OuterAngle, pCone->InnerAngle, F3DAUDIO_2PI);
255
256
FLOAT_BETWEEN_CHECK(pCone->InnerVolume, 0.0f, 2.0f);
257
FLOAT_BETWEEN_CHECK(pCone->OuterVolume, 0.0f, 2.0f);
258
259
FLOAT_BETWEEN_CHECK(pCone->InnerLPF, 0.0f, 1.0f);
260
FLOAT_BETWEEN_CHECK(pCone->OuterLPF, 0.0f, 1.0f);
261
262
FLOAT_BETWEEN_CHECK(pCone->InnerReverb, 0.0f, 2.0f);
263
FLOAT_BETWEEN_CHECK(pCone->OuterReverb, 0.0f, 2.0f);
264
265
return PARAM_CHECK_OK;
266
}
267
268
static inline uint8_t CheckCurve(F3DAUDIO_DISTANCE_CURVE *pCurve)
269
{
270
F3DAUDIO_DISTANCE_CURVE_POINT *points;
271
uint32_t i;
272
if (!pCurve)
273
{
274
return PARAM_CHECK_OK;
275
}
276
277
points = pCurve->pPoints;
278
POINTER_CHECK(points);
279
PARAM_CHECK(pCurve->PointCount >= 2, "Invalid number of points for curve");
280
281
for (i = 0; i < pCurve->PointCount; i += 1)
282
{
283
FLOAT_BETWEEN_CHECK(points[i].Distance, 0.0f, 1.0f);
284
}
285
286
PARAM_CHECK(
287
points[0].Distance == 0.0f,
288
"First point in the curve must be at distance 0.0f"
289
);
290
PARAM_CHECK(
291
points[pCurve->PointCount - 1].Distance == 1.0f,
292
"Last point in the curve must be at distance 1.0f"
293
);
294
295
for (i = 0; i < (pCurve->PointCount - 1); i += 1)
296
{
297
PARAM_CHECK(
298
points[i].Distance < points[i + 1].Distance,
299
"Curve points must be in strict ascending order"
300
);
301
}
302
303
return PARAM_CHECK_OK;
304
}
305
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
313
) {
314
uint32_t i, ChannelCount;
315
316
POINTER_CHECK(Instance);
317
POINTER_CHECK(pListener);
318
POINTER_CHECK(pEmitter);
319
POINTER_CHECK(pDSPSettings);
320
321
if (Flags & F3DAUDIO_CALCULATE_MATRIX)
322
{
323
POINTER_CHECK(pDSPSettings->pMatrixCoefficients);
324
}
325
if (Flags & F3DAUDIO_CALCULATE_ZEROCENTER)
326
{
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"
333
);
334
}
335
336
if (Flags & F3DAUDIO_CALCULATE_REDIRECT_TO_LFE)
337
{
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"
345
);
346
}
347
348
ChannelCount = SPEAKERCOUNT(Instance);
349
PARAM_CHECK(
350
pDSPSettings->DstChannelCount == ChannelCount,
351
"Invalid channel count, DSP settings and speaker configuration must agree"
352
);
353
PARAM_CHECK(
354
pDSPSettings->SrcChannelCount == pEmitter->ChannelCount,
355
"Invalid channel count, DSP settings and emitter must agree"
356
);
357
358
if (pListener->pCone)
359
{
360
PARAM_CHECK(
361
CheckCone(pListener->pCone) == PARAM_CHECK_OK,
362
"Invalid listener cone"
363
);
364
}
365
VECTOR_NORMAL_CHECK(pListener->OrientFront);
366
VECTOR_NORMAL_CHECK(pListener->OrientTop);
367
VECTOR_BASE_CHECK(pListener->OrientFront, pListener->OrientTop);
368
369
if (pEmitter->pCone)
370
{
371
VECTOR_NORMAL_CHECK(pEmitter->OrientFront);
372
PARAM_CHECK(
373
CheckCone(pEmitter->pCone) == PARAM_CHECK_OK,
374
"Invalid emitter cone"
375
);
376
}
377
else if (Flags & F3DAUDIO_CALCULATE_EMITTER_ANGLE)
378
{
379
VECTOR_NORMAL_CHECK(pEmitter->OrientFront);
380
}
381
if (pEmitter->ChannelCount > 1)
382
{
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);
387
}
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"
393
);
394
PARAM_CHECK(
395
pEmitter->ChannelRadius >= 0.0f,
396
"Invalid channel radius for emitter"
397
);
398
if (pEmitter->ChannelCount > 1)
399
{
400
PARAM_CHECK(
401
pEmitter->pChannelAzimuths != NULL,
402
"Invalid channel azimuths for multi-channel emitter"
403
);
404
if (pEmitter->pChannelAzimuths)
405
{
406
for (i = 0; i < pEmitter->ChannelCount; i += 1)
407
{
408
float currentAzimuth = pEmitter->pChannelAzimuths[i];
409
FLOAT_BETWEEN_CHECK(currentAzimuth, 0.0f, F3DAUDIO_2PI);
410
if (currentAzimuth == F3DAUDIO_2PI)
411
{
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"
417
);
418
}
419
}
420
}
421
}
422
FLOAT_BETWEEN_CHECK(pEmitter->CurveDistanceScaler, FLT_MIN, FLT_MAX);
423
FLOAT_BETWEEN_CHECK(pEmitter->DopplerScaler, 0.0f, FLT_MAX);
424
425
PARAM_CHECK(
426
CheckCurve(pEmitter->pVolumeCurve) == PARAM_CHECK_OK,
427
"Invalid Volume curve"
428
);
429
PARAM_CHECK(
430
CheckCurve(pEmitter->pLFECurve) == PARAM_CHECK_OK,
431
"Invalid LFE curve"
432
);
433
PARAM_CHECK(
434
CheckCurve(pEmitter->pLPFDirectCurve) == PARAM_CHECK_OK,
435
"Invalid LPFDirect curve"
436
);
437
PARAM_CHECK(
438
CheckCurve(pEmitter->pLPFReverbCurve) == PARAM_CHECK_OK,
439
"Invalid LPFReverb curve"
440
);
441
PARAM_CHECK(
442
CheckCurve(pEmitter->pReverbCurve) == PARAM_CHECK_OK,
443
"Invalid Reverb curve"
444
);
445
446
return PARAM_CHECK_OK;
447
}
448
449
/*
450
* MATRIX CALCULATION
451
*/
452
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.
455
*/
456
static inline float ComputeDistanceAttenuation(
457
float normalizedDistance,
458
F3DAUDIO_DISTANCE_CURVE *pCurve
459
) {
460
float res;
461
float alpha;
462
uint32_t n_points;
463
size_t i;
464
if (pCurve)
465
{
466
F3DAUDIO_DISTANCE_CURVE_POINT* points = pCurve->pPoints;
467
n_points = pCurve->PointCount;
468
469
/* By definition, the first point in the curve must be 0.0f
470
* -Adrien
471
*/
472
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.
475
*/
476
for (i = 1; (i < n_points) && (normalizedDistance >= points[i].Distance); i += 1);
477
if (i == n_points)
478
{
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."
483
*/
484
res = points[n_points - 1].DSPSetting;
485
}
486
else
487
{
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.
490
*/
491
alpha = (points[i].Distance - normalizedDistance) / (points[i].Distance - points[i - 1].Distance);
492
res = LERP(alpha, points[i].DSPSetting, points[i - 1].DSPSetting);
493
}
494
}
495
else
496
{
497
res = 1.0f;
498
if (normalizedDistance >= 1.0f)
499
{
500
res /= normalizedDistance;
501
}
502
}
503
return res;
504
}
505
506
static inline float ComputeConeParameter(
507
float distance,
508
float angle,
509
float innerAngle,
510
float outerAngle,
511
float innerParam,
512
float outerParam
513
) {
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
521
522
float halfInnerAngle, halfOuterAngle, alpha;
523
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."
527
*/
528
if (innerAngle == 0.0f && outerAngle == 0.0f)
529
{
530
return outerParam;
531
}
532
if (innerAngle == F3DAUDIO_2PI && outerAngle == F3DAUDIO_2PI)
533
{
534
return innerParam;
535
}
536
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)
541
{
542
return innerParam;
543
}
544
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.
548
*/
549
halfOuterAngle = outerAngle / 2.0f;
550
if (angle <= halfOuterAngle)
551
{
552
alpha = (angle - halfInnerAngle) / (halfOuterAngle - halfInnerAngle);
553
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.
561
*
562
* TODO: HIGH_ACCURACY version.
563
* -Adrien
564
*/
565
return LERP(alpha, innerParam, outerParam);
566
}
567
568
/* Otherwise, we're outside the outer angle, so we just return the outer param. */
569
return outerParam;
570
}
571
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
577
*/
578
#if 0
579
static F3DAUDIO_DISTANCE_CURVE_POINT DefaultVolumeCurvePoints[] =
580
{
581
{ 0.0f, 1.0f },
582
{ 1.0f, 0.0f }
583
};
584
static F3DAUDIO_DISTANCE_CURVE DefaultVolumeCurve =
585
{
586
DefaultVolumeCurvePoints,
587
ARRAY_COUNT(DefaultVolumeCurvePoints)
588
};
589
#endif
590
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
602
*/
603
typedef struct
604
{
605
float azimuth;
606
uint32_t matrixIdx;
607
} SpeakerInfo;
608
609
typedef struct
610
{
611
uint32_t configMask;
612
const SpeakerInfo *speakers;
613
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
617
*/
618
uint32_t numNonLFSpeakers;
619
620
int32_t LFSpeakerIdx;
621
} ConfigInfo;
622
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
627
*/
628
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)
639
640
const SpeakerInfo kMonoConfigSpeakers[] =
641
{
642
{ SPEAKER_AZIMUTH_CENTER, 0 },
643
};
644
const SpeakerInfo kStereoConfigSpeakers[] =
645
{
646
{ SPEAKER_AZIMUTH_SIDE_RIGHT, 1 },
647
{ SPEAKER_AZIMUTH_SIDE_LEFT, 0 },
648
};
649
const SpeakerInfo k2Point1ConfigSpeakers[] =
650
{
651
{ SPEAKER_AZIMUTH_SIDE_RIGHT, 1 },
652
{ SPEAKER_AZIMUTH_SIDE_LEFT, 0 },
653
};
654
const SpeakerInfo kSurroundConfigSpeakers[] =
655
{
656
{ SPEAKER_AZIMUTH_CENTER, 2 },
657
{ SPEAKER_AZIMUTH_FRONT_RIGHT, 1 },
658
{ SPEAKER_AZIMUTH_BACK_CENTER, 3 },
659
{ SPEAKER_AZIMUTH_FRONT_LEFT, 0 },
660
};
661
const SpeakerInfo kQuadConfigSpeakers[] =
662
{
663
{ SPEAKER_AZIMUTH_FRONT_RIGHT, 1 },
664
{ SPEAKER_AZIMUTH_BACK_RIGHT, 3 },
665
{ SPEAKER_AZIMUTH_BACK_LEFT, 2 },
666
{ SPEAKER_AZIMUTH_FRONT_LEFT, 0 },
667
};
668
const SpeakerInfo k4Point1ConfigSpeakers[] =
669
{
670
{ SPEAKER_AZIMUTH_FRONT_RIGHT, 1 },
671
{ SPEAKER_AZIMUTH_BACK_RIGHT, 4 },
672
{ SPEAKER_AZIMUTH_BACK_LEFT, 3 },
673
{ SPEAKER_AZIMUTH_FRONT_LEFT, 0 },
674
};
675
const SpeakerInfo k5Point1ConfigSpeakers[] =
676
{
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 },
682
};
683
const SpeakerInfo k7Point1ConfigSpeakers[] =
684
{
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 },
692
};
693
const SpeakerInfo k5Point1SurroundConfigSpeakers[] =
694
{
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 },
700
};
701
const SpeakerInfo k7Point1SurroundConfigSpeakers[] =
702
{
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 },
710
};
711
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
717
*/
718
const ConfigInfo kSpeakersConfigInfo[] =
719
{
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 },
730
};
731
732
/* A simple linear search is absolutely OK for 10 elements. */
733
static const ConfigInfo* GetConfigInfo(uint32_t speakerConfigMask)
734
{
735
uint32_t i;
736
for (i = 0; i < ARRAY_COUNT(kSpeakersConfigInfo); i += 1)
737
{
738
if (kSpeakersConfigInfo[i].configMask == speakerConfigMask)
739
{
740
return &kSpeakersConfigInfo[i];
741
}
742
}
743
744
FAudio_assert(0 && "Config info not found!");
745
return NULL;
746
}
747
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.
751
*/
752
static inline void FindSpeakerAzimuths(
753
const ConfigInfo* config,
754
float emitterAzimuth,
755
uint8_t skipCenter,
756
const SpeakerInfo **speakerInfo
757
) {
758
uint32_t i, nexti = 0;
759
float a0 = 0.0f, a1 = 0.0f;
760
761
FAudio_assert(config != NULL);
762
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).
769
*/
770
for (i = 0; i < config->numNonLFSpeakers; i += 1)
771
{
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;
776
777
if (a0 < a1)
778
{
779
if (emitterAzimuth >= a0 && emitterAzimuth < a1)
780
{
781
break;
782
}
783
}
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.)
791
*/
792
else
793
{
794
if (emitterAzimuth >= a0 || emitterAzimuth < a1)
795
{
796
break;
797
}
798
}
799
}
800
FAudio_assert(emitterAzimuth >= a0 || emitterAzimuth < a1);
801
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.
806
*/
807
if (skipCenter)
808
{
809
if (a0 == 0.0f)
810
{
811
if (i == 0)
812
{
813
i = config->numNonLFSpeakers - 1;
814
}
815
else
816
{
817
i -= 1;
818
}
819
}
820
else if (a1 == 0.0f)
821
{
822
nexti += 1;
823
if (nexti >= config->numNonLFSpeakers)
824
{
825
nexti -= config->numNonLFSpeakers;
826
}
827
}
828
}
829
speakerInfo[0] = &config->speakers[i];
830
speakerInfo[1] = &config->speakers[nexti];
831
}
832
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];
839
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:
846
*
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.
850
*
851
* For a distance below a certain threshold (DISTANCE_EQUAL_ENERGY), all
852
* speakers receive equal energy.
853
*
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.)
857
*
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.
862
*
863
* Above InnerRadius, only the two matching speakers receive sound.
864
*
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
868
*/
869
static inline void ComputeInnerRadiusDiffusionFactors(
870
float radialDistance,
871
float InnerRadius,
872
DiffusionSpeakerFactors diffusionFactors
873
) {
874
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.
878
*
879
* Note: It is SUSPICIOUSLY close to 1/sqrt(2), but I haven't figured out why.
880
* -Adrien
881
*/
882
#define DIFFUSION_LERP_MIDPOINT_VALUE 0.707107f
883
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;
893
894
normalizedRadialDist = radialDistance / actualInnerRadius;
895
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)
901
{
902
a = 1.0f;
903
ms = 0.0f;
904
os = 0.0f;
905
}
906
else if (normalizedRadialDist <= 0.5f)
907
{
908
/* Determined experimentally that this is indeed a linear law,
909
* with 100% confidence.
910
* -Adrien
911
*/
912
a = 1.0f - 2.0f * normalizedRadialDist;
913
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
920
*/
921
ms = LERP(2.0f * normalizedRadialDist, 0.0f, DIFFUSION_LERP_MIDPOINT_VALUE);
922
os = 1.0f - a - ms;
923
}
924
else if (normalizedRadialDist <= 1.0f)
925
{
926
a = 0.0f;
927
928
/* Similarly, this is a lerp based on the midpoint value; the
929
* real, high-accuracy curve also looks like a quadratic.
930
* -Adrien
931
*/
932
ms = LERP(2.0f * (normalizedRadialDist - 0.5f), DIFFUSION_LERP_MIDPOINT_VALUE, 1.0f);
933
os = 1.0f - a - ms;
934
}
935
else
936
{
937
a = 0.0f;
938
ms = 1.0f;
939
os = 0.0f;
940
}
941
diffusionFactors[DIFFUSION_SPEAKERS_ALL] = a;
942
diffusionFactors[DIFFUSION_SPEAKERS_MATCHING] = ms;
943
diffusionFactors[DIFFUSION_SPEAKERS_OPPOSITE] = os;
944
}
945
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.
953
*/
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
965
) {
966
float elevation, radialDistance;
967
F3DAUDIO_VECTOR projTopVec, projPlane;
968
uint8_t skipCenter = (flags & F3DAUDIO_CALCULATE_ZEROCENTER) ? 1 : 0;
969
DiffusionSpeakerFactors diffusionFactors = { 0.0f };
970
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;
980
981
/* We project against the listener basis' top vector to get the elevation of the
982
* current emitter channel position.
983
*/
984
elevation = VectorDot(listenerBasis->top, channelPosition);
985
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.
989
*/
990
projTopVec = VectorScale(listenerBasis->top, elevation);
991
projPlane = VectorSub(channelPosition, projTopVec);
992
radialDistance = VectorLength(projPlane);
993
994
ComputeInnerRadiusDiffusionFactors(
995
radialDistance,
996
innerRadius,
997
diffusionFactors
998
);
999
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)
1005
{
1006
nChannelsToDiffuseTo = curConfig->numNonLFSpeakers;
1007
totalEnergy = diffusionFactors[DIFFUSION_SPEAKERS_ALL] * attenuation;
1008
1009
if (skipCenter)
1010
{
1011
nChannelsToDiffuseTo -= 1;
1012
FAudio_assert(curConfig->speakers[0].azimuth == SPEAKER_AZIMUTH_CENTER);
1013
centerChannelIdx = curConfig->speakers[0].matrixIdx;
1014
}
1015
1016
energyPerChannel = totalEnergy / nChannelsToDiffuseTo;
1017
1018
for (iS = 0; iS < curConfig->numNonLFSpeakers; iS += 1)
1019
{
1020
const uint32_t curSpeakerIdx = curConfig->speakers[iS].matrixIdx;
1021
if (skipCenter && curSpeakerIdx == centerChannelIdx)
1022
{
1023
continue;
1024
}
1025
1026
pMatrixCoefficients[curSpeakerIdx * numSrcChannels + currentChannel] += energyPerChannel;
1027
}
1028
}
1029
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)
1035
{
1036
const float totalEnergy = diffusionFactors[DIFFUSION_SPEAKERS_MATCHING] * attenuation;
1037
1038
x = VectorDot(listenerBasis->front, projPlane);
1039
y = VectorDot(listenerBasis->right, projPlane);
1040
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.
1046
*/
1047
1048
/* atan2 returns [-PI, PI], but we want [0, 2PI] */
1049
emitterAzimuth = FAudio_atan2f(y, x);
1050
if (emitterAzimuth < 0.0f)
1051
{
1052
emitterAzimuth += F3DAUDIO_2PI;
1053
}
1054
1055
FindSpeakerAzimuths(curConfig, emitterAzimuth, skipCenter, infos);
1056
a0 = infos[0]->azimuth;
1057
a1 = infos[1]->azimuth;
1058
1059
/* The following code is necessary to handle the singularity in
1060
* (0 == 2PI). It'll give us a nice, well ordered interval.
1061
*/
1062
if (a0 > a1)
1063
{
1064
if (emitterAzimuth >= a0)
1065
{
1066
emitterAzimuth -= F3DAUDIO_2PI;
1067
}
1068
a0 -= F3DAUDIO_2PI;
1069
}
1070
FAudio_assert(emitterAzimuth >= a0 && emitterAzimuth <= a1);
1071
1072
val = (emitterAzimuth - a0) / (a1 - a0);
1073
1074
i0 = infos[0]->matrixIdx;
1075
i1 = infos[1]->matrixIdx;
1076
1077
pMatrixCoefficients[i0 * numSrcChannels + currentChannel] += (1.0f - val) * totalEnergy;
1078
pMatrixCoefficients[i1 * numSrcChannels + currentChannel] += ( val) * totalEnergy;
1079
}
1080
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)
1086
{
1087
/* This code is similar to the matching speakers code above. */
1088
const float totalEnergy = diffusionFactors[DIFFUSION_SPEAKERS_OPPOSITE] * attenuation;
1089
1090
x = VectorDot(listenerBasis->front, projPlane);
1091
y = VectorDot(listenerBasis->right, projPlane);
1092
1093
/* Similarly, we expect atan2 to be well behaved here. */
1094
emitterAzimuth = FAudio_atan2f(y, x);
1095
1096
/* Opposite speakers lie at azimuth + PI */
1097
emitterAzimuth += F3DAUDIO_PI;
1098
1099
/* Normalize to [0; 2PI) range. */
1100
if (emitterAzimuth < 0.0f)
1101
{
1102
emitterAzimuth += F3DAUDIO_2PI;
1103
}
1104
else if (emitterAzimuth > F3DAUDIO_2PI)
1105
{
1106
emitterAzimuth -= F3DAUDIO_2PI;
1107
}
1108
1109
FindSpeakerAzimuths(curConfig, emitterAzimuth, skipCenter, infos);
1110
a0 = infos[0]->azimuth;
1111
a1 = infos[1]->azimuth;
1112
1113
/* The following code is necessary to handle the singularity in
1114
* (0 == 2PI). It'll give us a nice, well ordered interval.
1115
*/
1116
if (a0 > a1)
1117
{
1118
if (emitterAzimuth >= a0)
1119
{
1120
emitterAzimuth -= F3DAUDIO_2PI;
1121
}
1122
a0 -= F3DAUDIO_2PI;
1123
}
1124
FAudio_assert(emitterAzimuth >= a0 && emitterAzimuth <= a1);
1125
1126
val = (emitterAzimuth - a0) / (a1 - a0);
1127
1128
i0 = infos[0]->matrixIdx;
1129
i1 = infos[1]->matrixIdx;
1130
1131
pMatrixCoefficients[i0 * numSrcChannels + currentChannel] += (1.0f - val) * totalEnergy;
1132
pMatrixCoefficients[i1 * numSrcChannels + currentChannel] += ( val) * totalEnergy;
1133
}
1134
1135
if (flags & F3DAUDIO_CALCULATE_REDIRECT_TO_LFE)
1136
{
1137
FAudio_assert(curConfig->LFSpeakerIdx != -1);
1138
pMatrixCoefficients[curConfig->LFSpeakerIdx * numSrcChannels + currentChannel] += LFEattenuation / numSrcChannels;
1139
}
1140
}
1141
1142
/* Calculations consist of several orthogonal steps that compose multiplicatively:
1143
*
1144
* First, we compute the attenuations (volume and LFE) due to distance, which
1145
* may involve an optional volume and/or LFE volume curve.
1146
*
1147
* Then, we compute those due to optional cones.
1148
*
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.)
1163
*
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.
1170
*
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.
1174
*
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
1178
*/
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
1190
) {
1191
uint32_t iEC;
1192
float curEmAzimuth;
1193
const ConfigInfo* curConfig = GetConfigInfo(ChannelMask);
1194
float attenuation = ComputeDistanceAttenuation(
1195
normalizedDistance,
1196
pEmitter->pVolumeCurve
1197
);
1198
/* TODO: this could be skipped if the destination has no LFE */
1199
float LFEattenuation = ComputeDistanceAttenuation(
1200
normalizedDistance,
1201
pEmitter->pLFECurve
1202
);
1203
1204
F3DAUDIO_VECTOR listenerToEmitter;
1205
F3DAUDIO_VECTOR listenerToEmChannel;
1206
F3DAUDIO_BASIS listenerBasis;
1207
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
1214
*/
1215
if (pListener->pCone)
1216
{
1217
/* Negate the dot product because we need listenerToEmitter in
1218
* this case
1219
* -Adrien
1220
*/
1221
const float angle = -FAudio_acosf(
1222
VectorDot(pListener->OrientFront, emitterToListener) /
1223
eToLDistance
1224
);
1225
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
1233
);
1234
attenuation *= listenerConeParam;
1235
LFEattenuation *= listenerConeParam;
1236
}
1237
1238
/* See note above. */
1239
if (pEmitter->pCone && pEmitter->ChannelCount == 1)
1240
{
1241
const float angle = FAudio_acosf(
1242
VectorDot(pEmitter->OrientFront, emitterToListener) /
1243
eToLDistance
1244
);
1245
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
1253
);
1254
attenuation *= emitterConeParam;
1255
}
1256
1257
FAudio_zero(MatrixCoefficients, sizeof(float) * SrcChannelCount * DstChannelCount);
1258
1259
/* In the SPEAKER_MONO case, we can skip all energy diffusion calculation. */
1260
if (DstChannelCount == 1)
1261
{
1262
for (iEC = 0; iEC < pEmitter->ChannelCount; iEC += 1)
1263
{
1264
curEmAzimuth = 0.0f;
1265
if (pEmitter->pChannelAzimuths)
1266
{
1267
curEmAzimuth = pEmitter->pChannelAzimuths[iEC];
1268
}
1269
1270
/* The MONO setup doesn't have an LFE speaker. */
1271
if (curEmAzimuth != F3DAUDIO_2PI)
1272
{
1273
MatrixCoefficients[iEC] = attenuation;
1274
}
1275
}
1276
}
1277
else if (curConfig != NULL)
1278
{
1279
listenerToEmitter = VectorScale(emitterToListener, -1.0f);
1280
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;
1285
1286
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_.
1291
*/
1292
if (pEmitter->ChannelCount == 1)
1293
{
1294
listenerToEmChannel = listenerToEmitter;
1295
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
1307
);
1308
}
1309
else /* Multi-channel emitter case. */
1310
{
1311
const F3DAUDIO_VECTOR emitterRight = VectorCross(pEmitter->OrientTop, pEmitter->OrientFront);
1312
1313
for (iEC = 0; iEC < pEmitter->ChannelCount; iEC += 1)
1314
{
1315
const float emChAzimuth = pEmitter->pChannelAzimuths[iEC];
1316
1317
/* LFEs are easy enough to deal with; we can
1318
* just do them separately.
1319
*/
1320
if (emChAzimuth == F3DAUDIO_2PI)
1321
{
1322
MatrixCoefficients[curConfig->LFSpeakerIdx * pEmitter->ChannelCount + iEC] = LFEattenuation;
1323
}
1324
else
1325
{
1326
/* First compute the emitter channel
1327
* vector relative to the emitter base...
1328
*/
1329
const F3DAUDIO_VECTOR emitterBaseToChannel = VectorAdd(
1330
VectorScale(pEmitter->OrientFront, pEmitter->ChannelRadius * FAudio_cosf(emChAzimuth)),
1331
VectorScale(emitterRight, pEmitter->ChannelRadius * FAudio_sinf(emChAzimuth))
1332
);
1333
/* ... then translate. */
1334
listenerToEmChannel = VectorAdd(
1335
listenerToEmitter,
1336
emitterBaseToChannel
1337
);
1338
1339
ComputeEmitterChannelCoefficients(
1340
curConfig,
1341
&listenerBasis,
1342
pEmitter->InnerRadius,
1343
listenerToEmChannel,
1344
attenuation,
1345
LFEattenuation,
1346
Flags,
1347
iEC,
1348
pEmitter->ChannelCount,
1349
MatrixCoefficients
1350
);
1351
}
1352
}
1353
}
1354
}
1355
else
1356
{
1357
FAudio_assert(0 && "Config info not found!");
1358
}
1359
1360
/* TODO: add post check to validate values
1361
* (sum < 1, all values > 0, no Inf / NaN..
1362
* Sum can be >1 when cone or curve is set to a gain!
1363
* Perhaps under a paranoid check disabled by default.
1364
*/
1365
}
1366
1367
/*
1368
* OTHER CALCULATIONS
1369
*/
1370
1371
/* DopplerPitchScalar
1372
* Adapted from algorithm published as a part of the webaudio specification:
1373
* https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/specification.html#Spatialization-doppler-shift
1374
* -Chad
1375
*/
1376
static inline void CalculateDoppler(
1377
float SpeedOfSound,
1378
const F3DAUDIO_LISTENER* pListener,
1379
const F3DAUDIO_EMITTER* pEmitter,
1380
F3DAUDIO_VECTOR emitterToListener,
1381
float eToLDistance,
1382
float* listenerVelocityComponent,
1383
float* emitterVelocityComponent,
1384
float* DopplerFactor
1385
) {
1386
float scaledSpeedOfSound;
1387
*DopplerFactor = 1.0f;
1388
1389
/* Project... */
1390
if (eToLDistance != 0.0f)
1391
{
1392
*listenerVelocityComponent =
1393
VectorDot(emitterToListener, pListener->Velocity) / eToLDistance;
1394
*emitterVelocityComponent =
1395
VectorDot(emitterToListener, pEmitter->Velocity) / eToLDistance;
1396
}
1397
else
1398
{
1399
*listenerVelocityComponent = 0.0f;
1400
*emitterVelocityComponent = 0.0f;
1401
}
1402
1403
if (pEmitter->DopplerScaler > 0.0f)
1404
{
1405
scaledSpeedOfSound = SpeedOfSound / pEmitter->DopplerScaler;
1406
1407
/* Clamp... */
1408
*listenerVelocityComponent = FAudio_min(
1409
*listenerVelocityComponent,
1410
scaledSpeedOfSound
1411
);
1412
*emitterVelocityComponent = FAudio_min(
1413
*emitterVelocityComponent,
1414
scaledSpeedOfSound
1415
);
1416
1417
/* ... then Multiply. */
1418
*DopplerFactor = (
1419
SpeedOfSound - pEmitter->DopplerScaler * *listenerVelocityComponent
1420
) / (
1421
SpeedOfSound - pEmitter->DopplerScaler * *emitterVelocityComponent
1422
);
1423
if (isnan(*DopplerFactor)) /* If emitter/listener are at the same pos... */
1424
{
1425
*DopplerFactor = 1.0f;
1426
}
1427
1428
/* Limit the pitch shifting to 2 octaves up and 1 octave down */
1429
*DopplerFactor = FAudio_clamp(
1430
*DopplerFactor,
1431
0.5f,
1432
4.0f
1433
);
1434
}
1435
}
1436
1437
void F3DAudioCalculate(
1438
const F3DAUDIO_HANDLE Instance,
1439
const F3DAUDIO_LISTENER *pListener,
1440
const F3DAUDIO_EMITTER *pEmitter,
1441
uint32_t Flags,
1442
F3DAUDIO_DSP_SETTINGS *pDSPSettings
1443
) {
1444
uint32_t i;
1445
F3DAUDIO_VECTOR emitterToListener;
1446
float eToLDistance, normalizedDistance, dp;
1447
1448
#define DEFAULT_POINTS(name, x1, y1, x2, y2) \
1449
static F3DAUDIO_DISTANCE_CURVE_POINT name##Points[2] = \
1450
{ \
1451
{ x1, y1 }, \
1452
{ x2, y2 } \
1453
}; \
1454
static F3DAUDIO_DISTANCE_CURVE name##Default = \
1455
{ \
1456
(F3DAUDIO_DISTANCE_CURVE_POINT*) &name##Points[0], 2 \
1457
};
1458
DEFAULT_POINTS(lpfDirect, 0.0f, 1.0f, 1.0f, 0.75f)
1459
DEFAULT_POINTS(lpfReverb, 0.0f, 0.75f, 1.0f, 0.75f)
1460
DEFAULT_POINTS(reverb, 0.0f, 1.0f, 1.0f, 0.0f)
1461
#undef DEFAULT_POINTS
1462
1463
/* For XACT, this calculates "Distance" */
1464
emitterToListener = VectorSub(pListener->Position, pEmitter->Position);
1465
eToLDistance = VectorLength(emitterToListener);
1466
pDSPSettings->EmitterToListenerDistance = eToLDistance;
1467
1468
F3DAudioCheckCalculateParams(Instance, pListener, pEmitter, Flags, pDSPSettings);
1469
1470
/* This is used by MATRIX, LPF, and REVERB */
1471
normalizedDistance = eToLDistance / pEmitter->CurveDistanceScaler;
1472
1473
if (Flags & F3DAUDIO_CALCULATE_MATRIX)
1474
{
1475
CalculateMatrix(
1476
SPEAKERMASK(Instance),
1477
Flags,
1478
pListener,
1479
pEmitter,
1480
pDSPSettings->SrcChannelCount,
1481
pDSPSettings->DstChannelCount,
1482
emitterToListener,
1483
eToLDistance,
1484
normalizedDistance,
1485
pDSPSettings->pMatrixCoefficients
1486
);
1487
}
1488
1489
if (Flags & F3DAUDIO_CALCULATE_LPF_DIRECT)
1490
{
1491
pDSPSettings->LPFDirectCoefficient = ComputeDistanceAttenuation(
1492
normalizedDistance,
1493
(pEmitter->pLPFDirectCurve != NULL) ?
1494
pEmitter->pLPFDirectCurve :
1495
&lpfDirectDefault
1496
);
1497
}
1498
1499
if (Flags & F3DAUDIO_CALCULATE_LPF_REVERB)
1500
{
1501
pDSPSettings->LPFReverbCoefficient = ComputeDistanceAttenuation(
1502
normalizedDistance,
1503
(pEmitter->pLPFReverbCurve != NULL) ?
1504
pEmitter->pLPFReverbCurve :
1505
&lpfReverbDefault
1506
);
1507
}
1508
1509
if (Flags & F3DAUDIO_CALCULATE_REVERB)
1510
{
1511
pDSPSettings->ReverbLevel = ComputeDistanceAttenuation(
1512
normalizedDistance,
1513
(pEmitter->pReverbCurve != NULL) ?
1514
pEmitter->pReverbCurve :
1515
&reverbDefault
1516
);
1517
}
1518
1519
/* For XACT, this calculates "DopplerPitchScalar" */
1520
if (Flags & F3DAUDIO_CALCULATE_DOPPLER)
1521
{
1522
CalculateDoppler(
1523
SPEEDOFSOUND(Instance),
1524
pListener,
1525
pEmitter,
1526
emitterToListener,
1527
eToLDistance,
1528
&pDSPSettings->ListenerVelocityComponent,
1529
&pDSPSettings->EmitterVelocityComponent,
1530
&pDSPSettings->DopplerFactor
1531
);
1532
}
1533
1534
/* For XACT, this calculates "OrientationAngle" */
1535
if (Flags & F3DAUDIO_CALCULATE_EMITTER_ANGLE)
1536
{
1537
/* Determined roughly.
1538
* Below that distance, the emitter angle is considered to be PI/2.
1539
*/
1540
#define EMITTER_ANGLE_NULL_DISTANCE 1.2e-7
1541
if (eToLDistance < EMITTER_ANGLE_NULL_DISTANCE)
1542
{
1543
pDSPSettings->EmitterToListenerAngle = F3DAUDIO_PI / 2.0f;
1544
}
1545
else
1546
{
1547
/* Note: pEmitter->OrientFront is normalized. */
1548
dp = VectorDot(emitterToListener, pEmitter->OrientFront) / eToLDistance;
1549
pDSPSettings->EmitterToListenerAngle = FAudio_acosf(dp);
1550
}
1551
}
1552
1553
/* Unimplemented Flags */
1554
if ( (Flags & F3DAUDIO_CALCULATE_DELAY) &&
1555
SPEAKERMASK(Instance) == SPEAKER_STEREO )
1556
{
1557
for (i = 0; i < pDSPSettings->DstChannelCount; i += 1)
1558
{
1559
pDSPSettings->pDelayTimes[i] = 0.0f;
1560
}
1561
FAudio_assert(0 && "DELAY not implemented!");
1562
}
1563
}
1564
1565
/* vim: set noexpandtab shiftwidth=8 tabstop=8: */
1566
1567