聲音設計
大多數 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,只會適度提升音量,確保 True Peak 不超過 −2 dBFS(為 AAC 超標預留空間,使 MP4 保持在 −1 dBTP 以下)。如果混音中大部分是短促的撞擊聲且之間有靜音,則無法在不壓縮的情況下達到 −14 LUFS;此時引擎會以較低的音量輸出並告知此情況。請使用持續的聲音(如 pad、較長的尾音)來解決問題,而不是強制提高增益。 - 在
package.json中新增指向新提示表的"sfx"腳本,或直接執行。
sfx/cues/effort.py 是一個完整且真實的範例:包含 128 種音效和一個 pad 背景音。
視覺事件 → 聲音
| 畫面事件 | 產生器 | 備註 |
|---|---|---|
| 打字 | mix.typing() | 抖動 ±4–6 毫秒,隨機增益 |
| 單詞 / 行顯示 | whoosh() + 結尾的 blip() | 短促、輕柔 |
| 徽章、晶片、提示框彈出 | pop() | 在一系列事件中提高音高 |
| 控制項對齊到步驟 | click() + 同調性的 blip() | |
| 草圖 / 繪製 | scratch() | |
| 鋪陳 | riser() | 確切結束在撞擊點上;每個影片一個大 riser |
| 成功 / 到達 | chime() | 同調性的和絃 |
| 主角轉場 | 進入時 whoosh(),撞擊點上 impact() | 每個影片一個 impact |
| 所有內容之下 | 帶有 envelope() 的 pad() | 在主角轉場下方降低音量 |
重點是強調,而不是敘述:並非每個動作都需要聲音。持續進行的動作(漂移、粒子、旋轉)保持無聲。
音量與空間
- 主角撞擊:增益 0.25–0.35。輔助音效:0.08–0.2。Pad:約 0.03,在主角轉場下方降低約 65%。
- 聲像跟隨螢幕上的位置(−1 左 … +1 右)。序列會橫掃整個立體聲場。
- 選擇一個調性,讓所有有調性的聲音都保持在該調性內;音高隨進度升高,並在回報處解決。(在 effort 範例中,pad 和滑塊音符是 D 大調,但場景 3 的 pop 和 chime 仍在 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}> 內部。