mirror of
https://github.com/zhenyan121/Cubed.git
synced 2026-08-09 02:07:04 +08:00
feat(audio): integrate OpenAL audio engine with WAV/MP3/FLAC support
This commit is contained in:
@@ -32,6 +32,8 @@ add_subdirectory(third_party/glad)
|
||||
|
||||
add_subdirectory(third_party/imgui)
|
||||
|
||||
add_subdirectory(third_party/dr_libs)
|
||||
|
||||
add_executable(${PROJECT_NAME})
|
||||
|
||||
add_subdirectory(src)
|
||||
@@ -129,6 +131,7 @@ target_link_libraries(${PROJECT_NAME}
|
||||
Freetype::Freetype
|
||||
tomlplusplus::tomlplusplus
|
||||
imgui
|
||||
dr_libs
|
||||
tbb
|
||||
protobuf::libprotobuf
|
||||
absl::log
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#pragma once
|
||||
#include "Cubed/audio/audio_engine.hpp"
|
||||
#include "Cubed/gameplay/client_world.hpp"
|
||||
#include "Cubed/gameplay/network_server.hpp"
|
||||
#include "Cubed/gameplay/server_world.hpp"
|
||||
@@ -62,6 +63,8 @@ private:
|
||||
|
||||
Window m_window{m_renderer};
|
||||
|
||||
AudioEngine m_audio;
|
||||
|
||||
inline static double last_time = glfwGetTime();
|
||||
inline static double current_time = glfwGetTime();
|
||||
inline static double delta_time = 0.0f;
|
||||
|
||||
20
include/Cubed/audio/audio_buffer.hpp
Normal file
20
include/Cubed/audio/audio_buffer.hpp
Normal file
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
#include "Cubed/audio/audio_data.hpp"
|
||||
|
||||
#include <AL/al.h>
|
||||
namespace Cubed {
|
||||
class AudioBuffer {
|
||||
public:
|
||||
AudioBuffer(const AudioData& data);
|
||||
AudioBuffer(const AudioBuffer&) = delete;
|
||||
AudioBuffer(AudioBuffer&&) = delete;
|
||||
AudioBuffer& operator=(const AudioBuffer&) = delete;
|
||||
AudioBuffer& operator=(AudioBuffer&&) = delete;
|
||||
~AudioBuffer();
|
||||
ALuint buffer() const;
|
||||
|
||||
private:
|
||||
ALuint m_buffer = 0;
|
||||
void set_data(const AudioData& data);
|
||||
};
|
||||
} // namespace Cubed
|
||||
10
include/Cubed/audio/audio_data.hpp
Normal file
10
include/Cubed/audio/audio_data.hpp
Normal file
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
namespace Cubed {
|
||||
struct AudioData {
|
||||
std::vector<int16_t> pcm;
|
||||
uint32_t channels;
|
||||
uint32_t sample_rate;
|
||||
};
|
||||
} // namespace Cubed
|
||||
38
include/Cubed/audio/audio_engine.hpp
Normal file
38
include/Cubed/audio/audio_engine.hpp
Normal file
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include "Cubed/audio/audio_source.hpp"
|
||||
#include "Cubed/audio/sound_manager.hpp"
|
||||
|
||||
#include <AL/al.h>
|
||||
#include <AL/alc.h>
|
||||
#include <glm/glm.hpp>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
namespace Cubed {
|
||||
|
||||
class ClientWorld;
|
||||
|
||||
class AudioEngine {
|
||||
public:
|
||||
AudioEngine();
|
||||
AudioEngine(const AudioEngine&) = delete;
|
||||
AudioEngine(AudioEngine&&) = delete;
|
||||
AudioEngine& operator=(const AudioEngine&) = delete;
|
||||
AudioEngine& operator=(AudioEngine&&) = delete;
|
||||
~AudioEngine();
|
||||
|
||||
void init();
|
||||
void play_bgm();
|
||||
void update(glm::vec3 listener_pos);
|
||||
|
||||
private:
|
||||
using SourcePool = std::unordered_map<std::string, ALuint>;
|
||||
bool m_init{false};
|
||||
ALCdevice* device{nullptr};
|
||||
ALCcontext* context{nullptr};
|
||||
glm::vec3 listener_pos;
|
||||
std::unique_ptr<AudioSource> m_bgm;
|
||||
SoundManager m_sounds;
|
||||
};
|
||||
} // namespace Cubed
|
||||
16
include/Cubed/audio/audio_loader.hpp
Normal file
16
include/Cubed/audio/audio_loader.hpp
Normal file
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
#include "Cubed/audio/audio_data.hpp"
|
||||
|
||||
#include <filesystem>
|
||||
namespace Cubed {
|
||||
|
||||
class AudioLoader {
|
||||
public:
|
||||
static AudioData load(const std::filesystem::path& path);
|
||||
|
||||
private:
|
||||
static AudioData load_wav(const std::filesystem::path& path);
|
||||
static AudioData load_mp3(const std::filesystem::path& path);
|
||||
static AudioData load_flac(const std::filesystem::path& path);
|
||||
};
|
||||
} // namespace Cubed
|
||||
26
include/Cubed/audio/audio_source.hpp
Normal file
26
include/Cubed/audio/audio_source.hpp
Normal file
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
#include "Cubed/audio/audio_buffer.hpp"
|
||||
|
||||
#include <AL/al.h>
|
||||
namespace Cubed {
|
||||
|
||||
enum class AudioState { INITIAL, PLAYING, PAUSED, STOPPED };
|
||||
|
||||
class AudioSource {
|
||||
public:
|
||||
AudioSource();
|
||||
~AudioSource();
|
||||
|
||||
void set_buffer(const AudioBuffer& buffer);
|
||||
void set_loop(bool on = true);
|
||||
|
||||
void play();
|
||||
void stop();
|
||||
void pause();
|
||||
|
||||
AudioState state();
|
||||
|
||||
private:
|
||||
ALuint m_source = 0;
|
||||
};
|
||||
} // namespace Cubed
|
||||
19
include/Cubed/audio/sound_manager.hpp
Normal file
19
include/Cubed/audio/sound_manager.hpp
Normal file
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
#include "Cubed/audio/audio_buffer.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
namespace Cubed {
|
||||
class SoundManager {
|
||||
public:
|
||||
SoundManager();
|
||||
~SoundManager();
|
||||
void load(const std::string& name);
|
||||
const AudioBuffer& get_buffer(const std::string& name) const;
|
||||
void init();
|
||||
void clear();
|
||||
|
||||
private:
|
||||
std::unordered_map<std::string, AudioBuffer> m_buffers;
|
||||
};
|
||||
} // namespace Cubed
|
||||
@@ -1,4 +1,5 @@
|
||||
#pragma once
|
||||
#include "Cubed/audio/audio_engine.hpp"
|
||||
#include "Cubed/gameplay/block.hpp"
|
||||
#include "Cubed/gameplay/chunk_pos.hpp"
|
||||
#include "Cubed/gameplay/client_chunk.hpp"
|
||||
@@ -40,7 +41,7 @@ struct PlayerRenderData {
|
||||
|
||||
class ClientWorld {
|
||||
public:
|
||||
ClientWorld();
|
||||
ClientWorld(AudioEngine& auido);
|
||||
~ClientWorld();
|
||||
void init(std::string_view player_name,
|
||||
std::shared_ptr<NetworkClient> client);
|
||||
@@ -112,6 +113,7 @@ private:
|
||||
ClientPlayer m_player;
|
||||
OtherPlayerHashMap m_player_info;
|
||||
ChunkHashMap m_chunks;
|
||||
AudioEngine& m_audio;
|
||||
std::vector<glm::vec4> m_planes;
|
||||
std::jthread m_client_thread;
|
||||
|
||||
|
||||
@@ -44,4 +44,10 @@ target_sources(${PROJECT_NAME}
|
||||
gameplay/session.cpp
|
||||
gameplay/network_client.cpp
|
||||
player_renderer.cpp
|
||||
audio/audio_engine.cpp
|
||||
audio/audio_loader.cpp
|
||||
audio/audio_source.cpp
|
||||
audio/audio_buffer.cpp
|
||||
audio/sound_manager.cpp
|
||||
audio/source_pool.cpp
|
||||
)
|
||||
@@ -12,7 +12,7 @@
|
||||
#include <imgui_impl_glfw.h>
|
||||
namespace Cubed {
|
||||
|
||||
App::App() {}
|
||||
App::App() : m_client_world(m_audio) {}
|
||||
|
||||
App::~App() {
|
||||
if (m_client) {
|
||||
@@ -59,6 +59,8 @@ void App::init(int argc, char** argv) {
|
||||
cursor_enter_callback);
|
||||
glfwSetCharCallback(m_window.get_glfw_window(), char_callback);
|
||||
|
||||
m_audio.init();
|
||||
|
||||
ChunkGenerator::init();
|
||||
BlockManager::init();
|
||||
m_renderer.init();
|
||||
@@ -357,6 +359,7 @@ void App::update() {
|
||||
m_renderer.update_fov(fov + 5.0f);
|
||||
}
|
||||
}
|
||||
m_audio.update(m_camera.get_camera_pos());
|
||||
m_renderer.update(delta_time);
|
||||
}
|
||||
|
||||
|
||||
22
src/audio/audio_buffer.cpp
Normal file
22
src/audio/audio_buffer.cpp
Normal file
@@ -0,0 +1,22 @@
|
||||
#include "Cubed/audio/audio_buffer.hpp"
|
||||
|
||||
namespace Cubed {
|
||||
AudioBuffer::AudioBuffer(const AudioData& data) {
|
||||
alGenBuffers(1, &m_buffer);
|
||||
set_data(data);
|
||||
}
|
||||
AudioBuffer::~AudioBuffer() { alDeleteBuffers(1, &m_buffer); }
|
||||
|
||||
ALuint AudioBuffer::buffer() const { return m_buffer; }
|
||||
void AudioBuffer::set_data(const AudioData& data) {
|
||||
ALenum format;
|
||||
if (data.channels == 1) {
|
||||
format = AL_FORMAT_MONO16;
|
||||
} else {
|
||||
format = AL_FORMAT_STEREO16;
|
||||
}
|
||||
alBufferData(m_buffer, format, data.pcm.data(), data.pcm.size(),
|
||||
data.sample_rate);
|
||||
}
|
||||
|
||||
} // namespace Cubed
|
||||
46
src/audio/audio_engine.cpp
Normal file
46
src/audio/audio_engine.cpp
Normal file
@@ -0,0 +1,46 @@
|
||||
#include "Cubed/audio/audio_engine.hpp"
|
||||
|
||||
#include "Cubed/tools/log.hpp"
|
||||
|
||||
#include <stdexcept>
|
||||
namespace Cubed {
|
||||
AudioEngine::AudioEngine() {};
|
||||
|
||||
AudioEngine::~AudioEngine() {
|
||||
if (!m_init) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_bgm.reset();
|
||||
m_sounds.clear();
|
||||
|
||||
alcMakeContextCurrent(nullptr);
|
||||
alcDestroyContext(context);
|
||||
alcCloseDevice(device);
|
||||
}
|
||||
|
||||
void AudioEngine::init() {
|
||||
device = alcOpenDevice(NULL);
|
||||
if (!device) {
|
||||
throw std::runtime_error("Failed to open OpenAL device.");
|
||||
}
|
||||
context = alcCreateContext(device, nullptr);
|
||||
|
||||
if (!context) {
|
||||
throw std::runtime_error("Failed to create OpenAL context.");
|
||||
}
|
||||
alcMakeContextCurrent(context);
|
||||
|
||||
m_sounds.init();
|
||||
m_bgm = std::make_unique<AudioSource>();
|
||||
m_bgm->set_buffer(m_sounds.get_buffer("bgm/bgm001.mp3"));
|
||||
Logger::info("Audio Engine Init Success");
|
||||
|
||||
m_init = true;
|
||||
}
|
||||
|
||||
void AudioEngine::play_bgm() { m_bgm->play(); }
|
||||
|
||||
void AudioEngine::update(glm::vec3 listener_pos) {}
|
||||
|
||||
} // namespace Cubed
|
||||
90
src/audio/audio_loader.cpp
Normal file
90
src/audio/audio_loader.cpp
Normal file
@@ -0,0 +1,90 @@
|
||||
#include "Cubed/audio/audio_loader.hpp"
|
||||
|
||||
#include "Cubed/tools/cubed_assert.hpp"
|
||||
|
||||
#include <dr_flac.h>
|
||||
#include <dr_mp3.h>
|
||||
#include <dr_wav.h>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
namespace Cubed {
|
||||
AudioData AudioLoader::load(const std::filesystem::path& path) {
|
||||
if (!fs::is_regular_file(path)) {
|
||||
std::string err = std::format("Path {} is not a file", path.string());
|
||||
ASSERT_MSG(false, err);
|
||||
throw std::runtime_error(err);
|
||||
}
|
||||
auto ext = path.extension().string();
|
||||
|
||||
if (ext == ".wav") {
|
||||
return load_wav(path);
|
||||
}
|
||||
if (ext == ".mp3") {
|
||||
return load_mp3(path);
|
||||
}
|
||||
if (ext == ".flac") {
|
||||
return load_flac(path);
|
||||
}
|
||||
throw std::runtime_error(std::format("Unsupported audio format {}", ext));
|
||||
}
|
||||
|
||||
AudioData AudioLoader::load_wav(const std::filesystem::path& path) {
|
||||
drwav wav{};
|
||||
if (!drwav_init_file(&wav, path.string().c_str(), nullptr)) {
|
||||
throw std::runtime_error("Failed to open wav");
|
||||
}
|
||||
AudioData data;
|
||||
data.channels = wav.channels;
|
||||
data.sample_rate = wav.sampleRate;
|
||||
data.pcm.resize(wav.totalPCMFrameCount * wav.channels);
|
||||
drwav_read_pcm_frames_s16(&wav, wav.totalPCMFrameCount, data.pcm.data());
|
||||
|
||||
drwav_uninit(&wav);
|
||||
return data;
|
||||
}
|
||||
|
||||
AudioData AudioLoader::load_mp3(const std::filesystem::path& path) {
|
||||
drmp3_config config;
|
||||
|
||||
drmp3_uint64 frame_count;
|
||||
|
||||
int16_t* pcm = drmp3_open_file_and_read_pcm_frames_s16(
|
||||
path.string().c_str(), &config, &frame_count, nullptr);
|
||||
|
||||
if (!pcm)
|
||||
throw std::runtime_error("mp3 load failed");
|
||||
|
||||
AudioData data;
|
||||
|
||||
data.channels = config.channels;
|
||||
data.sample_rate = config.sampleRate;
|
||||
|
||||
data.pcm.assign(pcm, pcm + frame_count * config.channels);
|
||||
|
||||
drmp3_free(pcm, nullptr);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
AudioData AudioLoader::load_flac(const std::filesystem::path& path) {
|
||||
drflac_uint64 frame_count;
|
||||
unsigned int channels;
|
||||
unsigned int sample_rate;
|
||||
|
||||
int16_t* pcm = drflac_open_file_and_read_pcm_frames_s16(
|
||||
path.string().c_str(), &channels, &sample_rate, &frame_count, nullptr);
|
||||
|
||||
if (!pcm)
|
||||
throw std::runtime_error("Failed to load flac");
|
||||
|
||||
AudioData data;
|
||||
data.channels = channels;
|
||||
data.sample_rate = sample_rate;
|
||||
|
||||
data.pcm.assign(pcm, pcm + frame_count * channels);
|
||||
|
||||
drflac_free(pcm, nullptr);
|
||||
|
||||
return data;
|
||||
}
|
||||
} // namespace Cubed
|
||||
50
src/audio/audio_source.cpp
Normal file
50
src/audio/audio_source.cpp
Normal file
@@ -0,0 +1,50 @@
|
||||
#include "Cubed/audio/audio_source.hpp"
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
namespace Cubed {
|
||||
AudioSource::AudioSource() { alGenSources(1, &m_source); }
|
||||
AudioSource::~AudioSource() {
|
||||
if (m_source != 0) {
|
||||
stop();
|
||||
alDeleteSources(1, &m_source);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioSource::set_buffer(const AudioBuffer& buffer) {
|
||||
if (state() == AudioState::PLAYING) {
|
||||
stop();
|
||||
}
|
||||
alSourcei(m_source, AL_BUFFER, buffer.buffer());
|
||||
}
|
||||
|
||||
void AudioSource::set_loop(bool on) {
|
||||
if (on) {
|
||||
alSourcei(m_source, AL_LOOPING, AL_TRUE);
|
||||
} else {
|
||||
alSourcei(m_source, AL_LOOPING, AL_FALSE);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioSource::play() { alSourcePlay(m_source); }
|
||||
|
||||
void AudioSource::stop() { alSourceStop(m_source); }
|
||||
void AudioSource::pause() { alSourcePause(m_source); }
|
||||
|
||||
AudioState AudioSource::state() {
|
||||
ALint state;
|
||||
alGetSourcei(m_source, AL_SOURCE_STATE, &state);
|
||||
switch (state) {
|
||||
case AL_INITIAL:
|
||||
return AudioState::INITIAL;
|
||||
case AL_PLAYING:
|
||||
return AudioState::PLAYING;
|
||||
case AL_STOPPED:
|
||||
return AudioState::STOPPED;
|
||||
case AL_PAUSED:
|
||||
return AudioState::PAUSED;
|
||||
}
|
||||
throw std::runtime_error("Invaild state");
|
||||
}
|
||||
|
||||
} // namespace Cubed
|
||||
37
src/audio/sound_manager.cpp
Normal file
37
src/audio/sound_manager.cpp
Normal file
@@ -0,0 +1,37 @@
|
||||
#include "Cubed/audio/sound_manager.hpp"
|
||||
|
||||
#include "Cubed/audio/audio_loader.hpp"
|
||||
#include "Cubed/tools/cubed_assert.hpp"
|
||||
|
||||
#include <filesystem>
|
||||
namespace fs = std::filesystem;
|
||||
namespace Cubed {
|
||||
SoundManager::SoundManager() {}
|
||||
SoundManager::~SoundManager() { clear(); }
|
||||
void SoundManager::clear() { m_buffers.clear(); }
|
||||
void SoundManager::init() { load("bgm/bgm001.mp3"); }
|
||||
|
||||
void SoundManager::load(const std::string& name) {
|
||||
fs::path sound_path{std::string(ASSETS_PATH) + "sound/" + name};
|
||||
try {
|
||||
|
||||
AudioData data = AudioLoader::load(sound_path);
|
||||
auto [_, inserted] = m_buffers.try_emplace(name, data);
|
||||
if (!inserted) {
|
||||
Logger::error("Key Already exist, check the sound name {}", name);
|
||||
}
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
Logger::error("Load Sound Error {}", e.what());
|
||||
}
|
||||
}
|
||||
const AudioBuffer& SoundManager::get_buffer(const std::string& name) const {
|
||||
auto it = m_buffers.find(name);
|
||||
if (it == m_buffers.end()) {
|
||||
std::string err = std::format("Can't Find Buffer {}", name);
|
||||
ASSERT_MSG(false, err);
|
||||
throw std::runtime_error(err);
|
||||
}
|
||||
return it->second;
|
||||
}
|
||||
} // namespace Cubed
|
||||
@@ -107,6 +107,10 @@ void DevPanel::show_about_table_bar() {
|
||||
ImGui::Text("OpenAl Soft");
|
||||
ImGui::Text("dr_libs");
|
||||
ImGui::Separator();
|
||||
ImGui::Text("Music");
|
||||
ImGui::Text("'Find a Peaceful Place' by ROZKOL (Free Music Archive), "
|
||||
"CC BY 4.0.");
|
||||
ImGui::Separator();
|
||||
ImGui::Text("Special Thanks");
|
||||
ImGui::Text("TANGERIME");
|
||||
ImGui::Text("SkyOnPole");
|
||||
|
||||
@@ -20,7 +20,8 @@ struct ChunkRenderData {
|
||||
};
|
||||
} // namespace
|
||||
|
||||
ClientWorld::ClientWorld() : m_player(*this) {}
|
||||
ClientWorld::ClientWorld(AudioEngine& auido)
|
||||
: m_player(*this), m_audio(auido) {}
|
||||
|
||||
ClientWorld::~ClientWorld() {
|
||||
stop_client_thread();
|
||||
@@ -356,6 +357,7 @@ void ClientWorld::init(std::string_view player_name,
|
||||
// request login
|
||||
Logger::info("Send Login Request");
|
||||
m_client->send(make_packet(req), 0);
|
||||
m_audio.play_bgm();
|
||||
}
|
||||
|
||||
void ClientWorld::start_client_thread(std::string_view uuid) {
|
||||
|
||||
Reference in New Issue
Block a user