Monorepo for Aesthetic.Computer aesthetic.computer

Add ElevenLabs TTS provider with scream mode (low stability, max style)

say:eleven for normal speech, say:scream defaults to ElevenLabs with
stability=0.1 and style=1.0 for intense vocal delivery. Scream colon
also works with say:openai:scream for gpt-4o-mini-tts instructions path.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

+75 -9
+60 -2
system/netlify/functions/say.js
··· 94 94 }; 95 95 } 96 96 97 + // ElevenLabs voice mapping (premade voice IDs) 98 + const ELEVEN_VOICES = { 99 + male: [ 100 + "SOYHLrjzK2X1ezoPC6cr", // Harry - Fierce Warrior 101 + "IKne3meq5aSn9XLyUdCD", // Charlie - Deep, Confident, Energetic 102 + "N2lVS1w4EtoT3dr4eOWO", // Callum - Husky Trickster 103 + "TX3LPaxmHKxFdv7VOQHJ", // Liam - Energetic 104 + ], 105 + female: [ 106 + "EXAVITQu4vr4xnSDxMaL", // Sarah 107 + "FGY2WhTYpPnrIDTdsKH5", // Laura 108 + "cgSgspJ2msm6clMCkdW9", // Jessica 109 + ], 110 + neutral: [ 111 + "SAz9YHcvj6GT2YYXdXww", // River - Relaxed, Neutral 112 + "cjVigY5qzO86Huf0OWal", // Eric 113 + "bIHbv24MWmeRgasZH58o", // Will 114 + ], 115 + }; 116 + 117 + // Generate audio with ElevenLabs TTS 118 + async function generateElevenLabs(text, gender, set, scream) { 119 + const voiceList = ELEVEN_VOICES[gender] || ELEVEN_VOICES.neutral; 120 + const voiceId = voiceList[set % voiceList.length]; 121 + 122 + const voiceSettings = scream 123 + ? { stability: 0.1, similarity_boost: 0.7, style: 1.0, use_speaker_boost: true } 124 + : { stability: 0.5, similarity_boost: 0.75, style: 0.4, use_speaker_boost: true }; 125 + 126 + const response = await fetch(`https://api.elevenlabs.io/v1/text-to-speech/${voiceId}`, { 127 + method: "POST", 128 + headers: { 129 + "xi-api-key": process.env.ELEVENLABS_API_KEY, 130 + "Content-Type": "application/json", 131 + }, 132 + body: JSON.stringify({ 133 + text, 134 + model_id: "eleven_multilingual_v2", 135 + voice_settings: voiceSettings, 136 + }), 137 + }); 138 + 139 + if (!response.ok) { 140 + const err = await response.text(); 141 + throw new Error(`ElevenLabs API error ${response.status}: ${err}`); 142 + } 143 + 144 + return { 145 + buffer: Buffer.from(await response.arrayBuffer()), 146 + voiceId: `eleven-${voiceId.slice(0, 8)}`, 147 + }; 148 + } 149 + 97 150 // Generate audio with Google Cloud TTS 98 151 async function generateGoogle(text, gender, set, isSSML) { 99 152 // Fetch GCP key from URL ··· 163 216 const set = parseInt(body.voice?.split(":")[1]) || 0; 164 217 const gender = body.voice?.split(":")[0]?.toLowerCase() || "neutral"; 165 218 166 - // Provider: "openai" (default), "google" 219 + // Provider: "openai" (default), "google", "eleven" 167 220 // Can be set via body.provider or defaults to openai 168 221 const provider = body.provider || "openai"; 169 222 170 223 // Instructions for gpt-4o-mini-tts emotional/style control (OpenAI only) 171 224 const instructions = provider === "openai" ? (body.instructions || null) : null; 225 + 226 + // Scream mode for ElevenLabs (low stability, max style) 227 + const scream = body.scream === true; 172 228 173 229 // Cache bust: if true, skip cache lookup and regenerate 174 230 const bustCache = body.bust === true; ··· 183 239 } 184 240 185 241 // Build voice identifier for cache key 186 - const voiceSpec = `${provider}-${gender}-${set}`; 242 + const voiceSpec = `${provider}-${gender}-${set}${scream ? "-scream" : ""}`; 187 243 const cacheKey = getCacheKey(provider, voiceSpec, text, instructions); 188 244 189 245 try { ··· 213 269 let result; 214 270 if (provider === "google") { 215 271 result = await generateGoogle(text, gender, set, isSSML); 272 + } else if (provider === "eleven") { 273 + result = await generateElevenLabs(text, gender, set, scream); 216 274 } else { 217 275 result = await generateOpenAI(text, gender, set, instructions); 218 276 }
+14 -7
system/public/aesthetic.computer/disks/say.mjs
··· 14 14 let text = ""; 15 15 let lastSpoken = ""; 16 16 let status = "idle"; // idle, speaking, error 17 - let provider = "openai"; // "openai" or "google" 17 + let provider = "openai"; // "openai", "google", or "eleven" 18 18 let gender = "neutral"; 19 19 let instructions = null; 20 + let scream = false; 20 21 21 22 // 🥾 Boot 22 23 function boot({ params, colon }) { ··· 32 33 for (const part of parts) { 33 34 if (part === "google") provider = "google"; 34 35 else if (part === "openai") provider = "openai"; 36 + else if (part === "eleven") provider = "eleven"; 35 37 else if (part === "male") gender = "male"; 36 38 else if (part === "female") gender = "female"; 37 39 else if (part === "scream") { 38 - provider = "openai"; 39 - instructions = "Deliver this as a blood-curdling scream. Shriek at the absolute top of your lungs with your voice cracking. Pure primal rage. Do NOT speak normally — only scream, raw and unhinged."; 40 + scream = true; 41 + if (provider === "openai") { 42 + instructions = "Deliver this as a blood-curdling scream. Shriek at the absolute top of your lungs with your voice cracking. Pure primal rage. Do NOT speak normally — only scream, raw and unhinged."; 43 + } else if (provider !== "eleven") { 44 + provider = "eleven"; // Default scream to ElevenLabs 45 + } 40 46 } 41 47 } 42 48 console.log(`Provider: ${provider}, Gender: ${gender}`); ··· 50 56 // Note: Top-left corner is reserved for prompt HUD label 51 57 52 58 // Provider indicator (below HUD area) 53 - const providerColor = instructions ? "red" : provider === "google" ? "cyan" : "lime"; 54 - const providerLabel = instructions ? `[${provider} SCREAM]` : `[${provider}]`; 59 + const providerColor = scream ? "red" : provider === "eleven" ? "orange" : provider === "google" ? "cyan" : "lime"; 60 + const providerLabel = scream ? `[${provider} SCREAM]` : `[${provider}]`; 55 61 ink(providerColor).write(providerLabel, { x: 6, y: 18 }); 56 62 57 63 // Instructions ··· 86 92 87 93 const voice = `${gender}:0`; 88 94 89 - console.log(`🗣️ Speaking: "${text}" with ${provider}, voice: ${voice}${instructions ? " [SCREAM]" : ""}`); 95 + console.log(`🗣️ Speaking: "${text}" with ${provider}, voice: ${voice}${scream ? " [SCREAM]" : ""}`); 90 96 speak(text, voice, "cloud", { 91 97 volume: 1, 92 - provider: provider, 98 + provider, 93 99 instructions, 100 + scream, 94 101 }); 95 102 } 96 103
+1
system/public/aesthetic.computer/lib/speech.mjs
··· 201 201 }; 202 202 203 203 if (opts.instructions) payload.instructions = opts.instructions; 204 + if (opts.scream) payload.scream = true; 204 205 205 206 // Create a promise that resolves when the fetch completes 206 207 let fetchResolve;