Refactor 3d audio for good spatial sound quality

This commit is contained in:
Wojtek Figat
2023-04-20 14:54:12 +02:00
parent 8c638e7947
commit 094a3cfe5a
14 changed files with 86 additions and 156 deletions

View File

@@ -31,19 +31,6 @@
#include "XAudio2/AudioBackendXAudio2.h"
#endif
const Char* ToString(AudioFormat value)
{
switch (value)
{
case AudioFormat::Raw:
return TEXT("Raw");
case AudioFormat::Vorbis:
return TEXT("Vorbis");
default:
return TEXT("");
}
}
float AudioDataInfo::GetLength() const
{
return (float)NumSamples / (float)Math::Max(1U, SampleRate * NumChannels);

View File

@@ -35,8 +35,7 @@ private:
virtual void Source_VolumeChanged(AudioSource* source) = 0;
virtual void Source_PitchChanged(AudioSource* source) = 0;
virtual void Source_IsLoopingChanged(AudioSource* source) = 0;
virtual void Source_MinDistanceChanged(AudioSource* source) = 0;
virtual void Source_AttenuationChanged(AudioSource* source) = 0;
virtual void Source_SpatialSetupChanged(AudioSource* source) = 0;
virtual void Source_ClipLoaded(AudioSource* source) = 0;
virtual void Source_Cleanup(AudioSource* source) = 0;
virtual void Source_Play(AudioSource* source) = 0;
@@ -141,14 +140,9 @@ public:
Instance->Source_IsLoopingChanged(source);
}
FORCE_INLINE static void MinDistanceChanged(AudioSource* source)
FORCE_INLINE static void SpatialSetupChanged(AudioSource* source)
{
Instance->Source_MinDistanceChanged(source);
}
FORCE_INLINE static void AttenuationChanged(AudioSource* source)
{
Instance->Source_AttenuationChanged(source);
Instance->Source_SpatialSetupChanged(source);
}
FORCE_INLINE static void ClipLoaded(AudioSource* source)

View File

@@ -16,7 +16,7 @@ AudioSource::AudioSource(const SpawnParams& params)
, _velocity(Vector3::Zero)
, _volume(1.0f)
, _pitch(1.0f)
, _minDistance(1.0f)
, _minDistance(1000.0f)
, _attenuation(1.0f)
, _loop(false)
, _playOnStart(false)
@@ -30,13 +30,9 @@ void AudioSource::SetVolume(float value)
value = Math::Saturate(value);
if (Math::NearEqual(_volume, value))
return;
_volume = value;
if (SourceIDs.HasItems())
{
AudioBackend::Source::VolumeChanged(this);
}
}
void AudioSource::SetPitch(float value)
@@ -44,27 +40,20 @@ void AudioSource::SetPitch(float value)
value = Math::Clamp(value, 0.5f, 2.0f);
if (Math::NearEqual(_pitch, value))
return;
_pitch = value;
if (SourceIDs.HasItems())
{
AudioBackend::Source::PitchChanged(this);
}
}
void AudioSource::SetIsLooping(bool value)
{
if (_loop == value)
return;
_loop = value;
// When streaming we handle looping manually by the proper buffers submission
if (SourceIDs.HasItems() && !UseStreaming())
{
AudioBackend::Source::IsLoopingChanged(this);
}
}
void AudioSource::SetPlayOnStart(bool value)
@@ -77,13 +66,9 @@ void AudioSource::SetMinDistance(float value)
value = Math::Max(0.0f, value);
if (Math::NearEqual(_minDistance, value))
return;
_minDistance = value;
if (SourceIDs.HasItems())
{
AudioBackend::Source::MinDistanceChanged(this);
}
AudioBackend::Source::SpatialSetupChanged(this);
}
void AudioSource::SetAttenuation(float value)
@@ -91,13 +76,10 @@ void AudioSource::SetAttenuation(float value)
value = Math::Max(0.0f, value);
if (Math::NearEqual(_attenuation, value))
return;
_attenuation = value;
if (SourceIDs.HasItems())
{
AudioBackend::Source::AttenuationChanged(this);
}
AudioBackend::Source::SpatialSetupChanged(this);
}
void AudioSource::Play()
@@ -313,6 +295,22 @@ void AudioSource::PlayInternal()
_isActuallyPlayingSth = true;
}
#if USE_EDITOR
#include "Engine/Debug/DebugDraw.h"
void AudioSource::OnDebugDrawSelected()
{
// Draw influence range
if (_allowSpatialization)
DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(_transform.Translation, _minDistance), Color::CornflowerBlue, 0, true);
// Base
Actor::OnDebugDrawSelected();
}
#endif
void AudioSource::Serialize(SerializeStream& stream, const void* otherObj)
{
// Base

View File

@@ -138,8 +138,7 @@ public:
/// <summary>
/// Gets the minimum distance at which audio attenuation starts. When the listener is closer to the source than this value, audio is heard at full volume. Once farther away the audio starts attenuating.
/// </summary>
/// <returns>The value.</returns>
API_PROPERTY(Attributes="EditorOrder(60), DefaultValue(1.0f), Limit(0, float.MaxValue, 0.1f), EditorDisplay(\"Audio Source\")")
API_PROPERTY(Attributes="EditorOrder(60), DefaultValue(1000.0f), Limit(0, float.MaxValue, 0.1f), EditorDisplay(\"Audio Source\")")
FORCE_INLINE float GetMinDistance() const
{
return _minDistance;
@@ -160,7 +159,7 @@ public:
}
/// <summary>
/// Sets the attenuation that controls how quickly does audio volume drop off as the listener moves further from the source.
/// Sets the attenuation that controls how quickly does audio volume drop off as the listener moves further from the source. At 0, no distance attenuation ever occurs.
/// </summary>
API_PROPERTY() void SetAttenuation(float value);
@@ -257,6 +256,9 @@ public:
const Vector3 size(50);
return BoundingBox(_transform.Translation - size, _transform.Translation + size);
}
#endif
#if USE_EDITOR
void OnDebugDrawSelected() override;
#endif
void Serialize(SerializeStream& stream, const void* otherObj) override;
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override;

