fix(space): 盲航刺耳音效重制 + 状态/反馈盲区修复(玩家代理取证迭代)
- F1 程序合成重制5件刺耳音效(alarm≥3kHz能量82%→0,全件≤-3.4dBFS)+新增挥空音 - F2 P0:撞击方位语音防顶断补播(保护期结束死亡窗 hit_* 零毫秒被杀根因) - F3 保护期教学句7连互截去重;F4 新句 protect_soon 50s预告;F5 boost_not_ready 冷却拒绝回音;F6 激活补CONFIRM震;F7 whiff - 验证:playtest 172/172 + guide 122 + 终检全绿 + 14游戏回归零回退 + live两轮复跑坐实 - 档案 research/space-polish/round1-2(取证+修复证据);04号文档追加R5修订记录
This commit is contained in:
@@ -0,0 +1,250 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""《盲航》程序合成音效重制(space polish R5,research/space-polish/round1 取证结论)。
|
||||
|
||||
重制 5 件 + 新增 1 件(落 web/space/audio/sfx/):
|
||||
alarm 全程最高暴露(每颗陨石两遍)→ 低谐波电子双音,5kHz 以上近零能量
|
||||
dodge 高频嘶声(-2.30dBFS / 63.6%≥3kHz)→ 中频气声扫频
|
||||
shield_block 保护期每 4s 一响 + 真峰值超标 → 柔和电涟波
|
||||
boost 真峰值 -2.70dBFS / 53%≥3kHz → 低频引擎冲刺
|
||||
shield 真峰值 -2.90dBFS 超标 → 上升 shimmer 重制
|
||||
whiff 新增:挥空/滑错方向的轻反馈(归因真话:无效手势有回音)
|
||||
|
||||
one-shot QC(同 gen_cipher_syn.py 标准):去直流 + 首尾 8ms cos² + 真峰值 ≤-3dBFS
|
||||
(编码后 ebur128 实测,MDCT 过冲超限自动降档重编)。
|
||||
|
||||
用法:.venv-tts/bin/python tools/gen_space_syn.py
|
||||
"""
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
import numpy as np
|
||||
import soundfile as sf
|
||||
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
FFMPEG = os.path.join(ROOT, "tools", "bin", "ffmpeg")
|
||||
OUT = os.path.join(ROOT, "tools", "gen_space_syn_wav")
|
||||
DST = os.path.join(ROOT, "web", "space", "audio", "sfx")
|
||||
os.makedirs(OUT, exist_ok=True)
|
||||
os.makedirs(DST, exist_ok=True)
|
||||
|
||||
sr = 44100
|
||||
ONE_SHOT_TP_CEIL = -3.0
|
||||
SAMPLE_PEAK = 0.66
|
||||
FADE_N = int(sr * 0.008)
|
||||
|
||||
_rng = np.random.default_rng(20260914)
|
||||
|
||||
|
||||
def dc(y):
|
||||
return y - np.mean(y)
|
||||
|
||||
|
||||
def fade(y):
|
||||
w = np.sin(np.linspace(0, np.pi / 2, FADE_N)) ** 2
|
||||
y = y.copy()
|
||||
y[:FADE_N] *= w
|
||||
y[-FADE_N:] *= w[::-1]
|
||||
return y
|
||||
|
||||
|
||||
def norm(y, peak=SAMPLE_PEAK):
|
||||
m = np.max(np.abs(y))
|
||||
return (y / m * peak) if m > 0 else y
|
||||
|
||||
|
||||
def stereo(y):
|
||||
return np.stack([y, y], axis=1).astype(np.float32)
|
||||
|
||||
|
||||
def bp_noise(n, lo, hi):
|
||||
x = _rng.standard_normal(n)
|
||||
X = np.fft.rfft(x)
|
||||
freqs = np.fft.rfftfreq(n, 1 / sr)
|
||||
mask = ((freqs >= lo) & (freqs <= hi)).astype(float)
|
||||
y = np.fft.irfft(X * mask, n)
|
||||
return y / (np.max(np.abs(y)) + 1e-9)
|
||||
|
||||
|
||||
def sweep_noise(n, f_lo, f_hi, bw=900):
|
||||
"""质心随时间线性扫动的带通噪声(气声/扫频体感,无离散高频谐波)。"""
|
||||
x = _rng.standard_normal(n)
|
||||
X = np.fft.rfft(x)
|
||||
freqs = np.fft.rfftfreq(n, 1 / sr)
|
||||
t = np.arange(n) / sr
|
||||
center = f_lo + (f_hi - f_lo) * np.minimum(t / (n / sr), 1.0)
|
||||
# 时变带通:按块扫中心频率
|
||||
y = np.zeros(n)
|
||||
blk = int(sr * 0.02)
|
||||
for s in range(0, n, blk):
|
||||
e = min(n, s + blk)
|
||||
c = center[s:e].mean()
|
||||
m = ((freqs >= max(20, c - bw)) & (freqs <= c + bw)).astype(float)
|
||||
seg = np.fft.irfft(X * m, n)
|
||||
y[s:e] = seg[s:e]
|
||||
m = np.max(np.abs(y))
|
||||
return y / m if m > 0 else y
|
||||
|
||||
|
||||
def tone(n, f, decay=0.0, harm=()):
|
||||
t = np.arange(n) / sr
|
||||
y = np.sin(2 * np.pi * f * t)
|
||||
for h, g in harm:
|
||||
y += g * np.sin(2 * np.pi * f * h * t)
|
||||
if decay:
|
||||
y *= np.exp(-t * decay)
|
||||
return y
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- one-shot 渲染
|
||||
def r_alarm(): # 1.0s 电子双音警报:700/560Hz 圆角短促 beep×2,谐波收敛 <2.5kHz
|
||||
n = int(sr * 1.0)
|
||||
y = np.zeros(n)
|
||||
for st, f in [(0.05, 700), (0.42, 560)]:
|
||||
L = int(sr * 0.20)
|
||||
tt = np.arange(L) / sr
|
||||
beep = np.sin(2 * np.pi * f * tt) + 0.22 * np.sin(2 * np.pi * f * 2 * tt) \
|
||||
+ 0.06 * np.sin(2 * np.pi * f * 3 * tt)
|
||||
beep *= (1 - np.exp(-tt * 250)) * np.exp(-np.maximum(tt - 0.12, 0) * 60)
|
||||
beep += bp_noise(L, 300, 2200) * np.exp(-tt * 90) * 0.10 # 电子毛边,低带
|
||||
y[int(st * sr):int(st * sr) + L] += beep
|
||||
return dc(y)
|
||||
|
||||
|
||||
def r_dodge(): # 1.2s 闪避成功:500→2100Hz 气声扫 + 一记软木鱼中频确认
|
||||
n = int(sr * 1.2)
|
||||
t = np.arange(n) / sr
|
||||
y = sweep_noise(n, 500, 2100, bw=1000)
|
||||
env = (1 - np.exp(-t * 120)) * np.exp(-np.maximum(t - 0.55, 0) * 7)
|
||||
y = y * env * 0.8
|
||||
L = int(sr * 0.16)
|
||||
tt = np.arange(L) / sr
|
||||
knock = np.sin(2 * np.pi * 740 * tt) * np.exp(-tt * 45)
|
||||
knock += bp_noise(L, 400, 1800) * np.exp(-tt * 70) * 0.5
|
||||
y[int(0.06 * sr):int(0.06 * sr) + L] += knock * 0.5
|
||||
return dc(y)
|
||||
|
||||
|
||||
def r_shield_block(): # 1.5s 护盾吸收:250-2300 电涟噪声衰减 + 150Hz 低频顿 + 弱 520Hz 余振
|
||||
n = int(sr * 1.5)
|
||||
t = np.arange(n) / sr
|
||||
rip = bp_noise(n, 250, 2300) * np.exp(-t * 4.5)
|
||||
rip *= 1 + 0.5 * np.sin(2 * np.pi * 11 * t) * np.exp(-t * 3) # 涟波调制
|
||||
th = np.sin(2 * np.pi * 150 * t) * np.exp(-t * 18) * 0.6
|
||||
ring = np.sin(2 * np.pi * 520 * t) * np.exp(-t * 10) * 0.18
|
||||
y = (rip * 0.7 + th + ring) * (1 - np.exp(-t * 300))
|
||||
return dc(y)
|
||||
|
||||
|
||||
def r_boost(): # 1.8s 引擎冲刺:55-90Hz 低哼 + 150-850Hz 推进噪声渐强,暗频谱
|
||||
n = int(sr * 1.8)
|
||||
t = np.arange(n) / sr
|
||||
rum = np.sin(2 * np.pi * 62 * t) + 0.5 * np.sin(2 * np.pi * 93 * t)
|
||||
rum *= (1 - np.exp(-t * 30)) * np.exp(-np.maximum(t - 1.2, 0) * 4)
|
||||
thrust = sweep_noise(n, 180, 850, bw=500)
|
||||
env = np.minimum(1, t / 0.5) * np.exp(-np.maximum(t - 1.1, 0) * 5)
|
||||
growl = bp_noise(n, 100, 500) * 0.5 * env
|
||||
y = rum * 0.55 + (thrust * 0.55 + growl) * env
|
||||
return dc(y)
|
||||
|
||||
|
||||
def r_shield(): # 1.5s 护盾展开:320→980Hz 正弦爬升 + 轻 shimmer 噪声,谐波 <4kHz
|
||||
n = int(sr * 1.5)
|
||||
t = np.arange(n) / sr
|
||||
f0 = 320 + (980 - 320) * np.minimum(t / 1.0, 1.0) ** 1.4
|
||||
ph = 2 * np.pi * np.cumsum(f0) / sr
|
||||
y = np.sin(ph) * 0.7 + 0.15 * np.sin(2 * ph)
|
||||
shim = bp_noise(n, 900, 3600) * 0.10 * np.minimum(1, t / 0.3)
|
||||
env = (1 - np.exp(-t * 60)) * np.exp(-np.maximum(t - 1.05, 0) * 8)
|
||||
return dc((y + shim) * env)
|
||||
|
||||
|
||||
def r_whiff(): # 0.4s 挥空轻反馈:150-900Hz 低带噪声叹 + 200Hz 软顿(设计音量低,game 层 0.55)
|
||||
n = int(sr * 0.4)
|
||||
t = np.arange(n) / sr
|
||||
y = bp_noise(n, 150, 900) * np.exp(-t * 14) * 0.7
|
||||
y += np.sin(2 * np.pi * 200 * t) * np.exp(-t * 30) * 0.5
|
||||
return dc(y * (1 - np.exp(-t * 400)))
|
||||
|
||||
|
||||
ONE_SHOTS = {
|
||||
"alarm": r_alarm,
|
||||
"dodge": r_dodge,
|
||||
"shield_block": r_shield_block,
|
||||
"boost": r_boost,
|
||||
"shield": r_shield,
|
||||
"whiff": r_whiff,
|
||||
}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- 编码 + 真峰值校验
|
||||
def encode(wav_path, base):
|
||||
for args, ext in [(["-c:a", "libvorbis", "-q:a", "3"], ".ogg"),
|
||||
(["-c:a", "aac", "-b:a", "128k"], ".m4a")]:
|
||||
subprocess.run([FFMPEG, "-y", "-v", "error", "-i", wav_path,
|
||||
"-ar", "44100", *args, base + ext], check=True)
|
||||
|
||||
|
||||
def true_peak_db(path):
|
||||
r = subprocess.run([FFMPEG, "-i", path, "-af", "ebur128=peak=true", "-f", "null", "-"],
|
||||
capture_output=True, text=True)
|
||||
peaks = [float(line.split("Peak:")[1].strip().split()[0])
|
||||
for line in r.stderr.splitlines() if "Peak:" in line and "dBFS" in line]
|
||||
return max(peaks) if peaks else float("nan")
|
||||
|
||||
|
||||
def check_tp(base):
|
||||
return max(true_peak_db(base + ".ogg"), true_peak_db(base + ".m4a"))
|
||||
|
||||
|
||||
def spectral_report(base):
|
||||
"""编码后解码回读:≥3kHz/≥5kHz 能量占比(刺耳复测口径,与取证轮一致)。"""
|
||||
r = subprocess.run([FFMPEG, "-v", "error", "-i", base + ".ogg", "-f", "f32le", "-ac", "1", "-ar", "44100", "-"],
|
||||
capture_output=True)
|
||||
y = np.frombuffer(r.stdout, dtype=np.float32)
|
||||
Y = np.abs(np.fft.rfft(y * np.hanning(len(y))))
|
||||
fr = np.fft.rfftfreq(len(y), 1 / sr)
|
||||
e = Y ** 2
|
||||
tot = e.sum() + 1e-12
|
||||
return e[fr >= 3000].sum() / tot * 100, e[fr >= 5000].sum() / tot * 100, fr[np.argmax(Y)] / 1000
|
||||
|
||||
|
||||
def main():
|
||||
report = []
|
||||
problems = []
|
||||
for fid, fn in ONE_SHOTS.items():
|
||||
wav_path = os.path.join(OUT, fid + ".wav")
|
||||
base = os.path.join(DST, fid)
|
||||
y = fade(norm(dc(fn())))
|
||||
sf.write(wav_path, stereo(y), sr)
|
||||
encode(wav_path, base)
|
||||
tp = check_tp(base)
|
||||
if tp > ONE_SHOT_TP_CEIL:
|
||||
gain = 10 ** ((ONE_SHOT_TP_CEIL - tp) / 20) * 0.98
|
||||
sf.write(wav_path, stereo(y * gain), sr)
|
||||
encode(wav_path, base)
|
||||
tp2 = check_tp(base)
|
||||
report.append((fid, len(y) / sr, tp, f"降 {20 * np.log10(gain):.1f}dB 重编 -> {tp2:.2f}"))
|
||||
tp = tp2
|
||||
else:
|
||||
report.append((fid, len(y) / sr, tp, ""))
|
||||
if tp > ONE_SHOT_TP_CEIL:
|
||||
problems.append(f"{fid}: 真峰值 {tp:.2f} dBFS 仍超 {ONE_SHOT_TP_CEIL}")
|
||||
print(f"{'id':14s} {'dur(s)':>7s} {'TP(dBFS)':>9s} {'%>3k':>7s} {'%>5k':>7s} {'peakkHz':>8s} note")
|
||||
for fid, dur, tp, note in report:
|
||||
e3, e5, pk = spectral_report(os.path.join(DST, fid))
|
||||
print(f"{fid:14s} {dur:7.2f} {tp:9.2f} {e3:7.1f} {e5:7.1f} {pk:8.2f} {note}")
|
||||
if e5 > 15.0:
|
||||
problems.append(f"{fid}: 5kHz+ 能量 {e5:.1f}% 仍偏高")
|
||||
if problems:
|
||||
print("PROBLEMS:")
|
||||
for p in problems:
|
||||
print(" ", p)
|
||||
return 1
|
||||
print(f"ALL OK: {len(ONE_SHOTS)} one-shot -> {DST}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
+44
-28
@@ -319,28 +319,55 @@ SIMON_LINES = [
|
||||
# H3:使用时如实报余量(替换主动重听路径的旧「用完了」绝对句)。
|
||||
("gd_chances_left1", _SIMON, "免费重听还剩一次。"),
|
||||
("gd_chances_left0", _SIMON, "免费重听用完了。之后重听会花一次机会,别怕,慢慢来。"),
|
||||
# ---- 状态查询(research/simon-polish round1,S1/S3/S4;input 阶段长按=问状态,
|
||||
# 13 句全部整句预生成、数字分档,禁运行时拼接;不加 guide 前缀——三档 guide 全可用,
|
||||
# 属玩家主动查询而非向导插播)----
|
||||
("status_len_short", _SIMON, "这一轮很短。"),
|
||||
("status_len_mid", _SIMON, "这一轮不长不短。"),
|
||||
("status_len_long", _SIMON, "这一轮挺长。"),
|
||||
("status_len_xlong", _SIMON, "这一轮很长,稳住。"),
|
||||
("status_prog_start", _SIMON, "刚开头,一个还没对。"),
|
||||
("status_prog_early", _SIMON, "刚答了个开头。"),
|
||||
("status_prog_half", _SIMON, "答了一半左右。"),
|
||||
("status_prog_late", _SIMON, "就剩最后几个了。"),
|
||||
("status_free2", _SIMON, "免费重听还有两次。"),
|
||||
("status_free1", _SIMON, "免费重听还有一次。"),
|
||||
("status_chance2", _SIMON, "免费用完,机会还剩两次。"),
|
||||
("status_chance1", _SIMON, "免费用完,机会还剩一次。"),
|
||||
("status_out", _SIMON, "免费和机会都用完了,答错就结束。"),
|
||||
|
||||
]
|
||||
|
||||
# ---------------------------------------------------------------- rhythm
|
||||
# R3 重构(docs/design/03-rhythm-voice-ids.md):31 句,全表同一 instruct 常量。
|
||||
# 鼓手模式重构(docs/design/24-rhythm-voice-ids.md):41 句,全表同一 instruct
|
||||
# 常量;向导层 9 句 gd_* 已在库沿用不重录(不在本表)。
|
||||
_RHY = _pair("低沉醇厚的男声,像节奏游戏主持人,平稳有力",
|
||||
"活力清晰的年轻女声,像节奏游戏主持人,情绪饱满但不夸张")
|
||||
|
||||
RHYTHM_LINES = [
|
||||
# ---- W 组 · 开场与流程 ----
|
||||
("welcome", _RHY, "欢迎来到节拍行者。跟着音乐,我奏一句,你跟一句。"),
|
||||
("welcome_back", _RHY, "欢迎回来。我奏一句,你跟一句。"),
|
||||
("tut_1", _RHY, "听,底鼓。在下一小节的同一个位置,单击。"),
|
||||
("tut_2", _RHY, "这次是左和右。跟着打,左滑,右滑。"),
|
||||
("tut_3", _RHY, "最后一句,三个音,中间是双击。"),
|
||||
("welcome", _RHY, "欢迎来到节拍行者。今天你是乐队的鼓手,整面屏幕都是你的鼓。"),
|
||||
("welcome_back", _RHY, "欢迎回来,鼓手。准备好了就开始。"),
|
||||
("tut_done", _RHY, "很好,演出开始。"),
|
||||
("tut_skip", _RHY, "跳过教学,直接开始。"),
|
||||
("mid_song", _RHY, "过半了,保持。"),
|
||||
# ---- Z 组 · 分区教学与区名 ----
|
||||
("tut_map", _RHY, "先认识你的鼓。屏幕横着分三列,左边、中间、右边;竖着分两行,上面清亮,下面低沉。随便摸一摸,听到哪面鼓,我就告诉你它在哪。"),
|
||||
("tut_probe", _RHY, "摸到三个不同的地方,我们就开始。"),
|
||||
("tut_z_kick", _RHY, "下面正中间,是底鼓,你的家。先听四下数拍,然后跟着我敲。"),
|
||||
("tut_z_sides", _RHY, "左下是军鼓,右下是低嗵。一左一右,跟着打。"),
|
||||
("tut_z_patt", _RHY, "上面一排更清亮。左上踩镲,上中高嗵,右上吊镲。听这一句,三个音。"),
|
||||
("tut_fill", _RHY, "最后有一段加花,跟着我连打。"),
|
||||
("zone_tl", _RHY, "左上,踩镲。"),
|
||||
("zone_tc", _RHY, "上中,高嗵。"),
|
||||
("zone_tr", _RHY, "右上,吊镲。"),
|
||||
("zone_bl", _RHY, "左下,军鼓。"),
|
||||
("zone_bc", _RHY, "下中,底鼓。"),
|
||||
("zone_br", _RHY, "右下,低嗵。"),
|
||||
# ---- C 组 · 教学纠正 ----
|
||||
("retry_early", _RHY, "早了,等鼓点再出手。"),
|
||||
("retry_late", _RHY, "晚了,跟着鼓点走。"),
|
||||
("retry_gesture", _RHY, "不是这个手势,再听一遍。"),
|
||||
("retry_wrong", _RHY, "敲错鼓了,再听一遍。"),
|
||||
("retry_listen", _RHY, "再听一遍,跟着来。"),
|
||||
("retry_skip", _RHY, "没关系,我们先走下一句。"),
|
||||
# ---- S 组 · 选曲与解锁 ----
|
||||
@@ -359,26 +386,12 @@ RHYTHM_LINES = [
|
||||
("new_best_combo", _RHY, "新连击纪录!"),
|
||||
("restart", _RHY, "单击再来一遍,右滑下一首,下滑回大厅。"),
|
||||
# ---- M 组 · 菜单与规则 ----
|
||||
("rules", _RHY, "规则:我奏一句,你在下一小节原样跟打。音要对,拍点也要对。"),
|
||||
("rules", _RHY, "规则:我敲一小节鼓,你在下一小节照着敲回来。鼓要选对,拍点也要对。"),
|
||||
("settings_hint", _RHY, "音色、语速、背景音乐,请回大厅修改。"),
|
||||
("menu_main", _RHY, "菜单。单击切换选项,双击确认。"),
|
||||
("menu_resume", _RHY, "继续演奏。"),
|
||||
("menu_lobby", _RHY, "回大厅。"),
|
||||
("skip_hint", _RHY, "单击可跳过旁白。"),
|
||||
# ---- 向导层(docs/design/14-guide-layer.md,schemes §1.5 定稿全文)----
|
||||
("gd_answer", _RHY, "现在跟着我打。"),
|
||||
("gd_miss", _RHY, "慢了半拍,下一小节跟上我就行。"),
|
||||
# ---- 向导层 round2(docs/design/14-guide-layer.md「round2 v1.1」,synthesis §3 定稿全文)----
|
||||
("gd_miss_early", _RHY, "抢拍了。等声音落下再滑。"),
|
||||
("gd_miss_late", _RHY, "慢了半拍。跟着节拍走。"),
|
||||
("gd_miss_wrong", _RHY, "方向不对。跟着音色滑。"),
|
||||
# ---- 向导层 round3(docs/design/14-guide-layer.md「round3 v1.2」,synthesis §3 定稿全文)----
|
||||
("gd_slow", _RHY, "我放慢一些,跟着试试。"),
|
||||
("gd_count", _RHY, "四拍之后,继续。"),
|
||||
# ---- 向导层 round4(docs/design/14-guide-layer.md「round4 v1.3」,synthesis §3 定稿全文)----
|
||||
# H5:归因真话律——应答窗口无任何尝试播此句(早/晚/错分档只留给真出手的情况)。
|
||||
("gd_miss_none", _RHY, "这拍没接。我停下的时候,就轮到你了。"),
|
||||
|
||||
]
|
||||
|
||||
# ---------------------------------------------------------------- space
|
||||
@@ -446,6 +459,9 @@ SPACE_LINES = [
|
||||
("settings_hint", _SPC, "音色、语速、背景音乐,请回大厅修改。"),
|
||||
("paused", _SPC, "已暂停。"),
|
||||
("skip_hint", _SPC, "单击可跳过旁白。"),
|
||||
# ---- R5 新增(space polish round1/round2,research/space-polish/,04-space-voice-ids.md 追加节)----
|
||||
("protect_soon", _SPC, "训练护盾还剩十秒。"),
|
||||
("boost_not_ready", _SPC, "引擎还在冷却,稍等一下。"),
|
||||
# ---- 向导层(docs/design/14-guide-layer.md,schemes §1.5 定稿全文)----
|
||||
("gd_hit_hint", _SPC, "被撞了。听方位,同向滑动就能拨开。"),
|
||||
# ---- 向导层 round2(docs/design/14-guide-layer.md「round2 v1.1」,synthesis §3 定稿全文)----
|
||||
@@ -2219,16 +2235,16 @@ KEY_LINES = {
|
||||
("gd_occ_dt", "这一轮,按回车是作答。"),
|
||||
("gd_occ_both", "这一轮,按上方向键和回车都是作答。"),
|
||||
],
|
||||
# rhythm:8 对(24-rhythm-voice-ids.md 键盘变体表,措辞含数字键/方向键/空格/回车)。
|
||||
"rhythm": [
|
||||
("tut_1", "听,底鼓。在下一小节的同一个位置,按空格。"),
|
||||
("tut_2", "这次是左和右。跟着打,按左方向键,按右方向键。"),
|
||||
("tut_3", "最后一句,三个音,中间是按回车。"),
|
||||
("tut_map", "先认识你的鼓。屏幕横着分三列,左边、中间、右边;竖着分两行,上面清亮,下面低沉。按数字一键到六键,听六面鼓。"),
|
||||
("tut_probe", "按出三个不同的键,我们就开始。"),
|
||||
("tut_z_kick", "数字五,是底鼓,你的家。先听四下数拍,然后跟着我敲。"),
|
||||
("tut_z_sides", "数字四,军鼓。数字六,低嗵。一左一右,跟着打。"),
|
||||
("tut_z_patt", "上面一排更清亮。数字一踩镲,数字二高嗵,数字三吊镲。听这一句,三个音。"),
|
||||
("select_hint", "左右方向键选曲目,按空格开始,按下方向键回大厅。"),
|
||||
("restart", "按空格再来一遍,按右方向键下一首,按下方向键回大厅。"),
|
||||
("menu_main", "菜单。按空格切换选项,按回车确认。"),
|
||||
("skip_hint", "按空格可跳过旁白。"),
|
||||
("gd_miss_early", "抢拍了。等声音落下再按。"),
|
||||
("gd_miss_wrong", "方向不对。跟着音色按方向键。"),
|
||||
],
|
||||
"space": [
|
||||
("drill_left", "听,左边有陨石。按左方向键,把它拨开!"),
|
||||
|
||||
Reference in New Issue
Block a user