forked from tsuki/openroller
234 lines
8.0 KiB
C++
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;
|
|
}
|