Engine²
Open-source game engine written in C++.
Loading...
Searching...
No Matches
SoundManager.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <atomic>
4#include <functional>
5#include <map>
6#include <miniaudio.h>
7#include <string_view>
8#include <unordered_map>
9
10#include "Engine.hpp"
11#include "FunctionContainer.hpp"
12
13namespace Sound::Resource {
14
15using CustomDataCallback = std::function<void(ma_device *pDevice, void *pOutput, ma_uint32 frameCount)>;
16
18 private:
19 ma_result _result;
20 ma_device_config _deviceConfig;
21 ma_device _device;
22 bool _deviceInit = false;
23 ma_engine _engine;
24 bool _engineInit = false;
25
26 // Deferred error reporting for audio callback (avoid logging in real-time callback)
27 // Bit flags: 0x1 = frame count too large, 0x2 = decoder read error, 0x4 = unknown format
28 std::atomic<uint32_t> _callbackErrorFlags{0};
29 static constexpr uint32_t ERROR_FRAME_TOO_LARGE = 0x1;
30 static constexpr uint32_t ERROR_DECODER_READ = 0x2;
31 static constexpr uint32_t ERROR_UNKNOWN_FORMAT = 0x4;
32
33 struct Sound {
34 std::string name;
35 std::string path;
36 ma_decoder decoder;
37 ma_sound engineSound;
38 bool loop = false;
39 bool isPlaying = false;
40 bool isPaused = false;
41 bool hasEngineSound = false;
42 bool usingEngine = false;
43 float volume = 1.0f;
44 ma_uint64 loopStartFrame = 0;
45 ma_uint64 loopEndFrame = 0;
46 bool decoderInitialized = false;
47 };
48
50 using is_transparent = void;
51
52 std::size_t operator()(std::string_view key) const noexcept { return std::hash<std::string_view>{}(key); }
53 };
54
56 using is_transparent = void;
57
58 bool operator()(std::string_view lhs, std::string_view rhs) const noexcept { return lhs == rhs; }
59 };
60
61 std::unordered_map<std::string, Sound, TransparentHash, TransparentEqual> _soundsToPlay;
62
64
65 public:
69 SoundManager() = default;
70
75
79 SoundManager(const SoundManager &) = delete;
80
85
90 SoundManager(SoundManager &&other) noexcept
91 : _result(other._result), _deviceConfig(other._deviceConfig), _device(other._device),
92 _deviceInit(other._deviceInit), _engine(other._engine), _engineInit(other._engineInit),
93 _callbackErrorFlags(other._callbackErrorFlags.load(std::memory_order_relaxed)),
94 _soundsToPlay(std::move(other._soundsToPlay)), _customCallbacks(std::move(other._customCallbacks))
95 {
96 other._deviceInit = false;
97 other._engineInit = false;
98 }
99
105 {
106 if (this != &other)
107 {
108 for (auto &[name, snd] : _soundsToPlay)
109 {
110 if (snd.hasEngineSound)
111 ma_sound_uninit(&snd.engineSound);
112 if (snd.decoderInitialized)
113 ma_decoder_uninit(&snd.decoder);
114 }
115 _soundsToPlay.clear();
116
117 if (_deviceInit)
118 {
119 ma_device_stop(&_device);
120 ma_device_uninit(&_device);
121 _deviceInit = false;
122 }
123 if (_engineInit)
124 {
125 ma_engine_uninit(&_engine);
126 _engineInit = false;
127 }
128 _result = other._result;
129 _deviceConfig = other._deviceConfig;
130 _device = other._device;
131 _deviceInit = other._deviceInit;
132 _engine = other._engine;
133 _engineInit = other._engineInit;
134 _callbackErrorFlags.store(other._callbackErrorFlags.load(std::memory_order_relaxed),
135 std::memory_order_relaxed);
136 _soundsToPlay = std::move(other._soundsToPlay);
137 _customCallbacks = std::move(other._customCallbacks);
138 other._deviceInit = false;
139 other._engineInit = false;
140 }
141 return *this;
142 }
143
159 static void data_callback(ma_device *pDevice, void *pOutput, const void * /*pInput*/, ma_uint32 frameCount)
160 {
161 auto *core = static_cast<Engine::Core *>(pDevice->pUserData);
162 auto &self = core->GetResource<SoundManager>();
163 const ma_uint32 channels = pDevice->playback.channels;
164 const ma_uint32 totalSamples = frameCount * channels;
165
166 std::memset(pOutput, 0, ma_get_bytes_per_frame(pDevice->playback.format, channels) * frameCount);
167
168 const auto &callbacks = self._customCallbacks.GetFunctions();
169 for (const auto &callback : callbacks)
170 {
171 (*callback)(pDevice, pOutput, frameCount);
172 }
173
174 std::array<float, 4096 * 2> mixBuffer{};
175 if (totalSamples > mixBuffer.size())
176 {
177 self._callbackErrorFlags.fetch_or(ERROR_FRAME_TOO_LARGE, std::memory_order_relaxed);
178 return;
179 }
180
181 for (auto &[name, sound] : self._soundsToPlay)
182 {
183 if (sound.usingEngine || !sound.isPlaying || sound.isPaused || !sound.decoderInitialized)
184 continue;
185
186 std::array<float, 4096 * 2> tempBuffer{};
187 ma_uint64 framesRead = 0;
188
189 ma_result result = ma_decoder_read_pcm_frames(&sound.decoder, tempBuffer.data(), frameCount, &framesRead);
190
191 if (result != MA_SUCCESS && result != MA_AT_END)
192 {
193 self._callbackErrorFlags.fetch_or(ERROR_DECODER_READ, std::memory_order_relaxed);
194 }
195
196 for (ma_uint32 i = 0; i < framesRead * channels; ++i)
197 {
198 mixBuffer[i] += tempBuffer[i] * sound.volume;
199 }
200
201 if (framesRead < frameCount)
202 {
203 if (sound.loop)
204 {
205 ma_decoder_seek_to_pcm_frame(&sound.decoder, sound.loopStartFrame);
206 }
207 else if (result == MA_AT_END)
208 {
209 sound.isPlaying = false;
210 }
211 }
212
213 if (sound.loop && sound.loopEndFrame > 0)
214 {
215 ma_uint64 currentFrame = 0;
216 ma_decoder_get_cursor_in_pcm_frames(&sound.decoder, &currentFrame);
217 if (currentFrame >= sound.loopEndFrame)
218 {
219 ma_decoder_seek_to_pcm_frame(&sound.decoder, sound.loopStartFrame);
220 }
221 }
222 }
223
224 switch (pDevice->playback.format)
225 {
226 case ma_format_f32: {
227 auto *output = static_cast<float *>(pOutput);
228 for (ma_uint32 i = 0; i < totalSamples; ++i)
229 {
230 output[i] += std::clamp(mixBuffer[i], -1.0f, 1.0f);
231 }
232 }
233 break;
234 case ma_format_s32: {
235 auto *output = static_cast<int32_t *>(pOutput);
236 for (ma_uint32 i = 0; i < totalSamples; ++i)
237 {
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));
241 }
242 }
243 break;
244 case ma_format_s16: {
245 auto *output = static_cast<int16_t *>(pOutput);
246 for (ma_uint32 i = 0; i < totalSamples; ++i)
247 {
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));
251 }
252 }
253 break;
254 case ma_format_u8: {
255 auto *output = static_cast<uint8_t *>(pOutput);
256 for (ma_uint32 i = 0; i < totalSamples; ++i)
257 {
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));
261 }
262 }
263 break;
264 default: self._callbackErrorFlags.fetch_or(ERROR_UNKNOWN_FORMAT, std::memory_order_relaxed); break;
265 }
266 }
267
273 inline void Init(Engine::Core &core)
274 {
275 _deviceConfig = ma_device_config_init(ma_device_type_playback);
276 _deviceConfig.playback.format = ma_format_unknown;
277 _deviceConfig.playback.channels = 2;
278 _deviceConfig.sampleRate = 44100;
280 _deviceConfig.pUserData = &core;
281
282 _result = ma_device_init(NULL, &_deviceConfig, &_device);
283 if (_result != MA_SUCCESS)
284 {
285 Log::Error(fmt::format("Failed to init audio device: {}", ma_result_description(_result)));
286 return;
287 }
288
289 _result = ma_device_start(&_device);
290 if (_result != MA_SUCCESS)
291 {
292 Log::Error(fmt::format("Failed to start audio device: {}", ma_result_description(_result)));
293 ma_device_uninit(&_device);
294 }
295 else
296 {
297 _deviceInit = true;
298 Log::Info(fmt::format("[Audio] Device started successfully. Device format={}, sample rate={}, channels={}",
299 static_cast<int>(_device.playback.format), _device.sampleRate,
300 _device.playback.channels));
301 }
302 }
303
311 {
312 uint32_t errors = _callbackErrorFlags.exchange(0, std::memory_order_relaxed);
313 if (errors & ERROR_FRAME_TOO_LARGE)
314 {
315 Log::Error("[Audio] Frame count too large for mix buffer");
316 }
317 if (errors & ERROR_DECODER_READ)
318 {
319 Log::Error("[Audio] Decoder read error occurred during playback");
320 }
321 if (errors & ERROR_UNKNOWN_FORMAT)
322 {
323 Log::Error("[Audio] Unknown audio format encountered during playback");
324 }
325 }
326
336 inline void RegisterSound(const std::string &soundName, const std::string &filePath, bool loop = false)
337 {
338 if (_soundsToPlay.contains(soundName))
339 {
340 Log::Warning(fmt::format("Could not register: Sound \"{}\" already exists", soundName));
341 return;
342 }
343
344 Sound sound;
345 sound.name = soundName;
346 sound.path = filePath;
347 sound.loop = loop;
348 sound.decoderInitialized = false;
349 _soundsToPlay.emplace(soundName, std::move(sound));
350 }
351
359 inline void UnregisterSound(const std::string &soundName)
360 {
361 auto it = _soundsToPlay.find(soundName);
362 if (it != _soundsToPlay.end())
363 {
364 if (it->second.hasEngineSound)
365 ma_sound_uninit(&it->second.engineSound);
366 if (it->second.decoderInitialized)
367 ma_decoder_uninit(&it->second.decoder);
368 _soundsToPlay.erase(it);
369 }
370 else
371 {
372 Log::Error(fmt::format("Could not unregister: Sound \"{}\" does not exist", soundName));
373 }
374 }
375
383 inline void Play(const std::string &soundName)
384 {
385 auto it = _soundsToPlay.find(soundName);
386 if (it != _soundsToPlay.end())
387 {
388 auto &snd = it->second;
389
390 if (!snd.usingEngine && !snd.decoderInitialized)
391 {
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);
394 if (_result != MA_SUCCESS)
395 {
396 Log::Error(fmt::format("Failed to initialize the audio decoder for '{}': {}", snd.name,
397 ma_result_description(_result)));
398 return;
399 }
400 snd.decoderInitialized = true;
401 }
402
403 snd.isPlaying = true;
404 snd.isPaused = false;
405
406 if (snd.usingEngine && snd.hasEngineSound)
407 {
408 ma_sound_start(&snd.engineSound);
409 }
410 }
411 else
412 {
413 Log::Error(fmt::format("Could not play: Sound \"{}\" does not exist", soundName));
414 }
415 }
416
424 inline void Stop(const std::string &soundName)
425 {
426 auto it = _soundsToPlay.find(soundName);
427 if (it != _soundsToPlay.end())
428 {
429 auto &snd = it->second;
430 snd.isPlaying = false;
431 snd.isPaused = false;
432
433 if (snd.usingEngine && snd.hasEngineSound)
434 {
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);
439 }
440 else
441 {
442 if (snd.decoderInitialized)
443 ma_decoder_seek_to_pcm_frame(&snd.decoder, 0);
444 }
445 }
446 else
447 {
448 Log::Error(fmt::format("Could not stop: Sound \"{}\" does not exist", soundName));
449 }
450 }
451
459 inline void Pause(const std::string &soundName)
460 {
461 auto it = _soundsToPlay.find(soundName);
462 if (it != _soundsToPlay.end())
463 {
464 auto &snd = it->second;
465 snd.isPaused = true;
466
467 if (snd.usingEngine && snd.hasEngineSound)
468 {
469 ma_sound_stop(&snd.engineSound);
470 }
471 }
472 else
473 {
474 Log::Error(fmt::format("Could not pause: Sound \"{}\" does not exist", soundName));
475 }
476 }
477
485 inline bool IsPlaying(const std::string &soundName)
486 {
487 auto it = _soundsToPlay.find(soundName);
488 if (it != _soundsToPlay.end())
489 {
490 return it->second.isPlaying && !it->second.isPaused;
491 }
492 Log::Error(fmt::format("Could not verify playing status: Sound \"{}\" does not exist", soundName));
493 return false;
494 }
495
502 inline void SetVolume(const std::string &soundName, float volume)
503 {
504 auto it = _soundsToPlay.find(soundName);
505 if (it != _soundsToPlay.end())
506 {
507 it->second.volume = std::clamp(volume, 0.0f, 1.0f);
508 }
509 else
510 {
511 Log::Error(fmt::format("Could not set volume: Sound \"{}\" does not exist", soundName));
512 }
513 }
514
520 inline void SetPitch(const std::string &soundName, float pitch)
521 {
522 auto it = _soundsToPlay.find(soundName);
523 if (it != _soundsToPlay.end())
524 {
525 auto &snd = it->second;
526 if (pitch <= 0.01f)
527 pitch = 0.01f;
528
529 if (!_engineInit)
530 {
531 ma_engine_config cfg = ma_engine_config_init();
532 cfg.sampleRate = _device.sampleRate;
533 ma_result r = ma_engine_init(&cfg, &_engine);
534 if (r != MA_SUCCESS)
535 {
536 Log::Error(fmt::format("Failed to init ma_engine: {}", ma_result_description(r)));
537 return;
538 }
539 _engineInit = true;
540 }
541
542 if (!snd.hasEngineSound)
543 {
544 ma_result r = ma_sound_init_from_file(&_engine, snd.path.c_str(), MA_SOUND_FLAG_STREAM, NULL, NULL,
545 &snd.engineSound);
546 if (r != MA_SUCCESS)
547 {
548 Log::Error(fmt::format("Failed to init ma_sound for '{}': {}", snd.name, ma_result_description(r)));
549 return;
550 }
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);
555
556 ma_uint64 cursor = 0u;
557 if (snd.decoderInitialized)
558 {
559 ma_decoder_get_cursor_in_pcm_frames(&snd.decoder, &cursor);
560 }
561 ma_sound_seek_to_pcm_frame(&snd.engineSound, cursor);
562
563 if (snd.isPlaying && !snd.isPaused)
564 {
565 ma_sound_start(&snd.engineSound);
566 }
567 }
568
569 if (snd.hasEngineSound)
570 {
571 ma_sound_set_pitch(&snd.engineSound, pitch);
572 }
573 }
574 else
575 {
576 Log::Error(fmt::format("Could not set pitch: Sound \"{}\" does not exist", soundName));
577 }
578 }
579
586 inline void SetLoop(const std::string &soundName, bool shouldLoop)
587 {
588 auto it = _soundsToPlay.find(soundName);
589 if (it != _soundsToPlay.end())
590 {
591 it->second.loop = shouldLoop;
592 }
593 else
594 {
595 Log::Error(fmt::format("Could not set loop: Sound \"{}\" does not exist", soundName));
596 }
597 }
598
606 inline void SetLoopPoints(const std::string &soundName, float startSeconds, float endSeconds = 0)
607 {
608 auto it = _soundsToPlay.find(soundName);
609 if (it != _soundsToPlay.end())
610 {
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)
615 {
617 fmt::format("Something went wrong while computing PCM frames length of sound \"{}\"", soundName));
618 return;
619 }
620
621 ma_uint64 startFrame = static_cast<ma_uint64>(startSeconds * sampleRate);
622 ma_uint64 endFrame = (endSeconds > 0.0f) ? static_cast<ma_uint64>(endSeconds * sampleRate) : totalFrames;
623
624 if (startFrame >= totalFrames || endFrame > totalFrames || startFrame >= endFrame)
625 {
626 Log::Warning(fmt::format("Invalid loop range for \"{}\": {}s to {}s, ignored", soundName, startSeconds,
627 endSeconds));
628 return;
629 }
630 it->second.loopStartFrame = startFrame;
631 it->second.loopEndFrame = endFrame;
632 }
633 else
634 {
635 Log::Error(fmt::format("Could not set loop points: Sound \"{}\" does not exist", soundName));
636 }
637 }
638
648 inline double GetPlayPosition(const std::string &soundName)
649 {
650 auto it = _soundsToPlay.find(soundName);
651 if (it == _soundsToPlay.end())
652 {
653 Log::Error(fmt::format("Could not get the playback position: Sound \"{}\" does not exist", soundName));
654 return -1.0f;
655 }
656
657 Sound &sound = it->second;
658 if (!sound.isPlaying)
659 {
660 Log::Warning(fmt::format("Sound \"{}\" is not currently playing", soundName));
661 return 0.0f;
662 }
663
664 ma_uint64 currentFrame;
665 _result = ma_decoder_get_cursor_in_pcm_frames(&sound.decoder, &currentFrame);
666 if (_result != MA_SUCCESS)
667 {
668 Log::Error(fmt::format("Could not get the playback position: {}", ma_result_description(_result)));
669 return -1.0f;
670 }
671 double position = static_cast<double>(currentFrame) / sound.decoder.outputSampleRate;
672 return position;
673 }
674
685 {
686 return _customCallbacks.AddFunction(callback);
687 }
688
696 {
697 if (!_customCallbacks.Contains(id))
698 {
699 Log::Error(fmt::format("Could not remove: Custom callback with ID {} does not exist", id));
700 return false;
701 }
702 _customCallbacks.DeleteFunction(id);
703 return true;
704 }
705
712 inline bool HasCustomCallback(FunctionUtils::FunctionID id) const { return _customCallbacks.Contains(id); }
713
718 {
719 // Delete all callbacks by iterating and collecting IDs first
720 std::vector<FunctionUtils::FunctionID> ids;
721 for (const auto &func : _customCallbacks.GetFunctions())
722 {
723 ids.push_back(func->GetID());
724 }
725 for (auto id : ids)
726 {
727 _customCallbacks.DeleteFunction(id);
728 }
729 }
730};
731
732} // namespace Sound::Resource
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
void is_transparent
Definition SoundManager.hpp:56
bool operator()(std::string_view lhs, std::string_view rhs) const noexcept
Definition SoundManager.hpp:58
void is_transparent
Definition SoundManager.hpp:50
std::size_t operator()(std::string_view key) const noexcept
Definition SoundManager.hpp:52