feat(new8/batch1): 两款新 RPG《不眠频率》radio +《回声地窟》dungeon 全套(多agent流水线:策划→资产→开发→测试→玩家R1→修复→R2)
- 程序章程 research/new8/00-program.md(8款概念/回合协议/验收门/进度账) - 设计:docs/design/15-radio.md+voice-ids(132句,6状态9抉择4结局,电话滤波资产侧带通)/16-dungeon.md+voice-ids(125句,脉冲回声/7层seed生成/三档战斗/元进度遗产) - 资产:TTS 970文件(GPU flock串行)+ Mureka BGM 8首 $0.24(台账 mureka_log.md)+ 程序合成 sfx(含预pan侧墙/开口、phone_ring铃声、amb_cave呼吸层、turn_tick)+ 终检 check_all_assets 8游戏全绿 - radio:game.js+playtest 221断言(4结局矩阵/pendingChoice盲玩公平/RING两级看门狗/菜单循环/存档schema) - dungeon:game.js+playtest 1610断言(seed全网格回声一致性/学期零死/格挡80%/死亡归因真话/元进度/菜单冻结/上滑5段状态链) - 引擎修复:audio.js _playVoice 加载竞态补杀 token(R1真机71分钟语音风暴根因)+ 同id 3s窗口≥5次熔断保险丝 + gesture.js 长按boost事件门(+66行,9套件回归全绿) - 大厅:新增 radio/dungeon 卡片 + GAMES 数组 - harness:sessions/radio.mjs+dungeon.mjs(主线+压力双剧本)+ transcribe 注册 + voice_text.json 刷新(8游戏704句)+ 13号文档 §20 登记 - R1 存档:4人设评审×2游戏 + 真机试玩报告×2(radio 5/5愿意玩;dungeon 0/4→确诊风暴)+ synthesis×2 + 转写8份 - R2 修复:radio F1-F5(看门狗补挂/rep_low改词重录/真铃声/ASK续玩语义/链末跳过保护)+ dungeon D0-1..D1-6(风暴根治/回声真实性/密度下限+呼吸层/导航最短路/菜单关闭反馈/金币档同账本)+ 实现核对登记
This commit is contained in:
+335
-10
@@ -18,7 +18,7 @@
|
||||
|
||||
用法(GPU 必须串行):
|
||||
source env.d/tts.sh
|
||||
flock /home/ubuntu/SoundGame/.gpu.lock .venv-tts/bin/python tools/gen_voice_dual.py --game simon --gender both
|
||||
flock /home/xsl/SoundGame/.gpu.lock .venv-tts/bin/python tools/gen_voice_dual.py --game simon --gender both
|
||||
.venv-tts/bin/python tools/gen_voice_dual.py --game all --check # 无需 GPU
|
||||
source env.d/android.sh && .venv-tts/bin/python tools/gen_voice_dual.py --game all --convert
|
||||
"""
|
||||
@@ -28,10 +28,10 @@ import re
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
os.environ.setdefault("HF_HOME", "/home/ubuntu/SoundGame/models/hf")
|
||||
os.environ.setdefault("HF_HOME", "/home/xsl/SoundGame/models/hf")
|
||||
os.environ.setdefault("HF_ENDPOINT", "https://hf-mirror.com")
|
||||
|
||||
ROOT = "/home/ubuntu/SoundGame"
|
||||
ROOT = "/home/xsl/SoundGame"
|
||||
STAGING = os.path.join(ROOT, "tools", "gen_voice_dual_wav")
|
||||
SPLIT_LIMIT = 40 # 超过 40 字拆句
|
||||
GAP_SEC = 0.12 # 拼接句间静音
|
||||
@@ -50,6 +50,9 @@ SPEAKERS = {
|
||||
"rhythm": {"male": "Uncle_Fu", "female": "Vivian"},
|
||||
"space": {"male": "Uncle_Fu", "female": "Serena"},
|
||||
"rpg": {"male": "Uncle_Fu", "female": "Serena"},
|
||||
# radio 默认 speaker 仅作兜底;双性别行按 VOICE_SPK["radio"] 逐句覆盖(HOST/PROD 双配对)
|
||||
"radio": {"male": "Uncle_Fu", "female": "Serena"},
|
||||
"dungeon": {"male": "Uncle_Fu", "female": "Serena"},
|
||||
}
|
||||
|
||||
# instruct 对:(male, female)。None 表示两性别共用同一 instruct。
|
||||
@@ -600,6 +603,322 @@ RPG_NPC = [
|
||||
]
|
||||
NPCS = {"rpg": RPG_NPC}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- radio
|
||||
# 《不眠频率》批次 1(docs/design/15-radio-voice-ids.md,132 句:双性别 103 + 平铺 NPC 29)。
|
||||
# 台词全文由提取脚本逐字照抄 voice-ids 文档;双性别组分 HOST(主播)/ PROD(导播/系统)
|
||||
# 两个 speaker 配对(同性别双音色,VOICE_SPK 覆盖);NPC 单音色平铺(RADIO_NPC)+ 通话带通(NPC_BANDPASS)。
|
||||
_RADIO_HOST = _pair("低沉温和的中年男声,深夜电台主播,沉稳亲切,有磁性",
|
||||
"温暖清晰的年轻女声,深夜电台主播,沉稳亲切,不甜腻")
|
||||
_RADIO_PROD = _pair("贴近耳机的低语男声,年轻电台导播,冷静克制",
|
||||
"贴近耳机的低语女声,年轻电台导播,冷静克制")
|
||||
RADIO_HOST_LINES = [
|
||||
("b_open", _RADIO_HOST, "午夜零点,红灯亮起。欢迎回到《今夜不眠》。"),
|
||||
("b_rain", _RADIO_HOST, "雨下了一整晚。热线灯,马上就要亮了。"),
|
||||
("z1_song", _RADIO_HOST, "大爷别急,这首歌我也会。咱们一句一句来。"),
|
||||
("z1_ask", _RADIO_HOST, "大爷,路口有什么招牌?念一个给我听听。"),
|
||||
("z1_stay", _RADIO_HOST, "哪儿也不去。我陪你,这首歌还长着呢。"),
|
||||
("z1_pol", _RADIO_HOST, "大爷,我把您转给派出所,他们马上就到。"),
|
||||
("l1_go", _RADIO_HOST, "你妈等的不是奖杯,是你进门喊的那声妈。"),
|
||||
("l1_free", _RADIO_HOST, "退不退,都是你的自由。夜还长,慢慢想。"),
|
||||
("k1_lull", _RADIO_HOST, "闭上眼睛。风把云朵盖在被子上,月亮替你值夜班。"),
|
||||
("k1_ask", _RADIO_HOST, "睡不着的时候,心里是不是装着事?说给我听听。"),
|
||||
("k1_truth", _RADIO_HOST, "糖糖,记住,大人的事,不是小孩的错。一个字都不是。"),
|
||||
("k1_lie", _RADIO_HOST, "睡吧。明天一睁眼,什么都会好起来的。"),
|
||||
("w1_say", _RADIO_HOST, "四十分钟前,有个孩子问我们:爸妈摔门,是不是她的错。"),
|
||||
("w1_say2", _RADIO_HOST, "今天有位小朋友,还在等爸妈回家切蛋糕。"),
|
||||
("w1_air", _RADIO_HOST, "陈姐,别挂。让他接电话——全城都在听。"),
|
||||
("w1_priv", _RADIO_HOST, "这里只有我们俩了。想说就说,想哭就哭。"),
|
||||
("w1_home", _RADIO_HOST, "吵架隔着电话吵不赢。回家吧,蛋糕别放到坏了。"),
|
||||
("h1_talk", _RADIO_HOST, "说说她。开学第一天,她跟你说的头一句话,是什么?"),
|
||||
("h1_stop", _RADIO_HOST, "听我的,前面三公里有服务区。学费急,命更急。"),
|
||||
("d9_face", _RADIO_HOST, "师父,三年了。这句晚安,我替您播。各位,晚安。"),
|
||||
("d9_avoid", _RADIO_HOST, "各位,今晚到这里。明晚零点,今夜不眠,不见不散。"),
|
||||
("e1_a", _RADIO_HOST, "天亮了,热线灯还亮着。你们的晚安,是这座城市的心跳。"),
|
||||
("e1_b", _RADIO_HOST, "这把话筒,我总算接得住了。明晚零点,接着不眠。"),
|
||||
("e2_a", _RADIO_HOST, "今晚的热线烧得发烫。全城的失眠者,都被接住了。"),
|
||||
("e2_b", _RADIO_HOST, "只是路过留言机的时候,你还是没敢看它。红灯灭得很慢。"),
|
||||
("e3_a", _RADIO_HOST, "口碑掉了一夜。可那句迟到三年的晚安,今晚说出去了。"),
|
||||
("e3_b", _RADIO_HOST, "一个人的频道,也是频道。师父,收到了吗?"),
|
||||
("e4_a", _RADIO_HOST, "今夜不眠,静音收播。有的夜班,谁也接不住谁。"),
|
||||
("e4_b", _RADIO_HOST, "留言机还在闪。你没看它,它也没怪你。明晚再说吧。"),
|
||||
]
|
||||
|
||||
RADIO_PROD_LINES = [
|
||||
("gd_brief", _RADIO_PROD, "单击继续节目。请选择的时候,向左或向右滑。"),
|
||||
("gd_continue", _RADIO_PROD, "单击屏幕,继续。"),
|
||||
("gd_again", _RADIO_PROD, "还在吗?单击继续。"),
|
||||
("gd_invalid", _RADIO_PROD, "这个手势,直播间里用不上。"),
|
||||
("gd_ring1", _RADIO_PROD, "热线在闪。单击,接听。"),
|
||||
("gd_ring2", _RADIO_PROD, "灯还亮着,听众在等。单击,接听。"),
|
||||
("sys_help", _RADIO_PROD, "单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。"),
|
||||
("sys_menu", _RADIO_PROD, "菜单。单击切换选项,双击确认。"),
|
||||
("sys_menu_resume", _RADIO_PROD, "回到直播。"),
|
||||
("sys_menu_restart", _RADIO_PROD, "重新开始今晚。"),
|
||||
("sys_menu_lobby", _RADIO_PROD, "保存并回大厅。"),
|
||||
("sys_settings", _RADIO_PROD, "音色、语速、背景音乐,请回大厅修改。"),
|
||||
("sys_skip", _RADIO_PROD, "想跳过这句,单击就行。"),
|
||||
("sys_continue_ask", _RADIO_PROD, "检测到你的夜班记录。单击继续上次,右滑开始新的夜晚。"),
|
||||
("sys_new_night", _RADIO_PROD, "新的夜晚,开始了。"),
|
||||
("sys_saved", _RADIO_PROD, "进度已保存。"),
|
||||
("prog_c1", _RADIO_PROD, "现在是今晚第一通电话。"),
|
||||
("prog_c2", _RADIO_PROD, "现在是今晚第二通电话。"),
|
||||
("prog_c3", _RADIO_PROD, "现在是今晚第三通电话。"),
|
||||
("prog_c4", _RADIO_PROD, "现在是今晚第四通电话。"),
|
||||
("prog_c5", _RADIO_PROD, "现在是今晚第五通电话。"),
|
||||
("rep_high", _RADIO_PROD, "今晚的口碑,在听众里立住了。"),
|
||||
("rep_mid", _RADIO_PROD, "今晚的口碑,不好不坏。"),
|
||||
("rep_low", _RADIO_PROD, "今晚的口碑,还没立起来。"),
|
||||
("b_rule", _RADIO_PROD, "台里的规矩就一条:先接住人,再接电话。"),
|
||||
("b_goal", _RADIO_PROD, "今晚五通电话。说什么话,由你决定。"),
|
||||
("z1_ring_in", _RADIO_PROD, "一线来电。七十多岁,雨夜,走丢了。"),
|
||||
("p1", _RADIO_PROD, "老人在发抖。向左滑,陪他哼那首老歌。向右滑,冷静问他路口的路牌。"),
|
||||
("z1_sdone", _RADIO_PROD, "他稳住了。老歌,比路牌管用。"),
|
||||
("z1_adone", _RADIO_PROD, "他慌了。先接住情绪,再走流程。"),
|
||||
("p2", _RADIO_PROD, "雨声更密了。向左滑,留在原地陪他等巡逻队。向右滑,转接派出所。"),
|
||||
("z1_staye", _RADIO_PROD, "十五分钟后,巡逻灯停在路口。全城听见老人笑了。"),
|
||||
("z1_pole", _RADIO_PROD, "转接完成,地址报得清。干净利落。"),
|
||||
("z1_bade", _RADIO_PROD, "地址没报清就转接,雨又大。这通砸了。先稳人,再办事。"),
|
||||
("z1_ok1", _RADIO_PROD, "就这么接。记住这个手感。"),
|
||||
("l1_in", _RADIO_PROD, "二线来电。姑娘,声音很轻,像怕吵到谁。"),
|
||||
("p3", _RADIO_PROD, "向左滑,告诉她这趟车值得上。向右滑,把选择还给她。"),
|
||||
("l1_goe", _RADIO_PROD, "二线收工。这句话,值一个头条。"),
|
||||
("l1_freee", _RADIO_PROD, "她走了。没上车,也没退票。年轻人的夜,长了点。"),
|
||||
("k1_in", _RADIO_PROD, "三线来电。听呼吸,是个小孩。"),
|
||||
("p4", _RADIO_PROD, "向左滑,唱一段摇篮曲哄她睡。向右滑,轻轻问她怎么了。"),
|
||||
("k1_lulle", _RADIO_PROD, "睡着了。可你没听见,她在怕什么。"),
|
||||
("p5", _RADIO_PROD, "孩子的声音在抖。向左滑,告诉她这不是她的错。向右滑,答应她明天都会好。"),
|
||||
("k1_truthe", _RADIO_PROD, "蛋糕。记下来——四线的来电,可能跟这个家有关。"),
|
||||
("k1_liee", _RADIO_PROD, "你许了个兑不了的现。孩子睡了,谎留下了。"),
|
||||
("w1_in", _RADIO_PROD, "四线来电。户外风声大,是位大姐。"),
|
||||
("p6", _RADIO_PROD, "摔门声你听见了。向左滑,替孩子给她带句话。向右滑,劝她回家把话说开。"),
|
||||
("w1_saye", _RADIO_PROD, "她攥着电话,很久没出声。风都停了。"),
|
||||
("w1_say2e", _RADIO_PROD, "话递到了。就差最后一脚。"),
|
||||
("p7", _RADIO_PROD, "她的呼吸平了。向左滑,留在直播里,让全城等这个门铃。向右滑,换垫乐,私下陪她说完。"),
|
||||
("w1_aire", _RADIO_PROD, "电话那头,传来跑步声。今晚的高光时刻。"),
|
||||
("w1_prive", _RADIO_PROD, "灯牌是你亲手关的,故事还是完整的。稳。"),
|
||||
("w1_homee", _RADIO_PROD, "她往回走了。成不成,看他们自己的了。"),
|
||||
("h1_in", _RADIO_PROD, "五线来电。引擎声没停过——他在开车!"),
|
||||
("p8", _RADIO_PROD, "方向盘比话筒危险。向左滑,陪他聊聊闺女。向右滑,让他立刻进服务区。"),
|
||||
("h1_danger", _RADIO_PROD, "车道偏了——听!"),
|
||||
("h1_talke", _RADIO_PROD, "吓醒的,也算醒。这一趟,聊到家。"),
|
||||
("h1_stope", _RADIO_PROD, "他下了高速。单子黄了,人齐了。"),
|
||||
("h1_echo", _RADIO_PROD, "对了。头通电话那位大爷,就是这片被巡逻队找到的。这城市的夜,是接着的。"),
|
||||
("g9_in", _RADIO_PROD, "五线收工。垫乐起——零点档,还剩最后一段。"),
|
||||
("p9", _RADIO_PROD, "留言机红灯在闪,是老主播三年前没播完的那句晚安。向左滑,替他播出去。向右滑,照常收播。"),
|
||||
("d9_facee", _RADIO_PROD, "带子里其实只有电流声。但空着的那一秒,你说满了。"),
|
||||
("d9_avoide", _RADIO_PROD, "留言机的红灯,又多亮了一夜。"),
|
||||
("ep_lead", _RADIO_PROD, "收播了。导播间替你记了一笔账——今晚的人都怎样了。"),
|
||||
("ep_zhou_ok", _RADIO_PROD, "老周:孙子替他买了老花镜。回信说,谢谢那首歌。"),
|
||||
("ep_zhou_no", _RADIO_PROD, "老周在便利店屋檐下坐到天亮。没出事,也没人听见他的歌。"),
|
||||
("ep_deer_ok", _RADIO_PROD, "小鹿上车了。她妈后来打电话说,那声妈,比奖杯值钱。"),
|
||||
("ep_deer_no", _RADIO_PROD, "小鹿退了票。检票口的灯,记得那趟没上成的车。"),
|
||||
("ep_kid_ok", _RADIO_PROD, "糖糖家的门,这个月没再摔过。她说这是主播魔法。"),
|
||||
("ep_kid_no", _RADIO_PROD, "糖糖后来又打来过一次。还是那句:楼下在吵。"),
|
||||
("ep_wife_ok", _RADIO_PROD, "陈姐家切了蛋糕。那句「回来吧」,是我这晚听过最好的声音。"),
|
||||
("ep_wife_no", _RADIO_PROD, "陈姐在公车站坐到了末班车。那通电话,成了没寄出的信。"),
|
||||
("ep_han_ok", _RADIO_PROD, "老韩到家了。后视镜上,多了朵小红花。"),
|
||||
("ep_han_no", _RADIO_PROD, "老韩在服务区睡了一觉。学费齐了,那首歌没放成。"),
|
||||
]
|
||||
|
||||
RADIO_LINES = RADIO_HOST_LINES + RADIO_PROD_LINES
|
||||
|
||||
# NPC 单音色平铺(id, speaker, instruct, 台词)→ web/radio/audio/voice/<id>.*
|
||||
#(转码时按 15-radio.md §6.2 逐来电者带通,见 NPC_BANDPASS;staging 保留未滤波原声)。
|
||||
RADIO_NPC = [
|
||||
("z1_a", "Uncle_Fu", "苍老沙哑的男声,七十五岁,疲惫害怕,带着雨夜的颤抖", "主播……我,我找不到回家的路了。"),
|
||||
("z1_b", "Uncle_Fu", "苍老沙哑的男声,七十五岁,疲惫害怕,带着雨夜的颤抖", "雨太大了。我本来,只想买副老花镜。"),
|
||||
("z1_songo", "Uncle_Fu", "苍老沙哑的男声,七十五岁,疲惫害怕,带着雨夜的颤抖", "对,对!路口修车铺,天天放这首!"),
|
||||
("z1_asko", "Uncle_Fu", "苍老沙哑的男声,七十五岁,疲惫害怕,带着雨夜的颤抖", "招牌……雨糊住了,我眼花,看不清啊……"),
|
||||
("z1_stayo", "Uncle_Fu", "苍老沙哑的男声,七十五岁,疲惫害怕,带着雨夜的颤抖", "好,好。有人说话,我就不怕了。"),
|
||||
("z1_polo", "Uncle_Fu", "苍老沙哑的男声,七十五岁,疲惫害怕,带着雨夜的颤抖", "好,好。谢谢你啊,谢谢。"),
|
||||
("z1_bad", "Uncle_Fu", "苍老沙哑的男声,七十五岁,疲惫害怕,带着雨夜的颤抖", "说不清……我真是,说不清了……"),
|
||||
("l1_a", "Serena", "年轻女声,倔强,带着哭腔,疲惫", "主播,退票窗口十一点半就关了。我站这儿半小时了。"),
|
||||
("l1_b", "Serena", "年轻女声,倔强,带着哭腔,疲惫", "票是回去看我妈的。可我试镜又黄了,两手空空……"),
|
||||
("l1_goo", "Serena", "年轻女声,倔强,带着哭腔,疲惫", "呜……嗯。不退了,我现在就去检票。谢谢您。"),
|
||||
("l1_freeo", "Serena", "年轻女声,倔强,带着哭腔,疲惫", "嗯……我再想想。窗口要关了,我先过去了。"),
|
||||
("k1_a", "Vivian", "稚嫩童声,八岁小女孩,困倦,胆怯", "喂……有人吗?我睡不着。"),
|
||||
("k1_b", "Vivian", "稚嫩童声,八岁小女孩,困倦,胆怯", "家里灯关了。可是楼下,一直有声音。"),
|
||||
("k1_lullo", "Vivian", "稚嫩童声,八岁小女孩,困倦,胆怯", "月亮也睡了……那我也睡。"),
|
||||
("k1_asko", "Vivian", "稚嫩童声,八岁小女孩,困倦,胆怯", "爸爸妈妈摔门了。是不是……我做错了什么呀。"),
|
||||
("k1_trutho", "Vivian", "稚嫩童声,八岁小女孩,困倦,胆怯", "嗯……那我睡了。他们明天,会一起给我切蛋糕吗?"),
|
||||
("k1_lieo", "Vivian", "稚嫩童声,八岁小女孩,困倦,胆怯", "真的吗?拉钩。晚安……"),
|
||||
("w1_a", "Vivian", "疲惫沙哑的中年女声,四十五岁,强撑着平静", "主播,我就想找个人说说。我家那口子,今晚要出远门的活儿。"),
|
||||
("w1_b", "Vivian", "疲惫沙哑的中年女声,四十五岁,强撑着平静", "明天孩子生日!蛋糕都订好了,他人要今晚走!"),
|
||||
("w1_sayo", "Vivian", "疲惫沙哑的中年女声,四十五岁,强撑着平静", "什么?她还没睡?这么晚了,她还醒着?"),
|
||||
("w1_say2o", "Vivian", "疲惫沙哑的中年女声,四十五岁,强撑着平静", "蛋糕……是他订的。他嘴上不说,心里都记着。"),
|
||||
("w1_airo", "Vivian", "疲惫沙哑的中年女声,四十五岁,强撑着平静", "喂!回来吧!蛋糕要化了,孩子等着呢!"),
|
||||
("w1_privq", "Vivian", "疲惫沙哑的中年女声,四十五岁,强撑着平静", "呜……其实他订完蛋糕就跟我道歉了。是我,把门摔得太响。"),
|
||||
("w1_homeo", "Vivian", "疲惫沙哑的中年女声,四十五岁,强撑着平静", "嗯……我这就往回走。谢谢你,听我瞎嚷嚷。"),
|
||||
("h1_a", "Eric", "沙哑粗犷的中年男声,货车司机,困倦强撑", "主播,别挂。陪我说话,我的眼皮,沉得很。"),
|
||||
("h1_b", "Eric", "沙哑粗犷的中年男声,货车司机,困倦强撑", "跑完这趟,闺女的学费就齐了。雨刷都快刮不动了。"),
|
||||
("h1_talko", "Eric", "沙哑粗犷的中年男声,货车司机,困倦强撑", "她说,爸,我在学校给你赚了朵小红花。哈哈,哈哈……"),
|
||||
("h1_dango", "Eric", "沙哑粗犷的中年男声,货车司机,困倦强撑", "嘶——好险!前车急刹,我擦着护栏过去了。彻底醒了!"),
|
||||
("h1_stopo", "Eric", "沙哑粗犷的中年男声,货车司机,困倦强撑", "行,听你的。进了服务区,我给你们放首歌听。"),
|
||||
]
|
||||
|
||||
# ---------------------------------------------------------------- dungeon
|
||||
# 《回声地窟》批次 1(docs/design/16-dungeon-voice-ids.md,125 句 ×2 性别,无 NPC)。
|
||||
_DUN = _pair("低沉醇厚的男声,像探窟向导,沉稳有力",
|
||||
"平静清晰的女声,像探窟向导,沉稳可信")
|
||||
DUNGEON_LINES = [
|
||||
("intro_1", _DUN, "你在黑暗中醒来。这里是回声地窟。"),
|
||||
("intro_2", _DUN, "伸手不见五指,声音就是你的眼睛。"),
|
||||
("sys_continue_ask", _DUN, "检测到探窟记录。单击继续,右滑重新开始。"),
|
||||
("sys_new_run", _DUN, "新的探窟,开始了。"),
|
||||
("tut_d0", _DUN, "先学会用耳朵看。单击,发出声波脉冲。"),
|
||||
("tut_echo_come", _DUN, "听,回声回来了。"),
|
||||
("tut_front_rule", _DUN, "回声回来越快,墙就越近。"),
|
||||
("tut_side_rule", _DUN, "两侧的回声,发闷是墙,空旷是通路。"),
|
||||
("tut_walk", _DUN, "按住屏幕不放,你就往前走。"),
|
||||
("tut_turn", _DUN, "左滑右滑,可以转身。"),
|
||||
("tut_replay", _DUN, "双击,再发一次脉冲,不要钱。"),
|
||||
("tut_status", _DUN, "上滑,随时听自己的状态。"),
|
||||
("tut_menu", _DUN, "下滑,打开菜单。"),
|
||||
("tut_mob", _DUN, "前面有东西!放心,它伤不到你。"),
|
||||
("tut_parry", _DUN, "听到它的动静,双击格挡。"),
|
||||
("tut_parry_ok", _DUN, "挡住了!"),
|
||||
("tut_parry_miss", _DUN, "没挡住也别怕,它伤不到你。"),
|
||||
("tut_attack", _DUN, "现在单击,挥拳打它。"),
|
||||
("tut_kill", _DUN, "打中了,就是这样。"),
|
||||
("tut_stairs", _DUN, "铁器的回声,就是下潜的楼梯。"),
|
||||
("tut_stairs_on", _DUN, "楼梯就在脚下。单击,下潜。"),
|
||||
("tut_done", _DUN, "教学完了。地窟很深,小心走。"),
|
||||
("tut_skip", _DUN, "已跳过教学。单击脉冲,先听听四周。"),
|
||||
("sys_saved", _DUN, "进度已保存。"),
|
||||
("sys_menu", _DUN, "菜单。单击切换,双击确认。"),
|
||||
("sys_menu_resume", _DUN, "返回探索。"),
|
||||
("sys_menu_save", _DUN, "保存并回大厅。"),
|
||||
("sys_menu_help", _DUN, "重新听帮助。"),
|
||||
("sys_menu_restart", _DUN, "重新开始本周目。"),
|
||||
("help_explore", _DUN, "单击脉冲,按住前进,左右滑转身。双击再听,上滑状态,下滑菜单。"),
|
||||
("help_battle", _DUN, "敌人前摇声响时双击格挡,单击挥击。打不过就按住屏幕逃跑。"),
|
||||
("settings_hint", _DUN, "音色、语速、背景音乐,请回大厅修改。"),
|
||||
("st_floor_1", _DUN, "这是第一层。"),
|
||||
("st_floor_2", _DUN, "这是第二层。"),
|
||||
("st_floor_3", _DUN, "这是第三层。"),
|
||||
("st_floor_4", _DUN, "这是第四层。"),
|
||||
("st_floor_5", _DUN, "这是第五层。"),
|
||||
("st_floor_6", _DUN, "这是第六层。"),
|
||||
("st_floor_7", _DUN, "这是第七层。"),
|
||||
("st_hp_1", _DUN, "你毫发无损。"),
|
||||
("st_hp_2", _DUN, "你受了点伤。"),
|
||||
("st_hp_3", _DUN, "你伤势不轻。"),
|
||||
("st_hp_4", _DUN, "你命悬一线。"),
|
||||
("st_gold_1", _DUN, "口袋空空。"),
|
||||
("st_gold_2", _DUN, "有些积蓄。"),
|
||||
("st_gold_3", _DUN, "收获可观。"),
|
||||
("st_gold_4", _DUN, "腰包鼓鼓。"),
|
||||
("st_face_e", _DUN, "你面朝东。"),
|
||||
("st_face_s", _DUN, "你面朝南。"),
|
||||
("st_face_w", _DUN, "你面朝西。"),
|
||||
("st_face_n", _DUN, "你面朝北。"),
|
||||
("st_threat_none", _DUN, "附近很安静。"),
|
||||
("st_threat_left", _DUN, "左边有脚步声。"),
|
||||
("st_threat_right", _DUN, "右边有脚步声。"),
|
||||
("st_threat_both", _DUN, "两边都有动静。"),
|
||||
("st_best_1", _DUN, "你还没到过第三层。"),
|
||||
("st_best_2", _DUN, "你到过中层。"),
|
||||
("st_best_3", _DUN, "你到过最深层。"),
|
||||
("evt_coin_1", _DUN, "捡到几枚金币。"),
|
||||
("evt_coin_2", _DUN, "捡到一小把金币。"),
|
||||
("evt_coin_3", _DUN, "捡到一大袋金币。"),
|
||||
("evt_potion", _DUN, "一瓶药水,收进腰带。"),
|
||||
("evt_potion_full", _DUN, "腰带满了,药水留在原地。"),
|
||||
("evt_weapon", _DUN, "捡到更快的短刃,出手更狠了。"),
|
||||
("evt_armor", _DUN, "换上更硬的护甲,更抗打了。"),
|
||||
("evt_stairs_lock", _DUN, "楼梯被大东西堵住了,先赢它。"),
|
||||
("evt_descend", _DUN, "你顺着楼梯,往下潜。"),
|
||||
("btl_bat", _DUN, "盲眼蝠扑了过来!"),
|
||||
("btl_rat", _DUN, "窟鼠窜了出来!"),
|
||||
("btl_mush", _DUN, "蘑菇怪拦住了你!"),
|
||||
("btl_crab", _DUN, "窟主石甲蟹,就在你面前。"),
|
||||
("btl_crawler", _DUN, "绕行者贴上来了,小心背后!"),
|
||||
("btl_spider", _DUN, "洞蛛落地了!"),
|
||||
("btl_leech", _DUN, "窟主腐沼蟒,挡住了楼梯。"),
|
||||
("btl_guard", _DUN, "石像卫醒了!"),
|
||||
("btl_king", _DUN, "窟中之王,就在深处。"),
|
||||
("btl_parried", _DUN, "挡下了大半!"),
|
||||
("btl_behind", _DUN, "它在你背后,转身再挡。"),
|
||||
("btl_swish", _DUN, "挥空了,它不在你面前。"),
|
||||
("btl_no_turn", _DUN, "它贴着你不放,不用转身。"),
|
||||
("btl_kill", _DUN, "它倒下了。"),
|
||||
("btl_loot_1", _DUN, "战利不多。"),
|
||||
("btl_loot_2", _DUN, "小有收获。"),
|
||||
("btl_loot_3", _DUN, "大有收获。"),
|
||||
("btl_lvup", _DUN, "你的拳脚更利落了。"),
|
||||
("btl_flee", _DUN, "你甩掉了它,被抓了一下。"),
|
||||
("btl_flee_boss", _DUN, "退路塌了,跑不掉!"),
|
||||
("btl_half_crab", _DUN, "石甲蟹的甲裂了!"),
|
||||
("btl_half_leech", _DUN, "腐沼蟒暴怒了!"),
|
||||
("btl_half_king", _DUN, "王怒了,脚步更重了!"),
|
||||
("die_intro", _DUN, "你倒下了。"),
|
||||
("die_cause_1", _DUN, "你被小怪拖垮了。"),
|
||||
("die_cause_2", _DUN, "你没挡住那记重击。"),
|
||||
("die_cause_3", _DUN, "你背后的偷袭,要了命。"),
|
||||
("die_adv_1", _DUN, "下次,前摇声响时就双击。"),
|
||||
("die_adv_2", _DUN, "打不过,就按住屏幕逃跑。"),
|
||||
("die_adv_3", _DUN, "身后有动静,先转身。"),
|
||||
("gd_pulse", _DUN, "单击脉冲,听听四周。"),
|
||||
("gd_turn", _DUN, "轮到你了。单击挥击。"),
|
||||
("gd_invalid", _DUN, "这个操作在这里用不上。"),
|
||||
("stair_hint_a", _DUN, "找找铁器的回声,那是楼梯。"),
|
||||
("stair_hint_b", _DUN, "楼梯有大东西守着,先赢它。"),
|
||||
("stair_dir_e", _DUN, "楼梯在东边。"),
|
||||
("stair_dir_s", _DUN, "楼梯在南边。"),
|
||||
("stair_dir_w", _DUN, "楼梯在西边。"),
|
||||
("stair_dir_n", _DUN, "楼梯在北边。"),
|
||||
("nav_e", _DUN, "往东。"),
|
||||
("nav_s", _DUN, "往南。"),
|
||||
("nav_w", _DUN, "往西。"),
|
||||
("nav_n", _DUN, "往北。"),
|
||||
("nav_done", _DUN, "到了,就是这里。"),
|
||||
("meta_gold_1", _DUN, "这趟没带回什么。"),
|
||||
("meta_gold_2", _DUN, "这趟带回一些金币。"),
|
||||
("meta_gold_3", _DUN, "这趟满载而归。"),
|
||||
("settle_stronger", _DUN, "下个周目,你会更强。"),
|
||||
("settle_win_1", _DUN, "你捧起了地窟之心,通关了!"),
|
||||
("settle_win_2", _DUN, "黑暗的尽头,是光。"),
|
||||
("settle_again", _DUN, "单击,再探一次。"),
|
||||
("unlock_lead", _DUN, "攒够了新的遗产。"),
|
||||
("unlock_potion", _DUN, "解锁行囊药,开局自带一瓶。"),
|
||||
("unlock_vigor", _DUN, "解锁体魄,起始更强壮。"),
|
||||
("unlock_veil", _DUN, "解锁护符,每层头一下减半。"),
|
||||
("unlock_echo", _DUN, "解锁回声强化,侧向听得更远。"),
|
||||
("unlock_blade", _DUN, "解锁武器,开局带着短刃。"),
|
||||
("unlock_none", _DUN, "金币还不够,继续攒。"),
|
||||
]
|
||||
|
||||
NPCS = {"rpg": RPG_NPC, "radio": RADIO_NPC}
|
||||
|
||||
# radio 双性别行的逐句 speaker 覆盖(HOST=主播 / PROD=导播,同性别两个音色,
|
||||
# 见 15-radio-voice-ids.md 录制说明:同场景高频对话的两者永不同 speaker)。
|
||||
VOICE_SPK = {"radio": {}}
|
||||
VOICE_SPK["radio"].update({fid: ("Uncle_Fu", "Serena") for fid, _i, _t in RADIO_HOST_LINES})
|
||||
VOICE_SPK["radio"].update({fid: ("Dylan", "Vivian") for fid, _i, _t in RADIO_PROD_LINES})
|
||||
|
||||
# radio 来电者电话滤波(15-radio.md §6.2,全部 + 轻压缩):按 NPC id 前缀分档,
|
||||
# convert 转码时应用;staging wav 保留未滤波原声以便重滤/回退(宁窄勿脏)。
|
||||
NPC_BANDPASS = {
|
||||
"radio": [
|
||||
("z1_", "highpass=f=500,lowpass=f=2800"), # 老周:老旧手机,窄而颤抖
|
||||
("l1_", "highpass=f=400,lowpass=f=3400"), # 小鹿:车站手机,干净偏冷
|
||||
("k1_", "highpass=f=450,lowpass=f=3000"), # 糖糖:家用座机,小而近
|
||||
("w1_", "highpass=f=420,lowpass=f=3100"), # 陈姐:户外手机,风噪
|
||||
("h1_", "highpass=f=300,lowpass=f=3400"), # 老韩:车载蓝牙,宽而轰
|
||||
]
|
||||
}
|
||||
_NPC_ACOMP = "acompressor=threshold=-20dB:ratio=3:attack=10:release=150" # 轻压缩(全档)
|
||||
|
||||
|
||||
def npc_af(game, fid):
|
||||
"""NPC id -> ffmpeg -af 字符串(无滤波档返回 None)。"""
|
||||
for prefix, af in NPC_BANDPASS.get(game, []):
|
||||
if fid.startswith(prefix):
|
||||
return af + "," + _NPC_ACOMP
|
||||
return None
|
||||
|
||||
|
||||
GAMES = {
|
||||
"storyline": STORYLINE_LINES,
|
||||
"escape": ESCAPE_LINES,
|
||||
@@ -607,6 +926,8 @@ GAMES = {
|
||||
"rhythm": RHYTHM_LINES,
|
||||
"space": SPACE_LINES,
|
||||
"rpg": RPG_LINES,
|
||||
"radio": RADIO_LINES,
|
||||
"dungeon": DUNGEON_LINES,
|
||||
}
|
||||
|
||||
|
||||
@@ -684,18 +1005,20 @@ def generate(games, genders, only):
|
||||
for gender in genders:
|
||||
speaker = SPEAKERS[game][gender]
|
||||
ins_idx = 0 if gender == "male" else 1
|
||||
spk_map = VOICE_SPK.get(game, {})
|
||||
outdir = os.path.join(STAGING, game, gender)
|
||||
os.makedirs(outdir, exist_ok=True)
|
||||
|
||||
# 展开成 (fid, chunk_idx, n_chunks, text, instruct),跳过已完成
|
||||
# 展开成 (fid, chunk_idx, n_chunks, text, instruct, speaker),跳过已完成
|
||||
items = []
|
||||
for fid, ins_pair, text in lines:
|
||||
path = staging_path(game, gender, fid)
|
||||
if valid_wav(path):
|
||||
continue
|
||||
spk = spk_map.get(fid, (speaker, speaker))[ins_idx]
|
||||
chunks = split_text(text)
|
||||
for ci, ctext in enumerate(chunks):
|
||||
items.append((fid, ci, len(chunks), ctext, ins_pair[ins_idx]))
|
||||
items.append((fid, ci, len(chunks), ctext, ins_pair[ins_idx], spk))
|
||||
if not items:
|
||||
print(f"[{game}/{gender}] nothing to do", flush=True)
|
||||
continue
|
||||
@@ -711,11 +1034,11 @@ def generate(games, genders, only):
|
||||
wavs, sr = model.generate_custom_voice(
|
||||
text=[b[3] for b in batch],
|
||||
language=["Chinese"] * len(batch),
|
||||
speaker=[speaker] * len(batch),
|
||||
speaker=[b[5] for b in batch],
|
||||
instruct=[b[4] for b in batch],
|
||||
max_new_tokens=2048,
|
||||
)
|
||||
for (fid, ci, _n, _t, _i), wav in zip(batch, wavs):
|
||||
for (fid, ci, _n, _t, _i, _s), wav in zip(batch, wavs):
|
||||
results.setdefault(fid, {})[ci] = np.asarray(wav, dtype=np.float32)
|
||||
print(f"[{game}/{gender}] batch {bi + 1}/{len(queue)} ({kind}, n={len(batch)})", flush=True)
|
||||
|
||||
@@ -822,16 +1145,18 @@ def convert(games):
|
||||
subprocess.run(["ffmpeg", "-y", "-v", "error", "-i", src,
|
||||
"-ar", "44100", "-c:a", "aac", "-b:a", "128k", base + ".m4a"], check=True)
|
||||
total += 1
|
||||
# NPC 平铺(staging <game>/npc/ → audio/voice/<id>.*)
|
||||
# NPC 平铺(staging <game>/npc/ → audio/voice/<id>.*;radio 按 NPC_BANDPASS 带通)
|
||||
for fid, _sp, _ins, _t in NPCS.get(game, []):
|
||||
src = os.path.join(STAGING, game, "npc", fid + ".wav")
|
||||
if not os.path.exists(src):
|
||||
print(f"[convert] WARN missing {game}/npc/{fid}.wav")
|
||||
continue
|
||||
base = os.path.join(ROOT, "web", game, "audio", "voice", fid)
|
||||
subprocess.run(["ffmpeg", "-y", "-v", "error", "-i", src,
|
||||
af = npc_af(game, fid)
|
||||
af_args = ["-af", af] if af else []
|
||||
subprocess.run(["ffmpeg", "-y", "-v", "error", "-i", src, *af_args,
|
||||
"-ar", "44100", "-c:a", "libvorbis", "-q:a", "3", base + ".ogg"], check=True)
|
||||
subprocess.run(["ffmpeg", "-y", "-v", "error", "-i", src,
|
||||
subprocess.run(["ffmpeg", "-y", "-v", "error", "-i", src, *af_args,
|
||||
"-ar", "44100", "-c:a", "aac", "-b:a", "128k", base + ".m4a"], check=True)
|
||||
total += 1
|
||||
print(f"[convert] {game} done")
|
||||
|
||||
Reference in New Issue
Block a user