Prechádzať zdrojové kódy

Sound patch from Ubuntux #11: Add music infrastruture + some musics in various places
[With mine minor fixes and updated MSVC project]
vcmi_music.diff

Michał W. Urbańczyk 16 rokov pred
rodič
commit
8c6c0df45e

+ 7 - 0
CBattleInterface.cpp

@@ -1785,6 +1785,7 @@ void CBattleInterface::battleFinished(const BattleResult& br)
 	CGI->curh->changeGraphic(0,0);
 	
 	SDL_Rect temp_rect = genRect(561, 470, (screen->w - 800)/2 + 165, (screen->h - 600)/2 + 19);
+	CGI->mush->stopMusic();
 	resWindow = new CBattleReslutWindow(br, temp_rect, this);
 	LOCPLINT->pushInt(resWindow);
 }
@@ -2787,30 +2788,36 @@ CBattleReslutWindow::CBattleReslutWindow(const BattleResult &br, const SDL_Rect
 	case 0: //normal victory
 		if((br.winner == 0 && weAreAttacker) || (br.winner == 1 && !weAreAttacker)) //we've won
 		{
+			CGI->mush->playMusic(musicBase::winBattle);
 			CSDL_Ext::printAtMiddle(CGI->generaltexth->allTexts[304], 235, 235, GEOR13, zwykly, background);
 		}
 		else
 		{
+			CGI->mush->playMusic(musicBase::loseCombat);
 			CSDL_Ext::printAtMiddle(CGI->generaltexth->allTexts[311], 235, 235, GEOR13, zwykly, background);
 		}
 		break;
 	case 1: //flee
 		if((br.winner == 0 && weAreAttacker) || (br.winner == 1 && !weAreAttacker)) //we've won
 		{
+			CGI->mush->playMusic(musicBase::winBattle);
 			CSDL_Ext::printAtMiddle(CGI->generaltexth->allTexts[303], 235, 235, GEOR13, zwykly, background);
 		}
 		else
 		{
+			CGI->mush->playMusic(musicBase::retreatBattle);
 			CSDL_Ext::printAtMiddle(CGI->generaltexth->allTexts[310], 235, 235, GEOR13, zwykly, background);
 		}
 		break;
 	case 2: //surrender
 		if((br.winner == 0 && weAreAttacker) || (br.winner == 1 && !weAreAttacker)) //we've won
 		{
+			CGI->mush->playMusic(musicBase::winBattle);
 			CSDL_Ext::printAtMiddle(CGI->generaltexth->allTexts[302], 235, 235, GEOR13, zwykly, background);
 		}
 		else
 		{
+			CGI->mush->playMusic(musicBase::surrenderBattle);
 			CSDL_Ext::printAtMiddle(CGI->generaltexth->allTexts[309], 235, 235, GEOR13, zwykly, background);
 		}
 		break;

+ 10 - 0
CCastleInterface.cpp

@@ -433,30 +433,39 @@ CCastleInterface::CCastleInterface(const CGTownInstance * Town)
 	{
 	case 0:
 		defname = "HALLCSTL.DEF";
+		musicID = musicBase::castleTown;
 		break;
 	case 1:
 		defname = "HALLRAMP.DEF";
+		musicID = musicBase::rampartTown;
 		break;
 	case 2:
 		defname = "HALLTOWR.DEF";
+		musicID = musicBase::towerTown;
 		break;
 	case 3:
 		defname = "HALLINFR.DEF";
+		musicID = musicBase::infernoTown;
 		break;
 	case 4:
 		defname = "HALLNECR.DEF";
+		musicID = musicBase::necroTown;
 		break;
 	case 5:
 		defname = "HALLDUNG.DEF";
+		musicID = musicBase::dungeonTown;
 		break;
 	case 6:
 		defname = "HALLSTRN.DEF";
+		musicID = musicBase::strongHoldTown;
 		break;
 	case 7:
 		defname = "HALLFORT.DEF";
+		musicID = musicBase::fortressTown;
 		break;
 	case 8:
 		defname = "HALLELEM.DEF";
+		musicID = musicBase::elemTown;
 		break;
 	default:
 		throw new std::string("Wrong town subID");
@@ -492,6 +501,7 @@ void CCastleInterface::close()
 		LOCPLINT->adventureInt->select(town->visitingHero);
 	LOCPLINT->castleInt = NULL;
 	LOCPLINT->popIntTotally(this);
+	CGI->mush->stopMusic(5000);
 }
 
 void CCastleInterface::splitF()

+ 2 - 0
CCastleInterface.h

@@ -85,6 +85,8 @@ public:
 	AdventureMapButton *exit;
 	AdventureMapButton *split;
 
+	musicBase::musicID musicID;
+
 	std::vector<CBuildingRect*> buildings; //building id, building def, structure struct, border, filling
 
 	CCastleInterface(const CGTownInstance * Town);

+ 3 - 1
CMT.cpp

@@ -147,8 +147,8 @@ int main(int argc, char** argv)
 		CPreGame * cpg = new CPreGame(); //main menu and submenus
 		tlog0<<"Initialization CPreGame (together): "<<tmh.getDif()<<std::endl;
 		tlog0<<"Initialization of VCMI (together): "<<total.getDif()<<std::endl;
-		cpg->mush = mush;
 
+		mush->playMusic(musicBase::mainMenu, -1);
 		StartInfo *options = new StartInfo(cpg->runLoop());
 
 		if(screen->w != conf.cc.resx   ||   screen->h != conf.cc.resy)
@@ -187,6 +187,7 @@ int main(int argc, char** argv)
 			THC tlog0<<"\tConnecting to the server: "<<tmh.getDif()<<std::endl;
 			cl.newGame(c,options);
 			client = &cl;
+			mush->stopMusic();
 			boost::thread t(boost::bind(&CClient::run,&cl));
 		}
 		else //load game
