diff --git a/CMakeLists.txt b/CMakeLists.txt index 1827cd4..9be3e55 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/include/Cubed/app.hpp b/include/Cubed/app.hpp index 06eabbe..ba0bd84 100644 --- a/include/Cubed/app.hpp +++ b/include/Cubed/app.hpp @@ -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; diff --git a/include/Cubed/audio/audio_buffer.hpp b/include/Cubed/audio/audio_buffer.hpp new file mode 100644 index 0000000..074a5ce --- /dev/null +++ b/include/Cubed/audio/audio_buffer.hpp @@ -0,0 +1,20 @@ +#pragma once +#include "Cubed/audio/audio_data.hpp" + +#include +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 \ No newline at end of file diff --git a/include/Cubed/audio/audio_data.hpp b/include/Cubed/audio/audio_data.hpp new file mode 100644 index 0000000..2369f54 --- /dev/null +++ b/include/Cubed/audio/audio_data.hpp @@ -0,0 +1,10 @@ +#pragma once +#include +#include +namespace Cubed { +struct AudioData { + std::vector pcm; + uint32_t channels; + uint32_t sample_rate; +}; +} // namespace Cubed diff --git a/include/Cubed/audio/audio_engine.hpp b/include/Cubed/audio/audio_engine.hpp new file mode 100644 index 0000000..5d75764 --- /dev/null +++ b/include/Cubed/audio/audio_engine.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include "Cubed/audio/audio_source.hpp" +#include "Cubed/audio/sound_manager.hpp" + +#include +#include +#include +#include +#include +#include +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; + bool m_init{false}; + ALCdevice* device{nullptr}; + ALCcontext* context{nullptr}; + glm::vec3 listener_pos; + std::unique_ptr m_bgm; + SoundManager m_sounds; +}; +} // namespace Cubed \ No newline at end of file diff --git a/include/Cubed/audio/audio_loader.hpp b/include/Cubed/audio/audio_loader.hpp new file mode 100644 index 0000000..bf020bb --- /dev/null +++ b/include/Cubed/audio/audio_loader.hpp @@ -0,0 +1,16 @@ +#pragma once +#include "Cubed/audio/audio_data.hpp" + +#include +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 diff --git a/include/Cubed/audio/audio_source.hpp b/include/Cubed/audio/audio_source.hpp new file mode 100644 index 0000000..0eeb9a5 --- /dev/null +++ b/include/Cubed/audio/audio_source.hpp @@ -0,0 +1,26 @@ +#pragma once +#include "Cubed/audio/audio_buffer.hpp" + +#include +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 diff --git a/include/Cubed/audio/sound_manager.hpp b/include/Cubed/audio/sound_manager.hpp new file mode 100644 index 0000000..af1b282 --- /dev/null +++ b/include/Cubed/audio/sound_manager.hpp @@ -0,0 +1,19 @@ +#pragma once +#include "Cubed/audio/audio_buffer.hpp" + +#include +#include +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 m_buffers; +}; +} // namespace Cubed \ No newline at end of file diff --git a/include/Cubed/gameplay/client_world.hpp b/include/Cubed/gameplay/client_world.hpp index ca1679f..2ea9316 100644 --- a/include/Cubed/gameplay/client_world.hpp +++ b/include/Cubed/gameplay/client_world.hpp @@ -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 client); @@ -112,6 +113,7 @@ private: ClientPlayer m_player; OtherPlayerHashMap m_player_info; ChunkHashMap m_chunks; + AudioEngine& m_audio; std::vector m_planes; std::jthread m_client_thread; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 03322e7..b38e744 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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 ) \ No newline at end of file diff --git a/src/app.cpp b/src/app.cpp index 9a1ff82..3e8ada4 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -12,7 +12,7 @@ #include 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); } diff --git a/src/audio/audio_buffer.cpp b/src/audio/audio_buffer.cpp new file mode 100644 index 0000000..10db445 --- /dev/null +++ b/src/audio/audio_buffer.cpp @@ -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 \ No newline at end of file diff --git a/src/audio/audio_engine.cpp b/src/audio/audio_engine.cpp new file mode 100644 index 0000000..27f56de --- /dev/null +++ b/src/audio/audio_engine.cpp @@ -0,0 +1,46 @@ +#include "Cubed/audio/audio_engine.hpp" + +#include "Cubed/tools/log.hpp" + +#include +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(); + 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 \ No newline at end of file diff --git a/src/audio/audio_loader.cpp b/src/audio/audio_loader.cpp new file mode 100644 index 0000000..05d4603 --- /dev/null +++ b/src/audio/audio_loader.cpp @@ -0,0 +1,90 @@ +#include "Cubed/audio/audio_loader.hpp" + +#include "Cubed/tools/cubed_assert.hpp" + +#include +#include +#include + +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 \ No newline at end of file diff --git a/src/audio/audio_source.cpp b/src/audio/audio_source.cpp new file mode 100644 index 0000000..daebcd7 --- /dev/null +++ b/src/audio/audio_source.cpp @@ -0,0 +1,50 @@ +#include "Cubed/audio/audio_source.hpp" + +#include + +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 \ No newline at end of file diff --git a/src/audio/sound_manager.cpp b/src/audio/sound_manager.cpp new file mode 100644 index 0000000..2073f9e --- /dev/null +++ b/src/audio/sound_manager.cpp @@ -0,0 +1,37 @@ +#include "Cubed/audio/sound_manager.hpp" + +#include "Cubed/audio/audio_loader.hpp" +#include "Cubed/tools/cubed_assert.hpp" + +#include +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 \ No newline at end of file diff --git a/src/dev_panel.cpp b/src/dev_panel.cpp index ff589b5..d2d9361 100644 --- a/src/dev_panel.cpp +++ b/src/dev_panel.cpp @@ -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"); diff --git a/src/gameplay/client_world.cpp b/src/gameplay/client_world.cpp index 6fde996..ba487fe 100644 --- a/src/gameplay/client_world.cpp +++ b/src/gameplay/client_world.cpp @@ -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) {