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

1004 Add Audio support to the engine #1321

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
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
15 changes: 8 additions & 7 deletions core/include/cubos/core/al/audio_context.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,18 +58,19 @@ namespace cubos::core::al
static std::shared_ptr<AudioContext> create();

/// @brief Enumerates the available devices.
/// @param[out] devices Vector to fill with the available device's specifiers.
/// @param[out] devices Vector to fill with the available devices.
virtual void enumerateDevices(std::vector<std::string>& devices) = 0;

/// @brief Creates a new audio device
/// @param listenerCount Number of audio listeners to be supported by the device.
/// @param specifier Identifier of the audio device.
/// @param specifier The specifier of the audio device, used to identify it
/// @param listenerCount The number of audio listener to be created by the device's engine.
/// @return Handle of the new device
diogomsmiranda marked this conversation as resolved.
Show resolved Hide resolved
virtual AudioDevice createDevice(unsigned int listenerCount, const std::string& specifier = "") = 0;
virtual AudioDevice createDevice(unsigned int listenerCount,
const std::string& specifier = "MiniaudioDevice") = 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.
/// @param datSize Size of the data to be written.
/// @return Handle of the new buffer.
virtual Buffer createBuffer(const void* data, size_t dataSize) = 0;
};
Expand All @@ -85,7 +86,7 @@ namespace cubos::core::al

/// @brief Gets the length in seconds of the audio buffer.
/// @return Length in seconds of the audio buffer.
virtual float length() = 0;
virtual size_t length() = 0;

protected:
Buffer() = default;
Expand Down Expand Up @@ -125,7 +126,7 @@ namespace cubos::core::al
/// @brief Sets whether the source position and velocity is relative to the listener or
/// not.
/// @param relative Relative flag.
virtual void setRelative(cubos::core::al::Listener listener) = 0;
virtual void setRelative(bool relative) = 0;

/// @brief Sets the maximum distance at which the source is audible.
/// @param maxDistance Maximum distance.
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 @@ -18,7 +18,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();

static std::string getDefaultDevice();

private:
ma_context mContext;
Expand Down
130 changes: 67 additions & 63 deletions core/src/al/miniaudio_context.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#define MINIAUDIO_IMPLEMENTATION
#include <cubos/core/al/miniaudio_context.hpp>
#include <cubos/core/log.hpp>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ clang-diagnostic-error ⚠️
cubos/core/log.hpp file not found

#include <cubos/core/reflection/external/string.hpp>
#include <cubos/core/tel/logging.hpp>

using namespace cubos::core::al;

Expand All @@ -16,30 +16,28 @@ class MiniaudioBuffer : public impl::Buffer
{
CUBOS_ERROR("Failed to initialize Decoder from data");
}
else
{
mValid = true;
}

mValid = true;
}

~MiniaudioBuffer() override
{
ma_decoder_uninit(&decoder);
}

float length() override
size_t length() override
{
ma_uint64 lengthInPCMFrames;
ma_result result = ma_decoder_get_length_in_pcm_frames(&decoder, &lengthInPCMFrames);

if (result != MA_SUCCESS)
{
CUBOS_ERROR("Failed to get the length of audio in PCM frames");
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<float>(lengthInPCMFrames) / static_cast<float>(decoder.outputSampleRate);
return static_cast<size_t>(lengthInPCMFrames) / decoder.outputSampleRate;
}

bool isValid() const
Expand All @@ -51,43 +49,6 @@ class MiniaudioBuffer : public impl::Buffer
bool mValid = false;
};

class MiniaudioListener : public impl::Listener
{
public:
MiniaudioListener(ma_engine& engine, unsigned int index)
: mEngine(engine)
, mIndex(index)
{
}

~MiniaudioListener() override = default;

void setPosition(const glm::vec3& position) override
{
ma_engine_listener_set_position(&mEngine, mIndex, position.x, position.y, position.z);
}

void setOrientation(const glm::vec3& forward, const glm::vec3& up) override
{
ma_engine_listener_set_direction(&mEngine, mIndex, forward.x, forward.y, forward.z);
ma_engine_listener_set_world_up(&mEngine, mIndex, up.x, up.y, up.z);
}

void setVelocity(const glm::vec3& velocity) override
{
ma_engine_listener_set_velocity(&mEngine, mIndex, velocity.x, velocity.y, velocity.z);
}

unsigned int index() const
{
return mIndex;
}

private:
ma_engine& mEngine;
unsigned int mIndex;
};

