🎹

BenzaitenAdlibのざっくり解説

2023/02/28に公開

これは何?

先日開催された「AIミュージックバトル!弁財天」にて使用したプログラム BenzaitenAdlib後処理について説明する記事です。

当日の解法共有LTでもお話しましたが、このプログラムは学習フェーズが公式サンプルのものと同じであるため、この記事では後処理についてのみ説明します。

用語

  • ノート とは、音符のことです。
  • ノート番号 とは、ノートの音の高さのことで、雑に言うと鍵盤でのキーの番号です[1]0 から 127 までの値をとり、ト音記号表記時の下第1線が 60 です。
  • 音のクラス とは、あるノート番号の十二平均律における音程で、要するにノート番号を 12 で割った余りです
  • オクターブ とは、一般に8度音程のことですが、この記事においては、音名の指定がない状態で登場した場合に「あるC(C, ハ音)からその上の最も近いB(H, ロ音)までの区間」を指します。

特殊記法

  • 有限数列 a_n の要素数は \text{len}(a_n) で表します。
  • ノート番号 n が属するオクターブにおけるクラス c の音を \mathbb{O}_n(c) で表します[2]

その他

  • 数列のインデックスはすべて1始まりです。

動作概略

モデルが生成したメロディラインを受け取り、下記のA,B,C,Dを順番に適用します。

A. メインノート列補正

モデルが生成するメロディは、128 個の16分音符として受け取れます。
これらに対してそれぞれ処理を行います。

ただし、

  • 処理中のノート番号を n (0 \le n \le 127) とします。
  • n が属する小節の伴奏に設定されたコードC_n (斜体のC)とします。
  • C_n の構成音クラスの列を \{c_n\} (0 \le c_n \le 11) とします。

以下、 A0, A1, A2, A3 の順に実施されますが、A1 と A2 は、いずれか一つだけ実施されます

A0: コードサフィックスの確率的除去

関数の引数として与えられる確率 p (0 \le p \le 1)C_n のサフィックスを除去します。
自明ですが、サフィックスを除去すると \text{len}(c_n) = 3 になります。

A1: 「攻め」の補正

A1-1: 開始音補正

最初から3個の16分音符(結合処理により実質符点4分音符1個分)についてはノート番号を 60 + x に更新します。
ただし x は、nが最初の音であるときに限り以下のように決定されます。

確率(%)
60 c_{\text{len}(c_n) - 1}
40 \{c_n\} からランダムに一つ選んだ値

n が2番めまたは3番目の音である場合は、1番めに選んだ音と同じ音で上書きします。

A1-2: 第5小節補正

n第5小節の冒頭から符点2分音符分の区間にある場合、約 45 % の確率で nQ_j で置き換えます。
ただし、

  • Q=\{\mathbb{O}_{k+6}(c_{\text{len}(c_n) - 2}), \mathbb{O}_{k+6}(c_{\text{len}(c_n) - 1}), \mathbb{O}_{k+6}(c_{\text{len}(c_n) - 1}) + 2 \}
    • ただし k を第4小節の最後のノート番号とする
  • jは、ノート n が第5小節内の16分音符 m 個目であるとして m + 13 で割った余り

A2: 「守り」の補正

A2-1: 低すぎる音の修正

ノート番号が 24 (C1)未満なら、直前の音のノート番号を k として、ランダムで以下のどちらかの音で置き換えます

選択肢 確率(%)
1 50 k
2 50 \mathbb{O}_{k+6}(c_j), ただし j はこれまでに選択肢2を選んだ回数を \text{len}(c_n) で割った余り。
A2-2: コード構成音を考慮した補正

ノートがコード C_n を構成する音に含まれない場合は、下記条件のもと、補正を行います。

n の位置が、上記画像の休符ではない位置と重なっている場合、nc_n に含まれるクラスに属する音であって、 n に最も近い音で置き換えます。

A2-3: 黒鍵補正

n が黒鍵であるとき、下記のいずれかの条件を満たしている場合、補正を行います。

  • プログラムの引数 strict_modeTrue になっている。
  • 以下の両方の条件を満たしている:
    • コード C_n がサフィックスを持っている
    • 過去に黒鍵補正を行った回数を 5 で割った余りが 1

補正することが確定した場合、 n を次の値で置き換えます。

確率(%)
50 n + 1
50 n - 1
A2-4: 同音補正

n が 最初の音ではなく、かつ、n の一つ前の音と同じである場合、下記の条件のもと、補正を行います。

n の位置が、上記画像の休符ではない位置と重なっている場合、n を下記の処理により置き換えます。
ただし、以下で「これまでに同音補正を行った回数を 4 で割った余りが 1 以外」という条件を P と呼ぶことにします。

  1. 禁止クラスリスト E を作り、n の1個前のノートのクラスを挿入します。
  2. P が真、かつ、n の前に音が存在するなら、En の2個前のノートのクラス を追加します。
  3. P が真、かつ、n の1つ前および2つ前に音が存在する、かつ、コード C_n の構成音が 4 つ以上なら、En の3個前のノートのクラス を追加します。
  4. C_n に含まれるノートクラスで、E含まれないものをすべて選び、この集合を F とします。
  5. F からランダムに一つを選んだものを a とすると、n\mathbb{O}_n(a) で置き換えます。

A3: 音程補正

A3-1A3-2 は常に実施されます。

A3-1: 高すぎる音の補正

ノート番号80(G#5)よりも高い音は1オクターブ下げます。

A3-2: 低すぎる音の補正

ノート番号58(B♭3)よりも低い音は1オクターブ上げます。

B. 9小節目のノート補完

8小節目最後の音を k とします。9小節目に以下のように音を追加します。

  1. 集合 \{ \mathbb{O}_k(x) | x \in c_k \} の音を16音符の長さにして順番に並べます。
  2. C_nのルート音を「16分音符1個 + 2分音符1個」の長さにして追加します。

C. 同一ノートの結合処理

同じ高さの16分音符を結合して8分音符や4分音符などにします。
こちらはスターターキットと全く同じ実装です。

D. ピッチベンドイベントの追加

生成されたMIDIに対して、以下の条件でピッチベンド(pitchwheel)を挿入します[4]

トラック1のノートそれぞれに対して、繰り返し以下の処理を行います:

以下の条件のいずれかを満たしている場合は、ピッチベンドイベントを追加します。

  • 現在処理中のノートが最初の音である
  • ノートの長さが8分音符以上、かつ、最後にピッチベンドイベントを挿入してから符点2分音符1個分の時間だけ経過している

上記条件のいずれかを満たしている場合、ノートの最初を16分音符1個分削り、ピッチベンドイベントを挿入します。
ただし、ビッチベンドカーブは以下の条件で決定します。(k, mは定数)

n の長さ カーブ 概形
8分音符以上 凹カーブ f(t)=-2^{-kt} + m
約符点8分音符以上 凸カーブ f(t)=2^{kt} + m

ソースコード

https://github.com/log5r/BenzaitenAdlib

脚注
  1. https://newt.phys.unsw.edu.au/jw/notes.html などが参考になります。 ↩︎

  2. 引数を添え字で表現するのはよいプラクティスではないかもしれませんが、今回は見やすさのためにこのように定義しました。 ↩︎

  3. もっと言えば、目的の音域に収まるようにwhile文で実装しなければならないところですが、できてませんね... ↩︎

  4. ちなみに、このピッチベンドイベントですが、試合本番では流れていませんでした。再生環境とローカルの環境に差異があったのが原因のようですが、はっきりとはわかっていません。 ↩︎

Discussion