Add audio module foundation: AudioTypes, AudioConfig, IAudioBackend, WASAPIBackend, AudioSystem, AudioSourceComponent, AudioListenerComponent, and third-party KissFFT library

This commit is contained in:
2026-03-20 20:31:24 +08:00
parent 00f70eccf1
commit 47808f5f90
18 changed files with 2134 additions and 0 deletions

View 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

View 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

View 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

View 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

View 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

View 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

View 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