mirror of
https://github.com/HarbourMasters/SpaghettiKart
synced 2026-08-12 04:07:35 -04:00
271 lines
8.9 KiB
C++
271 lines
8.9 KiB
C++
#include "HMAS.h"
|
|
|
|
#define MINIAUDIO_IMPLEMENTATION
|
|
#ifdef __SWITCH__
|
|
#define MA_NO_RUNTIME_LINKING
|
|
#endif
|
|
#include "audio/miniaudio.h"
|
|
#include <spdlog/spdlog.h>
|
|
#include "port/Engine.h"
|
|
#include "sounds.h"
|
|
|
|
HMAS::HMAS() {
|
|
ma_result result;
|
|
ma_engine_config engine = ma_engine_config_init();
|
|
|
|
engine.channels = 2;
|
|
engine.noDevice = MA_TRUE;
|
|
engine.sampleRate = GameEngine_GetSampleRate();
|
|
|
|
result = ma_engine_init(&engine, &gAudioEngine);
|
|
if (result != MA_SUCCESS) {
|
|
SPDLOG_ERROR("Failed to initialize audio engine: {}", ma_result_description(result));
|
|
return;
|
|
}
|
|
}
|
|
|
|
void HMAS::RegisterSound(HMAS_AudioId id, const std::string& filePath, HMAS_Info info) {
|
|
if (gRegistry.find(id) != gRegistry.end()) {
|
|
SPDLOG_WARN("Sound with ID {} already registered", static_cast<int>(id));
|
|
return;
|
|
}
|
|
|
|
ma_result result = ma_sound_init_from_file(&gAudioEngine, filePath.c_str(), 0, NULL, NULL, &gRegistry[id].sound);
|
|
if (result != MA_SUCCESS) {
|
|
SPDLOG_ERROR("Failed to load sound from file {}: {}", filePath, ma_result_description(result));
|
|
return;
|
|
}
|
|
|
|
if(info.loop.start != -1 && info.loop.end != -1) {
|
|
ma_data_source* source = ma_sound_get_data_source(&gRegistry[id].sound);
|
|
ma_data_source_set_loop_point_in_pcm_frames(source, info.loop.start, info.loop.end);
|
|
}
|
|
|
|
gRegistry[id].info = info;
|
|
SPDLOG_INFO("Sound with ID {} registered from file {}", static_cast<int>(id), filePath);
|
|
}
|
|
|
|
void HMAS::RegisterSound(HMAS_AudioId id, uint8_t* data, uint32_t size, HMAS_Info info) {
|
|
if (gRegistry.find(id) != gRegistry.end()) {
|
|
SPDLOG_WARN("Sound with ID {} already registered", static_cast<int>(id));
|
|
return;
|
|
}
|
|
|
|
ma_decoder_config config = ma_decoder_config_init(ma_format_f32, ma_engine_get_channels(&gAudioEngine), ma_engine_get_sample_rate(&gAudioEngine));
|
|
ma_result result = ma_decoder_init_memory(data, size, &config, &gRegistry[id].decoder);
|
|
if (result != MA_SUCCESS) {
|
|
SPDLOG_ERROR("Failed to initialize decoder from memory: {}", ma_result_description(result));
|
|
return;
|
|
}
|
|
|
|
if(info.loop.start != -1 && info.loop.end != -1) {
|
|
ma_data_source_set_loop_point_in_pcm_frames(&gRegistry[id].decoder, info.loop.start, info.loop.end);
|
|
}
|
|
|
|
result = ma_sound_init_from_data_source(&gAudioEngine, &gRegistry[id].decoder, 0, NULL, &gRegistry[id].sound);
|
|
|
|
if (result != MA_SUCCESS) {
|
|
SPDLOG_ERROR("Failed to load sound from memory: {}", ma_result_description(result));
|
|
return;
|
|
}
|
|
|
|
gRegistry[id].info = info;
|
|
|
|
SPDLOG_INFO("Sound with ID {} registered from memory buffer", static_cast<int>(id));
|
|
}
|
|
|
|
void HMAS::Play(HMAS_ChannelId channelId, HMAS_AudioId id, bool loop) {
|
|
if(channelId == HMAS_ChannelId::HMAS_MUSIC){
|
|
this->Stop(channelId);
|
|
}
|
|
|
|
auto& sample = gRegistry[id];
|
|
|
|
float pitch = 1.0f;
|
|
float volume = channelId == HMAS_ChannelId::HMAS_MUSIC ? 0.9f : 1.0f;
|
|
ma_sound_set_pitch(&sample.sound, pitch);
|
|
ma_sound_set_volume(&sample.sound, volume);
|
|
ma_sound_set_looping(&sample.sound, loop);
|
|
ma_sound_seek_to_pcm_frame(&sample.sound, 0);
|
|
ma_result result = ma_sound_start(&sample.sound);
|
|
if (result != MA_SUCCESS) {
|
|
SPDLOG_ERROR("Failed to start sound: {}", ma_result_description(result));
|
|
return;
|
|
}
|
|
|
|
auto channel = &this->gChannelSound[channelId];
|
|
channel->sound = &sample.sound;
|
|
channel->cursor = 0;
|
|
channel->pitch = pitch;
|
|
channel->volume = volume;
|
|
}
|
|
|
|
void HMAS::Stop(HMAS_ChannelId channelId) {
|
|
auto channel = &this->gChannelSound[channelId];
|
|
|
|
if (channel->sound == nullptr) {
|
|
return;
|
|
}
|
|
|
|
ma_sound_stop(channel->sound);
|
|
channel->sound = nullptr;
|
|
channel->cursor = 0;
|
|
channel->pitch = 1.0f;
|
|
channel->volume = 1.0f;
|
|
}
|
|
|
|
bool HMAS::IsPlaying(HMAS_ChannelId channelId) {
|
|
auto channel = &this->gChannelSound[channelId];
|
|
|
|
if (channel->sound == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
return ma_sound_is_playing(channel->sound);
|
|
}
|
|
|
|
void HMAS::SetPitch(HMAS_ChannelId channelId, float pitch) {
|
|
auto channel = &this->gChannelSound[channelId];
|
|
|
|
if (channel->sound == nullptr) {
|
|
return;
|
|
}
|
|
|
|
ma_sound_set_pitch(channel->sound, pitch);
|
|
channel->pitch = pitch;
|
|
}
|
|
|
|
void HMAS::SetVolume(HMAS_ChannelId channelId, float volume) {
|
|
auto channel = &this->gChannelSound[channelId];
|
|
|
|
if (channel->sound == nullptr) {
|
|
return;
|
|
}
|
|
|
|
ma_sound_set_volume(channel->sound, volume);
|
|
channel->volume = volume;
|
|
}
|
|
|
|
void HMAS::SetPause(HMAS_ChannelId channelId, bool pause) {
|
|
auto channel = &this->gChannelSound[channelId];
|
|
|
|
if (channel->sound == nullptr) {
|
|
return;
|
|
}
|
|
|
|
if(pause) {
|
|
ma_sound_get_cursor_in_pcm_frames(channel->sound, &channel->cursor);
|
|
ma_sound_stop(channel->sound);
|
|
} else {
|
|
ma_result result = ma_sound_start(channel->sound);
|
|
if (result != MA_SUCCESS) {
|
|
SPDLOG_ERROR("Failed to resume sound: {}", ma_result_description(result));
|
|
return;
|
|
}
|
|
ma_sound_seek_to_pcm_frame(channel->sound, channel->cursor);
|
|
}
|
|
}
|
|
|
|
void HMAS::AddEffect(HMAS_ChannelId channelId, HMAS_EffectType type, HMAS_EffectTransition transition, uint32_t frames, float target) {
|
|
auto& channel = gChannelSound[channelId];
|
|
channel.effects.push_back({type, transition, frames, target});
|
|
}
|
|
|
|
bool HMAS::IsIDRegistered(HMAS_AudioId id) {
|
|
return gRegistry.find(id) != gRegistry.end();
|
|
}
|
|
|
|
void HMAS::ProcessEffects() {
|
|
for (size_t i = 0; i < sizeof(gChannelSound) / sizeof(gChannelSound[0]); i++){
|
|
auto& channel = gChannelSound[i];
|
|
|
|
if (channel.sound == nullptr) {
|
|
continue;
|
|
}
|
|
|
|
if (channel.effects.empty()) {
|
|
continue;
|
|
}
|
|
|
|
auto& effect = channel.effects.front();
|
|
if (effect.numFrames > 0) {
|
|
effect.numFrames--;
|
|
switch (effect.type) {
|
|
case HMAS_EffectType::HMAS_EFFECT_VOLUME: {
|
|
float volume = effect.transition == HMAS_EffectTransition::HMAS_LINEAR ?
|
|
Lerp(channel.volume, effect.target, 1.0f / effect.numFrames) : effect.target;
|
|
this->SetVolume((HMAS_ChannelId) i, std::max(0.0f, volume));
|
|
break;
|
|
}
|
|
case HMAS_EffectType::HMAS_EFFECT_PITCH: {
|
|
float pitch = effect.transition == HMAS_EffectTransition::HMAS_LINEAR ?
|
|
Lerp(channel.pitch, effect.target, 1.0f / effect.numFrames) : effect.target;
|
|
this->SetPitch((HMAS_ChannelId) i, std::max(0.1f, pitch));
|
|
break;
|
|
}
|
|
case HMAS_EffectType::HMAS_EFFECT_PAUSE: {
|
|
this->SetPause((HMAS_ChannelId) i, effect.target < 0.5f);
|
|
break;
|
|
}
|
|
case HMAS_EffectType::HMAS_EFFECT_STOP: {
|
|
this->Stop((HMAS_ChannelId) i);
|
|
break;
|
|
}
|
|
default:
|
|
SPDLOG_WARN("Unknown effect type: {}", static_cast<int>(effect.type));
|
|
break;
|
|
}
|
|
} else {
|
|
SPDLOG_DEBUG("Removing effect: Type={}, Frames={}, Target={}", static_cast<int>(effect.type), effect.numFrames, effect.target);
|
|
channel.effects.erase(channel.effects.begin());
|
|
}
|
|
}
|
|
}
|
|
|
|
void HMAS::CreateBuffer(uint8_t *samples, uint32_t bufferSizeInBytes) {
|
|
this->ProcessEffects();
|
|
ma_uint32 bufferSizeInFrames = bufferSizeInBytes / ma_get_bytes_per_frame(ma_format_f32, ma_engine_get_channels(&gAudioEngine));
|
|
ma_engine_read_pcm_frames(&gAudioEngine, samples, bufferSizeInFrames, NULL);
|
|
}
|
|
|
|
HMAS::~HMAS() {
|
|
for (auto& pair : gRegistry) {
|
|
ma_sound_uninit(&pair.second.sound);
|
|
}
|
|
gRegistry.clear();
|
|
ma_engine_uninit(&gAudioEngine);
|
|
}
|
|
|
|
// Expose C API for HMAS
|
|
|
|
extern "C" void HMAS_Play(HMAS_ChannelId channelId, HMAS_AudioId id, bool loop) {
|
|
GameEngine::Instance->gHMAS->Play(channelId, id, loop);
|
|
}
|
|
|
|
extern "C" void HMAS_Stop(HMAS_ChannelId channelId) {
|
|
GameEngine::Instance->gHMAS->Stop(channelId);
|
|
}
|
|
|
|
extern "C" bool HMAS_IsPlaying(HMAS_ChannelId channelId) {
|
|
return GameEngine::Instance->gHMAS->IsPlaying(channelId);
|
|
}
|
|
|
|
extern "C" void HMAS_SetPitch(HMAS_ChannelId channelId, float pitch) {
|
|
GameEngine::Instance->gHMAS->SetPitch(channelId, pitch);
|
|
}
|
|
|
|
extern "C" void HMAS_SetVolume(HMAS_ChannelId channelId, float volume) {
|
|
GameEngine::Instance->gHMAS->SetVolume(channelId, volume);
|
|
}
|
|
|
|
extern "C" void HMAS_SetPause(HMAS_ChannelId channelId, bool pause) {
|
|
GameEngine::Instance->gHMAS->SetPause(channelId, pause);
|
|
}
|
|
|
|
extern "C" void HMAS_AddEffect(HMAS_ChannelId channelId, HMAS_EffectType type, HMAS_EffectTransition transition, uint32_t frames, float target) {
|
|
GameEngine::Instance->gHMAS->AddEffect(channelId, type, transition, frames, target);
|
|
}
|
|
|
|
extern "C" bool HMAS_IsIDRegistered(HMAS_AudioId id) {
|
|
return GameEngine::Instance->gHMAS->IsIDRegistered(id);
|
|
} |