Wizard Notes

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

クロマベクトルから音程に基づく特徴量:調性中心を算出(和音分析,拍検出)

音楽信号分析に使える良さげ特徴量を探していたところ、LibROSAにも実装されている tonal centroid features (調性中心特徴) という特徴量を見つけました。

音楽理論(音程の性質)と信号処理を組み合わせた手法であり、和音や和音進行分析に使える、シンプルかつ強力な特徴量となっています。

以下が元論文になります。

www.semanticscholar.org

Harte, C., Sandler, M., & Gasser, M. (2006). “Detecting Harmonic Change in Musical Audio.” In Proceedings of the 1st ACM Workshop on Audio and Music Computing Multimedia (pp. 21-26). Santa Barbara, CA, USA: ACM Press. doi:10.1145/1178723.1178727.

技術の肝

この手法の特徴は、クロマベクトルを音程空間ベクトルに変換するところです。

後述する三角関数(sin, cos)の基底(変換行列)に基づいて、

  • 五度圏
  • 短三度圏
  • 長三度圏

3つの音程空間(実際は6次元空間)にクロマベクトルの強度をマッピングします。

この音程空間は、音程の関係性を表す手法である Tonnetz に基づいています。

Tonnetzは平均律を仮定しオクターブを区別しないことで、無限に循環することからトーラスとして考えることができます。クロマベクトルの考え方と非常によく似ています。

このトーラス/Tonnetz 上でのノードの3つの方向(完全5度、短3度、長3度の間隔)に基づいて、それぞれの音程空間を構築することができます。

f:id:Kurene:20210306192912p:plain

実装方法

処理フロー

f:id:Kurene:20210306174118p:plain

まず前段の処理として、クロマベクトルを算出する必要があります。 論文では定Q変換 (CQT) の対数スペクトルと書かれていますが、例えば低演算量化のために短時間フーリエ変換 (STFT) ベースの手法を使うのもアリだと思います。

調整中心変換

音程空間ベクトルは、L1ノルムで正規化したクロマベクトルと、三角関数からなる変換行列との積として算出することができます。

f:id:Kurene:20210306172357p:plain

なお、この変換行列は事前計算できます。

変換行列の具体的な算出方法は以下になります。

f:id:Kurene:20210306181920p:plain

この三角関数波基底は2次元で1セットであり、極座標系として見ると理解しやすいと思います。

この基底により、ピッチクラス12音の強度は下図の3つの部分空間にマッピングされます。

f:id:Kurene:20210306182019p:plain

各部分空間では、原点に対する角度と距離によってそれぞれの音程知覚が表されます

L1ノルムで正規化するため、かならず円内部に Tonal Centroid があることが保証されています。

このTonal Centroid の座標の違いや変化によって、例えば和音の種類や和音進行の変化点を検知することが期待されます

なお、r1, r2, r3 は重み付けのための係数(円の半径)です。

librosa.feature.tonnetz

具体的な実装方法は、LibROSAのlibrosa.feature.tonnetzソースコードが参考になります。

主要な処理部のみ引用します。

github.com

def tonnetz(y=None, sr=22050, chroma=None, **kwargs):
    
    ...

    # 変換行列 phi 作成
    dim_map = np.linspace(0, 12, num=chroma.shape[0], endpoint=False)

    scale = np.asarray([7.0 / 6, 7.0 / 6, 3.0 / 2, 3.0 / 2, 2.0 / 3, 2.0 / 3])

    V = np.multiply.outer(scale, dim_map)

    # 0, 2, 4行目をcos()からsin()に
    V[::2] -= 0.5

    # 音程空間の円半径
    R = np.array([1, 1, 1, 1, 0.5, 0.5])  # Fifths  # Minor  # Major

    phi = R[:, np.newaxis] * np.cos(np.pi * V)

    # L1正規化したクロマベクトルとの内積計算
    return phi.dot(util.normalize(chroma, norm=1, axis=0))

どんなところで使えそうか

  • 協和・不協和具合の分析
  • コード進行,和音境界位置の分析
  • 楽曲構造の分析
  • 変化点検出
    • コード変化点検知
    • 拍・小説の検出