15.


ルーチンとクロック


クロックは、SuperColliderの中で自動のアルゴリズミックなスケジュ−リングを生成する際に使うメカニズムを提供します。クロックはルーチン、タスク、そしてパターンを再生します。


パターンとタスクはルーチンから作られます。


ルーチンに対する最初のアーギュメント(そして通常は唯一のアーギュメント)は関数です。


これが関数の一例です。


// 関数を実行するには.valueメッセージを送る

f = { "hello, world" };

f.value;


これはルーチンの中に関数を配置したものです。.yieldメッセージを送ることで関数の中の式を評価します。


r = Routine({ "hello, world".yield.postln });


ルーチンを実行するには、評価して下さい。


// ルーチンを評価するには、.nextメッセージを送る

r.next;


もう一度やってみましょう。


r.next;


これは一度ルーチンが"yield"(受け渡す)すると、関数の中にさらなるコードがあるか、resetメッセージを受け取らない限り、終了することを示します。


r.reset;

r.next


////////////////////////////////////////////////////////////////////////////////////////////////////


この引用では、ルーチンに.nextメッセージが送られると、3つの表現(文字列)を順番に引き渡すことを引き起こします。例えば、nextメッセージが送られると、文字列が引き渡され、nextメッセージが送られると、文字列が引き渡さるという様にです。


(

r = Routine({ 

"hello, world".yield;

"what a world".yield;

"i am a world".yield;

});

)


上の例での最後の.nextメッセージはnilを返すでしょう。最後の表現が引き渡されると、ルーチンはリセットされるまで常にnilを返します。


r.next;

r.next;

r.next;

r.next;


////////////////////////////////////////////////////////////////////////////////////////////////////


このルーチンを.doメッセージを使って書き直すと、loopを作ります。


(

r = Routine({ 


var array;

array = [ "hello, world", "what a world", "i am a world" ];


3.do({ array.choose.yield })

});

)


このdo loopをルーチンの中のループよりももう1回多く実行すると、前の例と同じ様に、3つの文字列とnilを返します。


4.do({ r.next.postln });


////////////////////////////////////////////////////////////////////////////////////////////////////


次に、このルーチンを.waitメッセージを含む形で書き直します。.waitメッセージは、その数字が示す秒単位で指定した時間だけ、ルーチンを「プレイ」するクロックを一時停止させます。


(

r = Routine({ 


var array;

array = [ "hello, world", "what a world", "i am a world" ];


3.do({ 1.wait; array.choose.postln })

});

)


以下のコードが示す様に、ルーチンに.resetメッセージを追加します。このようにして、ルーチンは常に再スタートできる様になります。そして、クロックとともにルーチンをプレイします。


以下のコードはSuperColliderがイベントをスケジューリングする時に使用する3つのクロックを示します。


SystemClock.play(r.reset); // 最も正確

AppClock.play(r.reset); // GUIで使用する

TempoClock.new.play(r.reset); // 主に拍でスケジューリングするために使われる


////////////////////////////////////////////////////////////////////////////////////////////////////


または、そのプロセスを省略することができ、その場合にはデフォルトとしてTempoClockが使用されます。


r.reset.play


////////////////////////////////////////////////////////////////////////////////////////////////////


ルーチンを用いたシンセシスのスケジューリング


ルーチンの中にシンセを入れます。シンセの中のSynthDefが必ずエンベロープを持つ様にし、そのエンベロープのdoneActionパラメータを2にセットします。これによって、エンベロープが終了した後に、そのシンセのために必要とされていたメモリが解放されます。


(

SynthDef("fm2", { 

arg bus = 0, freq = 440, carPartial = 1, modPartial = 1, index = 3, mul = 0.2, ts = 1;


// インデックスの値は通常0から24まで

// carPartial :: modPartial => car/mod ratio

var mod;

var car;

mod = SinOsc.ar(

freq * modPartial, 

0, 

freq * index * LFNoise1.kr(5.reciprocal).abs

);

car = SinOsc.ar(

(freq * carPartial) + mod, 

0, 

mul

);

Out.ar(

bus,

car * EnvGen.kr(Env.sine(1), doneAction: 2, timeScale: ts)

)

}).load(s);

)


(

r = Routine({


12.do({

Synth("fm2", [\freq, 400.0.rrand(1200), \carPartial, 0.5.rrand(2), \ts, 0.1.rrand(4)]);

2.wait;

})

});

)

r.reset.play;


////////////////////////////////////////////////////////////////////////////////////////////////////


ルーチンの中で引き起こされたシンセを、ルーチンの外側で実行するエコー・エフェクト・ユニットを通してプレイします。


(

SynthDef("echoplex", {

ReplaceOut.ar(

0,

CombN.ar(

In.ar(0, 1),

0.35,

[Rand(0.05, 0.3), Rand(0.05, 0.3)], 

// シンセが生成されるたびにランダムな値を生成する

7,

0.5

)

)

}).load(s);


// ~sourceグループをルートノードの先頭に追加し、~effectsグループをる−とノードの最後に追加する

~source = Group.head(s);

~effect = Group.tail(s);


r = Routine({


// ループはinf.doと同じ。例えば、永遠に実行される無限ループを生成する。

loop({

Synth.head( // ~sourceグループの先頭にシンセを追加する

~source,

"fm2", 

[

\outbus, 0, \freq, 400.0.rrand(1200), \modPartial, 0.3.rrand(2.0),

\carPartial, 0.5.rrand(11), \ts, 0.1.rrand(0.2)]

);

2.wait;

})

});

// 2つのechoplexesーこれらを~effectsグループの先頭に順に追加する

Synth.head(~effect, "echoplex");

Synth.head(~effect, "echoplex");

)

// ルーチンをプレイする

r.reset.play;


////////////////////////////////////////////////////////////////////////////////////////////////////