class MiniaudioSource : public impl::Source
{
public:
Expand All @@ -104,11 +65,17 @@ class MiniaudioSource : public impl::Source
void setBuffer(Buffer buffer) override
{
// Try to dynamically cast the Buffer to a MiniaudioBuffer.
auto miniaudioBuffer = std::static_pointer_cast<MiniaudioBuffer>(buffer);
auto miniaudioBuffer = std::dynamic_pointer_cast<MiniaudioBuffer>(buffer);

if (miniaudioBuffer == nullptr)
{
CUBOS_FAIL("Buffer is not of type MiniaudioBuffer.");
return;
}

if (ma_sound_init_from_data_source(&mEngine, &miniaudioBuffer->decoder, 0, nullptr, &mSound) != MA_SUCCESS)
{
CUBOS_ERROR("Failed to initialize sound from buffer");
CUBOS_ERROR("Failed to initialize sound from buffer.");
return;
}
}
Expand Down Expand Up @@ -138,16 +105,10 @@ class MiniaudioSource : public impl::Source
ma_sound_set_looping(&mSound, static_cast<ma_bool32>(looping));
}

void setRelative(Listener listener) override
void setRelative(bool relative) override
{
CUBOS_ASSERT(listener != nullptr);

// Try to dynamically cast the Listener to a MiniaudioListener.
auto miniaudioListener = std::static_pointer_cast<MiniaudioListener>(listener);

ma_sound_set_pinned_listener_index(&mSound, miniaudioListener->index());

ma_sound_set_positioning(&mSound, ma_positioning_relative);
relative ? ma_sound_set_positioning(&mSound, ma_positioning_relative)
: ma_sound_set_positioning(&mSound, ma_positioning_absolute);
}

void setMaxDistance(float maxDistance) override
Expand All @@ -174,7 +135,7 @@ class MiniaudioSource : public impl::Source
{
if (ma_sound_start(&mSound) != MA_SUCCESS)
{
CUBOS_ERROR("Failed to start sound");
CUBOS_ERROR("Failed to start sound.");
return;
}
}
Expand All @@ -184,11 +145,46 @@ class MiniaudioSource : public impl::Source
ma_engine& mEngine;
};

class MiniaudioListener : public impl::Listener
{
public:
MiniaudioListener(ma_engine& engine, unsigned int index)
: mEngine(engine)
, mIndex(index)
{
}

~MiniaudioListener() override
{
}
Comment on lines +158 to +159
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ modernize-use-equals-default ⚠️
use = default to define a trivial destructor

Suggested change
{
}
= default;


void setPosition(const glm::vec3& position) override
{
ma_engine_listener_set_position(&mEngine, mIndex, position.x, position.y, position.z);
}

void setOrientation(const glm::vec3& forward, const glm::vec3& up) override
{
ma_engine_listener_set_direction(&mEngine, mIndex, forward.x, forward.y, forward.z);
ma_engine_listener_set_world_up(&mEngine, mIndex, up.x, up.y, up.z);
}

void setVelocity(const glm::vec3& velocity) override
{
ma_engine_listener_set_velocity(&mEngine, mIndex, velocity.x, velocity.y, velocity.z);
}

private:
ma_engine& mEngine;
unsigned int mIndex;
};

