#!/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)
