← poker-tutor
#!/usr/bin/env python3
"""Generates the table sounds.
They are synthesised rather than recorded so the repo stays self-contained and nothing has to be
licensed. Each one is deliberately short and quiet — a sound you hear a hundred times an hour has
to sit under the game, not on top of it.
python3 Scripts/make-sounds.py
Writes into Sources/PokerUI/Resources/. Only re-run this if you want to change how they sound.
"""
import math
import os
import random
import struct
import wave
RATE = 44100
HERE = os.path.dirname(os.path.abspath(__file__))
OUT = os.path.join(HERE, "..", "Sources", "PokerUI", "Resources")
def write(name, samples, peak=0.5):
"""Normalises to `peak` and writes a mono 16-bit file."""
high = max(1e-9, max(abs(s) for s in samples))
scale = peak / high
frames = b"".join(struct.pack("<h", int(max(-1.0, min(1.0, s * scale)) * 32767)) for s in samples)
path = os.path.join(OUT, name)
with wave.open(path, "wb") as f:
f.setnchannels(1)
f.setsampwidth(2)
f.setframerate(RATE)
f.writeframes(frames)
print(f"{name}: {len(samples) / RATE:.3f}s")
def clink(start, length=0.09, pitch=2600.0, level=1.0):
"""One chip striking another: a couple of inharmonic partials over a noise transient."""
out = []
count = int(length * RATE)
for i in range(count):
t = i / RATE
env = math.exp(-t * 46)
transient = random.uniform(-1, 1) * math.exp(-t * 900) * 0.9
body = (
math.sin(2 * math.pi * pitch * t) * 0.6
+ math.sin(2 * math.pi * pitch * 2.71 * t) * 0.3
+ math.sin(2 * math.pi * pitch * 4.13 * t) * 0.15
)
out.append((body * env + transient) * level)
return start, out
def mix(parts, length):
"""Lays each part down at its own offset."""
buf = [0.0] * int(length * RATE)
for start, samples in parts:
offset = int(start * RATE)
for i, s in enumerate(samples):
if offset + i < len(buf):
buf[offset + i] += s
return buf
def chips():
"""Chips going in: three clinks landing on top of each other, slightly detuned."""
random.seed(11)
parts = [
clink(0.000, pitch=2450, level=1.00),
clink(0.026, pitch=3100, level=0.75),
clink(0.052, pitch=2150, level=0.55),
]
return mix(parts, 0.22)
def fold():
"""Two cards sliding away: band-limited noise with a fast decay, no pitch to it."""
random.seed(23)
length = 0.20
out = []
previous = 0.0
for i in range(int(length * RATE)):
t = i / RATE
# Two pushes, the second a little softer.
env = math.exp(-((t - 0.010) ** 2) / 0.00035) + 0.7 * math.exp(-((t - 0.075) ** 2) / 0.00045)
noise = random.uniform(-1, 1)
previous = previous * 0.72 + noise * 0.28 # low-passed, so it reads as paper not static
out.append(previous * env)
return out
def knock():
"""A check: two knuckles on the felt. Low, soft, over quickly."""
random.seed(31)
length = 0.16
out = []
for i in range(int(length * RATE)):
t = i / RATE
hit = math.exp(-t * 60) + 0.55 * math.exp(-max(0.0, t - 0.070) * 60) * (1 if t > 0.070 else 0)
tone = math.sin(2 * math.pi * 165 * t) * 0.8 + math.sin(2 * math.pi * 96 * t) * 0.5
thud = random.uniform(-1, 1) * math.exp(-t * 700) * 0.35
out.append(tone * hit * 0.9 + thud)
return out
def sweep():
"""The pot being pushed to the winner: a longer wash of chips."""
random.seed(47)
parts = [clink(0.02 * i + random.uniform(0, 0.012),
pitch=random.uniform(1900, 3300),
level=random.uniform(0.35, 0.85))
for i in range(9)]
return mix(parts, 0.46)
if __name__ == "__main__":
os.makedirs(OUT, exist_ok=True)
write("chips.wav", chips(), peak=0.42)
write("fold.wav", fold(), peak=0.30)
write("knock.wav", knock(), peak=0.34)
write("pot.wav", sweep(), peak=0.38)