メロディー自動生成 in SuperCollider

Ableton and Max Community Japanが今月配信していた、「作曲 vs 生成音楽」をテーマにした番組が面白かった。(配信が2021/1/30までだったので今はもう見れない)

AbelteonとMAXを使ったアルゴリズム作曲の基礎を実践しつつ、自動生成音楽の歴史とか、作るときの考え方についての話もたっぷり聞けて親切な内容だった。

中でも、「どう禁則を作るか」というところに創作性が発生するというのは何か作るときのとっかかりとして心に留めておきたい。

その中でやっていた、MAXでマルコフ連鎖の手法を使ってメロディーを自動生成するというのが面白そうだったので、SuperColliderでも似たようなこと試せないのかな?と調べてみたところ、SuperColliderのチュートリアルに、Strategies for Algorithmic Compositionのページを発見。

マルコフ連鎖を使って3つのノートを鳴らす参考コードがあったので、ここから発展させて、任意のスケール内の音を鳴らすコードを作ってみることにした。

コード全体

//トリガー
(
    var state, key, scale, bpm, matrix;
    state = 7.rand;
    key = "F5".notemidi;
    scale = Scale.spanish;
    bpm = 90;
    
    matrix = [
    [ 0, 3, 1, 1, 1, 1, 3 ],
    [ 3, 0, 3, 1, 1, 1, 1 ],
    [ 1, 3, 0, 3, 1, 1, 1 ],
    [ 1, 1, 3, 0, 3, 1, 1 ],
    [ 1, 1, 1, 3, 0, 3, 1 ],
    [ 1, 1, 1, 1, 3, 0, 3 ],
    [ 3, 1, 1, 1, 1, 3, 0 ],
    ]/10;

    {
        inf.do{
            var midi = ( state.degreeToKey( scale ) + key );
            var octave = [ 0, 12 ].wchoose( [ 0.8, 0.1 ] );
            Synth( \saw, [ \midi, midi+octave ] );
            state = Array.series(7).wchoose( matrix[state] );
            ( 60/bpm/3 ).wait; //三連符
        };
    }.fork;
)
//シンセ
(
    SynthDef( \saw, { |midi=70|
        var freq, env, sn;
        freq = midi.midicps;
        sn = Saw.ar( freq, 0.2 );
        env = EnvGen.kr( Env.perc( 0.1, 1 ), doneAction:2 );
        Out.ar( 0, sn*env );
    }).add;
)

wchooseメソッドは、リスト内からどの値を選択するかに確率をつけることができる。

[1, 2, 3, 4].wchoose([0.1, 0.2, 0.3, 0.4]);

のようにすると1〜4の中で大きい数字ほど選ばれる確率を高くすることができる。

マルコフ連鎖的な仕組みのためには「今の状態からどの状態に推移するかを確率的に決めたい」ので、推移する状態の数ぶんの確率リストのテーブル(matrix)を作成し、現在の値によってどの確率リストを採用するか決めている。

state = Array.series(7).wchoose( matrix[state] );

今の状態(state)が0の場合、0〜6の値からmatrix[0]=[ 0, 3, 1, 1, 1, 1, 3 ]/10(合計1にするために/10している)の確率によって次の値が選ばれるので、0が選ばれる確率は0、2か6が選ばれる確率が高いことになる。

レクチャーの中では既存の楽曲のMIDIデータから確率テーブルを生成する仕組みも自動でやっていたが、そこまでやるのは大変そうだったのでここでは手動で確率を書いていってる。

一応、連続した値が選ばれず、隣り合った値に推移する確率が高い、という設計にしてみたけれど、より”音楽的”にするためにはもうちょっと確率の値を考える必要がありそう。

key = "F5".notemidi;
scale = Scale.spanish;
midi = ( state.degreeToKey( scale ) + key );

上の部分で選ばれたstateの値(0〜6)をmidiノートに変換している。

degreeToKeyメソッドに度数(ただし0はじまりで数える)として値を与えると、任意のスケール内で何番目の値になるか返してくれる。マイナースケールだったら、2を与えると3(減三度は0番目から数えて3番目)を返す。

そこに基準のキーを加算してmidiノートとしてシンセに与えている。

ついでに

さっきのページの最後のほうにロジスティック写像(logistic map)を使った音生成というのも紹介されていたのでちょっと試してみる。

x_{n+1}=ax_{n}(1-x_{n})

のaのところに適当な値を入れるとカオスっぽい結果が得られる、っていうのを利用するアルゴリズムのようだ。

試しにaの値3.94のときn=500までをプロットしたもの↓

aの値3.04のとき↓

(プロットするのに使ったコード)

(
    m = Array.new();
    a = 3.04;
    p = 0.5;
    500.do{
	    p = ( a * p * (1.0-p) );
	    m = m.add( p );
    }
)
m.plot

というわけでaの値で結果が全然違う。3〜3.5あたりまでは規則的に振動していて、3.5から4に近づくとカオス度が上がっていくっぽい。

4を超すと値がinfになっちゃうので注意。

以下はlogistic mapで得た値を音階に変換してメロディーを生成するテスト。

(
var scale = Scale.romanianMinor;
var key = "C6".notemidi;
var state1 = 1.0.rand;
var state2 = 1.0.rand;
var r = 3.88;
var logisticmap, note;

//ロジスティック写像関数
logisticmap = { |previous=60| r*previous*(1.0-previous) };
			
{
    inf.do{
    //リードメロディ
    state1 = logisticmap.(state1);
    note = ( state1 * 14 ).round; 
    note = ( note.degreeToKey( scale ) + key );
    Synth(\asynth,[\midi, note]);
   //ベース
    state2 = logisticmap.(state2);
    note = ( state2 * 7 ).round; 
    note = ( note.degreeToKey( scale ) + key-24 );
    Synth(\asynth,[\midi, note]);
    0.25.wait;	
    };
}.fork;
)

鳴らしてみた

(ちなみにマルコフ連鎖の方の伴奏はニンテンドースイッチのkorgのやつで作っていて、リードにかかっているワウっぽいエフェクトが外部でかけている)

ていうか

ここまで書いてから気付いたけどSuperColliderのUGenにそもそもMarkov SynthLogisticというのがあった。

Logisticのほうは任意の周波数でロジスティックス写像を用いたノイズを信号として出力できるみたいだ。

Markov Synthのほうはinputしたシグナルを解析してマルコフ連鎖を生成してくれるっぽい?のでなんか凝ったことができそう(ただサンプル単位の値の推移を読むっぽいので音階の推移を解析するより使い方が複雑かも?)

このへんは後でまたちゃんと試してみたい。