#!/usr/bin/env python3 """Generates the game's sound effects, from scratch. Forgejo #146 asked for free SFX sources, and the ones worth using are listed in the ticket - Kenney's CC0 packs, OpenGameArt's CC0 sets, Freesound's CC0 filter. What ships here is synthesised instead, for one reason: a generated sound has no licence, no attribution to keep track of, and no binary in the repository whose provenance nobody can check a year from now. It also means the sounds are DATA rather than assets. If a shot is too sharp or an explosion too short, the fix is a number in this file and a re-run, not a hunt through a sound library. Run: python3 audio/make_sfx.py """ import math import random import struct import wave RATE = 44100 def _write(name, samples): peak = max(1e-9, max(abs(s) for s in samples)) frames = b"".join(struct.pack(" 0 else -1.0) out.append(tone * _env(i, n, 0.02, 2.2)) return out def explosion(seconds, low_hz, brightness, rumble_hz): """Noise through a one-pole low-pass, plus a falling sine underneath it. The sine is what gives it a size: without it a burst of noise is a hiss, however loud.""" n = int(RATE * seconds) out = [] last = 0.0 phase = 0.0 rng = random.Random(7) for i in range(n): t = i / n # The filter opens for an instant and closes as it decays, which is # what a real blast does - the crack first, the roar after. cutoff = brightness * (1.0 - t) ** 1.6 + 0.02 last += cutoff * (rng.uniform(-1.0, 1.0) - last) hz = low_hz * (rumble_hz / low_hz) ** t phase += 2.0 * math.pi * hz / RATE body = 0.55 * math.sin(phase) out.append((last * 1.4 + body) * _env(i, n, 0.004, 1.7)) return out if __name__ == "__main__": _write("sfx_shot", shot()) # A vessel: sharp, bright, over quickly. _write("sfx_ship_death", explosion(0.85, 220.0, 0.55, 45.0)) # A structure: slower, deeper, and it keeps falling down afterwards. _write("sfx_building_death", explosion(1.6, 130.0, 0.34, 26.0))