|
|
/* FAudio - XAudio Reimplementation for FNA
|
|
|
*
|
|
|
* Copyright (c) 2011-2020 Ethan Lee, Luigi Auriemma, and the MonoGame Team
|
|
|
*
|
|
|
* This software is provided 'as-is', without any express or implied warranty.
|
|
|
* In no event will the authors be held liable for any damages arising from
|
|
|
* the use of this software.
|
|
|
*
|
|
|
* Permission is granted to anyone to use this software for any purpose,
|
|
|
* including commercial applications, and to alter it and redistribute it
|
|
|
* freely, subject to the following restrictions:
|
|
|
*
|
|
|
* 1. The origin of this software must not be misrepresented; you must not
|
|
|
* claim that you wrote the original software. If you use this software in a
|
|
|
* product, an acknowledgment in the product documentation would be
|
|
|
* appreciated but is not required.
|
|
|
*
|
|
|
* 2. Altered source versions must be plainly marked as such, and must not be
|
|
|
* misrepresented as being the original software.
|
|
|
*
|
|
|
* 3. This notice may not be removed or altered from any source distribution.
|
|
|
*
|
|
|
* Ethan "flibitijibibo" Lee <flibitijibibo@flibitijibibo.com>
|
|
|
*
|
|
|
*/
|
|
|
|
|
|
#include "F3DAudio.h"
|
|
|
#include "FAudio_internal.h"
|
|
|
|
|
|
#include <math.h> /* ONLY USE THIS FOR isnan! */
|
|
|
#include <float.h> /* ONLY USE THIS FOR FLT_MIN/FLT_MAX! */
|
|
|
|
|
|
/* VS2010 doesn't define isnan (which is C99), so here it is. */
|
|
|
#if defined(_MSC_VER) && !defined(isnan)
|
|
|
#define isnan(x) _isnan(x)
|
|
|
#endif
|
|
|
|
|
|
/* UTILITY MACROS */
|
|
|
|
|
|
#define PARAM_CHECK_OK 1
|
|
|
#define PARAM_CHECK_FAIL (!PARAM_CHECK_OK)
|
|
|
|
|
|
#define ARRAY_COUNT(x) (sizeof(x) / sizeof(x[0]))
|
|
|
|
|
|
#define LERP(a, x, y) ((1.0f - a) * x + a * y)
|
|
|
|
|
|
/* PARAMETER CHECK MACROS */
|
|
|
|
|
|
#define PARAM_CHECK(cond, msg) FAudio_assert(cond && msg)
|
|
|
|
|
|
#define POINTER_CHECK(p) \
|
|
|
PARAM_CHECK(p != NULL, "Pointer " #p " must be != NULL")
|
|
|
|
|
|
#define FLOAT_BETWEEN_CHECK(f, a, b) \
|
|
|
PARAM_CHECK(f >= a, "Value" #f " is too low"); \
|
|
|
PARAM_CHECK(f <= b, "Value" #f " is too big")
|
|
|
|
|
|
|
|
|
/* Quote X3DAUDIO docs:
|
|
|
* "To be considered orthonormal, a pair of vectors must have a magnitude of
|
|
|
* 1 +- 1x10-5 and a dot product of 0 +- 1x10-5."
|
|
|
* VECTOR_NORMAL_CHECK verifies that vectors are normal (i.e. have norm 1 +- 1x10-5)
|
|
|
* VECTOR_BASE_CHECK verifies that a pair of vectors are orthogonal (i.e. their dot
|
|
|
* product is 0 +- 1x10-5)
|
|
|
*/
|
|
|
|
|
|
/* TODO: Switch to square length (to save CPU) */
|
|
|
#define VECTOR_NORMAL_CHECK(v) \
|
|
|
PARAM_CHECK( \
|
|
|
FAudio_fabsf(VectorLength(v) - 1.0f) <= 1e-5f, \
|
|
|
"Vector " #v " isn't normal" \
|
|
|
)
|
|
|
|
|
|
#define VECTOR_BASE_CHECK(u, v) \
|
|
|
PARAM_CHECK( \
|
|
|
FAudio_fabsf(VectorDot(u, v)) <= 1e-5f, \
|
|
|
"Vector u and v have non-negligible dot product" \
|
|
|
)
|
|
|
|
|
|
/*************************************
|
|
|
* F3DAudioInitialize Implementation *
|
|
|
*************************************/
|
|
|
|
|
|
/* F3DAUDIO_HANDLE Structure */
|
|
|
#define SPEAKERMASK(Instance) *((uint32_t*) &Instance[0])
|
|
|
#define SPEAKERCOUNT(Instance) *((uint32_t*) &Instance[4])
|
|
|
#define SPEAKER_LF_INDEX(Instance) *((uint32_t*) &Instance[8])
|
|
|
#define SPEEDOFSOUND(Instance) *((float*) &Instance[12])
|
|
|
#define SPEEDOFSOUNDEPSILON(Instance) *((float*) &Instance[16])
|
|
|
|
|
|
/* Export for unit tests */
|
|
|
F3DAUDIOAPI uint32_t F3DAudioCheckInitParams(
|
|
|
uint32_t SpeakerChannelMask,
|
|
|
float SpeedOfSound,
|
|
|
F3DAUDIO_HANDLE instance
|
|
|
) {
|
|
|
const uint32_t kAllowedSpeakerMasks[] =
|
|
|
{
|
|
|
SPEAKER_MONO,
|
|
|
SPEAKER_STEREO,
|
|
|
SPEAKER_2POINT1,
|
|
|
SPEAKER_QUAD,
|
|
|
SPEAKER_SURROUND,
|
|
|
SPEAKER_4POINT1,
|
|
|
SPEAKER_5POINT1,
|
|
|
SPEAKER_5POINT1_SURROUND,
|
|
|
SPEAKER_7POINT1,
|
|
|
SPEAKER_7POINT1_SURROUND,
|
|
|
};
|
|
|
uint8_t speakerMaskIsValid = 0;
|
|
|
uint32_t i;
|
|
|
|
|
|
POINTER_CHECK(instance);
|
|
|
|
|
|
for (i = 0; i < ARRAY_COUNT(kAllowedSpeakerMasks); i += 1)
|
|
|
{
|
|
|
if (SpeakerChannelMask == kAllowedSpeakerMasks[i])
|
|
|
{
|
|
|
speakerMaskIsValid = 1;
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/* The docs don't clearly say it, but the debug dll does check that
|
|
|
* we're exactly in one of the allowed speaker configurations.
|
|
|
* -Adrien
|
|
|
*/
|
|
|
PARAM_CHECK(
|
|
|
speakerMaskIsValid == 1,
|
|
|
"SpeakerChannelMask is invalid. Needs to be one of"
|
|
|
" MONO, STEREO, QUAD, 2POINT1, 4POINT1, 5POINT1, 7POINT1,"
|
|
|
" SURROUND, 5POINT1_SURROUND, or 7POINT1_SURROUND."
|
|
|
);
|
|
|
|
|
|
PARAM_CHECK(SpeedOfSound >= FLT_MIN, "SpeedOfSound needs to be >= FLT_MIN");
|
|
|
|
|
|
return PARAM_CHECK_OK;
|
|
|
}
|
|
|
|
|
|
void F3DAudioInitialize(
|
|
|
uint32_t SpeakerChannelMask,
|
|
|
float SpeedOfSound,
|
|
|
F3DAUDIO_HANDLE Instance
|
|
|
) {
|
|
|
F3DAudioInitialize8(SpeakerChannelMask, SpeedOfSound, Instance);
|
|
|
}
|
|
|
|
|
|
uint32_t F3DAudioInitialize8(
|
|
|
uint32_t SpeakerChannelMask,
|
|
|
float SpeedOfSound,
|
|
|
F3DAUDIO_HANDLE Instance
|
|
|
) {
|
|
|
union
|
|
|
{
|
|
|
float f;
|
|
|
uint32_t i;
|
|
|
} epsilonHack;
|
|
|
uint32_t speakerCount = 0;
|
|
|
|
|
|
if (!F3DAudioCheckInitParams(SpeakerChannelMask, SpeedOfSound, Instance))
|
|
|
{
|
|
|
return FAUDIO_E_INVALID_CALL;
|
|
|
}
|
|
|
|
|
|
SPEAKERMASK(Instance) = SpeakerChannelMask;
|
|
|
SPEEDOFSOUND(Instance) = SpeedOfSound;
|
|
|
|
|
|
/* "Convert" raw float to int... */
|
|
|
epsilonHack.f = SpeedOfSound;
|
|
|
/* ... Subtract epsilon value... */
|
|
|
epsilonHack.i -= 1;
|
|
|
/* ... Convert back to float. */
|
|
|
SPEEDOFSOUNDEPSILON(Instance) = epsilonHack.f;
|
|
|
|
|
|
SPEAKER_LF_INDEX(Instance) = 0xFFFFFFFF;
|
|
|
if (SpeakerChannelMask & SPEAKER_LOW_FREQUENCY)
|
|
|
{
|
|
|
if (SpeakerChannelMask & SPEAKER_FRONT_CENTER)
|
|
|
{
|
|
|
SPEAKER_LF_INDEX(Instance) = 3;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
SPEAKER_LF_INDEX(Instance) = 2;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
while (SpeakerChannelMask)
|
|
|
{
|
|
|
speakerCount += 1;
|
|
|
SpeakerChannelMask &= SpeakerChannelMask - 1;
|
|
|
}
|
|
|
SPEAKERCOUNT(Instance) = speakerCount;
|
|
|
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
|
|
|
/************************************
|
|
|
* F3DAudioCalculate Implementation *
|
|
|
************************************/
|
|
|
|
|
|
/* VECTOR UTILITIES */
|
|
|
|
|
|
static inline F3DAUDIO_VECTOR Vec(float x, float y, float z)
|
|
|
{
|
|
|
F3DAUDIO_VECTOR res;
|
|
|
res.x = x;
|
|
|
res.y = y;
|
|
|
res.z = z;
|
|
|
return res;
|
|
|
}
|
|
|
|
|
|
#define VectorAdd(u, v) Vec(u.x + v.x, u.y + v.y, u.z + v.z)
|
|
|
|
|
|
#define VectorSub(u, v) Vec(u.x - v.x, u.y - v.y, u.z - v.z)
|
|
|
|
|
|
#define VectorScale(u, s) Vec(u.x * s, u.y * s, u.z * s)
|
|
|
|
|
|
#define VectorCross(u, v) Vec( \
|
|
|
(u.y * v.z) - (u.z * v.y), \
|
|
|
(u.z * v.x) - (u.x * v.z), \
|
|
|
(u.x * v.y) - (u.y * v.x) \
|
|
|
)
|
|
|
|
|
|
#define VectorLength(v) FAudio_sqrtf( \
|
|
|
(v.x * v.x) + (v.y * v.y) + (v.z * v.z) \
|
|
|
)
|
|
|
|
|
|
#define VectorDot(u, v) ((u.x * v.x) + (u.y * v.y) + (u.z * v.z))
|
|
|
|
|
|
/* This structure represent a tuple of vectors that form a left-handed basis.
|
|
|
* That is, all vectors are normal, orthogonal to each other, and taken in the
|
|
|
* order front, right, top they follow the left-hand rule.
|
|
|
* (https://en.wikipedia.org/wiki/Right-hand_rule)
|
|
|
*/
|
|
|
typedef struct F3DAUDIO_BASIS
|
|
|
{
|
|
|
F3DAUDIO_VECTOR front;
|
|
|
F3DAUDIO_VECTOR right;
|
|
|
F3DAUDIO_VECTOR top;
|
|
|
} F3DAUDIO_BASIS;
|
|
|
|
|
|
/* CHECK UTILITY FUNCTIONS */
|
|
|
|
|
|
static inline uint8_t CheckCone(F3DAUDIO_CONE *pCone)
|
|
|
{
|
|
|
if (!pCone)
|
|
|
{
|
|
|
return PARAM_CHECK_OK;
|
|
|
}
|
|
|
|
|
|
FLOAT_BETWEEN_CHECK(pCone->InnerAngle, 0.0f, F3DAUDIO_2PI);
|
|
|
FLOAT_BETWEEN_CHECK(pCone->OuterAngle, pCone->InnerAngle, F3DAUDIO_2PI);
|
|
|
|
|
|
FLOAT_BETWEEN_CHECK(pCone->InnerVolume, 0.0f, 2.0f);
|
|
|
FLOAT_BETWEEN_CHECK(pCone->OuterVolume, 0.0f, 2.0f);
|
|
|
|
|
|
FLOAT_BETWEEN_CHECK(pCone->InnerLPF, 0.0f, 1.0f);
|
|
|
FLOAT_BETWEEN_CHECK(pCone->OuterLPF, 0.0f, 1.0f);
|
|
|
|
|
|
FLOAT_BETWEEN_CHECK(pCone->InnerReverb, 0.0f, 2.0f);
|
|
|
FLOAT_BETWEEN_CHECK(pCone->OuterReverb, 0.0f, 2.0f);
|
|
|
|
|
|
return PARAM_CHECK_OK;
|
|
|
}
|
|
|
|
|
|
static inline uint8_t CheckCurve(F3DAUDIO_DISTANCE_CURVE *pCurve)
|
|
|
{
|
|
|
F3DAUDIO_DISTANCE_CURVE_POINT *points;
|
|
|
uint32_t i;
|
|
|
if (!pCurve)
|
|
|
{
|
|
|
return PARAM_CHECK_OK;
|
|
|
}
|
|
|
|
|
|
points = pCurve->pPoints;
|
|
|
POINTER_CHECK(points);
|
|
|
PARAM_CHECK(pCurve->PointCount >= 2, "Invalid number of points for curve");
|
|
|
|
|
|
for (i = 0; i < pCurve->PointCount; i += 1)
|
|
|
{
|
|
|
FLOAT_BETWEEN_CHECK(points[i].Distance, 0.0f, 1.0f);
|
|
|
}
|
|
|
|
|
|
PARAM_CHECK(
|
|
|
points[0].Distance == 0.0f,
|
|
|
"First point in the curve must be at distance 0.0f"
|
|
|
);
|
|
|
PARAM_CHECK(
|
|
|
points[pCurve->PointCount - 1].Distance == 1.0f,
|
|
|
"Last point in the curve must be at distance 1.0f"
|
|
|
);
|
|
|
|
|
|
for (i = 0; i < (pCurve->PointCount - 1); i += 1)
|
|
|
{
|
|
|
PARAM_CHECK(
|
|
|
points[i].Distance < points[i + 1].Distance,
|
|
|
"Curve points must be in strict ascending order"
|
|
|
);
|
|
|
}
|
|
|
|
|
|
return PARAM_CHECK_OK;
|
|
|
}
|
|
|
|
|
|
/* Export for unit tests */
|
|
|
F3DAUDIOAPI uint8_t F3DAudioCheckCalculateParams(
|
|
|
const F3DAUDIO_HANDLE Instance,
|
|
|
const F3DAUDIO_LISTENER *pListener,
|
|
|
const F3DAUDIO_EMITTER *pEmitter,
|
|
|
uint32_t Flags,
|
|
|
F3DAUDIO_DSP_SETTINGS *pDSPSettings
|
|
|
) {
|
|
|
uint32_t i, ChannelCount;
|
|
|
|
|
|
POINTER_CHECK(Instance);
|
|
|
POINTER_CHECK(pListener);
|
|
|
POINTER_CHECK(pEmitter);
|
|
|
POINTER_CHECK(pDSPSettings);
|
|
|
|
|
|
if (Flags & F3DAUDIO_CALCULATE_MATRIX)
|
|
|
{
|
|
|
POINTER_CHECK(pDSPSettings->pMatrixCoefficients);
|
|
|
}
|
|
|
if (Flags & F3DAUDIO_CALCULATE_ZEROCENTER)
|
|
|
{
|
|
|
const uint32_t isCalculateMatrix = (Flags & F3DAUDIO_CALCULATE_MATRIX);
|
|
|
const uint32_t hasCenter = SPEAKERMASK(Instance) & SPEAKER_FRONT_CENTER;
|
|
|
PARAM_CHECK(
|
|
|
isCalculateMatrix && hasCenter,
|
|
|
"F3DAUDIO_CALCULATE_ZEROCENTER is only valid for matrix"
|
|
|
" calculations with an output format that has a center channel"
|
|
|
);
|
|
|
}
|
|
|
|
|
|
if (Flags & F3DAUDIO_CALCULATE_REDIRECT_TO_LFE)
|
|
|
{
|
|
|
const uint32_t isCalculateMatrix = (Flags & F3DAUDIO_CALCULATE_MATRIX);
|
|
|
const uint32_t hasLF = SPEAKERMASK(Instance) & SPEAKER_LOW_FREQUENCY;
|
|
|
PARAM_CHECK(
|
|
|
isCalculateMatrix && hasLF,
|
|
|
"F3DAUDIO_CALCULATE_REDIRECT_TO_LFE is only valid for matrix"
|
|
|
" calculations with an output format that has a low-frequency"
|
|
|
" channel"
|
|
|
);
|
|
|
}
|
|
|
|
|
|
ChannelCount = SPEAKERCOUNT(Instance);
|
|
|
PARAM_CHECK(
|
|
|
pDSPSettings->DstChannelCount == ChannelCount,
|
|
|
"Invalid channel count, DSP settings and speaker configuration must agree"
|
|
|
);
|
|
|
PARAM_CHECK(
|
|
|
pDSPSettings->SrcChannelCount == pEmitter->ChannelCount,
|
|
|
"Invalid channel count, DSP settings and emitter must agree"
|
|
|
);
|
|
|
|
|
|
if (pListener->pCone)
|
|
|
{
|
|
|
PARAM_CHECK(
|
|
|
CheckCone(pListener->pCone) == PARAM_CHECK_OK,
|
|
|
"Invalid listener cone"
|
|
|
);
|
|
|
}
|
|
|
VECTOR_NORMAL_CHECK(pListener->OrientFront);
|
|
|
VECTOR_NORMAL_CHECK(pListener->OrientTop);
|
|
|
VECTOR_BASE_CHECK(pListener->OrientFront, pListener->OrientTop);
|
|
|
|
|
|
if (pEmitter->pCone)
|
|
|
{
|
|
|
VECTOR_NORMAL_CHECK(pEmitter->OrientFront);
|
|
|
PARAM_CHECK(
|
|
|
CheckCone(pEmitter->pCone) == PARAM_CHECK_OK,
|
|
|
"Invalid emitter cone"
|
|
|
);
|
|
|
}
|
|
|
else if (Flags & F3DAUDIO_CALCULATE_EMITTER_ANGLE)
|
|
|
{
|
|
|
VECTOR_NORMAL_CHECK(pEmitter->OrientFront);
|
|
|
}
|
|
|
if (pEmitter->ChannelCount > 1)
|
|
|
{
|
|
|
/* Only used for multi-channel emitters */
|
|
|
VECTOR_NORMAL_CHECK(pEmitter->OrientFront);
|
|
|
VECTOR_NORMAL_CHECK(pEmitter->OrientTop);
|
|
|
VECTOR_BASE_CHECK(pEmitter->OrientFront, pEmitter->OrientTop);
|
|
|
}
|
|
|
FLOAT_BETWEEN_CHECK(pEmitter->InnerRadius, 0.0f, FLT_MAX);
|
|
|
FLOAT_BETWEEN_CHECK(pEmitter->InnerRadiusAngle, 0.0f, F3DAUDIO_2PI / 4.0f);
|
|
|
PARAM_CHECK(
|
|
|
pEmitter->ChannelCount > 0,
|
|
|
"Invalid channel count for emitter"
|
|
|
);
|
|
|
PARAM_CHECK(
|
|
|
pEmitter->ChannelRadius >= 0.0f,
|
|
|
"Invalid channel radius for emitter"
|
|
|
);
|
|
|
if (pEmitter->ChannelCount > 1)
|
|
|
{
|
|
|
PARAM_CHECK(
|
|
|
pEmitter->pChannelAzimuths != NULL,
|
|
|
"Invalid channel azimuths for multi-channel emitter"
|
|
|
);
|
|
|
if (pEmitter->pChannelAzimuths)
|
|
|
{
|
|
|
for (i = 0; i < pEmitter->ChannelCount; i += 1)
|
|
|
{
|
|
|
float currentAzimuth = pEmitter->pChannelAzimuths[i];
|
|
|
FLOAT_BETWEEN_CHECK(currentAzimuth, 0.0f, F3DAUDIO_2PI);
|
|
|
if (currentAzimuth == F3DAUDIO_2PI)
|
|
|
{
|
|
|
PARAM_CHECK(
|
|
|
!(Flags & F3DAUDIO_CALCULATE_REDIRECT_TO_LFE),
|
|
|
"F3DAUDIO_CALCULATE_REDIRECT_TO_LFE valid only for"
|
|
|
" matrix calculations with emitters that have no LFE"
|
|
|
" channel"
|
|
|
);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
FLOAT_BETWEEN_CHECK(pEmitter->CurveDistanceScaler, FLT_MIN, FLT_MAX);
|
|
|
FLOAT_BETWEEN_CHECK(pEmitter->DopplerScaler, 0.0f, FLT_MAX);
|
|
|
|
|
|
PARAM_CHECK(
|
|
|
CheckCurve(pEmitter->pVolumeCurve) == PARAM_CHECK_OK,
|
|
|
"Invalid Volume curve"
|
|
|
);
|
|
|
PARAM_CHECK(
|
|
|
CheckCurve(pEmitter->pLFECurve) == PARAM_CHECK_OK,
|
|
|
"Invalid LFE curve"
|
|
|
);
|
|
|
PARAM_CHECK(
|
|
|
CheckCurve(pEmitter->pLPFDirectCurve) == PARAM_CHECK_OK,
|
|
|
"Invalid LPFDirect curve"
|
|
|
);
|
|
|
PARAM_CHECK(
|
|
|
CheckCurve(pEmitter->pLPFReverbCurve) == PARAM_CHECK_OK,
|
|
|
"Invalid LPFReverb curve"
|
|
|
);
|
|
|
PARAM_CHECK(
|
|
|
CheckCurve(pEmitter->pReverbCurve) == PARAM_CHECK_OK,
|
|
|
"Invalid Reverb curve"
|
|
|
);
|
|
|
|
|
|
return PARAM_CHECK_OK;
|
|
|
}
|
|
|
|
|
|
/*
|
|
|
* MATRIX CALCULATION
|
|
|
*/
|
|
|
|
|
|
/* This function computes the distance either according to a curve if pCurve
|
|
|
* isn't NULL, or according to the inverse distance law 1/d otherwise.
|
|
|
*/
|
|
|
static inline float ComputeDistanceAttenuation(
|
|
|
float normalizedDistance,
|
|
|
F3DAUDIO_DISTANCE_CURVE *pCurve
|
|
|
) {
|
|
|
float res;
|
|
|
float alpha;
|
|
|
uint32_t n_points;
|
|
|
size_t i;
|
|
|
if (pCurve)
|
|
|
{
|
|
|
F3DAUDIO_DISTANCE_CURVE_POINT* points = pCurve->pPoints;
|
|
|
n_points = pCurve->PointCount;
|
|
|
|
|
|
/* By definition, the first point in the curve must be 0.0f
|
|
|
* -Adrien
|
|
|
*/
|
|
|
|
|
|
/* We advance i up until our normalizedDistance lies between the distances of
|
|
|
* the i_th and (i-1)_th points, or we reach the last point.
|
|
|
*/
|
|
|
for (i = 1; (i < n_points) && (normalizedDistance >= points[i].Distance); i += 1);
|
|
|
if (i == n_points)
|
|
|
{
|
|
|
/* We've reached the last point, so we use its value directly.
|
|
|
* Quote X3DAUDIO docs:
|
|
|
* "If an emitter moves beyond a distance of (CurveDistanceScaler × 1.0f),
|
|
|
* the last point on the curve is used to compute the volume output level."
|
|
|
*/
|
|
|
res = points[n_points - 1].DSPSetting;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
/* We're between two points: the distance attenuation is the linear interpolation of the DSPSetting
|
|
|
* values defined by our points, according to the distance.
|
|
|
*/
|
|
|
alpha = (points[i].Distance - normalizedDistance) / (points[i].Distance - points[i - 1].Distance);
|
|
|
res = LERP(alpha, points[i].DSPSetting, points[i - 1].DSPSetting);
|
|
|
}
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
res = 1.0f;
|
|
|
if (normalizedDistance >= 1.0f)
|
|
|
{
|
|
|
res /= normalizedDistance;
|
|
|
}
|
|
|
}
|
|
|
return res;
|
|
|
}
|
|
|
|
|
|
static inline float ComputeConeParameter(
|
|
|
float distance,
|
|
|
float angle,
|
|
|
float innerAngle,
|
|
|
float outerAngle,
|
|
|
float innerParam,
|
|
|
float outerParam
|
|
|
) {
|
|
|
/* When computing whether a point lies inside a cone, X3DAUDIO first determines
|
|
|
* whether the point is close enough to the apex of the cone.
|
|
|
* If it is, the innerParam is used.
|
|
|
* The following constant is the one that is used for this distance check;
|
|
|
* It is an approximation, found by manual binary search.
|
|
|
* TODO: find the exact value of the constant via automated binary search. */
|
|
|
#define CONE_NULL_DISTANCE_TOLERANCE 1e-7
|
|
|
|
|
|
float halfInnerAngle, halfOuterAngle, alpha;
|
|
|
|
|
|
/* Quote X3DAudio.h:
|
|
|
* "Set both cone angles to 0 or X3DAUDIO_2PI for omnidirectionality using
|
|
|
* only the outer or inner values respectively."
|
|
|
*/
|
|
|
if (innerAngle == 0.0f && outerAngle == 0.0f)
|
|
|
{
|
|
|
return outerParam;
|
|
|
}
|
|
|
if (innerAngle == F3DAUDIO_2PI && outerAngle == F3DAUDIO_2PI)
|
|
|
{
|
|
|
return innerParam;
|
|
|
}
|
|
|
|
|
|
/* If we're within the inner angle, or close enough to the apex, we use
|
|
|
* the innerParam. */
|
|
|
halfInnerAngle = innerAngle / 2.0f;
|
|
|
if (distance <= CONE_NULL_DISTANCE_TOLERANCE || angle <= halfInnerAngle)
|
|
|
{
|
|
|
return innerParam;
|
|
|
}
|
|
|
|
|
|
/* If we're between the inner angle and the outer angle, we must use
|
|
|
* some interpolation of the innerParam and outerParam according to the
|
|
|
* distance between our angle and the inner and outer angles.
|
|
|
*/
|
|
|
halfOuterAngle = outerAngle / 2.0f;
|
|
|
if (angle <= halfOuterAngle)
|
|
|
{
|
|
|
alpha = (angle - halfInnerAngle) / (halfOuterAngle - halfInnerAngle);
|
|
|
|
|
|
/* Sooo... This is awkward. MSDN doesn't say anything, but
|
|
|
* X3DAudio.h says that this should be lerped. However in
|
|
|
* practice the behaviour of X3DAudio isn't a lerp at all. It's
|
|
|
* easy to see with big (InnerAngle / OuterAngle) values. If we
|
|
|
* want accurate emulation, we'll need to either find what
|
|
|
* formula they use, or use a more advanced interpolation, like
|
|
|
* tricubic.
|
|
|
*
|
|
|
* TODO: HIGH_ACCURACY version.
|
|
|
* -Adrien
|
|
|
*/
|
|
|
return LERP(alpha, innerParam, outerParam);
|
|
|
}
|
|
|
|
|
|
/* Otherwise, we're outside the outer angle, so we just return the outer param. */
|
|
|
return outerParam;
|
|
|
}
|
|
|
|
|
|
/* X3DAudio.h declares something like this, but the default (if emitter is NULL)
|
|
|
* volume curve is a *computed* inverse law, while on the other hand a curve
|
|
|
* leads to a piecewise linear function. So a "default curve" like this is
|
|
|
* pointless, not sure what X3DAudio does with it...
|
|
|
* -Adrien
|
|
|
*/
|
|
|
#if 0
|
|
|
static F3DAUDIO_DISTANCE_CURVE_POINT DefaultVolumeCurvePoints[] =
|
|
|
{
|
|
|
{ 0.0f, 1.0f },
|
|
|
{ 1.0f, 0.0f }
|
|
|
};
|
|
|
static F3DAUDIO_DISTANCE_CURVE DefaultVolumeCurve =
|
|
|
{
|
|
|
DefaultVolumeCurvePoints,
|
|
|
ARRAY_COUNT(DefaultVolumeCurvePoints)
|
|
|
};
|
|
|
#endif
|
|
|
|
|
|
/* Here we declare the azimuths of every speaker for every speaker
|
|
|
* configuration, ordered by increasing angle, as well as the index to which
|
|
|
* they map in the final matrix for their respective configuration. It had to be
|
|
|
* reverse engineered by looking at the data from various X3DAudioCalculate()
|
|
|
* matrix results for the various speaker configurations; *in particular*, the
|
|
|
* azimuths are different from the ones in F3DAudio.h (and X3DAudio.h) for
|
|
|
* SPEAKER_STEREO (which is declared has having front L and R speakers in the
|
|
|
* bit mask, but in fact has L and R *side* speakers). LF speakers are
|
|
|
* deliberately not included in the SpeakerInfo list, rather, we store the index
|
|
|
* into a separate field (with a -1 sentinel value if it has no LF speaker).
|
|
|
* -Adrien
|
|
|
*/
|
|
|
typedef struct
|
|
|
{
|
|
|
float azimuth;
|
|
|
uint32_t matrixIdx;
|
|
|
} SpeakerInfo;
|
|
|
|
|
|
typedef struct
|
|
|
{
|
|
|
uint32_t configMask;
|
|
|
const SpeakerInfo *speakers;
|
|
|
|
|
|
/* Not strictly necessary because it can be inferred from the
|
|
|
* SpeakerCount field of the F3DAUDIO_HANDLE, but makes code much
|
|
|
* cleaner and less error prone
|
|
|
*/
|
|
|
uint32_t numNonLFSpeakers;
|
|
|
|
|
|
int32_t LFSpeakerIdx;
|
|
|
} ConfigInfo;
|
|
|
|
|
|
/* It is absolutely necessary that these are stored in increasing, *positive*
|
|
|
* azimuth order (i.e. all angles between [0; 2PI]), as we'll do a linear
|
|
|
* interval search inside FindSpeakerAzimuths.
|
|
|
* -Adrien
|
|
|
*/
|
|
|
|
|
|
#define SPEAKER_AZIMUTH_CENTER 0.0f
|
|
|
#define SPEAKER_AZIMUTH_FRONT_RIGHT_OF_CENTER (F3DAUDIO_PI * 1.0f / 8.0f)
|
|
|
#define SPEAKER_AZIMUTH_FRONT_RIGHT (F3DAUDIO_PI * 1.0f / 4.0f)
|
|
|
#define SPEAKER_AZIMUTH_SIDE_RIGHT (F3DAUDIO_PI * 1.0f / 2.0f)
|
|
|
#define SPEAKER_AZIMUTH_BACK_RIGHT (F3DAUDIO_PI * 3.0f / 4.0f)
|
|
|
#define SPEAKER_AZIMUTH_BACK_CENTER F3DAUDIO_PI
|
|
|
#define SPEAKER_AZIMUTH_BACK_LEFT (F3DAUDIO_PI * 5.0f / 4.0f)
|
|
|
#define SPEAKER_AZIMUTH_SIDE_LEFT (F3DAUDIO_PI * 3.0f / 2.0f)
|
|
|
#define SPEAKER_AZIMUTH_FRONT_LEFT (F3DAUDIO_PI * 7.0f / 4.0f)
|
|
|
#define SPEAKER_AZIMUTH_FRONT_LEFT_OF_CENTER (F3DAUDIO_PI * 15.0f / 8.0f)
|
|
|
|
|
|
const SpeakerInfo kMonoConfigSpeakers[] =
|
|
|
{
|
|
|
{ SPEAKER_AZIMUTH_CENTER, 0 },
|
|
|
};
|
|
|
const SpeakerInfo kStereoConfigSpeakers[] =
|
|
|
{
|
|
|
{ SPEAKER_AZIMUTH_SIDE_RIGHT, 1 },
|
|
|
{ SPEAKER_AZIMUTH_SIDE_LEFT, 0 },
|
|
|
};
|
|
|
const SpeakerInfo k2Point1ConfigSpeakers[] =
|
|
|
{
|
|
|
{ SPEAKER_AZIMUTH_SIDE_RIGHT, 1 },
|
|
|
{ SPEAKER_AZIMUTH_SIDE_LEFT, 0 },
|
|
|
};
|
|
|
const SpeakerInfo kSurroundConfigSpeakers[] =
|
|
|
{
|
|
|
{ SPEAKER_AZIMUTH_CENTER, 2 },
|
|
|
{ SPEAKER_AZIMUTH_FRONT_RIGHT, 1 },
|
|
|
{ SPEAKER_AZIMUTH_BACK_CENTER, 3 },
|
|
|
{ SPEAKER_AZIMUTH_FRONT_LEFT, 0 },
|
|
|
};
|
|
|
const SpeakerInfo kQuadConfigSpeakers[] =
|
|
|
{
|
|
|
{ SPEAKER_AZIMUTH_FRONT_RIGHT, 1 },
|
|
|
{ SPEAKER_AZIMUTH_BACK_RIGHT, 3 },
|
|
|
{ SPEAKER_AZIMUTH_BACK_LEFT, 2 },
|
|
|
{ SPEAKER_AZIMUTH_FRONT_LEFT, 0 },
|
|
|
};
|
|
|
const SpeakerInfo k4Point1ConfigSpeakers[] =
|
|
|
{
|
|
|
{ SPEAKER_AZIMUTH_FRONT_RIGHT, 1 },
|
|
|
{ SPEAKER_AZIMUTH_BACK_RIGHT, 4 },
|
|
|
{ SPEAKER_AZIMUTH_BACK_LEFT, 3 },
|
|
|
{ SPEAKER_AZIMUTH_FRONT_LEFT, 0 },
|
|
|
};
|
|
|
const SpeakerInfo k5Point1ConfigSpeakers[] =
|
|
|
{
|
|
|
{ SPEAKER_AZIMUTH_CENTER, 2 },
|
|
|
{ SPEAKER_AZIMUTH_FRONT_RIGHT, 1 },
|
|
|
{ SPEAKER_AZIMUTH_BACK_RIGHT, 5 },
|
|
|
{ SPEAKER_AZIMUTH_BACK_LEFT, 4 },
|
|
|
{ SPEAKER_AZIMUTH_FRONT_LEFT, 0 },
|
|
|
};
|
|
|
const SpeakerInfo k7Point1ConfigSpeakers[] =
|
|
|
{
|
|
|
{ SPEAKER_AZIMUTH_CENTER, 2 },
|
|
|
{ SPEAKER_AZIMUTH_FRONT_RIGHT_OF_CENTER, 7 },
|
|
|
{ SPEAKER_AZIMUTH_FRONT_RIGHT, 1 },
|
|
|
{ SPEAKER_AZIMUTH_BACK_RIGHT, 5 },
|
|
|
{ SPEAKER_AZIMUTH_BACK_LEFT, 4 },
|
|
|
{ SPEAKER_AZIMUTH_FRONT_LEFT, 0 },
|
|
|
{ SPEAKER_AZIMUTH_FRONT_LEFT_OF_CENTER, 6 },
|
|
|
};
|
|
|
const SpeakerInfo k5Point1SurroundConfigSpeakers[] =
|
|
|
{
|
|
|
{ SPEAKER_AZIMUTH_CENTER, 2 },
|
|
|
{ SPEAKER_AZIMUTH_FRONT_RIGHT, 1 },
|
|
|
{ SPEAKER_AZIMUTH_SIDE_RIGHT, 5 },
|
|
|
{ SPEAKER_AZIMUTH_SIDE_LEFT, 4 },
|
|
|
{ SPEAKER_AZIMUTH_FRONT_LEFT, 0 },
|
|
|
};
|
|
|
const SpeakerInfo k7Point1SurroundConfigSpeakers[] =
|
|
|
{
|
|
|
{ SPEAKER_AZIMUTH_CENTER, 2 },
|
|
|
{ SPEAKER_AZIMUTH_FRONT_RIGHT, 1 },
|
|
|
{ SPEAKER_AZIMUTH_SIDE_RIGHT, 7 },
|
|
|
{ SPEAKER_AZIMUTH_BACK_RIGHT, 5 },
|
|
|
{ SPEAKER_AZIMUTH_BACK_LEFT, 4 },
|
|
|
{ SPEAKER_AZIMUTH_SIDE_LEFT, 6 },
|
|
|
{ SPEAKER_AZIMUTH_FRONT_LEFT, 0 },
|
|
|
};
|
|
|
|
|
|
/* With that organization, the index of the LF speaker into the matrix array
|
|
|
* strangely looks *exactly* like the mystery field in the F3DAUDIO_HANDLE!!
|
|
|
* We're keeping a separate field within ConfigInfo because it makes the code
|
|
|
* much cleaner, though.
|
|
|
* -Adrien
|
|
|
*/
|
|
|
const ConfigInfo kSpeakersConfigInfo[] =
|
|
|
{
|
|
|
{ SPEAKER_MONO, kMonoConfigSpeakers, ARRAY_COUNT(kMonoConfigSpeakers), -1 },
|
|
|
{ SPEAKER_STEREO, kStereoConfigSpeakers, ARRAY_COUNT(kStereoConfigSpeakers), -1 },
|
|
|
{ SPEAKER_2POINT1, k2Point1ConfigSpeakers, ARRAY_COUNT(k2Point1ConfigSpeakers), 2 },
|
|
|
{ SPEAKER_SURROUND, kSurroundConfigSpeakers, ARRAY_COUNT(kSurroundConfigSpeakers), -1 },
|
|
|
{ SPEAKER_QUAD, kQuadConfigSpeakers, ARRAY_COUNT(kQuadConfigSpeakers), -1 },
|
|
|
{ SPEAKER_4POINT1, k4Point1ConfigSpeakers, ARRAY_COUNT(k4Point1ConfigSpeakers), 2 },
|
|
|
{ SPEAKER_5POINT1, k5Point1ConfigSpeakers, ARRAY_COUNT(k5Point1ConfigSpeakers), 3 },
|
|
|
{ SPEAKER_7POINT1, k7Point1ConfigSpeakers, ARRAY_COUNT(k7Point1ConfigSpeakers), 3 },
|
|
|
{ SPEAKER_5POINT1_SURROUND, k5Point1SurroundConfigSpeakers, ARRAY_COUNT(k5Point1SurroundConfigSpeakers), 3 },
|
|
|
{ SPEAKER_7POINT1_SURROUND, k7Point1SurroundConfigSpeakers, ARRAY_COUNT(k7Point1SurroundConfigSpeakers), 3 },
|
|
|
};
|
|
|
|
|
|
/* A simple linear search is absolutely OK for 10 elements. */
|
|
|
static const ConfigInfo* GetConfigInfo(uint32_t speakerConfigMask)
|
|
|
{
|
|
|
uint32_t i;
|
|
|
for (i = 0; i < ARRAY_COUNT(kSpeakersConfigInfo); i += 1)
|
|
|
{
|
|
|
if (kSpeakersConfigInfo[i].configMask == speakerConfigMask)
|
|
|
{
|
|
|
return &kSpeakersConfigInfo[i];
|
|
|
}
|
|
|
}
|
|
|
|
|
|
FAudio_assert(0 && "Config info not found!");
|
|
|
return NULL;
|
|
|
}
|
|
|
|
|
|
/* Given a configuration, this function finds the azimuths of the two speakers
|
|
|
* between which the emitter lies. All the azimuths here are relative to the
|
|
|
* listener's base, since that's where the speakers are defined.
|
|
|
*/
|
|
|
static inline void FindSpeakerAzimuths(
|
|
|
const ConfigInfo* config,
|
|
|
float emitterAzimuth,
|
|
|
uint8_t skipCenter,
|
|
|
const SpeakerInfo **speakerInfo
|
|
|
) {
|
|
|
uint32_t i, nexti = 0;
|
|
|
float a0 = 0.0f, a1 = 0.0f;
|
|
|
|
|
|
FAudio_assert(config != NULL);
|
|
|
|
|
|
/* We want to find, given an azimuth, which speakers are the closest
|
|
|
* ones (in terms of angle) to that azimuth.
|
|
|
* This is done by iterating through the list of speaker azimuths, as
|
|
|
* given to us by the current ConfigInfo (which stores speaker azimuths
|
|
|
* in increasing order of azimuth for each possible speaker configuration;
|
|
|
* each speaker azimuth is defined to be between 0 and 2PI by construction).
|
|
|
*/
|
|
|
for (i = 0; i < config->numNonLFSpeakers; i += 1)
|
|
|
{
|
|
|
/* a0 and a1 are the azimuths of candidate speakers */
|
|
|
a0 = config->speakers[i].azimuth;
|
|
|
nexti = (i + 1) % config->numNonLFSpeakers;
|
|
|
a1 = config->speakers[nexti].azimuth;
|
|
|
|
|
|
if (a0 < a1)
|
|
|
{
|
|
|
if (emitterAzimuth >= a0 && emitterAzimuth < a1)
|
|
|
{
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
/* It is possible for a speaker pair to enclose the singulary at 0 == 2PI:
|
|
|
* consider for example the quad config, which has a front left speaker
|
|
|
* at 7PI/4 and a front right speaker at PI/4. In that case a0 = 7PI/4 and
|
|
|
* a1 = PI/4, and the way we know whether our current azimuth lies between
|
|
|
* that pair is by checking whether the azimuth is greather than 7PI/4 or
|
|
|
* whether it's less than PI/4. (By contract, currentAzimuth is always less
|
|
|
* than 2PI.)
|
|
|
*/
|
|
|
else
|
|
|
{
|
|
|
if (emitterAzimuth >= a0 || emitterAzimuth < a1)
|
|
|
{
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
FAudio_assert(emitterAzimuth >= a0 || emitterAzimuth < a1);
|
|
|
|
|
|
/* skipCenter means that we don't want to use the center speaker.
|
|
|
* The easiest way to deal with this is to check whether either of our candidate
|
|
|
* speakers are the center, which always has an azimuth of 0.0. If that is the case
|
|
|
* we just replace it with either the previous one or the next one.
|
|
|
*/
|
|
|
if (skipCenter)
|
|
|
{
|
|
|
if (a0 == 0.0f)
|
|
|
{
|
|
|
if (i == 0)
|
|
|
{
|
|
|
i = config->numNonLFSpeakers - 1;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
i -= 1;
|
|
|
}
|
|
|
}
|
|
|
else if (a1 == 0.0f)
|
|
|
{
|
|
|
nexti += 1;
|
|
|
if (nexti >= config->numNonLFSpeakers)
|
|
|
{
|
|
|
nexti -= config->numNonLFSpeakers;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
speakerInfo[0] = &config->speakers[i];
|
|
|
speakerInfo[1] = &config->speakers[nexti];
|
|
|
}
|
|
|
|
|
|
/* Used to store diffusion factors */
|
|
|
/* See below for explanation. */
|
|
|
#define DIFFUSION_SPEAKERS_ALL 0
|
|
|
#define DIFFUSION_SPEAKERS_MATCHING 1
|
|
|
#define DIFFUSION_SPEAKERS_OPPOSITE 2
|
|
|
typedef float DiffusionSpeakerFactors[3];
|
|
|
|
|
|
/* ComputeInnerRadiusDiffusionFactors is a utility function that returns how
|
|
|
* energy dissipates to the speakers, given the radial distance between the
|
|
|
* emitter and the listener and the (optionally 0) InnerRadius distance. It
|
|
|
* returns 3 floats, via the diffusionFactors array, that say how much energy
|
|
|
* (after distance attenuation) will need to be distributed between each of the
|
|
|
* following cases:
|
|
|
*
|
|
|
* - SPEAKERS_ALL for all (non-LF) speakers, _INCLUDING_ the MATCHING and OPPOSITE.
|
|
|
* - SPEAKERS_OPPOSITE corresponds to the two speakers OPPOSITE the emitter.
|
|
|
* - SPEAKERS_MATCHING corresponds to the two speakers closest to the emitter.
|
|
|
*
|
|
|
* For a distance below a certain threshold (DISTANCE_EQUAL_ENERGY), all
|
|
|
* speakers receive equal energy.
|
|
|
*
|
|
|
* Above that, the amount that all speakers receive decreases linearly as radial
|
|
|
* distance increases, up until InnerRadius / 2. (If InnerRadius is null, we use
|
|
|
* MINIMUM_INNER_RADIUS.)
|
|
|
*
|
|
|
* At the same time, both opposite and matching speakers start to receive sound
|
|
|
* (in addition to the energy they receive from the aforementioned "all
|
|
|
* speakers" linear law) according to some unknown as of now law,
|
|
|
* that is currently emulated with a LERP. This is true up until InnerRadius.
|
|
|
*
|
|
|
* Above InnerRadius, only the two matching speakers receive sound.
|
|
|
*
|
|
|
* For more detail, see the "Inner Radius and Inner Radius Angle" in the
|
|
|
* MSDN docs for the X3DAUDIO_EMITTER structure.
|
|
|
* https://msdn.microsoft.com/en-us/library/windows/desktop/microsoft.directx_sdk.x3daudio.x3daudio_emitter(v=vs.85).aspx
|
|
|
*/
|
|
|
static inline void ComputeInnerRadiusDiffusionFactors(
|
|
|
float radialDistance,
|
|
|
float InnerRadius,
|
|
|
DiffusionSpeakerFactors diffusionFactors
|
|
|
) {
|
|
|
|
|
|
/* Determined experimentally; this is the midpoint value, i.e. the
|
|
|
* value at 0.5 for the matching speakers, used for the standard
|
|
|
* diffusion curve.
|
|
|
*
|
|
|
* Note: It is SUSPICIOUSLY close to 1/sqrt(2), but I haven't figured out why.
|
|
|
* -Adrien
|
|
|
*/
|
|
|
#define DIFFUSION_LERP_MIDPOINT_VALUE 0.707107f
|
|
|
|
|
|
/* X3DAudio always uses an InnerRadius-like behaviour (i.e. diffusing sound to more than
|
|
|
* a pair of speakers) even if InnerRadius is set to 0.0f.
|
|
|
* This constant determines the distance at which this behaviour is produced in that case. */
|
|
|
/* This constant was determined by manual binary search. TODO: get a more accurate version
|
|
|
* via an automated binary search. */
|
|
|
#define DIFFUSION_DISTANCE_MINIMUM_INNER_RADIUS 4e-7f
|
|
|
float actualInnerRadius = FAudio_max(InnerRadius, DIFFUSION_DISTANCE_MINIMUM_INNER_RADIUS);
|
|
|
float normalizedRadialDist;
|
|
|
float a, ms, os;
|
|
|
|
|
|
normalizedRadialDist = radialDistance / actualInnerRadius;
|
|
|
|
|
|
/* X3DAudio does another check for small radial distances before applying any InnerRadius-like
|
|
|
* behaviour. This is the constant that determines the threshold: below this distance we simply
|
|
|
* diffuse to all speakers equally. */
|
|
|
#define DIFFUSION_DISTANCE_EQUAL_ENERGY 1e-7f
|
|
|
if (radialDistance <= DIFFUSION_DISTANCE_EQUAL_ENERGY)
|
|
|
{
|
|
|
a = 1.0f;
|
|
|
ms = 0.0f;
|
|
|
os = 0.0f;
|
|
|
}
|
|
|
else if (normalizedRadialDist <= 0.5f)
|
|
|
{
|
|
|
/* Determined experimentally that this is indeed a linear law,
|
|
|
* with 100% confidence.
|
|
|
* -Adrien
|
|
|
*/
|
|
|
a = 1.0f - 2.0f * normalizedRadialDist;
|
|
|
|
|
|
/* Lerping here is an approximation.
|
|
|
* TODO: High accuracy version. Having stared at the curves long
|
|
|
* enough, I'm pretty sure this is a quadratic, but trying to
|
|
|
* polyfit with numpy didn't give nice, round polynomial
|
|
|
* coefficients...
|
|
|
* -Adrien
|
|
|
*/
|
|
|
ms = LERP(2.0f * normalizedRadialDist, 0.0f, DIFFUSION_LERP_MIDPOINT_VALUE);
|
|
|
os = 1.0f - a - ms;
|
|
|
}
|
|
|
else if (normalizedRadialDist <= 1.0f)
|
|
|
{
|
|
|
a = 0.0f;
|
|
|
|
|
|
/* Similarly, this is a lerp based on the midpoint value; the
|
|
|
* real, high-accuracy curve also looks like a quadratic.
|
|
|
* -Adrien
|
|
|
*/
|
|
|
ms = LERP(2.0f * (normalizedRadialDist - 0.5f), DIFFUSION_LERP_MIDPOINT_VALUE, 1.0f);
|
|
|
os = 1.0f - a - ms;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
a = 0.0f;
|
|
|
ms = 1.0f;
|
|
|
os = 0.0f;
|
|
|
}
|
|
|
diffusionFactors[DIFFUSION_SPEAKERS_ALL] = a;
|
|
|
diffusionFactors[DIFFUSION_SPEAKERS_MATCHING] = ms;
|
|
|
diffusionFactors[DIFFUSION_SPEAKERS_OPPOSITE] = os;
|
|
|
}
|
|
|
|
|
|
/* ComputeEmitterChannelCoefficients handles the coefficients calculation for 1
|
|
|
* column of the matrix. It uses ComputeInnerRadiusDiffusionFactors to separate
|
|
|
* into three discrete cases; and for each case does the right repartition of
|
|
|
* the energy after attenuation to the right speakers, in particular in the
|
|
|
* MATCHING and OPPOSITE cases, it gives each of the two speakers found a linear
|
|
|
* amount of the energy, according to the angular distance between the emitter
|
|
|
* and the speaker azimuth.
|
|
|
*/
|
|
|
static inline void ComputeEmitterChannelCoefficients(
|
|
|
const ConfigInfo *curConfig,
|
|
|
const F3DAUDIO_BASIS *listenerBasis,
|
|
|
float innerRadius,
|
|
|
F3DAUDIO_VECTOR channelPosition,
|
|
|
float attenuation,
|
|
|
float LFEattenuation,
|
|
|
uint32_t flags,
|
|
|
uint32_t currentChannel,
|
|
|
uint32_t numSrcChannels,
|
|
|
float *pMatrixCoefficients
|
|
|
) {
|
|
|
float elevation, radialDistance;
|
|
|
F3DAUDIO_VECTOR projTopVec, projPlane;
|
|
|
uint8_t skipCenter = (flags & F3DAUDIO_CALCULATE_ZEROCENTER) ? 1 : 0;
|
|
|
DiffusionSpeakerFactors diffusionFactors = { 0.0f };
|
|
|
|
|
|
float x, y;
|
|
|
float emitterAzimuth;
|
|
|
float energyPerChannel;
|
|
|
float totalEnergy;
|
|
|
uint32_t nChannelsToDiffuseTo;
|
|
|
uint32_t iS, centerChannelIdx = -1;
|
|
|
const SpeakerInfo* infos[2];
|
|
|
float a0, a1, val;
|
|
|
uint32_t i0, i1;
|
|
|
|
|
|
/* We project against the listener basis' top vector to get the elevation of the
|
|
|
* current emitter channel position.
|
|
|
*/
|
|
|
elevation = VectorDot(listenerBasis->top, channelPosition);
|
|
|
|
|
|
/* To obtain the projection in the front-right plane of the listener's basis of the
|
|
|
* emitter channel position, we simply remove the projection against the top vector.
|
|
|
* The radial distance is then the length of the projected vector.
|
|
|
*/
|
|
|
projTopVec = VectorScale(listenerBasis->top, elevation);
|
|
|
projPlane = VectorSub(channelPosition, projTopVec);
|
|
|
radialDistance = VectorLength(projPlane);
|
|
|
|
|
|
ComputeInnerRadiusDiffusionFactors(
|
|
|
radialDistance,
|
|
|
innerRadius,
|
|
|
diffusionFactors
|
|
|
);
|
|
|
|
|
|
/* See the ComputeInnerRadiusDiffusionFactors comment above for more context. */
|
|
|
/* DIFFUSION_SPEAKERS_ALL corresponds to diffusing part of the sound to all of the
|
|
|
* speakers, equally. The amount of sound is determined by the float value
|
|
|
* diffusionFactors[DIFFUSION_SPEAKERS_ALL]. */
|
|
|
if (diffusionFactors[DIFFUSION_SPEAKERS_ALL] > 0.0f)
|
|
|
{
|
|
|
nChannelsToDiffuseTo = curConfig->numNonLFSpeakers;
|
|
|
totalEnergy = diffusionFactors[DIFFUSION_SPEAKERS_ALL] * attenuation;
|
|
|
|
|
|
if (skipCenter)
|
|
|
{
|
|
|
nChannelsToDiffuseTo -= 1;
|
|
|
FAudio_assert(curConfig->speakers[0].azimuth == SPEAKER_AZIMUTH_CENTER);
|
|
|
centerChannelIdx = curConfig->speakers[0].matrixIdx;
|
|
|
}
|
|
|
|
|
|
energyPerChannel = totalEnergy / nChannelsToDiffuseTo;
|
|
|
|
|
|
for (iS = 0; iS < curConfig->numNonLFSpeakers; iS += 1)
|
|
|
{
|
|
|
const uint32_t curSpeakerIdx = curConfig->speakers[iS].matrixIdx;
|
|
|
if (skipCenter && curSpeakerIdx == centerChannelIdx)
|
|
|
{
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
pMatrixCoefficients[curSpeakerIdx * numSrcChannels + currentChannel] += energyPerChannel;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/* DIFFUSION_SPEAKERS_MATCHING corresponds to sending part of the sound to the speakers closest
|
|
|
* (in terms of azimuth) to the current position of the emitter. The amount of sound we shoud send
|
|
|
* corresponds here to diffusionFactors[DIFFUSION_SPEAKERS_MATCHING].
|
|
|
* We use the FindSpeakerAzimuths function to find the speakers that match. */
|
|
|
if (diffusionFactors[DIFFUSION_SPEAKERS_MATCHING] > 0.0f)
|
|
|
{
|
|
|
const float totalEnergy = diffusionFactors[DIFFUSION_SPEAKERS_MATCHING] * attenuation;
|
|
|
|
|
|
x = VectorDot(listenerBasis->front, projPlane);
|
|
|
y = VectorDot(listenerBasis->right, projPlane);
|
|
|
|
|
|
/* Now, a critical point: We shouldn't be sending sound to
|
|
|
* matching speakers when x and y are close to 0. That's the
|
|
|
* contract we get from ComputeInnerRadiusDiffusionFactors,
|
|
|
* which checks that we're not too close to the zero distance.
|
|
|
* This allows the atan2 calculation to give good results.
|
|
|
*/
|
|
|
|
|
|
/* atan2 returns [-PI, PI], but we want [0, 2PI] */
|
|
|
emitterAzimuth = FAudio_atan2f(y, x);
|
|
|
if (emitterAzimuth < 0.0f)
|
|
|
{
|
|
|
emitterAzimuth += F3DAUDIO_2PI;
|
|
|
}
|
|
|
|
|
|
FindSpeakerAzimuths(curConfig, emitterAzimuth, skipCenter, infos);
|
|
|
a0 = infos[0]->azimuth;
|
|
|
a1 = infos[1]->azimuth;
|
|
|
|
|
|
/* The following code is necessary to handle the singularity in
|
|
|
* (0 == 2PI). It'll give us a nice, well ordered interval.
|
|
|
*/
|
|
|
if (a0 > a1)
|
|
|
{
|
|
|
if (emitterAzimuth >= a0)
|
|
|
{
|
|
|
emitterAzimuth -= F3DAUDIO_2PI;
|
|
|
}
|
|
|
a0 -= F3DAUDIO_2PI;
|
|
|
}
|
|
|
FAudio_assert(emitterAzimuth >= a0 && emitterAzimuth <= a1);
|
|
|
|
|
|
val = (emitterAzimuth - a0) / (a1 - a0);
|
|
|
|
|
|
i0 = infos[0]->matrixIdx;
|
|
|
i1 = infos[1]->matrixIdx;
|
|
|
|
|
|
pMatrixCoefficients[i0 * numSrcChannels + currentChannel] += (1.0f - val) * totalEnergy;
|
|
|
pMatrixCoefficients[i1 * numSrcChannels + currentChannel] += ( val) * totalEnergy;
|
|
|
}
|
|
|
|
|
|
/* DIFFUSION_SPEAKERS_OPPOSITE corresponds to sending part of the sound to the speakers
|
|
|
* _opposite_ the ones that are the closest to the current emitter position.
|
|
|
* To find these, we simply find the ones that are closest to the current emitter's azimuth + PI
|
|
|
* using the FindSpeakerAzimuth function. */
|
|
|
if (diffusionFactors[DIFFUSION_SPEAKERS_OPPOSITE] > 0.0f)
|
|
|
{
|
|
|
/* This code is similar to the matching speakers code above. */
|
|
|
const float totalEnergy = diffusionFactors[DIFFUSION_SPEAKERS_OPPOSITE] * attenuation;
|
|
|
|
|
|
x = VectorDot(listenerBasis->front, projPlane);
|
|
|
y = VectorDot(listenerBasis->right, projPlane);
|
|
|
|
|
|
/* Similarly, we expect atan2 to be well behaved here. */
|
|
|
emitterAzimuth = FAudio_atan2f(y, x);
|
|
|
|
|
|
/* Opposite speakers lie at azimuth + PI */
|
|
|
emitterAzimuth += F3DAUDIO_PI;
|
|
|
|
|
|
/* Normalize to [0; 2PI) range. */
|
|
|
if (emitterAzimuth < 0.0f)
|
|
|
{
|
|
|
emitterAzimuth += F3DAUDIO_2PI;
|
|
|
}
|
|
|
else if (emitterAzimuth > F3DAUDIO_2PI)
|
|
|
{
|
|
|
emitterAzimuth -= F3DAUDIO_2PI;
|
|
|
}
|
|
|
|
|
|
FindSpeakerAzimuths(curConfig, emitterAzimuth, skipCenter, infos);
|
|
|
a0 = infos[0]->azimuth;
|
|
|
a1 = infos[1]->azimuth;
|
|
|
|
|
|
/* The following code is necessary to handle the singularity in
|
|
|
* (0 == 2PI). It'll give us a nice, well ordered interval.
|
|
|
*/
|
|
|
if (a0 > a1)
|
|
|
{
|
|
|
if (emitterAzimuth >= a0)
|
|
|
{
|
|
|
emitterAzimuth -= F3DAUDIO_2PI;
|
|
|
}
|
|
|
a0 -= F3DAUDIO_2PI;
|
|
|
}
|
|
|
FAudio_assert(emitterAzimuth >= a0 && emitterAzimuth <= a1);
|
|
|
|
|
|
val = (emitterAzimuth - a0) / (a1 - a0);
|
|
|
|
|
|
i0 = infos[0]->matrixIdx;
|
|
|
i1 = infos[1]->matrixIdx;
|
|
|
|
|
|
pMatrixCoefficients[i0 * numSrcChannels + currentChannel] += (1.0f - val) * totalEnergy;
|
|
|
pMatrixCoefficients[i1 * numSrcChannels + currentChannel] += ( val) * totalEnergy;
|
|
|
}
|
|
|
|
|
|
if (flags & F3DAUDIO_CALCULATE_REDIRECT_TO_LFE)
|
|
|
{
|
|
|
FAudio_assert(curConfig->LFSpeakerIdx != -1);
|
|
|
pMatrixCoefficients[curConfig->LFSpeakerIdx * numSrcChannels + currentChannel] += LFEattenuation / numSrcChannels;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/* Calculations consist of several orthogonal steps that compose multiplicatively:
|
|
|
*
|
|
|
* First, we compute the attenuations (volume and LFE) due to distance, which
|
|
|
* may involve an optional volume and/or LFE volume curve.
|
|
|
*
|
|
|
* Then, we compute those due to optional cones.
|
|
|
*
|
|
|
* We then compute how much energy is diffuse w.r.t InnerRadius. If InnerRadius
|
|
|
* is 0.0f, this step is computed as if it was InnerRadius was
|
|
|
* NON_NULL_DISTANCE_DISK_RADIUS. The way this works is, we look at the radial
|
|
|
* distance of the current emitter channel to the listener, with regard to the
|
|
|
* listener's top orientation (i.e. this distance is independant of the
|
|
|
* emitter's elevation!). If this distance is less than NULL_DISTANCE_RADIUS,
|
|
|
* energy is diffused equally between all channels. If it's greater than
|
|
|
* InnerRadius (or NON_NULL_DISTANCE_RADIUS, if InnerRadius is 0.0f, as
|
|
|
* mentioned above), the two closest speakers, by azimuth, receive all the
|
|
|
* energy. Between InnerRadius/2.0f and InnerRadius, the energy starts bleeding
|
|
|
* into the opposite speakers. Once we go below InnerRadius/2.0f, the energy
|
|
|
* also starts to bleed into the other (non-opposite) channels, if there are
|
|
|
* any. This computation is handled by the ComputeInnerRadiusDiffusionFactors
|
|
|
* function. (TODO: High-accuracy version of this.)
|
|
|
*
|
|
|
* Finally, if we're not in the equal diffusion case, we find out the azimuths
|
|
|
* of the two closest speakers (with azimuth being defined with respect to the
|
|
|
* listener's front orientation, in the plane normal to the listener's top
|
|
|
* vector), as well as the azimuths of the two opposite speakers, if necessary,
|
|
|
* and linearly interpolate with respect to the angular distance. In the equal
|
|
|
* diffusion case, each channel receives the same value.
|
|
|
*
|
|
|
* Note: in the case of multi-channel emitters, the distance attenuation is only
|
|
|
* compted once, but all the azimuths and InnerRadius calculations are done per
|
|
|
* emitter channel.
|
|
|
*
|
|
|
* TODO: Handle InnerRadiusAngle. But honestly the X3DAudio default behaviour is
|
|
|
* so wacky that I wonder if anybody has ever used it.
|
|
|
* -Adrien
|
|
|
*/
|
|
|
static inline void CalculateMatrix(
|
|
|
uint32_t ChannelMask,
|
|
|
uint32_t Flags,
|
|
|
const F3DAUDIO_LISTENER *pListener,
|
|
|
const F3DAUDIO_EMITTER *pEmitter,
|
|
|
uint32_t SrcChannelCount,
|
|
|
uint32_t DstChannelCount,
|
|
|
F3DAUDIO_VECTOR emitterToListener,
|
|
|
float eToLDistance,
|
|
|
float normalizedDistance,
|
|
|
float* MatrixCoefficients
|
|
|
) {
|
|
|
uint32_t iEC;
|
|
|
float curEmAzimuth;
|
|
|
const ConfigInfo* curConfig = GetConfigInfo(ChannelMask);
|
|
|
float attenuation = ComputeDistanceAttenuation(
|
|
|
normalizedDistance,
|
|
|
pEmitter->pVolumeCurve
|
|
|
);
|
|
|
/* TODO: this could be skipped if the destination has no LFE */
|
|
|
float LFEattenuation = ComputeDistanceAttenuation(
|
|
|
normalizedDistance,
|
|
|
pEmitter->pLFECurve
|
|
|
);
|
|
|
|
|
|
F3DAUDIO_VECTOR listenerToEmitter;
|
|
|
F3DAUDIO_VECTOR listenerToEmChannel;
|
|
|
F3DAUDIO_BASIS listenerBasis;
|
|
|
|
|
|
/* Note: For both cone calculations, the angle might be NaN or infinite
|
|
|
* if distance == 0... ComputeConeParameter *does* check for this
|
|
|
* special case. It is necessary that we still go through the
|
|
|
* ComputeConeParameter function, because omnidirectional cones might
|
|
|
* give either InnerVolume or OuterVolume.
|
|
|
* -Adrien
|
|
|
*/
|
|
|
if (pListener->pCone)
|
|
|
{
|
|
|
/* Negate the dot product because we need listenerToEmitter in
|
|
|
* this case
|
|
|
* -Adrien
|
|
|
*/
|
|
|
const float angle = -FAudio_acosf(
|
|
|
VectorDot(pListener->OrientFront, emitterToListener) /
|
|
|
eToLDistance
|
|
|
);
|
|
|
|
|
|
const float listenerConeParam = ComputeConeParameter(
|
|
|
eToLDistance,
|
|
|
angle,
|
|
|
pListener->pCone->InnerAngle,
|
|
|
pListener->pCone->OuterAngle,
|
|
|
pListener->pCone->InnerVolume,
|
|
|
pListener->pCone->OuterVolume
|
|
|
);
|
|
|
attenuation *= listenerConeParam;
|
|
|
LFEattenuation *= listenerConeParam;
|
|
|
}
|
|
|
|
|
|
/* See note above. */
|
|
|
if (pEmitter->pCone && pEmitter->ChannelCount == 1)
|
|
|
{
|
|
|
const float angle = FAudio_acosf(
|
|
|
VectorDot(pEmitter->OrientFront, emitterToListener) /
|
|
|
eToLDistance
|
|
|
);
|
|
|
|
|
|
const float emitterConeParam = ComputeConeParameter(
|
|
|
eToLDistance,
|
|
|
angle,
|
|
|
pEmitter->pCone->InnerAngle,
|
|
|
pEmitter->pCone->OuterAngle,
|
|
|
pEmitter->pCone->InnerVolume,
|
|
|
pEmitter->pCone->OuterVolume
|
|
|
);
|
|
|
attenuation *= emitterConeParam;
|
|
|
}
|
|
|
|
|
|
FAudio_zero(MatrixCoefficients, sizeof(float) * SrcChannelCount * DstChannelCount);
|
|
|
|
|
|
/* In the SPEAKER_MONO case, we can skip all energy diffusion calculation. */
|
|
|
if (DstChannelCount == 1)
|
|
|
{
|
|
|
for (iEC = 0; iEC < pEmitter->ChannelCount; iEC += 1)
|
|
|
{
|
|
|
curEmAzimuth = 0.0f;
|
|
|
if (pEmitter->pChannelAzimuths)
|
|
|
{
|
|
|
curEmAzimuth = pEmitter->pChannelAzimuths[iEC];
|
|
|
}
|
|
|
|
|
|
/* The MONO setup doesn't have an LFE speaker. */
|
|
|
if (curEmAzimuth != F3DAUDIO_2PI)
|
|
|
{
|
|
|
MatrixCoefficients[iEC] = attenuation;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
listenerToEmitter = VectorScale(emitterToListener, -1.0f);
|
|
|
|
|
|
/* Remember here that the coordinate system is Left-Handed. */
|
|
|
listenerBasis.front = pListener->OrientFront;
|
|
|
listenerBasis.right = VectorCross(pListener->OrientTop, pListener->OrientFront);
|
|
|
listenerBasis.top = pListener->OrientTop;
|
|
|
|
|
|
|
|
|
/* Handling the mono-channel emitter case separately is easier
|
|
|
* than having it as a separate case of a for-loop; indeed, in
|
|
|
* this case, we need to ignore the non-relevant values from the
|
|
|
* emitter, _even if they're set_.
|
|
|
*/
|
|
|
if (pEmitter->ChannelCount == 1)
|
|
|
{
|
|
|
listenerToEmChannel = listenerToEmitter;
|
|
|
|
|
|
ComputeEmitterChannelCoefficients(
|
|
|
curConfig,
|
|
|
&listenerBasis,
|
|
|
pEmitter->InnerRadius,
|
|
|
listenerToEmChannel,
|
|
|
attenuation,
|
|
|
LFEattenuation,
|
|
|
Flags,
|
|
|
0 /* currentChannel */,
|
|
|
1 /* numSrcChannels */,
|
|
|
MatrixCoefficients
|
|
|
);
|
|
|
}
|
|
|
else /* Multi-channel emitter case. */
|
|
|
{
|
|
|
const F3DAUDIO_VECTOR emitterRight = VectorCross(pEmitter->OrientTop, pEmitter->OrientFront);
|
|
|
|
|
|
for (iEC = 0; iEC < pEmitter->ChannelCount; iEC += 1)
|
|
|
{
|
|
|
const float emChAzimuth = pEmitter->pChannelAzimuths[iEC];
|
|
|
|
|
|
/* LFEs are easy enough to deal with; we can
|
|
|
* just do them separately.
|
|
|
*/
|
|
|
if (emChAzimuth == F3DAUDIO_2PI)
|
|
|
{
|
|
|
MatrixCoefficients[curConfig->LFSpeakerIdx * pEmitter->ChannelCount + iEC] = LFEattenuation;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
/* First compute the emitter channel
|
|
|
* vector relative to the emitter base...
|
|
|
*/
|
|
|
const F3DAUDIO_VECTOR emitterBaseToChannel = VectorAdd(
|
|
|
VectorScale(pEmitter->OrientFront, pEmitter->ChannelRadius * FAudio_cosf(emChAzimuth)),
|
|
|
VectorScale(emitterRight, pEmitter->ChannelRadius * FAudio_sinf(emChAzimuth))
|
|
|
);
|
|
|
/* ... then translate. */
|
|
|
listenerToEmChannel = VectorAdd(
|
|
|
listenerToEmitter,
|
|
|
emitterBaseToChannel
|
|
|
);
|
|
|
|
|
|
ComputeEmitterChannelCoefficients(
|
|
|
curConfig,
|
|
|
&listenerBasis,
|
|
|
pEmitter->InnerRadius,
|
|
|
listenerToEmChannel,
|
|
|
attenuation,
|
|
|
LFEattenuation,
|
|
|
Flags,
|
|
|
iEC,
|
|
|
pEmitter->ChannelCount,
|
|
|
MatrixCoefficients
|
|
|
);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
/* TODO: add post check to validate values
|
|
|
* (sum < 1, all values > 0, no Inf / NaN..
|
|
|
* Sum can be >1 when cone or curve is set to a gain!
|
|
|
* Perhaps under a paranoid check disabled by default.
|
|
|
*/
|
|
|
}
|
|
|
|
|
|
/*
|
|
|
* OTHER CALCULATIONS
|
|
|
*/
|
|
|
|
|
|
/* DopplerPitchScalar
|
|
|
* Adapted from algorithm published as a part of the webaudio specification:
|
|
|
* https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/specification.html#Spatialization-doppler-shift
|
|
|
* -Chad
|
|
|
*/
|
|
|
static inline void CalculateDoppler(
|
|
|
float SpeedOfSound,
|
|
|
const F3DAUDIO_LISTENER* pListener,
|
|
|
const F3DAUDIO_EMITTER* pEmitter,
|
|
|
F3DAUDIO_VECTOR emitterToListener,
|
|
|
float eToLDistance,
|
|
|
float* listenerVelocityComponent,
|
|
|
float* emitterVelocityComponent,
|
|
|
float* DopplerFactor
|
|
|
) {
|
|
|
float scaledSpeedOfSound;
|
|
|
*DopplerFactor = 1.0f;
|
|
|
|
|
|
/* Project... */
|
|
|
if (eToLDistance != 0.0f)
|
|
|
{
|
|
|
*listenerVelocityComponent =
|
|
|
VectorDot(emitterToListener, pListener->Velocity) / eToLDistance;
|
|
|
*emitterVelocityComponent =
|
|
|
VectorDot(emitterToListener, pEmitter->Velocity) / eToLDistance;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
*listenerVelocityComponent = 0.0f;
|
|
|
*emitterVelocityComponent = 0.0f;
|
|
|
}
|
|
|
|
|
|
if (pEmitter->DopplerScaler > 0.0f)
|
|
|
{
|
|
|
scaledSpeedOfSound = SpeedOfSound / pEmitter->DopplerScaler;
|
|
|
|
|
|
/* Clamp... */
|
|
|
*listenerVelocityComponent = FAudio_min(
|
|
|
*listenerVelocityComponent,
|
|
|
scaledSpeedOfSound
|
|
|
);
|
|
|
*emitterVelocityComponent = FAudio_min(
|
|
|
*emitterVelocityComponent,
|
|
|
scaledSpeedOfSound
|
|
|
);
|
|
|
|
|
|
/* ... then Multiply. */
|
|
|
*DopplerFactor = (
|
|
|
SpeedOfSound - pEmitter->DopplerScaler * *listenerVelocityComponent
|
|
|
) / (
|
|
|
SpeedOfSound - pEmitter->DopplerScaler * *emitterVelocityComponent
|
|
|
);
|
|
|
if (isnan(*DopplerFactor)) /* If emitter/listener are at the same pos... */
|
|
|
{
|
|
|
*DopplerFactor = 1.0f;
|
|
|
}
|
|
|
|
|
|
/* Limit the pitch shifting to 2 octaves up and 1 octave down */
|
|
|
*DopplerFactor = FAudio_clamp(
|
|
|
*DopplerFactor,
|
|
|
0.5f,
|
|
|
4.0f
|
|
|
);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void F3DAudioCalculate(
|
|
|
const F3DAUDIO_HANDLE Instance,
|
|
|
const F3DAUDIO_LISTENER *pListener,
|
|
|
const F3DAUDIO_EMITTER *pEmitter,
|
|
|
uint32_t Flags,
|
|
|
F3DAUDIO_DSP_SETTINGS *pDSPSettings
|
|
|
) {
|
|
|
uint32_t i;
|
|
|
F3DAUDIO_VECTOR emitterToListener;
|
|
|
float eToLDistance, normalizedDistance, dp;
|
|
|
|
|
|
#define DEFAULT_POINTS(name, x1, y1, x2, y2) \
|
|
|
static F3DAUDIO_DISTANCE_CURVE_POINT name##Points[2] = \
|
|
|
{ \
|
|
|
{ x1, y1 }, \
|
|
|
{ x2, y2 } \
|
|
|
}; \
|
|
|
static F3DAUDIO_DISTANCE_CURVE name##Default = \
|
|
|
{ \
|
|
|
(F3DAUDIO_DISTANCE_CURVE_POINT*) &name##Points[0], 2 \
|
|
|
};
|
|
|
DEFAULT_POINTS(lpfDirect, 0.0f, 1.0f, 1.0f, 0.75f)
|
|
|
DEFAULT_POINTS(lpfReverb, 0.0f, 0.75f, 1.0f, 0.75f)
|
|
|
DEFAULT_POINTS(reverb, 0.0f, 1.0f, 1.0f, 0.0f)
|
|
|
#undef DEFAULT_POINTS
|
|
|
|
|
|
/* For XACT, this calculates "Distance" */
|
|
|
emitterToListener = VectorSub(pListener->Position, pEmitter->Position);
|
|
|
eToLDistance = VectorLength(emitterToListener);
|
|
|
pDSPSettings->EmitterToListenerDistance = eToLDistance;
|
|
|
|
|
|
F3DAudioCheckCalculateParams(Instance, pListener, pEmitter, Flags, pDSPSettings);
|
|
|
|
|
|
/* This is used by MATRIX, LPF, and REVERB */
|
|
|
normalizedDistance = eToLDistance / pEmitter->CurveDistanceScaler;
|
|
|
|
|
|
if (Flags & F3DAUDIO_CALCULATE_MATRIX)
|
|
|
{
|
|
|
CalculateMatrix(
|
|
|
SPEAKERMASK(Instance),
|
|
|
Flags,
|
|
|
pListener,
|
|
|
pEmitter,
|
|
|
pDSPSettings->SrcChannelCount,
|
|
|
pDSPSettings->DstChannelCount,
|
|
|
emitterToListener,
|
|
|
eToLDistance,
|
|
|
normalizedDistance,
|
|
|
pDSPSettings->pMatrixCoefficients
|
|
|
);
|
|
|
}
|
|
|
|
|
|
if (Flags & F3DAUDIO_CALCULATE_LPF_DIRECT)
|
|
|
{
|
|
|
pDSPSettings->LPFDirectCoefficient = ComputeDistanceAttenuation(
|
|
|
normalizedDistance,
|
|
|
(pEmitter->pLPFDirectCurve != NULL) ?
|
|
|
pEmitter->pLPFDirectCurve :
|
|
|
&lpfDirectDefault
|
|
|
);
|
|
|
}
|
|
|
|
|
|
if (Flags & F3DAUDIO_CALCULATE_LPF_REVERB)
|
|
|
{
|
|
|
pDSPSettings->LPFReverbCoefficient = ComputeDistanceAttenuation(
|
|
|
normalizedDistance,
|
|
|
(pEmitter->pLPFReverbCurve != NULL) ?
|
|
|
pEmitter->pLPFReverbCurve :
|
|
|
&lpfReverbDefault
|
|
|
);
|
|
|
}
|
|
|
|
|
|
if (Flags & F3DAUDIO_CALCULATE_REVERB)
|
|
|
{
|
|
|
pDSPSettings->ReverbLevel = ComputeDistanceAttenuation(
|
|
|
normalizedDistance,
|
|
|
(pEmitter->pReverbCurve != NULL) ?
|
|
|
pEmitter->pReverbCurve :
|
|
|
&reverbDefault
|
|
|
);
|
|
|
}
|
|
|
|
|
|
/* For XACT, this calculates "DopplerPitchScalar" */
|
|
|
if (Flags & F3DAUDIO_CALCULATE_DOPPLER)
|
|
|
{
|
|
|
CalculateDoppler(
|
|
|
SPEEDOFSOUND(Instance),
|
|
|
pListener,
|
|
|
pEmitter,
|
|
|
emitterToListener,
|
|
|
eToLDistance,
|
|
|
&pDSPSettings->ListenerVelocityComponent,
|
|
|
&pDSPSettings->EmitterVelocityComponent,
|
|
|
&pDSPSettings->DopplerFactor
|
|
|
);
|
|
|
}
|
|
|
|
|
|
/* For XACT, this calculates "OrientationAngle" */
|
|
|
if (Flags & F3DAUDIO_CALCULATE_EMITTER_ANGLE)
|
|
|
{
|
|
|
/* Determined roughly.
|
|
|
* Below that distance, the emitter angle is considered to be PI/2.
|
|
|
*/
|
|
|
#define EMITTER_ANGLE_NULL_DISTANCE 1.2e-7
|
|
|
if (eToLDistance < EMITTER_ANGLE_NULL_DISTANCE)
|
|
|
{
|
|
|
pDSPSettings->EmitterToListenerAngle = F3DAUDIO_PI / 2.0f;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
/* Note: pEmitter->OrientFront is normalized. */
|
|
|
dp = VectorDot(emitterToListener, pEmitter->OrientFront) / eToLDistance;
|
|
|
pDSPSettings->EmitterToListenerAngle = FAudio_acosf(dp);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/* Unimplemented Flags */
|
|
|
if ( (Flags & F3DAUDIO_CALCULATE_DELAY) &&
|
|
|
SPEAKERMASK(Instance) == SPEAKER_STEREO )
|
|
|
{
|
|
|
for (i = 0; i < pDSPSettings->DstChannelCount; i += 1)
|
|
|
{
|
|
|
pDSPSettings->pDelayTimes[i] = 0.0f;
|
|
|
}
|
|
|
FAudio_assert(0 && "DELAY not implemented!");
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/* vim: set noexpandtab shiftwidth=8 tabstop=8: */
|
|
|
|