1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-01-16 16:29:55 +00:00

Rework music system

This commit is contained in:
Andrei Kortunov 2023-08-18 11:03:37 +04:00
parent 090da90302
commit e1cae5a029
24 changed files with 189 additions and 47 deletions

View file

@ -75,6 +75,7 @@
Feature #3537: Shader-based water ripples
Feature #5492: Let rain and snow collide with statics
Feature #6149: Dehardcode Lua API_REVISION
Feature #6152: Playing music via lua scripts
Feature #6447: Add LOD support to Object Paging
Feature #6491: Add support for Qt6
Feature #6556: Lua API for sounds
@ -99,6 +100,7 @@
Feature #7477: NegativeLight Magic Effect flag
Feature #7499: OpenMW-CS: Generate record filters by drag & dropping cell content to the filters field
Feature #7546: Start the game on Fredas
Feature #7568: Uninterruptable scripted music
Task #5896: Do not use deprecated MyGUI properties
Task #7113: Move from std::atoi to std::from_char
Task #7117: Replace boost::scoped_array with std::vector

View file

@ -61,7 +61,7 @@ add_openmw_dir (mwscript
add_openmw_dir (mwlua
luamanagerimp object objectlists userdataserializer luaevents engineevents objectvariant
context globalscripts localscripts playerscripts luabindings objectbindings cellbindings mwscriptbindings
camerabindings vfsbindings uibindings soundbindings inputbindings nearbybindings postprocessingbindings stats debugbindings
camerabindings vfsbindings musicbindings uibindings soundbindings inputbindings nearbybindings postprocessingbindings stats debugbindings
types/types types/door types/item types/actor types/container types/lockable types/weapon types/npc types/creature types/player types/activator types/book types/lockpick types/probe types/apparatus types/potion types/ingredient types/misc types/repair types/armor types/light types/static types/clothing types/levelledlist types/terminal
worker magicbindings factionbindings
)

View file

@ -26,6 +26,11 @@ namespace ESM
class ESMWriter;
}
namespace MWSound
{
enum class MusicType;
}
namespace MWWorld
{
class Ptr;
@ -282,6 +287,9 @@ namespace MWBase
virtual float getAngleToPlayer(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 MWSound::MusicType getMusicType() const = 0;
virtual void setMusicType(MWSound::MusicType type) = 0;
};
}

View file

@ -29,6 +29,14 @@ namespace MWSound
MaxCount
};
enum class MusicType
{
Special,
Explore,
Battle,
Scripted
};
class Sound;
class Stream;
struct Sound_Decoder;
@ -104,9 +112,13 @@ namespace MWBase
virtual void stopMusic() = 0;
///< Stops music if it's playing
virtual void streamMusic(const std::string& filename) = 0;
virtual void streamMusic(
const std::string& filename, MWSound::MusicType type = MWSound::MusicType::Scripted, float fade = 1.f)
= 0;
///< 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;
///< Returns true if music is playing

View file

@ -214,7 +214,8 @@ namespace MWGui
center();
// 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)

View file

@ -40,6 +40,7 @@
#include "factionbindings.hpp"
#include "inputbindings.hpp"
#include "magicbindings.hpp"
#include "musicbindings.hpp"
#include "nearbybindings.hpp"
#include "objectbindings.hpp"
#include "postprocessingbindings.hpp"
@ -392,6 +393,7 @@ namespace MWLua
{ "openmw.input", initInputPackage(context) },
{ "openmw.postprocessing", initPostprocessingPackage(context) },
{ "openmw.ui", initUserInterfacePackage(context) },
{ "openmw.music", initMusicPackage(context) },
};
}

View file

@ -0,0 +1,24 @@
#include "musicbindings.hpp"
#include "luabindings.hpp"
#include "../mwbase/environment.hpp"
#include "../mwbase/soundmanager.hpp"
#include "../mwbase/windowmanager.hpp"
#include "context.hpp"
#include "luamanagerimp.hpp"
namespace MWLua
{
sol::table initMusicPackage(const Context& context)
{
sol::table api(context.mLua->sol(), sol::create);
api["streamMusic"] = [](std::string_view fileName) {
MWBase::Environment::get().getSoundManager()->streamMusic(std::string(fileName));
};
api["stopMusic"] = []() { MWBase::Environment::get().getSoundManager()->stopMusic(); };
return LuaUtil::makeReadOnly(api);
}
}

View file

@ -0,0 +1,13 @@
#ifndef MWLUA_MUSICBINDINGS_H
#define MWLUA_MUSICBINDINGS_H
#include <sol/forward.hpp>
#include "context.hpp"
namespace MWLua
{
sol::table initMusicPackage(const Context&);
}
#endif // MWLUA_MUSICBINDINGS_H

