#!/usr/bin/env python3 """Package imagegen originals into palette SD/HD tiles and synthesize WAV cues. Only deterministic resampling, cropping and palette conversion happen here. Image creation uses the built-in imagegen tool; original sources are retained. """ import argparse import hashlib import json import math import random import struct import wave from pathlib import Path from PIL import Image, ImageOps ROOT = Path(__file__).resolve().parent def palette_image(path): raw = path.read_bytes() if len(raw) != 768: raise ValueError("Expected the original 768-byte VGA palette") colors = [(min(value, 63) << 2) | (min(value, 63) >> 4) for value in raw] palette = Image.new("P", (1, 1)) palette.putpalette(colors) return palette, colors def palette_sd(image, size, palette): image = image.convert("RGBA").resize(size, Image.Resampling.LANCZOS) alpha = image.getchannel("A").point(lambda a: 255 if a >= 128 else 0) rgb = image.convert("RGB").quantize(palette=palette, dither=Image.Dither.NONE).convert("RGBA") rgb.putalpha(alpha) return rgb def wav(name, duration, notes, alarm=False): rate = 22050 rng = random.Random(7066 + sum(map(ord, name))) samples = bytearray() energy = 0 peak = 0 for frame in range(int(duration * rate)): t = frame / rate step = min(len(notes) - 1, int(t / duration * len(notes))) f = notes[step] local = (t / duration * len(notes)) % 1 envelope = min(1, local * 24) * (1 - local) ** 0.6 signal = math.sin(2 * math.pi * f * t) signal += 0.23 * math.sin(2 * math.pi * f * 2 * t) if alarm: signal += 0.17 * math.sin(2 * math.pi * f * 0.5 * t) signal += 0.08 * rng.uniform(-1, 1) value = max(-32767, min(32767, int(signal * envelope * 7000))) samples.extend(struct.pack("