From 39b928b525a0a6ec6bab00e63621713695980fd7 Mon Sep 17 00:00:00 2001
From: Joseph Walton-Rivers <joseph@walton-rivers.uk>
Date: Mon, 2 May 2022 13:52:28 +0100
Subject: [PATCH] first stab at audio api

---
 .gitattributes                      |   1 +
 CMakeLists.txt                      |   1 +
 demo/data/click.ogg                 | Bin 0 -> 4371 bytes
 demo/demo/main.cpp                  |  18 ++-
 fggl/CMakeLists.txt                 |   1 +
 fggl/audio/CMakeLists.txt           |   7 +
 fggl/audio/openal/CMakeLists.txt    |   8 ++
 fggl/audio/openal/audio.cpp         |  49 +++++++
 fggl/audio/types.cpp                |  42 ++++++
 include/fggl/audio/audio.hpp        |  51 +++++++
 include/fggl/audio/openal/audio.hpp | 197 ++++++++++++++++++++++++++++
 include/fggl/data/storage.hpp       |   2 +-
 12 files changed, 373 insertions(+), 4 deletions(-)
 create mode 100644 .gitattributes
 create mode 100644 demo/data/click.ogg
 create mode 100644 fggl/audio/CMakeLists.txt
 create mode 100644 fggl/audio/openal/CMakeLists.txt
 create mode 100644 fggl/audio/openal/audio.cpp
 create mode 100644 fggl/audio/types.cpp
 create mode 100644 include/fggl/audio/audio.hpp
 create mode 100644 include/fggl/audio/openal/audio.hpp

diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..1b9fa98
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1 @@
+*,ogg filter=lfs diff=lfs merge=lfs -text
diff --git a/CMakeLists.txt b/CMakeLists.txt
index e1527a8..851e313 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -28,6 +28,7 @@ if (CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME)
                 spdlog/1.10.0
                 freetype/2.11.1
                 bullet3/3.22a
+                openal/1.21.1
             GENERATORS 
                 cmake_find_package
                 cmake_find_package_multi
diff --git a/demo/data/click.ogg b/demo/data/click.ogg
new file mode 100644
index 0000000000000000000000000000000000000000..62de5e45638cfd2a568406ff86f25ff98e2738e1
GIT binary patch
literal 4371
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
Q?kr0GsQUATpT6t-za7Z#;Q#;t

literal 0
HcmV?d00001

diff --git a/demo/demo/main.cpp b/demo/demo/main.cpp
index 45846b3..8eec855 100644
--- a/demo/demo/main.cpp
+++ b/demo/demo/main.cpp
@@ -23,6 +23,7 @@
 #include <memory>
 
 #include "fggl/app.hpp"
+#include "fggl/audio/openal/audio.hpp"
 
 #include "fggl/gfx/atlas.hpp"
 #include "fggl/gfx/window.hpp"
