mirror of
https://github.com/TriM-Organization/Musicreater.git
synced 2026-07-24 09:12:44 +00:00
82 lines
2.9 KiB
Python
82 lines
2.9 KiB
Python
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) |