soundtrack
This skill should be used when the user asks to "add music to the animation", "make the music in code", "synthesize a soundtrack", "compose background music that fits the cuts", "sync the music to the video", "check if the audio lines up with the picture", or "the music is too sudden / startling". Covers a code-synthesized score that lives inside the animation page (Web Audio API, rendered offline to WAV), AI-generated or user-supplied music, and an automatic check that cuts land on the beat without a startling jump in loudness.
1How It Works & Mechanism
The default route is "synthesize via code inside the page": paste templates into the animation page's <script>, where the soundtrack and visuals share the same BPM/SECTIONS/EVENTS so that sound for every action lands on the exact same frame—zero keys, zero audio files.
Paste `templates/score.js` into the animation page's `<script>`, before the live-preview block at the bottom (the starter marks the spot), and set:
The mechanism for music tracking visuals is a tempo map rather than manual cue matching: each scene is allocated an integer number of bars (roughly 70–110 bpm), naturally aligning every cut onto a downbeat.
- **Tempo map.** Each scene gets a whole number of bars (tempo flexes per scene, roughly 70-110 bpm), so every cut lands on a downbeat.
Cuts are signaled by harmony rather than volume (scenes move toward the dominant chord and cuts land on the tonic), backed by measured values explaining why volume spikes should not be used: +7~10 dB jumps are perceived by users as startling, whereas +3~5 dB with harmonic changes reads as a clean cut.
- **Mark cuts with harmony, not volume.** Each scene walks home to the dominant (G) and the cut arrives on the tonic (C). A loud accent on a cut (bass + bright chime at once) reads as a jump scare: users flagged a +7 to +10 dB jump as startling; +3 to +5 dB with a chord change reads as a clean edit.
Automated verification turns "alignment" and "non-startling" into two fail-safe criteria: reporting event-by-event offset to the nearest strong onset and entrance loudness jump; flagging OFF-BEAT if offset exceeds ±100 ms and STARTLE if loudness jumps over +6 dB, skipping the initial one second and final fade-out.
It fails on OFF-BEAT (more than ±100 ms, ~3 frames) or STARTLE (more than +6 dB versus the second before). The opening second and the final fade are skipped (no "before" to compare against; a fade-out is not a cut).
When using non-synthesized music, the direction reverses: visuals follow the music—render, mix, run the check, and shift scene boundaries in the page's SCENES according to the reported offsets before re-rendering; since scene functions only take local time, shifting boundaries does not touch scene code, making this iteration loop very cheap.
With music you don't control, reverse the direction: **the picture follows the music.**
Capability boundaries are explicitly declared: code synthesis excels at music boxes, piano-like plucks, pads, chimes, and light percussion, but cannot produce realistic vocals, orchestral arrangements, or polished genre productions—switch to AI music or real tracks when needed.
Synthesized in code: music box, piano-like plucks, pads, bells, soft percussion are fine. Realistic voices, orchestras and genre production are not; use AI music or a real track for those.
The AI music branch requires a user-provided API key, where the agent curls an external generation service directly (Lyria on OpenRouter) with music descriptions—this is the only branch in this skill that reads credentials and sends materials externally, positioned as "only when asked".
Use when the user wants something richer than a synthesized score. Needs an API key.
2Core Capabilities
3External Dependencies
| Kind | Dependency / Endpoint |
|---|---|
| cli | ffmpeg / ffprobe(分析视频与音频:抽灰度帧、抽 PCM;AI 支路里还用于混音) |
| api | OpenRouter 上的 Lyria(可选 AI 音乐生成,stream:true 收 base64 音频) |
| api | 该端点的凭证:从环境变量读取并放入 Authorization 头(仅 AI 音乐支路) |
| package | 兄弟 skill javascript-animation 的 render.mjs(离线渲染 SCORE 为 WAV 并 mux 进 MP4) |
| network | Web Audio API(浏览器内置,用于合成与离线渲染;非外部服务) |
4Recommended Governance & Evidence
- scripts/: Execute local script or command: node scripts/sync-check.mjs piece.mp4 [--cues-file piece.cues.json] [--tol
- 网络端点: Declare external network access: https://openrouter.ai/api/v1/chat/completions — AI music branch only (when explicitly requested by the user), Authori
- 本地工作目录: File read/write and artifact output: Assets and intermediate artifacts: bgm.mp3 (curl streaming audio written to disk), piece_music.mp4 (ffmpeg muxed output)
5Second-Pass Verification
- [ok] $OPENROUTER_API_KEY 是否真的被读取并外发(橙档判定关键) — references/ai-music.md 原文给出 `curl -s -N https://openrouter.ai/api/v1/chat/completions -H "Authorization: Bearer $OPENROUTER_API_KEY" …`,且开头写明 'Needs an API key.';端点与凭证同处一条可复制命令,调用点真实存在。
- [ok] 该支路是否真的是「可选、非默认」(避免过度判级) — SKILL.md 正文写 'Don't ask the user to choose a music source up front.',并把 AI 音乐放在 '## Other sources (only when asked)' 段下;默认段标题为 '## Default: synthesize it in the page (no key, no files)'。触发条件为用户显式要求。
- [ok] sync-check 的判据是否与文档一致(能力声明可信度) — 代码内 TOL = Number(opt('--tol', 100)) / 1000 对应 ±100ms;`const jump = 20 * Math.log10(rms(v.t, v.t + .3) / ref), startle = jump > 6;` 对应 +6 dB;无音轨时 console.error 并 process.exit(1);与 SKILL.md 逐条吻合,无夸大。
- [ok] 模板是否真的与画面共享数据(而非各自维护时间) — groove.js 头注释给出 schema(sections/events/kit/harmony/key)并写 'The picture reads the same BPM / SECTIONS / EVENTS, so sound and image can't drift apart.';score.js 同样由 cues(即 window.CUES)切段并做小节取整。声明与实现一致。
- [ok] 是否有比橙更高的行为(红档特征) — 全仓扫描 `sandbox|automationcontrolled|no-verify|stealth|bypass|ignoreHTTPSErrors|dangerously` 零命中;curl 未使用 -k/--insecure,无绕防护动作,无任意代码执行面(模板是页面内 Web Audio 代码)。
- [ok] 确定性声明(每次渲染一致)是否可在模板代码内定位 — score.js 的混响脉冲用固定种子 LCG 生成并注释 'deterministic noise tail (seeded, so every render is identical)';groove.js 同样用 `let s = 99 + c` 起手的 LCG 与固定种子噪声缓冲。声明属实。
6Verdict & Scenarios
7cd2fd4b7c5af0a2…