@@ -77,6 +78,7 @@ static void setupServiceLocators(fggl::util::ServiceLocator& locator) {
 
 	locator.supply<fggl::gui::FontLibrary>(std::make_shared<fggl::gui::FontLibrary>());
 	locator.supply<fggl::ecs3::TypeRegistry>(std::make_shared<fggl::ecs3::TypeRegistry>());
+	locator.supply<fggl::audio::AudioService>(std::make_shared<fggl::audio::openAL>());
 }
 
 int main(int argc, const char* argv[]) {
@@ -107,9 +109,19 @@ int main(int argc, const char* argv[]) {
     auto *menu = app.add_state<fggl::scenes::BasicMenu>("menu");
 
 	// add some menu items for the game states
-    menu->add("terrain", [&app]() { app.change_state("game"); });
-	menu->add("rollball", [&app]() { app.change_state("rollball"); });
-	menu->add("quit", [&app]() { app.running(false); });
+	auto audio = locator.get<fggl::audio::AudioService>();
+    menu->add("terrain", [&app, &audio]() {
+		audio->play("click.ogg", false);
+		app.change_state("game");
+	});
+	menu->add("rollball", [&app, &audio]() {
+		audio->play("click.ogg", false);
+		app.change_state("rollball");
+	});
+	menu->add("quit", [&app, &audio]() {
+		audio->play("click.ogg", false);
+		app.running(false);
+	});
 
 	// the game states themselves
     app.add_state<GameScene>("game");
diff --git a/fggl/CMakeLists.txt b/fggl/CMakeLists.txt
index 0d34492..7305b5b 100644
--- a/fggl/CMakeLists.txt
+++ b/fggl/CMakeLists.txt
@@ -69,6 +69,7 @@ target_link_libraries(${PROJECT_NAME} PUBLIC freetype)
 
 # Graphics backend
 add_subdirectory(gfx)
+add_subdirectory(audio)
 
 # physics integration
 add_subdirectory(phys/bullet)
diff --git a/fggl/audio/CMakeLists.txt b/fggl/audio/CMakeLists.txt
new file mode 100644
index 0000000..f4c1f30
--- /dev/null
+++ b/fggl/audio/CMakeLists.txt
@@ -0,0 +1,7 @@
+target_sources(fggl
+        PRIVATE
+            types.cpp
+
+)
+
+add_subdirectory(openal)
\ No newline at end of file
diff --git a/fggl/audio/openal/CMakeLists.txt b/fggl/audio/openal/CMakeLists.txt
new file mode 100644
index 0000000..1b7284d
--- /dev/null
+++ b/fggl/audio/openal/CMakeLists.txt
@@ -0,0 +1,8 @@
+find_package( OpenAL REQUIRED )
+target_link_libraries(fggl PUBLIC OpenAL::OpenAL )
+
+target_sources(fggl
+    PRIVATE
+        audio.cpp
+)
+
diff --git a/fggl/audio/openal/audio.cpp b/fggl/audio/openal/audio.cpp
new file mode 100644
index 0000000..6afd540
--- /dev/null
+++ b/fggl/audio/openal/audio.cpp
@@ -0,0 +1,49 @@
+/*
+ * ${license.title}
+ * Copyright (C) 2022 ${license.owner}
+ * ${license.mailto}
+ *
+ * This program 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.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+
+#include "fggl/audio/openal/audio.hpp"
+
+#include "fggl/util/service.h"
+#include "fggl/data/storage.hpp"
+
+namespace fggl::audio::openal {
+
+	void AudioServiceOAL::play(const std::string &filename, bool looping) {
+
+		// load audio clip into temp storage
+		AudioClip clip;
+		auto& locator = util::ServiceLocator::instance();
+		auto storage = locator.get<data::Storage>();
+		bool result = storage->load(data::StorageType::Data, filename, &clip);
+		if ( !result ) {
+			std::cerr << "error: can't load audio data" << std::endl;
+		}
+
+		play(clip, looping);
+	}
+
+	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);
+		}
+	}
+}
\ No newline at end of file
diff --git a/fggl/audio/types.cpp b/fggl/audio/types.cpp
new file mode 100644
index 0000000..40df669
--- /dev/null
+++ b/fggl/audio/types.cpp
@@ -0,0 +1,42 @@
+/*
+ * ${license.title}
+ * Copyright (C) 2022 ${license.owner}
+ * ${license.mailto}
+ *
+ * This program 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.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+#include "fggl/data/storage.hpp"
+#include "fggl/audio/audio.hpp"
+
+#include "../stb/stb_vorbis.h"
+
+namespace fggl::audio {
+
+
+} // 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.c_str(),
+													 &out->channels,
+													 &out->sampleRate,
+													 &out->data);
+		return out->sampleCount != -1;
+	}
+
+} // namespace fggl::data
\ No newline at end of file
diff --git a/include/fggl/audio/audio.hpp b/include/fggl/audio/audio.hpp
new file mode 100644
index 0000000..0b2eee0
--- /dev/null
+++ b/include/fggl/audio/audio.hpp
@@ -0,0 +1,51 @@
+/*
+ * ${license.title}
+ * Copyright (C) 2022 ${license.owner}
+ * ${license.mailto}
+ *
+ * This program 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.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+#ifndef FGGL_AUDIO_AUDIO_HPP
+#define FGGL_AUDIO_AUDIO_HPP
+
+#include <string>
+
+namespace fggl::audio {
+
+	/**
+	 * AudioClip is bit of audio loaded into memory.
+	 *
+	 * If the sampleCount is -1, the clip is invalid.
+	 */
+	struct AudioClip {
+		int channels;
+		int sampleRate;
+		int sampleCount;
+		short* data;
+	};
+
+	class AudioService {
+		public:
+			AudioService() = default;
+			virtual ~AudioService() = default;
+
+			virtual void play(const std::string& filename, bool looping = false) = 0;
+			virtual void play(AudioClip& clip, bool looping = false) = 0;
+	};
+
+} // namespace fggl::audio
+
+#endif //FGGL_AUDIO_AUDIO_HPP
diff --git a/include/fggl/audio/openal/audio.hpp b/include/fggl/audio/openal/audio.hpp
new file mode 100644
index 0000000..b9de3ee
--- /dev/null
+++ b/include/fggl/audio/openal/audio.hpp
@@ -0,0 +1,197 @@
+/*
+ * ${license.title}
+ * Copyright (C) 2022 ${license.owner}
+ * ${license.mailto}
+ *
+ * This program 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.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+//
+// Created by webpigeon on 01/05/22.
+//
+
+#ifndef FGGL_AUDIO_OPENAL_AUDIO_HPP
+#define FGGL_AUDIO_OPENAL_AUDIO_HPP
+
+#include <AL/al.h>
+#include <AL/alc.h>
+
+#include "fggl/audio/audio.hpp"
+#include "fggl/math/types.hpp"
+
+#include <string>
+#include <iostream>
+#include <memory>
+
+namespace fggl::audio::openal {
+
+	enum class AudioType {
+		MONO_8 = AL_FORMAT_MONO8,
+		MONO_16 = AL_FORMAT_MONO16,
+		STEREO_8 = AL_FORMAT_STEREO8,
+		STEREO_16 = AL_FORMAT_STEREO16
+	};
+
+	static void checkError(std::string context) {
+		auto code = alGetError();
+		if ( code == AL_NO_ERROR) {
+			return;
+		}
+
+		// 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";
+		}
+
+		std::cerr << "OpenAL error: " << context << ": " << error << std::endl;
+	}
+
+	class AudioBuffer {
+		public:
+			AudioBuffer() : m_buffer(0) {
+				alGenBuffers(1, &m_buffer);
+			}
+			~AudioBuffer() {
+				alDeleteBuffers(1, &m_buffer);
+			}
+
+			inline void setData(AudioType type, void* data, ALsizei size, ALsizei frequency) {
+				alBufferData(m_buffer, (ALenum)type, data, size, frequency);
+			}
+
+			inline ALuint value() {
+				return m_buffer;
+			}
+		private:
+			ALuint m_buffer;
+	};
+
+
+	class AudioSource {
+		public:
+			AudioSource() : m_source(0), m_splat() {
+				alGenSources(1, &m_source);
+			}
+			~AudioSource(){
+				alDeleteSources(1, &m_source);
+			}
+
+			inline void play() {
+				alSourcePlay( m_source );
+			}
+
+			inline void stop() {
+				alSourceStop(m_source);
+			}
+
+			inline void pause() {
+				alSourcePause( m_source );
+			}
+
+			inline void rewind() {
+				alSourceRewind( m_source );
+			}
+
+			inline void play(AudioBuffer& buffer, bool looping) {
+				alSourcei(m_source, AL_BUFFER, buffer.value());
+				alSourcei( m_source, AL_LOOPING, looping ? AL_TRUE : AL_FALSE);
+			}
+
+			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 velocity(math::vec3& value) {
+				alSource3f(m_source, AL_VELOCITY, value.x, value.y, value.z);
+			}
+			inline void position(math::vec3& value) {
+				alSource3f(m_source, AL_POSITION, value.x, value.y, value.z);
+			}
+
+			void direction(math::vec3& value) {
+				alSource3f(m_source, AL_DIRECTION, value.x, value.y, value.z);
+			}
+
+		private:
+			ALuint m_source;
+			AudioBuffer m_splat;
+	};
+
+
+	class AudioServiceOAL : public AudioService {
+		public:
+			AudioServiceOAL() : m_device(alcOpenDevice(nullptr)) {
+				if ( m_device != nullptr ) {
+					m_context = alcCreateContext(m_device, nullptr);
+					alcMakeContextCurrent(m_context);
+					checkError("context setup");
+
+					m_defaultSource = std::make_unique<AudioSource>();
+					checkError("default source setup");
+				}
+			}
+
+			~AudioServiceOAL() override {
+				if ( m_device != nullptr ) {
+					alcDestroyContext(m_context);
+					alcCloseDevice(m_device);
+				}
+			}
+
+			void play(const std::string& filename, bool looping = false) override;
+			void play(AudioClip& clip, bool looping = false) override;
+
+		private:
+			ALCdevice* m_device;
+			ALCcontext* m_context{nullptr};
+			std::unique_ptr<AudioSource> m_defaultSource{nullptr};
+	};
+
+
+} // namespace fggl::audio::openal
+
+namespace fggl::audio {
+	using openAL = openal::AudioServiceOAL;
+
+} // namepace fggl::audio
+
+#endif //FGGL_AUDIO_OPENAL_AUDIO_HPP
diff --git a/include/fggl/data/storage.hpp b/include/fggl/data/storage.hpp
index 6ae22d0..872c753 100644
--- a/include/fggl/data/storage.hpp
+++ b/include/fggl/data/storage.hpp
@@ -22,7 +22,7 @@ namespace fggl::data {
 			bool load(StorageType pool, const std::string &name, T *out) {
 				auto path = resolvePath(pool, name);
 				if (!std::filesystem::exists(path)) {
-					//spdlog::warn("Path {} does not exist!", path.c_str());
+					std::cerr << "Path " << path << " does not exist!" << std::endl;
 					return false;
 				}
 				return fggl_deserialize<T>(path, out);
-- 
GitLab