from __future__ import annotations import json from pathlib import Path from typing import Optional SAMPLES_DIR = Path(__file__).resolve().parent.parent / "assets" / "samples" / "kbsim" MANIFEST_PATH = SAMPLES_DIR / "manifest.json" DEFAULT_RELEASE_OFFSET = 0.08 SPECIAL_KEY_SAMPLES: dict[str, str] = { "Space": "SPACE", "Enter": "ENTER", "Backspace": "BACKSPACE", } def _load_manifest() -> dict[str, dict[str, list[str]]]: with MANIFEST_PATH.open(encoding="utf-8") as f: return json.load(f) MANIFEST = _load_manifest() def hash_code(value: str) -> int: """djb2 hash — mirrored in frontend/src/lib/audio/keySoundMap.ts""" hash_val = 5381 for ch in value: hash_val = ((hash_val * 33) ^ ord(ch)) & 0xFFFFFFFF return hash_val def press_variant_index(code: str) -> int: return hash_code(code) % 5 def press_variant_key(code: str) -> str: return f"GENERIC_R{press_variant_index(code)}" def resolve_press_sample_key(key_label: str, code: str, available: list[str]) -> str: special = SPECIAL_KEY_SAMPLES.get(key_label) if special and special in available: return special variant = press_variant_key(code) if variant in available: return variant for sample in available: if sample.startswith("GENERIC_R"): return sample return available[0] def resolve_release_sample_key(key_label: str, available: list[str]) -> str: special = SPECIAL_KEY_SAMPLES.get(key_label) if special and special in available: return special if "GENERIC" in available: return "GENERIC" return available[0] def sample_path(switch_id: str, phase: str, sample_key: str) -> Path: return SAMPLES_DIR / switch_id / phase / f"{sample_key}.mp3" def marker_release_time(time: float, release_time: Optional[float]) -> float: return release_time if release_time is not None else time + DEFAULT_RELEASE_OFFSET def resolve_marker_samples( switch_id: str, key_label: str, code: Optional[str], ) -> tuple[str, str]: switch_manifest = MANIFEST[switch_id] press_available = switch_manifest["press"] release_available = switch_manifest["release"] resolved_code = code or key_label press_key = resolve_press_sample_key(key_label, resolved_code, press_available) release_key = resolve_release_sample_key(key_label, release_available) return press_key, release_key