View File

@@ -56,11 +56,7 @@ void AudioBackendNone::Source_IsLoopingChanged(AudioSource* source)
{
}
void AudioBackendNone::Source_MinDistanceChanged(AudioSource* source)
{
}
void AudioBackendNone::Source_AttenuationChanged(AudioSource* source)
void AudioBackendNone::Source_SpatialSetupChanged(AudioSource* source)
{
}

View File

@@ -26,8 +26,7 @@ public:
void Source_VolumeChanged(AudioSource* source) override;
void Source_PitchChanged(AudioSource* source) override;
void Source_IsLoopingChanged(AudioSource* source) override;
void Source_MinDistanceChanged(AudioSource* source) override;
void Source_AttenuationChanged(AudioSource* source) override;
void Source_SpatialSetupChanged(AudioSource* source) override;
void Source_ClipLoaded(AudioSource* source) override;
void Source_Cleanup(AudioSource* source) override;
void Source_Play(AudioSource* source) override;

View File

@@ -21,7 +21,8 @@
#define ALC_MULTIPLE_LISTENERS 0
#define FLAX_COORD_SCALE 0.01f
#define FLAX_COORD_SCALE 0.01f // units are meters
#define FLAX_DST_TO_OAL(x) x * FLAX_COORD_SCALE
#define FLAX_POS_TO_OAL(vec) ((ALfloat)vec.X * -FLAX_COORD_SCALE), ((ALfloat)vec.Y * FLAX_COORD_SCALE), ((ALfloat)vec.Z * FLAX_COORD_SCALE)
#define FLAX_VEL_TO_OAL(vec) ((ALfloat)vec.X * -(FLAX_COORD_SCALE*FLAX_COORD_SCALE)), ((ALfloat)vec.Y * (FLAX_COORD_SCALE*FLAX_COORD_SCALE)), ((ALfloat)vec.Z * (FLAX_COORD_SCALE*FLAX_COORD_SCALE))
#if BUILD_RELEASE
@@ -102,7 +103,7 @@ namespace ALC
{
alcMakeContextCurrent(nullptr);
for (auto& context : Contexts)
for (ALCcontext* context : Contexts)
alcDestroyContext(context);
Contexts.Clear();
}
@@ -113,7 +114,7 @@ namespace ALC
{
AudioBackend::Listener::TransformChanged(listener);
const auto& velocity = listener->GetVelocity();
const Vector3 velocity = listener->GetVelocity();
alListener3f(AL_VELOCITY, FLAX_VEL_TO_OAL(velocity));
alListenerf(AL_GAIN, Audio::GetVolume());
}
@@ -146,13 +147,14 @@ namespace ALC
if (is3D)
{
alSourcef(sourceID, AL_ROLLOFF_FACTOR, source->GetAttenuation());
alSourcef(sourceID, AL_REFERENCE_DISTANCE, source->GetMinDistance());
alSourcef(sourceID, AL_REFERENCE_DISTANCE, FLAX_DST_TO_OAL(source->GetMinDistance()));
alSource3f(sourceID, AL_POSITION, FLAX_POS_TO_OAL(source->GetPosition()));
alSource3f(sourceID, AL_VELOCITY, FLAX_VEL_TO_OAL(source->GetVelocity()));
}
else
{
alSourcef(sourceID, AL_ROLLOFF_FACTOR, 0.0f);
alSourcef(sourceID, AL_REFERENCE_DISTANCE, 0.0f);
alSource3f(sourceID, AL_POSITION, 0.0f, 0.0f, 0.0f);
alSource3f(sourceID, AL_VELOCITY, 0.0f, 0.0f, 0.0f);
}
@@ -169,7 +171,7 @@ namespace ALC
if (!isChangingDevice)
{
for (auto& source : Audio::Sources)
for (AudioSource* source : Audio::Sources)
source->Cleanup();
}
@@ -204,10 +206,10 @@ namespace ALC
// Audio listeners and sources will avoid excessive context switching in such case.
alcMakeContextCurrent(Contexts[0]);
for (auto& listener : Audio::Listeners)
for (AudioListener* listener : Audio::Listeners)
Listener::Rebuild(listener);
for (auto& source : Audio::Sources)
for (AudioSource* source : Audio::Sources)
Source::Rebuild(source);
}
}
@@ -293,7 +295,7 @@ void AudioBackendOAL::Listener_OnAdd(AudioListener* listener)
ALC::RebuildContexts(false);
#else
AudioBackend::Listener::TransformChanged(listener);
const auto& velocity = listener->GetVelocity();
const Vector3 velocity = listener->GetVelocity();
alListener3f(AL_VELOCITY, FLAX_VEL_TO_OAL(velocity));
alListenerf(AL_GAIN, Audio::GetVolume());
#endif
@@ -310,7 +312,7 @@ void AudioBackendOAL::Listener_VelocityChanged(AudioListener* listener)
{
ALC_GET_LISTENER_CONTEXT(listener)
const auto& velocity = listener->GetVelocity();
const Vector3 velocity = listener->GetVelocity();
alListener3f(AL_VELOCITY, FLAX_VEL_TO_OAL(velocity));
}
@@ -318,11 +320,10 @@ void AudioBackendOAL::Listener_TransformChanged(AudioListener* listener)
{
ALC_GET_LISTENER_CONTEXT(listener)
const Vector3& position = listener->GetPosition();
const Quaternion& orientation = listener->GetOrientation();
const Vector3& flipX = Vector3(-1, 1, 1);
Vector3 alOrientation[2] =
const Vector3 position = listener->GetPosition();
const Quaternion orientation = listener->GetOrientation();
const Vector3 flipX(-1, 1, 1);
const Vector3 alOrientation[2] =
{
// Forward
orientation * Vector3::Forward * flipX,
@@ -394,23 +395,22 @@ void AudioBackendOAL::Source_IsLoopingChanged(AudioSource* source)
}
}
void AudioBackendOAL::Source_MinDistanceChanged(AudioSource* source)
void AudioBackendOAL::Source_SpatialSetupChanged(AudioSource* source)
{
if (!source->Is3D())
return;
const bool is3D = source->Is3D();
ALC_FOR_EACH_CONTEXT()
const uint32 sourceID = source->SourceIDs[i];
alSourcef(sourceID, AL_REFERENCE_DISTANCE, source->GetMinDistance());
}
}
void AudioBackendOAL::Source_AttenuationChanged(AudioSource* source)
{
if (!source->Is3D())
return;
ALC_FOR_EACH_CONTEXT()
const uint32 sourceID = source->SourceIDs[i];
alSourcef(sourceID, AL_ROLLOFF_FACTOR, source->GetAttenuation());
alSourcei(sourceID, AL_SOURCE_RELATIVE, !is3D);
if (is3D)
{
alSourcef(sourceID, AL_ROLLOFF_FACTOR, source->GetAttenuation());
alSourcef(sourceID, AL_REFERENCE_DISTANCE, FLAX_DST_TO_OAL(source->GetMinDistance()));
}
else
{
alSourcef(sourceID, AL_ROLLOFF_FACTOR, 0.0f);
alSourcef(sourceID, AL_REFERENCE_DISTANCE, 0.0f);
}
}
}
@@ -496,7 +496,7 @@ float AudioBackendOAL::Source_GetCurrentBufferTime(const AudioSource* source)
alGetSourcef(source->SourceIDs[0], AL_SEC_OFFSET, &time);
#else
ASSERT(source->Clip && source->Clip->IsLoaded());
const auto& clipInfo = source->Clip->AudioHeader.Info;
const AudioDataInfo& clipInfo = source->Clip->AudioHeader.Info;
ALint samplesPlayed;
alGetSourcei(source->SourceIDs[0], AL_SAMPLE_OFFSET, &samplesPlayed);
const uint32 totalSamples = clipInfo.NumSamples / clipInfo.NumChannels;
@@ -686,7 +686,7 @@ const Char* AudioBackendOAL::Base_Name()
void AudioBackendOAL::Base_OnActiveDeviceChanged()
{
// Cleanup
for (auto& source : Audio::Sources)
for (AudioSource* source : Audio::Sources)
source->Cleanup();
ALC::ClearContexts();
if (ALC::Device != nullptr)
@@ -696,7 +696,7 @@ void AudioBackendOAL::Base_OnActiveDeviceChanged()
}
// Open device
const auto& name = Audio::GetActiveDevice()->InternalName;
const StringAnsi& name = Audio::GetActiveDevice()->InternalName;
ALC::Device = alcOpenDevice(name.Get());
if (ALC::Device == nullptr)
{
@@ -817,6 +817,7 @@ bool AudioBackendOAL::Base_Init()
// Init
SetDopplerFactor(AudioSettings::Get()->DopplerFactor);
alDistanceModel(AL_INVERSE_DISTANCE_CLAMPED); // Default attenuation model
ALC::RebuildContexts(true);
Audio::SetActiveDeviceIndex(activeDeviceIndex);

View File

@@ -26,8 +26,7 @@ public:
void Source_VolumeChanged(AudioSource* source) override;
void Source_PitchChanged(AudioSource* source) override;
void Source_IsLoopingChanged(AudioSource* source) override;
void Source_MinDistanceChanged(AudioSource* source) override;
void Source_AttenuationChanged(AudioSource* source) override;
void Source_SpatialSetupChanged(AudioSource* source) override;
void Source_ClipLoaded(AudioSource* source) override;
void Source_Cleanup(AudioSource* source) override;
void Source_Play(AudioSource* source) override;

View File

@@ -25,8 +25,6 @@ API_ENUM() enum class AudioFormat
Vorbis,
};
const Char* ToString(AudioFormat value);
/// <summary>
/// Meta-data describing a chunk of audio.
/// </summary>

