使用Python標準庫中的wave模組繪製樂譜的簡單教程

使用Python標準庫中的wave模組繪製樂譜的簡單教程
1 Star2 Stars3 Stars4 Stars5 Stars 給文章打分!
Loading...

在本文中,我們將探討一種簡潔的方式,以此來視覺化你的MP3音樂收藏。此方法最終的結果將是一個對映你所有歌曲的正六邊形網格地圖,其中相似的音軌將處於相鄰的位置。不同區域的顏色對應不同的音樂流派(例如:古典、嘻哈、重搖滾)。舉個例子來說,下面是我所收藏音樂中三張專輯的對映圖:Paganini的《Violin Caprices》、Eminem的《The Eminem Show》和Coldplay的《X&Y》。

https://codertw.com/wp-content/uploads/2018/07/20180705004920-5b3d6b1002461.jpg (690×549)

為了讓它更加有趣(在某些情況下更簡單),我強加了一些限制。首先,解決方案應該不依賴於MP3檔案中任何已有的ID3標籤(例如,Arist,Genre),應該僅僅使用聲音的統計特性來計算歌曲的相似性。無論如何,很多我的MP3檔案標記都很糟糕,但我想使得該解決方案適用於任何音樂收藏檔案,不管它們的後設資料是多麼糟糕。第二,不應使用其他外部資訊來建立視覺化影象,需要輸入的僅僅是使用者的MP3檔案集。其實,通過利用一個已經被標記為特定流派的大型歌曲資料庫,就能提高解決方案的有效性,但是為了簡單起見,我想保持這個解決方案完全的獨立性。最後,雖然數字音樂有很多種格式(MP3、WMA、M4A、OGG等),但為了使其簡單化,這裡我僅僅關注MP3檔案。其實,本文開發的演算法針對其他格式的音訊也能很好地工作,只要這種格式的音訊可以轉換為WAV格式檔案。

建立音樂圖譜是一個很有趣的練習,它包含了音訊處理、機器學習和視覺化技術。基本步驟如下所示:

    轉換MP3檔案為低位元率WAV檔案。
    從WAV後設資料中提取統計特徵。
    找到這些特徵的一個最佳子集,使得在這個特徵空間中相鄰的歌曲人耳聽起來也相似。
    為了在一個XY二維平面上繪圖,使用降維技術將特徵向量對映到二維空間。
    生成一個由點組成的六角網格,然後使用最近鄰技術將XY平面上的每一首歌曲對映六角網格上的一個點。
    回到原始的高維特徵空間,將歌曲聚類到使用者定義數量的群組中(k=10能夠很好地實現視覺化目的)。對於每個群組,找到最接近群組中心的歌曲。
    在六角網格上,使用不同的顏色對k個群組中心的那首歌曲著色。
    根據其他歌曲在XY螢幕上到每個群組中心的距離,對它們插入不同的顏色。

下面,讓我們共同看看其中一些步驟的詳細資訊。
MP3檔案轉換成WAV格式

將我們的音樂檔案轉換成WAV格式的主要優勢是我們可以使用Python標準庫中的“wave”模組很容易地讀入資料,便於後面使用NumPy對資料進行操作。此外,我們還會以單聲道10kHz的取樣率對聲音檔案下采樣,以使得提取統計特徵的計算複雜度有所降低。為了處理轉換和下采樣,我使用了眾所周知的MPG123,這是一個免費的命令列MP3播放器,在Python中可以很容易呼叫它。下面的程式碼對一個音樂資料夾進行遞迴搜尋以找到所有的MP3檔案,然後呼叫MPG123將它們轉換為臨時的10kHz WAV檔案。然後,對這些WAV檔案進行特徵計算(下節中討論)。
 


import subprocess
import wave
import struct
import numpy
import csv
import sys
def read_wav(wav_file):
"""Returns two chunks of sound data from wave file."""
w = wave.open(wav_file)
n = 60 * 10000
if w.getnframes() < n * 2:
raise ValueError('Wave file too short')
frames = w.readframes(n)
wav_data1 = struct.unpack('%dh' % n, frames)
frames = w.readframes(n)
wav_data2 = struct.unpack('%dh' % n, frames)
return wav_data1, wav_data2
def compute_chunk_features(mp3_file):
"""Return feature vectors for two chunks of an MP3 file."""
# Extract MP3 file to a mono, 10kHz WAV file
mpg123_command = '..mpg123-1.12.3-x86-64mpg123.exe -w "%s" -r 10000 -m "%s"'
out_file = 'temp.wav'
cmd = mpg123_command % (out_file, mp3_file)
temp = subprocess.call(cmd)
# Read in chunks of data from WAV file
wav_data1, wav_data2 = read_wav(out_file)
# We'll cover how the features are computed in the next section!
return features(wav_data1), features(wav_data2)
# Main script starts here
# =======================
for path, dirs, files in os.walk('C:/Users/Christian/Music/'):
for f in files:
if not f.endswith('.mp3'):
# Skip any non-MP3 files
continue
mp3_file = os.path.join(path, f)
# Extract the track name (i.e. the file name) plus the names
# of the two preceding directories. This will be useful
# later for plotting.
tail, track = os.path.split(mp3_file)
tail, dir1 = os.path.split(tail)
tail, dir2 = os.path.split(tail)
# Compute features. feature_vec1 and feature_vec2 are lists of floating
# point numbers representing the statistical features we have extracted
# from the raw sound data.
try:
feature_vec1, feature_vec2 = compute_chunk_features(mp3_file)
except:
continue

