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:
parent
c5cf0e4086
commit
4af9bc2461
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
|
|
||||||
|
@ -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)
|
||||||
{
|
{
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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]));
|
||||||
|
@ -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();
|
||||||
|
@ -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));
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user