1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-11-06 09:09:40 +02:00

- some work on sound and music players:

-- Adventure map music will update with hero movement
-- implemented battle intro sounds
-- battle music tracks will  be selected randomly each time
- fixed #781
This commit is contained in:
Ivan Savenko
2011-08-19 19:50:24 +00:00
parent 583f37bf1f
commit 8054c85091
8 changed files with 237 additions and 95 deletions

View File

@@ -1450,7 +1450,7 @@ void CAdvMapInt::select(const CArmedInstance *sel, bool centerView /*= true*/)
assert(sel);
LOCPLINT->cb->setSelection(sel);
selection = sel;
CCS->musich->playMusic(CCS->musich->terrainMusics[LOCPLINT->cb->getTile(sel->visitablePos())->tertype]); //TODO: needs to be updated upon hero movement
CCS->musich->playMusic(CCS->musich->terrainMusics[LOCPLINT->cb->getTile(sel->visitablePos())->tertype]);
if(centerView)
centerOn(sel);

View File

@@ -1477,6 +1477,8 @@ CBattleInterface::~CBattleInterface()
delete siegeH;
curInt->battleInt = NULL;
//TODO:restart music (can be AI or terrain). May be easier to backup and restore it instead of re-selecting
}
void CBattleInterface::setPrintCellBorders(bool set)
@@ -2981,7 +2983,6 @@ void CBattleInterface::displayBattleFinished()
CCS->curh->changeGraphic(0,0);
SDL_Rect temp_rect = genRect(561, 470, (screen->w - 800)/2 + 165, (screen->h - 600)/2 + 19);
CCS->musich->stopMusic();
resWindow = new CBattleResultWindow(*bresult, temp_rect, this);
GH.pushInt(resWindow);
}

View File

@@ -951,7 +951,6 @@ CCastleInterface::CCastleInterface(const CGTownInstance * Town, int listPos):
CCastleInterface::~CCastleInterface()
{
LOCPLINT->castleInt = NULL;
CCS->musich->stopMusic(5000);
delete bicons;
}
@@ -1420,10 +1419,12 @@ CHallInterface::CHallInterface(const CGTownInstance *Town):
int buildingID = boxList[row][col][item];
building = CGI->buildh->buildings[town->subID][buildingID];
if (buildingID == 18 && vstd::contains(town->builtBuildings, town->town->hordeLvl[0]+37))
continue;
if (buildingID == 24 && vstd::contains(town->builtBuildings, town->town->hordeLvl[1]+37))
continue;
//Creature hordes - select unupgraded version if dwelling upgrade was not build yet
if (buildingID == 18 && !vstd::contains(town->builtBuildings, town->town->hordeLvl[0]+37))
break;
if (buildingID == 24 && !vstd::contains(town->builtBuildings, town->town->hordeLvl[1]+37))
break;
if(vstd::contains(town->builtBuildings,buildingID))
continue;
break;

View File

@@ -729,7 +729,6 @@ void startGame(StartInfo * options, CConnection *serv/* = NULL*/)
break;
}
CCS->musich->stopMusic();
client->connectionHandler = new boost::thread(&CClient::run, client);
}

View File

