mirror of
https://github.com/TriM-Organization/Musicreater.git
synced 2026-07-31 07:24:30 +00:00
Maybe yes, maybe no, but 音量响度 should let to go
This commit is contained in:
@@ -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