ならば

音とかで遊んでいたログ

マルコフ連鎖生成曲

マルコフ連鎖を使って作曲する試み。

文章にしろ曲にしろ、マルコフ連鎖を使って何かを生成する場合、マルコフ連鎖自体、つまり状態と遷移確率行列に相当するデータを準備する必要がある。このデータが最終的な出力の質のかなりの部分を左右する。このデータを準備すれば、生成の部分は特に凝ったことをしない限り、サイコロを転がす程度のことで済む。

実際にある文章や曲を元にデータを作ろうと思うと、状態の切り出しが難しい場合がある。英語の文章の場合は、最初から単語で分かち書きされているから楽だ。日本語の文章の場合は、形態素を状態とすると、状態の切り出しはMeCabなどの形態素解析プログラムに任せることができる。曲の場合は、多分今のところ簡単で汎用的な方法はない。本格的に研究するなら用意した曲の音響分析をしたりするのかもしれないけど、音響分析なんて全然知らないし、今回は実際にある曲を元にデータを作るつもりでも気軽に試したいだけだったから、状態の切り出しは手作業でやった。

データの準備の流れ。マルコフ連鎖の状態は、楽譜上のひとつの音符(休符)と同程度の情報ということで、音高と音価の組み合わせとした。手作業でやったのは、ある短い曲の楽譜上の音符(休符)をそれぞれ符号化してテキストファイルに落とし込んだこと。この段階で状態の切り出しは終わった。後はそのテキストファイルを入力として受け取って、状態と遷移確率行列に相当するデータを出力するスクリプトを書いて、実行。出力されたデータはChucKの曲生成プログラムに埋め込む。

出力されたデータを可視化したもの。各状態の中に書いてある文字と数は、それぞれ音名("rest"は例外で休符)と音価を表す。各状態間の矢印の太さは、その状態間の遷移確率の大きさを表す。
 



ChucKのプログラム。

.2::second => dur T;

ModalBar bar => JCRev r => dac;
6 => bar.preset;
.1 => r.mix;

[[59,2], [59,4], [61,2], [64,2], [64,4], 
 [64,8], [66,1], [66,2], [66,4], [68,1], 
 [68,2], [-1,2], [-1,4]] 
 @=> int notes[][];   // 状態のリスト
[[2,2,2,2,9], [12], [1,2,2,3,3,3,5,7],
 [0,0,2,2,4,8,11], [0], null, 
 [3], [3,3,3,8], [10,11],
 [6], [7,7,7], [10,10], 
 [0]] 
 @=> int matrix[][];  // 遷移確率行列(に相当する情報を持つリスト)

Std.rand2(0, notes.cap()-1) => int cur;
while(true) {
    notes[cur][0] => int m;
    if (m > 0) {
        m => Std.mtof => bar.freq;
        1 => bar.noteOn;
    }
    notes[cur][1]::T => now;
    1 => bar.noteOff;
    if (matrix[cur] == null) {
      break;
    } else {
      matrix[cur][Std.rand2(0, matrix[cur].cap()-1)] => cur;
    }
}

ModalBarはSTKのユニットジェネレータで、いろいろな鍵盤打楽器の音をシミュレートする。楽器の種類はパラメータ.presetで読み書きできる。

録音したもの。
Download



今回はデータをプログラムに埋め込んでいるけど、使い方によってはMIDIやOSCで外からリアルタイムに音楽情報を流し込んでデータを随時構成・更新しても面白いかもしれない。