首页 > 解决方案 > 使用 python 和 librosa 的音乐可视化工具

问题描述

我正在尝试制作一个小脚本来在 python 中生成一些音频文件的可视化。我的最终目标是生成一个 30fps 的视频,该视频由在 python 中生成的图像整理而成,组装一些图像资产。但我对声音分析部分有点卡住,因为我对声音及其背后的物理和数学几乎一无所知。无论如何。为了在 python 中处理导入声音,我使用了 librosa,它看起来非常强大。

import librosa
import numpy as np

#load an example audio
filename = librosa.ex('trumpet')
y, sr = librosa.load(filename, sr=44100)
#apply short-time Fourier transform
stft = np.abs(librosa.stft(y, n_fft=1024, hop_length=None, win_length=None, window='hann', center=True, dtype=None, pad_mode='reflect'))
# converting the matrix to decibel matrix
D = librosa.amplitude_to_db(stft, ref=np.max)

通过这种方式,我获得了一个Dshape矩阵(513,480),即频率范围内的 513 个“步”和 480 个数据点或帧。鉴于样本的持续时间约为 5.3 秒,这使其约为每秒 172.3 帧。因为我想要 30fps,所以我决定对采样率进行一些试验和错误,直到达到23038. 在加载文件时应用它:

y, sr = librosa.load(filename, sr=23038)

我获得了 89.998 fps 的帧速率,这似乎更适合我的目的。所以我继续降低数据的分辨率,平均我在 9 个“桶”中的频率读数:

#Initialize new empty array of target size
nD = np.empty(shape=(9,D.shape[1]))
#populate new array
for i in range(D.shape[1]):
    nD[:,i] = D[:,i].reshape(-1, 57).mean(axis=1)

这里的 9 来自一个事实,即我可以得到除以 57 的数字,这是 513 的因数。因此,我将 3 帧聚合在一起以达到我的 30fps:

count = 0
for i in range(0, nD.shape[1],3):
    try:
        nnD[:,count] = nD[:,i:i+3].reshape(-1, 3).mean(axis=1)
        count+=1
    except Exception:
        pass

这似乎完全是 hacky,try except 部分在那里,因为有时我有索引错误,并且计数递增计数器似乎很愚蠢。但令人难以置信的是似乎以某种方式起作用。出于测试目的,我做了一个可视化例程

from time import sleep
from os import system
nnD = ((nnD+80)/10).astype(int)
for i in range(nnD.shape[1]):
    for j in range(nnD.shape[0]):
        print ("#"*nnD[j,i])
    sleep(1/30)
    system('clear')

这显示了终端中由 # 组成的行。

现在,我的问题是:

我怎样才能以正确的方式做到这一点?

进一步来说:

1_有没有办法在不破坏采样率的情况下匹配傅里叶数据的帧速率?

2_是否有更合适的方法来聚合我的数据,可能是任意数字,而不是必须在 513 因子之间进行选择?

标签: pythonnumpyfftlibrosa

解决方案


如果您希望以秒为单位的时间分辨率约为N FPS,您可以执行以下代码。但请注意,由于跳数需要是整数,这会导致漂移,请参阅打印输出。因此需要定期重置同步,例如每 1 分钟一次。或者,也许每首歌都可以逃脱一次。

import math

def next_power_of_2(x):
    return 2**(math.ceil(math.log(x, 2)))

def params_for_fps(fps=30, sr=16000):
    frame_seconds=1.0/fps
    frame_hop = round(frame_seconds*sr) # in samples
    frame_fft = next_power_of_2(2*frame_hop)
    rel_error = (frame_hop-(frame_seconds*sr))/frame_hop
    
    return frame_hop, frame_fft, rel_error


seconds = 10*60
fps = 15
sr = 16000
frame_hop, frame_fft, frame_err = params_for_fps(fps=fps, sr=sr)
print(f"Frame timestep error {frame_err*100:.2f} %")
drift = frame_err * seconds
print(f"Drift over {seconds} seconds: {drift:.2f} seconds. {drift*fps:.2f} frames")

# Then variables can be used with
# librosa.stft(...hop_length=frame_hop, n_fft=frame_fft)

如果这种方法不够好,则需要根据(视频)帧计数器对音频特征进行插值。线性插值会很好。这允许补偿相关的漂移。这可以为每一帧动态完成,或者可以重新采样音频时间序列以与 FPS 帧对齐。


推荐阅读