mirror of
https://github.com/OpenMW/openmw.git
synced 2025-03-29 04:06:40 +00:00
Merge branch 'music' into 'master'
Rework music system See merge request OpenMW/openmw!3372
This commit is contained in:
commit
12159d95f3
19 changed files with 174 additions and 57 deletions
|
@ -75,6 +75,7 @@
|
||||||
Feature #3537: Shader-based water ripples
|
Feature #3537: Shader-based water ripples
|
||||||
Feature #5492: Let rain and snow collide with statics
|
Feature #5492: Let rain and snow collide with statics
|
||||||
Feature #6149: Dehardcode Lua API_REVISION
|
Feature #6149: Dehardcode Lua API_REVISION
|
||||||
|
Feature #6152: Playing music via lua scripts
|
||||||
Feature #6447: Add LOD support to Object Paging
|
Feature #6447: Add LOD support to Object Paging
|
||||||
Feature #6491: Add support for Qt6
|
Feature #6491: Add support for Qt6
|
||||||
Feature #6556: Lua API for sounds
|
Feature #6556: Lua API for sounds
|
||||||
|
@ -99,6 +100,7 @@
|
||||||
Feature #7477: NegativeLight Magic Effect flag
|
Feature #7477: NegativeLight Magic Effect flag
|
||||||
Feature #7499: OpenMW-CS: Generate record filters by drag & dropping cell content to the filters field
|
Feature #7499: OpenMW-CS: Generate record filters by drag & dropping cell content to the filters field
|
||||||
Feature #7546: Start the game on Fredas
|
Feature #7546: Start the game on Fredas
|
||||||
|
Feature #7568: Uninterruptable scripted music
|
||||||
Task #5896: Do not use deprecated MyGUI properties
|
Task #5896: Do not use deprecated MyGUI properties
|
||||||
Task #7113: Move from std::atoi to std::from_char
|
Task #7113: Move from std::atoi to std::from_char
|
||||||
Task #7117: Replace boost::scoped_array with std::vector
|
Task #7117: Replace boost::scoped_array with std::vector
|
||||||
|
|
|
@ -71,7 +71,7 @@ message(STATUS "Configuring OpenMW...")
|
||||||
set(OPENMW_VERSION_MAJOR 0)
|
set(OPENMW_VERSION_MAJOR 0)
|
||||||
set(OPENMW_VERSION_MINOR 49)
|
set(OPENMW_VERSION_MINOR 49)
|
||||||
set(OPENMW_VERSION_RELEASE 0)
|
set(OPENMW_VERSION_RELEASE 0)
|
||||||
set(OPENMW_LUA_API_REVISION 47)
|
set(OPENMW_LUA_API_REVISION 48)
|
||||||
|
|
||||||
set(OPENMW_VERSION_COMMITHASH "")
|
set(OPENMW_VERSION_COMMITHASH "")
|
||||||
set(OPENMW_VERSION_TAGHASH "")
|
set(OPENMW_VERSION_TAGHASH "")
|
||||||
|
|
|
@ -913,7 +913,13 @@ void OMW::Engine::go()
|
||||||
{
|
{
|
||||||
// start in main menu
|
// start in main menu
|
||||||
mWindowManager->pushGuiMode(MWGui::GM_MainMenu);
|
mWindowManager->pushGuiMode(MWGui::GM_MainMenu);
|
||||||
mSoundManager->playPlaylist("Title");
|
|
||||||
|
std::string titlefile = "music/special/morrowind title.mp3";
|
||||||
|
if (mVFS->exists(titlefile))
|
||||||
|
mSoundManager->streamMusic(titlefile, MWSound::MusicType::Special);
|
||||||
|
else
|
||||||
|
Log(Debug::Warning) << "Title music not found";
|
||||||
|
|
||||||
std::string_view logo = Fallback::Map::getString("Movies_Morrowind_Logo");
|
std::string_view logo = Fallback::Map::getString("Movies_Morrowind_Logo");
|
||||||
if (!logo.empty())
|
if (!logo.empty())
|
||||||
mWindowManager->playVideo(logo, /*allowSkipping*/ true, /*overrideSounds*/ false);
|
mWindowManager->playVideo(logo, /*allowSkipping*/ true, /*overrideSounds*/ false);
|
||||||
|
|
|
@ -26,6 +26,11 @@ namespace ESM
|
||||||
class ESMWriter;
|
class ESMWriter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace MWSound
|
||||||
|
{
|
||||||
|
enum class MusicType;
|
||||||
|
}
|
||||||
|
|
||||||
namespace MWWorld
|
namespace MWWorld
|
||||||
{
|
{
|
||||||
class Ptr;
|
class Ptr;
|
||||||
|
@ -282,6 +287,9 @@ namespace MWBase
|
||||||
virtual float getAngleToPlayer(const MWWorld::Ptr& ptr) const = 0;
|
virtual float getAngleToPlayer(const MWWorld::Ptr& ptr) const = 0;
|
||||||
virtual MWMechanics::GreetingState getGreetingState(const MWWorld::Ptr& ptr) const = 0;
|
virtual MWMechanics::GreetingState getGreetingState(const MWWorld::Ptr& ptr) const = 0;
|
||||||
virtual bool isTurningToPlayer(const MWWorld::Ptr& ptr) const = 0;
|
virtual bool isTurningToPlayer(const MWWorld::Ptr& ptr) const = 0;
|
||||||
|
|
||||||
|
virtual MWSound::MusicType getMusicType() const = 0;
|
||||||
|
virtual void setMusicType(MWSound::MusicType type) = 0;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,14 @@ namespace MWSound
|
||||||
MaxCount
|
MaxCount
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class MusicType
|
||||||
|
{
|
||||||
|
Special,
|
||||||
|
Explore,
|
||||||
|
Battle,
|
||||||
|
Scripted
|
||||||
|
};
|
||||||
|
|
||||||
class Sound;
|
class Sound;
|
||||||
class Stream;
|
class Stream;
|
||||||
struct Sound_Decoder;
|
struct Sound_Decoder;
|
||||||
|
@ -101,12 +109,17 @@ namespace MWBase
|
||||||
|
|
||||||
virtual void processChangedSettings(const std::set<std::pair<std::string, std::string>>& settings) = 0;
|
virtual void processChangedSettings(const std::set<std::pair<std::string, std::string>>& settings) = 0;
|
||||||
|
|
||||||
|
virtual bool isEnabled() const = 0;
|
||||||
|
///< Returns true if sound system is enabled
|
||||||
|
|
||||||
virtual void stopMusic() = 0;
|
virtual void stopMusic() = 0;
|
||||||
///< Stops music if it's playing
|
///< Stops music if it's playing
|
||||||
|
|
||||||
virtual void streamMusic(const std::string& filename) = 0;
|
virtual void streamMusic(const std::string& filename, MWSound::MusicType type, float fade = 1.f) = 0;
|
||||||
///< Play a soundifle
|
///< Play a soundifle
|
||||||
/// \param filename name of a sound file in "Music/" in the data directory.
|
/// \param filename name of a sound file in the data directory.
|
||||||
|
/// \param type music type.
|
||||||
|
/// \param fade time in seconds to fade out current track before start this one.
|
||||||
|
|
||||||
virtual bool isMusicPlaying() = 0;
|
virtual bool isMusicPlaying() = 0;
|
||||||
///< Returns true if music is playing
|
///< Returns true if music is playing
|
||||||
|
|
|
@ -214,7 +214,8 @@ namespace MWGui
|
||||||
center();
|
center();
|
||||||
|
|
||||||
// Play LevelUp Music
|
// Play LevelUp Music
|
||||||
MWBase::Environment::get().getSoundManager()->streamMusic("Special/MW_Triumph.mp3");
|
MWBase::Environment::get().getSoundManager()->streamMusic(
|
||||||
|
"Music/Special/MW_Triumph.mp3", MWSound::MusicType::Special);
|
||||||
}
|
}
|
||||||
|
|
||||||
void LevelupDialog::onOkButtonClicked(MyGUI::Widget* sender)
|
void LevelupDialog::onOkButtonClicked(MyGUI::Widget* sender)
|
||||||
|
|
|
@ -95,6 +95,15 @@ namespace MWLua
|
||||||
return MWBase::Environment::get().getSoundManager()->getSoundPlaying(MWWorld::Ptr(), fileName);
|
return MWBase::Environment::get().getSoundManager()->getSoundPlaying(MWWorld::Ptr(), fileName);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
api["streamMusic"] = [](std::string_view fileName) {
|
||||||
|
MWBase::SoundManager* sndMgr = MWBase::Environment::get().getSoundManager();
|
||||||
|
sndMgr->streamMusic(std::string(fileName), MWSound::MusicType::Scripted);
|
||||||
|
};
|
||||||
|
|
||||||
|
api["isMusicPlaying"] = []() { return MWBase::Environment::get().getSoundManager()->isMusicPlaying(); };
|
||||||
|
|
||||||
|
api["stopMusic"] = []() { MWBase::Environment::get().getSoundManager()->stopMusic(); };
|
||||||
|
|
||||||
return LuaUtil::makeReadOnly(api);
|
return LuaUtil::makeReadOnly(api);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,6 +112,8 @@ namespace MWLua
|
||||||
sol::state_view& lua = context.mLua->sol();
|
sol::state_view& lua = context.mLua->sol();
|
||||||
sol::table api(lua, sol::create);
|
sol::table api(lua, sol::create);
|
||||||
|
|
||||||
|
api["isEnabled"] = []() { return MWBase::Environment::get().getSoundManager()->isEnabled(); };
|
||||||
|
|
||||||
api["playSound3d"]
|
api["playSound3d"]
|
||||||
= [](std::string_view soundId, const Object& object, const sol::optional<sol::table>& options) {
|
= [](std::string_view soundId, const Object& object, const sol::optional<sol::table>& options) {
|
||||||
auto args = getPlaySoundArgs(options);
|
auto args = getPlaySoundArgs(options);
|
||||||
|
|
|
@ -1285,7 +1285,7 @@ namespace MWMechanics
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Actors::updateCombatMusic()
|
bool Actors::playerHasHostiles() const
|
||||||
{
|
{
|
||||||
const MWWorld::Ptr player = getPlayer();
|
const MWWorld::Ptr player = getPlayer();
|
||||||
const osg::Vec3f playerPos = player.getRefData().getPosition().asVec3();
|
const osg::Vec3f playerPos = player.getRefData().getPosition().asVec3();
|
||||||
|
@ -1315,19 +1315,7 @@ namespace MWMechanics
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if we still have any player enemies to switch music
|
return hasHostiles;
|
||||||
if (mCurrentMusic != MusicType::Explore && !hasHostiles
|
|
||||||
&& !(player.getClass().getCreatureStats(player).isDead()
|
|
||||||
&& MWBase::Environment::get().getSoundManager()->isMusicPlaying()))
|
|
||||||
{
|
|
||||||
MWBase::Environment::get().getSoundManager()->playPlaylist(std::string("Explore"));
|
|
||||||
mCurrentMusic = MusicType::Explore;
|
|
||||||
}
|
|
||||||
else if (mCurrentMusic != MusicType::Battle && hasHostiles)
|
|
||||||
{
|
|
||||||
MWBase::Environment::get().getSoundManager()->playPlaylist(std::string("Battle"));
|
|
||||||
mCurrentMusic = MusicType::Battle;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Actors::predictAndAvoidCollisions(float duration) const
|
void Actors::predictAndAvoidCollisions(float duration) const
|
||||||
|
@ -1735,8 +1723,6 @@ namespace MWMechanics
|
||||||
killDeadActors();
|
killDeadActors();
|
||||||
updateSneaking(playerCharacter, duration);
|
updateSneaking(playerCharacter, duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateCombatMusic();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Actors::notifyDied(const MWWorld::Ptr& actor)
|
void Actors::notifyDied(const MWWorld::Ptr& actor)
|
||||||
|
@ -1806,7 +1792,8 @@ namespace MWMechanics
|
||||||
// player's death animation is over
|
// player's death animation is over
|
||||||
MWBase::Environment::get().getStateManager()->askLoadRecent();
|
MWBase::Environment::get().getStateManager()->askLoadRecent();
|
||||||
// Play Death Music if it was the player dying
|
// Play Death Music if it was the player dying
|
||||||
MWBase::Environment::get().getSoundManager()->streamMusic("Special/MW_Death.mp3");
|
MWBase::Environment::get().getSoundManager()->streamMusic(
|
||||||
|
"Music/Special/MW_Death.mp3", MWSound::MusicType::Special);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
@ -74,9 +74,6 @@ namespace MWMechanics
|
||||||
void dropActors(const MWWorld::CellStore* cellStore, const MWWorld::Ptr& ignore);
|
void dropActors(const MWWorld::CellStore* cellStore, const MWWorld::Ptr& ignore);
|
||||||
///< Deregister all actors (except for \a ignore) in the given cell.
|
///< Deregister all actors (except for \a ignore) in the given cell.
|
||||||
|
|
||||||
void updateCombatMusic();
|
|
||||||
///< Update combat music state
|
|
||||||
|
|
||||||
void update(float duration, bool paused);
|
void update(float duration, bool paused);
|
||||||
///< Update actor stats and store desired velocity vectors in \a movement
|
///< Update actor stats and store desired velocity vectors in \a movement
|
||||||
|
|
||||||
|
@ -159,19 +156,14 @@ namespace MWMechanics
|
||||||
bool isReadyToBlock(const MWWorld::Ptr& ptr) const;
|
bool isReadyToBlock(const MWWorld::Ptr& ptr) const;
|
||||||
bool isAttackingOrSpell(const MWWorld::Ptr& ptr) const;
|
bool isAttackingOrSpell(const MWWorld::Ptr& ptr) const;
|
||||||
|
|
||||||
|
bool playerHasHostiles() const;
|
||||||
|
|
||||||
int getGreetingTimer(const MWWorld::Ptr& ptr) const;
|
int getGreetingTimer(const MWWorld::Ptr& ptr) const;
|
||||||
float getAngleToPlayer(const MWWorld::Ptr& ptr) const;
|
float getAngleToPlayer(const MWWorld::Ptr& ptr) const;
|
||||||
GreetingState getGreetingState(const MWWorld::Ptr& ptr) const;
|
GreetingState getGreetingState(const MWWorld::Ptr& ptr) const;
|
||||||
bool isTurningToPlayer(const MWWorld::Ptr& ptr) const;
|
bool isTurningToPlayer(const MWWorld::Ptr& ptr) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
enum class MusicType
|
|
||||||
{
|
|
||||||
Title,
|
|
||||||
Explore,
|
|
||||||
Battle
|
|
||||||
};
|
|
||||||
|
|
||||||
std::map<ESM::RefId, int> mDeathCount;
|
std::map<ESM::RefId, int> mDeathCount;
|
||||||
std::list<Actor> mActors;
|
std::list<Actor> mActors;
|
||||||
std::map<const MWWorld::LiveCellRefBase*, std::list<Actor>::iterator> mIndex;
|
std::map<const MWWorld::LiveCellRefBase*, std::list<Actor>::iterator> mIndex;
|
||||||
|
@ -182,7 +174,6 @@ namespace MWMechanics
|
||||||
float mTimerUpdateHello = 0;
|
float mTimerUpdateHello = 0;
|
||||||
float mSneakTimer = 0; // Times update of sneak icon
|
float mSneakTimer = 0; // Times update of sneak icon
|
||||||
float mSneakSkillTimer = 0; // Times sneak skill progress from "avoid notice"
|
float mSneakSkillTimer = 0; // Times sneak skill progress from "avoid notice"
|
||||||
MusicType mCurrentMusic = MusicType::Title;
|
|
||||||
|
|
||||||
void updateVisibility(const MWWorld::Ptr& ptr, CharacterController& ctrl) const;
|
void updateVisibility(const MWWorld::Ptr& ptr, CharacterController& ctrl) const;
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
|
|
||||||
#include "../mwbase/dialoguemanager.hpp"
|
#include "../mwbase/dialoguemanager.hpp"
|
||||||
#include "../mwbase/environment.hpp"
|
#include "../mwbase/environment.hpp"
|
||||||
|
#include "../mwbase/soundmanager.hpp"
|
||||||
#include "../mwbase/statemanager.hpp"
|
#include "../mwbase/statemanager.hpp"
|
||||||
#include "../mwbase/windowmanager.hpp"
|
#include "../mwbase/windowmanager.hpp"
|
||||||
#include "../mwbase/world.hpp"
|
#include "../mwbase/world.hpp"
|
||||||
|
@ -257,6 +258,7 @@ namespace MWMechanics
|
||||||
, mClassSelected(false)
|
, mClassSelected(false)
|
||||||
, mRaceSelected(false)
|
, mRaceSelected(false)
|
||||||
, mAI(true)
|
, mAI(true)
|
||||||
|
, mMusicType(MWSound::MusicType::Special)
|
||||||
{
|
{
|
||||||
// buildPlayer no longer here, needs to be done explicitly after all subsystems are up and running
|
// buildPlayer no longer here, needs to be done explicitly after all subsystems are up and running
|
||||||
}
|
}
|
||||||
|
@ -340,6 +342,8 @@ namespace MWMechanics
|
||||||
|
|
||||||
mActors.update(duration, paused);
|
mActors.update(duration, paused);
|
||||||
mObjects.update(duration, paused);
|
mObjects.update(duration, paused);
|
||||||
|
|
||||||
|
updateMusicState();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MechanicsManager::processChangedSettings(const Settings::CategorySettingVector& changed)
|
void MechanicsManager::processChangedSettings(const Settings::CategorySettingVector& changed)
|
||||||
|
@ -1572,6 +1576,31 @@ namespace MWMechanics
|
||||||
return (Misc::Rng::roll0to99(prng) >= target);
|
return (Misc::Rng::roll0to99(prng) >= target);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MechanicsManager::updateMusicState()
|
||||||
|
{
|
||||||
|
bool musicPlaying = MWBase::Environment::get().getSoundManager()->isMusicPlaying();
|
||||||
|
|
||||||
|
// Can not interrupt scripted music by built-in playlists
|
||||||
|
if (mMusicType == MWSound::MusicType::Scripted && musicPlaying)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const MWWorld::Ptr& player = MWMechanics::getPlayer();
|
||||||
|
bool hasHostiles = mActors.playerHasHostiles();
|
||||||
|
|
||||||
|
// check if we still have any player enemies to switch music
|
||||||
|
if (mMusicType != MWSound::MusicType::Explore && !hasHostiles
|
||||||
|
&& !(player.getClass().getCreatureStats(player).isDead() && musicPlaying))
|
||||||
|
{
|
||||||
|
MWBase::Environment::get().getSoundManager()->playPlaylist(std::string("Explore"));
|
||||||
|
mMusicType = MWSound::MusicType::Explore;
|
||||||
|
}
|
||||||
|
else if (mMusicType != MWSound::MusicType::Battle && hasHostiles)
|
||||||
|
{
|
||||||
|
MWBase::Environment::get().getSoundManager()->playPlaylist(std::string("Battle"));
|
||||||
|
mMusicType = MWSound::MusicType::Battle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void MechanicsManager::startCombat(const MWWorld::Ptr& ptr, const MWWorld::Ptr& target)
|
void MechanicsManager::startCombat(const MWWorld::Ptr& ptr, const MWWorld::Ptr& target)
|
||||||
{
|
{
|
||||||
CreatureStats& stats = ptr.getClass().getCreatureStats(ptr);
|
CreatureStats& stats = ptr.getClass().getCreatureStats(ptr);
|
||||||
|
|
|
@ -11,6 +11,11 @@
|
||||||
#include "npcstats.hpp"
|
#include "npcstats.hpp"
|
||||||
#include "objects.hpp"
|
#include "objects.hpp"
|
||||||
|
|
||||||
|
namespace MWSound
|
||||||
|
{
|
||||||
|
enum class MusicType;
|
||||||
|
}
|
||||||
|
|
||||||
namespace MWWorld
|
namespace MWWorld
|
||||||
{
|
{
|
||||||
class CellStore;
|
class CellStore;
|
||||||
|
@ -33,6 +38,8 @@ namespace MWMechanics
|
||||||
typedef std::map<ESM::RefId, OwnerMap> StolenItemsMap;
|
typedef std::map<ESM::RefId, OwnerMap> StolenItemsMap;
|
||||||
StolenItemsMap mStolenItems;
|
StolenItemsMap mStolenItems;
|
||||||
|
|
||||||
|
MWSound::MusicType mMusicType;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void buildPlayer();
|
void buildPlayer();
|
||||||
///< build player according to stored class/race/birthsign information. Will
|
///< build player according to stored class/race/birthsign information. Will
|
||||||
|
@ -232,7 +239,11 @@ namespace MWMechanics
|
||||||
GreetingState getGreetingState(const MWWorld::Ptr& ptr) const override;
|
GreetingState getGreetingState(const MWWorld::Ptr& ptr) const override;
|
||||||
bool isTurningToPlayer(const MWWorld::Ptr& ptr) const override;
|
bool isTurningToPlayer(const MWWorld::Ptr& ptr) const override;
|
||||||
|
|
||||||
|
MWSound::MusicType getMusicType() const override { return mMusicType; }
|
||||||
|
void setMusicType(MWSound::MusicType type) override { mMusicType = type; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void updateMusicState();
|
||||||
bool canCommitCrimeAgainst(const MWWorld::Ptr& victim, const MWWorld::Ptr& attacker);
|
bool canCommitCrimeAgainst(const MWWorld::Ptr& victim, const MWWorld::Ptr& attacker);
|
||||||
bool canReportCrime(
|
bool canReportCrime(
|
||||||
const MWWorld::Ptr& actor, const MWWorld::Ptr& victim, std::set<MWWorld::Ptr>& playerFollowers);
|
const MWWorld::Ptr& actor, const MWWorld::Ptr& victim, std::set<MWWorld::Ptr>& playerFollowers);
|
||||||
|
|
|
@ -63,10 +63,11 @@ namespace MWScript
|
||||||
public:
|
public:
|
||||||
void execute(Interpreter::Runtime& runtime) override
|
void execute(Interpreter::Runtime& runtime) override
|
||||||
{
|
{
|
||||||
std::string sound{ runtime.getStringLiteral(runtime[0].mInteger) };
|
std::string music{ runtime.getStringLiteral(runtime[0].mInteger) };
|
||||||
runtime.pop();
|
runtime.pop();
|
||||||
|
|
||||||
MWBase::Environment::get().getSoundManager()->streamMusic(sound);
|
MWBase::Environment::get().getSoundManager()->streamMusic(
|
||||||
|
Misc::ResourceHelpers::correctMusicPath(music), MWSound::MusicType::Scripted);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
#include <components/vfs/pathutil.hpp>
|
#include <components/vfs/pathutil.hpp>
|
||||||
|
|
||||||
#include "../mwbase/environment.hpp"
|
#include "../mwbase/environment.hpp"
|
||||||
|
#include "../mwbase/mechanicsmanager.hpp"
|
||||||
#include "../mwbase/statemanager.hpp"
|
#include "../mwbase/statemanager.hpp"
|
||||||
#include "../mwbase/world.hpp"
|
#include "../mwbase/world.hpp"
|
||||||
|
|
||||||
|
@ -131,15 +132,6 @@ namespace MWSound
|
||||||
|
|
||||||
Log(Debug::Info) << stream.str();
|
Log(Debug::Info) << stream.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: dehardcode this
|
|
||||||
std::vector<std::string> titleMusic;
|
|
||||||
std::string_view titlefile = "music/special/morrowind title.mp3";
|
|
||||||
if (mVFS->exists(titlefile))
|
|
||||||
titleMusic.emplace_back(titlefile);
|
|
||||||
else
|
|
||||||
Log(Debug::Warning) << "Title music not found";
|
|
||||||
mMusicFiles["Title"] = titleMusic;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SoundManager::~SoundManager()
|
SoundManager::~SoundManager()
|
||||||
|
@ -250,7 +242,7 @@ namespace MWSound
|
||||||
if (filename.empty())
|
if (filename.empty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Log(Debug::Info) << "Playing " << filename;
|
Log(Debug::Info) << "Playing \"" << filename << "\"";
|
||||||
mLastPlayedMusic = filename;
|
mLastPlayedMusic = filename;
|
||||||
|
|
||||||
DecoderPtr decoder = getDecoder();
|
DecoderPtr decoder = getDecoder();
|
||||||
|
@ -260,7 +252,7 @@ namespace MWSound
|
||||||
}
|
}
|
||||||
catch (std::exception& e)
|
catch (std::exception& e)
|
||||||
{
|
{
|
||||||
Log(Debug::Error) << "Failed to load audio from " << filename << ": " << e.what();
|
Log(Debug::Error) << "Failed to load audio from \"" << filename << "\": " << e.what();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -274,7 +266,7 @@ namespace MWSound
|
||||||
mOutput->streamSound(decoder, mMusic.get());
|
mOutput->streamSound(decoder, mMusic.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
void SoundManager::advanceMusic(const std::string& filename)
|
void SoundManager::advanceMusic(const std::string& filename, float fadeOut)
|
||||||
{
|
{
|
||||||
if (!isMusicPlaying())
|
if (!isMusicPlaying())
|
||||||
{
|
{
|
||||||
|
@ -284,7 +276,7 @@ namespace MWSound
|
||||||
|
|
||||||
mNextMusic = filename;
|
mNextMusic = filename;
|
||||||
|
|
||||||
mMusic->setFadeout(1.f);
|
mMusic->setFadeout(fadeOut);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SoundManager::startRandomTitle()
|
void SoundManager::startRandomTitle()
|
||||||
|
@ -319,16 +311,30 @@ namespace MWSound
|
||||||
tracklist.pop_back();
|
tracklist.pop_back();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SoundManager::streamMusic(const std::string& filename)
|
|
||||||
{
|
|
||||||
advanceMusic("Music/" + filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SoundManager::isMusicPlaying()
|
bool SoundManager::isMusicPlaying()
|
||||||
{
|
{
|
||||||
return mMusic && mOutput->isStreamPlaying(mMusic.get());
|
return mMusic && mOutput->isStreamPlaying(mMusic.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SoundManager::streamMusic(const std::string& filename, MusicType type, float fade)
|
||||||
|
{
|
||||||
|
const auto mechanicsManager = MWBase::Environment::get().getMechanicsManager();
|
||||||
|
|
||||||
|
// Can not interrupt scripted music by built-in playlists
|
||||||
|
if (mechanicsManager->getMusicType() == MusicType::Scripted && type != MusicType::Scripted
|
||||||
|
&& type != MusicType::Special)
|
||||||
|
return;
|
||||||
|
|
||||||
|
std::string normalizedName = VFS::Path::normalizeFilename(filename);
|
||||||
|
|
||||||
|
mechanicsManager->setMusicType(type);
|
||||||
|
advanceMusic(normalizedName, fade);
|
||||||
|
if (type == MWSound::MusicType::Battle)
|
||||||
|
mCurrentPlaylist = "Battle";
|
||||||
|
else if (type == MWSound::MusicType::Explore)
|
||||||
|
mCurrentPlaylist = "Explore";
|
||||||
|
}
|
||||||
|
|
||||||
void SoundManager::playPlaylist(const std::string& playlist)
|
void SoundManager::playPlaylist(const std::string& playlist)
|
||||||
{
|
{
|
||||||
if (mCurrentPlaylist == playlist)
|
if (mCurrentPlaylist == playlist)
|
||||||
|
@ -337,7 +343,8 @@ namespace MWSound
|
||||||
if (mMusicFiles.find(playlist) == mMusicFiles.end())
|
if (mMusicFiles.find(playlist) == mMusicFiles.end())
|
||||||
{
|
{
|
||||||
std::vector<std::string> filelist;
|
std::vector<std::string> filelist;
|
||||||
for (const auto& name : mVFS->getRecursiveDirectoryIterator("Music/" + playlist + '/'))
|
auto playlistPath = Misc::ResourceHelpers::correctMusicPath(playlist) + '/';
|
||||||
|
for (const auto& name : mVFS->getRecursiveDirectoryIterator(playlistPath))
|
||||||
filelist.push_back(name);
|
filelist.push_back(name);
|
||||||
|
|
||||||
mMusicFiles[playlist] = filelist;
|
mMusicFiles[playlist] = filelist;
|
||||||
|
@ -1127,6 +1134,14 @@ namespace MWSound
|
||||||
if (!mOutput->isInitialized() || mPlaybackPaused)
|
if (!mOutput->isInitialized() || mPlaybackPaused)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
MWBase::StateManager::State state = MWBase::Environment::get().getStateManager()->getState();
|
||||||
|
if (state == MWBase::StateManager::State_NoGame && !isMusicPlaying())
|
||||||
|
{
|
||||||
|
std::string titlefile = "music/special/morrowind title.mp3";
|
||||||
|
if (mVFS->exists(titlefile))
|
||||||
|
streamMusic(titlefile, MWSound::MusicType::Special);
|
||||||
|
}
|
||||||
|
|
||||||
updateSounds(duration);
|
updateSounds(duration);
|
||||||
if (MWBase::Environment::get().getStateManager()->getState() != MWBase::StateManager::State_NoGame)
|
if (MWBase::Environment::get().getStateManager()->getState() != MWBase::StateManager::State_NoGame)
|
||||||
{
|
{
|
||||||
|
|
|
@ -127,7 +127,7 @@ namespace MWSound
|
||||||
StreamPtr playVoice(DecoderPtr decoder, const osg::Vec3f& pos, bool playlocal);
|
StreamPtr playVoice(DecoderPtr decoder, const osg::Vec3f& pos, bool playlocal);
|
||||||
|
|
||||||
void streamMusicFull(const std::string& filename);
|
void streamMusicFull(const std::string& filename);
|
||||||
void advanceMusic(const std::string& filename);
|
void advanceMusic(const std::string& filename, float fadeOut = 1.f);
|
||||||
void startRandomTitle();
|
void startRandomTitle();
|
||||||
|
|
||||||
void cull3DSound(SoundBase* sound);
|
void cull3DSound(SoundBase* sound);
|
||||||
|
@ -173,12 +173,17 @@ namespace MWSound
|
||||||
|
|
||||||
void processChangedSettings(const Settings::CategorySettingVector& settings) override;
|
void processChangedSettings(const Settings::CategorySettingVector& settings) override;
|
||||||
|
|
||||||
|
bool isEnabled() const override { return mOutput->isInitialized(); }
|
||||||
|
///< Returns true if sound system is enabled
|
||||||
|
|
||||||
void stopMusic() override;
|
void stopMusic() override;
|
||||||
///< Stops music if it's playing
|
///< Stops music if it's playing
|
||||||
|
|
||||||
void streamMusic(const std::string& filename) override;
|
void streamMusic(const std::string& filename, MWSound::MusicType type, float fade = 1.f) override;
|
||||||
///< Play a soundifle
|
///< Play a soundifle
|
||||||
/// \param filename name of a sound file in "Music/" in the data directory.
|
/// \param filename name of a sound file in the data directory.
|
||||||
|
/// \param type music type.
|
||||||
|
/// \param fade time in seconds to fade out current track before start this one.
|
||||||
|
|
||||||
bool isMusicPlaying() override;
|
bool isMusicPlaying() override;
|
||||||
///< Returns true if music is playing
|
///< Returns true if music is playing
|
||||||
|
|
|
@ -391,8 +391,13 @@ namespace MWWorld
|
||||||
{
|
{
|
||||||
std::string_view video = Fallback::Map::getString("Movies_New_Game");
|
std::string_view video = Fallback::Map::getString("Movies_New_Game");
|
||||||
if (!video.empty())
|
if (!video.empty())
|
||||||
|
{
|
||||||
|
// Make sure that we do not continue to play a Title music after a new game video.
|
||||||
|
MWBase::Environment::get().getSoundManager()->stopMusic();
|
||||||
|
MWBase::Environment::get().getSoundManager()->playPlaylist(std::string("Explore"));
|
||||||
MWBase::Environment::get().getWindowManager()->playVideo(video, true);
|
MWBase::Environment::get().getWindowManager()->playVideo(video, true);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// enable collision
|
// enable collision
|
||||||
if (!mPhysics->toggleCollisionMode())
|
if (!mPhysics->toggleCollisionMode())
|
||||||
|
|
|
@ -156,6 +156,11 @@ std::string Misc::ResourceHelpers::correctSoundPath(const std::string& resPath)
|
||||||
return "sound\\" + resPath;
|
return "sound\\" + resPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string Misc::ResourceHelpers::correctMusicPath(const std::string& resPath)
|
||||||
|
{
|
||||||
|
return "music\\" + resPath;
|
||||||
|
}
|
||||||
|
|
||||||
std::string_view Misc::ResourceHelpers::meshPathForESM3(std::string_view resPath)
|
std::string_view Misc::ResourceHelpers::meshPathForESM3(std::string_view resPath)
|
||||||
{
|
{
|
||||||
constexpr std::string_view prefix = "meshes";
|
constexpr std::string_view prefix = "meshes";
|
||||||
|
|
|
@ -38,6 +38,9 @@ namespace Misc
|
||||||
// Adds "sound\\".
|
// Adds "sound\\".
|
||||||
std::string correctSoundPath(const std::string& resPath);
|
std::string correctSoundPath(const std::string& resPath);
|
||||||
|
|
||||||
|
// Adds "music\\".
|
||||||
|
std::string correctMusicPath(const std::string& resPath);
|
||||||
|
|
||||||
// Removes "meshes\\".
|
// Removes "meshes\\".
|
||||||
std::string_view meshPathForESM3(std::string_view resPath);
|
std::string_view meshPathForESM3(std::string_view resPath);
|
||||||
|
|
||||||
|
|
|
@ -72,4 +72,21 @@
|
||||||
-- @return #boolean
|
-- @return #boolean
|
||||||
-- @usage local isPlaying = ambient.isSoundFilePlaying("Sound\\test.mp3");
|
-- @usage local isPlaying = ambient.isSoundFilePlaying("Sound\\test.mp3");
|
||||||
|
|
||||||
|
---
|
||||||
|
-- Play a sound file as a music track
|
||||||
|
-- @function [parent=#ambient] streamMusic
|
||||||
|
-- @param #string fileName Path to file in VFS
|
||||||
|
-- @usage ambient.streamMusic("Music\\Test\\Test.mp3");
|
||||||
|
|
||||||
|
---
|
||||||
|
-- Stop to play current music
|
||||||
|
-- @function [parent=#ambient] stopMusic
|
||||||
|
-- @usage ambient.stopMusic();
|
||||||
|
|
||||||
|
---
|
||||||
|
-- Check if music is playing
|
||||||
|
-- @function [parent=#ambient] isMusicPlaying
|
||||||
|
-- @return #boolean
|
||||||
|
-- @usage local isPlaying = ambient.isMusicPlaying();
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -749,6 +749,13 @@
|
||||||
--- @{#Sound}: Sounds and Speech
|
--- @{#Sound}: Sounds and Speech
|
||||||
-- @field [parent=#core] #Sound sound
|
-- @field [parent=#core] #Sound sound
|
||||||
|
|
||||||
|
---
|
||||||
|
-- Checks if sound system is enabled (any functions to play sounds are no-ops when it is disabled).
|
||||||
|
-- It can not be enabled or disabled during runtime.
|
||||||
|
-- @function [parent=#Sound] isEnabled
|
||||||
|
-- @return #boolean
|
||||||
|
-- @usage local enabled = core.sound.isEnabled();
|
||||||
|
|
||||||
---
|
---
|
||||||
-- Play a 3D sound, attached to object
|
-- Play a 3D sound, attached to object
|
||||||
-- @function [parent=#Sound] playSound3d
|
-- @function [parent=#Sound] playSound3d
|
||||||
|
|
Loading…
Reference in a new issue