Skip to main content
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:
Swap 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: the voiceId 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_dialogue capability.

When NOT to use

  • Non-verbal vocal SFX (grunt, scream) → sound-effect with prompt male grunt of pain, mid-thirties, sharp, 400ms.
  • Pre-recorded voice from a real actor — import the .mp3 / .wav with summer_import_from_url and skip TTS.

Steps

1. Read the audio bible and any character notes

If a character bible exists, the voice should match the character’s age / gender / regional origin / energy. If it doesn’t, ask:
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:
For <character> I suggest <voice> (<role>). OK, or another cast voice?
Omitting 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: Roger (id CwhRBWXzGAHq8TQ4Fs17). I’ll use this for all <character> lines unless you say otherwise.
Ask before writing:
May I update .summer/memory/casting/voices.md with this locked voice assignment?
Save it in .summer/memory/casting/voices.md:
This is the cast bible. Every future voice line uses these ids — consistency is the point. If legacy .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 the voiceSettings 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.
Example lines:

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)

Use assets.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:
For positional NPCs, 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_dialogue so pacing is dialogue-aware.
  • Voice on Master bus. Must be on Voice bus 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 finished signal.
  • Mispronunciation of a fictional word. Spell it phonetically: Caelthorne → KEL-thorn or KAYL-thorn. Or use SSML if the model supports it.
  • Voice drifts in long lines. Increase similarityBoost toward 0.9; or split.
  • Need a non-English line. eleven_multilingual_v2 supports 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 speed past 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; the summer-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, then summer_import_from_url the result.

Handoff

Line hodge_hold_the_line.mp3 wired to NPC/VoiceLine on the Voice bus. 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-line with text_to_dialogue for the multi-turn exchange.
  • Set up music ducking on the Music bus when Voice is active (one Tween, or an AudioEffectCompressor on Music with sidechain set to Voice).

See also

  • audio-direction — Voice bus and ducking rules
  • sound-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; when summer --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.