Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(audio): add audioPlugin and audio sample #1383

Merged
merged 3 commits into from
Nov 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Point light shadows (#1188, **@tomas7770**).
- Added method to save Settings to file (#1349, **@SrGesus**).
- Added resource to easily configure magic numbers in solver (#1281, **@GCeSilva**).
- Audio Plugin (#1004, **@Dageus**, **@diogomsmiranda**).

### Changed

Expand Down Expand Up @@ -52,7 +53,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**).
Expand Down
21 changes: 18 additions & 3 deletions core/include/cubos/core/al/audio_context.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,16 @@ namespace cubos::core::al
{
public:
AudioContext() = default;
virtual ~AudioContext() = default;
virtual ~AudioContext();

/// @brief Creates an audio context.
/// @return AudioContext, or nullptr on failure.
static std::shared_ptr<AudioContext> create();

/// @brief Gets the maximum number of listeners supported by the audio context.
/// @return Maximum number of listeners.
virtual int getMaxListenerCount() const = 0;

/// @brief Enumerates the available devices.
/// @param[out] devices Vector to fill with the available device's specifiers.
virtual void enumerateDevices(std::vector<std::string>& devices) = 0;
Expand All @@ -72,6 +76,10 @@ namespace cubos::core::al
/// @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.
Expand Down Expand Up @@ -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;
};
Expand Down Expand Up @@ -188,8 +202,9 @@ namespace cubos::core::al
/// @return Handle of the new source.
virtual std::shared_ptr<impl::Source> 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<impl::Listener> listener(size_t index) = 0;

protected:
Expand Down
3 changes: 2 additions & 1 deletion core/include/cubos/core/al/miniaudio_context.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ namespace cubos::core::al
AudioDevice createDevice(unsigned int listenerCount, const std::string& specifier) override;
Buffer createBuffer(const void* data, size_t dataSize) override;
void enumerateDevices(std::vector<std::string>& devices) override;
std::string getDefaultDevice();
int getMaxListenerCount() const override;
std::string getDefaultDevice() override;

private:
#ifdef CUBOS_CORE_MINIAUDIO
Expand Down
2 changes: 2 additions & 0 deletions core/src/al/audio_context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,5 @@ std::shared_ptr<AudioContext> AudioContext::create()
return nullptr;
#endif
}

cubos::core::al::AudioContext::~AudioContext() = default;
88 changes: 63 additions & 25 deletions core/src/al/miniaudio_context.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
#define MINIAUDIO_IMPLEMENTATION
#include <cstring>

#include <cubos/core/al/miniaudio_context.hpp>
#include <cubos/core/reflection/external/string.hpp>
#include <cubos/core/tel/logging.hpp>
Expand All @@ -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");
}
Expand All @@ -26,6 +31,7 @@ class MiniaudioBuffer : public impl::Buffer
~MiniaudioBuffer() override
{
ma_decoder_uninit(&decoder);
operator delete(mData);
}

float length() override
Expand All @@ -50,6 +56,7 @@ class MiniaudioBuffer : public impl::Buffer

private:
bool mValid = false;
void* mData;
};

class MiniaudioListener : public impl::Listener
Expand Down Expand Up @@ -93,7 +100,8 @@ class MiniaudioSource : public impl::Source
{
public:
MiniaudioSource(ma_engine& engine)
: mEngine(engine)
: mSound({})
, mEngine(engine)
{
}

Expand All @@ -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<MiniaudioBuffer>(buffer);

Expand Down Expand Up @@ -180,6 +190,25 @@ class MiniaudioSource : public impl::Source
}
}

void stop() override
{
if (ma_sound_stop(&mSound) != MA_SUCCESS)
{
CUBOS_ERROR("Failed to stop sound");
return;
}
ma_sound_seek_to_pcm_frame(&mSound, 0);
}

void pause() override
{
if (ma_sound_stop(&mSound) != MA_SUCCESS)
{
CUBOS_ERROR("Failed to pause sound");
return;
}
}

