サウンドデザイン
多くのAI製モーショングラフィックスは無音です。音こそが作品を「プロの仕上がり」にします。
sfx/エンジンは、純粋なPython stdlibですべてのサウンドを合成します。サンプル不要、ライセンス不要、インストール不要、毎回同じ出力を生成します。映像と同じタイムラインJSONを読み込むため、音声が同期からずれることはありません。
全体の構成
| ファイル | 役割 |
|---|---|
sfx/synth.py | ジェネレーター:それぞれが48 kHzのモノラルサンプルを返す |
sfx/mix.py | Mix:音を時間軸に配置し、ラウドネスターゲットにマスタリングし、WAVを書き出す |
sfx/loudness.py | BS.1770 統合ラウドネス(ffmpegのebur128と同等) |
sfx/cues/<video>.py | キュース:1つのビデオのサウンドデザイン |
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):1文字につき1回の人間的なキーストロークを生成します。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 ms、ランダムゲイン |
| ワード/ラインの表示 | whoosh() + 着地時にblip() | 短く、空気感のある |
| バッジ、チップ、コールアウトのポップイン | pop() | シーケンス内でピッチを上げる |
| コントロールがステップにスナップ | click() + キー内のblip() | |
| スケッチ/描画 | scratch() | |
| ビルドアップ | riser() | ヒットの瞬間に正確に終了;ビデオにつき1つの大きなライザー |
| 成功/到着 | chime() | キー内のコード |
| ヒーロートランジション | インにwhoosh()、ヒットにimpact() | ビデオにつき1つのインパクト |
| 全体の下に | envelope()付きのpad() | ヒーロートランジションの下でダッキングする |
強調するものであって、ナレーションではありません。すべてのモーションに音が必要なわけではありません。連続的なモーション(ドリフト、グレイン、回転)は無音のままにします。
レベルと空間
- ヒーローヒット:ゲイン0.25–0.35。サポートサウンド:0.08–0.2。パッド:~0.03、ヒーロートランジションの下で約65%ダッキング。
- パンは画面上の位置に従います(-1 左 … +1 右)。シーケンスはステレオフィールドを掃引します。
- 1つのキーを選び、すべての音程のあるサウンドをそのキーに保ちます。進行に伴ってピッチが上昇し、決着点で解決します(effortの例ではパッドとスライダーのノートはDメジャーですが、シーン3のポップとチャイムはCメジャーのままです。それを真似しないでください)。
ジェネレーターの追加
sfx/synth.pyに関数を書き、SRのfloatのリストを返すようにします。最初のdocstring行にその用途を記述してください。--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/にファイルを生成できます。これらを<Sequence from={beat * FPS}>内の<Audio src={staticFile(...)} />で配置します。