feat(audio): add 3D audio playback with source pool and auto-loading

This commit is contained in:
2026-07-06 20:26:20 +08:00
parent 0f60d578fb
commit 765f5ca714
10 changed files with 150 additions and 12 deletions

View File

@@ -13,10 +13,12 @@ public:
~AudioBuffer();
ALuint buffer() const;
float duration() const;
uint32_t channels() const;
private:
ALuint m_buffer = 0;
float m_duration = 0.0f;
uint32_t m_channels = 0;
void set_data(const AudioData& data);
};
} // namespace Cubed

View File

@@ -3,6 +3,7 @@
#include "Cubed/audio/audio_fade.hpp"
#include "Cubed/audio/audio_source.hpp"
#include "Cubed/audio/sound_manager.hpp"
#include "Cubed/audio/source_pool.hpp"
#include <AL/al.h>
#include <AL/alc.h>
@@ -25,11 +26,11 @@ public:
void init();
void play_bgm();
void play_3d(const std::string& sound, const glm::vec3& pos);
void update_listener(glm::vec3 listener_pos);
void update(float dt);
private:
using SourcePool = std::unordered_map<std::string, ALuint>;
using FadeMap = std::unordered_map<std::string, AudioFade>;
bool m_init{false};
ALCdevice* device{nullptr};
@@ -38,5 +39,6 @@ private:
std::unique_ptr<AudioSource> m_bgm;
FadeMap m_fade_map;
SoundManager m_sounds;
std::shared_ptr<SourcePool> m_pool;
};
} // namespace Cubed

View File

@@ -2,6 +2,7 @@
#include "Cubed/audio/audio_buffer.hpp"
#include <AL/al.h>
#include <glm/glm.hpp>
namespace Cubed {
enum class AudioState { INITIAL, PLAYING, PAUSED, STOPPED };
@@ -12,9 +13,14 @@ public:
~AudioSource();
void set_buffer_2d(const AudioBuffer& buffer);
void set_buffer_3d(const AudioBuffer& buffer, const glm::vec3& vec3);
void set_loop(bool on = true);
void set_volume(float volume);
void play();
void play_2d(const AudioBuffer& buffer);
void play_3d(const AudioBuffer& buffer, const glm::vec3& pos);
void stop();
void pause();
float duration() const;
@@ -22,9 +28,14 @@ public:
float volume() const;
AudioState state() const;
void mark_in_use();
bool in_use() const;
void reset();
private:
ALuint m_source = 0;
float m_volume = 1.0f;
float m_duration = 0.0f;
bool m_using;
};
} // namespace Cubed

View File

