Category Archives: uncategorised

Arcade game screen showing SCORE: 125,480, LEVEL: 3, LIFE, BOMBS, and SHIELD: 88%.

Creating Doppler Sound Effects in Vertical Scrolling Games

Lessons from building the obstacle pass-by effect in a vertical scrolling game (Defold), and how to apply the same technique in any engine.


The problem

Your player races forward. Cars, trucks, hazards scroll toward them from the top of the screen, scream past on either side, and vanish off the bottom. Right now they’re silent — or worse, every one of them plays the same loop at full volume, producing a wall of noise.

You want the feeling of speed: an approaching engine that rises in pitch, flips into a whoosh at the moment of the pass, and drops away behind you. A real Doppler shift.

The good news: you don’t need physics simulation, DSP, or a middleware audio programmer. You need about four small functions and one design decision.

The key insight: pitch follows velocity, volume follows distance

A common mistake is to derive everything from distance. Don’t. If pitch tracks distance, cars sound like they’re shifting gears as they approach instead of holding a steady tone.

The trick used by Mario Kart (and documented by Audiokinetic’s Wwise team) is:

  • Pitch = relative velocity. While the object closes on the listener, hold the pitch high. After it passes, hold it low. Slide between the two only in a narrow band around the pass point.
  • Volume = live distance. Full volume inside a small radius, smooth falloff out to a “hear bubble” edge.
  • Pan = lateral offset. Left/right from horizontal separation.

Because your objects scroll at a known speed, “relative velocity” collapses to a single number: the sign and magnitude of object_y - listener_y. That’s the whole effect.

-- Pitch: held high while closing, smooth flip at the pass, held low after.
function pitch_ratio(dy, cfg)
    local hi = cfg.pitch_approach   -- e.g. 1.03
    local lo = cfg.pitch_recede     -- e.g. 0.95
    local cross = cfg.cross_span    -- whoosh width in px, e.g. 72
    if dy >= cross then return hi end
    if dy <= -cross then return lo end
    local t = (cross - dy) / (2 * cross)
    t = t * t * (3 - 2 * t)         -- smoothstep: no gear-step click
    return hi + (lo - hi) * t
end

Note how narrow the ratio range is: 1.03 → 0.95. Wide ranges make engine loops sound like gear changes. Subtle is convincing.

Volume: arcade proximity, not inverse-square

Real-world falloff sounds wrong in games. Use the classic arcade curve:

  1. Min-distance sphere — full volume inside a radius (e.g. 55 px).
  2. Quadratic rolloff beyond it: (1 - t)² where t is normalized distance to the bubble edge.
  3. Extra lateral weighting so a car one lane over stays quiet even when level with you:
local lat = 1 / (1 + (dx / lane_width)^2)
gain = radial * (0.28 + 0.72 * lat)

That last term is what makes two lanes away “a whisper” instead of “half volume” — critical when lanes are only ~170 px apart.

Pan and the camera-shake trap

Pan is trivially clamp(dx / pan_width, -1, 1).

But here’s a bug that cost us a debugging round: if you sample positions in world space, any screen shake modulates gain and pan every frame — the audio wobbles whenever the camera jolts. Sample the object’s position relative to its layout parent (local space) for the lateral axis, and world/screen space only for the vertical axis that actually scrolls.

Voice management: nearest-N wins

You can’t play a loop per spawned object. Keep a mixer tick (once per frame) that:

  1. Collects live objects inside the hear bubble.
  2. Sorts them: still-on-screen first, then nearest.
  3. Keeps only MAX_VOICES (3 works well).
  4. Mutes anything that dropped out; starts anything new.

Two lifecycle details that matter:

  • Let finished voices linger. An object culled off-screen should keep playing until its fade completes, otherwise the sound chops mid-note. Extend the recede window by a fraction (KEEP_FRAC ≈ 1.15) of travel time past the cull line.
  • Never set gain to exactly zero mid-play. Most engines (Defold included) drop zero-gain voices entirely, and later gain changes are then silent. Clamp to a tiny floor (~0.002) and use explicit stop for real mutes.

One voice per instance, not shared slots

Tempting shortcut: a pool of N loop players, reassigned each frame. Don’t. When the pool reassigns, one car steals another’s voice mid-pass and the mix stutters. Give each spawner its own component/channel and address it directly — memory is cheap, glitches are not.

Make late levels stay bright

If your game speeds up over time, a fixed pitch curve makes fast passes feel identical to slow ones. Scale the held pitch ends with scroll pace:

local pace = clamp(scroll_px_per_sec / reference_speed, 1, max_pace)
hi = hi + (pace - 1) * lift_per_pace   -- +0.08 per extra 1x start speed
lo = lo - (pace - 1) * drop_per_pace   -- +0.025 → stronger whoosh contrast

Cap both ends (e.g. 1.28 / 0.88). Now deep runs genuinely sound faster.

Architecture checklist

Whatever your engine, structure it like this — it keeps the math testable and the system reskinnable:

LayerResponsibilityTestable?
Pure math modulepitch/gain/pan/phase curves, voice selectionYes — plain unit tests
Mixer/tickper-frame orchestration, lifecycle, loggingMostly
Transportactual play/stop/pause calls, engine quirksNo
Snapshotmaps game state to {id, x, y, lane} recordsYes