@@ -195,6 +196,7 @@ int main(int argc, char** argv)
 			boost::algorithm::erase_last(fname,".vlgm1");
 			cl.load(fname);
 			client = &cl;
+			mush->stopMusic();
 			boost::thread t(boost::bind(&CClient::run,&cl));
 		}
 

+ 0 - 1
CMessage.h

@@ -4,7 +4,6 @@
 #include "global.h"
 #include <SDL_ttf.h>
 #include <SDL.h>
-#include "CPreGame.h"
 
 /*
  * CMessage.h, part of VCMI engine

+ 2 - 0
CPlayerInterface.cpp

@@ -1837,6 +1837,7 @@ void CPlayerInterface::heroCreated(const CGHeroInstance * hero)
 void CPlayerInterface::openTownWindow(const CGTownInstance * town)
 {
 	castleInt = new CCastleInterface(town);
+	CGI->mush->playMusic(castleInt->musicID, -1);
 	LOCPLINT->pushInt(castleInt);
 }
 
@@ -2197,6 +2198,7 @@ void CPlayerInterface::battleStart(CCreatureSet *army1, CCreatureSet *army2, int
 
 	boost::unique_lock<boost::recursive_mutex> un(*pim);
 	battleInt = new CBattleInterface(army1, army2, hero1, hero2, genRect(600, 800, (conf.cc.resx - 800)/2, (conf.cc.resy - 600)/2));
+	CGI->mush->playMusicFromSet(CGI->mush->battleMusics, -1);
 	pushInt(battleInt);
 }
 

+ 4 - 4
CPreGame.cpp

@@ -22,7 +22,7 @@
 #include <boost/bind.hpp>
 #include <cstdlib>
 #include "lib/Connection.h"
-
+#include "hch/CMusicHandler.h"
 /*
  * CPreGame.cpp, part of VCMI engine
  *
@@ -1973,7 +1973,7 @@ void CPreGame::scenHandleEv(SDL_Event& sEvent)
 		{
 			if (isItIn(&btns[i]->pos,sEvent.motion.x,sEvent.motion.y))
 			{
-				mush->playSound(soundBase::button);
+				CGI->mush->playSound(soundBase::button);
 				btns[i]->press(true);
 				ourScenSel->pressed=(Button*)btns[i];
 			}
@@ -1982,7 +1982,7 @@ void CPreGame::scenHandleEv(SDL_Event& sEvent)
 									&& (sEvent.button.x>55) && (sEvent.button.x<372))
 		{
 			int py = ((sEvent.button.y-121)/25)+ourScenSel->mapsel.slid->whereAreWe;
-			mush->playSound(soundBase::button);
+			CGI->mush->playSound(soundBase::button);
 			ourScenSel->mapsel.select(ourScenSel->mapsel.whichWL(py));
 		}
 
@@ -2237,7 +2237,7 @@ StartInfo CPreGame::runLoop()
 						current->highlighted=5;
 					}
 					if (current->highlighted)
-						mush->playSound(soundBase::button);
+						CGI->mush->playSound(soundBase::button);
 				}
 				else if ((sEvent.type==SDL_MOUSEBUTTONUP) && (sEvent.button.button == SDL_BUTTON_LEFT))
 				{

+ 1 - 2
CPreGame.h

@@ -6,7 +6,6 @@
 #include "StartInfo.h"
 #include "CMessage.h"
 #include "map.h"
-#include "hch/CMusicHandler.h"
 #include <boost/function.hpp>
 #include <boost/bind.hpp>
 #include <cstdlib>
@@ -21,6 +20,7 @@
  *
  */
 
