Formatting
publish / publish (push) Successful in 14m33s

This commit is contained in:
khodowany
2026-09-14 23:56:16 -05:00
parent 3816eb96c9
commit 59b96ddf95
16 changed files with 127 additions and 232 deletions
+2 -1
View File
@@ -8,6 +8,7 @@
#include <modulus.hpp>
auto sonus::detail::make_error(std::int32_t code, std::string message, modulus::source_location location) -> modulus::error {
return modulus::make_error("sonus", code, std::move(message), location);
}
@@ -20,7 +21,7 @@ auto sonus::detail::fail_void(std::int32_t code, std::string message, modulus::s
return sonus::detail::fail_void(sonus::detail::make_error(code, std::move(message), location));
}
auto sonus::detail::fail_void(std::expected<void, modulus::error> const& result) -> std::expected<void, modulus::error> {
auto sonus::detail::fail_void(std::expected<void, modulus::error> const &result) -> std::expected<void, modulus::error> {
assert(!result);
return sonus::detail::fail_void(result.error());
}
+1
View File
@@ -7,6 +7,7 @@
#include <sonus/config.hpp>
namespace sonus::detail {
inline auto find_playback_device(std::span<device_info const> devices, std::string_view id, std::string_view name) -> std::optional<std::size_t> {
+20 -17
View File
@@ -6,36 +6,41 @@
#include <utility>
#include <vector>
namespace sonus::detail {
// Publishers/collectors must be serialized; readers never lock or destroy snapshots.
template<typename T>
template <typename T>
class render_snapshot {
static_assert(std::atomic<std::uint32_t>::is_always_lock_free);
static_assert(std::atomic<T const*>::is_always_lock_free);
static_assert(std::atomic<T const *>::is_always_lock_free);
public:
class read_guard {
public:
explicit read_guard(render_snapshot& owner) : owner_(owner) {
explicit read_guard(render_snapshot &owner)
: owner_(owner) {
owner_.active_render_count_.fetch_add(1, std::memory_order_seq_cst);
value_ = owner_.active_.load(std::memory_order_seq_cst);
}
read_guard(read_guard const&) = delete;
auto operator=(read_guard const&) -> read_guard& = delete;
read_guard(read_guard const &) = delete;
auto operator=(read_guard const &) -> read_guard & = delete;
~read_guard() {
owner_.active_render_count_.fetch_sub(1, std::memory_order_seq_cst);
}
auto get() const -> T const* { return value_; }
auto get() const -> T const * {
return value_;
}
private:
render_snapshot& owner_;
T const* value_{};
render_snapshot &owner_;
T const *value_{};
};
auto read() -> read_guard { return read_guard{*this}; }
auto current() const -> T const* { return current_.get(); }
auto read() -> read_guard {
return read_guard{*this};
}
auto current() const -> T const * {
return current_.get();
}
auto publish(std::unique_ptr<T> next) -> void {
if (current_) {
retired_.push_back(std::move(current_));
@@ -44,7 +49,6 @@ public:
active_.store(current_.get(), std::memory_order_seq_cst);
collect();
}
auto collect() -> void {
// SC orders publication/count observation against reader entry/pointer load:
// a reader either contributes to the count or sees the new snapshot.
@@ -52,11 +56,10 @@ public:
retired_.clear();
}
}
private:
std::unique_ptr<T> current_{};
std::vector<std::unique_ptr<T>> retired_{};
std::atomic<T const*> active_{nullptr};
std::atomic<T const *> active_{nullptr};
std::atomic<std::uint32_t> active_render_count_{0};
};
+59 -123
View File
@@ -26,6 +26,7 @@
#include "detail/render_snapshot.hpp"
#include "detail/playback_devices.hpp"
namespace {
constexpr auto pi = 3.141592653589793238462643383279502884;
@@ -39,7 +40,7 @@ auto unavailable(std::string message) -> modulus::error {
}
auto miniaudio_error(ma_result result, std::string action) -> modulus::error {
auto const* description = ma_result_description(result);
auto const *description = ma_result_description(result);
auto message = std::move(action);
if (description != nullptr && std::string_view{description} != "No error") {
message += ": ";
@@ -60,7 +61,7 @@ auto contains_null(std::string_view value) -> std::bool_t {
return value.find('\0') != std::string_view::npos;
}
auto make_playback_device_id(ma_uint32 index, ma_device_info const& info) -> std::string {
auto make_playback_device_id(ma_uint32 index, ma_device_info const &info) -> std::string {
auto result = std::to_string(index);
result += ':';
result += (info.isDefault == MA_TRUE ? '1' : '0');
@@ -69,7 +70,7 @@ auto make_playback_device_id(ma_uint32 index, ma_device_info const& info) -> std
return result;
}
auto describe_playback_devices(ma_device_info const* devices, ma_uint32 count) -> std::vector<sonus::device_info> {
auto describe_playback_devices(ma_device_info const *devices, ma_uint32 count) -> std::vector<sonus::device_info> {
auto result = std::vector<sonus::device_info>{};
result.reserve(count);
for (auto index = ma_uint32{}; index < count; ++index) {
@@ -84,10 +85,10 @@ auto describe_playback_devices(ma_device_info const* devices, ma_uint32 count) -
auto to_miniaudio_profile(sonus::performance_profile profile) -> ma_performance_profile {
switch (profile) {
case sonus::performance_profile::LOW_LATENCY:
return ma_performance_profile_low_latency;
case sonus::performance_profile::CONSERVATIVE:
return ma_performance_profile_conservative;
case sonus::performance_profile::LOW_LATENCY:
return ma_performance_profile_low_latency;
case sonus::performance_profile::CONSERVATIVE:
return ma_performance_profile_conservative;
}
return ma_performance_profile_low_latency;
}
@@ -110,21 +111,18 @@ struct active_sound {
std::bool_t initialized{false};
std::bool_t loop{false};
std::atomic_bool finished{false};
active_sound() = default;
active_sound(active_sound const&) = delete;
auto operator=(active_sound const&) -> active_sound& = delete;
active_sound(active_sound&&) = delete;
auto operator=(active_sound&&) -> active_sound& = delete;
active_sound(active_sound const &) = delete;
auto operator=(active_sound const &) -> active_sound & = delete;
active_sound(active_sound &&) = delete;
auto operator=(active_sound &&) -> active_sound & = delete;
~active_sound() {
if (initialized) {
ma_sound_uninit(&sound);
}
}
static auto end_callback(void* user_data, ma_sound*) -> void {
auto* self = static_cast<active_sound*>(user_data);
static auto end_callback(void *user_data, ma_sound *) -> void {
auto *self = static_cast<active_sound *>(user_data);
if (self != nullptr) {
self->finished.store(true, std::memory_order_release);
}
@@ -134,11 +132,9 @@ struct active_sound {
struct temporary_context {
ma_context context{};
std::bool_t initialized{false};
temporary_context() = default;
temporary_context(temporary_context const&) = delete;
auto operator=(temporary_context const&) -> temporary_context& = delete;
temporary_context(temporary_context const &) = delete;
auto operator=(temporary_context const &) -> temporary_context & = delete;
~temporary_context() {
if (initialized) {
ma_context_uninit(&context);
@@ -169,32 +165,26 @@ struct sonus::engine::impl {
std::bool_t started{false};
mutable std::mutex mutex{};
mutable std::mutex render_mutex{};
std::unordered_map<sonus::sound_handle, std::unique_ptr<active_sound>> sounds{};
sonus::sound_handle next_sound_handle{1};
std::unordered_map<std::uint64_t, std::unique_ptr<active_sound>> sounds{};
std::uint64_t next_sound_handle{1};
sonus::detail::render_snapshot<metronome_render_config> metronome_configs{};
std::atomic_bool metronome_enabled{false};
std::uint32_t next_metronome_generation{1};
std::uint32_t observed_metronome_generation{0};
std::uint32_t beat_index{0};
std::uint64_t frames_until_next_beat{0};
std::size_t click_frame_index{0};
std::size_t click_frames_remaining{0};
std::bool_t click_is_downbeat{false};
impl() = default;
impl(impl const&) = delete;
auto operator=(impl const&) -> impl& = delete;
impl(impl const &) = delete;
auto operator=(impl const &) -> impl & = delete;
~impl() {
shutdown();
}
auto initialize(sonus::audio_config value) -> std::expected<void, modulus::error> {
config = std::move(value);
publish_metronome_locked(make_disabled_metronome_config_locked());
auto engine_config = ma_engine_config_init();
engine_config.noDevice = MA_TRUE;
engine_config.channels = config.channels;
@@ -202,19 +192,16 @@ struct sonus::engine::impl {
engine_config.periodSizeInFrames = config.period_size_frames;
engine_config.periodSizeInMilliseconds = config.period_size_milliseconds;
engine_config.noAutoStart = MA_TRUE;
auto result = ma_engine_init(&engine_config, &mixer);
if (result != MA_SUCCESS) {
return sonus::detail::fail_void(miniaudio_error(result, "failed to initialize miniaudio engine"));
}
mixer_initialized = true;
result = ma_engine_set_volume(&mixer, config.master_volume);
if (result != MA_SUCCESS) {
shutdown();
return sonus::detail::fail_void(miniaudio_error(result, "failed to set engine volume"));
}
if (!config.no_device) {
auto device_config = ma_device_config_init(ma_device_type_playback);
device_config.playback.format = ma_format_f32;
@@ -226,13 +213,11 @@ struct sonus::engine::impl {
device_config.performanceProfile = to_miniaudio_profile(config.performance);
device_config.dataCallback = sonus::engine::impl::device_callback;
device_config.pUserData = this;
auto selected = configure_playback_device(device_config);
if (!selected) {
shutdown();
return selected;
}
result = ma_device_init(context_initialized ? &context : nullptr, &device_config, &device);
if (result != MA_SUCCESS) {
shutdown();
@@ -240,10 +225,8 @@ struct sonus::engine::impl {
}
device_initialized = true;
}
return {};
}
auto start() -> std::expected<void, modulus::error> {
auto lock = std::scoped_lock{mutex};
if (started) {
@@ -263,7 +246,6 @@ struct sonus::engine::impl {
started = true;
return {};
}
auto stop_device() -> void {
auto lock = std::scoped_lock{mutex};
if (!started) {
@@ -275,7 +257,6 @@ struct sonus::engine::impl {
started = false;
collect_retired_metronome_configs_locked();
}
auto shutdown() -> void {
auto lock = std::scoped_lock{mutex};
if (device_initialized) {
@@ -298,26 +279,22 @@ struct sonus::engine::impl {
metronome_enabled.store(false, std::memory_order_release);
reset_metronome_render_state();
}
auto configure_playback_device(ma_device_config& device_config) -> std::expected<void, modulus::error> {
auto configure_playback_device(ma_device_config &device_config) -> std::expected<void, modulus::error> {
if (config.playback_device_id.empty() && config.playback_device_name.empty()) {
return {};
}
auto context_config = ma_context_config_init();
auto result = ma_context_init(nullptr, 0, &context_config, &context);
if (result != MA_SUCCESS) {
return sonus::detail::fail_void(miniaudio_error(result, "failed to initialize miniaudio context"));
}
context_initialized = true;
ma_device_info* playback_devices = nullptr;
ma_device_info *playback_devices = nullptr;
auto playback_device_count = ma_uint32{};
result = ma_context_get_devices(&context, &playback_devices, &playback_device_count, nullptr, nullptr);
if (result != MA_SUCCESS) {
return sonus::detail::fail_void(miniaudio_error(result, "failed to enumerate playback devices"));
}
auto devices = describe_playback_devices(playback_devices, playback_device_count);
auto selected = sonus::detail::find_playback_device(devices, config.playback_device_id, config.playback_device_name);
if (selected) {
@@ -325,16 +302,13 @@ struct sonus::engine::impl {
device_config.playback.pDeviceID = &playback_device_id;
return {};
}
auto selector = config.playback_device_id.empty() ? config.playback_device_name : config.playback_device_id;
return sonus::detail::fail_void(sonus::detail::error_not_found, "playback device not found: " + selector);
}
auto set_master_volume(std::float32_t volume) -> std::expected<void, modulus::error> {
if (!valid_unit_interval(volume)) {
return sonus::detail::fail_void(invalid("audio master_volume must be between 0 and 1"));
}
auto lock = std::scoped_lock{mutex};
auto result = ma_engine_set_volume(&mixer, volume);
if (result != MA_SUCCESS) {
@@ -343,55 +317,49 @@ struct sonus::engine::impl {
config.master_volume = volume;
return {};
}
auto play_file(std::filesystem::path const& path, sonus::playback_options options) -> std::expected<sonus::sound_handle, modulus::error> {
auto play_file(std::filesystem::path const &path, sonus::playback_options options) -> std::expected<std::uint64_t, modulus::error> {
if (path.empty()) {
return sonus::detail::fail<sonus::sound_handle>(sonus::detail::error_invalid_argument, "sound path must not be empty");
return sonus::detail::fail<std::uint64_t>(sonus::detail::error_invalid_argument, "sound path must not be empty");
}
auto valid_options = sonus::validate(options);
if (!valid_options) {
return sonus::detail::fail<sonus::sound_handle>(valid_options.error());
return sonus::detail::fail<std::uint64_t>(valid_options.error());
}
auto file_error = std::error_code{};
if (!std::filesystem::is_regular_file(path, file_error)) {
return sonus::detail::fail<sonus::sound_handle>(sonus::detail::make_error(sonus::detail::error_not_found, "sound file not found: " + path.string()));
return sonus::detail::fail<std::uint64_t>(sonus::detail::make_error(sonus::detail::error_not_found, "sound file not found: " + path.string()));
}
auto sound = std::make_unique<active_sound>();
sound->loop = options.loop;
auto const path_text = path.string();
auto flags = static_cast<ma_uint32>(MA_SOUND_FLAG_NO_SPATIALIZATION);
flags |= options.stream ? MA_SOUND_FLAG_STREAM : MA_SOUND_FLAG_DECODE;
auto lock = std::scoped_lock{mutex};
static_cast<void>(collect_finished_locked());
collect_retired_metronome_configs_locked();
auto result = ma_sound_init_from_file(&mixer, path_text.c_str(), flags, nullptr, nullptr, &sound->sound);
if (result != MA_SUCCESS) {
return sonus::detail::fail<sonus::sound_handle>(miniaudio_error(result, "failed to load sound file"));
return sonus::detail::fail<std::uint64_t>(miniaudio_error(result, "failed to load sound file"));
}
sound->initialized = true;
result = ma_sound_set_end_callback(&sound->sound, active_sound::end_callback, sound.get());
if (result != MA_SUCCESS) {
return sonus::detail::fail<sonus::sound_handle>(miniaudio_error(result, "failed to set sound end callback"));
return sonus::detail::fail<std::uint64_t>(miniaudio_error(result, "failed to set sound end callback"));
}
ma_sound_set_volume(&sound->sound, options.volume);
ma_sound_set_looping(&sound->sound, options.loop ? MA_TRUE : MA_FALSE);
result = ma_sound_start(&sound->sound);
if (result != MA_SUCCESS) {
return sonus::detail::fail<sonus::sound_handle>(miniaudio_error(result, "failed to start sound file"));
return sonus::detail::fail<std::uint64_t>(miniaudio_error(result, "failed to start sound file"));
}
auto handle = allocate_sound_handle_locked();
if (!handle) {
return sonus::detail::fail<sonus::sound_handle>(handle.error());
return sonus::detail::fail<std::uint64_t>(handle.error());
}
sounds.emplace(*handle, std::move(sound));
return *handle;
}
auto stop_sound(sonus::sound_handle handle) -> std::expected<void, modulus::error> {
auto stop_sound(std::uint64_t handle) -> std::expected<void, modulus::error> {
if (handle == 0) {
return sonus::detail::fail_void(sonus::detail::error_invalid_argument, "sound handle must not be zero");
}
@@ -404,16 +372,14 @@ struct sonus::engine::impl {
sounds.erase(found);
return {};
}
auto stop_all_sounds() -> void {
auto lock = std::scoped_lock{mutex};
for (auto& entry : sounds) {
for (auto &entry : sounds) {
static_cast<void>(ma_sound_stop(&entry.second->sound));
}
sounds.clear();
}
auto is_sound_playing(sonus::sound_handle handle) -> std::expected<std::bool_t, modulus::error> {
auto is_sound_playing(std::uint64_t handle) -> std::expected<std::bool_t, modulus::error> {
if (handle == 0) {
return sonus::detail::fail<std::bool_t>(sonus::detail::error_invalid_argument, "sound handle must not be zero");
}
@@ -428,65 +394,54 @@ struct sonus::engine::impl {
}
return ma_sound_is_playing(&found->second->sound) == MA_TRUE;
}
auto collect_finished_sounds() -> std::uint64_t {
auto lock = std::scoped_lock{mutex};
auto const collected = collect_finished_locked();
collect_retired_metronome_configs_locked();
return collected;
}
auto active_sound_count() -> std::uint64_t {
auto lock = std::scoped_lock{mutex};
static_cast<void>(collect_finished_locked());
collect_retired_metronome_configs_locked();
return sounds.size();
}
auto store_metronome(sonus::metronome_config value, std::bool_t enable) -> std::expected<void, modulus::error> {
auto valid = sonus::validate(value);
if (!valid) {
return valid;
}
auto lock = std::scoped_lock{mutex};
return publish_metronome_config_locked(value, enable);
}
auto update_metronome(sonus::metronome_config value) -> std::expected<void, modulus::error> {
auto valid = sonus::validate(value);
if (!valid) {
return valid;
}
auto lock = std::scoped_lock{mutex};
auto const* current = metronome_configs.current();
auto const *current = metronome_configs.current();
auto const enable = current != nullptr && current->enabled;
return publish_metronome_config_locked(value, enable);
}
auto stop_metronome() -> void {
auto lock = std::scoped_lock{mutex};
publish_metronome_locked(make_disabled_metronome_config_locked());
collect_retired_metronome_configs_locked();
}
auto publish_metronome_config_locked(sonus::metronome_config value, std::bool_t enable) -> std::expected<void, modulus::error> {
auto next = make_metronome_config(value, enable);
if (!next) {
return sonus::detail::fail_void(next.error());
}
publish_metronome_locked(std::move(*next));
collect_retired_metronome_configs_locked();
return {};
}
auto make_metronome_config(sonus::metronome_config value, std::bool_t enable) -> std::expected<std::unique_ptr<metronome_render_config>, modulus::error> {
auto click_frames = static_cast<std::uint64_t>(config.sample_rate) * static_cast<std::uint64_t>(value.click_duration.count()) / 1000U;
click_frames = std::clamp<std::uint64_t>(click_frames, 1U, std::numeric_limits<std::uint32_t>::max());
auto frames_per_beat = std::max<std::uint64_t>(1U, static_cast<std::uint64_t>(std::llround((static_cast<double>(config.sample_rate) * 60.0) / static_cast<double>(value.bpm))));
auto result = std::make_unique<metronome_render_config>();
result->enabled = enable;
result->beats_per_bar = value.beats_per_bar;
@@ -497,33 +452,27 @@ struct sonus::engine::impl {
}
return std::move(result);
}
auto make_disabled_metronome_config_locked() -> std::unique_ptr<metronome_render_config> {
auto result = std::make_unique<metronome_render_config>();
result->enabled = false;
return result;
}
auto publish_metronome_locked(std::unique_ptr<metronome_render_config> next) -> void {
next->generation = next_metronome_generation++;
if (next_metronome_generation == 0U) {
next_metronome_generation = 1U;
}
auto const enabled = next->enabled;
metronome_configs.publish(std::move(next));
metronome_enabled.store(enabled, std::memory_order_release);
}
auto collect_retired_metronome_configs_locked() -> void {
metronome_configs.collect();
}
auto allocate_sound_handle_locked() -> std::expected<sonus::sound_handle, modulus::error> {
if (sounds.size() >= static_cast<std::size_t>(std::numeric_limits<sonus::sound_handle>::max() - 1U)) {
return sonus::detail::fail<sonus::sound_handle>(sonus::detail::error_internal, "sound handle space exhausted");
auto allocate_sound_handle_locked() -> std::expected<std::uint64_t, modulus::error> {
if (sounds.size() >= static_cast<std::size_t>(std::numeric_limits<std::uint64_t>::max() - 1U)) {
return sonus::detail::fail<std::uint64_t>(sonus::detail::error_internal, "sound handle space exhausted");
}
auto attempts = std::size_t{};
auto const max_attempts = sounds.size() + 1U;
while (attempts < max_attempts) {
@@ -536,14 +485,11 @@ struct sonus::engine::impl {
}
++attempts;
}
return sonus::detail::fail<sonus::sound_handle>(sonus::detail::error_internal, "failed to allocate sound handle");
return sonus::detail::fail<std::uint64_t>(sonus::detail::error_internal, "failed to allocate sound handle");
}
static auto sound_finished(active_sound const& sound) -> std::bool_t {
static auto sound_finished(active_sound const &sound) -> std::bool_t {
return !sound.loop && (sound.finished.load(std::memory_order_acquire) || ma_sound_at_end(&sound.sound) == MA_TRUE);
}
auto collect_finished_locked() -> std::uint64_t {
auto collected = std::uint64_t{};
auto current = sounds.begin();
@@ -557,14 +503,11 @@ struct sonus::engine::impl {
}
return collected;
}
auto render_to(std::float32_t* output, std::uint64_t frame_count) -> ma_result {
auto render_to(std::float32_t *output, std::uint64_t frame_count) -> ma_result {
if (output == nullptr || frame_count == 0U) {
return MA_SUCCESS;
}
auto guard = metronome_configs.read();
auto const sample_count = frame_count * config.channels;
std::fill(output, output + sample_count, 0.0F);
auto result = ma_engine_read_pcm_frames(&mixer, output, frame_count, nullptr);
@@ -575,8 +518,7 @@ struct sonus::engine::impl {
mix_metronome(output, frame_count, guard.get());
return MA_SUCCESS;
}
auto mix_metronome(std::float32_t* output, std::uint64_t frame_count, metronome_render_config const* metro) -> void {
auto mix_metronome(std::float32_t *output, std::uint64_t frame_count, metronome_render_config const *metro) -> void {
if (metro == nullptr || !metro->enabled) {
if (metro != nullptr) {
observed_metronome_generation = metro->generation;
@@ -588,7 +530,6 @@ struct sonus::engine::impl {
observed_metronome_generation = metro->generation;
reset_metronome_render_state();
}
for (auto frame = std::uint64_t{}; frame < frame_count; ++frame) {
if (frames_until_next_beat == 0U) {
auto const downbeat = beat_index == 0U;
@@ -598,10 +539,9 @@ struct sonus::engine::impl {
beat_index = (beat_index + 1U) % std::max<std::uint32_t>(1U, metro->beats_per_bar);
frames_until_next_beat = metro->frames_per_beat;
}
auto sample = 0.0F;
if (click_frames_remaining > 0U) {
auto const& click = click_is_downbeat ? metro->downbeat_click : metro->beat_click;
auto const &click = click_is_downbeat ? metro->downbeat_click : metro->beat_click;
if (click_frame_index < click.size()) {
sample = click[click_frame_index];
++click_frame_index;
@@ -610,9 +550,8 @@ struct sonus::engine::impl {
click_frames_remaining = 0;
}
}
if (sample != 0.0F) {
auto* frame_out = output + frame * config.channels;
auto *frame_out = output + frame * config.channels;
for (auto channel = std::uint32_t{}; channel < config.channels; ++channel) {
auto const mixed = static_cast<std::float32_t>(frame_out[channel] + sample);
frame_out[channel] = std::clamp(mixed, static_cast<std::float32_t>(-1.0F), static_cast<std::float32_t>(1.0F));
@@ -621,7 +560,6 @@ struct sonus::engine::impl {
--frames_until_next_beat;
}
}
auto reset_metronome_render_state() -> void {
beat_index = 0;
frames_until_next_beat = 0;
@@ -629,13 +567,12 @@ struct sonus::engine::impl {
click_frames_remaining = 0;
click_is_downbeat = false;
}
static auto device_callback(ma_device* device, void* output, void const*, ma_uint32 frame_count) -> void {
auto* self = static_cast<sonus::engine::impl*>(device == nullptr ? nullptr : device->pUserData);
static auto device_callback(ma_device *device, void *output, void const *, ma_uint32 frame_count) -> void {
auto *self = static_cast<sonus::engine::impl *>(device == nullptr ? nullptr : device->pUserData);
if (self == nullptr || output == nullptr) {
return;
}
auto result = self->render_to(static_cast<std::float32_t*>(output), frame_count);
auto result = self->render_to(static_cast<std::float32_t *>(output), frame_count);
if (result != MA_SUCCESS) {
auto const sample_count = static_cast<std::uint64_t>(frame_count) * self->config.channels;
std::memset(output, 0, static_cast<std::size_t>(sample_count * sizeof(std::float32_t)));
@@ -643,7 +580,7 @@ struct sonus::engine::impl {
}
};
auto sonus::validate(sonus::audio_config const& config) -> std::expected<void, modulus::error> {
auto sonus::validate(sonus::audio_config const &config) -> std::expected<void, modulus::error> {
if (config.sample_rate < 8000U || config.sample_rate > 384000U) {
return sonus::detail::fail_void(invalid("audio sample_rate must be between 8000 and 384000"));
}
@@ -685,18 +622,16 @@ auto sonus::enumerate_playback_devices() -> std::expected<std::vector<sonus::dev
return sonus::detail::fail<std::vector<sonus::device_info>>(miniaudio_error(result, "failed to initialize miniaudio context"));
}
context.initialized = true;
ma_device_info* playback_devices = nullptr;
ma_device_info *playback_devices = nullptr;
auto playback_device_count = ma_uint32{};
result = ma_context_get_devices(&context.context, &playback_devices, &playback_device_count, nullptr, nullptr);
if (result != MA_SUCCESS) {
return sonus::detail::fail<std::vector<sonus::device_info>>(miniaudio_error(result, "failed to enumerate playback devices"));
}
return describe_playback_devices(playback_devices, playback_device_count);
}
auto sonus::validate(sonus::metronome_config const& config) -> std::expected<void, modulus::error> {
auto sonus::validate(sonus::metronome_config const &config) -> std::expected<void, modulus::error> {
if (config.bpm == 0U || config.bpm > 400U) {
return sonus::detail::fail_void(invalid("metronome bpm must be between 1 and 400"));
}
@@ -715,7 +650,7 @@ auto sonus::validate(sonus::metronome_config const& config) -> std::expected<voi
return {};
}
auto sonus::validate(sonus::playback_options const& options) -> std::expected<void, modulus::error> {
auto sonus::validate(sonus::playback_options const &options) -> std::expected<void, modulus::error> {
if (!valid_unit_interval(options.volume)) {
return sonus::detail::fail_void(invalid("playback volume must be between 0 and 1"));
}
@@ -735,11 +670,12 @@ auto sonus::engine::create(sonus::audio_config config) -> std::expected<sonus::e
return sonus::engine{std::move(implementation)};
}
sonus::engine::engine(std::unique_ptr<sonus::engine::impl> implementation) noexcept : implementation_{std::move(implementation)} {}
sonus::engine::engine(std::unique_ptr<sonus::engine::impl> implementation) noexcept
: implementation_{std::move(implementation)} {}
sonus::engine::engine(sonus::engine&& other) noexcept = default;
sonus::engine::engine(sonus::engine &&other) noexcept = default;
auto sonus::engine::operator=(sonus::engine&& other) noexcept -> sonus::engine& = default;
auto sonus::engine::operator=(sonus::engine &&other) noexcept -> sonus::engine & = default;
sonus::engine::~engine() = default;
@@ -779,14 +715,14 @@ auto sonus::engine::set_master_volume(std::float32_t volume) -> std::expected<vo
return implementation_->set_master_volume(volume);
}
auto sonus::engine::play_file(std::filesystem::path const& path, sonus::playback_options options) -> std::expected<sonus::sound_handle, modulus::error> {
auto sonus::engine::play_file(std::filesystem::path const &path, sonus::playback_options options) -> std::expected<std::uint64_t, modulus::error> {
if (!implementation_) {
return sonus::detail::fail<sonus::sound_handle>(sonus::detail::error_invalid_argument, "audio engine is empty");
return sonus::detail::fail<std::uint64_t>(sonus::detail::error_invalid_argument, "audio engine is empty");
}
return implementation_->play_file(path, options);
}
auto sonus::engine::stop(sonus::sound_handle handle) -> std::expected<void, modulus::error> {
auto sonus::engine::stop(std::uint64_t handle) -> std::expected<void, modulus::error> {
if (!implementation_) {
return sonus::detail::fail_void(sonus::detail::error_invalid_argument, "audio engine is empty");
}
@@ -799,7 +735,7 @@ auto sonus::engine::stop_all() -> void {
}
}
auto sonus::engine::is_playing(sonus::sound_handle handle) -> std::expected<std::bool_t, modulus::error> {
auto sonus::engine::is_playing(std::uint64_t handle) -> std::expected<std::bool_t, modulus::error> {
if (!implementation_) {
return sonus::detail::fail<std::bool_t>(sonus::detail::error_invalid_argument, "audio engine is empty");
}
@@ -838,7 +774,7 @@ auto sonus::engine::metronome_active() const -> std::bool_t {
return implementation_ && implementation_->metronome_enabled.load(std::memory_order_acquire);
}
auto sonus::engine::render(std::float32_t* output, std::uint64_t frame_count) -> std::expected<void, modulus::error> {
auto sonus::engine::render(std::float32_t *output, std::uint64_t frame_count) -> std::expected<void, modulus::error> {
if (!implementation_) {
return sonus::detail::fail_void(sonus::detail::error_invalid_argument, "audio engine is empty");
}