piątek, 5 marca 2010

Python i wykrycie beatu

W zasadzie nie wiem jak przetłumaczyć "beat" - uderzenie, rytm? Napisałem skrypt szukający beatów w pliku dźwiękowym. Jako materiał do analizy posłużyło pierwszy 10 sekund piosenki. Beat zdefiniowany jest jako skok chwilowej energii sygnału, co odbierane jest przez ucho jako głośniejszy dźwięk.
Więcej informacji tutaj: http://www.gamedev.net/reference/programming/features/beatdetection/
__author__ = "daniel"
__date__ = "$2010-03-04 00:21:00$"

from scipy.io import wavfile
import matplotlib.pyplot as pyplot
import numpy

if __name__ == "__main__":
# wczytanie pliku
fs, dane = wavfile.read("/home/daniel/Muzyka/steady10s.wav")
# laczenie dwoch kanalow w jeden
print dane.shape
dane = dane[:, 0] / 2.0 + dane[:, 1] / 2.0
# normalizacja
dane = dane / max(dane)
# obliczanie energii przenoszonej przez sygnal
dane = dane**2


# o tyle probek bedziemy skakac w czasie analizy
k = fs / 10
# dlugosc okna analizy
N = 2 ** 10
i = 0
# ilosc probek w pliku
length, = dane.shape
# const, ile razy wieksza musi byc wartosc energii sygnalu zeby uznany byl za beat
C = 2
srednia = []
# w ktorych momentach wykryto beat
beats = []
while i*k+N <= length:
srednia.append(abs(dane[i*k:i*k+N-1].mean()))
# sprawdzamy ostatnie 42 usrednienia, czyli ~1s
if (sum(srednia[-42:])/42 * C < srednia[-1]):
#print "Beat @", i*k+N/2, "i:", i
beats.append(i)
i+=1

srednia = numpy.array(srednia)
# skalowanie ybeats do 2-krotności średniej energii w danym punkcie (dla przejrzystosci)
ybeats = numpy.ones(shape=len(beats))
for i, b in enumerate(beats):
ybeats[i] *= srednia[b] * 2

# rysowanie wykresów (mocno podobne do matlaba)
pyplot.plot(srednia)
pyplot.hold(True)
pyplot.stem(beats, ybeats, 'k')
pyplot.title("Beats")
pyplot.savefig("/home/daniel/Obrazy/steady10sbeats.png")
pyplot.show()
A oto wynik: Hosted by imgur.com Szczęśliwym trafem Steady as she goes ma bardzo wyraźny rytm na początku, więc łatwo go wykryć. Kolejnym krokiem będzie analiza trudniejszego materiału.