From 9a7bb18d7832df98f9b8df357e3fd00cd0b249c3 Mon Sep 17 00:00:00 2001 From: Diogo Miranda Date: Tue, 24 Sep 2024 13:17:18 +0100 Subject: [PATCH] feat(audio): replace OpenAL audio device for Miniaudio backend (#1005) --- core/CMakeLists.txt | 4 +- core/include/cubos/core/al/audio_device.hpp | 55 ++--- core/src/al/audio_device.cpp | 8 +- core/src/al/miniaudio_device.cpp | 245 ++++++++++++++++++++ core/src/al/miniaudio_device.hpp | 31 +++ 5 files changed, 302 insertions(+), 41 deletions(-) create mode 100644 core/src/al/miniaudio_device.cpp create mode 100644 core/src/al/miniaudio_device.hpp diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 4150145166..6fa25bbf6e 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -85,8 +85,8 @@ set(CUBOS_CORE_SOURCE "src/gl/util.cpp" "src/al/audio_device.cpp" - "src/al/oal_audio_device.cpp" - "src/al/oal_audio_device.hpp" + "src/al/miniaudio_device.cpp" + "src/al/miniaudio_device.hpp" "src/ecs/entity/entity.cpp" "src/ecs/entity/hash.cpp" diff --git a/core/include/cubos/core/al/audio_device.hpp b/core/include/cubos/core/al/audio_device.hpp index 894498a5c8..eba051a652 100644 --- a/core/include/cubos/core/al/audio_device.hpp +++ b/core/include/cubos/core/al/audio_device.hpp @@ -9,6 +9,7 @@ #include #include +#include #include @@ -32,15 +33,6 @@ namespace cubos::core::al /// @ingroup core-al using Source = std::shared_ptr; - /// @brief Possible audio formats. - enum class Format - { - Mono8, - Mono16, - Stereo8, - Stereo16, - }; - /// @brief Audio device interface used to wrap low-level audio rendering APIs. class CUBOS_CORE_API AudioDevice { @@ -51,19 +43,19 @@ namespace cubos::core::al /// @brief Forbid copy construction. AudioDevice(const AudioDevice&) = delete; - /// @brief Creates an audio device from a given device @p specifier. + /// @brief Creates an audio device. /// @see enumerateDevices() - /// @param specifier Device specifier (empty for default). /// @return Audio device, or nullptr on failure. - static std::shared_ptr create(const std::string& specifier = ""); + static std::shared_ptr create(); /// @brief Enumerates the available devices. /// @param[out] devices Vector to fill with the available devices. static void enumerateDevices(std::vector& devices); /// @brief Creates a new audio buffer + /// @param filePath File path to create buffer from. /// @return Handle of the new buffer. - virtual Buffer createBuffer() = 0; + virtual Buffer createBuffer(const std::string& filePath) = 0; /// @brief Creates a new audio source. /// @return Handle of the new source. @@ -71,16 +63,20 @@ namespace cubos::core::al /// @brief Sets the position of the listener. /// @param position Position. - virtual void setListenerPosition(const glm::vec3& position) = 0; + /// @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. - virtual void setListenerOrientation(const glm::vec3& forward, const glm::vec3& up) = 0; + /// @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. - virtual void setListenerVelocity(const glm::vec3& velocity) = 0; + /// @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. @@ -92,13 +88,6 @@ namespace cubos::core::al public: virtual ~Buffer() = default; - /// @brief Fills the buffer with data. - /// @param format Audio format of the data. - /// @param size Size of the buffer in bytes. - /// @param data Buffer data. - /// @param frequency Audio frequency. - virtual void fill(Format format, std::size_t size, const void* data, std::size_t frequency) = 0; - protected: Buffer() = default; }; @@ -141,26 +130,22 @@ namespace cubos::core::al /// @brief Sets the maximum distance at which the source is audible. /// @param maxDistance Maximum distance. - virtual void setDistance(float maxDistance) = 0; + virtual void setMaxDistance(float maxDistance) = 0; - /// @brief Sets the cone angle of the source, in degrees. By default, 360. - /// @param coneAngle Angle, in degrees. - virtual void setConeAngle(float coneAngle) = 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 gain of the source. - /// @todo Find out what this is. + /// @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 setConeGain(float coneGain) = 0; + 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 Sets the distance under which the volume for the source would normally drop - /// by half. - /// @param referenceDistance Distance. - virtual void setReferenceDistance(float referenceDistance) = 0; - /// @brief Plays the source. virtual void play() = 0; diff --git a/core/src/al/audio_device.cpp b/core/src/al/audio_device.cpp index df78baa603..b77a7fd166 100644 --- a/core/src/al/audio_device.cpp +++ b/core/src/al/audio_device.cpp @@ -1,13 +1,13 @@ -#include "oal_audio_device.hpp" +#include "miniaudio_device.hpp" using namespace cubos::core::al; -std::shared_ptr AudioDevice::create(const std::string& specifier) +std::shared_ptr AudioDevice::create() { - return std::make_shared(specifier); + return std::make_shared(); } void AudioDevice::enumerateDevices(std::vector& devices) { - OALAudioDevice::enumerateDevices(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..936a2347ea --- /dev/null +++ b/core/src/al/miniaudio_device.cpp @@ -0,0 +1,245 @@ +#define MINIAUDIO_IMPLEMENTATION +#include "miniaudio_device.hpp" + +#include +#include + +using namespace cubos::core::al; + +class MiniaudioBuffer : public impl::Buffer +{ +public: + std::string path; + + MiniaudioBuffer(const std::string& filePath) + { + if (ma_decoder_init_file(filePath.c_str(), nullptr, &mDecoder) != MA_SUCCESS) + { + CUBOS_CRITICAL("Failed to load audio file: {}", filePath); + abort(); + } + + path = filePath; + } + + ~MiniaudioBuffer() + { + ma_decoder_uninit(&mDecoder); + } + +private: + ma_decoder mDecoder; +}; + +class MiniaudioSource : public impl::Source +{ +public: + MiniaudioSource() + { + if (ma_engine_init(nullptr, &mEngine) != MA_SUCCESS) + { + CUBOS_CRITICAL("Failed to initialize miniaudio engine."); + abort(); + } + } + + ~MiniaudioSource() override + { + ma_sound_uninit(&mSound); + ma_engine_uninit(&mEngine); + } + + void setBuffer(cubos::core::al::Buffer buffer) override + { + auto miniaudioBuffer = std::dynamic_pointer_cast(buffer); + if (ma_sound_init_from_file(&mEngine, miniaudioBuffer->path.c_str(), MA_SOUND_FLAG_STREAM, nullptr, nullptr, + &mSound) != MA_SUCCESS) + { + CUBOS_CRITICAL("Failed while initating sound from buffer file."); + abort(); + } + } + + void setPosition(const glm::vec3& position) override + { + ma_sound_set_position(&mSound, position.x, position.y, position.z); + } + + void setVelocity(const glm::vec3& velocity) override + { + ma_sound_set_velocity(&mSound, velocity.x, velocity.y, velocity.z); + } + + void setGain(float gain) override + { + ma_sound_set_volume(&mSound, gain); + } + + void setPitch(float pitch) override + { + ma_sound_set_pitch(&mSound, pitch); + } + + void setLooping(bool looping) override + { + ma_sound_set_looping(&mSound, looping); + } + + void setRelative(bool relative) override + { + relative ? ma_sound_set_positioning(&mSound, ma_positioning_relative) + : ma_sound_set_positioning(&mSound, ma_positioning_absolute); + } + + void setMaxDistance(float maxDistance) override + { + ma_sound_set_max_distance(&mSound, maxDistance); + } + + void setMinDistance(float minDistance) override + { + ma_sound_set_min_distance(&mSound, minDistance); + } + + void setCone(float innerAngle, float outerAngle, float outerGain = 1.0F) override + { + ma_sound_set_cone(&mSound, innerAngle, outerAngle, outerGain); + } + + void setConeDirection(const glm::vec3& direction) override + { + ma_sound_set_direction(&mSound, direction.x, direction.y, direction.z); + } + + void play() override + { + if (ma_sound_start(&mSound) != MA_SUCCESS) + { + CUBOS_CRITICAL("Failed to start sound."); + abort(); + } + } + +private: + ma_sound mSound; + ma_engine mEngine; +}; + +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 std::string& filePath) +{ + return std::make_shared(filePath); +} + +Source MiniaudioDevice::createSource() +{ + return std::make_shared(); +} + +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); +} diff --git a/core/src/al/miniaudio_device.hpp b/core/src/al/miniaudio_device.hpp new file mode 100644 index 0000000000..e24f0de6f5 --- /dev/null +++ b/core/src/al/miniaudio_device.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include + +namespace cubos::core::al +{ + /// Audio device implementation using miniaudio. + class MiniaudioDevice : public AudioDevice + { + public: + MiniaudioDevice(); + ~MiniaudioDevice() override; + + /// Enumerates the available devices. + /// @param devices The vector to fill with the available devices. + static void enumerateDevices(std::vector& devices); + static std::string getDefaultDevice(); + + Buffer createBuffer(const std::string& filePath) override; + Source createSource() override; + 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