class MiniaudioDevice : public impl::AudioDevice
{
public:
MiniaudioDevice(ma_context& context, const std::string& deviceName, ma_uint32 listenerCount)
: mContext(context)
, mListeners()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ readability-redundant-member-init ⚠️
initializer for member mListeners is redundant

Suggested change
, mListeners()
,

{
ma_device_info* pPlaybackDeviceInfos;
ma_uint32 playbackDeviceCount;
Expand Down Expand Up @@ -292,20 +288,28 @@ MiniaudioContext::~MiniaudioContext()

std::string MiniaudioContext::getDefaultDevice()
{
// Create a temporary context
ma_context context;
if (ma_context_init(nullptr, 0, nullptr, &context) != MA_SUCCESS)
{
CUBOS_ERROR("Failed to initialize audio context for default device lookup");
return "";
}

ma_device_info* pPlaybackDeviceInfos;
ma_uint32 playbackDeviceCount;

if (ma_context_get_devices(&mContext, &pPlaybackDeviceInfos, &playbackDeviceCount, nullptr, nullptr) != MA_SUCCESS)
if (ma_context_get_devices(&context, &pPlaybackDeviceInfos, &playbackDeviceCount, nullptr, nullptr) != MA_SUCCESS)
{
CUBOS_ERROR("Failed to enumerate audio devices when searching for default");
return "";
}

ma_context_uninit(&mContext);
ma_context_uninit(&context);

for (ma_uint32 i = 0; i < playbackDeviceCount; i++)
{
if (pPlaybackDeviceInfos[i].isDefault != 0u)
if (pPlaybackDeviceInfos[i].isDefault)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ readability-implicit-bool-conversion ⚠️
implicit conversion ma_bool32 (aka unsigned int) -> bool

Suggested change
if (pPlaybackDeviceInfos[i].isDefault)
if (pPlaybackDeviceInfos[i].isDefault != 0u)

{
return pPlaybackDeviceInfos[i].name;
}
Expand All @@ -324,7 +328,7 @@ void MiniaudioContext::enumerateDevices(std::vector<std::string>& devices)

if (ma_context_get_devices(&mContext, &pPlaybackDeviceInfos, &playbackDeviceCount, nullptr, nullptr) != MA_SUCCESS)
{
CUBOS_ERROR("Failed to enumerate audio devices");
CUBOS_ERROR("Failed to enumerate audio devices.");
return;
}

Expand All @@ -348,7 +352,7 @@ Buffer MiniaudioContext::createBuffer(const void* data, size_t dataSize)
auto buffer = std::make_shared<MiniaudioBuffer>(data, dataSize);
if (!buffer->isValid())
{
CUBOS_ERROR("Failed to create MiniaudioBuffer");
CUBOS_ERROR("Failed to create MiniaudioBuffer.");
return nullptr;
}

Expand All @@ -360,7 +364,7 @@ AudioDevice MiniaudioContext::createDevice(ma_uint32 listenerCount, const std::s
auto device = std::make_shared<MiniaudioDevice>(mContext, specifier, listenerCount);
if (!device->isValid())
{
CUBOS_ERROR("Failed to create MiniaudioDevice");
CUBOS_ERROR("Failed to create MiniaudioDevice.");
return nullptr;
}

Expand Down
6 changes: 6 additions & 0 deletions engine/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ 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/listener.cpp"
"src/audio/audio.cpp"
"src/audio/bridge.cpp"

"src/assets/plugin.cpp"
"src/assets/assets.cpp"
"src/assets/bridge.cpp"
Expand Down
28 changes: 28 additions & 0 deletions engine/include/cubos/engine/audio/audio.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/// @file
/// @brief Class @ref cubos::engine::Audio.
/// @ingroup audio-plugin
#pragma once

#include <cubos/core/al/miniaudio_context.hpp>
#include <cubos/core/memory/stream.hpp>
#include <cubos/core/reflection/reflect.hpp>

#include <cubos/engine/assets/asset.hpp>

namespace cubos::engine
{
/// @brief Asset containing raw Audio data.
///
/// @ingroup audio-plugin
struct CUBOS_ENGINE_API Audio
{
CUBOS_REFLECT;

/// @brief Audio buffer.
cubos::core::al::Buffer buffer;

explicit Audio(std::shared_ptr<cubos::core::al::AudioContext>, core::memory::Stream& stream);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ readability-named-parameter ⚠️
all parameters should be named in a function

Suggested change
explicit Audio(std::shared_ptr<cubos::core::al::AudioContext>, core::memory::Stream& stream);
explicit Audio(std::shared_ptr<cubos::core::al::AudioContext> /*context*/, core::memory::Stream& stream);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ performance-unnecessary-value-param ⚠️
the parameter context is copied for each invocation but only used as a const reference; consider making it a const reference

Suggested change
explicit Audio(std::shared_ptr<cubos::core::al::AudioContext>, core::memory::Stream& stream);
explicit Audio(const std::shared_ptr<cubos::core::al::AudioContext>&, core::memory::Stream& stream);

Audio(Audio&& other) noexcept;
~Audio();
};
} // namespace cubos::engine
33 changes: 33 additions & 0 deletions engine/include/cubos/engine/audio/bridge.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/// @file
/// @brief Class @ref cubos::engine::AudioBridge.
/// @ingroup audio-plugin

#pragma once

#include <cubos/engine/assets/bridges/file.hpp>
#include <cubos/engine/audio/audio.hpp>

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ modernize-pass-by-value ⚠️
pass by value and use std::move

Suggested change
#include <utility>

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ performance-unnecessary-value-param ⚠️
parameter context is passed by value and only copied once; consider moving it to avoid unnecessary copies

Suggested change
#include <utility>

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:
std::shared_ptr<cubos::core::al::AudioContext> mContext;

/// @brief Constructs a bridge.
AudioBridge(std::shared_ptr<cubos::core::al::AudioContext> context)
: FileBridge(core::reflection::reflect<Audio>())
, mContext(context)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ modernize-pass-by-value ⚠️
pass by value and use std::move

Suggested change
, mContext(context)
, mContext(std::move(context))

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ performance-unnecessary-value-param ⚠️
parameter context is passed by value and only copied once; consider moving it to avoid unnecessary copies

Suggested change
, mContext(context)
, mContext(std::move(context))

{
}

protected:
bool loadFromFile(Assets& assets, const AnyAsset& handle, core::memory::Stream& stream) override;
bool saveToFile(const Assets& assets, const AnyAsset& handle, core::memory::Stream& stream) override;
};
} // namespace cubos::engine
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 <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;

bool active = false; //< Is the listener active?

private:
cubos::core::al::Listener listener;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ readability-identifier-naming ⚠️
invalid case style for private member listener

Suggested change
cubos::core::al::Listener listener;
cubos::core::al::Listener mListener;

};
} // namespace cubos::engine
Loading
Loading