microquack / apps /web /src /droidtext.ts
Onur Solmaz
GPT-5.6 Sol
fix(emotions): generate web contract from Rust
c6af819
Raw History Blame Contribute Delete
1.57 kB
/**
* 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<UnitClass, string[]> = {
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<VoiceMode, string> = {
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;
}