#include "openroller/desktop/AudioManager.hpp" #include #include AudioManager::AudioManager() : device(0), bgmStream(nullptr), shotStream(nullptr), duration(0), shotBaseGain(1.0f), shotMuted(false), playing(false), startTime(0), accumulatedTicks(0) {} AudioManager::~AudioManager() { clear(); } void AudioManager::clear() { clearGameplaySounds(); if (bgmStream) SDL_DestroyAudioStream(bgmStream); if (shotStream) SDL_DestroyAudioStream(shotStream); if (device) SDL_CloseAudioDevice(device); device = 0; bgmStream = nullptr; shotStream = nullptr; duration = 0.0; shotBaseGain = 1.0f; shotMuted = false; playing = false; startTime = 0; accumulatedTicks = 0; } void AudioManager::clearGameplaySounds() { for (GameplaySoundSlot& slot : gameplaySounds) { for (SDL_AudioStream*& voice : slot.voices) { if (voice) SDL_DestroyAudioStream(voice); voice = nullptr; } slot.data.clear(); slot.nextVoice = 0; } } bool AudioManager::openStreams(const SDL_AudioSpec& bgmSpec, const Uint8* bgmData, Uint32 bgmLen, float bgmGain, const SDL_AudioSpec* shotSpec, const Uint8* shotData, Uint32 shotLen, float requestedShotGain) { const int bytesPerSample = SDL_AUDIO_BITSIZE(bgmSpec.format) / 8; if (bytesPerSample <= 0 || bgmSpec.channels <= 0 || bgmSpec.freq <= 0) return false; duration = static_cast(bgmLen) / (static_cast(bgmSpec.channels) * bytesPerSample * bgmSpec.freq); device = SDL_OpenAudioDevice(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, nullptr); if (!device) { std::cerr << "Audio device open error: " << SDL_GetError() << std::endl; return false; } if (!SDL_PauseAudioDevice(device)) { std::cerr << "Audio device pause error: " << SDL_GetError() << std::endl; return false; } bgmStream = SDL_CreateAudioStream(&bgmSpec, nullptr); if (shotSpec) shotStream = SDL_CreateAudioStream(shotSpec, nullptr); if (!bgmStream || (shotSpec && !shotStream)) { std::cerr << "Audio stream create error: " << SDL_GetError() << std::endl; return false; } SDL_AudioStream* streams[] = {bgmStream, shotStream}; const int streamCount = shotStream ? 2 : 1; if (!SDL_BindAudioStreams(device, streams, streamCount)) { std::cerr << "Audio stream bind error: " << SDL_GetError() << std::endl; return false; } bgmGain = std::clamp(bgmGain, 0.0f, 1.0f); shotBaseGain = std::clamp(requestedShotGain, 0.0f, 1.0f); if (!SDL_SetAudioStreamGain(bgmStream, bgmGain) || (shotStream && !SDL_SetAudioStreamGain(shotStream, shotBaseGain))) { std::cerr << "Audio gain error: " << SDL_GetError() << std::endl; return false; } if (!SDL_PutAudioStreamData(bgmStream, bgmData, static_cast(bgmLen)) || (shotStream && !SDL_PutAudioStreamData( shotStream, shotData, static_cast(shotLen)))) { std::cerr << "Audio queue error: " << SDL_GetError() << std::endl; return false; } shotMuted = false; return true; } bool AudioManager::loadMusic(const std::string& path, float gain) { clear(); SDL_AudioSpec loadedSpec{}; Uint8* loadedBuf = nullptr; Uint32 loadedLen = 0; if (!SDL_LoadWAV(path.c_str(), &loadedSpec, &loadedBuf, &loadedLen)) { std::cerr << "WAV Load Error: " << SDL_GetError() << std::endl; return false; } const bool opened = openStreams(loadedSpec, loadedBuf, loadedLen, gain); SDL_free(loadedBuf); if (opened) return true; clear(); return false; } bool AudioManager::loadMusicPair(const std::string& bgmPath, const std::string& shotPath, float bgmGain, float shotGain) { clear(); SDL_AudioSpec bgmSpec{}; SDL_AudioSpec shotSpec{}; Uint8* bgmBuf = nullptr; Uint8* shotBuf = nullptr; Uint32 bgmLen = 0; Uint32 shotLen = 0; if (!SDL_LoadWAV(bgmPath.c_str(), &bgmSpec, &bgmBuf, &bgmLen)) { std::cerr << "BGM WAV load error: " << SDL_GetError() << std::endl; return false; } if (!SDL_LoadWAV(shotPath.c_str(), &shotSpec, &shotBuf, &shotLen)) { std::cerr << "SHOT WAV load error: " << SDL_GetError() << "; playing BGM only" << std::endl; SDL_free(bgmBuf); return loadMusic(bgmPath, bgmGain); } const bool opened = openStreams(bgmSpec, bgmBuf, bgmLen, bgmGain, &shotSpec, shotBuf, shotLen, shotGain); SDL_free(bgmBuf); SDL_free(shotBuf); if (opened) return true; clear(); return false; } bool AudioManager::loadGameplaySounds(const std::array& paths, const std::array& gains) { clearGameplaySounds(); if (!device) return false; std::array specs{}; for (size_t i = 0; i < gameplaySounds.size(); ++i) { Uint8* wavData = nullptr; Uint32 wavLength = 0; if (paths[i].empty() || !SDL_LoadWAV(paths[i].c_str(), &specs[i], &wavData, &wavLength)) { std::cerr << "Gameplay SE WAV load error: " << paths[i] << ": " << SDL_GetError() << std::endl; if (wavData) SDL_free(wavData); clearGameplaySounds(); return false; } gameplaySounds[i].data.assign(wavData, wavData + wavLength); SDL_free(wavData); } std::array voices{}; size_t voiceIndex = 0; for (size_t i = 0; i < gameplaySounds.size(); ++i) { GameplaySoundSlot& slot = gameplaySounds[i]; for (SDL_AudioStream*& voice : slot.voices) { voice = SDL_CreateAudioStream(&specs[i], nullptr); if (!voice || !SDL_SetAudioStreamGain(voice, std::clamp(gains[i], 0.0f, 1.0f))) { std::cerr << "Gameplay SE stream error: " << SDL_GetError() << std::endl; clearGameplaySounds(); return false; } voices[voiceIndex++] = voice; } } if (!SDL_BindAudioStreams(device, voices.data(), static_cast(voices.size()))) { std::cerr << "Gameplay SE bind error: " << SDL_GetError() << std::endl; clearGameplaySounds(); return false; } return true; } void AudioManager::playGameplaySound(GameplaySound sound) { GameplaySoundSlot& slot = gameplaySounds[static_cast(sound)]; if (slot.data.empty()) return; SDL_AudioStream* voice = slot.voices[slot.nextVoice]; slot.nextVoice = (slot.nextVoice + 1) % slot.voices.size(); if (!voice) return; if (!SDL_ClearAudioStream(voice) || !SDL_PutAudioStreamData(voice, slot.data.data(), static_cast(slot.data.size())) || !SDL_FlushAudioStream(voice)) { std::cerr << "Gameplay SE playback error: " << SDL_GetError() << std::endl; } } void AudioManager::play() { if (device) { SDL_ResumeAudioDevice(device); playing = true; startTime = SDL_GetTicks(); accumulatedTicks = 0; } } void AudioManager::pause() { if (!device || !playing) return; const Uint64 now = SDL_GetTicks(); accumulatedTicks += now - startTime; SDL_PauseAudioDevice(device); playing = false; } void AudioManager::resume() { if (!device || playing) return; SDL_ResumeAudioDevice(device); startTime = SDL_GetTicks(); playing = true; } void AudioManager::setShotMuted(bool muted) { if (!shotStream || shotMuted == muted) return; if (SDL_SetAudioStreamGain(shotStream, muted ? 0.0f : shotBaseGain)) { shotMuted = muted; } else { std::cerr << "SHOT gain error: " << SDL_GetError() << std::endl; } } double AudioManager::getTime() const { const Uint64 liveTicks = playing ? SDL_GetTicks() - startTime : 0; return static_cast(accumulatedTicks + liveTicks) / 1000.0; } double AudioManager::getDuration() const { return duration; }