Wizard Notes

Python, JavaScript を使った音楽信号分析の技術録、作曲活動に関する雑記

scipy.signal.oaconvolve:FIRフィルタの畳み込み演算を効率的に行うオーバーラップ加算法の使い方と計算速度について

はじめに

音信号処理では音を加工する1つの方法としてFIRフィルタがよく使われます。

具体的な計算としては、元の信号とFIRフィルタ信号の畳み込み演算を行います。

この畳み込み演算を時間領域で素直に行うと、元の信号長×FIRフィルタ長の計算が必要です。

従って、例えばコンボリューションリバーブのようなFIRフィルタ長が比較的大きい場合では、計算量が多くなってしまう問題があります。

そこで、この畳み込みを効率的に行う方法としてオーバーラップ加算法(オーバーラップアド、重畳加算法)が知られています。

この手法は SciPyで scipy.signal.oaconvolve として実装されているため、すぐに利用することができます

そこで、この記事では scipy.signal.oaconvolve の使い方を紹介します。

また、時間領域での畳み込み演算と処理速度を比較します。

使い方

scipy.signal.oaconvolve — SciPy v1.8.0 Manual

SciPy のバージョン 0.14 以上で利用できます。

基本的に通常の畳み込み演算 scipy.signal.convolve と同じ引数でOKです。

すなわち、元信号とFIRフィルタを in1, in2 に引数として与えます。

scipy.signal.oaconvolve(in1, in2, mode='full', axes=None)

処理速度の比較

FIRフィルタ長をいくつか変えて実験してみました。

元の信号長は 1024**2サンプルで固定しています。

f:id:Kurene:20220305225759p:plain:w500

この結果から、FIRフィルタ長が小さいときは通常の畳み込み scipy.signal.convolveのほうが速いですが、FIRフィルタ長が大きくなるとscipy.signal.oaconvolveのほうが効率的であることが分かります。

import time
import numpy as np
from scipy import signal
from functools import partial

def measure_time(func, n_trial=10):
    elapsed_time = 0.0
    for k in range(0, n_trial):
        start = time.time()
        func()
        elapsed_time += time.time() - start
    elapsed_time /= n_trial
    
    return elapsed_time
    
for x_len in [1024**2]:
    for h_len in [16, 64, 256, 2048, 8192]:
        x = np.random.normal(0.0, 0.1, x_len)
        h = np.random.normal(0.0, 0.1, h_len) * np.linspace(1.0, 0.0, h_len)**2
        
        print(f"x: {x_len}, h: {h_len}")
        elapsed_time = measure_time(partial(signal.convolve,   x, h))
        print ("convolve\t", f"\t{elapsed_time:.3f} sec")
        elapsed_time = measure_time(partial(signal.oaconvolve,   x, h))
        print ("oaconvolve\t", f"\t{elapsed_time:.3f} sec")

まとめ

畳み込み演算を効率的に行うオーバーラップ加算法の Scipy 実装である scipy.signal.oaconvolve の使い方と計算速度の検証を行いました。

オーバーラップ加算法はバッチ処理だけでなくリアルタイム処理でも利用できる手法なので、FIRフィルタを使うプラグインを自作するときにも役立ちます。