Conception sonore
La plupart des graphiques animés générés par IA sont silencieux. Le son est ce qui donne l'impression qu'une production est aboutie.
Le moteur sfx/ synthétise tous les sons en pur Python sans dépendances externes : pas d'échantillons, pas de licences, pas d'installations, même résultat à chaque exécution. Il lit le même fichier JSON de timeline que l'image, ce qui garantit que le son ne peut pas se désynchroniser.
Comment tout s'assemble
| Fichier | Rôle |
|---|---|
sfx/synth.py | générateurs : chacun renvoie des échantillons mono à 48 kHz |
sfx/mix.py | Mix : placer les sons dans le temps, masteriser vers un objectif de volume sonore, écrire un fichier WAV |
sfx/loudness.py | Volume sonore intégré BS.1770 (identique à ebur128 de ffmpeg) |
sfx/cues/<video>.py | une feuille de repérage : la conception sonore d'une vidéo |
python3 -m sfx --list # générateurs avec leur utilité
python3 -m sfx sfx/cues/<video>.py # rendu → WAV masterisé
Créer une feuille de repérage pour une nouvelle vidéo
Créez sfx/cues/<video>.py :
from sfx.synth import click, pop, whoosh, chime, riser, impact
TIMELINE = "timeline.json" # le même fichier que la composition lit
OUT = "public/sfx.wav"
TARGET_LUFS = -14.0 # optionnel, c'est la valeur par défaut
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):tprovient toujours detl, jamais d'un nombre tapé. Les décalages par rapport à un temps (s1["exit"] + 0.05) sont autorisés.mix.typing(start, end, chars): une frappe de touche humanisée par caractère.mix.put_stereo(t, left, right, gain): pour les sources stéréo commepad().- Le mastering est automatique : le moteur écrête doucement et ajuste le gain pour atteindre
TARGET_LUFS, en augmentant la puissance uniquement ce qu'il faut pour que le pic réel reste ≤ −2 dBFS (marge pour le dépassement AAC, de sorte que le MP4 reste sous −1 dBTP). Un mix composé principalement de bruits secs avec du silence entre eux ne peut pas atteindre −14 sans écrasement ; le moteur sort alors plus faiblement et le signale. Corrigez avec un son continu (une nappe, des queues plus longues), pas en forçant. - Ajoutez un script
"sfx"danspackage.jsonpointant vers la nouvelle feuille de repérage, ou exécutez-la directement.
sfx/cues/effort.py est un exemple réel complet : 128 sons plus une nappe de fond.
Événement visuel → son
| À l'écran | Générateur | Notes |
|---|---|---|
| Saisie | mix.typing() | gigue ±4–6 ms, gain aléatoire |
| Révélation d'un mot / d'une ligne | whoosh() + blip() sur l'atterrissage | court, aérien |
| Badge, puce, appel apparaît | pop() | augmenter la hauteur sur une séquence |
| Contrôle se verrouille sur un pas | click() + blip() dans la tonalité | |
| Esquisse / dessin | scratch() | |
| Accumulation | riser() | se termine exactement sur le temps fort ; un grand riser par vidéo |
| Succès / arrivée | chime() | accord dans la tonalité |
| Transition héroïque | whoosh() à l'entrée, impact() sur le temps fort | un impact par vidéo |
| En dessous de tout | pad() avec une envelope() | l'atténuer sous la transition héroïque |
Accentuez, ne racontez pas : tous les mouvements n'ont pas besoin d'un son. Les mouvements continus (dérive, grain, rotation) restent silencieux.
Niveaux et espace
- Temps forts héroïques : gain 0,25–0,35. Sons d'accompagnement : 0,08–0,2. Nappe : ~0,03, atténuée d'environ 65 % sous la transition héroïque.
- Le panoramique suit la position à l'écran (−1 gauche … +1 droite). Les séquences balayent le champ stéréo.
- Choisissez une tonalité et gardez tous les sons tonaux dedans ; la hauteur augmente avec la progression et se résout sur la récompense. (Dans l'exemple effort, la nappe et les notes du slider sont en ré majeur, mais les pops de la scène 3 et le chime sont encore en do majeur. Ne copiez pas cela.)
Ajouter un générateur
Écrivez une fonction dans sfx/synth.py qui renvoie une liste de flottants à SR. Mettez son utilité dans la première ligne de docstring ; --list l'affiche. Utilisez le random au niveau du module pour le bruit afin que les rendus restent reproductibles. Utilisez bandnoise() pour tout ce qui est aérien.
Vérifier
npm run sfx # ou python3 -m sfx sfx/cues/<video>.py
npm run wave # out/wave.png + volume sonore du WAV
npm run build
node scripts/wave.mjs out/effort.mp4 # volume sonore de ce que vous publierez réellement
Dans out/wave.png, chaque ligne corail est un temps de la timeline. Chaque transitoire doit se situer sur une ligne ou juste après. Un temps fort entre les lignes signifie une heure codée en dur ou un WAV obsolète. Utilisez --timeline pour une vidéo avec son propre fichier de timeline.
Optionnel : audio généré
Pour les voix off ou les sons que vous ne pouvez pas synthétiser, le MCP ElevenLabs (voir .mcp.json.example) peut générer des fichiers dans public/. Placez-les avec <Audio src={staticFile(...)} /> à l'intérieur d'un <Sequence from={beat * FPS}>.