Add audio module foundation: AudioTypes, AudioConfig, IAudioBackend, WASAPIBackend, AudioSystem, AudioSourceComponent, AudioListenerComponent, and third-party KissFFT library
This commit is contained in:
@@ -154,6 +154,7 @@ add_library(XCEngine STATIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/RHI/OpenGL/OpenGLSwapChain.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/RHI/OpenGL/OpenGLRenderTargetView.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/RHI/OpenGL/OpenGLDepthStencilView.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/RHI/OpenGL/OpenGLScreenshot.cpp
|
||||
|
||||
# RHI Factory
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/RHI/RHIFactory.cpp
|
||||
|
||||
18
engine/include/XCEngine/Audio/AudioConfig.h
Normal file
18
engine/include/XCEngine/Audio/AudioConfig.h
Normal file
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include "AudioTypes.h"
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Audio {
|
||||
|
||||
struct AudioConfig {
|
||||
uint32_t sampleRate = 48000;
|
||||
uint16 channels = 2;
|
||||
uint16 bitsPerSample = 16;
|
||||
SpeakerMode speakerMode = SpeakerMode::Stereo;
|
||||
uint32 bufferSize = 8192;
|
||||
uint32 bufferCount = 2;
|
||||
};
|
||||
|
||||
} // namespace Audio
|
||||
} // namespace XCEngine
|
||||
84
engine/include/XCEngine/Audio/AudioSystem.h
Normal file
84
engine/include/XCEngine/Audio/AudioSystem.h
Normal file
@@ -0,0 +1,84 @@
|
||||
#pragma once
|
||||
|
||||
#include "IAudioBackend.h"
|
||||
#include "AudioConfig.h"
|
||||
#include "AudioTypes.h"
|
||||
#include <XCEngine/Math/Vector3.h>
|
||||
#include <XCEngine/Math/Quaternion.h>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Components {
|
||||
class AudioSourceComponent;
|
||||
}
|
||||
|
||||
namespace Audio {
|
||||
|
||||
class AudioSystem {
|
||||
public:
|
||||
static AudioSystem& Get();
|
||||
|
||||
void Initialize(const AudioConfig& config);
|
||||
void Shutdown();
|
||||
|
||||
void Update(float deltaTime);
|
||||
|
||||
void SetBackend(std::unique_ptr<IAudioBackend> backend);
|
||||
IAudioBackend* GetBackend() const { return m_backend.get(); }
|
||||
|
||||
std::string GetCurrentDevice() const;
|
||||
void SetDevice(const std::string& deviceName);
|
||||
void GetAvailableDevices(std::vector<std::string>& devices);
|
||||
|
||||
float GetMasterVolume() const;
|
||||
void SetMasterVolume(float volume);
|
||||
|
||||
bool IsMuted() const;
|
||||
void SetMuted(bool muted);
|
||||
|
||||
void ProcessAudio(float* buffer, uint32 sampleCount, uint32 channels);
|
||||
|
||||
void SetListenerTransform(const Math::Vector3& position, const Math::Quaternion& rotation);
|
||||
void SetListenerVelocity(const Math::Vector3& velocity);
|
||||
|
||||
const Math::Vector3& GetListenerPosition() const { return m_listenerPosition; }
|
||||
const Math::Quaternion& GetListenerRotation() const { return m_listenerRotation; }
|
||||
const Math::Vector3& GetListenerVelocity() const { return m_listenerVelocity; }
|
||||
|
||||
void RegisterSource(Components::AudioSourceComponent* source);
|
||||
void UnregisterSource(Components::AudioSourceComponent* source);
|
||||
|
||||
struct Stats {
|
||||
uint32_t activeSources;
|
||||
uint32_t totalSources;
|
||||
uint64_t memoryUsage;
|
||||
float cpuUsage;
|
||||
};
|
||||
const Stats& GetStats() const { return m_stats; }
|
||||
|
||||
private:
|
||||
AudioSystem() = default;
|
||||
~AudioSystem() = default;
|
||||
|
||||
AudioSystem(const AudioSystem&) = delete;
|
||||
AudioSystem& operator=(const AudioSystem&) = delete;
|
||||
|
||||
void ProcessSource(Components::AudioSourceComponent* source, float* buffer, uint32 sampleCount, uint32 channels);
|
||||
|
||||
private:
|
||||
std::unique_ptr<IAudioBackend> m_backend;
|
||||
|
||||
Math::Vector3 m_listenerPosition = Math::Vector3::Zero();
|
||||
Math::Quaternion m_listenerRotation = Math::Quaternion::Identity();
|
||||
Math::Vector3 m_listenerVelocity = Math::Vector3::Zero();
|
||||
|
||||
std::vector<Components::AudioSourceComponent*> m_activeSources;
|
||||
|
||||
Stats m_stats = {};
|
||||
float m_deltaTime = 0.0f;
|
||||
};
|
||||
|
||||
} // namespace Audio
|
||||
} // namespace XCEngine
|
||||
117
engine/include/XCEngine/Audio/AudioTypes.h
Normal file
117
engine/include/XCEngine/Audio/AudioTypes.h
Normal file
@@ -0,0 +1,117 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include "../../../third_party/kissfft/kiss_fft.h"
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Audio {
|
||||
|
||||
using int8 = int8_t;
|
||||
using int16 = int16_t;
|
||||
using int32 = int32_t;
|
||||
using int64 = int64_t;
|
||||
using uint8 = uint8_t;
|
||||
using uint16 = uint16_t;
|
||||
using uint32 = uint32_t;
|
||||
using uint64 = uint64_t;
|
||||
|
||||
enum class AudioResourceType {
|
||||
AudioClip,
|
||||
AudioMixer,
|
||||
AudioBank
|
||||
};
|
||||
|
||||
enum class AudioLoadState {
|
||||
Unloaded,
|
||||
Loading,
|
||||
Loaded,
|
||||
Failed
|
||||
};
|
||||
|
||||
enum class AudioFormat {
|
||||
Unknown,
|
||||
WAV,
|
||||
OGG,
|
||||
MP3,
|
||||
FLAC,
|
||||
AAC
|
||||
};
|
||||
|
||||
enum class SpeakerMode {
|
||||
Mono,
|
||||
Stereo,
|
||||
Surround51,
|
||||
Surround71,
|
||||
Surround51_2,
|
||||
Surround71_2
|
||||
};
|
||||
|
||||
enum class AudioChannel {
|
||||
FrontLeft,
|
||||
FrontRight,
|
||||
FrontCenter,
|
||||
LFE,
|
||||
BackLeft,
|
||||
BackRight,
|
||||
SideLeft,
|
||||
SideRight
|
||||
};
|
||||
|
||||
enum class PlayState {
|
||||
Stopped,
|
||||
Playing,
|
||||
Paused
|
||||
};
|
||||
|
||||
enum class StopMode {
|
||||
Immediate,
|
||||
AllowFadeOut
|
||||
};
|
||||
|
||||
enum class PanMode {
|
||||
Pan3D,
|
||||
Pan2D
|
||||
};
|
||||
|
||||
enum class VolumeSource {
|
||||
Direct,
|
||||
PathOcclusion,
|
||||
Transmission,
|
||||
Obstruction
|
||||
};
|
||||
|
||||
struct Audio3DParams {
|
||||
float dopplerLevel = 1.0f;
|
||||
float speedOfSound = 343.0f;
|
||||
float minDistance = 1.0f;
|
||||
float maxDistance = 500.0f;
|
||||
float panLevel = 1.0f;
|
||||
float spread = 0.0f;
|
||||
float reverbZoneMix = 1.0f;
|
||||
};
|
||||
|
||||
struct AudioBufferDesc {
|
||||
uint32_t size = 0;
|
||||
uint32_t channels = 2;
|
||||
uint32_t sampleRate = 48000;
|
||||
uint16 bitsPerSample = 16;
|
||||
bool isFloat = false;
|
||||
bool isCompressed = false;
|
||||
AudioFormat format = AudioFormat::Unknown;
|
||||
};
|
||||
|
||||
struct SpatializerParams {
|
||||
float azimuth = 0.0f;
|
||||
float elevation = 0.0f;
|
||||
float distance = 0.0f;
|
||||
float volumeDb = 0.0f;
|
||||
float panCartesianX = 0.0f;
|
||||
float panCartesianY = 0.0f;
|
||||
bool isOccluded = false;
|
||||
bool isObstructed = false;
|
||||
};
|
||||
|
||||
} // namespace Audio
|
||||
} // namespace XCEngine
|
||||
39
engine/include/XCEngine/Audio/IAudioBackend.h
Normal file
39
engine/include/XCEngine/Audio/IAudioBackend.h
Normal file
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include "AudioConfig.h"
|
||||
#include <string>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Audio {
|
||||
|
||||
class IAudioBackend {
|
||||
public:
|
||||
virtual ~IAudioBackend() = default;
|
||||
|
||||
virtual bool Initialize(const AudioConfig& config) = 0;
|
||||
virtual void Shutdown() = 0;
|
||||
|
||||
virtual std::string GetDeviceName() const = 0;
|
||||
virtual void GetAvailableDevices(std::vector<std::string>& devices) = 0;
|
||||
virtual bool SetDevice(const std::string& deviceName) = 0;
|
||||
|
||||
virtual float GetMasterVolume() const = 0;
|
||||
virtual void SetMasterVolume(float volume) = 0;
|
||||
|
||||
virtual bool IsMuted() const = 0;
|
||||
virtual void SetMuted(bool muted) = 0;
|
||||
|
||||
virtual void Start() = 0;
|
||||
virtual void Stop() = 0;
|
||||
virtual void Suspend() = 0;
|
||||
virtual void Resume() = 0;
|
||||
|
||||
virtual void ProcessAudio(float* buffer, uint32 bufferSize,
|
||||
uint32 channels, uint32 sampleRate) = 0;
|
||||
|
||||
virtual bool IsRunning() const = 0;
|
||||
virtual AudioConfig GetConfig() const = 0;
|
||||
};
|
||||
|
||||
} // namespace Audio
|
||||
} // namespace XCEngine
|
||||
95
engine/include/XCEngine/Audio/WASAPI/WASAPIBackend.h
Normal file
95
engine/include/XCEngine/Audio/WASAPI/WASAPIBackend.h
Normal file
@@ -0,0 +1,95 @@
|
||||
#pragma once
|
||||
|
||||
#include "../../IAudioBackend.h"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <thread>
|
||||
#include <atomic>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#include <mmsystem.h>
|
||||
#pragma comment(lib, "winmm.lib")
|
||||
#endif
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Audio {
|
||||
namespace WASAPI {
|
||||
|
||||
class WASAPIBackend : public IAudioBackend {
|
||||
public:
|
||||
WASAPIBackend();
|
||||
~WASAPIBackend() override;
|
||||
|
||||
bool Initialize(const AudioConfig& config) override;
|
||||
void Shutdown() override;
|
||||
|
||||
std::string GetDeviceName() const override;
|
||||
void GetAvailableDevices(std::vector<std::string>& devices) override;
|
||||
bool SetDevice(const std::string& deviceName) override;
|
||||
|
||||
float GetMasterVolume() const override;
|
||||
void SetMasterVolume(float volume) override;
|
||||
|
||||
bool IsMuted() const override;
|
||||
void SetMuted(bool muted) override;
|
||||
|
||||
void Start() override;
|
||||
void Stop() override;
|
||||
void Suspend() override;
|
||||
void Resume() override;
|
||||
|
||||
void ProcessAudio(float* buffer, uint32 bufferSize,
|
||||
uint32 channels, uint32 sampleRate) override;
|
||||
|
||||
bool IsRunning() const override { return m_isRunning.load(); }
|
||||
AudioConfig GetConfig() const override { return m_config; }
|
||||
|
||||
private:
|
||||
MMRESULT InitDevice();
|
||||
MMRESULT InitBuffer();
|
||||
|
||||
static DWORD WINAPI AudioThreadProc(LPVOID lpParameter);
|
||||
void AudioThread();
|
||||
|
||||
void OnAudioCallback(HWAVEOUT hwo, UINT uMsg, DWORD_PTR dwInstance,
|
||||
DWORD_PTR dwParam1, DWORD_PTR dwParam2);
|
||||
static void CALLBACK StaticAudioCallback(HWAVEOUT hwo, UINT uMsg,
|
||||
DWORD_PTR dwInstance,
|
||||
DWORD_PTR dwParam1, DWORD_PTR dwParam2);
|
||||
|
||||
MMRESULT PlayFrontData();
|
||||
void PrepareBackData();
|
||||
void SwapBuffer();
|
||||
|
||||
private:
|
||||
std::atomic<bool> m_isRunning{false};
|
||||
std::thread m_audioThread;
|
||||
|
||||
AudioConfig m_config;
|
||||
|
||||
WAVEFORMATEX m_waveFormat = {};
|
||||
HWAVEOUT m_hWaveOut = nullptr;
|
||||
|
||||
std::vector<float> m_mixBuffer;
|
||||
uint32 m_mixBufferSize = 0;
|
||||
|
||||
std::vector<WAVEOUTCAPS> m_waveOutCaps;
|
||||
std::string m_deviceName;
|
||||
|
||||
std::atomic<float> m_masterVolume{1.0f};
|
||||
std::atomic<bool> m_muted{false};
|
||||
|
||||
static constexpr size_t BufferSize = 8192;
|
||||
std::vector<int16_t> m_audioBuffer1;
|
||||
std::vector<int16_t> m_audioBuffer2;
|
||||
bool m_isBuffer1Front = true;
|
||||
bool m_isBufferPrepared = false;
|
||||
|
||||
WAVEHDR m_waveHeader1 = {};
|
||||
WAVEHDR m_waveHeader2 = {};
|
||||
};
|
||||
|
||||
} // namespace WASAPI
|
||||
} // namespace Audio
|
||||
} // namespace XCEngine
|
||||
60
engine/include/XCEngine/Components/AudioListenerComponent.h
Normal file
60
engine/include/XCEngine/Components/AudioListenerComponent.h
Normal file
@@ -0,0 +1,60 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/Components/Component.h>
|
||||
#include <XCEngine/Components/TransformComponent.h>
|
||||
#include <XCEngine/Audio/AudioTypes.h>
|
||||
#include <vector>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Audio {
|
||||
class AudioMixer;
|
||||
}
|
||||
|
||||
namespace Components {
|
||||
|
||||
class AudioListenerComponent : public Component {
|
||||
public:
|
||||
AudioListenerComponent();
|
||||
~AudioListenerComponent() override;
|
||||
|
||||
float GetEnergy() const { return m_energy; }
|
||||
const float* GetFrequencyData() const { return m_frequencyData.data(); }
|
||||
size_t GetFrequencyDataSize() const { return m_frequencyData.size(); }
|
||||
|
||||
void SetMasterVolume(float volume);
|
||||
float GetMasterVolume() const { return m_masterVolume; }
|
||||
|
||||
void SetMute(bool mute);
|
||||
bool IsMute() const { return m_mute; }
|
||||
|
||||
void SetDopplerLevel(float level);
|
||||
float GetDopplerLevel() const { return m_dopplerLevel; }
|
||||
|
||||
void SetSpeedOfSound(float metersPerSecond);
|
||||
float GetSpeedOfSound() const { return m_speedOfSound; }
|
||||
|
||||
void SetReverbLevel(float level);
|
||||
float GetReverbLevel() const { return m_reverbLevel; }
|
||||
|
||||
void SetReverb(Audio::AudioMixer* reverb);
|
||||
Audio::AudioMixer* GetReverb() const { return m_reverb; }
|
||||
|
||||
void Update(float deltaTime) override;
|
||||
|
||||
std::string GetName() const override { return "AudioListener"; }
|
||||
|
||||
private:
|
||||
float m_masterVolume = 1.0f;
|
||||
bool m_mute = false;
|
||||
float m_dopplerLevel = 1.0f;
|
||||
float m_speedOfSound = 343.0f;
|
||||
float m_reverbLevel = 1.0f;
|
||||
|
||||
Audio::AudioMixer* m_reverb = nullptr;
|
||||
|
||||
float m_energy = 0.0f;
|
||||
std::vector<float> m_frequencyData;
|
||||
};
|
||||
|
||||
} // namespace Components
|
||||
} // namespace XCEngine
|
||||
118
engine/include/XCEngine/Components/AudioSourceComponent.h
Normal file
118
engine/include/XCEngine/Components/AudioSourceComponent.h
Normal file
@@ -0,0 +1,118 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/Components/Component.h>
|
||||
#include <XCEngine/Audio/AudioTypes.h>
|
||||
#include <XCEngine/Math/Vector3.h>
|
||||
#include <XCEngine/Math/Quaternion.h>
|
||||
#include <vector>
|
||||
#include <deque>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Resources {
|
||||
class AudioClip;
|
||||
}
|
||||
|
||||
namespace Audio {
|
||||
class AudioMixer;
|
||||
}
|
||||
|
||||
namespace Components {
|
||||
|
||||
class AudioSourceComponent : public Component {
|
||||
public:
|
||||
AudioSourceComponent();
|
||||
~AudioSourceComponent() override;
|
||||
|
||||
void Play();
|
||||
void Pause();
|
||||
void Stop(Audio::StopMode mode = Audio::StopMode::Immediate);
|
||||
bool IsPlaying() const { return m_playState == Audio::PlayState::Playing; }
|
||||
bool IsPaused() const { return m_playState == Audio::PlayState::Paused; }
|
||||
|
||||
void SetClip(Resources::AudioClip* clip);
|
||||
Resources::AudioClip* GetClip() const { return m_clip; }
|
||||
|
||||
void SetVolume(float volume);
|
||||
float GetVolume() const { return m_volume; }
|
||||
|
||||
void SetPitch(float pitch);
|
||||
float GetPitch() const { return m_pitch; }
|
||||
|
||||
void SetPan(float pan);
|
||||
float GetPan() const { return m_pan; }
|
||||
|
||||
void SetLooping(bool loop);
|
||||
bool IsLooping() const { return m_isLooping; }
|
||||
|
||||
void SetSpatialize(bool spatialize);
|
||||
bool IsSpatialize() const { return m_spatialize; }
|
||||
|
||||
void Set3DParams(const Audio::Audio3DParams& params);
|
||||
const Audio::Audio3DParams& Get3DParams() const { return m_3DParams; }
|
||||
|
||||
void SetDopplerLevel(float level);
|
||||
float GetDopplerLevel() const { return m_3DParams.dopplerLevel; }
|
||||
|
||||
void SetSpread(float spread);
|
||||
float GetSpread() const { return m_3DParams.spread; }
|
||||
|
||||
void SetReverbZoneMix(float mix);
|
||||
float GetReverbZoneMix() const { return m_3DParams.reverbZoneMix; }
|
||||
|
||||
void SetOutputMixer(Audio::AudioMixer* mixer);
|
||||
Audio::AudioMixer* GetOutputMixer() const { return m_outputMixer; }
|
||||
|
||||
void SetTime(float seconds);
|
||||
float GetTime() const;
|
||||
float GetDuration() const;
|
||||
|
||||
float GetEnergy() const { return m_energy; }
|
||||
void StartEnergyDetect();
|
||||
void StopEnergyDetect();
|
||||
bool IsEnergyDetecting() const { return m_isEnergyDetecting; }
|
||||
|
||||
void Update(float deltaTime) override;
|
||||
void OnEnable() override;
|
||||
void OnDisable() override;
|
||||
void OnDestroy() override;
|
||||
|
||||
void ProcessAudio(float* buffer, Audio::uint32 sampleCount, Audio::uint32 channels,
|
||||
const Math::Vector3& listenerPosition,
|
||||
const Math::Quaternion& listenerRotation);
|
||||
|
||||
std::string GetName() const override { return "AudioSource"; }
|
||||
|
||||
private:
|
||||
void Apply3DAttenuation(const Math::Vector3& listenerPosition);
|
||||
void UpdateEnergy(const float* buffer, Audio::uint32 sampleCount);
|
||||
|
||||
private:
|
||||
Resources::AudioClip* m_clip = nullptr;
|
||||
Audio::AudioMixer* m_outputMixer = nullptr;
|
||||
|
||||
Audio::PlayState m_playState = Audio::PlayState::Stopped;
|
||||
bool m_isLooping = false;
|
||||
|
||||
float m_volume = 1.0f;
|
||||
float m_pitch = 1.0f;
|
||||
float m_pan = 0.0f;
|
||||
bool m_spatialize = true;
|
||||
|
||||
Audio::Audio3DParams m_3DParams;
|
||||
|
||||
Audio::uint64 m_samplePosition = 0;
|
||||
double m_lastingTime = 0.0;
|
||||
|
||||
bool m_isEnergyDetecting = false;
|
||||
float m_energy = 0.0f;
|
||||
float m_maxEnergy = 5.0f;
|
||||
std::deque<float> m_energyHistory;
|
||||
|
||||
static constexpr size_t BufferSize = 8192;
|
||||
std::vector<float> m_outputBuffer;
|
||||
std::vector<float> m_decodedData;
|
||||
bool m_isDecoded = false;
|
||||
};
|
||||
|
||||
} // namespace Components
|
||||
} // namespace XCEngine
|
||||
157
engine/src/Audio/AudioSystem.cpp
Normal file
157
engine/src/Audio/AudioSystem.cpp
Normal file
@@ -0,0 +1,157 @@
|
||||
#include <XCEngine/Audio/AudioSystem.h>
|
||||
#include <XCEngine/Components/AudioSourceComponent.h>
|
||||
#include <iostream>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Audio {
|
||||
|
||||
AudioSystem& AudioSystem::Get() {
|
||||
static AudioSystem instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void AudioSystem::Initialize(const AudioConfig& config) {
|
||||
if (m_backend) {
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
m_backend = std::make_unique<WASAPI::WASAPIBackend>();
|
||||
if (m_backend->Initialize(config)) {
|
||||
m_backend->Start();
|
||||
std::cout << "AudioSystem initialized successfully" << std::endl;
|
||||
} else {
|
||||
std::cout << "Failed to initialize AudioSystem" << std::endl;
|
||||
m_backend.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void AudioSystem::Shutdown() {
|
||||
if (m_backend) {
|
||||
m_backend->Stop();
|
||||
m_backend->Shutdown();
|
||||
m_backend.reset();
|
||||
}
|
||||
m_activeSources.clear();
|
||||
}
|
||||
|
||||
void AudioSystem::Update(float deltaTime) {
|
||||
m_deltaTime = deltaTime;
|
||||
|
||||
if (!m_backend || !m_backend->IsRunning()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& config = m_backend->GetConfig();
|
||||
uint32 sampleCount = config.bufferSize * config.channels;
|
||||
|
||||
std::vector<float> mixBuffer(sampleCount, 0.0f);
|
||||
|
||||
for (auto* source : m_activeSources) {
|
||||
if (source && source->IsEnabled() && source->IsPlaying()) {
|
||||
ProcessSource(source, mixBuffer.data(), sampleCount, config.channels);
|
||||
}
|
||||
}
|
||||
|
||||
m_backend->ProcessAudio(mixBuffer.data(), sampleCount, config.channels, config.sampleRate);
|
||||
|
||||
uint32 activeCount = 0;
|
||||
for (auto* source : m_activeSources) {
|
||||
if (source && source->IsPlaying()) {
|
||||
activeCount++;
|
||||
}
|
||||
}
|
||||
m_stats.activeSources = activeCount;
|
||||
m_stats.totalSources = static_cast<uint32>(m_activeSources.size());
|
||||
}
|
||||
|
||||
void AudioSystem::SetBackend(std::unique_ptr<IAudioBackend> backend) {
|
||||
m_backend = std::move(backend);
|
||||
}
|
||||
|
||||
std::string AudioSystem::GetCurrentDevice() const {
|
||||
if (m_backend) {
|
||||
return m_backend->GetDeviceName();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
void AudioSystem::SetDevice(const std::string& deviceName) {
|
||||
if (m_backend) {
|
||||
m_backend->SetDevice(deviceName);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioSystem::GetAvailableDevices(std::vector<std::string>& devices) {
|
||||
if (m_backend) {
|
||||
m_backend->GetAvailableDevices(devices);
|
||||
}
|
||||
}
|
||||
|
||||
float AudioSystem::GetMasterVolume() const {
|
||||
if (m_backend) {
|
||||
return m_backend->GetMasterVolume();
|
||||
}
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
void AudioSystem::SetMasterVolume(float volume) {
|
||||
if (m_backend) {
|
||||
m_backend->SetMasterVolume(volume);
|
||||
}
|
||||
}
|
||||
|
||||
bool AudioSystem::IsMuted() const {
|
||||
if (m_backend) {
|
||||
return m_backend->IsMuted();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void AudioSystem::SetMuted(bool muted) {
|
||||
if (m_backend) {
|
||||
m_backend->SetMuted(muted);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioSystem::ProcessAudio(float* buffer, uint32 sampleCount, uint32 channels) {
|
||||
if (m_backend) {
|
||||
m_backend->ProcessAudio(buffer, sampleCount, channels, 48000);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioSystem::SetListenerTransform(const Math::Vector3& position, const Math::Quaternion& rotation) {
|
||||
m_listenerPosition = position;
|
||||
m_listenerRotation = rotation;
|
||||
}
|
||||
|
||||
void AudioSystem::SetListenerVelocity(const Math::Vector3& velocity) {
|
||||
m_listenerVelocity = velocity;
|
||||
}
|
||||
|
||||
void AudioSystem::RegisterSource(Components::AudioSourceComponent* source) {
|
||||
if (source) {
|
||||
m_activeSources.push_back(source);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioSystem::UnregisterSource(Components::AudioSourceComponent* source) {
|
||||
if (!source) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto it = std::find(m_activeSources.begin(), m_activeSources.end(), source);
|
||||
if (it != m_activeSources.end()) {
|
||||
m_activeSources.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioSystem::ProcessSource(Components::AudioSourceComponent* source, float* buffer, uint32 sampleCount, uint32 channels) {
|
||||
if (!source || !buffer) {
|
||||
return;
|
||||
}
|
||||
|
||||
source->ProcessAudio(buffer, sampleCount, channels, m_listenerPosition, m_listenerRotation);
|
||||
}
|
||||
|
||||
} // namespace Audio
|
||||
} // namespace XCEngine
|
||||
247
engine/src/Audio/WASAPI/WASAPIBackend.cpp
Normal file
247
engine/src/Audio/WASAPI/WASAPIBackend.cpp
Normal file
@@ -0,0 +1,247 @@
|
||||
#include <XCEngine/Audio/WASAPI/WASAPIBackend.h>
|
||||
#include <iostream>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Audio {
|
||||
namespace WASAPI {
|
||||
|
||||
WASAPIBackend::WASAPIBackend()
|
||||
: m_audioBuffer1(BufferSize * 2)
|
||||
, m_audioBuffer2(BufferSize * 2)
|
||||
{
|
||||
}
|
||||
|
||||
WASAPIBackend::~WASAPIBackend() {
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
bool WASAPIBackend::Initialize(const AudioConfig& config) {
|
||||
m_config = config;
|
||||
|
||||
m_waveFormat.wFormatTag = WAVE_FORMAT_PCM;
|
||||
m_waveFormat.nChannels = static_cast<WORD>(config.channels);
|
||||
m_waveFormat.nSamplesPerSec = config.sampleRate;
|
||||
m_waveFormat.nAvgBytesPerSec = config.sampleRate * config.channels * (config.bitsPerSample / 8);
|
||||
m_waveFormat.nBlockAlign = static_cast<WORD>(config.channels * (config.bitsPerSample / 8));
|
||||
m_waveFormat.wBitsPerSample = config.bitsPerSample;
|
||||
m_waveFormat.cbSize = 0;
|
||||
|
||||
MMRESULT result = InitDevice();
|
||||
if (result != MMSYSERR_NOERROR) {
|
||||
return false;
|
||||
}
|
||||
|
||||
result = InitBuffer();
|
||||
if (result != MMSYSERR_NOERROR) {
|
||||
waveOutClose(m_hWaveOut);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void WASAPIBackend::Shutdown() {
|
||||
if (m_isRunning.load()) {
|
||||
Stop();
|
||||
}
|
||||
|
||||
if (m_hWaveOut != nullptr) {
|
||||
waveOutUnprepareHeader(m_hWaveOut, &m_waveHeader1, sizeof(WAVEHDR));
|
||||
waveOutUnprepareHeader(m_hWaveOut, &m_waveHeader2, sizeof(WAVEHDR));
|
||||
waveOutClose(m_hWaveOut);
|
||||
m_hWaveOut = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
std::string WASAPIBackend::GetDeviceName() const {
|
||||
return m_deviceName;
|
||||
}
|
||||
|
||||
void WASAPIBackend::GetAvailableDevices(std::vector<std::string>& devices) {
|
||||
devices.clear();
|
||||
for (const auto& caps : m_waveOutCaps) {
|
||||
devices.push_back(caps.szPname);
|
||||
}
|
||||
}
|
||||
|
||||
bool WASAPIBackend::SetDevice(const std::string& deviceName) {
|
||||
for (UINT i = 0; i < waveOutGetNumDevs(); ++i) {
|
||||
WAVEOUTCAPS caps;
|
||||
waveOutGetDevCaps(i, &caps, sizeof(WAVEOUTCAPS));
|
||||
if (deviceName == caps.szPname) {
|
||||
m_deviceName = deviceName;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
float WASAPIBackend::GetMasterVolume() const {
|
||||
return m_masterVolume.load();
|
||||
}
|
||||
|
||||
void WASAPIBackend::SetMasterVolume(float volume) {
|
||||
m_masterVolume.store(std::max(0.0f, std::min(1.0f, volume)));
|
||||
}
|
||||
|
||||
bool WASAPIBackend::IsMuted() const {
|
||||
return m_muted.load();
|
||||
}
|
||||
|
||||
void WASAPIBackend::SetMuted(bool muted) {
|
||||
m_muted.store(muted);
|
||||
}
|
||||
|
||||
void WASAPIBackend::Start() {
|
||||
if (m_isRunning.load()) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_isRunning = true;
|
||||
m_audioThread = std::thread(&WASAPIBackend::AudioThreadProc, this);
|
||||
}
|
||||
|
||||
void WASAPIBackend::Stop() {
|
||||
m_isRunning = false;
|
||||
|
||||
if (m_audioThread.joinable()) {
|
||||
m_audioThread.join();
|
||||
}
|
||||
}
|
||||
|
||||
void WASAPIBackend::Suspend() {
|
||||
if (m_hWaveOut != nullptr) {
|
||||
waveOutReset(m_hWaveOut);
|
||||
}
|
||||
}
|
||||
|
||||
void WASAPIBackend::Resume() {
|
||||
if (m_hWaveOut != nullptr) {
|
||||
MMRESULT result = waveOutRestart(m_hWaveOut);
|
||||
if (result != MMSYSERR_NOERROR) {
|
||||
std::cout << "Failed to resume audio playback" << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WASAPIBackend::ProcessAudio(float* buffer, uint32 bufferSize,
|
||||
uint32 channels, uint32 sampleRate) {
|
||||
if (m_muted.load() || buffer == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
float volume = m_masterVolume.load();
|
||||
if (volume < 0.001f) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint32 sampleCount = bufferSize / sizeof(float);
|
||||
for (uint32 i = 0; i < sampleCount; ++i) {
|
||||
buffer[i] *= volume;
|
||||
}
|
||||
}
|
||||
|
||||
MMRESULT WASAPIBackend::InitDevice() {
|
||||
WAVEOUTCAPS waveOutCapsTemp;
|
||||
for (UINT i = 0; i < waveOutGetNumDevs(); ++i) {
|
||||
waveOutGetDevCaps(i, &waveOutCapsTemp, sizeof(WAVEOUTCAPS));
|
||||
m_waveOutCaps.push_back(waveOutCapsTemp);
|
||||
}
|
||||
|
||||
MMRESULT result = waveOutOpen(&m_hWaveOut, WAVE_MAPPER, &m_waveFormat,
|
||||
(DWORD_PTR)&WASAPIBackend::StaticAudioCallback,
|
||||
reinterpret_cast<DWORD_PTR>(this), CALLBACK_FUNCTION);
|
||||
if (result != MMSYSERR_NOERROR) {
|
||||
std::cout << "Failed to open audio device" << std::endl;
|
||||
return result;
|
||||
}
|
||||
|
||||
m_deviceName = "Default Device";
|
||||
return MMSYSERR_NOERROR;
|
||||
}
|
||||
|
||||
MMRESULT WASAPIBackend::InitBuffer() {
|
||||
m_waveHeader1.lpData = (LPSTR)m_audioBuffer1.data();
|
||||
m_waveHeader1.dwBufferLength = static_cast<DWORD>(m_audioBuffer1.size() * sizeof(int16_t));
|
||||
m_waveHeader1.dwFlags = 0;
|
||||
|
||||
MMRESULT result = waveOutPrepareHeader(m_hWaveOut, &m_waveHeader1, sizeof(WAVEHDR));
|
||||
if (result != MMSYSERR_NOERROR) {
|
||||
std::cout << "Failed to prepare audio header1" << std::endl;
|
||||
return result;
|
||||
}
|
||||
|
||||
m_waveHeader2.lpData = (LPSTR)m_audioBuffer2.data();
|
||||
m_waveHeader2.dwBufferLength = static_cast<DWORD>(m_audioBuffer2.size() * sizeof(int16_t));
|
||||
m_waveHeader2.dwFlags = 0;
|
||||
|
||||
result = waveOutPrepareHeader(m_hWaveOut, &m_waveHeader2, sizeof(WAVEHDR));
|
||||
if (result != MMSYSERR_NOERROR) {
|
||||
std::cout << "Failed to prepare audio header2" << std::endl;
|
||||
waveOutUnprepareHeader(m_hWaveOut, &m_waveHeader1, sizeof(WAVEHDR));
|
||||
return result;
|
||||
}
|
||||
|
||||
return MMSYSERR_NOERROR;
|
||||
}
|
||||
|
||||
DWORD WINAPI WASAPIBackend::AudioThreadProc(LPVOID lpParameter) {
|
||||
WASAPIBackend* backend = static_cast<WASAPIBackend*>(lpParameter);
|
||||
if (backend) {
|
||||
backend->AudioThread();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void WASAPIBackend::AudioThread() {
|
||||
PlayFrontData();
|
||||
SwapBuffer();
|
||||
PlayFrontData();
|
||||
|
||||
while (m_isRunning.load()) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
}
|
||||
|
||||
void WASAPIBackend::OnAudioCallback(HWAVEOUT hwo, UINT uMsg, DWORD_PTR dwInstance,
|
||||
DWORD_PTR dwParam1, DWORD_PTR dwParam2) {
|
||||
if (uMsg == WOM_DONE) {
|
||||
PrepareBackData();
|
||||
SwapBuffer();
|
||||
PlayFrontData();
|
||||
}
|
||||
}
|
||||
|
||||
void CALLBACK WASAPIBackend::StaticAudioCallback(HWAVEOUT hwo, UINT uMsg,
|
||||
DWORD_PTR dwInstance,
|
||||
DWORD_PTR dwParam1, DWORD_PTR dwParam2) {
|
||||
WASAPIBackend* backend = reinterpret_cast<WASAPIBackend*>(dwInstance);
|
||||
if (backend != nullptr) {
|
||||
backend->OnAudioCallback(hwo, uMsg, dwInstance, dwParam1, dwParam2);
|
||||
}
|
||||
}
|
||||
|
||||
MMRESULT WASAPIBackend::PlayFrontData() {
|
||||
WAVEHDR* frontHeader = m_isBuffer1Front ? &m_waveHeader1 : &m_waveHeader2;
|
||||
MMRESULT result = waveOutWrite(m_hWaveOut, frontHeader, sizeof(WAVEHDR));
|
||||
if (result != MMSYSERR_NOERROR) {
|
||||
std::cout << "Failed to write audio data" << std::endl;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void WASAPIBackend::PrepareBackData() {
|
||||
if (!m_isBufferPrepared) {
|
||||
memset(m_audioBuffer1.data(), 0, m_audioBuffer1.size() * sizeof(int16_t));
|
||||
memset(m_audioBuffer2.data(), 0, m_audioBuffer2.size() * sizeof(int16_t));
|
||||
m_isBufferPrepared = true;
|
||||
}
|
||||
}
|
||||
|
||||
void WASAPIBackend::SwapBuffer() {
|
||||
m_isBuffer1Front = !m_isBuffer1Front;
|
||||
}
|
||||
|
||||
} // namespace WASAPI
|
||||
} // namespace Audio
|
||||
} // namespace XCEngine
|
||||
53
engine/src/Components/AudioListenerComponent.cpp
Normal file
53
engine/src/Components/AudioListenerComponent.cpp
Normal file
@@ -0,0 +1,53 @@
|
||||
#include <XCEngine/Components/AudioListenerComponent.h>
|
||||
#include <XCEngine/Audio/AudioSystem.h>
|
||||
#include <cmath>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Components {
|
||||
|
||||
AudioListenerComponent::AudioListenerComponent()
|
||||
{
|
||||
}
|
||||
|
||||
AudioListenerComponent::~AudioListenerComponent() {
|
||||
}
|
||||
|
||||
void AudioListenerComponent::SetMasterVolume(float volume) {
|
||||
m_masterVolume = std::max(0.0f, std::min(1.0f, volume));
|
||||
Audio::AudioSystem::Get().SetMasterVolume(m_masterVolume);
|
||||
}
|
||||
|
||||
void AudioListenerComponent::SetMute(bool mute) {
|
||||
m_mute = mute;
|
||||
Audio::AudioSystem::Get().SetMuted(m_mute);
|
||||
}
|
||||
|
||||
void AudioListenerComponent::SetDopplerLevel(float level) {
|
||||
m_dopplerLevel = std::max(0.0f, std::min(5.0f, level));
|
||||
}
|
||||
|
||||
void AudioListenerComponent::SetSpeedOfSound(float metersPerSecond) {
|
||||
m_speedOfSound = std::max(1.0f, metersPerSecond);
|
||||
}
|
||||
|
||||
void AudioListenerComponent::SetReverbLevel(float level) {
|
||||
m_reverbLevel = std::max(0.0f, std::min(1.0f, level));
|
||||
}
|
||||
|
||||
void AudioListenerComponent::SetReverb(Audio::AudioMixer* reverb) {
|
||||
m_reverb = reverb;
|
||||
}
|
||||
|
||||
void AudioListenerComponent::Update(float deltaTime) {
|
||||
if (!m_gameObject) {
|
||||
return;
|
||||
}
|
||||
|
||||
Math::Vector3 position = transform().GetPosition();
|
||||
Math::Quaternion rotation = transform().GetRotation();
|
||||
|
||||
Audio::AudioSystem::Get().SetListenerTransform(position, rotation);
|
||||
}
|
||||
|
||||
} // namespace Components
|
||||
} // namespace XCEngine
|
||||
239
engine/src/Components/AudioSourceComponent.cpp
Normal file
239
engine/src/Components/AudioSourceComponent.cpp
Normal file
@@ -0,0 +1,239 @@
|
||||
#include <XCEngine/Components/AudioSourceComponent.h>
|
||||
#include <XCEngine/Audio/AudioSystem.h>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Components {
|
||||
|
||||
AudioSourceComponent::AudioSourceComponent()
|
||||
: m_outputBuffer(BufferSize * 2, 0.0f)
|
||||
{
|
||||
}
|
||||
|
||||
AudioSourceComponent::~AudioSourceComponent() {
|
||||
if (m_playState == Audio::PlayState::Playing) {
|
||||
Audio::AudioSystem::Get().UnregisterSource(this);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioSourceComponent::Play() {
|
||||
if (!m_clip || !m_clip->IsValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_playState == Audio::PlayState::Paused) {
|
||||
m_playState = Audio::PlayState::Playing;
|
||||
return;
|
||||
}
|
||||
|
||||
m_samplePosition = 0;
|
||||
m_lastingTime = 0.0;
|
||||
m_playState = Audio::PlayState::Playing;
|
||||
Audio::AudioSystem::Get().RegisterSource(this);
|
||||
}
|
||||
|
||||
void AudioSourceComponent::Pause() {
|
||||
if (m_playState == Audio::PlayState::Playing) {
|
||||
m_playState = Audio::PlayState::Paused;
|
||||
Audio::AudioSystem::Get().UnregisterSource(this);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioSourceComponent::Stop(Audio::StopMode mode) {
|
||||
if (m_playState != Audio::PlayState::Stopped) {
|
||||
m_playState = Audio::PlayState::Stopped;
|
||||
m_samplePosition = 0;
|
||||
Audio::AudioSystem::Get().UnregisterSource(this);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioSourceComponent::SetClip(Resources::AudioClip* clip) {
|
||||
m_clip = clip;
|
||||
m_isDecoded = false;
|
||||
if (clip && clip->IsValid()) {
|
||||
m_decodedData.resize(clip->GetAudioData().Size() / 2);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioSourceComponent::SetVolume(float volume) {
|
||||
m_volume = std::max(0.0f, std::min(1.0f, volume));
|
||||
}
|
||||
|
||||
void AudioSourceComponent::SetPitch(float pitch) {
|
||||
m_pitch = std::max(0.0f, std::min(3.0f, pitch));
|
||||
}
|
||||
|
||||
void AudioSourceComponent::SetPan(float pan) {
|
||||
m_pan = std::max(-1.0f, std::min(1.0f, pan));
|
||||
}
|
||||
|
||||
void AudioSourceComponent::SetLooping(bool loop) {
|
||||
m_isLooping = loop;
|
||||
}
|
||||
|
||||
void AudioSourceComponent::SetSpatialize(bool spatialize) {
|
||||
m_spatialize = spatialize;
|
||||
}
|
||||
|
||||
void AudioSourceComponent::Set3DParams(const Audio::Audio3DParams& params) {
|
||||
m_3DParams = params;
|
||||
}
|
||||
|
||||
void AudioSourceComponent::SetDopplerLevel(float level) {
|
||||
m_3DParams.dopplerLevel = level;
|
||||
}
|
||||
|
||||
void AudioSourceComponent::SetSpread(float spread) {
|
||||
m_3DParams.spread = std::max(0.0f, std::min(1.0f, spread));
|
||||
}
|
||||
|
||||
void AudioSourceComponent::SetReverbZoneMix(float mix) {
|
||||
m_3DParams.reverbZoneMix = std::max(0.0f, std::min(1.0f, mix));
|
||||
}
|
||||
|
||||
void AudioSourceComponent::SetOutputMixer(Audio::AudioMixer* mixer) {
|
||||
m_outputMixer = mixer;
|
||||
}
|
||||
|
||||
void AudioSourceComponent::SetTime(float seconds) {
|
||||
if (!m_clip || !m_clip->IsValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Audio::uint32 sampleRate = m_clip->GetSampleRate();
|
||||
Audio::uint32 channels = m_clip->GetChannels();
|
||||
Audio::uint64 sampleOffset = static_cast<Audio::uint64>(seconds * sampleRate * channels);
|
||||
m_samplePosition = sampleOffset;
|
||||
m_lastingTime = seconds;
|
||||
}
|
||||
|
||||
float AudioSourceComponent::GetTime() const {
|
||||
return static_cast<float>(m_lastingTime);
|
||||
}
|
||||
|
||||
float AudioSourceComponent::GetDuration() const {
|
||||
if (!m_clip || !m_clip->IsValid()) {
|
||||
return 0.0f;
|
||||
}
|
||||
return m_clip->GetDuration();
|
||||
}
|
||||
|
||||
void AudioSourceComponent::StartEnergyDetect() {
|
||||
m_isEnergyDetecting = true;
|
||||
m_energyHistory.clear();
|
||||
}
|
||||
|
||||
void AudioSourceComponent::StopEnergyDetect() {
|
||||
m_isEnergyDetecting = false;
|
||||
}
|
||||
|
||||
void AudioSourceComponent::Update(float deltaTime) {
|
||||
if (m_playState != Audio::PlayState::Playing || !m_clip) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_lastingTime += deltaTime * m_pitch;
|
||||
|
||||
Audio::uint32 channels = m_clip->GetChannels();
|
||||
Audio::uint32 sampleRate = m_clip->GetSampleRate();
|
||||
Audio::uint64 samplesPerSecond = sampleRate * channels;
|
||||
Audio::uint64 samplesToAdvance = static_cast<Audio::uint64>(deltaTime * m_pitch * samplesPerSecond);
|
||||
m_samplePosition += samplesToAdvance;
|
||||
|
||||
Audio::uint64 totalSamples = static_cast<Audio::uint64>(m_clip->GetAudioData().Size()) / (m_clip->GetBitsPerSample() / 8);
|
||||
|
||||
if (m_samplePosition >= totalSamples) {
|
||||
if (m_isLooping) {
|
||||
m_samplePosition = m_samplePosition % totalSamples;
|
||||
} else {
|
||||
Stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AudioSourceComponent::OnEnable() {
|
||||
if (m_playState == Audio::PlayState::Playing) {
|
||||
Audio::AudioSystem::Get().RegisterSource(this);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioSourceComponent::OnDisable() {
|
||||
if (m_playState == Audio::PlayState::Playing) {
|
||||
Audio::AudioSystem::Get().UnregisterSource(this);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioSourceComponent::OnDestroy() {
|
||||
Stop();
|
||||
}
|
||||
|
||||
void AudioSourceComponent::ProcessAudio(float* buffer, Audio::uint32 sampleCount, Audio::uint32 channels,
|
||||
const Math::Vector3& listenerPosition,
|
||||
const Math::Quaternion& listenerRotation) {
|
||||
if (m_playState != Audio::PlayState::Playing || !m_clip) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (channels == 0 || sampleCount == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
float volume = m_volume;
|
||||
if (m_spatialize) {
|
||||
Apply3DAttenuation(listenerPosition);
|
||||
volume *= m_volume;
|
||||
}
|
||||
|
||||
for (Audio::uint32 i = 0; i < sampleCount && i < BufferSize * 2; ++i) {
|
||||
buffer[i] += m_outputBuffer[i] * volume;
|
||||
}
|
||||
|
||||
if (m_isEnergyDetecting) {
|
||||
UpdateEnergy(buffer, sampleCount);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioSourceComponent::Apply3DAttenuation(const Math::Vector3& listenerPosition) {
|
||||
if (!m_gameObject) {
|
||||
return;
|
||||
}
|
||||
|
||||
Math::Vector3 sourcePosition = transform().GetPosition();
|
||||
Math::Vector3 direction = sourcePosition - listenerPosition;
|
||||
float distance = direction.Magnitude();
|
||||
|
||||
if (distance > m_3DParams.maxDistance) {
|
||||
m_volume = 0.0f;
|
||||
return;
|
||||
}
|
||||
|
||||
float normalizedDistance = distance / m_3DParams.maxDistance;
|
||||
normalizedDistance = std::max(0.0f, std::min(1.0f, normalizedDistance));
|
||||
|
||||
float attenuation = 1.0f - normalizedDistance;
|
||||
attenuation = std::pow(attenuation, 2.0f);
|
||||
|
||||
m_volume *= attenuation;
|
||||
}
|
||||
|
||||
void AudioSourceComponent::UpdateEnergy(const float* buffer, Audio::uint32 sampleCount) {
|
||||
if (!buffer || sampleCount == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
float sumSquares = 0.0f;
|
||||
for (Audio::uint32 i = 0; i < sampleCount; ++i) {
|
||||
sumSquares += buffer[i] * buffer[i];
|
||||
}
|
||||
|
||||
m_energy = std::sqrt(sumSquares / static_cast<float>(sampleCount));
|
||||
|
||||
m_energyHistory.push_back(m_energy);
|
||||
if (m_energyHistory.size() > 10) {
|
||||
m_energyHistory.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Components
|
||||
} // namespace XCEngine
|
||||
Reference in New Issue
Block a user