1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-11-28 08:48:48 +02:00
vcmi/client/CMusicHandler.cpp
Arseniy Shestakov 9fd1cff090 Refactoring: always use std prefix for shared_ptr, unique_ptr and make_shared
Long time ago it's was used without prefix to make future switch from boost to std version easier.
I discusses this with Ivan and decide to drop these using from Global.h now.

This change wouldn't break anything because there was already code with prefix for each of three cases.
2015-12-29 05:43:33 +03:00

506 lines
12 KiB
C++

#include "StdInc.h"
#include <SDL_mixer.h>
#include "CMusicHandler.h"
#include "CGameInfo.h"
#include "../lib/CCreatureHandler.h"
#include "../lib/spells/CSpellHandler.h"
#include "../lib/JsonNode.h"
#include "../lib/GameConstants.h"
#include "../lib/filesystem/Filesystem.h"
#include "../lib/StringConstants.h"
#include "../lib/CRandomGenerator.h"
/*
* CMusicHandler.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#define VCMI_SOUND_NAME(x)
#define VCMI_SOUND_FILE(y) #y,
// sounds mapped to soundBase enum
static std::string sounds[] = {
"", // invalid
"", // todo
VCMI_SOUND_LIST
};
#undef VCMI_SOUND_NAME
#undef VCMI_SOUND_FILE
// Not pretty, but there's only one music handler object in the game.
static void soundFinishedCallbackC(int channel)
{
CCS->soundh->soundFinishedCallback(channel);
}
static void musicFinishedCallbackC(void)
{
CCS->musich->musicFinishedCallback();
}
void CAudioBase::init()
{
if (initialized)
return;
if (Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 1024)==-1)
{
logGlobal->errorStream() << "Mix_OpenAudio error: " << Mix_GetError();
return;
}
initialized = true;
}
void CAudioBase::release()
{
if (initialized)
{
Mix_CloseAudio();
initialized = false;
}
}
void CAudioBase::setVolume(ui32 percent)
{
if (percent > 100)
percent = 100;
volume = percent;
}
void CSoundHandler::onVolumeChange(const JsonNode &volumeNode)
{
setVolume(volumeNode.Float());
}
CSoundHandler::CSoundHandler():
listener(settings.listen["general"]["sound"])
{
listener(std::bind(&CSoundHandler::onVolumeChange, this, _1));
// Vectors for helper(s)
pickupSounds =
{
soundBase::pickup01, soundBase::pickup02, soundBase::pickup03,
soundBase::pickup04, soundBase::pickup05, soundBase::pickup06, soundBase::pickup07
};
horseSounds = // must be the same order as terrains (see ETerrainType);
{
soundBase::horseDirt, soundBase::horseSand, soundBase::horseGrass,
soundBase::horseSnow, soundBase::horseSwamp, soundBase::horseRough,
soundBase::horseSubterranean, soundBase::horseLava,
soundBase::horseWater, soundBase::horseRock
};
battleIntroSounds =
{
soundBase::battle00, soundBase::battle01,
soundBase::battle02, soundBase::battle03, soundBase::battle04,
soundBase::battle05, soundBase::battle06, soundBase::battle07
};
};
void CSoundHandler::init()
{
CAudioBase::init();
if (initialized)
{
// Load sounds
Mix_ChannelFinished(soundFinishedCallbackC);
}
}
void CSoundHandler::release()
{
if (initialized)
{
Mix_HaltChannel(-1);
for (auto &chunk : soundChunks)
{
if (chunk.second)
Mix_FreeChunk(chunk.second);
}
}
CAudioBase::release();
}
// Allocate an SDL chunk and cache it.
Mix_Chunk *CSoundHandler::GetSoundChunk(std::string &sound, bool cache)
{
try
{
if (cache && soundChunks.find(sound) != soundChunks.end())
return soundChunks[sound];
auto data = CResourceHandler::get()->load(ResourceID(std::string("SOUNDS/") + sound, EResType::SOUND))->readAll();
SDL_RWops *ops = SDL_RWFromMem(data.first.release(), data.second);
Mix_Chunk *chunk = Mix_LoadWAV_RW(ops, 1); // will free ops
if (cache)
soundChunks.insert(std::pair<std::string, Mix_Chunk *>(sound, chunk));
return chunk;
}
catch(std::exception &e)
{
logGlobal->warnStream() << "Cannot get sound " << sound << " chunk: " << e.what();
return nullptr;
}
}
// Plays a sound, and return its channel so we can fade it out later
int CSoundHandler::playSound(soundBase::soundID soundID, int repeats)
{
assert(soundID < soundBase::sound_after_last);
auto sound = sounds[soundID];
logGlobal->traceStream() << "Attempt to play sound " << soundID << " with file name " << sound << " with cache";
return playSound(sound, repeats, true);
}
int CSoundHandler::playSound(std::string sound, int repeats, bool cache)
{
if (!initialized || sound.empty())
return -1;
int channel;
Mix_Chunk *chunk = GetSoundChunk(sound, cache);
if (chunk)
{
channel = Mix_PlayChannel(-1, chunk, repeats);
if (channel == -1)
{
logGlobal->errorStream() << "Unable to play sound file " << sound << " , error " << Mix_GetError();
if (!cache)
Mix_FreeChunk(chunk);
}
else if (cache)
callbacks[channel];
else
callbacks[channel] = [chunk]{ Mix_FreeChunk(chunk);};
}
else
channel = -1;
return channel;
}
// Helper. Randomly select a sound from an array and play it
int CSoundHandler::playSoundFromSet(std::vector<soundBase::soundID> &sound_vec)
{
return playSound(*RandomGeneratorUtil::nextItem(sound_vec, CRandomGenerator::getDefault()));
}
void CSoundHandler::stopSound( int handler )
{
if (initialized && handler != -1)
Mix_HaltChannel(handler);
}
// Sets the sound volume, from 0 (mute) to 100
void CSoundHandler::setVolume(ui32 percent)
{
CAudioBase::setVolume(percent);
if (initialized)
Mix_Volume(-1, (MIX_MAX_VOLUME * volume)/100);
}
void CSoundHandler::setCallback(int channel, std::function<void()> function)
{
std::map<int, std::function<void()> >::iterator iter;
iter = callbacks.find(channel);
//channel not found. It may have finished so fire callback now
if(iter == callbacks.end())
function();
else
iter->second = function;
}
void CSoundHandler::soundFinishedCallback(int channel)
{
std::map<int, std::function<void()> >::iterator iter;
iter = callbacks.find(channel);
assert(iter != callbacks.end());
if (iter->second)
iter->second();
callbacks.erase(iter);
}
void CMusicHandler::onVolumeChange(const JsonNode &volumeNode)
{
setVolume(volumeNode.Float());
}
CMusicHandler::CMusicHandler():
listener(settings.listen["general"]["music"])
{
listener(std::bind(&CMusicHandler::onVolumeChange, this, _1));
// Map music IDs
// Vectors for helper
const std::string setEnemy[] = {"AITheme0", "AITheme1", "AITheme2"};
const std::string setBattle[] = {"Combat01", "Combat02", "Combat03", "Combat04"};
auto fillSet = [=](std::string setName, const std::string list[], size_t amount)
{
for (size_t i=0; i < amount; i++)
addEntryToSet(setName, i, "music/" + list[i]);
};
fillSet("enemy-turn", setEnemy, ARRAY_COUNT(setEnemy));
fillSet("battle", setBattle, ARRAY_COUNT(setBattle));
JsonNode terrains(ResourceID("config/terrains.json"));
for (auto entry : terrains.Struct())
{
int terrIndex = vstd::find_pos(GameConstants::TERRAIN_NAMES, entry.first);
addEntryToSet("terrain", terrIndex, "Music/" + entry.second["music"].String());
}
}
void CMusicHandler::addEntryToSet(std::string set, int musicID, std::string musicURI)
{
musicsSet[set][musicID] = musicURI;
}
void CMusicHandler::init()
{
CAudioBase::init();
if (initialized)
Mix_HookMusicFinished(musicFinishedCallbackC);
}
void CMusicHandler::release()
{
if (initialized)
{
boost::mutex::scoped_lock guard(musicMutex);
Mix_HookMusicFinished(nullptr);
current.reset();
next.reset();
}
CAudioBase::release();
}
void CMusicHandler::playMusic(std::string musicURI, bool loop)
{
if (current && current->isTrack( musicURI))
return;
queueNext(this, "", musicURI, loop);
}
void CMusicHandler::playMusicFromSet(std::string whichSet, bool loop)
{
auto selectedSet = musicsSet.find(whichSet);
if (selectedSet == musicsSet.end())
{
logGlobal->errorStream() << "Error: playing music from non-existing set: " << whichSet;
return;
}
if (current && current->isSet(whichSet))
return;
// in this mode - play random track from set
queueNext(this, whichSet, "", loop);
}
void CMusicHandler::playMusicFromSet(std::string whichSet, int entryID, bool loop)
{
auto selectedSet = musicsSet.find(whichSet);
if (selectedSet == musicsSet.end())
{
logGlobal->errorStream() << "Error: playing music from non-existing set: " << whichSet;
return;
}
auto selectedEntry = selectedSet->second.find(entryID);
if (selectedEntry == selectedSet->second.end())
{
logGlobal->errorStream() << "Error: playing non-existing entry " << entryID << " from set: " << whichSet;
return;
}
if (current && current->isTrack( selectedEntry->second))
return;
// in this mode - play specific track from set
queueNext(this, "", selectedEntry->second, loop);
}
void CMusicHandler::queueNext(std::unique_ptr<MusicEntry> queued)
{
if (!initialized)
return;
boost::mutex::scoped_lock guard(musicMutex);
next = std::move(queued);
if (current.get() == nullptr || !current->stop(1000))
{
current.reset(next.release());
current->play();
}
}
void CMusicHandler::queueNext(CMusicHandler *owner, std::string setName, std::string musicURI, bool looped)
{
try
{
queueNext(make_unique<MusicEntry>(owner, setName, musicURI, looped));
}
catch(std::exception &e)
{
logGlobal->errorStream() << "Failed to queue music. setName=" << setName << "\tmusicURI=" << musicURI;
logGlobal->errorStream() << "Exception: " << e.what();
}
}
void CMusicHandler::stopMusic(int fade_ms)
{
if (!initialized)
return;
boost::mutex::scoped_lock guard(musicMutex);
if (current.get() != nullptr)
current->stop(fade_ms);
next.reset();
}
void CMusicHandler::setVolume(ui32 percent)
{
CAudioBase::setVolume(percent);
if (initialized)
Mix_VolumeMusic((MIX_MAX_VOLUME * volume)/100);
}
void CMusicHandler::musicFinishedCallback(void)
{
boost::mutex::scoped_lock guard(musicMutex);
if (current.get() != nullptr)
{
//return if current music still not finished
if (current->play())
return;
else
current.reset();
}
if (current.get() == nullptr && next.get() != nullptr)
{
current.reset(next.release());
current->play();
}
}
MusicEntry::MusicEntry(CMusicHandler *owner, std::string setName, std::string musicURI, bool looped):
owner(owner),
music(nullptr),
musicFile(nullptr),
loop(looped ? -1 : 1),
setName(setName)
{
if (!musicURI.empty())
load(musicURI);
}
MusicEntry::~MusicEntry()
{
logGlobal->traceStream()<<"Del-ing music file "<<currentName;
if (music)
Mix_FreeMusic(music);
}
void MusicEntry::load(std::string musicURI)
{
if (music)
{
logGlobal->traceStream()<<"Del-ing music file "<<currentName;
Mix_FreeMusic(music);
music = nullptr;
}
currentName = musicURI;
logGlobal->traceStream()<<"Loading music file "<<musicURI;
data = CResourceHandler::get()->load(ResourceID(musicURI, EResType::MUSIC))->readAll();
musicFile = SDL_RWFromConstMem(data.first.get(), data.second);
music = Mix_LoadMUS_RW(musicFile, SDL_FALSE);
if(!music)
{
SDL_FreeRW(musicFile);
musicFile = nullptr;
logGlobal->warnStream() << "Warning: Cannot open " << currentName << ": " << Mix_GetError();
return;
}
}
bool MusicEntry::play()
{
if (!(loop--) && music) //already played once - return
return false;
if (!setName.empty())
{
auto set = owner->musicsSet[setName];
load(RandomGeneratorUtil::nextItem(set, CRandomGenerator::getDefault())->second);
}
logGlobal->traceStream()<<"Playing music file "<<currentName;
if(Mix_PlayMusic(music, 1) == -1)
{
logGlobal->errorStream() << "Unable to play music (" << Mix_GetError() << ")";
return false;
}
return true;
}
bool MusicEntry::stop(int fade_ms)
{
if (Mix_PlayingMusic())
{
logGlobal->traceStream()<<"Stoping music file "<<currentName;
loop = 0;
Mix_FadeOutMusic(fade_ms);
return true;
}
return false;
}
bool MusicEntry::isSet(std::string set)
{
return !setName.empty() && set == setName;
}
bool MusicEntry::isTrack(std::string track)
{
return setName.empty() && track == currentName;
}