"""Procedural sound kit: every WAV the game plays, synthesized from maths. Run: /Applications/Blender.app/Contents/MacOS/Blender -b -P tools/gen_sounds.py (Blender only for its bundled numpy -- no bpy used) Licence-free by construction: sines, noise and envelopes, no recordings. Deterministic (seeded), so a rebuild is byte-for-byte reproducible. Loop discipline: anything the game loops (engine, skid, boost, siren) is built from integer-Hz components over an exact 1 s so the seam is mathematically silent; noise beds get a crossfaded seam instead. One-shots just decay to zero. """ import math import os import wave import numpy as np SR = 22050 OUT = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "assets", "sounds") rng = np.random.default_rng(1982) def t(dur): return np.arange(int(SR * dur)) / SR def env_exp(dur, tau): return np.exp(-t(dur) / tau) def noise(dur): return rng.standard_normal(int(SR * dur)) def band(x, lo, hi, peaks=()): """FFT bandpass with optional resonant peaks [(hz, width, gain)].""" spec = np.fft.rfft(x) f = np.fft.rfftfreq(len(x), 1 / SR) mask = ((f >= lo) & (f <= hi)).astype(float) edge = (hi - lo) * 0.15 + 1.0 mask = np.convolve(mask, np.hanning(9) / np.hanning(9).sum(), mode="same") for hz, width, gain in peaks: mask += gain * np.exp(-((f - hz) / width) ** 2) return np.fft.irfft(spec * mask, len(x)) def seam(x, fade=0.06): """Crossfade the tail into the head so a noise bed loops cleanly.""" n = int(SR * fade) w = np.linspace(0, 1, n) x[:n] = x[:n] * w + x[-n:] * (1 - w) return x[:-n] def norm(x, peak=0.85): return x / (np.max(np.abs(x)) + 1e-9) * peak def write(name, x): x16 = (np.clip(x, -1, 1) * 32767).astype("