- 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)
143 lines
7.1 KiB
Markdown
143 lines
7.1 KiB
Markdown
# 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、预置音色表)。流水线层面:
|
||
|
||
```bash
|
||
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 生成)
|
||
|
||
```bash
|
||
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)
|
||
|
||
```bash
|
||
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,文件名固定。
|