Files
openroller/apps/desktop/src/AudioManager.cpp
T
tsuki 831d96e562 Initial public source release
Split reusable rendering and format support into vectorail-core and vectorail-gc.
2026-08-02 17:05:27 +02:00

234 lines
8.0 KiB
C++

#include "openroller/desktop/AudioManager.hpp"
#include <algorithm>
#include <iostream>
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<double>(bgmLen) /
(static_cast<double>(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<int>(bgmLen)) ||
(shotStream && !SDL_PutAudioStreamData(
shotStream, shotData, static_cast<int>(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<std::string, 3>& paths,
const std::array<float, 3>& gains) {
clearGameplaySounds();
if (!device) return false;
std::array<SDL_AudioSpec, 3> 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<SDL_AudioStream*, 6> 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<int>(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<size_t>(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<int>(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<double>(accumulatedTicks + liveTicks) / 1000.0;
}
double AudioManager::getDuration() const {
return duration;
}