mcpField
S
sound-design

sound-design

github.com/whaleyxbtCreative & media713145 installsUpdated 3w ago
Repository

About sound-design

sound-design is a Python toolkit for designing sound effects for motion graphics, intended for creators who need audio aligned with a visual timeline. It synthesizes sounds using the standard library and provides mixing, loudness mastering, and WAV output.

Key features

  1. 01Synthesize effects with Python's standard library
  2. 02Place and mix sounds on a timeline
  3. 03Automatically master loudness
  4. 04Write WAV output
  5. 05Provide multiple sound generators

Use cases

  1. 01Add effects to animated text and interface motion
  2. 02Build audio synchronized to a video timeline
  3. 03Mix short effects and control loudness

MCP support

The README mentions optional use of ElevenLabs MCP to generate voiceover or sounds that cannot be synthesized, writing files into public/.

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/whaleyxbt/claude-motion --skill sound-design

After installing, type/sound-designin Claude Code to confirm it loaded.

Details

From the project README

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

FileRole
sfx/synth.pygenerators: each returns mono samples at 48 kHz
sfx/mix.pyMix: place sounds in time, master to a loudness target, write WAV
sfx/loudness.pyBS.1770 integrated loudness (matches ffmpeg's ebur128)
sfx/cues/<video>.pya 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 WAV

Writing 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): t always comes from tl, 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 like pad().
  • 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 in package.json pointing 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 screenGeneratorNotes
Typingmix.typing()jitter ±4–6 ms, random gain
Word / line revealwhoosh() + blip() on the landingshort, airy
Badge, chip, callout pops inpop()raise pitch across a sequence
Control snaps to a stepclick() + blip() in key
Sketch / draw-onscratch()
Build-upriser()end exactly on the hit; one big riser per video
Success / arrivalchime()chord in key
Hero transitionwhoosh() in, impact() on the hitone impact per video
Under everythingpad() 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 post

In 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}>.

Comments

Markdown supported