Files
SoundGame/docs/asset-pipeline.md
T
xsl 24c526af68 feat(assets): SA3 音效补齐 radio 11+1 与 dungeon 13源+15pan(token 走 hf-mirror 实测可用)
- 模型 17/17 文件经镜像全量缓存(gated+token,直连本机不通;sfx.md/setup.md/asset-pipeline.md 镜像结论同步更新)
- 修复迁移遗留:stable_audio_3/models/ 源码子包被 .gitignore models/ 规则误伤丢失,从上游恢复并加白名单
- radio:arg_muffled/brake_screech 等叙事件落地(接线原有 L111/L154 零改动)+ phone_ring SYN→SA3 升级;dungeon 13 源 A/B 对齐(RMS 偏差 ≤0.3dB)全量同名替换,SYN 原版备份 tools/asset_fix_backup/sa3_ab_20260902/
- 关键玩法语义达标:echo 距离档 0.83<1.43<2.18<2.98s 严格递增;one-shot 真峰值全 ≤-3dBFS(解码回读法)
- check_all_assets 8 游戏全绿(radio 254/254、dungeon 303/303);playtest radio 221/dungeon 1610 全绿
2026-09-02 19:35:38 +08:00

6.6 KiB
Raw Blame History

SoundGame — 资产生产流水线

三类音频资产(对白 voice / 音效 sfx / 音乐 bgm)的完整生成与再生成流程。 所有脚本都在 app/src/<flavor>/gen/ 下,可随时复现全部资产。

通用约定:

  • GPU 任务必须串行:flock /home/ubuntu/SoundGame/.gpu.lock <命令> (TTS 与 Stable Audio 3 共用一块 RTX 3090)。
  • 环境脚本:source env.d/tts.sh(HF_HOME + hf-mirror 镜像)、 source env.d/android.sh(ffmpeg 在 PATH)、source env.d/sfx.sh(HF_TOKEN, 含密钥不入库,模板见 env.d/sfx.example.sh)。
  • 中间产物 wav 输出到各 gen 目录或 /tmp/slg/(不入库),最终 ogg 进 app/src/<flavor>/assets/audio/{bgm,voice,sfx}/。

1. TTS 对白(Qwen3-TTS CustomVoice)

引擎与用法详见 env.d/tts.md(CustomVoice 预置音色 + instruct 情感指令、 VoiceDesign、预置音色表)。流水线层面:

source env.d/tts.sh
# 《声命线》:48 条,6 条一批;输出 /tmp/slg/wav/,可传文件名参数只重生指定条目
flock .gpu.lock .venv-tts/bin/python app/src/storyline/gen/gen_voice.py [文件id...]
bash app/src/storyline/gen/convert.sh        # wav -> ogg 进 assets(含电台带通效果)

# 《声东击西》:33 条,逐条生成;输出 gen/wav_voice/
cd app/src/reflex/gen
flock /home/ubuntu/SoundGame/.gpu.lock /home/ubuntu/SoundGame/.venv-tts/bin/python gen_voice.py

