Wizard Notes

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

Python:直列オールパスフィルタによる人工残響の生成と試聴

信号処理に欠かせないオールパスフィルタ―」で紹介したオールパスフィルタの利用例として、人工残響フィルタを実装してみました。

設計方法

直列オールパスフィルタによる人工残響生成はシュレーダリバーブ の一部として知られています。

具体的には、多段のオールパスフィルタで構成します.

実装は簡単ですが,オールパスフィルタの性質により振幅は指数関数的に減衰し、出力信号の音色に変な色付けがされない*1となっていることから、残響にとって都合よい特性となっています。

なお、よりよい残響生成のために、各オールパスフィルタの遅延は互いに素で、遅延の値の大きさが違っているほうがよいようです。

また、gは0.7前後に設定されるようです。

フィルタ応答の比較

パルスを入力して一定時間の応答をプロットしてみました。

遅延の量をうまく設定し、オールパスフィルタの数を増やすと残響の量・時間が長くなっていることがわかります。

なお、各フィルタの遅延量を互いに素にしない場合、応答の時間波形は疎になる傾向があります。

試聴

ドライなピアノの音に人工残響フィルタを適用してみました。

ピアノの音色自体には変化がなく残響が付いていることが確認できます。

ただし、少し金属的な響きが聞こえるため、直列オールパスフィルタ単体で人工残響を生成する場合はそういった響きを利用するような使い方(プレートリバーブとか)になるかと思います。

Python実装

今回はリアルタイム信号処理向けの実装ではなくシミュレーション用途だったためscipy.signal.lfilterを使っています。

import numpy as np
from scipy.signal import lfilter
import soundfile as sf
import matplotlib.pyplot as plt


sr       = 48000
duration = 0.5

gain = -0.7
#delay_list = [100, 250, 1000, 2000]
#delay_list = [113, 347, 1021, 2017]
delay_list = [113, 347, 1021, 2017, 3041, 4211]

x = np.zeros(int(sr*duration))
x[0] = 1.0

#x, sr = sf.read("in.wav")
#x = x[:,0] + x[:,1]

y = np.zeros(x.shape)
y[:] = x
for delay in delay_list:
    b = np.zeros(delay+1)
    a = np.zeros(delay+1)
    b[0] = -gain
    a[0] = 1.0
    b[delay] = 1.0
    a[delay] = -gain
    
    length = max(a.shape[0], b.shape[0]) - 1
    
    y[:] = lfilter(b, a, y)
   
plt.subplot(2,1,1)
plt.plot(x)
plt.subplot(2,1,2)
plt.title(f"g={gain:0.2f}, delay_list={delay_list}")
plt.plot(y)

plt.tight_layout()
plt.show()

sf.write("out.wav", y, sr)

参考文献

*1:無色/有色でない/Colorless