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