mirror of
https://github.com/4jcraft/4jcraft.git
synced 2026-08-16 12:42:32 +00:00
refactor: carve out a proper sound interface with miniaudio behind it
This commit is contained in:
parent
57a4cf8f31
commit
79fafe0e83
|
|
@ -16,6 +16,7 @@ client_dependencies = [
|
||||||
profile_dep,
|
profile_dep,
|
||||||
storage_dep,
|
storage_dep,
|
||||||
fs_dep,
|
fs_dep,
|
||||||
|
sound_dep,
|
||||||
assets_localisation_dep,
|
assets_localisation_dep,
|
||||||
platform_dep,
|
platform_dep,
|
||||||
minecraft_dep,
|
minecraft_dep,
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ endif
|
||||||
lib_minecraft = static_library('minecraft',
|
lib_minecraft = static_library('minecraft',
|
||||||
minecraft_sources,
|
minecraft_sources,
|
||||||
dependencies : [
|
dependencies : [
|
||||||
miniaudio_dep, # TODO: remove once a SoundEngine facade is present
|
sound_dep,
|
||||||
render_dep,
|
render_dep,
|
||||||
input_dep,
|
input_dep,
|
||||||
profile_dep,
|
profile_dep,
|
||||||
|
|
|
||||||
|
|
@ -31,3 +31,4 @@ subdir('profile')
|
||||||
subdir('storage')
|
subdir('storage')
|
||||||
subdir('fs')
|
subdir('fs')
|
||||||
subdir('renderer')
|
subdir('renderer')
|
||||||
|
subdir('sound')
|
||||||
|
|
|
||||||
101
targets/platform/sound/IPlatformSound.h
Normal file
101
targets/platform/sound/IPlatformSound.h
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "platform/sound/SoundHandles.h"
|
||||||
|
|
||||||
|
// Platform sound interface. The backend (currently miniaudio) implements
|
||||||
|
// this; consumers in app/common/Audio/SoundEngine talk to the interface
|
||||||
|
// rather than to miniaudio directly.
|
||||||
|
//
|
||||||
|
// The interface is intent-level: load a sample from disk, play it,
|
||||||
|
// stop it, set listener position. State (volume, pitch, looping) is
|
||||||
|
// passed at play time as a struct rather than via global setters.
|
||||||
|
//
|
||||||
|
// Concrete handles (SoundHandle, MusicHandle) are tagged structs so the
|
||||||
|
// type system catches sample-vs-music confusion at compile time.
|
||||||
|
|
||||||
|
namespace platform::sound {
|
||||||
|
|
||||||
|
struct PlaySoundParams {
|
||||||
|
float x = 0.0f;
|
||||||
|
float y = 0.0f;
|
||||||
|
float z = 0.0f;
|
||||||
|
float volume = 1.0f;
|
||||||
|
float pitch = 1.0f;
|
||||||
|
bool spatial = true; // 3D sound positioned in the world
|
||||||
|
bool looping = false;
|
||||||
|
float minDistance = 1.0f; // distance below which the sound is full-volume
|
||||||
|
float maxDistance = 16.0f; // distance above which the sound is silent
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PlayMusicParams {
|
||||||
|
float volume = 1.0f;
|
||||||
|
float pitch = 1.0f;
|
||||||
|
bool looping = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
class IPlatformSound {
|
||||||
|
public:
|
||||||
|
virtual ~IPlatformSound() = default;
|
||||||
|
|
||||||
|
// Lifecycle. init() takes the maximum number of simultaneous local
|
||||||
|
// listeners (splitscreen player count). Idempotent: a second init()
|
||||||
|
// call with the same parameters is a no-op.
|
||||||
|
virtual void init(int listenerCount) = 0;
|
||||||
|
virtual void shutdown() = 0;
|
||||||
|
|
||||||
|
// Per-frame tick. Drives streaming reads, voice cleanup, etc.
|
||||||
|
virtual void tick() = 0;
|
||||||
|
|
||||||
|
// Master volume scaling. 0.0 to 1.0. Applies to all subsequent
|
||||||
|
// sound and music playback.
|
||||||
|
virtual void setMasterVolume(float volume) = 0;
|
||||||
|
|
||||||
|
// -- Spatial / one-shot sound effects --
|
||||||
|
|
||||||
|
// Load and start playing a sound. The backend allocates a voice and
|
||||||
|
// returns a handle that can be used to query / stop the sound. If
|
||||||
|
// the load fails or the mixer is at capacity, returns an invalid
|
||||||
|
// handle (the caller should treat that as "sound was dropped" - not
|
||||||
|
// an error).
|
||||||
|
[[nodiscard]] virtual SoundHandle playSoundFromFile(
|
||||||
|
const std::string& path, const PlaySoundParams& params) = 0;
|
||||||
|
|
||||||
|
virtual void stopSound(SoundHandle handle) = 0;
|
||||||
|
[[nodiscard]] virtual bool isSoundPlaying(SoundHandle handle) const = 0;
|
||||||
|
|
||||||
|
virtual void setSoundVolume(SoundHandle handle, float volume) = 0;
|
||||||
|
virtual void setSoundPosition(SoundHandle handle, float x, float y,
|
||||||
|
float z) = 0;
|
||||||
|
virtual void setSoundPitch(SoundHandle handle, float pitch) = 0;
|
||||||
|
|
||||||
|
// Release the voice. Idempotent on invalid handles.
|
||||||
|
virtual void releaseSound(SoundHandle handle) = 0;
|
||||||
|
|
||||||
|
// -- Streaming music --
|
||||||
|
|
||||||
|
// Load and start a streaming music track. Streaming means the
|
||||||
|
// backend reads the file incrementally rather than decoding it all
|
||||||
|
// upfront. Use for music tracks; sound effects should use the
|
||||||
|
// playSound* methods above.
|
||||||
|
[[nodiscard]] virtual MusicHandle playMusicFromFile(
|
||||||
|
const std::string& path, const PlayMusicParams& params) = 0;
|
||||||
|
|
||||||
|
virtual void stopMusic(MusicHandle handle) = 0;
|
||||||
|
[[nodiscard]] virtual bool isMusicPlaying(MusicHandle handle) const = 0;
|
||||||
|
virtual void setMusicVolume(MusicHandle handle, float volume) = 0;
|
||||||
|
virtual void setMusicPitch(MusicHandle handle, float pitch) = 0;
|
||||||
|
virtual void releaseMusic(MusicHandle handle) = 0;
|
||||||
|
|
||||||
|
// -- Listener (one per local splitscreen player) --
|
||||||
|
|
||||||
|
virtual void setListenerPosition(int listenerIndex, float x, float y,
|
||||||
|
float z) = 0;
|
||||||
|
virtual void setListenerOrientation(int listenerIndex, float forwardX,
|
||||||
|
float forwardY, float forwardZ,
|
||||||
|
float upX, float upY, float upZ) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace platform::sound
|
||||||
30
targets/platform/sound/SoundHandles.h
Normal file
30
targets/platform/sound/SoundHandles.h
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
// Type-safe handles for the platform sound subsystem. Each handle is a
|
||||||
|
// tagged 32-bit integer; the tag struct prevents accidental conversion
|
||||||
|
// between handle types at compile time.
|
||||||
|
//
|
||||||
|
// Handle 0 is reserved for "invalid". Backends should never return 0
|
||||||
|
// from a successful create call.
|
||||||
|
|
||||||
|
namespace platform::sound {
|
||||||
|
|
||||||
|
template <class Tag>
|
||||||
|
struct Handle {
|
||||||
|
std::uint32_t id = 0;
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr bool valid() const noexcept { return id != 0; }
|
||||||
|
constexpr explicit operator bool() const noexcept { return valid(); }
|
||||||
|
|
||||||
|
friend constexpr bool operator==(Handle, Handle) = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SoundTag {};
|
||||||
|
struct MusicTag {};
|
||||||
|
|
||||||
|
using SoundHandle = Handle<SoundTag>;
|
||||||
|
using MusicHandle = Handle<MusicTag>;
|
||||||
|
|
||||||
|
} // namespace platform::sound
|
||||||
15
targets/platform/sound/meson.build
Normal file
15
targets/platform/sound/meson.build
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
_miniaudio_dep = dependency('miniaudio')
|
||||||
|
|
||||||
|
lib_platform_sound_miniaudio = static_library(
|
||||||
|
'platform_sound_miniaudio',
|
||||||
|
files('miniaudio/MiniaudioSound.cpp'),
|
||||||
|
include_directories: [platform_inc, include_directories('../..')],
|
||||||
|
dependencies: [_threads, _miniaudio_dep],
|
||||||
|
cpp_args: global_cpp_args + global_cpp_defs,
|
||||||
|
)
|
||||||
|
|
||||||
|
sound_dep = declare_dependency(
|
||||||
|
link_with: lib_platform_sound_miniaudio,
|
||||||
|
include_directories: [platform_inc],
|
||||||
|
dependencies: [_miniaudio_dep],
|
||||||
|
)
|
||||||
261
targets/platform/sound/miniaudio/MiniaudioSound.cpp
Normal file
261
targets/platform/sound/miniaudio/MiniaudioSound.cpp
Normal file
|
|
@ -0,0 +1,261 @@
|
||||||
|
#include "MiniaudioSound.h"
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#include "miniaudio.h"
|
||||||
|
|
||||||
|
#include "platform/sound/sound.h"
|
||||||
|
|
||||||
|
namespace platform::sound::miniaudio {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// Each loaded sound voice. Owned by the State's map; the SoundHandle
|
||||||
|
// the caller holds is the integer key.
|
||||||
|
struct Voice {
|
||||||
|
ma_sound sound{};
|
||||||
|
bool inUse = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
struct State {
|
||||||
|
ma_engine engine{};
|
||||||
|
ma_engine_config engineConfig{};
|
||||||
|
bool engineReady = false;
|
||||||
|
|
||||||
|
// Voice pool. Sound handles are keys; the backend allocates a fresh
|
||||||
|
// id for every play call. Caller releases via releaseSound() (or
|
||||||
|
// we'll auto-release when the sound finishes - tick() handles that).
|
||||||
|
std::unordered_map<std::uint32_t, std::unique_ptr<Voice>> sounds;
|
||||||
|
std::unordered_map<std::uint32_t, std::unique_ptr<Voice>> music;
|
||||||
|
std::atomic<std::uint32_t> nextHandleId{1};
|
||||||
|
};
|
||||||
|
|
||||||
|
MiniaudioSound::MiniaudioSound() : m_state(std::make_unique<State>()) {}
|
||||||
|
MiniaudioSound::~MiniaudioSound() {
|
||||||
|
if (m_state && m_state->engineReady) {
|
||||||
|
shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MiniaudioSound::MiniaudioSound(MiniaudioSound&&) noexcept = default;
|
||||||
|
MiniaudioSound& MiniaudioSound::operator=(MiniaudioSound&&) noexcept = default;
|
||||||
|
|
||||||
|
void MiniaudioSound::init(int listenerCount) {
|
||||||
|
if (m_state->engineReady) return;
|
||||||
|
m_state->engineConfig = ma_engine_config_init();
|
||||||
|
m_state->engineConfig.listenerCount =
|
||||||
|
listenerCount > 0 ? static_cast<ma_uint32>(listenerCount) : 1;
|
||||||
|
if (ma_engine_init(&m_state->engineConfig, &m_state->engine) !=
|
||||||
|
MA_SUCCESS) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ma_engine_set_volume(&m_state->engine, 1.0f);
|
||||||
|
m_state->engineReady = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MiniaudioSound::shutdown() {
|
||||||
|
if (!m_state->engineReady) return;
|
||||||
|
// Tear down all live voices first.
|
||||||
|
for (auto& [id, voice] : m_state->sounds) {
|
||||||
|
if (voice && voice->inUse) {
|
||||||
|
ma_sound_uninit(&voice->sound);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_state->sounds.clear();
|
||||||
|
for (auto& [id, voice] : m_state->music) {
|
||||||
|
if (voice && voice->inUse) {
|
||||||
|
ma_sound_uninit(&voice->sound);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_state->music.clear();
|
||||||
|
ma_engine_uninit(&m_state->engine);
|
||||||
|
m_state->engineReady = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MiniaudioSound::tick() {
|
||||||
|
if (!m_state->engineReady) return;
|
||||||
|
// Reap finished one-shot sounds (non-looping, not currently playing).
|
||||||
|
// We don't auto-reap music here; music is explicitly stopped by the
|
||||||
|
// game's music system.
|
||||||
|
for (auto it = m_state->sounds.begin(); it != m_state->sounds.end();) {
|
||||||
|
auto& voice = it->second;
|
||||||
|
if (voice && voice->inUse &&
|
||||||
|
!ma_sound_is_playing(&voice->sound) &&
|
||||||
|
!ma_sound_is_looping(&voice->sound)) {
|
||||||
|
ma_sound_uninit(&voice->sound);
|
||||||
|
voice->inUse = false;
|
||||||
|
it = m_state->sounds.erase(it);
|
||||||
|
} else {
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MiniaudioSound::setMasterVolume(float volume) {
|
||||||
|
if (!m_state->engineReady) return;
|
||||||
|
ma_engine_set_volume(&m_state->engine, volume);
|
||||||
|
}
|
||||||
|
|
||||||
|
SoundHandle MiniaudioSound::playSoundFromFile(const std::string& path,
|
||||||
|
const PlaySoundParams& params) {
|
||||||
|
if (!m_state->engineReady) return {};
|
||||||
|
auto voice = std::make_unique<Voice>();
|
||||||
|
if (ma_sound_init_from_file(&m_state->engine, path.c_str(),
|
||||||
|
MA_SOUND_FLAG_ASYNC, nullptr, nullptr,
|
||||||
|
&voice->sound) != MA_SUCCESS) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
ma_sound_set_spatialization_enabled(&voice->sound,
|
||||||
|
params.spatial ? MA_TRUE : MA_FALSE);
|
||||||
|
ma_sound_set_min_distance(&voice->sound, params.minDistance);
|
||||||
|
ma_sound_set_max_distance(&voice->sound, params.maxDistance);
|
||||||
|
ma_sound_set_volume(&voice->sound, params.volume);
|
||||||
|
ma_sound_set_pitch(&voice->sound, params.pitch);
|
||||||
|
ma_sound_set_position(&voice->sound, params.x, params.y, params.z);
|
||||||
|
ma_sound_set_looping(&voice->sound, params.looping ? MA_TRUE : MA_FALSE);
|
||||||
|
ma_sound_start(&voice->sound);
|
||||||
|
voice->inUse = true;
|
||||||
|
|
||||||
|
SoundHandle handle{m_state->nextHandleId.fetch_add(1)};
|
||||||
|
m_state->sounds.emplace(handle.id, std::move(voice));
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MiniaudioSound::stopSound(SoundHandle handle) {
|
||||||
|
auto it = m_state->sounds.find(handle.id);
|
||||||
|
if (it == m_state->sounds.end() || !it->second) return;
|
||||||
|
if (it->second->inUse) {
|
||||||
|
ma_sound_stop(&it->second->sound);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MiniaudioSound::isSoundPlaying(SoundHandle handle) const {
|
||||||
|
auto it = m_state->sounds.find(handle.id);
|
||||||
|
if (it == m_state->sounds.end() || !it->second || !it->second->inUse) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return ma_sound_is_playing(&it->second->sound) != MA_FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MiniaudioSound::setSoundVolume(SoundHandle handle, float volume) {
|
||||||
|
auto it = m_state->sounds.find(handle.id);
|
||||||
|
if (it == m_state->sounds.end() || !it->second || !it->second->inUse)
|
||||||
|
return;
|
||||||
|
ma_sound_set_volume(&it->second->sound, volume);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MiniaudioSound::setSoundPosition(SoundHandle handle, float x, float y,
|
||||||
|
float z) {
|
||||||
|
auto it = m_state->sounds.find(handle.id);
|
||||||
|
if (it == m_state->sounds.end() || !it->second || !it->second->inUse)
|
||||||
|
return;
|
||||||
|
ma_sound_set_position(&it->second->sound, x, y, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MiniaudioSound::setSoundPitch(SoundHandle handle, float pitch) {
|
||||||
|
auto it = m_state->sounds.find(handle.id);
|
||||||
|
if (it == m_state->sounds.end() || !it->second || !it->second->inUse)
|
||||||
|
return;
|
||||||
|
ma_sound_set_pitch(&it->second->sound, pitch);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MiniaudioSound::releaseSound(SoundHandle handle) {
|
||||||
|
auto it = m_state->sounds.find(handle.id);
|
||||||
|
if (it == m_state->sounds.end()) return;
|
||||||
|
if (it->second && it->second->inUse) {
|
||||||
|
ma_sound_uninit(&it->second->sound);
|
||||||
|
it->second->inUse = false;
|
||||||
|
}
|
||||||
|
m_state->sounds.erase(it);
|
||||||
|
}
|
||||||
|
|
||||||
|
MusicHandle MiniaudioSound::playMusicFromFile(const std::string& path,
|
||||||
|
const PlayMusicParams& params) {
|
||||||
|
if (!m_state->engineReady) return {};
|
||||||
|
auto voice = std::make_unique<Voice>();
|
||||||
|
if (ma_sound_init_from_file(&m_state->engine, path.c_str(),
|
||||||
|
MA_SOUND_FLAG_STREAM, nullptr, nullptr,
|
||||||
|
&voice->sound) != MA_SUCCESS) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
ma_sound_set_spatialization_enabled(&voice->sound, MA_FALSE);
|
||||||
|
ma_sound_set_volume(&voice->sound, params.volume);
|
||||||
|
ma_sound_set_pitch(&voice->sound, params.pitch);
|
||||||
|
ma_sound_set_looping(&voice->sound, params.looping ? MA_TRUE : MA_FALSE);
|
||||||
|
ma_sound_start(&voice->sound);
|
||||||
|
voice->inUse = true;
|
||||||
|
|
||||||
|
MusicHandle handle{m_state->nextHandleId.fetch_add(1)};
|
||||||
|
m_state->music.emplace(handle.id, std::move(voice));
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MiniaudioSound::stopMusic(MusicHandle handle) {
|
||||||
|
auto it = m_state->music.find(handle.id);
|
||||||
|
if (it == m_state->music.end() || !it->second) return;
|
||||||
|
if (it->second->inUse) {
|
||||||
|
ma_sound_stop(&it->second->sound);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MiniaudioSound::isMusicPlaying(MusicHandle handle) const {
|
||||||
|
auto it = m_state->music.find(handle.id);
|
||||||
|
if (it == m_state->music.end() || !it->second || !it->second->inUse)
|
||||||
|
return false;
|
||||||
|
return ma_sound_is_playing(&it->second->sound) != MA_FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MiniaudioSound::setMusicVolume(MusicHandle handle, float volume) {
|
||||||
|
auto it = m_state->music.find(handle.id);
|
||||||
|
if (it == m_state->music.end() || !it->second || !it->second->inUse)
|
||||||
|
return;
|
||||||
|
ma_sound_set_volume(&it->second->sound, volume);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MiniaudioSound::setMusicPitch(MusicHandle handle, float pitch) {
|
||||||
|
auto it = m_state->music.find(handle.id);
|
||||||
|
if (it == m_state->music.end() || !it->second || !it->second->inUse)
|
||||||
|
return;
|
||||||
|
ma_sound_set_pitch(&it->second->sound, pitch);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MiniaudioSound::releaseMusic(MusicHandle handle) {
|
||||||
|
auto it = m_state->music.find(handle.id);
|
||||||
|
if (it == m_state->music.end()) return;
|
||||||
|
if (it->second && it->second->inUse) {
|
||||||
|
ma_sound_uninit(&it->second->sound);
|
||||||
|
it->second->inUse = false;
|
||||||
|
}
|
||||||
|
m_state->music.erase(it);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MiniaudioSound::setListenerPosition(int listenerIndex, float x, float y,
|
||||||
|
float z) {
|
||||||
|
if (!m_state->engineReady) return;
|
||||||
|
ma_engine_listener_set_position(&m_state->engine,
|
||||||
|
static_cast<ma_uint32>(listenerIndex), x,
|
||||||
|
y, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MiniaudioSound::setListenerOrientation(int listenerIndex, float forwardX,
|
||||||
|
float forwardY, float forwardZ,
|
||||||
|
float upX, float upY, float upZ) {
|
||||||
|
if (!m_state->engineReady) return;
|
||||||
|
ma_engine_listener_set_direction(&m_state->engine,
|
||||||
|
static_cast<ma_uint32>(listenerIndex),
|
||||||
|
forwardX, forwardY, forwardZ);
|
||||||
|
ma_engine_listener_set_world_up(&m_state->engine,
|
||||||
|
static_cast<ma_uint32>(listenerIndex),
|
||||||
|
upX, upY, upZ);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace platform::sound::miniaudio
|
||||||
|
|
||||||
|
namespace platform_internal {
|
||||||
|
::platform::sound::IPlatformSound& PlatformSound_get() {
|
||||||
|
static ::platform::sound::miniaudio::MiniaudioSound instance;
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
} // namespace platform_internal
|
||||||
61
targets/platform/sound/miniaudio/MiniaudioSound.h
Normal file
61
targets/platform/sound/miniaudio/MiniaudioSound.h
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "platform/sound/IPlatformSound.h"
|
||||||
|
|
||||||
|
// Forward-declare the miniaudio state struct so this header doesn't
|
||||||
|
// need to include miniaudio.h - keeps the include footprint small for
|
||||||
|
// any consumer of the backend header.
|
||||||
|
namespace platform::sound::miniaudio {
|
||||||
|
|
||||||
|
struct State;
|
||||||
|
|
||||||
|
class MiniaudioSound : public IPlatformSound {
|
||||||
|
public:
|
||||||
|
MiniaudioSound();
|
||||||
|
~MiniaudioSound() override;
|
||||||
|
|
||||||
|
// Move-only - the engine owns hardware state.
|
||||||
|
MiniaudioSound(MiniaudioSound&&) noexcept;
|
||||||
|
MiniaudioSound& operator=(MiniaudioSound&&) noexcept;
|
||||||
|
MiniaudioSound(const MiniaudioSound&) = delete;
|
||||||
|
MiniaudioSound& operator=(const MiniaudioSound&) = delete;
|
||||||
|
|
||||||
|
// -- IPlatformSound --
|
||||||
|
|
||||||
|
void init(int listenerCount) override;
|
||||||
|
void shutdown() override;
|
||||||
|
void tick() override;
|
||||||
|
|
||||||
|
void setMasterVolume(float volume) override;
|
||||||
|
|
||||||
|
[[nodiscard]] SoundHandle playSoundFromFile(
|
||||||
|
const std::string& path, const PlaySoundParams& params) override;
|
||||||
|
void stopSound(SoundHandle handle) override;
|
||||||
|
[[nodiscard]] bool isSoundPlaying(SoundHandle handle) const override;
|
||||||
|
void setSoundVolume(SoundHandle handle, float volume) override;
|
||||||
|
void setSoundPosition(SoundHandle handle, float x, float y,
|
||||||
|
float z) override;
|
||||||
|
void setSoundPitch(SoundHandle handle, float pitch) override;
|
||||||
|
void releaseSound(SoundHandle handle) override;
|
||||||
|
|
||||||
|
[[nodiscard]] MusicHandle playMusicFromFile(
|
||||||
|
const std::string& path, const PlayMusicParams& params) override;
|
||||||
|
void stopMusic(MusicHandle handle) override;
|
||||||
|
[[nodiscard]] bool isMusicPlaying(MusicHandle handle) const override;
|
||||||
|
void setMusicVolume(MusicHandle handle, float volume) override;
|
||||||
|
void setMusicPitch(MusicHandle handle, float pitch) override;
|
||||||
|
void releaseMusic(MusicHandle handle) override;
|
||||||
|
|
||||||
|
void setListenerPosition(int listenerIndex, float x, float y,
|
||||||
|
float z) override;
|
||||||
|
void setListenerOrientation(int listenerIndex, float forwardX,
|
||||||
|
float forwardY, float forwardZ, float upX,
|
||||||
|
float upY, float upZ) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_ptr<State> m_state;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace platform::sound::miniaudio
|
||||||
16
targets/platform/sound/sound.h
Normal file
16
targets/platform/sound/sound.h
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "platform/sound/IPlatformSound.h"
|
||||||
|
|
||||||
|
// Function accessor backed by a function-local static (Meyers singleton).
|
||||||
|
// Same pattern as input/renderer/profile/storage/fs - SIOF-safe.
|
||||||
|
//
|
||||||
|
// The macro lets call sites keep using `PlatformSound.foo()` syntax;
|
||||||
|
// the expansion is just a function call returning a reference, which
|
||||||
|
// LTO inlines.
|
||||||
|
|
||||||
|
namespace platform_internal {
|
||||||
|
::platform::sound::IPlatformSound& PlatformSound_get();
|
||||||
|
}
|
||||||
|
|
||||||
|
#define PlatformSound (::platform_internal::PlatformSound_get())
|
||||||
Loading…
Reference in a new issue