More Efficient Way to Rapidly Change Sound Pitch

So, when driving a car, the pitch of the car goes up as it gets faster, and then when you reach the next gear, it goes back down, and then comes back up through the pitch again. This is because at the bottom of each gear you are at low RPMs, and at the top of each gear, you’re at high RPM. I’ve noticed a lot of ping issues with my current script, and I’m curious as if there are any other ways to do it to mitigate some of these issues.

Current code

while true do
if script.Parent.Velocity.Magnitude < 10 then
script.Parent.Engine.Playing = true
script.Parent.Engine.PlaybackSpeed = ((script.Parent.Velocity.Magnitude/100)+.1)
script.Parent.Engine.Volume = ((script.Parent.Velocity.Magnitude/100)+.5)
elseif script.Parent.Velocity.Magnitude < 125 then
script.Parent.Engine.PlaybackSpeed = ((script.Parent.Velocity.Magnitude/250)+.2)
script.Parent.Engine.Volume = ((script.Parent.Velocity.Magnitude/250)+.6)
elseif script.Parent.Velocity.Magnitude < 250 then
script.Parent.Engine.PlaybackSpeed = ((script.Parent.Velocity.Magnitude/500)+.5)
script.Parent.Engine.Volume = ((script.Parent.Velocity.Magnitude/500)+.6)
elseif script.Parent.Velocity.Magnitude < 390 then
script.Parent.Engine.PlaybackSpeed = ((script.Parent.Velocity.Magnitude/750)+.53)
script.Parent.Engine.Volume = ((script.Parent.Velocity.Magnitude/750)+.6)
elseif script.Parent.Velocity.Magnitude < 500 then
script.Parent.Engine.PlaybackSpeed = ((script.Parent.Velocity.Magnitude/1000)+.59)
script.Parent.Engine.Volume = ((script.Parent.Velocity.Magnitude/1000)+.6)

“Ping issues” are you doing this from a server or client script?

1 Like

Server script inside of a vehicleseat, with the sound also inside of the vehicleseat.
When my ping gets high the updates of the sound pitch get really sporadic, it won’t go up softly, it’ll jump from like .5 to 1, to .5 to 1, over and over, and sounds awful.


use some sort of lerping like this

local lerp = function(a,b,c) return a+(a-b)*c end
pitch = lerp(pitch,targetpitch,rate)

Also that isnt RPM that is some fake alternative. If it was actual RPM you could easily set the pitch based on wheel speed multiplied by whatever ratios there are. Also that is the point, server sided things dont run as smoothly (due to replication) as if done locally (unless you do it correctly like I do with client interpolation).


I handle vehicles locally at the moment because it looks extremely smooth and due to how i replicate data clients are able to interpolate to those values based on pings.


It’s clearly not actual RPM based stuff, but it’s trying to get sounds similar to how higher and lower RPMs affect the engine noise of a car.

The only reason I’m doing it server sided is because I want it to heard by all clients. Like, I don’t want a client to only hear their car, I want them to hear other vehicles as well, as well as have that distance aspect involved.

I was more or less posting to see if there is a more efficient way to handle it other than a while loop.

1 Like

And dont write long code like that on purpose, you could easily shave off like many characters and make it more readable / modifyable by using variables for speed and the sound

For best user experience it would be better to handle this on the client instead of the server. If it’s handled on the client, the effect is guaranteed to be smooth and accurate instead of laggy (that is, if you script it to be those things, too, which is what the next things are for).

Next, you may want to tween the sound’s values so they doesn’t instantly jump from point to point (making it feel ‘laggy’). Your code looks like it should do this roughly due to it updating so quickly, however even if it was run on the client, the replication would still add some latency to the velocity value itself. To fix this, you could use TweenService (won’t work very well if the value is constantly updating quickly) or another form of tweening (lerping the value with a single RenderStepped connection or something) which is more complicated but works even if it is being spammed. Whenever the values change, they should now smoothly tween to their target value from their starting value so it feels smooth for the player.

Another thing is to get rid of all those if statements. The best way to do this that I can think of is just simply interpolating the volume and playback speed based on the velocity. It’s kind of what you are already doing, but expanding the concept even further so that you don’t need any if statements at all. You will lose some accuracy from doing this (unless you further play with your equation), but it’ll be smooth and consistent.
e.g. volume = 0.5 + math.clamp(speed / 1000, 0, 0.5)
This is pretty much the same as what you are doing for each value, aside from the clamp that in this case stops the volume going over 1.5 or below 0. Just keep it in mind that this is possible instead of doing different values for each one.

The final thing is kinda small, and it is just to use variables. You don’t need to write out all those big paths heaps of times, and doing so isn’t very efficient either. Instead:

local parent = script.Parent
local engine = parent.Engine
local speed = parent.Velocity.Magnitude

if speed < 10 then
    local smallSpeed = speed / 100
    engine.Playing = true -- see note #1 and #2
    engine.PlaybackSpeed = smallSpeed + 0.1 -- see note #3
    engine.Volume = smallSpeed + 0.5
elseif speed < 125 then
    local smallSpeed = speed / 250
    engine.PlaybackSpeed = smallSpeed + 0.2
    engine.Volume = smallSpeed + 0.6

As you can see, this is much more readable. Things can be understood at a glance, and you don’t need to make duplicate calculations and indexes.

Note #1: Playing it when the velocity is low wouldn’t work if the vehicle suddenly jumped in speed. You may have done this to avoid spamming the playing property, but the driver could also drive slowly and there wouldn’t be any point. I think you want to instead separate this from the sound calculations and force it to only run once (when the vehicle starts moving, at any speed).

Note #2: Maybe you should use the sound:Play() method instead? Not sure what your specific implementation is, though, so you can ignore that if I’m wrong.

Note #3: Using another variable here, since the calculation of smallSpeed is being done twice in the same scope. In addition to this, you don’t need to wrap the full mathematical expression in brackets if it’s a simple var = x statement.

Anyway, hopefully this is helpful! :slight_smile:


I actually want the jumps between the gears rather than tweening. It’s just under heavy ping it’ll free on like a high rpm, and then jump to low, and that’s bad sounding. But when the ping isn’t a problem, my script sounds fine. Yea, I could’ve done more cleaning to the script, but I’ve been using that for the past ~2 years now? So yea, I was a nub back then.

As far as running on the client. I’m just not sure how that becomes possible when it’s all connected back to the car. Especially when considering the fact that there are up to 10 vehicles in the server, and I want each client to hear the (up to) 9 other vehicles on track, with the sound coming from their respective positions, and also hearing their own sound coming from their own car.

1 Like

That’s where the tweening comes in so the change is smooth. Does it jump to low randomly or is it intended by the player (i.e. you mean in cases where the player is driving at a constant speed with erratic speed changes vs player is slowing down)?

You could have a single LocalScript responsible for all the cars. To add cars, listen for a child being added to a specific folder or something, or have a RemoteEvent fire from the server when a new car is created.

For performance reasons you could also only check cars that are within x distance from the player (where you won’t be able to hear them regardless). Since you’d be running lots of these at one time, this is also where you may want to start making your code more efficient.