diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000000000000000000000000000000000..1b9fa9897e25dff2bf7cf2c9f0caa97af12411a9 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*,ogg filter=lfs diff=lfs merge=lfs -text diff --git a/CMakeLists.txt b/CMakeLists.txt index e1527a8acd015982bf54426d5818d02f8bef4972..851e31324dd4f2e0756babc065a3f58ffb64426b 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 Binary files /dev/null and b/demo/data/click.ogg differ diff --git a/demo/demo/main.cpp b/demo/demo/main.cpp index 45846b301cff1f443d9ba2641711d985ea8a16f5..8eec8551a933e04f336533ead3c98223f6572f5c 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 0d344924a1dfc983a20ac90e5d35f6a7c5df987b..7305b5b474370d83bb0b468c004e0c758b9c75b0 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 0000000000000000000000000000000000000000..f4c1f309d78973af88e548d274c9664eea0d5367 --- /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 0000000000000000000000000000000000000000..1b7284d2cb481502cb37b21c40367960bfc6ab60 --- /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 0000000000000000000000000000000000000000..6afd5402cc69bb5d0f458a5f30537b0977a73778 --- /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 0000000000000000000000000000000000000000..40df669d73a3dfc8b1273c0d67a6676e9e7eea59 --- /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 0000000000000000000000000000000000000000..0b2eee08a7370689c20cb3da3736cf3c8ae1e211 --- /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 0000000000000000000000000000000000000000..b9de3eea7a940f9b2c4ea1f3099a71f8d6ea54e8 --- /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 6ae22d0eb609c6616df547b8f095e65e7edd5aae..872c753e20e78ed6c9804e214c4f62ccb344c64c 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);