mirror of
https://github.com/TriM-Organization/Musicreater.git
synced 2026-07-30 15:05:02 +00:00
Maybe yes, maybe no, but 音量响度 should let to go
This commit is contained in:
+20
-3
@@ -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
@@ -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音高:音符盒音调"""
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user