/** * Droid-speak captions: turn a phrase's unit-class sequence into text, the * way a protocol droid would transcribe it. Deterministic per (classes, seed). * This is the placeholder for the future droid-speak language model. */ import type { VoiceMode } from "./emotions.generated.js"; export type UnitClass = "rise" | "fall" | "flat" | "trill" | "warble" | "blat"; const WORDS: Record = { rise: ["bweep", "wheep", "brdee", "vwee"], fall: ["boo", "bwoop", "dwoo", "vhoom"], flat: ["beep", "bip", "dit", "bop"], trill: ["brrrt", "drrrr", "trrri", "prrp"], warble: ["wooble", "wawoo", "oowee", "wubwub"], blat: ["bzzt", "grzzk", "blat", "vrrk"], }; const ENDINGS: Record = { ramble: ".", curious: "?", happy: "!", excited: "!", sad: "…", scared: "!", angry: "!!", sleepy: "…", affirmative: "!", negative: ".", surprised: "!", confused: "?", }; function hash(seed: number, i: number): number { let h = (seed ^ Math.imul(i + 1, 2654435761)) >>> 0; h = Math.imul(h ^ (h >>> 13), 0x5bd1e995) >>> 0; return (h ^ (h >>> 15)) >>> 0; } export function droidText(classes: UnitClass[], voice: VoiceMode, seed: number): string { const words = classes.map((cls, i) => { const options = WORDS[cls]; const word = options[hash(seed, i) % options.length] ?? "beep"; // repeated classes read as a stutter: "bip-bip" return classes[i - 1] === cls && hash(seed, i) % 3 === 0 ? `${word}-${word}` : word; }); const ending = ENDINGS[voice]; return words.join(" ") + ending; }