1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-04-17 11:56:46 +02:00

Music: remember playback position of music tracks

Town & terrain themes will now resume from previously stopped position
instead of playing from start, as it was in original game.
Fixes #965
This commit is contained in:
Ivan Savenko 2022-11-13 14:05:51 +02:00
parent c5cf0e4086
commit 4af9bc2461
9 changed files with 59 additions and 32 deletions

View File

@ -9,6 +9,7 @@
*/ */
#include "StdInc.h" #include "StdInc.h"
#include <SDL_mixer.h> #include <SDL_mixer.h>
#include <SDL.h>
#include "CMusicHandler.h" #include "CMusicHandler.h"
#include "CGameInfo.h" #include "CGameInfo.h"
@ -410,15 +411,15 @@ void CMusicHandler::release()
CAudioBase::release(); CAudioBase::release();
} }
void CMusicHandler::playMusic(const std::string & musicURI, bool loop) void CMusicHandler::playMusic(const std::string & musicURI, bool loop, bool fromStart)
{ {
if (current && current->isTrack(musicURI)) if (current && current->isTrack(musicURI))
return; return;
queueNext(this, "", musicURI, loop); queueNext(this, "", musicURI, loop, fromStart);
} }
void CMusicHandler::playMusicFromSet(const std::string & whichSet, bool loop) void CMusicHandler::playMusicFromSet(const std::string & whichSet, bool loop, bool fromStart)
{ {
auto selectedSet = musicsSet.find(whichSet); auto selectedSet = musicsSet.find(whichSet);
if (selectedSet == musicsSet.end()) if (selectedSet == musicsSet.end())
@ -431,10 +432,10 @@ void CMusicHandler::playMusicFromSet(const std::string & whichSet, bool loop)
return; return;
// in this mode - play random track from set // in this mode - play random track from set
queueNext(this, whichSet, "", loop); queueNext(this, whichSet, "", loop, fromStart);
} }
void CMusicHandler::playMusicFromSet(const std::string & whichSet, const std::string & entryID, bool loop) void CMusicHandler::playMusicFromSet(const std::string & whichSet, const std::string & entryID, bool loop, bool fromStart)
{ {
auto selectedSet = musicsSet.find(whichSet); auto selectedSet = musicsSet.find(whichSet);
if (selectedSet == musicsSet.end()) if (selectedSet == musicsSet.end())
@ -454,7 +455,7 @@ void CMusicHandler::playMusicFromSet(const std::string & whichSet, const std::st
return; return;
// in this mode - play specific track from set // in this mode - play specific track from set
queueNext(this, "", selectedEntry->second, loop); queueNext(this, "", selectedEntry->second, loop, fromStart);
} }
void CMusicHandler::queueNext(std::unique_ptr<MusicEntry> queued) void CMusicHandler::queueNext(std::unique_ptr<MusicEntry> queued)
@ -473,11 +474,11 @@ void CMusicHandler::queueNext(std::unique_ptr<MusicEntry> queued)
} }
} }
void CMusicHandler::queueNext(CMusicHandler *owner, const std::string & setName, const std::string & musicURI, bool looped) void CMusicHandler::queueNext(CMusicHandler *owner, const std::string & setName, const std::string & musicURI, bool looped, bool fromStart)
{ {
try try
{ {
queueNext(make_unique<MusicEntry>(owner, setName, musicURI, looped)); queueNext(make_unique<MusicEntry>(owner, setName, musicURI, looped, fromStart));
} }
catch(std::exception &e) catch(std::exception &e)
{ {
@ -526,10 +527,13 @@ void CMusicHandler::musicFinishedCallback()
} }
} }
MusicEntry::MusicEntry(CMusicHandler *owner, std::string setName, std::string musicURI, bool looped): MusicEntry::MusicEntry(CMusicHandler *owner, std::string setName, std::string musicURI, bool looped, bool fromStart):
owner(owner), owner(owner),
music(nullptr), music(nullptr),
startTime(uint64_t(-1)),
startPosition(0),
loop(looped ? -1 : 1), loop(looped ? -1 : 1),
fromStart(fromStart),
setName(std::move(setName)) setName(std::move(setName))
{ {
if (!musicURI.empty()) if (!musicURI.empty())
@ -578,20 +582,39 @@ bool MusicEntry::play()
} }
logGlobal->trace("Playing music file %s", currentName); logGlobal->trace("Playing music file %s", currentName);
if(Mix_PlayMusic(music, 1) == -1)
{ if ( !fromStart && owner->trackPositions.count(currentName) > 0 && owner->trackPositions[currentName] > 0)
logGlobal->error("Unable to play music (%s)", Mix_GetError()); {
return false; float timeToStart = owner->trackPositions[currentName];
} startPosition = std::round(timeToStart * 1000);
return true;
if (Mix_FadeInMusicPos(music, 1, 1000, timeToStart) == -1)
{
logGlobal->error("Unable to play music (%s)", Mix_GetError());
return false;
}
}
else if(Mix_PlayMusic(music, 1) == -1)
{
logGlobal->error("Unable to play music (%s)", Mix_GetError());
return false;
}
startTime = SDL_GetTicks64();
return true;
} }
bool MusicEntry::stop(int fade_ms) bool MusicEntry::stop(int fade_ms)
{ {
if (Mix_PlayingMusic()) if (Mix_PlayingMusic())
{ {
logGlobal->trace("Stopping music file %s", currentName);
loop = 0; loop = 0;
uint64_t endTime = SDL_GetTicks64();
assert(startTime != uint64_t(-1));
float playDuration = (endTime - startTime + startPosition) / 1000.f;
owner->trackPositions[currentName] = playDuration;
logGlobal->info("Stopping music file %s at %f", currentName, playDuration);
Mix_FadeOutMusic(fade_ms); Mix_FadeOutMusic(fade_ms);
return true; return true;
} }

View File

@ -99,7 +99,10 @@ class MusicEntry
Mix_Music *music; Mix_Music *music;
int loop; // -1 = indefinite int loop; // -1 = indefinite
//if not null - set from which music will be randomly selected bool fromStart;
uint64_t startTime;
uint64_t startPosition;
//if not null - set from which music will be randomly selected
std::string setName; std::string setName;
std::string currentName; std::string currentName;
@ -110,7 +113,7 @@ public:
bool isSet(std::string setName); bool isSet(std::string setName);
bool isTrack(std::string trackName); bool isTrack(std::string trackName);
MusicEntry(CMusicHandler *owner, std::string setName, std::string musicURI, bool looped); MusicEntry(CMusicHandler *owner, std::string setName, std::string musicURI, bool looped, bool fromStart);
~MusicEntry(); ~MusicEntry();
bool play(); bool play();
@ -128,10 +131,11 @@ private:
std::unique_ptr<MusicEntry> current; std::unique_ptr<MusicEntry> current;
std::unique_ptr<MusicEntry> next; std::unique_ptr<MusicEntry> next;
void queueNext(CMusicHandler *owner, const std::string & setName, const std::string & musicURI, bool looped); void queueNext(CMusicHandler *owner, const std::string & setName, const std::string & musicURI, bool looped, bool fromStart);
void queueNext(std::unique_ptr<MusicEntry> queued); void queueNext(std::unique_ptr<MusicEntry> queued);
std::map<std::string, std::map<std::string, std::string>> musicsSet; std::map<std::string, std::map<std::string, std::string>> musicsSet;
std::map<std::string, float> trackPositions;
public: public:
CMusicHandler(); CMusicHandler();
@ -145,11 +149,11 @@ public:
void setVolume(ui32 percent) override; void setVolume(ui32 percent) override;
/// play track by URI, if loop = true music will be looped /// play track by URI, if loop = true music will be looped
void playMusic(const std::string & musicURI, bool loop); void playMusic(const std::string & musicURI, bool loop, bool fromStart);
/// play random track from this set /// play random track from this set
void playMusicFromSet(const std::string & musicSet, bool loop); void playMusicFromSet(const std::string & musicSet, bool loop, bool fromStart);
/// play specific track from set /// play specific track from set
void playMusicFromSet(const std::string & musicSet, const std::string & entryID, bool loop); void playMusicFromSet(const std::string & musicSet, const std::string & entryID, bool loop, bool fromStart);
void stopMusic(int fade_ms=1000); void stopMusic(int fade_ms=1000);
void musicFinishedCallback(); void musicFinishedCallback();

View File

@ -276,7 +276,7 @@ void CPlayerInterface::heroMoved(const TryMoveHero & details, bool verbose)
{ {
updateAmbientSounds(); updateAmbientSounds();
//We may need to change music - select new track, music handler will change it if needed //We may need to change music - select new track, music handler will change it if needed
CCS->musich->playMusicFromSet("terrain", LOCPLINT->cb->getTile(hero->visitablePos())->terType->name, true); CCS->musich->playMusicFromSet("terrain", LOCPLINT->cb->getTile(hero->visitablePos())->terType->name, true, false);
if(details.result == TryMoveHero::TELEPORTATION) if(details.result == TryMoveHero::TELEPORTATION)
{ {

View File

@ -411,7 +411,7 @@ CBattleInterface::CBattleInterface(const CCreatureSet *army1, const CCreatureSet
{ {
if(LOCPLINT->battleInt) if(LOCPLINT->battleInt)
{ {
CCS->musich->playMusicFromSet("battle", true); CCS->musich->playMusicFromSet("battle", true, true);
battleActionsStarted = true; battleActionsStarted = true;
blockUI(settings["session"]["spectate"].Bool()); blockUI(settings["session"]["spectate"].Bool());
battleIntroSoundChannel = -1; battleIntroSoundChannel = -1;
@ -457,7 +457,7 @@ CBattleInterface::~CBattleInterface()
if (adventureInt && adventureInt->selection) if (adventureInt && adventureInt->selection)
{ {
const auto & terrain = *(LOCPLINT->cb->getTile(adventureInt->selection->visitablePos())->terType); const auto & terrain = *(LOCPLINT->cb->getTile(adventureInt->selection->visitablePos())->terType);
CCS->musich->playMusicFromSet("terrain", terrain.name, true); CCS->musich->playMusicFromSet("terrain", terrain.name, true, false);
} }
animsAreDisplayed.setn(false); animsAreDisplayed.setn(false);
} }

View File

@ -506,7 +506,7 @@ CBattleResultWindow::CBattleResultWindow(const BattleResult & br, CPlayerInterfa
break; break;
} }
CCS->musich->playMusic("Music/Win Battle", false); CCS->musich->playMusic("Music/Win Battle", false, true);
CCS->videoh->open("WIN3.BIK"); CCS->videoh->open("WIN3.BIK");
std::string str = CGI->generaltexth->allTexts[text]; std::string str = CGI->generaltexth->allTexts[text];
@ -543,7 +543,7 @@ CBattleResultWindow::CBattleResultWindow(const BattleResult & br, CPlayerInterfa
logGlobal->error("Invalid battle result code %d. Assumed normal.", static_cast<int>(br.result)); logGlobal->error("Invalid battle result code %d. Assumed normal.", static_cast<int>(br.result));
break; break;
} }
CCS->musich->playMusic(musicName, false); CCS->musich->playMusic(musicName, false, true);
CCS->videoh->open(videoName); CCS->videoh->open(videoName);
labels.push_back(std::make_shared<CLabel>(235, 235, FONT_SMALL, CENTER, Colors::WHITE, CGI->generaltexth->allTexts[text])); labels.push_back(std::make_shared<CLabel>(235, 235, FONT_SMALL, CENTER, Colors::WHITE, CGI->generaltexth->allTexts[text]));

View File

@ -115,7 +115,7 @@ void CMenuScreen::show(SDL_Surface * to)
void CMenuScreen::activate() void CMenuScreen::activate()
{ {
CCS->musich->playMusic("Music/MainMenu", true); CCS->musich->playMusic("Music/MainMenu", true, true);
if(!config["video"].isNull()) if(!config["video"].isNull())
CCS->videoh->open(config["video"]["name"].String()); CCS->videoh->open(config["video"]["name"].String());
CIntObject::activate(); CIntObject::activate();

View File

@ -29,7 +29,7 @@ CPrologEpilogVideo::CPrologEpilogVideo(CCampaignScenario::SScenarioPrologEpilog
updateShadow(); updateShadow();
CCS->videoh->open(CCampaignHandler::prologVideoName(spe.prologVideo)); CCS->videoh->open(CCampaignHandler::prologVideoName(spe.prologVideo));
CCS->musich->playMusic("Music/" + CCampaignHandler::prologMusicName(spe.prologMusic), true); CCS->musich->playMusic("Music/" + CCampaignHandler::prologMusicName(spe.prologMusic), true, true);
// MPTODO: Custom campaign crashing on this? // MPTODO: Custom campaign crashing on this?
// voiceSoundHandle = CCS->soundh->playSound(CCampaignHandler::prologVoiceName(spe.prologVideo)); // voiceSoundHandle = CCS->soundh->playSound(CCampaignHandler::prologVoiceName(spe.prologVideo));

View File

@ -1413,7 +1413,7 @@ void CAdvMapInt::select(const CArmedInstance *sel, bool centerView)
auto pos = sel->visitablePos(); auto pos = sel->visitablePos();
auto tile = LOCPLINT->cb->getTile(pos); auto tile = LOCPLINT->cb->getTile(pos);
if(tile) if(tile)
CCS->musich->playMusicFromSet("terrain", tile->terType->name, true); CCS->musich->playMusicFromSet("terrain", tile->terType->name, true, false);
} }
if(centerView) if(centerView)
centerOn(sel); centerOn(sel);
@ -1863,7 +1863,7 @@ void CAdvMapInt::aiTurnStarted()
return; return;
adjustActiveness(true); adjustActiveness(true);
CCS->musich->playMusicFromSet("enemy-turn", true); CCS->musich->playMusicFromSet("enemy-turn", true, false);
adventureInt->minimap.setAIRadar(true); adventureInt->minimap.setAIRadar(true);
adventureInt->infoBar.startEnemyTurn(LOCPLINT->cb->getCurrentPlayer()); adventureInt->infoBar.startEnemyTurn(LOCPLINT->cb->getCurrentPlayer());
adventureInt->infoBar.showAll(screen);//force refresh on inactive object adventureInt->infoBar.showAll(screen);//force refresh on inactive object

View File

@ -1171,7 +1171,7 @@ CCastleInterface::CCastleInterface(const CGTownInstance * Town, const CGTownInst
townlist->onSelect = std::bind(&CCastleInterface::townChange, this); townlist->onSelect = std::bind(&CCastleInterface::townChange, this);
recreateIcons(); recreateIcons();
CCS->musich->playMusic(town->town->clientInfo.musicTheme, true); CCS->musich->playMusic(town->town->clientInfo.musicTheme, true, false);
} }
CCastleInterface::~CCastleInterface() CCastleInterface::~CCastleInterface()