Routineで作曲?
作曲と言ってもどうやるんだよという話ですが、せっかくプログラム言語でやってるので、メロディー書くとか心を込めて歌い上げるとか、そうした方向ではないものを目指したい。
うまく行くかどうかはともかく、どんな方法が可能なのかだけを血も涙もなしに考えてみる。
( Tempo.tempo = 1; SynthDef("hoge", { arg nn; var e = EnvGen.ar(Env.perc(0, 1), doneAction:2); o = SinOsc.ar(nn.midicps, 0, e); Out.ar(0, Pan2.ar(o, 0, 0.5)); }).store; r = Routine { loop { n = 12.rand; n.postln; Synth(\hoge, [\nn, n+72]); // シンセを起動 0.2.wait; } }.play; ) r.stop;
Routineの中にSynthを入れてやると音の並びができますね。
半音階をランダムに鳴らしているだけですが、一応これだけでも作曲と言えないことはない(本当か)
こういうところから始めて、どんどん自分の考えを入れていってやると、自分の曲って感じになっていくのかも知れません。
今度はいろんな音色を使ってみたいです。
( SynthDef("hoge", { arg ds, fq, dur, pan; var d = Dust.ar(ds); e = EnvGen.ar(Env.linen(0, 0.05, 0), d); f = if(ds>10, 1, e); o = LFPulse.ar(fq, 0, 0.5, f); Out.ar(0, Pan2.ar(o, pan)); }).store; SynthDef("moge", { arg ds, fq, dur, pan; var d = Dust.ar(ds); e = EnvGen.ar(Env.linen(0, 0.05, 0), d); f = if(ds>10, 1, e); o = LFNoise0.ar(fq, f); Out.ar(0, Pan2.ar(o, pan)); }).store; SynthDef("mohe", { arg ds, fq, dur, pan; var e = EnvGen.ar(Env.perc(0, dur)); m = SinOsc.ar(ds, 0, fq/ds, fq/2); o = SinOsc.ar(m, 0, e); Out.ar(0, Pan2.ar(o, pan)); }).store; r = Routine { var inst; inst = ["hoge","moge","mohe"]; // シンセ名 loop { a = 15.0.rand; b = 10000.rand; c = 5.0.rand; d = 2.0.rand-1; e = 3.rand; inst[e].postln; a = Synth(inst[e], [\ds, a, \fq, b, \dur, c, \pan, d]); c.wait; a.free; } }.play; ) r.stop; a.free;
これまた適当シンセですが。ちなみにシンセ名を配列から呼んでいる。
パラメータを変えつつ音色が切り替わって、何らかの展開らしきが、あるようなないような、それにしてもサイコロばかりでは芸がないのう。
( SynthDef("hoge", { arg nn; var e = EnvGen.ar(Env.perc(0, 1), doneAction:2); o = SinOsc.ar(nn.midicps, 0, e); Out.ar(0, Pan2.ar(o, 0, 0.5)); }).store; r = Routine { 24.do { |i| if( coin(0.5), { // 確率50% ("---"++i).postln; while( {n!=i}, { // iが出たら終わる n = 24.rand; n.postln; Synth(\hoge, [\nn, n/2+72]); 0.1.wait; }) }, { ("==="++i).postln; while( {n!=i}, { // iが出たら終わる n = 24.rand; m = 5.rand+1; (""++n++" "++m).postln; Synth(\hoge, [\nn, n/2+72]); Synth(\hoge, [\nn, n/2+m+72]); (1/m).wait; }) }); }; "done".postln; }.play; )
制御文を無理矢理かませてみました。
またしてもデタラメに音を選んでますが、iと同じ数が出たら次のフレーズに行く。24周すると終わり。
2種類のフレーズをサイコロで選んでますが、このcoinというのも初めて見た。これは与えた確率に従って、真(True)か偽(False)をBooleanで返す。便利ですね。
coin(0.5).postln;
自分が知らないだけで、他にも大量に便利なものが隠れているのではという気がします。ううむ
Task
上の「while曲」はひとつのRoutineの中に処理内容まとめて書いてますが、さらに入り組んだ構造が作りたいとなると、やはり分割して書けたほうが後々楽だと思うのですよ常識的に考えて。
で、Routineをネストできるかとかいろいろ試してみたが、結果としては、
( SynthDef("hoge", { arg nn; var e = EnvGen.ar(Env.perc(0, 1), doneAction:2); o = SinOsc.ar(nn.midicps, 0, e); Out.ar(0, Pan2.ar(o, 0, 0.5)); }).store; ~cnd = 0; // グローバル変数 ~wt = 0.00001; // 謎の待ち時間 a = Task { c.stop; // cを一時停止 ("---" ++ ~cnd).postln; while( {n != ~cnd}, { n = 24.rand; n.postln; Synth(\hoge, [\nn, n/2+72]); (0.1 - ~wt).wait; }); c.resume; // cを再開 }; b = Task { c.stop; // cを一時停止 ("===" ++ ~cnd).postln; while( {n != ~cnd}, { n = 24.rand; m = 5.rand+1; (""++n++" "++m).postln; Synth(\hoge, [\nn, n/2+72]); Synth(\hoge, [\nn, n/2+m+72]); (1/m - ~wt).wait; }); c.resume; // cを再開 }; c = Task { 24.do { if( coin(0.5), { a.start }, { b.start }); // aまたはbを実行 ~wt.wait; // 謎の待ち ~cnd = ~cnd + 1; }; "done".postln; }.start; )
Taskというのを使いまして、こんな感じになりました。
TaskはRoutineと微妙に違うようで、途中で止めたりできます。
あと変数の頭に~をつけてにょろりとさせるとグローバル変数というものになり、どこからでも見られて大変便利です。
さて、サブのTaskを呼び出した時に、メインTaskを止めなきゃならんとか、メインTaskに謎の待ち時間を入れる必要があるなど、少々不便な気もしますが、こうしないとwait無視で突進してしまいalready playingとかalready playingとかalready playingとか言われてぐちゃぐちゃになり爆発炎上して悲惨です。他にうまい方法があれば教えて下さい。
またどのくらいタイミングが合ってるのかも疑問ですが、コード的にきもすけわりーので謎待ち時間を減じている。
次回もう少しRoutine/Taskについて考えます。