ぽきたに 〜ありきたりな非凡〜

日々の独り言とちょっとした発信

【Python】WAVファイルの波形データにFFTかけて周波数スペクトルを複数表示する【サウンドプログラミング】

どうもたっきーです。

最近の悩みは虫歯です;;

これは前回からの続きなので前回のヤツやってないと動作しません()

 

 

参考

aidiary.hatenablog.com

aidiary.hatenablog.com

窓関数を用いる理由 - ロジカルアーツ研究所

tacky0612.hatenablog.com

 

なにする?

WAVファイルのデータにFFTかけて周波数解析し、matplotlibで周波数スペクトルを複数表示する。

調べてみたところ、FFTかけるときに窓関数というものをかけるのが一般的らしいのでそれも実装。

 

環境

macOS High Sierra 10.13
Python 3.6.3
numpy 1.13.3
matplotlib 2.1.0

 

FFTとは

高速フーリエ変換(fast Fourier transform)とは、離散フーリエ変換(discrete Fourier transform, DFT)を計算機上で高速に計算するアルゴリズムのこと。

ちなみにDFTを直接計算するとめちゃめちゃ時間かかる。。。

そこで考えられたのがFFTってわけ。

めちょ早い。

数学が苦手なので中身の数式には触れない。

Numpyを使えばnp.fft.fft()で一瞬で求められる。

 

周波数スペクトルとは

光や音や電磁波信号は様々な周波数の成分から構成されている。そのようなものから周波数毎の強さを定量的に求める処理をスペクトル解析(spectrum analysis)と呼ぶ。

んで、そのスペクトル解析で求められたものを、x軸に周波数、y軸に強度をグラフにプロットしたものを周波数スペクトルと呼ぶ。

 

例↓↓↓(一応、強度については0〜1で正規化してある。)

f:id:tacky0612:20171212170734p:plain

 

 

窓関数とは

こんな関数↓

f:id:tacky0612:20171212171401p:plain

参考

僕の窓関数に対する理解は、『数値解析が容易になるべんりツール』って感じ。(簡単になるってニュアンスとはまたちょっと違う)

まぁ色んな窓関数があるわけだが、王道はハミング窓らしいので今回は無心でハミング窓を使用。

窓関数を用いる理由についてはここが分かりやすかった。

 

コード

まずは関数の定義。

#coding:utf-8
import wave
import numpy as np
import matplotlib.pyplot as plt
import random

def wave_load(filename):
    # open wave file
    wf = wave.open(filename,'r')
    channels = wf.getnchannels()

    # load wave data
    chunk_size = wf.getnframes()
    amp  = (2**8) ** wf.getsampwidth() / 2
    data = wf.readframes(chunk_size)   # バイナリ読み込み
    data = np.frombuffer(data,'int16') # intに変換
    data = data / amp                  # 振幅正規化
    data = data[::channels]
   
    return data


def fft_load(count,instrument,size,start,end):
    '''
    count = グラフに入れたい数
    instrument = 楽器のディレクトリ
    size = FFTのサンプル数(2**n)
    start =乱数の開始位置
    end = 乱数の終点位置

    '''
    st = 10000   # サンプリングする開始位置
    hammingWindow = np.hamming(size)    # ハミング窓
    fs = 44100 #サンプリングレート
    d = 1.0 / fs #サンプリングレートの逆数
    freqList = np.fft.fftfreq(size, d)
    
    for i in range(count):
        n = random.randint(start,end)
        filename = "./" + instrument + "/output/" + str(n) +".wav"
        wave = wave_load(filename)
        windowedData = hammingWindow * wave[st:st+size]  # 切り出した波形データ(窓関数あり)
        data = np.fft.fft(windowedData)
        data = data / max(abs(data)) # 0~1正規化
        plt.plot(freqList,abs(data))
         
    plt.axis([0,fs/16,0,1]) #第二引数でグラフのy軸方向の範囲指定
    plt.title(instrument)
    plt.xlabel("Frequency[Hz]")
    plt.ylabel("amplitude spectrum")
    plt.show()

    return data

 んで、各引数は以下の通り。

  1. グラフに入れたいWAVファイルの数
  2. 楽器のディレクトリ名(例. "piano")
  3. FFTのサンプル数 (2の乗数)
  4. 乱数の開始位置
  5. 乱数の終点位置

それでは、スペクトルを一個だけ表示してみる。

IN

fft_load(1,"piano",1024,0,1000)

 OUT

f:id:tacky0612:20171212173325p:plain

複数表示してみる。

IN

fft_load(7,"Eguitar",1024,0,1000)

OUT

f:id:tacky0612:20171212173500p:plain

うまく表示できました✋

 

