diff --git a/Source/Engine/Audio/AudioBackend.h b/Source/Engine/Audio/AudioBackend.h
index 6f94df859..61d2c223a 100644
--- a/Source/Engine/Audio/AudioBackend.h
+++ b/Source/Engine/Audio/AudioBackend.h
@@ -41,6 +41,7 @@ private:
virtual void Source_TransformChanged(AudioSource* source) = 0;
virtual void Source_VolumeChanged(AudioSource* source) = 0;
virtual void Source_PitchChanged(AudioSource* source) = 0;
+ virtual void Source_PanChanged(AudioSource* source) = 0;
virtual void Source_IsLoopingChanged(AudioSource* source) = 0;
virtual void Source_SpatialSetupChanged(AudioSource* source) = 0;
virtual void Source_ClipLoaded(AudioSource* source) = 0;
@@ -143,6 +144,11 @@ public:
Instance->Source_PitchChanged(source);
}
+ FORCE_INLINE static void PanChanged(AudioSource* source)
+ {
+ Instance->Source_PanChanged(source);
+ }
+
FORCE_INLINE static void IsLoopingChanged(AudioSource* source)
{
Instance->Source_IsLoopingChanged(source);
diff --git a/Source/Engine/Audio/AudioSource.cpp b/Source/Engine/Audio/AudioSource.cpp
index 6ad80f617..2e702cbae 100644
--- a/Source/Engine/Audio/AudioSource.cpp
+++ b/Source/Engine/Audio/AudioSource.cpp
@@ -45,6 +45,16 @@ void AudioSource::SetPitch(float value)
AudioBackend::Source::PitchChanged(this);
}
+void AudioSource::SetPan(float value)
+{
+ value = Math::Clamp(value, -1.0f, 1.0f);
+ if (Math::NearEqual(_pan, value))
+ return;
+ _pan = value;
+ if (SourceIDs.HasItems())
+ AudioBackend::Source::PanChanged(this);
+}
+
void AudioSource::SetIsLooping(bool value)
{
if (_loop == value)
@@ -339,6 +349,7 @@ void AudioSource::Serialize(SerializeStream& stream, const void* otherObj)
SERIALIZE(Clip);
SERIALIZE_MEMBER(Volume, _volume);
SERIALIZE_MEMBER(Pitch, _pitch);
+ SERIALIZE_MEMBER(Pan, _pan);
SERIALIZE_MEMBER(MinDistance, _minDistance);
SERIALIZE_MEMBER(Attenuation, _attenuation);
SERIALIZE_MEMBER(DopplerFactor, _dopplerFactor);
@@ -355,6 +366,7 @@ void AudioSource::Deserialize(DeserializeStream& stream, ISerializeModifier* mod
DESERIALIZE(Clip);
DESERIALIZE_MEMBER(Volume, _volume);
DESERIALIZE_MEMBER(Pitch, _pitch);
+ DESERIALIZE_MEMBER(Pan, _pan);
DESERIALIZE_MEMBER(MinDistance, _minDistance);
DESERIALIZE_MEMBER(Attenuation, _attenuation);
DESERIALIZE_MEMBER(DopplerFactor, _dopplerFactor);
diff --git a/Source/Engine/Audio/AudioSource.h b/Source/Engine/Audio/AudioSource.h
index 91383d00f..3b6f32153 100644
--- a/Source/Engine/Audio/AudioSource.h
+++ b/Source/Engine/Audio/AudioSource.h
@@ -46,6 +46,7 @@ private:
Vector3 _prevPos;
float _volume;
float _pitch;
+ float _pan = 0.0f;
float _minDistance;
float _attenuation = 1.0f;
float _dopplerFactor = 1.0f;
@@ -109,6 +110,20 @@ public:
///
API_PROPERTY() void SetPitch(float value);
+ ///
+ /// Gets the stereo pan of the played audio (-1 is left speaker, 1 is right speaker, 0 is balanced). The default is 1. Used by non-spatial audio only.
+ ///
+ API_PROPERTY(Attributes="EditorOrder(30), DefaultValue(0.0f), Limit(-1.0f, 1.0f), EditorDisplay(\"Audio Source\")")
+ FORCE_INLINE float GetPan() const
+ {
+ return _pan;
+ }
+
+ ///
+ /// Sets the stereo pan of the played audio (-1 is left speaker, 1 is right speaker, 0 is balanced). The default is 0. Used by non-spatial audio only.
+ ///
+ API_PROPERTY() void SetPan(float value);
+
///
/// Determines whether the audio clip should loop when it finishes playing.
///
diff --git a/Source/Engine/Audio/None/AudioBackendNone.cpp b/Source/Engine/Audio/None/AudioBackendNone.cpp
index f35ffd0c8..42a31b775 100644
--- a/Source/Engine/Audio/None/AudioBackendNone.cpp
+++ b/Source/Engine/Audio/None/AudioBackendNone.cpp
@@ -52,6 +52,10 @@ void AudioBackendNone::Source_PitchChanged(AudioSource* source)
{
}
+void AudioBackendNone::Source_PanChanged(AudioSource* source)
+{
+}
+
void AudioBackendNone::Source_IsLoopingChanged(AudioSource* source)
{
}
diff --git a/Source/Engine/Audio/None/AudioBackendNone.h b/Source/Engine/Audio/None/AudioBackendNone.h
index 59bc076c7..1e90b42ac 100644
--- a/Source/Engine/Audio/None/AudioBackendNone.h
+++ b/Source/Engine/Audio/None/AudioBackendNone.h
@@ -25,6 +25,7 @@ public:
void Source_TransformChanged(AudioSource* source) override;
void Source_VolumeChanged(AudioSource* source) override;
void Source_PitchChanged(AudioSource* source) override;
+ void Source_PanChanged(AudioSource* source) override;
void Source_IsLoopingChanged(AudioSource* source) override;
void Source_SpatialSetupChanged(AudioSource* source) override;
void Source_ClipLoaded(AudioSource* source) override;
diff --git a/Source/Engine/Audio/OpenAL/AudioBackendOAL.cpp b/Source/Engine/Audio/OpenAL/AudioBackendOAL.cpp
index 98397c17d..cc5036e25 100644
--- a/Source/Engine/Audio/OpenAL/AudioBackendOAL.cpp
+++ b/Source/Engine/Audio/OpenAL/AudioBackendOAL.cpp
@@ -164,6 +164,11 @@ namespace ALC
alSource3f(sourceID, AL_POSITION, 0.0f, 0.0f, 0.0f);
alSource3f(sourceID, AL_VELOCITY, 0.0f, 0.0f, 0.0f);
}
+#ifdef AL_EXT_STEREO_ANGLES
+ const float panAngle = source->GetPan() * PI_HALF;
+ const ALfloat panAngles[2] = { (ALfloat)(PI / 6.0 - panAngle), (ALfloat)(-PI / 6.0 - panAngle) }; // Angles are specified counter-clockwise in radians
+ alSourcefv(sourceID, AL_STEREO_ANGLES, panAngles);
+#endif
}
// Restore state after Cleanup
@@ -392,6 +397,18 @@ void AudioBackendOAL::Source_PitchChanged(AudioSource* source)
}
}
+void AudioBackendOAL::Source_PanChanged(AudioSource* source)
+{
+#ifdef AL_EXT_STEREO_ANGLES
+ const float panAngle = source->GetPan() * PI_HALF;
+ const ALfloat panAngles[2] = { (ALfloat)(PI / 6.0 - panAngle), (ALfloat)(-PI / 6.0 - panAngle) }; // Angles are specified counter-clockwise in radians
+ ALC_FOR_EACH_CONTEXT()
+ const uint32 sourceID = source->SourceIDs[i];
+ alSourcefv(sourceID, AL_STEREO_ANGLES, panAngles);
+ }
+#endif
+}
+
void AudioBackendOAL::Source_IsLoopingChanged(AudioSource* source)
{
const bool loop = source->GetIsLooping() && !source->UseStreaming();
@@ -409,6 +426,9 @@ void AudioBackendOAL::Source_SpatialSetupChanged(AudioSource* source)
alSourcei(sourceID, AL_SOURCE_RELATIVE, !is3D);
if (is3D)
{
+#ifdef AL_SOFT_source_spatialize
+ alSourcei(sourceID, AL_SOURCE_SPATIALIZE_SOFT, AL_TRUE);
+#endif
alSourcef(sourceID, AL_ROLLOFF_FACTOR, source->GetAttenuation());
alSourcef(sourceID, AL_DOPPLER_FACTOR, source->GetDopplerFactor());
alSourcef(sourceID, AL_REFERENCE_DISTANCE, FLAX_DST_TO_OAL(source->GetMinDistance()));
diff --git a/Source/Engine/Audio/OpenAL/AudioBackendOAL.h b/Source/Engine/Audio/OpenAL/AudioBackendOAL.h
index 82844a7a2..5590de518 100644
--- a/Source/Engine/Audio/OpenAL/AudioBackendOAL.h
+++ b/Source/Engine/Audio/OpenAL/AudioBackendOAL.h
@@ -25,6 +25,7 @@ public:
void Source_TransformChanged(AudioSource* source) override;
void Source_VolumeChanged(AudioSource* source) override;
void Source_PitchChanged(AudioSource* source) override;
+ void Source_PanChanged(AudioSource* source) override;
void Source_IsLoopingChanged(AudioSource* source) override;
void Source_SpatialSetupChanged(AudioSource* source) override;
void Source_ClipLoaded(AudioSource* source) override;
diff --git a/Source/Engine/Audio/XAudio2/AudioBackendXAudio2.cpp b/Source/Engine/Audio/XAudio2/AudioBackendXAudio2.cpp
index 040ae5e02..6172234f4 100644
--- a/Source/Engine/Audio/XAudio2/AudioBackendXAudio2.cpp
+++ b/Source/Engine/Audio/XAudio2/AudioBackendXAudio2.cpp
@@ -118,6 +118,7 @@ namespace XAudio2
WAVEFORMATEX Format;
XAUDIO2_SEND_DESCRIPTOR Destination;
float Pitch;
+ float Pan;
float StartTime;
float DopplerFactor;
uint64 LastBufferStartSamplesPlayed;
@@ -140,6 +141,7 @@ namespace XAudio2
Destination.Flags = 0;
Destination.pOutputVoice = nullptr;
Pitch = 1.0f;
+ Pan = 0.0f;
StartTime = 0.0f;
IsDirty = false;
Is3D = false;
@@ -399,6 +401,7 @@ void AudioBackendXAudio2::Source_OnAdd(AudioSource* source)
aSource->Data.InnerRadius = FLAX_DST_TO_XAUDIO(source->GetMinDistance());
aSource->Is3D = source->Is3D();
aSource->Pitch = source->GetPitch();
+ aSource->Pan = source->GetPan();
aSource->DopplerFactor = source->GetDopplerFactor();
aSource->UpdateTransform(source);
aSource->UpdateVelocity(source);
@@ -455,6 +458,16 @@ void AudioBackendXAudio2::Source_PitchChanged(AudioSource* source)
}
}
+void AudioBackendXAudio2::Source_PanChanged(AudioSource* source)
+{
+ auto aSource = XAudio2::GetSource(source);
+ if (aSource)
+ {
+ aSource->Pan = source->GetPan();
+ aSource->IsDirty = true;
+ }
+}
+
void AudioBackendXAudio2::Source_IsLoopingChanged(AudioSource* source)
{
auto aSource = XAudio2::GetSource(source);
@@ -812,11 +825,12 @@ void AudioBackendXAudio2::Base_Update()
dsp.SrcChannelCount = source.Data.ChannelCount;
if (source.Is3D && listener)
{
+ // 3D sound
X3DAudioCalculate(XAudio2::X3DInstance, &listener->Data, &source.Data, X3DAUDIO_CALCULATE_MATRIX | X3DAUDIO_CALCULATE_DOPPLER, &dsp);
}
else
{
- // Stereo
+ // 2D sound
dsp.DopplerFactor = 1.0f;
Platform::MemoryClear(dsp.pMatrixCoefficients, sizeof(matrixCoefficients));
dsp.pMatrixCoefficients[0] = 1.0f;
@@ -828,6 +842,13 @@ void AudioBackendXAudio2::Base_Update()
{
dsp.pMatrixCoefficients[3] = 1.0f;
}
+ const float panLeft = Math::Min(1.0f - source.Pan, 1.0f);
+ const float panRight = Math::Min(1.0f + source.Pan, 1.0f);
+ if (source.Format.nChannels >= 2)
+ {
+ dsp.pMatrixCoefficients[0] *= panLeft;
+ dsp.pMatrixCoefficients[3] *= panRight;
+ }
}
const float frequencyRatio = dopplerFactor * source.Pitch * dsp.DopplerFactor * source.DopplerFactor;
diff --git a/Source/Engine/Audio/XAudio2/AudioBackendXAudio2.h b/Source/Engine/Audio/XAudio2/AudioBackendXAudio2.h
index 9fff0af03..1257cb2df 100644
--- a/Source/Engine/Audio/XAudio2/AudioBackendXAudio2.h
+++ b/Source/Engine/Audio/XAudio2/AudioBackendXAudio2.h
@@ -25,6 +25,7 @@ public:
void Source_TransformChanged(AudioSource* source) override;
void Source_VolumeChanged(AudioSource* source) override;
void Source_PitchChanged(AudioSource* source) override;
+ void Source_PanChanged(AudioSource* source) override;
void Source_IsLoopingChanged(AudioSource* source) override;
void Source_SpatialSetupChanged(AudioSource* source) override;
void Source_ClipLoaded(AudioSource* source) override;