Merge remote-tracking branch 'origin/master' into 1.6

# Conflicts:
#	Source/Editor/Scripting/CodeEditors/VisualStudioCodeEditor.cpp
#	Source/Tools/Flax.Build/Deps/Dependencies/OpenAL.cs
This commit is contained in:
Wojtek Figat
2023-04-22 18:59:41 +02:00
51 changed files with 1397 additions and 673 deletions

View File

@@ -31,17 +31,9 @@
#include "XAudio2/AudioBackendXAudio2.h"
#endif
const Char* ToString(AudioFormat value)
float AudioDataInfo::GetLength() const
{
switch (value)
{
case AudioFormat::Raw:
return TEXT("Raw");
case AudioFormat::Vorbis:
return TEXT("Vorbis");
default:
return TEXT("");
}
return (float)NumSamples / (float)Math::Max(1U, SampleRate * NumChannels);
}
Array<AudioListener*> Audio::Listeners;
@@ -57,6 +49,7 @@ namespace
float Volume = 1.0f;
int32 ActiveDeviceIndex = -1;
bool MuteOnFocusLoss = true;
bool EnableHRTF = true;
}
class AudioService : public EngineService
@@ -94,6 +87,7 @@ void AudioSettings::Apply()
if (AudioBackend::Instance != nullptr)
{
Audio::SetDopplerFactor(DopplerFactor);
Audio::SetEnableHRTF(EnableHRTF);
}
}
@@ -141,6 +135,19 @@ void Audio::SetDopplerFactor(float value)
AudioBackend::SetDopplerFactor(value);
}
bool Audio::GetEnableHRTF()
{
return EnableHRTF;
}
void Audio::SetEnableHRTF(bool value)
{
if (EnableHRTF == value)
return;
EnableHRTF = value;
AudioBackend::Listener::ReinitializeAll();
}
void Audio::OnAddListener(AudioListener* listener)
{
ASSERT(!Listeners.Contains(listener));

View File

@@ -0,0 +1,12 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
namespace FlaxEngine
{
partial struct AudioDataInfo
{
/// <summary>
/// Gets the length of the audio data (in seconds).
/// </summary>
public float Length => (float)NumSamples / (float)Mathf.Max(1U, SampleRate * NumChannels);
}
}

View File

@@ -13,12 +13,11 @@
/// </summary>
API_CLASS(Static) class FLAXENGINE_API Audio
{
DECLARE_SCRIPTING_TYPE_NO_SPAWN(Audio);
DECLARE_SCRIPTING_TYPE_NO_SPAWN(Audio);
friend class AudioStreamingHandler;
friend class AudioClip;
public:
/// <summary>
/// The audio listeners collection registered by the service.
/// </summary>
@@ -45,7 +44,6 @@ public:
API_EVENT() static Action ActiveDeviceChanged;
public:
/// <summary>
/// Gets the active device.
/// </summary>
@@ -65,7 +63,6 @@ public:
API_PROPERTY() static void SetActiveDeviceIndex(int32 index);
public:
/// <summary>
/// Gets the master volume applied to all the audio sources (normalized to range 0-1).
/// </summary>
@@ -90,8 +87,18 @@ public:
/// <param name="value">The value.</param>
API_PROPERTY() static void SetDopplerFactor(float value);
public:
/// <summary>
/// Gets the preference to use HRTF audio (when available on platform). Default is true.
/// </summary>
API_PROPERTY() static bool GetEnableHRTF();
/// <summary>
/// Sets the preference to use HRTF audio (when available on platform). Default is true.
/// </summary>
/// <param name="value">The value.</param>
API_PROPERTY() static void SetEnableHRTF(bool value);
public:
static void OnAddListener(AudioListener* listener);
static void OnRemoveListener(AudioListener* listener);

View File

@@ -16,6 +16,13 @@ class AudioBackend
public:
enum class FeatureFlags
{
None = 0,
// Supports multi-channel (incl. stereo) audio playback for spatial sources (3D), otherwise 3d audio needs to be in mono format.
SpatialMultiChannel = 1,
};
static AudioBackend* Instance;
private:
@@ -25,6 +32,7 @@ private:
virtual void Listener_OnRemove(AudioListener* listener) = 0;
virtual void Listener_VelocityChanged(AudioListener* listener) = 0;
virtual void Listener_TransformChanged(AudioListener* listener) = 0;
virtual void Listener_ReinitializeAll() = 0;
// Source
virtual void Source_OnAdd(AudioSource* source) = 0;
@@ -33,9 +41,9 @@ 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_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;
@@ -50,12 +58,13 @@ private:
virtual void Source_DequeueProcessedBuffers(AudioSource* source) = 0;
// Buffer
virtual void Buffer_Create(uint32& bufferId) = 0;
virtual void Buffer_Delete(uint32& bufferId) = 0;
virtual uint32 Buffer_Create() = 0;
virtual void Buffer_Delete(uint32 bufferId) = 0;
virtual void Buffer_Write(uint32 bufferId, byte* samples, const AudioDataInfo& info) = 0;
// Base
virtual const Char* Base_Name() = 0;
virtual FeatureFlags Base_Features() = 0;
virtual void Base_OnActiveDeviceChanged() = 0;
virtual void Base_SetDopplerFactor(float value) = 0;
virtual void Base_SetVolume(float value) = 0;
@@ -94,6 +103,11 @@ public:
{
Instance->Listener_TransformChanged(listener);
}
FORCE_INLINE static void ReinitializeAll()
{
Instance->Listener_ReinitializeAll();
}
};
class Source
@@ -130,19 +144,19 @@ 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);
}
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)
@@ -210,15 +224,14 @@ public:
{
public:
FORCE_INLINE static void Create(uint32& bufferId)
FORCE_INLINE static uint32 Create()
{
Instance->Buffer_Create(bufferId);
return Instance->Buffer_Create();
}
FORCE_INLINE static void Delete(uint32& bufferId)
FORCE_INLINE static void Delete(uint32 bufferId)
{
if (Instance)
Instance->Buffer_Delete(bufferId);
Instance->Buffer_Delete(bufferId);
}
FORCE_INLINE static void Write(uint32 bufferId, byte* samples, const AudioDataInfo& info)
@@ -232,6 +245,11 @@ public:
return Instance->Base_Name();
}
FORCE_INLINE static FeatureFlags Features()
{
return Instance->Base_Features();
}
FORCE_INLINE static void OnActiveDeviceChanged()
{
Instance->Base_OnActiveDeviceChanged();

View File

@@ -19,7 +19,7 @@ REGISTER_BINARY_ASSET_WITH_UPGRADER(AudioClip, "FlaxEngine.AudioClip", AudioClip
bool AudioClip::StreamingTask::Run()
{
AssetReference<AudioClip> ref = _asset.Get();
if (ref == nullptr)
if (ref == nullptr || AudioBackend::Instance == nullptr)
return true;
ScopeLock lock(ref->Locker);
const auto& queue = ref->StreamingQueue;
@@ -32,77 +32,25 @@ bool AudioClip::StreamingTask::Run()
{
const auto idx = queue[i];
uint32& bufferId = clip->Buffers[idx];
if (bufferId == AUDIO_BUFFER_ID_INVALID)
{
AudioBackend::Buffer::Create(bufferId);
bufferId = AudioBackend::Buffer::Create();
}
else
{
// Release unused data
AudioBackend::Buffer::Delete(bufferId);
bufferId = 0;
bufferId = AUDIO_BUFFER_ID_INVALID;
}
}
// Load missing buffers data
const auto format = clip->Format();
AudioDataInfo info = clip->AudioHeader.Info;
const uint32 bytesPerSample = info.BitDepth / 8;
// Load missing buffers data (from asset chunks)
for (int32 i = 0; i < queue.Count(); i++)
{
const auto idx = queue[i];
const uint32 bufferId = clip->Buffers[idx];
if (bufferId == AUDIO_BUFFER_ID_INVALID)
continue;
byte* data;
uint32 dataSize;
Array<byte> outTmp;
const auto chunk = clip->GetChunk(idx);
if (chunk == nullptr || chunk->IsMissing())
if (clip->WriteBuffer(queue[i]))
{
LOG(Warning, "Missing audio streaming data chunk.");
return true;
}
// Get raw data or decompress it
switch (format)
{
case AudioFormat::Vorbis:
{
#if COMPILE_WITH_OGG_VORBIS
OggVorbisDecoder decoder;
MemoryReadStream stream(chunk->Get(), chunk->Size());
AudioDataInfo outInfo;
if (decoder.Convert(&stream, outInfo, outTmp))
{
LOG(Warning, "Audio data decode failed (OggVorbisDecoder).");
return true;
}
// TODO: validate decompressed data header info?
data = outTmp.Get();
dataSize = outTmp.Count();
#else
LOG(Warning, "OggVorbisDecoder is disabled.");
return true;
#endif
}
break;
case AudioFormat::Raw:
{
data = chunk->Get();
dataSize = chunk->Size();
}
break;
default:
return true;
}
// Write samples to the audio buffer
info.NumSamples = dataSize / bytesPerSample;
AudioBackend::Buffer::Write(bufferId, data, info);
}
// Update the sources
@@ -150,11 +98,6 @@ AudioClip::~AudioClip()
ASSERT(_streamingTask == nullptr);
}
float AudioClip::GetLength() const
{
return AudioHeader.Info.NumSamples / static_cast<float>(Math::Max(1U, AudioHeader.Info.SampleRate * AudioHeader.Info.NumChannels));
}
float AudioClip::GetBufferStartTime(int32 bufferIndex) const
{
ASSERT(IsLoaded());
@@ -164,7 +107,7 @@ float AudioClip::GetBufferStartTime(int32 bufferIndex) const
int32 AudioClip::GetFirstBufferIndex(float time, float& offset) const
{
ASSERT(IsLoaded());
ASSERT(time >= 0 && time <= GetLength());
time = Math::Clamp(time, 0.0f, GetLength());
for (int32 i = 0; i < _totalChunks; i++)
{
@@ -205,6 +148,8 @@ bool AudioClip::ExtractData(Array<byte>& resultData, AudioDataInfo& resultDataIn
ASSERT(!IsVirtual());
if (WaitForLoaded())
return true;
ScopeLock lock(Locker);
auto storageLock = Storage->LockSafe();
// Allocate memory
ASSERT(_totalChunksSize > 0);
@@ -250,6 +195,7 @@ bool AudioClip::ExtractDataRaw(Array<byte>& resultData, AudioDataInfo& resultDat
{
if (WaitForLoaded())
return true;
ScopeLock lock(Locker);
switch (Format())
{
case AudioFormat::Raw:
@@ -362,7 +308,7 @@ bool AudioClip::init(AssetInitData& initData)
}
if (initData.CustomData.Length() != sizeof(AudioHeader))
{
LOG(Warning, "Missing audio clip header.");
LOG(Warning, "Missing audio data.");
return true;
}
@@ -422,64 +368,114 @@ Asset::LoadResult AudioClip::load()
// Load the whole audio at once
if (LoadChunk(0))
return LoadResult::CannotLoadData;
auto chunk0 = GetChunk(0);
if (chunk0 == nullptr || chunk0->IsMissing())
return LoadResult::MissingDataChunk;
// Create single buffer
if (!AudioBackend::Instance)
return LoadResult::Failed;
uint32 bufferId;
AudioBackend::Buffer::Create(bufferId);
Buffers[0] = bufferId;
Buffers[0] = AudioBackend::Buffer::Create();
// Write samples to the audio buffer
switch (AudioHeader.Format)
{
case AudioFormat::Vorbis:
{
#if COMPILE_WITH_OGG_VORBIS
OggVorbisDecoder decoder;
MemoryReadStream stream(chunk0->Get(), chunk0->Size());
AudioDataInfo outInfo;
Array<byte> outTmp;
if (decoder.Convert(&stream, outInfo, outTmp))
{
LOG(Warning, "Audio data decode failed (OggVorbisDecoder).");
return LoadResult::InvalidData;
}
AudioBackend::Buffer::Write(bufferId, outTmp.Get(), outInfo);
#endif
break;
}
case AudioFormat::Raw:
{
AudioBackend::Buffer::Write(bufferId, chunk0->Get(), AudioHeader.Info);
break;
}
default:
return LoadResult::InvalidData;
}
// Write data to audio buffer
if (WriteBuffer(0))
return LoadResult::Failed;
return LoadResult::Ok;
}
void AudioClip::unload(bool isReloading)
{
bool hasAnyBuffer = false;
for (const AUDIO_BUFFER_ID_TYPE bufferId : Buffers)
hasAnyBuffer |= bufferId != AUDIO_BUFFER_ID_INVALID;
// Stop any audio sources that are using this clip right now
// TODO: find better way to collect audio sources using audio clip and impl it for AudioStreamingHandler too
for (int32 sourceIndex = 0; sourceIndex < Audio::Sources.Count(); sourceIndex++)
{
const auto src = Audio::Sources[sourceIndex];
if (src->Clip == this)
src->Stop();
}
StopStreaming();
StreamingQueue.Clear();
if (Buffers.HasItems())
if (hasAnyBuffer && AudioBackend::Instance)
{
for (int32 i = 0; i < Buffers.Count(); i++)
for (AUDIO_BUFFER_ID_TYPE bufferId : Buffers)
{
auto bufferId = Buffers[i];
if (bufferId != AUDIO_BUFFER_ID_INVALID)
{
AudioBackend::Buffer::Delete(bufferId);
}
}
Buffers.Clear();
}
Buffers.Clear();
_totalChunks = 0;
Platform::MemoryClear(&AudioHeader, sizeof(AudioHeader));
}
bool AudioClip::WriteBuffer(int32 chunkIndex)
{
// Ignore if buffer is not created
const uint32 bufferId = Buffers[chunkIndex];
if (bufferId == AUDIO_BUFFER_ID_INVALID)
return false;
// Ensure audio backend exists
if (AudioBackend::Instance == nullptr)
return true;
const auto chunk = GetChunk(chunkIndex);
if (chunk == nullptr || chunk->IsMissing())
{
LOG(Warning, "Missing audio data.");
return true;
}
Span<byte> data;
Array<byte> tmp1, tmp2;
AudioDataInfo info = AudioHeader.Info;
const uint32 bytesPerSample = info.BitDepth / 8;
// Get raw data or decompress it
switch (Format())
{
case AudioFormat::Vorbis:
{
#if COMPILE_WITH_OGG_VORBIS
OggVorbisDecoder decoder;
MemoryReadStream stream(chunk->Get(), chunk->Size());
AudioDataInfo tmpInfo;
if (decoder.Convert(&stream, tmpInfo, tmp1))
{
LOG(Warning, "Audio data decode failed (OggVorbisDecoder).");
return true;
}
// TODO: validate decompressed data header info?
data = Span<byte>(tmp1.Get(), tmp1.Count());
#else
LOG(Warning, "OggVorbisDecoder is disabled.");
return true;
#endif
}
break;
case AudioFormat::Raw:
{
data = Span<byte>(chunk->Get(), chunk->Size());
}
break;
default:
return true;
}
info.NumSamples = data.Length() / bytesPerSample;
// Convert to Mono if used as 3D source and backend doesn't support it
if (Is3D() && info.NumChannels > 1 && EnumHasNoneFlags(AudioBackend::Features(), AudioBackend::FeatureFlags::SpatialMultiChannel))
{
const uint32 samplesPerChannel = info.NumSamples / info.NumChannels;
const uint32 monoBufferSize = samplesPerChannel * bytesPerSample;
tmp2.Resize(monoBufferSize);
AudioTool::ConvertToMono(data.Get(), tmp2.Get(), info.BitDepth, samplesPerChannel, info.NumChannels);
info.NumChannels = 1;
info.NumSamples = samplesPerChannel;
data = Span<byte>(tmp2.Get(), tmp2.Count());
}
// Write samples to the audio buffer
AudioBackend::Buffer::Write(bufferId, data.Get(), info);
return false;
}

View File

@@ -17,9 +17,9 @@ class AudioSource;
/// <seealso cref="BinaryAsset" />
API_CLASS(NoSpawn) class FLAXENGINE_API AudioClip : public BinaryAsset, public StreamableResource
{
DECLARE_BINARY_ASSET_HEADER(AudioClip, 2);
public:
DECLARE_BINARY_ASSET_HEADER(AudioClip, 2);
public:
/// <summary>
/// Audio Clip resource header structure, version 2. Added on 08.08.2019.
/// </summary>
@@ -40,12 +40,10 @@ public:
class StreamingTask : public ThreadPoolTask
{
private:
WeakAssetReference<AudioClip> _asset;
FlaxStorage::LockData _dataLock;
public:
/// <summary>
/// Init
/// </summary>
@@ -57,7 +55,6 @@ public:
}
public:
// [ThreadPoolTask]
bool HasReference(Object* resource) const override
{
@@ -65,28 +62,24 @@ public:
}
protected:
// [ThreadPoolTask]
bool Run() override;
void OnEnd() override;
};
private:
int32 _totalChunks;
int32 _totalChunksSize;
StreamingTask* _streamingTask;
float _buffersStartTimes[ASSET_FILE_DATA_CHUNKS + 1];
public:
/// <summary>
/// Finalizes an instance of the <see cref="AudioClip"/> class.
/// </summary>
~AudioClip();
public:
/// <summary>
/// The audio clip header data.
/// </summary>
@@ -103,11 +96,9 @@ public:
Array<int32, FixedAllocation<ASSET_FILE_DATA_CHUNKS>> StreamingQueue;
public:
/// <summary>
/// Gets the audio data format.
/// </summary>
/// <returns>The value.</returns>
API_PROPERTY() FORCE_INLINE AudioFormat Format() const
{
return AudioHeader.Format;
@@ -116,7 +107,6 @@ public:
/// <summary>
/// Gets the audio data info metadata.
/// </summary>
/// <returns>The value.</returns>
API_PROPERTY() FORCE_INLINE const AudioDataInfo& Info() const
{
return AudioHeader.Info;
@@ -125,7 +115,6 @@ public:
/// <summary>
/// Returns true if the sound source is three dimensional (volume and pitch varies based on listener distance and velocity).
/// </summary>
/// <returns>The value.</returns>
API_PROPERTY() FORCE_INLINE bool Is3D() const
{
return AudioHeader.Is3D;
@@ -134,7 +123,6 @@ public:
/// <summary>
/// Returns true if the sound is using data streaming.
/// </summary>
/// <returns>The value.</returns>
API_PROPERTY() FORCE_INLINE bool IsStreamable() const
{
return AudioHeader.Streamable;
@@ -143,7 +131,6 @@ public:
/// <summary>
/// Returns true if the sound data is during streaming by an async task.
/// </summary>
/// <returns>The streaming task existence value flag.</returns>
API_PROPERTY() FORCE_INLINE bool IsStreamingTaskActive() const
{
return _streamingTask != nullptr;
@@ -152,11 +139,12 @@ public:
/// <summary>
/// Gets the length of the audio clip (in seconds).
/// </summary>
/// <returns>The value.</returns>
API_PROPERTY() float GetLength() const;
API_PROPERTY() float GetLength() const
{
return AudioHeader.Info.GetLength();
}
public:
/// <summary>
/// Gets the buffer start time (in seconds).
/// </summary>
@@ -173,7 +161,6 @@ public:
int32 GetFirstBufferIndex(float time, float& offset) const;
public:
/// <summary>
/// Extracts the source audio data from the asset storage. Loads the whole asset. The result data is in an asset format.
/// </summary>
@@ -199,10 +186,9 @@ public:
API_FUNCTION() bool ExtractDataRaw(API_PARAM(Out) Array<byte>& resultData, API_PARAM(Out) AudioDataInfo& resultDataInfo);
public:
// [BinaryAsset]
void CancelStreaming() override;
// [StreamableResource]
int32 GetMaxResidency() const override;
int32 GetCurrentResidency() const override;
@@ -213,9 +199,12 @@ public:
void CancelStreamingTasks() override;
protected:
// [BinaryAsset]
bool init(AssetInitData& initData) override;
LoadResult load() override;
void unload(bool isReloading) override;
private:
// Writes audio samples into Audio Backend buffer and handles automatic decompression or format conversion for runtime playback.
bool WriteBuffer(int32 chunkIndex);
};

View File

@@ -10,9 +10,9 @@
/// </summary>
API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API AudioSettings : public SettingsBase
{
DECLARE_SCRIPTING_TYPE_MINIMAL(AudioSettings);
public:
DECLARE_SCRIPTING_TYPE_MINIMAL(AudioSettings);
public:
/// <summary>
/// If checked, audio playback will be disabled in build game. Can be used if game uses custom audio playback engine.
/// </summary>
@@ -31,8 +31,14 @@ public:
API_FIELD(Attributes="EditorOrder(200), DefaultValue(true), EditorDisplay(\"General\", \"Mute On Focus Loss\")")
bool MuteOnFocusLoss = true;
public:
/// <summary>
/// Enables or disables HRTF audio for in-engine processing of 3D audio (if supported by platform).
/// If enabled, the user should be using two-channel/headphones audio output and have all other surround virtualization disabled (Atmos, DTS:X, vendor specific, etc.)
/// </summary>
API_FIELD(Attributes="EditorOrder(300), DefaultValue(true), EditorDisplay(\"Spatial Audio\")")
bool EnableHRTF = true;
public:
/// <summary>
/// Gets the instance of the settings asset (default value if missing). Object returned by this method is always loaded with valid data to use.
/// </summary>
@@ -46,5 +52,6 @@ public:
DESERIALIZE(DisableAudio);
DESERIALIZE(DopplerFactor);
DESERIALIZE(MuteOnFocusLoss);
DESERIALIZE(EnableHRTF);
}
};

View File

@@ -16,10 +16,10 @@ AudioSource::AudioSource(const SpawnParams& params)
, _velocity(Vector3::Zero)
, _volume(1.0f)
, _pitch(1.0f)
, _minDistance(1.0f)
, _attenuation(1.0f)
, _minDistance(1000.0f)
, _loop(false)
, _playOnStart(false)
, _allowSpatialization(true)
{
Clip.Changed.Bind<AudioSource, &AudioSource::OnClipChanged>(this);
Clip.Loaded.Bind<AudioSource, &AudioSource::OnClipLoaded>(this);
@@ -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,30 @@ 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::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)
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 +76,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 +86,28 @@ 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::SetDopplerFactor(float value)
{
value = Math::Max(0.0f, value);
if (Math::NearEqual(_dopplerFactor, value))
return;
_dopplerFactor = value;
if (SourceIDs.HasItems())
AudioBackend::Source::SpatialSetupChanged(this);
}
void AudioSource::SetAllowSpatialization(bool value)
{
if (_allowSpatialization == value)
return;
_allowSpatialization = value;
if (SourceIDs.HasItems())
AudioBackend::Source::SpatialSetupChanged(this);
}
void AudioSource::Play()
@@ -231,7 +241,7 @@ bool AudioSource::Is3D() const
{
if (Clip == nullptr || Clip->WaitForLoaded())
return false;
return Clip->Is3D();
return _allowSpatialization && Clip->Is3D();
}
void AudioSource::RequestStreamingBuffersUpdate()
@@ -313,6 +323,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
@@ -323,10 +349,13 @@ 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);
SERIALIZE_MEMBER(Loop, _loop);
SERIALIZE_MEMBER(PlayOnStart, _playOnStart);
SERIALIZE_MEMBER(AllowSpatialization, _allowSpatialization);
}
void AudioSource::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
@@ -334,13 +363,16 @@ void AudioSource::Deserialize(DeserializeStream& stream, ISerializeModifier* mod
// Base
Actor::Deserialize(stream, modifier);
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);
DESERIALIZE_MEMBER(Loop, _loop);
DESERIALIZE_MEMBER(PlayOnStart, _playOnStart);
DESERIALIZE_MEMBER(AllowSpatialization, _allowSpatialization);
DESERIALIZE(Clip);
}
bool AudioSource::HasContentLoaded() const

View File

@@ -46,10 +46,13 @@ private:
Vector3 _prevPos;
float _volume;
float _pitch;
float _pan = 0.0f;
float _minDistance;
float _attenuation;
float _attenuation = 1.0f;
float _dopplerFactor = 1.0f;
bool _loop;
bool _playOnStart;
bool _allowSpatialization;
bool _isActuallyPlayingSth = false;
bool _needToUpdateStreamingBuffers = false;
@@ -107,6 +110,20 @@ public:
/// </summary>
API_PROPERTY() void SetPitch(float value);
/// <summary>
/// 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.
/// </summary>
API_PROPERTY(Attributes="EditorOrder(30), DefaultValue(0.0f), Limit(-1.0f, 1.0f), EditorDisplay(\"Audio Source\")")
FORCE_INLINE float GetPan() const
{
return _pan;
}
/// <summary>
/// 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.
/// </summary>
API_PROPERTY() void SetPan(float value);
/// <summary>
/// Determines whether the audio clip should loop when it finishes playing.
/// </summary>
@@ -138,8 +155,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,10 +176,38 @@ 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);
/// <summary>
/// Gets the doppler effect factor. Scale for source velocity. Default is 1.
/// </summary>
API_PROPERTY(Attributes="EditorOrder(75), DefaultValue(1.0f), Limit(0, float.MaxValue, 0.1f), EditorDisplay(\"Audio Source\")")
FORCE_INLINE float GetDopplerFactor() const
{
return _dopplerFactor;
}
/// <summary>
/// Sets the doppler effect factor. Scale for source velocity. Default is 1.
/// </summary>
API_PROPERTY() void SetDopplerFactor(float value);
/// <summary>
/// If checked, source can play spatial 3d audio (when audio clip supports it), otherwise will always play as 2d sound. At 0, no distance attenuation ever occurs.
/// </summary>
API_PROPERTY(Attributes="EditorOrder(80), DefaultValue(true), EditorDisplay(\"Audio Source\")")
FORCE_INLINE bool GetAllowSpatialization() const
{
return _allowSpatialization;
}
/// <summary>
/// If checked, source can play spatial 3d audio (when audio clip supports it), otherwise will always play as 2d sound.
/// </summary>
API_PROPERTY() void SetAllowSpatialization(bool value);
public:
/// <summary>
/// Starts playing the currently assigned audio clip.
@@ -257,6 +301,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

@@ -22,6 +22,10 @@ void AudioBackendNone::Listener_TransformChanged(AudioListener* listener)
{
}
void AudioBackendNone::Listener_ReinitializeAll()
{
}
void AudioBackendNone::Source_OnAdd(AudioSource* source)
{
source->Restore();
@@ -48,15 +52,15 @@ void AudioBackendNone::Source_PitchChanged(AudioSource* source)
{
}
void AudioBackendNone::Source_PanChanged(AudioSource* source)
{
}
void AudioBackendNone::Source_IsLoopingChanged(AudioSource* source)
{
}
void AudioBackendNone::Source_MinDistanceChanged(AudioSource* source)
{
}
void AudioBackendNone::Source_AttenuationChanged(AudioSource* source)
void AudioBackendNone::Source_SpatialSetupChanged(AudioSource* source)
{
}
@@ -110,12 +114,12 @@ void AudioBackendNone::Source_DequeueProcessedBuffers(AudioSource* source)
{
}
void AudioBackendNone::Buffer_Create(uint32& bufferId)
uint32 AudioBackendNone::Buffer_Create()
{
bufferId = 1;
return 1;
}
void AudioBackendNone::Buffer_Delete(uint32& bufferId)
void AudioBackendNone::Buffer_Delete(uint32 bufferId)
{
}
@@ -128,6 +132,11 @@ const Char* AudioBackendNone::Base_Name()
return TEXT("None");
}
AudioBackend::FeatureFlags AudioBackendNone::Base_Features()
{
return FeatureFlags::None;
}
void AudioBackendNone::Base_OnActiveDeviceChanged()
{
}

View File

@@ -18,15 +18,16 @@ public:
void Listener_OnRemove(AudioListener* listener) override;
void Listener_VelocityChanged(AudioListener* listener) override;
void Listener_TransformChanged(AudioListener* listener) override;
void Listener_ReinitializeAll() override;
void Source_OnAdd(AudioSource* source) override;
void Source_OnRemove(AudioSource* source) override;
void Source_VelocityChanged(AudioSource* source) override;
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_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;
@@ -39,10 +40,11 @@ public:
void Source_GetQueuedBuffersCount(AudioSource* source, int32& queuedBuffersCount) override;
void Source_QueueBuffer(AudioSource* source, uint32 bufferId) override;
void Source_DequeueProcessedBuffers(AudioSource* source) override;
void Buffer_Create(uint32& bufferId) override;
void Buffer_Delete(uint32& bufferId) override;
uint32 Buffer_Create() override;
void Buffer_Delete(uint32 bufferId) override;
void Buffer_Write(uint32 bufferId, byte* samples, const AudioDataInfo& info) override;
const Char* Base_Name() override;
FeatureFlags Base_Features() override;
void Base_OnActiveDeviceChanged() override;
void Base_SetDopplerFactor(float value) override;
void Base_SetVolume(float value) override;

View File

@@ -17,10 +17,14 @@
//#define AL_LIBTYPE_STATIC
#include <OpenAL/al.h>
#include <OpenAL/alc.h>
#include <OpenAL/alext.h>
#define ALC_MULTIPLE_LISTENERS 0
#define FLAX_POS_TO_OAL(vec) ((ALfloat)vec.X * -0.01f), ((ALfloat)vec.Y * 0.01f), ((ALfloat)vec.Z * -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
#define ALC_CHECK_ERROR(method)
#else
@@ -55,8 +59,8 @@
namespace ALC
{
ALCdevice* Device = nullptr;
bool AL_EXT_float32 = false;
Array<ALCcontext*, FixedAllocation<AUDIO_MAX_LISTENERS>> Contexts;
AudioBackend::FeatureFlags Features = AudioBackend::FeatureFlags::None;
bool IsExtensionSupported(const char* extension)
{
@@ -100,7 +104,7 @@ namespace ALC
{
alcMakeContextCurrent(nullptr);
for (auto& context : Contexts)
for (ALCcontext* context : Contexts)
alcDestroyContext(context);
Contexts.Clear();
}
@@ -111,8 +115,8 @@ namespace ALC
{
AudioBackend::Listener::TransformChanged(listener);
const auto& velocity = listener->GetVelocity();
alListener3f(AL_VELOCITY, FLAX_POS_TO_OAL(velocity));
const Vector3 velocity = listener->GetVelocity();
alListener3f(AL_VELOCITY, FLAX_VEL_TO_OAL(velocity));
alListenerf(AL_GAIN, Audio::GetVolume());
}
}
@@ -124,8 +128,6 @@ namespace ALC
ASSERT(source->SourceIDs.IsEmpty());
const bool is3D = source->Is3D();
const bool loop = source->GetIsLooping() && !source->UseStreaming();
const Vector3 position = is3D ? source->GetPosition() : Vector3::Zero;
const Vector3 velocity = is3D ? source->GetVelocity() : Vector3::Zero;
ALC_FOR_EACH_CONTEXT()
uint32 sourceID = 0;
@@ -140,13 +142,33 @@ namespace ALC
alSourcef(sourceID, AL_GAIN, source->GetVolume());
alSourcef(sourceID, AL_PITCH, source->GetPitch());
alSourcef(sourceID, AL_SEC_OFFSET, 0.0f);
alSourcef(sourceID, AL_REFERENCE_DISTANCE, source->GetMinDistance());
alSourcef(sourceID, AL_ROLLOFF_FACTOR, source->GetAttenuation());
alSourcei(sourceID, AL_LOOPING, loop);
alSourcei(sourceID, AL_SOURCE_RELATIVE, !is3D);
alSourcei(sourceID, AL_BUFFER, 0);
alSource3f(sourceID, AL_POSITION, FLAX_POS_TO_OAL(position));
alSource3f(sourceID, AL_VELOCITY, FLAX_POS_TO_OAL(velocity));
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()));
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_DOPPLER_FACTOR, 1.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);
}
#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
@@ -156,11 +178,11 @@ namespace ALC
void RebuildContexts(bool isChangingDevice)
{
LOG(Info, "Rebuild audio contexts");
LOG(Info, "Rebuilding audio contexts");
if (!isChangingDevice)
{
for (auto& source : Audio::Sources)
for (AudioSource* source : Audio::Sources)
source->Cleanup();
}
@@ -169,28 +191,36 @@ namespace ALC
if (Device == nullptr)
return;
ALCint attrsHrtf[] = { ALC_HRTF_SOFT, ALC_TRUE };
const ALCint* attrList = nullptr;
if (Audio::GetEnableHRTF())
{
LOG(Info, "Enabling OpenAL HRTF");
attrList = attrsHrtf;
}
#if ALC_MULTIPLE_LISTENERS
const int32 numListeners = Audio::Listeners.Count();
const int32 numContexts = numListeners > 1 ? numListeners : 1;
Contexts.Resize(numContexts);
ALC_FOR_EACH_CONTEXT()
ALCcontext* context = alcCreateContext(Device, nullptr);
ALCcontext* context = alcCreateContext(Device, attrList);
Contexts[i] = context;
}
#else
Contexts.Resize(1);
Contexts[0] = alcCreateContext(Device, nullptr);
Contexts[0] = alcCreateContext(Device, attrList);
#endif
// If only one context is available keep it active as an optimization.
// 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);
}
}
@@ -276,8 +306,8 @@ void AudioBackendOAL::Listener_OnAdd(AudioListener* listener)
ALC::RebuildContexts(false);
#else
AudioBackend::Listener::TransformChanged(listener);
const auto& velocity = listener->GetVelocity();
alListener3f(AL_VELOCITY, FLAX_POS_TO_OAL(velocity));
const Vector3 velocity = listener->GetVelocity();
alListener3f(AL_VELOCITY, FLAX_VEL_TO_OAL(velocity));
alListenerf(AL_GAIN, Audio::GetVolume());
#endif
}
@@ -293,28 +323,34 @@ void AudioBackendOAL::Listener_VelocityChanged(AudioListener* listener)
{
ALC_GET_LISTENER_CONTEXT(listener)
const auto& velocity = listener->GetVelocity();
alListener3f(AL_VELOCITY, FLAX_POS_TO_OAL(velocity));
const Vector3 velocity = listener->GetVelocity();
alListener3f(AL_VELOCITY, FLAX_VEL_TO_OAL(velocity));
}
void AudioBackendOAL::Listener_TransformChanged(AudioListener* listener)
{
ALC_GET_LISTENER_CONTEXT(listener)
const Vector3& position = listener->GetPosition();
const Quaternion& orientation = listener->GetOrientation();
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,
orientation * Vector3::Forward * flipX,
// Up
orientation * Vector3::Up
orientation * Vector3::Up * flipX
};
alListenerfv(AL_ORIENTATION, (float*)alOrientation);
alListener3f(AL_POSITION, FLAX_POS_TO_OAL(position));
}
void AudioBackendOAL::Listener_ReinitializeAll()
{
ALC::RebuildContexts(false);
}
void AudioBackendOAL::Source_OnAdd(AudioSource* source)
{
ALC::Source::Rebuild(source);
@@ -327,25 +363,21 @@ void AudioBackendOAL::Source_OnRemove(AudioSource* source)
void AudioBackendOAL::Source_VelocityChanged(AudioSource* source)
{
const bool is3D = source->Is3D();
const Vector3 velocity = is3D ? source->GetVelocity() : Vector3::Zero;
if (!source->Is3D())
return;
ALC_FOR_EACH_CONTEXT()
const uint32 sourceID = source->SourceIDs[i];
alSource3f(sourceID, AL_VELOCITY, FLAX_POS_TO_OAL(velocity));
alSource3f(sourceID, AL_VELOCITY, FLAX_VEL_TO_OAL(source->GetVelocity()));
}
}
void AudioBackendOAL::Source_TransformChanged(AudioSource* source)
{
const bool is3D = source->Is3D();
const Vector3 position = is3D ? source->GetPosition() : Vector3::Zero;
if (!source->Is3D())
return;
ALC_FOR_EACH_CONTEXT()
const uint32 sourceID = source->SourceIDs[i];
alSource3f(sourceID, AL_POSITION, FLAX_POS_TO_OAL(position));
alSource3f(sourceID, AL_POSITION, FLAX_POS_TO_OAL(source->GetPosition()));
}
}
@@ -353,7 +385,6 @@ void AudioBackendOAL::Source_VolumeChanged(AudioSource* source)
{
ALC_FOR_EACH_CONTEXT()
const uint32 sourceID = source->SourceIDs[i];
alSourcef(sourceID, AL_GAIN, source->GetVolume());
}
}
@@ -362,36 +393,52 @@ void AudioBackendOAL::Source_PitchChanged(AudioSource* source)
{
ALC_FOR_EACH_CONTEXT()
const uint32 sourceID = source->SourceIDs[i];
alSourcef(sourceID, AL_PITCH, source->GetPitch());
}
}
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();
ALC_FOR_EACH_CONTEXT()
const uint32 sourceID = source->SourceIDs[i];
alSourcei(sourceID, AL_LOOPING, loop);
}
}
void AudioBackendOAL::Source_MinDistanceChanged(AudioSource* source)
void AudioBackendOAL::Source_SpatialSetupChanged(AudioSource* source)
{
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)
{
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)
{
#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()));
}
else
{
alSourcef(sourceID, AL_ROLLOFF_FACTOR, 0.0f);
alSourcef(sourceID, AL_DOPPLER_FACTOR, 1.0f);
alSourcef(sourceID, AL_REFERENCE_DISTANCE, 0.0f);
}
}
}
@@ -405,7 +452,6 @@ void AudioBackendOAL::Source_ClipLoaded(AudioSource* source)
ALC_FOR_EACH_CONTEXT()
const uint32 sourceID = source->SourceIDs[i];
alSourcei(sourceID, AL_SOURCE_RELATIVE, !is3D);
alSourcei(sourceID, AL_LOOPING, loop);
}
@@ -415,7 +461,6 @@ void AudioBackendOAL::Source_Cleanup(AudioSource* source)
{
ALC_FOR_EACH_CONTEXT()
const uint32 sourceID = source->SourceIDs[i];
alSourcei(sourceID, AL_BUFFER, 0);
ALC_CHECK_ERROR(alSourcei);
alDeleteSources(1, &sourceID);
@@ -479,7 +524,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;
@@ -544,13 +589,15 @@ void AudioBackendOAL::Source_DequeueProcessedBuffers(AudioSource* source)
}
}
void AudioBackendOAL::Buffer_Create(uint32& bufferId)
uint32 AudioBackendOAL::Buffer_Create()
{
uint32 bufferId;
alGenBuffers(1, &bufferId);
ALC_CHECK_ERROR(alGenBuffers);
return bufferId;
}
void AudioBackendOAL::Buffer_Delete(uint32& bufferId)
void AudioBackendOAL::Buffer_Delete(uint32 bufferId)
{
alDeleteBuffers(1, &bufferId);
ALC_CHECK_ERROR(alDeleteBuffers);
@@ -567,7 +614,7 @@ void AudioBackendOAL::Buffer_Write(uint32 bufferId, byte* samples, const AudioDa
{
if (info.BitDepth > 16)
{
if (ALC::AL_EXT_float32)
if (ALC::IsExtensionSupported("AL_EXT_float32"))
{
const uint32 bufferSize = info.NumSamples * sizeof(float);
float* sampleBufferFloat = (float*)Allocator::Allocate(bufferSize);
@@ -666,10 +713,15 @@ const Char* AudioBackendOAL::Base_Name()
return TEXT("OpenAL");
}
AudioBackend::FeatureFlags AudioBackendOAL::Base_Features()
{
return ALC::Features;
}
void AudioBackendOAL::Base_OnActiveDeviceChanged()
{
// Cleanup
for (auto& source : Audio::Sources)
for (AudioSource* source : Audio::Sources)
source->Cleanup();
ALC::ClearContexts();
if (ALC::Device != nullptr)
@@ -679,7 +731,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)
{
@@ -688,7 +740,6 @@ void AudioBackendOAL::Base_OnActiveDeviceChanged()
}
// Setup
ALC::AL_EXT_float32 = ALC::IsExtensionSupported("AL_EXT_float32");
ALC::RebuildContexts(true);
}
@@ -800,10 +851,14 @@ bool AudioBackendOAL::Base_Init()
}
// Init
ALC::AL_EXT_float32 = ALC::IsExtensionSupported("AL_EXT_float32");
SetDopplerFactor(AudioSettings::Get()->DopplerFactor);
Base_SetDopplerFactor(AudioSettings::Get()->DopplerFactor);
alDistanceModel(AL_INVERSE_DISTANCE_CLAMPED); // Default attenuation model
ALC::RebuildContexts(true);
Audio::SetActiveDeviceIndex(activeDeviceIndex);
#ifdef AL_SOFT_source_spatialize
if (ALC::IsExtensionSupported("AL_SOFT_source_spatialize"))
ALC::Features = EnumAddFlags(ALC::Features, FeatureFlags::SpatialMultiChannel);
#endif
// Log service info
LOG(Info, "{0} ({1})", String(alGetString(AL_RENDERER)), String(alGetString(AL_VERSION)));

View File

@@ -18,15 +18,16 @@ public:
void Listener_OnRemove(AudioListener* listener) override;
void Listener_VelocityChanged(AudioListener* listener) override;
void Listener_TransformChanged(AudioListener* listener) override;
void Listener_ReinitializeAll() override;
void Source_OnAdd(AudioSource* source) override;
void Source_OnRemove(AudioSource* source) override;
void Source_VelocityChanged(AudioSource* source) override;
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_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;
@@ -39,10 +40,11 @@ public:
void Source_GetQueuedBuffersCount(AudioSource* source, int32& queuedBuffersCount) override;
void Source_QueueBuffer(AudioSource* source, uint32 bufferId) override;
void Source_DequeueProcessedBuffers(AudioSource* source) override;
void Buffer_Create(uint32& bufferId) override;
void Buffer_Delete(uint32& bufferId) override;
uint32 Buffer_Create() override;
void Buffer_Delete(uint32 bufferId) override;
void Buffer_Write(uint32 bufferId, byte* samples, const AudioDataInfo& info) override;
const Char* Base_Name() override;
FeatureFlags Base_Features() override;
void Base_OnActiveDeviceChanged() override;
void Base_SetDopplerFactor(float value) override;
void Base_SetVolume(float value) override;

View File

@@ -25,12 +25,10 @@ API_ENUM() enum class AudioFormat
Vorbis,
};
const Char* ToString(AudioFormat value);
/// <summary>
/// Meta-data describing a chunk of audio.
/// </summary>
API_STRUCT() struct AudioDataInfo
API_STRUCT(NoDefault) struct AudioDataInfo
{
DECLARE_SCRIPTING_TYPE_MINIMAL(AudioDataInfo);
@@ -53,4 +51,9 @@ DECLARE_SCRIPTING_TYPE_MINIMAL(AudioDataInfo);
/// The number of bits per sample.
/// </summary>
API_FIELD() uint32 BitDepth;
/// <summary>
/// Gets the length of the audio data (in seconds).
/// </summary>
float GetLength() const;
};

View File

@@ -9,6 +9,7 @@
#include "Engine/Audio/Audio.h"
#include "Engine/Audio/AudioSource.h"
#include "Engine/Audio/AudioListener.h"
#include "Engine/Threading/Threading.h"
#if PLATFORM_WINDOWS
// Tweak Win ver
@@ -26,7 +27,10 @@
#define MAX_OUTPUT_CHANNELS 8
#define MAX_CHANNELS_MATRIX_SIZE (MAX_INPUT_CHANNELS*MAX_OUTPUT_CHANNELS)
#define FLAX_POS_TO_XAUDIO(vec) X3DAUDIO_VECTOR(vec.X * 0.01f, vec.Y * 0.01f, vec.Z * 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))
namespace XAudio2
@@ -68,7 +72,7 @@ namespace XAudio2
{
const Vector3& velocity = AudioListener->GetVelocity();
Data.Velocity = FLAX_POS_TO_XAUDIO(velocity);
Data.Velocity = FLAX_VEL_TO_XAUDIO(velocity);
}
};
@@ -103,7 +107,6 @@ namespace XAudio2
}
public:
AudioSource* Source;
void PeekSamples();
@@ -116,12 +119,15 @@ namespace XAudio2
WAVEFORMATEX Format;
XAUDIO2_SEND_DESCRIPTOR Destination;
float Pitch;
float Pan;
float StartTime;
float DopplerFactor;
uint64 LastBufferStartSamplesPlayed;
int32 BuffersProcessed;
bool IsDirty;
bool Is3D;
bool IsPlaying;
bool IsForceMono3D;
VoiceCallback Callback;
Source()
@@ -137,6 +143,7 @@ namespace XAudio2
Destination.Flags = 0;
Destination.pOutputVoice = nullptr;
Pitch = 1.0f;
Pan = 0.0f;
StartTime = 0.0f;
IsDirty = false;
Is3D = false;
@@ -166,7 +173,7 @@ namespace XAudio2
{
const Vector3& velocity = source->GetVelocity();
Data.Velocity = FLAX_POS_TO_XAUDIO(velocity);
Data.Velocity = FLAX_VEL_TO_XAUDIO(velocity);
}
};
@@ -198,13 +205,12 @@ namespace XAudio2
DWORD ChannelMask;
UINT32 SampleRate;
UINT32 Channels;
bool UseRedirectToLFE;
bool ForceDirty = true;
Listener Listeners[AUDIO_MAX_LISTENERS];
CriticalSection Locker;
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()
{
@@ -302,7 +308,6 @@ void AudioBackendXAudio2::Listener_OnAdd(AudioListener* listener)
void AudioBackendXAudio2::Listener_OnRemove(AudioListener* listener)
{
// Free listener
XAudio2::Listener* aListener = XAudio2::GetListener(listener);
if (aListener)
{
@@ -331,12 +336,18 @@ void AudioBackendXAudio2::Listener_TransformChanged(AudioListener* listener)
}
}
void AudioBackendXAudio2::Listener_ReinitializeAll()
{
// TODO: Implement XAudio2 reinitialization; read HRTF audio value from Audio class
}
void AudioBackendXAudio2::Source_OnAdd(AudioSource* source)
{
// Skip if has no clip (needs audio data to create a source - needs data format information)
if (source->Clip == nullptr || !source->Clip->IsLoaded())
return;
auto clip = source->Clip.Get();
ScopeLock lock(XAudio2::Locker);
// Get first free source
XAudio2::Source* aSource = nullptr;
@@ -360,10 +371,10 @@ void AudioBackendXAudio2::Source_OnAdd(AudioSource* source)
}
// Initialize audio data format information (from clip)
auto& header = clip->AudioHeader;
const auto& header = clip->AudioHeader;
auto& format = aSource->Format;
format.wFormatTag = WAVE_FORMAT_PCM;
format.nChannels = header.Info.NumChannels;
format.nChannels = source->Is3D() ? 1 : header.Info.NumChannels; // 3d audio is always mono (AudioClip auto-converts before buffer write)
format.nSamplesPerSec = header.Info.SampleRate;
format.wBitsPerSample = header.Info.BitDepth;
format.nBlockAlign = (WORD)(format.nChannels * (format.wBitsPerSample / 8));
@@ -389,12 +400,16 @@ void AudioBackendXAudio2::Source_OnAdd(AudioSource* source)
// Prepare source state
aSource->Callback.Source = source;
aSource->IsDirty = true;
aSource->Data.ChannelCount = header.Info.NumChannels;
aSource->Data.ChannelCount = format.nChannels;
aSource->Data.InnerRadius = FLAX_DST_TO_XAUDIO(source->GetMinDistance());
aSource->Is3D = source->Is3D();
aSource->IsForceMono3D = header.Is3D && header.Info.NumChannels > 1;
aSource->Pitch = source->GetPitch();
aSource->Data.InnerRadius = source->GetMinDistance();
aSource->Pan = source->GetPan();
aSource->DopplerFactor = source->GetDopplerFactor();
aSource->UpdateTransform(source);
aSource->UpdateVelocity(source);
aSource->Voice->SetVolume(source->GetVolume());
// 0 is invalid ID so shift them
sourceID++;
@@ -406,6 +421,7 @@ void AudioBackendXAudio2::Source_OnAdd(AudioSource* source)
void AudioBackendXAudio2::Source_OnRemove(AudioSource* source)
{
ScopeLock lock(XAudio2::Locker);
source->Cleanup();
}
@@ -448,6 +464,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);
@@ -462,8 +488,10 @@ void AudioBackendXAudio2::Source_IsLoopingChanged(AudioSource* source)
// Looping is defined during buffer submission so reset source buffer (this is called only for non-streamable sources that ue single buffer)
XAudio2::Locker.Lock();
const uint32 bufferId = source->Clip->Buffers[0];
XAudio2::Buffer* aBuffer = XAudio2::Buffers[bufferId - 1];
XAudio2::Locker.Unlock();
const bool isPlaying = source->IsActuallyPlayingSth();
if (isPlaying)
@@ -491,23 +519,29 @@ 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
auto clip = source->Clip.Get();
if (clip && clip->IsLoaded())
{
const auto& header = clip->AudioHeader;
aSource->Data.ChannelCount = source->Is3D() ? 1 : header.Info.NumChannels; // 3d audio is always mono (AudioClip auto-converts before buffer write)
aSource->IsForceMono3D = header.Is3D && header.Info.NumChannels > 1;
}
aSource->Is3D = source->Is3D();
aSource->DopplerFactor = source->GetDopplerFactor();
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)
{
ScopeLock lock(XAudio2::Locker);
auto aSource = XAudio2::GetSource(source);
if (!aSource)
{
@@ -518,6 +552,7 @@ void AudioBackendXAudio2::Source_ClipLoaded(AudioSource* source)
void AudioBackendXAudio2::Source_Cleanup(AudioSource* source)
{
ScopeLock lock(XAudio2::Locker);
auto aSource = XAudio2::GetSource(source);
if (!aSource)
return;
@@ -590,7 +625,8 @@ float AudioBackendXAudio2::Source_GetCurrentBufferTime(const AudioSource* source
const auto& clipInfo = source->Clip->AudioHeader.Info;
XAUDIO2_VOICE_STATE state;
aSource->Voice->GetState(&state);
const UINT32 totalSamples = clipInfo.NumSamples / clipInfo.NumChannels;
const uint32 numChannels = clipInfo.NumChannels;
const uint32 totalSamples = clipInfo.NumSamples / numChannels;
state.SamplesPlayed -= aSource->LastBufferStartSamplesPlayed % totalSamples; // Offset by the last buffer start to get time relative to its begin
time = aSource->StartTime + (state.SamplesPlayed % totalSamples) / static_cast<float>(Math::Max(1U, clipInfo.SampleRate));
}
@@ -603,8 +639,10 @@ void AudioBackendXAudio2::Source_SetNonStreamingBuffer(AudioSource* source)
if (!aSource)
return;
XAudio2::Locker.Lock();
const uint32 bufferId = source->Clip->Buffers[0];
XAudio2::Buffer* aBuffer = XAudio2::Buffers[bufferId - 1];
XAudio2::Locker.Unlock();
XAUDIO2_BUFFER buffer = { 0 };
buffer.pContext = aBuffer;
@@ -666,8 +704,11 @@ void AudioBackendXAudio2::Source_DequeueProcessedBuffers(AudioSource* source)
}
}
void AudioBackendXAudio2::Buffer_Create(uint32& bufferId)
uint32 AudioBackendXAudio2::Buffer_Create()
{
uint32 bufferId;
ScopeLock lock(XAudio2::Locker);
// Get first free buffer slot
XAudio2::Buffer* aBuffer = nullptr;
for (int32 i = 0; i < XAudio2::Buffers.Count(); i++)
@@ -689,10 +730,12 @@ void AudioBackendXAudio2::Buffer_Create(uint32& bufferId)
}
aBuffer->Data.Resize(0);
return bufferId;
}
void AudioBackendXAudio2::Buffer_Delete(uint32& bufferId)
void AudioBackendXAudio2::Buffer_Delete(uint32 bufferId)
{
ScopeLock lock(XAudio2::Locker);
XAudio2::Buffer*& aBuffer = XAudio2::Buffers[bufferId - 1];
aBuffer->Data.Resize(0);
Delete(aBuffer);
@@ -701,7 +744,9 @@ void AudioBackendXAudio2::Buffer_Delete(uint32& bufferId)
void AudioBackendXAudio2::Buffer_Write(uint32 bufferId, byte* samples, const AudioDataInfo& info)
{
XAudio2::Locker.Lock();
XAudio2::Buffer* aBuffer = XAudio2::Buffers[bufferId - 1];
XAudio2::Locker.Unlock();
const uint32 bytesPerSample = info.BitDepth / 8;
const int32 samplesLength = info.NumSamples * bytesPerSample;
@@ -715,6 +760,11 @@ const Char* AudioBackendXAudio2::Base_Name()
return TEXT("XAudio2");
}
AudioBackend::FeatureFlags AudioBackendXAudio2::Base_Features()
{
return FeatureFlags::None;
}
void AudioBackendXAudio2::Base_OnActiveDeviceChanged()
{
}
@@ -762,7 +812,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;
@@ -787,19 +836,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];
@@ -807,15 +850,16 @@ void AudioBackendXAudio2::Base_Update()
continue;
dsp.SrcChannelCount = source.Data.ChannelCount;
if (source.Is3D)
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(XAudio2::MatrixCoefficients));
Platform::MemoryClear(dsp.pMatrixCoefficients, sizeof(matrixCoefficients));
dsp.pMatrixCoefficients[0] = 1.0f;
if (source.Format.nChannels == 1)
{
@@ -825,9 +869,20 @@ 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;
if (source.IsForceMono3D)
{
// Hack to fix playback speed for 3D clip that has auto-converted stereo to mono at runtime
dsp.DopplerFactor *= 0.5f;
}
const float frequencyRatio = dopplerFactor * source.Pitch * dsp.DopplerFactor * source.DopplerFactor;
source.Voice->SetFrequencyRatio(frequencyRatio);
source.Voice->SetOutputMatrix(XAudio2::MasteringVoice, dsp.SrcChannelCount, dsp.DstChannelCount, dsp.pMatrixCoefficients);

View File

@@ -18,15 +18,16 @@ public:
void Listener_OnRemove(AudioListener* listener) override;
void Listener_VelocityChanged(AudioListener* listener) override;
void Listener_TransformChanged(AudioListener* listener) override;
void Listener_ReinitializeAll() override;
void Source_OnAdd(AudioSource* source) override;
void Source_OnRemove(AudioSource* source) override;
void Source_VelocityChanged(AudioSource* source) override;
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_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;
@@ -39,10 +40,11 @@ public:
void Source_GetQueuedBuffersCount(AudioSource* source, int32& queuedBuffersCount) override;
void Source_QueueBuffer(AudioSource* source, uint32 bufferId) override;
void Source_DequeueProcessedBuffers(AudioSource* source) override;
void Buffer_Create(uint32& bufferId) override;
void Buffer_Delete(uint32& bufferId) override;
uint32 Buffer_Create() override;
void Buffer_Delete(uint32 bufferId) override;
void Buffer_Write(uint32 bufferId, byte* samples, const AudioDataInfo& info) override;
const Char* Base_Name() override;
FeatureFlags Base_Features() override;
void Base_OnActiveDeviceChanged() override;
void Base_SetDopplerFactor(float value) override;
void Base_SetVolume(float value) override;