サウンドプログラミング入門――音響合成の基本とC言語による実装 (Software Design plus)

サウンドプログラミング入門――音響合成の基本とC言語による実装 (Software Design plus)

 
入門 Python 3

入門 Python 3

 
退屈なことはPythonにやらせよう ―ノンプログラマーにもできる自動化処理プログラミング

退屈なことはPythonにやらせよう ―ノンプログラマーにもできる自動化処理プログラミング

 

 

 

 

 

おわりに・感想

この後、色んな楽器の周波数スペクトルを表示してみたが、目視では楽器ごとの周波数スペクトルの規則性みたいなのはあまり感じられなかった。(あくまで主観。)

しかし、低音楽器であるベースは周波数の低い位置に周波数スペクトルが固まっていた。↓

f:id:tacky0612:20171212173923p:plain

って、まぁ当たり前なんだが。

 

 あ、この前に鹿と戯れてきましたカワイイ。

 

 

【Python】複数のWAVファイルの波形を表示するプログラム【サウンドプログラミング】

どうもこんにちは。たっきーです。

最近カフェインをよく摂取している。

 

 

参考

ism1000ch.hatenablog.com

tacky0612.hatenablog.com

tacky0612.hatenablog.com

 

 

なにする?

WAVファイルの波形をmatplotlibで複数表示する。

ただし、読み込まれるデータはバイナリなので数値を変換する必要がある。

割りとここのパクってfor文で回してるだけな感じある。(ありがとうございます。)

 

環境

  • numpy 1.13.3
  • matplotlib 2.1.0

 

下準備

ここで作ったWAVデータを用意する。

まぁ、実は複数の連番WAVファイルを用意するだけでいいが。

 

コード

とりあえず関数の定義。WAVファイルの数値変換と振幅正規化してる。

time = ○○で表示する時間を指定できる。

わかりやすいようにx軸y軸にラベルとグラフタイトルと付け加えている。

一応、y軸を固定しないと勝手に最適化(?)されてしまうのでy軸を-1~1で固定してある。

#coding:utf-8
%matplotlib inline
import wave
import numpy as np
import matplotlib.pyplot as plt

def wave_plot(filename):
#     open wave file
    wf = wave.open(filename,'r')
    channels = wf.getnchannels()
#     print(wf.getparams())

#     load wave data
    time = 1 #sec
    chunk_size =44100*time #1sec = 44100
    amp  = (2**8) ** wf.getsampwidth() / 2
    data = wf.readframes(chunk_size)   # バイナリ読み込み
    data = np.frombuffer(data,'int16') # intに変換
    data = data / amp                  # 振幅正規化(-1~1)

    # make time axis
    rate = wf.getframerate()
    size = float(chunk_size)
    x = np.arange(0, size/rate, 1.0/rate)

    # plot マルチチャンネルに対応
    for i in range(channels):
        plt.plot(x,data[i::channels])

    plt.title(filename)
    plt.xlabel("time[s]")
    plt.ylabel("standerdized amplitude")
    
    plt.ylim([-1,1]) #y軸を-1~1で固定
    plt.show()
#     print(data)
#     print(len(data))
#     print(type(data))
#     print("===========================================================")

 

あとは 以下のように関数を呼び出すとグラフが表示出来る。

wave_plot("filename.wav")

 OUTPUT

f:id:tacky0612:20171128124424p:plain

多分、青色が左の音の波形で、橙色が右の音の波形(だと思う) 

 

 

 あとはfor文で回すだけ。

print("count of data")
count = input() #終点の位置を入力
print("instrument")
instrument = input()
for i in range(int(count)):
    name = "./" + instrument + "/output/" + str(i) +".wav"
#     print(name)
    wave_plot(name)

 OUTPUT(略)

f:id:tacky0612:20171128125141p:plain

 

こんな感じで表示される。

2列〜3列で表示したいんだがどうしたらいいんだ…って感じ。

 

詳解 ディープラーニング ~TensorFlow・Kerasによる時系列データ処理~

詳解 ディープラーニング ~TensorFlow・Kerasによる時系列データ処理~

 
入門 Python 3

入門 Python 3

 

 

 

おわりに・感想

ささっと書いたのですごい雑なコードになってしまった。。。

今回は波形を1秒毎に表示してみたけど、案外1秒間ずっと無音の部分とかあった。

なので、今度時間がある時に無音部分を判別してはじくプログラムとか組んでみたいなぁ(どうせやらない。)

楽器(今回はpiano,guitar,bass,drams)の音色を機械学習で分類したいんだが、流石に波形みるだけじゃあまり規則性みたいなものは目視ではあまり感じられなかった。(timeを短くしてみてもあまりわからなかった。)

