/ game-development / Synthesizing A Game's Sound Effects With Nothing But Python
game-development 7 min read

Synthesizing A Game's Sound Effects With Nothing But Python

How I built the 12 sound effects in Orchard Deck from raw waveforms and envelopes in Python, plus the math for turning music into seamless loops.

Orchard Deck title screen hero art for the cozy card game

Every sound effect in Orchard Deck, all twelve of them, came out of a Python script that writes WAV files using only the standard library. No audio editor, no sample packs, no plugins. Just math that decides what number each sample of the waveform should be, written into the file format by hand. I want to walk through the actual synthesis, the waveforms and envelopes and loop math, because building sound from raw numbers turned out to be one of the more satisfying parts of the project.

A WAV file is, underneath, a small header followed by a long list of numbers. Each number is one sample, the amplitude of the sound at one instant, and the file plays back those samples at a fixed rate. At forty four thousand one hundred samples per second, the standard, one second of audio is forty four thousand one hundred numbers. The Python wave module writes the header for you and takes the samples as packed bytes from the struct module. So the entire job of a synthesizer is to produce the right list of numbers. Everything else is the math of which numbers.

Play Orchard Deck in your browser

Waveforms Are Just Functions Of Time

The simplest sound is a sine wave at a single frequency, the sine function sampled over time. For each sample index, you compute the time in seconds by dividing the index by the sample rate, and the amplitude is the sine of two times pi times the frequency times that time. A four hundred and forty hertz tone is the sine of two pi times four hundred and forty times t, sampled forty four thousand one hundred times a second. That produces a clean musical A, a pure tone with no character.

Pure tones are boring, so the more interesting effects use other waveforms. A square wave is the sign of that sine, so it flips between full positive and full negative, which gives a buzzy retro texture. A sawtooth ramps linearly up and snaps back down, computed from the fractional part of the frequency times time, which gives a brighter, harsher tone. Noise, which I use for things like the soft shuffle of a card, is just random samples, each an independent random number in the amplitude range.

The payouts and pickups are not single tones, they are little chords and sweeps. A chord is the sum of several sine waves at different frequencies, added sample by sample and scaled down so the sum does not clip past the maximum amplitude. A sweep is a tone whose frequency changes over its duration, so instead of a constant frequency you feed in one that rises or falls across the samples, which gives the rising chime of a good harvest or the descending tone of a card going barren. All of this is arithmetic on a list of numbers.

Envelopes Are What Make It Sound Like Something

A raw waveform played at full amplitude for its whole duration sounds wrong, because real sounds do not start and stop instantly. They swell in and fade out. The shape of that swell and fade over the life of a sound is the envelope, and applying one is what takes a synthesized tone from a test beep to something that feels like a game sound effect.

An envelope is a multiplier on amplitude that changes across the duration of the sound. The classic shape has four phases. The attack is the rise from silence to peak at the very start. The decay is the drop from that peak to a sustained level. The sustain holds at that level through the body of the sound. The release is the fade back down to silence at the end. For each sample, you work out which phase it falls in, compute the multiplier for that phase, and multiply the raw waveform sample by it. A short sharp click is a fast attack and fast release with almost no sustain. A soft pad is a slow attack and a long release.

The twelve effects in Orchard Deck mostly differ in their envelopes more than their waveforms. A button tap is a tiny envelope, a few milliseconds of attack and release on a short tone, so it reads as a crisp click. A coin collect is a brighter waveform with a quick attack and a slightly longer release so it rings a little. The barren sound is a descending sweep with a slow release so it sags. Same waveform generators, different envelopes wrapped around them, and that is enough to give twelve distinct sounds. The envelope is where the personality lives, and it is just a multiplier that changes shape over time.

Making Music Loop Seamlessly

The background music needed a different kind of math, because the problem there is making a finite clip play forever with no audible seam at the join. A loop that clicks or thumps every time it restarts is worse than no loop at all, and the click comes from a mismatch at the boundary.

There are two boundaries to worry about. The first is amplitude. If the last sample of the clip is at full positive amplitude and the first sample is at full negative, the playback jumps instantly between them when it wraps, and that jump is a click, a discontinuity the ear hears as a pop. The fix is a crossfade at the seam. You take the tail and the head of the clip and blend them, fading the tail down while fading the head up across the overlap region, so the amplitude curve flows smoothly from the end back into the beginning. Done right, the join becomes inaudible because there is no discontinuity left for the ear to catch.

The second boundary is the musical phrase. Even with the amplitude smoothed, a loop sounds wrong if it restarts in the middle of a beat, because the rhythm stumbles at the wrap. So the clip has to be trimmed so its length is a whole number of beats, which means the end lands exactly where the beginning would naturally continue. I find the boundary from the tempo, computing how many samples one bar occupies and trimming to a clean multiple of that. With the length set to a whole number of bars and a crossfade smoothing the join, the music loops with no seam you can hear, all of it sample arithmetic, the same toolkit as the sound effects pointed at a different problem.

The thing I took away from doing the audio this way is that sound is not a mysterious medium you need special tools to touch. It is a list of numbers played fast, and once you accept that, a sine function and an envelope and a crossfade are enough to make a game's whole audio identity from scratch. Twelve effects and looping music, all from one script, with separate buses for Music and SFX so the player can mix them.

All of it is playing in the browser build right now, twelve effects and a seamless music loop, every one of them a list of numbers a short Python script worked out and wrote into a WAV file. Play Orchard Deck in your browser.

Built by Kevin

Like this? You'll like what I'm building too.

Two ways to support and get more of this work.

Desktop App

HEARTH

A privacy-first Life OS for your desktop. Journal, tasks, and notes that stay on your machine. Coming soon, direct download from this site.

Read more
Digital Products

MY TOOLKITS

Receipts-first toolkits for shipping after hours, building Claude agents, publishing on Amazon, and more. The exact methods I used, not theory.

Browse on Whop

Need This Built?

Kevin builds products solo, from first version to live. If you want something like this made, work with him.