StudioOne 4のChordTrack機能のしくみを考えてみる(1)

Studio OneでDTMerやってるしゃをみんです。活動履歴はぜひWORKSPROFILEページをご確認ください。つい最近新曲を出したし新しい曲も近くリリース予定です。

先日Studio Oneの第4世代が発表されました。とても面白い機能が多数実装されましたが、ChordTrackという新機能がひときわ目を引いたかと思います。発表を見たときはCubaseのアレを思い浮かべてほほう、今回はStudio Oneが追随する側に回ったのかな??などと一瞬思ったのですがよく見ると予想のかなり上をゆくもので、何とインストゥルメント/オーディオトラック問わずコード進行の自動解析、更には任意のコードへ一発変換できちゃうという、とても面白い技術でした。

しかし一体どうやったのか、とても不思議な技術だなと感じた方も多いと思いますが、多重ピッチ音声の採譜から個別音符のピッチシフト(本質的には音源分離問題)まで、ノウハウはある程度揃ってはいるので、実際に僕らの手でそれっぽいプログラムを作り上げることも可能だと思います(実際のStudio Oneに関しては、多分メロダインの技術でも借りたのかなと勝手に推測していますが)。

なので、自分なりの方法でChordTrack的な機能を作ってみよう、というのが今回の記事の目的です。

ちなみに考えるのはオーディオの方です。インストゥルメントの方は普通にロジック決めて並び替えれば良いのは想像しにくく無いですが、具体的にどういうロジックなのか、StudioOne4持ってる方で細かく考察してくれる人が居ればいいなーと思います(自分は諸事情でアップグレードは保留中です)。

コード認識

自分は音楽作品のコード進行認識というタスクを研究している者なので、この機能がDAWに実装されたという事実は特に興味深く感じました。自分の研究で扱うオーディオは色んな音が重畳した複雑なミックス音源なので、結構機械学習とかDeep Learningに助けてもらわないとそこそこ良い精度には届かないのですが(しゃをみんのQiita記事や本ブログの過去記事を参照)、ChordTrackの場合、DAWのシングルトラックを扱う、というところが特徴です。このオーディオデータは、

  1. (大体の場合)単楽器である。打楽器など余計な雑音が入らないクリーンな音声。
  2. 表裏拍の位置があらかじめ分かっている。

というたいへん都合のいい設定を持っているので、自分が扱う問題よりもだいぶハードルは低く、扱いやすくなります。おそらく、①多重音高解析(メロダインのDirect Note Access機能とか)で音声をピアノロール形式に採譜し、②ビート単位でコードの種類を判断する、という手順で十分でしょう。

さて、ここでは更にハードルを下げて、ピアノ音限定で実装を考えてみます。イケイケなディープラーニングを含め自動採譜の方法は色々ありますが、ここでは基本的な手法であるNMF(Nonnegative Matrix Factorization)を採用します。

NMFのきほん

NMF、訳して非負行列因子分解。2つの行列の積を求める方法を数学で習ったと思いますが、逆に行列を二つの非負行列の積に分解するのがNMFです。数式であらわすと、

行列Vを与えられた時、行列WとHを(近似的に)求める、という問題です。ここでkはコンポーネント数と言い、需要によって事前に決めておく整数です。

楽器音声で考えてみましょう。音声は短時間フーリエ変換を用いて、横軸が時間、縦軸が周波数の二次元行列に変換することができます。スペクトログラムというやつで、これが上の数式の行列Vにあたります。

そして右の積では、行列Wのn列目はとある単音(ピアノの一音など)の周波数域パターン(templateと呼ぶ)、行列Hのn行目はその単音の時間域上の音量変化(activationと呼ぶ)をあらわします。このn列目とn行目の行列積がこの単音のスペクトログラムになります。

さらにすべてのk行とk列の行列積から得られた、k個の単音スペクトログラムを足し合わせると元の音声のスペクトログラムになります。この計算過程はまさに、WとHの行列積を求める過程にあたりますね。