もっとmatplotlibを上手く使いこなせるようになりてぇ。。。

 

 

 

 

【Python】WAVファイルを等間隔に分割するプログラムpart2【サウンドプログラミング】

どうも、たっきーです。

今回は前回の続き。

 

 

なにする?

前回のヤァツの強化版作りたい。

前回のヤァツはファイル一個に対してしか分割できなかったけど、今回は複数ファイルを一括で等間隔で分割したい。

 

前回

tacky0612.hatenablog.com

 

環境

  • numpy 1.13.3
  • scipy 0.19-.1

 

下準備

f:id:tacky0612:20171124154839p:plain

とりあえず等間隔に分割したいWAVファイルを0〜n.wavというファイル名で用意する。(nはファイル数-1)

データ数が100ぐらいならいいけど、それより多くなると面倒くさいので自動で0〜nで名前をふれる方法があれば教えて欲しいでござる…。

  

コード

import wave
import struct
import math
import os
from scipy import fromstring, int16

# 一応既に同じ名前のディレクトリがないか確認。
file = os.path.exists("output")
print(file)

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

def cut_wav(filename,time,start):  # WAVファイルを刈り奪る 形をしてるだろ? 
    # timeの単位は[sec]

    # ファイルを読み出し
    wavf = str(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)  # 秒数[sec]
    frames = int(ch * fr * t)
    num_cut = int(integer//t)

    # 確認用
#     print("Channel: ", ch)
#     print("Sample width: ", width)
#     print("Frame Rate: ", fr)
    print("Frame num: ", fn)
#     print("Params: ", wr.getparams())
    print("Total time: ", total_time ,"sec")
#     print("Total time(integer)",integer)
#     print("Time: ", t) 
#     print("Frames: ", frames) 
    print("Number of cut: ",num_cut)

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


    for i in range(num_cut):
#         print(i)
        # 出力データを生成
        outf = 'output/' + str(i+start) + '.wav' 
        start_cut = i*frames
        end_cut = i*frames + frames
#         print(start_cut)
#         print(end_cut)
        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 i+start+1 # 現在のカット済のファイル名の次を返す(はず)

n = 0
print("cut time = ")
cut_time = input()
print("Number of file = ")
a = input()
num_file = int(a)+1
print('=====================================================')
for name_file in range(int(num_file)):
    print("name_file : ",name_file,".wav")
    n = cut_wav(name_file,cut_time,n)
    print('return :',n)
    print('=====================================================')

 

前回書いたコードをちょっと手入れした。(ちょっと汚いコードなのは性格。)

cut_wav()をちょっと変更して、

  • returnでカット済のファイル名の次の値を返す
  • 始まりのファイル名を引数に追加

あとは、for文でcut_wav()を回してるだけ。 

分割したい時間と最後のファイル名(ファイル数-1)を指定できるようにしてある。

 

このコードをデータセットを同じディレクトリ内に保存する。

f:id:tacky0612:20171124163415p:plain

あとはターミナルで以下コマンド叩くだけ。

$ cd データセットとこのコードが入ってるディレクトリへのPath

$ python コード名(この場合cut_wav).py

 ってやると、分割したい時間と最後のファイル名(ファイル数-1)の入力を求められるので入力する。今回は1秒毎に分割したくて最後のファイル名が87.wavなので、cut_timeには1、Number of fileには87を入力する。

f:id:tacky0612:20171124164818p:plain

 こんな感じ。

   ↓ 終点 ↓

f:id:tacky0612:20171124164905p:plain

 最後のreturn(今回は1900)が分割後のファイル数になる。

同じディレクトリ内にoutputってディレクトリが生成され、その中に分割後のWAVファイルが格納されるようになっている。

f:id:tacky0612:20171124165215p:plain

 ※returnが1900なのに生成された最後のWAVファイルが1899なのは0〜1899で1900個であるため。(return に+1している為でもある。)

 

上手く分割出来た✌

 

退屈なことはPythonにやらせよう ―ノンプログラマーにもできる自動化処理プログラミング

退屈なことはPythonにやらせよう ―ノンプログラマーにもできる自動化処理プログラミング

 
サウンドプログラミング入門――音響合成の基本とC言語による実装 (Software Design plus)

サウンドプログラミング入門――音響合成の基本とC言語による実装 (Software Design plus)

 

 

おわりに・感想

88個のWAVファイルを分割することで1900個のWAVファイルに増やすこと(いや、増えてはいないが)が出来た。

考えられる問題点は、1秒で分割したので1秒間が全部無音とかが存在しそうってことかな。(検証してないのでわからん)

そういうのがあったら機械学習やる上で不都合なのでどうにかして除去したい。

今日はこんなくらいで、さよならー。

 

 

 

スポンサードリンク