Keep all tunables in one config table with guards in tests (e.g. “recede window < hear window”, “whoosh width ≤ 100 px”). Future-you will tune this constantly; a config table plus validation beats hunting magic numbers.

Tuning cheat sheet

Start here, adjust by ear:

hear_bubble      = 520 px     approach window
min_dist         = 55 px      full-volume sphere
max_dist         = 520 px     silent at edge
pitch_approach   = 1.03       held while closing
pitch_recede     = 0.95       held after passing
whoosh_width     = 72 px      (or 0.12 s of travel, whichever is larger)
pan_width        = 2 x lane spacing
max_voices       = 3
lane_detune      = +/-0.4 %   separates side-by-side sources

Summary

The “Doppler” effect is three decoupled curves — pitch from velocity sign, volume from 2D distance, pan from lateral offset — driven once per frame by a nearest-N voice mixer. No physics, no DSP. The hard parts aren’t the math; they’re the engineering around it: local-vs-world coordinates, zero-gain voice drops, lingering fades, and per-instance voices. Get those right and a stock engine loop becomes a convincing rush of traffic.

Technique references: Mario Kart-style relative-velocity pitch (Lilley/Scruffy write-up), Audiokinetic Wwise Doppler blog, arcade min/max distance falloff (GameMaker / shmup tradition).

How to fix a noisy GPU fan

If you have an old GPU and you have noticed it is creating more noise now than when it was first installed, it may be because the bearing is no longer sufficiently lubricated.

A new fan off EBay, or new to you, might not be a sound investment. This is true assuming you can even find a replacement. Luckily most GPU fans can be serviced.

To Lubricate the Fan Bearing:

Remove the GPU from your computer.

Spin the fan manually. If it is not mostly silent, then there is a problem with the bearing. More than likely, there is just insufficient lubricant.

Carefully peel back the sticker on the top of the fan to expose the bearing. If access to the bearing is not provided on the top of the fan, then remove the fan from the GPU housing. Peel back the sticker on the back of the fan. If you see a yellowish mark on the inside of the sticker where the bearing is, this confirms the lubricant has leaked somewhat. If access to the bearing is not provided at the bottom of the fan either then very carefully try to remove the blade.

Once the bearing is exposed apply a small drop of light machine oil or sewing machine oil to the bearing. Singer oil is a good choice.

Rotate the fan manually to work the oil into the bearing.

Replace the sticker or use a small piece of tape to cover the bearing.

Before reinstalling the GPU, you might consider also applying fresh thermal paste to the GPU processor. This will help keep temperatures down, giving the fan less work to do.

How to fix Azure Error Message: “The current transaction cannot be committed and cannot support operations that write to the log file. Roll back the transaction. Cannot find the object because it does not exist or you do not have permissions.”

There can be a number of reasons for this error but below steps through a potential scenario and fix for the error as referenced in the post title.

(TL;DR it’s probably down to permissions. Code example below.)

If you are running a Data Factory pipeline that calls a Store Procedure the process may fail throwing the error “The current transaction cannot be committed and cannot support operations that write to the log file. Roll back the transaction. Cannot find the object because it does not exist or you do not have permissions.”

The first part of that error is boilerplate stuff i.e. the query cannot do what it has to do so it is rolling back.

The second part of the error is what needs to be addressed. Either the object does not exist or the credentials the process is using does not have the required permission to do what the query has specified.

If you can confirm the object exits i.e. the table, then it can only be a matter of credential permissions.

The question becomes what are you trying to do with the object?

SELECT, DELETE, TRUNCATE all require that the credentials i.e. the user, has the permissions to carry out those statements.

If your query needs to TRUNCATE a table and the credentials used by Data Factory do not have permission to perform a TRUNCATE on a table then Azure will throw the aforementioned error.

In order to resolve that problem specifically, i.e. for a user to TRUNCATE a table, the user needs ALTER permissions. Below is an example of how to apply ALTER permissions on the object TestTable in the schema dbo to the user TestUser. Just update the schema, object and user to fit your needs.

GRANT ALTER ON OBJECT::dbo.TestTable TO TestUser;
GO 

The above conforms to applying the principle of least privilege as the ability to alter objects only applies to that object specifically. In closing if the object exists look at what the query does to object and ensure the user has the required permissions to do it.

An icon of a jetpack

How to fix Jetpack for WordPress.com not pushing posts to Facebook or Twitter

If you have set up the connections for Facebook, Twitter, etc. through Jetpack but your posts are not being pushed to those platforms try the following.

Make sure you have given permission to editors and authors of your site to use the established Jetpack connections. To do that go back to the Jetpack connection settings.

In the “Publicize posts” section click the drop down arrow to the far right.

Click the check box allowing the social media platform to be used by more than just the administrators. (Obviously this will allow your authors to publish to the specified social media platform so only do this if you trust your authors having this access.)

Once this is done your next published post should also be pushed across your connected social media platforms.

NOTE:

If the post was already published “Updating” the post will not share the post across the social media platforms. You will need to save the post as a “Draft” and “Publish” it again. This should then push the post to the social media platforms.