Realistic Doppler Effect (Aeronautica)

Hello,
I’m thinking of making an actually realistic doppler effect.
I have this game called Aeronautica. It’s basically a flight simulator.
So far from my Roblox experiences, it depicts the best doppler effects.

Some stuffs I think of from the effect, or how the real nature actually is:

  1. Front & Back direction of the aircraft emits their engine sound with different pitch, or maybe even different audio asset.
    1.1. When an aircraft is getting closer, it pitches up slowly, though depends on aircraft’s speed.
    1.2. During flyby, it pitches down significantly when it’s right above your head.

  2. The further an aircraft is, the sound emitted is like ‘hummed’. Here, the soundwaves gets distorted by the air as they get further from you.

  3. When an aircraft is going much faster, the front direction of the sound partially disappears; or, the distance between you and the aircraft needs to be closer in order to be heard.
    3.1. This is basically getting closer to mach speeds, where the Front direction of the sound is completely silent as the aircraft itself is faster.

I’m thinking of taking the use of something like .LookVector and distance formula (positionSubtract.Magnitude`) from both Camera and the aircraft, but I don’t know any other required math formulas and completion. Any ideas? :thinking:

4 Likes

You actually only need a single formula for calculating the frequency shift caused by the Doppler effect. You can find it on the Wikipedia page for it. It looks like this:

0ed997bc5f31f32413fa3827be77f0708f141915

f is the resulting frequency and f0 is the starting frequency of your sound. For the purpose of using the formula with Roblox sounds you can leave out f0 to calculate a scalar you can use with a PitchShiftSoundEffect instance instead.

c is a constant representing the speed of your waves in the current medium you’re in. For example, if you’re calculating the doppler effect for sound waves traveling through air c should be equal to 343, as that’s the speed of sound through air in m/s.

Lastly vr and vs represent the relative velocities of the audio source and receiver towards each other. Vr should be positive if the receiver is moving towards the source and Vs should be positive if the source is moving away from the receiver.

Based on information from this StackExchange post you can calculate vr and vs like this:

  1. First you calculate the vector from the receiver to the source:
local DistanceVector = SourcePosition - ReceiverPosition 
  1. Secondly you calculate vr and vs using the following equations:
local RelativeRecieverVelocity = RecieverVelocity:Dot(DistanceVector) / DistanceVector.Magnitude
local RelativeSourceVelocity = SourceVelocity:Dot(DistanceVector) / DistanceVector.Magnitude

The ReceiverVelocity and SourceVelocity are the velocity vectors of the receiver and source in studs/s.

Lastly you can calculate the DopplerShift scalar with the formula provided above and apply the resulting value to a PitchShiftSoundEffect | Documentation - Roblox Creator Hub to actually shift sounds according to the Doppler effect.

3 Likes

you were one of the devs of aeronautica? dang i love that game, i quit a while ago cuz i have to use a pretty trashy laptop so i cant rlly run it good, but anyways, hasnt roblox released those new sound APIs, im not too complete about them, but maybe test them, unless u already did, otherwise just follow what the other guy said

What or how do I do with RelativeVelocities? I don’t know the context of the ± symbols.
How would I get ReceiverVelocity? It’s based on my Camera, not my character.

Roblox doesn’t provide a way to get a cameras velocity directly but you can calculate it yourself. Using the .Heartbeat event of the RunService you can calculate your cameras velocity every frame. Save your cameras position vector and subtract its position vector next frame by it to get the cameras velocity per DT. Since the formula calls for velocity in Studs/s you have to multiply the velocity vector obtained above by 1/DT, where DT is the delta time given by the .Heartbeat event:

--Camera.Position is pseudocode, replace it with the actual position of your camera

local RunService = game:GetService("RunService")

local OldCameraPosition = Camera.Position

RunService.Heartbeat:Connect(function(DT)
    PositionChange = Camera.Position - OldCameraPosition
    OldCameraPosition =  Camera.Position
    Velocity = PositionChange * 1/DT
    
    --Perform remaining doppler effect calculations here and update the pitch of your sounds

end)

Regarding the formula:
The ± symbols in the function mean that vr and vs can be either positive or negative based on the movement direction of both the receiver and source relative to each other. In the context of Luau you can write the equation like this:

local DopplerScalar = (c + RelativeRecieverVelocity) / (c + RelativeSourceVelocity)

Keep in mind that using the camera as the receiver will result in sounds shifting in pitch if you zoom in and out or pan the camera. It might be better to use the character itself to avoid this problem. This issue is caused by the fact that both the sources AND the receivers velocities play a role in the formula. If the receiver moves towards the source at for example 10 m/s you will experience the same shift in pitch as if the source is moving towards the receiver at 10 m/s.

Progress 1

I’ve done with both Camera and character relatives, but the result is strange.

The result varies and is very random, I used print() in the loop to see the equation result.

So, I tried it with these aircrafts flying at random speeds, ranging from 75 to 600 studs/s.
When the aircraft is approaching, it prints 0.3+, or 0.5+, and any numbers below 1.

As it flies above me, it prints with significant changes— with large intervals. Sometimes it prints 7+, sometimes 2+, and sometimes even negative numbers?

They stay with their values with smaller decimal changes as they get further.

This randomness definitely depends on the speed of aircraft.

I assume I must’ve done the formula wrong.

local prevCamPos = Camera.CFrame.Position

game["Run Service"].Heartbeat:Connect(function(dt)
		
	local distVector = (prevCamPos - planeRP.Position)
	local partVelocity = planeRP.AssemblyLinearVelocity
	
	local newCamPos = Cam.CFrame.Position - prevCamPos
	prevCamPos = Cam.CFrame.Position
	local camVelocity = newCamPos * 1/dt
	
	local relReceivVel = camVelocity:Dot(distVector)/distVector.Magnitude
	local relSourceVel = partVelocity:Dot(distVector)/distVector.Magnitude
	
	local result = (c + relReceivVel) / (c + relSourceVel)
	
	pitchShift.Octave = result

end)

The first issue you’re having is that your scalar is actually inverted. The pitch should increase as you’re moving towards your source. The problem is caused by your distVector being inverted. It should be SourcePosition - ReceiverPosition and not the other way around. Additionally you should use the current position of the camera for calculating it and not the previous one.

Secondly .AssemblyLinearVelocity seems to produces weird results, at least for me. Try calculating the velocity for your source the same way you do it for the receiver. From what I can tell getting the velocity reliably works a lot better that way.

The function returning negative values is also normal. Imagine a scenario where you’re in a field of sound waves produced by a source. If you move away from a source with the exact same velocity as its sound new sound waves will be unable to reach you. Now if you speed up even more you will start catching up to sound waves in front of you and run into them from behind. That’s what a negative value represents.

Due to limitations with Roblox you’re unable to actually hear this happening in game though as the pitch modifier only has a range from 0.5 to 2. I did find this post describing a method to get around this limitation but it’s up to you to decide if it’s worth the effort.

I just realized I made a typo in one of my original responses. You have to multiply by (1/DT) not 1/DT. The correct way of doing it looks like this: Velocity = PositionChange * (1/DT).

Progress 2

It seems to start working the way, but still some debris.

I fixed the distanceVector value and It still doesn’t pitch up during approach. It will only pitch down by little decimal values.

However, passing by and straying away is proper now.

But something. The faster the aircraft, the larger the range of pitch (approach-leave) is. Since the c value is only 343 studs/s, if they’re faster than that, it does negative values on approach. That should be what it is.


Quite off-topic this one. Speaking of c as the speed of sound, I’m still unsure what exactly the speed should be according to Roblox world.

It’s said that 1 stud is equal to 0.28 meters.
A meter is roughly 3.571+ studs.
The speed of sound is 343 m/s.
Which means the speed of sound here should be 1225 studs/s.

I’ve tried applying it on an aircraft, and I think it’s way too fast.
I tried 343 stud/s instead, and it’s… slower.

The correct value for C is 343 since you’re performing all your calculations using studs as your unit. Please provide the current state of your code. It’s hard for me to troubleshoot the problems you’re having without it.

	game["Run Service"].Heartbeat:Connect(function(dt)
		
		local newCamPos = Cam.CFrame.Position - prevCamPos
		prevCamPos = Cam.CFrame.Position
		local camVel = newCamPos * (1/dt)
		
		local distVector = (planeRP.Position - newCamPos)
		local partVel = planeRP.Velocity
		
		local relReceivVel = camVel:Dot(distVector)/distVector.Magnitude
		local relSourceVel = partVel:Dot(distVector)/distVector.Magnitude
		
		local result = (c + relReceivVel) / (c + relSourceVel)
		
		f_pitch.Octave = result

	end)