+struct CMusicHandler;
 class CPreGame;
 class CDefHandler;
 extern CPreGame * CPG;
@@ -256,7 +256,6 @@ public:
 	bool first; //hasn't we showed the scensel
 	int fromnewgame; //1 - new game; 0 - load game; 2 - save game
 	std::vector<Slider *> interested;
-	CMusicHandler * mush;
 	std::vector<HighButton *> btns;
 	SDL_Rect * currentMessage;
 	SDL_Surface * behindCurMes;

+ 4 - 0
client/VCMI_client.vcproj

@@ -464,6 +464,10 @@
 				RelativePath="..\CMessage.h"
 				>
 			</File>
+			<File
+				RelativePath="..\hch\CMusicBase.h"
+				>
+			</File>
 			<File
 				RelativePath="..\hch\CMusicHandler.h"
 				>

+ 82 - 0
hch/CMusicBase.h

@@ -0,0 +1,82 @@
+#ifndef __CMUSICBASE_H__
+#define __CMUSICBASE_H__
+
+// Use some magic to keep the list of files and their code name in sync.
+
+#define VCMI_MUSIC_LIST \
+	VCMI_MUSIC_ID(AITheme0) VCMI_MUSIC_FILE("AITheme0.mp3") \
+	VCMI_MUSIC_ID(AITheme1) VCMI_MUSIC_FILE("AITHEME1.MP3") \
+	VCMI_MUSIC_ID(AITheme2) VCMI_MUSIC_FILE("AITHEME2.MP3") \
+	VCMI_MUSIC_ID(bladeABCampaign) VCMI_MUSIC_FILE("BladeABCampaign.mp3") \
+	VCMI_MUSIC_ID(bladeDBCampaign) VCMI_MUSIC_FILE("BladeDBCampaign.mp3") \
+	VCMI_MUSIC_ID(bladeDSCampaign) VCMI_MUSIC_FILE("BladeDSCampaign.mp3") \
+	VCMI_MUSIC_ID(bladeFLCampaign) VCMI_MUSIC_FILE("BladeFLCampaign.mp3") \
+	VCMI_MUSIC_ID(bladeFWCampaign) VCMI_MUSIC_FILE("BladeFWCampaign.mp3") \
+	VCMI_MUSIC_ID(bladePFCampaign) VCMI_MUSIC_FILE("BladePFCampaign.mp3") \
+	VCMI_MUSIC_ID(campainMusic01) VCMI_MUSIC_FILE("CampainMusic01.mp3") \
+	VCMI_MUSIC_ID(campainMusic02) VCMI_MUSIC_FILE("CampainMusic02.mp3") \
+	VCMI_MUSIC_ID(campainMusic03) VCMI_MUSIC_FILE("CampainMusic03.mp3") \
+	VCMI_MUSIC_ID(campainMusic04) VCMI_MUSIC_FILE("CampainMusic04.mp3") \
+	VCMI_MUSIC_ID(campainMusic05) VCMI_MUSIC_FILE("CampainMusic05.mp3") \
+	VCMI_MUSIC_ID(campainMusic06) VCMI_MUSIC_FILE("CampainMusic06.mp3") \
+	VCMI_MUSIC_ID(campainMusic07) VCMI_MUSIC_FILE("CampainMusic07.mp3") \
+	VCMI_MUSIC_ID(campainMusic08) VCMI_MUSIC_FILE("CampainMusic08.mp3") \
+	VCMI_MUSIC_ID(campainMusic09) VCMI_MUSIC_FILE("CampainMusic09.mp3") \
+	VCMI_MUSIC_ID(campainMusic10) VCMI_MUSIC_FILE("CampainMusic10.mp3") \
+	VCMI_MUSIC_ID(campainMusic11) VCMI_MUSIC_FILE("CampainMusic11.mp3") \
+	VCMI_MUSIC_ID(combat1) VCMI_MUSIC_FILE("COMBAT01.MP3") \
+	VCMI_MUSIC_ID(combat2) VCMI_MUSIC_FILE("COMBAT02.MP3") \
+	VCMI_MUSIC_ID(combat3) VCMI_MUSIC_FILE("COMBAT03.MP3") \
+	VCMI_MUSIC_ID(combat4) VCMI_MUSIC_FILE("COMBAT04.MP3") \
+	VCMI_MUSIC_ID(castleTown) VCMI_MUSIC_FILE("CstleTown.mp3") \
+	VCMI_MUSIC_ID(defendCastle) VCMI_MUSIC_FILE("Defend Castle.mp3") \
+	VCMI_MUSIC_ID(dirt) VCMI_MUSIC_FILE("DIRT.MP3") \
+	VCMI_MUSIC_ID(dungeonTown) VCMI_MUSIC_FILE("DUNGEON.MP3") \
+	VCMI_MUSIC_ID(elemTown) VCMI_MUSIC_FILE("ElemTown.mp3") \
+	VCMI_MUSIC_ID(evilTheme) VCMI_MUSIC_FILE("EvilTheme.mp3") \
+	VCMI_MUSIC_ID(fortressTown) VCMI_MUSIC_FILE("FortressTown.mp3") \
+	VCMI_MUSIC_ID(goodTheme) VCMI_MUSIC_FILE("GoodTheme.mp3") \
+	VCMI_MUSIC_ID(grass) VCMI_MUSIC_FILE("GRASS.MP3") \
+	VCMI_MUSIC_ID(infernoTown) VCMI_MUSIC_FILE("InfernoTown.mp3") \
+	VCMI_MUSIC_ID(lava) VCMI_MUSIC_FILE("LAVA.MP3") \
+	VCMI_MUSIC_ID(loopLepr) VCMI_MUSIC_FILE("LoopLepr.mp3") \
+	VCMI_MUSIC_ID(loseCampain) VCMI_MUSIC_FILE("Lose Campain.mp3") \
+	VCMI_MUSIC_ID(loseCastle) VCMI_MUSIC_FILE("LoseCastle.mp3") \
+	VCMI_MUSIC_ID(loseCombat) VCMI_MUSIC_FILE("LoseCombat.mp3") \
+	VCMI_MUSIC_ID(mainMenu) VCMI_MUSIC_FILE("MAINMENU.MP3") \
+	VCMI_MUSIC_ID(mainMenuWoG) VCMI_MUSIC_FILE("MainMenuWoG.mp3") \
+	VCMI_MUSIC_ID(necroTown) VCMI_MUSIC_FILE("necroTown.mp3") \
+	VCMI_MUSIC_ID(neutralTheme) VCMI_MUSIC_FILE("NeutralTheme.mp3") \
+	VCMI_MUSIC_ID(rampartTown) VCMI_MUSIC_FILE("RAMPART.MP3") \
+	VCMI_MUSIC_ID(retreatBattle) VCMI_MUSIC_FILE("Retreat Battle.mp3") \
+	VCMI_MUSIC_ID(rough) VCMI_MUSIC_FILE("ROUGH.MP3") \
+	VCMI_MUSIC_ID(sand) VCMI_MUSIC_FILE("SAND.MP3") \
+	VCMI_MUSIC_ID(secretTheme) VCMI_MUSIC_FILE("SecretTheme.mp3") \
+	VCMI_MUSIC_ID(snow) VCMI_MUSIC_FILE("SNOW.MP3") \
+	VCMI_MUSIC_ID(strongHoldTown) VCMI_MUSIC_FILE("StrongHold.mp3") \
+	VCMI_MUSIC_ID(surrenderBattle) VCMI_MUSIC_FILE("Surrender Battle.mp3") \
+	VCMI_MUSIC_ID(swamp) VCMI_MUSIC_FILE("SWAMP.MP3") \
+	VCMI_MUSIC_ID(towerTown) VCMI_MUSIC_FILE("TowerTown.mp3") \
+	VCMI_MUSIC_ID(ultimateLose) VCMI_MUSIC_FILE("UltimateLose.mp3") \
+	VCMI_MUSIC_ID(underground) VCMI_MUSIC_FILE("Underground.mp3") \
+	VCMI_MUSIC_ID(water) VCMI_MUSIC_FILE("WATER.MP3") \
+	VCMI_MUSIC_ID(winBattle) VCMI_MUSIC_FILE("Win Battle.mp3") \
+	VCMI_MUSIC_ID(winScenario) VCMI_MUSIC_FILE("Win Scenario.mp3" )
+
+class musicBase
+{
+public:
+	// Make a list of enums
+#define VCMI_MUSIC_ID(x) x,
+#define VCMI_MUSIC_FILE(y)
+	enum musicID {
+		music_todo=0,			// temp entry until code is fixed
+		VCMI_MUSIC_LIST
+	};
+#undef VCMI_MUSIC_ID
+#undef VCMI_MUSIC_FILE
+};
+
+#endif // __CMUSICBASE_H__
+
+

