Sounddesign
Die meisten KI-generierten Motion Graphics sind stumm. Der Sound ist es, der einem Werk das „produzierte“ Gefühl verleiht.
Die sfx/-Engine synthetisiert jedes Geräusch in reinem Python-Standardcode: keine Samples, keine Lizenzen, keine Installationen, gleiche Ausgabe bei jedem Durchlauf. Sie liest dasselbe Timeline-JSON wie das Bild, sodass der Ton nicht aus dem Takt geraten kann.
Wie es zusammenhängt
| Datei | Rolle |
|---|---|
sfx/synth.py | Generatoren: jeder liefert Mono-Samples bei 48 kHz zurück |
sfx/mix.py | Mix: Sounds zeitlich platzieren, auf eine Lautstärkevorgabe mastern, WAV schreiben |
sfx/loudness.py | BS.1770 integrierte Lautstärke (entspricht ffmpegs ebur128) |
sfx/cues/<video>.py | ein Cue-Sheet: das Sounddesign eines Videos |
python3 -m sfx --list # Generatoren mit Verwendungszweck
python3 -m sfx sfx/cues/<video>.py # Rendern → gemasterte WAV
Ein Cue-Sheet für ein neues Video schreiben
Erstellen Sie sfx/cues/<video>.py:
from sfx.synth import click, pop, whoosh, chime, riser, impact
TIMELINE = "timeline.json" # dieselbe Datei, die die Komposition liest
OUT = "public/sfx.wav"
TARGET_LUFS = -14.0 # optional, dies ist der Standardwert
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):tstammt immer austl, niemals eine eingegebene Zahl. Versätze von einem Beat (s1["exit"] + 0.05) sind in Ordnung.mix.typing(start, end, chars): ein humanisierter Tastendruck pro Zeichen.mix.put_stereo(t, left, right, gain): für Stereo-Quellen wiepad().- Das Mastering erfolgt automatisch: Die Engine soft-clipt und stellt die Verstärkung so ein, dass
TARGET_LUFSerreicht wird. Dabei wird der Antrieb nur so weit erhöht, wie nötig, um die echte Spitze ≤ −2 dBFS zu halten (Platz für AAC-Übersteuerungen, sodass die MP4 unter −1 dBTP bleibt). Ein Mix mit meist kurzen Hits und Stille dazwischen kann −14 nicht ohne Clipping erreichen; die Engine gibt dann leiser aus und meldet dies. Beheben Sie dies mit anhaltendem Sound (einem Pad, längeren Ausklängen), nicht durch Erzwingen. - Fügen Sie ein
"sfx"-Skript inpackage.jsonhinzu, das auf das neue Cue-Sheet verweist, oder führen Sie es direkt aus.
sfx/cues/effort.py ist ein vollständiges, reales Beispiel: 128 Sounds plus ein Pad-Bett.
Visuelles Ereignis → Sound
| Auf dem Bildschirm | Generator | Hinweise |
|---|---|---|
| Tippen | mix.typing() | Jitter ±4–6 ms, zufällige Verstärkung |
| Wort-/Zeileneinblendung | whoosh() + blip() auf dem Landepunkt | kurz, luftig |
| Badge, Chip, Callout erscheinen | pop() | Tonhöhe über eine Sequenz erhöhen |
| Steuerelement rastet ein | click() + blip() in der Tonart | |
| Skizze / Zeichnung | scratch() | |
| Aufbau | riser() | exakt auf dem Hit enden; ein großer Riser pro Video |
| Erfolg / Ankunft | chime() | Akkord in der Tonart |
| Hero-Übergang | whoosh() rein, impact() auf den Hit | ein Impact pro Video |
| Unter allem | pad() mit einem envelope() | unter dem Hero-Übergang absenken |
Akzentuieren, nicht erzählen: Nicht jede Bewegung braucht einen Sound. Kontinuierliche Bewegungen (Drift, Körnung, Rotation) bleiben stumm.
Pegel und Raum
- Hero-Hits: Verstärkung 0,25–0,35. Unterstützende Sounds: 0,08–0,2. Pad: ~0,03, unter dem Hero-Übergang um ~65 % abgesenkt.
- Panorama folgt der Bildschirmposition (−1 links … +1 rechts). Sequenzen streifen über das Stereofeld.
- Wählen Sie eine Tonart und halten Sie jeden tonalen Sound darin; die Tonhöhe steigt mit dem Fortschritt und löst sich beim Höhepunkt auf. (Im effort-Beispiel sind das Pad und die Schieberegler-Noten in D-Dur, aber die Pops und die Glocke in Szene 3 sind immer noch in C-Dur. Kopieren Sie das nicht.)
Einen Generator hinzufügen
Schreiben Sie eine Funktion in sfx/synth.py, die eine Liste von Floats bei SR zurückgibt. Fügen Sie den Verwendungszweck in die erste Docstring-Zeile ein; --list zeigt dies an. Verwenden Sie das modulweite random für Rauschen, damit die Renderings reproduzierbar bleiben. Bauen Sie auf bandnoise() für alles Luftige auf.
Überprüfen
npm run sfx # oder python3 -m sfx sfx/cues/<video>.py
npm run wave # out/wave.png + Lautstärke der WAV
npm run build
node scripts/wave.mjs out/effort.mp4 # Lautstärke dessen, was Sie tatsächlich posten
In out/wave.png ist jede Korallenlinie ein Timeline-Beat. Jeder Einschwingvorgang sollte auf einer Linie oder kurz danach sitzen. Ein Hit zwischen den Linien bedeutet eine fest codierte Zeit oder eine veraltete WAV. Übergeben Sie --timeline für ein Video mit eigener Timeline-Datei.
Optional: Generiertes Audio
Für Voiceover oder Sounds, die Sie nicht synthetisieren können, kann der ElevenLabs MCP (siehe .mcp.json.example) Dateien in public/ generieren. Platzieren Sie diese mit <Audio src={staticFile(...)} /> innerhalb eines <Sequence from={beat * FPS}>.