From ce65fa8e883fe68150ae82e493ab531fb1afcb5e Mon Sep 17 00:00:00 2001 From: Zack Kollar Date: Sun, 1 Dec 2024 00:46:46 -0500 Subject: [PATCH 1/3] :hammer: Move audio asset loading to AssetManager, update audio engine APIs accordingly --- src/engine/assets/asset_manager.cpp | 19 +++++ src/engine/assets/asset_manager.h | 1 + src/engine/audio/audio_engine.cpp | 54 ++++++------- src/engine/audio/audio_engine.h | 9 ++- src/engine/audio/audio_file.cpp | 114 ++++++++++++++++++++-------- src/engine/audio/audio_file.h | 50 +++++++----- src/engine/audio/audio_queue.cpp | 7 +- src/engine/audio/audio_queue.h | 5 +- src/game/main.cpp | 18 +++-- 9 files changed, 183 insertions(+), 94 deletions(-) diff --git a/src/engine/assets/asset_manager.cpp b/src/engine/assets/asset_manager.cpp index 66de5f0..e0984a9 100644 --- a/src/engine/assets/asset_manager.cpp +++ b/src/engine/assets/asset_manager.cpp @@ -77,6 +77,18 @@ AssetHandle AssetManager::load(const std::string &path) { m_totalAssets--; throw std::runtime_error(createInfo.errorMsg); } + // Load AudioFile + } else if constexpr (std::is_same_v) { + AudioFile::CreateInfo createInfo; + if (auto audioFile = AudioFile::createFromFile(path, createInfo)) { + auto asset = std::make_shared(std::move(*audioFile)); + m_assets.try_emplace(path, asset, 1); + m_loadedAssets++; + return AssetHandle(asset); + } else { + m_totalAssets--; + throw std::runtime_error(createInfo.errorMsg); + } } else { // Asset type-specific loading logic here throw std::runtime_error("Unsupported asset type"); @@ -143,4 +155,11 @@ AssetManager::loadAsync(const std::string &); template bool AssetManager::exists(const std::string &) const; template void AssetManager::remove(const std::string &); +template AssetHandle +AssetManager::load(const std::string &); +template std::future> +AssetManager::loadAsync(const std::string &); +template bool AssetManager::exists(const std::string &) const; +template void AssetManager::remove(const std::string &); + } // namespace ste \ No newline at end of file diff --git a/src/engine/assets/asset_manager.h b/src/engine/assets/asset_manager.h index 7b1a740..bee6561 100644 --- a/src/engine/assets/asset_manager.h +++ b/src/engine/assets/asset_manager.h @@ -11,6 +11,7 @@ #include #include "engine/async/thread_pool.h" +#include "engine/audio/audio_file.h" #include "engine/rendering/shader.h" #include "engine/rendering/texture.h" diff --git a/src/engine/audio/audio_engine.cpp b/src/engine/audio/audio_engine.cpp index 3670851..f9386d3 100644 --- a/src/engine/audio/audio_engine.cpp +++ b/src/engine/audio/audio_engine.cpp @@ -79,8 +79,11 @@ void AudioChannel::mix(float *buffer, size_t frames) { m_position = static_cast(readPosition); } -void AudioChannel::play(std::shared_ptr file, float vol) { - m_currentFile = std::move(file); +void AudioChannel::play(AudioFile *file, float vol) { + if (!file) + return; + + m_currentFile = file; m_position = 0; m_volume = vol; m_targetVolume = vol; @@ -90,7 +93,7 @@ void AudioChannel::play(std::shared_ptr file, float vol) { void AudioChannel::stop() { m_active = false; - m_currentFile.reset(); + m_currentFile = nullptr; m_position = 0; } @@ -217,37 +220,34 @@ AudioEngine::~AudioEngine() { stopAll(); } -void AudioEngine::playSound(const std::string &path, float m_volume) { - try { - auto file = std::make_shared(path); - int channel = findFreeChannel(); - if (channel != -1) { - m_commandQueue.pushPlay(file, m_volume, channel); - } - } catch (const AudioFileException &e) { - std::cerr << "Failed to load sound file: " << e.what() << std::endl; - // Handle or propagate error - // Could log error or throw depending on your error handling strategy +void AudioEngine::playSound(AssetHandle sound, float volume) { + if (!sound.isValid()) { + std::cerr << "Attempted to play invalid sound asset" << std::endl; + return; + } + + int channel = findFreeChannel(); + if (channel != -1) { + m_commandQueue.pushPlay(sound.operator->(), volume, channel); } } -void AudioEngine::playMusic(const std::string &path, bool loop) { - try { - auto file = std::make_shared(path); - file->setLooping(loop); +void AudioEngine::playMusic(AssetHandle music, bool loop) { + if (!music.isValid()) { + std::cerr << "Attempted to play invalid music asset" << std::endl; + return; + } - // Stop any currently playing music - stopChannel(0); + // Set looping state on the audio file + music->setLooping(loop); - // Queue the new music - m_commandQueue.pushPlay(file, 1.0f, 0); // Channel 0 reserved for music - } catch (const AudioFileException &e) { - std::cerr << "Failed to load sound file: " << e.what() << std::endl; + // Stop any currently playing music + stopChannel(0); - // Handle or propagate error - } + // Queue the new music + m_commandQueue.pushPlay(music.operator->(), 1.0f, + 0); // Channel 0 reserved for music } - void AudioEngine::stopChannel(int channelId) { if (channelId >= 0 && channelId < static_cast(MAX_CHANNELS)) { m_commandQueue.pushStop(channelId); diff --git a/src/engine/audio/audio_engine.h b/src/engine/audio/audio_engine.h index 79027e4..803b1e0 100644 --- a/src/engine/audio/audio_engine.h +++ b/src/engine/audio/audio_engine.h @@ -7,6 +7,7 @@ #include "audio_file.h" #include "audio_queue.h" +#include "engine/assets/asset_manager.h" namespace ste { @@ -18,7 +19,7 @@ class AudioChannel { // Core audio functions void update(float deltaTime); void mix(float *buffer, size_t frames); - void play(std::shared_ptr file, float vol = 1.0f); + void play(AudioFile *file, float vol = 1.0f); void stop(); // State control @@ -36,7 +37,7 @@ class AudioChannel { private: // Audio data - std::shared_ptr m_currentFile; + AudioFile *m_currentFile = nullptr; size_t m_position = 0; // Volume control @@ -76,8 +77,8 @@ class AudioEngine { AudioEngine &operator=(AudioEngine &&) = delete; // Main interface - void playSound(const std::string &path, float volume = 1.0f); - void playMusic(const std::string &path, bool loop = true); + void playSound(AssetHandle sound, float volume = 1.0f); + void playMusic(AssetHandle music, bool loop = true); void stopChannel(int channelId); void stopAll(); void setChannelVolume(int channelId, float volume); diff --git a/src/engine/audio/audio_file.cpp b/src/engine/audio/audio_file.cpp index 04c190e..25bb0a6 100644 --- a/src/engine/audio/audio_file.cpp +++ b/src/engine/audio/audio_file.cpp @@ -1,9 +1,9 @@ +// audio_file.cpp #include "audio_file.h" #include #include #include - #include namespace ste { @@ -18,10 +18,10 @@ struct WAVHeader { char fmt[4]; // "fmt " uint32_t fmtSize; // Format chunk size uint16_t audioFormat; // Audio format (1 = PCM) - uint16_t numChannels; // Number of m_channels - uint32_t m_sampleRate; // Sample rate + uint16_t numChannels; // Number of channels + uint32_t sampleRate; // Sample rate uint32_t byteRate; // Bytes per second - uint16_t blockAlign; // Bytes per sample * m_channels + uint16_t blockAlign; // Bytes per sample * channels uint16_t bitsPerSample; // Bits per sample }; #pragma pack(pop) @@ -33,21 +33,42 @@ bool fileExists(const std::string &path) { } } // namespace -AudioFile::AudioFile(const std::string &path) : m_filename(path) { +std::optional AudioFile::createFromFile(const std::string &path, + CreateInfo &createInfo) { if (!fileExists(path)) { - throw AudioFileException("File not found: " + path); + createInfo.success = false; + createInfo.errorMsg = "File not found: " + path; + return std::nullopt; } + std::vector samples; + uint32_t sampleRate; + uint32_t channels; + bool success = false; + std::string ext = getFileExtension(path); if (ext == "wav") { - loadWAV(path); + success = loadWAV(path, samples, sampleRate, channels, createInfo); } else if (ext == "ogg") { - loadOGG(path); + success = loadOGG(path, samples, sampleRate, channels, createInfo); } else { - throw AudioFileException("Unsupported file format: " + ext); + createInfo.success = false; + createInfo.errorMsg = "Unsupported file format: " + ext; + return std::nullopt; } + + if (!success) { + return std::nullopt; + } + + return AudioFile(path, std::move(samples), sampleRate, channels); } +AudioFile::AudioFile(const std::string &filename, std::vector &&samples, + uint32_t sampleRate, uint32_t channels) + : m_samples(std::move(samples)), m_filename(filename), + m_sampleRate(sampleRate), m_channels(channels) {} + AudioFile::~AudioFile() = default; AudioFile::AudioFile(AudioFile &&other) noexcept @@ -66,10 +87,14 @@ AudioFile &AudioFile::operator=(AudioFile &&other) noexcept { return *this; } -void AudioFile::loadWAV(const std::string &path) { +bool AudioFile::loadWAV(const std::string &path, std::vector &samples, + uint32_t &sampleRate, uint32_t &channels, + CreateInfo &createInfo) { std::ifstream file(path, std::ios::binary); if (!file) { - throw AudioFileException("Failed to open WAV file: " + path); + createInfo.success = false; + createInfo.errorMsg = "Failed to open WAV file: " + path; + return false; } // Read and validate header @@ -79,15 +104,21 @@ void AudioFile::loadWAV(const std::string &path) { if (std::strncmp(header.riff, "RIFF", 4) != 0 || std::strncmp(header.wave, "WAVE", 4) != 0 || std::strncmp(header.fmt, "fmt ", 4) != 0) { - throw AudioFileException("Invalid WAV file format"); + createInfo.success = false; + createInfo.errorMsg = "Invalid WAV file format"; + return false; } if (header.audioFormat != 1) { // PCM = 1 - throw AudioFileException("Unsupported WAV format: non-PCM"); + createInfo.success = false; + createInfo.errorMsg = "Unsupported WAV format: non-PCM"; + return false; } if (header.bitsPerSample != 16) { - throw AudioFileException("Unsupported WAV format: not 16-bit"); + createInfo.success = false; + createInfo.errorMsg = "Unsupported WAV format: not 16-bit"; + return false; } // Find data chunk @@ -106,23 +137,35 @@ void AudioFile::loadWAV(const std::string &path) { file.read(reinterpret_cast(pcmData.data()), chunkSize); // Store audio properties - m_sampleRate = header.m_sampleRate; - m_channels = header.numChannels; + sampleRate = header.sampleRate; + channels = header.numChannels; - // Convert to float m_samples - convertToFloat(pcmData); + // Convert to float samples + convertToFloat(pcmData, samples); + return true; } -void AudioFile::loadOGG(const std::string &path) { +bool AudioFile::loadOGG(const std::string &path, std::vector &samples, + uint32_t &sampleRate, uint32_t &channels, + CreateInfo &createInfo) { OggVorbis_File vf; if (ov_fopen(path.c_str(), &vf) != 0) { - throw AudioFileException("Failed to open OGG file: " + path); + createInfo.success = false; + createInfo.errorMsg = "Failed to open OGG file: " + path; + return false; } // Get file info vorbis_info *vi = ov_info(&vf, -1); - m_sampleRate = static_cast(vi->rate); - m_channels = static_cast(vi->channels); + if (!vi) { + createInfo.success = false; + createInfo.errorMsg = "Failed to get OGG file info"; + ov_clear(&vf); + return false; + } + + sampleRate = static_cast(vi->rate); + channels = static_cast(vi->channels); // Read all PCM data std::vector pcmData; @@ -132,25 +175,34 @@ void AudioFile::loadOGG(const std::string &path) { while ((bytesRead = ov_read(&vf, buffer, sizeof(buffer), 0, 2, 1, ¤tSection)) > 0) { - size_t m_samplesRead = bytesRead / sizeof(int16_t); + size_t samplesRead = bytesRead / sizeof(int16_t); size_t currentSize = pcmData.size(); - pcmData.resize(currentSize + m_samplesRead); + pcmData.resize(currentSize + samplesRead); std::memcpy(pcmData.data() + currentSize, buffer, bytesRead); } + if (bytesRead < 0) { + createInfo.success = false; + createInfo.errorMsg = "Error reading OGG file data"; + ov_clear(&vf); + return false; + } + ov_clear(&vf); - // Convert to float m_samples - convertToFloat(pcmData); + // Convert to float samples + convertToFloat(pcmData, samples); + return true; } -void AudioFile::convertToFloat(const std::vector &pcmData) { - m_samples.resize(pcmData.size()); +void AudioFile::convertToFloat(const std::vector &pcmData, + std::vector &samples) { + samples.resize(pcmData.size()); const float scale = 1.0f / 32768.0f; // Scale factor for 16-bit audio - // Convert m_samples from int16 to float + // Convert samples from int16 to float for (size_t i = 0; i < pcmData.size(); ++i) { - m_samples[i] = static_cast(pcmData[i]) * scale; + samples[i] = static_cast(pcmData[i]) * scale; } } @@ -164,4 +216,4 @@ std::string AudioFile::getFileExtension(const std::string &path) { return ""; } -}; // namespace ste \ No newline at end of file +} // namespace ste \ No newline at end of file diff --git a/src/engine/audio/audio_file.h b/src/engine/audio/audio_file.h index c54a491..d84d044 100644 --- a/src/engine/audio/audio_file.h +++ b/src/engine/audio/audio_file.h @@ -1,31 +1,32 @@ +// audio_file.h #pragma once #include +#include #include #include #include namespace ste { -class AudioFileException : public std::runtime_error { -public: - explicit AudioFileException(const std::string &message) - : std::runtime_error(message) {} -}; - class AudioFile { public: - explicit AudioFile(const std::string &path); + struct CreateInfo { + std::string errorMsg; + bool success = true; + }; + + static std::optional createFromFile(const std::string &path, + CreateInfo &createInfo); + ~AudioFile(); + AudioFile(AudioFile &&other) noexcept; + AudioFile &operator=(AudioFile &&other) noexcept; - // Delete copy operations to prevent accidental copies + // Delete copy operations AudioFile(const AudioFile &) = delete; AudioFile &operator=(const AudioFile &) = delete; - // Allow move operations - AudioFile(AudioFile &&) noexcept; - AudioFile &operator=(AudioFile &&) noexcept; - // Getters [[nodiscard]] const float *data() const { return m_samples.data(); } [[nodiscard]] size_t size() const { return m_samples.size(); } @@ -38,19 +39,26 @@ class AudioFile { void setLooping(bool loop) { m_looping = loop; } private: + explicit AudioFile(const std::string &filename, std::vector &&samples, + uint32_t sampleRate, uint32_t channels); + + static bool loadWAV(const std::string &path, std::vector &samples, + uint32_t &sampleRate, uint32_t &channels, + CreateInfo &createInfo); + + static bool loadOGG(const std::string &path, std::vector &samples, + uint32_t &sampleRate, uint32_t &channels, + CreateInfo &createInfo); + + static std::string getFileExtension(const std::string &path); + static void convertToFloat(const std::vector &pcmData, + std::vector &samples); + std::vector m_samples; std::string m_filename; uint32_t m_sampleRate = 44100; uint32_t m_channels = 0; bool m_looping = false; - - // File format handlers - void loadWAV(const std::string &path); - void loadOGG(const std::string &path); - - // Utility functions - static std::string getFileExtension(const std::string &path); - void convertToFloat(const std::vector &pcmData); }; -}; // namespace ste \ No newline at end of file +} // namespace ste \ No newline at end of file diff --git a/src/engine/audio/audio_queue.cpp b/src/engine/audio/audio_queue.cpp index 9ef6637..cfd73fa 100644 --- a/src/engine/audio/audio_queue.cpp +++ b/src/engine/audio/audio_queue.cpp @@ -4,11 +4,12 @@ namespace ste { -bool AudioQueue::pushPlay(std::shared_ptr file, float volume, - int channelId) { +bool AudioQueue::pushPlay(AudioFile *file, float volume = 1.0f, + int channelId = -1) { AudioCommand cmd; cmd.type = AudioCommand::Type::Play; - cmd.file = file; // shared_ptr handles ref counting + cmd.file = + file; // Store the raw pointer - the AssetHandle maintains ownership cmd.value1 = volume; cmd.channelId = channelId; return m_queue.try_push(cmd); diff --git a/src/engine/audio/audio_queue.h b/src/engine/audio/audio_queue.h index 933855e..9560ec0 100644 --- a/src/engine/audio/audio_queue.h +++ b/src/engine/audio/audio_queue.h @@ -21,7 +21,7 @@ struct AudioCommand { }; Type type; - std::shared_ptr file; + AudioFile *file = nullptr; float value1 = 0.0f; // volume, pitch, or x position float value2 = 0.0f; // fade time or y position int channelId = -1; @@ -39,8 +39,7 @@ class AudioQueue { SPSCQueue m_queue; public: - bool pushPlay(std::shared_ptr file, float volume = 1.0f, - int channelId = -1); + bool pushPlay(AudioFile *file, float volume, int channelId); bool pushStop(int channelId); bool pushVolume(int channelId, float volume); bool pushFade(int channelId, float targetVolume, float duration); diff --git a/src/game/main.cpp b/src/game/main.cpp index d88f40a..6d611f6 100644 --- a/src/game/main.cpp +++ b/src/game/main.cpp @@ -98,6 +98,14 @@ int main(int argc, char *argv[]) { return -1; } + // Load sounds + auto realTrapShit = assetManager->load( + ste::getAssetPath("sfx/real-trap-shit.wav")); + auto slowDown = + assetManager->load(ste::getAssetPath("sfx/slowdown.wav")); + auto music = assetManager->load( + ste::getAssetPath("music/paniots-nine.wav")); + // Enable alpha blending glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); @@ -181,7 +189,7 @@ int main(int argc, char *argv[]) { }, 0, true); - audioEngine.playMusic("./assets/music/paniots-nine.wav", true); + audioEngine.playMusic(music, true); bool running = true; while (running) { @@ -198,20 +206,20 @@ int main(int argc, char *argv[]) { case SDLK_1: timeScale->targetScale = 0.5f; audioEngine.setSpeed(0.5f); - audioEngine.playSound("./assets/sfx/slowdown.wav"); + audioEngine.playSound(slowDown); break; case SDLK_2: timeScale->targetScale = 1.0f; audioEngine.setSpeed(1.0f); - audioEngine.playSound("./assets/sfx/slowdown.wav"); + audioEngine.playSound(slowDown); break; case SDLK_3: timeScale->targetScale = 2.0f; audioEngine.setSpeed(2.0f); - audioEngine.playSound("./assets/sfx/slowdown.wav"); + audioEngine.playSound(slowDown); break; case SDLK_SPACE: - audioEngine.playSound("./assets/sfx/real-trap-shit.wav"); + audioEngine.playSound(realTrapShit); // Spawn textured entities for (int i = 0; i < 50; i++) { From 7de0b311c0a6515e76f53eb331f0a6f06e6b0b43 Mon Sep 17 00:00:00 2001 From: Zack Kollar Date: Sun, 1 Dec 2024 00:51:48 -0500 Subject: [PATCH 2/3] :hammer: Fix erronious audio channel pitches being wrong --- src/engine/audio/audio_engine.cpp | 6 +++++- src/engine/audio/audio_engine.h | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/engine/audio/audio_engine.cpp b/src/engine/audio/audio_engine.cpp index f9386d3..deb24df 100644 --- a/src/engine/audio/audio_engine.cpp +++ b/src/engine/audio/audio_engine.cpp @@ -95,6 +95,8 @@ void AudioChannel::stop() { m_active = false; m_currentFile = nullptr; m_position = 0; + m_currentSpeed = 1.0f; + m_targetSpeed = 1.0f; } void AudioChannel::setVolume(float vol) { @@ -379,10 +381,12 @@ void AudioEngine::audioCallback(float *buffer, size_t frames) { } } -int AudioEngine::findFreeChannel() const { +int AudioEngine::findFreeChannel() { // Skip channel 0 (reserved for music) for (size_t i = 1; i < MAX_CHANNELS; ++i) { if (!m_channels[i].isActive()) { + // Optionally force a stop to ensure clean state + m_channels[i].stop(); return static_cast(i); } } diff --git a/src/engine/audio/audio_engine.h b/src/engine/audio/audio_engine.h index 803b1e0..9965971 100644 --- a/src/engine/audio/audio_engine.h +++ b/src/engine/audio/audio_engine.h @@ -114,7 +114,7 @@ class AudioEngine { void processCommands(); void mixAudio(float *buffer, size_t frames); - [[nodiscard]] int findFreeChannel() const; + [[nodiscard]] int findFreeChannel(); }; }; // namespace ste \ No newline at end of file From 16eb23888ec7d29bf73b888f6e349ff8b4c9948d Mon Sep 17 00:00:00 2001 From: Zack Kollar Date: Sun, 1 Dec 2024 01:02:04 -0500 Subject: [PATCH 3/3] :hammer: Only build Release and on Ubuntu for now for CI --- .github/workflows/cmake-multi-platform.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/cmake-multi-platform.yml b/.github/workflows/cmake-multi-platform.yml index 9790b43..75f2cb1 100644 --- a/.github/workflows/cmake-multi-platform.yml +++ b/.github/workflows/cmake-multi-platform.yml @@ -11,12 +11,12 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-latest, windows-latest] - build_type: [Release, RelWithDebInfo] + os: [ubuntu-latest] # , windows-latest] + build_type: [Release] # RelWithDebInfo] include: - - os: windows-latest - cpp_compiler: cl - separator: '\\' + # - os: windows-latest + # cpp_compiler: cl + # separator: '\\' - os: ubuntu-latest cpp_compiler: g++ separator: "/"