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