おさらいすると、コード音声のスペクトログラム(行列V)を、k個の単音音声の周波数域パターン(行列W)と音量変化情報(行列H)に分解する、それがNMFがやっていることです。そしてこの過程で得られた行列Hが、我々が求めたいピアノロールになります。

この分解の解は一意的に求まるものではなく、一般的にはWとHを(W・HとVの距離がなるべく小さくなるように)繰り返し更新していくことで近似解を求めるアプローチをとります。

以下より、実際の実装を考えていきます。以下の論文を参考しています。

A. Dessein, A. Cont, G. Lemaitre. Real-time polyphonic music transcription with non-negative matrix factorization and beta-divergence. 11th International Society for Music Information Retrieval Conference, Aug 2010.

今回は初めて、計算過程をJupyter notebookで記録してみました。ぜひそちらと併せて読み進めてみてください(リンク)。

Templateベクトルの学習

繰り返し更新の方法でWとHを求めるには、まず両行列を初期化する必要があるのですが、ランダムに初期化しても自動的に期待する形に変化してくれません。なので、事前に片方の行列を決めておいて、もう片方の結果を「誘導」する必要があります。

ここで事前に決めることができるのは、各音高の周波数パターンをあらわす行列Wです。Wの各列は、ピアノの各鍵の音を用意して、それぞれk=1のNMFを走らせることで求まります。

求まったTemplateたちを可視化すると(In[3])、ちゃんと各ピッチの倍音構造が学習できているのがわかりますね。

(細かい話:上の説明では、1音高につき1コンポーネントが割り当てられており、これは単音の周波数域の形が常に不変であるという仮定の上に立っていますが、実際のピアノ音はアタックとサステインで倍音構造がだいぶ違うので、あまり良い仮定ではありません。今回はピアノロールを求めたいだけなので大きな問題はありませんが、元音声から単音を分離するなど、より良い近似を求めたい場合は、1音高にコンポーネントを二、三個割り当てて、行列の階数を増やすという工夫ができます)

採譜

各音高のTemplateベクトルをくっつけて行列Wができました。あとはNMFのアルゴリズムで行列Hを推定します。

実験用に、以下のコード進行をピアノ音源で弾いて書き出しました。

C – G/B – Amin – Emin/G- Dmin – C#aug – F/C – G7

このピアノ音声のスペクトログラムが、分解前の行列Vになります(In[4])。

NMFの近似アルゴリズムは、普通はWとHを交互に更新するのですが、ここではWを固定し、Hだけを繰り返し更新します。求まった行列Hをみると(In[5])、わりと正確に採譜できているのがわかりますね。

Hを二値化して、メロダインが検出した結果と比較してみましょう。

メロダインもまだまだですね。

(技術的な詳細: 論文の設定に沿って、NMFの最適化基準はbetaダイバージェンスとし、Template学習の際はβ=2(すなわちユークリッド距離)、採譜の際はβ=0.5に設定しました。)

コード判定

上に求まった行列Hを、しきい値を設定して二値化して、本当の意味でピアノロールにするも良いのですが、コード認識だけ行うのであればHをそのまま使う方がより確実だと思います。Hを一拍単位に分割し、それぞれコードの定義に当てはめればよし。

(具体的な手順:Hを分割→各セグメントを時間方向に加算して1ベクトルに圧縮→ベクトルを正規化→低音を判別する→12次元クロマベクトルに圧縮・正規化→各コードのバイナリテンプレートとの相関距離を計算、最小距離のコードが選ばれる)

認識結果をみると(Out[8])、今回は正確に認識できたようです。

まとめ

今回の実験では、音色をピアノに限定して、コードを自動認識する手順まで考えました。NMFを使って音声の採譜を行い、各拍ごとにコードを判定するという手順です。採用した手法自体はとても基本的なもので、もちろんいくらでも改良の余地はあると思います。

次回は目玉機能である、コードの変更機能について考えてみます。こちらもNMFを使いますが、採譜ではなく綺麗に各音高を分離したいので、もう少し工夫を加えることになります。