本文将演示如何使用Python创建音乐可视化器。
如何可视化音乐?
我们首先需要知道音乐是如何组成的,以及如何将它可视化。音乐是声音的组合。声音是我们耳朵检测到的振动。振动由频率和振幅(速度和响度)定义。
最简单的可视化方法是画一排条形图。每个条代表一个频率。当音乐播放时,这些条会根据频率的振幅上下移动。
用Python实现
在开始编码之前,需要安装必须的Python库。本文使用Pygame(图形)和Librosa(声音)。
Librosa具有非常有用的功能,可以帮助我们分析声音。
下面的Python代码返回一个与特定时间相对应的频率幅度的二维数组:
import librosaimport numpy as npimport pygamefilename = "music3.wav"# getting information from the filetime_series, sample_rate = librosa.load(filename) # getting a matrix which contains amplitude values according to frequency and time indexesstft = np.abs(librosa.stft(time_series, hop_length=512, n_fft=2048*4))# converting the matrix to decibel matrixspectrogram = librosa.amplitude_to_db(stft, ref=np.max)
librosa.load()读取给定文件,并保留有关该文件的信息以供以后使用。mple_rate是每个周期采集的样本数。time_series是一个一维数组,表示每次采样的时间。
Libros.stft()返回包含频率和时间的二维数组。你可以看到我把这个数组从振幅转换成了分贝。除非您使用分贝单位,否则无需执行此步骤。
短时傅里叶变换(STFT)是一种与傅里叶变换相关的变换,用于确定信号局部区域的正弦频率和相位内容,因为它随着时间的变化而变化。
hop_length是帧之间的采样数。n_fft是每一帧的采样数。当增加n_fft时,结果变得更加准确,我将其设置为其默认值的4倍。
您还可以使用matplotlib查看STFT的结果:
librosa.display.specshow(self.spectrogram, y_axis='log', x_axis='time')plt.title('Your title')plt.colorbar(format='%+2.0f dB')plt.tight_layout()plt.show()
您可以使用索引访问数组的值。但是我们该如何选择它的时间和频率呢?
# getting an array of frequenciesfrequencies = librosa.core.fft_frequencies(n_fft=2048*4) # getting an array of time periodictimes = librosa.core.frames_to_time(np.arange(spectrogram.shape[1]), sr=sample_rate, hop_length=512, n_fft=2048*4)time_index_ratio = len(times)/times[len(times) - 1]frequencies_index_ratio = len(frequencies)/frequencies[len(frequencies)-1]
我将2d数组分成多个数组,这些数组表示特定索引的时间或频率。采样率是常数。因此,我们可以在时间和索引之间创建一个比率,并在频率上创建相同的比率。然后,我们只要把时间和频率乘以这个比率,我们就得到了索引:
def get_decibel(target_time, freq): return spectrogram[int(freq * frequencies_index_ratio)][int(target_time * time_index_ratio)]
现在,我们需要可视化了。
创建一个代表频率条的类:
def clamp(min_value, max_value, value): if value < min_value: return min_value if value > max_value: return max_value return valueclass AudioBar: def __init__(self, x, y, freq, color, width=50, min_height=10, max_height=100, min_decibel=-80, max_decibel=0): self.x, self.y, self.freq = x, y, freq self.color = color self.width, self.min_height, self.max_height = width, min_height, max_height self.height = min_height self.min_decibel, self.max_decibel = min_decibel, max_decibel self.__decibel_height_ratio = (self.max_height - self.min_height)/(self.max_decibel - self.min_decibel) def update(self, dt, decibel): desired_height = decibel * self.__decibel_height_ratio + self.max_height speed = (desired_height - self.height)/0.1 self.height += speed * dt self.height = clamp(self.min_height, self.max_height, self.height) def render(self, screen): pygame.draw.rect(screen, self.color, (self.x, self.y + self.max_height - self.height, self.width, self.height))
我创建了x、y坐标、条形频率、颜色以及它的高度和分贝的范围。定义高度和分贝之间的比例,以以便稍后确定条形的高度。在update()方法中,我获得了与当前分贝相对应的期望条形图高度,并将速度设置为条形图的增长速度。
pygame.init()infoObject = pygame.display.Info()screen_w = int(infoObject.current_w/2.5)screen_h = int(infoObject.current_w/2.5)# Set up the drawing windowscreen = pygame.display.set_mode([screen_w, screen_h])bars = []frequencies = np.arange(100, 8000, 100)r = len(frequencies)width = screen_w/rx = (screen_w - width*r)/2for c in frequencies: bars.append(AudioBar(x, 300, c, (255, 0, 0), max_height=400, width=width)) x += width
这里我创建一个数组来保存这些条形图。以100的步长创建了从100Hz到8000Hz的80个条,并将它们添加到数组中。
然后,您只需运行一个Pygame窗口并绘制条形图:
t = pygame.time.get_ticks()getTicksLastFrame = tpygame.mixer.music.load(filename)pygame.mixer.music.play(0)# Run until the user asks to quitrunning = Truewhile running: t = pygame.time.get_ticks() deltaTime = (t - getTicksLastFrame) / 1000.0 getTicksLastFrame = t # Did the user click the window close button? for event in pygame.event.get(): if event.type == pygame.QUIT: running = False # Fill the background with white screen.fill((255, 255, 255)) for b in bars: b.update(deltaTime, get_decibel(pygame.mixer.music.get_pos()/1000.0, b.freq)) b.render(screen) # Flip the display pygame.display.flip()# Done! Time to quit.pygame.quit()
请注意,这里使用pygame.mixer播放音乐,并使用pygame.mixer.music.get_pos()访问时间。