View File

@@ -26,7 +26,8 @@
#define MAX_OUTPUT_CHANNELS 8
#define MAX_CHANNELS_MATRIX_SIZE (MAX_INPUT_CHANNELS*MAX_OUTPUT_CHANNELS)
#define FLAX_COORD_SCALE 0.01f
#define FLAX_COORD_SCALE 0.01f // units are meters
#define FLAX_DST_TO_XAUDIO(x) x * FLAX_COORD_SCALE
#define FLAX_POS_TO_XAUDIO(vec) X3DAUDIO_VECTOR(vec.X * FLAX_COORD_SCALE, vec.Y * FLAX_COORD_SCALE, vec.Z * FLAX_COORD_SCALE)
#define FLAX_VEL_TO_XAUDIO(vec) X3DAUDIO_VECTOR(vec.X * (FLAX_COORD_SCALE*FLAX_COORD_SCALE), vec.Y * (FLAX_COORD_SCALE*FLAX_COORD_SCALE), vec.Z * (FLAX_COORD_SCALE*FLAX_COORD_SCALE))
#define FLAX_VEC_TO_XAUDIO(vec) (*((X3DAUDIO_VECTOR*)&vec))
@@ -105,7 +106,6 @@ namespace XAudio2
}
public:
AudioSource* Source;
void PeekSamples();
@@ -200,13 +200,11 @@ namespace XAudio2
DWORD ChannelMask;
UINT32 SampleRate;
UINT32 Channels;
bool UseRedirectToLFE;
bool ForceDirty = true;
Listener Listeners[AUDIO_MAX_LISTENERS];
Array<Source> Sources(32); // TODO: use ChunkedArray for better performance
Array<Buffer*> Buffers(64); // TODO: use ChunkedArray for better performance or use buffers pool?
EngineCallback Callback;
float MatrixCoefficients[MAX_CHANNELS_MATRIX_SIZE];
Listener* GetListener()
{
@@ -397,9 +395,9 @@ void AudioBackendXAudio2::Source_OnAdd(AudioSource* source)
aSource->Callback.Source = source;
aSource->IsDirty = true;
aSource->Data.ChannelCount = header.Info.NumChannels;
aSource->Data.InnerRadius = FLAX_DST_TO_XAUDIO(source->GetMinDistance());
aSource->Is3D = source->Is3D();
aSource->Pitch = source->GetPitch();
aSource->Data.InnerRadius = source->GetMinDistance();
aSource->UpdateTransform(source);
aSource->UpdateVelocity(source);
@@ -498,21 +496,17 @@ void AudioBackendXAudio2::Source_IsLoopingChanged(AudioSource* source)
aSource->Voice->Start();
}
void AudioBackendXAudio2::Source_MinDistanceChanged(AudioSource* source)
void AudioBackendXAudio2::Source_SpatialSetupChanged(AudioSource* source)
{
auto aSource = XAudio2::GetSource(source);
if (aSource)
{
aSource->Data.InnerRadius = source->GetMinDistance();
// TODO: implement attenuation settings for 3d audio
aSource->Data.InnerRadius = FLAX_DST_TO_XAUDIO(source->GetMinDistance());
aSource->IsDirty = true;
}
}
void AudioBackendXAudio2::Source_AttenuationChanged(AudioSource* source)
{
// TODO: implement it
}
void AudioBackendXAudio2::Source_ClipLoaded(AudioSource* source)
{
auto aSource = XAudio2::GetSource(source);
@@ -769,7 +763,6 @@ bool AudioBackendXAudio2::Base_Init()
LOG(Error, "Failed to get XAudio2 mastering voice channel mask. Error: 0x{0:x}", hr);
return true;
}
XAudio2::UseRedirectToLFE = ((XAudio2::ChannelMask & SPEAKER_LOW_FREQUENCY) != 0);
// Initialize spatial audio subsystem
DWORD dwChannelMask;
@@ -794,19 +787,13 @@ bool AudioBackendXAudio2::Base_Init()
void AudioBackendXAudio2::Base_Update()
{
// Note: only one listener is supported for now
const auto listener = XAudio2::GetListener();
if (!listener)
{
// How can we play audio when no one is listening
return;
}
// Update dirty voices
const auto listener = XAudio2::GetListener();
const float dopplerFactor = AudioSettings::Get()->DopplerFactor;
float matrixCoefficients[MAX_CHANNELS_MATRIX_SIZE];
X3DAUDIO_DSP_SETTINGS dsp = { 0 };
dsp.DstChannelCount = XAudio2::Channels;
dsp.pMatrixCoefficients = XAudio2::MatrixCoefficients;
dsp.pMatrixCoefficients = matrixCoefficients;
for (int32 i = 0; i < XAudio2::Sources.Count(); i++)
{
auto& source = XAudio2::Sources[i];
@@ -814,7 +801,7 @@ void AudioBackendXAudio2::Base_Update()
continue;
dsp.SrcChannelCount = source.Data.ChannelCount;
if (source.Is3D)
if (source.Is3D && listener)
{
X3DAudioCalculate(XAudio2::X3DInstance, &listener->Data, &source.Data, X3DAUDIO_CALCULATE_MATRIX | X3DAUDIO_CALCULATE_DOPPLER, &dsp);
}
@@ -822,7 +809,7 @@ void AudioBackendXAudio2::Base_Update()
{
// Stereo
dsp.DopplerFactor = 1.0f;
Platform::MemoryClear(dsp.pMatrixCoefficients, sizeof(XAudio2::MatrixCoefficients));
Platform::MemoryClear(dsp.pMatrixCoefficients, sizeof(matrixCoefficients));
dsp.pMatrixCoefficients[0] = 1.0f;
if (source.Format.nChannels == 1)
{

View File

@@ -26,8 +26,7 @@ public:
void Source_VolumeChanged(AudioSource* source) override;
void Source_PitchChanged(AudioSource* source) override;
void Source_IsLoopingChanged(AudioSource* source) override;
void Source_MinDistanceChanged(AudioSource* source) override;
void Source_AttenuationChanged(AudioSource* source) override;
void Source_SpatialSetupChanged(AudioSource* source) override;
void Source_ClipLoaded(AudioSource* source) override;
void Source_Cleanup(AudioSource* source) override;
void Source_Play(AudioSource* source) override;