whiskers.git / tools / make-sounds.py
1#!/usr/bin/env python3
2"""Write Whiskers' sound effects as small WAV files. They are synthesised here, not
3taken from anywhere, so they carry no licence. Run again after changing the recipes.
4
5    tools/make-sounds.py android/app/src/main/res/raw
6"""
7import math
8import struct
9import sys
10import wave
11from pathlib import Path
12
13RATE = 22050
14
15
16def write(path, samples):
17    with wave.open(str(path), "wb") as w:
18        w.setnchannels(1)
19        w.setsampwidth(2)
20        w.setframerate(RATE)
21        w.writeframes(b"".join(struct.pack("<h", int(max(-1, min(1, s)) * 32000)) for s in samples))
22
23
24def tone(freq_of_t, seconds, volume=0.5):
25    n = int(RATE * seconds)
26    out, phase = [], 0.0
27    for i in range(n):
28        t = i / RATE
29        phase += 2 * math.pi * freq_of_t(t) / RATE
30        # Short fade in and out so it does not click.
31        env = min(1.0, i / (0.01 * RATE), (n - i) / (0.03 * RATE))
32        out.append(math.sin(phase) * volume * env)
33    return out
34
35
36def silence(seconds):
37    return [0.0] * int(RATE * seconds)
38
39
40def giggle():
41    """Three quick rising chirps: a tickle."""
42    out = []
43    for base in (520, 600, 700):
44        out += tone(lambda t, b=base: b + 900 * t, 0.11)
45        out += silence(0.04)
46    return out
47
48
49def dizzy():
50    """A slow warble sliding down, like a cartoon head-spin."""
51    return tone(lambda t: 600 - 250 * t + 80 * math.sin(2 * math.pi * 7 * t), 0.9, 0.45)
52
53
54def main(out):
55    out = Path(out)
56    out.mkdir(parents=True, exist_ok=True)
57    write(out / "giggle.wav", giggle())
58    write(out / "dizzy.wav", dizzy())
59
60
61if __name__ == "__main__":
62    main(sys.argv[1])