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,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

View 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

View 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

View 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