【问题标题】:Music visualizer using python and librosa使用 python 和 librosa 的音乐可视化工具
【发布时间】:2021-04-13 13:53:11
【问题描述】:

我正在尝试制作一个小脚本来在 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)

通过这种方式,我获得了一个形状为(513,480) 的矩阵D,这意味着频率范围内有 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 的因子之间进行选择?

【问题讨论】:

  • 您真正想要的是调整stft() call's n_fft and hop_length 以调整分析包含的“即时”数量。
  • @AKX 我想过这个,但我无法弄清楚“反向公式”是什么。

标签: python numpy fft librosa


【解决方案1】:

如果您希望以秒为单位的时间分辨率为大约 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 帧对齐。

【讨论】:

    【解决方案2】:

    这可以很好地解决问题。正如我所怀疑的,您需要调整hop_length

    import time
    
    import librosa
    import numpy as np
    import scipy.signal
    
    # Tunable parameters
    hop_length_secs = 1 / 30
    bands = 10  # How many frequency bands?
    characters = " ..::##@"  # Characters to print things with
    
    filename = librosa.ex("trumpet")
    y, sr = librosa.load(filename, sr=22050)
    sound_length = y.shape[0] / sr
    print(f"{sound_length = }")
    hop_length_samples = int(hop_length_secs * sr)
    print(f"{hop_length_secs = }")
    print(f"{hop_length_samples = }")
    
    stft = np.abs(librosa.stft(y, n_fft=1024, hop_length=hop_length_samples))
    num_bins, num_samples = stft.shape
    
    # This should be approximately `sound_length` now
    print(f"{num_samples * hop_length_secs = }")
    
    # Resample to the desired number of frequency bins
    stft2 = np.abs(scipy.signal.resample(stft, bands, axis=0))
    stft2 = stft2 / np.max(stft2)  # Normalize to 0..1
    
    
    # Remap the 0..1 signal to integer indices
    # -- the square root boosts the otherwise lower signals for better visibility.
    char_indices = (np.sqrt(stft2) * (len(characters) - 1)).astype(np.uint8)
    
    # Print out the signal "in time".
    for y in range(num_samples):
        print("".join(characters[i] for i in char_indices[:, y]))
        time.sleep(hop_length_secs)
    

    输出是

    sound_length = 5.333378684807256
    hop_length_secs = 0.03333333333333333
    hop_length_samples = 735
    num_samples * hop_length_secs = 5.366666666666666
    
    .:..
    .##:.. .
    .#:: . .
    .#:. .
    .#:.
    .#:. . .
    ....
    .@#:.. ...
    .##:.. .
    .#:. .
    .#:.
    .::.
    .#:.
    .::.
    .::.
    .::.
    ....
    .::.
    .##:.. .
    .##:.. .
    .#:. .
    .#:.
    .::.
    .:..
    .:..
    .:..
    .:..
    .##:..
    .##:.. .
    .##:.. .
    .##:..
    .:..
    
    (etc...)
    

    如果你眯着眼睛,它看起来确实像一个可视化...

    【讨论】:

      猜你喜欢
      • 2012-04-22
      • 2010-09-14
      • 2011-03-06
      • 1970-01-01
      • 2011-03-10
      • 1970-01-01
      • 2011-06-30
      • 2023-03-12
      • 1970-01-01
      相关资源
      最近更新 更多