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メッセージが送られると、３つの表現（文字列）を順番に引き渡すことを引き起こします。例えば、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回多く実行すると、前の例と同じ様に、３つの文字列と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がイベントをスケジューリングする時に使用する３つのクロックを示します。

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

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

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

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

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

r.reset.play

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

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

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

(

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;

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