84 lines
2.4 KiB
Python
84 lines
2.4 KiB
Python
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
|