voice-line is a skill from the Summer skill library: a guide your AI agent reads when the task matches, so it builds the good version on the first try. Generate voice lines (NPC barks, narrator, dialogue) with a character-to-voice choice and multi-line support.
When your agent uses it
- “the NPC needs a greeting / a bark / a narrator intro read aloud”
- Giving a character a voice that fits their personality (gruff, cheerful, robotic)
- A back-and-forth dialogue between two or more characters
Install
summer setup installs every skill for your agent. To add only this one:
claude-code for your agent, such as codex or cursor. See Skills for every option.
The skill
Overview
The hardest part of TTS isn’t the line — it’s picking the right voice; pick wrong and a tough warlord NPC sounds like a podcast host. On the ElevenLabs models only Summer’s game cast may speak: thevoiceId of assets.generate_audio must be one of the cast voices below, and any other id is refused (voice_not_allowed). The fal speech models (minimax-speech-2.8-hd, gemini-3.8-flash-tts, …) speak with their own prebuilt voices instead. So the pick comes from the cast (or the project’s cast bible), and this skill’s job is to make that pick deliberately and then lock it.
Then it generates one line (or a multi-turn dialogue) with the right stability / style / similarity / speed for the delivery — flat for narration, expressive for a bark, fast for an excited shout.
When to use
- NPC bark, narrator line, dialogue between two characters, tutorial voice, menu announcer.
- Multi-line conversation with two or more voices → use
text_to_dialoguecapability.
When NOT to use
- Non-verbal vocal SFX (grunt, scream) →
sound-effectwith promptmale grunt of pain, mid-thirties, sharp, 400ms. - Pre-recorded voice from a real actor — import the .mp3 / .wav with
summer_import_from_urland skip TTS.
Steps
1. Read the audio bible and any character notes
Tell me the character: gender, rough age, accent or regional flavor, and energy (calm / measured / excited / gruff). Or just give me a reference — “sounds like the captain in Mass Effect” works.
2. Get the voice id
Check the cast bible first (step 1). If the character already has a locked id, use it and skip to step 5. If not, pick from Summer’s game cast with the user. There is no preview-URL endpoint on this surface — do not claim you auditioned anything; the person listens to the generated line. The cast (name, role, id):
Ask:
ForOmitting<character>I suggest<voice>(<role>). OK, or another cast voice?
voiceId entirely is valid — assets.generate_audio falls back to the default voice (George, JBFqnCBsd6RMkjVDRZzb). That is fine for a throwaway read-through, never for a shipped cast.
Voice is the highest-stakes audio decision in the project. Do not guess ids — an id outside the cast is refused, not a different-sounding voice.
3. Character-to-voice decision tree (when the user wants a recommendation)
If the user says “you pick”, lean on this tree:4. Lock the voice id
After the pick:Locked voice:Ask before writing:Roger(idCwhRBWXzGAHq8TQ4Fs17). I’ll use this for all<character>lines unless you say otherwise.
May I update .summer/memory/casting/voices.md with this locked voice assignment?
Save it in .summer/memory/casting/voices.md:
.summer/voice-cast.md already exists, read it first and either keep using it for this project or ask to migrate it into .summer/memory/casting/voices.md. Do not maintain two conflicting cast files.
5. Tune stability / similarity / style / speed for delivery
These go in thevoiceSettings object of assets.generate_audio, in camelCase — stability, similarityBoost, style, speed, useSpeakerBoost. A snake_case key such as similarity_boost is not one of its fields and does nothing.
Defaults that work as a starting point:
Ask:
Delivery flavor? Calm idle / combat shout / cinematic / robotic / scared / cold? I’ll set stability and style to match.
6. Build the line
For a single line, the prompt is just the text. Punctuation and formatting matter:- Periods = clear pauses (~300ms). Use them.
- Commas = short pauses (~100ms).
- Em dashes = sharp cut.
- All-caps word = stress:
You SHALL not pass. - Ellipsis = trailing pause:
I... I don't know what to say. - Parenthetical stage direction is ignored —
(angrily)is read as the word “angrily”. Use stability/style instead.
7. Confirm and call
modelId defaults to eleven_multilingual_v2. For very short / latency-sensitive in-game barks, eleven_flash_v2_5 is faster; for highest fidelity, eleven_multilingual_v2 is the default. A line spends the person’s credits; dryRun: true returns its estimate first.
There is no outputPath — the answer is a hosted asset, asset (assetId, fileUrl, durationSeconds; MP3, audio/mpeg). Land it in the project explicitly:
8. Multi-line dialogue (two or more speakers)
Useassets.generate_audio with capability: "text_to_dialogue":
There is no speakers roster and no script array. The whole conversation is one flat inputs array of { text, voiceId } objects, in turn order — the voice id is repeated on every line:
inputs is required for this capability; omitting it is refused as invalid input (inputs_required). modelId defaults to eleven_v3 here, not eleven_multilingual_v2. The result is a single MP3 with both voices and dialogue-aware pacing the model handles internally — import it with summer_import_from_url like any other clip. For very long scripts, split into chunks of ~6 turns and concatenate.
9. Wire the line as AudioStreamPlayer on the Voice bus
One script adds the player with its settings; scenePath is the exact scene to change:
AudioStreamPlayer3D with max_distance=15.0 so distant NPCs aren’t audible.
The bible’s mix rule says voice ducks music: when this player starts, fade the music bus by -6 dB. Wire this in the bus setup or in code with AudioServer.set_bus_volume_db().
10. Save to the cast bible
After generation, append to.summer/memory/casting/voices.md so future skills find existing lines and don’t regenerate. Include line text + filename + voice id.
If the assignment is marked priority: locked, do not replace the voice id automatically. If a scene, script, import config, or user prompt conflicts with the locked memory, stop and ask whether to update memory or fix the implementation.
Reference card — voice-pick by archetype
Anti-patterns
- Inventing voices. On the ElevenLabs models only the cast in step 2 may speak, and there are no preview URLs on this surface. Pick from the cast with the user.
- Guessing a voice id. A wrong id is a provider error, not a different voice.
- Reusing a voice id across two characters. Players notice instantly. Lock the cast.
- Changing a locked voice without approval. A cast voice is project memory, not a throwaway generation option.
- Stage directions in the text.
(angrily) Get out!is read literally as “angrily get out”. Use stability/style. - One long blob for a multi-turn scene. Use
text_to_dialogueso pacing is dialogue-aware. - Voice on
Masterbus. Must be onVoicebus so it can duck music per the bible. - No ducking on dialogue. Music drowns voice; players miss lines. Always duck.
- Picking a voice that doesn’t match the character bible. A noble queen with a cockney accent breaks immersion unless that’s the joke.
Edge cases
- Line longer than ~1000 characters. ElevenLabs caps per-call. Split at sentence boundaries, generate each, and play them in order by chaining the player’s
finishedsignal. - Mispronunciation of a fictional word. Spell it phonetically:
Caelthorne→KEL-thornorKAYL-thorn. Or use SSML if the model supports it. - Voice drifts in long lines. Increase
similarityBoosttoward 0.9; or split. - Need a non-English line.
eleven_multilingual_v2supports 29 languages. Just write in the target language; the same voice id works. - User wants the line shorter on regen. Edit the text first; don’t try to compress with
speedpast 1.15 (artifacts). - NPC bark variants. Generate 3–5 variants of the same intent (
"They're at the gate!","Enemy approaching!","Look out!") and pick at random in code so the bark doesn’t repeat.
Without Summer Engine
In a session without Summer Engine tools (the assets profile; thesummer-assets skill has the whole loop), do steps 1-8 and skip step 9 (AudioStreamPlayer). Step 10 still applies when the project has a .summer/ folder; otherwise give the person the voice id with the files. Save the file where the person asked with out (the summer-assets skill says how), and tell them the path and format.
Fallback (no MCP)
Print the call (text, voice id, params, target path). User runs via the Summer dashboard, thensummer_import_from_url the result.
Handoff
Linehodge_hold_the_line.mp3wired toNPC/VoiceLineon theVoicebus. Cast bible updated. Next:
- Generate 3 bark variants of the same intent so the NPC doesn’t sound like a recording.
- For the upcoming gate scene, run
/voice-linewithtext_to_dialoguefor the multi-turn exchange.- Set up music ducking on the
Musicbus whenVoiceis active (one Tween, or anAudioEffectCompressoronMusicwithsidechainset toVoice).
See also
audio-direction— Voice bus and ducking rulessound-effect— non-verbal vocal SFX (grunt, scream)adaptive-music— ducking integration
From a shell
No Summer MCP tools in this session? Run each one as a command; whensummer --version is missing or below 4.0, use npx -y summer-engine@latest in place of summer. Fields are flags in kebab-case (--scene-path res://main.tscn) or one --input '<json>', and summer ops describe <op> prints the exact input. The summer-cli skill has the rest.
Source:
library/skills/voice-line/SKILL.md, rendered from summer-engine@4.3.0 on npm.
