"""Synthesises the Player's two cue sounds into assets/sounds/ (standard library only). - stretching_cue.wav: a soft singing bowl, slow attack, long gentle decay. - workout_cue.wav: a short, muted wooden "tok". Both are kept quiet (peak about -12 dBFS) so they sit under the user's music. """ import math import random import struct import wave from pathlib import Path RATE = 22050 def write(path: Path, samples: list[float]) -> None: peak = max(abs(s) for s in samples) or 1.0 scale = 0.25 / peak # about -12 dBFS with wave.open(str(path), "wb") as out: out.setnchannels(1) out.setsampwidth(2) out.setframerate(RATE) out.writeframes(b"".join(struct.pack(" list[float]: # Inharmonic partials of a bowl, each fading at its own pace, with a slight beat. partials = [(392.0, 1.0, 1.4), (393.2, 0.6, 1.5), (1062.0, 0.35, 0.8), (2010.0, 0.12, 0.45)] samples = [] for n in range(int(seconds * RATE)): t = n / RATE attack = min(1.0, t / 0.04) value = sum(a * math.exp(-t / decay) * math.sin(2 * math.pi * f * t) for f, a, decay in partials) samples.append(attack * value) return samples def wood_tok(seconds: float = 0.12) -> list[float]: rng = random.Random(7) samples = [] for n in range(int(seconds * RATE)): t = n / RATE body = math.exp(-t / 0.018) * (math.sin(2 * math.pi * 820 * t) + 0.4 * math.sin(2 * math.pi * 1640 * t)) click = math.exp(-t / 0.002) * (rng.random() * 2 - 1) * 0.3 samples.append(body + click) return samples def main() -> None: out = Path(__file__).resolve().parents[2] / "assets" / "sounds" out.mkdir(parents=True, exist_ok=True) write(out / "stretching_cue.wav", singing_bowl()) write(out / "workout_cue.wav", wood_tok()) for f in sorted(out.glob("*.wav")): print(f"{f.name}: {f.stat().st_size / 1000:.0f} kB") if __name__ == "__main__": main()