特徵提取

在Python中,一個單聲道10kHz的波形檔案表示為一個範圍為-254到255的整數列表,每秒聲音包含10000個整數。每個整數代表歌曲在對應時間點上的相對幅度。我們將分別從兩首歌曲中分別提取一段時長60秒的片段,所以每個片段將由600000個整數表示。上面程式碼中的函式“read_wav”返回了這些整數列表。下面是從Eminem的《The Eminem Show》中一些歌曲中提取的10秒聲音波形圖:

https://codertw.com/wp-content/uploads/2018/07/20180705004920-5b3d6b1035d98.jpg (690×518)

為了對比,下面是Paganini的《Violin Caprices》中的一些片段波形圖:

https://codertw.com/wp-content/uploads/2018/07/20180705004920-5b3d6b1067e2a.jpg (690×518)

從上面兩個圖中可以看出,這些片段的波形結構差別很明顯,但一般來看Eminem的歌曲波形圖看起來都有些相似,《Violin Caprices》的歌曲也是這樣。接下來,我們將從這些波形圖中提取一些統計特徵,這些特徵將捕捉到歌曲之間的差異,然後通過這些歌曲聽起來的相似性,我們使用機器學習技術將它們分組。

我們將要提取的第一組特徵集是波形的統計矩(均值、標準差、偏態和峰態)。除了對幅度進行這些計算,我們還將對遞增平滑後的幅度進行計算來獲取不同時間尺度的音樂特性。我使用了長度分別為1、10、100和1000個樣點的平滑窗,當然可能其他的值也能取得很好的結果。

分別利用上面所有大小的平滑窗對幅度進行相應計算。為了獲取訊號的短時變化量,我還計算了一階差分幅度(平滑過的)的統計特性。

上面的特徵在時間域給出了一個相當全面的波形統計總結,但是計算一些頻率域的特徵也是有幫助的。像嘻哈這種重低音音樂在低頻部分有更多的能量,而經典音樂在高頻部分佔有更多的比例。

將這些特徵放在一起,我們就得到了每首歌曲的42種不同特徵。下面的Python程式碼從一系列幅度值計算了這些特徵:


def moments(x):
mean = x.mean()
std = x.var()**0.5
skewness = ((x - mean)**3).mean() / std**3
kurtosis = ((x - mean)**4).mean() / std**4
return [mean, std, skewness, kurtosis]
def fftfeatures(wavdata):
f = numpy.fft.fft(wavdata)
f = f[2:(f.size / 2   1)]
f = abs(f)
total_power = f.sum()
f = numpy.array_split(f, 10)
return [e.sum() / total_power for e in f]
def features(x):
x = numpy.array(x)
f = []
xs = x
diff = xs[1:] - xs[:-1]
f.extend(moments(xs))
f.extend(moments(diff))
xs = x.reshape(-1, 10).mean(1)
diff = xs[1:] - xs[:-1]
f.extend(moments(xs))
f.extend(moments(diff))
xs = x.reshape(-1, 100).mean(1)
diff = xs[1:] - xs[:-1]
f.extend(moments(xs))
f.extend(moments(diff))
xs = x.reshape(-1, 1000).mean(1)
diff = xs[1:] - xs[:-1]
f.extend(moments(xs))
f.extend(moments(diff))
f.extend(fftfeatures(x))
return f
# f will be a list of 42 floating point features with the following
# names:
# amp1mean
# amp1std
# amp1skew
# amp1kurt
# amp1dmean
# amp1dstd
# amp1dskew
# amp1dkurt
# amp10mean
# amp10std
# amp10skew
# amp10kurt
# amp10dmean
# amp10dstd
# amp10dskew
# amp10dkurt
# amp100mean
# amp100std
# amp100skew
# amp100kurt
# amp100dmean
# amp100dstd
# amp100dskew
# amp100dkurt
# amp1000mean
# amp1000std
# amp1000skew
# amp1000kurt
# amp1000dmean
# amp1000dstd
# amp1000dskew
# amp1000dkurt
# power1
# power2
# power3
# power4
# power5
# power6
# power7
# power8
# power9
# power10

