Maybe yes, maybe no, but 音量响度 should let to go

This commit is contained in:
2026-07-20 20:27:29 +08:00
parent 813ffab782
commit 2804760c03
5 changed files with 1682 additions and 192 deletions
+20 -3
View File
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
"""
音·创 v3 的功能性内容合辑
此处存放无需本体类的内容
"""
"""
@@ -35,13 +36,29 @@ def enumerated_stuffcopy_dictionary(
def incremental_save_path(
file_name: str, suffix: str = ".file", appendix_connector: str = "-"
file_name: str,
suffix: str = ".file",
appendix_connector: str = "-",
parent_path: Union[Path, str] = Path.cwd(),
) -> Path:
ss_path = Path(file_name).with_suffix(suffix)
"""
生成一个文件路径,该文件地址指向的文件名应为 `file_name` 添加一个后缀 `suffix`。\n
如果 `file_name` 已经存在,则添加一个后缀 `appendix_connector` 和一个数字。
此数字由 2 开始,每存在此文件,则递增 1。直到找到最后那个惟一的未被占用的文件路径。\n
例如:
- file_name = "test", suffix = ".txt", appendix_connector = "-", parent_path = "/tmp/" \n
若在 `/tmp` 中:
1. `test.txt` 不存在,则返回 Path("/tmp/test.txt")
2. 上述文件存在,且 `test-1.txt` 不存在,则返回 Path("/tmp/test-1.txt")
3. 上述文件皆存在,但 `test-2.txt` 不存在,则返回 Path("/tmp/test-2.txt")
...
"""
ss_path = parent_path / Path(file_name).with_suffix(suffix)
if ss_path.exists():
now_appendix = 2
while (
ss_path := Path(file_name + appendix_connector + str(now_appendix) + suffix)
ss_path := parent_path
/ Path(file_name + appendix_connector + str(now_appendix) + suffix)
).exists():
now_appendix += 1
return ss_path
+76 -187
View File
@@ -438,37 +438,82 @@ MC_INSTRUMENT_BLOCKS_TABLE: Dict[str, Tuple[str, ...]] = {
}
"""MC乐器对音符盒下垫方块对照表"""
MC_INSTRUMENT_VOLUME_BALANCE_TABLE: Dict[str, float] = {
"note.bass": 0.18975738,
"note.bassattack": 0.19250469,
"note.snare": 0.09636449,
"note.hat": 0.041303635,
"note.bd": 0.25398168,
"note.bell": 0.03190162,
"note.flute": 0.050610494,
"note.chime": 0.011847978,
"note.guitar": 0.03356794,
"note.xylophone": 0.025973769,
"note.iron_xylophone": 0.044438824,
"note.cow_bell": 0.09123069,
"note.didgeridoo": 0.06332747,
"note.bit": 0.028824601,
"note.banjo": 0.02944984,
"note.pling": 0.11315284,
"note.trumpet": 0.069420114,
"note.trumpet_exposed": 0.083734825,
"note.trumpet_oxidized": 0.047252066,
"note.trumpet_weathered": 0.06659823,
"note.harp": 0.12101666,
MC_INSTRUMENT_SOUND_INFO_TABLE: Dict[str, Dict[str, float]] = {
"note.bass": {"C-LUFS": -1178, "MS": 532.0, "SR": 7218},
"note.bassattack": {"C-LUFS": -1188, "MS": 484.8, "SR": 7921},
"note.snare": {"C-LUFS": -1173, "MS": 80.0, "SR": 48000},
"note.hat": {"C-LUFS": -1480, "MS": 85.3, "SR": 48000},
"note.bd": {"C-LUFS": -957, "MS": 137.8, "SR": 29718},
"note.bell": {"C-LUFS": -2510, "MS": 440.7, "SR": 32531},
"note.flute": {"C-LUFS": -2076, "MS": 618.3, "SR": 19875},
"note.chime": {"C-LUFS": -2984, "MS": 1106.8, "SR": 38859},
"note.guitar": {"C-LUFS": -2539, "MS": 567.8, "SR": 24796},
"note.xylophone": {"C-LUFS": -2419, "MS": 142.4, "SR": 39562},
"note.iron_xylophone": {"C-LUFS": -2078, "MS": 603.8, "SR": 9328},
"note.cow_bell": {"C-LUFS": -1427, "MS": 177.2, "SR": 33234},
"note.didgeridoo": {"C-LUFS": -2062, "MS": 441.5, "SR": 15656},
"note.bit": {"C-LUFS": -2677, "MS": 384.0, "SR": 48000},
"note.banjo": {"C-LUFS": -2207, "MS": 466.5, "SR": 31828},
"note.pling": {"C-LUFS": -1528, "MS": 651.5, "SR": 18468},
"note.trumpet": {"C-LUFS": -1678, "MS": 277.3, "SR": 48000},
"note.trumpet_exposed": {"C-LUFS": -1682, "MS": 325.3, "SR": 48000},
"note.trumpet_oxidized": {"C-LUFS": -2225, "MS": 314.7, "SR": 48000},
"note.trumpet_weathered": {"C-LUFS": -1875, "MS": 282.7, "SR": 48000},
"note.harp": {"C-LUFS": -1448, "MS": 588.7, "SR": 15656},
# 嗯……
"fire.ignite": 0.004931174,
"firework.blast": 0.015445901,
"firework.twinkle": 0.032410737,
"mob.cat.meow-2": 0.0675417,
"mob.cat.meow-4": 0.05923138,
"mob.zombie.wood": 0.14135803,
"fire.ignite": {"C-LUFS": -2788, "MS": 426.2, "SR": 40246},
"firework.blast": {"C-LUFS": -2523, "MS": 1004.3, "SR": 44100},
"firework.twinkle": {"C-LUFS": -2021, "MS": 1335.1, "SR": 44100},
"mob.cat.meow-2": {"C-LUFS": -1406, "MS": 648.1, "SR": 33182},
"mob.cat.meow-4": {"C-LUFS": -1412, "MS": 653.6, "SR": 34467},
"mob.zombie.wood": {"C-LUFS": -1017, "MS": 940.4, "SR": 44100},
}
"""
MC 乐器对应音效信息表
C-LUFS: 响度,单位`百 LUFS`
MS: 持续时间,单位`毫秒`
SR: 采样率,单位`赫兹`
"""
# 以上的 LUFS 值可以用于计算下面这个倍率
# 我们期望所有的音效的响度都为 -20 LUFS
# 那么需要改变每一个采样的音量大小就需要
# data * (10 ** ((原响度 - 目标响度) / 20))
# 其中 10 ** ((原响度 - 目标响度) / 20) 为倍率
# 上表中的响度单位是 百 LUFS
# 因此实际计算的过程中记得把这一百除回来
# 公式就变成了:
# data * (10 ** ((C_LUFS - 2000) / 2000))
MC_INSTRUMENT_VOLUME_BALANCE_TABLE: Dict[str, float] = {
"note.bass": 0.3881503659906483,
"note.bassattack": 0.3926449353995998,
"note.snare": 0.3859224115947292,
"note.hat": 0.5495408738576245,
"note.bd": 0.3009539168873202,
"note.bell": 1.7988709151287878,
"note.flute": 1.0914403364487566,
"note.chime": 3.1045595881283554,
"note.guitar": 1.8599445632225762,
"note.xylophone": 1.6199439939036293,
"note.iron_xylophone": 1.093956366272094,
"note.cow_bell": 0.517011257968655,
"note.didgeridoo": 1.0739894123412448,
"note.bit": 2.1802183971859455,
"note.banjo": 1.2691121444451907,
"note.pling": 0.580764417521312,
"note.trumpet": 0.690239803840242,
"note.trumpet_exposed": 0.6934258060165691,
"note.trumpet_oxidized": 1.2956866975170194,
"note.trumpet_weathered": 0.8659643233600653,
"note.harp": 0.5296634438916578,
"fire.ignite": 2.4774220576332855,
"firework.blast": 1.8259967490749676,
"firework.twinkle": 1.0244717803103813,
"mob.cat.meow-2": 0.5046612975635284,
"mob.cat.meow-4": 0.5081594425605606,
"mob.zombie.wood": 0.32247793193163765,
}
"""乐器响度平衡表,倍率"""
MC_EILLES_RT261_INSTRUMENT_REPLACE_TABLE: Dict[str, str] = {
"note.trumpet": "note.flute",
@@ -519,7 +564,7 @@ MM_INSTRUMENT_RANGE_TABLE: Dict[str, Tuple[Tuple[int, int], int]] = {
"note.banjo": ((42, 66), 54),
"note.flute": ((54, 78), 66),
"note.bass": ((18, 42), 30),
"note.snare": ((-1, 128), 0), # 实际上是 0~127
"note.snare": ((-1, 128), 0), # 实际上是 0~127,此处仅作兼容性处理
"note.didgeridoo": ((18, 42), 30),
"mob.zombie.wood": ((-1, 128), 0),
"note.bit": ((42, 66), 54),
@@ -538,6 +583,7 @@ MM_INSTRUMENT_RANGE_TABLE: Dict[str, Tuple[Tuple[int, int], int]] = {
"""
不同乐器的音域偏离对照表
元组里的是范围,后面的整数是游戏里的默认采样音高
单位是在 Midi 中的音高
"""
MM_INSTRUMENT_DEVIATION_TABLE: Dict[str, int] = {
@@ -580,164 +626,7 @@ MM_INSTRUMENT_DEVIATION_TABLE: Dict[str, int] = {
# 金羿ELS 音符方块对照表
MN_EILLES_NOTE_BLOCK_TABLE: Dict[int, str] = {
0: "C",
1: "C#",
2: "D",
3: "D#",
4: "E",
5: "F",
6: "F#",
7: "G",
8: "G#",
9: "A",
10: "A#",
11: "B",
12: "C",
13: "C#",
14: "D",
15: "D#",
16: "E",
17: "F",
18: "F#",
19: "G",
20: "G#",
21: "A",
22: "A#",
23: "B",
24: "C",
25: "C#",
26: "D",
27: "D#",
28: "E",
29: "F",
30: "F#",
31: "G",
32: "G#",
33: "A",
34: "A#",
35: "B",
36: "C",
37: "C#",
38: "D",
39: "D#",
40: "E",
41: "F",
42: "F#",
43: "G",
44: "G#",
45: "A",
46: "A#",
47: "B",
48: "C",
49: "C#",
50: "D",
51: "D#",
52: "E",
53: "F",
54: "F#",
55: "G",
56: "G#",
57: "A",
58: "A#",
59: "B",
60: "C",
61: "C#",
62: "D",
63: "D#",
64: "E",
65: "F",
66: "F#",
67: "G",
68: "G#",
69: "A",
70: "A#",
71: "B",
72: "C",
73: "C#",
74: "D",
75: "D#",
76: "E",
77: "F",
78: "F#",
79: "G",
80: "G#",
81: "A",
82: "A#",
83: "B",
84: "C",
85: "C#",
86: "D",
87: "D#",
88: "E",
89: "F",
90: "F#",
91: "G",
92: "G#",
93: "A",
94: "A#",
95: "B",
96: "C",
97: "C#",
98: "D",
99: "D#",
100: "E",
101: "F",
102: "F#",
103: "G",
104: "G#",
105: "A",
106: "A#",
107: "B",
108: "C",
109: "C#",
110: "D",
111: "D#",
112: "E",
113: "F",
114: "F#",
115: "G",
116: "G#",
117: "A",
118: "A#",
119: "B",
120: "C",
121: "C#",
122: "D",
123: "D#",
124: "E",
125: "F",
126: "F#",
127: "G",
}
# 即将启用
# height2note = {
# 0.5: 0,
# 0.53: 1,
# 0.56: 2,
# 0.6: 3,
# 0.63: 4,
# 0.67: 5,
# 0.7: 6,
# 0.75: 7,
# 0.8: 8,
# 0.84: 9,
# 0.9: 10,
# 0.94: 11,
# 1.0: 12,
# 1.05: 13,
# 1.12: 14,
# 1.2: 15,
# 1.25: 16,
# 1.33: 17,
# 1.4: 18,
# 1.5: 19,
# 1.6: 20,
# 1.7: 21,
# 1.8: 22,
# 1.9: 23,
# 2.0: 24,
# }
# """音高对照表\n
# MC音高:音符盒音调"""
+10
View File
@@ -59,6 +59,13 @@
"sounddevice",
"librosa",
]
preview-advanced = [
"numpy < 3.0",
"soundfile",
"sounddevice",
"librosa",
"pyloudnorm",
]
full = [
"mido >= 1.3, < 2.0",
"numpy < 3.0",
@@ -67,6 +74,7 @@
"soundfile",
"sounddevice",
"librosa",
"pyloudnorm",
]
dev = [
"mido >= 1.3, < 2.0",
@@ -76,6 +84,8 @@
"soundfile",
"sounddevice",
"librosa",
"pyloudnorm",
"matplotlib",
"dill",
"rich",
"pyinstaller",
+82
View File
@@ -0,0 +1,82 @@
import os
import numpy as np
import soundfile as sf
import pyloudnorm as pyln
def analyze_game_sfx(wav_dir: str) -> dict:
"""
批量分析游戏音效的听感响度(LUFS)。
对于短音效,优先使用 Momentary Loudness;若文件过短则回退到 K-weighted RMS。
"""
results = {}
if not os.path.exists(wav_dir):
print(f"错误: 目录不存在 -> {wav_dir}")
return results
wav_files = [f for f in os.listdir(wav_dir) if f.lower().endswith('.wav')]
print(f"找到 {len(wav_files)} 个 WAV 文件,开始分析...\n")
for filename in sorted(wav_files):
filepath = os.path.join(wav_dir, filename)
try:
data, sr = sf.read(filepath)
# 确保为二维数组 (samples, channels)
if data.ndim == 1:
data = data.reshape(-1, 1)
duration = len(data) / sr
# --- 核心响度计算 ---
# ITU-R BS.1770 要求至少 ~400ms 才能准确计算 Integrated Loudness
# 游戏音效通常很短,我们采用以下策略:
meter = pyln.Meter(sr, block_size=duration) # 400ms block
# if duration >= 0.4:
# 使用 Momentary Loudness (无门限,适合短音效)
loudness = meter.integrated_loudness(data)
# else:
# # 极短音效:手动 K-weighting 后计算 RMS → 转为 LUFS
# # pyloudnorm 内部有 K-weight filter,这里用简化方式
# filtered = meter._filter_k_weighting(data.T).T
# rms = np.sqrt(np.mean(filtered ** 2))
# # LUFS ≈ -0.691 + 10*log10(rms^2),参考 BS.1770 公式
# loudness = -0.691 + 10 * np.log10(max(rms ** 2, 1e-12))
# method = "K-weighted RMS"
# 静音检测
if np.max(np.abs(data)) < 1e-6:
loudness = -np.inf
results[filename] = {
"loudness_lufs": round(loudness, 2),
"duration_sec": round(duration, 4),
"sample_rate": sr,
"channels": data.shape[1],
}
except Exception as e:
results[filename] = {"error": str(e)}
print(f" ⚠ 跳过 {filename}: {e}")
return results
if __name__ == "__main__":
TARGET_DIR = "vanilla_assets/wav/"
report = analyze_game_sfx(TARGET_DIR)
# 打印结果字典
print("\n===== 分析结果 =====")
print("文件名 | 响度(LUFS) | 时长(s) | 采样率")
for name, info in report.items():
if "error" in info:
print(f"{name}: 错误 - {info['error']}")
else:
print(f"{name}: {info['loudness_lufs']:>7.2f} LUFS | "
f"{info['duration_sec']:.3f}s | {info['sample_rate']}Hz")
# 也输出原始字典方便程序化使用
print("\n===== Raw Dict =====")
print(report)
Generated
+1494 -2
View File
File diff suppressed because it is too large Load Diff