8#include <unordered_map>
15using CustomDataCallback = std::function<void(ma_device *pDevice,
void *pOutput, ma_uint32 frameCount)>;
52 std::size_t
operator()(std::string_view key)
const noexcept {
return std::hash<std::string_view>{}(key); }
58 bool operator()(std::string_view lhs, std::string_view rhs)
const noexcept {
return lhs == rhs; }
61 std::unordered_map<std::string, Sound, TransparentHash, TransparentEqual>
_soundsToPlay;
96 other._deviceInit =
false;
97 other._engineInit =
false;
110 if (snd.hasEngineSound)
111 ma_sound_uninit(&snd.engineSound);
112 if (snd.decoderInitialized)
113 ma_decoder_uninit(&snd.decoder);
135 std::memory_order_relaxed);
138 other._deviceInit =
false;
139 other._engineInit =
false;
159 static void data_callback(ma_device *pDevice,
void *pOutput,
const void * , ma_uint32 frameCount)
161 auto *core =
static_cast<Engine::Core *
>(pDevice->pUserData);
163 const ma_uint32 channels = pDevice->playback.channels;
164 const ma_uint32 totalSamples = frameCount * channels;
166 std::memset(pOutput, 0, ma_get_bytes_per_frame(pDevice->playback.format, channels) * frameCount);
168 const auto &callbacks = self._customCallbacks.GetFunctions();
169 for (
const auto &callback : callbacks)
171 (*callback)(pDevice, pOutput, frameCount);
174 std::array<float, 4096 * 2> mixBuffer{};
175 if (totalSamples > mixBuffer.size())
181 for (
auto &[name, sound] : self._soundsToPlay)
183 if (sound.usingEngine || !sound.isPlaying || sound.isPaused || !sound.decoderInitialized)
186 std::array<float, 4096 * 2> tempBuffer{};
187 ma_uint64 framesRead = 0;
189 ma_result result = ma_decoder_read_pcm_frames(&sound.decoder, tempBuffer.data(), frameCount, &framesRead);
191 if (result != MA_SUCCESS && result != MA_AT_END)
196 for (ma_uint32 i = 0; i < framesRead * channels; ++i)
198 mixBuffer[i] += tempBuffer[i] * sound.volume;
201 if (framesRead < frameCount)
205 ma_decoder_seek_to_pcm_frame(&sound.decoder, sound.loopStartFrame);
207 else if (result == MA_AT_END)
209 sound.isPlaying =
false;
213 if (sound.loop && sound.loopEndFrame > 0)
215 ma_uint64 currentFrame = 0;
216 ma_decoder_get_cursor_in_pcm_frames(&sound.decoder, ¤tFrame);
217 if (currentFrame >= sound.loopEndFrame)
219 ma_decoder_seek_to_pcm_frame(&sound.decoder, sound.loopStartFrame);
224 switch (pDevice->playback.format)
226 case ma_format_f32: {
227 auto *output =
static_cast<float *
>(pOutput);
228 for (ma_uint32 i = 0; i < totalSamples; ++i)
230 output[i] += std::clamp(mixBuffer[i], -1.0f, 1.0f);
234 case ma_format_s32: {
235 auto *output =
static_cast<int32_t *
>(pOutput);
236 for (ma_uint32 i = 0; i < totalSamples; ++i)
238 float sample = std::clamp(mixBuffer[i], -1.0f, 1.0f);
239 const int64_t mixed =
static_cast<int64_t
>(output[i]) +
static_cast<int64_t
>(sample * 2147483647.0f);
240 output[i] =
static_cast<int32_t
>(std::clamp<int64_t>(mixed, INT32_MIN, INT32_MAX));
244 case ma_format_s16: {
245 auto *output =
static_cast<int16_t *
>(pOutput);
246 for (ma_uint32 i = 0; i < totalSamples; ++i)
248 float sample = std::clamp(mixBuffer[i], -1.0f, 1.0f);
249 const int32_t mixed =
static_cast<int32_t
>(output[i]) +
static_cast<int32_t
>(sample * 32767.0f);
250 output[i] =
static_cast<int16_t
>(std::clamp<int32_t>(mixed, INT16_MIN, INT16_MAX));
255 auto *output =
static_cast<uint8_t *
>(pOutput);
256 for (ma_uint32 i = 0; i < totalSamples; ++i)
258 float sample = std::clamp(mixBuffer[i], -1.0f, 1.0f);
259 const int mixed =
static_cast<int>(output[i]) +
static_cast<int>(sample * 127.5f);
260 output[i] =
static_cast<uint8_t
>(std::clamp(mixed, 0, 255));
264 default: self._callbackErrorFlags.fetch_or(
ERROR_UNKNOWN_FORMAT, std::memory_order_relaxed);
break;
275 _deviceConfig = ma_device_config_init(ma_device_type_playback);
285 Log::Error(fmt::format(
"Failed to init audio device: {}", ma_result_description(
_result)));
292 Log::Error(fmt::format(
"Failed to start audio device: {}", ma_result_description(
_result)));
298 Log::Info(fmt::format(
"[Audio] Device started successfully. Device format={}, sample rate={}, channels={}",
315 Log::Error(
"[Audio] Frame count too large for mix buffer");
319 Log::Error(
"[Audio] Decoder read error occurred during playback");
323 Log::Error(
"[Audio] Unknown audio format encountered during playback");
336 inline void RegisterSound(
const std::string &soundName,
const std::string &filePath,
bool loop =
false)
340 Log::Warning(fmt::format(
"Could not register: Sound \"{}\" already exists", soundName));
345 sound.
name = soundName;
346 sound.
path = filePath;
364 if (it->second.hasEngineSound)
365 ma_sound_uninit(&it->second.engineSound);
366 if (it->second.decoderInitialized)
367 ma_decoder_uninit(&it->second.decoder);
372 Log::Error(fmt::format(
"Could not unregister: Sound \"{}\" does not exist", soundName));
383 inline void Play(
const std::string &soundName)
388 auto &snd = it->second;
390 if (!snd.usingEngine && !snd.decoderInitialized)
392 ma_decoder_config decoderConfig = ma_decoder_config_init(ma_format_f32, 2, 44100);
393 _result = ma_decoder_init_file(snd.path.c_str(), &decoderConfig, &snd.decoder);
396 Log::Error(fmt::format(
"Failed to initialize the audio decoder for '{}': {}", snd.name,
397 ma_result_description(
_result)));
400 snd.decoderInitialized =
true;
403 snd.isPlaying =
true;
404 snd.isPaused =
false;
406 if (snd.usingEngine && snd.hasEngineSound)
408 ma_sound_start(&snd.engineSound);
413 Log::Error(fmt::format(
"Could not play: Sound \"{}\" does not exist", soundName));
424 inline void Stop(
const std::string &soundName)
429 auto &snd = it->second;
430 snd.isPlaying =
false;
431 snd.isPaused =
false;
433 if (snd.usingEngine && snd.hasEngineSound)
435 ma_sound_stop(&snd.engineSound);
436 ma_sound_seek_to_pcm_frame(&snd.engineSound, 0);
437 if (snd.decoderInitialized)
438 ma_decoder_seek_to_pcm_frame(&snd.decoder, 0);
442 if (snd.decoderInitialized)
443 ma_decoder_seek_to_pcm_frame(&snd.decoder, 0);
448 Log::Error(fmt::format(
"Could not stop: Sound \"{}\" does not exist", soundName));
459 inline void Pause(
const std::string &soundName)
464 auto &snd = it->second;
467 if (snd.usingEngine && snd.hasEngineSound)
469 ma_sound_stop(&snd.engineSound);
474 Log::Error(fmt::format(
"Could not pause: Sound \"{}\" does not exist", soundName));
490 return it->second.isPlaying && !it->second.isPaused;
492 Log::Error(fmt::format(
"Could not verify playing status: Sound \"{}\" does not exist", soundName));
502 inline void SetVolume(
const std::string &soundName,
float volume)
507 it->second.volume = std::clamp(volume, 0.0f, 1.0f);
511 Log::Error(fmt::format(
"Could not set volume: Sound \"{}\" does not exist", soundName));
520 inline void SetPitch(
const std::string &soundName,
float pitch)
525 auto &snd = it->second;
531 ma_engine_config cfg = ma_engine_config_init();
532 cfg.sampleRate =
_device.sampleRate;
533 ma_result r = ma_engine_init(&cfg, &
_engine);
536 Log::Error(fmt::format(
"Failed to init ma_engine: {}", ma_result_description(r)));
542 if (!snd.hasEngineSound)
544 ma_result r = ma_sound_init_from_file(&
_engine, snd.path.c_str(), MA_SOUND_FLAG_STREAM, NULL, NULL,
548 Log::Error(fmt::format(
"Failed to init ma_sound for '{}': {}", snd.name, ma_result_description(r)));
551 snd.hasEngineSound =
true;
552 snd.usingEngine =
true;
553 ma_sound_set_looping(&snd.engineSound, snd.loop ? MA_TRUE : MA_FALSE);
554 ma_sound_set_volume(&snd.engineSound, snd.volume);
556 ma_uint64 cursor = 0u;
557 if (snd.decoderInitialized)
559 ma_decoder_get_cursor_in_pcm_frames(&snd.decoder, &cursor);
561 ma_sound_seek_to_pcm_frame(&snd.engineSound, cursor);
563 if (snd.isPlaying && !snd.isPaused)
565 ma_sound_start(&snd.engineSound);
569 if (snd.hasEngineSound)
571 ma_sound_set_pitch(&snd.engineSound, pitch);
576 Log::Error(fmt::format(
"Could not set pitch: Sound \"{}\" does not exist", soundName));
586 inline void SetLoop(
const std::string &soundName,
bool shouldLoop)
591 it->second.loop = shouldLoop;
595 Log::Error(fmt::format(
"Could not set loop: Sound \"{}\" does not exist", soundName));
606 inline void SetLoopPoints(
const std::string &soundName,
float startSeconds,
float endSeconds = 0)
611 auto &decoder = it->second.decoder;
612 ma_uint64 sampleRate = decoder.outputSampleRate;
613 ma_uint64 totalFrames;
614 if (ma_decoder_get_length_in_pcm_frames(&decoder, &totalFrames) != MA_SUCCESS)
617 fmt::format(
"Something went wrong while computing PCM frames length of sound \"{}\"", soundName));
621 ma_uint64 startFrame =
static_cast<ma_uint64
>(startSeconds * sampleRate);
622 ma_uint64 endFrame = (endSeconds > 0.0f) ?
static_cast<ma_uint64
>(endSeconds * sampleRate) : totalFrames;
624 if (startFrame >= totalFrames || endFrame > totalFrames || startFrame >= endFrame)
626 Log::Warning(fmt::format(
"Invalid loop range for \"{}\": {}s to {}s, ignored", soundName, startSeconds,
630 it->second.loopStartFrame = startFrame;
631 it->second.loopEndFrame = endFrame;
635 Log::Error(fmt::format(
"Could not set loop points: Sound \"{}\" does not exist", soundName));
653 Log::Error(fmt::format(
"Could not get the playback position: Sound \"{}\" does not exist", soundName));
657 Sound &sound = it->second;
660 Log::Warning(fmt::format(
"Sound \"{}\" is not currently playing", soundName));
664 ma_uint64 currentFrame;
665 _result = ma_decoder_get_cursor_in_pcm_frames(&sound.
decoder, ¤tFrame);
668 Log::Error(fmt::format(
"Could not get the playback position: {}", ma_result_description(
_result)));
671 double position =
static_cast<double>(currentFrame) / sound.
decoder.outputSampleRate;
699 Log::Error(fmt::format(
"Could not remove: Custom callback with ID {} does not exist",
id));
720 std::vector<FunctionUtils::FunctionID> ids;
723 ids.push_back(func->GetID());
The core is the place where all the data of the engine is stored. It contains the registry (entities)...
Definition Core.hpp:33
TResource & GetResource()
Get a reference of a resource.
Definition Core.inl:14
Container for functions, allowing for dynamic storage and invocation.
Definition FunctionContainer.hpp:14
void Play(const std::string &soundName)
Play the target sound.
Definition SoundManager.hpp:383
ma_engine _engine
Definition SoundManager.hpp:23
bool _engineInit
Definition SoundManager.hpp:24
void Stop(const std::string &soundName)
Stop the target sound.
Definition SoundManager.hpp:424
void CheckCallbackErrors()
Check and log any deferred errors from the audio callback.
Definition SoundManager.hpp:310
void RegisterSound(const std::string &soundName, const std::string &filePath, bool loop=false)
Register the provided sound file.
Definition SoundManager.hpp:336
ma_result _result
Definition SoundManager.hpp:19
void SetPitch(const std::string &soundName, float pitch)
Set the playback pitch (speed) for a sound.
Definition SoundManager.hpp:520
void SetLoop(const std::string &soundName, bool shouldLoop)
Set whether the sound should loop.
Definition SoundManager.hpp:586
bool HasCustomCallback(FunctionUtils::FunctionID id) const
Check if a custom callback exists by its ID.
Definition SoundManager.hpp:712
SoundManager(SoundManager &&other) noexcept
Definition SoundManager.hpp:90
bool _deviceInit
Definition SoundManager.hpp:22
ma_device _device
Definition SoundManager.hpp:21
SoundManager(const SoundManager &)=delete
SoundManager & operator=(SoundManager &&other) noexcept
Definition SoundManager.hpp:104
void SetVolume(const std::string &soundName, float volume)
Set the volume of a specific sound.
Definition SoundManager.hpp:502
bool IsPlaying(const std::string &soundName)
Check if the target sound is playing.
Definition SoundManager.hpp:485
std::unordered_map< std::string, Sound, TransparentHash, TransparentEqual > _soundsToPlay
Definition SoundManager.hpp:61
static void data_callback(ma_device *pDevice, void *pOutput, const void *, ma_uint32 frameCount)
Audio data callback used by the playback device to fill the output buffer.
Definition SoundManager.hpp:159
double GetPlayPosition(const std::string &soundName)
Get the current playback position of a sound in seconds.
Definition SoundManager.hpp:648
SoundManager & operator=(const SoundManager &)=delete
void SetLoopPoints(const std::string &soundName, float startSeconds, float endSeconds=0)
Set the loop start and end points for a sound.
Definition SoundManager.hpp:606
ma_device_config _deviceConfig
Definition SoundManager.hpp:20
FunctionUtils::FunctionID AddCustomCallback(const CustomDataCallback &callback)
Definition SoundManager.hpp:684
static constexpr uint32_t ERROR_UNKNOWN_FORMAT
Definition SoundManager.hpp:31
void ClearCustomCallbacks()
Clear all custom callbacks.
Definition SoundManager.hpp:717
std::atomic< uint32_t > _callbackErrorFlags
Definition SoundManager.hpp:28
FunctionUtils::FunctionContainer< void, ma_device *, void *, ma_uint32 > _customCallbacks
Definition SoundManager.hpp:63
static constexpr uint32_t ERROR_FRAME_TOO_LARGE
Definition SoundManager.hpp:29
void UnregisterSound(const std::string &soundName)
Unregister the target sound file from the sound vector.
Definition SoundManager.hpp:359
static constexpr uint32_t ERROR_DECODER_READ
Definition SoundManager.hpp:30
void Init(Engine::Core &core)
Initialize the sound system.
Definition SoundManager.hpp:273
bool RemoveCustomCallback(FunctionUtils::FunctionID id)
Remove a custom audio callback by its ID.
Definition SoundManager.hpp:695
void Pause(const std::string &soundName)
Pause the target sound.
Definition SoundManager.hpp:459
std::size_t FunctionID
FunctionID class to represent a unique identifier for functions.
Definition FunctionID.hpp:9
void Warning(const T &msg) noexcept
Definition Logger.hpp:49
void Info(const T &msg) noexcept
Definition Logger.hpp:47
void Error(const T &msg) noexcept
Definition Logger.hpp:51
Definition SoundManager.cpp:5
std::function< void(ma_device *pDevice, void *pOutput, ma_uint32 frameCount)> CustomDataCallback
Definition SoundManager.hpp:15
Definition PluginSound.hpp:5
std::string path
Definition SoundManager.hpp:35
bool usingEngine
Definition SoundManager.hpp:42
bool isPaused
Definition SoundManager.hpp:40
ma_uint64 loopStartFrame
Definition SoundManager.hpp:44
bool decoderInitialized
Definition SoundManager.hpp:46
ma_sound engineSound
Definition SoundManager.hpp:37
bool hasEngineSound
Definition SoundManager.hpp:41
bool isPlaying
Definition SoundManager.hpp:39
ma_decoder decoder
Definition SoundManager.hpp:36
bool loop
Definition SoundManager.hpp:38
ma_uint64 loopEndFrame
Definition SoundManager.hpp:45
std::string name
Definition SoundManager.hpp:34
float volume
Definition SoundManager.hpp:43
Definition SoundManager.hpp:55
void is_transparent
Definition SoundManager.hpp:56
bool operator()(std::string_view lhs, std::string_view rhs) const noexcept
Definition SoundManager.hpp:58
Definition SoundManager.hpp:49
void is_transparent
Definition SoundManager.hpp:50
std::size_t operator()(std::string_view key) const noexcept
Definition SoundManager.hpp:52