Commit f9b63108 authored by Unicorn369's avatar Unicorn369 Committed by fallenstardust

add sound_openal (by kevinlul)

parent b538dc43
#include "sound_manager.h"
#include "config.h"
#ifdef IRRKLANG_STATIC
#include "../ikpmp3/ikpMP3.h"
#endif
namespace ygo {
bool SoundManager::Init(double sounds_volume, double music_volume, bool sounds_enabled, bool music_enabled, void* payload) {
soundsEnabled = sounds_enabled;
musicEnabled = music_enabled;
rnd.seed(time(0));
bgm_scene = -1;
RefreshBGMList();
RefreshChantsList();
#ifdef YGOPRO_USE_IRRKLANG
soundEngine = irrklang::createIrrKlangDevice();
if(!soundEngine) {
return soundsEnabled = musicEnabled = false;
} else {
#ifdef IRRKLANG_STATIC
irrklang::ikpMP3Init(soundEngine);
#endif
sfxVolume = sounds_volume;
bgmVolume = music_volume;
return true;
}
#else
try {
openal = std::make_unique<YGOpen::OpenALSingleton>();
sfx = std::make_unique<YGOpen::OpenALSoundLayer>(openal);
bgm = std::make_unique<YGOpen::OpenALSoundLayer>(openal);
sfx->setVolume(sounds_volume);
bgm->setVolume(music_volume);
return true;
}
catch (std::runtime_error& e) {
return soundsEnabled = musicEnabled = false;
}
#endif // YGOPRO_USE_IRRKLANG
}
SoundManager::~SoundManager() {
#ifdef YGOPRO_USE_IRRKLANG
if (soundBGM)
soundBGM->drop();
if (soundEngine)
soundEngine->drop();
#endif
}
void SoundManager::RefreshBGMList() {
Utils::Makedirectory(TEXT("./sound/BGM/"));
Utils::Makedirectory(TEXT("./sound/BGM/duel"));
Utils::Makedirectory(TEXT("./sound/BGM/menu"));
Utils::Makedirectory(TEXT("./sound/BGM/deck"));
Utils::Makedirectory(TEXT("./sound/BGM/advantage"));
Utils::Makedirectory(TEXT("./sound/BGM/disadvantage"));
Utils::Makedirectory(TEXT("./sound/BGM/win"));
Utils::Makedirectory(TEXT("./sound/BGM/lose"));
Utils::Makedirectory(TEXT("./sound/chants"));
RefreshBGMDir(TEXT(""), BGM::DUEL);
RefreshBGMDir(TEXT("duel"), BGM::DUEL);
RefreshBGMDir(TEXT("menu"), BGM::MENU);
RefreshBGMDir(TEXT("deck"), BGM::DECK);
RefreshBGMDir(TEXT("advantage"), BGM::ADVANTAGE);
RefreshBGMDir(TEXT("disadvantage"), BGM::DISADVANTAGE);
RefreshBGMDir(TEXT("win"), BGM::WIN);
RefreshBGMDir(TEXT("lose"), BGM::LOSE);
}
void SoundManager::RefreshBGMDir(path_string path, BGM scene) {
for(auto& file : Utils::FindfolderFiles(TEXT("./sound/BGM/") + path, { TEXT("mp3"), TEXT("ogg"), TEXT("wav") })) {
auto conv = Utils::ToUTF8IfNeeded(path + TEXT("/") + file);
BGMList[BGM::ALL].push_back(conv);
BGMList[scene].push_back(conv);
}
}
void SoundManager::RefreshChantsList() {
for(auto& file : Utils::FindfolderFiles(TEXT("./sound/chants"), { TEXT("mp3"), TEXT("ogg"), TEXT("wav") })) {
auto scode = Utils::GetFileName(TEXT("./sound/chants/") + file);
unsigned int code = std::stoi(scode);
if(code && !ChantsList.count(code))
ChantsList[code] = Utils::ToUTF8IfNeeded(file);
}
}
void SoundManager::PlaySoundEffect(SFX sound) {
static const std::map<SFX, const char*> fx = {
{SUMMON, "./sound/summon.wav"},
{SPECIAL_SUMMON, "./sound/specialsummon.wav"},
{ACTIVATE, "./sound/activate.wav"},
{SET, "./sound/set.wav"},
{FLIP, "./sound/flip.wav"},
{REVEAL, "./sound/reveal.wav"},
{EQUIP, "./sound/equip.wav"},
{DESTROYED, "./sound/destroyed.wav"},
{BANISHED, "./sound/banished.wav"},
{TOKEN, "./sound/token.wav"},
{ATTACK, "./sound/attack.wav"},
{DIRECT_ATTACK, "./sound/directattack.wav"},
{DRAW, "./sound/draw.wav"},
{SHUFFLE, "./sound/shuffle.wav"},
{DAMAGE, "./sound/damage.wav"},
{RECOVER, "./sound/gainlp.wav"},
{COUNTER_ADD, "./sound/addcounter.wav"},
{COUNTER_REMOVE, "./sound/removecounter.wav"},
{COIN, "./sound/coinflip.wav"},
{DICE, "./sound/diceroll.wav"},
{NEXT_TURN, "./sound/nextturn.wav"},
{PHASE, "./sound/phase.wav"},
{PLAYER_ENTER, "./sound/playerenter.wav"},
{CHAT, "./sound/chatmessage.wav"}
};
if (!soundsEnabled) return;
#ifdef YGOPRO_USE_IRRKLANG
if (soundEngine) {
auto sfx = soundEngine->play2D(fx.at(sound), false, true);
sfx->setVolume(sfxVolume);
sfx->setIsPaused(false);
}
#else
if (sfx) sfx->play(fx.at(sound), false);
#endif
}
void SoundManager::PlayMusic(const std::string& song, bool loop) {
if(!musicEnabled) return;
#ifdef YGOPRO_USE_IRRKLANG
if(!soundBGM || soundBGM->getSoundSource()->getName() != song) {
StopBGM();
if (soundEngine) {
soundBGM = soundEngine->play2D(song.c_str(), loop, false, true);
soundBGM->setVolume(bgmVolume);
}
}
#else
StopBGM();
if (bgm) bgmCurrent = bgm->play(song, loop);
#endif
}
void SoundManager::PlayBGM(BGM scene) {
auto& list = BGMList[scene];
int count = list.size();
#ifdef YGOPRO_USE_IRRKLANG
if(musicEnabled && (scene != bgm_scene || (soundBGM && soundBGM->isFinished()) || !soundBGM) && count > 0) {
#else
if (musicEnabled && (scene != bgm_scene || !bgm->exists(bgmCurrent)) && count > 0) {
#endif
bgm_scene = scene;
int bgm = (std::uniform_int_distribution<>(0, count - 1))(rnd);
std::string BGMName = "./sound/BGM/" + list[bgm];
PlayMusic(BGMName, true);
}
}
void SoundManager::StopBGM() {
#ifdef YGOPRO_USE_IRRKLANG
if(soundBGM) {
soundBGM->stop();
soundBGM->drop();
soundBGM = nullptr;
}
#else
bgm->stopAll();
#endif
}
bool SoundManager::PlayChant(unsigned int code) {
if(ChantsList.count(code)) {
#ifdef YGOPRO_USE_IRRKLANG
if (soundEngine && !soundEngine->isCurrentlyPlaying(("./sound/chants/" + ChantsList[code]).c_str())) {
auto chant = soundEngine->play2D(("./sound/chants/" + ChantsList[code]).c_str());
chant->setVolume(sfxVolume);
chant->setIsPaused(false);
}
#else
if (bgm) bgm->play("./sound/chants/" + ChantsList[code], false);
#endif
return true;
}
return false;
}
void SoundManager::SetSoundVolume(double volume) {
#ifdef YGOPRO_USE_IRRKLANG
sfxVolume = volume;
#else
if (sfx) sfx->setVolume(volume);
#endif
}
void SoundManager::SetMusicVolume(double volume) {
#ifdef YGOPRO_USE_IRRKLANG
if (soundBGM) soundBGM->setVolume(volume);
bgmVolume = volume;
#else
if (bgm) bgm->setVolume(volume);
#endif
}
void SoundManager::EnableSounds(bool enable) {
soundsEnabled = enable;
}
void SoundManager::EnableMusic(bool enable) {
musicEnabled = enable;
if(!musicEnabled) {
#ifdef YGOPRO_USE_IRRKLANG
if(soundBGM){
if(!soundBGM->isFinished())
soundBGM->stop();
soundBGM->drop();
soundBGM = nullptr;
}
#else
StopBGM();
#endif
}
}
} // namespace ygo
#ifndef SOUNDMANAGER_H
#define SOUNDMANAGER_H
#include <random>
#ifdef YGOPRO_USE_IRRKLANG
#include <irrKlang.h>
#else
#include "sound_openal.h"
#endif
#include "utils.h"
namespace ygo {
class SoundManager {
public:
enum SFX {
SUMMON,
SPECIAL_SUMMON,
ACTIVATE,
SET,
FLIP,
REVEAL,
EQUIP,
DESTROYED,
BANISHED,
TOKEN,
ATTACK,
DIRECT_ATTACK,
DRAW,
SHUFFLE,
DAMAGE,
RECOVER,
COUNTER_ADD,
COUNTER_REMOVE,
COIN,
DICE,
NEXT_TURN,
PHASE,
PLAYER_ENTER,
CHAT
};
enum BGM {
ALL,
DUEL,
MENU,
DECK,
ADVANTAGE,
DISADVANTAGE,
WIN,
LOSE
};
#ifndef YGOPRO_USE_IRRKLANG
SoundManager() : openal(nullptr), sfx(nullptr) {}
#endif
~SoundManager();
bool Init(double sounds_volume, double music_volume, bool sounds_enabled, bool music_enabled, void* payload = nullptr);
void RefreshBGMList();
void PlaySoundEffect(SFX sound);
void PlayMusic(const std::string& song, bool loop);
void PlayBGM(BGM scene);
void StopBGM();
bool PlayChant(unsigned int code);
void SetSoundVolume(double volume);
void SetMusicVolume(double volume);
void EnableSounds(bool enable);
void EnableMusic(bool enable);
private:
std::vector<std::string> BGMList[8];
std::map<unsigned int, std::string> ChantsList;
int bgm_scene = -1;
std::mt19937 rnd;
#ifdef YGOPRO_USE_IRRKLANG
irrklang::ISoundEngine* soundEngine;
irrklang::ISound* soundBGM;
double sfxVolume = 1.0;
double bgmVolume = 1.0;
#else
std::unique_ptr<YGOpen::OpenALSingleton> openal;
std::unique_ptr<YGOpen::OpenALSoundLayer> sfx;
std::unique_ptr<YGOpen::OpenALSoundLayer> bgm;
int bgmCurrent = -1;
#endif
void RefreshBGMDir(path_string path, BGM scene);
void RefreshChantsList();
bool soundsEnabled = false;
bool musicEnabled = false;
};
}
#endif //SOUNDMANAGER_H
\ No newline at end of file
#include "sound_openal.h"
#include <array>
#include <iterator>
#include <mpg123.h>
#include <sndfile.h>
#include "utils.h"
namespace YGOpen {
static void delete_ALCdevice(ALCdevice* ptr)
{
if (ptr) {
alcCloseDevice(ptr);
}
}
static void delete_ALCcontext(ALCcontext* ptr)
{
if (ptr) {
alcMakeContextCurrent(nullptr);
alcDestroyContext(ptr);
}
}
OpenALSingleton::OpenALSingleton() : device(nullptr, delete_ALCdevice), context(nullptr, delete_ALCcontext) {
device.reset(alcOpenDevice(nullptr));
if (!device) {
throw std::runtime_error("Failed to create OpenAL audio device!");
}
context.reset(alcCreateContext(device.get(), nullptr));
if (!alcMakeContextCurrent(context.get())) {
throw std::runtime_error("Failed to set OpenAL audio context!");
}
mpg123_init();
}
OpenALSingleton::~OpenALSingleton() {
mpg123_exit();
}
OpenALSoundLayer::OpenALSoundLayer(const std::unique_ptr<OpenALSingleton>& openal) : openal(openal), buffers(), playing() {}
OpenALSoundLayer::~OpenALSoundLayer() {
stopAll();
for (const auto& iter : buffers) {
alDeleteBuffers(1, &iter.second->id);
}
}
static inline bool alUtilInitBuffer(std::shared_ptr<OpenALSoundBuffer> data) {
alGetError();
alGenBuffers(1, &data->id);
ALenum error = alGetError();
if (error != AL_NO_ERROR) return false;
alBufferData(data->id, data->format, data->buffer.data(), data->buffer.size(), data->frequency);
error = alGetError();
if (error != AL_NO_ERROR) return false;
return true;
}
static inline ALenum alUtilFormatFromMp3(const int channels, const int encoding) {
if (channels & MPG123_STEREO) {
if (encoding & MPG123_ENC_SIGNED_16) {
return AL_FORMAT_STEREO16;
}
else {
return AL_FORMAT_STEREO8;
}
}
else {
if (encoding & MPG123_ENC_SIGNED_16) {
return AL_FORMAT_MONO16;
}
else {
return AL_FORMAT_MONO8;
}
}
}
static std::shared_ptr<OpenALSoundBuffer> loadMp3(const std::string& filename) {
auto data = std::make_shared<OpenALSoundBuffer>();
std::vector<unsigned char> buffer;
size_t bufferSize;
int mpgError, channels, encoding;
long rate;
auto mpgHandle = mpg123_new(nullptr, &mpgError);
if (!mpgHandle) return nullptr;
if (mpg123_open(mpgHandle, filename.c_str()) != MPG123_OK ||
mpg123_getformat(mpgHandle, &rate, &channels, &encoding) != MPG123_OK) {
mpg123_delete(mpgHandle);
return nullptr;
}
mpg123_format_none(mpgHandle);
mpg123_format(mpgHandle, rate, channels, encoding);
bufferSize = mpg123_outblock(mpgHandle);
buffer.resize(bufferSize);
for(size_t read = 1; read != 0 && mpgError == MPG123_OK; ) {
mpgError = mpg123_read(mpgHandle, buffer.data(), bufferSize, &read);
data->buffer.insert(data->buffer.end(), buffer.begin(), buffer.end());
}
mpg123_close(mpgHandle);
mpg123_delete(mpgHandle);
if (mpgError != MPG123_DONE) return nullptr;
data->format = alUtilFormatFromMp3(channels, encoding);
data->frequency = rate;
return alUtilInitBuffer(data) ? data : nullptr;
}
static std::shared_ptr<OpenALSoundBuffer> loadSnd(const std::string& filename) {
auto data = std::make_shared<OpenALSoundBuffer>();
SF_INFO info;
std::unique_ptr<SNDFILE, std::function<void(SNDFILE*)>> file(
sf_open(filename.c_str(), SFM_READ, &info),
[](SNDFILE* ptr) { sf_close(ptr); });
if (!file) return nullptr;
data->frequency = info.samplerate;
data->format = info.channels == 1 ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16;
data->buffer.resize(info.frames * info.channels * sizeof(short));
if (sf_readf_short(file.get(), reinterpret_cast<short*>(data->buffer.data()), info.frames) != info.frames) return nullptr;
return alUtilInitBuffer(data) ? data : nullptr;
}
bool OpenALSoundLayer::load(const std::string& filename)
{
std::shared_ptr<OpenALSoundBuffer> data(nullptr);
auto ext = ygo::Utils::GetFileExtension(filename);
if (ext == "mp3") {
data = loadMp3(filename);
}
else {
data = loadSnd(filename);
}
if (!data) return false;
buffers[filename] = data;
return true;
}
int OpenALSoundLayer::play(const std::string& filename, bool loop)
{
maintain();
if (buffers.find(filename) == buffers.end()) {
if (!load(filename)) return -1;
}
ALuint buffer = buffers.at(filename)->id;
ALuint source;
alGetError();
alGenSources(1, &source);
ALenum error = alGetError();
if (error != AL_NO_ERROR) return -1;
alSourcei(source, AL_BUFFER, buffer);
alSourcei(source, AL_LOOPING, loop ? AL_TRUE : AL_FALSE);
alSourcef(source, AL_GAIN, volume);
alSourcePlay(source);
error = alGetError();
if (error != AL_NO_ERROR) {
alDeleteSources(1, &source);
return -1;
}
playing.insert(source);
return source;
}
bool OpenALSoundLayer::exists(int sound)
{
maintain();
return playing.find(sound) != playing.end();
}
void OpenALSoundLayer::stop(int sound)
{
maintain();
const auto iter = playing.find(sound);
if (playing.find(sound) != playing.end()) {
alSourceStop(sound);
alDeleteSources(1, &*iter);
playing.erase(iter);
}
}
void OpenALSoundLayer::stopAll()
{
for (const auto& iter : playing) {
alSourceStop(iter);
alDeleteSources(1, &iter);
}
playing.clear();
}
void OpenALSoundLayer::setVolume(float gain)
{
volume = gain;
for (const auto& iter : playing) {
alSourcef(iter, AL_GAIN, volume);
}
}
void OpenALSoundLayer::maintain() {
std::unordered_set<ALuint> toDelete;
for (const auto& iter : playing) {
ALint state;
alGetSourcei(iter, AL_SOURCE_STATE, &state);
if (state != AL_PLAYING) {
toDelete.insert(iter);
}
}
for (const auto& iter : toDelete) {
alSourceStop(iter);
alDeleteSources(1, &iter);
playing.erase(iter);
}
}
} // namespace YGOpen
#ifndef YGOPEN_SOUND_OPENAL_H
#define YGOPEN_SOUND_OPENAL_H
#include <memory>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include <AL/al.h>
#include <AL/alc.h>
namespace YGOpen {
/* Modified from minetest: src/client/sound.h, src/client/sound_openal.cpp
* https://github.com/minetest/minetest
* Licensed under GNU LGPLv2.1
*/
struct OpenALSoundBuffer
{
ALenum format;
ALsizei frequency;
ALuint id;
std::vector<char> buffer;
};
class OpenALSingleton {
public:
OpenALSingleton();
~OpenALSingleton();
std::unique_ptr<ALCdevice, void (*)(ALCdevice* ptr)> device;
std::unique_ptr<ALCcontext, void(*)(ALCcontext* ptr)> context;
};
class OpenALSoundLayer {
public:
OpenALSoundLayer(const std::unique_ptr<OpenALSingleton>& openal);
~OpenALSoundLayer();
bool load(const std::string& filename);
int play(const std::string& filename, bool loop);
bool exists(int sound);
void stop(int sound);
void stopAll();
void setVolume(float gain);
private:
void maintain();
const std::unique_ptr<OpenALSingleton>& openal;
std::unordered_map<std::string, std::shared_ptr<OpenALSoundBuffer>> buffers;
std::unordered_set<ALuint> playing;
float volume = 1.0f;
};
}
#endif //YGOPEN_SOUND_OPENAL_H
This diff is collapsed.
#ifndef UTILS_H
#define UTILS_H
#include <irrlicht.h>
#include <string>
#include <vector>
#include <functional>
#include <fstream>
#include <map>
#ifndef _WIN32
#include <dirent.h>
#include <sys/stat.h>
#endif
using path_string = std::basic_string<irr::fschar_t>;
namespace ygo {
class Utils {
public:
class IrrArchiveHelper {
public:
irr::io::IFileArchive* archive;
std::map<path_string/*folder name*/, std::pair<std::pair<int/*begin folder offset*/, int/*end folder offset*/>, std::pair<int/*begin file offset*/, int/*end file offset*/>>> folderindexes;
IrrArchiveHelper(irr::io::IFileArchive* archive) { ParseList(archive); };
void ParseList(irr::io::IFileArchive* archive);
};
static bool Makedirectory(const path_string& path);
static bool Movefile(const path_string& source, const path_string& destination);
static path_string ParseFilename(const std::wstring& input);
static path_string ParseFilename(const std::string& input);
static std::string ToUTF8IfNeeded(const path_string& input);
static std::wstring ToUnicodeIfNeeded(const path_string& input);
static bool Deletefile(const path_string& source);
static bool ClearDirectory(const path_string& path);
static bool Deletedirectory(const path_string& source);
static void CreateResourceFolders();
static void takeScreenshot(irr::IrrlichtDevice* device);
static void ToggleFullscreen();
static void changeCursor(irr::gui::ECURSOR_ICON icon);
static void FindfolderFiles(const path_string& path, const std::function<void(path_string, bool, void*)>& cb, void* payload = nullptr);
static std::vector<path_string> FindfolderFiles(const path_string& path, std::vector<path_string> extensions, int subdirectorylayers = 0);
static void FindfolderFiles(IrrArchiveHelper& archive, const path_string& path, const std::function<bool(int, path_string, bool, void*)>& cb, void* payload = nullptr);
static std::vector<int> FindfolderFiles(IrrArchiveHelper& archive, const path_string& path, std::vector<path_string> extensions, int subdirectorylayers = 0);
static irr::io::IReadFile* FindandOpenFileFromArchives(const path_string& path, const path_string& name);
static std::wstring NormalizePath(std::wstring path, bool trailing_slash = true);
static std::wstring GetFileExtension(std::wstring file);
static std::wstring GetFilePath(std::wstring file);
static std::wstring GetFileName(std::wstring file);
static std::string NormalizePath(std::string path, bool trailing_slash = true);
static std::string GetFileExtension(std::string file);
static std::string GetFilePath(std::string file);
static std::string GetFileName(std::string file);
};
}
#endif //UTILS_H
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment