Merge branch 'skibidi-dop-doppler' into 'master'

Doppler, take two

See merge request OpenMW/openmw!4660
This commit is contained in:
Epoch 2025-08-02 08:39:20 +00:00
commit ba1b5451c7
23 changed files with 180 additions and 31 deletions

View File

@ -291,6 +291,7 @@ bool Launcher::SettingsPage::loadSettings()
}
}
loadSettingBool(Settings::sound().mCameraListener, *cameraListenerCheckBox);
dopplerSpinBox->setValue(Settings::sound().mDopplerFactor);
}
// Interface Changes
@ -482,6 +483,8 @@ void Launcher::SettingsPage::saveSettings()
const bool cCameraListener = cameraListenerCheckBox->checkState() != Qt::Unchecked;
Settings::sound().mCameraListener.set(cCameraListener);
Settings::sound().mDopplerFactor.set(dopplerSpinBox->value());
}
// Interface Changes

View File

@ -1224,6 +1224,51 @@
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout">
<item>
<widget class="QLabel" name="dopplerLabel">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Controls the strength of the Doppler effect. Zero means it is completely disabled.&lt;/p&gt;&lt;p&gt;The Doppler effect increases or decreases the pitch of sounds relative to the velocity of the sound source and the listener.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Doppler Factor</string>
</property>
</widget>
</item>
<item>
<widget class="QDoubleSpinBox" name="dopplerSpinBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>283</width>
<height>0</height>
</size>
</property>
<property name="decimals">
<number>2</number>
</property>
<property name="minimum">
<double>0.000000000000000</double>
</property>
<property name="maximum">
<double>1.000000000000000</double>
</property>
<property name="singleStep">
<double>0.010000000000000</double>
</property>
<property name="value">
<double>0.250000000000000</double>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer>
<property name="orientation">

View File

@ -234,6 +234,8 @@ namespace MWBase
const osg::Vec3f& pos, const osg::Vec3f& dir, const osg::Vec3f& up, bool underwater)
= 0;
virtual void setListenerVel(const osg::Vec3f& vel) = 0;
virtual void updatePtr(const MWWorld::ConstPtr& old, const MWWorld::ConstPtr& updated) = 0;
void setSimulationTimeScale(float scale) { mSimulationTimeScale = scale; }

View File

@ -496,6 +496,8 @@ namespace MWBase
virtual float getSunVisibility() const = 0;
virtual float getSunPercentage() const = 0;
virtual float getPhysicsFrameRateDt() const = 0;
virtual bool findInteriorPositionInWorldSpace(const MWWorld::CellStore* cell, osg::Vec3f& result) = 0;
/// Teleports \a ptr to the closest reference of \a id (e.g. DivineMarker, PrisonMarker, TempleMarker)

View File

@ -93,9 +93,10 @@ namespace
namespace MWPhysics
{
PhysicsSystem::PhysicsSystem(Resource::ResourceSystem* resourceSystem, osg::ref_ptr<osg::Group> parentNode)
: mShapeManager(
std::make_unique<Resource::BulletShapeManager>(resourceSystem->getVFS(), resourceSystem->getSceneManager(),
resourceSystem->getNifFileManager(), Settings::cells().mCacheExpiryDelay))
: mPhysicsDt(1.f / 60.f)
, mShapeManager(std::make_unique<Resource::BulletShapeManager>(resourceSystem->getVFS(),
resourceSystem->getSceneManager(), resourceSystem->getNifFileManager(),
Settings::cells().mCacheExpiryDelay))
, mResourceSystem(resourceSystem)
, mDebugDrawEnabled(false)
, mTimeAccum(0.0f)
@ -103,7 +104,6 @@ namespace MWPhysics
, mWaterHeight(0)
, mWaterEnabled(false)
, mParentNode(std::move(parentNode))
, mPhysicsDt(1.f / 60.f)
{
mResourceSystem->addResourceManager(mShapeManager.get());

View File

@ -287,6 +287,8 @@ namespace MWPhysics
void reportStats(unsigned int frameNumber, osg::Stats& stats) const;
void reportCollision(const btVector3& position, const btVector3& normal);
float mPhysicsDt;
private:
void updateWater();
@ -330,8 +332,6 @@ namespace MWPhysics
osg::ref_ptr<osg::Group> mParentNode;
float mPhysicsDt;
std::size_t mSimulationsCounter = 0;
std::array<std::vector<Simulation>, 2> mSimulations;
std::vector<std::pair<MWWorld::Ptr, osg::Vec3f>> mActorsPositions;

View File

@ -15,6 +15,7 @@
#include <components/misc/constants.hpp>
#include <components/misc/resourcehelpers.hpp>
#include <components/misc/thread.hpp>
#include <components/settings/values.hpp>
#include <components/vfs/manager.hpp>
#include "efxpresets.h"
@ -963,6 +964,7 @@ namespace MWSound
// Speed of sound is in units per second. Take the sound speed in air (assumed
// meters per second), multiply by the units per meter to get the speed in u/s.
alSpeedOfSound(Constants::SoundSpeedInAir * Constants::UnitsPerMeter);
alDopplerFactor(Settings::sound().mDopplerFactor);
alGetError();
mInitialized = true;
@ -1142,8 +1144,8 @@ namespace MWSound
alSource3f(source, AL_VELOCITY, 0.0f, 0.0f, 0.0f);
}
void OpenALOutput::initCommon3D(ALuint source, const osg::Vec3f& pos, ALfloat mindist, ALfloat maxdist,
ALfloat gain, ALfloat pitch, bool loop, bool useenv)
void OpenALOutput::initCommon3D(ALuint source, const osg::Vec3f& pos, const osg::Vec3f& vel, ALfloat mindist,
ALfloat maxdist, ALfloat gain, ALfloat pitch, bool loop, bool useenv)
{
alSourcef(source, AL_REFERENCE_DISTANCE, mindist);
alSourcef(source, AL_MAX_DISTANCE, maxdist);
@ -1179,11 +1181,11 @@ namespace MWSound
alSourcef(source, AL_PITCH, pitch);
alSourcefv(source, AL_POSITION, pos.ptr());
alSource3f(source, AL_DIRECTION, 0.0f, 0.0f, 0.0f);
alSource3f(source, AL_VELOCITY, 0.0f, 0.0f, 0.0f);
alSourcefv(source, AL_VELOCITY, vel.ptr());
}
void OpenALOutput::updateCommon(
ALuint source, const osg::Vec3f& pos, ALfloat maxdist, ALfloat gain, ALfloat pitch, bool useenv)
void OpenALOutput::updateCommon(ALuint source, const osg::Vec3f& pos, const osg::Vec3f& vel, ALfloat maxdist,
ALfloat gain, ALfloat pitch, bool useenv)
{
if (useenv && mListenerEnv == Env_Underwater && !mWaterFilter)
{
@ -1195,7 +1197,7 @@ namespace MWSound
alSourcef(source, AL_PITCH, pitch);
alSourcefv(source, AL_POSITION, pos.ptr());
alSource3f(source, AL_DIRECTION, 0.0f, 0.0f, 0.0f);
alSource3f(source, AL_VELOCITY, 0.0f, 0.0f, 0.0f);
alSourcefv(source, AL_VELOCITY, vel.ptr());
}
bool OpenALOutput::playSound(Sound* sound, Sound_Handle data, float offset)
@ -1248,8 +1250,9 @@ namespace MWSound
}
source = mFreeSources.front();
initCommon3D(source, sound->getPosition(), sound->getMinDistance(), sound->getMaxDistance(),
sound->getRealVolume(), getTimeScaledPitch(sound), sound->getIsLooping(), sound->getUseEnv());
initCommon3D(source, sound->getPosition(), sound->getVelocity(), sound->getMinDistance(),
sound->getMaxDistance(), sound->getRealVolume(), getTimeScaledPitch(sound), sound->getIsLooping(),
sound->getUseEnv());
alSourcei(source, AL_BUFFER, GET_PTRID(data));
alSourcef(source, AL_SEC_OFFSET, offset);
if (getALError() != AL_NO_ERROR)
@ -1312,8 +1315,8 @@ namespace MWSound
return;
ALuint source = GET_PTRID(sound->mHandle);
updateCommon(source, sound->getPosition(), sound->getMaxDistance(), sound->getRealVolume(),
getTimeScaledPitch(sound), sound->getUseEnv());
updateCommon(source, sound->getPosition(), sound->getVelocity(), sound->getMaxDistance(),
sound->getRealVolume(), getTimeScaledPitch(sound), sound->getUseEnv());
getALError();
}
@ -1360,8 +1363,8 @@ namespace MWSound
if (sound->getIsLooping())
Log(Debug::Warning) << "Warning: cannot loop stream \"" << decoder->getName() << "\"";
initCommon3D(source, sound->getPosition(), sound->getMinDistance(), sound->getMaxDistance(),
sound->getRealVolume(), getTimeScaledPitch(sound), false, sound->getUseEnv());
initCommon3D(source, sound->getPosition(), sound->getVelocity(), sound->getMinDistance(),
sound->getMaxDistance(), sound->getRealVolume(), getTimeScaledPitch(sound), false, sound->getUseEnv());
if (getALError() != AL_NO_ERROR)
return false;
@ -1443,8 +1446,8 @@ namespace MWSound
OpenAL_SoundStream* stream = reinterpret_cast<OpenAL_SoundStream*>(sound->mHandle);
ALuint source = stream->mSource;
updateCommon(source, sound->getPosition(), sound->getMaxDistance(), sound->getRealVolume(),
getTimeScaledPitch(sound), sound->getUseEnv());
updateCommon(source, sound->getPosition(), sound->getVelocity(), sound->getMaxDistance(),
sound->getRealVolume(), getTimeScaledPitch(sound), sound->getUseEnv());
getALError();
}
@ -1459,12 +1462,13 @@ namespace MWSound
}
void OpenALOutput::updateListener(
const osg::Vec3f& pos, const osg::Vec3f& atdir, const osg::Vec3f& updir, Environment env)
const osg::Vec3f& pos, const osg::Vec3f& atdir, const osg::Vec3f& updir, const osg::Vec3f& vel, Environment env)
{
if (mContext)
{
ALfloat orient[6] = { atdir.x(), atdir.y(), atdir.z(), updir.x(), updir.y(), updir.z() };
alListenerfv(AL_POSITION, pos.ptr());
alListenerfv(AL_VELOCITY, vel.ptr());
alListenerfv(AL_ORIENTATION, orient);
if (env != mListenerEnv)
@ -1497,6 +1501,7 @@ namespace MWSound
}
mListenerPos = pos;
mListenerVel = vel;
mListenerEnv = env;
}
@ -1582,7 +1587,6 @@ namespace MWSound
: SoundOutput(mgr)
, mDevice(nullptr)
, mContext(nullptr)
, mListenerPos(0.0f, 0.0f, 0.0f)
, mListenerEnv(Env_Normal)
, mWaterFilter(0)
, mWaterEffect(0)

View File

@ -46,6 +46,7 @@ namespace MWSound
StreamVec mActiveStreams;
osg::Vec3f mListenerPos;
osg::Vec3f mListenerVel;
Environment mListenerEnv;
ALuint mWaterFilter;
@ -64,11 +65,11 @@ namespace MWSound
std::unique_ptr<DefaultDeviceThread> mDefaultDeviceThread;
void initCommon2D(ALuint source, const osg::Vec3f& pos, ALfloat gain, ALfloat pitch, bool loop, bool useenv);
void initCommon3D(ALuint source, const osg::Vec3f& pos, ALfloat mindist, ALfloat maxdist, ALfloat gain,
ALfloat pitch, bool loop, bool useenv);
void initCommon3D(ALuint source, const osg::Vec3f& pos, const osg::Vec3f& vel, ALfloat mindist, ALfloat maxdist,
ALfloat gain, ALfloat pitch, bool loop, bool useenv);
void updateCommon(
ALuint source, const osg::Vec3f& pos, ALfloat maxdist, ALfloat gain, ALfloat pitch, bool useenv);
void updateCommon(ALuint source, const osg::Vec3f& pos, const osg::Vec3f& vel, ALfloat maxdist, ALfloat gain,
ALfloat pitch, bool useenv);
float getTimeScaledPitch(SoundBase* sound);
@ -108,8 +109,8 @@ namespace MWSound
void startUpdate() override;
void finishUpdate() override;
void updateListener(
const osg::Vec3f& pos, const osg::Vec3f& atdir, const osg::Vec3f& updir, Environment env) override;
void updateListener(const osg::Vec3f& pos, const osg::Vec3f& atdir, const osg::Vec3f& updir,
const osg::Vec3f& vel, Environment env) override;
void pauseSounds(int types) override;
void resumeSounds(int types) override;

