From fb3d9c43fb4c6ec0b93162f1fc44dd39e34093aa Mon Sep 17 00:00:00 2001 From: Evan Caplinger Date: Wed, 19 Nov 2025 00:35:35 -0800 Subject: [PATCH 1/2] Initial commit of new architecture - problems expected, proceed with caution --- .gitignore | 4 + CMakeLists.txt | 4 + audio-cpp/context.hpp | 40 ++++++ audio-cpp/device.hpp | 44 +++++++ audio-cpp/drivers/audio_context.hpp | 11 -- audio-cpp/drivers/audio_device.hpp | 14 --- audio-cpp/engine.hpp | 68 ++++++++++ audio-cpp/listener.hpp | 17 +++ audio-cpp/resource_manager.hpp | 26 ++++ audio-cpp/sound.hpp | 101 ++++++++++++--- audio-cpp/source.hpp | 86 +++++++++++++ audio-cpp/types.hpp | 28 +++++ conanfile.py | 7 +- src/audio-cpp/context.cpp | 54 ++++++++ src/audio-cpp/device.cpp | 9 ++ src/audio-cpp/engine.cpp | 101 +++++++++++++++ src/audio-cpp/resource_manager.cpp | 30 +++++ src/audio-cpp/sound.cpp | 184 +++++++++++++++++++++------- src/audio-cpp/source.cpp | 55 +++++++++ 19 files changed, 797 insertions(+), 86 deletions(-) create mode 100644 audio-cpp/context.hpp create mode 100644 audio-cpp/device.hpp delete mode 100644 audio-cpp/drivers/audio_context.hpp delete mode 100644 audio-cpp/drivers/audio_device.hpp create mode 100644 audio-cpp/engine.hpp create mode 100644 audio-cpp/listener.hpp create mode 100644 audio-cpp/resource_manager.hpp create mode 100644 audio-cpp/source.hpp create mode 100644 audio-cpp/types.hpp create mode 100644 src/audio-cpp/context.cpp create mode 100644 src/audio-cpp/device.cpp create mode 100644 src/audio-cpp/engine.cpp create mode 100644 src/audio-cpp/resource_manager.cpp create mode 100644 src/audio-cpp/source.cpp diff --git a/.gitignore b/.gitignore index 0edcf46..418c29d 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,7 @@ compile_commands.json CMakeUserPresets.json .vscode/ .DS_Store + +# Random user stuff +garbage/ +notes/ diff --git a/CMakeLists.txt b/CMakeLists.txt index f80c8b8..0fc9248 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,9 +12,13 @@ build_library( PACKAGES miniaudio + flecs + atlas LINK_PACKAGES miniaudio::miniaudio + flecs::flecs_static + atlas::atlas ) generate_compile_commands() diff --git a/audio-cpp/context.hpp b/audio-cpp/context.hpp new file mode 100644 index 0000000..35dea2c --- /dev/null +++ b/audio-cpp/context.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include +#include +#include + +namespace audio { + +struct context_config { + +}; + +/** +* @brief Low-level audio context for setting up miniaudio +*/ +class context { + +public: + context(); + + std::span enumerate_devices(); + + void create_device(device* pDevice, const device_info& info); + + std::unique_ptr create_device(const std::string& deviceName, + const device_info& info); + + ma_context* get_wrapped_object() { return &m_context; } + +private: + + ma_context m_context; + ma_context_config m_config; + + std::map m_device_list; + + void populate_device_list(); +}; + +}; // namespace audio diff --git a/audio-cpp/device.hpp b/audio-cpp/device.hpp new file mode 100644 index 0000000..3777c05 --- /dev/null +++ b/audio-cpp/device.hpp @@ -0,0 +1,44 @@ +#pragma once + +#include +#include + +#include + +namespace audio { + +using device_id = ma_device_id; + +enum device_type { + playback = ma_device_type_playback, + capture = ma_device_type_capture, + duplex = ma_device_type_duplex, + loopback = ma_device_type_loopback +}; + +struct device_info { + std::string name; + device_type type; + device_id id; +}; + +struct device_config { + device_info info; + uint32_t sampleRate; + uint32_t periodSizeInFrames; +}; + +class device { + +public: + device(); + device(const device_config& config); + + ma_device* get_wrapped_object() { return &m_device; } + +private: + ma_device_config m_config; + ma_device m_device; +}; + +}; diff --git a/audio-cpp/drivers/audio_context.hpp b/audio-cpp/drivers/audio_context.hpp deleted file mode 100644 index 8a1b606..0000000 --- a/audio-cpp/drivers/audio_context.hpp +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once - -namespace audio { - /** - * @brief Low-level audio context for setting up miniaudio - */ - class audio_context { - public: - audio_context() = default; - }; -}; \ No newline at end of file diff --git a/audio-cpp/drivers/audio_device.hpp b/audio-cpp/drivers/audio_device.hpp deleted file mode 100644 index e9f4fae..0000000 --- a/audio-cpp/drivers/audio_device.hpp +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once -#include - -namespace audio { - class device_enumeration { - public: - device_enumeration() { - ma_context test_context; - if (ma_context_init(nullptr, 0, nullptr, &test_context) != - MA_SUCCESS) { - } - } - }; -}; \ No newline at end of file diff --git a/audio-cpp/engine.hpp b/audio-cpp/engine.hpp new file mode 100644 index 0000000..cd77443 --- /dev/null +++ b/audio-cpp/engine.hpp @@ -0,0 +1,68 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include + +namespace audio { + +using listener_index = uint32_t; + +struct engine_config { + device_config device; + + // spatialization params + unsigned int spatial_interpolation_ms; +}; + +// forward declaration to avoid dependency cycle +class source; + +class engine { + +public: + engine(const engine_config& p_config, + flecs::world& p_registry, + context* p_context); + + void init(); + + void update(); + + void uninit(); + + void set_active_listener(listener* p_listener); + + ma_engine* get_instance() { return &m_engine; } + +private: + + void set_listener_transform(listener_index p_index, + const atlas::transform& p_transform); + + void set_listener_velocity(listener_index p_index, + glm::vec3 p_linear_velocity, + glm::vec3 p_angular_velocity); + + flecs::world* m_registry; + + flecs::query m_query_source; + flecs::query m_query_source_transform; + flecs::query m_query_source_velocity; + flecs::query m_query_listener_transform; + flecs::query m_query_listener_velocity; + + ma_engine m_engine; + ma_engine_config m_config; + + device* m_device = nullptr; + context* m_context = nullptr; + + listener* m_active_listener = nullptr; +}; + +}; diff --git a/audio-cpp/listener.hpp b/audio-cpp/listener.hpp new file mode 100644 index 0000000..279dce2 --- /dev/null +++ b/audio-cpp/listener.hpp @@ -0,0 +1,17 @@ +#include +#include + +namespace audio { + +class listener { +public: + listener() = default; + + void set_ma_index(ma_uint32 p_index) { m_ma_index = p_index; } + ma_uint32 get_ma_index() { return m_ma_index; } + +private: + ma_uint32 m_ma_index; +}; + +}; diff --git a/audio-cpp/resource_manager.hpp b/audio-cpp/resource_manager.hpp new file mode 100644 index 0000000..83705a9 --- /dev/null +++ b/audio-cpp/resource_manager.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include +#include + +#include + +namespace audio { + +// not interested in manually configuring this yet, but likely will be in the future... +struct resource_manager_config { + +}; + +class resource_manager { + +public: + resource_manager(); + +private: + ma_resource_manager m_rm; + ma_resource_manager_config m_rm_config; + +}; + +}; diff --git a/audio-cpp/sound.hpp b/audio-cpp/sound.hpp index 2fdaab2..3529415 100644 --- a/audio-cpp/sound.hpp +++ b/audio-cpp/sound.hpp @@ -1,26 +1,91 @@ #pragma once #include +#include #include +#include +#include namespace audio { + +using source_index = uint32_t; + +enum sound_type { + from_file +}; + +struct sound_properties { + sound_type type = sound_type::from_file; + std::string filename; + // NOTE: supporting data callbacks will require more work to fully abstract + // void* data_callback(void) = nullptr; + bool persistent = false; + bool consider_position = false; + bool consider_rotation = false; + bool consider_velocity = false; + bool decode_on_init = false; + float doppler_factor = 0.f; + int polyphonic_voices = 1; +}; + +/** + * @note Sound abstraction around the miniaudio API + * + */ +class sound { +public: + sound(const sound_properties& p_properties); + ~sound(); + /** - * @note Sound abstraction around the miniaudio API - * + * @brief Initialize the sound with the given engine. + * @param p_engine: A pointer to the engine. */ - class sound { - public: - sound(const std::string& p_filename); - ~sound(); - - void on_play(); - void on_stop(); - - private: - void cleanup(); - - private: - ma_decoder m_decoder; - ma_device m_audio_device_handler; - ma_device_config m_audio_device_config; - }; + void init(engine* p_engine); + + /** + * @brief Should be called once per tick. The sound should carry out any + * pending instructions, including playing itself if necessary, stopping, + * uninitializing itself if it is not persistent, etc. + * @note If this is called before set_transform or set_velocity, the + * changes will not be applied until after the sound has already been + * updated. + * @param p_engine: A pointer to the engine. + */ + void update(engine* p_engine); + + /** + * @brief Uninitialize the sound. + */ + void uninit(); + + void set_transform(const atlas::transform& p_transform); + + /** + * @brief Set this sound to play at the next tick. + */ + void play() { m_should_play = false; } + + /** + * @brief Set this sound to stop playing at the next tick. + */ + void stop() { m_should_play = true; } + +private: + void apply_config(); + + void cleanup(); + + bool m_is_playing = false; + bool m_should_play = false; + bool m_should_stop = false; + bool m_config_dirty = false; + bool m_initialized = false; + + const sound_properties m_properties; + + atlas::transform m_transform; + + ma_sound m_sound; }; + +}; // namespace audio diff --git a/audio-cpp/source.hpp b/audio-cpp/source.hpp new file mode 100644 index 0000000..0bf2795 --- /dev/null +++ b/audio-cpp/source.hpp @@ -0,0 +1,86 @@ +#pragma once +#include +#include +#include +#include +#include + +namespace audio { + +using source_index = uint32_t; + +struct source_settings { + bool spatial = false; +}; + +/** + * @note Sound abstraction around the miniaudio API + * + */ +class source { +public: + source(const source_settings& p_settings); + + /** + * @brief Add a new sound to the spatialized source. + * @param p_sound: The new sound to be added. + * @returns The index of the sound, which can be later referenced to + * interact with the newly added sound. + */ + source_index add(sound p_sound); + + /** + * @brief Play a specific sound that has been registered with the + * spatialized source. + * @param p_index: The index to the sound to be played, which has been + * previously returned from the "add" function call. + */ + void play(source_index p_index); + + /** + * @brief Stop a specific sound that has been registered with the + * spatialized source. + * @param p_index: The index to the sound to be played, which has been + * previously returned from the "add" function call. + */ + void stop(source_index p_index); + + /** + * @brief Get a pointer to the sound at a specific index. + * @param p_index: The index to the sound, which has been previously + * returned from the "add" function call. + * @returns A pointer to the sound at the specified index. + */ + sound* get_sound(source_index p_index) { return &m_sounds.at(p_index); } + + /** + * @brief Stop all currently playing sounds. + */ + void stop_all(); + + /** + * @brief Initialize the spatialized source. + */ + void init(engine* p_engine); + + + void update(engine* p_engine); + + + void uninit(engine* p_engine); + + // NOTE: any object with a spatialized_source should also have a transform; + // otherwise, it will behave as a regular source + void set_transform(atlas::transform p_transform); + + void set_velocity(glm::vec3 p_linear_velocity, + glm::vec3 p_angular_velocity); + +private: + void cleanup(); + + std::vector m_sounds; + +}; + +}; // namespace audio diff --git a/audio-cpp/types.hpp b/audio-cpp/types.hpp new file mode 100644 index 0000000..5002940 --- /dev/null +++ b/audio-cpp/types.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include +#include +#include +#include + +namespace audio { + +class audio_exception : public std::exception { +public: + audio_exception(ma_result result) + : std::exception() + { + std::stringstream err_msg; + err_msg << "miniaudio error code " << static_cast(result); + m_err_msg = err_msg.str(); + } + + const char* what() const noexcept override { + return m_err_msg.c_str(); + } + +private: + std::string m_err_msg; +}; + +}; // namespace audio diff --git a/conanfile.py b/conanfile.py index 82fd141..e3ad1c7 100644 --- a/conanfile.py +++ b/conanfile.py @@ -9,7 +9,7 @@ class AudioRecipe(ConanFile): name = "audio-cpp" - version = "1.0" + version = "1.1" package_type = "library" license = "Apache-2.0" homepage = "https://github.com/engine3d-dev/engine3d-audio" @@ -24,10 +24,13 @@ def build_requirements(self): self.tool_requires("make/4.4.1") self.tool_requires("cmake/3.27.1") self.tool_requires("engine3d-cmake-utils/4.0") - self.requires("boost-ext-ut/2.1.0") + self.requires("boost-ext-ut/2.3.1") def requirements(self): self.requires("miniaudio/1.0") + self.requires("atlas/0.2") + self.requires("flecs/4.0.4") + self.requires("glm/1.0.1") # This is how exporting the sources work def export_sources(self): diff --git a/src/audio-cpp/context.cpp b/src/audio-cpp/context.cpp new file mode 100644 index 0000000..b836b10 --- /dev/null +++ b/src/audio-cpp/context.cpp @@ -0,0 +1,54 @@ +#include +#include +#include + +namespace audio { + +context::context() { + m_config = ma_context_config_init(); + + ma_context_init(NULL, 0, &m_config, &m_context); +} + +std::span context::enumerate_devices() { + // safe to assume we should repopulate device list + populate_device_list(); + + // build list of device info + std::vector r; + for (std::map::iterator it = m_device_list.begin(); + it != m_device_list.end(); it++) { + r.push_back({ .name = it->first }); + } + + return r; +} + +void context::populate_device_list() { + // initialize stuff for miniaudio call + ma_uint32 device_count; + ma_device_info* playback_devices; + ma_result result; + + result = ma_context_get_devices(&m_context, &playback_devices, &device_count, NULL, NULL); + if (result != MA_SUCCESS) { + throw std::runtime_error("error getting devices"); + } + + // destroy and rebuild device list + m_device_list.clear(); + for (int i = 0; i < device_count; i++) { + ma_device_info device = playback_devices[i]; + m_device_list.insert(std::make_pair(std::string(device.name), device.id)); + } +} + +std::unique_ptr context::create_device(const std::string& deviceName, + const device_config& config) +{ + device_info newConfig = std::copy(config); + newConfig.id = m_device_list[deviceName]; + return std::make_unique(newConfig); +} + +}; // namespace audio diff --git a/src/audio-cpp/device.cpp b/src/audio-cpp/device.cpp new file mode 100644 index 0000000..45f01f6 --- /dev/null +++ b/src/audio-cpp/device.cpp @@ -0,0 +1,9 @@ +#include + +namespace audio { + +device::device(const device_config& config) { + m_config = ma_device_config_init(config.type); +} + +}; diff --git a/src/audio-cpp/engine.cpp b/src/audio-cpp/engine.cpp new file mode 100644 index 0000000..092a645 --- /dev/null +++ b/src/audio-cpp/engine.cpp @@ -0,0 +1,101 @@ +#include +#include +#include +#include +#include +#include + +namespace audio { + +engine(const engine_config& p_config, + flecs::world& p_registry, + context* p_context) + : m_context(p_context) + , m_registry(&p_registry) { + m_device = device(config.device); + + m_engine_config = ma_engine_config_init(); + m_engine_config.pContext = pContext->get_wrapped_object(); + m_engine_config.gainSmoothTimeInMilliseconds = p_config.spatial_interpolation_ms; + + ma_result result; + + result = ma_engine_init(&m_engine_config, &m_engine); + if (result != MA_SUCCESS) { + throw new audio_exception(result); + } + + m_query_source = m_registry->query_builder().build(); + m_query_source_transform = + m_registry->query_builder().build(); + m_query_source_velocity = + m_registry->query_builder().build(); +} + +void engine::init() { + m_query_source.each([this] (flecs::entity p_entity, + source& p_source) { + p_source.init(this); + } +} + +void engine::uninit() { + m_query_source.each([this] (flecs::entity p_entity, + source& p_source) { + p_source.uninit(); + } +} + +void engine::update() { + m_query_std_source.each([this] (flecs::entity p_entity, + source& p_source) { + p_source.update(this); + }); + + m_query_source_transform.each([this] (flecs::entity p_entity, + source& p_source, + atlas::transform& p_transform) { + p_source.set_transform(p_transform); + }); + + m_query_source_velocity.each([this] (flecs::entity p_entity, + source& p_source, + atlas::physics_body& p_body) { + p_source.set_velocity(p_body.linear_velocity, p_body.angular_velocity); + }); + + m_query_listener_transform.each([this] (flecs::entity p_entity, + listener& p_listener, + atlas::transform& p_transform) { + if (&p_listener == m_active_listener) { + // TODO: check whether we should be setting this stuff + // (e.g., in config) + glm::vec3 position = p_transform.position; + ma_engine_listener_set_position(&m_engine, + 0, // TODO: add support for internal MA listener indices + position.x, + position.y, + position.z); + + glm::quat quaternion = atlas::to_quat(m_transform.quaternion); + glm::vec3 forward = glm::rotate(quaternion, + glm::vec3(0.0f, 0.0f, -1.0f)); + ma_engine_listener_set_direction(&m_sound, + 0, // TODO: add support for internal MA listener indices + forward.x, + forward.y, + forward.z); + } + }); + + m_query_listener_velocty.each([this] (flecs::entity p_entity, + listener& p_listener, + atlas::physics_body& p_body){ + + if (&p_listener == m_active_listener) { + + } + }); +} + +}; // namespace audio diff --git a/src/audio-cpp/resource_manager.cpp b/src/audio-cpp/resource_manager.cpp new file mode 100644 index 0000000..24e232b --- /dev/null +++ b/src/audio-cpp/resource_manager.cpp @@ -0,0 +1,30 @@ +#include +#include + +#include +#include + +#include + +namespace audio { + +resource_manager::resource_manager() { + m_rm_config = ma_resource_manager_config_init(); + + ma_result r = ma_resource_manager_init(&m_rm_config, &m_rm); + if (r != MA_SUCCESS) { + throw new audio_exception(r); + } +} + +resource_manager::resource_manager(resource_manager_config& config) { + m_rm_config = ma_resource_manager_config_init(); + // set values according to config passed in + + ma_result r = ma_resource_manager_init(&m_rm_config, &m_rm); + if (r != MA_SUCCESS) { + // throw exception + } +} + +}; diff --git a/src/audio-cpp/sound.cpp b/src/audio-cpp/sound.cpp index ceb9371..23d0619 100644 --- a/src/audio-cpp/sound.cpp +++ b/src/audio-cpp/sound.cpp @@ -1,62 +1,164 @@ -// #include "sound.hpp" #include +#include +#include +#include namespace audio { - - void data_callback(ma_device* p_device, - /*NOLINT*/void* p_output, - /*NOLINT*/ const void* p_input, - ma_uint32 p_frame_count) { - ma_decoder* decoder = (ma_decoder*)p_device->pUserData; - if (decoder == nullptr) { - return; - } - ma_decoder_read_pcm_frames(decoder, p_output, p_frame_count, NULL); +sound::sound(const sound_properties& p_properties) + : m_properties(p_properties) +{ + +} - (void)p_input; +void sound::init(engine* p_engine) { + if (m_initialized) { + return; } - sound::sound(const std::string& p_filename) { - ma_decoder_init_file(p_filename.c_str(), nullptr, &m_decoder); + // get raw ma_engine instance + ma_engine* engine_inst = p_engine->get_instance(); - m_audio_device_config = ma_device_config_init(ma_device_type_playback); + ma_result result; + switch (m_properties.type) { + case sound_type::from_file: + int cfg_flags = 0; + if (m_properties.decode_on_init) { + cfg_flags |= MA_SOUND_FLAG_DECODE; + } - m_audio_device_config.playback.format = m_decoder.outputFormat; - m_audio_device_config.playback.channels = m_decoder.outputChannels; - m_audio_device_config.sampleRate = m_decoder.outputSampleRate; - m_audio_device_config.dataCallback = data_callback; - m_audio_device_config.pUserData = &m_decoder; + result = ma_sound_init_from_file(engine_inst, + m_properties.filename.c_str(), + cfg_flags, + NULL, + NULL, + &m_sound); + break; - auto res = ma_device_init( - nullptr, &m_audio_device_config, &m_audio_device_handler); - if (res != MA_SUCCESS) { - cleanup(); - return; - } + //case sound_type::from_callback: + // // TODO: program this + // break; } - sound::~sound() { - cleanup(); + if (result != MA_SUCCESS) { + throw new audio_exception(result); } - void sound::on_play() { - auto res = ma_device_start(&m_audio_device_handler); - if (res != MA_SUCCESS) { - cleanup(); - } + m_initialized = true; +} + +void sound::apply_config() { + if (!m_initialized) { + return; + } + + ma_sound_set_doppler_factor(&m_sound, m_properties.doppler_factor); +} + +void sound::update(engine* p_engine) { + // reload config if necessary + if (m_config_dirty) { + apply_config(); + } + + bool was_playing = m_is_playing; + m_is_playing = ma_sound_is_playing(&m_sound); + + if (m_is_playing && m_should_stop) { + ma_sound_stop(&m_sound); + m_is_playing = false; } - void sound::on_stop() { - auto res = ma_device_stop(&m_audio_device_handler); + // free resources if sound is not persistent and has ended + if (!m_properties.persistent && was_playing && !m_is_playing) { + uninit(); + } + + // the interpolation of this value cannot be controlled on a sound-by-sound + // basis yet, but may be if each sound is eventually given its own spatial- + // izer. for now, it will be interpolated according to engine_config's var + // spatial_interpolation_ms. + if (m_properties.consider_position) { + glm::highp_vec3 position = m_transform.position; + ma_sound_set_position(&m_sound, position.x, position.y, position.z); + } - if (res != MA_SUCCESS) { - cleanup(); + if (m_properties.consider_rotation) { + glm::quat quaternion = atlas::to_quat(m_transform.quaternion); + glm::vec3 forward = quaternion * glm::vec3(0.0f, 0.0f, -1.0f); + ma_sound_set_direction(&m_sound, forward.x, forward.y, forward.z); + } + + if (m_properties.consider_velocity) { + // TODO: give velocity to miniaudio + } + + // play sound if we should play + if (m_should_play) { + if (!m_initialized) { + init(p_engine); + } + + // TODO: add playhead positioning + // replace with ma_sound_seek_to_second, which cannot be included for + // whatever reason... + ma_sound_seek_to_pcm_frame(&m_sound, 0); + if (!m_is_playing) { + ma_sound_start(&m_sound); + m_is_playing = true; } + + m_should_play = false; } +} - void sound::cleanup() { - ma_device_uninit(&m_audio_device_handler); - ma_decoder_uninit(&m_decoder); +void sound::uninit() { + if (m_initialized) { + ma_sound_uninit(&m_sound); + m_initialized = false; } -}; \ No newline at end of file +} + +// sound::sound(const std::string& p_filename) { +// ma_decoder_init_file(p_filename.c_str(), nullptr, &m_decoder); +// +// m_audio_device_config = ma_device_config_init(ma_device_type_playback); +// +// m_audio_device_config.playback.format = m_decoder.outputFormat; +// m_audio_device_config.playback.channels = m_decoder.outputChannels; +// m_audio_device_config.sampleRate = m_decoder.outputSampleRate; +// m_audio_device_config.dataCallback = data_callback; +// m_audio_device_config.pUserData = &m_decoder; +// +// auto res = ma_device_init( +// nullptr, &m_audio_device_config, &m_audio_device_handler); +// if (res != MA_SUCCESS) { +// cleanup(); +// return; +// } +// } +// +// sound::~sound() { +// cleanup(); +// } +// +// void sound::on_play() { +// auto res = ma_device_start(&m_audio_device_handler); +// if (res != MA_SUCCESS) { +// cleanup(); +// } +// } +// +// void sound::on_stop() { +// auto res = ma_device_stop(&m_audio_device_handler); +// +// if (res != MA_SUCCESS) { +// cleanup(); +// } +// } +// +// void sound::cleanup() { +// ma_device_uninit(&m_audio_device_handler); +// ma_decoder_uninit(&m_decoder); +// } +}; diff --git a/src/audio-cpp/source.cpp b/src/audio-cpp/source.cpp new file mode 100644 index 0000000..b98b70a --- /dev/null +++ b/src/audio-cpp/source.cpp @@ -0,0 +1,55 @@ +#include + +namespace audio { + +source_index source::add(sound p_sound) { + m_sounds.push_back(p_sound); + return (source_index) m_sounds.size() - 1; +} + +void source::play(source_index p_index) { + m_sounds.at(p_index).play(); +} + +void source::stop(source_index p_index) { + m_sounds.at(p_index).stop(); +} + +void source::stop_all() { + for (sound& curr_sound : m_sounds) { + curr_sound.stop(); + } +} + +void source::init(engine* p_engine) { + for (sound& curr_sound : m_sounds) { + curr_sound.init(p_engine); + } +} + +void source::update(engine* p_engine) { + for (sound& curr_sound : m_sounds) { + curr_sound.update(p_engine); + } +} + +void source::uninit() { + for (sound& curr_sound : m_sounds) { + curr_sound.uninit(); + } +} + +void set_transform(atlas::transform p_transform) { + for (sound& curr_sound : m_sounds) { + curr_sound.set_transform(p_transform); + } +} + +void set_velocity(glm::vec3 p_linear_velocity, + glm::vec3 p_angular_velocity) { + for (sound& curr_sound : m_sounds) { + curr_sound.set_velocity(p_linear_velocity, p_angular_velocity); + } +} + +}; // namespace audio From eca0bb8dc9f5c35adaa73620ca916bd4a1d9be96 Mon Sep 17 00:00:00 2001 From: Evan Caplinger Date: Thu, 11 Dec 2025 02:55:58 -0800 Subject: [PATCH 2/2] Initial implementation of new audio architecture --- audio-cpp/context.hpp | 15 +++--- audio-cpp/device.hpp | 27 +++------- audio-cpp/engine.hpp | 17 ++++-- audio-cpp/listener.hpp | 5 +- audio-cpp/sound.hpp | 39 ++++++-------- audio-cpp/source.hpp | 3 +- audio-cpp/types.hpp | 42 ++++++++++++++- conanfile.py | 1 + src/CMakeLists.txt | 5 +- src/audio-cpp/context.cpp | 26 +++++---- src/audio-cpp/device.cpp | 22 +++++++- src/audio-cpp/engine.cpp | 109 ++++++++++++++++++++++++++++---------- src/audio-cpp/sound.cpp | 42 +++++++++++++-- src/audio-cpp/source.cpp | 6 +-- 14 files changed, 249 insertions(+), 110 deletions(-) diff --git a/audio-cpp/context.hpp b/audio-cpp/context.hpp index 35dea2c..d9b6cfd 100644 --- a/audio-cpp/context.hpp +++ b/audio-cpp/context.hpp @@ -1,8 +1,10 @@ #pragma once -#include +#include #include -#include +#include + +#include namespace audio { @@ -18,15 +20,12 @@ class context { public: context(); - std::span enumerate_devices(); - - void create_device(device* pDevice, const device_info& info); - - std::unique_ptr create_device(const std::string& deviceName, - const device_info& info); + std::vector enumerate_devices(); ma_context* get_wrapped_object() { return &m_context; } + ma_device_id get_id_for_device(const device_info& info); + private: ma_context m_context; diff --git a/audio-cpp/device.hpp b/audio-cpp/device.hpp index 3777c05..071c2b3 100644 --- a/audio-cpp/device.hpp +++ b/audio-cpp/device.hpp @@ -3,36 +3,21 @@ #include #include -#include +#include +#include + +#include namespace audio { using device_id = ma_device_id; -enum device_type { - playback = ma_device_type_playback, - capture = ma_device_type_capture, - duplex = ma_device_type_duplex, - loopback = ma_device_type_loopback -}; - -struct device_info { - std::string name; - device_type type; - device_id id; -}; - -struct device_config { - device_info info; - uint32_t sampleRate; - uint32_t periodSizeInFrames; -}; class device { public: - device(); - device(const device_config& config); + device() = default; + device(const device_config& p_config, context* p_context); ma_device* get_wrapped_object() { return &m_device; } diff --git a/audio-cpp/engine.hpp b/audio-cpp/engine.hpp index cd77443..c65153b 100644 --- a/audio-cpp/engine.hpp +++ b/audio-cpp/engine.hpp @@ -16,7 +16,7 @@ struct engine_config { device_config device; // spatialization params - unsigned int spatial_interpolation_ms; + unsigned int spatial_interpolation_ms = 50; }; // forward declaration to avoid dependency cycle @@ -25,9 +25,11 @@ class source; class engine { public: + engine() = default; engine(const engine_config& p_config, flecs::world& p_registry, context* p_context); + ~engine(); void init(); @@ -35,7 +37,7 @@ class engine { void uninit(); - void set_active_listener(listener* p_listener); + void set_active_listener(const listener* p_listener) { m_active_listener = p_listener; } ma_engine* get_instance() { return &m_engine; } @@ -59,10 +61,15 @@ class engine { ma_engine m_engine; ma_engine_config m_config; - device* m_device = nullptr; - context* m_context = nullptr; + device m_device; + //context* m_context = nullptr; + ma_context m_context; - listener* m_active_listener = nullptr; + const listener* m_active_listener = nullptr; + + bool m_initialized = false; + + ma_engine_config m_engine_config; }; }; diff --git a/audio-cpp/listener.hpp b/audio-cpp/listener.hpp index 279dce2..cb6bef8 100644 --- a/audio-cpp/listener.hpp +++ b/audio-cpp/listener.hpp @@ -1,4 +1,5 @@ -#include +#pragma once + #include namespace audio { @@ -8,7 +9,7 @@ class listener { listener() = default; void set_ma_index(ma_uint32 p_index) { m_ma_index = p_index; } - ma_uint32 get_ma_index() { return m_ma_index; } + [[nodiscard]] ma_uint32 get_ma_index() const { return m_ma_index; } private: ma_uint32 m_ma_index; diff --git a/audio-cpp/sound.hpp b/audio-cpp/sound.hpp index 3529415..dd7b0ff 100644 --- a/audio-cpp/sound.hpp +++ b/audio-cpp/sound.hpp @@ -1,40 +1,30 @@ #pragma once + #include #include #include #include +#include #include namespace audio { using source_index = uint32_t; -enum sound_type { - from_file -}; - -struct sound_properties { - sound_type type = sound_type::from_file; - std::string filename; - // NOTE: supporting data callbacks will require more work to fully abstract - // void* data_callback(void) = nullptr; - bool persistent = false; - bool consider_position = false; - bool consider_rotation = false; - bool consider_velocity = false; - bool decode_on_init = false; - float doppler_factor = 0.f; - int polyphonic_voices = 1; -}; - /** * @note Sound abstraction around the miniaudio API * */ class sound { public: + sound() = default; sound(const sound_properties& p_properties); - ~sound(); + + ~sound() { + if (m_initialized) { + uninit(); + } + } /** * @brief Initialize the sound with the given engine. @@ -60,15 +50,18 @@ class sound { void set_transform(const atlas::transform& p_transform); + void set_velocity(const glm::vec3& p_linear_velocity, + const glm::vec3& p_angular_velocity); + /** * @brief Set this sound to play at the next tick. */ - void play() { m_should_play = false; } + void play() { m_should_play = true; } /** * @brief Set this sound to stop playing at the next tick. */ - void stop() { m_should_play = true; } + void stop() { m_should_stop = true; } private: void apply_config(); @@ -81,9 +74,11 @@ class sound { bool m_config_dirty = false; bool m_initialized = false; - const sound_properties m_properties; + sound_properties m_properties; atlas::transform m_transform; + glm::vec3 m_linear_velocity; + glm::vec3 m_angular_velocity; ma_sound m_sound; }; diff --git a/audio-cpp/source.hpp b/audio-cpp/source.hpp index 0bf2795..77661ff 100644 --- a/audio-cpp/source.hpp +++ b/audio-cpp/source.hpp @@ -19,6 +19,7 @@ struct source_settings { */ class source { public: + source() = default; source(const source_settings& p_settings); /** @@ -67,7 +68,7 @@ class source { void update(engine* p_engine); - void uninit(engine* p_engine); + void uninit(); // NOTE: any object with a spatialized_source should also have a transform; // otherwise, it will behave as a regular source diff --git a/audio-cpp/types.hpp b/audio-cpp/types.hpp index 5002940..582af8c 100644 --- a/audio-cpp/types.hpp +++ b/audio-cpp/types.hpp @@ -7,6 +7,46 @@ namespace audio { +enum device_type { + playback = ma_device_type_playback, + capture = ma_device_type_capture, + duplex = ma_device_type_duplex, + loopback = ma_device_type_loopback +}; + +// DEVICE CONFIGURATION TYPES +struct device_info { + std::string name; + device_type type; +}; + +struct device_config { + device_info info; + device_type type = device_type::playback; + uint32_t sample_rate = 44100; + uint32_t period_size_in_frames = 1024; +}; + +// SOUND CONFIGURATION TYPES +enum sound_type { + from_file +}; + +struct sound_properties { + sound_type type = sound_type::from_file; + std::string filename; + // NOTE: supporting data callbacks will require more work to fully abstract + // void* data_callback(void) = nullptr; + bool persistent = false; + bool consider_position = false; + bool consider_rotation = false; + bool consider_velocity = false; + bool decode_on_init = false; + float gain = 1.f; + float doppler_factor = 0.f; + int polyphonic_voices = 1; +}; + class audio_exception : public std::exception { public: audio_exception(ma_result result) @@ -17,7 +57,7 @@ class audio_exception : public std::exception { m_err_msg = err_msg.str(); } - const char* what() const noexcept override { + [[nodiscard]] const char* what() const noexcept override { return m_err_msg.c_str(); } diff --git a/conanfile.py b/conanfile.py index e3ad1c7..d59d341 100644 --- a/conanfile.py +++ b/conanfile.py @@ -31,6 +31,7 @@ def requirements(self): self.requires("atlas/0.2") self.requires("flecs/4.0.4") self.requires("glm/1.0.1") + self.requires("spdlog/1.15.1") # This is how exporting the sources work def export_sources(self): diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4469584..7aad1c3 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -4,8 +4,11 @@ set(AUDIO_SRC_DIRS audio-cpp) set( all_src - ${AUDIO_SRC_DIRS}/utilities.cpp + ${AUDIO_SRC_DIRS}/context.cpp + ${AUDIO_SRC_DIRS}/device.cpp + ${AUDIO_SRC_DIRS}/engine.cpp ${AUDIO_SRC_DIRS}/sound.cpp + ${AUDIO_SRC_DIRS}/source.cpp ) add_library(${PROJECT_NAME} ${all_headers} ${all_src}) diff --git a/src/audio-cpp/context.cpp b/src/audio-cpp/context.cpp index b836b10..4a74169 100644 --- a/src/audio-cpp/context.cpp +++ b/src/audio-cpp/context.cpp @@ -1,16 +1,18 @@ #include #include +#include #include +#include namespace audio { context::context() { m_config = ma_context_config_init(); - ma_context_init(NULL, 0, &m_config, &m_context); + ma_context_init(nullptr, 0, nullptr, &m_context); } -std::span context::enumerate_devices() { +std::vector context::enumerate_devices() { // safe to assume we should repopulate device list populate_device_list(); @@ -18,7 +20,9 @@ std::span context::enumerate_devices() { std::vector r; for (std::map::iterator it = m_device_list.begin(); it != m_device_list.end(); it++) { - r.push_back({ .name = it->first }); + r.push_back({ + .name = it->first + }); } return r; @@ -30,9 +34,13 @@ void context::populate_device_list() { ma_device_info* playback_devices; ma_result result; - result = ma_context_get_devices(&m_context, &playback_devices, &device_count, NULL, NULL); + result = ma_context_get_devices(&m_context, + &playback_devices, + &device_count, + nullptr, + nullptr); if (result != MA_SUCCESS) { - throw std::runtime_error("error getting devices"); + throw new audio_exception(result); } // destroy and rebuild device list @@ -43,12 +51,8 @@ void context::populate_device_list() { } } -std::unique_ptr context::create_device(const std::string& deviceName, - const device_config& config) -{ - device_info newConfig = std::copy(config); - newConfig.id = m_device_list[deviceName]; - return std::make_unique(newConfig); +ma_device_id context::get_id_for_device(const device_info& p_info) { + return m_device_list[p_info.name]; } }; // namespace audio diff --git a/src/audio-cpp/device.cpp b/src/audio-cpp/device.cpp index 45f01f6..39de863 100644 --- a/src/audio-cpp/device.cpp +++ b/src/audio-cpp/device.cpp @@ -1,9 +1,27 @@ #include +#include +#include +#include namespace audio { -device::device(const device_config& config) { - m_config = ma_device_config_init(config.type); +device::device(const device_config& p_config, + context* p_context) { + m_config = ma_device_config_init(static_cast(p_config.type)); + m_config.sampleRate = p_config.sample_rate; + m_config.periodSizeInFrames = p_config.period_size_in_frames; + m_config.playback.channels = 2; + + ma_device_id new_id = p_context->get_id_for_device(p_config.info); + m_config.playback.pDeviceID = &new_id; + + ma_result result = ma_device_init(p_context->get_wrapped_object(), + &m_config, + &m_device); + if (result != MA_SUCCESS) { + std::printf("could not initialize device; error %d\n", result); + throw new audio_exception(result); + } } }; diff --git a/src/audio-cpp/engine.cpp b/src/audio-cpp/engine.cpp index 092a645..a579bde 100644 --- a/src/audio-cpp/engine.cpp +++ b/src/audio-cpp/engine.cpp @@ -1,5 +1,7 @@ #include #include +#include +#include #include #include #include @@ -7,57 +9,101 @@ namespace audio { -engine(const engine_config& p_config, +engine::engine(const engine_config& p_config, flecs::world& p_registry, context* p_context) - : m_context(p_context) - , m_registry(&p_registry) { - m_device = device(config.device); - - m_engine_config = ma_engine_config_init(); - m_engine_config.pContext = pContext->get_wrapped_object(); - m_engine_config.gainSmoothTimeInMilliseconds = p_config.spatial_interpolation_ms; + : //m_context(p_context), + m_registry(&p_registry) + , m_device(p_config.device, p_context) { + m_config = ma_engine_config_init(); + m_config.pContext = p_context->get_wrapped_object(); + //m_config.pDevice = m_device.get_wrapped_object(); + m_config.gainSmoothTimeInMilliseconds = p_config.spatial_interpolation_ms; ma_result result; - result = ma_engine_init(&m_engine_config, &m_engine); + result = ma_engine_init(&m_config, &m_engine); if (result != MA_SUCCESS) { + std::printf("could not initialize engine; got error %d\n", result); throw new audio_exception(result); } + std::printf("done with this engine constructor thing\n"); + + m_initialized = false; + + //result = ma_context_init(nullptr, 0, nullptr, &m_context); + //if (result != MA_SUCCESS) { + // return; + //} + + //m_engine_config = ma_engine_config_init(); + //m_engine_config.pContext = &m_context; + + // result = ma_engine_init(&m_engine_config, &m_engine); + // if (result != MA_SUCCESS) { + // std::printf("epic fail!!!!: %d\n", result); + // return; + // } + +} + +engine::~engine() { + uninit(); + ma_engine_uninit(&m_engine); +} + +void engine::init() { + console_log_info("engine::init: start"); + m_query_source = m_registry->query_builder().build(); m_query_source_transform = - m_registry->query_builder().build(); + m_registry->query_builder().build(); m_query_source_velocity = - m_registry->query_builder().build(); -} + m_registry->query_builder().build(); + m_query_listener_transform = + m_registry->query_builder().build(); -void engine::init() { m_query_source.each([this] (flecs::entity p_entity, source& p_source) { p_source.init(this); - } + }); + + m_initialized = true; + + console_log_info("engine::init: done"); } void engine::uninit() { m_query_source.each([this] (flecs::entity p_entity, source& p_source) { p_source.uninit(); - } + }); + + m_initialized = false; } void engine::update() { - m_query_std_source.each([this] (flecs::entity p_entity, - source& p_source) { + //console_log_info("engine::update: start"); + + if (!m_initialized) { + return; + } + + //console_log_info("engine::update: doing source query"); + m_query_source.each([this] (flecs::entity p_entity, + source& p_source) { p_source.update(this); }); + //console_log_info("engine::update: doing transform query"); m_query_source_transform.each([this] (flecs::entity p_entity, source& p_source, atlas::transform& p_transform) { p_source.set_transform(p_transform); }); + //console_log_info("engine::update: doing physics_body query"); m_query_source_velocity.each([this] (flecs::entity p_entity, source& p_source, atlas::physics_body& p_body) { @@ -77,10 +123,9 @@ void engine::update() { position.y, position.z); - glm::quat quaternion = atlas::to_quat(m_transform.quaternion); - glm::vec3 forward = glm::rotate(quaternion, - glm::vec3(0.0f, 0.0f, -1.0f)); - ma_engine_listener_set_direction(&m_sound, + glm::quat quaternion = atlas::to_quat(p_transform.quaternion); + glm::vec3 forward = quaternion * glm::vec3(0.0f, 0.0f, -1.0f); + ma_engine_listener_set_direction(&m_engine, 0, // TODO: add support for internal MA listener indices forward.x, forward.y, @@ -88,14 +133,20 @@ void engine::update() { } }); - m_query_listener_velocty.each([this] (flecs::entity p_entity, - listener& p_listener, - atlas::physics_body& p_body){ - - if (&p_listener == m_active_listener) { - - } - }); + //m_query_listener_velocity.each([this] (flecs::entity p_entity, + // listener& p_listener, + // atlas::physics_body& p_body){ + + // if (&p_listener == m_active_listener) { + // ma_engine_listener_set_velocity(&m_engine, + // 0, + // p_body.linear_velocity.x, + // p_body.linear_velocity.y, + // p_body.linear_velocity.z); + // } + //}); + // + //console_log_info("engine::update: done"); } }; // namespace audio diff --git a/src/audio-cpp/sound.cpp b/src/audio-cpp/sound.cpp index 23d0619..719dbb6 100644 --- a/src/audio-cpp/sound.cpp +++ b/src/audio-cpp/sound.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -16,6 +17,8 @@ void sound::init(engine* p_engine) { return; } + std::printf("sound is being initialized...\n"); + // get raw ma_engine instance ma_engine* engine_inst = p_engine->get_instance(); @@ -41,10 +44,15 @@ void sound::init(engine* p_engine) { } if (result != MA_SUCCESS) { + std::printf("could not initialize sound, got response %d\n", result); throw new audio_exception(result); } + ma_sound_set_volume(&m_sound, m_properties.gain); + m_initialized = true; + + std::printf("sound has been initialized\n"); } void sound::apply_config() { @@ -89,22 +97,38 @@ void sound::update(engine* p_engine) { ma_sound_set_direction(&m_sound, forward.x, forward.y, forward.z); } + // TODO: handle angular velocity or get rid of functionality if (m_properties.consider_velocity) { - // TODO: give velocity to miniaudio + ma_sound_set_velocity(&m_sound, + m_linear_velocity.x, + m_linear_velocity.y, + m_linear_velocity.z); } // play sound if we should play if (m_should_play) { if (!m_initialized) { - init(p_engine); + return; } + std::printf("sound is playing...\n"); + // TODO: add playhead positioning // replace with ma_sound_seek_to_second, which cannot be included for // whatever reason... - ma_sound_seek_to_pcm_frame(&m_sound, 0); + ma_result r; + r = ma_sound_seek_to_pcm_frame(&m_sound, 0); + if (r != MA_SUCCESS) { + std::printf("could not seek, got result%d\n", r); + throw new audio_exception(r); + } + if (!m_is_playing) { - ma_sound_start(&m_sound); + r = ma_sound_start(&m_sound); + if (r != MA_SUCCESS) { + std::printf("could not play sound, got result%d\n", r); + throw new audio_exception(r); + } m_is_playing = true; } @@ -119,6 +143,16 @@ void sound::uninit() { } } +void sound::set_transform(const atlas::transform& p_transform) { + m_transform = p_transform; +} + +void sound::set_velocity(const glm::vec3& p_linear_velocity, + const glm::vec3& p_angular_velocity) { + m_linear_velocity = p_linear_velocity; + m_angular_velocity = p_angular_velocity; +} + // sound::sound(const std::string& p_filename) { // ma_decoder_init_file(p_filename.c_str(), nullptr, &m_decoder); // diff --git a/src/audio-cpp/source.cpp b/src/audio-cpp/source.cpp index b98b70a..c0f5453 100644 --- a/src/audio-cpp/source.cpp +++ b/src/audio-cpp/source.cpp @@ -39,14 +39,14 @@ void source::uninit() { } } -void set_transform(atlas::transform p_transform) { +void source::set_transform(atlas::transform p_transform) { for (sound& curr_sound : m_sounds) { curr_sound.set_transform(p_transform); } } -void set_velocity(glm::vec3 p_linear_velocity, - glm::vec3 p_angular_velocity) { +void source::set_velocity(glm::vec3 p_linear_velocity, + glm::vec3 p_angular_velocity) { for (sound& curr_sound : m_sounds) { curr_sound.set_velocity(p_linear_velocity, p_angular_velocity); }