From 234ecfc9d0014ddda3da7e25c3a539680043c4ba Mon Sep 17 00:00:00 2001 From: Joseph Walton-Rivers <joseph@walton-rivers.uk> Date: Sat, 5 Nov 2022 17:58:57 +0000 Subject: [PATCH] cleanup audio subsystem --- demo/demo/main.cpp | 11 +++- fggl/audio/CMakeLists.txt | 3 +- fggl/audio/fallback/CMakeLists.txt | 4 ++ fggl/audio/fallback/audio.cpp | 37 +++++++++++++ fggl/audio/openal/CMakeLists.txt | 5 +- fggl/audio/openal/audio.cpp | 63 ++++++++++++++++++--- fggl/audio/openal/module.cpp | 39 +++++++++++++ fggl/audio/types.cpp | 10 ---- fggl/gfx/ogl4/module.cpp | 1 - include/fggl/audio/audio.hpp | 22 ++++++-- include/fggl/audio/null_audio.hpp | 21 +++---- include/fggl/audio/openal/audio.hpp | 83 +++++++++++++++------------- include/fggl/audio/openal/module.hpp | 15 ++--- 13 files changed, 225 insertions(+), 89 deletions(-) create mode 100644 fggl/audio/fallback/CMakeLists.txt create mode 100644 fggl/audio/fallback/audio.cpp create mode 100644 fggl/audio/openal/module.cpp diff --git a/demo/demo/main.cpp b/demo/demo/main.cpp index 3228169..5c6729b 100644 --- a/demo/demo/main.cpp +++ b/demo/demo/main.cpp @@ -65,8 +65,8 @@ static void setup_menu(fggl::App& app) { } menu->add("quit", [&app]() { - auto* audio = app.service<fggl::audio::AudioService>(); - audio->play("click.ogg", false); + //auto* audio = app.service<fggl::audio::AudioService>(); + //audio->play("click.ogg", false); app.running(false); }); } @@ -98,6 +98,13 @@ int main(int argc, const char* argv[]) { // create the application fggl::App app( &moduleManager, "fggl-demo" ); + // force asset loading + { + auto* assets = app.service<fggl::assets::AssetManager>(); + auto* loader = app.service<fggl::assets::Loader>(); + loader->load("click.ogg", fggl::audio::OGG_VORBIS, assets); + } + auto* windowing = app.service<fggl::display::WindowService>(); // make a window for our application diff --git a/fggl/audio/CMakeLists.txt b/fggl/audio/CMakeLists.txt index f6e2776..bb05710 100644 --- a/fggl/audio/CMakeLists.txt +++ b/fggl/audio/CMakeLists.txt @@ -3,4 +3,5 @@ target_sources(fggl types.cpp ) -add_subdirectory(openal) \ No newline at end of file +add_subdirectory(openal) +add_subdirectory(fallback) \ No newline at end of file diff --git a/fggl/audio/fallback/CMakeLists.txt b/fggl/audio/fallback/CMakeLists.txt new file mode 100644 index 0000000..1ad64e8 --- /dev/null +++ b/fggl/audio/fallback/CMakeLists.txt @@ -0,0 +1,4 @@ +target_sources(fggl + PRIVATE + audio.cpp +) diff --git a/fggl/audio/fallback/audio.cpp b/fggl/audio/fallback/audio.cpp new file mode 100644 index 0000000..a8544c2 --- /dev/null +++ b/fggl/audio/fallback/audio.cpp @@ -0,0 +1,37 @@ +/* + * This file is part of FGGL. + * + * FGGL is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any + * later version. + * + * FGGL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with FGGL. + * If not, see <https://www.gnu.org/licenses/>. + */ + +// +// Created by webpigeon on 05/11/22. +// + +#include "fggl/audio/null_audio.hpp" + +namespace fggl::audio { + + void NullAudioService::play(const std::string &, bool) { + } + + void NullAudioService::play(const fggl::audio::AudioClipShort &, bool) { + } + + bool NullAudio::factory(modules::ModuleService service, modules::Services &services){ + if (service == SERVICE_AUDIO_PLAYBACK) { + services.bind<audio::AudioService, audio::NullAudioService>(); + return true; + } + return false; + } + +} // namespace fggl::audio \ No newline at end of file diff --git a/fggl/audio/openal/CMakeLists.txt b/fggl/audio/openal/CMakeLists.txt index 8c1882f..34fc1ac 100644 --- a/fggl/audio/openal/CMakeLists.txt +++ b/fggl/audio/openal/CMakeLists.txt @@ -18,7 +18,8 @@ else () endif () target_sources(fggl - PRIVATE + PRIVATE audio.cpp - ) + module.cpp +) diff --git a/fggl/audio/openal/audio.cpp b/fggl/audio/openal/audio.cpp index 3b1eea0..1cfb4b7 100644 --- a/fggl/audio/openal/audio.cpp +++ b/fggl/audio/openal/audio.cpp @@ -27,28 +27,73 @@ */ #include "fggl/audio/openal/audio.hpp" +#include "fggl/assets/types.hpp" +#include "fggl/assets/manager.hpp" + #include "fggl/data/storage.hpp" +#include "../../stb/stb_vorbis.h" namespace fggl::audio::openal { + assets::AssetRefRaw load_vorbis(assets::Loader* /*loader*/, const assets::AssetGUID &guid, const assets::AssetData &data, void* userPtr) { + auto *filePath = std::get<assets::AssetPath *>(data); + auto *manager = static_cast<assets::AssetManager*>(userPtr); + + // vorbis + auto* clip = new AudioClipShort(); + clip->sampleCount = stb_vorbis_decode_filename( filePath->c_str(), &clip->channels, &clip->sampleRate, &clip->data); + debug::info("clip loaded: channels={}, sampleRate={}, sampleCount={}", clip->channels, clip->sampleRate, clip->sampleCount); + + if ( clip->sampleCount == -1 ) { + return nullptr; + } + + manager->set(guid, clip); + return nullptr; + } + + void AudioSource::play(const AudioClipShort &clip, bool looping) { + check_error("pre play"); + + AudioType format = clip.channels == 1 ? AudioType::MONO_16 : AudioType::STEREO_16; + m_splat.setData(format, clip.data, clip.size(), clip.sampleRate); + check_error("saving to buffer"); + + play(m_splat, looping); + check_error("post play"); + } + void AudioServiceOAL::play(const std::string &filename, bool looping) { + debug::info("beginning audio: {}", filename); // load audio clip into temp storage - AudioClip clip; - bool result = m_storage->load(data::StorageType::Data, filename, &clip); - if (!result) { - std::cerr << "error: can't load audio data" << std::endl; + auto* clip = m_assets->get<AudioClipShort>(filename); + if ( clip == nullptr ) { + debug::warning("audio asset requested, but not loaded: {}", filename); return; } - play(clip, looping); + play(*clip, looping); + debug::info("played audio: {}", filename); } - void AudioServiceOAL::play(AudioClip &clip, bool looping) { - // play the audio on the default (non-positioned) source - if (m_defaultSource != nullptr) { - m_defaultSource->play(clip, looping); + void AudioServiceOAL::play(const AudioClipShort &clip, bool looping) { + if ( m_defaultSource == nullptr ){ + return; } + + // play the audio on the default (non-positioned) source + m_defaultSource->play(clip, looping); + } + + void AudioServiceOAL::release() { + m_defaultSource = nullptr; + + alcMakeContextCurrent(nullptr); + alcDestroyContext(m_context); + alcCloseDevice(m_device); + m_context = nullptr; + m_device = nullptr; } } \ No newline at end of file diff --git a/fggl/audio/openal/module.cpp b/fggl/audio/openal/module.cpp new file mode 100644 index 0000000..056abd3 --- /dev/null +++ b/fggl/audio/openal/module.cpp @@ -0,0 +1,39 @@ +/* + * This file is part of FGGL. + * + * FGGL is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any + * later version. + * + * FGGL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with FGGL. + * If not, see <https://www.gnu.org/licenses/>. + */ + +// +// Created by webpigeon on 05/11/22. +// + +#include "fggl/audio/openal/module.hpp" + +namespace fggl::audio { + + bool OpenAL::factory(modules::ModuleService service, modules::Services &services) { + if (service == SERVICE_AUDIO_PLAYBACK) { + auto* assets = services.get<assets::AssetManager>(); + + { + auto *assetLoader = services.get<assets::Loader>(); + assetLoader->setFactory(OGG_VORBIS, openal::load_vorbis, assets::LoadType::PATH); + } + + services.bind<audio::AudioService, openal::AudioServiceOAL>(assets); + return true; + } + + return false; + } + +} \ No newline at end of file diff --git a/fggl/audio/types.cpp b/fggl/audio/types.cpp index 485a900..ade96e0 100644 --- a/fggl/audio/types.cpp +++ b/fggl/audio/types.cpp @@ -15,7 +15,6 @@ #include "fggl/data/storage.hpp" #include "fggl/audio/audio.hpp" -#include "../stb/stb_vorbis.h" namespace fggl::audio { @@ -23,13 +22,4 @@ namespace fggl::audio { namespace fggl::data { - template<> - bool fggl_deserialize<audio::AudioClip>(std::filesystem::path &data, audio::AudioClip *out) { - out->sampleCount = stb_vorbis_decode_filename(data.string().c_str(), - &out->channels, - &out->sampleRate, - &out->data); - return out->sampleCount != -1; - } - } // namespace fggl::data diff --git a/fggl/gfx/ogl4/module.cpp b/fggl/gfx/ogl4/module.cpp index b20b384..a3c0d4e 100644 --- a/fggl/gfx/ogl4/module.cpp +++ b/fggl/gfx/ogl4/module.cpp @@ -173,7 +173,6 @@ namespace fggl::gfx { auto *storage = services.get<data::Storage>(); auto *fontLibrary = services.get<gui::FontLibrary>(); auto *assets = services.get<assets::AssetManager>(); - services.bind<WindowGraphics, ogl4::WindowGraphics>(storage, fontLibrary, assets); // register as responsible for creating rendering components diff --git a/include/fggl/audio/audio.hpp b/include/fggl/audio/audio.hpp index 8b77de4..1d9c912 100644 --- a/include/fggl/audio/audio.hpp +++ b/include/fggl/audio/audio.hpp @@ -16,8 +16,10 @@ #define FGGL_AUDIO_AUDIO_HPP #include <string> + #include "fggl/data/storage.hpp" #include "fggl/modules/module.hpp" +#include "fggl/assets/module.hpp" //! backend independent audio interface namespace fggl::audio { @@ -27,12 +29,21 @@ namespace fggl::audio { * * If the sampleCount is -1, the clip is invalid. */ - struct AudioClip { + template<typename T> + struct AudioClip { int channels = 0; int sampleRate = 0; int sampleCount = -1; - short *data = nullptr; - }; + T *data = nullptr; + + [[nodiscard]] + inline int size() const { + return sampleCount * sizeof(T); + } + }; + + using AudioClipShort = AudioClip<short>; + using AudioClipByte = AudioClip<char>; constexpr modules::ModuleService SERVICE_AUDIO_PLAYBACK = modules::make_service("fggl::audio::AudioService"); @@ -43,8 +54,9 @@ namespace fggl::audio { class AudioService { public: constexpr static const modules::ModuleService service = SERVICE_AUDIO_PLAYBACK; - virtual void play(const std::string &filename, bool looping = false) = 0; - virtual void play(AudioClip &clip, bool looping = false) = 0; + + virtual void play(const assets::AssetGUID &asset, bool looping = false) = 0; + virtual void play(const AudioClipShort &clip, bool looping = false) = 0; virtual ~AudioService() = default; }; diff --git a/include/fggl/audio/null_audio.hpp b/include/fggl/audio/null_audio.hpp index fb2bfd8..10df67a 100644 --- a/include/fggl/audio/null_audio.hpp +++ b/include/fggl/audio/null_audio.hpp @@ -26,11 +26,15 @@ namespace fggl::audio { class NullAudioService : public AudioService { public: NullAudioService() = default; - virtual ~NullAudioService() = default; + ~NullAudioService() override = default; - void play(const std::string & /*filename*/, bool /*looping = false*/) override {} + NullAudioService(NullAudioService&) = delete; + NullAudioService(NullAudioService&&) = delete; + NullAudioService& operator=(const NullAudioService&) = delete; + NullAudioService& operator=(NullAudioService&&) = delete; - void play(AudioClip & /*clip*/, bool /*looping = false*/) override {} + void play(const std::string & /*filename*/, bool /*looping = false*/) override; + void play(const AudioClipShort & /*clip*/, bool /*looping = false*/) override; }; struct NullAudio { @@ -39,18 +43,9 @@ namespace fggl::audio { SERVICE_AUDIO_PLAYBACK }; constexpr static const std::array<modules::ModuleService, 0> depends = {}; - static const modules::ServiceFactory factory; + bool factory(modules::ModuleService, modules::Services&); }; - bool null_factory(modules::ModuleService service, modules::Services &services) { - if (service == SERVICE_AUDIO_PLAYBACK) { - services.bind<audio::AudioService, audio::NullAudioService>(); - return true; - } - return false; - } - - const modules::ServiceFactory NullAudio::factory = null_factory; } // namespace fggl::audio diff --git a/include/fggl/audio/openal/audio.hpp b/include/fggl/audio/openal/audio.hpp index 9727ad8..b1d4bcf 100644 --- a/include/fggl/audio/openal/audio.hpp +++ b/include/fggl/audio/openal/audio.hpp @@ -23,6 +23,8 @@ #include <AL/alc.h> #include "fggl/audio/audio.hpp" +#include "fggl/assets/manager.hpp" + #include "fggl/data/storage.hpp" #include "fggl/math/types.hpp" @@ -32,6 +34,10 @@ namespace fggl::audio::openal { + constexpr uint32_t NULL_BUFFER_ID = 0; + + assets::AssetRefRaw load_vorbis(assets::Loader* loader, const assets::AssetGUID &, const assets::AssetData &, void* userPtr); + enum class AudioType { MONO_8 = AL_FORMAT_MONO8, MONO_16 = AL_FORMAT_MONO16, @@ -39,7 +45,7 @@ namespace fggl::audio::openal { STEREO_16 = AL_FORMAT_STEREO16 }; - static void checkError(std::string context) { + static void check_error(const std::string& context) { auto code = alGetError(); if (code == AL_NO_ERROR) { return; @@ -48,25 +54,25 @@ namespace fggl::audio::openal { // now we check the error message std::string error = "unknown"; switch (code) { - case ALC_INVALID_DEVICE: error = "Invalid Device"; - break; - case ALC_INVALID_CONTEXT: error = "Invalid Context"; - break; - case ALC_INVALID_ENUM: error = "Invalid enum"; - break; - case ALC_INVALID_VALUE: error = "Invalid value"; - break; - case ALC_OUT_OF_MEMORY: error = "Out of memory"; - break; - default: error = "unknown error"; + case ALC_INVALID_DEVICE: error = "Invalid Device"; + break; + case ALC_INVALID_CONTEXT: error = "Invalid Context"; + break; + case ALC_INVALID_ENUM: error = "Invalid enum"; + break; + case ALC_INVALID_VALUE: error = "Invalid value"; + break; + case ALC_OUT_OF_MEMORY: error = "Out of memory"; + break; + default: error = "unknown error"; } - std::cerr << "OpenAL error: " << context << ": " << error << std::endl; + debug::error("OpenAL error: context={}, error={}", context, error); } class AudioBuffer { public: - AudioBuffer() : m_buffer(0) { + AudioBuffer() : m_buffer(NULL_BUFFER_ID) { alGenBuffers(1, &m_buffer); } @@ -74,16 +80,24 @@ namespace fggl::audio::openal { alDeleteBuffers(1, &m_buffer); } + AudioBuffer(const AudioBuffer&) = delete; + AudioBuffer(const AudioBuffer&&) = delete; + + AudioBuffer& operator=(const AudioBuffer&) = delete; + AudioBuffer& operator=(const AudioBuffer&&) = delete; + inline void setData(AudioType type, void *data, ALsizei size, ALsizei frequency) { + assert( m_buffer != 0 ); + assert( data != nullptr ); alBufferData(m_buffer, (ALenum) type, data, size, frequency); } - inline ALuint value() { + inline ALuint value() const { return m_buffer; } private: - ALuint m_buffer; + ALuint m_buffer = 0; }; class AudioSource { @@ -96,6 +110,11 @@ namespace fggl::audio::openal { alDeleteSources(1, &m_source); } + AudioSource(const AudioSource& source) = delete; + AudioSource(const AudioSource&& source) = delete; + AudioSource& operator=(const AudioSource&) = delete; + AudioSource& operator=(AudioSource&&) = delete; + inline void play() { alSourcePlay(m_source); } @@ -115,21 +134,10 @@ namespace fggl::audio::openal { inline void play(AudioBuffer &buffer, bool looping) { alSourcei(m_source, AL_BUFFER, buffer.value()); alSourcei(m_source, AL_LOOPING, looping ? AL_TRUE : AL_FALSE); + alSourcePlay(m_source); } - inline void play(AudioClip &clip, bool looping) { - checkError("pre play"); - - AudioType format = clip.channels == 1 ? AudioType::MONO_8 : AudioType::STEREO_8; - m_splat.setData(format, clip.data, clip.sampleCount, clip.sampleRate); - checkError("saving to buffer"); - - alSourcei(m_source, AL_BUFFER, m_splat.value()); - alSourcei(m_source, AL_LOOPING, looping ? AL_TRUE : AL_FALSE); - checkError("setting parameters"); - - play(); - } + inline void play(const AudioClipShort &clip, bool looping = false); inline void velocity(math::vec3 &value) { alSource3f(m_source, AL_VELOCITY, value.x, value.y, value.z); @@ -150,32 +158,33 @@ namespace fggl::audio::openal { class AudioServiceOAL : public AudioService { public: - explicit AudioServiceOAL(data::Storage *storage) : m_device(alcOpenDevice(nullptr)), m_storage(storage) { + explicit AudioServiceOAL(assets::AssetManager *assets) : m_device(alcOpenDevice(nullptr)), m_assets(assets) { if (m_device != nullptr) { m_context = alcCreateContext(m_device, nullptr); alcMakeContextCurrent(m_context); - checkError("context setup"); + check_error("context setup"); m_defaultSource = std::make_unique<AudioSource>(); - checkError("default source setup"); + check_error("default source setup"); } } ~AudioServiceOAL() override { if (m_device != nullptr) { - alcDestroyContext(m_context); - alcCloseDevice(m_device); + release(); } } - void play(const std::string &filename, bool looping = false) override; - void play(AudioClip &clip, bool looping = false) override; + void play(const assets::AssetGUID &filename, bool looping = false) override; + void play(const AudioClipShort &clip, bool looping = false) override; private: ALCdevice *m_device; ALCcontext *m_context{nullptr}; std::unique_ptr<AudioSource> m_defaultSource{nullptr}; - data::Storage *m_storage = nullptr; + assets::AssetManager* m_assets; + + void release(); }; } // namespace fggl::audio::openal diff --git a/include/fggl/audio/openal/module.hpp b/include/fggl/audio/openal/module.hpp index 238e4b7..260ef67 100644 --- a/include/fggl/audio/openal/module.hpp +++ b/include/fggl/audio/openal/module.hpp @@ -21,30 +21,27 @@ #include <array> #include <string> + +#include "fggl/assets/module.hpp" + #include "fggl/audio/audio.hpp" #include "fggl/audio/openal/audio.hpp" namespace fggl::audio { + constexpr auto OGG_VORBIS = assets::AssetType::make("audio/vorbis"); + struct OpenAL { constexpr static const char *name = "fggl::audio::OpenAL"; constexpr static const std::array<modules::ModuleService, 1> provides = { SERVICE_AUDIO_PLAYBACK }; constexpr static const std::array<modules::ModuleService, 1> depends = { - modules::make_service("fggl::data::Storage") + assets::AssetManager::service }; static bool factory(modules::ModuleService name, modules::Services &serviceManager); }; - bool OpenAL::factory(modules::ModuleService service, modules::Services &services) { - if (service == SERVICE_AUDIO_PLAYBACK) { - auto storage = services.get<data::Storage>(); - services.bind<audio::AudioService, openal::AudioServiceOAL>(storage); - return true; - } - return false; - } } // namespace fggl::audio -- GitLab