Files
SoundGame/docs/asset-pipeline.md
T
xsl f5eae6d37c test+docs: 「对白没读完」教训固化到框架(引擎回归测试 + 审计工具 + 四处文档)
- web/harness/engine-voice.test.mjs:引擎 voice 句尾保真三不变式(I1 源未耗尽不得结算 /
  I2 耗尽后排空窗才断开 / I3 零消耗走旧路径),可驱动 SPN mock 模拟真实消耗,
  15 断言全绿;防止未来重构回潮
- tools/audit_voice_tails.py:voice 资产尾部静默审计工具入库(含判读口径头注:
  尾静默紧=裁剪工艺≠剪字,句尾完整性验收在引擎层)
- 文档:HANDOVER(§1.4 回归清单 / §5.3 质量手段 / §6 已解决账)、roadmap §4
  新增全游戏「句尾完整性」真机听测项、asset-pipeline QC 口径、methodology §5
  度量盲区教训(媒体播完类问题转写层无效,必须真人耳测+引擎断言)、
  web-engine-api voice 自然结束语义
- 修正:结束判定改用 filter.sourcePosition(SimpleFilter 单调精确计数器);
  10 套件全绿(8 playtest + guide + engine-voice)
2026-09-06 16:08:18 +08:00

7.1 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 失败。
  • 尾部裁剪与句尾完整性(2026-09 口径):管线峰值归一 + 静音尾裁剪很紧, 绝大多数 voice 文件尾部静默 <120ms 属预期,不等于剪字。「句子是否读完」 的验收在引擎层(源消耗位置判定 + 300ms 排空窗,见 web/engine/audio.js _finishVoice 与 web/harness/engine-voice.test.mjs);资产侧怀疑剪字时用 ASR 抽查末字/语气词核对,量化工具 tools/audit_voice_tails.py(判读口径 见其文件头注)。
  • 音色分配见各游戏文档(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,文件名固定。