sound-effect · git:20261015.2ab41ad · 2026-10-15 · sha256 3afeff33cf0db340

sound-effect git:20261015.2ab41adA

Immutable. This exact content never changes and is served at /api/v1/blob/3afeff33cf0db340.

---
name: sound-effect
description: "Make sound effects (swings, hits, whooshes, reloads, UI): three versions to pick from, then wire them."
license: MIT
compatibility: [Cursor, Claude Code, Windsurf, Codex]
category: audio
user-invocable: true
allowed-tools: Read Grep Glob Write Edit summer_assets_generate_sfx_choices summer_assets_choices summer_assets_generate_audio summer_assets_search summer_import_from_url summer_import_asset_by_id summer_add_node summer_set_prop summer_instantiate_scene summer_inspect_node summer_get_scene_tree summer_call
paths: ["audio/sfx/**", "scripts/**", "**/*.tscn"]
---

# /sound-effect — Make a Sound Effect and Wire It

> **IMPORTANT — the first sound effect in a chat:** before you generate it, ask the person one short question:
>
> > Do you want me to create three versions with three different models, and show them to you in a page where you pick?
>
> Say why in one plain line: the best model changes a lot from one sound to the next, so picking by ear gives a better result.
>
> - **Yes** → `assets.generate_sfx_choices`, tell them where to listen (their Studio tab opens the page, else `reviewUrl`), then `assets.choices` until they decide. Import the approved clips and wire them. The model they approve becomes their default.
> - **No** → `assets.generate_audio` with `capability: "sound_effects"` and **no** `modelId` (it uses their approved default, else ElevenLabs).
>
> Ask once per chat. After they answer, do not ask again in the same chat; follow their answer for the rest of it.

## When to use

- One-shots: swing, clash, scrape, hit, punch, impact, whoosh, pickup, jump, land, door, UI click.
- Timed sequences tied to an animation: reload, attack combo, door unlock.
- Short loops (hum, servo, energy field) up to a few seconds.

Not for: music or stingers (`music-track`), long location ambience (`ambient-bed`), voice (`voice-line`), the SFX vocabulary of the game (`audio-direction`).

## Steps

### 1. Read the audio bible

Read `.summer/audio-bible.md` if it exists and map the request to one of its SFX classes; obey its character and DON'Ts. If it is missing, offer `/audio-direction` or go on with sensible defaults.

### 2. Reuse before you generate

Generation costs credits (`dryRun: true` on the generate call shows how many). `assets.search({ query: "<subject>", assetType: "audio", scope: "all" })`; if a clip fits, ask whether to use it.

### 3. Write the prompt

Describe the physical event, not a mood. The shape that tested well:

```
Foley one-shot: <physical event with materials>, <transient + tail described>, close-mic, dry, no reverb, no music, no voices.
```

- Sword clash: `Foley one-shot: two steel longswords strike edge to edge, bright sharp metallic impact, short ringing tail that dies in half a second, close-mic, dry, no reverb, no music, no voices.`
- Punch: `Foley one-shot: a bare fist hits a heavy leather bag, deep thud with a slap on top, no tail, close-mic, dry, no reverb, no music, no voices.`
- Boulder drop: `Foley one-shot: a large boulder drops onto packed dirt, heavy low thump with a short rumble and a few small stones settling, close-mic, dry, no music, no voices.`

**Whooshes and swings:** say `air movement only, no metal ring, no impact`, or the model adds a clang.

**Timed sequences:** give the exact total length and the time of each event, and set `durationSeconds` to that length:

```
Foley sequence, exactly 2 seconds, three distinct mechanical sounds: at 0.2 s the empty magazine is released and pulled out with a metal click; at 1.0 s a full magazine is slammed in with a solid clack; at 1.6 s the charging handle is pulled back and released with a sharp metallic snap. Close-mic, dry, no gunshot, no music.
```

**Loops:** write `seamless loop` in the text and pass `loop: true`. Only ElevenLabs SFX v2 makes loops: use `assets.generate_audio` with `modelId: "eleven_text_to_sound_v2"`, or include it in a choice set.

Avoid: vibe words (`epic`, `cool`, `realistic`), several unrelated events in one prompt (split them, unless it is a timed sequence), and no length.

### 4. Length

- One-shots: 0.5-2 s.
- Animation-synced sounds (reloads, attacks): `durationSeconds` = the animation length, with the event times written in the prompt.
- Minimums: Stable Audio 3 SFX >= 1 s, Sonilo SFX 1.1 >= 0.5 s, ElevenLabs SFX v2 0.5-30 s. When a choice set includes Stable Audio 3, use at least 1 s and let the player use the front of the clip.

### 5. Pick the models