@@ -8,8 +8,8 @@ class SoundManager {
public:
SoundManager();
~SoundManager();
void load(const std::string& name);
const AudioBuffer& get_buffer(const std::string& name) const;
const AudioBuffer& load(const std::string& name);
const AudioBuffer& get_buffer(const std::string& name);
void init();
void clear();

View File

@@ -0,0 +1,21 @@
#pragma once
#include "Cubed/audio/audio_source.hpp"
namespace Cubed {
class SourcePool {
private:
std::vector<AudioSource> m_sources;
public:
explicit SourcePool(size_t size);
~SourcePool();
SourcePool(const SourcePool&) = delete;
SourcePool(SourcePool&&) = delete;
SourcePool& operator=(const SourcePool&) = delete;
SourcePool& operator=(SourcePool&&) = delete;
void update();
AudioSource* acquire();
};
} // namespace Cubed

View File

@@ -9,6 +9,7 @@ AudioBuffer::~AudioBuffer() { alDeleteBuffers(1, &m_buffer); }
ALuint AudioBuffer::buffer() const { return m_buffer; }
float AudioBuffer::duration() const { return m_duration; }
uint32_t AudioBuffer::channels() const { return m_channels; }
void AudioBuffer::set_data(const AudioData& data) {
ALenum format;
@@ -17,6 +18,7 @@ void AudioBuffer::set_data(const AudioData& data) {
} else {
format = AL_FORMAT_STEREO16;
}
m_channels = data.channels;
alBufferData(m_buffer, format, data.pcm.data(), data.pcm.size(),
data.sample_rate);
m_duration = static_cast<float>(data.pcm.size()) /

View File

@@ -12,6 +12,7 @@ AudioEngine::~AudioEngine() {
}
m_bgm.reset();
m_pool.reset();
m_sounds.clear();
alcMakeContextCurrent(nullptr);
@@ -37,6 +38,19 @@ void AudioEngine::init() {
m_fade_map.try_emplace("bgm", m_bgm.get(), 5.0f, 2.0f);
ALCint max_mono = 0;
alcGetIntegerv(device, ALC_MONO_SOURCES, 1, &max_mono);
if (max_mono <= 1) {
Logger::error("Can't get max mono");
max_mono = 4;
}
Logger::info("Set Source Pool Size {}", static_cast<int>(max_mono));
// Reserve a source for BGM
m_pool = std::make_shared<SourcePool>(max_mono - 1);
Logger::info("Audio Engine Init Success");
m_init = true;
@@ -44,11 +58,25 @@ void AudioEngine::init() {
void AudioEngine::play_bgm() { m_bgm->play(); }
void AudioEngine::play_3d(const std::string& sound, const glm::vec3& pos) {
if (!m_pool) {
Logger::error("Source Pool is nullptr");
return;
}
auto* source = m_pool->acquire();
if (!source) {
Logger::error("Source is Full");
}
auto& buffer = m_sounds.get_buffer(sound);
source->play_3d(buffer, pos);
}
void AudioEngine::update_listener(glm::vec3 listener_pos) {}
void AudioEngine::update(float dt) {
for (auto& [key, fade] : m_fade_map) {
fade.update();
}
m_pool->update();
}
} // namespace Cubed

View File

@@ -1,5 +1,7 @@
#include "Cubed/audio/audio_source.hpp"
#include "Cubed/tools/log.hpp"
#include <algorithm>
#include <stdexcept>
@@ -22,6 +24,22 @@ void AudioSource::set_buffer_2d(const AudioBuffer& buffer) {
alSource3f(m_source, AL_POSITION, 0, 0, 0);
}
void AudioSource::set_buffer_3d(const AudioBuffer& buffer,
const glm::vec3& pos) {
if (buffer.channels() != 1) {
Logger::warn("3D sound should use mono audio.");
}
if (state() != AudioState::STOPPED && state() != AudioState::INITIAL) {
stop();
}
m_duration = buffer.duration();
alSourcei(m_source, AL_BUFFER, buffer.buffer());
alSource3f(m_source, AL_POSITION, pos.x, pos.y, pos.z);
}
void AudioSource::set_loop(bool on) {
if (on) {
alSourcei(m_source, AL_LOOPING, AL_TRUE);
@@ -38,6 +56,16 @@ void AudioSource::set_volume(float volume) {
void AudioSource::play() { alSourcePlay(m_source); }
void AudioSource::play_2d(const AudioBuffer& buffer) {
set_buffer_2d(buffer);
play();
}
void AudioSource::play_3d(const AudioBuffer& buffer, const glm::vec3& pos) {
set_buffer_3d(buffer, pos);
play();
}
void AudioSource::stop() { alSourceStop(m_source); }
void AudioSource::pause() { alSourcePause(m_source); }
@@ -67,4 +95,14 @@ AudioState AudioSource::state() const {
throw std::runtime_error("Invaild state");
}
void AudioSource::mark_in_use() { m_using = true; }
bool AudioSource::in_use() const { return m_using; }
void AudioSource::reset() {
alSourcei(m_source, AL_BUFFER, 0);
alSourcef(m_source, AL_GAIN, 1.0f);
alSourcef(m_source, AL_PITCH, 1.0f);
alSource3f(m_source, AL_POSITION, 0.0f, 0.0f, 0.0f);
alSourcei(m_source, AL_LOOPING, AL_FALSE);
m_using = false;
}
} // namespace Cubed

View File

@@ -9,29 +9,39 @@ namespace Cubed {
SoundManager::SoundManager() {}
SoundManager::~SoundManager() { clear(); }
void SoundManager::clear() { m_buffers.clear(); }
void SoundManager::init() { load("bgm/bgm001.mp3"); }
void SoundManager::init() {
try {
// load("bgm/bgm001.mp3");
} catch (const std::exception& e) {
}
}
void SoundManager::load(const std::string& name) {
fs::path sound_path{std::string(ASSETS_PATH) + "sound/" + name};
const AudioBuffer& SoundManager::load(const std::string& name) {
fs::path sound_path{fs::path(ASSETS_PATH) / "sound" / name};
try {
AudioData data = AudioLoader::load(sound_path);
auto [_, inserted] = m_buffers.try_emplace(name, data);
auto [pos, inserted] = m_buffers.try_emplace(name, data);
if (!inserted) {
Logger::error("Key Already exist, check the sound name {}", name);
}
return pos->second;
} catch (const std::exception& e) {
Logger::error("Load Sound Error {}", e.what());
throw;
}
}
const AudioBuffer& SoundManager::get_buffer(const std::string& name) const {
const AudioBuffer& SoundManager::get_buffer(const std::string& name) {
auto it = m_buffers.find(name);
if (it == m_buffers.end()) {
try {
return load(name);
} catch (const std::exception& e) {
std::string err = std::format("Can't Find Buffer {}", name);
ASSERT_MSG(false, err);
throw std::runtime_error(err);
}
}
return it->second;
}
} // namespace Cubed

View File

@@ -0,0 +1,24 @@
#include "Cubed/audio/source_pool.hpp"
namespace Cubed {
SourcePool::SourcePool(size_t size) { m_sources.resize(size); }
SourcePool::~SourcePool() { m_sources.clear(); }
void SourcePool::update() {
for (auto& source : m_sources) {
if (source.in_use() && source.state() == AudioState::STOPPED) {
source.reset();
}
}
}
AudioSource* SourcePool::acquire() {
for (auto& source : m_sources) {
if (!source.in_use()) {
source.mark_in_use();
return &source;
}
}
return nullptr;
}
} // namespace Cubed