View File

@ -31,6 +31,8 @@ namespace MWSound
struct SoundParams
{
osg::Vec3f mPos;
osg::Vec3f mLastPos;
osg::Vec3f mVel;
float mVolume = 1.0f;
float mBaseVolume = 1.0f;
float mPitch = 1.0f;
@ -57,6 +59,8 @@ namespace MWSound
public:
void setPosition(const osg::Vec3f& pos) { mParams.mPos = pos; }
void setLastPosition(const osg::Vec3f& lastpos) { mParams.mLastPos = lastpos; }
void setVelocity(const osg::Vec3f& vel) { mParams.mVel = vel; }
void setVolume(float volume) { mParams.mVolume = volume; }
void setBaseVolume(float volume) { mParams.mBaseVolume = volume; }
void setFadeout(float duration) { setFade(duration, 0.0, Play_StopAtFadeEnd); }
@ -150,6 +154,8 @@ namespace MWSound
}
const osg::Vec3f& getPosition() const { return mParams.mPos; }
const osg::Vec3f& getLastPosition() const { return mParams.mLastPos; }
const osg::Vec3f& getVelocity() const { return mParams.mVel; }
float getRealVolume() const { return mParams.mVolume * mParams.mBaseVolume * mParams.mFadeVolume; }
float getPitch() const { return mParams.mPitch; }
float getMinDistance() const { return mParams.mMinDistance; }

View File

