Pythonを使用してテンポ解析をおこなってみた


初めに

今回はPythonを使用してテンポ解析を行っていきました。
本記事は作成したプログラムの紹介と結果についてお伝えできればと思います。
今後何回か更新していこうと思います。
※精度がとてつもなく悪いです…
※プログラミングができる方は改善してコメントをお願いします<(_ _)>

使用環境

Python 3.9.1
Windows 10 Home Edition
Anaconda Powershell Prompt

テンポ解析の詳細

解析順序

  1. wavファイルをN秒ごとに分割
  2. Librosaを使用して全体のテンポを解析
  3. 分割したwavファイルごとにテンポを解析
  4. テンポの推移をmatplotlibで表示

1. wavファイルをN秒ごとに分割

wavファイルを分割 → def cut_wav():
outputファイルを自動生成してそこに、
0.wav -> 1.wav ...と保存していきます。

2.Librosaを使用して全体のテンポを解析

Librosaでテンポ解析をしていきます。
全体の曲を通すと、全体のテンポが出力されます。
※どのようにテンポを出しているかは、ウェブサイトをご覧ください。
Librosa

3. 分割したwavファイルごとにテンポを解析

「2.Librosaを使用して全体のテンポを解析」と同じように、
分割したファイルごとテンポを推定していきます。
結構な外れ値が出てくるので注意が必要です。
※今後改善予定

4. テンポの推移をmatplotlibで表示

推定したテンポをグラフで表示します。常に一定のテンポであれば直線が表示されるはずです。

実際のプログラム

import numpy as np
import librosa
import wave
import struct
import math
import os
from scipy import fromstring, int16
import matplotlib.pyplot as plt 

#----------------------------------------
# wavファイルの分割
#---------------------------------------

def cut_wav(filename, time):
    #ファイル読み出し
    wavf = filename + '.wav'
    wr = wave.open(wavf, 'r')

    #waveファイルが持つ性質を取得
    ch = wr.getnchannels()
    width = wr.getsampwidth()
    fr = wr.getframerate()
    fn = wr.getnframes()
    total_time = 1.0 * fn / fr
    integer = math.floor(total_time)
    t = int(time)
    frames = int(ch * fr * t)
    num_cut = int(integer//t)

    # 確認用
    print("total time(s) : ", total_time)
    print("total time(integer) : ", integer)
    print("time : ", t)
    print("number of cut : ", num_cut)

    # waveの実データを取得し数値化
    data = wr.readframes(wr.getnframes())
    wr.close()
    X = np.frombuffer(data, dtype=int16)

    print()

    for i in range(num_cut):
        print(str(i) + ".wav --> OK!")
        #出力データを生成
        outf = 'output/' + str(i) + '.wav' 
        start_cut = i*frames
        end_cut = i*frames + frames
        Y = X[start_cut:end_cut]
        outd = struct.pack("h" * len(Y), *Y)

        # 書き出し
        ww = wave.open(outf, 'w')
        ww.setnchannels(ch)
        ww.setsampwidth(width)
        ww.setframerate(fr)
        ww.writeframes(outd)
        ww.close()
    return num_cut

#----------------------------------------
# 全体のテンポを求める
#---------------------------------------
def totaltempo(filename):
    #検索するファイル名の作成 => output/ i .wav
    name = filename + ".wav"

    #wavファイルの読み込み
    y, sr = librosa.load(name)

    #テンポとビートの抽出
    tempo , beat_frames = librosa.beat.beat_track(y=y, sr=sr)

    #全体のテンポを表示
    print()
    print("total tempo : ", int(tempo))
    print()

#----------------------------------------
# 分割テンポを求める
#---------------------------------------

def temposearch(num, time):
    #return用変数の宣言
    l = []
    t = []
    t_time = 0
    before_tempo = 0

    print("division tempo")

    #音楽の読み込み
    for i in range(0,num,1):
        #検索するファイル名の作成 => output/ i .wav
        name = "output/" + str(i) + ".wav"

        #wavファイルの読み込み
        y, sr = librosa.load(name)

        #テンポとビートの抽出
        tempo , beat_frames = librosa.beat.beat_track(y=y, sr=sr)
        int_tempo = int(tempo)

        #テンポの表示
        print(str(i+1) +  ":" + str(int_tempo))

        #return用変数へ代入
        l.append(int_tempo)
        t_time = t_time + int(time)
        t.append(t_time)

    return l, t



#---------------------------------
# メイン関数
#---------------------------------
if __name__ == '__main__':
    # すでに同じ名前のディレクトリが無いか確認
    file = os.path.exists("output")
    print(file)

    if file == False:
        #保存先ディレクトリの作成
        os.mkdir("output")

    #ファイル名とカット時間を入力しwavファイルを分割
    f_name = input('input filename -> ')
    cut_time = input('input cut time -> ')
    n = int(cut_wav(f_name,cut_time))

    #テンポ解析
    totaltempo(f_name)
    tempo, time = temposearch(n, cut_time)

    print()

    #タイトル用
    name = "テンポ解析 " + cut_time + "秒で分割"

    #グラフ描写
    plt.title(name, fontname="MS Gothic")
    plt.xlabel("時間(s)", fontname="MS Gothic")
    plt.ylabel("テンポ(bpm)", fontname="MS Gothic")
    plt.ylim(60, 180)
    plt.plot(time, tempo)
    plt.show()

実行結果

今回は「威風堂々」という曲を解析していきます。
解析した曲はこちらから→フリーWave,MP3

(base) PS C:\Users\Name\tempo> python tempo_main.py
True
input filename -> ifudoudou
input cut time -> 10
total time(s) :  204.56489795918367
total time(integer) :  204
time :  10
number of cut :  20

0.wav --> OK!
1.wav --> OK!
2.wav --> OK!
3.wav --> OK!
4.wav --> OK!
5.wav --> OK!
6.wav --> OK!
7.wav --> OK!
8.wav --> OK!
9.wav --> OK!
10.wav --> OK!
11.wav --> OK!
12.wav --> OK!
13.wav --> OK!
14.wav --> OK!
15.wav --> OK!
16.wav --> OK!
17.wav --> OK!
18.wav --> OK!
19.wav --> OK!

total tempo :  117

division tempo
1:123
2:117
3:117
4:117
5:123
6:123
7:123
8:123
9:99
10:99
11:99
12:99
13:117
14:117
15:123
16:117
17:99
18:99
19:99
20:117

(base) PS C:\Users\Name\tempo>

表示するグラフ↓

結果について

結構、精度が悪いのがわかるでしょうか??
テンポが一定のところも数値にばらつきがみられますね。

感想

単純に曲数が足りていないので、精度がよくわからないです。
たくさん解析をしてこのプログラムの特徴を見ていこうと思います。
改善点やこんな曲を分析してほしい!という意見があればどんどんコメントお願いします!