"""Generate synthetic mechanical keyboard switch samples.""" import math import struct import wave from pathlib import Path SAMPLE_RATE = 48000 OUT_DIR = Path(__file__).resolve().parent.parent / "assets" / "samples" def write_wav(path: Path, samples: list[float]) -> None: pcm = b"".join( struct.pack(" list[float]: env = [] attack_samples = int(attack * SAMPLE_RATE) decay_samples = int(decay * SAMPLE_RATE) for i in range(length): if i < attack_samples: env.append(i / max(attack_samples, 1)) elif i < attack_samples + decay_samples: t = (i - attack_samples) / max(decay_samples, 1) env.append(1.0 - t) else: env.append(0.0) return env def generate_click( freq: float, duration: float, attack: float, decay: float, noise_mix: float = 0.0, click_burst: bool = False, ) -> list[float]: length = int(duration * SAMPLE_RATE) env = envelope(length, attack, decay) out = [] for i in range(length): t = i / SAMPLE_RATE tone = math.sin(2 * math.pi * freq * t) * (0.7 if not click_burst else 0.4) if click_burst and t < 0.008: tone += math.sin(2 * math.pi * (freq * 2.8) * t) * 0.5 noise = 0.0 if noise_mix > 0: noise = (((i * 1103515245 + 12345) & 0x7FFFFFFF) / 0x7FFFFFFF - 0.5) * noise_mix out.append((tone + noise) * env[i]) peak = max(abs(s) for s in out) or 1.0 return [s / peak * 0.85 for s in out] def main() -> None: OUT_DIR.mkdir(parents=True, exist_ok=True) profiles = { "cherry-mx-blue.wav": generate_click( 2800, 0.09, 0.001, 0.085, noise_mix=0.15, click_burst=True ), "cherry-mx-red.wav": generate_click( 1200, 0.05, 0.002, 0.045, noise_mix=0.08 ), "cherry-mx-brown.wav": generate_click( 1800, 0.07, 0.002, 0.065, noise_mix=0.12, click_burst=True ), } for name, samples in profiles.items(): write_wav(OUT_DIR / name, samples) print(f"Wrote {OUT_DIR / name}") if __name__ == "__main__": main()