208 lines
5.3 KiB
C++
208 lines
5.3 KiB
C++
#include "AudioEngine.h"
|
|
#include <iostream>
|
|
|
|
// Static member definitions
|
|
ma_engine AudioEngine::s_Engine;
|
|
std::unordered_map<uint64_t, ma_sound*> AudioEngine::s_SoundMap;
|
|
std::unordered_map<uint64_t, PlayingInfo> AudioEngine::s_PlayingInfoMap;
|
|
|
|
|
|
std::vector<float> AudioEngine::s_MasterVU;
|
|
|
|
|
|
bool AudioEngine::Init() {
|
|
ma_engine_config config = ma_engine_config_init();
|
|
config.onProcess = AudioEngine::EngineProcessCallback;
|
|
config.pProcessUserData = nullptr;
|
|
|
|
ma_result r = ma_engine_init(&config, &s_Engine);
|
|
if (r != MA_SUCCESS) {
|
|
Logger::LogError("AudioEngine: ma_engine_init failed (%d)", r);
|
|
return false;
|
|
}
|
|
|
|
Logger::LogVerbose("AudioEngine initialized with master-output callback");
|
|
return true;
|
|
}
|
|
|
|
|
|
void AudioEngine::Shutdown() {
|
|
// Uninit & delete every sound
|
|
for (auto& kv : s_SoundMap) {
|
|
ma_sound_uninit(kv.second);
|
|
delete kv.second;
|
|
}
|
|
s_SoundMap.clear();
|
|
s_PlayingInfoMap.clear();
|
|
ma_engine_uninit(&s_Engine);
|
|
}
|
|
|
|
void AudioEngine::Play(uint64_t uaid, bool loop) {
|
|
// 1) Tear down existing instance (if any)
|
|
Cleanup(uaid);
|
|
|
|
// 2) Fetch and validate asset
|
|
const AssetInfo* asset = AssetManager::GetAssetByID(uaid);
|
|
if (!asset || !asset->loaded || asset->type != AssetType::Audio) {
|
|
Logger::LogError("AudioEngine::Play invalid asset %llu", uaid);
|
|
return;
|
|
}
|
|
|
|
// 3) Heap-allocate and init the sound from file
|
|
ma_sound* sound = new ma_sound;
|
|
if (ma_sound_init_from_file(
|
|
&s_Engine,
|
|
asset->path.c_str(),
|
|
/*flags=*/0,
|
|
/*pGroup=*/nullptr,
|
|
/*pFence=*/nullptr,
|
|
sound
|
|
) != MA_SUCCESS)
|
|
{
|
|
Logger::LogError("AudioEngine::Play failed to load '%s'", asset->path.c_str());
|
|
delete sound;
|
|
return;
|
|
}
|
|
|
|
// 4) Store and start playback
|
|
s_SoundMap[uaid] = sound;
|
|
ma_sound_set_looping(sound, loop ? MA_TRUE : MA_FALSE);
|
|
ma_sound_start(sound);
|
|
|
|
// 5) Compute totalTime
|
|
ma_uint64 frames = 0;
|
|
ma_sound_get_length_in_pcm_frames(sound, &frames);
|
|
double sampleRate = ma_engine_get_sample_rate(&s_Engine);
|
|
|
|
// 6) Push PlayingInfo
|
|
PlayingInfo info;
|
|
info.uaid = uaid;
|
|
info.name = asset->filename;
|
|
info.currentTime= 0.0;
|
|
info.totalTime = double(frames) / sampleRate;
|
|
info.looping = loop;
|
|
info.hasStarted = false;
|
|
info.sound = sound;
|
|
|
|
s_PlayingInfoMap[uaid] = info;
|
|
}
|
|
|
|
void AudioEngine::Stop(uint64_t uaid) {
|
|
if (auto it = s_SoundMap.find(uaid); it != s_SoundMap.end()) {
|
|
ma_sound_stop(it->second);
|
|
}
|
|
}
|
|
|
|
void AudioEngine::StopAll() {
|
|
for (auto& kv : s_SoundMap) {
|
|
ma_sound_stop(kv.second);
|
|
}
|
|
}
|
|
|
|
void AudioEngine::Unload(uint64_t uaid) {
|
|
Cleanup(uaid);
|
|
}
|
|
|
|
|
|
|
|
|
|
void AudioEngine::CleanupSound(uint64_t uaid)
|
|
{
|
|
auto itSound = s_SoundMap.find(uaid);
|
|
if (itSound != s_SoundMap.end()) {
|
|
ma_sound_uninit(itSound->second);
|
|
delete itSound->second;
|
|
s_SoundMap.erase(itSound);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
void AudioEngine::Update()
|
|
{
|
|
for (auto it = s_PlayingInfoMap.begin(); it != s_PlayingInfoMap.end(); )
|
|
{
|
|
uint64_t uaid = it->first;
|
|
PlayingInfo &info = it->second;
|
|
ma_sound* s = info.sound;
|
|
|
|
bool shouldErase = (!s) ||
|
|
(!info.looping && info.hasStarted && !ma_sound_is_playing(s));
|
|
|
|
if (shouldErase) {
|
|
CleanupSound(uaid);
|
|
|
|
it = s_PlayingInfoMap.erase(it);
|
|
} else {
|
|
|
|
if (!info.hasStarted && ma_sound_is_playing(s)) {
|
|
info.hasStarted = true;
|
|
}
|
|
|
|
ma_uint64 cursor = 0;
|
|
if (ma_sound_get_cursor_in_pcm_frames(s, &cursor) == MA_SUCCESS) {
|
|
info.currentTime = double(cursor) / ma_engine_get_sample_rate(&s_Engine);
|
|
}
|
|
|
|
++it;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void AudioEngine::SetVolume(uint64_t uaid, float v) {
|
|
if (auto it = s_SoundMap.find(uaid); it != s_SoundMap.end()) {
|
|
ma_sound_set_volume(it->second, v);
|
|
}
|
|
}
|
|
|
|
void AudioEngine::SetLooping(uint64_t uaid, bool loop) {
|
|
if (auto it = s_SoundMap.find(uaid); it != s_SoundMap.end()) {
|
|
ma_sound_set_looping(it->second, loop ? MA_TRUE : MA_FALSE);
|
|
}
|
|
}
|
|
|
|
void AudioEngine::Cleanup(uint64_t uaid) {
|
|
if (auto it = s_SoundMap.find(uaid); it != s_SoundMap.end()) {
|
|
ma_sound_uninit(it->second);
|
|
delete it->second;
|
|
s_SoundMap.erase(it);
|
|
}
|
|
s_PlayingInfoMap.erase(uaid);
|
|
}
|
|
|
|
const std::unordered_map<uint64_t, PlayingInfo>& AudioEngine::GetPlayingInfoMap() {
|
|
return s_PlayingInfoMap;
|
|
}
|
|
|
|
|
|
void AudioEngine::EngineProcessCallback(
|
|
void* /*pUserData*/,
|
|
float* pFramesOut,
|
|
ma_uint64 frameCount)
|
|
{
|
|
// Fetch the playback device and its channel count
|
|
ma_device* pDevice = ma_engine_get_device(&s_Engine);
|
|
uint32_t channels = pDevice->playback.channels;
|
|
|
|
// Ensure our master VU vector matches the channel count
|
|
s_MasterVU.assign(channels, 0.0f);
|
|
|
|
// Compute peak per channel
|
|
for (ma_uint64 i = 0; i < frameCount; ++i) {
|
|
for (uint32_t ch = 0; ch < channels; ++ch) {
|
|
float v = fabsf(pFramesOut[i * channels + ch]);
|
|
if (v > s_MasterVU[ch]) {
|
|
s_MasterVU[ch] = v;
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
const std::vector<float>& AudioEngine::GetMasterVU()
|
|
{
|
|
return s_MasterVU;
|
|
}
|