ES-DE/es-core/src/Sound.cpp

231 lines
7.1 KiB
C++

// SPDX-License-Identifier: MIT
//
// EmulationStation Desktop Edition
// Sound.cpp
//
// Higher-level audio functions.
// Navigation sounds, audio sample playback etc.
//
#include "Sound.h"
#include "AudioManager.h"
#include "Log.h"
#include "Settings.h"
#include "ThemeData.h"
#include "resources/ResourceManager.h"
std::shared_ptr<Sound> Sound::get(const std::string& path)
{
auto it = sMap.find(path);
if (it != sMap.cend())
return it->second;
std::shared_ptr<Sound> sound {std::shared_ptr<Sound>(new Sound(path))};
AudioManager::getInstance().registerSound(sound);
sMap[path] = sound;
return sound;
}
std::shared_ptr<Sound> Sound::getFromTheme(ThemeData* const theme,
const std::string& view,
const std::string& element)
{
std::string elemName {element.substr(6, std::string::npos)};
if (theme == nullptr) {
LOG(LogDebug) << "Sound::getFromTheme(): Using fallback sound file for \"" << elemName
<< "\"";
return get(ResourceManager::getInstance().getResourcePath(":/sounds/" + elemName + ".wav"));
}
LOG(LogDebug) << "Sound::getFromTheme(): Looking for tag <sound name=\"" << elemName << "\">";
const ThemeData::ThemeElement* elem {theme->getElement(view, element, "sound")};
if (!elem || !elem->has("path")) {
LOG(LogDebug) << "Sound::getFromTheme(): Tag not found, using fallback sound file";
return get(ResourceManager::getInstance().getResourcePath(":/sounds/" + elemName + ".wav"));
}
if (!Utils::FileSystem::exists(elem->get<std::string>("path"))) {
LOG(LogError) << "Sound::getFromTheme(): Navigation sound tag found but sound file does "
"not exist, falling back to default sound";
return get(ResourceManager::getInstance().getResourcePath(":/sounds/" + elemName + ".wav"));
}
LOG(LogDebug) << "Sound::getFromTheme(): Tag found, ready to load theme sound file";
return get(elem->get<std::string>("path"));
}
Sound::Sound(const std::string& path)
: mSampleData(nullptr)
, mSamplePos(0)
, mSampleLength(0)
, mPlaying(false)
{
loadFile(path);
}
void Sound::loadFile(const std::string& path)
{
mPath = path;
init();
}
void Sound::init()
{
if (mSampleData != nullptr)
deinit();
if (mPath.empty())
return;
// Load WAV file via SDL.
SDL_AudioSpec wave;
Uint8* data {nullptr};
Uint32 dlen {0};
if (SDL_LoadWAV(mPath.c_str(), &wave, &data, &dlen) == nullptr) {
LOG(LogError) << "Failed to load theme navigation sound file: " << SDL_GetError();
return;
}
// Convert sound file to the format required by ES-DE.
SDL_AudioStream* conversionStream {
SDL_NewAudioStream(wave.format, wave.channels, wave.freq, AudioManager::sAudioFormat.format,
AudioManager::sAudioFormat.channels, AudioManager::sAudioFormat.freq)};
if (conversionStream == nullptr) {
LOG(LogError) << "Failed to create sample conversion stream: " << SDL_GetError();
return;
}
if (SDL_AudioStreamPut(conversionStream, data, dlen) == -1) {
LOG(LogError) << "Failed to put samples in the conversion stream: " << SDL_GetError();
SDL_FreeAudioStream(conversionStream);
return;
}
int sampleLength {SDL_AudioStreamAvailable(conversionStream)};
Uint8* converted {new Uint8[sampleLength]};
if (SDL_AudioStreamGet(conversionStream, converted, sampleLength) == -1) {
LOG(LogError) << "Failed to convert sound file '" << mPath << "': " << SDL_GetError();
SDL_FreeAudioStream(conversionStream);
delete[] converted;
return;
}
mSampleData = converted;
mSampleLength = sampleLength;
mSamplePos = 0;
mSampleFormat.freq = AudioManager::sAudioFormat.freq;
mSampleFormat.channels = AudioManager::sAudioFormat.channels;
mSampleFormat.format = AudioManager::sAudioFormat.format;
SDL_FreeAudioStream(conversionStream);
SDL_FreeWAV(data);
}
void Sound::deinit()
{
mPlaying = false;
if (mSampleData != nullptr) {
SDL_LockAudioDevice(AudioManager::sAudioDevice);
delete[] mSampleData;
mSampleData = nullptr;
mSampleLength = 0;
mSamplePos = 0;
SDL_UnlockAudioDevice(AudioManager::sAudioDevice);
sMap.erase(mPath);
}
}
void Sound::play()
{
if (mSampleData == nullptr)
return;
if (!Settings::getInstance()->getBool("NavigationSounds"))
return;
if (!AudioManager::getInstance().getHasAudioDevice())
return;
SDL_LockAudioDevice(AudioManager::sAudioDevice);
if (mPlaying)
// Replay from start. rewind the sample to the beginning.
mSamplePos = 0;
else
// Flag our sample as playing.
mPlaying = true;
SDL_UnlockAudioDevice(AudioManager::sAudioDevice);
// Tell the AudioManager to start playing samples.
AudioManager::getInstance().play();
}
void Sound::stop()
{
// Flag our sample as not playing and rewind its position.
SDL_LockAudioDevice(AudioManager::sAudioDevice);
mPlaying = false;
mSamplePos = 0;
SDL_UnlockAudioDevice(AudioManager::sAudioDevice);
}
void Sound::setPosition(Uint32 newPosition)
{
mSamplePos = newPosition;
if (mSamplePos >= mSampleLength) {
// Got to or beyond the end of the sample. stop playing.
mPlaying = false;
mSamplePos = 0;
}
}
NavigationSounds& NavigationSounds::getInstance()
{
static NavigationSounds instance;
return instance;
}
void NavigationSounds::deinit()
{
for (auto sound : mNavigationSounds) {
AudioManager::getInstance().unregisterSound(sound);
sound->deinit();
}
mNavigationSounds.clear();
}
void NavigationSounds::loadThemeNavigationSounds(ThemeData* const theme)
{
if (theme) {
LOG(LogDebug) << "NavigationSounds::loadThemeNavigationSounds(): "
"Theme includes navigation sound support, loading custom sounds";
}
else {
LOG(LogDebug) << "NavigationSounds::loadThemeNavigationSounds(): "
"Theme does not include navigation sound support, using fallback sounds";
}
mNavigationSounds.push_back(Sound::getFromTheme(theme, "all", "sound_systembrowse"));
mNavigationSounds.push_back(Sound::getFromTheme(theme, "all", "sound_quicksysselect"));
mNavigationSounds.push_back(Sound::getFromTheme(theme, "all", "sound_select"));
mNavigationSounds.push_back(Sound::getFromTheme(theme, "all", "sound_back"));
mNavigationSounds.push_back(Sound::getFromTheme(theme, "all", "sound_scroll"));
mNavigationSounds.push_back(Sound::getFromTheme(theme, "all", "sound_favorite"));
mNavigationSounds.push_back(Sound::getFromTheme(theme, "all", "sound_launch"));
}
void NavigationSounds::playThemeNavigationSound(NavigationSoundsID soundID)
{
NavigationSounds::getInstance().mNavigationSounds[soundID]->play();
}
bool NavigationSounds::isPlayingThemeNavigationSound(NavigationSoundsID soundID)
{
return NavigationSounds::getInstance().mNavigationSounds[soundID]->isPlaying();
}