読者です 読者をやめる 読者になる 読者になる

すこしふしぎ.

VR/HI系院生による技術ブログ.まったりいきましょ.(友人ズとブログリレー中.さぼったら焼肉おごらなきゃいけない)

【python】フレームごとのフィルタリング - オーバーラップアド(wip)【サウンドプログラミング】

python SoundProcessing

こんばんは.1000chです. 久しぶりにXSSの勉強をしているのですが,思うように攻撃することができないです(もちろん練習用サイトですよ). 自分が攻められないということは,無意識にwebサイトにその脆弱性を埋め込んでいる可能性があるということなので, 精進しないといけないなぁと思う次第であります.

さて今回は久しぶりにサウンドプログラミングを遣って行こうと思います. 以前いろいろなエフェクターを作りましたが,今のところ時間領域データのままで扱えるエフェクターしか作っていません. ということで,次の目標は「周波数領域データをいじくるエフェクタをつくること」にします.

今回はまず,入力信号を周波数領域でいじくる為の手法,オーバーラップアドについて書きます.

周波数領域でデータをいじくるってどゆこと?

まずはここの説明から行きましょう.

いまま作っていたエフェクターは,時間領域のデータをそのまま加工していました. 要するに,波形をそのまま歪めるなり重ねるなり弄っていたということです. 例えばリミッタは振幅振幅が規定値を超えたらある値に置き換える,ディレイは波形を記録して遅れて加算する,というような処理でした. どちらも入力信号を直接いじって出力信号を計算しています. 図にするとこんな感じです.

f:id:ism1000ch:20140805233559j:plain

ところで,信号はFFTすると周波数領域のデータ(スペクトル)になり,そのデータをIFFTすると元の信号に戻るのでした. この性質を利用し,入力信号をスペクトルに変換,スペクトルにフィルタをかけてから出力信号を計算するというエフェクトの掛け方があります. これが周波数領域でデータをいじくる,ということです. わかりづらいので図にしましょう.

f:id:ism1000ch:20140805233947j:plain

言葉にすると複雑に聞こえますが,やってること自体はそんな難しくないですね.

ようは

  • FFT(DFT)して
  • フィルタかけて
  • IFFT(IDFT)する

だけです.

フレーム単位のFFT

しかし,現実はそんなに甘くはありません. というのも,FFT入力信号は周期的なものであることを仮定しているからです. ずっと同じ音が続くので無い限り,音楽,音声の周波数特性は時間的に変化していきます. であれば,当然入力音声が周期的であるとは考えにくいですね.

じゃどうするのかというと,入力音声を細かいフレームに区切って考えていきます. 秒単位でみたら変化している音声でも,ミリ秒単位でみれば周期的であると言えるでしょう. 図にするとこんな感じ.

f:id:ism1000ch:20140806000603j:plain

このフレーム単位であれば周期性を仮定できるので,FFT / IFFTにより適切な波形に復元できます. というわけで,周波数領域でデータをいじる為には,フレーム単位でフィルタリングを行い,各出力を適宜合成する必要があります.

オーバーラップアド

ただし,何も考えずにフレームを区切ってつなぎ合わせるだけでは問題が発生します. 1フレーム目の終わりと2フレーム目の始まりデータがズレた場合,波形データが不連続となりノイズの原因となってしまうのです. 図にするとこんな感じ.

f:id:ism1000ch:20140806001528j:plain

そこで,「各フレームの特徴は保ちつつ」「滑らかに接続する」必要が出てきます.

ここで出てくるのがオーバーラップアドという手法です. オーバーラップ(overlap:重ねて)アド(add:足す)の名の通り,この手法ではフレームをズラしつつ,窓関数で重み付けをして加算していきます. イメージはこんな感じ.

f:id:ism1000ch:20140806003630j:plain

  • フレーム1を取り出し窓関数をかけて適宜処理を行う.
  • 同じくフレーム2も窓関数をかけて処理を行う.
  • これらをもとの時間軸上で加算する.

以上がオーバーラップアドのやり方です. 図はfilterと窓関数の順が逆っぽく見えてしまいますねミスですごめんなさい

これなら確かに不連続とかもなさそうです. 参考文献ではズラし量としてN/2,重み付けとしてハニング窓を利用していました. 場合によって重みづけはリニアでもいいかもしれません.

ともあれ,これで周波数領域のデータいじりもできそうです.

プログラム化してみた...しかし...

では,以上のオーバーラップアドを行うプログラムを作ってみます.

とりあえず今回はフィルタ効果はなしで,入力波形をそのまま返すようにします.

# coding:utf-8

import numpy as np
import scipy.io.wavfile as scw
import matplotlib.pyplot as plt
import pyaudio as pa

src_name = "./test_voice_mono.wav"
N = 1024

def process(data):
    # 与えられたデータに窓かけてfftしてフィルタかけてifft
    window = np.hanning(N)
    dt = data * window
    
    spectrum = np.fft.fft(dt,n=N)
    #フィルタかけたかったらここでspectrumをいじくる
    
    ret_dt = np.fft.ifft(spectrum,n=N)
    return ret_dt


if __name__ == '__main__':
    rate, data = scw.read(src_name)

    ret_data = np.zeros(data.size)
    index = 0

    while(ret_data[index:].size > N):
        ret_data[index:index+N] += process(data[index:index+N])
        index += N/2

    plt.subplot(2,1,1)
    plt.plot(np.arange(data.size),data)

    plt.subplot(2,1,2)
    plt.plot(np.arange(data.size),ret_data)

    plt.show()

実行結果

f:id:ism1000ch:20140806004917p:plain

見た目はそれっぽいですね.

しかしこの音を聞いてみると...

なんだこのノイズは!!!

というぐらいにノイズが載っていました(´・ω・`)

何が原因なのだろうか..このままだと使い物にならないよ(´・ω・`)

まとめ

  • 周波数領域でデータいじりたい!
  • フレーム分割して周波数領域フィルタリングすればok
  • フレーム間はオーバーラップアドつかえばok
  • プログラム書いた
  • ノイズばかりだった(´・ω・`)あっれー