選擇一個最優的特徵子集

我們已經計算了42種不同的特種,但是並不是所有特徵都有助於判斷兩首歌曲聽起來是否相同。下一步就是找到這些特徵的一個最優子集,以便在這個減小的特徵空間中兩個特徵向量之間的歐幾里得距離能夠很好地對應兩首歌聽起來的相似性。

變數選擇的過程是一個有監督的機器學習問題,所以我們需要一些訓練資料集合,這些訓練集能夠引導演算法找到最好的變數子集。我並非通過手動處理音樂集並標記哪些歌曲聽起來相似來建立演算法的訓練集,而是使用了一個更簡單的方法:從每首歌曲中提取兩段時長為1分鐘的樣本,然後試圖找到一個最能匹配同一首歌曲中的兩個片段的演算法。

為了找到針對所有歌曲能夠達到最好平均匹配度的特徵集,我使用了一個遺傳演算法(在R語言的genalg包中)對42個變數中的每一個進行選取。下圖顯示了經過遺傳演算法的100次迭代,目標函式的改進情況(例如,一首歌的兩個樣本片段通過最近鄰分類器來匹配到底有多麼穩定)。

https://codertw.com/wp-content/uploads/2018/07/20180705004920-5b3d6b108a949.jpg (574×447)

如果我們強制距離函式使用所有的42個特徵,那麼目標函式的值將變為275。而通過正確地使用遺傳演算法來選取特徵變數,我們已經將目標函式(例如,錯誤率)減小到了90,這是一個非常重大的改進。最後選取的最優特徵集包括:

    amp10mean
    amp10std
    amp10skew
    amp10dstd
    amp10dskew
    amp10dkurt
    amp100mean
    amp100std
    amp100dstd
    amp1000mean
    power2
    power3
    power4
    power5
    power6
    power7
    power8
    power9

在二維空間視覺化資料

我們最優的特徵集使用了18個特徵變數來比較歌曲的相似性,但是我們想最終在2維平面上視覺化音樂集合,所以我們需要將這個18維的空間降到2維,以便於我們繪畫。為了實現這個目的,我簡單地使用了前兩個主成分來作為X和Y座標。當然,這會引入一些錯誤到視覺化圖中,可能會造成一些在18維空間中相近的歌曲在2維平面中卻不再相近。不過,這些錯誤無可避免,但幸好它們不會將這種關係扭曲得太厲害—聽起來相似的歌曲在2維平面上仍然會大致集聚在一起。
將點對映到一個六角網格

從主成分中生成的2D點在平面上不規則地分佈。雖然這個不規則的分佈描述了18維特徵向量在2維平面上最“準確”的佈置,但我還是想通過犧牲一些準確率來將它們對映到一個很酷的畫面上,即一個有規律間隔的六角網格。通過以下操作實現:

    將xy平面的點嵌入到一個更大的六角網格點陣中。
    從六角形最外層的點開始,將最近的不規則間隔的主成分點分配給每個六角網格點。
    延伸2D平面的點,使它們完全填充六角網格,組成一個引人注目的圖。

https://codertw.com/wp-content/uploads/2018/07/20180705004920-5b3d6b10b3d01.jpg (500×474)

為圖上色

這個練習的一個主要目的是不對音樂集的內容做任何假設。這意味著我不想將預定義的顏色分配給特定的音樂流派。相反,我在18維空間中聚合特徵向量以找到聚集聽起來相似的音樂的容器,並將顏色分配給這些群組中心。結果是一個自適應著色演算法,它會找出你所要求的儘可能多的細節(因為使用者可以定義群組的數量,也即是顏色數量)。正如前面提到的,我發現使用k=10的群組數量往往會給出好的結果。
最終輸出

為了娛樂,這裡給出我音樂集中3668首歌曲的視覺化圖。全解析度圖片可以從這裡獲得。如果你放大圖片,你將會看到演算法工作的相當好:著色的區域對應著相同音樂流派的音軌,並且經常是相同的藝術家,正如我們希望的那樣。

https://codertw.com/wp-content/uploads/2018/07/20180705004920-5b3d6b10d8b59.jpg (618×506)

您可能感興趣的文章:

python使用wxPython開啟並播放wav檔案的方法python使用beautifulsoup從愛奇藝網抓取視訊播放python3音樂播放器簡單實現程式碼python實現定時播放mp3python使用PyGame模組播放聲音的方法python通過wxPython開啟一個音訊檔案並播放的方法python使用PyGame播放Midi和Mp3檔案的方法python使用win32com庫播放mp3檔案的方法Python實現線上音樂播放器python開發簡易版線上音樂播放器Python呼叫系統底層API播放wav檔案的方法

相關文章

程式語言 最新文章