Mode d’emploi
- Copiez la commande et examinez le paquet avant de l’installer dans votre environnement agent.
- Consultez le document original pour les outils, identifiants et environnements requis.
- Décrivez les entrées et le livrable souhaité ; comparez la sortie aux sources.
npx skills add https://github.com/whaleyxbt/claude-motion --skill sound-designAvant de commencer
- L’installation n’inclut ni abonnement au modèle, ni quota API, ni accès aux fichiers.
- Étoiles et installations reflètent l’activité, pas un test indépendant de qualité ou de sécurité.
Rendu depuis le dépôt source en conservant titres, exemples, code, tableaux, liens et images.
Sound design
Most AI-made motion graphics are silent. Sound is what makes a piece feel produced.
The sfx/ engine synthesizes every sound in pure Python stdlib: no samples, no licenses, no installs, same output on every run. It reads the same timeline JSON as the picture, so sound can't drift out of sync.
How it fits together
| File | Role |
|---|---|
sfx/synth.py | generators: each returns mono samples at 48 kHz |
sfx/mix.py | Mix: place sounds in time, master to a loudness target, write WAV |
sfx/loudness.py | BS.1770 integrated loudness (matches ffmpeg's ebur128) |
sfx/cues/<video>.py | a cue sheet: one video's sound design |
python3 -m sfx --list # generators with what each is for
python3 -m sfx sfx/cues/<video>.py # render → mastered WAVWriting a cue sheet for a new video
Create sfx/cues/<video>.py:
from sfx.synth import click, pop, whoosh, chime, riser, impact
TIMELINE = "timeline.json" # the same file the composition reads
OUT = "public/sfx.wav"
TARGET_LUFS = -14.0 # optional, this is the default
def score(mix, tl):
s1 = tl["s1"]
mix.put(s1["title"], whoosh(0.4, 500, 2400, peak=0.45), 0.12)
for i, t in enumerate(s1["items"]):
mix.put(t, pop(900 + i * 150, 400), 0.3, pan=-0.4 + i * 0.3)mix.put(t, samples, gain, pan):talways comes fromtl, never a typed number. Offsets from a beat (s1["exit"] + 0.05) are fine.mix.typing(start, end, chars): one humanized keystroke per character.mix.put_stereo(t, left, right, gain): for stereo sources likepad().- Mastering is automatic: the engine soft-clips and sets gain to hit
TARGET_LUFS, raising drive only as far as needed to keep the true peak ≤ −2 dBFS (room for AAC overshoot, so the MP4 stays under −1 dBTP). A mix that's mostly short hits with silence between can't reach −14 without crushing; the engine then comes out quieter and says so. Fix it with sustained sound (a pad, longer tails), not by forcing it. - Add an
"sfx"script inpackage.jsonpointing at the new cue sheet, or run it directly.
sfx/cues/effort.py is a complete, real example: 128 sounds plus a pad bed.
Visual event → sound
| On screen | Generator | Notes |
|---|---|---|
| Typing | mix.typing() | jitter ±4–6 ms, random gain |
| Word / line reveal | whoosh() + blip() on the landing | short, airy |
| Badge, chip, callout pops in | pop() | raise pitch across a sequence |
| Control snaps to a step | click() + blip() in key | |
| Sketch / draw-on | scratch() | |
| Build-up | riser() | end exactly on the hit; one big riser per video |
| Success / arrival | chime() | chord in key |
| Hero transition | whoosh() in, impact() on the hit | one impact per video |
| Under everything | pad() with an envelope() | duck it under the hero transition |
Accent, don't narrate: not every motion needs a sound. Continuous motion (drift, grain, rotation) stays silent.
Levels and space
- Hero hits: gain 0.25–0.35. Supporting sounds: 0.08–0.2. Pad: ~0.03, ducked ~65% under the hero transition.
- Pan follows on-screen position (−1 left … +1 right). Sequences sweep across the stereo field.
- Pick one key and keep every tonal sound in it; pitch rises with progress and resolves on the payoff. (In the effort example the pad and slider notes are D major, but the scene 3 pops and chime are still in C major. Don't copy that.)
Adding a generator
Write a function in sfx/synth.py that returns a list of floats at SR. Put what it's for in the first docstring line; --list shows it. Use the module-level random for noise so renders stay reproducible. Build on bandnoise() for anything airy.
Verify
npm run sfx # or python3 -m sfx sfx/cues/<video>.py
npm run wave # out/wave.png + loudness of the WAV
npm run build
node scripts/wave.mjs out/effort.mp4 # loudness of what you'll actually postIn out/wave.png every coral line is a timeline beat. Each transient should sit on a line or a hair after it. A hit between lines means a hardcoded time or a stale WAV. Pass --timeline for a video with its own timeline file.
Optional: generated audio
For voiceover or sounds you can't synthesize, the ElevenLabs MCP (see .mcp.json.example) can generate files into public/. Place them with <Audio src={staticFile(...)} /> inside a <Sequence from={beat * FPS}>.




