調波打楽器音分離とは?
一般的な楽曲では、様々な楽器音が含まれています。 そのため、元の楽曲信号から直接、音楽的な情報(例:コード進行)を分析するのは 計算機ではなかなか難しいです*1。
そこで、分析の前処理として、 打楽器の音と非打楽器(調波楽器)の音を分離する調波打楽器音分離 (HPSS: Hermonic/Percussive Source Seperation) *2が良く使われています。
この記事では、HPSSの概要とPython (LibROSA) のコードの解説します。
調波打楽器音分離のアイディア
フーリエ変換等を使って周波数に変換した楽曲信号を考えます。
上記の画像のように、楽曲信号には複数の楽器の音が含まれています。
調波打楽器音分離では、楽曲信号は調波楽器音と打楽器音を足し合わせた構造であると考え、
楽曲信号から調波楽器音と打楽器音を推定します。*3
ただし調波/打楽器音は未知であるため、何らかの仮定をおく必要があります。
調波打楽器音分離では、上記の画像を見て気づくように、
- 調波楽器音: 時間方向に連続的
- 打楽器音: 周波数方向に連続的
であるという特徴を用いることで、調波/打楽器音を推定します。 (もしくは、調波/打楽器音について何らかの事前学習を行う手法もあります。)
アルゴリズムの設計
非負値行列因子分解+基底クラスタリング
非負値行列因子分解を用いて複数の基底を求めた後、
その基底をSVM(サポートベクターマシン)で調波/打楽器音基底に2クラス分類します。そして、調波/打楽器音に分類された基底のみを用いて対応するアクティベーション行列との行列積を求めることで、調波/打楽器音のみの信号のスペクトログラムを推定します。なお、基底分類用の2クラスSVMの学習を事前学習しています。
最適化問題(行列因子分解)として解く
次のコスト項と制約に基づく最適化問題として解く手法です。
- 調波/打楽器音の時間/周波数方向の連続性に基づくコスト項
- 等式制約
- 非負値制約
以下の論文では補助関数に基づく反復アルゴリズムにより、調波/打楽器音のスペクトログラムを推定します。
深層学習を使った手法
MaD TwinNet
というニューラルネットを使った手法が2018年に提案されています。
名称の通り、"MaD (Masker and Denoiser)"と呼ばれるネットワーク構造と、TwinNet という2つの構造から成るネットワークを使っています。
メディアンフィルタベースの手法
最適化問題として解く場合、アルゴリズムが複雑になり、また、どうしても計算量が多くなってしまいます。そのため、大量の楽曲データを解析したり、高速な処理が求められたり、計算資源が限られているユースケースでは使いづらいことがあります。
一方で、以下の論文では、画像処理などでよく使われているメディアンフィルタを用いることで、高速に調波/打楽器音を推定しています。
Harmonic/Percussive Separation using Median Filtering - ResearchGate
この手法では、周波数/時間方向の1次元メディアンフィルタを元の楽曲信号にフィルタリングすることで調波/打楽器音を算出します。LibROSAの実装もこちらの手法がベースとなっています。
LibROSAにおける調波打楽器分離(HPSS)の実装
使い方
# Extract harmonic and percussive components y, sr = librosa.load(librosa.util.example_audio_file()) y_harmonic, y_percussive = librosa.effects.hpss(y) # Get a more isolated percussive component by widening its margin y_harmonic, y_percussive = librosa.effects.hpss(y, margin=(1.0,5.0))
実装の詳細
LibROSAのHPSS(調波打楽器音分離)の実装を見ていきます。
librosa.effects.hpss()
楽曲信号y
をそのまま引数にできる、便利なラッパーです。
中身は、時間周波数領域への変換と逆変換を行っているだけです。
y_harmonic
、y_percussive
がそれぞれ調波/打楽器音です。これら信号を様々な分析に利用したり、```librosa.output.write_wav()````で書き出したりします。
HPSSの本体はlibrosa.decompose.hpss()
であり、それに時間周波数領域の楽曲信号をstft
渡しています。
def hpss(y, **kwargs): # Compute the STFT matrix stft = core.stft(y) # Decompose into harmonic and percussives stft_harm, stft_perc = decompose.hpss(stft, **kwargs) # Invert the STFTs. Adjust length to match the input. y_harm = util.fix_length(core.istft(stft_harm, dtype=y.dtype), len(y)) y_perc = util.fix_length(core.istft(stft_perc, dtype=y.dtype), len(y)) return y_harm, y_perc
librosa.decompose.hpss()
処理の流れは以下通りです。
- 時間周波数領域の楽曲信号
S
を、振幅S
と位相phase
に分離 kernel_size
より、調波/打楽器音成分用のメディアンフィルタの長さwin_harm
/win_perc
を設定margin
より、調波/打楽器音成分用の閾値を設定(後述)- 周波数/時間方向の1次元メディアンフィルタを適用し、それぞれ調波/打楽器音の振幅スペクトログラム
mask_harm
、mask_perc
を算出 - ソフトマスク(ウィーナーフィルタ)
librosa.util.softmask()
を適用margin_harm/margin_perc > 1
の場合、その値に応じて閾値処理
- 返り値
mask==True
: 調波/打楽器の振幅スペクトログラムを返すmask==False
: 元の楽曲信号の位相をそのまま利用して、調波/打楽器の時間周波数領域の信号を返す
def hpss(S, kernel_size=31, power=2.0, mask=False, margin=1.0): # (1) if np.iscomplexobj(S): S, phase = core.magphase(S) else: phase = 1 # (2) if np.isscalar(kernel_size): win_harm = kernel_size win_perc = kernel_size else: win_harm = kernel_size[0] win_perc = kernel_size[1] # (3) if np.isscalar(margin): margin_harm = margin margin_perc = margin else: margin_harm = margin[0] margin_perc = margin[1] # (4) Compute median filters. Pre-allocation here preserves memory layout. harm = np.empty_like(S) harm[:] = median_filter(S, size=(1, win_harm), mode='reflect') perc = np.empty_like(S) perc[:] = median_filter(S, size=(win_perc, 1), mode='reflect') ... # (5) mask_harm = util.softmask(harm, perc * margin_harm, power=power, split_zeros=split_zeros) mask_perc = util.softmask(perc, harm * margin_perc, power=power, split_zeros=split_zeros) # (6) if mask: return mask_harm, mask_perc return ((S * mask_harm) * phase, (S * mask_perc) * phase)
LibROSAの実装では、メディアンフィルタによって算出した調波/打楽器音の振幅スペクトログラムをそのまま使うのではなく、ソフトマスク(ウィーナーフィルタ)を利用することで聴感的な歪みを軽減しています。
また、調波/打楽器音を抽出しやすくするために、mask_harm
、mask_perc
という変数の値によって閾値処理を行っています。これは下記の論文で提案されている手法です。
Extending Harmonic-Percussive Separation of Audio Signals - ISMIR 2014
以下のページでは、この手法を適用した結果が公開されています。
https://www.audiolabs-erlangen.de/resources/2014-ISMIR-ExtHPSep/www.audiolabs-erlangen.de
LibROSA: 調波打楽器分離(HPSS)の適用例
コード
import librosa import numpy as np import matplotlib.pyplot as plt # Main sr = 16000 margin = 1.5 offset = 116 duration = 20 filepath = "02 Retrograde Amnesia.mp3" y, sr = librosa.load(filepath, sr=sr, offset=offset, duration=duration, mono=True) y_harm, y_perc = librosa.effects.hpss(y, margin=margin) # Dump signals librosa.output.write_wav('hpss_org.wav', y, sr) librosa.output.write_wav('hpss_harm.wav', y_harm, sr) librosa.output.write_wav('hpss_perc.wav', y_perc, sr) # Plot plt.subplot(3,1,1) plt.plot(y) plt.title("Original signal") plt.subplot(3,1,2) plt.plot(y_harm) plt.title("Harmonic signal") plt.subplot(3,1,3) plt.plot(y_perc) plt.title("Percussive signal") plt.tight_layout() plt.show()
実際の楽曲への適用
自身の楽曲に適用してみました。
課題としては、
- 調波楽器(エレキギター、ボーカル)のアタック音が打楽器音の方に含まれてしまう
- (特に)打楽器音の音質が悪い
が挙げられます。
コード進行やリズムの分析などの用途としては十分かもしれませんが、調波/打楽器音単体を聞きたいというような用途に対してはまだ余地があるように思います。そのような場合、パラメタ(例:フィルタサイズ、マージン)をチューニングしてみるか、最適化ベースの手法を検討してみるのもよいかもしれません。
また、調波音を取り出すメディアンフィルタが時間方向に大きな幅を持つことが、リアルタイム処理の場合は問題となります。従って、フィルタの形状等に何らかの工夫が必要になります。
まとめ
調波打楽器音分離のLibROSAでの実装を中心に解説しました。