コード認識
コード認識は、時間ごとに鳴っているハーモニーを推定し、開始/終了時刻と信頼度を持つコード区間を返します。キー検出と違い、「今鳴っているコードは何か」を見る局所的なハーモニー解析です。
コンピュータにとってのコードとは
コードは複数の音が 1 つのまとまりとして鳴るものです。たとえば C メジャーは C + E + G です。認識で重要なのは、オクターブや楽器ではなく、含まれるピッチクラスの集合で、これはまさにクロマが捉えるものです。C メジャーコードはどう配置されても C・E・G のビンを点灯させます。認識が生のスペクトルではなくクロマ上で動くのはこのためです。
クロマとテンプレート
libsonare はフレーム単位、またはビート同期したクロマ要約をコードテンプレート集合と比較します。テンプレートには 4 種類の三和音(major、minor、diminished、augmented)に加え、音を足したり変化させたりするより豊かなクオリティ(7th、9th、add9、half-diminished、sus 系)が含まれます。さらにその外側に、6th コード(6・m6)、マイナーメジャー 7th(mM7)、7sus4、ドミナントの拡張と変化音(11・13・7b9・7#9)があります。各区間で最も合うルートとクオリティが選ばれます。
上のクロマグラムと同じクリップでこの探索を走らせると、返ってくる区間はこうなります。
4 種類の三和音以外のテンプレートは、最良の三和音より一定以上よく一致しない限り採用されません。これにより、ノイズの多いクロマで普通の三和音が不安定な拡張コードに化けるのを抑えます。この「一定以上」は 1 種類ではなく 2 種類です。通常の余裕は相関で 0.05 で、三和音以外のクオリティには既定でこれが適用されます。7th、9th、sus2・sus4(3 音のコードですが、この比較では三和音側ではありません)、そして 6・m6・7sus4 もここに入ります。広いほうの 0.09 は、より素朴なコードで説明できる響きにテンションを 1 つ足しただけの 5 つのクオリティ(mM7、11、13、7b9、7#9)に限って使われます。
6th 系が広い余裕ではなくペナルティを受ける理由
6・m6・7sus4 の取り違えは「似ている」ではなく「一致する」性質のものです。maj6 は短 3 度下の m7 と、m6 はその下の m7b5 と、7sus4 は完全 4 度上の sus2add4 と(C7sus4 = C F G B♭ = Fsus2add4)、それぞれ同じ 4 つのピッチクラスで綴られます。クロマグラムにはこのペアを分ける情報がなく、分けられるのは後述のベース音の根拠だけです。そのためこれらのテンプレートは相関に固定で 0.07 のペナルティ(kAnagramQualityPenalty)を持ち、既定では従来からの読みを維持して、低域がルートを示したときにだけ 6th 側へ倒します。このペナルティの上にさらに広い余裕を重ねると、どれだけ根拠があっても選ばれなくなるため、余裕は通常の 0.05 のままです。
三和音だけか、全クオリティか
useTriadsOnly(C++ と Python では use_triads_only)は 2 つのテンプレート集合のどちらかを選ぶもので、中間はありません。オンなら 12 ルート × 4 種類の三和音で 48 テンプレート、オフなら 12 ルート × 24 クオリティで 288 テンプレートです。24 クオリティの内訳は、基本の 16 クオリティ(maj、m、dim、aug、7、maj7、m7、sus2、sus4、add9、madd9、dim7、m7b5、maj9、9、sus2add4)と、extended_chord_qualities() が加える 8 クオリティ(6、m6、mM7、7sus4、11、13、7b9、7#9)です。「7th までで拡張なし」という段階はありません。
2 つの入口で既定値が違います。detectChords() と ChordConfig は false のままなので全クオリティを探索します。曲全体を解析する analyze() / MusicAnalyzer は true にするので、useTriadsOnly: false を渡すまで、7th コードはその内側の三和音として返ります。
全クオリティを有効にすると、リードシートで実際に使われるラベルが手に入りますが、代償が 3 つあります。三和音以外のラベルはすべて余裕を超える必要があるため、境界線上の 7th はやはり三和音に落ち、持続するコードの途中で 2 つの読みが行き来することがあります。上に挙げた同音異名のペアも選ばれうるようになり、ベースパートのない素材ではペナルティだけで決まります。そして経過音や強い倍音でクロマがずれたときに当てはまりうる形が 20 種類増えるため、密で歪んだミックスでは短い拡張コードの区間が増え、minDuration とスムージングに吸収させることになります。安定したハーモニーの輪郭が欲しいなら三和音のみ、クオリティそのものが目的なら全クオリティ、という使い分けです。
イメージとしては、各フレームのクロマという「12 音の点灯パターン」を、あらかじめ用意したコードごとの理想パターン(テンプレート)と重ね、最もよく重なるものを選ぶ作業です。これを時間方向に並べるとコード進行になります。
タイミングとスムージング
コード検出はフレームレベルのクロマでも、ビート同期クロマでも実行できます。コード変化はビートにそろうことが多いため、通常はビート同期のほうが音楽的に整理された変化になります。スムージングと最小長のマージにより、ごく短いラベルのちらつきを避けます。
単体の detectChords() でもビート同期を使える
単体の detectChords() は useBeatSync が true のときにビートグリッドを推定します。曲全体の analyze() / MusicAnalyzer は共有するビート解析を再利用します。フレーム単位の境界が必要なら useBeatSync: false を渡してください。ビートグリッドを検出できない場合はフレーム単位のクロマへフォールバックします。オプションと既定値(useBeatSync、useHmm、chromaMethod、tuning)は楽曲解析にあります。
必要に応じて HMM スムージング(隠れマルコフモデル。各区間を単独で判定するのではなく、つながりとして自然なコード列を優先する手法)をコード候補にかけ、キーコンテキストを併用してちらつきをさらに抑えられます。ストリーミングでは推定が時間とともに更新されるため、十分な文脈がたまるまでは暫定値として扱います。
遷移モデルはカデンツを一律には扱わず、段階をつけて評価します。V7 -> I と v -> i は音度としては同じ動きなので、ルートの動きだけを見る判定では区別できません。しかし実際に解決しているのはドミナントのトライトーンであり、これはハーモニー上もっとも強い手がかりです。そのためトライトーンを含むドミナントのカデンツを最も優遇し、次に期待どおりのクオリティで綴られたカデンツ、そして解決力を持たないクオリティでカデンツ的なルートの動きだけがある場合、という順に重み付けされます。
よくある混同
ハーモニー的に近いコードは音を共有するため、置き換えが起きます。C メジャーと A マイナー 7 は 3 つのピッチクラスを共有し、コードに経過メロディ音が加わるとより豊かな拡張に見えます。
原因は、12 のピッチクラスを 1 本のベクトルへ畳み込むことそのものです。あるコードとその平行調側のコード、たとえば A と F#m は 3 音のうち 2 音を共有するため、クロマ上の根拠がほとんど同じになります。しかもそのベクトルには、共有された音のどれがルートなのかを示す情報がありません。
その手がかりを持っているのが低域です。そこで認識器は、各テンプレートのスコアに低域のルート顕著性の項を加え、ベースがルートを鳴らしている候補を優先します。項そのものより、その加え方のほうが重要です。
- 決定打ではなくタイブレークとして使います。ベース音がルートと一致するのは基本形のときだけです。クロマの明確な判定を覆せるほど大きな重みを与えると、すべての転回形がベース音のコードとして貼り替えられ、直そうとした混同より悪い結果になります。
- 低域が単独で示す値ではなく、低域が上乗せする分を測ります。候補ルートが低域エネルギーに占める割合から、同じ割合を和声クロマ上で測った値を差し引き、クロマがまだ持っていなかった分のベース根拠だけを残します。ベースパートのない素材では低域のクロマグラムが和声側の縮小コピーになり、2 つの割合が打ち消し合うため、この項は漏れ込みがたまたま有利にしたピッチクラスを後押しすることはなく、判定に影響しません。
隣接コード間のときどきの入れ替わりは想定してください。クリーンで持続する素材で最も強く、密で歪んだミックスでは倍音がテンプレートと照合するクロマをぼかすため最も弱くなります。信頼度の低い区間は UI で淡く表示するなど、確定ではなく候補として扱うと実態に合います。
検出したコードをプロジェクトの注釈にする
検出したコードは解析の出力で、秒単位の時間範囲とルート、ベース、クオリティからなります。プロジェクトはハーモニーを別の形で持ちます。タイムライン上の四分音符単位のコードシンボル注釈ストリームで、アレンジと一緒に移動し、保存と読み込みを経ても残ります。一方から他方へ移すには 2 つの変換が要ります。
コードそのものは chordSymbolFromAnalysis(chord) に通します。返り値は注釈のフィールド { rootPc, quality, extensions, slashBassPc } です。両者はクオリティの番号付けが違います。解析側には 25 のクオリティがあり、注釈側には 8 つのクオリティ族しかなく、残りは音度のエクステンションで表します。番号を手でずらすと、拡張和音のほとんどが誤ったラベルになります。ベースに B を置いた G7 は解析クオリティでは Dominant7(序数 4)で、変換すると { rootPc: 7, quality: 5, extensions: [7], slashBassPc: 11 } になります。基本形のコードは slashBassPc: 255 です。
時刻の変換は利用者に任されています。テンポが一定なら、秒の時刻は 秒 × bpm / 60 で四分音符単位になり、これで各エントリの startPpq と endPpq が決まります。あとは project.annotateChords([...]) がプロジェクトのコードストリームを 1 回の取り消し可能な編集で置き換え、保存したプロジェクト JSON では annotation.chords の下に入ります。フィールドの一覧と、対応する Python と C の名前は プロジェクト MIDI にあります。
libsonare がどう計算するか
ChordAnalyzer は STFT または NNLS クロマを作り、use_triads_only に応じて generate_triad_templates() か generate_all_chord_templates() を取り、テンプレート相関に低域ルートの項(bass_root_weight。C++ のみで、0 で無効)を加えて候補を採点します。三和音以外のテンプレートはそれぞれの余裕(既定は kTetradThreshold の 0.05、mM7・11・13・7b9・7#9 は kExtendedQualityThreshold の 0.09)を超えた場合だけ三和音より優先され、minDuration 未満の短い区間はマージされます。既定値は minDuration = 0.3、smoothingWindow = 2.0、threshold = 0.5、nFft = 2048、hopLength = 512、useBeatSync = true です。単体検出は有効時にビートを推定し、曲全体の解析は共有するビートグリッドを再利用します。ビートグリッドがない場合はフレーム単位のクロマへフォールバックします。公開バインディングでは、コードのルート、クオリティ、時刻、信頼度に加え、バインディングに応じて転回形、キー、HMM のオプションを扱えます。
進行を名前の並びではなくハーモニーとして読むには、各コードをキーに対するローマ数字(I、IV、V7、vi)と機能(tonic、subdominant、dominant、chromatic)でラベル付けします。chordFunctions({ chords, key }) は、すでに手元にあるコードに再検出なしで roman と function を付け足します。chordFunctionalAnalysis は検出とラベル付けを一度に行い、数値の文字列だけのリストではなく、同じ時刻付きエントリ(start、end、name、roman、function など)を返します。WASM バインディングでは detectKey が mode を名前('major'、'minor'、'dorian' など)で返し、これが chordFunctions の受け取る形です。