diff --git a/CHANGELOG.md b/CHANGELOG.md index 01f3ae702e..8598b9f2a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - A proper Nix package which can be used to install Cubos and Tesseratos (#1327, **RiscadoA**). - Added the option to use Shadow Normal Offset Bias algorithm (#1308, **@GalaxyCrush**) - UI text element using MSDF for text rendering (#1300, **@mkuritsu**). +- Audio Plugin (#1004, **@Dageus**, **@diogomsmiranda**). ### Changed @@ -47,7 +48,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Deadzone for input axis (#844, **@kuukitenshi**). - Generic Camera component to hold projection matrix (#1331, **@mkuritsu**). - Initial application debugging through Tesseratos (#1303, **@RiscadoA**). -- Print stacktrace with *cpptrace* on calls to CUBOS_FAIL (#1172, **@RiscadoA**). +- Print stacktrace with _cpptrace_ on calls to CUBOS_FAIL (#1172, **@RiscadoA**). - Orthographic Camera component (#1182, **@mkuritsu**). - Importer plugin (#1299, **@Scarface1809**). - Handle body rotation on penetration solving (#1272, **@fallenatlas**). diff --git a/core/include/cubos/core/al/audio_context.hpp b/core/include/cubos/core/al/audio_context.hpp index 31375c033f..4091cf73d0 100644 --- a/core/include/cubos/core/al/audio_context.hpp +++ b/core/include/cubos/core/al/audio_context.hpp @@ -57,6 +57,10 @@ namespace cubos::core::al /// @return AudioContext, or nullptr on failure. static std::shared_ptr create(); + /// @brief Gets the maximum number of listeners supported by the audio context. + /// @return Maximum number of listeners. + static int getMaxListenerCount(); + /// @brief Enumerates the available devices. /// @param[out] devices Vector to fill with the available device's specifiers. virtual void enumerateDevices(std::vector& devices) = 0; @@ -65,13 +69,17 @@ namespace cubos::core::al /// @param listenerCount Number of audio listeners to be supported by the device. /// @param specifier Identifier of the audio device. /// @return Handle of the new device - virtual AudioDevice createDevice(unsigned int listenerCount, const std::string& specifier = "") = 0; + virtual AudioDevice createDevice(unsigned int listenerCount) = 0; /// @brief Creates a new audio buffer. /// @param data Data to be written to the buffer, either .wav, .mp3 or .flac. /// @param dataSize Size of the data to be written. /// @return Handle of the new buffer. virtual Buffer createBuffer(const void* data, size_t dataSize) = 0; + + /// @brief Gets the default audio device. + /// @return Name of the default audio device. + virtual std::string getDefaultDevice() = 0; }; /// @brief Namespace to store the abstract types implemented by the audio device implementations. @@ -148,6 +156,12 @@ namespace cubos::core::al /// @brief Plays the source. virtual void play() = 0; + /// @brief Stops the source, restarting buffer to position 0. + virtual void stop() = 0; + + /// @brief Pauses the source, allowing to be played from the moment it was paused. + virtual void pause() = 0; + protected: Source() = default; }; @@ -188,8 +202,9 @@ namespace cubos::core::al /// @return Handle of the new source. virtual std::shared_ptr createSource() = 0; - /// @brief Creates a new audio listener. - /// @return Handle of the new listener. + /// @brief Gets the listener with the specific index. + /// @param index Index of the listener. + /// @return Handle of the listener. virtual std::shared_ptr listener(size_t index) = 0; protected: diff --git a/core/include/cubos/core/al/miniaudio_context.hpp b/core/include/cubos/core/al/miniaudio_context.hpp index 78dd598d9a..abaca28bc9 100644 --- a/core/include/cubos/core/al/miniaudio_context.hpp +++ b/core/include/cubos/core/al/miniaudio_context.hpp @@ -17,10 +17,10 @@ namespace cubos::core::al MiniaudioContext(); ~MiniaudioContext() override; - AudioDevice createDevice(unsigned int listenerCount, const std::string& specifier) override; + AudioDevice createDevice(unsigned int listenerCount) override; Buffer createBuffer(const void* data, size_t dataSize) override; void enumerateDevices(std::vector& devices) override; - std::string getDefaultDevice(); + std::string getDefaultDevice() override; private: #ifdef CUBOS_CORE_MINIAUDIO diff --git a/core/src/al/audio_context.cpp b/core/src/al/audio_context.cpp index 8e4af8a4d5..40abddcb9e 100644 --- a/core/src/al/audio_context.cpp +++ b/core/src/al/audio_context.cpp @@ -12,3 +12,12 @@ std::shared_ptr AudioContext::create() return nullptr; #endif } + +int AudioContext::getMaxListenerCount() +{ +#ifdef CUBOS_CORE_MINIAUDIO + return MA_ENGINE_MAX_LISTENERS; +#else + return 0; +#endif +} diff --git a/core/src/al/miniaudio_context.cpp b/core/src/al/miniaudio_context.cpp index f5a87a90bf..608b52f213 100644 --- a/core/src/al/miniaudio_context.cpp +++ b/core/src/al/miniaudio_context.cpp @@ -1,4 +1,6 @@ #define MINIAUDIO_IMPLEMENTATION +#include + #include #include #include @@ -11,9 +13,12 @@ class MiniaudioBuffer : public impl::Buffer public: ma_decoder decoder; - MiniaudioBuffer(const void* data, size_t dataSize) + MiniaudioBuffer(const void* srcData, size_t dataSize) { - if (ma_decoder_init_memory(data, dataSize, nullptr, &decoder) != MA_SUCCESS) + mData = operator new(dataSize); + std::memcpy(mData, srcData, dataSize); + + if (ma_decoder_init_memory(mData, dataSize, nullptr, &decoder) != MA_SUCCESS) { CUBOS_ERROR("Failed to initialize Decoder from data"); } @@ -26,6 +31,7 @@ class MiniaudioBuffer : public impl::Buffer ~MiniaudioBuffer() override { ma_decoder_uninit(&decoder); + operator delete(mData); } float length() override @@ -50,6 +56,7 @@ class MiniaudioBuffer : public impl::Buffer private: bool mValid = false; + void* mData; }; class MiniaudioListener : public impl::Listener @@ -93,7 +100,8 @@ class MiniaudioSource : public impl::Source { public: MiniaudioSource(ma_engine& engine) - : mEngine(engine) + : mSound({}) + , mEngine(engine) { } @@ -104,6 +112,8 @@ class MiniaudioSource : public impl::Source void setBuffer(Buffer buffer) override { + ma_sound_uninit(&mSound); + // Try to dynamically cast the Buffer to a MiniaudioBuffer. auto miniaudioBuffer = std::static_pointer_cast(buffer); @@ -180,54 +190,44 @@ class MiniaudioSource : public impl::Source } } -private: - ma_sound mSound; - ma_engine& mEngine; -}; - -class MiniaudioDevice : public impl::AudioDevice -{ -public: - MiniaudioDevice(ma_context& context, const std::string& deviceName, ma_uint32 listenerCount) - : mContext(context) + void stop() override { - ma_device_info* pPlaybackDeviceInfos; - ma_uint32 playbackDeviceCount; - ma_result result = - ma_context_get_devices(&mContext, &pPlaybackDeviceInfos, &playbackDeviceCount, nullptr, nullptr); - - if (result != MA_SUCCESS) + if (ma_sound_stop(&mSound) != MA_SUCCESS) { - CUBOS_FAIL("Failed to enumerate audio devices"); + CUBOS_ERROR("Failed to stop sound"); return; } + ma_sound_seek_to_pcm_frame(&mSound, 0); + } - ma_device_id* deviceId = nullptr; - for (ma_uint32 i = 0; i < playbackDeviceCount; i++) - { - if (deviceName == pPlaybackDeviceInfos[i].name) - { - deviceId = &pPlaybackDeviceInfos[i].id; - break; - } - } - - if (deviceId == nullptr) + void pause() override + { + if (ma_sound_stop(&mSound) != MA_SUCCESS) { - CUBOS_FAIL("Audio device '{}' not found", deviceName); + CUBOS_ERROR("Failed to pause sound"); return; } + } - ma_engine_config engineConfig = ma_engine_config_init(); +private: + ma_sound mSound; + ma_engine& mEngine; +}; +class MiniaudioDevice : public impl::AudioDevice +{ +public: + MiniaudioDevice(ma_uint32 listenerCount) + { if (listenerCount > MA_ENGINE_MAX_LISTENERS) { CUBOS_FAIL("Maximum number of listeners is 4"); return; } + ma_engine_config engineConfig = ma_engine_config_init(); + engineConfig.listenerCount = listenerCount; - engineConfig.pPlaybackDeviceID = deviceId; // Use the found device ID if (ma_engine_init(&engineConfig, &mEngine) != MA_SUCCESS) { @@ -246,6 +246,7 @@ class MiniaudioDevice : public impl::AudioDevice ~MiniaudioDevice() override { + ma_engine_uninit(&mEngine); ma_device_uninit(&mDevice); } @@ -270,7 +271,6 @@ class MiniaudioDevice : public impl::AudioDevice } private: - ma_context mContext; ma_device mDevice; ma_engine mEngine; std::vector> mListeners; @@ -308,11 +308,9 @@ std::string MiniaudioContext::getDefaultDevice() return ""; } - ma_context_uninit(&mContext); - for (ma_uint32 i = 0; i < playbackDeviceCount; i++) { - if (pPlaybackDeviceInfos[i].isDefault != 0u) + if (pPlaybackDeviceInfos[i].isDefault != 0U) { return pPlaybackDeviceInfos[i].name; } @@ -339,8 +337,6 @@ void MiniaudioContext::enumerateDevices(std::vector& devices) return; } - ma_context_uninit(&mContext); - devices.reserve(playbackDeviceCount); for (ma_uint32 i = 0; i < playbackDeviceCount; i++) @@ -375,10 +371,10 @@ Buffer MiniaudioContext::createBuffer(const void* data, size_t dataSize) #endif } -AudioDevice MiniaudioContext::createDevice(unsigned int listenerCount, const std::string& specifier) +AudioDevice MiniaudioContext::createDevice(unsigned int listenerCount) { #ifdef CUBOS_CORE_MINIAUDIO - auto device = std::make_shared(mContext, specifier, listenerCount); + auto device = std::make_shared(listenerCount); if (!device->isValid()) { CUBOS_ERROR("Failed to create MiniaudioDevice"); @@ -388,7 +384,6 @@ AudioDevice MiniaudioContext::createDevice(unsigned int listenerCount, const std return device; #else (void)listenerCount; - (void)specifier; return nullptr; #endif } diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt index 0bb41db8f3..831abfe25c 100644 --- a/engine/CMakeLists.txt +++ b/engine/CMakeLists.txt @@ -39,6 +39,15 @@ set(CUBOS_ENGINE_SOURCE "src/utils/free_camera/plugin.cpp" "src/utils/free_camera/controller.cpp" + "src/audio/plugin.cpp" + "src/audio/source.cpp" + "src/audio/source_impl.cpp" + "src/audio/listener.cpp" + "src/audio/listener_impl.cpp" + "src/audio/pause.cpp" + "src/audio/play.cpp" + "src/audio/stop.cpp" + "src/audio/audio.cpp" "src/audio/bridge.cpp" diff --git a/engine/include/cubos/engine/audio/listener.hpp b/engine/include/cubos/engine/audio/listener.hpp new file mode 100644 index 0000000000..c48278def5 --- /dev/null +++ b/engine/include/cubos/engine/audio/listener.hpp @@ -0,0 +1,25 @@ +/// @file +/// @brief Component @ref cubos::engine::AudioListener. +/// @ingroup audio-plugin + +#pragma once + +#include + +#include +#include + +#include + +namespace cubos::engine +{ + /// @brief Component which adds an AudioListener to the entitiy + /// @ingroup audio-plugin + struct CUBOS_ENGINE_API AudioListener + { + CUBOS_REFLECT; + + /// @brief Whether the listener is active or not. + bool active{false}; + }; +} // namespace cubos::engine diff --git a/engine/include/cubos/engine/audio/pause.hpp b/engine/include/cubos/engine/audio/pause.hpp new file mode 100644 index 0000000000..3dff7333fe --- /dev/null +++ b/engine/include/cubos/engine/audio/pause.hpp @@ -0,0 +1,22 @@ +/// @file +/// @brief Component @ref cubos::engine::AudioPause. +/// @ingroup audio-plugin + +#pragma once + +#include + +#include + +#include +#include + +namespace cubos::engine +{ + /// @brief Component which triggers the pause of an audio source. + /// @ingroup audio-plugin + struct CUBOS_ENGINE_API AudioPause + { + CUBOS_REFLECT; + }; +} // namespace cubos::engine diff --git a/engine/include/cubos/engine/audio/play.hpp b/engine/include/cubos/engine/audio/play.hpp new file mode 100644 index 0000000000..bffa980e9f --- /dev/null +++ b/engine/include/cubos/engine/audio/play.hpp @@ -0,0 +1,22 @@ +/// @file +/// @brief Component @ref cubos::engine::AudioPlay. +/// @ingroup audio-plugin + +#pragma once + +#include + +#include + +#include +#include + +namespace cubos::engine +{ + /// @brief Component which triggers the play of an audio source. + /// @ingroup audio-plugin + struct CUBOS_ENGINE_API AudioPlay + { + CUBOS_REFLECT; + }; +} // namespace cubos::engine diff --git a/engine/include/cubos/engine/audio/plugin.hpp b/engine/include/cubos/engine/audio/plugin.hpp new file mode 100644 index 0000000000..8a29f2463b --- /dev/null +++ b/engine/include/cubos/engine/audio/plugin.hpp @@ -0,0 +1,40 @@ +/// @dir +/// @brief @ref audio-plugin plugin directory. + +/// @file +/// @brief Plugin entry point. +/// @ingroup audio-plugin + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace cubos::engine +{ + /// @defgroup audio-plugin Audio + /// @ingroup engine + /// @brief Adds audio to @b Cubos + /// + /// ## Settings + /// - `audio.listener.count` - number of listeners per audio device (default: `4`). + /// + /// ## Dependencies + /// - @ref settings-plugin + + /// @brief Initializes the audio context (after @ref settingsTag). + CUBOS_ENGINE_API extern Tag audioInitTag; + + /// @brief Initializes the audio state (after @ref audioInitTag). + CUBOS_ENGINE_API extern Tag audioStateInitTag; + + /// @brief Plugin entry function. + /// @param cubos @b Cubos main class. + /// @ingroup assets-plugin + CUBOS_ENGINE_API void audioPlugin(Cubos& cubos); +} // namespace cubos::engine diff --git a/engine/include/cubos/engine/audio/source.hpp b/engine/include/cubos/engine/audio/source.hpp new file mode 100644 index 0000000000..05ad857fdd --- /dev/null +++ b/engine/include/cubos/engine/audio/source.hpp @@ -0,0 +1,57 @@ +/// @file +/// @brief Component @ref cubos::engine::AudioSource. +/// @ingroup audio-plugin + +#pragma once + +#include + +#include +#include + +#include +#include +#include + +namespace cubos::engine +{ + /// @brief Component which adds an AudioSource to the entitiy + /// @ingroup audio-plugin + struct CUBOS_ENGINE_API AudioSource + { + CUBOS_REFLECT; + + /// @brief Whether the source is playing or not. + bool playing{false}; + + /// @brief Whether the source is looping or not. + bool looping{false}; + + /// @brief Gain of the source. + float gain{1.0F}; + + /// @brief Pitch of the source. + float pitch{1.0F}; + + /// @brief Maximum distance of the source. + float maxDistance{FLT_MAX}; + + /// @brief Minimum distance of the source. + float minDistance{1.0F}; + + /// @brief Inner cone angle of the source. + float innerConeAngle{360.0F}; + + /// @brief Outer cone angle of the source. + float outerConeAngle{360.0F}; + + /// @brief Outer cone gain of the source. + float outerConeGain{1.0F}; + + /// @brief Direction of the source's cone. + glm::vec3 coneDirection; + + /// @brief Sound to be played by the source. + Asset