+ 105 - 43
hch/CMusicHandler.cpp

@@ -24,7 +24,12 @@
 
 using namespace boost::assign;
 
-boost::bimap<soundBase::soundID, std::string> sounds;
+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) {
+	CGI->mush->musicFinishedCallback();
+}
 
 CMusicHandler::~CMusicHandler()
 {
@@ -42,6 +47,20 @@ CMusicHandler::~CMusicHandler()
 			Mix_FreeChunk(it->second);
 	}
 
+	Mix_HookMusicFinished(NULL);
+
+	musicMutex.lock();
+
+	if (currentMusic) {
+		Mix_HaltMusic();
+		Mix_FreeMusic(currentMusic);
+	}
+
+	if (nextMusic)
+		Mix_FreeMusic(nextMusic);
+
+	musicMutex.unlock();
+
 	Mix_CloseAudio();
 }
 
@@ -75,51 +94,23 @@ void CMusicHandler::initMusics()
 		soundBase::horseSubterranean, soundBase::horseLava,
 		soundBase::horseWater, soundBase::horseRock;
 
-	//AITheme0 = Mix_LoadMUS(DATA_DIR "MP3" PATHSEPARATOR "AITheme0.mp3");
-	//AITheme1 = Mix_LoadMUS(DATA_DIR "MP3" PATHSEPARATOR "AITHEME1.mp3");
-	//AITheme2 = Mix_LoadMUS(DATA_DIR "MP3" PATHSEPARATOR "AITHEME2.mp3");
-	//buildTown = Mix_LoadWAV("MP3" PATHSEPARATOR "BUILDTWN.wav");
-	//combat1 = Mix_LoadMUS(DATA_DIR "MP3" PATHSEPARATOR "COMBAT01.mp3");
-	//combat2 = Mix_LoadMUS(DATA_DIR "MP3" PATHSEPARATOR "COMBAT02.mp3");
-	//combat3 = Mix_LoadMUS(DATA_DIR "MP3" PATHSEPARATOR "COMBAT03.mp3");
-	//combat4 = Mix_LoadMUS(DATA_DIR "MP3" PATHSEPARATOR "COMBAT04.mp3");
-	//castleTown = Mix_LoadMUS(DATA_DIR "MP3" PATHSEPARATOR "CstleTown.mp3");
-	//defendCastle = Mix_LoadMUS(DATA_DIR "MP3" PATHSEPARATOR "Defend Castle.mp3");
-	//dirt = Mix_LoadMUS(DATA_DIR "MP3" PATHSEPARATOR "DIRT.mp3");
-	//dungeon = Mix_LoadMUS(DATA_DIR "MP3" PATHSEPARATOR "DUNGEON.mp3");
-	//elemTown = Mix_LoadMUS(DATA_DIR "MP3" PATHSEPARATOR "ElemTown.mp3");
-	//evilTheme = Mix_LoadMUS(DATA_DIR "MP3" PATHSEPARATOR "EvilTheme.mp3");
-	//fortressTown = Mix_LoadMUS(DATA_DIR "MP3" PATHSEPARATOR "FortressTown.mp3");
-	//goodTheme = Mix_LoadMUS(DATA_DIR "MP3" PATHSEPARATOR "GoodTheme.mp3");
-	//grass = Mix_LoadMUS(DATA_DIR "MP3" PATHSEPARATOR "GRASS.mp3");
-	//infernoTown = Mix_LoadMUS(DATA_DIR "MP3" PATHSEPARATOR "InfernoTown.mp3");
-	//lava = Mix_LoadMUS(DATA_DIR "MP3" PATHSEPARATOR "LAVA.mp3");
-	//loopLepr = Mix_LoadMUS(DATA_DIR "MP3" PATHSEPARATOR "LoopLepr.mp3");
-	//loseCampain = Mix_LoadMUS(DATA_DIR "MP3" PATHSEPARATOR "Lose Campain.mp3");
-	//loseCastle = Mix_LoadMUS(DATA_DIR "MP3" PATHSEPARATOR "LoseCastle.mp3");
-	//loseCombat = Mix_LoadMUS(DATA_DIR "MP3" PATHSEPARATOR "LoseCombat.mp3");
-	//mainMenu = Mix_LoadMUS(DATA_DIR "MP3" PATHSEPARATOR "MAINMENU.mp3");
-	//mainMenuWoG = Mix_LoadMUS(DATA_DIR "MP3" PATHSEPARATOR "MainMenuWoG.mp3");
-	//necroTown = Mix_LoadMUS(DATA_DIR "MP3" PATHSEPARATOR "necroTown.mp3");
-	//neutralTheme = Mix_LoadMUS(DATA_DIR "MP3" PATHSEPARATOR "NeutralTheme.mp3");
-	//rampart = Mix_LoadMUS(DATA_DIR "MP3" PATHSEPARATOR "RAMPART.mp3");
-	//retreatBattle = Mix_LoadMUS(DATA_DIR "MP3" PATHSEPARATOR "Retreat Battle.mp3");
-	//rough = Mix_LoadMUS(DATA_DIR "MP3" PATHSEPARATOR "ROUGH.mp3");
-	//sand = Mix_LoadMUS(DATA_DIR "MP3" PATHSEPARATOR "SAND.mp3");
-	//secretTheme = Mix_LoadMUS(DATA_DIR "MP3" PATHSEPARATOR "SecretTheme.mp3");
-	//snow = Mix_LoadMUS(DATA_DIR "MP3" PATHSEPARATOR "SNOW.mp3");
-	//stronghold = Mix_LoadMUS(DATA_DIR "MP3" PATHSEPARATOR "StrongHold.mp3");
-	//surrenderBattle = Mix_LoadMUS(DATA_DIR "MP3" PATHSEPARATOR "Surrender Battle.mp3");
-	//swamp = Mix_LoadMUS(DATA_DIR "MP3" PATHSEPARATOR "SWAMP.mp3");
-	//towerTown = Mix_LoadMUS(DATA_DIR "MP3" PATHSEPARATOR "TowerTown.mp3");
-	//ultimateLose = Mix_LoadMUS(DATA_DIR "MP3" PATHSEPARATOR "UltimateLose.mp3");
-	//underground = Mix_LoadMUS(DATA_DIR "MP3" PATHSEPARATOR "Underground.mp3");
-	//water = Mix_LoadMUS(DATA_DIR "MP3" PATHSEPARATOR "WATER.mp3");
-	//winBattle = Mix_LoadMUS(DATA_DIR "MP3" PATHSEPARATOR "Win Battle.mp3");
-	//winScenario = Mix_LoadMUS(DATA_DIR "MP3" PATHSEPARATOR "Win Scenario.mp3");
+	// Map music IDs
+#define VCMI_MUSIC_ID(x) ( musicBase::x ,
+#define VCMI_MUSIC_FILE(y) y )
+	musics = map_list_of
+		VCMI_MUSIC_LIST;
+#undef VCMI_MUSIC_NAME
+#undef VCMI_MUSIC_FILE
+
+	Mix_HookMusicFinished(musicFinishedCallbackC);
+
+	// Vector for helper
+	battleMusics += musicBase::combat1, musicBase::combat2, 
+		musicBase::combat3, musicBase::combat4;
 
 	// Load sounds
 	sndh = new CSndHandler(std::string(DATA_DIR "Data" PATHSEPARATOR "Heroes3.snd"));
