如何用Python从音频计算谐波序列以求解Brightness(亮度)?
基于MIDI-DDSP定义的音频Brightness计算实现方案
步骤1:加载音频
先用librosa加载目标音频,获取波形数据y和采样率sr:
import librosa import numpy as np # 替换为你的音频文件路径 y, sr = librosa.load("audio.wav", sr=None)
步骤2:估计基频与提取谐波序列
MIDI-DDSP中的谐波分布基于音频的基频(f0),先估计每帧的基频,再提取对应谐波的幅值:
2.1 估计基频f0
用librosa的PYIN算法(适用于浊音基频估计):
# 配置基频范围(可根据音频类型调整,这里是C2到C7) f0, voiced_flag, _ = librosa.pyin( y, fmin=librosa.note_to_hz('C2'), fmax=librosa.note_to_hz('C7'), frame_length=2048, # 帧长 hop_length=512 # 帧移,控制时间分辨率 )
2.2 提取谐波分布h(k,i)
先计算音频的STFT幅值谱,再为每个浊音帧提取60次谐波的幅值(对应MIDI-DDSP中|h|=60):
n_fft = 2048 # 计算STFT幅值谱与频率轴 D = librosa.stft(y, n_fft=n_fft, hop_length=512) mag_spectrum = np.abs(D) freq_axis = librosa.fft_frequencies(sr=sr, n_fft=n_fft) n_harmonics = 60 # 初始化谐波分布矩阵(行数:谐波次数,列数:帧数) harmonic_dist = np.zeros((n_harmonics, len(f0))) for frame_idx in range(len(f0)): if not voiced_flag[frame_idx]: continue # 非浊音帧谐波幅值设为0 current_f0 = f0[frame_idx] # 生成1~60次谐波的频率 harmonic_freqs = current_f0 * np.arange(1, n_harmonics + 1) # 找到每个谐波频率对应的最接近的频谱bin索引 bin_indices = np.argmin(np.abs(freq_axis[:, np.newaxis] - harmonic_freqs), axis=0) # 提取对应幅值作为h(k,i) harmonic_dist[:, frame_idx] = mag_spectrum[bin_indices, frame_idx]
步骤3:计算Brightness(亮度)
根据MIDI-DDSP的定义,Brightness是谐波分布的频谱质心(以bin数为单位),公式为:
Brightness(i) = (Σ(k * h_k(i))) / (Σ(h_k(i)))
其中k∈[1,60],i为时间帧索引
代码实现:
brightness = np.zeros(len(f0)) for frame_idx in range(len(f0)): mags = harmonic_dist[:, frame_idx] total_mag = np.sum(mags) if total_mag == 0: brightness[frame_idx] = 0.0 # 非浊音帧亮度设为0 continue # 计算加权和:谐波次数(1~60)乘以对应幅值 weighted_sum = np.sum(np.arange(1, n_harmonics + 1) * mags) brightness[frame_idx] = weighted_sum / total_mag
关于求和方式的说明
你提到的(100*1 + 200*2 + 400*4...)本质就是上述公式中的分子部分——各谐波幅值乘以对应谐波次数的加权求和。除以总幅值后得到的归一化值,就是MIDI-DDSP定义的Brightness,它能反映谐波能量在高频段的分布比例,值越大表示音频越明亮。
注意事项
- 基频估计的准确性直接影响谐波序列的提取,若PYIN效果不佳,可尝试调整
frame_length或改用librosa.piptrack。 - 若谐波频率超过采样率的一半(Nyquist频率),对应幅值会自动为0,无需额外处理。
- 帧长和帧移的选择需平衡时间与频率分辨率,音乐类音频可尝试
hop_length=256。
内容的提问来源于stack exchange,提问作者Megan
相关产品推荐
相关产品推荐

