如何用Python創建音樂可視化器

本文將演示如何使用Python創建音樂可視化器。

如何用Python創建音樂可視化器

如何可視化音樂?

我們首先需要知道音樂是如何組成的,以及如何將它可視化。音樂是聲音的組合。聲音是我們耳朵檢測到的振動。振動由頻率和振幅(速度和響度)定義。

最簡單的可視化方法是畫一排條形圖。每個條代表一個頻率。當音樂播放時,這些條會根據頻率的振幅上下移動。

用Python實現

在開始編碼之前,需要安裝必須的Python庫。本文使用Pygame(圖形)和Librosa(聲音)。

Librosa具有非常有用的功能,可以幫助我們分析聲音。

下面的Python代碼返回一個與特定時間相對應的頻率幅度的二維數組:

<code>import librosa
import numpy as np
import pygame

filename = "music3.wav"
# getting information from the file
time_series, sample_rate = librosa.load(filename)  

# getting a matrix which contains amplitude values according to frequency and time indexes
stft = np.abs(librosa.stft(time_series, hop_length=512, n_fft=2048*4))

# converting the matrix to decibel matrix
spectrogram = librosa.amplitude_to_db(stft, ref=np.max)  /<code>

librosa.load()讀取給定文件,並保留有關該文件的信息以供以後使用。mple_rate是每個週期採集的樣本數。time_series是一個一維數組,表示每次採樣的時間。

Libros.stft()返回包含頻率和時間的二維數組。你可以看到我把這個數組從振幅轉換成了分貝。除非您使用分貝單位,否則無需執行此步驟。

短時傅里葉變換(STFT)是一種與傅里葉變換相關的變換,用於確定信號局部區域的正弦頻率和相位內容,因為它隨著時間的變化而變化。

hop_length是幀之間的採樣數。n_fft是每一幀的採樣數。當增加n_fft時,結果變得更加準確,我將其設置為其默認值的4倍。

您還可以使用matplotlib查看STFT的結果:

<code>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()/<code>
如何用Python創建音樂可視化器

您可以使用索引訪問數組的值。但是我們該如何選擇它的時間和頻率呢?

<code># getting an array of frequencies
frequencies = librosa.core.fft_frequencies(n_fft=2048*4)  
# getting an array of time periodic
times = 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]/<code>

我將2d數組分成多個數組,這些數組表示特定索引的時間或頻率。採樣率是常數。因此,我們可以在時間和索引之間創建一個比率,並在頻率上創建相同的比率。然後,我們只要把時間和頻率乘以這個比率,我們就得到了索引:

<code>def get_decibel(target_time, freq):
    return spectrogram[int(freq * frequencies_index_ratio)][int(target_time * time_index_ratio)]/<code>

現在,我們需要可視化了。

創建一個代表頻率條的類:

<code>def clamp(min_value, max_value, value):

    if value < min_value:
        return min_value

    if value > max_value:
        return max_value

    return value

class 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))/<code>

我創建了x、y座標、條形頻率、顏色以及它的高度和分貝的範圍。定義高度和分貝之間的比例,以以便稍後確定條形的高度。在update()方法中,我獲得了與當前分貝相對應的期望條形圖高度,並將速度設置為條形圖的增長速度。

<code>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 window
screen = pygame.display.set_mode([screen_w, screen_h])

bars = []
frequencies = np.arange(100, 8000, 100)
r = len(frequencies)

width = screen_w/r
x = (screen_w - width*r)/2
for c in frequencies:
    bars.append(AudioBar(x, 300, c, (255, 0, 0), max_height=400, width=width))
    x += width/<code>

這裡我創建一個數組來保存這些條形圖。以100的步長創建了從100Hz到8000Hz的80個條,並將它們添加到數組中。

然後,您只需運行一個Pygame窗口並繪製條形圖:

<code>t = pygame.time.get_ticks()
getTicksLastFrame = t

pygame.mixer.music.load(filename)
pygame.mixer.music.play(0)

# Run until the user asks to quit
running = True
while 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()/<code>

請注意,這裡使用pygame.mixer播放音樂,並使用pygame.mixer.music.get_pos()訪問時間。


分享到:


相關文章: