# How do I simulate a 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 simulate 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 dont know is how much the frequency should change depending 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 in medium (typically a big number) 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


but i think its wrong, it wont produce any change in frequency, for eg: if vr = 0 (player dont move) and emitter have constant speed, then vr and vs wont change (while they should).

maybe should i calculate velocity of player relatively to velocity of emitter ?

like this :

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


then how vr and vs should be feed ?

2) wikipedia also give another formula to simulate the effect of a vehicle 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 suppose the receiver dont move, which is not the case here. if player and emitter move at same speed (or 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 correct formula, using SkimFlux 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 on SkimFlux picture.


EDIT2 :

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)


NOTE : it use vector projection, described here :

then vr,s and vs,r should be injected in first wikipedia formula :

I tested it and it works sucessfully, 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. – yoozer8 Feb 9 '12 at 14:35
• – bobobobo Sep 10 '13 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. – tigrou Feb 9 '12 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...) – tigrou Feb 9 '12 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. – SkimFlux Feb 9 '12 at 17:12
• well, actually i found a lot easier way to find vr,r vs,r : en.wikipedia.org/wiki/Vector_projection – tigrou Feb 10 '12 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.