From f685309f7b5827dfb4dce06d5570495e77b46af5 Mon Sep 17 00:00:00 2001 From: Diogo Miranda Date: Sun, 29 Sep 2024 22:26:26 +0100 Subject: [PATCH 1/6] feat(audio): replace OpenAL audio device for Miniaudio backend (#1005) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: João Miguel Nogueira <101069446+Dageus@users.noreply.github.com> --- core/include/cubos/core/al/audio_device.hpp | 163 ++++++++++++ .../cubos/core/al/miniaudio_device.hpp | 77 ++++++ core/src/al/audio_device.cpp | 13 + core/src/al/miniaudio_device.cpp | 238 ++++++++++++++++++ 4 files changed, 491 insertions(+) create mode 100644 core/include/cubos/core/al/audio_device.hpp create mode 100644 core/include/cubos/core/al/miniaudio_device.hpp create mode 100644 core/src/al/audio_device.cpp create mode 100644 core/src/al/miniaudio_device.cpp diff --git a/core/include/cubos/core/al/audio_device.hpp b/core/include/cubos/core/al/audio_device.hpp new file mode 100644 index 0000000000..98f3e62378 --- /dev/null +++ b/core/include/cubos/core/al/audio_device.hpp @@ -0,0 +1,163 @@ +/// @file +/// @brief Class @ref cubos::core::al::AudioDevice and related types. +/// @ingroup core-al + +#pragma once + +#include +#include +#include + +#include +#include + +#include + +namespace cubos::core::al +{ + namespace impl + { + class Buffer; + class Source; + } // namespace impl + + /// @brief Handle to an audio buffer. + /// @see impl::Buffer - audio buffer interface. + /// @see AudioDevice::createBuffer() + /// @ingroup core-al + using Buffer = std::shared_ptr; + + /// @brief Handle to an audio source. + /// @see impl::Source - audio source interface. + /// @see AudioDevice::createSource() + /// @ingroup core-al + using Source = std::shared_ptr; + + /// @brief Audio device interface used to wrap low-level audio rendering APIs. + class CUBOS_CORE_API AudioDevice + { + public: + AudioDevice() = default; + virtual ~AudioDevice() = default; + + /// @brief Forbid copy construction. + AudioDevice(const AudioDevice&) = delete; + + /// @brief Creates an audio device. + /// @see enumerateDevices() + /// @return Audio device, or nullptr on failure. + static std::shared_ptr create(); + + /// @brief Creates a new audio buffer + /// @param filePath File path to create buffer from. + /// @return Handle of the new buffer. + static Buffer createBuffer(const void* data, size_t dataSize); + + /// @brief Creates a new audio source. + /// @return Handle of the new source. + static Source createSource(); + + /// @brief Enumerates the available devices. + /// @param[out] devices Vector to fill with the available devices. + static void enumerateDevices(std::vector& devices); + + /// @brief Sets the position of the listener. + /// @param position Position. + /// @param listenerIndex Index of the listener + virtual void setListenerPosition(const glm::vec3& position, unsigned int listenerIndex = 0) = 0; + + /// @brief Sets the orientation of the listener. + /// @param forward Forward direction of the listener. + /// @param up Up direction of the listener. + /// @param listenerIndex Index of the listener + virtual void setListenerOrientation(const glm::vec3& forward, const glm::vec3& up, + unsigned int listenerIndex = 0) = 0; + + /// @brief Sets the velocity of the listener. Used to implement the doppler effect. + /// @param velocity Velocity of the listener. + /// @param listenerIndex Index of the listener + virtual void setListenerVelocity(const glm::vec3& velocity, unsigned int listenerIndex = 0) = 0; + }; + + /// @brief Namespace to store the abstract types implemented by the audio device implementations. + namespace impl + { + /// @brief Abstract audio buffer. + class CUBOS_CORE_API Buffer + { + public: + ma_decoder mDecoder; + + virtual ~Buffer() = default; + virtual size_t getLength() = 0; + static std::shared_ptr create(const void* data, size_t dataSize); + + protected: + Buffer() = default; + }; + + /// @brief Abstract audio source. + class CUBOS_CORE_API Source + { + public: + virtual ~Source() = default; + + /// @brief Creates a source + static std::shared_ptr create(); + + /// @brief Sets the buffer to be played by the source. + /// @param buffer Buffer. + virtual void setBuffer(cubos::core::al::Buffer buffer) = 0; + + /// @brief Sets the position of the source, by default, in the world space. + /// @see setRelative() to change this behavior. + /// @param position Position. + virtual void setPosition(const glm::vec3& position) = 0; + + /// @brief Sets the velocity of the source, by default, in the world space. + /// @param velocity Velocity. + virtual void setVelocity(const glm::vec3& velocity) = 0; + + /// @brief Sets the gain of the source. + /// @param gain Gain. + virtual void setGain(float gain) = 0; + + /// @brief Sets the pitch of the source. + /// @param pitch Pitch. + virtual void setPitch(float pitch) = 0; + + /// @brief Sets whether the source plays in a loop. + /// @param looping Looping flag. + virtual void setLooping(bool looping) = 0; + + /// @brief Sets whether the source position and velocity is relative to the listener or + /// not. + /// @param relative Relative flag. + virtual void setRelative(bool relative) = 0; + + /// @brief Sets the maximum distance at which the source is audible. + /// @param maxDistance Maximum distance. + virtual void setMaxDistance(float maxDistance) = 0; + + /// @brief Sets the minimum distance at which the source starts to attenuate. + /// @param minDistance Minimum distance. + virtual void setMinDistance(float minDistance) = 0; + + /// @brief Sets the cone angle, in degrees. While also setting the outerGain. + /// @param innerAngle Outer angle, in degrees. + /// @param outerAngle Inner angle, in degrees. + /// @param coneGain Gain. + virtual void setCone(float innerAngle, float outerAngle, float outerGain) = 0; + + /// @brief Sets the cone direction of the source. + /// @param direction Direction. + virtual void setConeDirection(const glm::vec3& direction) = 0; + + /// @brief Plays the source. + virtual void play() = 0; + + protected: + Source() = default; + }; + } // namespace impl +} // namespace cubos::core::al diff --git a/core/include/cubos/core/al/miniaudio_device.hpp b/core/include/cubos/core/al/miniaudio_device.hpp new file mode 100644 index 0000000000..00e3b0f782 --- /dev/null +++ b/core/include/cubos/core/al/miniaudio_device.hpp @@ -0,0 +1,77 @@ +#pragma once + +#include + +#include + +namespace cubos::core::al +{ + class MiniaudioBuffer : public impl::Buffer + { + public: + ~MiniaudioBuffer() override; + size_t getLength() override; + + static std::shared_ptr create(const void* data, size_t dataSize) + { + return std::shared_ptr(new MiniaudioBuffer(data, dataSize)); + } + + protected: + MiniaudioBuffer(const void* data, size_t dataSize); + }; + + class MiniaudioSource : public impl::Source + { + public: + ~MiniaudioSource() override; + + static std::shared_ptr create() + { + return std::shared_ptr(new MiniaudioSource()); + } + + void setBuffer(cubos::core::al::Buffer buffer) override; + void setPosition(const glm::vec3& position) override; + void setVelocity(const glm::vec3& velocity) override; + void setGain(float gain) override; + void setPitch(float pitch) override; + void setLooping(bool looping) override; + void setRelative(bool relative) override; + void setMaxDistance(float maxDistance) override; + void setMinDistance(float minDistance) override; + void setCone(float innerAngle, float outerAngle, float outerGain) override; + void setConeDirection(const glm::vec3& direction) override; + void play() override; + + protected: + MiniaudioSource(); + + private: + ma_sound mSound; + ma_engine mEngine; + }; + + /// Audio device implementation using miniaudio. + class MiniaudioDevice : public AudioDevice + { + public: + MiniaudioDevice(); + ~MiniaudioDevice() override; + + static void enumerateDevices(std::vector& devices); + static std::string getDefaultDevice(); + static Buffer createBuffer(const void* data, size_t dataSize); + static Source createSource(); + + void setListenerPosition(const glm::vec3& position, ma_uint32 listenerIndex = 0) override; + void setListenerOrientation(const glm::vec3& forward, const glm::vec3& up, + ma_uint32 listenerIndex = 0) override; + void setListenerVelocity(const glm::vec3& velocity, ma_uint32 listenerIndex = 0) override; + + private: + ma_context mContext; + ma_device mDevice; + ma_engine mEngine; + }; +} // namespace cubos::core::al diff --git a/core/src/al/audio_device.cpp b/core/src/al/audio_device.cpp new file mode 100644 index 0000000000..ecb5a512cf --- /dev/null +++ b/core/src/al/audio_device.cpp @@ -0,0 +1,13 @@ +#include + +using namespace cubos::core::al; + +std::shared_ptr AudioDevice::create() +{ + return std::make_shared(); +} + +void AudioDevice::enumerateDevices(std::vector& devices) +{ + MiniaudioDevice::enumerateDevices(devices); +} diff --git a/core/src/al/miniaudio_device.cpp b/core/src/al/miniaudio_device.cpp new file mode 100644 index 0000000000..c8d2b3f1c3 --- /dev/null +++ b/core/src/al/miniaudio_device.cpp @@ -0,0 +1,238 @@ +#define MINIAUDIO_IMPLEMENTATION +#include +#include +#include + +using namespace cubos::core::al; + +MiniaudioBuffer::MiniaudioBuffer(const void* data, size_t dataSize) +{ + if (ma_decoder_init_memory(data, dataSize, nullptr, &mDecoder) != MA_SUCCESS) + { + CUBOS_CRITICAL("Failed to initialize Decoder from data"); + abort(); + } +} + +size_t MiniaudioBuffer::getLength() +{ + ma_uint64 lengthInPCMFrames; + ma_result result = ma_decoder_get_length_in_pcm_frames(&mDecoder, &lengthInPCMFrames); + + if (result != MA_SUCCESS) + { + CUBOS_ERROR("Failed to get the length of audio in PCM frames."); + return 0; + } + + // Calculate the length in seconds: Length in PCM frames divided by the sample rate. + return static_cast(lengthInPCMFrames) / mDecoder.outputSampleRate; +} + +MiniaudioBuffer::~MiniaudioBuffer() +{ + ma_decoder_uninit(&mDecoder); +}; + +MiniaudioSource::MiniaudioSource() +{ + if (ma_engine_init(nullptr, &mEngine) != MA_SUCCESS) + { + CUBOS_CRITICAL("Failed to initialize miniaudio engine."); + abort(); + } +} + +MiniaudioSource::~MiniaudioSource() +{ + ma_sound_uninit(&mSound); + ma_engine_uninit(&mEngine); +} + +void MiniaudioSource::setBuffer(Buffer buffer) +{ + if (ma_sound_init_from_data_source(&mEngine, &buffer->mDecoder, 0, nullptr, &mSound) != MA_SUCCESS) + { + CUBOS_CRITICAL("Failed to initialize sound from buffer."); + abort(); + } +} + +void MiniaudioSource::setPosition(const glm::vec3& position) +{ + ma_sound_set_position(&mSound, position.x, position.y, position.z); +} + +void MiniaudioSource::setVelocity(const glm::vec3& velocity) +{ + ma_sound_set_velocity(&mSound, velocity.x, velocity.y, velocity.z); +} + +void MiniaudioSource::setGain(float gain) +{ + ma_sound_set_volume(&mSound, gain); +} + +void MiniaudioSource::setPitch(float pitch) +{ + ma_sound_set_pitch(&mSound, pitch); +} + +void MiniaudioSource::setLooping(bool looping) +{ + ma_sound_set_looping(&mSound, static_cast(looping)); +} + +void MiniaudioSource::setRelative(bool relative) +{ + relative ? ma_sound_set_positioning(&mSound, ma_positioning_relative) + : ma_sound_set_positioning(&mSound, ma_positioning_absolute); +} + +void MiniaudioSource::setMaxDistance(float maxDistance) +{ + ma_sound_set_max_distance(&mSound, maxDistance); +} + +void MiniaudioSource::setMinDistance(float minDistance) +{ + ma_sound_set_min_distance(&mSound, minDistance); +} + +void MiniaudioSource::setCone(float innerAngle, float outerAngle, float outerGain = 1.0F) +{ + ma_sound_set_cone(&mSound, innerAngle, outerAngle, outerGain); +} + +void MiniaudioSource::setConeDirection(const glm::vec3& direction) +{ + ma_sound_set_direction(&mSound, direction.x, direction.y, direction.z); +} + +void MiniaudioSource::play() +{ + if (ma_sound_start(&mSound) != MA_SUCCESS) + { + CUBOS_CRITICAL("Failed to start sound."); + abort(); + } +} + +MiniaudioDevice::MiniaudioDevice() +{ + // Initialize miniaudio context. + if (ma_context_init(nullptr, 0, nullptr, &mContext) != MA_SUCCESS) + { + CUBOS_CRITICAL("Failed to initialize miniaudio context."); + abort(); + } + + // Initialize miniaudio engine + if (ma_engine_init(nullptr, &mEngine) != MA_SUCCESS) + { + CUBOS_CRITICAL("Failed to initialize miniaudio engine."); + abort(); + } + + // Configure the device. + ma_device_config deviceConfig = ma_device_config_init(ma_device_type_playback); + deviceConfig.playback.format = ma_format_f32; // Set to ma_format_unknown to use the device's native format. + deviceConfig.playback.channels = 2; // Set to 0 to use the device's native channel count. + deviceConfig.sampleRate = 48000; // Set to 0 to use the device's native sample rate. + + // Initialize the audio device. + if (ma_device_init(&mContext, &deviceConfig, &mDevice) != MA_SUCCESS) + { + CUBOS_CRITICAL("Failed to initialize audio device."); + ma_context_uninit(&mContext); + abort(); + } + + ma_device_start(&mDevice); +} + +MiniaudioDevice::~MiniaudioDevice() +{ + + ma_device_uninit(&mDevice); + ma_context_uninit(&mContext); +} + +void MiniaudioDevice::enumerateDevices(std::vector& devices) +{ + ma_context context; + if (ma_context_init(nullptr, 0, nullptr, &context) != MA_SUCCESS) + { + CUBOS_CRITICAL("Failed to initialize audio context."); + abort(); + } + + ma_device_info* pPlaybackDeviceInfos; + ma_uint32 playbackDeviceCount; + if (ma_context_get_devices(&context, &pPlaybackDeviceInfos, &playbackDeviceCount, nullptr, nullptr) != MA_SUCCESS) + { + CUBOS_CRITICAL("Failed to enumerate devices."); + ma_context_uninit(&context); // Uninitialize context before aborting + abort(); + } + + for (ma_uint32 i = 0; i < playbackDeviceCount; i++) + { + devices.emplace_back(pPlaybackDeviceInfos[i].name); + } + + ma_context_uninit(&context); +} + +std::string MiniaudioDevice::getDefaultDevice() +{ + ma_context context; + if (ma_context_init(nullptr, 0, nullptr, &context) != MA_SUCCESS) + { + CUBOS_CRITICAL("Failed to initialize audio context."); + abort(); + } + + std::string defaultDeviceName; + ma_context_enumerate_devices( + &context, + [](ma_context*, ma_device_type deviceType, const ma_device_info* pDeviceInfo, void* pUserData) -> ma_bool32 { + auto* pDefaultDeviceName = static_cast(pUserData); + if (deviceType == ma_device_type_playback && pDeviceInfo->isDefault == MA_TRUE) + { + *pDefaultDeviceName = pDeviceInfo->name; // Set the default device name + return MA_FALSE; + } + return MA_TRUE; + }, + &defaultDeviceName); // Pass defaultDeviceName as pUserData + + ma_context_uninit(&context); + return defaultDeviceName; +} + +Buffer MiniaudioDevice::createBuffer(const void* data, size_t dataSize) +{ + return MiniaudioBuffer::create(data, dataSize); +} + +Source MiniaudioDevice::createSource() +{ + return MiniaudioSource::create(); +} + +void MiniaudioDevice::setListenerPosition(const glm::vec3& position, ma_uint32 listenerIndex) +{ + ma_engine_listener_set_position(&mEngine, listenerIndex, position.x, position.y, position.z); +} + +void MiniaudioDevice::setListenerOrientation(const glm::vec3& forward, const glm::vec3& up, ma_uint32 listenerIndex) +{ + ma_engine_listener_set_direction(&mEngine, listenerIndex, forward.x, forward.y, forward.z); + ma_engine_listener_set_world_up(&mEngine, listenerIndex, up.x, up.y, up.z); +} + +void MiniaudioDevice::setListenerVelocity(const glm::vec3& velocity, ma_uint32 listenerIndex) +{ + ma_engine_listener_set_velocity(&mEngine, listenerIndex, velocity.x, velocity.y, velocity.z); +} From 02d6ab0269481addff112b16280a2f96539ed7f5 Mon Sep 17 00:00:00 2001 From: Diogo Miranda Date: Sun, 29 Sep 2024 22:26:52 +0100 Subject: [PATCH 2/6] feat(audio): Add Audio asset (#230) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: João Miguel Nogueira <101069446+Dageus@users.noreply.github.com> --- engine/CMakeLists.txt | 3 ++ engine/include/cubos/engine/audio/audio.hpp | 27 ++++++++++++ engine/include/cubos/engine/audio/bridge.hpp | 30 +++++++++++++ engine/src/audio/audio.cpp | 44 ++++++++++++++++++++ engine/src/audio/bridge.cpp | 26 ++++++++++++ 5 files changed, 130 insertions(+) create mode 100644 engine/include/cubos/engine/audio/audio.hpp create mode 100644 engine/include/cubos/engine/audio/bridge.hpp create mode 100644 engine/src/audio/audio.cpp create mode 100644 engine/src/audio/bridge.cpp diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt index e29913c7ee..13a420688d 100644 --- a/engine/CMakeLists.txt +++ b/engine/CMakeLists.txt @@ -36,6 +36,9 @@ set(CUBOS_ENGINE_SOURCE "src/utils/free_camera/plugin.cpp" "src/utils/free_camera/controller.cpp" + "src/audio/audio.cpp" + "src/audio/bridge.cpp" + "src/assets/plugin.cpp" "src/assets/assets.cpp" "src/assets/bridge.cpp" diff --git a/engine/include/cubos/engine/audio/audio.hpp b/engine/include/cubos/engine/audio/audio.hpp new file mode 100644 index 0000000000..82a9aca1fc --- /dev/null +++ b/engine/include/cubos/engine/audio/audio.hpp @@ -0,0 +1,27 @@ +/// @file +/// @brief Class @ref cubos::engine::Audio. +/// @ingroup audio-plugin +#pragma once + +#include +#include +#include + +#include + +namespace cubos::engine +{ + /// @brief Asset containing raw Audio data. + /// + /// @ingroup audio-plugin + struct CUBOS_ENGINE_API Audio + { + CUBOS_REFLECT; + std::shared_ptr mData; // Raw data of the audio + size_t mLength; // Audio length in seconds TODO: add getter in audio + + explicit Audio(core::memory::Stream& stream); + Audio(Audio&& other) noexcept; + ~Audio(); + }; +} // namespace cubos::engine diff --git a/engine/include/cubos/engine/audio/bridge.hpp b/engine/include/cubos/engine/audio/bridge.hpp new file mode 100644 index 0000000000..576afe8cdd --- /dev/null +++ b/engine/include/cubos/engine/audio/bridge.hpp @@ -0,0 +1,30 @@ +/// @file +/// @brief Class @ref cubos::engine::AudioBridge. +/// @ingroup audio-plugin + +#pragma once + +#include +#include + +namespace cubos::engine +{ + /// @brief Bridge which loads and saves @ref Sound assets. + /// + /// Uses the format specified in @ref Audio::loadFrom and @ref Audio::writeTo + /// + /// @ingroup audio-plugin + class AudioBridge : public FileBridge + { + public: + /// @brief Constructs a bridge. + AudioBridge() + : FileBridge(core::reflection::reflect