@@ -28,7 +28,13 @@ using namespace boost::assign;
static boost::bimap<soundBase::soundID, std::string> sounds;
// Not pretty, but there's only one music handler object in the game.
static void musicFinishedCallbackC(void) {
static void soundFinishedCallbackC(int channel)
{
CCS->soundh->soundFinishedCallback(channel);
}
static void musicFinishedCallbackC(void)
{
CCS->musich->musicFinishedCallback();
}
@@ -48,7 +54,8 @@ void CAudioBase::init()
void CAudioBase::release()
{
if (initialized) {
if (initialized)
{
Mix_CloseAudio();
initialized = false;
}
@@ -75,32 +82,41 @@ CSoundHandler::CSoundHandler()
// 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) {
if (initialized)
{
// Load sounds
sndh.add_file(std::string(DATA_DIR "/Data/Heroes3.snd"));
sndh.add_file(std::string(DATA_DIR "/Data/Heroes3-cd2.snd"));
sndh.add_file(std::string(DATA_DIR "/Data/H3ab_ahd.snd"));
Mix_ChannelFinished(soundFinishedCallbackC);
}
}
void CSoundHandler::release()
{
if (initialized) {
if (initialized)
{
Mix_HaltChannel(-1);
std::map<soundBase::soundID, Mix_Chunk *>::iterator it;
for (it=soundChunks.begin(); it != soundChunks.end(); it++) {
for (it=soundChunks.begin(); it != soundChunks.end(); it++)
{
if (it->second)
Mix_FreeChunk(it->second);
}
@@ -128,7 +144,8 @@ Mix_Chunk *CSoundHandler::GetSoundChunk(soundBase::soundID soundID)
Mix_Chunk *chunk;
chunk = Mix_LoadWAV_RW(ops, 1); // will free ops
if (!chunk) {
if (!chunk)
{
tlog1 << "Unable to mix sound" << it->second << "(" << Mix_GetError() << ")" << std::endl;
return NULL;
}
@@ -268,8 +285,11 @@ int CSoundHandler::playSound(soundBase::soundID soundID, int repeats)
channel = Mix_PlayChannel(-1, chunk, repeats);
if (channel == -1)
tlog1 << "Unable to play sound file " << soundID << " , error " << Mix_GetError() << std::endl;
} else {
else
callbacks[channel];//insert empty callback
}
else
{
channel = -1;
}
@@ -297,7 +317,32 @@ void CSoundHandler::setVolume(unsigned int percent)
Mix_Volume(-1, (MIX_MAX_VOLUME * volume)/100);
}
CMusicHandler::CMusicHandler(): currentMusic(NULL), nextMusic(NULL)
void CSoundHandler::setCallback(int channel, boost::function<void()> function)
{
std::map<int, boost::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, boost::function<void()> >::iterator iter;
iter = callbacks.find(channel);
assert(iter != callbacks.end());
if (iter->second)
iter->second();
callbacks.erase(iter);
}
CMusicHandler::CMusicHandler()
{
// Map music IDs
#define VCMI_MUSIC_ID(x) ( musicBase::x ,
@@ -332,21 +377,14 @@ void CMusicHandler::init()
void CMusicHandler::release()
{
if (initialized) {
if (initialized)
{
boost::mutex::scoped_lock guard(musicMutex);
Mix_HookMusicFinished(NULL);
musicMutex.lock();
if (currentMusic)
{
Mix_HaltMusic();
Mix_FreeMusic(currentMusic);
}
if (nextMusic)
Mix_FreeMusic(nextMusic);
musicMutex.unlock();
current.reset();
next.reset();
}
CAudioBase::release();
@@ -355,43 +393,40 @@ void CMusicHandler::release()
// Plays a music
// loop: -1 always repeats, 0=do not play, 1+=number of loops
void CMusicHandler::playMusic(musicBase::musicID musicID, int loop)
{
if (current.get() != NULL && *current == musicID)
return;
queueNext(new MusicEntry(this, musicID, loop));
}
// Helper. Randomly plays tracks from music_vec
void CMusicHandler::playMusicFromSet(std::vector<musicBase::musicID> &music_vec, int loop)
{
if (current.get() != NULL && *current == music_vec)
return;
queueNext(new MusicEntry(this, music_vec, loop));
}
void CMusicHandler::queueNext(MusicEntry *queued)
{
if (!initialized)
return;
std::string filename = DATA_DIR "/Mp3/";
filename += musics[musicID];
boost::mutex::scoped_lock guard(musicMutex);
musicMutex.lock();
next.reset(queued);
if (nextMusic)
if (current.get() != NULL)
{
// There's already a music queued, so remove it
Mix_FreeMusic(nextMusic);
nextMusic = NULL;
}
if (currentMusic)
{
// A music is already playing. Stop it and the callback will
// start the new one
nextMusic = LoadMUS(filename.c_str());
nextMusicLoop = loop;
Mix_FadeOutMusic(1000);
current->stop(1000);
}
else
{
currentMusic = LoadMUS(filename.c_str());
PlayMusic(currentMusic,loop);
current = next;
current->play();
}
musicMutex.unlock();
}
// Helper. Randomly select a music from an array and play it
void CMusicHandler::playMusicFromSet(std::vector<musicBase::musicID> &music_vec, int loop)
{
playMusic(music_vec[rand() % music_vec.size()], loop);
}
// Stop and free the current music
@@ -400,14 +435,12 @@ void CMusicHandler::stopMusic(int fade_ms)
if (!initialized)
return;
musicMutex.lock();
boost::mutex::scoped_lock guard(musicMutex);
if (currentMusic)
{
Mix_FadeOutMusic(fade_ms);
}
if (current.get() != NULL)
current->stop(fade_ms);
next.reset();
musicMutex.unlock();
}
// Sets the music volume, from 0 (mute) to 100
@@ -422,42 +455,100 @@ void CMusicHandler::setVolume(unsigned int percent)
// Called by SDL when a music finished.
void CMusicHandler::musicFinishedCallback(void)
{
musicMutex.lock();
boost::mutex::scoped_lock guard(musicMutex);
if (currentMusic)
if (current.get() != NULL)
{
Mix_FreeMusic(currentMusic);
currentMusic = NULL;
//return if current music still not finished
if (current->play())
return;
else
current.reset();
}
if (nextMusic)
if (current.get() == NULL && next.get() != NULL)
{
currentMusic = nextMusic;
nextMusic = NULL;
PlayMusic(currentMusic,nextMusicLoop);
current = next;
current->play();
}
}
musicMutex.unlock();
MusicEntry::MusicEntry(CMusicHandler *_owner, musicBase::musicID _musicID, int _loopCount):
owner(_owner),
music(NULL),
loopCount(_loopCount)
{
load(_musicID);
}
Mix_Music * CMusicHandler::LoadMUS(const char *file)
MusicEntry::MusicEntry(CMusicHandler *_owner, std::vector<musicBase::musicID> &_musicVec, int _loopCount):
currentID(musicBase::music_todo),
owner(_owner),
music(NULL),
loopCount(_loopCount),
musicVec(_musicVec)
{
Mix_Music *ret = Mix_LoadMUS(file);
if(!ret) //load music and check for error
tlog1 << "Unable to load music file (" << file <<"). Error: " << Mix_GetError() << std::endl;
//In this case music will be loaded only on playing - no need to call load() here
}
MusicEntry::~MusicEntry()
{
tlog5<<"Del-ing music file "<<filename<<"\n";
if (music)
Mix_FreeMusic(music);
}
void MusicEntry::load(musicBase::musicID ID)
{
currentID = ID;
filename = DATA_DIR "/Mp3/";
filename += owner->musics[ID];
tlog5<<"Loading music file "<<filename<<"\n";
if (music)
Mix_FreeMusic(music);
music = Mix_LoadMUS(filename.c_str());
#ifdef _WIN32
//The assertion will fail if old MSVC libraries pack .dll is used
assert(Mix_GetMusicType(ret) == MUS_MP3_MAD);
assert(Mix_GetMusicType(music) == MUS_MP3_MAD);
#endif
return ret;
}
int CMusicHandler::PlayMusic(Mix_Music *music, int loops)
bool MusicEntry::play()
{
int ret = Mix_PlayMusic(music, loops);
if(ret == -1)
tlog1 << "Unable to play music (" << Mix_GetError() << ")" << std::endl;
tlog5<<"Playing music file "<<filename<<"\n";
if (loopCount == 0)
return false;
return ret;
if (loopCount > 0)
loopCount--;
if (!musicVec.empty())
load(musicVec.at(rand() % musicVec.size()));
if(Mix_PlayMusic(music, 1) == -1)
{
tlog1 << "Unable to play music (" << Mix_GetError() << ")" << std::endl;
return false;
}
return true;
}
void MusicEntry::stop(int fade_ms)
{
tlog5<<"Stoping music file "<<filename<<"\n";
loopCount = 0;
Mix_FadeOutMusic(fade_ms);
}
bool MusicEntry::operator == (musicBase::musicID _musicID) const
{
return musicVec.empty() && currentID == _musicID;
}
bool MusicEntry::operator == (std::vector<musicBase::musicID> &_musicVec) const
{
return musicVec == _musicVec;
}

View File

@@ -2,6 +2,9 @@
#define __CMUSICHANDLER_H__
#include <boost/thread/mutex.hpp>
#include <boost/function.hpp>
#include <memory>
#include "CSoundBase.h"
#include "CMusicBase.h"
@@ -73,6 +76,10 @@ private:
Mix_Chunk *GetSoundChunk(soundBase::soundID soundID);
//have entry for every currently active channel
//boost::function will be NULL if callback was not set
std::map<int, boost::function<void()> > callbacks;
public:
CSoundHandler();
@@ -87,29 +94,60 @@ public:
int playSound(soundBase::soundID soundID, int repeats=0);
int playSoundFromSet(std::vector<soundBase::soundID> &sound_vec);
void stopSound(int handler);
void setCallback(int channel, boost::function<void()> function);
void soundFinishedCallback(int channel);
std::vector <struct CreaturesBattleSounds> CBattleSounds;
std::map<const CSpell*, soundBase::soundID> spellSounds;
// Sets
std::vector<soundBase::soundID> pickupSounds;
std::vector<soundBase::soundID> horseSounds;
std::vector<soundBase::soundID> battleIntroSounds;
};
// Helper
#define battle_sound(creature,what_sound) CCS->soundh->CBattleSounds[(creature)->idNumber].what_sound
class CMusicHandler;
//Class for handling one music file
class MusicEntry
{
std::string filename; //used only for debugging and console messages
musicBase::musicID currentID;
CMusicHandler *owner;
Mix_Music *music;
int loopCount;
//if not empty - vector from which music will be randomly selected
std::vector<musicBase::musicID> musicVec;
void load(musicBase::musicID);
public:
bool operator == (musicBase::musicID musicID) const;
bool operator == (std::vector<musicBase::musicID> &_musicVec) const;
MusicEntry(CMusicHandler *owner, musicBase::musicID musicID, int _loopCount);
MusicEntry(CMusicHandler *owner, std::vector<musicBase::musicID> &_musicVec, int _loopCount);
~MusicEntry();
bool play();
void stop(int fade_ms=0);
};
class CMusicHandler: public CAudioBase
{
private:
// Because we use the SDL music callback, our music variables must
// be protected
boost::mutex musicMutex;
Mix_Music *currentMusic;
Mix_Music *nextMusic;
int nextMusicLoop;
Mix_Music * LoadMUS(const char *file); //calls Mix_LoadMUS and checks for errors
int PlayMusic(Mix_Music *music, int loops); //calls Mix_PlayMusic and checks for errors
std::auto_ptr<MusicEntry> current;
std::auto_ptr<MusicEntry> next;
void queueNext(MusicEntry *queued);
public:
CMusicHandler();

View File

@@ -257,6 +257,8 @@ void CPlayerInterface::heroMoved(const TryMoveHero & details)
if(makingTurn && ho->tempOwner == playerID) //we are moving our hero - we may need to update assigned path
{
//We may need to change music - select new track, music handler will change it if needed
CCS->musich->playMusic(CCS->musich->terrainMusics[LOCPLINT->cb->getTile(ho->visitablePos())->tertype]);
if(details.result == TryMoveHero::TELEPORTATION || details.start == details.end)
{
if(adventureInt->terrain.currentPath)
@@ -569,7 +571,11 @@ void CPlayerInterface::battleStart(const CCreatureSet *army1, const CCreatureSet
SDL_Delay(20);
boost::unique_lock<boost::recursive_mutex> un(*pim);
CCS->musich->playMusicFromSet(CCS->musich->battleMusics, -1);
CCS->musich->stopMusic();
int channel = CCS->soundh->playSoundFromSet(CCS->soundh->battleIntroSounds);
CCS->soundh->setCallback(channel, boost::bind(&CMusicHandler::playMusicFromSet, CCS->musich, CCS->musich->battleMusics, -1));
GH.pushInt(battleInt);
}
@@ -2051,13 +2057,19 @@ void CPlayerInterface::acceptTurn()
boost::unique_lock<boost::recursive_mutex> un(*pim);
/* TODO: This isn't quite right. First day in game should play
* NEWDAY. And we don't play NEWMONTH. */
//Select sound for day start
int totalDays = cb->getDate();
int day = cb->getDate(1);
if (day != 1)
int week = cb->getDate(2);
if (totalDays == 1)
CCS->soundh->playSound(soundBase::newDay);
else
else if (day != 1)
CCS->soundh->playSound(soundBase::newDay);
else if (week != 1)
CCS->soundh->playSound(soundBase::newWeek);
else
CCS->soundh->playSound(soundBase::newMonth);
adventureInt->infoBar.newDay(day);

View File

@@ -478,7 +478,7 @@ void CClient::serialize( Handler &h, const int version )
{
if(pid == 255)
{
CBattleCallback * cbc = new CBattleCallback(gs, pid, this);
//CBattleCallback * cbc = new CBattleCallback(gs, pid, this);//FIXME: unused?
CBattleGameInterface *cbgi = CDynLibHandler::getNewBattleAI(dllname);
battleints[pid] = cbgi;
cbgi->init(cb);