From a1936d666ce2c3cf71439a6306f3ace1531fa256 Mon Sep 17 00:00:00 2001 From: sebastian Date: Tue, 4 Aug 2026 18:00:33 +0200 Subject: [PATCH] ADD: Async Sound Loading + AudioLayer --- CMakeLists.txt | 4 +++ engine/src/core/Application.h | 2 ++ engine/src/core/audio/SoundBuffer.cpp | 36 ++++++++----------- engine/src/core/audio/SoundBuffer.h | 13 ++++++- engine/src/core/audio/SoundSource.h | 26 ++++++++++++++ engine/src/loader/AssetLoader.cpp | 40 +++++++++++++++++++++ engine/src/loader/AssetLoader.h | 1 + engine/src/loader/assets/AssetManager.cpp | 23 ++++++++++++ engine/src/loader/assets/AssetManager.h | 6 ++++ engine/src/loader/assets/AssetRequests.cpp | 13 +++++-- engine/src/loader/assets/AssetRequests.h | 8 ++++- engine/src/loader/assets/LoadedAssets.h | 9 ++++- engine/src/loader/assets/RawAssetData.h | 11 +++++- engine/src/loader/sounds/SoundUploader.cpp | 41 ++++++++++++++++++++++ engine/src/loader/sounds/SoundUploader.h | 19 ++++++++++ game/src/AudioLayer.cpp | 28 +++++++++++++++ game/src/AudioLayer.h | 29 +++++++++++++++ game/src/GameLayer.cpp | 18 ---------- game/src/GameLayer.h | 2 +- game/src/GameScene.cpp | 3 ++ 20 files changed, 285 insertions(+), 47 deletions(-) create mode 100644 engine/src/loader/sounds/SoundUploader.cpp create mode 100644 engine/src/loader/sounds/SoundUploader.h create mode 100644 game/src/AudioLayer.cpp create mode 100644 game/src/AudioLayer.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 8afe3ca..1a4e171 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -198,6 +198,8 @@ add_library(Engine STATIC engine/src/core/audio/SoundBuffer.h engine/src/core/audio/SoundSource.cpp engine/src/core/audio/SoundSource.h + engine/src/loader/sounds/SoundUploader.cpp + engine/src/loader/sounds/SoundUploader.h ) target_include_directories(Engine PUBLIC engine/src ${dr_libs_SOURCE_DIR}) target_link_libraries(Engine PUBLIC OpenGL::GL glfw glad glm::glm imgui spdlog::spdlog tinyobjloader stb_image OpenAL::OpenAL) @@ -252,6 +254,8 @@ add_executable(ColorRace game/src/ludo/LudoAiController.h game/src/ludo/ecs/highlight/HighlightSystem.cpp game/src/ludo/ecs/highlight/HighlightSystem.h + game/src/AudioLayer.cpp + game/src/AudioLayer.h ) diff --git a/engine/src/core/Application.h b/engine/src/core/Application.h index da38366..61ff6fb 100644 --- a/engine/src/core/Application.h +++ b/engine/src/core/Application.h @@ -10,6 +10,7 @@ #include "EngineConfig.h" #include "Window.h" +#include "audio/AudioDevice.h" #include "inputsOutputs/context/InputContextStack.h" #include "inputsOutputs/inputs/Keyboard.h" #include "inputsOutputs/inputs/Mouse.h" @@ -41,6 +42,7 @@ namespace engine { Mouse m_mouse{*m_window}; SceneManager m_sceneManager{m_inputContextStack}; ImGuiContext m_imguiContext{*m_window}; // neu + engine::audio::AudioDevice m_audioDevice; }; diff --git a/engine/src/core/audio/SoundBuffer.cpp b/engine/src/core/audio/SoundBuffer.cpp index f3f092d..178bd80 100644 --- a/engine/src/core/audio/SoundBuffer.cpp +++ b/engine/src/core/audio/SoundBuffer.cpp @@ -10,28 +10,22 @@ #include "AudioDevice.h" namespace engine::audio { - SoundBuffer::SoundBuffer(const std::string &path) { - unsigned int channels, sampleRate; - drwav_uint64 frameCount; - - drwav_int16* pcmData = drwav_open_file_and_read_pcm_frames_s16( - path.c_str(), &channels, &sampleRate, &frameCount, nullptr - ); - - if (!pcmData) { - throw std::runtime_error("Failed to load sound file: " + path); - } - - ALenum format = (channels == 1) ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16; - drwav_uint64 dataSize = frameCount * channels * sizeof(drwav_int16); - - alGenBuffers(1, &m_bufferID); - alBufferData(m_bufferID, format, pcmData, dataSize, sampleRate); - checkAlError("alBufferData"); - drwav_free(pcmData, nullptr); - } - SoundBuffer::~SoundBuffer() { if (m_bufferID) alDeleteBuffers(1, &m_bufferID); } + + SoundBuffer::SoundBuffer(SoundBuffer &&other) noexcept : m_bufferID(other.m_bufferID) { + other.m_bufferID = 0; + } + + SoundBuffer & SoundBuffer::operator=(SoundBuffer &&other) noexcept { + if (this != &other) { + release(); + + m_bufferID = other.m_bufferID; + other.m_bufferID = 0; + } + + return *this; + } } // engine \ No newline at end of file diff --git a/engine/src/core/audio/SoundBuffer.h b/engine/src/core/audio/SoundBuffer.h index 7e2ea58..d4fe0b1 100644 --- a/engine/src/core/audio/SoundBuffer.h +++ b/engine/src/core/audio/SoundBuffer.h @@ -11,17 +11,28 @@ namespace engine::audio { class SoundBuffer { public: - explicit SoundBuffer(const std::string& path); + explicit SoundBuffer(ALuint bufferID) : m_bufferID(bufferID) {} ~SoundBuffer(); SoundBuffer(const SoundBuffer&) = delete; + SoundBuffer& operator=(const SoundBuffer&) = delete; + SoundBuffer(SoundBuffer&& other) noexcept; + SoundBuffer& operator=(SoundBuffer&& other) noexcept; + ALuint id() const { return m_bufferID; } private: ALuint m_bufferID = 0; + + void release() { + if (m_bufferID != 0) { + alDeleteBuffers(1, &m_bufferID); + m_bufferID = 0; + } + } }; } // engine diff --git a/engine/src/core/audio/SoundSource.h b/engine/src/core/audio/SoundSource.h index 5737de0..c467ba8 100644 --- a/engine/src/core/audio/SoundSource.h +++ b/engine/src/core/audio/SoundSource.h @@ -23,6 +23,24 @@ namespace engine::audio { SoundSource(const SoundSource&) = delete; SoundSource& operator=(const SoundSource&) = delete; + SoundSource(SoundSource&& other) noexcept + : m_source(other.m_source) + { + other.m_source = 0; + } + + SoundSource& operator=(SoundSource&& other) noexcept + { + if (this != &other) { + release(); + + m_source = other.m_source; + other.m_source = 0; + } + + return *this; + } + void setBuffer(const SoundBuffer& buffer) { alSourcei(m_source, AL_BUFFER, static_cast(buffer.id())); checkAlError("alSourcei(AL_BUFFER)"); @@ -50,6 +68,14 @@ namespace engine::audio { } private: ALuint m_source = 0; + + void release() + { + if (m_source != 0) { + alDeleteSources(1, &m_source); + m_source = 0; + } + } }; inline void updateListener(float px, float py, float pz, diff --git a/engine/src/loader/AssetLoader.cpp b/engine/src/loader/AssetLoader.cpp index 75d29aa..cb2b212 100644 --- a/engine/src/loader/AssetLoader.cpp +++ b/engine/src/loader/AssetLoader.cpp @@ -4,10 +4,12 @@ #include "AssetLoader.h" +#include #include #include "models/ModelImporter.h" #include "models/ModelUploader.h" +#include "sounds/SoundUploader.h" #include "spdlog/spdlog.h" #include "textures/TextureImporter.h" #include "textures/TextureUploader.h" @@ -25,6 +27,8 @@ void engine::AssetLoader::scheduleAsset(const AssetRequest &request) { spdlog::debug("Scheduled Texture Request: {} -> {}", req.name, req.path); } else if constexpr (std::is_same_v) { spdlog::debug("Scheduled Model Request: {} -> {}", req.name, req.path); + } else if constexpr (std::is_same_v) { + spdlog::debug("Scheduled Sound Request: {} -> {}", req.name, req.path); } }, request); } @@ -62,6 +66,9 @@ std::vector engine::AssetLoader::processUploadQueue(int max std::cout << "Moddel Assets" << modelAssets.size() << std::endl; result.insert(result.end(), modelAssets.begin(), modelAssets.end()); std::cout << "Result size" << result.size() << std::endl; + } else if constexpr (std::is_same_v) { + auto soundAsset = SoundUploader::upload(rawData); + result.emplace_back(LoadedSound{rawData.name, std::make_shared(std::move(soundAsset))}); } else { static_assert( always_false, @@ -104,6 +111,10 @@ void engine::AssetLoader::loadingThreadFunc() { return processTextureRequest(req); } else if constexpr (std::is_same_v) { return processModelRequest(req); + } else if constexpr (std::is_same_v) { + return processSoundRequest(req); + } else { + static_assert(always_false, "Unhandled asset type in AssetLoader::loadingThreadFunc"); } }, request); @@ -128,6 +139,35 @@ engine::RawModelBundle engine::AssetLoader::processModelRequest(const ModelReque return modelData; } +engine::RawSoundData engine::AssetLoader::processSoundRequest(const SoundRequest &request) { + RawSoundData soundData; + unsigned int channels, sampleRate; + drwav_uint64 frameCount; + + drwav_int16* pcmData = drwav_open_file_and_read_pcm_frames_s16( + request.path.c_str(), &channels, &sampleRate, &frameCount, nullptr + ); + + if (!pcmData) { + throw std::runtime_error("Failed to load sound file: " + request.path); + } + + soundData.name = request.name; + soundData.channels = channels; + soundData.sampleRate = sampleRate; + soundData.bitsPerSample = 16; + size_t dataSize = frameCount * channels * sizeof(drwav_int16); + soundData.pcmData.resize(dataSize); + std::memcpy( + soundData.pcmData.data(), + pcmData, + dataSize + ); + drwav_free(pcmData, nullptr); + + return soundData; +} + std::vector engine::AssetLoader::determineUploadQueue(int maxPerFrame) { std::vector result; std::lock_guard lock(readyMutex); diff --git a/engine/src/loader/AssetLoader.h b/engine/src/loader/AssetLoader.h index d0d57a0..5cf82c3 100644 --- a/engine/src/loader/AssetLoader.h +++ b/engine/src/loader/AssetLoader.h @@ -46,6 +46,7 @@ inline constexpr bool always_false = false; static RawTextureData processTextureRequest(const TextureRequest& request); static RawModelBundle processModelRequest(const ModelRequest& request); + static RawSoundData processSoundRequest(const SoundRequest& request); std::vector determineUploadQueue(int maxPerFrame); std::queue pendingQueue; diff --git a/engine/src/loader/assets/AssetManager.cpp b/engine/src/loader/assets/AssetManager.cpp index aa32ae8..b657810 100644 --- a/engine/src/loader/assets/AssetManager.cpp +++ b/engine/src/loader/assets/AssetManager.cpp @@ -28,6 +28,8 @@ void engine::AssetManager::insertAsset(LoadedAsset loadedAsset) { insertModel(loaded.name, loaded.model); } else if constexpr (std::is_same_v) { insertTexture(loaded.name, loaded.texture); + } else if constexpr (std::is_same_v) { + insertSound(loaded.name, loaded.sound); } }, loadedAsset); } @@ -61,3 +63,24 @@ void engine::AssetManager::insertTexture(const std::string &name, std::shared_pt void engine::AssetManager::unloadTexture(const std::string &name) { m_textures.erase(name); } + +std::shared_ptr engine::AssetManager::getSound(const std::string &name) const { + auto it = m_loadedAssets.find(name); + if (it == m_loadedAssets.end()) { + throw std::runtime_error("Sound not found: " + name); + } + return it->second; +} + +bool engine::AssetManager::hasSound(const std::string &name) const { + return m_loadedAssets.contains(name); +} + +void engine::AssetManager::insertSound(const std::string &name, std::shared_ptr sound) { + std::cout << "Inserting sound " << name << std::endl; + m_loadedAssets[name] = std::move(sound); +} + +void engine::AssetManager::unloadSound(const std::string &name) { + m_loadedAssets.erase(name); +} diff --git a/engine/src/loader/assets/AssetManager.h b/engine/src/loader/assets/AssetManager.h index e291eec..9611abd 100644 --- a/engine/src/loader/assets/AssetManager.h +++ b/engine/src/loader/assets/AssetManager.h @@ -29,10 +29,16 @@ namespace engine { void insertTexture(const std::string& name, std::shared_ptr texture); void unloadTexture(const std::string& name); + std::shared_ptr getSound(const std::string& name) const; + bool hasSound(const std::string& name) const; + void insertSound(const std::string& name, std::shared_ptr sound); + void unloadSound(const std::string& name); + private: std::unordered_map> m_textures; std::unordered_map> m_models; + std::unordered_map> m_loadedAssets; }; } diff --git a/engine/src/loader/assets/AssetRequests.cpp b/engine/src/loader/assets/AssetRequests.cpp index 9a69581..075af78 100644 --- a/engine/src/loader/assets/AssetRequests.cpp +++ b/engine/src/loader/assets/AssetRequests.cpp @@ -8,6 +8,9 @@ #include #include +#include "RawAssetData.h" +#include "loader/AssetLoader.h" + namespace engine { @@ -27,17 +30,21 @@ namespace engine { req.name, req.path ); - } - else + } else if constexpr(std::is_same_v) { return std::format( "ModelRequest{{name='{}', path='{}'}}", req.name, req.path ); + } else if constexpr(std::is_same_v){ + return std::format( + "SoundRequest{{name='{}', path='{}'}}", + req.name, + req.path); } }, request); } -} \ No newline at end of file +} diff --git a/engine/src/loader/assets/AssetRequests.h b/engine/src/loader/assets/AssetRequests.h index f05d291..c5c2e69 100644 --- a/engine/src/loader/assets/AssetRequests.h +++ b/engine/src/loader/assets/AssetRequests.h @@ -28,11 +28,17 @@ namespace engine { ModelSplitPolicy splitPolicy = ModelSplitPolicy::Combined; }; + struct SoundRequest { + std::string name; + std::string path; + }; + using AssetRequest = std::variant< TextureRequest, - ModelRequest + ModelRequest, + SoundRequest >; diff --git a/engine/src/loader/assets/LoadedAssets.h b/engine/src/loader/assets/LoadedAssets.h index cd91f27..290c16a 100644 --- a/engine/src/loader/assets/LoadedAssets.h +++ b/engine/src/loader/assets/LoadedAssets.h @@ -9,6 +9,8 @@ #include #include #include + +#include "core/audio/SoundBuffer.h" #include "loader/textures/Texture2D.h" #include "loader/models/Model.h" @@ -24,7 +26,12 @@ namespace engine { std::shared_ptr model; }; - using LoadedAsset = std::variant; + struct LoadedSound { + std::string name; + std::shared_ptr sound; + }; + + using LoadedAsset = std::variant; } #endif //COLORRACE_LOADEDASSETS_H diff --git a/engine/src/loader/assets/RawAssetData.h b/engine/src/loader/assets/RawAssetData.h index 83d18cf..feb8532 100644 --- a/engine/src/loader/assets/RawAssetData.h +++ b/engine/src/loader/assets/RawAssetData.h @@ -76,11 +76,20 @@ namespace engine { std::vector textures; }; + struct RawSoundData { + std::string name; + std::vector pcmData; + int channels; + int sampleRate; + int bitsPerSample; + }; + using IntermediateAsset = std::variant< RawTextureData, - RawModelBundle + RawModelBundle, + RawSoundData >; diff --git a/engine/src/loader/sounds/SoundUploader.cpp b/engine/src/loader/sounds/SoundUploader.cpp new file mode 100644 index 0000000..175374f --- /dev/null +++ b/engine/src/loader/sounds/SoundUploader.cpp @@ -0,0 +1,41 @@ +// +// Created by sebastian on 04.08.26. +// + +#include "SoundUploader.h" + +#include + +#include "core/audio/AudioDevice.h" + +engine::audio::SoundBuffer engine::SoundUploader::upload(const RawSoundData &data) { + auto* context = alcGetCurrentContext(); + + if (!context) { + std::cerr << "NO OPENAL CONTEXT IN UPLOAD THREAD\n"; + } + + ALenum format; + if (data.channels == 1) { + format = AL_FORMAT_MONO16; + } else if (data.channels == 2) { + format = AL_FORMAT_STEREO16; + } else { + throw std::runtime_error("Unsupported channel count"); + } + + ALuint bufferID = 0; + alGenBuffers(1, &bufferID); + + alBufferData( + bufferID, + format, + data.pcmData.data(), + static_cast(data.pcmData.size()), + data.sampleRate + ); + + audio::checkAlError("alBufferData"); + + return audio::SoundBuffer(bufferID); +} diff --git a/engine/src/loader/sounds/SoundUploader.h b/engine/src/loader/sounds/SoundUploader.h new file mode 100644 index 0000000..65a9cbd --- /dev/null +++ b/engine/src/loader/sounds/SoundUploader.h @@ -0,0 +1,19 @@ +// +// Created by sebastian on 04.08.26. +// + +#ifndef COLORRACE_SOUNDUPLOADER_H +#define COLORRACE_SOUNDUPLOADER_H +#include "loader/assets/LoadedAssets.h" +#include "loader/assets/RawAssetData.h" + + +namespace engine { + class SoundUploader { + public: + static audio::SoundBuffer upload(const RawSoundData& data); + }; +} + + +#endif //COLORRACE_SOUNDUPLOADER_H diff --git a/game/src/AudioLayer.cpp b/game/src/AudioLayer.cpp new file mode 100644 index 0000000..71a59fb --- /dev/null +++ b/game/src/AudioLayer.cpp @@ -0,0 +1,28 @@ +// +// Created by sebastian on 04.08.26. +// + +#include "AudioLayer.h" + +#include "GLFW/glfw3.h" + +void ludo::AudioLayer::onUpdate(float deltaTime) { + Layer::onUpdate(deltaTime); + if (getKeyboard().keyPressEvent(GLFW_KEY_P)) { + for (auto& soundSource : soundSources) { + soundSource.play(); + } + } +} + +void ludo::AudioLayer::onAttachImpl() { + Layer::onAttachImpl(); + auto rollingDiceSoundBufffer = m_assetManager.getSound("rolling_dice"); + + auto rollingDiceSoundSource = engine::audio::SoundSource(); + rollingDiceSoundSource.setBuffer(*rollingDiceSoundBufffer); + rollingDiceSoundSource.setPosition(0.0f, 0.0f, 0.0f); + rollingDiceSoundSource.setGain(1.0f); + + soundSources.push_back(std::move(rollingDiceSoundSource)); +} diff --git a/game/src/AudioLayer.h b/game/src/AudioLayer.h new file mode 100644 index 0000000..75b7c8c --- /dev/null +++ b/game/src/AudioLayer.h @@ -0,0 +1,29 @@ +// +// Created by sebastian on 04.08.26. +// + +#ifndef COLORRACE_AUDIOLAYER_H +#define COLORRACE_AUDIOLAYER_H +#include "core/audio/SoundSource.h" +#include "layer/Layer.h" +#include "loader/assets/AssetManager.h" + + +namespace ludo { + class AudioLayer :public engine::Layer { + public: + AudioLayer(engine::AssetManager& assetManager, engine::Keyboard &keyboard, engine::Mouse &mouse): Layer(keyboard, mouse), m_assetManager(assetManager) { + } + + void onUpdate(float deltaTime) override; + + protected: + void onAttachImpl() override; + private: + engine::AssetManager& m_assetManager; + std::vector soundSources; + }; + +} + +#endif //COLORRACE_AUDIOLAYER_H diff --git a/game/src/GameLayer.cpp b/game/src/GameLayer.cpp index 6dd9411..ddef115 100644 --- a/game/src/GameLayer.cpp +++ b/game/src/GameLayer.cpp @@ -26,12 +26,6 @@ void GameLayer::onAttachImpl() { m_cameraController = std::make_unique(getInputStack(), getInputContext()); m_piecePresenter.spawnPieces(m_gameMode->getState()); - - try { - m_soundBuffer = std::make_unique("assets/sounds/rolling_dice.wav"); - } catch (const std::exception& e) { - std::cerr <<"Audiofehler: " << e.what() << std::endl; - } } void GameLayer::onDetachImpl() { @@ -87,18 +81,6 @@ void GameLayer::onUpdate(float deltaTime) { m_gameMode->sendCommand(ludo::MovePieceCommand{m_localPlayer, *pieceIndex}); } } - - if (getKeyboard().keyPressEvent(GLFW_KEY_P)) { - audio::SoundSource source; - source.setBuffer(*m_soundBuffer); - source.setPosition(0.0f, 0.0f, 0.0f); - source.setGain(1.0f); - - source.play(); - while (source.isPlaying()) { - - } - } } void GameLayer::onRender() { diff --git a/game/src/GameLayer.h b/game/src/GameLayer.h index 0d23c87..c8f8632 100644 --- a/game/src/GameLayer.h +++ b/game/src/GameLayer.h @@ -59,7 +59,7 @@ private: std::vector aiControllers; - engine::audio::AudioDevice m_audioDevice; + std::unique_ptr m_soundBuffer; diff --git a/game/src/GameScene.cpp b/game/src/GameScene.cpp index 0b00375..02b1aee 100644 --- a/game/src/GameScene.cpp +++ b/game/src/GameScene.cpp @@ -4,6 +4,7 @@ #include "GameScene.h" +#include "AudioLayer.h" #include "GameLayer.h" #include "core/inputsOutputs/inputs/Mouse.h" #include "ecs/standardComponents/MeshComponent.h" @@ -38,6 +39,7 @@ void GameScene::onEnter() { pieceModels["Green"] = assetManager->getModel("pawn_green"); pieceModels["Yellow"] = assetManager->getModel("pawn_yellow"); + addLayer(std::make_unique(*assetManager, m_keyboard, m_mouse)); addLayer(std::make_unique(*m_camera, entityRegistry, m_keyboard, m_mouse, engine::SphereFactory::createUVSphere(1.0f))); addLayer(std::make_unique(m_gameMode, entityRegistry, m_camera, m_pointLight, m_keyboard, m_mouse, assetManager->getModel("gameboard"), pieceModels)); @@ -54,5 +56,6 @@ std::vector GameScene::getRequiredAssets() const { requests.emplace_back(engine::ModelRequest{"pawn_green", "assets/models/chess_green.obj", engine::ModelSplitPolicy::Combined}); requests.emplace_back(engine::ModelRequest{"pawn_yellow", "assets/models/chess_yellow.obj", engine::ModelSplitPolicy::Combined}); requests.emplace_back(engine::ModelRequest{ "gameboard", "assets/models/GameBoard.obj", engine::ModelSplitPolicy::Combined}); + requests.emplace_back(engine::SoundRequest{ "rolling_dice", "assets/sounds/rolling_dice.wav"}); return requests; } \ No newline at end of file