Wizard Notes

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

適応逆ノッチフィルタによるサイン波/調波成分の抽出(Python実装)

前回紹介した適応ノッチフィルタ は、正弦波信号を除去するアルゴリズムでした。

www.wizard-notes.com

www.wizard-notes.com

しかし、音楽信号処理でよくある楽音を抽出したいという要求には適応ノッチフィルタは利用しにくいです。

そこで今回は、正弦波信号を抽出する適応逆ノッチフィルタを紹介します。

適応逆ノッチフィルタの構成

逆ノッチフィルタの周波数応答(振幅特性)は下図のようになります。

ちょうどノッチフィルタの振幅特性を反転したような特性です。

そのため、ノッチフィルタの回路を使って実装することができます。

逆ノッチフィルタの回路は以下のようになります。

赤い破線で囲まれているのがノッチフィルタの回路です。

[px]

この理由ですが、ノッチフィルタ出力信号の周波数特性は以下のようになります、

  • ωcから離れるほど、振幅・位相特性は入力信号とほぼ同じになる
  • ωcに近づくと、振幅は急激に小さくなる

従って、入力信号からノッチフィルタ出力信号を引くと、

  • ωcの遠方では、逆位相の関係で打ち消しあう
  • ωcの近傍では、ノッチフィルタ出力信号は入力信号の振幅に影響を与えない

というように動くため、結果的に入力信号のωc近傍の成分のみを抽出することができます。

[px]

[px]

なお、式を整理すると、

となるため、以下のような回路として扱うこともできます。

直列適応逆ノッチフィルタ

適応ノッチフィルタと同様に、適応逆ノッチフィルタも直列接続することで複数の正弦波を抽出することが可能です。

注意として、直列適応逆ノッチフィルタの構成は以下のように並列回路となります。

適応ノッチフィルタを直列接続することで複数の正弦波を除去した信号を得て、その信号の逆相信号を入力信号に加えることで複数の正弦波のみが残った信号を得ることができます。

実装

結局のところ、前回の適応ノッチフィルタ直列適応ノッチフィルタを利用すればOKです。

AdaptiveNotchFilterクラスは前回記事を参考にしてください。

if __name__ == "__main__":
    filepath = "./orgel.wav"
    savefilepath = "./save.wav"
    sig, sr = sf.read(filepath, always_2d=True)

    M = 8
    bandwidth = 0.99
    anf_list = [[AdaptiveNotchFilter(sr, bandwidth=bandwidth), 
                 AdaptiveNotchFilter(sr, bandwidth=bandwidth)] for m in range(M)]

    current_frame = 0
    blocksize = 512
    tmpdata = np.zeros((blocksize, sig.shape[1]))
    tmpdata2 = np.zeros((blocksize, sig.shape[1]))
    savedata = np.zeros(sig.shape)

    def callback(outdata, frames, time, status):
        global current_frame, tmpdata, tmpdata2, anf, anf_list
        chunksize = min(sig.shape[0] - current_frame, frames)
        tmpdata[:] *= 0.0
        tmpdata[0:chunksize] = sig[current_frame:current_frame + chunksize]

        for m in range(M):
            for k in range(outdata.shape[1]):
                anf_list[m][k].online(tmpdata[:,k], tmpdata2[:,k])
            tmpdata[:] = tmpdata2
            tmpdata2[:] *= 0.0
            
        tmpdata2[0:chunksize] = sig[current_frame:current_frame + chunksize]
        outdata[:] = tmpdata2 - tmpdata
        
        if chunksize < frames:
            raise sd.CallbackStop()
        else:
            savedata[current_frame:current_frame + chunksize] = outdata[:]
        current_frame += chunksize


    event = threading.Event()
    with sd.OutputStream(
        samplerate=sr, 
        blocksize=blocksize,
        channels=sig.shape[1],
        callback=callback,
        finished_callback=event.set
    ):
        event.wait()
        
    sf.write(savefilepath, savedata, sr)

実行結果

www.youtube.com

前回と同じ「正弦波1~2本+白色雑音」の音源に適用してみました。

適応ノッチフィルタの数は 1 (中図) と 8 (下図)を試しています。

適応ノッチフィルタの数が1つだと、前回は2つの正弦波が同時になっている区間ではうまく抽出できていません。

一方で、適応ノッチフィルタの数が8つだと2つの正弦波を抽出できています。

ただし、抽出に利用されていないフィルタが関係のない周波数成分を抽出しているため、ミュージカルノイズのような雑音が聞こえています。

[px]

まとめ

Pythonで適応逆ノッチフィルタによる調波成分の抽出を実装しました。

C言語でのプログラミングやチューニングのコツについては、プログラム101付き 音声信号処理 が参考になると思います。

参考文献