# 《暗室》:41 条,8 条一批,统一峰值归一 0.89;输出 gen/wav/voice/
flock .gpu.lock .venv-tts/bin/python app/src/escape/gen/gen_voice.py
  • 每条生成后自动校验:时长 > 0.3s 且峰值 > 0.05,不达标直接 assert 失败。
  • 音色分配见各游戏文档(docs/games/*.md):旁白 Uncle_Fu、林晚 Serena、 猎狗 Dylan、小满/广播 Vivian;escape 全员 Uncle_Fu 靠 instruct 区分语气。
  • 已知不稳条目:reflex 的 num_6("六"),单独用 app/src/reflex/gen/fix_num6.py 重试生成(最多 4 次取时长合格的一条)。
  • 改台词只需改 gen_voice.py 里的 LINES 表再重跑;文件名即游戏代码引用的 id。

2. 音效(Stable Audio 3 + 程序合成)

2a. Stable Audio 3 small-sfx(主力,AI 生成)

source env.d/sfx.sh          # HF_TOKEN(gated 模型;本机直连不通,实测 gated+token 走 hf-mirror 可用,2026-09-02)
cd tools/stable-audio-3
uv run stable-audio --model small-sfx \
  -p "footsteps on gravel, slow walking" --duration 3 \
  -o /home/ubuntu/SoundGame/tools/samples/sfx_test.wav
  • 提示词必须英文;--duration ≤ 120s;--steps 默认 8;small 模型峰值显存 约 2.4GB。批量:-p "p1" "p2" --duration 3 5。
  • 首次使用需完成 gated 许可 + token 流程,详见 env.d/sfx.md / docs/setup.md。
  • 生成后用 ffmpeg 转 ogg 进 assets(规格见下文"资产规格")。

2b. 程序合成(保底/已在线方案,numpy + soundfile,纯 CPU 无需 GPU 锁)

游戏 脚本 输出
storyline app/src/storyline/gen/gen_sfx.py /tmp/slg/wav/sfx/(24kHz),convert.sh 转 ogg
reflex app/src/reflex/gen/gen_sfx_bgm.py gen/wav_sfx/(44.1kHz),convert_audio.py 转 ogg
escape app/src/escape/gen/gen_sfx.py gen/wav/sfx/(44.1kHz),手动 ffmpeg 转 ogg

当前线上资产全部为程序合成版;Stable Audio 3 环境已就绪,可按 README 待办逐步替换(替换时保持同名同目录,游戏代码零改动)。

3. BGM

3a. Mureka API(storyline / escape)

source env.d/mureka.sh       # MUREKA_API_KEY(含密钥不入库,模板 env.d/mureka.example.sh)
python3 tools/mureka_gen.py \
  --prompt "dark ambient horror game background music, low drone, ..., seamless loop, instrumental" \
  --out tools/mureka_out/storyline          # 提交→轮询→下载全部候选 flac
python3 tools/mureka_gen.py --task-id <id> --out tools/mureka_out/xxx   # 只轮询已有任务

后处理(两条已上线 BGM 相同参数,记录见 tools/mureka_log.md):

裁剪前 120s → 首尾 3s acrossfade(tri) 无缝循环化 → loudnorm I=-20/TP=-2/LRA=11
→ aresample 44.1kHz 立体声 → libvorbis -q:a 3

成品 117.00s,保持原文件名(dark_drone.ogg / drone.ogg)直接替换 assets。 原程序合成版备份在 tools/bgm_backup/。

成本参考:POST /v1/instrumental/generate 每条任务 $0.03(一次出 2 个 候选),项目预算上限 $0.50,已用 $0.06(2 条任务均一次成功)。每次调用必须 登记到 tools/mureka_log.md。

3b. 程序合成(reflex,有意保留)

app/src/reflex/gen/gen_sfx_bgm.py 的 make_pulse_bgm(bpm, bars=8): 心跳底鼓 + 低音脉冲音序 + 镲噪声 + 不协和 pad,100/128/152 BPM 三档各 8 小节。 玩法要求 BPM 随波次精确切换,AI 生成曲目 BPM 不精确会破坏机制,不要用 Mureka 替换 reflex 的 BGM。

资产规格

项 标准
容器/编码 ogg vorbis(-c:a libvorbis)
码率 voice/sfx -q:a 4;bgm -q:a 3(控制体积)
采样率 TTS 源 24kHz;程序合成 reflex/escape 44.1kHz;BGM 统一 aresample 44.1kHz
BGM 响度 loudnorm I=-20 LUFS / TP=-2 / LRA=11
BGM 循环 首尾交叉淡化无缝循环(Mureka 版 3s acrossfade;合成版 2s/4s 等功率交叉)
时长校验 每条 > 0.3s(数字语音等短条目除外,fix_num6 放宽到 0.3~1.5s 区间判定)
峰值校验 生成后 assert 峰值 > 0.05(防静音/失败条目混入)
峰值归一 storyline 合成 sfx 0.85;escape voice 0.89 / sfx 0.85(环境音 0.55-0.7);reflex voice 0.6 / sfx 0.7-0.95

目录约定

app/src/<flavor>/
  assets/audio/{bgm,voice,sfx}/*.ogg   # 最终资产(入库)
  gen/                                 # 生成脚本(入库)+ 中间 wav/字体(不入库)
tools/
  mureka_gen.py / mureka_log.md        # Mureka 调用脚本与台账(入库)
  mureka_out/  bgm_backup/  samples/   # 原始下载/备份/冒烟输出(不入库)
  stable-audio-3/                      # SFX 仓库(代码入库,.venv 不入库)

引擎按 id 前缀(bgm/ voice/ sfx/)分发到三个播放层,文件名即代码中的 引用 id;新增资产保持"同名替换、新增先改代码引用"的原则。

封面图

  • storyline:gen/make_cover.py(PIL,字体 /tmp/slg/NotoSansSC.ttf)
  • escape:gen/gen_cover.py(PIL,字体 gen/font.otf,不入库需自备)
  • reflex:封面无独立生成脚本(骨架渐变图 + 后期替换)
  • 统一输出 app/src/<flavor>/res/drawable/cover.png,1080x1920,文件名固定。