private:
ma_sound mSound;
ma_engine& mEngine;
Expand All @@ -191,6 +220,12 @@ class MiniaudioDevice : public impl::AudioDevice
MiniaudioDevice(ma_context& context, const std::string& deviceName, ma_uint32 listenerCount)
: mContext(context)
{
if (listenerCount > MA_ENGINE_MAX_LISTENERS)
{
CUBOS_FAIL("Maximum number of listeners is 4");
return;
}

ma_device_info* pPlaybackDeviceInfos;
ma_uint32 playbackDeviceCount;
ma_result result =
Expand All @@ -203,31 +238,29 @@ class MiniaudioDevice : public impl::AudioDevice
}

ma_device_id* deviceId = nullptr;
for (ma_uint32 i = 0; i < playbackDeviceCount; i++)

if (!deviceName.empty()) // in case a device name is provided
{
if (deviceName == pPlaybackDeviceInfos[i].name)
for (ma_uint32 i = 0; i < playbackDeviceCount; i++)
{
deviceId = &pPlaybackDeviceInfos[i].id;
break;
if (deviceName == pPlaybackDeviceInfos[i].name)
{
deviceId = &pPlaybackDeviceInfos[i].id;
break;
}
}
}

if (deviceId == nullptr)
{
CUBOS_FAIL("Audio device '{}' not found", deviceName);
return;
if (deviceId == nullptr)
{
CUBOS_FAIL("Audio device '{}' not found", deviceName);
return;
}
}

ma_engine_config engineConfig = ma_engine_config_init();

if (listenerCount > MA_ENGINE_MAX_LISTENERS)
{
CUBOS_FAIL("Maximum number of listeners is 4");
return;
}

engineConfig.listenerCount = listenerCount;
engineConfig.pPlaybackDeviceID = deviceId; // Use the found device ID
engineConfig.pPlaybackDeviceID = deviceId;

if (ma_engine_init(&engineConfig, &mEngine) != MA_SUCCESS)
{
Expand All @@ -246,6 +279,7 @@ class MiniaudioDevice : public impl::AudioDevice

~MiniaudioDevice() override
{
ma_engine_uninit(&mEngine);
ma_device_uninit(&mDevice);
}

Expand Down Expand Up @@ -296,6 +330,15 @@ MiniaudioContext::~MiniaudioContext()
#endif
}

int MiniaudioContext::getMaxListenerCount() const
{
#ifdef CUBOS_CORE_MINIAUDIO
return MA_ENGINE_MAX_LISTENERS;
#else
return 0;
#endif
}

std::string MiniaudioContext::getDefaultDevice()
{
#ifdef CUBOS_CORE_MINIAUDIO
Expand All @@ -308,11 +351,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;
}
Expand All @@ -339,8 +380,6 @@ void MiniaudioContext::enumerateDevices(std::vector<std::string>& devices)
return;
}

ma_context_uninit(&mContext);

devices.reserve(playbackDeviceCount);

for (ma_uint32 i = 0; i < playbackDeviceCount; i++)
Expand Down Expand Up @@ -388,7 +427,6 @@ AudioDevice MiniaudioContext::createDevice(unsigned int listenerCount, const std
return device;
#else
(void)listenerCount;
(void)specifier;
return nullptr;
#endif
}
9 changes: 9 additions & 0 deletions engine/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
25 changes: 25 additions & 0 deletions engine/include/cubos/engine/audio/listener.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/// @file
/// @brief Component @ref cubos::engine::AudioListener.
/// @ingroup audio-plugin

#pragma once

#include <map>

#include <cubos/core/al/audio_context.hpp>
#include <cubos/core/reflection/reflect.hpp>

#include <cubos/engine/api.hpp>

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
22 changes: 22 additions & 0 deletions engine/include/cubos/engine/audio/pause.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/// @file
/// @brief Component @ref cubos::engine::AudioPause.
/// @ingroup audio-plugin

#pragma once

#include <glm/glm.hpp>

#include <cubos/core/reflection/reflect.hpp>

#include <cubos/engine/api.hpp>
#include <cubos/engine/prelude.hpp>

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
22 changes: 22 additions & 0 deletions engine/include/cubos/engine/audio/play.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/// @file
/// @brief Component @ref cubos::engine::AudioPlay.
/// @ingroup audio-plugin

#pragma once

#include <glm/glm.hpp>

#include <cubos/core/reflection/reflect.hpp>

#include <cubos/engine/api.hpp>
#include <cubos/engine/prelude.hpp>

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
Loading
Loading