SFX Loom
Sound effectsBlogAPIPricingLicense
Log inStart free
SFX Loom

Sound effects for games, generated from a text prompt.

Product

ExamplesSound effectsBlogAPIPricingSound licenseCreate account

Sound effects

WeaponsMagic & spellsUI & menusFootsteps & foleyImpacts & explosionsSci-fi

Legal

Terms of servicePrivacy policyRefund policy

Contact

support@sfxloom.com

© 2026 SFX Loom

Payments are processed by Lemon Squeezy, our Merchant of Record.

Blog

Batch-Generate Every Sound Effect in Your Game with One Script

November 4, 2026 · 8 min read

Most games need far more sound effects than anyone plans for: every button, footstep surface, pickup, hit and door. Generating them one by one in a browser gets old fast. A better workflow is to keep the list of sounds in your repo and let a script fill in whatever is missing. This guide builds that script in about 80 lines of Python with the SFX Loom API.

What the script does:

  • Reads your sound list from a CSV file.
  • Skips sounds that already exist, so you can run it again any time.
  • Works out the credit cost, checks your balance and asks before spending anything.
  • Generates every missing sound and saves each take as a WAV file named after its row.

1. Write the sound list

Create sounds.csv next to your project. One row per sound: a file-friendly name, the prompt, the duration in seconds and how many takes you want.

name,prompt,duration,variations
ui_click,"Soft UI button click, short and crisp, modern mobile game menu, no reverb, no music",0.5,2
ui_confirm,"Positive UI confirm chime, two quick rising notes, clean, no music",1,2
coin_pickup,"Bright coin pickup, short metallic ding, retro arcade game, no music",0.5,3
sword_swing,"Fast sword swing whoosh through the air, close-up, no music, no background noise",1,4
footstep_gravel,"Single footstep on gravel, close-up, no background noise",0.5,4
door_wood_open,"Old wooden door creaking open slowly, close-up, indoor, no music",2.5,2
explosion_big,"Big explosion with deep boom and debris falling, cinematic game, no music",4,2

Durations must be on the model’s step grid. The default model accepts 0.5 to 22 seconds in 0.5-second steps. Short effects such as clicks, pickups and footsteps sound best at 0.5–1 second. If you need help with the prompts, the prompt guide and the indie game sound checklist are good starting points.

2. Set up

Create an API key in the app under API, then install requests and export the key:

pip install requests
export SFXLOOM_API_KEY="sfxl_..."

3. The script

Save this as generate_sounds.py. Set OUT_DIR to the folder your engine imports audio from, for example Assets/Audio/SFX in Unity or audio/sfx in a Godot project.

#!/usr/bin/env python3
"""Generate every sound in sounds.csv that doesn't exist yet, using the SFX Loom API."""
import csv
import os
import sys
import time
from pathlib import Path

import requests

API = "https://sfxloom.com/api/v1"
MODEL = "elevenlabs/sound-effects-v2"
SOUND_LIST = Path("sounds.csv")
OUT_DIR = Path("Assets/Audio/SFX")

session = requests.Session()
session.headers["Authorization"] = f"Bearer {os.environ['SFXLOOM_API_KEY']}"


def call(method, path, **kwargs):
    """One API request. Waits and retries on 429 (rate-limited requests are never charged)."""
    while True:
        res = session.request(method, API + path, timeout=120, **kwargs)
        if res.status_code == 429:
            time.sleep(int(res.headers.get("Retry-After", "10")))
            continue
        if not res.ok:
            error = res.json()["error"]
            raise RuntimeError(f"{res.status_code} {error['code']}: {error['message']}")
        return res


def credits_per_take(model, duration):
    for tier in model["pricing"]["credits_per_variation_by_duration"]:
        if tier["min_duration"] <= duration <= tier["max_duration"]:
            return tier["credits"]
    raise ValueError(f"{duration}s is outside the model's duration range")


with SOUND_LIST.open(newline="", encoding="utf-8") as f:
    rows = list(csv.DictReader(f))

# A row is done once its first take exists. Delete the files to regenerate a sound.
todo = [r for r in rows if not (OUT_DIR / f"{r['name']}_1.wav").exists()]
if not todo:
    sys.exit("Every sound already exists.")

models = {m["id"]: m for m in call("GET", "/models").json()["data"]}
cost = sum(credits_per_take(models[MODEL], float(r["duration"])) * int(r["variations"]) for r in todo)
balance = call("GET", "/balance").json()["total"]
print(f"{len(todo)} sounds to generate, {cost} credits at most. Balance: {balance}.")
if cost > balance:
    sys.exit("Not enough credits.")
if input("Generate? [y/N] ").strip().lower() != "y":
    sys.exit()

OUT_DIR.mkdir(parents=True, exist_ok=True)
for row in todo:
    print(f"{row['name']}: generating {row['variations']} take(s)...")
    gen = call("POST", "/generations", json={
        "model": MODEL,
        "prompt": row["prompt"],
        "params": {"duration": float(row["duration"])},
        "variations": int(row["variations"]),
    }).json()

    while gen["status"] in ("queued", "processing"):
        time.sleep(3)
        gen = call("GET", f"/generations/{gen['id']}").json()

    takes = [v for v in gen["variations"] if v["status"] == "completed"]
    for i, take in enumerate(takes, start=1):
        audio = call("GET", f"/sounds/{take['sound_id']}/download", params={"format": "wav"})
        path = OUT_DIR / f"{row['name']}_{i}.wav"
        path.write_bytes(audio.content)
        print(f"  saved {path}")
    if not takes:
        print("  failed, credits refunded")

print("Done.")

4. Run it

$ python generate_sounds.py
7 sounds to generate, 19 credits at most. Balance: 412.
Generate? [y/N] y
ui_click: generating 2 take(s)...
  saved Assets/Audio/SFX/ui_click_1.wav
  saved Assets/Audio/SFX/ui_click_2.wav
...

The cost line is an upper bound: credits are reserved per take, and any take that fails is refunded automatically. Generations run one after another, which keeps you well under the per-minute limits; if you do hit one, the script waits for the Retry-After time and carries on.

Why this workflow works

  • The sound list is documentation. Anyone on the team can see which sounds exist and what they were meant to be, and the prompts live in version control next to the code.
  • Re-runs are cheap. Add a row and run the script again: only the new sound is generated. Don’t like a take? Delete its files and run it again.
  • Takes are ready for randomization. Files named footstep_gravel_1.wav to _4.wav drop straight into a random container. See the Unity and Godot import guides for how to play a random take each time.
  • Nothing is lost. Everything you generate also lands in your library, where you can trim a take on the waveform and download it again later.

Ideas to extend it

  • Add a model column and pick the model per row. GET /models already returns both.
  • Use format=ogg for long ambience and loops, and set the loop parameter to true so the end flows into the start.
  • Run it in CI to regenerate placeholder audio when the sound list changes.

New to the API? Start with the step-by-step API tutorial or the full documentation.

Generate game sound effects from a prompt

5 free credits · no card required

Start free

More guides

  • How to Generate Game Sound Effects with an API (Python and JavaScript)
  • Generate Sound Effects Inside the Unity Editor with AI
  • How to Write Prompts for AI Sound Effects (With Examples)