| Model id | Name | Use it for |
|---|---|---|
| `eleven_text_to_sound_v2` | ElevenLabs SFX v2 | Strong default on most sounds; the only one with seamless loops |
| `sonilo-sfx-1.1` | Sonilo SFX 1.1 | Strong default on most sounds |
| `stable-audio-3-sfx` | Stable Audio 3 SFX | Sometimes the best on fast movement (swings, whooshes); less consistent |

A choice set makes all three by default. To compare only two, pass `models`: ElevenLabs + Sonilo is the strong pair; include Stable Audio 3 for movement sounds.

### 6. Generate

Show the prompt and the length before you call; every version is a billed generation.

**Choice set (the person said yes):**

```
assets.generate_sfx_choices({ text: "<prompt>", durationSeconds: 1.0 })
```

The answer has `choiceSetId`, `reviewUrl`, the `clips` and `studioTabOpened`. The page opens in their open Summer Studio tab; if `studioTabOpened` is false, give them `reviewUrl`. Do not pick for them. Then:

```
assets.choices({ choiceSetId: "<id>", waitSeconds: 50 })
```

Call it again while `status` is `pending`. When it is `decided`, each clip with `approved: true` has `audioUrl`, `assetId` and the person's `note`. If they approved none, ask what to change and make a new set.

**One version (the person said no, or later sounds after a decided set):**

```
assets.generate_audio({ capability: "sound_effects", text: "<prompt>", durationSeconds: 1.0 })
```

Leave out `modelId` so their approved default runs. The description goes in `text`, not `prompt` (without `text` the call is refused: `text_required`). The answer's `asset` has `assetId`, `fileUrl` and `durationSeconds`.

No Summer project (an asset-only request, or the engine is not installed or not running): stop here. The clips are in the person's Summer library; give them the `audioUrl` of each approved version and skip steps 7 and 8.

### 7. Import

```
summer_import_from_url(url: "<audioUrl>", path: "res://audio/sfx/sword_clash_01.<ext>")
```

or `summer_import_asset_by_id(assetId: "<assetId>", path: "res://audio/sfx/sword_clash_01.<ext>")`. Keep the extension of the file you got (the end of `audioUrl` or `fileUrl`). When the person approved several versions, import each one (`_01`, `_02`, …) and play a random one per event for variety.

One-shots keep loop OFF in the Import dock; looping a one-shot clicks. For a loop: WAV uses Edit > Loop Mode = Forward; OGG/MP3 use Loop = on.

### 8. Wire

| Node | Use for |
|---|---|
| `AudioStreamPlayer` | UI and non-positional sounds |
| `AudioStreamPlayer2D` | 2D positional |
| `AudioStreamPlayer3D` | 3D positional (weapon swing in the world, footstep) |

One script adds the player with its settings; `scenePath` is the exact scene to change:

```
summer_run_script(scenePath="res://main.tscn", source="""
func run(ctx):
    ctx.add_node("AudioStreamPlayer3D", "SwingSFX", ctx.find("Player"), {
        "stream": load("res://audio/sfx/sword_swing_01.wav"),
        "bus": "SFX",
        "max_distance": 25.0,
    })
    ctx.save_scene()
""")
```

The bus must match the bible (`SFX`, `UI`, `Ambient`, …); the default bus is `Master`. If the bus does not exist, run `/audio-direction`.

Fire animation-synced sounds from the animation's Call Method Track (`play()`), not from `_process`.

For a sound that must outlive its emitter (a death blow), spawn a self-freeing player at runtime:

```gdscript
# scripts/audio/SfxOneShot.gd
extends AudioStreamPlayer3D

func _ready() -> void:
	finished.connect(queue_free)
	play()
```

Save it as a `.tscn` and instantiate it per event in GDScript (`preload(...).instantiate()` + `add_child`).

### 9. Check

`summer_inspect_node(path="Player/SwingSFX")`: verify `stream`, `bus`, `volume_db`. Too quiet: raise `volume_db` (+3 to +9, never past +12), else regenerate with a heavier description. Too long: `stop()` on a timer.

## Without Summer Engine

In a session without Summer Engine tools (the assets profile; the `summer-assets` skill has the whole loop), do steps 1-6 and skip steps 7-9 (import, wiring, scene check). With `assets.generate_sfx_choices`, the person picks in the page; then save each approved clip where the person asked with `assets.get` and `out` (the `summer-assets` skill says how), and tell them the path and format.

## Fallback (no MCP)

Print the prompt, the length and the target path (`res://audio/sfx/<name>.wav`), and tell the person to make it in Summer Studio, then import it.

## See also

- `audio-direction` — the SFX vocabulary this skill obeys
- `ambient-bed` — long looping textures
- `music-track` — music
- `voice-line` — speech
- `summer_docs_read` id `reference/mcp-tools-reference`