@ -119,6 +119,7 @@ namespace MWSound
, mListenerPos(0, 0, 0)
, mListenerDir(1, 0, 0)
, mListenerUp(0, 0, 1)
, mListenerVel(0, 0, 0)
, mUnderwaterSound(nullptr)
, mNearWaterSound(nullptr)
, mPlaybackPaused(false)
@ -960,7 +961,7 @@ namespace MWSound
}
mOutput->startUpdate();
mOutput->updateListener(mListenerPos, mListenerDir, mListenerUp, env);
mOutput->updateListener(mListenerPos, mListenerDir, mListenerUp, mListenerVel, env);
updateMusic(duration);
@ -977,7 +978,13 @@ namespace MWSound
if (sound->getIs3D())
{
if (!ptr.isEmpty())
{
sound->setLastPosition(sound->getPosition());
sound->setPosition(ptr.getRefData().getPosition().asVec3());
MWBase::World* world = MWBase::Environment::get().getWorld();
sound->setVelocity(
(sound->getPosition() - sound->getLastPosition()) / world->getPhysicsFrameRateDt());
}
cull3DSound(sound);
}
@ -1013,8 +1020,11 @@ namespace MWSound
{
if (!ptr.isEmpty())
{
sound->setLastPosition(sound->getPosition());
MWBase::World* world = MWBase::Environment::get().getWorld();
sound->setPosition(world->getActorHeadTransform(ptr).getTrans());
sound->setVelocity(
(sound->getPosition() - sound->getLastPosition()) / world->getPhysicsFrameRateDt());
}
cull3DSound(sound);
@ -1153,6 +1163,11 @@ namespace MWSound
mWaterSoundUpdater.setUnderwater(underwater);
}
void SoundManager::setListenerVel(const osg::Vec3f& vel)
{
mListenerVel = vel;
}
void SoundManager::updatePtr(const MWWorld::ConstPtr& old, const MWWorld::ConstPtr& updated)
{
SoundMap::iterator snditer = mActiveSounds.find(old.mRef);

View File

@ -92,6 +92,7 @@ namespace MWSound
osg::Vec3f mListenerPos;
osg::Vec3f mListenerDir;
osg::Vec3f mListenerUp;
osg::Vec3f mListenerVel;
int mPausedSoundTypes[BlockerType::MaxCount] = {};
@ -283,6 +284,8 @@ namespace MWSound
void setListenerPosDir(
const osg::Vec3f& pos, const osg::Vec3f& dir, const osg::Vec3f& up, bool underwater) override;
void setListenerVel(const osg::Vec3f& vel) override;
void updatePtr(const MWWorld::ConstPtr& old, const MWWorld::ConstPtr& updated) override;
void clear() override;

View File

@ -61,8 +61,8 @@ namespace MWSound
virtual void startUpdate() = 0;
virtual void finishUpdate() = 0;
virtual void updateListener(
const osg::Vec3f& pos, const osg::Vec3f& atdir, const osg::Vec3f& updir, Environment env)
virtual void updateListener(const osg::Vec3f& pos, const osg::Vec3f& atdir, const osg::Vec3f& updir,
const osg::Vec3f& vel, Environment env)
= 0;
virtual void pauseSounds(int types) = 0;

View File

@ -461,6 +461,11 @@ namespace MWWorld
update(magicBoltState, duration);
for (const auto& sound : magicBoltState.mSounds)
{
sound->setVelocity(direction * speed);
}
// For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit
// result.
std::vector<MWWorld::Ptr> targetActors;

View File

@ -1460,6 +1460,8 @@ namespace MWWorld
void World::queueMovement(const Ptr& ptr, const osg::Vec3f& velocity)
{
mPhysics->queueObjectMovement(ptr, velocity);
if (ptr == MWMechanics::getPlayer())
MWBase::Environment::get().getSoundManager()->setListenerVel(velocity);
}
void World::updateAnimatedCollisionShape(const Ptr& ptr)
@ -3182,6 +3184,11 @@ namespace MWWorld
return mWeatherManager->getSunPercentage(getTimeStamp().getHour());
}
float World::getPhysicsFrameRateDt() const
{
return mPhysics->mPhysicsDt;
}
bool World::findInteriorPositionInWorldSpace(const MWWorld::CellStore* cell, osg::Vec3f& result)
{
if (cell->isExterior())

View File

@ -584,6 +584,8 @@ namespace MWWorld
float getSunVisibility() const override;
float getSunPercentage() const override;
float getPhysicsFrameRateDt() const override;
bool findInteriorPositionInWorldSpace(const MWWorld::CellStore* cell, osg::Vec3f& result) override;
/// Teleports \a ptr to the closest reference of \a id (e.g. DivineMarker, PrisonMarker, TempleMarker)

View File

@ -24,6 +24,7 @@ namespace Settings
SettingValue<HrtfMode> mHrtfEnable{ mIndex, "Sound", "hrtf enable" };
SettingValue<std::string> mHrtf{ mIndex, "Sound", "hrtf" };
SettingValue<bool> mCameraListener{ mIndex, "Sound", "camera listener" };
SettingValue<float> mDopplerFactor{ mIndex, "Sound", "doppler factor", makeClampSanitizerFloat(0, 1) };
};
}

View File

