声音设计
大多数 AI 生成的动态图形都是无声的。声音才能让作品显得精致。
sfx/ 引擎使用纯 Python 标准库合成所有声音:无需样本、无需许可证、无需安装,每次运行输出相同。它读取与画面相同的时间线 JSON,因此声音不会不同步。
各部分如何协同工作
| 文件 | 角色 |
|---|---|
sfx/synth.py | 发生器:每个返回 48 kHz 的单声道采样 |
sfx/mix.py | Mix:在时间轴上放置声音,母带处理至目标响度,写入 WAV |
sfx/loudness.py | BS.1770 集成响度(与 ffmpeg 的 ebur128 一致) |
sfx/cues/<video>.py | 提示表:一个视频的声音设计 |
python3 -m sfx --list # 列出发生器及其用途
python3 -m sfx sfx/cues/<video>.py # 渲染 → 母带后的 WAV
为新的视频编写提示表
创建 sfx/cues/<video>.py:
from sfx.synth import click, pop, whoosh, chime, riser, impact
TIMELINE = "timeline.json" # 与合成读取的同一文件
OUT = "public/sfx.wav"
TARGET_LUFS = -14.0 # 可选,此为默认值
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始终来自tl,切勿使用硬编码数字。允许相对于节拍的偏移(例如s1["exit"] + 0.05)。mix.typing(start, end, chars):每个字符一次拟人化的按键声。mix.put_stereo(t, left, right, gain):用于立体声源,如pad()。- 母带处理是自动的:引擎进行软削波并设置增益以达到
TARGET_LUFS,仅增加必要的驱动量以保持真峰值 ≤ −2 dBFS(为 AAC 过冲留出余量,使 MP4 保持在 −1 dBTP 以下)。如果混音主要由短促的敲击声和静默组成,则无法在不压碎的情况下达到 −14;引擎会以较低音量输出并提示。修复方法是用持续的声音(如铺垫、较长的尾音),而不是强制提高增益。 - 在
package.json中添加指向新提示表的"sfx"脚本,或直接运行。
sfx/cues/effort.py 是一个完整且真实的示例:包含 128 种声音和一个铺垫音床。
视觉事件 → 声音
| 屏幕上的内容 | 发生器 | 备注 |
|---|---|---|
| 打字 | mix.typing() | 抖动 ±4–6 毫秒,随机增益 |
| 文字/行揭示 | whoosh() + blip()(在落地时) | 短促、空灵 |
| 徽章、芯片、标注弹出 | pop() | 在序列中逐渐提高音高 |
| 控件跳到某个步骤 | click() + blip()(在调内) | |
| 草图/手绘效果 | scratch() | |
| 蓄势 | riser() | 精确结束在击中点;每个视频一个大的升调音效 |
| 成功/到达 | chime() | 调内和弦 |
| 主角转场 | whoosh()(进入时),impact()(在击中点) | 每个视频一个冲击音效 |
| 所有内容下方 | pad() 配合 envelope() | 在主角转场时将其压低 |
强调,而非叙述:并非每个动作都需要声音。连续动作(漂移、颗粒、旋转)保持静音。
电平与空间
- 主角击中:增益 0.25–0.35。辅助声音:0.08–0.2。铺垫:约 0.03,在主角转场下压低约 65%。
- 声像跟随屏幕位置(−1 左 … +1 右)。序列扫过立体声场。
- 选择一个调性,保持所有有调性的声音都在该调内;音高随进度上升,并在高潮处解决。(在 effort 示例中,铺垫和滑条音符是 D 大调,但场景 3 的弹出和铃声仍然是 C 大调。不要照搬。)
添加发生器
在 sfx/synth.py 中编写一个函数,返回 SR 频率下的浮点数列表。将它的用途写在第一个文档字符串行中;--list 会显示它。使用模块级别的 random 来生成噪声,以便渲染可重现。对于任何空灵的声音,在 bandnoise() 的基础上构建。
验证
npm run sfx # 或 python3 -m sfx sfx/cues/<video>.py
npm run wave # out/wave.png + WAV 的响度
npm run build
node scripts/wave.mjs out/effort.mp4 # 实际发布的视频的响度
在 out/wave.png 中,每条珊瑚色线对应一个时间线节拍。每个瞬态应位于线上或略后。节拍之间的敲击声意味着硬编码的时间或过时的 WAV。对于有自己时间线文件的视频,传递 --timeline。
可选:生成音频
对于画外音或无法合成的声音,ElevenLabs MCP(参见 .mcp.json.example)可以将文件生成到 public/。使用 <Audio src={staticFile(...)} /> 将其放置在 <Sequence from={beat * FPS}> 内部。