View file

@ -1285,7 +1285,7 @@ namespace MWMechanics
}
}
void Actors::updateCombatMusic()
bool Actors::playerHasHostiles() const
{
const MWWorld::Ptr player = getPlayer();
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
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;
}
return hasHostiles;
}
void Actors::predictAndAvoidCollisions(float duration) const
@ -1735,8 +1723,6 @@ namespace MWMechanics
killDeadActors();
updateSneaking(playerCharacter, duration);
}
updateCombatMusic();
}
void Actors::notifyDied(const MWWorld::Ptr& actor)
@ -1806,7 +1792,8 @@ namespace MWMechanics
// player's death animation is over
MWBase::Environment::get().getStateManager()->askLoadRecent();
// 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
{

View file

@ -74,9 +74,6 @@ namespace MWMechanics
void dropActors(const MWWorld::CellStore* cellStore, const MWWorld::Ptr& ignore);
///< Deregister all actors (except for \a ignore) in the given cell.
void updateCombatMusic();
///< Update combat music state
void update(float duration, bool paused);
///< 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 isAttackingOrSpell(const MWWorld::Ptr& ptr) const;
bool playerHasHostiles() const;
int getGreetingTimer(const MWWorld::Ptr& ptr) const;
float getAngleToPlayer(const MWWorld::Ptr& ptr) const;
GreetingState getGreetingState(const MWWorld::Ptr& ptr) const;
bool isTurningToPlayer(const MWWorld::Ptr& ptr) const;
private:
enum class MusicType
{
Title,
Explore,
Battle
};
std::map<ESM::RefId, int> mDeathCount;
std::list<Actor> mActors;
std::map<const MWWorld::LiveCellRefBase*, std::list<Actor>::iterator> mIndex;
@ -182,7 +174,6 @@ namespace MWMechanics
float mTimerUpdateHello = 0;
float mSneakTimer = 0; // Times update of sneak icon
float mSneakSkillTimer = 0; // Times sneak skill progress from "avoid notice"
MusicType mCurrentMusic = MusicType::Title;
void updateVisibility(const MWWorld::Ptr& ptr, CharacterController& ctrl) const;

View file

@ -24,6 +24,7 @@
#include "../mwbase/dialoguemanager.hpp"
#include "../mwbase/environment.hpp"
#include "../mwbase/soundmanager.hpp"
#include "../mwbase/statemanager.hpp"
#include "../mwbase/windowmanager.hpp"
#include "../mwbase/world.hpp"
@ -257,6 +258,7 @@ namespace MWMechanics
, mClassSelected(false)
, mRaceSelected(false)
, mAI(true)
, mMusicType(MWSound::MusicType::Special)
{
// 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);
mObjects.update(duration, paused);
updateMusicState();
}
void MechanicsManager::processChangedSettings(const Settings::CategorySettingVector& changed)
@ -1572,6 +1576,31 @@ namespace MWMechanics
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)
{
CreatureStats& stats = ptr.getClass().getCreatureStats(ptr);

View file

@ -11,6 +11,11 @@
#include "npcstats.hpp"
#include "objects.hpp"
namespace MWSound
{
enum class MusicType;
}
namespace MWWorld
{
class CellStore;
@ -33,6 +38,8 @@ namespace MWMechanics
typedef std::map<ESM::RefId, OwnerMap> StolenItemsMap;
StolenItemsMap mStolenItems;
MWSound::MusicType mMusicType;
public:
void buildPlayer();
///< build player according to stored class/race/birthsign information. Will
@ -232,7 +239,11 @@ namespace MWMechanics
GreetingState getGreetingState(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:
void updateMusicState();
bool canCommitCrimeAgainst(const MWWorld::Ptr& victim, const MWWorld::Ptr& attacker);
bool canReportCrime(
const MWWorld::Ptr& actor, const MWWorld::Ptr& victim, std::set<MWWorld::Ptr>& playerFollowers);

View file

@ -63,10 +63,11 @@ namespace MWScript
public:
void execute(Interpreter::Runtime& runtime) override
{
std::string sound{ runtime.getStringLiteral(runtime[0].mInteger) };
std::string music{ runtime.getStringLiteral(runtime[0].mInteger) };
runtime.pop();
MWBase::Environment::get().getSoundManager()->streamMusic(sound);
MWBase::Environment::get().getSoundManager()->streamMusic(
Misc::ResourceHelpers::correctMusicPath(music));
}
};

View file

@ -14,6 +14,7 @@
#include <components/vfs/pathutil.hpp>
#include "../mwbase/environment.hpp"
#include "../mwbase/mechanicsmanager.hpp"
#include "../mwbase/statemanager.hpp"
#include "../mwbase/world.hpp"
@ -250,7 +251,7 @@ namespace MWSound
if (filename.empty())
return;
Log(Debug::Info) << "Playing " << filename;
Log(Debug::Info) << "Playing \"" << filename << "\"";
mLastPlayedMusic = filename;
DecoderPtr decoder = getDecoder();
@ -260,7 +261,7 @@ namespace MWSound
}
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;
}
@ -274,7 +275,7 @@ namespace MWSound
mOutput->streamSound(decoder, mMusic.get());
}
void SoundManager::advanceMusic(const std::string& filename)
void SoundManager::advanceMusic(const std::string& filename, float fadeOut)
{
if (!isMusicPlaying())
{
@ -284,7 +285,7 @@ namespace MWSound
mNextMusic = filename;
mMusic->setFadeout(1.f);
mMusic->setFadeout(fadeOut);
}
void SoundManager::startRandomTitle()
@ -319,16 +320,30 @@ namespace MWSound
tracklist.pop_back();
}
void SoundManager::streamMusic(const std::string& filename)
{
advanceMusic("Music/" + filename);
}
bool SoundManager::isMusicPlaying()
{
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)
{
if (mCurrentPlaylist == playlist)
@ -337,7 +352,8 @@ namespace MWSound
if (mMusicFiles.find(playlist) == mMusicFiles.end())
{
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);
mMusicFiles[playlist] = filelist;

View file

@ -127,7 +127,7 @@ namespace MWSound
StreamPtr playVoice(DecoderPtr decoder, const osg::Vec3f& pos, bool playlocal);
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 cull3DSound(SoundBase* sound);
@ -176,9 +176,12 @@ namespace MWSound
void stopMusic() override;
///< Stops music if it's playing
void streamMusic(const std::string& filename) override;
void streamMusic(const std::string& filename, MWSound::MusicType type = MWSound::MusicType::Scripted,
float fade = 1.f) override;
///< 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;
///< Returns true if music is playing

View file

@ -391,7 +391,10 @@ namespace MWWorld
{
std::string_view video = Fallback::Map::getString("Movies_New_Game");
if (!video.empty())
{
MWBase::Environment::get().getSoundManager()->stopMusic();
MWBase::Environment::get().getWindowManager()->playVideo(video, true);
}
}
// enable collision

View file

@ -156,6 +156,11 @@ std::string Misc::ResourceHelpers::correctSoundPath(const std::string& 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)
{
constexpr std::string_view prefix = "meshes";

View file

@ -38,6 +38,9 @@ namespace Misc
// Adds "sound\\".
std::string correctSoundPath(const std::string& resPath);
// Adds "music\\".
std::string correctMusicPath(const std::string& resPath);
// Removes "meshes\\".
std::string_view meshPathForESM3(std::string_view resPath);

View file

@ -23,6 +23,7 @@ Lua API reference
openmw_nearby
openmw_input
openmw_ambient
openmw_music
openmw_ui
openmw_camera
openmw_postprocessing

View file

@ -0,0 +1,7 @@
Package openmw.music
====================
.. include:: version.rst
.. raw:: html
:file: generated_html/openmw_music.html

View file

@ -27,6 +27,8 @@
+------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.input <Package openmw.input>` | by player scripts | | User input. |
+------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.music <Package openmw.music>` | by player scripts | | Music system. |
+------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.ui <Package openmw.ui>` | by player scripts | | Controls :ref:`user interface <User interface reference>`. |
+------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.camera <Package openmw.camera>` | by player scripts | | Controls camera. |

View file

@ -77,6 +77,7 @@ local env = {
nearby = require('openmw.nearby'),
self = require('openmw.self'),
input = require('openmw.input'),
music = require('openmw.music'),
ui = require('openmw.ui'),
camera = require('openmw.camera'),
aux_util = require('openmw_aux.util'),

View file

@ -13,6 +13,7 @@ set(LUA_API_FILES
openmw/async.lua
openmw/core.lua
openmw/debug.lua
openmw/music.lua
openmw/nearby.lua
openmw/postprocessing.lua
openmw/self.lua

View file

@ -0,0 +1,19 @@
---
-- `openmw.music` provides access to music system.
-- @module music
-- @usage local music = require('openmw.music')
---
-- Play a sound file as a music track
-- @function [parent=#music] streamMusic
-- @param #string fileName Path to file in VFS
-- @usage music.streamMusic("Music\\Test\\Test.mp3");
---
-- Stop to play current music
-- @function [parent=#music] stopMusic
-- @usage music.stopMusic();
return nil