# How do I simulate the Doppler effect in a game?

I am trying to simulate the Doppler effect in a game (a car racing game). I am not using a specific sound library that simulates the effect; I only have a callback function where I mix the data.

I already figured out how to change frequency of a sample in the mixer function.

What I don't know is how much the frequency should change, depending on player and emitter position and velocity.

Here is what I have in the game:

//player
vec3 p.pos;
vec3 p.vel;

//emitter
vec3 e.pos;
vec3 e.vel;

1. According to Wikipedia, the relation between emitted frequency and observed frequency is given by:

float f = (c + vr) / (c + vs) * fo

where c is a constant, the velocity of waves in the medium (typically a big number), and vs and vr are source and receiver velocities relative to medium.

So I guess:

float vr = p.vel.length; //player speed
float vs = e.vel.length; //emitter speed

However, I think this is wrong. It won't produce any change in frequency (for example) if vr = 0 (player doesn't move) and the emitter has constant speed, then vr and vs won't change (while they should).

Maybe should I calculate the velocity of the player relatively to the velocity of the emitter, like this?:

relative_speed = distance(p.pos + p.vel, e.pos + e.vel) -
distance(p.pos, e.pos);

Then how should vr and vs be found?

1. Wikipedia gives another formula to simulate the effect of a vehicle when that vehicle passes by the observer:

vr = vs * cos(theta);

//theta is angle between observer and emitter
//theta = atan2(e.pos.y-p.pos.y, e.pos.x-p.pos.x);?

However, this formula supposes that the receiver doesn't move, which is not the case here. If the player and the emitter move at same speed (or with a small difference), there should be no Doppler effect. This function is also specific to one case, I suppose the final formula should be the same no mater the situation.

EDIT: I'm trying to find a correct formula, using SkimFlux's post:

vr,r = vr.vel * cos(shortest_angle_between ( vr.vel , vs.pos - vr.pos));
vs,r = vs.vel * cos(shortest_angle_between ( vs.vel , vr.pos - vs.pos));

//is there a easier/faster way to find them out?
//note: vr.vel and vs.vel are vectors, the green and red arrows in SkimFlux's picture.

EDIT 2:

For those interested, here is final formula:

vec2 dist = vs.pos - vr.pos;

vr,r = dotproduct(vr.vel, dist) / length(dist)
vs,r = dotproduct(vs.vel, dist) / length(dist)

It uses vector projection:

Then vr,s and vs,r should be injected into the first Wikipedia formula:

I tested it and it works successfully, providing great results.

• You can adapt the formula that assumes the receiver isn't moving by replacing the actual movement of the source with its movement relative to the receiver. Commented Feb 9, 2012 at 14:35
• Commented Sep 10, 2013 at 13:27

1) Assumes that both objects are moving on the same line - (this is explained in the wikipedia page you linked) your conclusion is correct, in this situation, with constant velocities, the frequency shift is constant. For the frequency shift to change, the relative velocities need to change, hence formula 2), for the situation where Vs is constant but not colinear with the S-R axis.

Formula 2) is misleading however: Vr should be read as Vs,r, that is, the radial/relative component of the source velocity.

Please note that the Doppler effect depends only on velocities, you only need the positions to find the S-R axis.

Edit: this should help you figure out the velocities, you need to use the Vs,r and Vr,r quantities with formula 1:

• ok thanks you for your answer (and picture), it helps a lot. now everything is clear, i should combine formula 1 and 2 together. as you explained, formula2 will is usefull when objects are not in same line. the last part is to find out vr,r and vs,r. vr,r = vr.vel * cos(shortest_angle_between ( vr.vel , vs.pos - vr.pos)); vs,r = vs.vel * cos( shortest_angle_between ( vs.vel , vr.pos - vs.pos)); //is there a easier/faster way to find them out ? //note vr.vel and vs.vel are vectors, the green and red arrows on SkimFlux picture. Commented Feb 9, 2012 at 14:13
• I edited first post and added formula with correct formatting. Can you check them ? (first time i use gamedev stackexchange. i didnt know it wont keep line returns in reply, and that comment is locked after 5 min...) Commented Feb 9, 2012 at 14:19
• @user1083855 Yes, those look right. One way to make it simpler/faster would be to follow Jim's suggestion and use formula 2) with the relative movement between both. I don't think it's really the same because the real Doppler effect depends on the velocities of both entities relative to the sound medium (the air), but in a game situation it will probably be close enough and save you an expensive cos operation. Commented Feb 9, 2012 at 17:12
• well, actually i found a lot easier way to find vr,r vs,r : en.wikipedia.org/wiki/Vector_projection Commented Feb 10, 2012 at 14:39

For XACT, there is the doppler pitch scalar variable should be specified, ie relative speed, where 1.0 is the same speed, but < 1.0 is slower and > 1.0 is faster

Thank you guys for the code, which I've transferred to this piece of C#, where a sound is calculated between screen position and a cue. Works precisely

soundElements.ForEach(e =>
{
var cuePosition = new Vector3(e.PhysicPosition, 0);
var distance = cuePosition - ScreenCenter;
var distanceLength = distance.Length();
e.Cue.SetVariable("Distance", distanceLength);
var dopplerPitchScalar = 1.0f;
if (e.AssociatedBody != null)
{
//https://gamedev.stackexchange.com/questions/23583/how-do-i-simulate-a-doppler-effect-in-a-game
var screenVelocity = Vector3.Dot(ScreenVelocity, distance) / distanceLength;
var cueVelocity = Vector3.Dot(new Vector3(e.AssociatedBody.LinearVelocity, 0), distance) / distanceLength;
var relativeVelocity = screenVelocity - cueVelocity;
dopplerPitchScalar = (1f + relativeVelocity / SoundEffect.SpeedOfSound) / (1f - relativeVelocity / SoundEffect.SpeedOfSound);
//Console.WriteLine(\$"C: {ScreenCenter}, V: {ScreenVelocity}, D: {dopplerPitchScalar}");
}
e.Cue.SetVariable("DopplerPitchScalar", dopplerPitchScalar);
});

Btw.