HTML5 重写:6 款游戏 + 用户系统 + 全局设置 + 5 轮迭代重构

- 新增 web/ 纯音频 HTML5 平台:共享引擎(手势/WebAudio 三层/设置/震动)、
  Node 零依赖服务器(静态 + 注册登录 + 进度存档 API)
- 游戏:剑与声(RPG 新作)、声命线、暗室(重设计)、音序回响(重设计)、
  节拍行者(跟随领奏重构)、盲航(同向闪避重构);声东击西删除
- 全局功能:男声/女声双套 TTS 资产、三档语速、BGM 音量三档、
  震动反馈语言(10 种震型)、服务器进度同步
- 资产:899 ogg + 899 m4a(Qwen3-TTS 双性别语音、Stable Audio 3 音效、
  Mureka BGM 4+3 首),终检零缺失
- 文档:docs/design/ 13 篇设计审查文档、docs/audio/ 资产管线、
  web/server/README.md API 文档
This commit is contained in:
SoundGame Dev
2026-08-29 21:55:44 +08:00
parent f0f0a9b50e
commit cc0f8c008d
1885 changed files with 11246 additions and 5 deletions
+83
View File
@@ -0,0 +1,83 @@
"""盲航(space)TTS 对白批量生成:教学旁白 + 游戏内提示 + 数字播报。
用法:flock /home/ubuntu/SoundGame/.gpu.lock .venv-tts/bin/python gen_voice.py
输出 24kHz wav 到 web/space/audio/build/voice/,随后由 ffmpeg 转 ogg/m4a。
"""
import os
os.environ.setdefault("HF_HOME", "/home/ubuntu/SoundGame/models/hf")
os.environ.setdefault("HF_ENDPOINT", "https://hf-mirror.com")
import numpy as np
import soundfile as sf
import torch
from qwen_tts import Qwen3TTSModel
OUT = "/home/ubuntu/SoundGame/web/space/audio/build/voice"
os.makedirs(OUT, exist_ok=True)
CALM = "冷静、沉稳的飞船导航合成语音,吐字清晰"
URGENT = "急促、紧张的警报语气,音量偏大"
# (文件名, 文本, instruct)
LINES = [
("intro1", "欢迎登舰,飞行员。这里是盲航号——没有舷窗,没有仪表,你的双眼已经没用了。从现在起,只靠耳朵飞。", CALM),
("intro2", "陨石会从四个方向逼近。呼啸声在左边,就向右滑;在右边,就向左滑;上方传来尖锐呼啸,就向下滑;下方传来低沉轰隆,就向上滑。永远朝声音的反面躲。", CALM),
("intro3", "单击屏幕,引擎加速,可以冲过空隙;双击展开护盾,挡下一次撞击,之后三十秒才能再次充能。警报声越响,陨石越近。", CALM),
("intro4", "船体只能承受三次撞击。教学完毕,盲航开始。祝你好运。", CALM),
("hit1", "船体受损!还能承受两次撞击。", URGENT),
("hit2", "船体严重受损!再挨一次就完了!", URGENT),
("shield_block", "护盾挡下了撞击。", CALM),
("shield_ready", "护盾充能完毕。", CALM),
("gameover", "飞船坠毁。", "沉重、惋惜的语气"),
("restart", "单击屏幕,再次启航。", CALM),
("n_time", "存活时间", CALM),
("n_sec", "秒", CALM),
("n_dodge", "共躲过陨石", CALM),
("n_ge", "颗", CALM),
("n_milestone", "已躲过", CALM),
("num0", "零", CALM),
("num1", "一", CALM),
("num2", "二", CALM),
("num3", "三", CALM),
("num4", "四", CALM),
("num5", "五", CALM),
("num6", "六", CALM),
("num7", "七", CALM),
("num8", "八", CALM),
("num9", "九", CALM),
("num_shi", "十", CALM),
("num_bai", "百", CALM),
]
model = Qwen3TTSModel.from_pretrained(
"Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice",
device_map="cuda:0",
dtype=torch.bfloat16,
)
print("model loaded")
# 已有非空 wav 的行跳过(支持失败续跑)
todo = [(n, t, i) for n, t, i in LINES
if not (os.path.exists(f"{OUT}/{n}.wav") and os.path.getsize(f"{OUT}/{n}.wav") > 1024)]
print(f"todo: {len(todo)} / {len(LINES)}")
BATCH = 6 # 小批量,失败时损失可控
for i in range(0, len(todo), BATCH):
chunk = todo[i:i + BATCH]
wavs, sr = model.generate_custom_voice(
text=[c[1] for c in chunk],
language=["Chinese"] * len(chunk),
speaker=["Serena"] * len(chunk),
instruct=[c[2] for c in chunk],
)
for (name, _, _), wav in zip(chunk, wavs):
path = f"{OUT}/{name}.wav"
sf.write(path, wav, sr)
dur = len(wav) / sr
peak = float(np.max(np.abs(wav)))
print(f"{name}: {dur:.2f}s peak={peak:.3f}")
assert dur > 0.3, f"{name} too short ({dur:.2f}s)"
assert peak > 0.05, f"{name} too quiet (peak={peak:.3f})"
print("ALL_DONE")