@ -121,3 +121,13 @@ Sound Settings
When true, uses the camera position and direction for audio instead of the player position.
This makes audio in third person sound relative to camera instead of the player.
False is vanilla Morrowind behaviour.
.. omw-setting::
:title: doppler factor
:type: float32
:range: 0.0 (disabled), 1.0 (maximum strength)
:default: 0.25
:location: :bdg-success:`Launcher > Settings > Audio`
This setting controls the strength of the Doppler effect. The Doppler effect increases or decreases the pitch of sounds
relative to the velocity of the sound source and the listener.

View File

@ -1451,5 +1451,13 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov
<source>Run Script After Startup:</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Controls the strength of the Doppler effect. Zero means it is completely disabled.&lt;/p&gt;&lt;p&gt;The Doppler effect increases or decreases the pitch of sounds relative to the velocity of the sound source and the listener.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Doppler Factor</source>
<translation type="unfinished"></translation>
</message>
</context>
</TS>

View File

@ -1451,5 +1451,13 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If enabled - makes transitions between different animations/poses much smoother. Also allows to load animation blending config YAML files that can be bundled with animations in order to customise blending styles.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation></translation>
</message>
<message>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Controls the strength of the Doppler effect. Zero means it is completely disabled.&lt;/p&gt;&lt;p&gt;The Doppler effect increases or decreases the pitch of sounds relative to the velocity of the sound source and the listener.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation></translation>
</message>
<message>
<source>Doppler Factor</source>
<translation></translation>
</message>
</context>
</TS>

View File

@ -1454,5 +1454,13 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov
<source>Run Script After Startup:</source>
<translation>Script à lancer après démarrage :</translation>
</message>
<message>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Controls the strength of the Doppler effect. Zero means it is completely disabled.&lt;/p&gt;&lt;p&gt;The Doppler effect increases or decreases the pitch of sounds relative to the velocity of the sound source and the listener.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Doppler Factor</source>
<translation type="unfinished"></translation>
</message>
</context>
</TS>

View File

@ -1466,5 +1466,13 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov
<source>Use the Camera as the Sound Listener</source>
<translation>Использовать камеру как слушателя</translation>
</message>
<message>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Controls the strength of the Doppler effect. Zero means it is completely disabled.&lt;/p&gt;&lt;p&gt;The Doppler effect increases or decreases the pitch of sounds relative to the velocity of the sound source and the listener.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Определяет силу эффекта Доплера. Нулевое значение означает, что эффект отключен полностью.&lt;/p&gt;&lt;p&gt;Эффект Доплера увеличивает или уменьшает высоту звуков в зависимости от скорости источника звука и слушателя.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
</message>
<message>
<source>Doppler Factor</source>
<translation>Множитель эффекта Доплера</translation>
</message>
</context>
</TS>

View File

@ -1470,5 +1470,13 @@ de ordinarie fonterna i Morrowind. Bocka denna ruta om du ändå föredrar ordin
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If enabled - makes transitions between different animations/poses much smoother. Also allows to load animation blending config YAML files that can be bundled with animations in order to customise blending styles.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Vid aktivering gör denna funktion att övergångarna mellan olika animationer och poser blir mycket mjukare. Funktionen gör det också möjligt att konfigurera animationsövergångarna i YAML-filer. Dessa filer kan buntas ihop tillsammans med nya animationsfiler.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
</message>
<message>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Controls the strength of the Doppler effect. Zero means it is completely disabled.&lt;/p&gt;&lt;p&gt;The Doppler effect increases or decreases the pitch of sounds relative to the velocity of the sound source and the listener.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Kontrollerar styrkan dopplereffekten. Noll innebär helt inaktiverat.&lt;/p&gt;&lt;p&gt;Dopplereffekten höjer eller sänker tonhöjden på ljud i förhållande till ljudkällans hastighet och lyssnaren.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
</message>
<message>
<source>Doppler Factor</source>
<translation>Dopplerfaktor</translation>
</message>
</context>
</TS>

View File

@ -624,6 +624,9 @@ hrtf =
# Specifies whether to use camera as audio listener
camera listener = false
# Specifies strength of doppler effect
doppler factor = 0.25
[Video]
# Resolution of the OpenMW window or screen.