+	nextMusic = NULL;
 }
 
 // Allocate an SDL chunk and cache it.
@@ -260,4 +251,75 @@ void CMusicHandler::stopSound( int handler )
 {
 	if (handler != -1)
 		Mix_HaltChannel(handler);
+}
+
+// Plays a music
+// loop: -1 always repeats, 0=do not play, 1+=number of loops
+void CMusicHandler::playMusic(musicBase::musicID musicID, int loop)
+{
+	if (!sndh)
+		return;
+
+	std::string filename = DATA_DIR "Mp3" PATHSEPARATOR;
+	filename += musics[musicID];
+
+	musicMutex.lock();
+
+	if (nextMusic) {
+		// 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 = Mix_LoadMUS(filename.c_str());
+		nextMusicLoop = loop;
+		Mix_FadeOutMusic(1000);
+	} else {
+		currentMusic = Mix_LoadMUS(filename.c_str());
+		if (Mix_PlayMusic(currentMusic, loop) == -1)
+			tlog1 << "Unable to play sound file " << musicID << "(" << Mix_GetError() << ")" << std::endl;
+	}
+
+	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
+void CMusicHandler::stopMusic(int fade_ms)
+{
+	musicMutex.lock();
+
+	if (currentMusic) {
+		Mix_FadeOutMusic(fade_ms);
+	}
+
+	musicMutex.unlock();
+}
+
+// Called by SDL when a music finished.
+void CMusicHandler::musicFinishedCallback(void)
+{
+	musicMutex.lock();
+
+	if (currentMusic) {
+		Mix_FreeMusic(currentMusic);
+		currentMusic = NULL;
+	}
+
+	if (nextMusic) {
+		currentMusic = nextMusic;
+		nextMusic = NULL;
+		if (Mix_PlayMusic(currentMusic, nextMusicLoop) == -1)
+			tlog1 << "Unable to play music (" << Mix_GetError() << ")" << std::endl;
+	}
+
+	musicMutex.unlock();
 }

+ 26 - 3
hch/CMusicHandler.h

@@ -1,7 +1,11 @@
 #ifndef __CMUSICHANDLER_H__
 #define __CMUSICHANDLER_H__
 
+#include <boost/thread/mutex.hpp>
+
 #include "CSoundBase.h"
+#include "CMusicBase.h"
+
 
 /*
  * CMusicHandler.h, part of VCMI engine
@@ -13,12 +17,16 @@
  *
  */
 
-struct Mix_Chunk;
 class CSndHandler;
+struct _Mix_Music;
+typedef struct _Mix_Music Mix_Music;
+struct Mix_Chunk;
 
 class CMusicHandler
 {
 private:
+	bool audioInit;
+
 	CSndHandler *sndh;
 	soundBase::soundID getSoundID(std::string &fileName);
 
@@ -26,10 +34,16 @@ private:
 
 	Mix_Chunk *GetSoundChunk(soundBase::soundID soundID);
 
-	bool audioInit;
+	// Because we use the SDL music callback, our music variables must
+	// be protected
+	boost::mutex musicMutex;
+	Mix_Music *currentMusic;
+	Mix_Music *nextMusic;
+	int nextMusicLoop;
 
 public:
-	CMusicHandler(): sndh(NULL), audioInit(false) {};
+CMusicHandler(): audioInit(false), sndh(NULL), currentMusic(NULL) {};
+
 	~CMusicHandler();
 
 	void initMusics();
@@ -43,6 +57,15 @@ public:
 	// Sets
 	std::vector<soundBase::soundID> pickup_sounds;
 	std::vector<soundBase::soundID> horseSounds;
+
+	// Musics
+	std::map<musicBase::musicID, std::string> musics;
+	std::vector<musicBase::musicID> battleMusics;
+
+	void playMusic(musicBase::musicID musicID, int loop=1);
+	void playMusicFromSet(std::vector<musicBase::musicID> &music_vec, int loop=1);
+	void stopMusic(int fade_ms=1000);
+	void musicFinishedCallback(void);
 };
 
 #endif // __CMUSICHANDLER_H__