Worn canvas shoes, a pile of gravel, and a small recorder
|

Procedural Audio Generation with AI

Write a tiny Python script with no outside libraries that turns a seed number into a short noise-burst footstep, so every seed gives you a slightly different step.

This is the write-it-yourself footstep pass, not a tour of procedural audio tools. You will not install anything or compare products. You will write about fifty lines of plain Python, run it, and listen to the result.

Be clear about what you get. This makes a placeholder, not a finished sample. It is a soft click or scuff you can drop into a prototype so your character is not silent. When your game is further along, you will likely replace it with recorded or designed sounds.

Why seed a footstep

Play the same footstep sound over and over and players start to notice. A common fix is to keep a few variations and pick between them.

A seed gives you those variations for free. A seed is just a number you feed into a random number generator. The same seed always produces the same sequence of “random” values. So seed 3 always makes the same step, and seed 4 always makes a slightly different one.

That means you can generate a small set of steps, keep the seeds you like, and recreate them exactly later.

What you need

  • Python 3 installed.
  • A text editor.
  • Any audio player that opens .wav files.

The script uses only modules that ship with Python: math, random, struct, and wave. Nothing to install.

The idea in plain words

A footstep, at its simplest, is a short burst of noise that starts quickly and fades out. You will build it in four small parts:

  1. Noise. A list of random values between -1 and 1. Played back, that sounds like a hiss.
  2. Pitch. You read through the noise list at a speed set by the seed. Reading faster sounds higher. Reading slower sounds lower.
  3. Smoothing. A simple filter takes off some of the harsh top end, so the hiss becomes more of a soft scuff. The seed sets how much it smooths.
  4. Envelope. A volume shape that rises almost instantly and then fades. This turns endless hiss into one short hit.

Step 1: Write the script

Create a file called footstep.py and type this in:

import math
import random
import struct
import wave

SAMPLE_RATE = 44100

def make_footstep(seed, length_s=0.12):
    rng = random.Random(seed)

    # The seed picks a "pitch" factor and a brightness.
    pitch = rng.uniform(0.8, 1.25)
    brightness = rng.uniform(0.2, 0.5)

    # A short stretch of raw noise. A higher pitch reads it faster.
    noise = [rng.uniform(-1.0, 1.0) for _ in range(2048)]

    n = int(SAMPLE_RATE * length_s)
    out = []
    smooth = 0.0
    pos = 0.0
    for i in range(n):
        t = i / n                       # 0 at the start, 1 at the end
        raw = noise[int(pos) % len(noise)]
        pos += pitch

        # One-pole low-pass: blend toward the new sample.
        smooth += brightness * (raw - smooth)

        # Fast attack, quick fade.
        env = min(1.0, t * 40.0) * math.exp(-t * 8.0)
        out.append(smooth * env)

    peak = max(abs(s) for s in out) or 1.0
    return [s / peak * 0.8 for s in out]

def write_wav(path, samples):
    with wave.open(path, "wb") as f:
        f.setnchannels(1)
        f.setsampwidth(2)
        f.setframerate(SAMPLE_RATE)
        frames = b"".join(
            struct.pack("<h", int(s * 32767)) for s in samples
        )
        f.writeframes(frames)

if __name__ == "__main__":
    for seed in range(4):
        write_wav(f"step_{seed}.wav", make_footstep(seed))
        print("wrote", f"step_{seed}.wav")

Step 2: Run it

Open a terminal in the same folder and run:

python3 footstep.py

On some systems the command is python instead of python3. You should see four lines, one per file, and four new files named step_0.wav through step_3.wav.

Open them in your audio player. Each should be a short, soft burst. They should sound related but not identical. Some will feel a little higher, some a little duller.

Run the script again. You will get the exact same four files. That is the seed doing its job.

Step 3: Understand each part

Here is what the important lines do.

rng = random.Random(seed) creates a random number generator that belongs to this one footstep. Using its own generator, instead of the shared one, keeps each step independent. Generating step 2 never changes what step 3 sounds like.

pitch and brightness are the two values the seed controls. The ranges are starting points chosen for this tutorial, not measured values. Change them and listen.

noise is a short list of random samples. The loop reads from it with pos, which moves forward by pitch each sample. A pitch above 1 skips ahead and sounds higher. Below 1 it reads values more than once and sounds lower. The % len(noise) wraps around if the step runs past the end of the list, so a short table is enough.

smooth += brightness * (raw - smooth) is a one-pole low-pass filter. Each new output moves part of the way toward the incoming sample. A small brightness moves slowly, which rounds off fast changes and sounds duller. A larger one keeps more of the hiss.

env is the envelope. min(1.0, t * 40.0) ramps the volume up very fast, so the step does not start with a hard click. math.exp(-t * 8.0) fades it down across the rest of the step. Raise the 8 for a shorter, tighter step. Lower it for a longer scrape.

The peak lines normalize the result so the loudest point lands at the same level for every seed. Without this, some seeds would come out much louder than others.

write_wav packs each sample into a 16-bit number and saves a mono file. That format opens in nearly every audio player and game engine.

Step 4: Make it your own

Change one thing at a time and listen after each change. Write down what you tried.

  • Length. Pass a different length_s to make_footstep. Shorter feels lighter. Longer feels heavier.
  • Pitch range. Narrow uniform(0.8, 1.25) if your steps sound too different from each other. Widen it if they sound too similar.
  • Surface feel. Lower brightness reads softer. Higher brightness reads grittier. Try a “soft” set and a “hard” set with different ranges.
  • More seeds. Change range(4) to range(20), listen to all of them, and keep the seeds you like.
  • Two layers. Generate two steps with different seeds and add them sample by sample before normalizing. You will get a busier sound.

Keep your favorite seeds in a list. Since the output is deterministic, the seed is the sound. You can regenerate any step later from its number alone.

Step 5: Use it in your prototype

Copy a few of the .wav files into your game project. Most engines can import a mono 16-bit WAV directly.

Play a different step each time your character’s foot lands. A simple approach is to cycle through your files in order, or pick one at random. If your engine lets you vary pitch at play time, add a small random amount on top for even more variety.

Do not ship this as your final audio without listening on the speakers and headphones your players will use. It is a placeholder that keeps your game from feeling empty while you build.

Where to go next

Once this works, try the same pattern for other small sounds. A quicker envelope with higher brightness gives you a tick for menus. A longer, darker one gives you a soft thud for landing.

Each new sound is still just noise, a pitch, a filter, and an envelope, all driven by a seed.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *