> ## Documentation Index
> Fetch the complete documentation index at: https://docs.summerengine.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Music track skill for AI agents

> Generate a music track: game loops of 30s or more with a loop point, or linear cinematic cues. A Summer Engine skill for AI agents.

`music-track` is a skill from the [Summer skill library](/mcp/skills): a guide your AI agent reads when the task matches, so it builds the good version on the first try. Generate a music track: game loops of 30s or more with a loop point, or linear cinematic cues.

| | |
| - | - |
| Status | Stable |
| Domains | Audio, Asset pipeline |
| Version | `1.0.2` |
| Engine version | `>=4.6` |
| Recommended set | Yes, installed by `--recommended` |

## When your agent uses it

* Generating a looped or cinematic music track — main theme, boss music, title screen, exploration loop, combat track
* A game has no music yet or needs a new track for a level, a cutscene, or the credits

## Install

`summer setup` installs every skill for your agent. To add only this one:

```bash theme={null}
npx -y summer-engine@latest skills install music-track --agent claude-code
```

Swap `claude-code` for your agent, such as `codex` or `cursor`. See [Skills](/mcp/skills#quick-start) for every option.

## The skill

## Overview

ElevenLabs Music renders a track from a prompt. Summer clamps the requested length to **3–600 seconds** before sending it, so anything outside that range is silently pulled to the nearest bound. Quality depends on prompt specificity: tempo (BPM), key, instrumentation, mood, structural cue. "Epic orchestral" returns generic stock music. "85 BPM, D minor, solo cello and detuned upright piano, sparse, melancholic, no drums, 4/4" returns a usable score for a melancholic exploration scene.

This skill produces one track aligned with the audio bible's music style and dynamic music plan, then wires it as an `AudioStreamPlayer` on the `Music` bus with a marked loop point if it's a loop, or as a linear cinematic stream if it's a one-shot.

## When to use

* Looped track for a level, biome, menu, or game state (calm / tension / combat).
* Cinematic linear track for an intro, ending, boss reveal, or cutscene.
* Replacing a placeholder loop with on-bible music.

## When NOT to use

* Short stings / event punctuation (\<5s) → use `sound-effect` with class `Stinger`.
* Adaptive layered stems with state-driven crossfades → `adaptive-music`.
* Ambient bed with no melody → `ambient-bed`.

## Steps

### 1. Read the audio bible

```
Read .summer/audio-bible.md
```

The bible names the primary style, tempo range, key center, time signature, and the dynamic music model. This skill writes prompts that respect those numbers.

If no bible exists:

> No `.summer/audio-bible.md`. Music without an audio bible drifts into stock-library territory. Run `/audio-direction` first, or I can ship a one-off with explicit defaults — your call.

### 2. Identify the track's role

Ask if not obvious:

> Which slot in the dynamic music plan? Calm exploration, tension, combat, boss, victory, menu, ending, or one-off cutscene?

Each slot has different rules:

| Slot | Tempo | Energy | Instrumentation density | Loop or linear |
| - | - | - | - | - |
| Calm exploration | bible low end (e.g. 70–90) | Low | Sparse | Loop, ≥45s |
| Tension | mid (e.g. 90–110) | Medium, no drums or sparse pulse | Adds one instrument | Loop, ≥45s |
| Combat | high (e.g. 120–150) | High, full drums | Full | Loop, ≥45s |
| Boss | high or low-and-menacing | Distinct from combat | Distinct, named instrument | Loop, ≥60s |
| Victory sting | n/a | Resolution | n/a | Linear, 4–8s (use SFX skill instead) |
| Menu / title | bible mid | Atmospheric | Anchor instrument | Loop, ≥30s |
| Cutscene | varies | Linear arc | Varies | Linear, 60–180s |
| Ending | varies | Linear arc with resolution | Varies | Linear, 60–300s |

### 3. Build the prompt — tempo + key + instrumentation + mood + structural cue

Prompts that work:

```
85 BPM, D minor, 4/4, solo cello and detuned upright piano, sparse, melancholic
exploration loop, no drums, gentle reverb, even dynamics, 60s

128 BPM, D dorian, 4/4, full orchestral combat, low brass ostinato, taiko drums
on 1 and 3, strings tremolo, urgent, no melodic resolution, 60s loop

90 BPM, A minor, 6/8, lo-fi exploration, vintage Rhodes electric piano,
tape hiss, warm bass, soft brushed snare, no vocals, 45s loop

70 BPM, F minor, 4/4, ambient menu loop, pad layer, distant choir, single
piano motif every 8 bars, no drums, 30s loop with seamless return

110 BPM, C minor harmonic, 4/4, dark synthwave boss intro, gated reverb snare,
arpeggiated bass synth, lead saw on melody, builds to cymbal swell at 0:50,
linear 60s, ends on sustained pad
```

Prompts that DON'T work (and why):

| Bad prompt | Failure mode |
| - | - |
| `epic orchestral combat music` | No tempo, no key, no instrumentation. Generic stock. |
| `Hollow Knight style track` | The model can't reference IPs and won't try to clone style words. |
| `hopeful but sad menu music with strings and stuff` | "Strings and stuff" is vague; model picks the wrong section. |
| `something for the boss fight` | No tempo, no key, no mood vector. Random. |
| `loop` (just the word) | No structure. Returns 30s of music that doesn't loop. |

### 4. Tempo, key, instrumentation specifics that matter

**Tempo (BPM)** — name a single number, not a range. Music model interprets ranges as mid-point with drift. `85 BPM` is better than `80–90 BPM`.

**Key** — name mode (`D minor`, `D dorian`, `D natural minor`, `C minor harmonic`). The bible's modulation rules go here: combat tracks shift to dorian, calm tracks to natural minor.

**Instrumentation** — name 2–4 specific instruments, not sections. `solo cello` not `strings`. `upright piano` not `keys`. `Rhodes electric piano` not `electric piano`. The model's vocabulary is closer to a session musician's than a producer's.

**Mood** — concrete adjectives anchored to motion: `melancholic and slow`, `urgent and driving`, `triumphant and rising`. Avoid genre adjectives like `epic`, `chill`, `hype`.

**Structural cue** — for linear tracks, name the arc: `builds at 0:30 to a hit at 0:50, decays`. For loops, name the cell: `4-bar phrase repeats, instrument swap every 8 bars`.

### 5. Loop vs cinematic decision

| Choose loop when | Choose linear when |
| - | - |
| The slot plays for an unbounded time (level, menu, exploration) | The slot has a known length (cutscene, intro, ending) |
| Player attention is on gameplay | Player attention is on the music |
| You want to layer adaptive stems on top later | The arc is the point |

For loops: generate **≥30s, prefer 45–60s**. Shorter loops are recognized as repetition within 90s of play and become annoying. The bible's "no state stays >90s without variation" rule means even a 60s loop should contain internal variation (instrument swap on alternating cycles).

For linear: generate **≥60s for cutscenes, up to 300s for endings**. Ask for one rendered piece rather than splicing; the model handles long arcs.

### 6. Confirm and call

Show the prompt:

> Prompt:
> `85 BPM, D minor, 4/4, solo cello and detuned upright piano, sparse, melancholic exploration loop, no drums, gentle reverb, 60s loop`
> Duration 60s. This uses credits. Generate?

The `music` capability reads the description from **`prompt`**, not `text` — the mirror image of `sound_effects`. Without `prompt` the call is refused (`prompt_required`).

```
assets.generate_audio({
  capability: "music",
  prompt: "85 BPM, D minor, 4/4, solo cello and detuned upright piano, sparse, melancholic exploration loop, no drums, gentle reverb, 60s loop",
  durationSeconds: 60
})
// Then: summer_import_from_url(url: "<asset.fileUrl>", path: "res://audio/music/calm_exploration.mp3")
```

`durationSeconds` is clamped to 3–600, and a longer track costs more: `dryRun: true` returns the `estimate` in credits first. Music comes back as MP3 (`audio/mpeg`), so a `.mp3` target path is correct.

### 7. Mark the loop point in Summer Engine

ElevenLabs Music does not return a loop-clean cut. You must mark the loop point
in Summer Engine's Import dock so playback returns to the loop point on
completion, not to 0:00.

The MP3 importer exposes exactly five options — `loop`, `loop_offset`, `bpm`, `beat_count`, `bar_beats`. There is **no `loop_mode`** on MP3 or OGG; `loop_mode` belongs to the WAV importer, where it is spelled `edit/loop_mode`.

In `audio/music/calm_exploration.mp3.import`:

```
[params]
loop = true
loop_offset = 0.0
```

If the start has a fade-in but the body is loopable, set `loop_offset = 4.0` (4 seconds of intro skipped on loop, plays full track first time). `bpm` + `beat_count` + `bar_beats` are what you set if you want beat-synced transitions later — see `adaptive-music`.

For seamless looping, the cleaner option is to ask the model to generate a cell (one bar / one phrase) and concatenate; or generate at 60s and trust the model's structural cue. Crossfade looping in code is the practical fallback (step 9).

### 8. Wire it as `AudioStreamPlayer` on the `Music` bus

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("AudioStreamPlayer", "MusicCalm", null, {
        "stream": load("res://audio/music/calm_exploration.mp3"),
        "bus": "Music",
        "volume_db": -8.0,
        "autoplay": true,
    })
    ctx.save_scene()
""")
```

Music is `AudioStreamPlayer` (not 2D / 3D) — non-positional. The bible's `Music -8 dB` mix sits the music behind SFX where it belongs.

### 9. Crossfade-loop pattern (no marked loop point)

If the loop click bothers the user, crossfade two players. Set `loop = false` in the import file first, so each player stops at the end.

```gdscript theme={null}
# scripts/audio/MusicLoopCrossfade.gd
extends Node
@export var stream: AudioStream
@export var fade := 1.0
@export var volume_db := -8.0
var _players: Array[AudioStreamPlayer] = []
var _current := 0

func _ready() -> void:
	for i in 2:
		var p := AudioStreamPlayer.new()
		p.stream = stream
		p.bus = "Music"
		p.volume_db = volume_db
		add_child(p)
		_players.append(p)
	_players[0].play()

func _process(_delta: float) -> void:
	var a := _players[_current]
	if not a.playing or a.get_playback_position() < stream.get_length() - fade:
		return
	_current = 1 - _current
	var b := _players[_current]
	b.volume_db = -80.0
	b.play()
	var t := create_tween().set_parallel()
	t.tween_property(a, "volume_db", -80.0, fade)
	t.tween_property(b, "volume_db", volume_db, fade)
	t.chain().tween_callback(a.stop)
```

The next player starts `fade` seconds before the current one ends and the two cross over, so the seam is never heard.

## Reference card — prompts by slot

```
Calm explore (folk):    78 BPM, D minor, 6/8, fingerpicked acoustic guitar,
                        upright bass on 1, soft hand percussion, no melody on
                        first 8 bars, 60s loop

Tension (orchestral):   95 BPM, D dorian, 4/4, low strings sustained, single
                        timpani roll every 8 bars, sparse french horn motif,
                        no drums, 60s loop

Combat (orchestral):    140 BPM, D dorian, 4/4, taiko drums on 1 and 3,
                        ostinato low brass, strings tremolo, urgent and driving,
                        no melodic resolution, 60s loop

Boss (synthwave):       110 BPM, C minor harmonic, 4/4, gated snare, arpeggio
                        bass synth, lead saw on melody, builds at 0:50,
                        linear 60s

Menu (lo-fi):           70 BPM, F minor, 4/4, vintage Rhodes, tape hiss,
                        soft brushed snare, single piano motif every 8 bars,
                        45s loop

Cutscene (cinematic):   varies tempo, D minor, 4/4, solo piano opens,
                        strings enter at 0:20, full orchestra at 0:50,
                        decay to solo piano at 1:30, linear 120s
```

## Anti-patterns

* **Genre adjectives only.** `epic`, `chill`, `hype` — meaningless.
* **Naming a game or composer.** Model won't replicate; use ingredients.
* **Loop shorter than 30s.** Recognized as repetition fast.
* **Music on `Master` bus at 0 dB.** Buries SFX. Use `Music` bus at `-8 dB`.
* **Generating before reading the bible.** Style drift.
* **Skipping the loop-mark step.** Music returns to 0:00 with a click on every loop.

## Edge cases

* **Clip is 50s but you wanted 60s.** Either regenerate or extend by overlap-crossfading the last 5s onto the start in an audio editor.
* **Loop click on every cycle.** Use the crossfade pattern in step 9.
* **Clip is too long.** Trim the tail in the import dock with `loop_offset`, or in an audio editor.
* **Track sounds nothing like the references.** Re-read the bible; the prompt may be missing the anchor instrument. Add it explicitly.
* **Track has unexpected vocals.** Add `no vocals, instrumental only` to the prompt.

## 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 (loop point, `AudioStreamPlayer`, crossfade script). Save the file where the person asked with `out` (the `summer-assets` skill says how), and tell them the path and format. For a loop track, tell them it is made to loop.

## Fallback (no MCP)

Print the prompt and instruct the user to run via the Summer dashboard, then `summer_import_from_url` the rendered MP3.

## Handoff

> Track `calm_exploration.mp3` wired to `Game/MusicCalm` on the `Music` bus, autoplay on. Next:
>
> * Mark the loop point in the import dock if the bible calls for looped playback.
> * Generate the tension and combat counterparts with `/music-track` again.
> * To wire state-driven crossfades between calm / tension / combat, run `/adaptive-music`.

## See also

* `audio-direction` — defines style, tempo, key, model
* `adaptive-music` — wiring stems to game state
* `sound-effect` — short stings
* `ambient-bed` — non-melodic location bed

## 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.

| In this skill | Command |
| - | - |
| `summer_assets_generate_audio` | `summer assets generate-audio` |
| `summer_assets_search` | `summer assets search` |
| `summer_add_node` | `summer engine add-node` |
| `summer_import_from_url` | `summer engine import-from-url` |
| `summer_inspect_node` | `summer engine inspect-node` |
| `summer_run_script` | `summer engine run-script` |
| `summer_set_prop` | `summer engine set-prop` |

## Related skills

* [`adaptive-music`](/skills/adaptive-music): Wire music stems to game state: combat, explore, boss and tension crossfades on a shared bus.
* [`audio-direction`](/skills/audio-direction): Define the game's sound (music style, SFX vocabulary, dynamic music) as .summer/audio-bible.md.

***

Source: [`library/skills/music-track/SKILL.md`](https://github.com/SummerEngine/summer/blob/main/library/skills/music-track/SKILL.md), rendered from `summer-engine@4.3.0` on npm.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.